blob: 63184a1a86f48bfa142d37cd367fef8440fda33b [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:
aticig7b521f72022-07-15 00:43:09 +0300144 self.flavor_info = yaml.safe_load(stream)
tiernoa3572692018-05-14 13:09:33 +0200145 else:
aticig7b521f72022-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
ahmadsa61ccd642017-04-20 18:17:28 -0700609 def new_image(self, image_dict):
sousaedu80135b92021-02-17 15:05:18 +0100610 """Adds a tenant image to VIM
ahmadsa61ccd642017-04-20 18:17:28 -0700611 Params: image_dict
tierno1ec592d2020-06-16 15:29:47 +0000612 name (string) - The name of the AMI. Valid only for EBS-based images.
613 description (string) - The description of the AMI.
614 image_location (string) - Full path to your AMI manifest in Amazon S3 storage. Only used for S3-based AMI’s.
615 architecture (string) - The architecture of the AMI. Valid choices are: * i386 * x86_64
616 kernel_id (string) - The ID of the kernel with which to launch the instances
617 root_device_name (string) - The root device name (e.g. /dev/sdh)
618 block_device_map (boto.ec2.blockdevicemapping.BlockDeviceMapping) - A BlockDeviceMapping data structure
619 describing the EBS volumes associated with the Image.
620 virtualization_type (string) - The virutalization_type of the image. Valid choices are: * paravirtual * hvm
621 sriov_net_support (string) - Advanced networking support. Valid choices are: * simple
622 snapshot_id (string) - A snapshot ID for the snapshot to be used as root device for the image. Mutually
623 exclusive with block_device_map, requires root_device_name
624 delete_root_volume_on_termination (bool) - Whether to delete the root volume of the image after instance
625 termination. Only applies when creating image from snapshot_id. Defaults to False. Note that leaving
626 volumes behind after instance termination is not free
ahmadsa61ccd642017-04-20 18:17:28 -0700627 Returns: image_id - image ID of the newly created image
628 """
ahmadsa61ccd642017-04-20 18:17:28 -0700629 try:
630 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100631 image_location = image_dict.get("image_location", None)
632
ahmadsa61ccd642017-04-20 18:17:28 -0700633 if image_location:
634 image_location = str(self.account_id) + str(image_location)
635
sousaedu80135b92021-02-17 15:05:18 +0100636 image_id = self.conn.register_image(
637 image_dict.get("name", None),
638 image_dict.get("description", None),
639 image_location,
640 image_dict.get("architecture", None),
641 image_dict.get("kernel_id", None),
642 image_dict.get("root_device_name", None),
643 image_dict.get("block_device_map", None),
644 image_dict.get("virtualization_type", None),
645 image_dict.get("sriov_net_support", None),
646 image_dict.get("snapshot_id", None),
647 image_dict.get("delete_root_volume_on_termination", None),
648 )
649
ahmadsa61ccd642017-04-20 18:17:28 -0700650 return image_id
651 except Exception as e:
652 self.format_vimconn_exception(e)
653
654 def delete_image(self, image_id):
655 """Deletes a tenant image from VIM
656 Returns the image_id if image is deleted or raises an exception on error"""
657
658 try:
659 self._reload_connection()
660 self.conn.deregister_image(image_id)
sousaedu80135b92021-02-17 15:05:18 +0100661
ahmadsa61ccd642017-04-20 18:17:28 -0700662 return image_id
663 except Exception as e:
664 self.format_vimconn_exception(e)
665
666 def get_image_id_from_path(self, path):
sousaedu80135b92021-02-17 15:05:18 +0100667 """
ahmadsa61ccd642017-04-20 18:17:28 -0700668 Params: path - location of the image
669 Returns: image_id - ID of the matching image
sousaedu80135b92021-02-17 15:05:18 +0100670 """
ahmadsa61ccd642017-04-20 18:17:28 -0700671 self._reload_connection()
672 try:
673 filters = {}
sousaedu80135b92021-02-17 15:05:18 +0100674
ahmadsa61ccd642017-04-20 18:17:28 -0700675 if path:
sousaedu80135b92021-02-17 15:05:18 +0100676 tokens = path.split("/")
677 filters["owner_id"] = tokens[0]
678 filters["name"] = "/".join(tokens[1:])
679
ahmadsa61ccd642017-04-20 18:17:28 -0700680 image = self.conn.get_all_images(filters=filters)[0]
sousaedu80135b92021-02-17 15:05:18 +0100681
ahmadsa61ccd642017-04-20 18:17:28 -0700682 return image.id
683 except Exception as e:
684 self.format_vimconn_exception(e)
685
686 def get_image_list(self, filter_dict={}):
687 """Obtain tenant images from VIM
688 Filter_dict can be:
689 name: image name
690 id: image uuid
691 checksum: image checksum
692 location: image path
693 Returns the image list of dictionaries:
694 [{<the fields at Filter_dict plus some VIM specific>}, ...]
695 List can be empty
696 """
ahmadsa61ccd642017-04-20 18:17:28 -0700697 self.logger.debug("Getting image list from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100698
ahmadsa61ccd642017-04-20 18:17:28 -0700699 try:
700 self._reload_connection()
701 image_id = None
702 filters = {}
sousaedu80135b92021-02-17 15:05:18 +0100703
704 if "id" in filter_dict:
705 image_id = filter_dict["id"]
706
707 if "name" in filter_dict:
708 filters["name"] = filter_dict["name"]
709
710 if "location" in filter_dict:
711 filters["location"] = filter_dict["location"]
712
ahmadsa61ccd642017-04-20 18:17:28 -0700713 # filters['image_type'] = 'machine'
714 # filter_dict['owner_id'] = self.account_id
715 images = self.conn.get_all_images(image_id, filters=filters)
716 image_list = []
sousaedu80135b92021-02-17 15:05:18 +0100717
ahmadsa61ccd642017-04-20 18:17:28 -0700718 for image in images:
sousaedu80135b92021-02-17 15:05:18 +0100719 image_list.append(
720 {
721 "id": str(image.id),
722 "name": str(image.name),
723 "status": str(image.state),
724 "owner": str(image.owner_id),
725 "location": str(image.location),
726 "is_public": str(image.is_public),
727 "architecture": str(image.architecture),
728 "platform": str(image.platform),
729 }
730 )
731
ahmadsa61ccd642017-04-20 18:17:28 -0700732 return image_list
733 except Exception as e:
734 self.format_vimconn_exception(e)
735
sousaedu80135b92021-02-17 15:05:18 +0100736 def new_vminstance(
737 self,
738 name,
739 description,
740 start,
741 image_id,
742 flavor_id,
Alexis Romerob70f4ed2022-03-11 18:00:49 +0100743 affinity_group_list,
sousaedu80135b92021-02-17 15:05:18 +0100744 net_list,
745 cloud_config=None,
746 disk_list=None,
747 availability_zone_index=None,
748 availability_zone_list=None,
749 ):
ahmadsa61ccd642017-04-20 18:17:28 -0700750 """Create a new VM/instance in AWS
751 Params: name
752 decription
753 start: (boolean) indicates if VM must start or created in pause mode.
754 image_id - image ID in AWS
755 flavor_id - instance type ID in AWS
756 net_list
757 name
758 net_id - subnet_id from AWS
tierno1ec592d2020-06-16 15:29:47 +0000759 vpci - (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
760 capabilities
garciadeblasc4f4d732018-10-25 18:17:24 +0200761 model: (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
ahmadsa61ccd642017-04-20 18:17:28 -0700762 mac_address: (optional) mac address to assign to this interface
763 type: (mandatory) can be one of:
764 virtual, in this case always connected to a network of type 'net_type=bridge'
tierno1ec592d2020-06-16 15:29:47 +0000765 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a
766 data/ptp network ot it
tierno66eba6e2017-11-10 17:09:18 +0100767 can created unconnected
768 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
tierno1ec592d2020-06-16 15:29:47 +0000769 VFnotShared - (SRIOV without VLAN tag) same as PF for network connectivity. VF where no other
770 VFs are allocated on the same physical NIC
ahmadsa61ccd642017-04-20 18:17:28 -0700771 bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
tierno1ec592d2020-06-16 15:29:47 +0000772 port_security': (optional) If False it must avoid any traffic filtering at this interface.
773 If missing or True, it must apply the default VIM behaviour
774 vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
775 interface. 'net_list' is modified
ahmadsa61ccd642017-04-20 18:17:28 -0700776 elastic_ip - True/False to define if an elastic_ip is required
777 cloud_config': (optional) dictionary with:
778 key-pairs': (optional) list of strings with the public key to be inserted to the default user
779 users': (optional) list of users to be inserted, each item is a dict with:
780 name': (mandatory) user name,
781 key-pairs': (optional) list of strings with the public key to be inserted to the user
782 user-data': (optional) string is a text script to be passed directly to cloud-init
783 config-files': (optional). List of files to be transferred. Each item is a dict with:
784 dest': (mandatory) string with the destination absolute path
785 encoding': (optional, by default text). Can be one of:
786 b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
787 content' (mandatory): string with the content of the file
788 permissions': (optional) string with file permissions, typically octal notation '0644'
789 owner: (optional) file owner, string with the format 'owner:group'
790 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
791 security-groups:
792 subnet_id
793 security_group_id
794 disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
795 image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
796 size': (mandatory) string with the size of the disk in GB
tierno98e909c2017-10-14 13:27:03 +0200797 Returns a tuple with the instance identifier and created_items or raises an exception on error
798 created_items can be None or a dictionary where this method can include key-values that will be passed to
799 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
800 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
801 as not present.
ahmadsa61ccd642017-04-20 18:17:28 -0700802 """
ahmadsa61ccd642017-04-20 18:17:28 -0700803 self.logger.debug("Creating a new VM instance")
sousaedu80135b92021-02-17 15:05:18 +0100804
ahmadsa61ccd642017-04-20 18:17:28 -0700805 try:
aticig4e86d0c2022-06-02 20:03:13 +0300806 created_items = {}
ahmadsa61ccd642017-04-20 18:17:28 -0700807 self._reload_connection()
aticig4e86d0c2022-06-02 20:03:13 +0300808 reservation = None
tierno0a1437e2017-10-02 00:17:43 +0200809 _, userdata = self._create_user_data(cloud_config)
ahmadsa61ccd642017-04-20 18:17:28 -0700810
811 if not net_list:
812 reservation = self.conn.run_instances(
813 image_id,
814 key_name=self.key_pair,
815 instance_type=flavor_id,
816 security_groups=self.security_groups,
sousaedu80135b92021-02-17 15:05:18 +0100817 user_data=userdata,
ahmadsa61ccd642017-04-20 18:17:28 -0700818 )
aticig4e86d0c2022-06-02 20:03:13 +0300819
ahmadsa61ccd642017-04-20 18:17:28 -0700820 else:
ahmadsa61ccd642017-04-20 18:17:28 -0700821 for index, subnet in enumerate(net_list):
aticig4e86d0c2022-06-02 20:03:13 +0300822
823 net_intr = self.conn_vpc.create_network_interface(
sousaedu80135b92021-02-17 15:05:18 +0100824 subnet_id=subnet.get("net_id"),
825 groups=None,
aticig4e86d0c2022-06-02 20:03:13 +0300826 )
827
828 interface = boto.ec2.networkinterface.NetworkInterfaceSpecification(
829 network_interface_id=net_intr.id,
830 device_index=index,
831 )
832
833 interfaces = boto.ec2.networkinterface.NetworkInterfaceCollection(
834 interface
sousaedu80135b92021-02-17 15:05:18 +0100835 )
ahmadsa61ccd642017-04-20 18:17:28 -0700836
sousaedu80135b92021-02-17 15:05:18 +0100837 if subnet.get("elastic_ip"):
ahmadsa61ccd642017-04-20 18:17:28 -0700838 eip = self.conn.allocate_address()
sousaedu80135b92021-02-17 15:05:18 +0100839 self.conn.associate_address(
840 allocation_id=eip.allocation_id,
841 network_interface_id=net_intr.id,
842 )
ahmadsa61ccd642017-04-20 18:17:28 -0700843
844 if index == 0:
aticig4e86d0c2022-06-02 20:03:13 +0300845 try:
846 reservation = self.conn.run_instances(
847 image_id,
848 key_name=self.key_pair,
849 instance_type=flavor_id,
850 security_groups=self.security_groups,
851 network_interfaces=interfaces,
852 user_data=userdata,
853 )
854 except Exception as instance_create_error:
855 self.logger.debug(traceback.format_exc())
856 self.format_vimconn_exception(instance_create_error)
857
858 if index > 0:
859 try:
860 if reservation:
861 instance_id = self.wait_for_instance_id(reservation)
862 if instance_id and self.wait_for_vm(
863 instance_id, "running"
864 ):
865 self.conn.attach_network_interface(
866 network_interface_id=net_intr.id,
867 instance_id=instance_id,
868 device_index=index,
869 )
870 except Exception as attach_network_error:
871 self.logger.debug(traceback.format_exc())
872 self.format_vimconn_exception(attach_network_error)
873
874 if instance_id := self.wait_for_instance_id(reservation):
875 time.sleep(30)
876 instance_status = self.refresh_vms_status(instance_id)
877 refreshed_instance_status = instance_status.get(instance_id)
878 instance_interfaces = refreshed_instance_status.get(
879 "interfaces"
ahmadsa61ccd642017-04-20 18:17:28 -0700880 )
aticig4e86d0c2022-06-02 20:03:13 +0300881 for idx, interface in enumerate(instance_interfaces):
882 if idx == index:
883 net_list[index]["vim_id"] = instance_interfaces[
884 idx
885 ].get("vim_interface_id")
sousaedu80135b92021-02-17 15:05:18 +0100886
aticig4e86d0c2022-06-02 20:03:13 +0300887 instance_id = self.wait_for_instance_id(reservation)
888 created_items["vm_id:" + str(instance_id)] = True
montesmoreno3b299bb2017-10-02 18:19:05 +0200889
aticig4e86d0c2022-06-02 20:03:13 +0300890 return instance_id, created_items
ahmadsa61ccd642017-04-20 18:17:28 -0700891 except Exception as e:
aticig4e86d0c2022-06-02 20:03:13 +0300892 self.logger.debug(traceback.format_exc())
ahmadsa61ccd642017-04-20 18:17:28 -0700893 self.format_vimconn_exception(e)
894
895 def get_vminstance(self, vm_id):
896 """Returns the VM instance information from VIM"""
ahmadsa61ccd642017-04-20 18:17:28 -0700897 try:
898 self._reload_connection()
899 reservation = self.conn.get_all_instances(vm_id)
sousaedu80135b92021-02-17 15:05:18 +0100900
ahmadsa61ccd642017-04-20 18:17:28 -0700901 return reservation[0].instances[0].__dict__
902 except Exception as e:
903 self.format_vimconn_exception(e)
904
garciadeblas89598d42022-06-30 13:57:43 +0200905 def delete_vminstance(self, vm_id, created_items=None, volumes_to_hold=None):
ahmadsa61ccd642017-04-20 18:17:28 -0700906 """Removes a VM instance from VIM
907 Returns the instance identifier"""
ahmadsa61ccd642017-04-20 18:17:28 -0700908 try:
909 self._reload_connection()
910 self.logger.debug("DELETING VM_ID: " + str(vm_id))
aticig4e86d0c2022-06-02 20:03:13 +0300911 reservation = self.conn.get_all_instances(vm_id)[0]
912 if hasattr(reservation, "instances"):
913 instance = reservation.instances[0]
sousaedu80135b92021-02-17 15:05:18 +0100914
aticig4e86d0c2022-06-02 20:03:13 +0300915 self.conn.terminate_instances(vm_id)
916 if self.wait_for_vm(vm_id, "terminated"):
917 for interface in instance.interfaces:
918 self.conn_vpc.delete_network_interface(
919 network_interface_id=interface.id,
920 )
921 if self.network_delete_on_termination:
922 for net in self.network_delete_on_termination:
923 try:
924 self.conn_vpc.delete_subnet(net)
925 except Exception as net_delete_error:
926 if isinstance(net_delete_error, EC2ResponseError):
927 self.logger.warning(f"Deleting network {net}: failed")
928 else:
929 self.format_vimconn_exception(net_delete_error)
930
931 return vm_id
ahmadsa61ccd642017-04-20 18:17:28 -0700932 except Exception as e:
933 self.format_vimconn_exception(e)
934
aticig4e86d0c2022-06-02 20:03:13 +0300935 def wait_for_instance_id(self, reservation):
936 if not reservation:
937 return False
938
939 self._reload_connection()
940 elapsed_time = 0
941 while elapsed_time < 30:
942 if reservation.instances:
943 instance_id = reservation.instances[0].id
944 return instance_id
945 time.sleep(5)
946 elapsed_time += 5
947 else:
948 raise vimconn.VimConnException(
949 "Failed to get instance_id for reservation",
aticig4e86d0c2022-06-02 20:03:13 +0300950 http_code=vimconn.HTTP_Request_Timeout,
951 )
952
953 def wait_for_vm(self, vm_id, status):
954 """wait until vm is in the desired status and return True.
955 If the timeout is reached generate an exception"""
956
957 self._reload_connection()
958
959 elapsed_time = 0
960 while elapsed_time < self.server_timeout:
961 if self.conn.get_all_instances(vm_id):
962 reservation = self.conn.get_all_instances(vm_id)[0]
963 if hasattr(reservation, "instances"):
964 instance = reservation.instances[0]
965 if instance.state == status:
966 return True
967 time.sleep(5)
968 elapsed_time += 5
969
970 # if we exceeded the timeout
971 else:
972 raise vimconn.VimConnException(
973 "Timeout waiting for instance " + vm_id + " to get " + status,
974 http_code=vimconn.HTTP_Request_Timeout,
975 )
976
ahmadsa61ccd642017-04-20 18:17:28 -0700977 def refresh_vms_status(self, vm_list):
sousaedu80135b92021-02-17 15:05:18 +0100978 """Get the status of the virtual machines and their interfaces/ports
ahmadsa61ccd642017-04-20 18:17:28 -0700979 Params: the list of VM identifiers
980 Returns a dictionary with:
981 vm_id: #VIM id of this Virtual Machine
982 status: #Mandatory. Text with one of:
983 # DELETED (not found at vim)
984 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
985 # OTHER (Vim reported other status not understood)
986 # ERROR (VIM indicates an ERROR status)
987 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
988 # BUILD (on building process), ERROR
989 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
990 #
991 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
992 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
993 interfaces: list with interface info. Each item a dictionary with:
994 vim_interface_id - The ID of the ENI.
995 vim_net_id - The ID of the VPC subnet.
996 mac_address - The MAC address of the interface.
997 ip_address - The IP address of the interface within the subnet.
998 """
999 self.logger.debug("Getting VM instance information from VIM")
sousaedu80135b92021-02-17 15:05:18 +01001000
ahmadsa61ccd642017-04-20 18:17:28 -07001001 try:
1002 self._reload_connection()
aticig4e86d0c2022-06-02 20:03:13 +03001003 elapsed_time = 0
1004 while elapsed_time < self.server_timeout:
1005 reservation = self.conn.get_all_instances(vm_list)[0]
1006 if reservation:
1007 break
1008 time.sleep(5)
1009 elapsed_time += 5
1010
1011 # if we exceeded the timeout
1012 else:
1013 raise vimconn.VimConnException(
1014 vm_list + "could not be gathered, refresh vm status failed",
1015 http_code=vimconn.HTTP_Request_Timeout,
1016 )
1017
ahmadsa61ccd642017-04-20 18:17:28 -07001018 instances = {}
1019 instance_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001020
ahmadsa61ccd642017-04-20 18:17:28 -07001021 for instance in reservation.instances:
aticig4e86d0c2022-06-02 20:03:13 +03001022 if hasattr(instance, "id"):
ahmadsa61ccd642017-04-20 18:17:28 -07001023 try:
aticig4e86d0c2022-06-02 20:03:13 +03001024 if instance.state in ("pending"):
1025 instance_dict["status"] = "BUILD"
1026 elif instance.state in ("available", "running", "up"):
1027 instance_dict["status"] = "ACTIVE"
1028 else:
1029 instance_dict["status"] = "ERROR"
sousaedu80135b92021-02-17 15:05:18 +01001030
aticig4e86d0c2022-06-02 20:03:13 +03001031 instance_dict["error_msg"] = ""
1032 instance_dict["interfaces"] = []
1033
1034 for interface in instance.interfaces:
1035 interface_dict = {
1036 "vim_interface_id": interface.id,
1037 "vim_net_id": interface.subnet_id,
1038 "mac_address": interface.mac_address,
1039 }
1040
1041 if (
1042 hasattr(interface, "publicIp")
1043 and interface.publicIp is not None
1044 ):
1045 interface_dict["ip_address"] = (
1046 interface.publicIp
1047 + ";"
1048 + interface.private_ip_address
1049 )
1050 else:
1051 interface_dict[
1052 "ip_address"
1053 ] = interface.private_ip_address
1054
1055 instance_dict["interfaces"].append(interface_dict)
1056 except Exception as e:
1057 self.logger.error(
1058 "Exception getting vm status: %s", str(e), exc_info=True
1059 )
1060 instance_dict["status"] = "DELETED"
1061 instance_dict["error_msg"] = str(e)
1062 finally:
1063 instance_dictionary = vars(instance)
1064 cleared_instance_dict = {
1065 key: instance_dictionary[key]
1066 for key in instance_dictionary
1067 if not (isinstance(instance_dictionary[key], object))
1068 }
1069 instance_dict["vim_info"] = cleared_instance_dict
1070
1071 instances[instance.id] = instance_dict
sousaedu80135b92021-02-17 15:05:18 +01001072
ahmadsa61ccd642017-04-20 18:17:28 -07001073 return instances
1074 except Exception as e:
1075 self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
1076 self.format_vimconn_exception(e)
1077
tierno98e909c2017-10-14 13:27:03 +02001078 def action_vminstance(self, vm_id, action_dict, created_items={}):
ahmadsa61ccd642017-04-20 18:17:28 -07001079 """Send and action over a VM instance from VIM
1080 Returns the vm_id if the action was successfully sent to the VIM"""
1081
1082 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1083 try:
1084 self._reload_connection()
1085 if "start" in action_dict:
1086 self.conn.start_instances(vm_id)
1087 elif "stop" in action_dict or "stop" in action_dict:
1088 self.conn.stop_instances(vm_id)
1089 elif "terminate" in action_dict:
1090 self.conn.terminate_instances(vm_id)
1091 elif "reboot" in action_dict:
1092 self.conn.reboot_instances(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01001093
tierno98e909c2017-10-14 13:27:03 +02001094 return None
ahmadsa61ccd642017-04-20 18:17:28 -07001095 except Exception as e:
1096 self.format_vimconn_exception(e)
elumalai8658c2c2022-04-28 19:09:31 +05301097
1098 def migrate_instance(self, vm_id, compute_host=None):
1099 """
1100 Migrate a vdu
1101 param:
1102 vm_id: ID of an instance
1103 compute_host: Host to migrate the vdu to
1104 """
1105 # TODO: Add support for migration
1106 raise vimconn.VimConnNotImplemented("Not implemented")
sritharan29a4c1a2022-05-05 12:15:04 +00001107
1108 def resize_instance(self, vm_id, flavor_id=None):
1109 """
1110 resize a vdu
1111 param:
1112 vm_id: ID of an instance
1113 flavor_id: flavor to resize the vdu
1114 """
1115 # TODO: Add support for resize
1116 raise vimconn.VimConnNotImplemented("Not implemented")