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
34 from boto
.exception
import BotoServerError
, EC2ResponseError
36 from ipconflict
import check_conflicts
38 from osm_ro_plugin
import vimconn
41 __author__
= "Saboor Ahmad"
42 __date__
= "10-Apr-2017"
45 class vimconnector(vimconn
.VimConnector
):
61 uuid - id asigned to this VIM
62 name - name assigned to this VIM, can be used for logging
63 tenant_id - ID to be used for tenant
64 tenant_name - name of tenant to be used VIM tenant to be used
65 url_admin - optional, url used for administrative tasks
66 user - credentials of the VIM user
67 passwd - credentials of the VIM user
68 log_level - if must use a different log_level than the general one
69 config - dictionary with misc VIM information
70 region_name - name of region to deploy the instances
71 vpc_cidr_block - default CIDR block for VPC
72 security_groups - default security group to specify this instance
73 persistent_info - dict where the class can store information that will be available among class
74 destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an
75 empty dict. Useful to store login/tokens information for speed up communication
77 vimconn
.VimConnector
.__init
__(
92 self
.persistent_info
= persistent_info
96 self
.a_creds
["aws_access_key_id"] = user
98 raise vimconn
.VimConnAuthException("Username is not specified")
101 self
.a_creds
["aws_secret_access_key"] = passwd
103 raise vimconn
.VimConnAuthException("Password is not specified")
105 if "region_name" in config
:
106 self
.region
= config
.get("region_name")
108 raise vimconn
.VimConnException("AWS region_name is not specified at config")
111 self
.subnet_data
= {}
114 self
.account_id
= None
115 self
.network_delete_on_termination
= []
116 self
.server_timeout
= 180
118 self
.vpc_id
= self
.get_tenant_list()[0]["id"]
119 # we take VPC CIDR block if specified, otherwise we use the default CIDR
120 # block suggested by AWS while creating instance
121 self
.vpc_cidr_block
= "10.0.0.0/24"
124 self
.vpc_id
= tenant_name
126 if "vpc_cidr_block" in config
:
127 self
.vpc_cidr_block
= config
["vpc_cidr_block"]
129 self
.security_groups
= None
130 if "security_groups" in config
:
131 self
.security_groups
= config
["security_groups"]
134 if "key_pair" in config
:
135 self
.key_pair
= config
["key_pair"]
137 self
.flavor_info
= None
138 if "flavor_info" in config
:
139 flavor_data
= config
.get("flavor_info")
140 if isinstance(flavor_data
, str):
142 if flavor_data
[0] == "@": # read from a file
143 with
open(flavor_data
[1:], "r") as stream
:
144 self
.flavor_info
= yaml
.safe_load(stream
)
146 self
.flavor_info
= yaml
.safe_load(flavor_data
)
147 except yaml
.YAMLError
as e
:
148 self
.flavor_info
= None
150 raise vimconn
.VimConnException(
151 "Bad format at file '{}': {}".format(flavor_data
[1:], e
)
154 raise vimconn
.VimConnException(
155 "Error reading file '{}': {}".format(flavor_data
[1:], e
)
157 elif isinstance(flavor_data
, dict):
158 self
.flavor_info
= flavor_data
160 self
.logger
= logging
.getLogger("ro.vim.aws")
163 self
.logger
.setLevel(getattr(logging
, log_level
))
165 def __setitem__(self
, index
, value
):
167 index - name of value of set
171 self
.a_creds
["aws_access_key_id"] = value
172 elif index
== "passwd":
173 self
.a_creds
["aws_secret_access_key"] = value
174 elif index
== "region":
177 vimconn
.VimConnector
.__setitem
__(self
, index
, value
)
179 def _reload_connection(self
):
180 """Returns: sets boto.EC2 and boto.VPC connection to work with AWS services"""
182 self
.conn
= boto
.ec2
.connect_to_region(
184 aws_access_key_id
=self
.a_creds
["aws_access_key_id"],
185 aws_secret_access_key
=self
.a_creds
["aws_secret_access_key"],
187 self
.conn_vpc
= boto
.vpc
.connect_to_region(
189 aws_access_key_id
=self
.a_creds
["aws_access_key_id"],
190 aws_secret_access_key
=self
.a_creds
["aws_secret_access_key"],
192 # client = boto3.client("sts", aws_access_key_id=self.a_creds['aws_access_key_id'],
193 # aws_secret_access_key=self.a_creds['aws_secret_access_key'])
194 # self.account_id = client.get_caller_identity()["Account"]
195 except Exception as e
:
196 self
.format_vimconn_exception(e
)
198 def format_vimconn_exception(self
, e
):
199 """Params: an Exception object
200 Returns: Raises the exception 'e' passed in mehtod parameters
205 raise vimconn
.VimConnConnectionException(type(e
).__name
__ + ": " + str(e
))
207 def get_tenant_list(self
, filter_dict
={}):
208 """Obtain tenants of VIM
209 filter_dict dictionary that can contain the following keys:
210 name: filter by tenant name
211 id: filter by tenant uuid/id
213 Returns the tenant list of dictionaries, and empty list if no tenant match all the filers:
214 [{'name':'<name>, 'id':'<id>, ...}, ...]
217 self
._reload
_connection
()
220 if filter_dict
!= {}:
221 if "id" in filter_dict
:
222 vpc_ids
.append(filter_dict
["id"])
224 tenants
= self
.conn_vpc
.get_all_vpcs(vpc_ids
, None)
227 for tenant
in tenants
:
230 "id": str(tenant
.id),
231 "name": str(tenant
.id),
232 "status": str(tenant
.state
),
233 "cidr_block": str(tenant
.cidr_block
),
238 except Exception as e
:
239 self
.format_vimconn_exception(e
)
241 def new_tenant(self
, tenant_name
, tenant_description
):
242 """Adds a new tenant to VIM with this name and description, this is done using admin_url if provided
243 "tenant_name": string max lenght 64
244 "tenant_description": string max length 256
245 returns the tenant identifier or raise exception
247 self
.logger
.debug("Adding a new VPC")
250 self
._reload
_connection
()
251 vpc
= self
.conn_vpc
.create_vpc(self
.vpc_cidr_block
)
252 self
.conn_vpc
.modify_vpc_attribute(vpc
.id, enable_dns_support
=True)
253 self
.conn_vpc
.modify_vpc_attribute(vpc
.id, enable_dns_hostnames
=True)
255 gateway
= self
.conn_vpc
.create_internet_gateway()
256 self
.conn_vpc
.attach_internet_gateway(gateway
.id, vpc
.id)
257 route_table
= self
.conn_vpc
.create_route_table(vpc
.id)
258 self
.conn_vpc
.create_route(route_table
.id, "0.0.0.0/0", gateway
.id)
260 self
.vpc_data
[vpc
.id] = {
261 "gateway": gateway
.id,
262 "route_table": route_table
.id,
263 "subnets": self
.subnet_sizes(self
.vpc_cidr_block
),
267 except Exception as e
:
268 self
.format_vimconn_exception(e
)
270 def delete_tenant(self
, tenant_id
):
271 """Delete a tenant from VIM
272 tenant_id: returned VIM tenant_id on "new_tenant"
273 Returns None on success. Raises and exception of failure. If tenant is not found raises vimconnNotFoundException
275 self
.logger
.debug("Deleting specified VPC")
278 self
._reload
_connection
()
279 vpc
= self
.vpc_data
.get(tenant_id
)
281 if "gateway" in vpc
and "route_table" in vpc
:
282 gateway_id
, route_table_id
= vpc
["gateway"], vpc
["route_table"]
283 self
.conn_vpc
.detach_internet_gateway(gateway_id
, tenant_id
)
284 self
.conn_vpc
.delete_vpc(tenant_id
)
285 self
.conn_vpc
.delete_route(route_table_id
, "0.0.0.0/0")
287 self
.conn_vpc
.delete_vpc(tenant_id
)
288 except Exception as e
:
289 self
.format_vimconn_exception(e
)
291 def subnet_sizes(self
, cidr
):
292 """Calculates possible subnets given CIDR value of VPC"""
302 ip
= netaddr
.IPNetwork(cidr
)
306 for netmask
in netmasks
:
307 if str(mask
) == netmask
:
308 pub_split
= list(ip
.subnet(24))
311 subnets
= pub_split
if pub_split
else (list(ip
.subnet(28)))
313 return map(str, subnets
)
321 provider_network_profile
=None,
323 """Adds a tenant network to VIM
325 'net_name': name of the network
327 'bridge': overlay isolated network
328 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
329 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
330 'ip_profile': is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
331 'ip-version': can be one of ["IPv4","IPv6"]
332 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
333 'gateway-address': (Optional) ip_schema, that is X.X.X.X
334 'dns-address': (Optional) ip_schema,
335 'dhcp': (Optional) dict containing
336 'enabled': {"type": "boolean"},
337 'start-address': ip_schema, first IP to grant
338 'count': number of IPs to grant.
339 'shared': if this network can be seen/use by other tenants/organization
340 Returns a tuple with the network identifier and created_items, or raises an exception on error
341 created_items can be None or a dictionary where this method can include key-values that will be passed to
342 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
343 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
346 self
.logger
.debug("Adding a subnet to VPC")
350 self
._reload
_connection
()
353 if self
.conn_vpc
.get_all_subnets():
354 existing_subnet
= self
.conn_vpc
.get_all_subnets()[0]
355 if not self
.availability_zone
:
356 self
.availability_zone
= str(existing_subnet
.availability_zone
)
358 if self
.vpc_data
.get(vpc_id
, None):
360 set(self
.vpc_data
[vpc_id
]["subnets"])
362 self
.get_network_details(
363 {"tenant_id": vpc_id
}, detail
="cidr_block"
368 vpc
= self
.get_tenant_list({"id": vpc_id
})[0]
369 subnet_list
= self
.subnet_sizes(vpc
["cidr_block"])
373 self
.get_network_details(
374 {"tenant_id": vpc
["id"]}, detail
="cidr_block"
380 selected_cidr_block
= random
.choice(cidr_block
)
384 subnet
.cidr_block
for subnet
in self
.conn_vpc
.get_all_subnets()
386 all_subnets
.append(selected_cidr_block
)
387 conflict
= check_conflicts(all_subnets
)
389 subnet
= self
.conn_vpc
.create_subnet(
390 vpc_id
, selected_cidr_block
, self
.availability_zone
394 selected_cidr_block
= random
.choice(cidr_block
)
396 raise vimconn
.VimConnException(
397 "Failed to find a proper CIDR which does not overlap"
398 "with existing subnets",
399 http_code
=vimconn
.HTTP_Request_Timeout
,
402 except (EC2ResponseError
, BotoServerError
) as error
:
403 self
.format_vimconn_exception(error
)
405 created_items
["net:" + str(subnet
.id)] = True
407 return subnet
.id, created_items
408 except Exception as e
:
409 self
.format_vimconn_exception(e
)
411 def get_network_details(self
, filters
, detail
):
412 """Get specified details related to a subnet"""
414 subnet_list
= self
.get_network_list(filters
)
416 for net
in subnet_list
:
417 detail_list
.append(net
[detail
])
421 def get_network_list(self
, filter_dict
={}):
422 """Obtain tenant networks of VIM
424 'filter_dict' (optional) contains entries to return only networks that matches ALL entries:
425 name: string => returns only networks with this name
426 id: string => returns networks with this VIM id, this imply returns one network at most
427 shared: boolean >= returns only networks that are (or are not) shared
428 tenant_id: sting => returns only networks that belong to this tenant/project
429 ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin
431 #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
432 Returns the network list of dictionaries. each dictionary contains:
433 'id': (mandatory) VIM network id
434 'name': (mandatory) VIM network name
435 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
436 'error_msg': (optional) text that explains the ERROR status
437 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
438 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
439 authorization, or some other unspecific error
441 self
.logger
.debug("Getting all subnets from VIM")
444 self
._reload
_connection
()
447 if filter_dict
!= {}:
448 if "tenant_id" in filter_dict
:
449 tfilters
["vpcId"] = filter_dict
.get("tenant_id")
451 subnets
= self
.conn_vpc
.get_all_subnets(
452 subnet_ids
=filter_dict
.get("SubnetId", None), filters
=tfilters
458 if net
.id == filter_dict
.get("name"):
459 self
.availability_zone
= str(net
.availability_zone
)
464 "status": str(net
.state
),
465 "vpc_id": str(net
.vpc_id
),
466 "cidr_block": str(net
.cidr_block
),
472 except Exception as e
:
473 self
.format_vimconn_exception(e
)
475 def get_network(self
, net_id
):
476 """Obtain network details from the 'net_id' VIM network
477 Return a dict that contains:
478 'id': (mandatory) VIM network id, that is, net_id
479 'name': (mandatory) VIM network name
480 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
481 'error_msg': (optional) text that explains the ERROR status
482 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
483 Raises an exception upon error or when network is not found
485 self
.logger
.debug("Getting Subnet from VIM")
488 self
._reload
_connection
()
489 subnet
= self
.conn_vpc
.get_all_subnets(net_id
)[0]
491 "id": str(subnet
.id),
492 "name": str(subnet
.id),
493 "status": str(subnet
.state
),
494 "vpc_id": str(subnet
.vpc_id
),
495 "cidr_block": str(subnet
.cidr_block
),
496 "availability_zone": str(subnet
.availability_zone
),
498 except Exception as e
:
499 self
.format_vimconn_exception(e
)
501 def delete_network(self
, net_id
, created_items
=None):
503 Removes a tenant network from VIM and its associated elements
504 :param net_id: VIM identifier of the network, provided by method new_network
505 :param created_items: dictionary with extra items to be deleted. provided by method new_network
506 Returns the network identifier or raises an exception upon error or when network is not found
508 self
.logger
.debug("Deleting subnet from VIM")
511 self
._reload
_connection
()
512 self
.logger
.debug("DELETING NET_ID: " + str(net_id
))
513 self
.conn_vpc
.delete_subnet(net_id
)
517 except Exception as e
:
518 if isinstance(e
, EC2ResponseError
):
519 self
.network_delete_on_termination
.append(net_id
)
521 f
"{net_id} could not be deleted, deletion will retry after dependencies resolved"
524 self
.format_vimconn_exception(e
)
526 def refresh_nets_status(self
, net_list
):
527 """Get the status of the networks
529 'net_list': a list with the VIM network id to be get the status
530 Returns a dictionary with:
531 'net_id': #VIM id of this network
532 status: #Mandatory. Text with one of:
533 # DELETED (not found at vim)
534 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
535 # OTHER (Vim reported other status not understood)
536 # ERROR (VIM indicates an ERROR status)
537 # ACTIVE, INACTIVE, DOWN (admin down),
538 # BUILD (on building process)
539 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
540 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
543 self
._reload
_connection
()
548 for net_id
in net_list
:
553 subnet
= self
.conn_vpc
.get_all_subnets(net_id
)[0]
555 if subnet
.state
== "pending":
556 subnet_dict
["status"] = "BUILD"
557 elif subnet
.state
== "available":
558 subnet_dict
["status"] = "ACTIVE"
560 subnet_dict
["status"] = "ERROR"
561 subnet_dict
["error_msg"] = ""
563 subnet_dict
["status"] = "DELETED"
564 subnet_dict
["error_msg"] = "Network not found"
566 subnet_dictionary
= vars(subnet
)
567 cleared_subnet_dict
= {
568 key
: subnet_dictionary
[key
]
569 for key
in subnet_dictionary
570 if not isinstance(subnet_dictionary
[key
], object)
572 subnet_dict
["vim_info"] = cleared_subnet_dict
573 dict_entry
[net_id
] = subnet_dict
576 except Exception as e
:
577 self
.format_vimconn_exception(e
)
579 def get_flavor(self
, flavor_id
):
580 """Obtain flavor details from the VIM
581 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
582 Raises an exception upon error or if not found
584 self
.logger
.debug("Getting instance type")
587 if flavor_id
in self
.flavor_info
:
588 return self
.flavor_info
[flavor_id
]
590 raise vimconn
.VimConnNotFoundException(
591 "Cannot find flavor with this flavor ID/Name"
593 except Exception as e
:
594 self
.format_vimconn_exception(e
)
596 def new_image(self
, image_dict
):
597 """Adds a tenant image to VIM
599 name (string) - The name of the AMI. Valid only for EBS-based images.
600 description (string) - The description of the AMI.
601 image_location (string) - Full path to your AMI manifest in Amazon S3 storage. Only used for S3-based AMI’s.
602 architecture (string) - The architecture of the AMI. Valid choices are: * i386 * x86_64
603 kernel_id (string) - The ID of the kernel with which to launch the instances
604 root_device_name (string) - The root device name (e.g. /dev/sdh)
605 block_device_map (boto.ec2.blockdevicemapping.BlockDeviceMapping) - A BlockDeviceMapping data structure
606 describing the EBS volumes associated with the Image.
607 virtualization_type (string) - The virutalization_type of the image. Valid choices are: * paravirtual * hvm
608 sriov_net_support (string) - Advanced networking support. Valid choices are: * simple
609 snapshot_id (string) - A snapshot ID for the snapshot to be used as root device for the image. Mutually
610 exclusive with block_device_map, requires root_device_name
611 delete_root_volume_on_termination (bool) - Whether to delete the root volume of the image after instance
612 termination. Only applies when creating image from snapshot_id. Defaults to False. Note that leaving
613 volumes behind after instance termination is not free
614 Returns: image_id - image ID of the newly created image
617 self
._reload
_connection
()
618 image_location
= image_dict
.get("image_location", None)
621 image_location
= str(self
.account_id
) + str(image_location
)
623 image_id
= self
.conn
.register_image(
624 image_dict
.get("name", None),
625 image_dict
.get("description", None),
627 image_dict
.get("architecture", None),
628 image_dict
.get("kernel_id", None),
629 image_dict
.get("root_device_name", None),
630 image_dict
.get("block_device_map", None),
631 image_dict
.get("virtualization_type", None),
632 image_dict
.get("sriov_net_support", None),
633 image_dict
.get("snapshot_id", None),
634 image_dict
.get("delete_root_volume_on_termination", None),
638 except Exception as e
:
639 self
.format_vimconn_exception(e
)
641 def delete_image(self
, image_id
):
642 """Deletes a tenant image from VIM
643 Returns the image_id if image is deleted or raises an exception on error"""
646 self
._reload
_connection
()
647 self
.conn
.deregister_image(image_id
)
650 except Exception as e
:
651 self
.format_vimconn_exception(e
)
653 def get_image_id_from_path(self
, path
):
655 Params: path - location of the image
656 Returns: image_id - ID of the matching image
658 self
._reload
_connection
()
663 tokens
= path
.split("/")
664 filters
["owner_id"] = tokens
[0]
665 filters
["name"] = "/".join(tokens
[1:])
667 image
= self
.conn
.get_all_images(filters
=filters
)[0]
670 except Exception as e
:
671 self
.format_vimconn_exception(e
)
673 def get_image_list(self
, filter_dict
={}):
674 """Obtain tenant images from VIM
678 checksum: image checksum
680 Returns the image list of dictionaries:
681 [{<the fields at Filter_dict plus some VIM specific>}, ...]
684 self
.logger
.debug("Getting image list from VIM")
687 self
._reload
_connection
()
691 if "id" in filter_dict
:
692 image_id
= filter_dict
["id"]
694 if "name" in filter_dict
:
695 filters
["name"] = filter_dict
["name"]
697 if "location" in filter_dict
:
698 filters
["location"] = filter_dict
["location"]
700 # filters['image_type'] = 'machine'
701 # filter_dict['owner_id'] = self.account_id
702 images
= self
.conn
.get_all_images(image_id
, filters
=filters
)
709 "name": str(image
.name
),
710 "status": str(image
.state
),
711 "owner": str(image
.owner_id
),
712 "location": str(image
.location
),
713 "is_public": str(image
.is_public
),
714 "architecture": str(image
.architecture
),
715 "platform": str(image
.platform
),
720 except Exception as e
:
721 self
.format_vimconn_exception(e
)
734 availability_zone_index
=None,
735 availability_zone_list
=None,
737 """Create a new VM/instance in AWS
740 start: (boolean) indicates if VM must start or created in pause mode.
741 image_id - image ID in AWS
742 flavor_id - instance type ID in AWS
745 net_id - subnet_id from AWS
746 vpci - (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
748 model: (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
749 mac_address: (optional) mac address to assign to this interface
750 type: (mandatory) can be one of:
751 virtual, in this case always connected to a network of type 'net_type=bridge'
752 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a
753 data/ptp network ot it
754 can created unconnected
755 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
756 VFnotShared - (SRIOV without VLAN tag) same as PF for network connectivity. VF where no other
757 VFs are allocated on the same physical NIC
758 bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
759 port_security': (optional) If False it must avoid any traffic filtering at this interface.
760 If missing or True, it must apply the default VIM behaviour
761 vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
762 interface. 'net_list' is modified
763 elastic_ip - True/False to define if an elastic_ip is required
764 cloud_config': (optional) dictionary with:
765 key-pairs': (optional) list of strings with the public key to be inserted to the default user
766 users': (optional) list of users to be inserted, each item is a dict with:
767 name': (mandatory) user name,
768 key-pairs': (optional) list of strings with the public key to be inserted to the user
769 user-data': (optional) string is a text script to be passed directly to cloud-init
770 config-files': (optional). List of files to be transferred. Each item is a dict with:
771 dest': (mandatory) string with the destination absolute path
772 encoding': (optional, by default text). Can be one of:
773 b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
774 content' (mandatory): string with the content of the file
775 permissions': (optional) string with file permissions, typically octal notation '0644'
776 owner: (optional) file owner, string with the format 'owner:group'
777 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
781 disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
782 image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
783 size': (mandatory) string with the size of the disk in GB
784 Returns a tuple with the instance identifier and created_items or raises an exception on error
785 created_items can be None or a dictionary where this method can include key-values that will be passed to
786 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
787 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
790 self
.logger
.debug("Creating a new VM instance")
794 self
._reload
_connection
()
796 _
, userdata
= self
._create
_user
_data
(cloud_config
)
799 reservation
= self
.conn
.run_instances(
801 key_name
=self
.key_pair
,
802 instance_type
=flavor_id
,
803 security_groups
=self
.security_groups
,
808 for index
, subnet
in enumerate(net_list
):
809 net_intr
= self
.conn_vpc
.create_network_interface(
810 subnet_id
=subnet
.get("net_id"),
814 interface
= boto
.ec2
.networkinterface
.NetworkInterfaceSpecification(
815 network_interface_id
=net_intr
.id,
819 interfaces
= boto
.ec2
.networkinterface
.NetworkInterfaceCollection(
823 if subnet
.get("elastic_ip"):
824 eip
= self
.conn
.allocate_address()
825 self
.conn
.associate_address(
826 allocation_id
=eip
.allocation_id
,
827 network_interface_id
=net_intr
.id,
832 reservation
= self
.conn
.run_instances(
834 key_name
=self
.key_pair
,
835 instance_type
=flavor_id
,
836 security_groups
=self
.security_groups
,
837 network_interfaces
=interfaces
,
840 except Exception as instance_create_error
:
841 self
.logger
.debug(traceback
.format_exc())
842 self
.format_vimconn_exception(instance_create_error
)
847 instance_id
= self
.wait_for_instance_id(reservation
)
848 if instance_id
and self
.wait_for_vm(
849 instance_id
, "running"
851 self
.conn
.attach_network_interface(
852 network_interface_id
=net_intr
.id,
853 instance_id
=instance_id
,
856 except Exception as attach_network_error
:
857 self
.logger
.debug(traceback
.format_exc())
858 self
.format_vimconn_exception(attach_network_error
)
860 if instance_id
:= self
.wait_for_instance_id(reservation
):
862 instance_status
= self
.refresh_vms_status(instance_id
)
863 refreshed_instance_status
= instance_status
.get(instance_id
)
864 instance_interfaces
= refreshed_instance_status
.get(
867 for idx
, interface
in enumerate(instance_interfaces
):
869 net_list
[index
]["vim_id"] = instance_interfaces
[
871 ].get("vim_interface_id")
873 instance_id
= self
.wait_for_instance_id(reservation
)
874 created_items
["vm_id:" + str(instance_id
)] = True
876 return instance_id
, created_items
877 except Exception as e
:
878 self
.logger
.debug(traceback
.format_exc())
879 self
.format_vimconn_exception(e
)
881 def get_vminstance(self
, vm_id
):
882 """Returns the VM instance information from VIM"""
884 self
._reload
_connection
()
885 reservation
= self
.conn
.get_all_instances(vm_id
)
887 return reservation
[0].instances
[0].__dict
__
888 except Exception as e
:
889 self
.format_vimconn_exception(e
)
891 def delete_vminstance(self
, vm_id
, created_items
=None, volumes_to_hold
=None):
892 """Removes a VM instance from VIM
893 Returns the instance identifier"""
895 self
._reload
_connection
()
896 self
.logger
.debug("DELETING VM_ID: " + str(vm_id
))
897 reservation
= self
.conn
.get_all_instances(vm_id
)[0]
898 if hasattr(reservation
, "instances"):
899 instance
= reservation
.instances
[0]
901 self
.conn
.terminate_instances(vm_id
)
902 if self
.wait_for_vm(vm_id
, "terminated"):
903 for interface
in instance
.interfaces
:
904 self
.conn_vpc
.delete_network_interface(
905 network_interface_id
=interface
.id,
907 if self
.network_delete_on_termination
:
908 for net
in self
.network_delete_on_termination
:
910 self
.conn_vpc
.delete_subnet(net
)
911 except Exception as net_delete_error
:
912 if isinstance(net_delete_error
, EC2ResponseError
):
913 self
.logger
.warning(f
"Deleting network {net}: failed")
915 self
.format_vimconn_exception(net_delete_error
)
918 except Exception as e
:
919 self
.format_vimconn_exception(e
)
921 def wait_for_instance_id(self
, reservation
):
925 self
._reload
_connection
()
927 while elapsed_time
< 30:
928 if reservation
.instances
:
929 instance_id
= reservation
.instances
[0].id
934 raise vimconn
.VimConnException(
935 "Failed to get instance_id for reservation",
936 http_code
=vimconn
.HTTP_Request_Timeout
,
939 def wait_for_vm(self
, vm_id
, status
):
940 """wait until vm is in the desired status and return True.
941 If the timeout is reached generate an exception"""
943 self
._reload
_connection
()
946 while elapsed_time
< self
.server_timeout
:
947 if self
.conn
.get_all_instances(vm_id
):
948 reservation
= self
.conn
.get_all_instances(vm_id
)[0]
949 if hasattr(reservation
, "instances"):
950 instance
= reservation
.instances
[0]
951 if instance
.state
== status
:
956 # if we exceeded the timeout
958 raise vimconn
.VimConnException(
959 "Timeout waiting for instance " + vm_id
+ " to get " + status
,
960 http_code
=vimconn
.HTTP_Request_Timeout
,
963 def refresh_vms_status(self
, vm_list
):
964 """Get the status of the virtual machines and their interfaces/ports
965 Params: the list of VM identifiers
966 Returns a dictionary with:
967 vm_id: #VIM id of this Virtual Machine
968 status: #Mandatory. Text with one of:
969 # DELETED (not found at vim)
970 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
971 # OTHER (Vim reported other status not understood)
972 # ERROR (VIM indicates an ERROR status)
973 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
974 # BUILD (on building process), ERROR
975 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
977 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
978 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
979 interfaces: list with interface info. Each item a dictionary with:
980 vim_interface_id - The ID of the ENI.
981 vim_net_id - The ID of the VPC subnet.
982 mac_address - The MAC address of the interface.
983 ip_address - The IP address of the interface within the subnet.
985 self
.logger
.debug("Getting VM instance information from VIM")
988 self
._reload
_connection
()
990 while elapsed_time
< self
.server_timeout
:
991 reservation
= self
.conn
.get_all_instances(vm_list
)[0]
997 # if we exceeded the timeout
999 raise vimconn
.VimConnException(
1000 vm_list
+ "could not be gathered, refresh vm status failed",
1001 http_code
=vimconn
.HTTP_Request_Timeout
,
1007 for instance
in reservation
.instances
:
1008 if hasattr(instance
, "id"):
1010 if instance
.state
in ("pending"):
1011 instance_dict
["status"] = "BUILD"
1012 elif instance
.state
in ("available", "running", "up"):
1013 instance_dict
["status"] = "ACTIVE"
1015 instance_dict
["status"] = "ERROR"
1017 instance_dict
["error_msg"] = ""
1018 instance_dict
["interfaces"] = []
1020 for interface
in instance
.interfaces
:
1022 "vim_interface_id": interface
.id,
1023 "vim_net_id": interface
.subnet_id
,
1024 "mac_address": interface
.mac_address
,
1028 hasattr(interface
, "publicIp")
1029 and interface
.publicIp
is not None
1031 interface_dict
["ip_address"] = (
1034 + interface
.private_ip_address
1037 interface_dict
["ip_address"] = (
1038 interface
.private_ip_address
1041 instance_dict
["interfaces"].append(interface_dict
)
1042 except Exception as e
:
1044 "Exception getting vm status: %s", str(e
), exc_info
=True
1046 instance_dict
["status"] = "DELETED"
1047 instance_dict
["error_msg"] = str(e
)
1049 instance_dictionary
= vars(instance
)
1050 cleared_instance_dict
= {
1051 key
: instance_dictionary
[key
]
1052 for key
in instance_dictionary
1053 if not (isinstance(instance_dictionary
[key
], object))
1055 instance_dict
["vim_info"] = cleared_instance_dict
1057 instances
[instance
.id] = instance_dict
1060 except Exception as e
:
1061 self
.logger
.error("Exception getting vm status: %s", str(e
), exc_info
=True)
1062 self
.format_vimconn_exception(e
)
1064 def action_vminstance(self
, vm_id
, action_dict
, created_items
={}):
1065 """Send and action over a VM instance from VIM
1066 Returns the vm_id if the action was successfully sent to the VIM"""
1068 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1070 self
._reload
_connection
()
1071 if "start" in action_dict
:
1072 self
.conn
.start_instances(vm_id
)
1073 elif "stop" in action_dict
or "stop" in action_dict
:
1074 self
.conn
.stop_instances(vm_id
)
1075 elif "terminate" in action_dict
:
1076 self
.conn
.terminate_instances(vm_id
)
1077 elif "reboot" in action_dict
:
1078 self
.conn
.reboot_instances(vm_id
)
1081 except Exception as e
:
1082 self
.format_vimconn_exception(e
)
1084 def migrate_instance(self
, vm_id
, compute_host
=None):
1088 vm_id: ID of an instance
1089 compute_host: Host to migrate the vdu to
1091 # TODO: Add support for migration
1092 raise vimconn
.VimConnNotImplemented("Not implemented")
1094 def resize_instance(self
, vm_id
, flavor_id
=None):
1098 vm_id: ID of an instance
1099 flavor_id: flavor to resize the vdu
1101 # TODO: Add support for resize
1102 raise vimconn
.VimConnNotImplemented("Not implemented")