# Copyright 2014 Eucalyptus Systems, Inc.
#
# Redistribution and use of this software in source and binary forms,
# with or without modification, are permitted provided that the following
# conditions are met:
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import division
import argparse
import base64
import math
import uuid
from requestbuilder import Arg, MutuallyExclusiveArgList
from requestbuilder.exceptions import ArgumentError
from requestbuilder.mixins import FileTransferProgressBarMixin
from euca2ools.commands.argtypes import b64encoded_file_contents, filesize
from euca2ools.commands.ec2 import EC2Request
from euca2ools.commands.ec2.mixins import S3AccessMixin
from euca2ools.commands.ec2.resumeimport import ResumeImport
from euca2ools.commands.s3.getobject import GetObject
import euca2ools.util
class ImportInstance(EC2Request, S3AccessMixin, FileTransferProgressBarMixin):
DESCRIPTION = 'Import an instance into the cloud'
ARGS = [Arg('source', metavar='FILE', route_to=None,
help='file containing the disk image to import (required)'),
Arg('-t', '--instance-type', metavar='INSTANCETYPE', required=True,
dest='LaunchSpecification.InstanceType',
help='the type of instance to import to (required)'),
Arg('-f', '--format', dest='DiskImage.1.Image.Format',
metavar='FORMAT', required=True, help='''the image's format
("vmdk", "raw", or "vhd") (required)'''),
Arg('-a', '--architecture', metavar='ARCH', required=True,
dest='LaunchSpecification.Architecture',
help="the instance's processor architecture (required)"),
Arg('-p', '--platform', dest='Platform', required=True,
choices=('Windows', 'Linux'),
help="the instance's operating system (required)"),
MutuallyExclusiveArgList(
Arg('-b', '--bucket', route_to=None,
help='the bucket to upload the volume to'),
Arg('--manifest-url', metavar='URL',
dest='DiskImage.1.Image.ImportManifestUrl',
help='''a pre-signed URL that points to the import
manifest to use'''))
.required(),
Arg('--prefix', route_to=None, help='''a prefix to add to the
names of the volume parts as they are uploaded'''),
Arg('-x', '--expires', metavar='DAYS', type=int, default=30,
route_to=None, help='''how long the import manifest should
remain valid, in days (default: 30 days)'''),
Arg('--no-upload', action='store_true', route_to=None,
help='''start the import process, but do not actually upload
the volume (see euca-resume-import)'''),
Arg('-d', '--description', dest='Description',
help='a description for the import task (not the volume)'),
Arg('-g', '--group', metavar='GROUP',
dest='LaunchSpecification.GroupName.1',
help='name of the security group to create the instance in'),
Arg('-z', '--availability-zone', metavar='ZONE',
dest='LaunchSpecification.Placement.AvailabilityZone',
help='the zone in which to create the instance'),
Arg('-s', '--volume-size', metavar='GiB', type=int,
dest='DiskImage.1.Volume.Size',
help='size of the volume to import to, in GiB'),
Arg('--image-size', dest='DiskImage.1.Image.Bytes',
metavar='BYTES', type=filesize,
help='size of the image (required for non-raw files'),
MutuallyExclusiveArgList(
Arg('--user-data', metavar='DATA', type=base64.b64encode,
dest='LaunchSpecification.UserData.Data',
help='user data to supply to the instance'),
Arg('--user-data-file', metavar='FILE',
type=b64encoded_file_contents,
dest='LaunchSpecification.UserData', help='''file
containing user data to supply to the instance''')),
Arg('--subnet', metavar='SUBNET',
dest='LaunchSpecification.SubnetId', help='''[VPC only] subnet
to create the instance's network interface in'''),
Arg('--private-ip-address', metavar='ADDRESS',
dest='LaunchSpecification.PrivateIpAddress',
help='''[VPC only] assign a specific primary private IP address
to the instance's interface'''),
Arg('--monitor', action='store_true',
dest='LaunchSpecification.Monitoring.Enabled',
help='enable detailed monitoring for the instance'),
Arg('--instance-initiated-shutdown-behavior',
dest='LaunchSpecification.InstanceInitiatedShutdownBehavior',
choices=('stop', 'terminate'), help='''whether to "stop"
(default) or terminate the instance when it shuts down'''),
Arg('--key', dest='LaunchSpecification.KeyName', metavar='KEYPAIR',
help='''[Eucalyptus only] name of the key pair to use when
running the instance'''),
# This is not yet implemented
Arg('--ignore-region-affinity', action='store_true', route_to=None,
help=argparse.SUPPRESS),
# This does no validation, but it does prevent taking action
Arg('--dry-run', action='store_true', route_to=None,
help=argparse.SUPPRESS),
# This is not yet implemented
Arg('--dont-verify-format', action='store_true', route_to=None,
help=argparse.SUPPRESS)]
LIST_TAGS = ['volumes']
def configure(self):
EC2Request.configure(self)
self.configure_s3_access()
if (self.params['DiskImage.1.Image.Format'].upper() in
('VMDK', 'VHD', 'RAW')):
self.params['DiskImage.1.Image.Format'] = \
self.params['DiskImage.1.Image.Format'].upper()
if not self.params.get('DiskImage.1.Image.Bytes'):
if self.params['DiskImage.1.Image.Format'] == 'RAW':
image_size = euca2ools.util.get_filesize(self.args['source'])
self.params['DiskImage.1.Image.Bytes'] = image_size
elif self.params['DiskImage.1.Image.Format'] == 'VMDK':
image_size = euca2ools.util.get_vmdk_image_size(
self.args['source'])
self.params['DiskImage.1.Image.Bytes'] = image_size
else:
raise ArgumentError(
'argument --image-size is required for {0} files'
.format(self.params['DiskImage.1.Image.Format']))
if not self.params.get('DiskImage.1.Volume.Size'):
vol_size = math.ceil(self.params['DiskImage.1.Image.Bytes'] /
2 ** 30)
self.params['DiskImage.1.Volume.Size'] = int(vol_size)
if not self.args.get('expires'):
self.args['expires'] = 30
if self.args['expires'] < 1:
raise ArgumentError(
'argument -x/--expires: value must be positive')
def main(self):
if self.args.get('dry_run'):
return
if self.args.get('bucket'):
self.ensure_bucket_exists(self.args['bucket'])
if not self.args.get('DiskImage.1.Image.ImportManifestUrl'):
manifest_key = '{0}/{1}.manifest.xml'.format(uuid.uuid4(),
self.args['source'])
if self.args.get('prefix'):
manifest_key = '/'.join((self.args['prefix'], manifest_key))
getobj = GetObject.from_other(
self, service=self.args['s3_service'],
auth=self.args['s3_auth'],
source='/'.join((self.args['bucket'], manifest_key)))
days = self.args.get('expires') or 30
get_url = getobj.get_presigned_url2(days * 86400) # in seconds
self.log.info('generated manifest GET URL: %s', get_url)
self.params['DiskImage.1.Image.ImportManifestUrl'] = get_url
result = self.send()
# The manifest creation and uploading parts are done by ResumeImport.
if not self.args.get('no_upload'):
resume = ResumeImport.from_other(
self, source=self.args['source'],
task=result['conversionTask']['conversionTaskId'],
s3_service=self.args['s3_service'],
s3_auth=self.args['s3_auth'], expires=self.args['expires'],
show_progress=self.args.get('show_progress', False))
resume.main()
return result
def print_result(self, result):
self.print_conversion_task(result['conversionTask'])