--- /dev/null
+#!/usr/bin/python
+
+#
+# Copyright 2016 RIFT.IO Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import boto3
+import botocore
+from . import aws_table
+from . import exceptions
+
+import logging
+logger = logging.getLogger('rwcal.aws.drv')
+logger.setLevel(logging.DEBUG)
+
+class AWSDriver(object):
+ """
+ Driver for AWS
+ """
+ def __init__(self, key, secret, region,ssh_key=None,vpcid = None,availability_zone = None,default_subnet_id = None):
+ """
+ Constructor for AWSDriver
+ Arguments:
+ key : AWS user access key
+ secret : AWS user access secret
+ region : AWS region
+ ssh_key: Name of key pair to connect to EC2 instance
+ vpcid : VPC ID for the resources
+ availability_zone: Avaialbility zone to allocate EC2 instance.
+ default_subnet_id: Default subnet id to be used for the EC2 instance interfaces at instance creation time
+ Returns: AWS Driver Object
+ """
+ self._access_key = key
+ self._access_secret = secret
+ self._region = region
+ self._availability_zone = availability_zone
+ self._ssh_key = ssh_key
+
+ self._sess = boto3.session.Session(aws_access_key_id = self._access_key,
+ aws_secret_access_key = self._access_secret,
+ region_name = self._region)
+ self._ec2_resource_handle = self._sess.resource(service_name = 'ec2')
+ self._s3_handle = self._sess.resource(service_name = 's3')
+ self._iam_handle = self._sess.resource(service_name = 'iam')
+
+ self._acct_arn = self._iam_handle.CurrentUser().arn
+ self._account_id = self._acct_arn.split(':')[4]
+ # If VPC id is not passed; use default VPC for the account
+ if vpcid is None:
+ self._vpcid = self._default_vpc_id
+ else:
+ self._vpcid = vpcid
+
+ self._default_subnet_id = default_subnet_id
+ # If default_subnet_is is not passed; get default subnet for AZ.
+ # We use this to create first network interface during instance creation time. This subnet typically should have associate public address
+ # to get public address.
+ if default_subnet_id is None:
+ self._default_subnet_id = self._get_default_subnet_id_for_az
+
+
+ @property
+ def default_subnet_id(self):
+ """
+ Returns default subnet id for account
+ """
+ return self._default_subnet_id
+
+ @property
+ def _ec2_client_handle(self):
+ """
+ Low level EC2 client connection handle
+ Arguments: None
+ Returns: EC2 Client Connection Handle
+ """
+ return self._ec2_resource_handle.meta.client
+
+ @property
+ def _default_vpc_id(self):
+ """
+ Method to get Default VPC ID
+ Arguments: None
+ Returns: Default EC2.Vpc Resource ID for AWS account
+ """
+ return self._default_vpc.vpc_id
+
+ @property
+ def _default_vpc(self):
+ """
+ Method to get Default VPC Resource Object
+ Arguments: None
+ Returns: Default EC2.Vpc Resource for AWS account
+ """
+ try:
+ response = list(self._ec2_resource_handle.vpcs.all())
+ except Exception as e:
+ logger.error("AWSDriver: Get of Default VPC failed with exception: %s" %(repr(e)))
+ raise
+ default_vpc = [vpc for vpc in response if vpc.is_default]
+ assert(len(default_vpc) == 1)
+ return default_vpc[0]
+
+ def _get_vpc_info(self,VpcId):
+ """
+ Get Vpc resource for specificed VpcId
+ Arguments:
+ - VpcId (String) : VPC ID
+ Returns: EC2.Vpc Resouce
+ """
+ VpcIds = list()
+ VpcIds.append(VpcId)
+ response = list(self._ec2_resource_handle.vpcs.filter(
+ VpcIds = VpcIds))
+ if response:
+ assert(len(response) == 1)
+ return response[0]
+ return None
+
+
+ def upload_image(self, **kwargs):
+ """
+ Upload image to s3
+ Arguments: **kwargs -- dictionary
+ {
+ 'image_path' : File location for the image,
+ 'image_prefix' : Name-Prefix of the image on S3
+ 'public_key' : The path to the user's PEM encoded RSA public key certificate file,
+ 'private_key' : The path to the user's PEM encoded RSA private key file,
+ 'arch' : One of ["i386", "x86_64"],
+ 's3_bucket' : Name of S3 bucket where this image should be uploaded
+ (e.g. 'Rift.Cal' or 'Rift.VNF' or 'Rift.3rdPartyVM' etc)
+ 'kernelId' : Id of the default kernel to launch the AMI with (OPTIONAL)
+ 'ramdiskId' : Id of the default ramdisk to launch the AMI with (OPTIONAL)
+ 'block_device_mapping : block_device_mapping string (OPTIONAL)
+ Default block-device-mapping scheme to launch the AMI with. This scheme
+ defines how block devices may be exposed to an EC2 instance of this AMI
+ if the instance-type of the instance is entitled to the specified device.
+ The scheme is a comma-separated list of key=value pairs, where each key
+ is a "virtual-name" and each value, the corresponding native device name
+ desired. Possible virtual-names are:
+ - "ami": denotes the root file system device, as seen by the instance.
+ - "root": denotes the root file system device, as seen by the kernel.
+ - "swap": denotes the swap device, if present.
+ - "ephemeralN": denotes Nth ephemeral store; N is a non-negative integer.
+ Note that the contents of the AMI form the root file system. Samples of
+ block-device-mappings are:
+ '"ami=sda1","root=/dev/sda1","ephemeral0=sda2","swap=sda3"'
+ '"ami=0","root=/dev/dsk/c0d0s0","ephemeral0=1"'
+ }
+ Returns: None
+ """
+ import subprocess
+ import tempfile
+ import os
+ import shutil
+
+ CREATE_BUNDLE_CMD = 'ec2-bundle-image --cert {public_key} --privatekey {private_key} --user {account_id} --image {image_path} --prefix {image_prefix} --arch {arch}'
+ UPLOAD_BUNDLE_CMD = 'ec2-upload-bundle --bucket {bucket} --access-key {key} --secret-key {secret} --manifest {manifest} --region {region} --retry'
+
+ cmdline = CREATE_BUNDLE_CMD.format(public_key = kwargs['public_key'],
+ private_key = kwargs['private_key'],
+ account_id = self._account_id,
+ image_path = kwargs['image_path'],
+ image_prefix = kwargs['image_prefix'],
+ arch = kwargs['arch'])
+
+ if 'kernelId' in kwargs:
+ cmdline += (' --kernel ' + kwargs['kernelId'])
+
+ if 'ramdiskId' in kwargs:
+ cmdline += (' --ramdisk ' + kwargs['ramdiskId'])
+
+ if 'block_device_mapping' in kwargs:
+ cmdline += ' --block-device-mapping ' + kwargs['block_device_mapping']
+
+ ### Create Temporary Directory
+ try:
+ tmp_dir = tempfile.mkdtemp()
+ except Exception as e:
+ logger.error("Failed to create temporary directory. Exception Details: %s" %(repr(e)))
+ raise
+
+ cmdline += (" --destination " + tmp_dir)
+ logger.info('AWSDriver: Executing ec2-bundle-image command. Target directory name: %s. This command may take a while...\n' %(tmp_dir))
+ result = subprocess.call(cmdline.split())
+ if result == 0:
+ logger.info('AWSDriver: ec2-bundle-image command succeeded')
+ else:
+ logger.error('AWSDriver: ec2-bundle-image command failed. Return code %d. CMD: %s'%(result, cmdline))
+ raise OSError('AWSDriver: ec2-bundle-image command failed. Return code %d' %(result))
+
+ logger.info('AWSDriver: Initiating image upload. This may take a while...')
+
+ cmdline = UPLOAD_BUNDLE_CMD.format(bucket = kwargs['s3_bucket'],
+ key = self._access_key,
+ secret = self._access_secret,
+ manifest = tmp_dir+'/'+kwargs['image_prefix']+'.manifest.xml',
+ region = self._region)
+ result = subprocess.call(cmdline.split())
+ if result == 0:
+ logger.info('AWSDriver: ec2-upload-bundle command succeeded')
+ else:
+ logger.error('AWSDriver: ec2-upload-bundle command failed. Return code %d. CMD: %s'%(result, cmdline))
+ raise OSError('AWSDriver: ec2-upload-bundle command failed. Return code %d' %(result))
+ ### Delete the temporary directory
+ logger.info('AWSDriver: Deleting temporary directory and other software artifacts')
+ shutil.rmtree(tmp_dir, ignore_errors = True)
+
+
+ def register_image(self, **kwargs):
+ """
+ Registers an image uploaded to S3 with EC2
+ Arguments: **kwargs -- dictionary
+ {
+ Name (string) : Name of the image
+ ImageLocation(string) : Location of image manifest file in S3 (e.g. 'rift.cal.images/test-img.manifest.xml')
+ Description(string) : Description for the image (OPTIONAL)
+ Architecture (string) : Possible values 'i386' or 'x86_64' (OPTIONAL)
+ KernelId(string) : Kernel-ID Refer: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedKernels.html#AmazonKernelImageIDs (OPTIONAL)
+ RamdiskId(string) : Ramdisk-ID Refer: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedKernels.html#AmazonKernelImageIDs (OPTIONAL)
+ RootDeviceName(string): The name of the root device (for example, /dev/sda1 , or /dev/xvda ) (OPTIONAL)
+ BlockDeviceMappings(list) : List of dictionary of block device mapping (OPTIONAL)
+ [
+ {
+ 'VirtualName': 'string',
+ 'DeviceName': 'string',
+ 'Ebs': {
+ 'SnapshotId': 'string',
+ 'VolumeSize': 123,
+ 'DeleteOnTermination': True|False,
+ 'VolumeType': 'standard'|'io1'|'gp2',
+ 'Iops': 123,
+ 'Encrypted': True|False
+ },
+ 'NoDevice': 'string'
+ },
+ ]
+ VirtualizationType(string): The type of virtualization (OPTIONAL)
+ Default: paravirtual
+ SriovNetSupport(string): (OPTIONAL)
+ Set to ``simple`` to enable enhanced networking for the AMI and any instances that are launched from the AMI.
+ This option is supported only for HVM AMIs. Specifying this option with a PV AMI can make instances launched from the AMI unreachable.
+
+ Returns:
+ image_id: UUID of the image
+ """
+
+ kwargs['DryRun'] = False
+ try:
+ response = self._ec2_client_handle.register_image(**kwargs)
+ except Exception as e:
+ logger.error("AWSDriver: List image operation failed with exception: %s" %(repr(e)))
+ raise
+ assert response['ResponseMetadata']['HTTPStatusCode'] == 200
+ return response['ImageId']
+
+
+ def deregister_image(self, ImageId):
+ """
+ DeRegisters image from EC2.
+ Arguments:
+ - ImageId (string): ImageId generated by AWS in register_image call
+ Returns: None
+ """
+ try:
+ response = self._ec2_client_handle.deregister_image(
+ ImageId = ImageId)
+ except Exception as e:
+ logger.error("AWSDriver: deregister_image operation failed with exception: %s" %(repr(e)))
+ raise
+ assert response['ResponseMetadata']['HTTPStatusCode'] == 200
+
+ def get_image(self, ImageId):
+ """
+ Returns a dictionary object describing the Image identified by ImageId
+ """
+ try:
+ response = list(self._ec2_resource_handle.images.filter(ImageIds = [ImageId]))
+ except Exception as e:
+ logger.error("AWSDriver: List image operation failed with exception: %s" %(repr(e)))
+ raise
+ return response[0]
+
+ def list_images(self):
+ """
+ Returns list of dictionaries. Each dictionary contains attributes associated with image
+ Arguments: None
+ Returns: List of dictionaries.
+ """
+ try:
+ response = list(self._ec2_resource_handle.images.filter(Owners = [self._account_id]))
+ except Exception as e:
+ logger.error("AWSDriver: List image operation failed with exception: %s" %(repr(e)))
+ raise
+ return response
+
+ def create_image_from_instance(self,InstanceId,ImageName,VolumeSize = 16):
+ """
+ Creates AWS AMI from the instance root device Volume and registers the same
+ Caller is expected to stop the instance and restart the instance if required
+ Arguments:
+ - InstanceId (String) : AWS EC2 Instance Id
+ - ImageName (String) : Name for AMI
+ Returns
+ - AWS AMI Image Id
+ """
+
+ try:
+ inst = self.get_instance(InstanceId)
+ # Find Volume Id of Root Device
+ if inst.root_device_type == 'ebs':
+ for dev in inst.block_device_mappings:
+ if inst.root_device_name == dev['DeviceName']:
+ volume_id = dev['Ebs']['VolumeId']
+ break
+
+ rsp=self._ec2_resource_handle.create_snapshot(VolumeId=volume_id)
+ snapshot_id = rsp.id
+
+ #Wait for the snapshot to be completed
+ attempts = 0
+ while attempts < 2:
+ try:
+ attempts = attempts + 1
+ waiter = self._ec2_client_handle.get_waiter('snapshot_completed')
+ waiter.wait(SnapshotIds=[snapshot_id])
+ except botocore.exceptions.WaiterError as e:
+ logger.error("AWSDriver: Create Snapshot for image still not completed. Will wait for another iteration")
+ continue
+ except Exception as e:
+ logger.error("AWSDriver: Createing Snapshot for instance failed during image creation: %s", (repr(e)))
+ raise
+ break
+
+ logger.debug("AWSDriver: Snapshot %s completed successfully from instance %s",snapshot_id,InstanceId)
+ image_id = self.register_image(Name=ImageName,VirtualizationType='hvm',
+ RootDeviceName='/dev/sda1',SriovNetSupport='simple',
+ BlockDeviceMappings=[{'DeviceName':'/dev/sda1',
+ 'Ebs':{'SnapshotId':snapshot_id,'VolumeSize': VolumeSize,
+ 'VolumeType': 'standard', 'DeleteOnTermination': True}}],
+ Architecture='x86_64')
+ return image_id
+ else:
+ logger.error("AWSDriver: Create Image failed as Instance Root device Type should be ebs to create image")
+ raise exceptions.RWErrorFailure("AWSDriver: Create Image failed as Instance Root device Type should be ebs to create image")
+ except Exception as e:
+ logger.error("AWSDriver: Createing image from instance failed with exception: %s", (repr(e)))
+ raise
+
+ def list_instances(self):
+ """
+ Returns list of resource object representing EC2 instance.
+ Arguments: None
+ Returns: List of EC2.Instance object
+ """
+ instance_list = []
+ try:
+ # Skip Instances in terminated state
+ response = self._ec2_resource_handle.instances.filter(
+ Filters = [
+ { 'Name': 'instance-state-name',
+ 'Values': ['pending',
+ 'running',
+ 'shutting-down',
+ 'stopping',
+ 'stopped']
+ }
+ ])
+ except Exception as e:
+ logger.error("AWSDriver: List instances operation failed with exception: %s" %(repr(e)))
+ raise
+ for instance in response:
+ instance_list.append(instance)
+ return instance_list
+
+ def get_instance(self, InstanceId):
+ """
+ Returns a EC2 resource Object describing the Instance identified by InstanceId
+ Arguments:
+ - InstnaceId (String) : MANDATORY, EC2 Instance Id
+ Returns: EC2.Instance object
+ """
+
+ try:
+ instance = list(self._ec2_resource_handle.instances.filter(
+ InstanceIds = [InstanceId]))
+ except Exception as e:
+ logger.error("AWSDriver: Get instances operation failed with exception: %s" %(repr(e)))
+ raise
+ if len(instance) == 0:
+ logger.error("AWSDriver: instance with id %s not avaialble" %InstanceId)
+ raise exceptions.RWErrorNotFound("AWSDriver: instance with id %s not avaialble" %InstanceId)
+ elif len(instance) > 1:
+ logger.error("AWSDriver: Duplicate instances with id %s is avaialble" %InstanceId)
+ raise exceptions.RWErrorDuplicate("AWSDriver: Duplicate instances with id %s is avaialble" %InstanceId)
+ return instance[0]
+
+ def create_instance(self,**kwargs):
+ """
+ Create an EC2instance.
+ Arguments: **kwargs -- dictionary
+ {
+ ImageId (string): MANDATORY, Id of AMI to create instance
+ SubetId (string): Id of Subnet to start EC2 instance. EC2 instance will be started in VPC subnet resides.
+ Default subnet from account used if not present
+ InstanceType(string): AWS Instance Type name. Default: t2.micro
+ SecurityGroupIds: AWS Security Group Id to associate with the instance. Default from VPC used if not present
+ KeyName (string): Key pair name. Default key pair from account used if not present
+ MinCount (Integer): Minimum number of instance to start. Default: 1
+ MaxCount (Integer): Maximum number of instance to start. Default: 1
+ Placement (Dict) : Dictionary having Placement group details
+ {AvailabilityZone (String): AZ to create the instance}
+ UserData (string) : cloud-init config file
+ }
+ Returns: List of EC2.Instance object
+ """
+
+ if 'ImageId' not in kwargs:
+ logger.error("AWSDriver: Mandatory parameter ImageId not available during create_instance")
+ raise AttributeError("Mandatory parameter ImageId not available during create_instance")
+
+ #Validate image exists and is avaialble
+ try:
+ image_res = self._ec2_resource_handle.Image(kwargs['ImageId'])
+ image_res.load()
+ except Exception as e:
+ logger.error("AWSDriver: Image with id %s not available and failed with exception: %s",kwargs['ImageId'],(repr(e)))
+ raise AttributeError("AWSDriver: Image with id %s not available and failed with exception: %s",kwargs['ImageId'],(repr(e)))
+ if image_res.state != 'available':
+ logger.error("AWSDriver: Image state is not available for image with id %s; Current state is %s",
+ image_res.id,image_res.state)
+ raise AttributeError("ImageId is not valid")
+
+ # If MinCount or MaxCount is not passed set them to default of 1
+ if 'MinCount' not in kwargs:
+ kwargs['MinCount'] = 1
+ if 'MaxCount' not in kwargs:
+ kwargs['MaxCount'] = kwargs['MinCount']
+
+ if 'KeyName' not in kwargs:
+ if not self._ssh_key:
+ logger.error("AWSDriver: Key not available during create_instance to allow SSH")
+ else:
+ kwargs['KeyName'] = self._ssh_key
+
+ if 'Placement' not in kwargs and self._availability_zone is not None:
+ placement = {'AvailabilityZone':self._availability_zone}
+ kwargs['Placement'] = placement
+
+ if 'SubnetId' not in kwargs and 'NetworkInterfaces' not in kwargs:
+ if self._default_subnet_id:
+ kwargs['SubnetId'] = self._default_subnet_id
+ else:
+ logger.error("AWSDriver: Valid subnetid not present during create instance")
+ raise AttributeError("Valid subnet not present during create instance")
+
+ if self._availability_zone and 'SubnetId' in kwargs:
+ subnet = self.get_subnet(SubnetId= kwargs['SubnetId'])
+ if not subnet:
+ logger.error("AWSDriver: Valid subnet not found for subnetid %s",kwargs['SubnetId'])
+ raise AttributeError("Valid subnet not found for subnetid %s",kwargs['SubnetId'])
+ if subnet.availability_zone != self._availability_zone:
+ logger.error("AWSDriver: AZ of Subnet %s %s doesnt match account AZ %s",kwargs['SubnetId'],
+ subnet.availability_zone,self._availability_zone)
+ raise AttributeError("AWSDriver: AZ of Subnet %s %s doesnt match account AZ %s",kwargs['SubnetId'],
+ subnet.availability_zone,self._availability_zone)
+
+ # If instance type is not passed; use t2.micro as default
+ if 'InstanceType' not in kwargs or kwargs['InstanceType'] is None:
+ kwargs['InstanceType'] = 't2.micro'
+ inst_type = kwargs['InstanceType']
+ if inst_type not in aws_table.INSTANCE_TYPES.keys():
+ logger.error("AWSDriver: Invalid instance type %s used",inst_type)
+ raise AttributeError('InstanceType %s is not valid' %inst_type)
+
+ #validate instance_type for AMI
+ if image_res.sriov_net_support == 'simple':
+ if image_res.virtualization_type != 'hvm':
+ logger.error("AWSDriver: Image with id %s has SRIOV net support but virtualization type is not hvm",kwargs['ImageId'])
+ raise AttributeError('Invalid Image with id %s' %kwargs['ImageId'])
+ if aws_table.INSTANCE_TYPES[inst_type]['sriov'] is False:
+ logger.warning("AWSDriver: Image %s support SR-IOV but instance type %s does not support HVM",kwargs['ImageId'],inst_type)
+
+ if image_res.virtualization_type == 'paravirtual' and aws_table.INSTANCE_TYPES[inst_type]['paravirt'] is False: # Need to check virt type str for PV
+ logger.error("AWSDriver: Image %s requires PV support but instance %s does not support PV",kwargs['ImageId'],inst_type)
+ raise AttributeError('Image %s requires PV support but instance %s does not support PV',kwargs['ImageId'],inst_type)
+
+ if image_res.root_device_type == 'instance-store' and aws_table.INSTANCE_TYPES[inst_type]['disk'] == 0:
+ logger.error("AWSDriver: Image %s uses instance-store root device type that is not supported by instance type %s",kwargs['ImageId'],inst_type)
+ raise AttributeError("AWSDriver: Image %s uses instance-store root device type that is not supported by instance type %s",kwargs['ImageId'],inst_type)
+
+
+ # Support of instance type varies across regions and also based on account. So we are not validating it
+ #if inst_type not in aws_table.REGION_DETAILS[self._region]['instance_types']:
+ # logger.error("AWSDriver: instance type %s not supported in region %s",inst_type,self._region)
+ # raise AttributeError("AWSDriver: instance type %s not supported in region %s",inst_type,self._region)
+
+ try:
+ instances = self._ec2_resource_handle.create_instances(**kwargs)
+ except Exception as e:
+ logger.error("AWSDriver: Creating instance failed with exception: %s" %(repr(e)))
+ raise
+ return instances
+
+ def terminate_instance(self,InstanceId):
+ """
+ Termintae an EC2 instance
+ Arguments:
+ - InstanceId (String): ID of EC2 instance
+ Returns: None
+ """
+
+ InstanceIds = InstanceId
+ if type(InstanceIds) is not list:
+ InstanceIds = list()
+ InstanceIds.append(InstanceId)
+
+ try:
+ response = self._ec2_client_handle.terminate_instances(InstanceIds=InstanceIds)
+ except Exception as e:
+ logger.error("AWSDriver: Terminate instance failed with exception: %s" %(repr(e)))
+ raise
+ return response
+
+ def stop_instance(self,InstanceId):
+ """
+ Stop an EC2 instance. Stop is supported only for EBS backed instance
+ Arguments:
+ - InstanceId (String): ID of EC2 instance
+ Returns: None
+ """
+
+ InstanceIds = InstanceId
+ if type(InstanceIds) is not list:
+ InstanceIds = list()
+ InstanceIds.append(InstanceId)
+
+ try:
+ response = self._ec2_client_handle.stop_instances(InstanceIds=InstanceIds)
+ except Exception as e:
+ logger.error("AWSDriver: Stop for instance %s failed with exception: %s",InstanceId,repr(e))
+ raise
+ return response
+
+ def start_instance(self,InstanceId):
+ """
+ Start an EC2 instance. Start is supported only for EBS backed instance
+ Arguments:
+ - InstanceId (String): ID of EC2 instance
+ Returns: None
+ """
+
+ InstanceIds = InstanceId
+ if type(InstanceIds) is not list:
+ InstanceIds = list()
+ InstanceIds.append(InstanceId)
+
+ try:
+ response = self._ec2_client_handle.start_instances(InstanceIds=InstanceIds)
+ except Exception as e:
+ logger.error("AWSDriver: Start for instance %s failed with exception: %s",InstanceId,repr(e))
+ raise
+ return response
+
+ @property
+ def _get_default_subnet_id_for_az(self):
+ """
+ Get default subnet id for AWS Driver registered Availability Zone
+ Arguments: None
+ Returns: SubnetId (String)
+ """
+
+ if self._availability_zone:
+ subnet = self._get_default_subnet_for_az(self._availability_zone)
+ return subnet.id
+ else:
+ return None
+
+ def _get_default_subnet_for_az(self,AvailabilityZone):
+ """
+ Get default Subnet for Avaialbility Zone
+ Arguments:
+ - AvailabilityZone (String) : EC2 AZ
+ Returns: EC2.Subnet object
+ """
+
+ AvailabilityZones = [AvailabilityZone]
+ try:
+ response = list(self._ec2_resource_handle.subnets.filter(
+ Filters = [
+ {'Name':'availability-zone',
+ 'Values': AvailabilityZones}]))
+ except Exception as e:
+ logger.error("AWSDriver: Get default subnet for Availability zone failed with exception: %s" %(repr(e)))
+ raise
+ default_subnet = [subnet for subnet in response if subnet.default_for_az is True and subnet.vpc_id == self._vpcid]
+ assert(len(default_subnet) == 1)
+ return default_subnet[0]
+
+ def get_subnet_list(self,VpcId=None):
+ """
+ List all the subnets
+ Arguments:
+ - VpcId (String) - VPC ID to filter the subnet list
+ Returns: List of EC2.Subnet Object
+ """
+
+ try:
+ VpcIds = VpcId
+ if VpcId is not None:
+ if type(VpcIds) is not list:
+ VpcIds = list()
+ VpcIds.append(VpcId)
+ response = list(self._ec2_resource_handle.subnets.filter(
+ Filters = [
+ { 'Name': 'vpc-id',
+ 'Values': VpcIds}]))
+ else:
+ response = list(self._ec2_resource_handle.subnets.all())
+ except Exception as e:
+ logger.error("AWSDriver: List subnets operation failed with exception: %s" %(repr(e)))
+ raise
+ return response
+
+ def get_subnet(self,SubnetId):
+ """
+ Get the subnet for specified SubnetId
+ Arguments:
+ - SubnetId (String) - MANDATORY
+ Returns: EC2.Subnet Object
+ """
+
+ try:
+ response = list(self._ec2_resource_handle.subnets.filter(SubnetIds=[SubnetId]))
+ except botocore.exceptions.ClientError as e:
+ if e.response['Error']['Code'] == 'InvalidSubnetID.NotFound':
+ logger.error("AWSDriver: Get Subnet Invalid SubnetID %s",SubnetId)
+ raise exceptions.RWErrorNotFound("AWSDriver: Delete Subnet Invalid SubnetID %s",SubnetId)
+ else:
+ logger.error("AWSDriver: Creating network interface failed with exception: %s",(repr(e)))
+ raise
+ except Exception as e:
+ logger.error("AWSDriver: Get subnet operation failed with exception: %s" %(repr(e)))
+ raise
+ if len(response) == 0:
+ logger.error("AWSDriver: subnet with id %s is not avaialble" %SubnetId)
+ raise exceptions.RWErrorNotFoun("AWSDriver: subnet with id %s is not avaialble" %SubnetId)
+ elif len(response) > 1:
+ logger.error("AWSDriver: Duplicate subnet with id %s is avaialble" %SubnetId)
+ raise exceptions.RWErrorDuplicate("AWSDriver: Duplicate subnet with id %s is avaialble" %SubnetId)
+ return response[0]
+
+ def create_subnet(self,**kwargs):
+ """
+ Create a EC2 subnet based on specified CIDR
+ Arguments:
+ - CidrBlock (String): MANDATORY. CIDR for subnet. CIDR should be within VPC CIDR
+ - VpcId (String): VPC ID to create the subnet. Default AZ from AWS Driver registration used if not present.
+ - AvailabilityZone (String): Availability zone to create subnet. Default AZ from AWS Driver registration used
+ if not present
+ Returns: EC2.Subnet Object
+ """
+
+ if 'CidrBlock' not in kwargs:
+ logger.error("AWSDriver: Insufficent params for create_subnet. CidrBlock is mandatory parameter")
+ raise AttributeError("AWSDriver: Insufficent params for create_subnet. CidrBlock is mandatory parameter")
+
+ if 'VpcId' not in kwargs:
+ kwargs['VpcId'] = self._vpcid
+ if 'AvailabilityZone' not in kwargs and self._availability_zone is not None:
+ kwargs['AvailabilityZone'] = self._availability_zone
+
+ vpc = self._get_vpc_info(kwargs['VpcId'])
+ if not vpc:
+ logger.error("AWSDriver: Subnet creation failed as VpcId %s does not exist", kwargs['VpcId'])
+ raise exceptions.RWErrorNotFound("AWSDriver: Subnet creation failed as VpcId %s does not exist", kwargs['VpcId'])
+ if vpc.state != 'available':
+ logger.error("AWSDriver: Subnet creation failed as VpcId %s is not in available state. Current state is %s", kwargs['VpcId'],vpc.state)
+ raise exceptions.RWErrorNotConnected("AWSDriver: Subnet creation failed as VpcId %s is not in available state. Current state is %s", kwargs['VpcId'],vpc.state)
+
+ try:
+ subnet = self._ec2_resource_handle.create_subnet(**kwargs)
+ except botocore.exceptions.ClientError as e:
+ if e.response['Error']['Code'] == 'InvalidSubnet.Conflict':
+ logger.error("AWSDriver: Create Subnet for ip %s failed due to overalp with existing subnet in VPC %s",kwargs['CidrBlock'],kwargs['VpcId'])
+ raise exceptions.RWErrorExists("AWSDriver: Create Subnet for ip %s failed due to overalp with existing subnet in VPC %s",kwargs['CidrBlock'],kwargs['VpcId'])
+ elif e.response['Error']['Code'] == 'InvalidSubnet.Range':
+ logger.error("AWSDriver: Create Subnet for ip %s failed as it is not in VPC CIDR range for VPC %s",kwargs['CidrBlock'],kwargs['VpcId'])
+ raise AttributeError("AWSDriver: Create Subnet for ip %s failed as it is not in VPC CIDR range for VPC %s",kwargs['CidrBlock'],kwargs['VpcId'])
+ else:
+ logger.error("AWSDriver: Creating subnet failed with exception: %s",(repr(e)))
+ raise
+ except Exception as e:
+ logger.error("AWSDriver: Creating subnet failed with exception: %s" %(repr(e)))
+ raise
+ return subnet
+
+ def modify_subnet(self,SubnetId,MapPublicIpOnLaunch):
+ """
+ Modify a EC2 subnet
+ Arguements:
+ - SubnetId (String): MANDATORY, EC2 Subnet ID
+ - MapPublicIpOnLaunch (Boolean): Flag to indicate if subnet is associated with public IP
+ """
+
+ try:
+ response = self._ec2_client_handle.modify_subnet_attribute(SubnetId=SubnetId,MapPublicIpOnLaunch={'Value':MapPublicIpOnLaunch})
+ except botocore.exceptions.ClientError as e:
+ if e.response['Error']['Code'] == 'InvalidSubnetID.NotFound':
+ logger.error("AWSDriver: Modify Subnet Invalid SubnetID %s",SubnetId)
+ raise exceptions.RWErrorNotFound("AWSDriver: Modify Subnet Invalid SubnetID %s",SubnetId)
+ else:
+ logger.error("AWSDriver: Modify subnet failed with exception: %s",(repr(e)))
+ raise
+ except Exception as e:
+ logger.error("AWSDriver: Modify subnet failed with exception: %s",(repr(e)))
+ raise
+
+
+ def delete_subnet(self,SubnetId):
+ """
+ Delete a EC2 subnet
+ Arguements:
+ - SubnetId (String): MANDATORY, EC2 Subnet ID
+ Returns: None
+ """
+
+ try:
+ response = self._ec2_client_handle.delete_subnet(SubnetId=SubnetId)
+ except botocore.exceptions.ClientError as e:
+ if e.response['Error']['Code'] == 'InvalidSubnetID.NotFound':
+ logger.error("AWSDriver: Delete Subnet Invalid SubnetID %s",SubnetId)
+ raise exceptions.RWErrorNotFound("AWSDriver: Delete Subnet Invalid SubnetID %s",SubnetId)
+ else:
+ logger.error("AWSDriver: Delete subnet failed with exception: %s",(repr(e)))
+ raise
+ except Exception as e:
+ logger.error("AWSDriver: Delete subnet failed with exception: %s",(repr(e)))
+ raise
+
+ def get_network_interface_list(self,SubnetId=None,VpcId=None,InstanceId = None):
+ """
+ List all the network interfaces
+ Arguments:
+ - SubnetId (String)
+ - VpcId (String)
+ - InstanceId (String)
+ Returns List of EC2.NetworkInterface
+ """
+
+ try:
+ if InstanceId is not None:
+ InstanceIds = [InstanceId]
+ response = list(self._ec2_resource_handle.network_interfaces.filter(
+ Filters = [
+ { 'Name': 'attachment.instance-id',
+ 'Values': InstanceIds}]))
+ elif SubnetId is not None:
+ SubnetIds = SubnetId
+ if type(SubnetId) is not list:
+ SubnetIds = list()
+ SubnetIds.append(SubnetId)
+ response = list(self._ec2_resource_handle.network_interfaces.filter(
+ Filters = [
+ { 'Name': 'subnet-id',
+ 'Values': SubnetIds}]))
+ elif VpcId is not None:
+ VpcIds = VpcId
+ if type(VpcIds) is not list:
+ VpcIds = list()
+ VpcIds.append(VpcId)
+ response = list(self._ec2_resource_handle.network_interfaces.filter(
+ Filters = [
+ { 'Name': 'vpc-id',
+ 'Values': VpcIds}]))
+ else:
+ response = list(self._ec2_resource_handle.network_interfaces.all())
+ except Exception as e:
+ logger.error("AWSDriver: List network interfaces operation failed with exception: %s" %(repr(e)))
+ raise
+ return response
+
+ def get_network_interface(self,NetworkInterfaceId):
+ """
+ Get the network interface
+ Arguments:
+ NetworkInterfaceId (String): MANDATORY, EC2 Network Interface Id
+ Returns: EC2.NetworkInterface Object
+ """
+
+ try:
+ response = list(self._ec2_resource_handle.network_interfaces.filter(NetworkInterfaceIds=[NetworkInterfaceId]))
+ except Exception as e:
+ logger.error("AWSDriver: List Network Interfaces operation failed with exception: %s" %(repr(e)))
+ raise
+ if len(response) == 0:
+ logger.error("AWSDriver: Network interface with id %s is not avaialble" %NetworkInterfaceId)
+ raise exceptions.RWErrorNotFound("AWSDriver: Network interface with id %s is not avaialble" %NetworkInterfaceId)
+ elif len(response) > 1:
+ logger.error("AWSDriver: Duplicate Network interface with id %s is avaialble" %NetworkInterfaceId)
+ raise exceptions.RWErrorDuplicate("AWSDriver: Duplicate Network interface with id %s is avaialble" %NetworkInterfaceId)
+ return response[0]
+
+ def create_network_interface(self,**kwargs):
+ """
+ Create a network interface in specified subnet
+ Arguments:
+ - SubnetId (String): MANDATORY, Subnet to create network interface
+ Returns: EC2.NetworkInterface Object
+ """
+
+ if 'SubnetId' not in kwargs:
+ logger.error("AWSDriver: Insufficent params for create_network_inteface . SubnetId is mandatory parameters")
+ raise AttributeError("AWSDriver: Insufficent params for create_network_inteface . SubnetId is mandatory parameters")
+
+ try:
+ interface = self._ec2_resource_handle.create_network_interface(**kwargs)
+ except botocore.exceptions.ClientError as e:
+ if e.response['Error']['Code'] == 'InvalidSubnetID.NotFound':
+ logger.error("AWSDriver: Create Network interface failed as subnet %s is not found",kwargs['SubnetId'])
+ raise exceptions.RWErrorNotFound("AWSDriver: Create Network interface failed as subnet %s is not found",kwargs['SubnetId'])
+ else:
+ logger.error("AWSDriver: Creating network interface failed with exception: %s",(repr(e)))
+ raise
+ except Exception as e:
+ logger.error("AWSDriver: Creating network interface failed with exception: %s" %(repr(e)))
+ raise
+ return interface
+
+ def delete_network_interface(self,NetworkInterfaceId):
+ """
+ Delete a network interface
+ Arguments:
+ - NetworkInterfaceId(String): MANDATORY
+ Returns: None
+ """
+ try:
+ response = self._ec2_client_handle.delete_network_interface(NetworkInterfaceId=NetworkInterfaceId)
+ except botocore.exceptions.ClientError as e:
+ if e.response['Error']['Code'] == 'InvalidNetworkInterfaceID.NotFound':
+ logger.error("AWSDriver: Delete Network interface not found for interface ID %s",NetworkInterfaceId)
+ raise exceptions.RWErrorNotFound("AWSDriver: Delete Network interface not found for interface ID %s",NetworkInterfaceId)
+ else:
+ logger.error("AWSDriver: Delete network interface failed with exception: %s",(repr(e)))
+ raise
+ except Exception as e:
+ logger.error("AWSDriver: Delete network interface failed with exception: %s",(repr(e)))
+ raise
+
+ def associate_public_ip_to_network_interface(self,NetworkInterfaceId):
+ """
+ Allocate a Elastic IP and associate to network interface
+ Arguments:
+ NetworkInterfaceId (String): MANDATORY
+ Returns: None
+ """
+ try:
+ response = self._ec2_client_handle.allocate_address(Domain='vpc')
+ self._ec2_client_handle.associate_address(NetworkInterfaceId=NetworkInterfaceId,AllocationId = response['AllocationId'])
+ except Exception as e:
+ logger.error("AWSDriver: Associating Public IP to network interface %s failed with exception: %s",NetworkInterfaceId,(repr(e)))
+ raise
+ return response
+
+ def disassociate_public_ip_from_network_interface(self,NetworkInterfaceId):
+ """
+ Disassociate a Elastic IP from network interface and release the same
+ Arguments:
+ NetworkInterfaceId (String): MANDATORY
+ Returns: None
+ """
+ try:
+ interface = self.get_network_interface(NetworkInterfaceId=NetworkInterfaceId)
+ if interface and interface.association and 'AssociationId' in interface.association:
+ self._ec2_client_handle.disassociate_address(AssociationId = interface.association['AssociationId'])
+ self._ec2_client_handle.release_address(AllocationId=interface.association['AllocationId'])
+ except Exception as e:
+ logger.error("AWSDriver: Associating Public IP to network interface %s failed with exception: %s",NetworkInterfaceId,(repr(e)))
+ raise
+
+ def attach_network_interface(self,**kwargs):
+ """
+ Attach network interface to running EC2 instance. Used to add additional interfaces to instance
+ Arguments:
+ - NetworkInterfaceId (String): MANDATORY,
+ - InstanceId(String) : MANDATORY
+ - DeviceIndex (Integer): MANDATORY
+ Returns: Dict with AttachmentId which is string
+ """
+
+ if 'NetworkInterfaceId' not in kwargs or 'InstanceId' not in kwargs or 'DeviceIndex' not in kwargs:
+ logger.error('AWSDriver: Attach network interface to instance requires NetworkInterfaceId and InstanceId as mandatory parameters')
+ raise AttributeError('AWSDriver: Attach network interface to instance requires NetworkInterfaceId and InstanceId as mandatory parameters')
+
+ try:
+ response = self._ec2_client_handle.attach_network_interface(**kwargs)
+ except Exception as e:
+ logger.error("AWSDriver: Attach network interface failed with exception: %s",(repr(e)))
+ raise
+ return response
+
+ def detach_network_interface(self,**kwargs):
+ """
+ Detach network interface from instance
+ Arguments:
+ - AttachmentId (String)
+ Returns: None
+ """
+
+ if 'AttachmentId' not in kwargs:
+ logger.error('AWSDriver: Detach network interface from instance requires AttachmentId as mandatory parameters')
+ raise AttributeError('AWSDriver: Detach network interface from instance requires AttachmentId as mandatory parameters')
+
+ try:
+ response = self._ec2_client_handle.detach_network_interface(**kwargs)
+ except Exception as e:
+ logger.error("AWSDriver: Detach network interface failed with exception: %s",(repr(e)))
+ raise
+
+ def map_flavor_to_instance_type(self,ram,vcpus,disk,inst_types = None):
+ """
+ Method to find a EC2 instance type matching the requested params
+ Arguments:
+ - ram (Integer) : RAM size in MB
+ - vcpus (Integer): VPCU count
+ - disk (Integer): Storage size in GB
+ - inst_types (List): List of string having list of EC2 instance types to choose from
+ assumed to be in order of resource size
+ Returns
+ InstanceType (String) - EC2 Instance Type
+ """
+ if inst_types is None:
+ inst_types = ['c3.large','c3.xlarge','c3.2xlarge', 'c3.4xlarge', 'c3.8xlarge']
+
+ for inst in inst_types:
+ if inst in aws_table.INSTANCE_TYPES:
+ if ( aws_table.INSTANCE_TYPES[inst]['ram'] > ram and
+ aws_table.INSTANCE_TYPES[inst]['vcpu'] > vcpus and
+ aws_table.INSTANCE_TYPES[inst]['disk'] > disk):
+ return inst
+ return 't2.micro'
+
+ def upload_ssh_key(self,key_name,public_key):
+ """
+ Method to upload Public Key to AWS
+ Arguments:
+ - keyname (String): Name for the key pair
+ - public_key (String): Base 64 encoded public key
+ Returns None
+ """
+ self._ec2_resource_handle.import_key_pair(KeyName=key_name,PublicKeyMaterial=public_key)
+
+ def delete_ssh_key(self,key_name):
+ """
+ Method to delete Public Key from AWS
+ Arguments:
+ - keyname (String): Name for the key pair
+ Returns None
+ """
+ self._ec2_client_handle.delete_key_pair(KeyName=key_name)
+
+