Merge branch 'master' into vnffg
[osm/RO.git] / osm_ro / vimconn_aws.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2017 xFlow Research Pvt. Ltd
5 # This file is part of openmano
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: saboor.ahmad@xflowresearch.com
22 ##
23
24 '''
25 AWS-connector implements all the methods to interact with AWS using the BOTO client
26 '''
27
28 __author__ = "Saboor Ahmad"
29 __date__ = "10-Apr-2017"
30
31 import vimconn
32 import yaml
33 import logging
34 import netaddr
35 import time
36
37 try:
38 import boto
39 import boto.ec2
40 import boto.vpc
41 except:
42 exit("Boto not avialable. Try activating your virtualenv OR `pip install boto`")
43
44
45 class vimconnector(vimconn.vimconnector):
46 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
47 config={}, persistent_info={}):
48 """ Params: uuid - id asigned to this VIM
49 name - name assigned to this VIM, can be used for logging
50 tenant_id - ID to be used for tenant
51 tenant_name - name of tenant to be used VIM tenant to be used
52 url_admin - optional, url used for administrative tasks
53 user - credentials of the VIM user
54 passwd - credentials of the VIM user
55 log_level - if must use a different log_level than the general one
56 config - dictionary with misc VIM information
57 region_name - name of region to deploy the instances
58 vpc_cidr_block - default CIDR block for VPC
59 security_groups - default security group to specify this instance
60 persistent_info - dict where the class can store information that will be available among class
61 destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an
62 empty dict. Useful to store login/tokens information for speed up communication
63 """
64
65 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
66 config, persistent_info)
67
68 self.persistent_info = persistent_info
69 self.a_creds = {}
70 if user:
71 self.a_creds['aws_access_key_id'] = user
72 else:
73 raise vimconn.vimconnAuthException("Username is not specified")
74 if passwd:
75 self.a_creds['aws_secret_access_key'] = passwd
76 else:
77 raise vimconn.vimconnAuthException("Password is not specified")
78 if 'region_name' in config:
79 self.region = config.get('region_name')
80 else:
81 raise vimconn.vimconnNotFoundException("AWS region_name is not specified at config")
82
83 self.vpc_data = {}
84 self.subnet_data = {}
85 self.conn = None
86 self.conn_vpc = None
87 self.account_id = None
88
89 self.vpc_id = self.get_tenant_list()[0]['id']
90 # we take VPC CIDR block if specified, otherwise we use the default CIDR
91 # block suggested by AWS while creating instance
92 self.vpc_cidr_block = '10.0.0.0/24'
93
94 if tenant_id:
95 self.vpc_id = tenant_id
96 if 'vpc_cidr_block' in config:
97 self.vpc_cidr_block = config['vpc_cidr_block']
98
99 self.security_groups = None
100 if 'security_groups' in config:
101 self.security_groups = config['security_groups']
102
103 self.key_pair = None
104 if 'key_pair' in config:
105 self.key_pair = config['key_pair']
106
107 self.flavor_info = None
108 if 'flavor_info' in config:
109 flavor_data = config.get('flavor_info')
110 if isinstance(flavor_data, str):
111 try:
112 with open(flavor_data[1:], 'r') as stream:
113 self.flavor_info = yaml.load(stream)
114 except yaml.YAMLError as e:
115 self.flavor_info = None
116 raise vimconn.vimconnException("Bad format at file '{}': {}".format(flavor_data[1:], e))
117 except IOError as e:
118 raise vimconn.vimconnException("Error reading file '{}': {}".format(flavor_data[1:], e))
119 elif isinstance(flavor_data, dict):
120 self.flavor_info = flavor_data
121
122 self.logger = logging.getLogger('openmano.vim.aws')
123 if log_level:
124 self.logger.setLevel(getattr(logging, log_level))
125
126 def __setitem__(self, index, value):
127 """Params: index - name of value of set
128 value - value to set
129 """
130 if index == 'user':
131 self.a_creds['aws_access_key_id'] = value
132 elif index == 'passwd':
133 self.a_creds['aws_secret_access_key'] = value
134 elif index == 'region':
135 self.region = value
136 else:
137 vimconn.vimconnector.__setitem__(self, index, value)
138
139 def _reload_connection(self):
140 """Returns: sets boto.EC2 and boto.VPC connection to work with AWS services
141 """
142
143 try:
144 self.conn = boto.ec2.connect_to_region(self.region, aws_access_key_id=self.a_creds['aws_access_key_id'],
145 aws_secret_access_key=self.a_creds['aws_secret_access_key'])
146 self.conn_vpc = boto.vpc.connect_to_region(self.region, aws_access_key_id=self.a_creds['aws_access_key_id'],
147 aws_secret_access_key=self.a_creds['aws_secret_access_key'])
148 # client = boto3.client("sts", aws_access_key_id=self.a_creds['aws_access_key_id'], aws_secret_access_key=self.a_creds['aws_secret_access_key'])
149 # self.account_id = client.get_caller_identity()["Account"]
150 except Exception as e:
151 self.format_vimconn_exception(e)
152
153 def format_vimconn_exception(self, e):
154 """Params: an Exception object
155 Returns: Raises the exception 'e' passed in mehtod parameters
156 """
157
158 self.conn = None
159 self.conn_vpc = None
160 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e))
161
162 def get_availability_zones_list(self):
163 """Obtain AvailabilityZones from AWS
164 """
165
166 try:
167 self._reload_connection()
168 az_list = []
169 for az in self.conn.get_all_zones():
170 az_list.append(az.name)
171 return az_list
172 except Exception as e:
173 self.format_vimconn_exception(e)
174
175 def get_tenant_list(self, filter_dict={}):
176 """Obtain tenants of VIM
177 filter_dict dictionary that can contain the following keys:
178 name: filter by tenant name
179 id: filter by tenant uuid/id
180 <other VIM specific>
181 Returns the tenant list of dictionaries, and empty list if no tenant match all the filers:
182 [{'name':'<name>, 'id':'<id>, ...}, ...]
183 """
184
185 try:
186 self._reload_connection()
187 vpc_ids = []
188 tfilters = {}
189 if filter_dict != {}:
190 if 'id' in filter_dict:
191 vpc_ids.append(filter_dict['id'])
192 tfilters['name'] = filter_dict['id']
193 tenants = self.conn_vpc.get_all_vpcs(vpc_ids, tfilters)
194 tenant_list = []
195 for tenant in tenants:
196 tenant_list.append({'id': str(tenant.id), 'name': str(tenant.id), 'status': str(tenant.state),
197 'cidr_block': str(tenant.cidr_block)})
198 return tenant_list
199 except Exception as e:
200 self.format_vimconn_exception(e)
201
202 def new_tenant(self, tenant_name, tenant_description):
203 """Adds a new tenant to VIM with this name and description, this is done using admin_url if provided
204 "tenant_name": string max lenght 64
205 "tenant_description": string max length 256
206 returns the tenant identifier or raise exception
207 """
208
209 self.logger.debug("Adding a new VPC")
210 try:
211 self._reload_connection()
212 vpc = self.conn_vpc.create_vpc(self.vpc_cidr_block)
213 self.conn_vpc.modify_vpc_attribute(vpc.id, enable_dns_support=True)
214 self.conn_vpc.modify_vpc_attribute(vpc.id, enable_dns_hostnames=True)
215
216 gateway = self.conn_vpc.create_internet_gateway()
217 self.conn_vpc.attach_internet_gateway(gateway.id, vpc.id)
218 route_table = self.conn_vpc.create_route_table(vpc.id)
219 self.conn_vpc.create_route(route_table.id, '0.0.0.0/0', gateway.id)
220
221 self.vpc_data[vpc.id] = {'gateway': gateway.id, 'route_table': route_table.id,
222 'subnets': self.subnet_sizes(len(self.get_availability_zones_list()),
223 self.vpc_cidr_block)}
224 return vpc.id
225 except Exception as e:
226 self.format_vimconn_exception(e)
227
228 def delete_tenant(self, tenant_id):
229 """Delete a tenant from VIM
230 tenant_id: returned VIM tenant_id on "new_tenant"
231 Returns None on success. Raises and exception of failure. If tenant is not found raises vimconnNotFoundException
232 """
233
234 self.logger.debug("Deleting specified VPC")
235 try:
236 self._reload_connection()
237 vpc = self.vpc_data.get(tenant_id)
238 if 'gateway' in vpc and 'route_table' in vpc:
239 gateway_id, route_table_id = vpc['gateway'], vpc['route_table']
240 self.conn_vpc.detach_internet_gateway(gateway_id, tenant_id)
241 self.conn_vpc.delete_vpc(tenant_id)
242 self.conn_vpc.delete_route(route_table_id, '0.0.0.0/0')
243 else:
244 self.conn_vpc.delete_vpc(tenant_id)
245 except Exception as e:
246 self.format_vimconn_exception(e)
247
248 def subnet_sizes(self, availability_zones, cidr):
249 """Calcualtes possible subnets given CIDR value of VPC
250 """
251
252 if availability_zones != 2 and availability_zones != 3:
253 self.logger.debug("Number of AZs should be 2 or 3")
254 raise vimconn.vimconnNotSupportedException("Number of AZs should be 2 or 3")
255
256 netmasks = ('255.255.252.0', '255.255.254.0', '255.255.255.0', '255.255.255.128')
257 ip = netaddr.IPNetwork(cidr)
258 mask = ip.netmask
259
260 if str(mask) not in netmasks:
261 self.logger.debug("Netmask " + str(mask) + " not found")
262 raise vimconn.vimconnNotFoundException("Netmask " + str(mask) + " not found")
263
264 if availability_zones == 2:
265 for n, netmask in enumerate(netmasks):
266 if str(mask) == netmask:
267 subnets = list(ip.subnet(n + 24))
268 else:
269 for n, netmask in enumerate(netmasks):
270 if str(mask) == netmask:
271 pub_net = list(ip.subnet(n + 24))
272 pri_subs = pub_net[1:]
273 pub_mask = pub_net[0].netmask
274 pub_split = list(ip.subnet(26)) if (str(pub_mask) == '255.255.255.0') else list(ip.subnet(27))
275 pub_subs = pub_split[:3]
276 subnets = pub_subs + pri_subs
277
278 return map(str, subnets)
279
280 def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None):
281 """Adds a tenant network to VIM
282 Params:
283 'net_name': name of the network
284 'net_type': one of:
285 'bridge': overlay isolated network
286 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
287 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
288 'ip_profile': is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
289 'ip-version': can be one of ["IPv4","IPv6"]
290 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
291 'gateway-address': (Optional) ip_schema, that is X.X.X.X
292 'dns-address': (Optional) ip_schema,
293 'dhcp': (Optional) dict containing
294 'enabled': {"type": "boolean"},
295 'start-address': ip_schema, first IP to grant
296 'count': number of IPs to grant.
297 'shared': if this network can be seen/use by other tenants/organization
298 'vlan': in case of a data or ptp net_type, the intended vlan tag to be used for the network
299 Returns the network identifier on success or raises and exception on failure
300 """
301
302 self.logger.debug("Adding a subnet to VPC")
303 try:
304 self._reload_connection()
305 subnet = None
306 vpc_id = self.vpc_id
307 if self.vpc_data.get(vpc_id, None):
308 cidr_block = list(set(self.vpc_data[vpc_id]['subnets']) - set(self.get_network_details({'tenant_id': vpc_id}, detail='cidr_block')))[0]
309 else:
310 vpc = self.get_tenant_list({'id': vpc_id})[0]
311 subnet_list = self.subnet_sizes(len(self.get_availability_zones_list()), vpc['cidr_block'])
312 cidr_block = list(set(subnet_list) - set(self.get_network_details({'tenant_id': vpc['id']}, detail='cidr_block')))[0]
313 subnet = self.conn_vpc.create_subnet(vpc_id, cidr_block)
314 return subnet.id
315 except Exception as e:
316 self.format_vimconn_exception(e)
317
318 def get_network_details(self, filters, detail):
319 """Get specified details related to a subnet
320 """
321 detail_list = []
322 subnet_list = self.get_network_list(filters)
323 for net in subnet_list:
324 detail_list.append(net[detail])
325 return detail_list
326
327 def get_network_list(self, filter_dict={}):
328 """Obtain tenant networks of VIM
329 Params:
330 'filter_dict' (optional) contains entries to return only networks that matches ALL entries:
331 name: string => returns only networks with this name
332 id: string => returns networks with this VIM id, this imply returns one network at most
333 shared: boolean >= returns only networks that are (or are not) shared
334 tenant_id: sting => returns only networks that belong to this tenant/project
335 ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active
336 #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
337 Returns the network list of dictionaries. each dictionary contains:
338 'id': (mandatory) VIM network id
339 'name': (mandatory) VIM network name
340 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
341 'error_msg': (optional) text that explains the ERROR status
342 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
343 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
344 authorization, or some other unspecific error
345 """
346
347 self.logger.debug("Getting all subnets from VIM")
348 try:
349 self._reload_connection()
350 tfilters = {}
351 if filter_dict != {}:
352 if 'tenant_id' in filter_dict:
353 tfilters['vpcId'] = filter_dict['tenant_id']
354 subnets = self.conn_vpc.get_all_subnets(subnet_ids=filter_dict.get('name', None), filters=tfilters)
355 net_list = []
356 for net in subnets:
357 net_list.append(
358 {'id': str(net.id), 'name': str(net.id), 'status': str(net.state), 'vpc_id': str(net.vpc_id),
359 'cidr_block': str(net.cidr_block), 'type': 'bridge'})
360 return net_list
361 except Exception as e:
362 self.format_vimconn_exception(e)
363
364 def get_network(self, net_id):
365 """Obtain network details from the 'net_id' VIM network
366 Return a dict that contains:
367 'id': (mandatory) VIM network id, that is, net_id
368 'name': (mandatory) VIM network name
369 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
370 'error_msg': (optional) text that explains the ERROR status
371 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
372 Raises an exception upon error or when network is not found
373 """
374
375 self.logger.debug("Getting Subnet from VIM")
376 try:
377 self._reload_connection()
378 subnet = self.conn_vpc.get_all_subnets(net_id)[0]
379 return {'id': str(subnet.id), 'name': str(subnet.id), 'status': str(subnet.state),
380 'vpc_id': str(subnet.vpc_id), 'cidr_block': str(subnet.cidr_block)}
381 except Exception as e:
382 self.format_vimconn_exception(e)
383
384 def delete_network(self, net_id):
385 """Deletes a tenant network from VIM
386 Returns the network identifier or raises an exception upon error or when network is not found
387 """
388
389 self.logger.debug("Deleting subnet from VIM")
390 try:
391 self._reload_connection()
392 self.logger.debug("DELETING NET_ID: " + str(net_id))
393 self.conn_vpc.delete_subnet(net_id)
394 return net_id
395 except Exception as e:
396 self.format_vimconn_exception(e)
397
398 def refresh_nets_status(self, net_list):
399 """Get the status of the networks
400 Params:
401 'net_list': a list with the VIM network id to be get the status
402 Returns a dictionary with:
403 'net_id': #VIM id of this network
404 status: #Mandatory. Text with one of:
405 # DELETED (not found at vim)
406 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
407 # OTHER (Vim reported other status not understood)
408 # ERROR (VIM indicates an ERROR status)
409 # ACTIVE, INACTIVE, DOWN (admin down),
410 # BUILD (on building process)
411 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
412 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
413 'net_id2': ...
414 """
415
416 self._reload_connection()
417 try:
418 dict_entry = {}
419 for net_id in net_list:
420 subnet_dict = {}
421 subnet = None
422 try:
423 subnet = self.conn_vpc.get_all_subnets(net_id)[0]
424 if subnet.state == "pending":
425 subnet_dict['status'] = "BUILD"
426 elif subnet.state == "available":
427 subnet_dict['status'] = 'ACTIVE'
428 else:
429 subnet_dict['status'] = 'ERROR'
430 subnet_dict['error_msg'] = ''
431 except Exception as e:
432 subnet_dict['status'] = 'DELETED'
433 subnet_dict['error_msg'] = 'Network not found'
434 finally:
435 try:
436 subnet_dict['vim_info'] = yaml.safe_dump(subnet, default_flow_style=True, width=256)
437 except yaml.YAMLError as e:
438 subnet_dict['vim_info'] = str(subnet)
439 dict_entry[net_id] = subnet_dict
440 return dict_entry
441 except Exception as e:
442 self.format_vimconn_exception(e)
443
444 def get_flavor(self, flavor_id):
445 """Obtain flavor details from the VIM
446 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
447 Raises an exception upon error or if not found
448 """
449
450 self.logger.debug("Getting instance type")
451 try:
452 if flavor_id in self.flavor_info:
453 return self.flavor_info[flavor_id]
454 else:
455 raise vimconn.vimconnNotFoundException("Cannot find flavor with this flavor ID/Name")
456 except Exception as e:
457 self.format_vimconn_exception(e)
458
459 def get_flavor_id_from_data(self, flavor_dict):
460 """Obtain flavor id that match the flavor description
461 Params:
462 'flavor_dict': dictionary that contains:
463 'disk': main hard disk in GB
464 'ram': memory in MB
465 'vcpus': number of virtual cpus
466 #todo: complete parameters for EPA
467 Returns the flavor_id or raises a vimconnNotFoundException
468 """
469
470 self.logger.debug("Getting flavor id from data")
471 try:
472 flavor = None
473 for key, values in self.flavor_info.iteritems():
474 if (values["ram"], values["cpus"], values["disk"]) == (
475 flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"]):
476 flavor = (key, values)
477 break
478 elif (values["ram"], values["cpus"], values["disk"]) >= (
479 flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"]):
480 if not flavor:
481 flavor = (key, values)
482 else:
483 if (flavor[1]["ram"], flavor[1]["cpus"], flavor[1]["disk"]) >= (
484 values["ram"], values["cpus"], values["disk"]):
485 flavor = (key, values)
486 if flavor:
487 return flavor[0]
488 raise vimconn.vimconnNotFoundException("Cannot find flavor with this flavor ID/Name")
489 except Exception as e:
490 self.format_vimconn_exception(e)
491
492 def new_image(self, image_dict):
493 """ Adds a tenant image to VIM
494 Params: image_dict
495 name (string) - The name of the AMI. Valid only for EBS-based images.
496 description (string) - The description of the AMI.
497 image_location (string) - Full path to your AMI manifest in Amazon S3 storage. Only used for S3-based AMI’s.
498 architecture (string) - The architecture of the AMI. Valid choices are: * i386 * x86_64
499 kernel_id (string) - The ID of the kernel with which to launch the instances
500 root_device_name (string) - The root device name (e.g. /dev/sdh)
501 block_device_map (boto.ec2.blockdevicemapping.BlockDeviceMapping) - A BlockDeviceMapping data structure describing the EBS volumes associated with the Image.
502 virtualization_type (string) - The virutalization_type of the image. Valid choices are: * paravirtual * hvm
503 sriov_net_support (string) - Advanced networking support. Valid choices are: * simple
504 snapshot_id (string) - A snapshot ID for the snapshot to be used as root device for the image. Mutually exclusive with block_device_map, requires root_device_name
505 delete_root_volume_on_termination (bool) - Whether to delete the root volume of the image after instance termination. Only applies when creating image from snapshot_id. Defaults to False. Note that leaving volumes behind after instance termination is not free
506 Returns: image_id - image ID of the newly created image
507 """
508
509 try:
510 self._reload_connection()
511 image_location = image_dict.get('image_location', None)
512 if image_location:
513 image_location = str(self.account_id) + str(image_location)
514
515 image_id = self.conn.register_image(image_dict.get('name', None), image_dict.get('description', None),
516 image_location, image_dict.get('architecture', None),
517 image_dict.get('kernel_id', None),
518 image_dict.get('root_device_name', None),
519 image_dict.get('block_device_map', None),
520 image_dict.get('virtualization_type', None),
521 image_dict.get('sriov_net_support', None),
522 image_dict.get('snapshot_id', None),
523 image_dict.get('delete_root_volume_on_termination', None))
524 return image_id
525 except Exception as e:
526 self.format_vimconn_exception(e)
527
528 def delete_image(self, image_id):
529 """Deletes a tenant image from VIM
530 Returns the image_id if image is deleted or raises an exception on error"""
531
532 try:
533 self._reload_connection()
534 self.conn.deregister_image(image_id)
535 return image_id
536 except Exception as e:
537 self.format_vimconn_exception(e)
538
539 def get_image_id_from_path(self, path):
540 '''
541 Params: path - location of the image
542 Returns: image_id - ID of the matching image
543 '''
544 self._reload_connection()
545 try:
546 filters = {}
547 if path:
548 tokens = path.split('/')
549 filters['owner_id'] = tokens[0]
550 filters['name'] = '/'.join(tokens[1:])
551 image = self.conn.get_all_images(filters=filters)[0]
552 return image.id
553 except Exception as e:
554 self.format_vimconn_exception(e)
555
556 def get_image_list(self, filter_dict={}):
557 """Obtain tenant images from VIM
558 Filter_dict can be:
559 name: image name
560 id: image uuid
561 checksum: image checksum
562 location: image path
563 Returns the image list of dictionaries:
564 [{<the fields at Filter_dict plus some VIM specific>}, ...]
565 List can be empty
566 """
567
568 self.logger.debug("Getting image list from VIM")
569 try:
570 self._reload_connection()
571 image_id = None
572 filters = {}
573 if 'id' in filter_dict:
574 image_id = filter_dict['id']
575 if 'name' in filter_dict:
576 filters['name'] = filter_dict['name']
577 if 'location' in filter_dict:
578 filters['location'] = filter_dict['location']
579 # filters['image_type'] = 'machine'
580 # filter_dict['owner_id'] = self.account_id
581 images = self.conn.get_all_images(image_id, filters=filters)
582 image_list = []
583 for image in images:
584 image_list.append({'id': str(image.id), 'name': str(image.name), 'status': str(image.state),
585 'owner': str(image.owner_id), 'location': str(image.location),
586 'is_public': str(image.is_public), 'architecture': str(image.architecture),
587 'platform': str(image.platform)})
588 return image_list
589 except Exception as e:
590 self.format_vimconn_exception(e)
591
592 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None,
593 disk_list=None, availability_zone_index=None, availability_zone_list=None):
594 """Create a new VM/instance in AWS
595 Params: name
596 decription
597 start: (boolean) indicates if VM must start or created in pause mode.
598 image_id - image ID in AWS
599 flavor_id - instance type ID in AWS
600 net_list
601 name
602 net_id - subnet_id from AWS
603 vpci - (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM capabilities
604 model: (optional and only have sense for type==virtual) interface model: virtio, e2000, ...
605 mac_address: (optional) mac address to assign to this interface
606 type: (mandatory) can be one of:
607 virtual, in this case always connected to a network of type 'net_type=bridge'
608 PF - (passthrough): depending on VIM capabilities it can be connected to a data/ptp network ot it
609 can created unconnected
610 VF - (SRIOV with VLAN tag): same as PF for network connectivity.
611 VFnotShared - (SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs
612 are allocated on the same physical NIC
613 bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
614 port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing or True, it must apply the default VIM behaviour
615 vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this interface. 'net_list' is modified
616 elastic_ip - True/False to define if an elastic_ip is required
617 cloud_config': (optional) dictionary with:
618 key-pairs': (optional) list of strings with the public key to be inserted to the default user
619 users': (optional) list of users to be inserted, each item is a dict with:
620 name': (mandatory) user name,
621 key-pairs': (optional) list of strings with the public key to be inserted to the user
622 user-data': (optional) string is a text script to be passed directly to cloud-init
623 config-files': (optional). List of files to be transferred. Each item is a dict with:
624 dest': (mandatory) string with the destination absolute path
625 encoding': (optional, by default text). Can be one of:
626 b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
627 content' (mandatory): string with the content of the file
628 permissions': (optional) string with file permissions, typically octal notation '0644'
629 owner: (optional) file owner, string with the format 'owner:group'
630 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
631 security-groups:
632 subnet_id
633 security_group_id
634 disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
635 image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
636 size': (mandatory) string with the size of the disk in GB
637 Returns: instance identifier or raises an exception on error
638 """
639
640 self.logger.debug("Creating a new VM instance")
641 try:
642 self._reload_connection()
643 instance = None
644 userdata = None
645 if isinstance(cloud_config, dict):
646 if cloud_config.get("user-data"):
647 userdata = cloud_config["user-data"]
648 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
649 if userdata:
650 raise vimconn.vimconnConflictException(
651 "Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
652 userdata_dict = {}
653 # default user
654 if cloud_config.get("key-pairs"):
655 userdata_dict["ssh-authorized-keys"] = cloud_config["key-pairs"]
656 userdata_dict["users"] = [{"default": None, "ssh-authorized-keys": cloud_config["key-pairs"]}]
657 if cloud_config.get("users"):
658 if "users" not in userdata_dict:
659 userdata_dict["users"] = ["default"]
660 for user in cloud_config["users"]:
661 user_info = {
662 "name": user["name"],
663 "sudo": "ALL = (ALL)NOPASSWD:ALL"
664 }
665 if "user-info" in user:
666 user_info["gecos"] = user["user-info"]
667 if user.get("key-pairs"):
668 user_info["ssh-authorized-keys"] = user["key-pairs"]
669 userdata_dict["users"].append(user_info)
670
671 if cloud_config.get("config-files"):
672 userdata_dict["write_files"] = []
673 for file in cloud_config["config-files"]:
674 file_info = {
675 "path": file["dest"],
676 "content": file["content"]
677 }
678 if file.get("encoding"):
679 file_info["encoding"] = file["encoding"]
680 if file.get("permissions"):
681 file_info["permissions"] = file["permissions"]
682 if file.get("owner"):
683 file_info["owner"] = file["owner"]
684 userdata_dict["write_files"].append(file_info)
685 userdata = "#cloud-config\n"
686 userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
687 self.logger.debug("userdata: %s", userdata)
688 elif isinstance(cloud_config, str):
689 userdata = cloud_config
690
691 if not net_list:
692 reservation = self.conn.run_instances(
693 image_id,
694 key_name=self.key_pair,
695 instance_type=flavor_id,
696 security_groups=self.security_groups,
697 user_data=userdata
698 )
699 instance = reservation.instances[0]
700 else:
701 net_list = [net_list[0]]
702 for index, subnet in enumerate(net_list):
703 net_intr = boto.ec2.networkinterface.NetworkInterfaceSpecification(subnet_id=subnet.get('net_id'),
704 groups=None,
705 associate_public_ip_address=True)
706
707 if subnet.get('elastic_ip'):
708 eip = self.conn.allocate_address()
709 self.conn.associate_address(allocation_id=eip.allocation_id, network_interface_id=net_intr.id)
710
711 if index == 0:
712 reservation = self.conn.run_instances(
713 image_id,
714 key_name=self.key_pair,
715 instance_type=flavor_id,
716 security_groups=self.security_groups,
717 network_interfaces=boto.ec2.networkinterface.NetworkInterfaceCollection(net_intr),
718 user_data=userdata
719 )
720 instance = reservation.instances[0]
721 else:
722 while True:
723 try:
724 self.conn.attach_network_interface(
725 network_interface_id=boto.ec2.networkinterface.NetworkInterfaceCollection(net_intr),
726 instance_id=instance.id, device_index=0)
727 break
728 except:
729 time.sleep(10)
730 return instance.id
731 except Exception as e:
732 self.format_vimconn_exception(e)
733
734 def get_vminstance(self, vm_id):
735 """Returns the VM instance information from VIM"""
736
737 try:
738 self._reload_connection()
739 reservation = self.conn.get_all_instances(vm_id)
740 return reservation[0].instances[0].__dict__
741 except Exception as e:
742 self.format_vimconn_exception(e)
743
744 def delete_vminstance(self, vm_id):
745 """Removes a VM instance from VIM
746 Returns the instance identifier"""
747
748 try:
749 self._reload_connection()
750 self.logger.debug("DELETING VM_ID: " + str(vm_id))
751 self.conn.terminate_instances(vm_id)
752 return vm_id
753 except Exception as e:
754 self.format_vimconn_exception(e)
755
756 def refresh_vms_status(self, vm_list):
757 """ Get the status of the virtual machines and their interfaces/ports
758 Params: the list of VM identifiers
759 Returns a dictionary with:
760 vm_id: #VIM id of this Virtual Machine
761 status: #Mandatory. Text with one of:
762 # DELETED (not found at vim)
763 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
764 # OTHER (Vim reported other status not understood)
765 # ERROR (VIM indicates an ERROR status)
766 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
767 # BUILD (on building process), ERROR
768 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
769 #
770 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
771 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
772 interfaces: list with interface info. Each item a dictionary with:
773 vim_interface_id - The ID of the ENI.
774 vim_net_id - The ID of the VPC subnet.
775 mac_address - The MAC address of the interface.
776 ip_address - The IP address of the interface within the subnet.
777 """
778 self.logger.debug("Getting VM instance information from VIM")
779 try:
780 self._reload_connection()
781 reservation = self.conn.get_all_instances(vm_list)[0]
782 instances = {}
783 instance_dict = {}
784 for instance in reservation.instances:
785 try:
786 if instance.state in ("pending"):
787 instance_dict['status'] = "BUILD"
788 elif instance.state in ("available", "running", "up"):
789 instance_dict['status'] = 'ACTIVE'
790 else:
791 instance_dict['status'] = 'ERROR'
792 instance_dict['error_msg'] = ""
793 instance_dict['interfaces'] = []
794 interface_dict = {}
795 for interface in instance.interfaces:
796 interface_dict['vim_interface_id'] = interface.id
797 interface_dict['vim_net_id'] = interface.subnet_id
798 interface_dict['mac_address'] = interface.mac_address
799 if hasattr(interface, 'publicIp') and interface.publicIp != None:
800 interface_dict['ip_address'] = interface.publicIp + ";" + interface.private_ip_address
801 else:
802 interface_dict['ip_address'] = interface.private_ip_address
803 instance_dict['interfaces'].append(interface_dict)
804 except Exception as e:
805 self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
806 instance_dict['status'] = "DELETED"
807 instance_dict['error_msg'] = str(e)
808 finally:
809 try:
810 instance_dict['vim_info'] = yaml.safe_dump(instance, default_flow_style=True, width=256)
811 except yaml.YAMLError as e:
812 # self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
813 instance_dict['vim_info'] = str(instance)
814 instances[instance.id] = instance_dict
815 return instances
816 except Exception as e:
817 self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
818 self.format_vimconn_exception(e)
819
820 def action_vminstance(self, vm_id, action_dict):
821 """Send and action over a VM instance from VIM
822 Returns the vm_id if the action was successfully sent to the VIM"""
823
824 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
825 try:
826 self._reload_connection()
827 if "start" in action_dict:
828 self.conn.start_instances(vm_id)
829 elif "stop" in action_dict or "stop" in action_dict:
830 self.conn.stop_instances(vm_id)
831 elif "terminate" in action_dict:
832 self.conn.terminate_instances(vm_id)
833 elif "reboot" in action_dict:
834 self.conn.reboot_instances(vm_id)
835 return vm_id
836 except Exception as e:
837 self.format_vimconn_exception(e)