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
35 from osm_ro_plugin
import vimconn
38 __author__
= "Saboor Ahmad"
39 __date__
= "10-Apr-2017"
42 class vimconnector(vimconn
.VimConnector
):
58 uuid - id asigned to this VIM
59 name - name assigned to this VIM, can be used for logging
60 tenant_id - ID to be used for tenant
61 tenant_name - name of tenant to be used VIM tenant to be used
62 url_admin - optional, url used for administrative tasks
63 user - credentials of the VIM user
64 passwd - credentials of the VIM user
65 log_level - if must use a different log_level than the general one
66 config - dictionary with misc VIM information
67 region_name - name of region to deploy the instances
68 vpc_cidr_block - default CIDR block for VPC
69 security_groups - default security group to specify this instance
70 persistent_info - dict where the class can store information that will be available among class
71 destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an
72 empty dict. Useful to store login/tokens information for speed up communication
74 vimconn
.VimConnector
.__init
__(
89 self
.persistent_info
= persistent_info
93 self
.a_creds
["aws_access_key_id"] = user
95 raise vimconn
.VimConnAuthException("Username is not specified")
98 self
.a_creds
["aws_secret_access_key"] = passwd
100 raise vimconn
.VimConnAuthException("Password is not specified")
102 if "region_name" in config
:
103 self
.region
= config
.get("region_name")
105 raise vimconn
.VimConnException("AWS region_name is not specified at config")
108 self
.subnet_data
= {}
111 self
.account_id
= None
113 self
.vpc_id
= self
.get_tenant_list()[0]["id"]
114 # we take VPC CIDR block if specified, otherwise we use the default CIDR
115 # block suggested by AWS while creating instance
116 self
.vpc_cidr_block
= "10.0.0.0/24"
119 self
.vpc_id
= tenant_id
121 if "vpc_cidr_block" in config
:
122 self
.vpc_cidr_block
= config
["vpc_cidr_block"]
124 self
.security_groups
= None
125 if "security_groups" in config
:
126 self
.security_groups
= config
["security_groups"]
129 if "key_pair" in config
:
130 self
.key_pair
= config
["key_pair"]
132 self
.flavor_info
= None
133 if "flavor_info" in config
:
134 flavor_data
= config
.get("flavor_info")
135 if isinstance(flavor_data
, str):
137 if flavor_data
[0] == "@": # read from a file
138 with
open(flavor_data
[1:], "r") as stream
:
139 self
.flavor_info
= yaml
.safe_load(stream
)
141 self
.flavor_info
= yaml
.safe_load(flavor_data
)
142 except yaml
.YAMLError
as e
:
143 self
.flavor_info
= None
145 raise vimconn
.VimConnException(
146 "Bad format at file '{}': {}".format(flavor_data
[1:], e
)
149 raise vimconn
.VimConnException(
150 "Error reading file '{}': {}".format(flavor_data
[1:], e
)
152 elif isinstance(flavor_data
, dict):
153 self
.flavor_info
= flavor_data
155 self
.logger
= logging
.getLogger("ro.vim.aws")
158 self
.logger
.setLevel(getattr(logging
, log_level
))
160 def __setitem__(self
, index
, value
):
162 index - name of value of set
166 self
.a_creds
["aws_access_key_id"] = value
167 elif index
== "passwd":
168 self
.a_creds
["aws_secret_access_key"] = value
169 elif index
== "region":
172 vimconn
.VimConnector
.__setitem
__(self
, index
, value
)
174 def _reload_connection(self
):
175 """Returns: sets boto.EC2 and boto.VPC connection to work with AWS services"""
177 self
.conn
= boto
.ec2
.connect_to_region(
179 aws_access_key_id
=self
.a_creds
["aws_access_key_id"],
180 aws_secret_access_key
=self
.a_creds
["aws_secret_access_key"],
182 self
.conn_vpc
= boto
.vpc
.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 # client = boto3.client("sts", aws_access_key_id=self.a_creds['aws_access_key_id'],
188 # aws_secret_access_key=self.a_creds['aws_secret_access_key'])
189 # self.account_id = client.get_caller_identity()["Account"]
190 except Exception as e
:
191 self
.format_vimconn_exception(e
)
193 def format_vimconn_exception(self
, e
):
194 """Params: an Exception object
195 Returns: Raises the exception 'e' passed in mehtod parameters
200 raise vimconn
.VimConnConnectionException(type(e
).__name
__ + ": " + str(e
))
202 def get_availability_zones_list(self
):
203 """Obtain AvailabilityZones from AWS"""
205 self
._reload
_connection
()
208 for az
in self
.conn
.get_all_zones():
209 az_list
.append(az
.name
)
212 except Exception as e
:
213 self
.format_vimconn_exception(e
)
215 def get_tenant_list(self
, filter_dict
={}):
216 """Obtain tenants of VIM
217 filter_dict dictionary that can contain the following keys:
218 name: filter by tenant name
219 id: filter by tenant uuid/id
221 Returns the tenant list of dictionaries, and empty list if no tenant match all the filers:
222 [{'name':'<name>, 'id':'<id>, ...}, ...]
225 self
._reload
_connection
()
229 if filter_dict
!= {}:
230 if "id" in filter_dict
:
231 vpc_ids
.append(filter_dict
["id"])
232 tfilters
["name"] = filter_dict
["id"]
234 tenants
= self
.conn_vpc
.get_all_vpcs(vpc_ids
, tfilters
)
237 for tenant
in tenants
:
240 "id": str(tenant
.id),
241 "name": str(tenant
.id),
242 "status": str(tenant
.state
),
243 "cidr_block": str(tenant
.cidr_block
),
248 except Exception as e
:
249 self
.format_vimconn_exception(e
)
251 def new_tenant(self
, tenant_name
, tenant_description
):
252 """Adds a new tenant to VIM with this name and description, this is done using admin_url if provided
253 "tenant_name": string max lenght 64
254 "tenant_description": string max length 256
255 returns the tenant identifier or raise exception
257 self
.logger
.debug("Adding a new VPC")
260 self
._reload
_connection
()
261 vpc
= self
.conn_vpc
.create_vpc(self
.vpc_cidr_block
)
262 self
.conn_vpc
.modify_vpc_attribute(vpc
.id, enable_dns_support
=True)
263 self
.conn_vpc
.modify_vpc_attribute(vpc
.id, enable_dns_hostnames
=True)
265 gateway
= self
.conn_vpc
.create_internet_gateway()
266 self
.conn_vpc
.attach_internet_gateway(gateway
.id, vpc
.id)
267 route_table
= self
.conn_vpc
.create_route_table(vpc
.id)
268 self
.conn_vpc
.create_route(route_table
.id, "0.0.0.0/0", gateway
.id)
270 self
.vpc_data
[vpc
.id] = {
271 "gateway": gateway
.id,
272 "route_table": route_table
.id,
273 "subnets": self
.subnet_sizes(
274 len(self
.get_availability_zones_list()), self
.vpc_cidr_block
279 except Exception as e
:
280 self
.format_vimconn_exception(e
)
282 def delete_tenant(self
, tenant_id
):
283 """Delete a tenant from VIM
284 tenant_id: returned VIM tenant_id on "new_tenant"
285 Returns None on success. Raises and exception of failure. If tenant is not found raises vimconnNotFoundException
287 self
.logger
.debug("Deleting specified VPC")
290 self
._reload
_connection
()
291 vpc
= self
.vpc_data
.get(tenant_id
)
293 if "gateway" in vpc
and "route_table" in vpc
:
294 gateway_id
, route_table_id
= vpc
["gateway"], vpc
["route_table"]
295 self
.conn_vpc
.detach_internet_gateway(gateway_id
, tenant_id
)
296 self
.conn_vpc
.delete_vpc(tenant_id
)
297 self
.conn_vpc
.delete_route(route_table_id
, "0.0.0.0/0")
299 self
.conn_vpc
.delete_vpc(tenant_id
)
300 except Exception as e
:
301 self
.format_vimconn_exception(e
)
303 def subnet_sizes(self
, availability_zones
, cidr
):
304 """Calculates possible subnets given CIDR value of VPC"""
305 if availability_zones
!= 2 and availability_zones
!= 3:
306 self
.logger
.debug("Number of AZs should be 2 or 3")
308 raise vimconn
.VimConnNotSupportedException("Number of AZs should be 2 or 3")
316 ip
= netaddr
.IPNetwork(cidr
)
319 if str(mask
) not in netmasks
:
320 self
.logger
.debug("Netmask " + str(mask
) + " not found")
322 raise vimconn
.VimConnNotFoundException(
323 "Netmask " + str(mask
) + " not found"
326 if availability_zones
== 2:
327 for n
, netmask
in enumerate(netmasks
):
328 if str(mask
) == netmask
:
329 subnets
= list(ip
.subnet(n
+ 24))
331 for n
, netmask
in enumerate(netmasks
):
332 if str(mask
) == netmask
:
333 pub_net
= list(ip
.subnet(n
+ 24))
334 pri_subs
= pub_net
[1:]
335 pub_mask
= pub_net
[0].netmask
339 if (str(pub_mask
) == "255.255.255.0")
340 else list(ip
.subnet(27))
342 pub_subs
= pub_split
[:3]
343 subnets
= pub_subs
+ pri_subs
345 return map(str, subnets
)
353 provider_network_profile
=None,
355 """Adds a tenant network to VIM
357 'net_name': name of the network
359 'bridge': overlay isolated network
360 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
361 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
362 'ip_profile': is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
363 'ip-version': can be one of ["IPv4","IPv6"]
364 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
365 'gateway-address': (Optional) ip_schema, that is X.X.X.X
366 'dns-address': (Optional) ip_schema,
367 'dhcp': (Optional) dict containing
368 'enabled': {"type": "boolean"},
369 'start-address': ip_schema, first IP to grant
370 'count': number of IPs to grant.
371 'shared': if this network can be seen/use by other tenants/organization
372 Returns a tuple with the network identifier and created_items, or raises an exception on error
373 created_items can be None or a dictionary where this method can include key-values that will be passed to
374 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
375 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
378 self
.logger
.debug("Adding a subnet to VPC")
382 self
._reload
_connection
()
386 if self
.vpc_data
.get(vpc_id
, None):
388 set(self
.vpc_data
[vpc_id
]["subnets"])
390 self
.get_network_details(
391 {"tenant_id": vpc_id
}, detail
="cidr_block"
396 vpc
= self
.get_tenant_list({"id": vpc_id
})[0]
397 subnet_list
= self
.subnet_sizes(
398 len(self
.get_availability_zones_list()), vpc
["cidr_block"]
403 self
.get_network_details(
404 {"tenant_id": vpc
["id"]}, detail
="cidr_block"
409 subnet
= self
.conn_vpc
.create_subnet(vpc_id
, cidr_block
)
411 return subnet
.id, created_items
412 except Exception as e
:
413 self
.format_vimconn_exception(e
)
415 def get_network_details(self
, filters
, detail
):
416 """Get specified details related to a subnet"""
418 subnet_list
= self
.get_network_list(filters
)
420 for net
in subnet_list
:
421 detail_list
.append(net
[detail
])
425 def get_network_list(self
, filter_dict
={}):
426 """Obtain tenant networks of VIM
428 'filter_dict' (optional) contains entries to return only networks that matches ALL entries:
429 name: string => returns only networks with this name
430 id: string => returns networks with this VIM id, this imply returns one network at most
431 shared: boolean >= returns only networks that are (or are not) shared
432 tenant_id: sting => returns only networks that belong to this tenant/project
433 ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin
435 #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
436 Returns the network list of dictionaries. each dictionary contains:
437 'id': (mandatory) VIM network id
438 'name': (mandatory) VIM network name
439 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
440 'error_msg': (optional) text that explains the ERROR status
441 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
442 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
443 authorization, or some other unspecific error
445 self
.logger
.debug("Getting all subnets from VIM")
448 self
._reload
_connection
()
451 if filter_dict
!= {}:
452 if "tenant_id" in filter_dict
:
453 tfilters
["vpcId"] = filter_dict
["tenant_id"]
455 subnets
= self
.conn_vpc
.get_all_subnets(
456 subnet_ids
=filter_dict
.get("name", None), filters
=tfilters
465 "status": str(net
.state
),
466 "vpc_id": str(net
.vpc_id
),
467 "cidr_block": str(net
.cidr_block
),
473 except Exception as e
:
474 self
.format_vimconn_exception(e
)
476 def get_network(self
, net_id
):
477 """Obtain network details from the 'net_id' VIM network
478 Return a dict that contains:
479 'id': (mandatory) VIM network id, that is, net_id
480 'name': (mandatory) VIM network name
481 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
482 'error_msg': (optional) text that explains the ERROR status
483 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
484 Raises an exception upon error or when network is not found
486 self
.logger
.debug("Getting Subnet from VIM")
489 self
._reload
_connection
()
490 subnet
= self
.conn_vpc
.get_all_subnets(net_id
)[0]
493 "id": str(subnet
.id),
494 "name": str(subnet
.id),
495 "status": str(subnet
.state
),
496 "vpc_id": str(subnet
.vpc_id
),
497 "cidr_block": str(subnet
.cidr_block
),
499 except Exception as e
:
500 self
.format_vimconn_exception(e
)
502 def delete_network(self
, net_id
, created_items
=None):
504 Removes a tenant network from VIM and its associated elements
505 :param net_id: VIM identifier of the network, provided by method new_network
506 :param created_items: dictionary with extra items to be deleted. provided by method new_network
507 Returns the network identifier or raises an exception upon error or when network is not found
509 self
.logger
.debug("Deleting subnet from VIM")
512 self
._reload
_connection
()
513 self
.logger
.debug("DELETING NET_ID: " + str(net_id
))
514 self
.conn_vpc
.delete_subnet(net_id
)
517 except Exception as e
:
518 self
.format_vimconn_exception(e
)
520 def refresh_nets_status(self
, net_list
):
521 """Get the status of the networks
523 'net_list': a list with the VIM network id to be get the status
524 Returns a dictionary with:
525 'net_id': #VIM id of this network
526 status: #Mandatory. Text with one of:
527 # DELETED (not found at vim)
528 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
529 # OTHER (Vim reported other status not understood)
530 # ERROR (VIM indicates an ERROR status)
531 # ACTIVE, INACTIVE, DOWN (admin down),
532 # BUILD (on building process)
533 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
534 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
537 self
._reload
_connection
()
542 for net_id
in net_list
:
547 subnet
= self
.conn_vpc
.get_all_subnets(net_id
)[0]
549 if subnet
.state
== "pending":
550 subnet_dict
["status"] = "BUILD"
551 elif subnet
.state
== "available":
552 subnet_dict
["status"] = "ACTIVE"
554 subnet_dict
["status"] = "ERROR"
555 subnet_dict
["error_msg"] = ""
557 subnet_dict
["status"] = "DELETED"
558 subnet_dict
["error_msg"] = "Network not found"
561 subnet_dict
["vim_info"] = yaml
.safe_dump(
562 subnet
, default_flow_style
=True, width
=256
564 except yaml
.YAMLError
:
565 subnet_dict
["vim_info"] = str(subnet
)
567 dict_entry
[net_id
] = subnet_dict
570 except Exception as e
:
571 self
.format_vimconn_exception(e
)
573 def get_flavor(self
, flavor_id
):
574 """Obtain flavor details from the VIM
575 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
576 Raises an exception upon error or if not found
578 self
.logger
.debug("Getting instance type")
581 if flavor_id
in self
.flavor_info
:
582 return self
.flavor_info
[flavor_id
]
584 raise vimconn
.VimConnNotFoundException(
585 "Cannot find flavor with this flavor ID/Name"
587 except Exception as e
:
588 self
.format_vimconn_exception(e
)
590 def get_flavor_id_from_data(self
, flavor_dict
):
591 """Obtain flavor id that match the flavor description
593 'flavor_dict': dictionary that contains:
594 'disk': main hard disk in GB
596 'vcpus': number of virtual cpus
597 #todo: complete parameters for EPA
598 Returns the flavor_id or raises a vimconnNotFoundException
600 self
.logger
.debug("Getting flavor id from data")
604 for key
, values
in self
.flavor_info
.items():
605 if (values
["ram"], values
["cpus"], values
["disk"]) == (
607 flavor_dict
["vcpus"],
610 flavor
= (key
, values
)
612 elif (values
["ram"], values
["cpus"], values
["disk"]) >= (
614 flavor_dict
["vcpus"],
618 flavor
= (key
, values
)
620 if (flavor
[1]["ram"], flavor
[1]["cpus"], flavor
[1]["disk"]) >= (
625 flavor
= (key
, values
)
630 raise vimconn
.VimConnNotFoundException(
631 "Cannot find flavor with this flavor ID/Name"
633 except Exception as e
:
634 self
.format_vimconn_exception(e
)
636 def new_image(self
, image_dict
):
637 """Adds a tenant image to VIM
639 name (string) - The name of the AMI. Valid only for EBS-based images.
640 description (string) - The description of the AMI.
641 image_location (string) - Full path to your AMI manifest in Amazon S3 storage. Only used for S3-based AMI’s.
642 architecture (string) - The architecture of the AMI. Valid choices are: * i386 * x86_64
643 kernel_id (string) - The ID of the kernel with which to launch the instances
644 root_device_name (string) - The root device name (e.g. /dev/sdh)
645 block_device_map (boto.ec2.blockdevicemapping.BlockDeviceMapping) - A BlockDeviceMapping data structure
646 describing the EBS volumes associated with the Image.
647 virtualization_type (string) - The virutalization_type of the image. Valid choices are: * paravirtual * hvm
648 sriov_net_support (string) - Advanced networking support. Valid choices are: * simple
649 snapshot_id (string) - A snapshot ID for the snapshot to be used as root device for the image. Mutually
650 exclusive with block_device_map, requires root_device_name
651 delete_root_volume_on_termination (bool) - Whether to delete the root volume of the image after instance
652 termination. Only applies when creating image from snapshot_id. Defaults to False. Note that leaving
653 volumes behind after instance termination is not free
654 Returns: image_id - image ID of the newly created image
657 self
._reload
_connection
()
658 image_location
= image_dict
.get("image_location", None)
661 image_location
= str(self
.account_id
) + str(image_location
)
663 image_id
= self
.conn
.register_image(
664 image_dict
.get("name", None),
665 image_dict
.get("description", None),
667 image_dict
.get("architecture", None),
668 image_dict
.get("kernel_id", None),
669 image_dict
.get("root_device_name", None),
670 image_dict
.get("block_device_map", None),
671 image_dict
.get("virtualization_type", None),
672 image_dict
.get("sriov_net_support", None),
673 image_dict
.get("snapshot_id", None),
674 image_dict
.get("delete_root_volume_on_termination", None),
678 except Exception as e
:
679 self
.format_vimconn_exception(e
)
681 def delete_image(self
, image_id
):
682 """Deletes a tenant image from VIM
683 Returns the image_id if image is deleted or raises an exception on error"""
686 self
._reload
_connection
()
687 self
.conn
.deregister_image(image_id
)
690 except Exception as e
:
691 self
.format_vimconn_exception(e
)
693 def get_image_id_from_path(self
, path
):
695 Params: path - location of the image
696 Returns: image_id - ID of the matching image
698 self
._reload
_connection
()
703 tokens
= path
.split("/")
704 filters
["owner_id"] = tokens
[0]
705 filters
["name"] = "/".join(tokens
[1:])
707 image
= self
.conn
.get_all_images(filters
=filters
)[0]
710 except Exception as e
:
711 self
.format_vimconn_exception(e
)
713 def get_image_list(self
, filter_dict
={}):
714 """Obtain tenant images from VIM
718 checksum: image checksum
720 Returns the image list of dictionaries:
721 [{<the fields at Filter_dict plus some VIM specific>}, ...]
724 self
.logger
.debug("Getting image list from VIM")
727 self
._reload
_connection
()
731 if "id" in filter_dict
:
732 image_id
= filter_dict
["id"]
734 if "name" in filter_dict
:
735 filters
["name"] = filter_dict
["name"]
737 if "location" in filter_dict
:
738 filters
["location"] = filter_dict
["location"]
740 # filters['image_type'] = 'machine'
741 # filter_dict['owner_id'] = self.account_id
742 images
= self
.conn
.get_all_images(image_id
, filters
=filters
)
749 "name": str(image
.name
),
750 "status": str(image
.state
),
751 "owner": str(image
.owner_id
),
752 "location": str(image
.location
),
753 "is_public": str(image
.is_public
),
754 "architecture": str(image
.architecture
),
755 "platform": str(image
.platform
),
760 except Exception as e
:
761 self
.format_vimconn_exception(e
)
774 availability_zone_index
=None,
775 availability_zone_list
=None,
777 """Create a new VM/instance in AWS
780 start: (boolean) indicates if VM must start or created in pause mode.
781 image_id - image ID in AWS
782 flavor_id - instance type ID in AWS
785 net_id - subnet_id from AWS
786 vpci - (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
788 model: (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
789 mac_address: (optional) mac address to assign to this interface
790 type: (mandatory) can be one of:
791 virtual, in this case always connected to a network of type 'net_type=bridge'
792 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a
793 data/ptp network ot it
794 can created unconnected
795 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
796 VFnotShared - (SRIOV without VLAN tag) same as PF for network connectivity. VF where no other
797 VFs are allocated on the same physical NIC
798 bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
799 port_security': (optional) If False it must avoid any traffic filtering at this interface.
800 If missing or True, it must apply the default VIM behaviour
801 vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
802 interface. 'net_list' is modified
803 elastic_ip - True/False to define if an elastic_ip is required
804 cloud_config': (optional) dictionary with:
805 key-pairs': (optional) list of strings with the public key to be inserted to the default user
806 users': (optional) list of users to be inserted, each item is a dict with:
807 name': (mandatory) user name,
808 key-pairs': (optional) list of strings with the public key to be inserted to the user
809 user-data': (optional) string is a text script to be passed directly to cloud-init
810 config-files': (optional). List of files to be transferred. Each item is a dict with:
811 dest': (mandatory) string with the destination absolute path
812 encoding': (optional, by default text). Can be one of:
813 b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
814 content' (mandatory): string with the content of the file
815 permissions': (optional) string with file permissions, typically octal notation '0644'
816 owner: (optional) file owner, string with the format 'owner:group'
817 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
821 disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
822 image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
823 size': (mandatory) string with the size of the disk in GB
824 Returns a tuple with the instance identifier and created_items or raises an exception on error
825 created_items can be None or a dictionary where this method can include key-values that will be passed to
826 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
827 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
830 self
.logger
.debug("Creating a new VM instance")
833 self
._reload
_connection
()
835 _
, userdata
= self
._create
_user
_data
(cloud_config
)
838 reservation
= self
.conn
.run_instances(
840 key_name
=self
.key_pair
,
841 instance_type
=flavor_id
,
842 security_groups
=self
.security_groups
,
846 for index
, subnet
in enumerate(net_list
):
847 net_intr
= boto
.ec2
.networkinterface
.NetworkInterfaceSpecification(
848 subnet_id
=subnet
.get("net_id"),
850 associate_public_ip_address
=True,
853 if subnet
.get("elastic_ip"):
854 eip
= self
.conn
.allocate_address()
855 self
.conn
.associate_address(
856 allocation_id
=eip
.allocation_id
,
857 network_interface_id
=net_intr
.id,
861 reservation
= self
.conn
.run_instances(
863 key_name
=self
.key_pair
,
864 instance_type
=flavor_id
,
865 security_groups
=self
.security_groups
,
866 network_interfaces
=boto
.ec2
.networkinterface
.NetworkInterfaceCollection(
874 self
.conn
.attach_network_interface(
875 network_interface_id
=boto
.ec2
.networkinterface
.NetworkInterfaceCollection(
878 instance_id
=instance
.id,
885 net_list
[index
]["vim_id"] = (
886 reservation
.instances
[0].interfaces
[index
].id
889 instance
= reservation
.instances
[0]
891 return instance
.id, None
892 except Exception as e
:
893 self
.format_vimconn_exception(e
)
895 def get_vminstance(self
, vm_id
):
896 """Returns the VM instance information from VIM"""
898 self
._reload
_connection
()
899 reservation
= self
.conn
.get_all_instances(vm_id
)
901 return reservation
[0].instances
[0].__dict
__
902 except Exception as e
:
903 self
.format_vimconn_exception(e
)
905 def delete_vminstance(self
, vm_id
, created_items
=None):
906 """Removes a VM instance from VIM
907 Returns the instance identifier"""
909 self
._reload
_connection
()
910 self
.logger
.debug("DELETING VM_ID: " + str(vm_id
))
911 self
.conn
.terminate_instances(vm_id
)
914 except Exception as e
:
915 self
.format_vimconn_exception(e
)
917 def refresh_vms_status(self
, vm_list
):
918 """Get the status of the virtual machines and their interfaces/ports
919 Params: the list of VM identifiers
920 Returns a dictionary with:
921 vm_id: #VIM id of this Virtual Machine
922 status: #Mandatory. Text with one of:
923 # DELETED (not found at vim)
924 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
925 # OTHER (Vim reported other status not understood)
926 # ERROR (VIM indicates an ERROR status)
927 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
928 # BUILD (on building process), ERROR
929 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
931 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
932 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
933 interfaces: list with interface info. Each item a dictionary with:
934 vim_interface_id - The ID of the ENI.
935 vim_net_id - The ID of the VPC subnet.
936 mac_address - The MAC address of the interface.
937 ip_address - The IP address of the interface within the subnet.
939 self
.logger
.debug("Getting VM instance information from VIM")
942 self
._reload
_connection
()
943 reservation
= self
.conn
.get_all_instances(vm_list
)[0]
947 for instance
in reservation
.instances
:
949 if instance
.state
in ("pending"):
950 instance_dict
["status"] = "BUILD"
951 elif instance
.state
in ("available", "running", "up"):
952 instance_dict
["status"] = "ACTIVE"
954 instance_dict
["status"] = "ERROR"
956 instance_dict
["error_msg"] = ""
957 instance_dict
["interfaces"] = []
960 for interface
in instance
.interfaces
:
961 interface_dict
["vim_interface_id"] = interface
.id
962 interface_dict
["vim_net_id"] = interface
.subnet_id
963 interface_dict
["mac_address"] = interface
.mac_address
966 hasattr(interface
, "publicIp")
967 and interface
.publicIp
is not None
969 interface_dict
["ip_address"] = (
970 interface
.publicIp
+ ";" + interface
.private_ip_address
973 interface_dict
["ip_address"] = interface
.private_ip_address
975 instance_dict
["interfaces"].append(interface_dict
)
976 except Exception as e
:
978 "Exception getting vm status: %s", str(e
), exc_info
=True
980 instance_dict
["status"] = "DELETED"
981 instance_dict
["error_msg"] = str(e
)
984 instance_dict
["vim_info"] = yaml
.safe_dump(
985 instance
, default_flow_style
=True, width
=256
987 except yaml
.YAMLError
:
988 # self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
989 instance_dict
["vim_info"] = str(instance
)
991 instances
[instance
.id] = instance_dict
994 except Exception as e
:
995 self
.logger
.error("Exception getting vm status: %s", str(e
), exc_info
=True)
996 self
.format_vimconn_exception(e
)
998 def action_vminstance(self
, vm_id
, action_dict
, created_items
={}):
999 """Send and action over a VM instance from VIM
1000 Returns the vm_id if the action was successfully sent to the VIM"""
1002 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1004 self
._reload
_connection
()
1005 if "start" in action_dict
:
1006 self
.conn
.start_instances(vm_id
)
1007 elif "stop" in action_dict
or "stop" in action_dict
:
1008 self
.conn
.stop_instances(vm_id
)
1009 elif "terminate" in action_dict
:
1010 self
.conn
.terminate_instances(vm_id
)
1011 elif "reboot" in action_dict
:
1012 self
.conn
.reboot_instances(vm_id
)
1015 except Exception as e
:
1016 self
.format_vimconn_exception(e
)