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