bug 425 fix SR-IOV PCI-PASSTHROUGH interfaces
[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 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a data/ptp network ot it
609 can created unconnected
610 'SR-IOV' or '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 a tuple with the instance identifier and created_items or raises an exception on error
638 created_items can be None or a dictionary where this method can include key-values that will be passed to
639 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
640 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
641 as not present.
642 """
643
644 self.logger.debug("Creating a new VM instance")
645 try:
646 self._reload_connection()
647 instance = None
648 _, userdata = self._create_user_data(cloud_config)
649
650 if not net_list:
651 reservation = self.conn.run_instances(
652 image_id,
653 key_name=self.key_pair,
654 instance_type=flavor_id,
655 security_groups=self.security_groups,
656 user_data=userdata
657 )
658 else:
659 for index, subnet in enumerate(net_list):
660 net_intr = boto.ec2.networkinterface.NetworkInterfaceSpecification(subnet_id=subnet.get('net_id'),
661 groups=None,
662 associate_public_ip_address=True)
663
664 if subnet.get('elastic_ip'):
665 eip = self.conn.allocate_address()
666 self.conn.associate_address(allocation_id=eip.allocation_id, network_interface_id=net_intr.id)
667
668 if index == 0:
669 reservation = self.conn.run_instances(
670 image_id,
671 key_name=self.key_pair,
672 instance_type=flavor_id,
673 security_groups=self.security_groups,
674 network_interfaces=boto.ec2.networkinterface.NetworkInterfaceCollection(net_intr),
675 user_data=userdata
676 )
677 else:
678 while True:
679 try:
680 self.conn.attach_network_interface(
681 network_interface_id=boto.ec2.networkinterface.NetworkInterfaceCollection(net_intr),
682 instance_id=instance.id, device_index=0)
683 break
684 except:
685 time.sleep(10)
686 net_list[index]['vim_id'] = reservation.instances[0].interfaces[index].id
687
688 instance = reservation.instances[0]
689 return instance.id, None
690 except Exception as e:
691 self.format_vimconn_exception(e)
692
693 def get_vminstance(self, vm_id):
694 """Returns the VM instance information from VIM"""
695
696 try:
697 self._reload_connection()
698 reservation = self.conn.get_all_instances(vm_id)
699 return reservation[0].instances[0].__dict__
700 except Exception as e:
701 self.format_vimconn_exception(e)
702
703 def delete_vminstance(self, vm_id, created_items=None):
704 """Removes a VM instance from VIM
705 Returns the instance identifier"""
706
707 try:
708 self._reload_connection()
709 self.logger.debug("DELETING VM_ID: " + str(vm_id))
710 self.conn.terminate_instances(vm_id)
711 return vm_id
712 except Exception as e:
713 self.format_vimconn_exception(e)
714
715 def refresh_vms_status(self, vm_list):
716 """ Get the status of the virtual machines and their interfaces/ports
717 Params: the list of VM identifiers
718 Returns a dictionary with:
719 vm_id: #VIM id of this Virtual Machine
720 status: #Mandatory. Text with one of:
721 # DELETED (not found at vim)
722 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
723 # OTHER (Vim reported other status not understood)
724 # ERROR (VIM indicates an ERROR status)
725 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
726 # BUILD (on building process), ERROR
727 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
728 #
729 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
730 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
731 interfaces: list with interface info. Each item a dictionary with:
732 vim_interface_id - The ID of the ENI.
733 vim_net_id - The ID of the VPC subnet.
734 mac_address - The MAC address of the interface.
735 ip_address - The IP address of the interface within the subnet.
736 """
737 self.logger.debug("Getting VM instance information from VIM")
738 try:
739 self._reload_connection()
740 reservation = self.conn.get_all_instances(vm_list)[0]
741 instances = {}
742 instance_dict = {}
743 for instance in reservation.instances:
744 try:
745 if instance.state in ("pending"):
746 instance_dict['status'] = "BUILD"
747 elif instance.state in ("available", "running", "up"):
748 instance_dict['status'] = 'ACTIVE'
749 else:
750 instance_dict['status'] = 'ERROR'
751 instance_dict['error_msg'] = ""
752 instance_dict['interfaces'] = []
753 interface_dict = {}
754 for interface in instance.interfaces:
755 interface_dict['vim_interface_id'] = interface.id
756 interface_dict['vim_net_id'] = interface.subnet_id
757 interface_dict['mac_address'] = interface.mac_address
758 if hasattr(interface, 'publicIp') and interface.publicIp != None:
759 interface_dict['ip_address'] = interface.publicIp + ";" + interface.private_ip_address
760 else:
761 interface_dict['ip_address'] = interface.private_ip_address
762 instance_dict['interfaces'].append(interface_dict)
763 except Exception as e:
764 self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
765 instance_dict['status'] = "DELETED"
766 instance_dict['error_msg'] = str(e)
767 finally:
768 try:
769 instance_dict['vim_info'] = yaml.safe_dump(instance, default_flow_style=True, width=256)
770 except yaml.YAMLError as e:
771 # self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
772 instance_dict['vim_info'] = str(instance)
773 instances[instance.id] = instance_dict
774 return instances
775 except Exception as e:
776 self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
777 self.format_vimconn_exception(e)
778
779 def action_vminstance(self, vm_id, action_dict, created_items={}):
780 """Send and action over a VM instance from VIM
781 Returns the vm_id if the action was successfully sent to the VIM"""
782
783 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
784 try:
785 self._reload_connection()
786 if "start" in action_dict:
787 self.conn.start_instances(vm_id)
788 elif "stop" in action_dict or "stop" in action_dict:
789 self.conn.stop_instances(vm_id)
790 elif "terminate" in action_dict:
791 self.conn.terminate_instances(vm_id)
792 elif "reboot" in action_dict:
793 self.conn.reboot_instances(vm_id)
794 return None
795 except Exception as e:
796 self.format_vimconn_exception(e)