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
.load(stream
, Loader
=yaml
.Loader
)
146 self
.flavor_info
= yaml
.load(flavor_data
, Loader
=yaml
.Loader
)
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_availability_zones_list(self
):
208 """Obtain AvailabilityZones from AWS"""
210 self
._reload
_connection
()
213 for az
in self
.conn
.get_all_zones():
214 az_list
.append(az
.name
)
217 except Exception as e
:
218 self
.format_vimconn_exception(e
)
220 def get_tenant_list(self
, filter_dict
={}):
221 """Obtain tenants of VIM
222 filter_dict dictionary that can contain the following keys:
223 name: filter by tenant name
224 id: filter by tenant uuid/id
226 Returns the tenant list of dictionaries, and empty list if no tenant match all the filers:
227 [{'name':'<name>, 'id':'<id>, ...}, ...]
230 self
._reload
_connection
()
233 if filter_dict
!= {}:
234 if "id" in filter_dict
:
235 vpc_ids
.append(filter_dict
["id"])
237 tenants
= self
.conn_vpc
.get_all_vpcs(vpc_ids
, None)
240 for tenant
in tenants
:
243 "id": str(tenant
.id),
244 "name": str(tenant
.id),
245 "status": str(tenant
.state
),
246 "cidr_block": str(tenant
.cidr_block
),
251 except Exception as e
:
252 self
.format_vimconn_exception(e
)
254 def new_tenant(self
, tenant_name
, tenant_description
):
255 """Adds a new tenant to VIM with this name and description, this is done using admin_url if provided
256 "tenant_name": string max lenght 64
257 "tenant_description": string max length 256
258 returns the tenant identifier or raise exception
260 self
.logger
.debug("Adding a new VPC")
263 self
._reload
_connection
()
264 vpc
= self
.conn_vpc
.create_vpc(self
.vpc_cidr_block
)
265 self
.conn_vpc
.modify_vpc_attribute(vpc
.id, enable_dns_support
=True)
266 self
.conn_vpc
.modify_vpc_attribute(vpc
.id, enable_dns_hostnames
=True)
268 gateway
= self
.conn_vpc
.create_internet_gateway()
269 self
.conn_vpc
.attach_internet_gateway(gateway
.id, vpc
.id)
270 route_table
= self
.conn_vpc
.create_route_table(vpc
.id)
271 self
.conn_vpc
.create_route(route_table
.id, "0.0.0.0/0", gateway
.id)
273 self
.vpc_data
[vpc
.id] = {
274 "gateway": gateway
.id,
275 "route_table": route_table
.id,
276 "subnets": self
.subnet_sizes(self
.vpc_cidr_block
),
280 except Exception as e
:
281 self
.format_vimconn_exception(e
)
283 def delete_tenant(self
, tenant_id
):
284 """Delete a tenant from VIM
285 tenant_id: returned VIM tenant_id on "new_tenant"
286 Returns None on success. Raises and exception of failure. If tenant is not found raises vimconnNotFoundException
288 self
.logger
.debug("Deleting specified VPC")
291 self
._reload
_connection
()
292 vpc
= self
.vpc_data
.get(tenant_id
)
294 if "gateway" in vpc
and "route_table" in vpc
:
295 gateway_id
, route_table_id
= vpc
["gateway"], vpc
["route_table"]
296 self
.conn_vpc
.detach_internet_gateway(gateway_id
, tenant_id
)
297 self
.conn_vpc
.delete_vpc(tenant_id
)
298 self
.conn_vpc
.delete_route(route_table_id
, "0.0.0.0/0")
300 self
.conn_vpc
.delete_vpc(tenant_id
)
301 except Exception as e
:
302 self
.format_vimconn_exception(e
)
304 def subnet_sizes(self
, cidr
):
305 """Calculates possible subnets given CIDR value of VPC"""
315 ip
= netaddr
.IPNetwork(cidr
)
319 for netmask
in netmasks
:
320 if str(mask
) == netmask
:
321 pub_split
= list(ip
.subnet(24))
324 subnets
= pub_split
if pub_split
else (list(ip
.subnet(28)))
326 return map(str, subnets
)
334 provider_network_profile
=None,
336 """Adds a tenant network to VIM
338 'net_name': name of the network
340 'bridge': overlay isolated network
341 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
342 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
343 'ip_profile': is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
344 'ip-version': can be one of ["IPv4","IPv6"]
345 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
346 'gateway-address': (Optional) ip_schema, that is X.X.X.X
347 'dns-address': (Optional) ip_schema,
348 'dhcp': (Optional) dict containing
349 'enabled': {"type": "boolean"},
350 'start-address': ip_schema, first IP to grant
351 'count': number of IPs to grant.
352 'shared': if this network can be seen/use by other tenants/organization
353 Returns a tuple with the network identifier and created_items, or raises an exception on error
354 created_items can be None or a dictionary where this method can include key-values that will be passed to
355 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
356 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
359 self
.logger
.debug("Adding a subnet to VPC")
363 self
._reload
_connection
()
366 if self
.conn_vpc
.get_all_subnets():
367 existing_subnet
= self
.conn_vpc
.get_all_subnets()[0]
368 if not self
.availability_zone
:
369 self
.availability_zone
= str(existing_subnet
.availability_zone
)
371 if self
.vpc_data
.get(vpc_id
, None):
373 set(self
.vpc_data
[vpc_id
]["subnets"])
375 self
.get_network_details(
376 {"tenant_id": vpc_id
}, detail
="cidr_block"
381 vpc
= self
.get_tenant_list({"id": vpc_id
})[0]
382 subnet_list
= self
.subnet_sizes(vpc
["cidr_block"])
386 self
.get_network_details(
387 {"tenant_id": vpc
["id"]}, detail
="cidr_block"
393 selected_cidr_block
= random
.choice(cidr_block
)
397 subnet
.cidr_block
for subnet
in self
.conn_vpc
.get_all_subnets()
399 all_subnets
.append(selected_cidr_block
)
400 conflict
= check_conflicts(all_subnets
)
402 subnet
= self
.conn_vpc
.create_subnet(
403 vpc_id
, selected_cidr_block
, self
.availability_zone
407 selected_cidr_block
= random
.choice(cidr_block
)
409 raise vimconn
.VimConnException(
410 "Failed to find a proper CIDR which does not overlap"
411 "with existing subnets",
412 http_code
=vimconn
.HTTP_Request_Timeout
,
415 except (EC2ResponseError
, BotoServerError
) as error
:
416 self
.format_vimconn_exception(error
)
418 created_items
["net:" + str(subnet
.id)] = True
420 return subnet
.id, created_items
421 except Exception as e
:
422 self
.format_vimconn_exception(e
)
424 def get_network_details(self
, filters
, detail
):
425 """Get specified details related to a subnet"""
427 subnet_list
= self
.get_network_list(filters
)
429 for net
in subnet_list
:
430 detail_list
.append(net
[detail
])
434 def get_network_list(self
, filter_dict
={}):
435 """Obtain tenant networks of VIM
437 'filter_dict' (optional) contains entries to return only networks that matches ALL entries:
438 name: string => returns only networks with this name
439 id: string => returns networks with this VIM id, this imply returns one network at most
440 shared: boolean >= returns only networks that are (or are not) shared
441 tenant_id: sting => returns only networks that belong to this tenant/project
442 ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin
444 #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
445 Returns the network list of dictionaries. each dictionary contains:
446 'id': (mandatory) VIM network id
447 'name': (mandatory) VIM network name
448 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
449 'error_msg': (optional) text that explains the ERROR status
450 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
451 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
452 authorization, or some other unspecific error
454 self
.logger
.debug("Getting all subnets from VIM")
457 self
._reload
_connection
()
460 if filter_dict
!= {}:
461 if "tenant_id" in filter_dict
:
462 tfilters
["vpcId"] = filter_dict
.get("tenant_id")
464 subnets
= self
.conn_vpc
.get_all_subnets(
465 subnet_ids
=filter_dict
.get("SubnetId", None), filters
=tfilters
471 if net
.id == filter_dict
.get("name"):
472 self
.availability_zone
= str(net
.availability_zone
)
477 "status": str(net
.state
),
478 "vpc_id": str(net
.vpc_id
),
479 "cidr_block": str(net
.cidr_block
),
485 except Exception as e
:
486 self
.format_vimconn_exception(e
)
488 def get_network(self
, net_id
):
489 """Obtain network details from the 'net_id' VIM network
490 Return a dict that contains:
491 'id': (mandatory) VIM network id, that is, net_id
492 'name': (mandatory) VIM network name
493 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
494 'error_msg': (optional) text that explains the ERROR status
495 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
496 Raises an exception upon error or when network is not found
498 self
.logger
.debug("Getting Subnet from VIM")
501 self
._reload
_connection
()
502 subnet
= self
.conn_vpc
.get_all_subnets(net_id
)[0]
504 "id": str(subnet
.id),
505 "name": str(subnet
.id),
506 "status": str(subnet
.state
),
507 "vpc_id": str(subnet
.vpc_id
),
508 "cidr_block": str(subnet
.cidr_block
),
509 "availability_zone": str(subnet
.availability_zone
),
511 except Exception as e
:
512 self
.format_vimconn_exception(e
)
514 def delete_network(self
, net_id
, created_items
=None):
516 Removes a tenant network from VIM and its associated elements
517 :param net_id: VIM identifier of the network, provided by method new_network
518 :param created_items: dictionary with extra items to be deleted. provided by method new_network
519 Returns the network identifier or raises an exception upon error or when network is not found
521 self
.logger
.debug("Deleting subnet from VIM")
524 self
._reload
_connection
()
525 self
.logger
.debug("DELETING NET_ID: " + str(net_id
))
526 self
.conn_vpc
.delete_subnet(net_id
)
530 except Exception as e
:
531 if isinstance(e
, EC2ResponseError
):
532 self
.network_delete_on_termination
.append(net_id
)
534 f
"{net_id} could not be deleted, deletion will retry after dependencies resolved"
537 self
.format_vimconn_exception(e
)
539 def refresh_nets_status(self
, net_list
):
540 """Get the status of the networks
542 'net_list': a list with the VIM network id to be get the status
543 Returns a dictionary with:
544 'net_id': #VIM id of this network
545 status: #Mandatory. Text with one of:
546 # DELETED (not found at vim)
547 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
548 # OTHER (Vim reported other status not understood)
549 # ERROR (VIM indicates an ERROR status)
550 # ACTIVE, INACTIVE, DOWN (admin down),
551 # BUILD (on building process)
552 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
553 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
556 self
._reload
_connection
()
561 for net_id
in net_list
:
566 subnet
= self
.conn_vpc
.get_all_subnets(net_id
)[0]
568 if subnet
.state
== "pending":
569 subnet_dict
["status"] = "BUILD"
570 elif subnet
.state
== "available":
571 subnet_dict
["status"] = "ACTIVE"
573 subnet_dict
["status"] = "ERROR"
574 subnet_dict
["error_msg"] = ""
576 subnet_dict
["status"] = "DELETED"
577 subnet_dict
["error_msg"] = "Network not found"
579 subnet_dictionary
= vars(subnet
)
580 cleared_subnet_dict
= {
581 key
: subnet_dictionary
[key
]
582 for key
in subnet_dictionary
583 if not isinstance(subnet_dictionary
[key
], object)
585 subnet_dict
["vim_info"] = cleared_subnet_dict
586 dict_entry
[net_id
] = subnet_dict
589 except Exception as e
:
590 self
.format_vimconn_exception(e
)
592 def get_flavor(self
, flavor_id
):
593 """Obtain flavor details from the VIM
594 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
595 Raises an exception upon error or if not found
597 self
.logger
.debug("Getting instance type")
600 if flavor_id
in self
.flavor_info
:
601 return self
.flavor_info
[flavor_id
]
603 raise vimconn
.VimConnNotFoundException(
604 "Cannot find flavor with this flavor ID/Name"
606 except Exception as e
:
607 self
.format_vimconn_exception(e
)
609 def get_flavor_id_from_data(self
, flavor_dict
):
610 """Obtain flavor id that match the flavor description
612 'flavor_dict': dictionary that contains:
613 'disk': main hard disk in GB
615 'vcpus': number of virtual cpus
616 #todo: complete parameters for EPA
617 Returns the flavor_id or raises a vimconnNotFoundException
619 self
.logger
.debug("Getting flavor id from data")
623 for key
, values
in self
.flavor_info
.items():
624 if (values
["ram"], values
["cpus"], values
["disk"]) == (
626 flavor_dict
["vcpus"],
629 flavor
= (key
, values
)
631 elif (values
["ram"], values
["cpus"], values
["disk"]) >= (
633 flavor_dict
["vcpus"],
637 flavor
= (key
, values
)
639 if (flavor
[1]["ram"], flavor
[1]["cpus"], flavor
[1]["disk"]) >= (
644 flavor
= (key
, values
)
649 raise vimconn
.VimConnNotFoundException(
650 "Cannot find flavor with this flavor ID/Name"
652 except Exception as e
:
653 self
.format_vimconn_exception(e
)
655 def new_image(self
, image_dict
):
656 """Adds a tenant image to VIM
658 name (string) - The name of the AMI. Valid only for EBS-based images.
659 description (string) - The description of the AMI.
660 image_location (string) - Full path to your AMI manifest in Amazon S3 storage. Only used for S3-based AMI’s.
661 architecture (string) - The architecture of the AMI. Valid choices are: * i386 * x86_64
662 kernel_id (string) - The ID of the kernel with which to launch the instances
663 root_device_name (string) - The root device name (e.g. /dev/sdh)
664 block_device_map (boto.ec2.blockdevicemapping.BlockDeviceMapping) - A BlockDeviceMapping data structure
665 describing the EBS volumes associated with the Image.
666 virtualization_type (string) - The virutalization_type of the image. Valid choices are: * paravirtual * hvm
667 sriov_net_support (string) - Advanced networking support. Valid choices are: * simple
668 snapshot_id (string) - A snapshot ID for the snapshot to be used as root device for the image. Mutually
669 exclusive with block_device_map, requires root_device_name
670 delete_root_volume_on_termination (bool) - Whether to delete the root volume of the image after instance
671 termination. Only applies when creating image from snapshot_id. Defaults to False. Note that leaving
672 volumes behind after instance termination is not free
673 Returns: image_id - image ID of the newly created image
676 self
._reload
_connection
()
677 image_location
= image_dict
.get("image_location", None)
680 image_location
= str(self
.account_id
) + str(image_location
)
682 image_id
= self
.conn
.register_image(
683 image_dict
.get("name", None),
684 image_dict
.get("description", None),
686 image_dict
.get("architecture", None),
687 image_dict
.get("kernel_id", None),
688 image_dict
.get("root_device_name", None),
689 image_dict
.get("block_device_map", None),
690 image_dict
.get("virtualization_type", None),
691 image_dict
.get("sriov_net_support", None),
692 image_dict
.get("snapshot_id", None),
693 image_dict
.get("delete_root_volume_on_termination", None),
697 except Exception as e
:
698 self
.format_vimconn_exception(e
)
700 def delete_image(self
, image_id
):
701 """Deletes a tenant image from VIM
702 Returns the image_id if image is deleted or raises an exception on error"""
705 self
._reload
_connection
()
706 self
.conn
.deregister_image(image_id
)
709 except Exception as e
:
710 self
.format_vimconn_exception(e
)
712 def get_image_id_from_path(self
, path
):
714 Params: path - location of the image
715 Returns: image_id - ID of the matching image
717 self
._reload
_connection
()
722 tokens
= path
.split("/")
723 filters
["owner_id"] = tokens
[0]
724 filters
["name"] = "/".join(tokens
[1:])
726 image
= self
.conn
.get_all_images(filters
=filters
)[0]
729 except Exception as e
:
730 self
.format_vimconn_exception(e
)
732 def get_image_list(self
, filter_dict
={}):
733 """Obtain tenant images from VIM
737 checksum: image checksum
739 Returns the image list of dictionaries:
740 [{<the fields at Filter_dict plus some VIM specific>}, ...]
743 self
.logger
.debug("Getting image list from VIM")
746 self
._reload
_connection
()
750 if "id" in filter_dict
:
751 image_id
= filter_dict
["id"]
753 if "name" in filter_dict
:
754 filters
["name"] = filter_dict
["name"]
756 if "location" in filter_dict
:
757 filters
["location"] = filter_dict
["location"]
759 # filters['image_type'] = 'machine'
760 # filter_dict['owner_id'] = self.account_id
761 images
= self
.conn
.get_all_images(image_id
, filters
=filters
)
768 "name": str(image
.name
),
769 "status": str(image
.state
),
770 "owner": str(image
.owner_id
),
771 "location": str(image
.location
),
772 "is_public": str(image
.is_public
),
773 "architecture": str(image
.architecture
),
774 "platform": str(image
.platform
),
779 except Exception as e
:
780 self
.format_vimconn_exception(e
)
793 availability_zone_index
=None,
794 availability_zone_list
=None,
796 """Create a new VM/instance in AWS
799 start: (boolean) indicates if VM must start or created in pause mode.
800 image_id - image ID in AWS
801 flavor_id - instance type ID in AWS
804 net_id - subnet_id from AWS
805 vpci - (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
807 model: (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
808 mac_address: (optional) mac address to assign to this interface
809 type: (mandatory) can be one of:
810 virtual, in this case always connected to a network of type 'net_type=bridge'
811 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a
812 data/ptp network ot it
813 can created unconnected
814 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
815 VFnotShared - (SRIOV without VLAN tag) same as PF for network connectivity. VF where no other
816 VFs are allocated on the same physical NIC
817 bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
818 port_security': (optional) If False it must avoid any traffic filtering at this interface.
819 If missing or True, it must apply the default VIM behaviour
820 vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
821 interface. 'net_list' is modified
822 elastic_ip - True/False to define if an elastic_ip is required
823 cloud_config': (optional) dictionary with:
824 key-pairs': (optional) list of strings with the public key to be inserted to the default user
825 users': (optional) list of users to be inserted, each item is a dict with:
826 name': (mandatory) user name,
827 key-pairs': (optional) list of strings with the public key to be inserted to the user
828 user-data': (optional) string is a text script to be passed directly to cloud-init
829 config-files': (optional). List of files to be transferred. Each item is a dict with:
830 dest': (mandatory) string with the destination absolute path
831 encoding': (optional, by default text). Can be one of:
832 b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
833 content' (mandatory): string with the content of the file
834 permissions': (optional) string with file permissions, typically octal notation '0644'
835 owner: (optional) file owner, string with the format 'owner:group'
836 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
840 disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
841 image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
842 size': (mandatory) string with the size of the disk in GB
843 Returns a tuple with the instance identifier and created_items or raises an exception on error
844 created_items can be None or a dictionary where this method can include key-values that will be passed to
845 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
846 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
849 self
.logger
.debug("Creating a new VM instance")
853 self
._reload
_connection
()
855 _
, userdata
= self
._create
_user
_data
(cloud_config
)
858 reservation
= self
.conn
.run_instances(
860 key_name
=self
.key_pair
,
861 instance_type
=flavor_id
,
862 security_groups
=self
.security_groups
,
867 for index
, subnet
in enumerate(net_list
):
869 net_intr
= self
.conn_vpc
.create_network_interface(
870 subnet_id
=subnet
.get("net_id"),
874 interface
= boto
.ec2
.networkinterface
.NetworkInterfaceSpecification(
875 network_interface_id
=net_intr
.id,
879 interfaces
= boto
.ec2
.networkinterface
.NetworkInterfaceCollection(
883 if subnet
.get("elastic_ip"):
884 eip
= self
.conn
.allocate_address()
885 self
.conn
.associate_address(
886 allocation_id
=eip
.allocation_id
,
887 network_interface_id
=net_intr
.id,
892 reservation
= self
.conn
.run_instances(
894 key_name
=self
.key_pair
,
895 instance_type
=flavor_id
,
896 security_groups
=self
.security_groups
,
897 network_interfaces
=interfaces
,
900 except Exception as instance_create_error
:
901 self
.logger
.debug(traceback
.format_exc())
902 self
.format_vimconn_exception(instance_create_error
)
907 instance_id
= self
.wait_for_instance_id(reservation
)
908 if instance_id
and self
.wait_for_vm(
909 instance_id
, "running"
911 self
.conn
.attach_network_interface(
912 network_interface_id
=net_intr
.id,
913 instance_id
=instance_id
,
916 except Exception as attach_network_error
:
917 self
.logger
.debug(traceback
.format_exc())
918 self
.format_vimconn_exception(attach_network_error
)
920 if instance_id
:= self
.wait_for_instance_id(reservation
):
922 instance_status
= self
.refresh_vms_status(instance_id
)
923 refreshed_instance_status
= instance_status
.get(instance_id
)
924 instance_interfaces
= refreshed_instance_status
.get(
927 for idx
, interface
in enumerate(instance_interfaces
):
929 net_list
[index
]["vim_id"] = instance_interfaces
[
931 ].get("vim_interface_id")
933 instance_id
= self
.wait_for_instance_id(reservation
)
934 created_items
["vm_id:" + str(instance_id
)] = True
936 return instance_id
, created_items
937 except Exception as e
:
938 self
.logger
.debug(traceback
.format_exc())
939 self
.format_vimconn_exception(e
)
941 def get_vminstance(self
, vm_id
):
942 """Returns the VM instance information from VIM"""
944 self
._reload
_connection
()
945 reservation
= self
.conn
.get_all_instances(vm_id
)
947 return reservation
[0].instances
[0].__dict
__
948 except Exception as e
:
949 self
.format_vimconn_exception(e
)
951 def delete_vminstance(self
, vm_id
, created_items
=None, volumes_2hold
=None):
952 """Removes a VM instance from VIM
953 Returns the instance identifier"""
955 self
._reload
_connection
()
956 self
.logger
.debug("DELETING VM_ID: " + str(vm_id
))
957 reservation
= self
.conn
.get_all_instances(vm_id
)[0]
958 if hasattr(reservation
, "instances"):
959 instance
= reservation
.instances
[0]
961 self
.conn
.terminate_instances(vm_id
)
962 if self
.wait_for_vm(vm_id
, "terminated"):
963 for interface
in instance
.interfaces
:
964 self
.conn_vpc
.delete_network_interface(
965 network_interface_id
=interface
.id,
967 if self
.network_delete_on_termination
:
968 for net
in self
.network_delete_on_termination
:
970 self
.conn_vpc
.delete_subnet(net
)
971 except Exception as net_delete_error
:
972 if isinstance(net_delete_error
, EC2ResponseError
):
973 self
.logger
.warning(f
"Deleting network {net}: failed")
975 self
.format_vimconn_exception(net_delete_error
)
978 except Exception as e
:
979 self
.format_vimconn_exception(e
)
981 def wait_for_instance_id(self
, reservation
):
985 self
._reload
_connection
()
987 while elapsed_time
< 30:
988 if reservation
.instances
:
989 instance_id
= reservation
.instances
[0].id
994 raise vimconn
.VimConnException(
995 "Failed to get instance_id for reservation",
997 http_code
=vimconn
.HTTP_Request_Timeout
,
1000 def wait_for_vm(self
, vm_id
, status
):
1001 """wait until vm is in the desired status and return True.
1002 If the timeout is reached generate an exception"""
1004 self
._reload
_connection
()
1007 while elapsed_time
< self
.server_timeout
:
1008 if self
.conn
.get_all_instances(vm_id
):
1009 reservation
= self
.conn
.get_all_instances(vm_id
)[0]
1010 if hasattr(reservation
, "instances"):
1011 instance
= reservation
.instances
[0]
1012 if instance
.state
== status
:
1017 # if we exceeded the timeout
1019 raise vimconn
.VimConnException(
1020 "Timeout waiting for instance " + vm_id
+ " to get " + status
,
1021 http_code
=vimconn
.HTTP_Request_Timeout
,
1024 def refresh_vms_status(self
, vm_list
):
1025 """Get the status of the virtual machines and their interfaces/ports
1026 Params: the list of VM identifiers
1027 Returns a dictionary with:
1028 vm_id: #VIM id of this Virtual Machine
1029 status: #Mandatory. Text with one of:
1030 # DELETED (not found at vim)
1031 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1032 # OTHER (Vim reported other status not understood)
1033 # ERROR (VIM indicates an ERROR status)
1034 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1035 # BUILD (on building process), ERROR
1036 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1038 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1039 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1040 interfaces: list with interface info. Each item a dictionary with:
1041 vim_interface_id - The ID of the ENI.
1042 vim_net_id - The ID of the VPC subnet.
1043 mac_address - The MAC address of the interface.
1044 ip_address - The IP address of the interface within the subnet.
1046 self
.logger
.debug("Getting VM instance information from VIM")
1049 self
._reload
_connection
()
1051 while elapsed_time
< self
.server_timeout
:
1052 reservation
= self
.conn
.get_all_instances(vm_list
)[0]
1058 # if we exceeded the timeout
1060 raise vimconn
.VimConnException(
1061 vm_list
+ "could not be gathered, refresh vm status failed",
1062 http_code
=vimconn
.HTTP_Request_Timeout
,
1068 for instance
in reservation
.instances
:
1069 if hasattr(instance
, "id"):
1071 if instance
.state
in ("pending"):
1072 instance_dict
["status"] = "BUILD"
1073 elif instance
.state
in ("available", "running", "up"):
1074 instance_dict
["status"] = "ACTIVE"
1076 instance_dict
["status"] = "ERROR"
1078 instance_dict
["error_msg"] = ""
1079 instance_dict
["interfaces"] = []
1081 for interface
in instance
.interfaces
:
1083 "vim_interface_id": interface
.id,
1084 "vim_net_id": interface
.subnet_id
,
1085 "mac_address": interface
.mac_address
,
1089 hasattr(interface
, "publicIp")
1090 and interface
.publicIp
is not None
1092 interface_dict
["ip_address"] = (
1095 + interface
.private_ip_address
1100 ] = interface
.private_ip_address
1102 instance_dict
["interfaces"].append(interface_dict
)
1103 except Exception as e
:
1105 "Exception getting vm status: %s", str(e
), exc_info
=True
1107 instance_dict
["status"] = "DELETED"
1108 instance_dict
["error_msg"] = str(e
)
1110 instance_dictionary
= vars(instance
)
1111 cleared_instance_dict
= {
1112 key
: instance_dictionary
[key
]
1113 for key
in instance_dictionary
1114 if not (isinstance(instance_dictionary
[key
], object))
1116 instance_dict
["vim_info"] = cleared_instance_dict
1118 instances
[instance
.id] = instance_dict
1121 except Exception as e
:
1122 self
.logger
.error("Exception getting vm status: %s", str(e
), exc_info
=True)
1123 self
.format_vimconn_exception(e
)
1125 def action_vminstance(self
, vm_id
, action_dict
, created_items
={}):
1126 """Send and action over a VM instance from VIM
1127 Returns the vm_id if the action was successfully sent to the VIM"""
1129 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1131 self
._reload
_connection
()
1132 if "start" in action_dict
:
1133 self
.conn
.start_instances(vm_id
)
1134 elif "stop" in action_dict
or "stop" in action_dict
:
1135 self
.conn
.stop_instances(vm_id
)
1136 elif "terminate" in action_dict
:
1137 self
.conn
.terminate_instances(vm_id
)
1138 elif "reboot" in action_dict
:
1139 self
.conn
.reboot_instances(vm_id
)
1142 except Exception as e
:
1143 self
.format_vimconn_exception(e
)
1145 def migrate_instance(self
, vm_id
, compute_host
=None):
1149 vm_id: ID of an instance
1150 compute_host: Host to migrate the vdu to
1152 # TODO: Add support for migration
1153 raise vimconn
.VimConnNotImplemented("Not implemented")
1155 def resize_instance(self
, vm_id
, flavor_id
=None):
1159 vm_id: ID of an instance
1160 flavor_id: flavor to resize the vdu
1162 # TODO: Add support for resize
1163 raise vimconn
.VimConnNotImplemented("Not implemented")