2c472795c5195545985656cf8cf32384760fa523
[osm/SO.git] / rwcal / plugins / vala / rwcal_aws / rift / rwcal / aws / aws_drv.py
1 #!/usr/bin/python
2
3 #
4 # Copyright 2016 RIFT.IO Inc
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18
19 import boto3
20 import botocore
21 from . import aws_table
22 from . import exceptions
23
24 import logging
25 logger = logging.getLogger('rwcal.aws.drv')
26 logger.setLevel(logging.DEBUG)
27
28 class AWSDriver(object):
29 """
30 Driver for AWS
31 """
32 def __init__(self, key, secret, region,ssh_key=None,vpcid = None,availability_zone = None,default_subnet_id = None):
33 """
34 Constructor for AWSDriver
35 Arguments:
36 key : AWS user access key
37 secret : AWS user access secret
38 region : AWS region
39 ssh_key: Name of key pair to connect to EC2 instance
40 vpcid : VPC ID for the resources
41 availability_zone: Avaialbility zone to allocate EC2 instance.
42 default_subnet_id: Default subnet id to be used for the EC2 instance interfaces at instance creation time
43 Returns: AWS Driver Object
44 """
45 self._access_key = key
46 self._access_secret = secret
47 self._region = region
48 self._availability_zone = availability_zone
49 self._ssh_key = ssh_key
50
51 self._sess = boto3.session.Session(aws_access_key_id = self._access_key,
52 aws_secret_access_key = self._access_secret,
53 region_name = self._region)
54 self._ec2_resource_handle = self._sess.resource(service_name = 'ec2')
55 self._s3_handle = self._sess.resource(service_name = 's3')
56 self._iam_handle = self._sess.resource(service_name = 'iam')
57
58 self._acct_arn = self._iam_handle.CurrentUser().arn
59 self._account_id = self._acct_arn.split(':')[4]
60 # If VPC id is not passed; use default VPC for the account
61 if vpcid is None:
62 self._vpcid = self._default_vpc_id
63 else:
64 self._vpcid = vpcid
65
66 self._default_subnet_id = default_subnet_id
67 # If default_subnet_is is not passed; get default subnet for AZ.
68 # We use this to create first network interface during instance creation time. This subnet typically should have associate public address
69 # to get public address.
70 if default_subnet_id is None:
71 self._default_subnet_id = self._get_default_subnet_id_for_az
72
73
74 @property
75 def default_subnet_id(self):
76 """
77 Returns default subnet id for account
78 """
79 return self._default_subnet_id
80
81 @property
82 def _ec2_client_handle(self):
83 """
84 Low level EC2 client connection handle
85 Arguments: None
86 Returns: EC2 Client Connection Handle
87 """
88 return self._ec2_resource_handle.meta.client
89
90 @property
91 def _default_vpc_id(self):
92 """
93 Method to get Default VPC ID
94 Arguments: None
95 Returns: Default EC2.Vpc Resource ID for AWS account
96 """
97 return self._default_vpc.vpc_id
98
99 @property
100 def _default_vpc(self):
101 """
102 Method to get Default VPC Resource Object
103 Arguments: None
104 Returns: Default EC2.Vpc Resource for AWS account
105 """
106 try:
107 response = list(self._ec2_resource_handle.vpcs.all())
108 except Exception as e:
109 logger.error("AWSDriver: Get of Default VPC failed with exception: %s" %(repr(e)))
110 raise
111 default_vpc = [vpc for vpc in response if vpc.is_default]
112 assert(len(default_vpc) == 1)
113 return default_vpc[0]
114
115 def _get_vpc_info(self,VpcId):
116 """
117 Get Vpc resource for specificed VpcId
118 Arguments:
119 - VpcId (String) : VPC ID
120 Returns: EC2.Vpc Resouce
121 """
122 VpcIds = list()
123 VpcIds.append(VpcId)
124 response = list(self._ec2_resource_handle.vpcs.filter(
125 VpcIds = VpcIds))
126 if response:
127 assert(len(response) == 1)
128 return response[0]
129 return None
130
131
132 def upload_image(self, **kwargs):
133 """
134 Upload image to s3
135 Arguments: **kwargs -- dictionary
136 {
137 'image_path' : File location for the image,
138 'image_prefix' : Name-Prefix of the image on S3
139 'public_key' : The path to the user's PEM encoded RSA public key certificate file,
140 'private_key' : The path to the user's PEM encoded RSA private key file,
141 'arch' : One of ["i386", "x86_64"],
142 's3_bucket' : Name of S3 bucket where this image should be uploaded
143 (e.g. 'Rift.Cal' or 'Rift.VNF' or 'Rift.3rdPartyVM' etc)
144 'kernelId' : Id of the default kernel to launch the AMI with (OPTIONAL)
145 'ramdiskId' : Id of the default ramdisk to launch the AMI with (OPTIONAL)
146 'block_device_mapping : block_device_mapping string (OPTIONAL)
147 Default block-device-mapping scheme to launch the AMI with. This scheme
148 defines how block devices may be exposed to an EC2 instance of this AMI
149 if the instance-type of the instance is entitled to the specified device.
150 The scheme is a comma-separated list of key=value pairs, where each key
151 is a "virtual-name" and each value, the corresponding native device name
152 desired. Possible virtual-names are:
153 - "ami": denotes the root file system device, as seen by the instance.
154 - "root": denotes the root file system device, as seen by the kernel.
155 - "swap": denotes the swap device, if present.
156 - "ephemeralN": denotes Nth ephemeral store; N is a non-negative integer.
157 Note that the contents of the AMI form the root file system. Samples of
158 block-device-mappings are:
159 '"ami=sda1","root=/dev/sda1","ephemeral0=sda2","swap=sda3"'
160 '"ami=0","root=/dev/dsk/c0d0s0","ephemeral0=1"'
161 }
162 Returns: None
163 """
164 import subprocess
165 import tempfile
166 import os
167 import shutil
168
169 CREATE_BUNDLE_CMD = 'ec2-bundle-image --cert {public_key} --privatekey {private_key} --user {account_id} --image {image_path} --prefix {image_prefix} --arch {arch}'
170 UPLOAD_BUNDLE_CMD = 'ec2-upload-bundle --bucket {bucket} --access-key {key} --secret-key {secret} --manifest {manifest} --region {region} --retry'
171
172 cmdline = CREATE_BUNDLE_CMD.format(public_key = kwargs['public_key'],
173 private_key = kwargs['private_key'],
174 account_id = self._account_id,
175 image_path = kwargs['image_path'],
176 image_prefix = kwargs['image_prefix'],
177 arch = kwargs['arch'])
178
179 if 'kernelId' in kwargs:
180 cmdline += (' --kernel ' + kwargs['kernelId'])
181
182 if 'ramdiskId' in kwargs:
183 cmdline += (' --ramdisk ' + kwargs['ramdiskId'])
184
185 if 'block_device_mapping' in kwargs:
186 cmdline += ' --block-device-mapping ' + kwargs['block_device_mapping']
187
188 ### Create Temporary Directory
189 try:
190 tmp_dir = tempfile.mkdtemp()
191 except Exception as e:
192 logger.error("Failed to create temporary directory. Exception Details: %s" %(repr(e)))
193 raise
194
195 cmdline += (" --destination " + tmp_dir)
196 logger.info('AWSDriver: Executing ec2-bundle-image command. Target directory name: %s. This command may take a while...\n' %(tmp_dir))
197 result = subprocess.call(cmdline.split())
198 if result == 0:
199 logger.info('AWSDriver: ec2-bundle-image command succeeded')
200 else:
201 logger.error('AWSDriver: ec2-bundle-image command failed. Return code %d. CMD: %s'%(result, cmdline))
202 raise OSError('AWSDriver: ec2-bundle-image command failed. Return code %d' %(result))
203
204 logger.info('AWSDriver: Initiating image upload. This may take a while...')
205
206 cmdline = UPLOAD_BUNDLE_CMD.format(bucket = kwargs['s3_bucket'],
207 key = self._access_key,
208 secret = self._access_secret,
209 manifest = tmp_dir+'/'+kwargs['image_prefix']+'.manifest.xml',
210 region = self._region)
211 result = subprocess.call(cmdline.split())
212 if result == 0:
213 logger.info('AWSDriver: ec2-upload-bundle command succeeded')
214 else:
215 logger.error('AWSDriver: ec2-upload-bundle command failed. Return code %d. CMD: %s'%(result, cmdline))
216 raise OSError('AWSDriver: ec2-upload-bundle command failed. Return code %d' %(result))
217 ### Delete the temporary directory
218 logger.info('AWSDriver: Deleting temporary directory and other software artifacts')
219 shutil.rmtree(tmp_dir, ignore_errors = True)
220
221
222 def register_image(self, **kwargs):
223 """
224 Registers an image uploaded to S3 with EC2
225 Arguments: **kwargs -- dictionary
226 {
227 Name (string) : Name of the image
228 ImageLocation(string) : Location of image manifest file in S3 (e.g. 'rift.cal.images/test-img.manifest.xml')
229 Description(string) : Description for the image (OPTIONAL)
230 Architecture (string) : Possible values 'i386' or 'x86_64' (OPTIONAL)
231 KernelId(string) : Kernel-ID Refer: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedKernels.html#AmazonKernelImageIDs (OPTIONAL)
232 RamdiskId(string) : Ramdisk-ID Refer: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedKernels.html#AmazonKernelImageIDs (OPTIONAL)
233 RootDeviceName(string): The name of the root device (for example, /dev/sda1 , or /dev/xvda ) (OPTIONAL)
234 BlockDeviceMappings(list) : List of dictionary of block device mapping (OPTIONAL)
235 [
236 {
237 'VirtualName': 'string',
238 'DeviceName': 'string',
239 'Ebs': {
240 'SnapshotId': 'string',
241 'VolumeSize': 123,
242 'DeleteOnTermination': True|False,
243 'VolumeType': 'standard'|'io1'|'gp2',
244 'Iops': 123,
245 'Encrypted': True|False
246 },
247 'NoDevice': 'string'
248 },
249 ]
250 VirtualizationType(string): The type of virtualization (OPTIONAL)
251 Default: paravirtual
252 SriovNetSupport(string): (OPTIONAL)
253 Set to ``simple`` to enable enhanced networking for the AMI and any instances that are launched from the AMI.
254 This option is supported only for HVM AMIs. Specifying this option with a PV AMI can make instances launched from the AMI unreachable.
255
256 Returns:
257 image_id: UUID of the image
258 """
259
260 kwargs['DryRun'] = False
261 try:
262 response = self._ec2_client_handle.register_image(**kwargs)
263 except Exception as e:
264 logger.error("AWSDriver: List image operation failed with exception: %s" %(repr(e)))
265 raise
266 assert response['ResponseMetadata']['HTTPStatusCode'] == 200
267 return response['ImageId']
268
269
270 def deregister_image(self, ImageId):
271 """
272 DeRegisters image from EC2.
273 Arguments:
274 - ImageId (string): ImageId generated by AWS in register_image call
275 Returns: None
276 """
277 try:
278 response = self._ec2_client_handle.deregister_image(
279 ImageId = ImageId)
280 except Exception as e:
281 logger.error("AWSDriver: deregister_image operation failed with exception: %s" %(repr(e)))
282 raise
283 assert response['ResponseMetadata']['HTTPStatusCode'] == 200
284
285 def get_image(self, ImageId):
286 """
287 Returns a dictionary object describing the Image identified by ImageId
288 """
289 try:
290 response = list(self._ec2_resource_handle.images.filter(ImageIds = [ImageId]))
291 except Exception as e:
292 logger.error("AWSDriver: List image operation failed with exception: %s" %(repr(e)))
293 raise
294 return response[0]
295
296 def list_images(self):
297 """
298 Returns list of dictionaries. Each dictionary contains attributes associated with image
299 Arguments: None
300 Returns: List of dictionaries.
301 """
302 try:
303 response = list(self._ec2_resource_handle.images.filter(Owners = [self._account_id]))
304 except Exception as e:
305 logger.error("AWSDriver: List image operation failed with exception: %s" %(repr(e)))
306 raise
307 return response
308
309 def create_image_from_instance(self,InstanceId,ImageName,VolumeSize = 16):
310 """
311 Creates AWS AMI from the instance root device Volume and registers the same
312 Caller is expected to stop the instance and restart the instance if required
313 Arguments:
314 - InstanceId (String) : AWS EC2 Instance Id
315 - ImageName (String) : Name for AMI
316 Returns
317 - AWS AMI Image Id
318 """
319
320 try:
321 inst = self.get_instance(InstanceId)
322 # Find Volume Id of Root Device
323 if inst.root_device_type == 'ebs':
324 for dev in inst.block_device_mappings:
325 if inst.root_device_name == dev['DeviceName']:
326 volume_id = dev['Ebs']['VolumeId']
327 break
328
329 rsp=self._ec2_resource_handle.create_snapshot(VolumeId=volume_id)
330 snapshot_id = rsp.id
331
332 #Wait for the snapshot to be completed
333 attempts = 0
334 while attempts < 2:
335 try:
336 attempts = attempts + 1
337 waiter = self._ec2_client_handle.get_waiter('snapshot_completed')
338 waiter.wait(SnapshotIds=[snapshot_id])
339 except botocore.exceptions.WaiterError as e:
340 logger.error("AWSDriver: Create Snapshot for image still not completed. Will wait for another iteration")
341 continue
342 except Exception as e:
343 logger.error("AWSDriver: Createing Snapshot for instance failed during image creation: %s", (repr(e)))
344 raise
345 break
346
347 logger.debug("AWSDriver: Snapshot %s completed successfully from instance %s",snapshot_id,InstanceId)
348 image_id = self.register_image(Name=ImageName,VirtualizationType='hvm',
349 RootDeviceName='/dev/sda1',SriovNetSupport='simple',
350 BlockDeviceMappings=[{'DeviceName':'/dev/sda1',
351 'Ebs':{'SnapshotId':snapshot_id,'VolumeSize': VolumeSize,
352 'VolumeType': 'standard', 'DeleteOnTermination': True}}],
353 Architecture='x86_64')
354 return image_id
355 else:
356 logger.error("AWSDriver: Create Image failed as Instance Root device Type should be ebs to create image")
357 raise exceptions.RWErrorFailure("AWSDriver: Create Image failed as Instance Root device Type should be ebs to create image")
358 except Exception as e:
359 logger.error("AWSDriver: Createing image from instance failed with exception: %s", (repr(e)))
360 raise
361
362 def list_instances(self):
363 """
364 Returns list of resource object representing EC2 instance.
365 Arguments: None
366 Returns: List of EC2.Instance object
367 """
368 instance_list = []
369 try:
370 # Skip Instances in terminated state
371 response = self._ec2_resource_handle.instances.filter(
372 Filters = [
373 { 'Name': 'instance-state-name',
374 'Values': ['pending',
375 'running',
376 'shutting-down',
377 'stopping',
378 'stopped']
379 }
380 ])
381 except Exception as e:
382 logger.error("AWSDriver: List instances operation failed with exception: %s" %(repr(e)))
383 raise
384 for instance in response:
385 instance_list.append(instance)
386 return instance_list
387
388 def get_instance(self, InstanceId):
389 """
390 Returns a EC2 resource Object describing the Instance identified by InstanceId
391 Arguments:
392 - InstnaceId (String) : MANDATORY, EC2 Instance Id
393 Returns: EC2.Instance object
394 """
395
396 try:
397 instance = list(self._ec2_resource_handle.instances.filter(
398 InstanceIds = [InstanceId]))
399 except Exception as e:
400 logger.error("AWSDriver: Get instances operation failed with exception: %s" %(repr(e)))
401 raise
402 if len(instance) == 0:
403 logger.error("AWSDriver: instance with id %s not avaialble" %InstanceId)
404 raise exceptions.RWErrorNotFound("AWSDriver: instance with id %s not avaialble" %InstanceId)
405 elif len(instance) > 1:
406 logger.error("AWSDriver: Duplicate instances with id %s is avaialble" %InstanceId)
407 raise exceptions.RWErrorDuplicate("AWSDriver: Duplicate instances with id %s is avaialble" %InstanceId)
408 return instance[0]
409
410 def create_instance(self,**kwargs):
411 """
412 Create an EC2instance.
413 Arguments: **kwargs -- dictionary
414 {
415 ImageId (string): MANDATORY, Id of AMI to create instance
416 SubetId (string): Id of Subnet to start EC2 instance. EC2 instance will be started in VPC subnet resides.
417 Default subnet from account used if not present
418 InstanceType(string): AWS Instance Type name. Default: t2.micro
419 SecurityGroupIds: AWS Security Group Id to associate with the instance. Default from VPC used if not present
420 KeyName (string): Key pair name. Default key pair from account used if not present
421 MinCount (Integer): Minimum number of instance to start. Default: 1
422 MaxCount (Integer): Maximum number of instance to start. Default: 1
423 Placement (Dict) : Dictionary having Placement group details
424 {AvailabilityZone (String): AZ to create the instance}
425 UserData (string) : cloud-init config file
426 }
427 Returns: List of EC2.Instance object
428 """
429
430 if 'ImageId' not in kwargs:
431 logger.error("AWSDriver: Mandatory parameter ImageId not available during create_instance")
432 raise AttributeError("Mandatory parameter ImageId not available during create_instance")
433
434 #Validate image exists and is avaialble
435 try:
436 image_res = self._ec2_resource_handle.Image(kwargs['ImageId'])
437 image_res.load()
438 except Exception as e:
439 logger.error("AWSDriver: Image with id %s not available and failed with exception: %s",kwargs['ImageId'],(repr(e)))
440 raise AttributeError("AWSDriver: Image with id %s not available and failed with exception: %s",kwargs['ImageId'],(repr(e)))
441 if image_res.state != 'available':
442 logger.error("AWSDriver: Image state is not available for image with id %s; Current state is %s",
443 image_res.id,image_res.state)
444 raise AttributeError("ImageId is not valid")
445
446 # If MinCount or MaxCount is not passed set them to default of 1
447 if 'MinCount' not in kwargs:
448 kwargs['MinCount'] = 1
449 if 'MaxCount' not in kwargs:
450 kwargs['MaxCount'] = kwargs['MinCount']
451
452 if 'KeyName' not in kwargs:
453 if not self._ssh_key:
454 logger.error("AWSDriver: Key not available during create_instance to allow SSH")
455 else:
456 kwargs['KeyName'] = self._ssh_key
457
458 if 'Placement' not in kwargs and self._availability_zone is not None:
459 placement = {'AvailabilityZone':self._availability_zone}
460 kwargs['Placement'] = placement
461
462 if 'SubnetId' not in kwargs and 'NetworkInterfaces' not in kwargs:
463 if self._default_subnet_id:
464 kwargs['SubnetId'] = self._default_subnet_id
465 else:
466 logger.error("AWSDriver: Valid subnetid not present during create instance")
467 raise AttributeError("Valid subnet not present during create instance")
468
469 if self._availability_zone and 'SubnetId' in kwargs:
470 subnet = self.get_subnet(SubnetId= kwargs['SubnetId'])
471 if not subnet:
472 logger.error("AWSDriver: Valid subnet not found for subnetid %s",kwargs['SubnetId'])
473 raise AttributeError("Valid subnet not found for subnetid %s",kwargs['SubnetId'])
474 if subnet.availability_zone != self._availability_zone:
475 logger.error("AWSDriver: AZ of Subnet %s %s doesnt match account AZ %s",kwargs['SubnetId'],
476 subnet.availability_zone,self._availability_zone)
477 raise AttributeError("AWSDriver: AZ of Subnet %s %s doesnt match account AZ %s",kwargs['SubnetId'],
478 subnet.availability_zone,self._availability_zone)
479
480 # If instance type is not passed; use t2.micro as default
481 if 'InstanceType' not in kwargs or kwargs['InstanceType'] is None:
482 kwargs['InstanceType'] = 't2.micro'
483 inst_type = kwargs['InstanceType']
484 if inst_type not in aws_table.INSTANCE_TYPES.keys():
485 logger.error("AWSDriver: Invalid instance type %s used",inst_type)
486 raise AttributeError('InstanceType %s is not valid' %inst_type)
487
488 #validate instance_type for AMI
489 if image_res.sriov_net_support == 'simple':
490 if image_res.virtualization_type != 'hvm':
491 logger.error("AWSDriver: Image with id %s has SRIOV net support but virtualization type is not hvm",kwargs['ImageId'])
492 raise AttributeError('Invalid Image with id %s' %kwargs['ImageId'])
493 if aws_table.INSTANCE_TYPES[inst_type]['sriov'] is False:
494 logger.warning("AWSDriver: Image %s support SR-IOV but instance type %s does not support HVM",kwargs['ImageId'],inst_type)
495
496 if image_res.virtualization_type == 'paravirtual' and aws_table.INSTANCE_TYPES[inst_type]['paravirt'] is False: # Need to check virt type str for PV
497 logger.error("AWSDriver: Image %s requires PV support but instance %s does not support PV",kwargs['ImageId'],inst_type)
498 raise AttributeError('Image %s requires PV support but instance %s does not support PV',kwargs['ImageId'],inst_type)
499
500 if image_res.root_device_type == 'instance-store' and aws_table.INSTANCE_TYPES[inst_type]['disk'] == 0:
501 logger.error("AWSDriver: Image %s uses instance-store root device type that is not supported by instance type %s",kwargs['ImageId'],inst_type)
502 raise AttributeError("AWSDriver: Image %s uses instance-store root device type that is not supported by instance type %s",kwargs['ImageId'],inst_type)
503
504
505 # Support of instance type varies across regions and also based on account. So we are not validating it
506 #if inst_type not in aws_table.REGION_DETAILS[self._region]['instance_types']:
507 # logger.error("AWSDriver: instance type %s not supported in region %s",inst_type,self._region)
508 # raise AttributeError("AWSDriver: instance type %s not supported in region %s",inst_type,self._region)
509
510 try:
511 instances = self._ec2_resource_handle.create_instances(**kwargs)
512 except Exception as e:
513 logger.error("AWSDriver: Creating instance failed with exception: %s" %(repr(e)))
514 raise
515 return instances
516
517 def terminate_instance(self,InstanceId):
518 """
519 Termintae an EC2 instance
520 Arguments:
521 - InstanceId (String): ID of EC2 instance
522 Returns: None
523 """
524
525 InstanceIds = InstanceId
526 if type(InstanceIds) is not list:
527 InstanceIds = list()
528 InstanceIds.append(InstanceId)
529
530 try:
531 response = self._ec2_client_handle.terminate_instances(InstanceIds=InstanceIds)
532 except Exception as e:
533 logger.error("AWSDriver: Terminate instance failed with exception: %s" %(repr(e)))
534 raise
535 return response
536
537 def stop_instance(self,InstanceId):
538 """
539 Stop an EC2 instance. Stop is supported only for EBS backed instance
540 Arguments:
541 - InstanceId (String): ID of EC2 instance
542 Returns: None
543 """
544
545 InstanceIds = InstanceId
546 if type(InstanceIds) is not list:
547 InstanceIds = list()
548 InstanceIds.append(InstanceId)
549
550 try:
551 response = self._ec2_client_handle.stop_instances(InstanceIds=InstanceIds)
552 except Exception as e:
553 logger.error("AWSDriver: Stop for instance %s failed with exception: %s",InstanceId,repr(e))
554 raise
555 return response
556
557 def start_instance(self,InstanceId):
558 """
559 Start an EC2 instance. Start is supported only for EBS backed instance
560 Arguments:
561 - InstanceId (String): ID of EC2 instance
562 Returns: None
563 """
564
565 InstanceIds = InstanceId
566 if type(InstanceIds) is not list:
567 InstanceIds = list()
568 InstanceIds.append(InstanceId)
569
570 try:
571 response = self._ec2_client_handle.start_instances(InstanceIds=InstanceIds)
572 except Exception as e:
573 logger.error("AWSDriver: Start for instance %s failed with exception: %s",InstanceId,repr(e))
574 raise
575 return response
576
577 @property
578 def _get_default_subnet_id_for_az(self):
579 """
580 Get default subnet id for AWS Driver registered Availability Zone
581 Arguments: None
582 Returns: SubnetId (String)
583 """
584
585 if self._availability_zone:
586 subnet = self._get_default_subnet_for_az(self._availability_zone)
587 return subnet.id
588 else:
589 return None
590
591 def _get_default_subnet_for_az(self,AvailabilityZone):
592 """
593 Get default Subnet for Avaialbility Zone
594 Arguments:
595 - AvailabilityZone (String) : EC2 AZ
596 Returns: EC2.Subnet object
597 """
598
599 AvailabilityZones = [AvailabilityZone]
600 try:
601 response = list(self._ec2_resource_handle.subnets.filter(
602 Filters = [
603 {'Name':'availability-zone',
604 'Values': AvailabilityZones}]))
605 except Exception as e:
606 logger.error("AWSDriver: Get default subnet for Availability zone failed with exception: %s" %(repr(e)))
607 raise
608 default_subnet = [subnet for subnet in response if subnet.default_for_az is True and subnet.vpc_id == self._vpcid]
609 assert(len(default_subnet) == 1)
610 return default_subnet[0]
611
612 def get_subnet_list(self,VpcId=None):
613 """
614 List all the subnets
615 Arguments:
616 - VpcId (String) - VPC ID to filter the subnet list
617 Returns: List of EC2.Subnet Object
618 """
619
620 try:
621 VpcIds = VpcId
622 if VpcId is not None:
623 if type(VpcIds) is not list:
624 VpcIds = list()
625 VpcIds.append(VpcId)
626 response = list(self._ec2_resource_handle.subnets.filter(
627 Filters = [
628 { 'Name': 'vpc-id',
629 'Values': VpcIds}]))
630 else:
631 response = list(self._ec2_resource_handle.subnets.all())
632 except Exception as e:
633 logger.error("AWSDriver: List subnets operation failed with exception: %s" %(repr(e)))
634 raise
635 return response
636
637 def get_subnet(self,SubnetId):
638 """
639 Get the subnet for specified SubnetId
640 Arguments:
641 - SubnetId (String) - MANDATORY
642 Returns: EC2.Subnet Object
643 """
644
645 try:
646 response = list(self._ec2_resource_handle.subnets.filter(SubnetIds=[SubnetId]))
647 except botocore.exceptions.ClientError as e:
648 if e.response['Error']['Code'] == 'InvalidSubnetID.NotFound':
649 logger.error("AWSDriver: Get Subnet Invalid SubnetID %s",SubnetId)
650 raise exceptions.RWErrorNotFound("AWSDriver: Delete Subnet Invalid SubnetID %s",SubnetId)
651 else:
652 logger.error("AWSDriver: Creating network interface failed with exception: %s",(repr(e)))
653 raise
654 except Exception as e:
655 logger.error("AWSDriver: Get subnet operation failed with exception: %s" %(repr(e)))
656 raise
657 if len(response) == 0:
658 logger.error("AWSDriver: subnet with id %s is not avaialble" %SubnetId)
659 raise exceptions.RWErrorNotFoun("AWSDriver: subnet with id %s is not avaialble" %SubnetId)
660 elif len(response) > 1:
661 logger.error("AWSDriver: Duplicate subnet with id %s is avaialble" %SubnetId)
662 raise exceptions.RWErrorDuplicate("AWSDriver: Duplicate subnet with id %s is avaialble" %SubnetId)
663 return response[0]
664
665 def create_subnet(self,**kwargs):
666 """
667 Create a EC2 subnet based on specified CIDR
668 Arguments:
669 - CidrBlock (String): MANDATORY. CIDR for subnet. CIDR should be within VPC CIDR
670 - VpcId (String): VPC ID to create the subnet. Default AZ from AWS Driver registration used if not present.
671 - AvailabilityZone (String): Availability zone to create subnet. Default AZ from AWS Driver registration used
672 if not present
673 Returns: EC2.Subnet Object
674 """
675
676 if 'CidrBlock' not in kwargs:
677 logger.error("AWSDriver: Insufficent params for create_subnet. CidrBlock is mandatory parameter")
678 raise AttributeError("AWSDriver: Insufficent params for create_subnet. CidrBlock is mandatory parameter")
679
680 if 'VpcId' not in kwargs:
681 kwargs['VpcId'] = self._vpcid
682 if 'AvailabilityZone' not in kwargs and self._availability_zone is not None:
683 kwargs['AvailabilityZone'] = self._availability_zone
684
685 vpc = self._get_vpc_info(kwargs['VpcId'])
686 if not vpc:
687 logger.error("AWSDriver: Subnet creation failed as VpcId %s does not exist", kwargs['VpcId'])
688 raise exceptions.RWErrorNotFound("AWSDriver: Subnet creation failed as VpcId %s does not exist", kwargs['VpcId'])
689 if vpc.state != 'available':
690 logger.error("AWSDriver: Subnet creation failed as VpcId %s is not in available state. Current state is %s", kwargs['VpcId'],vpc.state)
691 raise exceptions.RWErrorNotConnected("AWSDriver: Subnet creation failed as VpcId %s is not in available state. Current state is %s", kwargs['VpcId'],vpc.state)
692
693 try:
694 subnet = self._ec2_resource_handle.create_subnet(**kwargs)
695 except botocore.exceptions.ClientError as e:
696 if e.response['Error']['Code'] == 'InvalidSubnet.Conflict':
697 logger.error("AWSDriver: Create Subnet for ip %s failed due to overalp with existing subnet in VPC %s",kwargs['CidrBlock'],kwargs['VpcId'])
698 raise exceptions.RWErrorExists("AWSDriver: Create Subnet for ip %s failed due to overalp with existing subnet in VPC %s",kwargs['CidrBlock'],kwargs['VpcId'])
699 elif e.response['Error']['Code'] == 'InvalidSubnet.Range':
700 logger.error("AWSDriver: Create Subnet for ip %s failed as it is not in VPC CIDR range for VPC %s",kwargs['CidrBlock'],kwargs['VpcId'])
701 raise AttributeError("AWSDriver: Create Subnet for ip %s failed as it is not in VPC CIDR range for VPC %s",kwargs['CidrBlock'],kwargs['VpcId'])
702 else:
703 logger.error("AWSDriver: Creating subnet failed with exception: %s",(repr(e)))
704 raise
705 except Exception as e:
706 logger.error("AWSDriver: Creating subnet failed with exception: %s" %(repr(e)))
707 raise
708 return subnet
709
710 def modify_subnet(self,SubnetId,MapPublicIpOnLaunch):
711 """
712 Modify a EC2 subnet
713 Arguements:
714 - SubnetId (String): MANDATORY, EC2 Subnet ID
715 - MapPublicIpOnLaunch (Boolean): Flag to indicate if subnet is associated with public IP
716 """
717
718 try:
719 response = self._ec2_client_handle.modify_subnet_attribute(SubnetId=SubnetId,MapPublicIpOnLaunch={'Value':MapPublicIpOnLaunch})
720 except botocore.exceptions.ClientError as e:
721 if e.response['Error']['Code'] == 'InvalidSubnetID.NotFound':
722 logger.error("AWSDriver: Modify Subnet Invalid SubnetID %s",SubnetId)
723 raise exceptions.RWErrorNotFound("AWSDriver: Modify Subnet Invalid SubnetID %s",SubnetId)
724 else:
725 logger.error("AWSDriver: Modify subnet failed with exception: %s",(repr(e)))
726 raise
727 except Exception as e:
728 logger.error("AWSDriver: Modify subnet failed with exception: %s",(repr(e)))
729 raise
730
731
732 def delete_subnet(self,SubnetId):
733 """
734 Delete a EC2 subnet
735 Arguements:
736 - SubnetId (String): MANDATORY, EC2 Subnet ID
737 Returns: None
738 """
739
740 try:
741 response = self._ec2_client_handle.delete_subnet(SubnetId=SubnetId)
742 except botocore.exceptions.ClientError as e:
743 if e.response['Error']['Code'] == 'InvalidSubnetID.NotFound':
744 logger.error("AWSDriver: Delete Subnet Invalid SubnetID %s",SubnetId)
745 raise exceptions.RWErrorNotFound("AWSDriver: Delete Subnet Invalid SubnetID %s",SubnetId)
746 else:
747 logger.error("AWSDriver: Delete subnet failed with exception: %s",(repr(e)))
748 raise
749 except Exception as e:
750 logger.error("AWSDriver: Delete subnet failed with exception: %s",(repr(e)))
751 raise
752
753 def get_network_interface_list(self,SubnetId=None,VpcId=None,InstanceId = None):
754 """
755 List all the network interfaces
756 Arguments:
757 - SubnetId (String)
758 - VpcId (String)
759 - InstanceId (String)
760 Returns List of EC2.NetworkInterface
761 """
762
763 try:
764 if InstanceId is not None:
765 InstanceIds = [InstanceId]
766 response = list(self._ec2_resource_handle.network_interfaces.filter(
767 Filters = [
768 { 'Name': 'attachment.instance-id',
769 'Values': InstanceIds}]))
770 elif SubnetId is not None:
771 SubnetIds = SubnetId
772 if type(SubnetId) is not list:
773 SubnetIds = list()
774 SubnetIds.append(SubnetId)
775 response = list(self._ec2_resource_handle.network_interfaces.filter(
776 Filters = [
777 { 'Name': 'subnet-id',
778 'Values': SubnetIds}]))
779 elif VpcId is not None:
780 VpcIds = VpcId
781 if type(VpcIds) is not list:
782 VpcIds = list()
783 VpcIds.append(VpcId)
784 response = list(self._ec2_resource_handle.network_interfaces.filter(
785 Filters = [
786 { 'Name': 'vpc-id',
787 'Values': VpcIds}]))
788 else:
789 response = list(self._ec2_resource_handle.network_interfaces.all())
790 except Exception as e:
791 logger.error("AWSDriver: List network interfaces operation failed with exception: %s" %(repr(e)))
792 raise
793 return response
794
795 def get_network_interface(self,NetworkInterfaceId):
796 """
797 Get the network interface
798 Arguments:
799 NetworkInterfaceId (String): MANDATORY, EC2 Network Interface Id
800 Returns: EC2.NetworkInterface Object
801 """
802
803 try:
804 response = list(self._ec2_resource_handle.network_interfaces.filter(NetworkInterfaceIds=[NetworkInterfaceId]))
805 except Exception as e:
806 logger.error("AWSDriver: List Network Interfaces operation failed with exception: %s" %(repr(e)))
807 raise
808 if len(response) == 0:
809 logger.error("AWSDriver: Network interface with id %s is not avaialble" %NetworkInterfaceId)
810 raise exceptions.RWErrorNotFound("AWSDriver: Network interface with id %s is not avaialble" %NetworkInterfaceId)
811 elif len(response) > 1:
812 logger.error("AWSDriver: Duplicate Network interface with id %s is avaialble" %NetworkInterfaceId)
813 raise exceptions.RWErrorDuplicate("AWSDriver: Duplicate Network interface with id %s is avaialble" %NetworkInterfaceId)
814 return response[0]
815
816 def create_network_interface(self,**kwargs):
817 """
818 Create a network interface in specified subnet
819 Arguments:
820 - SubnetId (String): MANDATORY, Subnet to create network interface
821 Returns: EC2.NetworkInterface Object
822 """
823
824 if 'SubnetId' not in kwargs:
825 logger.error("AWSDriver: Insufficent params for create_network_inteface . SubnetId is mandatory parameters")
826 raise AttributeError("AWSDriver: Insufficent params for create_network_inteface . SubnetId is mandatory parameters")
827
828 try:
829 interface = self._ec2_resource_handle.create_network_interface(**kwargs)
830 except botocore.exceptions.ClientError as e:
831 if e.response['Error']['Code'] == 'InvalidSubnetID.NotFound':
832 logger.error("AWSDriver: Create Network interface failed as subnet %s is not found",kwargs['SubnetId'])
833 raise exceptions.RWErrorNotFound("AWSDriver: Create Network interface failed as subnet %s is not found",kwargs['SubnetId'])
834 else:
835 logger.error("AWSDriver: Creating network interface failed with exception: %s",(repr(e)))
836 raise
837 except Exception as e:
838 logger.error("AWSDriver: Creating network interface failed with exception: %s" %(repr(e)))
839 raise
840 return interface
841
842 def delete_network_interface(self,NetworkInterfaceId):
843 """
844 Delete a network interface
845 Arguments:
846 - NetworkInterfaceId(String): MANDATORY
847 Returns: None
848 """
849 try:
850 response = self._ec2_client_handle.delete_network_interface(NetworkInterfaceId=NetworkInterfaceId)
851 except botocore.exceptions.ClientError as e:
852 if e.response['Error']['Code'] == 'InvalidNetworkInterfaceID.NotFound':
853 logger.error("AWSDriver: Delete Network interface not found for interface ID %s",NetworkInterfaceId)
854 raise exceptions.RWErrorNotFound("AWSDriver: Delete Network interface not found for interface ID %s",NetworkInterfaceId)
855 else:
856 logger.error("AWSDriver: Delete network interface failed with exception: %s",(repr(e)))
857 raise
858 except Exception as e:
859 logger.error("AWSDriver: Delete network interface failed with exception: %s",(repr(e)))
860 raise
861
862 def associate_public_ip_to_network_interface(self,NetworkInterfaceId):
863 """
864 Allocate a Elastic IP and associate to network interface
865 Arguments:
866 NetworkInterfaceId (String): MANDATORY
867 Returns: None
868 """
869 try:
870 response = self._ec2_client_handle.allocate_address(Domain='vpc')
871 self._ec2_client_handle.associate_address(NetworkInterfaceId=NetworkInterfaceId,AllocationId = response['AllocationId'])
872 except Exception as e:
873 logger.error("AWSDriver: Associating Public IP to network interface %s failed with exception: %s",NetworkInterfaceId,(repr(e)))
874 raise
875 return response
876
877 def disassociate_public_ip_from_network_interface(self,NetworkInterfaceId):
878 """
879 Disassociate a Elastic IP from network interface and release the same
880 Arguments:
881 NetworkInterfaceId (String): MANDATORY
882 Returns: None
883 """
884 try:
885 interface = self.get_network_interface(NetworkInterfaceId=NetworkInterfaceId)
886 if interface and interface.association and 'AssociationId' in interface.association:
887 self._ec2_client_handle.disassociate_address(AssociationId = interface.association['AssociationId'])
888 self._ec2_client_handle.release_address(AllocationId=interface.association['AllocationId'])
889 except Exception as e:
890 logger.error("AWSDriver: Associating Public IP to network interface %s failed with exception: %s",NetworkInterfaceId,(repr(e)))
891 raise
892
893 def attach_network_interface(self,**kwargs):
894 """
895 Attach network interface to running EC2 instance. Used to add additional interfaces to instance
896 Arguments:
897 - NetworkInterfaceId (String): MANDATORY,
898 - InstanceId(String) : MANDATORY
899 - DeviceIndex (Integer): MANDATORY
900 Returns: Dict with AttachmentId which is string
901 """
902
903 if 'NetworkInterfaceId' not in kwargs or 'InstanceId' not in kwargs or 'DeviceIndex' not in kwargs:
904 logger.error('AWSDriver: Attach network interface to instance requires NetworkInterfaceId and InstanceId as mandatory parameters')
905 raise AttributeError('AWSDriver: Attach network interface to instance requires NetworkInterfaceId and InstanceId as mandatory parameters')
906
907 try:
908 response = self._ec2_client_handle.attach_network_interface(**kwargs)
909 except Exception as e:
910 logger.error("AWSDriver: Attach network interface failed with exception: %s",(repr(e)))
911 raise
912 return response
913
914 def detach_network_interface(self,**kwargs):
915 """
916 Detach network interface from instance
917 Arguments:
918 - AttachmentId (String)
919 Returns: None
920 """
921
922 if 'AttachmentId' not in kwargs:
923 logger.error('AWSDriver: Detach network interface from instance requires AttachmentId as mandatory parameters')
924 raise AttributeError('AWSDriver: Detach network interface from instance requires AttachmentId as mandatory parameters')
925
926 try:
927 response = self._ec2_client_handle.detach_network_interface(**kwargs)
928 except Exception as e:
929 logger.error("AWSDriver: Detach network interface failed with exception: %s",(repr(e)))
930 raise
931
932 def map_flavor_to_instance_type(self,ram,vcpus,disk,inst_types = None):
933 """
934 Method to find a EC2 instance type matching the requested params
935 Arguments:
936 - ram (Integer) : RAM size in MB
937 - vcpus (Integer): VPCU count
938 - disk (Integer): Storage size in GB
939 - inst_types (List): List of string having list of EC2 instance types to choose from
940 assumed to be in order of resource size
941 Returns
942 InstanceType (String) - EC2 Instance Type
943 """
944 if inst_types is None:
945 inst_types = ['c3.large','c3.xlarge','c3.2xlarge', 'c3.4xlarge', 'c3.8xlarge']
946
947 for inst in inst_types:
948 if inst in aws_table.INSTANCE_TYPES:
949 if ( aws_table.INSTANCE_TYPES[inst]['ram'] > ram and
950 aws_table.INSTANCE_TYPES[inst]['vcpu'] > vcpus and
951 aws_table.INSTANCE_TYPES[inst]['disk'] > disk):
952 return inst
953 return 't2.micro'
954
955 def upload_ssh_key(self,key_name,public_key):
956 """
957 Method to upload Public Key to AWS
958 Arguments:
959 - keyname (String): Name for the key pair
960 - public_key (String): Base 64 encoded public key
961 Returns None
962 """
963 self._ec2_resource_handle.import_key_pair(KeyName=key_name,PublicKeyMaterial=public_key)
964
965 def delete_ssh_key(self,key_name):
966 """
967 Method to delete Public Key from AWS
968 Arguments:
969 - keyname (String): Name for the key pair
970 Returns None
971 """
972 self._ec2_client_handle.delete_key_pair(KeyName=key_name)
973
974