Reformatting RO
[osm/RO.git] / RO-VIM-aws / osm_rovim_aws / vimconn_aws.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2017 xFlow Research Pvt. Ltd
5 # This file is part of openmano
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: saboor.ahmad@xflowresearch.com
22 ##
23
24 """
25 AWS-connector implements all the methods to interact with AWS using the BOTO client
26 """
27
28 __author__ = "Saboor Ahmad"
29 __date__ = "10-Apr-2017"
30
31 from osm_ro_plugin import vimconn
32 import yaml
33 import logging
34 import netaddr
35 import time
36
37 import boto
38 import boto.ec2
39 import boto.vpc
40
41
42 class vimconnector(vimconn.VimConnector):
43 def __init__(
44 self,
45 uuid,
46 name,
47 tenant_id,
48 tenant_name,
49 url,
50 url_admin=None,
51 user=None,
52 passwd=None,
53 log_level=None,
54 config={},
55 persistent_info={},
56 ):
57 """Params:
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
73 """
74 vimconn.VimConnector.__init__(
75 self,
76 uuid,
77 name,
78 tenant_id,
79 tenant_name,
80 url,
81 url_admin,
82 user,
83 passwd,
84 log_level,
85 config,
86 persistent_info,
87 )
88
89 self.persistent_info = persistent_info
90 self.a_creds = {}
91
92 if user:
93 self.a_creds["aws_access_key_id"] = user
94 else:
95 raise vimconn.VimConnAuthException("Username is not specified")
96
97 if passwd:
98 self.a_creds["aws_secret_access_key"] = passwd
99 else:
100 raise vimconn.VimConnAuthException("Password is not specified")
101
102 if "region_name" in config:
103 self.region = config.get("region_name")
104 else:
105 raise vimconn.VimConnException("AWS region_name is not specified at config")
106
107 self.vpc_data = {}
108 self.subnet_data = {}
109 self.conn = None
110 self.conn_vpc = None
111 self.account_id = None
112
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"
117
118 if tenant_id:
119 self.vpc_id = tenant_id
120
121 if "vpc_cidr_block" in config:
122 self.vpc_cidr_block = config["vpc_cidr_block"]
123
124 self.security_groups = None
125 if "security_groups" in config:
126 self.security_groups = config["security_groups"]
127
128 self.key_pair = None
129 if "key_pair" in config:
130 self.key_pair = config["key_pair"]
131
132 self.flavor_info = None
133 if "flavor_info" in config:
134 flavor_data = config.get("flavor_info")
135 if isinstance(flavor_data, str):
136 try:
137 if flavor_data[0] == "@": # read from a file
138 with open(flavor_data[1:], "r") as stream:
139 self.flavor_info = yaml.load(stream, Loader=yaml.Loader)
140 else:
141 self.flavor_info = yaml.load(flavor_data, Loader=yaml.Loader)
142 except yaml.YAMLError as e:
143 self.flavor_info = None
144
145 raise vimconn.VimConnException(
146 "Bad format at file '{}': {}".format(flavor_data[1:], e)
147 )
148 except IOError as e:
149 raise vimconn.VimConnException(
150 "Error reading file '{}': {}".format(flavor_data[1:], e)
151 )
152 elif isinstance(flavor_data, dict):
153 self.flavor_info = flavor_data
154
155 self.logger = logging.getLogger("ro.vim.aws")
156
157 if log_level:
158 self.logger.setLevel(getattr(logging, log_level))
159
160 def __setitem__(self, index, value):
161 """Params:
162 index - name of value of set
163 value - value to set
164 """
165 if index == "user":
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":
170 self.region = value
171 else:
172 vimconn.VimConnector.__setitem__(self, index, value)
173
174 def _reload_connection(self):
175 """Returns: sets boto.EC2 and boto.VPC connection to work with AWS services"""
176 try:
177 self.conn = boto.ec2.connect_to_region(
178 self.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"],
181 )
182 self.conn_vpc = boto.vpc.connect_to_region(
183 self.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"],
186 )
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)
192
193 def format_vimconn_exception(self, e):
194 """Params: an Exception object
195 Returns: Raises the exception 'e' passed in mehtod parameters
196 """
197 self.conn = None
198 self.conn_vpc = None
199
200 raise vimconn.VimConnConnectionException(type(e).__name__ + ": " + str(e))
201
202 def get_availability_zones_list(self):
203 """Obtain AvailabilityZones from AWS"""
204 try:
205 self._reload_connection()
206 az_list = []
207
208 for az in self.conn.get_all_zones():
209 az_list.append(az.name)
210
211 return az_list
212 except Exception as e:
213 self.format_vimconn_exception(e)
214
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
220 <other VIM specific>
221 Returns the tenant list of dictionaries, and empty list if no tenant match all the filers:
222 [{'name':'<name>, 'id':'<id>, ...}, ...]
223 """
224 try:
225 self._reload_connection()
226 vpc_ids = []
227 tfilters = {}
228
229 if filter_dict != {}:
230 if "id" in filter_dict:
231 vpc_ids.append(filter_dict["id"])
232 tfilters["name"] = filter_dict["id"]
233
234 tenants = self.conn_vpc.get_all_vpcs(vpc_ids, tfilters)
235 tenant_list = []
236
237 for tenant in tenants:
238 tenant_list.append(
239 {
240 "id": str(tenant.id),
241 "name": str(tenant.id),
242 "status": str(tenant.state),
243 "cidr_block": str(tenant.cidr_block),
244 }
245 )
246
247 return tenant_list
248 except Exception as e:
249 self.format_vimconn_exception(e)
250
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
256 """
257 self.logger.debug("Adding a new VPC")
258
259 try:
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)
264
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)
269
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
275 ),
276 }
277
278 return vpc.id
279 except Exception as e:
280 self.format_vimconn_exception(e)
281
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
286 """
287 self.logger.debug("Deleting specified VPC")
288
289 try:
290 self._reload_connection()
291 vpc = self.vpc_data.get(tenant_id)
292
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")
298 else:
299 self.conn_vpc.delete_vpc(tenant_id)
300 except Exception as e:
301 self.format_vimconn_exception(e)
302
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")
307
308 raise vimconn.VimConnNotSupportedException("Number of AZs should be 2 or 3")
309
310 netmasks = (
311 "255.255.252.0",
312 "255.255.254.0",
313 "255.255.255.0",
314 "255.255.255.128",
315 )
316 ip = netaddr.IPNetwork(cidr)
317 mask = ip.netmask
318
319 if str(mask) not in netmasks:
320 self.logger.debug("Netmask " + str(mask) + " not found")
321
322 raise vimconn.VimConnNotFoundException(
323 "Netmask " + str(mask) + " not found"
324 )
325
326 if availability_zones == 2:
327 for n, netmask in enumerate(netmasks):
328 if str(mask) == netmask:
329 subnets = list(ip.subnet(n + 24))
330 else:
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
336
337 pub_split = (
338 list(ip.subnet(26))
339 if (str(pub_mask) == "255.255.255.0")
340 else list(ip.subnet(27))
341 )
342 pub_subs = pub_split[:3]
343 subnets = pub_subs + pri_subs
344
345 return map(str, subnets)
346
347 def new_network(
348 self,
349 net_name,
350 net_type,
351 ip_profile=None,
352 shared=False,
353 provider_network_profile=None,
354 ):
355 """Adds a tenant network to VIM
356 Params:
357 'net_name': name of the network
358 'net_type': one of:
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
376 as not present.
377 """
378 self.logger.debug("Adding a subnet to VPC")
379
380 try:
381 created_items = {}
382 self._reload_connection()
383 subnet = None
384 vpc_id = self.vpc_id
385
386 if self.vpc_data.get(vpc_id, None):
387 cidr_block = list(
388 set(self.vpc_data[vpc_id]["subnets"])
389 - set(
390 self.get_network_details(
391 {"tenant_id": vpc_id}, detail="cidr_block"
392 )
393 )
394 )[0]
395 else:
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"]
399 )
400 cidr_block = list(
401 set(subnet_list)
402 - set(
403 self.get_network_details(
404 {"tenant_id": vpc["id"]}, detail="cidr_block"
405 )
406 )
407 )[0]
408
409 subnet = self.conn_vpc.create_subnet(vpc_id, cidr_block)
410
411 return subnet.id, created_items
412 except Exception as e:
413 self.format_vimconn_exception(e)
414
415 def get_network_details(self, filters, detail):
416 """Get specified details related to a subnet"""
417 detail_list = []
418 subnet_list = self.get_network_list(filters)
419
420 for net in subnet_list:
421 detail_list.append(net[detail])
422
423 return detail_list
424
425 def get_network_list(self, filter_dict={}):
426 """Obtain tenant networks of VIM
427 Params:
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
434 state active
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
444 """
445 self.logger.debug("Getting all subnets from VIM")
446
447 try:
448 self._reload_connection()
449 tfilters = {}
450
451 if filter_dict != {}:
452 if "tenant_id" in filter_dict:
453 tfilters["vpcId"] = filter_dict["tenant_id"]
454
455 subnets = self.conn_vpc.get_all_subnets(
456 subnet_ids=filter_dict.get("name", None), filters=tfilters
457 )
458 net_list = []
459
460 for net in subnets:
461 net_list.append(
462 {
463 "id": str(net.id),
464 "name": str(net.id),
465 "status": str(net.state),
466 "vpc_id": str(net.vpc_id),
467 "cidr_block": str(net.cidr_block),
468 "type": "bridge",
469 }
470 )
471
472 return net_list
473 except Exception as e:
474 self.format_vimconn_exception(e)
475
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
485 """
486 self.logger.debug("Getting Subnet from VIM")
487
488 try:
489 self._reload_connection()
490 subnet = self.conn_vpc.get_all_subnets(net_id)[0]
491
492 return {
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),
498 }
499 except Exception as e:
500 self.format_vimconn_exception(e)
501
502 def delete_network(self, net_id, created_items=None):
503 """
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
508 """
509 self.logger.debug("Deleting subnet from VIM")
510
511 try:
512 self._reload_connection()
513 self.logger.debug("DELETING NET_ID: " + str(net_id))
514 self.conn_vpc.delete_subnet(net_id)
515
516 return net_id
517 except Exception as e:
518 self.format_vimconn_exception(e)
519
520 def refresh_nets_status(self, net_list):
521 """Get the status of the networks
522 Params:
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)
535 'net_id2': ...
536 """
537 self._reload_connection()
538
539 try:
540 dict_entry = {}
541
542 for net_id in net_list:
543 subnet_dict = {}
544 subnet = None
545
546 try:
547 subnet = self.conn_vpc.get_all_subnets(net_id)[0]
548
549 if subnet.state == "pending":
550 subnet_dict["status"] = "BUILD"
551 elif subnet.state == "available":
552 subnet_dict["status"] = "ACTIVE"
553 else:
554 subnet_dict["status"] = "ERROR"
555 subnet_dict["error_msg"] = ""
556 except Exception:
557 subnet_dict["status"] = "DELETED"
558 subnet_dict["error_msg"] = "Network not found"
559 finally:
560 try:
561 subnet_dict["vim_info"] = yaml.safe_dump(
562 subnet, default_flow_style=True, width=256
563 )
564 except yaml.YAMLError:
565 subnet_dict["vim_info"] = str(subnet)
566
567 dict_entry[net_id] = subnet_dict
568
569 return dict_entry
570 except Exception as e:
571 self.format_vimconn_exception(e)
572
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
577 """
578 self.logger.debug("Getting instance type")
579
580 try:
581 if flavor_id in self.flavor_info:
582 return self.flavor_info[flavor_id]
583 else:
584 raise vimconn.VimConnNotFoundException(
585 "Cannot find flavor with this flavor ID/Name"
586 )
587 except Exception as e:
588 self.format_vimconn_exception(e)
589
590 def get_flavor_id_from_data(self, flavor_dict):
591 """Obtain flavor id that match the flavor description
592 Params:
593 'flavor_dict': dictionary that contains:
594 'disk': main hard disk in GB
595 'ram': memory in MB
596 'vcpus': number of virtual cpus
597 #todo: complete parameters for EPA
598 Returns the flavor_id or raises a vimconnNotFoundException
599 """
600 self.logger.debug("Getting flavor id from data")
601
602 try:
603 flavor = None
604 for key, values in self.flavor_info.items():
605 if (values["ram"], values["cpus"], values["disk"]) == (
606 flavor_dict["ram"],
607 flavor_dict["vcpus"],
608 flavor_dict["disk"],
609 ):
610 flavor = (key, values)
611 break
612 elif (values["ram"], values["cpus"], values["disk"]) >= (
613 flavor_dict["ram"],
614 flavor_dict["vcpus"],
615 flavor_dict["disk"],
616 ):
617 if not flavor:
618 flavor = (key, values)
619 else:
620 if (flavor[1]["ram"], flavor[1]["cpus"], flavor[1]["disk"]) >= (
621 values["ram"],
622 values["cpus"],
623 values["disk"],
624 ):
625 flavor = (key, values)
626
627 if flavor:
628 return flavor[0]
629
630 raise vimconn.VimConnNotFoundException(
631 "Cannot find flavor with this flavor ID/Name"
632 )
633 except Exception as e:
634 self.format_vimconn_exception(e)
635
636 def new_image(self, image_dict):
637 """Adds a tenant image to VIM
638 Params: image_dict
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
655 """
656 try:
657 self._reload_connection()
658 image_location = image_dict.get("image_location", None)
659
660 if image_location:
661 image_location = str(self.account_id) + str(image_location)
662
663 image_id = self.conn.register_image(
664 image_dict.get("name", None),
665 image_dict.get("description", None),
666 image_location,
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),
675 )
676
677 return image_id
678 except Exception as e:
679 self.format_vimconn_exception(e)
680
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"""
684
685 try:
686 self._reload_connection()
687 self.conn.deregister_image(image_id)
688
689 return image_id
690 except Exception as e:
691 self.format_vimconn_exception(e)
692
693 def get_image_id_from_path(self, path):
694 """
695 Params: path - location of the image
696 Returns: image_id - ID of the matching image
697 """
698 self._reload_connection()
699 try:
700 filters = {}
701
702 if path:
703 tokens = path.split("/")
704 filters["owner_id"] = tokens[0]
705 filters["name"] = "/".join(tokens[1:])
706
707 image = self.conn.get_all_images(filters=filters)[0]
708
709 return image.id
710 except Exception as e:
711 self.format_vimconn_exception(e)
712
713 def get_image_list(self, filter_dict={}):
714 """Obtain tenant images from VIM
715 Filter_dict can be:
716 name: image name
717 id: image uuid
718 checksum: image checksum
719 location: image path
720 Returns the image list of dictionaries:
721 [{<the fields at Filter_dict plus some VIM specific>}, ...]
722 List can be empty
723 """
724 self.logger.debug("Getting image list from VIM")
725
726 try:
727 self._reload_connection()
728 image_id = None
729 filters = {}
730
731 if "id" in filter_dict:
732 image_id = filter_dict["id"]
733
734 if "name" in filter_dict:
735 filters["name"] = filter_dict["name"]
736
737 if "location" in filter_dict:
738 filters["location"] = filter_dict["location"]
739
740 # filters['image_type'] = 'machine'
741 # filter_dict['owner_id'] = self.account_id
742 images = self.conn.get_all_images(image_id, filters=filters)
743 image_list = []
744
745 for image in images:
746 image_list.append(
747 {
748 "id": str(image.id),
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),
756 }
757 )
758
759 return image_list
760 except Exception as e:
761 self.format_vimconn_exception(e)
762
763 def new_vminstance(
764 self,
765 name,
766 description,
767 start,
768 image_id,
769 flavor_id,
770 net_list,
771 cloud_config=None,
772 disk_list=None,
773 availability_zone_index=None,
774 availability_zone_list=None,
775 ):
776 """Create a new VM/instance in AWS
777 Params: name
778 decription
779 start: (boolean) indicates if VM must start or created in pause mode.
780 image_id - image ID in AWS
781 flavor_id - instance type ID in AWS
782 net_list
783 name
784 net_id - subnet_id from AWS
785 vpci - (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
786 capabilities
787 model: (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
788 mac_address: (optional) mac address to assign to this interface
789 type: (mandatory) can be one of:
790 virtual, in this case always connected to a network of type 'net_type=bridge'
791 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a
792 data/ptp network ot it
793 can created unconnected
794 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
795 VFnotShared - (SRIOV without VLAN tag) same as PF for network connectivity. VF where no other
796 VFs are allocated on the same physical NIC
797 bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
798 port_security': (optional) If False it must avoid any traffic filtering at this interface.
799 If missing or True, it must apply the default VIM behaviour
800 vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
801 interface. 'net_list' is modified
802 elastic_ip - True/False to define if an elastic_ip is required
803 cloud_config': (optional) dictionary with:
804 key-pairs': (optional) list of strings with the public key to be inserted to the default user
805 users': (optional) list of users to be inserted, each item is a dict with:
806 name': (mandatory) user name,
807 key-pairs': (optional) list of strings with the public key to be inserted to the user
808 user-data': (optional) string is a text script to be passed directly to cloud-init
809 config-files': (optional). List of files to be transferred. Each item is a dict with:
810 dest': (mandatory) string with the destination absolute path
811 encoding': (optional, by default text). Can be one of:
812 b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
813 content' (mandatory): string with the content of the file
814 permissions': (optional) string with file permissions, typically octal notation '0644'
815 owner: (optional) file owner, string with the format 'owner:group'
816 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
817 security-groups:
818 subnet_id
819 security_group_id
820 disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
821 image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
822 size': (mandatory) string with the size of the disk in GB
823 Returns a tuple with the instance identifier and created_items or raises an exception on error
824 created_items can be None or a dictionary where this method can include key-values that will be passed to
825 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
826 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
827 as not present.
828 """
829 self.logger.debug("Creating a new VM instance")
830
831 try:
832 self._reload_connection()
833 instance = None
834 _, userdata = self._create_user_data(cloud_config)
835
836 if not net_list:
837 reservation = self.conn.run_instances(
838 image_id,
839 key_name=self.key_pair,
840 instance_type=flavor_id,
841 security_groups=self.security_groups,
842 user_data=userdata,
843 )
844 else:
845 for index, subnet in enumerate(net_list):
846 net_intr = boto.ec2.networkinterface.NetworkInterfaceSpecification(
847 subnet_id=subnet.get("net_id"),
848 groups=None,
849 associate_public_ip_address=True,
850 )
851
852 if subnet.get("elastic_ip"):
853 eip = self.conn.allocate_address()
854 self.conn.associate_address(
855 allocation_id=eip.allocation_id,
856 network_interface_id=net_intr.id,
857 )
858
859 if index == 0:
860 reservation = self.conn.run_instances(
861 image_id,
862 key_name=self.key_pair,
863 instance_type=flavor_id,
864 security_groups=self.security_groups,
865 network_interfaces=boto.ec2.networkinterface.NetworkInterfaceCollection(
866 net_intr
867 ),
868 user_data=userdata,
869 )
870 else:
871 while True:
872 try:
873 self.conn.attach_network_interface(
874 network_interface_id=boto.ec2.networkinterface.NetworkInterfaceCollection(
875 net_intr
876 ),
877 instance_id=instance.id,
878 device_index=0,
879 )
880 break
881 except Exception:
882 time.sleep(10)
883
884 net_list[index]["vim_id"] = (
885 reservation.instances[0].interfaces[index].id
886 )
887
888 instance = reservation.instances[0]
889
890 return instance.id, None
891 except Exception as e:
892 self.format_vimconn_exception(e)
893
894 def get_vminstance(self, vm_id):
895 """Returns the VM instance information from VIM"""
896 try:
897 self._reload_connection()
898 reservation = self.conn.get_all_instances(vm_id)
899
900 return reservation[0].instances[0].__dict__
901 except Exception as e:
902 self.format_vimconn_exception(e)
903
904 def delete_vminstance(self, vm_id, created_items=None):
905 """Removes a VM instance from VIM
906 Returns the instance identifier"""
907 try:
908 self._reload_connection()
909 self.logger.debug("DELETING VM_ID: " + str(vm_id))
910 self.conn.terminate_instances(vm_id)
911
912 return vm_id
913 except Exception as e:
914 self.format_vimconn_exception(e)
915
916 def refresh_vms_status(self, vm_list):
917 """Get the status of the virtual machines and their interfaces/ports
918 Params: the list of VM identifiers
919 Returns a dictionary with:
920 vm_id: #VIM id of this Virtual Machine
921 status: #Mandatory. Text with one of:
922 # DELETED (not found at vim)
923 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
924 # OTHER (Vim reported other status not understood)
925 # ERROR (VIM indicates an ERROR status)
926 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
927 # BUILD (on building process), ERROR
928 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
929 #
930 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
931 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
932 interfaces: list with interface info. Each item a dictionary with:
933 vim_interface_id - The ID of the ENI.
934 vim_net_id - The ID of the VPC subnet.
935 mac_address - The MAC address of the interface.
936 ip_address - The IP address of the interface within the subnet.
937 """
938 self.logger.debug("Getting VM instance information from VIM")
939
940 try:
941 self._reload_connection()
942 reservation = self.conn.get_all_instances(vm_list)[0]
943 instances = {}
944 instance_dict = {}
945
946 for instance in reservation.instances:
947 try:
948 if instance.state in ("pending"):
949 instance_dict["status"] = "BUILD"
950 elif instance.state in ("available", "running", "up"):
951 instance_dict["status"] = "ACTIVE"
952 else:
953 instance_dict["status"] = "ERROR"
954
955 instance_dict["error_msg"] = ""
956 instance_dict["interfaces"] = []
957 interface_dict = {}
958
959 for interface in instance.interfaces:
960 interface_dict["vim_interface_id"] = interface.id
961 interface_dict["vim_net_id"] = interface.subnet_id
962 interface_dict["mac_address"] = interface.mac_address
963
964 if (
965 hasattr(interface, "publicIp")
966 and interface.publicIp is not None
967 ):
968 interface_dict["ip_address"] = (
969 interface.publicIp + ";" + interface.private_ip_address
970 )
971 else:
972 interface_dict["ip_address"] = interface.private_ip_address
973
974 instance_dict["interfaces"].append(interface_dict)
975 except Exception as e:
976 self.logger.error(
977 "Exception getting vm status: %s", str(e), exc_info=True
978 )
979 instance_dict["status"] = "DELETED"
980 instance_dict["error_msg"] = str(e)
981 finally:
982 try:
983 instance_dict["vim_info"] = yaml.safe_dump(
984 instance, default_flow_style=True, width=256
985 )
986 except yaml.YAMLError:
987 # self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
988 instance_dict["vim_info"] = str(instance)
989
990 instances[instance.id] = instance_dict
991
992 return instances
993 except Exception as e:
994 self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
995 self.format_vimconn_exception(e)
996
997 def action_vminstance(self, vm_id, action_dict, created_items={}):
998 """Send and action over a VM instance from VIM
999 Returns the vm_id if the action was successfully sent to the VIM"""
1000
1001 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1002 try:
1003 self._reload_connection()
1004 if "start" in action_dict:
1005 self.conn.start_instances(vm_id)
1006 elif "stop" in action_dict or "stop" in action_dict:
1007 self.conn.stop_instances(vm_id)
1008 elif "terminate" in action_dict:
1009 self.conn.terminate_instances(vm_id)
1010 elif "reboot" in action_dict:
1011 self.conn.reboot_instances(vm_id)
1012
1013 return None
1014 except Exception as e:
1015 self.format_vimconn_exception(e)