1 # -*- coding: utf-8 -*-
4 # Copyright 2017 xFlow Research Pvt. Ltd
5 # This file is part of openmano
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: saboor.ahmad@xflowresearch.com
25 AWS-connector implements all the methods to interact with AWS using the BOTO client
28 __author__
= "Saboor Ahmad"
29 __date__
= "10-Apr-2017"
42 exit("Boto not avialable. Try activating your virtualenv OR `pip install boto`")
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
65 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
66 config
, persistent_info
)
68 self
.persistent_info
= persistent_info
71 self
.a_creds
['aws_access_key_id'] = user
73 raise vimconn
.vimconnAuthException("Username is not specified")
75 self
.a_creds
['aws_secret_access_key'] = passwd
77 raise vimconn
.vimconnAuthException("Password is not specified")
78 if 'region_name' in config
:
79 self
.region
= config
.get('region_name')
81 raise vimconn
.vimconnNotFoundException("AWS region_name is not specified at config")
87 self
.account_id
= None
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'
95 self
.vpc_id
= tenant_id
96 if 'vpc_cidr_block' in config
:
97 self
.vpc_cidr_block
= config
['vpc_cidr_block']
99 self
.security_groups
= None
100 if 'security_groups' in config
:
101 self
.security_groups
= config
['security_groups']
104 if 'key_pair' in config
:
105 self
.key_pair
= config
['key_pair']
107 self
.flavor_info
= None
108 if 'flavor_info' in config
:
109 flavor_data
= config
.get('flavor_info')
110 if isinstance(flavor_data
, str):
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
))
118 raise vimconn
.vimconnException("Error reading file '{}': {}".format(flavor_data
[1:], e
))
119 elif isinstance(flavor_data
, dict):
120 self
.flavor_info
= flavor_data
122 self
.logger
= logging
.getLogger('openmano.vim.aws')
124 self
.logger
.setLevel(getattr(logging
, log_level
))
126 def __setitem__(self
, index
, value
):
127 """Params: index - name of value of set
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':
137 vimconn
.vimconnector
.__setitem
__(self
, index
, value
)
139 def _reload_connection(self
):
140 """Returns: sets boto.EC2 and boto.VPC connection to work with AWS services
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
)
153 def format_vimconn_exception(self
, e
):
154 """Params: an Exception object
155 Returns: Raises the exception 'e' passed in mehtod parameters
160 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ": " + str(e
))
162 def get_availability_zones_list(self
):
163 """Obtain AvailabilityZones from AWS
167 self
._reload
_connection
()
169 for az
in self
.conn
.get_all_zones():
170 az_list
.append(az
.name
)
172 except Exception as e
:
173 self
.format_vimconn_exception(e
)
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
181 Returns the tenant list of dictionaries, and empty list if no tenant match all the filers:
182 [{'name':'<name>, 'id':'<id>, ...}, ...]
186 self
._reload
_connection
()
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
)
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
)})
199 except Exception as e
:
200 self
.format_vimconn_exception(e
)
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
209 self
.logger
.debug("Adding a new VPC")
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)
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)
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
)}
225 except Exception as e
:
226 self
.format_vimconn_exception(e
)
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
234 self
.logger
.debug("Deleting specified VPC")
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')
244 self
.conn_vpc
.delete_vpc(tenant_id
)
245 except Exception as e
:
246 self
.format_vimconn_exception(e
)
248 def subnet_sizes(self
, availability_zones
, cidr
):
249 """Calcualtes possible subnets given CIDR value of VPC
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")
256 netmasks
= ('255.255.252.0', '255.255.254.0', '255.255.255.0', '255.255.255.128')
257 ip
= netaddr
.IPNetwork(cidr
)
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")
264 if availability_zones
== 2:
265 for n
, netmask
in enumerate(netmasks
):
266 if str(mask
) == netmask
:
267 subnets
= list(ip
.subnet(n
+ 24))
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
278 return map(str, subnets
)
280 def new_network(self
, net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
281 """Adds a tenant network to VIM
283 'net_name': name of the network
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
302 self
.logger
.debug("Adding a subnet to VPC")
304 self
._reload
_connection
()
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]
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
)
315 except Exception as e
:
316 self
.format_vimconn_exception(e
)
318 def get_network_details(self
, filters
, detail
):
319 """Get specified details related to a subnet
322 subnet_list
= self
.get_network_list(filters
)
323 for net
in subnet_list
:
324 detail_list
.append(net
[detail
])
327 def get_network_list(self
, filter_dict
={}):
328 """Obtain tenant networks of VIM
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
347 self
.logger
.debug("Getting all subnets from VIM")
349 self
._reload
_connection
()
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
)
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'})
361 except Exception as e
:
362 self
.format_vimconn_exception(e
)
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
375 self
.logger
.debug("Getting Subnet from VIM")
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
)
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
389 self
.logger
.debug("Deleting subnet from VIM")
391 self
._reload
_connection
()
392 self
.logger
.debug("DELETING NET_ID: " + str(net_id
))
393 self
.conn_vpc
.delete_subnet(net_id
)
395 except Exception as e
:
396 self
.format_vimconn_exception(e
)
398 def refresh_nets_status(self
, net_list
):
399 """Get the status of the networks
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)
416 self
._reload
_connection
()
419 for net_id
in net_list
:
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'
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'
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
441 except Exception as e
:
442 self
.format_vimconn_exception(e
)
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
450 self
.logger
.debug("Getting instance type")
452 if flavor_id
in self
.flavor_info
:
453 return self
.flavor_info
[flavor_id
]
455 raise vimconn
.vimconnNotFoundException("Cannot find flavor with this flavor ID/Name")
456 except Exception as e
:
457 self
.format_vimconn_exception(e
)
459 def get_flavor_id_from_data(self
, flavor_dict
):
460 """Obtain flavor id that match the flavor description
462 'flavor_dict': dictionary that contains:
463 'disk': main hard disk in GB
465 'vcpus': number of virtual cpus
466 #todo: complete parameters for EPA
467 Returns the flavor_id or raises a vimconnNotFoundException
470 self
.logger
.debug("Getting flavor id from data")
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
)
478 elif (values
["ram"], values
["cpus"], values
["disk"]) >= (
479 flavor_dict
["ram"], flavor_dict
["vcpus"], flavor_dict
["disk"]):
481 flavor
= (key
, values
)
483 if (flavor
[1]["ram"], flavor
[1]["cpus"], flavor
[1]["disk"]) >= (
484 values
["ram"], values
["cpus"], values
["disk"]):
485 flavor
= (key
, values
)
488 raise vimconn
.vimconnNotFoundException("Cannot find flavor with this flavor ID/Name")
489 except Exception as e
:
490 self
.format_vimconn_exception(e
)
492 def new_image(self
, image_dict
):
493 """ Adds a tenant image to VIM
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
510 self
._reload
_connection
()
511 image_location
= image_dict
.get('image_location', None)
513 image_location
= str(self
.account_id
) + str(image_location
)
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))
525 except Exception as e
:
526 self
.format_vimconn_exception(e
)
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"""
533 self
._reload
_connection
()
534 self
.conn
.deregister_image(image_id
)
536 except Exception as e
:
537 self
.format_vimconn_exception(e
)
539 def get_image_id_from_path(self
, path
):
541 Params: path - location of the image
542 Returns: image_id - ID of the matching image
544 self
._reload
_connection
()
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]
553 except Exception as e
:
554 self
.format_vimconn_exception(e
)
556 def get_image_list(self
, filter_dict
={}):
557 """Obtain tenant images from VIM
561 checksum: image checksum
563 Returns the image list of dictionaries:
564 [{<the fields at Filter_dict plus some VIM specific>}, ...]
568 self
.logger
.debug("Getting image list from VIM")
570 self
._reload
_connection
()
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
)
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
)})
589 except Exception as e
:
590 self
.format_vimconn_exception(e
)
592 def new_vminstance(self
, name
, description
, start
, image_id
, flavor_id
, net_list
, cloud_config
=None,
594 """Create a new VM/instance in AWS
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
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)
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
640 self
.logger
.debug("Creating a new VM instance")
642 self
._reload
_connection
()
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"):
650 raise vimconn
.vimconnConflictException(
651 "Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
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"]:
662 "name": user
["name"],
663 "sudo": "ALL = (ALL)NOPASSWD:ALL"
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
)
671 if cloud_config
.get("config-files"):
672 userdata_dict
["write_files"] = []
673 for file in cloud_config
["config-files"]:
675 "path": file["dest"],
676 "content": file["content"]
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
692 reservation
= self
.conn
.run_instances(
694 key_name
=self
.key_pair
,
695 instance_type
=flavor_id
,
696 security_groups
=self
.security_groups
,
699 instance
= reservation
.instances
[0]
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'),
705 associate_public_ip_address
=True)
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)
712 reservation
= self
.conn
.run_instances(
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
),
720 instance
= reservation
.instances
[0]
724 self
.conn
.attach_network_interface(
725 network_interface_id
=boto
.ec2
.networkinterface
.NetworkInterfaceCollection(net_intr
),
726 instance_id
=instance
.id, device_index
=0)
731 except Exception as e
:
732 self
.format_vimconn_exception(e
)
734 def get_vminstance(self
, vm_id
):
735 """Returns the VM instance information from VIM"""
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
)
744 def delete_vminstance(self
, vm_id
):
745 """Removes a VM instance from VIM
746 Returns the instance identifier"""
749 self
._reload
_connection
()
750 self
.logger
.debug("DELETING VM_ID: " + str(vm_id
))
751 self
.conn
.terminate_instances(vm_id
)
753 except Exception as e
:
754 self
.format_vimconn_exception(e
)
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
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.
778 self
.logger
.debug("Getting VM instance information from VIM")
780 self
._reload
_connection
()
781 reservation
= self
.conn
.get_all_instances(vm_list
)[0]
784 for instance
in reservation
.instances
:
786 if instance
.state
in ("pending"):
787 instance_dict
['status'] = "BUILD"
788 elif instance
.state
in ("available", "running", "up"):
789 instance_dict
['status'] = 'ACTIVE'
791 instance_dict
['status'] = 'ERROR'
792 instance_dict
['error_msg'] = ""
793 instance_dict
['interfaces'] = []
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
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
)
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
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
)
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"""
824 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
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
)
836 except Exception as e
:
837 self
.format_vimconn_exception(e
)