blob: 5fc070410839253351399b1e0b497259800e0bb1 [file] [log] [blame]
ahmadsa61ccd642017-04-20 18:17:28 -07001# -*- 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
sousaedu80135b92021-02-17 15:05:18 +010024"""
ahmadsa61ccd642017-04-20 18:17:28 -070025AWS-connector implements all the methods to interact with AWS using the BOTO client
sousaedu80135b92021-02-17 15:05:18 +010026"""
ahmadsa61ccd642017-04-20 18:17:28 -070027import logging
aticig4e86d0c2022-06-02 20:03:13 +030028import random
ahmadsa61ccd642017-04-20 18:17:28 -070029import time
aticig4e86d0c2022-06-02 20:03:13 +030030import traceback
ahmadsa61ccd642017-04-20 18:17:28 -070031
tierno7d782ef2019-10-04 12:56:31 +000032import boto
33import boto.ec2
aticig4e86d0c2022-06-02 20:03:13 +030034from boto.exception import BotoServerError, EC2ResponseError
tierno7d782ef2019-10-04 12:56:31 +000035import boto.vpc
aticig4e86d0c2022-06-02 20:03:13 +030036from ipconflict import check_conflicts
sousaedu049cbb12022-01-05 11:39:35 +000037import netaddr
38from osm_ro_plugin import vimconn
39import yaml
40
41__author__ = "Saboor Ahmad"
42__date__ = "10-Apr-2017"
ahmadsa61ccd642017-04-20 18:17:28 -070043
44
tierno72774862020-05-04 11:44:15 +000045class vimconnector(vimconn.VimConnector):
sousaedu80135b92021-02-17 15:05:18 +010046 def __init__(
47 self,
48 uuid,
49 name,
50 tenant_id,
51 tenant_name,
52 url,
53 url_admin=None,
54 user=None,
55 passwd=None,
56 log_level=None,
57 config={},
58 persistent_info={},
59 ):
60 """Params:
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
ahmadsa61ccd642017-04-20 18:17:28 -070076 """
sousaedu80135b92021-02-17 15:05:18 +010077 vimconn.VimConnector.__init__(
78 self,
79 uuid,
80 name,
81 tenant_id,
82 tenant_name,
83 url,
84 url_admin,
85 user,
86 passwd,
87 log_level,
88 config,
89 persistent_info,
90 )
Vance Shipleyae266972017-05-12 01:58:43 +053091
92 self.persistent_info = persistent_info
ahmadsa61ccd642017-04-20 18:17:28 -070093 self.a_creds = {}
sousaedu80135b92021-02-17 15:05:18 +010094
ahmadsa61ccd642017-04-20 18:17:28 -070095 if user:
sousaedu80135b92021-02-17 15:05:18 +010096 self.a_creds["aws_access_key_id"] = user
ahmadsa61ccd642017-04-20 18:17:28 -070097 else:
tierno72774862020-05-04 11:44:15 +000098 raise vimconn.VimConnAuthException("Username is not specified")
sousaedu80135b92021-02-17 15:05:18 +010099
ahmadsa61ccd642017-04-20 18:17:28 -0700100 if passwd:
sousaedu80135b92021-02-17 15:05:18 +0100101 self.a_creds["aws_secret_access_key"] = passwd
ahmadsa61ccd642017-04-20 18:17:28 -0700102 else:
tierno72774862020-05-04 11:44:15 +0000103 raise vimconn.VimConnAuthException("Password is not specified")
sousaedu80135b92021-02-17 15:05:18 +0100104
105 if "region_name" in config:
106 self.region = config.get("region_name")
ahmadsa61ccd642017-04-20 18:17:28 -0700107 else:
tierno72774862020-05-04 11:44:15 +0000108 raise vimconn.VimConnException("AWS region_name is not specified at config")
ahmadsa61ccd642017-04-20 18:17:28 -0700109
110 self.vpc_data = {}
111 self.subnet_data = {}
112 self.conn = None
113 self.conn_vpc = None
114 self.account_id = None
aticig4e86d0c2022-06-02 20:03:13 +0300115 self.network_delete_on_termination = []
116 self.server_timeout = 180
ahmadsa61ccd642017-04-20 18:17:28 -0700117
sousaedu80135b92021-02-17 15:05:18 +0100118 self.vpc_id = self.get_tenant_list()[0]["id"]
ahmadsa61ccd642017-04-20 18:17:28 -0700119 # we take VPC CIDR block if specified, otherwise we use the default CIDR
120 # block suggested by AWS while creating instance
sousaedu80135b92021-02-17 15:05:18 +0100121 self.vpc_cidr_block = "10.0.0.0/24"
ahmadsa61ccd642017-04-20 18:17:28 -0700122
aticig4e86d0c2022-06-02 20:03:13 +0300123 if tenant_name:
124 self.vpc_id = tenant_name
sousaedu80135b92021-02-17 15:05:18 +0100125
126 if "vpc_cidr_block" in config:
127 self.vpc_cidr_block = config["vpc_cidr_block"]
ahmadsa61ccd642017-04-20 18:17:28 -0700128
129 self.security_groups = None
sousaedu80135b92021-02-17 15:05:18 +0100130 if "security_groups" in config:
131 self.security_groups = config["security_groups"]
ahmadsa61ccd642017-04-20 18:17:28 -0700132
133 self.key_pair = None
sousaedu80135b92021-02-17 15:05:18 +0100134 if "key_pair" in config:
135 self.key_pair = config["key_pair"]
ahmadsa61ccd642017-04-20 18:17:28 -0700136
137 self.flavor_info = None
sousaedu80135b92021-02-17 15:05:18 +0100138 if "flavor_info" in config:
139 flavor_data = config.get("flavor_info")
ahmadsa61ccd642017-04-20 18:17:28 -0700140 if isinstance(flavor_data, str):
141 try:
tiernoa3572692018-05-14 13:09:33 +0200142 if flavor_data[0] == "@": # read from a file
sousaedu80135b92021-02-17 15:05:18 +0100143 with open(flavor_data[1:], "r") as stream:
aticig78ac0cf2022-07-15 00:43:09 +0300144 self.flavor_info = yaml.safe_load(stream)
tiernoa3572692018-05-14 13:09:33 +0200145 else:
aticig78ac0cf2022-07-15 00:43:09 +0300146 self.flavor_info = yaml.safe_load(flavor_data)
ahmadsa61ccd642017-04-20 18:17:28 -0700147 except yaml.YAMLError as e:
148 self.flavor_info = None
sousaedu80135b92021-02-17 15:05:18 +0100149
150 raise vimconn.VimConnException(
151 "Bad format at file '{}': {}".format(flavor_data[1:], e)
152 )
ahmadsa61ccd642017-04-20 18:17:28 -0700153 except IOError as e:
sousaedu80135b92021-02-17 15:05:18 +0100154 raise vimconn.VimConnException(
155 "Error reading file '{}': {}".format(flavor_data[1:], e)
156 )
ahmadsa61ccd642017-04-20 18:17:28 -0700157 elif isinstance(flavor_data, dict):
tiernof44e9e52017-06-13 18:48:30 +0200158 self.flavor_info = flavor_data
ahmadsa61ccd642017-04-20 18:17:28 -0700159
sousaedu80135b92021-02-17 15:05:18 +0100160 self.logger = logging.getLogger("ro.vim.aws")
161
ahmadsa61ccd642017-04-20 18:17:28 -0700162 if log_level:
163 self.logger.setLevel(getattr(logging, log_level))
164
165 def __setitem__(self, index, value):
sousaedu80135b92021-02-17 15:05:18 +0100166 """Params:
167 index - name of value of set
168 value - value to set
ahmadsa61ccd642017-04-20 18:17:28 -0700169 """
sousaedu80135b92021-02-17 15:05:18 +0100170 if index == "user":
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":
ahmadsa61ccd642017-04-20 18:17:28 -0700175 self.region = value
176 else:
tierno72774862020-05-04 11:44:15 +0000177 vimconn.VimConnector.__setitem__(self, index, value)
ahmadsa61ccd642017-04-20 18:17:28 -0700178
179 def _reload_connection(self):
sousaedu80135b92021-02-17 15:05:18 +0100180 """Returns: sets boto.EC2 and boto.VPC connection to work with AWS services"""
ahmadsa61ccd642017-04-20 18:17:28 -0700181 try:
sousaedu80135b92021-02-17 15:05:18 +0100182 self.conn = boto.ec2.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 self.conn_vpc = boto.vpc.connect_to_region(
188 self.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"],
191 )
tierno1ec592d2020-06-16 15:29:47 +0000192 # 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'])
ahmadsa61ccd642017-04-20 18:17:28 -0700194 # self.account_id = client.get_caller_identity()["Account"]
195 except Exception as e:
196 self.format_vimconn_exception(e)
197
198 def format_vimconn_exception(self, e):
199 """Params: an Exception object
200 Returns: Raises the exception 'e' passed in mehtod parameters
201 """
ahmadsa61ccd642017-04-20 18:17:28 -0700202 self.conn = None
203 self.conn_vpc = None
sousaedu80135b92021-02-17 15:05:18 +0100204
tierno72774862020-05-04 11:44:15 +0000205 raise vimconn.VimConnConnectionException(type(e).__name__ + ": " + str(e))
ahmadsa61ccd642017-04-20 18:17:28 -0700206
207 def get_availability_zones_list(self):
sousaedu80135b92021-02-17 15:05:18 +0100208 """Obtain AvailabilityZones from AWS"""
ahmadsa61ccd642017-04-20 18:17:28 -0700209 try:
210 self._reload_connection()
211 az_list = []
sousaedu80135b92021-02-17 15:05:18 +0100212
ahmadsa61ccd642017-04-20 18:17:28 -0700213 for az in self.conn.get_all_zones():
214 az_list.append(az.name)
sousaedu80135b92021-02-17 15:05:18 +0100215
ahmadsa61ccd642017-04-20 18:17:28 -0700216 return az_list
217 except Exception as e:
218 self.format_vimconn_exception(e)
219
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
225 <other VIM specific>
226 Returns the tenant list of dictionaries, and empty list if no tenant match all the filers:
227 [{'name':'<name>, 'id':'<id>, ...}, ...]
228 """
ahmadsa61ccd642017-04-20 18:17:28 -0700229 try:
230 self._reload_connection()
231 vpc_ids = []
sousaedu80135b92021-02-17 15:05:18 +0100232
ahmadsa61ccd642017-04-20 18:17:28 -0700233 if filter_dict != {}:
sousaedu80135b92021-02-17 15:05:18 +0100234 if "id" in filter_dict:
235 vpc_ids.append(filter_dict["id"])
sousaedu80135b92021-02-17 15:05:18 +0100236
aticig4e86d0c2022-06-02 20:03:13 +0300237 tenants = self.conn_vpc.get_all_vpcs(vpc_ids, None)
ahmadsa61ccd642017-04-20 18:17:28 -0700238 tenant_list = []
sousaedu80135b92021-02-17 15:05:18 +0100239
ahmadsa61ccd642017-04-20 18:17:28 -0700240 for tenant in tenants:
sousaedu80135b92021-02-17 15:05:18 +0100241 tenant_list.append(
242 {
243 "id": str(tenant.id),
244 "name": str(tenant.id),
245 "status": str(tenant.state),
246 "cidr_block": str(tenant.cidr_block),
247 }
248 )
249
ahmadsa61ccd642017-04-20 18:17:28 -0700250 return tenant_list
251 except Exception as e:
252 self.format_vimconn_exception(e)
253
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
259 """
ahmadsa61ccd642017-04-20 18:17:28 -0700260 self.logger.debug("Adding a new VPC")
sousaedu80135b92021-02-17 15:05:18 +0100261
ahmadsa61ccd642017-04-20 18:17:28 -0700262 try:
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)
267
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)
sousaedu80135b92021-02-17 15:05:18 +0100271 self.conn_vpc.create_route(route_table.id, "0.0.0.0/0", gateway.id)
ahmadsa61ccd642017-04-20 18:17:28 -0700272
sousaedu80135b92021-02-17 15:05:18 +0100273 self.vpc_data[vpc.id] = {
274 "gateway": gateway.id,
275 "route_table": route_table.id,
aticig4e86d0c2022-06-02 20:03:13 +0300276 "subnets": self.subnet_sizes(self.vpc_cidr_block),
sousaedu80135b92021-02-17 15:05:18 +0100277 }
278
ahmadsa61ccd642017-04-20 18:17:28 -0700279 return vpc.id
280 except Exception as e:
281 self.format_vimconn_exception(e)
282
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
287 """
ahmadsa61ccd642017-04-20 18:17:28 -0700288 self.logger.debug("Deleting specified VPC")
sousaedu80135b92021-02-17 15:05:18 +0100289
ahmadsa61ccd642017-04-20 18:17:28 -0700290 try:
291 self._reload_connection()
292 vpc = self.vpc_data.get(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100293
294 if "gateway" in vpc and "route_table" in vpc:
295 gateway_id, route_table_id = vpc["gateway"], vpc["route_table"]
ahmadsa61ccd642017-04-20 18:17:28 -0700296 self.conn_vpc.detach_internet_gateway(gateway_id, tenant_id)
297 self.conn_vpc.delete_vpc(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100298 self.conn_vpc.delete_route(route_table_id, "0.0.0.0/0")
ahmadsa61ccd642017-04-20 18:17:28 -0700299 else:
300 self.conn_vpc.delete_vpc(tenant_id)
301 except Exception as e:
302 self.format_vimconn_exception(e)
303
aticig4e86d0c2022-06-02 20:03:13 +0300304 def subnet_sizes(self, cidr):
sousaedu80135b92021-02-17 15:05:18 +0100305 """Calculates possible subnets given CIDR value of VPC"""
sousaedu80135b92021-02-17 15:05:18 +0100306 netmasks = (
aticig4e86d0c2022-06-02 20:03:13 +0300307 "255.255.0.0",
308 "255.255.128.0",
309 "255.255.192.0",
310 "255.255.224.0",
311 "255.255.240.0",
312 "255.255.248.0",
sousaedu80135b92021-02-17 15:05:18 +0100313 )
aticig4e86d0c2022-06-02 20:03:13 +0300314
ahmadsa61ccd642017-04-20 18:17:28 -0700315 ip = netaddr.IPNetwork(cidr)
316 mask = ip.netmask
aticig4e86d0c2022-06-02 20:03:13 +0300317 pub_split = ()
ahmadsa61ccd642017-04-20 18:17:28 -0700318
aticig4e86d0c2022-06-02 20:03:13 +0300319 for netmask in netmasks:
320 if str(mask) == netmask:
321 pub_split = list(ip.subnet(24))
322 break
sousaedu80135b92021-02-17 15:05:18 +0100323
aticig4e86d0c2022-06-02 20:03:13 +0300324 subnets = pub_split if pub_split else (list(ip.subnet(28)))
ahmadsa61ccd642017-04-20 18:17:28 -0700325
326 return map(str, subnets)
327
sousaedu80135b92021-02-17 15:05:18 +0100328 def new_network(
329 self,
330 net_name,
331 net_type,
332 ip_profile=None,
333 shared=False,
334 provider_network_profile=None,
335 ):
ahmadsa61ccd642017-04-20 18:17:28 -0700336 """Adds a tenant network to VIM
337 Params:
338 'net_name': name of the network
339 'net_type': one of:
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
garciadeblasebd66722019-01-31 16:01:31 +0000353 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
357 as not present.
ahmadsa61ccd642017-04-20 18:17:28 -0700358 """
ahmadsa61ccd642017-04-20 18:17:28 -0700359 self.logger.debug("Adding a subnet to VPC")
sousaedu80135b92021-02-17 15:05:18 +0100360
ahmadsa61ccd642017-04-20 18:17:28 -0700361 try:
garciadeblasebd66722019-01-31 16:01:31 +0000362 created_items = {}
ahmadsa61ccd642017-04-20 18:17:28 -0700363 self._reload_connection()
364 subnet = None
365 vpc_id = self.vpc_id
aticig4e86d0c2022-06-02 20:03:13 +0300366 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)
sousaedu80135b92021-02-17 15:05:18 +0100370
ahmadsa61ccd642017-04-20 18:17:28 -0700371 if self.vpc_data.get(vpc_id, None):
sousaedu80135b92021-02-17 15:05:18 +0100372 cidr_block = list(
373 set(self.vpc_data[vpc_id]["subnets"])
374 - set(
375 self.get_network_details(
376 {"tenant_id": vpc_id}, detail="cidr_block"
377 )
378 )
aticig4e86d0c2022-06-02 20:03:13 +0300379 )
ahmadsa61ccd642017-04-20 18:17:28 -0700380 else:
sousaedu80135b92021-02-17 15:05:18 +0100381 vpc = self.get_tenant_list({"id": vpc_id})[0]
aticig4e86d0c2022-06-02 20:03:13 +0300382 subnet_list = self.subnet_sizes(vpc["cidr_block"])
sousaedu80135b92021-02-17 15:05:18 +0100383 cidr_block = list(
384 set(subnet_list)
385 - set(
386 self.get_network_details(
387 {"tenant_id": vpc["id"]}, detail="cidr_block"
388 )
389 )
aticig4e86d0c2022-06-02 20:03:13 +0300390 )
sousaedu80135b92021-02-17 15:05:18 +0100391
aticig4e86d0c2022-06-02 20:03:13 +0300392 try:
393 selected_cidr_block = random.choice(cidr_block)
394 retry = 15
395 while retry > 0:
396 all_subnets = [
397 subnet.cidr_block for subnet in self.conn_vpc.get_all_subnets()
398 ]
399 all_subnets.append(selected_cidr_block)
400 conflict = check_conflicts(all_subnets)
401 if not conflict:
402 subnet = self.conn_vpc.create_subnet(
403 vpc_id, selected_cidr_block, self.availability_zone
404 )
405 break
406 retry -= 1
407 selected_cidr_block = random.choice(cidr_block)
408 else:
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,
413 )
414
415 except (EC2ResponseError, BotoServerError) as error:
416 self.format_vimconn_exception(error)
417
418 created_items["net:" + str(subnet.id)] = True
sousaedu80135b92021-02-17 15:05:18 +0100419
garciadeblasebd66722019-01-31 16:01:31 +0000420 return subnet.id, created_items
ahmadsa61ccd642017-04-20 18:17:28 -0700421 except Exception as e:
422 self.format_vimconn_exception(e)
423
424 def get_network_details(self, filters, detail):
sousaedu80135b92021-02-17 15:05:18 +0100425 """Get specified details related to a subnet"""
ahmadsa61ccd642017-04-20 18:17:28 -0700426 detail_list = []
427 subnet_list = self.get_network_list(filters)
sousaedu80135b92021-02-17 15:05:18 +0100428
ahmadsa61ccd642017-04-20 18:17:28 -0700429 for net in subnet_list:
430 detail_list.append(net[detail])
sousaedu80135b92021-02-17 15:05:18 +0100431
ahmadsa61ccd642017-04-20 18:17:28 -0700432 return detail_list
433
434 def get_network_list(self, filter_dict={}):
435 """Obtain tenant networks of VIM
436 Params:
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
tierno1ec592d2020-06-16 15:29:47 +0000442 ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin
443 state active
ahmadsa61ccd642017-04-20 18:17:28 -0700444 #(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
453 """
ahmadsa61ccd642017-04-20 18:17:28 -0700454 self.logger.debug("Getting all subnets from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100455
ahmadsa61ccd642017-04-20 18:17:28 -0700456 try:
457 self._reload_connection()
458 tfilters = {}
sousaedu80135b92021-02-17 15:05:18 +0100459
ahmadsa61ccd642017-04-20 18:17:28 -0700460 if filter_dict != {}:
sousaedu80135b92021-02-17 15:05:18 +0100461 if "tenant_id" in filter_dict:
aticig4e86d0c2022-06-02 20:03:13 +0300462 tfilters["vpcId"] = filter_dict.get("tenant_id")
sousaedu80135b92021-02-17 15:05:18 +0100463
464 subnets = self.conn_vpc.get_all_subnets(
aticig4e86d0c2022-06-02 20:03:13 +0300465 subnet_ids=filter_dict.get("SubnetId", None), filters=tfilters
sousaedu80135b92021-02-17 15:05:18 +0100466 )
aticig4e86d0c2022-06-02 20:03:13 +0300467
ahmadsa61ccd642017-04-20 18:17:28 -0700468 net_list = []
sousaedu80135b92021-02-17 15:05:18 +0100469
ahmadsa61ccd642017-04-20 18:17:28 -0700470 for net in subnets:
aticig4e86d0c2022-06-02 20:03:13 +0300471 if net.id == filter_dict.get("name"):
472 self.availability_zone = str(net.availability_zone)
473 net_list.append(
474 {
475 "id": str(net.id),
476 "name": str(net.id),
477 "status": str(net.state),
478 "vpc_id": str(net.vpc_id),
479 "cidr_block": str(net.cidr_block),
480 "type": "bridge",
481 }
482 )
sousaedu80135b92021-02-17 15:05:18 +0100483
ahmadsa61ccd642017-04-20 18:17:28 -0700484 return net_list
485 except Exception as e:
486 self.format_vimconn_exception(e)
487
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
497 """
ahmadsa61ccd642017-04-20 18:17:28 -0700498 self.logger.debug("Getting Subnet from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100499
ahmadsa61ccd642017-04-20 18:17:28 -0700500 try:
501 self._reload_connection()
502 subnet = self.conn_vpc.get_all_subnets(net_id)[0]
sousaedu80135b92021-02-17 15:05:18 +0100503 return {
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),
aticig4e86d0c2022-06-02 20:03:13 +0300509 "availability_zone": str(subnet.availability_zone),
sousaedu80135b92021-02-17 15:05:18 +0100510 }
ahmadsa61ccd642017-04-20 18:17:28 -0700511 except Exception as e:
512 self.format_vimconn_exception(e)
513
garciadeblasebd66722019-01-31 16:01:31 +0000514 def delete_network(self, net_id, created_items=None):
515 """
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
ahmadsa61ccd642017-04-20 18:17:28 -0700519 Returns the network identifier or raises an exception upon error or when network is not found
520 """
ahmadsa61ccd642017-04-20 18:17:28 -0700521 self.logger.debug("Deleting subnet from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100522
ahmadsa61ccd642017-04-20 18:17:28 -0700523 try:
524 self._reload_connection()
525 self.logger.debug("DELETING NET_ID: " + str(net_id))
526 self.conn_vpc.delete_subnet(net_id)
sousaedu80135b92021-02-17 15:05:18 +0100527
ahmadsa61ccd642017-04-20 18:17:28 -0700528 return net_id
aticig4e86d0c2022-06-02 20:03:13 +0300529
ahmadsa61ccd642017-04-20 18:17:28 -0700530 except Exception as e:
aticig4e86d0c2022-06-02 20:03:13 +0300531 if isinstance(e, EC2ResponseError):
532 self.network_delete_on_termination.append(net_id)
533 self.logger.warning(
534 f"{net_id} could not be deleted, deletion will retry after dependencies resolved"
535 )
536 else:
537 self.format_vimconn_exception(e)
ahmadsa61ccd642017-04-20 18:17:28 -0700538
539 def refresh_nets_status(self, net_list):
540 """Get the status of the networks
541 Params:
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)
554 'net_id2': ...
555 """
ahmadsa61ccd642017-04-20 18:17:28 -0700556 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100557
ahmadsa61ccd642017-04-20 18:17:28 -0700558 try:
559 dict_entry = {}
sousaedu80135b92021-02-17 15:05:18 +0100560
ahmadsa61ccd642017-04-20 18:17:28 -0700561 for net_id in net_list:
562 subnet_dict = {}
563 subnet = None
sousaedu80135b92021-02-17 15:05:18 +0100564
ahmadsa61ccd642017-04-20 18:17:28 -0700565 try:
566 subnet = self.conn_vpc.get_all_subnets(net_id)[0]
sousaedu80135b92021-02-17 15:05:18 +0100567
ahmadsa61ccd642017-04-20 18:17:28 -0700568 if subnet.state == "pending":
sousaedu80135b92021-02-17 15:05:18 +0100569 subnet_dict["status"] = "BUILD"
ahmadsa61ccd642017-04-20 18:17:28 -0700570 elif subnet.state == "available":
sousaedu80135b92021-02-17 15:05:18 +0100571 subnet_dict["status"] = "ACTIVE"
ahmadsa61ccd642017-04-20 18:17:28 -0700572 else:
sousaedu80135b92021-02-17 15:05:18 +0100573 subnet_dict["status"] = "ERROR"
574 subnet_dict["error_msg"] = ""
tierno1ec592d2020-06-16 15:29:47 +0000575 except Exception:
sousaedu80135b92021-02-17 15:05:18 +0100576 subnet_dict["status"] = "DELETED"
577 subnet_dict["error_msg"] = "Network not found"
ahmadsa61ccd642017-04-20 18:17:28 -0700578 finally:
aticig4e86d0c2022-06-02 20:03:13 +0300579 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)
584 }
585 subnet_dict["vim_info"] = cleared_subnet_dict
ahmadsa61ccd642017-04-20 18:17:28 -0700586 dict_entry[net_id] = subnet_dict
sousaedu80135b92021-02-17 15:05:18 +0100587
ahmadsa61ccd642017-04-20 18:17:28 -0700588 return dict_entry
589 except Exception as e:
590 self.format_vimconn_exception(e)
591
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
596 """
ahmadsa61ccd642017-04-20 18:17:28 -0700597 self.logger.debug("Getting instance type")
sousaedu80135b92021-02-17 15:05:18 +0100598
ahmadsa61ccd642017-04-20 18:17:28 -0700599 try:
600 if flavor_id in self.flavor_info:
601 return self.flavor_info[flavor_id]
602 else:
sousaedu80135b92021-02-17 15:05:18 +0100603 raise vimconn.VimConnNotFoundException(
604 "Cannot find flavor with this flavor ID/Name"
605 )
ahmadsa61ccd642017-04-20 18:17:28 -0700606 except Exception as e:
607 self.format_vimconn_exception(e)
608
609 def get_flavor_id_from_data(self, flavor_dict):
610 """Obtain flavor id that match the flavor description
611 Params:
612 'flavor_dict': dictionary that contains:
613 'disk': main hard disk in GB
614 'ram': memory in MB
615 'vcpus': number of virtual cpus
616 #todo: complete parameters for EPA
617 Returns the flavor_id or raises a vimconnNotFoundException
618 """
ahmadsa61ccd642017-04-20 18:17:28 -0700619 self.logger.debug("Getting flavor id from data")
sousaedu80135b92021-02-17 15:05:18 +0100620
ahmadsa61ccd642017-04-20 18:17:28 -0700621 try:
622 flavor = None
tierno7d782ef2019-10-04 12:56:31 +0000623 for key, values in self.flavor_info.items():
Vance Shipley8c4285d2017-05-12 04:02:50 +0530624 if (values["ram"], values["cpus"], values["disk"]) == (
sousaedu80135b92021-02-17 15:05:18 +0100625 flavor_dict["ram"],
626 flavor_dict["vcpus"],
627 flavor_dict["disk"],
628 ):
ahmadsa61ccd642017-04-20 18:17:28 -0700629 flavor = (key, values)
630 break
Vance Shipley8c4285d2017-05-12 04:02:50 +0530631 elif (values["ram"], values["cpus"], values["disk"]) >= (
sousaedu80135b92021-02-17 15:05:18 +0100632 flavor_dict["ram"],
633 flavor_dict["vcpus"],
634 flavor_dict["disk"],
635 ):
ahmadsa61ccd642017-04-20 18:17:28 -0700636 if not flavor:
637 flavor = (key, values)
638 else:
Vance Shipley8c4285d2017-05-12 04:02:50 +0530639 if (flavor[1]["ram"], flavor[1]["cpus"], flavor[1]["disk"]) >= (
sousaedu80135b92021-02-17 15:05:18 +0100640 values["ram"],
641 values["cpus"],
642 values["disk"],
643 ):
ahmadsa61ccd642017-04-20 18:17:28 -0700644 flavor = (key, values)
sousaedu80135b92021-02-17 15:05:18 +0100645
ahmadsa61ccd642017-04-20 18:17:28 -0700646 if flavor:
647 return flavor[0]
sousaedu80135b92021-02-17 15:05:18 +0100648
649 raise vimconn.VimConnNotFoundException(
650 "Cannot find flavor with this flavor ID/Name"
651 )
ahmadsa61ccd642017-04-20 18:17:28 -0700652 except Exception as e:
653 self.format_vimconn_exception(e)
654
655 def new_image(self, image_dict):
sousaedu80135b92021-02-17 15:05:18 +0100656 """Adds a tenant image to VIM
ahmadsa61ccd642017-04-20 18:17:28 -0700657 Params: image_dict
tierno1ec592d2020-06-16 15:29:47 +0000658 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
ahmadsa61ccd642017-04-20 18:17:28 -0700673 Returns: image_id - image ID of the newly created image
674 """
ahmadsa61ccd642017-04-20 18:17:28 -0700675 try:
676 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100677 image_location = image_dict.get("image_location", None)
678
ahmadsa61ccd642017-04-20 18:17:28 -0700679 if image_location:
680 image_location = str(self.account_id) + str(image_location)
681
sousaedu80135b92021-02-17 15:05:18 +0100682 image_id = self.conn.register_image(
683 image_dict.get("name", None),
684 image_dict.get("description", None),
685 image_location,
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),
694 )
695
ahmadsa61ccd642017-04-20 18:17:28 -0700696 return image_id
697 except Exception as e:
698 self.format_vimconn_exception(e)
699
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"""
703
704 try:
705 self._reload_connection()
706 self.conn.deregister_image(image_id)
sousaedu80135b92021-02-17 15:05:18 +0100707
ahmadsa61ccd642017-04-20 18:17:28 -0700708 return image_id
709 except Exception as e:
710 self.format_vimconn_exception(e)
711
712 def get_image_id_from_path(self, path):
sousaedu80135b92021-02-17 15:05:18 +0100713 """
ahmadsa61ccd642017-04-20 18:17:28 -0700714 Params: path - location of the image
715 Returns: image_id - ID of the matching image
sousaedu80135b92021-02-17 15:05:18 +0100716 """
ahmadsa61ccd642017-04-20 18:17:28 -0700717 self._reload_connection()
718 try:
719 filters = {}
sousaedu80135b92021-02-17 15:05:18 +0100720
ahmadsa61ccd642017-04-20 18:17:28 -0700721 if path:
sousaedu80135b92021-02-17 15:05:18 +0100722 tokens = path.split("/")
723 filters["owner_id"] = tokens[0]
724 filters["name"] = "/".join(tokens[1:])
725
ahmadsa61ccd642017-04-20 18:17:28 -0700726 image = self.conn.get_all_images(filters=filters)[0]
sousaedu80135b92021-02-17 15:05:18 +0100727
ahmadsa61ccd642017-04-20 18:17:28 -0700728 return image.id
729 except Exception as e:
730 self.format_vimconn_exception(e)
731
732 def get_image_list(self, filter_dict={}):
733 """Obtain tenant images from VIM
734 Filter_dict can be:
735 name: image name
736 id: image uuid
737 checksum: image checksum
738 location: image path
739 Returns the image list of dictionaries:
740 [{<the fields at Filter_dict plus some VIM specific>}, ...]
741 List can be empty
742 """
ahmadsa61ccd642017-04-20 18:17:28 -0700743 self.logger.debug("Getting image list from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100744
ahmadsa61ccd642017-04-20 18:17:28 -0700745 try:
746 self._reload_connection()
747 image_id = None
748 filters = {}
sousaedu80135b92021-02-17 15:05:18 +0100749
750 if "id" in filter_dict:
751 image_id = filter_dict["id"]
752
753 if "name" in filter_dict:
754 filters["name"] = filter_dict["name"]
755
756 if "location" in filter_dict:
757 filters["location"] = filter_dict["location"]
758
ahmadsa61ccd642017-04-20 18:17:28 -0700759 # filters['image_type'] = 'machine'
760 # filter_dict['owner_id'] = self.account_id
761 images = self.conn.get_all_images(image_id, filters=filters)
762 image_list = []
sousaedu80135b92021-02-17 15:05:18 +0100763
ahmadsa61ccd642017-04-20 18:17:28 -0700764 for image in images:
sousaedu80135b92021-02-17 15:05:18 +0100765 image_list.append(
766 {
767 "id": str(image.id),
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),
775 }
776 )
777
ahmadsa61ccd642017-04-20 18:17:28 -0700778 return image_list
779 except Exception as e:
780 self.format_vimconn_exception(e)
781
sousaedu80135b92021-02-17 15:05:18 +0100782 def new_vminstance(
783 self,
784 name,
785 description,
786 start,
787 image_id,
788 flavor_id,
Alexis Romerob70f4ed2022-03-11 18:00:49 +0100789 affinity_group_list,
sousaedu80135b92021-02-17 15:05:18 +0100790 net_list,
791 cloud_config=None,
792 disk_list=None,
793 availability_zone_index=None,
794 availability_zone_list=None,
795 ):
ahmadsa61ccd642017-04-20 18:17:28 -0700796 """Create a new VM/instance in AWS
797 Params: name
798 decription
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
802 net_list
803 name
804 net_id - subnet_id from AWS
tierno1ec592d2020-06-16 15:29:47 +0000805 vpci - (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
806 capabilities
garciadeblasc4f4d732018-10-25 18:17:24 +0200807 model: (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
ahmadsa61ccd642017-04-20 18:17:28 -0700808 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'
tierno1ec592d2020-06-16 15:29:47 +0000811 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a
812 data/ptp network ot it
tierno66eba6e2017-11-10 17:09:18 +0100813 can created unconnected
814 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
tierno1ec592d2020-06-16 15:29:47 +0000815 VFnotShared - (SRIOV without VLAN tag) same as PF for network connectivity. VF where no other
816 VFs are allocated on the same physical NIC
ahmadsa61ccd642017-04-20 18:17:28 -0700817 bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
tierno1ec592d2020-06-16 15:29:47 +0000818 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
ahmadsa61ccd642017-04-20 18:17:28 -0700822 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)
837 security-groups:
838 subnet_id
839 security_group_id
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
tierno98e909c2017-10-14 13:27:03 +0200843 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
847 as not present.
ahmadsa61ccd642017-04-20 18:17:28 -0700848 """
ahmadsa61ccd642017-04-20 18:17:28 -0700849 self.logger.debug("Creating a new VM instance")
sousaedu80135b92021-02-17 15:05:18 +0100850
ahmadsa61ccd642017-04-20 18:17:28 -0700851 try:
aticig4e86d0c2022-06-02 20:03:13 +0300852 created_items = {}
ahmadsa61ccd642017-04-20 18:17:28 -0700853 self._reload_connection()
aticig4e86d0c2022-06-02 20:03:13 +0300854 reservation = None
tierno0a1437e2017-10-02 00:17:43 +0200855 _, userdata = self._create_user_data(cloud_config)
ahmadsa61ccd642017-04-20 18:17:28 -0700856
857 if not net_list:
858 reservation = self.conn.run_instances(
859 image_id,
860 key_name=self.key_pair,
861 instance_type=flavor_id,
862 security_groups=self.security_groups,
sousaedu80135b92021-02-17 15:05:18 +0100863 user_data=userdata,
ahmadsa61ccd642017-04-20 18:17:28 -0700864 )
aticig4e86d0c2022-06-02 20:03:13 +0300865
ahmadsa61ccd642017-04-20 18:17:28 -0700866 else:
ahmadsa61ccd642017-04-20 18:17:28 -0700867 for index, subnet in enumerate(net_list):
aticig4e86d0c2022-06-02 20:03:13 +0300868
869 net_intr = self.conn_vpc.create_network_interface(
sousaedu80135b92021-02-17 15:05:18 +0100870 subnet_id=subnet.get("net_id"),
871 groups=None,
aticig4e86d0c2022-06-02 20:03:13 +0300872 )
873
874 interface = boto.ec2.networkinterface.NetworkInterfaceSpecification(
875 network_interface_id=net_intr.id,
876 device_index=index,
877 )
878
879 interfaces = boto.ec2.networkinterface.NetworkInterfaceCollection(
880 interface
sousaedu80135b92021-02-17 15:05:18 +0100881 )
ahmadsa61ccd642017-04-20 18:17:28 -0700882
sousaedu80135b92021-02-17 15:05:18 +0100883 if subnet.get("elastic_ip"):
ahmadsa61ccd642017-04-20 18:17:28 -0700884 eip = self.conn.allocate_address()
sousaedu80135b92021-02-17 15:05:18 +0100885 self.conn.associate_address(
886 allocation_id=eip.allocation_id,
887 network_interface_id=net_intr.id,
888 )
ahmadsa61ccd642017-04-20 18:17:28 -0700889
890 if index == 0:
aticig4e86d0c2022-06-02 20:03:13 +0300891 try:
892 reservation = self.conn.run_instances(
893 image_id,
894 key_name=self.key_pair,
895 instance_type=flavor_id,
896 security_groups=self.security_groups,
897 network_interfaces=interfaces,
898 user_data=userdata,
899 )
900 except Exception as instance_create_error:
901 self.logger.debug(traceback.format_exc())
902 self.format_vimconn_exception(instance_create_error)
903
904 if index > 0:
905 try:
906 if reservation:
907 instance_id = self.wait_for_instance_id(reservation)
908 if instance_id and self.wait_for_vm(
909 instance_id, "running"
910 ):
911 self.conn.attach_network_interface(
912 network_interface_id=net_intr.id,
913 instance_id=instance_id,
914 device_index=index,
915 )
916 except Exception as attach_network_error:
917 self.logger.debug(traceback.format_exc())
918 self.format_vimconn_exception(attach_network_error)
919
920 if instance_id := self.wait_for_instance_id(reservation):
921 time.sleep(30)
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(
925 "interfaces"
ahmadsa61ccd642017-04-20 18:17:28 -0700926 )
aticig4e86d0c2022-06-02 20:03:13 +0300927 for idx, interface in enumerate(instance_interfaces):
928 if idx == index:
929 net_list[index]["vim_id"] = instance_interfaces[
930 idx
931 ].get("vim_interface_id")
sousaedu80135b92021-02-17 15:05:18 +0100932
aticig4e86d0c2022-06-02 20:03:13 +0300933 instance_id = self.wait_for_instance_id(reservation)
934 created_items["vm_id:" + str(instance_id)] = True
montesmoreno3b299bb2017-10-02 18:19:05 +0200935
aticig4e86d0c2022-06-02 20:03:13 +0300936 return instance_id, created_items
ahmadsa61ccd642017-04-20 18:17:28 -0700937 except Exception as e:
aticig4e86d0c2022-06-02 20:03:13 +0300938 self.logger.debug(traceback.format_exc())
ahmadsa61ccd642017-04-20 18:17:28 -0700939 self.format_vimconn_exception(e)
940
941 def get_vminstance(self, vm_id):
942 """Returns the VM instance information from VIM"""
ahmadsa61ccd642017-04-20 18:17:28 -0700943 try:
944 self._reload_connection()
945 reservation = self.conn.get_all_instances(vm_id)
sousaedu80135b92021-02-17 15:05:18 +0100946
ahmadsa61ccd642017-04-20 18:17:28 -0700947 return reservation[0].instances[0].__dict__
948 except Exception as e:
949 self.format_vimconn_exception(e)
950
garciadeblase634e9e2022-06-30 13:57:43 +0200951 def delete_vminstance(self, vm_id, created_items=None, volumes_to_hold=None):
ahmadsa61ccd642017-04-20 18:17:28 -0700952 """Removes a VM instance from VIM
953 Returns the instance identifier"""
ahmadsa61ccd642017-04-20 18:17:28 -0700954 try:
955 self._reload_connection()
956 self.logger.debug("DELETING VM_ID: " + str(vm_id))
aticig4e86d0c2022-06-02 20:03:13 +0300957 reservation = self.conn.get_all_instances(vm_id)[0]
958 if hasattr(reservation, "instances"):
959 instance = reservation.instances[0]
sousaedu80135b92021-02-17 15:05:18 +0100960
aticig4e86d0c2022-06-02 20:03:13 +0300961 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,
966 )
967 if self.network_delete_on_termination:
968 for net in self.network_delete_on_termination:
969 try:
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")
974 else:
975 self.format_vimconn_exception(net_delete_error)
976
977 return vm_id
ahmadsa61ccd642017-04-20 18:17:28 -0700978 except Exception as e:
979 self.format_vimconn_exception(e)
980
aticig4e86d0c2022-06-02 20:03:13 +0300981 def wait_for_instance_id(self, reservation):
982 if not reservation:
983 return False
984
985 self._reload_connection()
986 elapsed_time = 0
987 while elapsed_time < 30:
988 if reservation.instances:
989 instance_id = reservation.instances[0].id
990 return instance_id
991 time.sleep(5)
992 elapsed_time += 5
993 else:
994 raise vimconn.VimConnException(
995 "Failed to get instance_id for reservation",
996 reservation,
997 http_code=vimconn.HTTP_Request_Timeout,
998 )
999
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"""
1003
1004 self._reload_connection()
1005
1006 elapsed_time = 0
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:
1013 return True
1014 time.sleep(5)
1015 elapsed_time += 5
1016
1017 # if we exceeded the timeout
1018 else:
1019 raise vimconn.VimConnException(
1020 "Timeout waiting for instance " + vm_id + " to get " + status,
1021 http_code=vimconn.HTTP_Request_Timeout,
1022 )
1023
ahmadsa61ccd642017-04-20 18:17:28 -07001024 def refresh_vms_status(self, vm_list):
sousaedu80135b92021-02-17 15:05:18 +01001025 """Get the status of the virtual machines and their interfaces/ports
ahmadsa61ccd642017-04-20 18:17:28 -07001026 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
1037 #
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.
1045 """
1046 self.logger.debug("Getting VM instance information from VIM")
sousaedu80135b92021-02-17 15:05:18 +01001047
ahmadsa61ccd642017-04-20 18:17:28 -07001048 try:
1049 self._reload_connection()
aticig4e86d0c2022-06-02 20:03:13 +03001050 elapsed_time = 0
1051 while elapsed_time < self.server_timeout:
1052 reservation = self.conn.get_all_instances(vm_list)[0]
1053 if reservation:
1054 break
1055 time.sleep(5)
1056 elapsed_time += 5
1057
1058 # if we exceeded the timeout
1059 else:
1060 raise vimconn.VimConnException(
1061 vm_list + "could not be gathered, refresh vm status failed",
1062 http_code=vimconn.HTTP_Request_Timeout,
1063 )
1064
ahmadsa61ccd642017-04-20 18:17:28 -07001065 instances = {}
1066 instance_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001067
ahmadsa61ccd642017-04-20 18:17:28 -07001068 for instance in reservation.instances:
aticig4e86d0c2022-06-02 20:03:13 +03001069 if hasattr(instance, "id"):
ahmadsa61ccd642017-04-20 18:17:28 -07001070 try:
aticig4e86d0c2022-06-02 20:03:13 +03001071 if instance.state in ("pending"):
1072 instance_dict["status"] = "BUILD"
1073 elif instance.state in ("available", "running", "up"):
1074 instance_dict["status"] = "ACTIVE"
1075 else:
1076 instance_dict["status"] = "ERROR"
sousaedu80135b92021-02-17 15:05:18 +01001077
aticig4e86d0c2022-06-02 20:03:13 +03001078 instance_dict["error_msg"] = ""
1079 instance_dict["interfaces"] = []
1080
1081 for interface in instance.interfaces:
1082 interface_dict = {
1083 "vim_interface_id": interface.id,
1084 "vim_net_id": interface.subnet_id,
1085 "mac_address": interface.mac_address,
1086 }
1087
1088 if (
1089 hasattr(interface, "publicIp")
1090 and interface.publicIp is not None
1091 ):
1092 interface_dict["ip_address"] = (
1093 interface.publicIp
1094 + ";"
1095 + interface.private_ip_address
1096 )
1097 else:
1098 interface_dict[
1099 "ip_address"
1100 ] = interface.private_ip_address
1101
1102 instance_dict["interfaces"].append(interface_dict)
1103 except Exception as e:
1104 self.logger.error(
1105 "Exception getting vm status: %s", str(e), exc_info=True
1106 )
1107 instance_dict["status"] = "DELETED"
1108 instance_dict["error_msg"] = str(e)
1109 finally:
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))
1115 }
1116 instance_dict["vim_info"] = cleared_instance_dict
1117
1118 instances[instance.id] = instance_dict
sousaedu80135b92021-02-17 15:05:18 +01001119
ahmadsa61ccd642017-04-20 18:17:28 -07001120 return instances
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)
1124
tierno98e909c2017-10-14 13:27:03 +02001125 def action_vminstance(self, vm_id, action_dict, created_items={}):
ahmadsa61ccd642017-04-20 18:17:28 -07001126 """Send and action over a VM instance from VIM
1127 Returns the vm_id if the action was successfully sent to the VIM"""
1128
1129 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1130 try:
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)
sousaedu80135b92021-02-17 15:05:18 +01001140
tierno98e909c2017-10-14 13:27:03 +02001141 return None
ahmadsa61ccd642017-04-20 18:17:28 -07001142 except Exception as e:
1143 self.format_vimconn_exception(e)
elumalai8658c2c2022-04-28 19:09:31 +05301144
1145 def migrate_instance(self, vm_id, compute_host=None):
1146 """
1147 Migrate a vdu
1148 param:
1149 vm_id: ID of an instance
1150 compute_host: Host to migrate the vdu to
1151 """
1152 # TODO: Add support for migration
1153 raise vimconn.VimConnNotImplemented("Not implemented")
sritharan29a4c1a2022-05-05 12:15:04 +00001154
1155 def resize_instance(self, vm_id, flavor_id=None):
1156 """
1157 resize a vdu
1158 param:
1159 vm_id: ID of an instance
1160 flavor_id: flavor to resize the vdu
1161 """
1162 # TODO: Add support for resize
1163 raise vimconn.VimConnNotImplemented("Not implemented")