blob: 393acba0704c431e40944bd8eaacd3adc8941b7f [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
ahmadsa61ccd642017-04-20 18:17:28 -0700207 def get_tenant_list(self, filter_dict={}):
208 """Obtain tenants of VIM
209 filter_dict dictionary that can contain the following keys:
210 name: filter by tenant name
211 id: filter by tenant uuid/id
212 <other VIM specific>
213 Returns the tenant list of dictionaries, and empty list if no tenant match all the filers:
214 [{'name':'<name>, 'id':'<id>, ...}, ...]
215 """
ahmadsa61ccd642017-04-20 18:17:28 -0700216 try:
217 self._reload_connection()
218 vpc_ids = []
sousaedu80135b92021-02-17 15:05:18 +0100219
ahmadsa61ccd642017-04-20 18:17:28 -0700220 if filter_dict != {}:
sousaedu80135b92021-02-17 15:05:18 +0100221 if "id" in filter_dict:
222 vpc_ids.append(filter_dict["id"])
sousaedu80135b92021-02-17 15:05:18 +0100223
aticig4e86d0c2022-06-02 20:03:13 +0300224 tenants = self.conn_vpc.get_all_vpcs(vpc_ids, None)
ahmadsa61ccd642017-04-20 18:17:28 -0700225 tenant_list = []
sousaedu80135b92021-02-17 15:05:18 +0100226
ahmadsa61ccd642017-04-20 18:17:28 -0700227 for tenant in tenants:
sousaedu80135b92021-02-17 15:05:18 +0100228 tenant_list.append(
229 {
230 "id": str(tenant.id),
231 "name": str(tenant.id),
232 "status": str(tenant.state),
233 "cidr_block": str(tenant.cidr_block),
234 }
235 )
236
ahmadsa61ccd642017-04-20 18:17:28 -0700237 return tenant_list
238 except Exception as e:
239 self.format_vimconn_exception(e)
240
241 def new_tenant(self, tenant_name, tenant_description):
242 """Adds a new tenant to VIM with this name and description, this is done using admin_url if provided
243 "tenant_name": string max lenght 64
244 "tenant_description": string max length 256
245 returns the tenant identifier or raise exception
246 """
ahmadsa61ccd642017-04-20 18:17:28 -0700247 self.logger.debug("Adding a new VPC")
sousaedu80135b92021-02-17 15:05:18 +0100248
ahmadsa61ccd642017-04-20 18:17:28 -0700249 try:
250 self._reload_connection()
251 vpc = self.conn_vpc.create_vpc(self.vpc_cidr_block)
252 self.conn_vpc.modify_vpc_attribute(vpc.id, enable_dns_support=True)
253 self.conn_vpc.modify_vpc_attribute(vpc.id, enable_dns_hostnames=True)
254
255 gateway = self.conn_vpc.create_internet_gateway()
256 self.conn_vpc.attach_internet_gateway(gateway.id, vpc.id)
257 route_table = self.conn_vpc.create_route_table(vpc.id)
sousaedu80135b92021-02-17 15:05:18 +0100258 self.conn_vpc.create_route(route_table.id, "0.0.0.0/0", gateway.id)
ahmadsa61ccd642017-04-20 18:17:28 -0700259
sousaedu80135b92021-02-17 15:05:18 +0100260 self.vpc_data[vpc.id] = {
261 "gateway": gateway.id,
262 "route_table": route_table.id,
aticig4e86d0c2022-06-02 20:03:13 +0300263 "subnets": self.subnet_sizes(self.vpc_cidr_block),
sousaedu80135b92021-02-17 15:05:18 +0100264 }
265
ahmadsa61ccd642017-04-20 18:17:28 -0700266 return vpc.id
267 except Exception as e:
268 self.format_vimconn_exception(e)
269
270 def delete_tenant(self, tenant_id):
271 """Delete a tenant from VIM
272 tenant_id: returned VIM tenant_id on "new_tenant"
273 Returns None on success. Raises and exception of failure. If tenant is not found raises vimconnNotFoundException
274 """
ahmadsa61ccd642017-04-20 18:17:28 -0700275 self.logger.debug("Deleting specified VPC")
sousaedu80135b92021-02-17 15:05:18 +0100276
ahmadsa61ccd642017-04-20 18:17:28 -0700277 try:
278 self._reload_connection()
279 vpc = self.vpc_data.get(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100280
281 if "gateway" in vpc and "route_table" in vpc:
282 gateway_id, route_table_id = vpc["gateway"], vpc["route_table"]
ahmadsa61ccd642017-04-20 18:17:28 -0700283 self.conn_vpc.detach_internet_gateway(gateway_id, tenant_id)
284 self.conn_vpc.delete_vpc(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100285 self.conn_vpc.delete_route(route_table_id, "0.0.0.0/0")
ahmadsa61ccd642017-04-20 18:17:28 -0700286 else:
287 self.conn_vpc.delete_vpc(tenant_id)
288 except Exception as e:
289 self.format_vimconn_exception(e)
290
aticig4e86d0c2022-06-02 20:03:13 +0300291 def subnet_sizes(self, cidr):
sousaedu80135b92021-02-17 15:05:18 +0100292 """Calculates possible subnets given CIDR value of VPC"""
sousaedu80135b92021-02-17 15:05:18 +0100293 netmasks = (
aticig4e86d0c2022-06-02 20:03:13 +0300294 "255.255.0.0",
295 "255.255.128.0",
296 "255.255.192.0",
297 "255.255.224.0",
298 "255.255.240.0",
299 "255.255.248.0",
sousaedu80135b92021-02-17 15:05:18 +0100300 )
aticig4e86d0c2022-06-02 20:03:13 +0300301
ahmadsa61ccd642017-04-20 18:17:28 -0700302 ip = netaddr.IPNetwork(cidr)
303 mask = ip.netmask
aticig4e86d0c2022-06-02 20:03:13 +0300304 pub_split = ()
ahmadsa61ccd642017-04-20 18:17:28 -0700305
aticig4e86d0c2022-06-02 20:03:13 +0300306 for netmask in netmasks:
307 if str(mask) == netmask:
308 pub_split = list(ip.subnet(24))
309 break
sousaedu80135b92021-02-17 15:05:18 +0100310
aticig4e86d0c2022-06-02 20:03:13 +0300311 subnets = pub_split if pub_split else (list(ip.subnet(28)))
ahmadsa61ccd642017-04-20 18:17:28 -0700312
313 return map(str, subnets)
314
sousaedu80135b92021-02-17 15:05:18 +0100315 def new_network(
316 self,
317 net_name,
318 net_type,
319 ip_profile=None,
320 shared=False,
321 provider_network_profile=None,
322 ):
ahmadsa61ccd642017-04-20 18:17:28 -0700323 """Adds a tenant network to VIM
324 Params:
325 'net_name': name of the network
326 'net_type': one of:
327 'bridge': overlay isolated network
328 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
329 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
330 'ip_profile': is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
331 'ip-version': can be one of ["IPv4","IPv6"]
332 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
333 'gateway-address': (Optional) ip_schema, that is X.X.X.X
334 'dns-address': (Optional) ip_schema,
335 'dhcp': (Optional) dict containing
336 'enabled': {"type": "boolean"},
337 'start-address': ip_schema, first IP to grant
338 'count': number of IPs to grant.
339 'shared': if this network can be seen/use by other tenants/organization
garciadeblasebd66722019-01-31 16:01:31 +0000340 Returns a tuple with the network identifier and created_items, or raises an exception on error
341 created_items can be None or a dictionary where this method can include key-values that will be passed to
342 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
343 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
344 as not present.
ahmadsa61ccd642017-04-20 18:17:28 -0700345 """
ahmadsa61ccd642017-04-20 18:17:28 -0700346 self.logger.debug("Adding a subnet to VPC")
sousaedu80135b92021-02-17 15:05:18 +0100347
ahmadsa61ccd642017-04-20 18:17:28 -0700348 try:
garciadeblasebd66722019-01-31 16:01:31 +0000349 created_items = {}
ahmadsa61ccd642017-04-20 18:17:28 -0700350 self._reload_connection()
351 subnet = None
352 vpc_id = self.vpc_id
aticig4e86d0c2022-06-02 20:03:13 +0300353 if self.conn_vpc.get_all_subnets():
354 existing_subnet = self.conn_vpc.get_all_subnets()[0]
355 if not self.availability_zone:
356 self.availability_zone = str(existing_subnet.availability_zone)
sousaedu80135b92021-02-17 15:05:18 +0100357
ahmadsa61ccd642017-04-20 18:17:28 -0700358 if self.vpc_data.get(vpc_id, None):
sousaedu80135b92021-02-17 15:05:18 +0100359 cidr_block = list(
360 set(self.vpc_data[vpc_id]["subnets"])
361 - set(
362 self.get_network_details(
363 {"tenant_id": vpc_id}, detail="cidr_block"
364 )
365 )
aticig4e86d0c2022-06-02 20:03:13 +0300366 )
ahmadsa61ccd642017-04-20 18:17:28 -0700367 else:
sousaedu80135b92021-02-17 15:05:18 +0100368 vpc = self.get_tenant_list({"id": vpc_id})[0]
aticig4e86d0c2022-06-02 20:03:13 +0300369 subnet_list = self.subnet_sizes(vpc["cidr_block"])
sousaedu80135b92021-02-17 15:05:18 +0100370 cidr_block = list(
371 set(subnet_list)
372 - set(
373 self.get_network_details(
374 {"tenant_id": vpc["id"]}, detail="cidr_block"
375 )
376 )
aticig4e86d0c2022-06-02 20:03:13 +0300377 )
sousaedu80135b92021-02-17 15:05:18 +0100378
aticig4e86d0c2022-06-02 20:03:13 +0300379 try:
380 selected_cidr_block = random.choice(cidr_block)
381 retry = 15
382 while retry > 0:
383 all_subnets = [
384 subnet.cidr_block for subnet in self.conn_vpc.get_all_subnets()
385 ]
386 all_subnets.append(selected_cidr_block)
387 conflict = check_conflicts(all_subnets)
388 if not conflict:
389 subnet = self.conn_vpc.create_subnet(
390 vpc_id, selected_cidr_block, self.availability_zone
391 )
392 break
393 retry -= 1
394 selected_cidr_block = random.choice(cidr_block)
395 else:
396 raise vimconn.VimConnException(
397 "Failed to find a proper CIDR which does not overlap"
398 "with existing subnets",
399 http_code=vimconn.HTTP_Request_Timeout,
400 )
401
402 except (EC2ResponseError, BotoServerError) as error:
403 self.format_vimconn_exception(error)
404
405 created_items["net:" + str(subnet.id)] = True
sousaedu80135b92021-02-17 15:05:18 +0100406
garciadeblasebd66722019-01-31 16:01:31 +0000407 return subnet.id, created_items
ahmadsa61ccd642017-04-20 18:17:28 -0700408 except Exception as e:
409 self.format_vimconn_exception(e)
410
411 def get_network_details(self, filters, detail):
sousaedu80135b92021-02-17 15:05:18 +0100412 """Get specified details related to a subnet"""
ahmadsa61ccd642017-04-20 18:17:28 -0700413 detail_list = []
414 subnet_list = self.get_network_list(filters)
sousaedu80135b92021-02-17 15:05:18 +0100415
ahmadsa61ccd642017-04-20 18:17:28 -0700416 for net in subnet_list:
417 detail_list.append(net[detail])
sousaedu80135b92021-02-17 15:05:18 +0100418
ahmadsa61ccd642017-04-20 18:17:28 -0700419 return detail_list
420
421 def get_network_list(self, filter_dict={}):
422 """Obtain tenant networks of VIM
423 Params:
424 'filter_dict' (optional) contains entries to return only networks that matches ALL entries:
425 name: string => returns only networks with this name
426 id: string => returns networks with this VIM id, this imply returns one network at most
427 shared: boolean >= returns only networks that are (or are not) shared
428 tenant_id: sting => returns only networks that belong to this tenant/project
tierno1ec592d2020-06-16 15:29:47 +0000429 ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin
430 state active
ahmadsa61ccd642017-04-20 18:17:28 -0700431 #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
432 Returns the network list of dictionaries. each dictionary contains:
433 'id': (mandatory) VIM network id
434 'name': (mandatory) VIM network name
435 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
436 'error_msg': (optional) text that explains the ERROR status
437 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
438 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
439 authorization, or some other unspecific error
440 """
ahmadsa61ccd642017-04-20 18:17:28 -0700441 self.logger.debug("Getting all subnets from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100442
ahmadsa61ccd642017-04-20 18:17:28 -0700443 try:
444 self._reload_connection()
445 tfilters = {}
sousaedu80135b92021-02-17 15:05:18 +0100446
ahmadsa61ccd642017-04-20 18:17:28 -0700447 if filter_dict != {}:
sousaedu80135b92021-02-17 15:05:18 +0100448 if "tenant_id" in filter_dict:
aticig4e86d0c2022-06-02 20:03:13 +0300449 tfilters["vpcId"] = filter_dict.get("tenant_id")
sousaedu80135b92021-02-17 15:05:18 +0100450
451 subnets = self.conn_vpc.get_all_subnets(
aticig4e86d0c2022-06-02 20:03:13 +0300452 subnet_ids=filter_dict.get("SubnetId", None), filters=tfilters
sousaedu80135b92021-02-17 15:05:18 +0100453 )
aticig4e86d0c2022-06-02 20:03:13 +0300454
ahmadsa61ccd642017-04-20 18:17:28 -0700455 net_list = []
sousaedu80135b92021-02-17 15:05:18 +0100456
ahmadsa61ccd642017-04-20 18:17:28 -0700457 for net in subnets:
aticig4e86d0c2022-06-02 20:03:13 +0300458 if net.id == filter_dict.get("name"):
459 self.availability_zone = str(net.availability_zone)
460 net_list.append(
461 {
462 "id": str(net.id),
463 "name": str(net.id),
464 "status": str(net.state),
465 "vpc_id": str(net.vpc_id),
466 "cidr_block": str(net.cidr_block),
467 "type": "bridge",
468 }
469 )
sousaedu80135b92021-02-17 15:05:18 +0100470
ahmadsa61ccd642017-04-20 18:17:28 -0700471 return net_list
472 except Exception as e:
473 self.format_vimconn_exception(e)
474
475 def get_network(self, net_id):
476 """Obtain network details from the 'net_id' VIM network
477 Return a dict that contains:
478 'id': (mandatory) VIM network id, that is, net_id
479 'name': (mandatory) VIM network name
480 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
481 'error_msg': (optional) text that explains the ERROR status
482 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
483 Raises an exception upon error or when network is not found
484 """
ahmadsa61ccd642017-04-20 18:17:28 -0700485 self.logger.debug("Getting Subnet from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100486
ahmadsa61ccd642017-04-20 18:17:28 -0700487 try:
488 self._reload_connection()
489 subnet = self.conn_vpc.get_all_subnets(net_id)[0]
sousaedu80135b92021-02-17 15:05:18 +0100490 return {
491 "id": str(subnet.id),
492 "name": str(subnet.id),
493 "status": str(subnet.state),
494 "vpc_id": str(subnet.vpc_id),
495 "cidr_block": str(subnet.cidr_block),
aticig4e86d0c2022-06-02 20:03:13 +0300496 "availability_zone": str(subnet.availability_zone),
sousaedu80135b92021-02-17 15:05:18 +0100497 }
ahmadsa61ccd642017-04-20 18:17:28 -0700498 except Exception as e:
499 self.format_vimconn_exception(e)
500
garciadeblasebd66722019-01-31 16:01:31 +0000501 def delete_network(self, net_id, created_items=None):
502 """
503 Removes a tenant network from VIM and its associated elements
504 :param net_id: VIM identifier of the network, provided by method new_network
505 :param created_items: dictionary with extra items to be deleted. provided by method new_network
ahmadsa61ccd642017-04-20 18:17:28 -0700506 Returns the network identifier or raises an exception upon error or when network is not found
507 """
ahmadsa61ccd642017-04-20 18:17:28 -0700508 self.logger.debug("Deleting subnet from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100509
ahmadsa61ccd642017-04-20 18:17:28 -0700510 try:
511 self._reload_connection()
512 self.logger.debug("DELETING NET_ID: " + str(net_id))
513 self.conn_vpc.delete_subnet(net_id)
sousaedu80135b92021-02-17 15:05:18 +0100514
ahmadsa61ccd642017-04-20 18:17:28 -0700515 return net_id
aticig4e86d0c2022-06-02 20:03:13 +0300516
ahmadsa61ccd642017-04-20 18:17:28 -0700517 except Exception as e:
aticig4e86d0c2022-06-02 20:03:13 +0300518 if isinstance(e, EC2ResponseError):
519 self.network_delete_on_termination.append(net_id)
520 self.logger.warning(
521 f"{net_id} could not be deleted, deletion will retry after dependencies resolved"
522 )
523 else:
524 self.format_vimconn_exception(e)
ahmadsa61ccd642017-04-20 18:17:28 -0700525
526 def refresh_nets_status(self, net_list):
527 """Get the status of the networks
528 Params:
529 'net_list': a list with the VIM network id to be get the status
530 Returns a dictionary with:
531 'net_id': #VIM id of this network
532 status: #Mandatory. Text with one of:
533 # DELETED (not found at vim)
534 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
535 # OTHER (Vim reported other status not understood)
536 # ERROR (VIM indicates an ERROR status)
537 # ACTIVE, INACTIVE, DOWN (admin down),
538 # BUILD (on building process)
539 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
540 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
541 'net_id2': ...
542 """
ahmadsa61ccd642017-04-20 18:17:28 -0700543 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100544
ahmadsa61ccd642017-04-20 18:17:28 -0700545 try:
546 dict_entry = {}
sousaedu80135b92021-02-17 15:05:18 +0100547
ahmadsa61ccd642017-04-20 18:17:28 -0700548 for net_id in net_list:
549 subnet_dict = {}
550 subnet = None
sousaedu80135b92021-02-17 15:05:18 +0100551
ahmadsa61ccd642017-04-20 18:17:28 -0700552 try:
553 subnet = self.conn_vpc.get_all_subnets(net_id)[0]
sousaedu80135b92021-02-17 15:05:18 +0100554
ahmadsa61ccd642017-04-20 18:17:28 -0700555 if subnet.state == "pending":
sousaedu80135b92021-02-17 15:05:18 +0100556 subnet_dict["status"] = "BUILD"
ahmadsa61ccd642017-04-20 18:17:28 -0700557 elif subnet.state == "available":
sousaedu80135b92021-02-17 15:05:18 +0100558 subnet_dict["status"] = "ACTIVE"
ahmadsa61ccd642017-04-20 18:17:28 -0700559 else:
sousaedu80135b92021-02-17 15:05:18 +0100560 subnet_dict["status"] = "ERROR"
561 subnet_dict["error_msg"] = ""
tierno1ec592d2020-06-16 15:29:47 +0000562 except Exception:
sousaedu80135b92021-02-17 15:05:18 +0100563 subnet_dict["status"] = "DELETED"
564 subnet_dict["error_msg"] = "Network not found"
ahmadsa61ccd642017-04-20 18:17:28 -0700565 finally:
aticig4e86d0c2022-06-02 20:03:13 +0300566 subnet_dictionary = vars(subnet)
567 cleared_subnet_dict = {
568 key: subnet_dictionary[key]
569 for key in subnet_dictionary
570 if not isinstance(subnet_dictionary[key], object)
571 }
572 subnet_dict["vim_info"] = cleared_subnet_dict
ahmadsa61ccd642017-04-20 18:17:28 -0700573 dict_entry[net_id] = subnet_dict
sousaedu80135b92021-02-17 15:05:18 +0100574
ahmadsa61ccd642017-04-20 18:17:28 -0700575 return dict_entry
576 except Exception as e:
577 self.format_vimconn_exception(e)
578
579 def get_flavor(self, flavor_id):
580 """Obtain flavor details from the VIM
581 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
582 Raises an exception upon error or if not found
583 """
ahmadsa61ccd642017-04-20 18:17:28 -0700584 self.logger.debug("Getting instance type")
sousaedu80135b92021-02-17 15:05:18 +0100585
ahmadsa61ccd642017-04-20 18:17:28 -0700586 try:
587 if flavor_id in self.flavor_info:
588 return self.flavor_info[flavor_id]
589 else:
sousaedu80135b92021-02-17 15:05:18 +0100590 raise vimconn.VimConnNotFoundException(
591 "Cannot find flavor with this flavor ID/Name"
592 )
ahmadsa61ccd642017-04-20 18:17:28 -0700593 except Exception as e:
594 self.format_vimconn_exception(e)
595
ahmadsa61ccd642017-04-20 18:17:28 -0700596 def new_image(self, image_dict):
sousaedu80135b92021-02-17 15:05:18 +0100597 """Adds a tenant image to VIM
ahmadsa61ccd642017-04-20 18:17:28 -0700598 Params: image_dict
tierno1ec592d2020-06-16 15:29:47 +0000599 name (string) - The name of the AMI. Valid only for EBS-based images.
600 description (string) - The description of the AMI.
601 image_location (string) - Full path to your AMI manifest in Amazon S3 storage. Only used for S3-based AMI’s.
602 architecture (string) - The architecture of the AMI. Valid choices are: * i386 * x86_64
603 kernel_id (string) - The ID of the kernel with which to launch the instances
604 root_device_name (string) - The root device name (e.g. /dev/sdh)
605 block_device_map (boto.ec2.blockdevicemapping.BlockDeviceMapping) - A BlockDeviceMapping data structure
606 describing the EBS volumes associated with the Image.
607 virtualization_type (string) - The virutalization_type of the image. Valid choices are: * paravirtual * hvm
608 sriov_net_support (string) - Advanced networking support. Valid choices are: * simple
609 snapshot_id (string) - A snapshot ID for the snapshot to be used as root device for the image. Mutually
610 exclusive with block_device_map, requires root_device_name
611 delete_root_volume_on_termination (bool) - Whether to delete the root volume of the image after instance
612 termination. Only applies when creating image from snapshot_id. Defaults to False. Note that leaving
613 volumes behind after instance termination is not free
ahmadsa61ccd642017-04-20 18:17:28 -0700614 Returns: image_id - image ID of the newly created image
615 """
ahmadsa61ccd642017-04-20 18:17:28 -0700616 try:
617 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100618 image_location = image_dict.get("image_location", None)
619
ahmadsa61ccd642017-04-20 18:17:28 -0700620 if image_location:
621 image_location = str(self.account_id) + str(image_location)
622
sousaedu80135b92021-02-17 15:05:18 +0100623 image_id = self.conn.register_image(
624 image_dict.get("name", None),
625 image_dict.get("description", None),
626 image_location,
627 image_dict.get("architecture", None),
628 image_dict.get("kernel_id", None),
629 image_dict.get("root_device_name", None),
630 image_dict.get("block_device_map", None),
631 image_dict.get("virtualization_type", None),
632 image_dict.get("sriov_net_support", None),
633 image_dict.get("snapshot_id", None),
634 image_dict.get("delete_root_volume_on_termination", None),
635 )
636
ahmadsa61ccd642017-04-20 18:17:28 -0700637 return image_id
638 except Exception as e:
639 self.format_vimconn_exception(e)
640
641 def delete_image(self, image_id):
642 """Deletes a tenant image from VIM
643 Returns the image_id if image is deleted or raises an exception on error"""
644
645 try:
646 self._reload_connection()
647 self.conn.deregister_image(image_id)
sousaedu80135b92021-02-17 15:05:18 +0100648
ahmadsa61ccd642017-04-20 18:17:28 -0700649 return image_id
650 except Exception as e:
651 self.format_vimconn_exception(e)
652
653 def get_image_id_from_path(self, path):
sousaedu80135b92021-02-17 15:05:18 +0100654 """
ahmadsa61ccd642017-04-20 18:17:28 -0700655 Params: path - location of the image
656 Returns: image_id - ID of the matching image
sousaedu80135b92021-02-17 15:05:18 +0100657 """
ahmadsa61ccd642017-04-20 18:17:28 -0700658 self._reload_connection()
659 try:
660 filters = {}
sousaedu80135b92021-02-17 15:05:18 +0100661
ahmadsa61ccd642017-04-20 18:17:28 -0700662 if path:
sousaedu80135b92021-02-17 15:05:18 +0100663 tokens = path.split("/")
664 filters["owner_id"] = tokens[0]
665 filters["name"] = "/".join(tokens[1:])
666
ahmadsa61ccd642017-04-20 18:17:28 -0700667 image = self.conn.get_all_images(filters=filters)[0]
sousaedu80135b92021-02-17 15:05:18 +0100668
ahmadsa61ccd642017-04-20 18:17:28 -0700669 return image.id
670 except Exception as e:
671 self.format_vimconn_exception(e)
672
673 def get_image_list(self, filter_dict={}):
674 """Obtain tenant images from VIM
675 Filter_dict can be:
676 name: image name
677 id: image uuid
678 checksum: image checksum
679 location: image path
680 Returns the image list of dictionaries:
681 [{<the fields at Filter_dict plus some VIM specific>}, ...]
682 List can be empty
683 """
ahmadsa61ccd642017-04-20 18:17:28 -0700684 self.logger.debug("Getting image list from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100685
ahmadsa61ccd642017-04-20 18:17:28 -0700686 try:
687 self._reload_connection()
688 image_id = None
689 filters = {}
sousaedu80135b92021-02-17 15:05:18 +0100690
691 if "id" in filter_dict:
692 image_id = filter_dict["id"]
693
694 if "name" in filter_dict:
695 filters["name"] = filter_dict["name"]
696
697 if "location" in filter_dict:
698 filters["location"] = filter_dict["location"]
699
ahmadsa61ccd642017-04-20 18:17:28 -0700700 # filters['image_type'] = 'machine'
701 # filter_dict['owner_id'] = self.account_id
702 images = self.conn.get_all_images(image_id, filters=filters)
703 image_list = []
sousaedu80135b92021-02-17 15:05:18 +0100704
ahmadsa61ccd642017-04-20 18:17:28 -0700705 for image in images:
sousaedu80135b92021-02-17 15:05:18 +0100706 image_list.append(
707 {
708 "id": str(image.id),
709 "name": str(image.name),
710 "status": str(image.state),
711 "owner": str(image.owner_id),
712 "location": str(image.location),
713 "is_public": str(image.is_public),
714 "architecture": str(image.architecture),
715 "platform": str(image.platform),
716 }
717 )
718
ahmadsa61ccd642017-04-20 18:17:28 -0700719 return image_list
720 except Exception as e:
721 self.format_vimconn_exception(e)
722
sousaedu80135b92021-02-17 15:05:18 +0100723 def new_vminstance(
724 self,
725 name,
726 description,
727 start,
728 image_id,
729 flavor_id,
Alexis Romerob70f4ed2022-03-11 18:00:49 +0100730 affinity_group_list,
sousaedu80135b92021-02-17 15:05:18 +0100731 net_list,
732 cloud_config=None,
733 disk_list=None,
734 availability_zone_index=None,
735 availability_zone_list=None,
736 ):
ahmadsa61ccd642017-04-20 18:17:28 -0700737 """Create a new VM/instance in AWS
738 Params: name
739 decription
740 start: (boolean) indicates if VM must start or created in pause mode.
741 image_id - image ID in AWS
742 flavor_id - instance type ID in AWS
743 net_list
744 name
745 net_id - subnet_id from AWS
tierno1ec592d2020-06-16 15:29:47 +0000746 vpci - (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
747 capabilities
garciadeblasc4f4d732018-10-25 18:17:24 +0200748 model: (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
ahmadsa61ccd642017-04-20 18:17:28 -0700749 mac_address: (optional) mac address to assign to this interface
750 type: (mandatory) can be one of:
751 virtual, in this case always connected to a network of type 'net_type=bridge'
tierno1ec592d2020-06-16 15:29:47 +0000752 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a
753 data/ptp network ot it
tierno66eba6e2017-11-10 17:09:18 +0100754 can created unconnected
755 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
tierno1ec592d2020-06-16 15:29:47 +0000756 VFnotShared - (SRIOV without VLAN tag) same as PF for network connectivity. VF where no other
757 VFs are allocated on the same physical NIC
ahmadsa61ccd642017-04-20 18:17:28 -0700758 bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
tierno1ec592d2020-06-16 15:29:47 +0000759 port_security': (optional) If False it must avoid any traffic filtering at this interface.
760 If missing or True, it must apply the default VIM behaviour
761 vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
762 interface. 'net_list' is modified
ahmadsa61ccd642017-04-20 18:17:28 -0700763 elastic_ip - True/False to define if an elastic_ip is required
764 cloud_config': (optional) dictionary with:
765 key-pairs': (optional) list of strings with the public key to be inserted to the default user
766 users': (optional) list of users to be inserted, each item is a dict with:
767 name': (mandatory) user name,
768 key-pairs': (optional) list of strings with the public key to be inserted to the user
769 user-data': (optional) string is a text script to be passed directly to cloud-init
770 config-files': (optional). List of files to be transferred. Each item is a dict with:
771 dest': (mandatory) string with the destination absolute path
772 encoding': (optional, by default text). Can be one of:
773 b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
774 content' (mandatory): string with the content of the file
775 permissions': (optional) string with file permissions, typically octal notation '0644'
776 owner: (optional) file owner, string with the format 'owner:group'
777 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
778 security-groups:
779 subnet_id
780 security_group_id
781 disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
782 image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
783 size': (mandatory) string with the size of the disk in GB
tierno98e909c2017-10-14 13:27:03 +0200784 Returns a tuple with the instance identifier and created_items or raises an exception on error
785 created_items can be None or a dictionary where this method can include key-values that will be passed to
786 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
787 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
788 as not present.
ahmadsa61ccd642017-04-20 18:17:28 -0700789 """
ahmadsa61ccd642017-04-20 18:17:28 -0700790 self.logger.debug("Creating a new VM instance")
sousaedu80135b92021-02-17 15:05:18 +0100791
ahmadsa61ccd642017-04-20 18:17:28 -0700792 try:
aticig4e86d0c2022-06-02 20:03:13 +0300793 created_items = {}
ahmadsa61ccd642017-04-20 18:17:28 -0700794 self._reload_connection()
aticig4e86d0c2022-06-02 20:03:13 +0300795 reservation = None
tierno0a1437e2017-10-02 00:17:43 +0200796 _, userdata = self._create_user_data(cloud_config)
ahmadsa61ccd642017-04-20 18:17:28 -0700797
798 if not net_list:
799 reservation = self.conn.run_instances(
800 image_id,
801 key_name=self.key_pair,
802 instance_type=flavor_id,
803 security_groups=self.security_groups,
sousaedu80135b92021-02-17 15:05:18 +0100804 user_data=userdata,
ahmadsa61ccd642017-04-20 18:17:28 -0700805 )
aticig4e86d0c2022-06-02 20:03:13 +0300806
ahmadsa61ccd642017-04-20 18:17:28 -0700807 else:
ahmadsa61ccd642017-04-20 18:17:28 -0700808 for index, subnet in enumerate(net_list):
aticig4e86d0c2022-06-02 20:03:13 +0300809 net_intr = self.conn_vpc.create_network_interface(
sousaedu80135b92021-02-17 15:05:18 +0100810 subnet_id=subnet.get("net_id"),
811 groups=None,
aticig4e86d0c2022-06-02 20:03:13 +0300812 )
813
814 interface = boto.ec2.networkinterface.NetworkInterfaceSpecification(
815 network_interface_id=net_intr.id,
816 device_index=index,
817 )
818
819 interfaces = boto.ec2.networkinterface.NetworkInterfaceCollection(
820 interface
sousaedu80135b92021-02-17 15:05:18 +0100821 )
ahmadsa61ccd642017-04-20 18:17:28 -0700822
sousaedu80135b92021-02-17 15:05:18 +0100823 if subnet.get("elastic_ip"):
ahmadsa61ccd642017-04-20 18:17:28 -0700824 eip = self.conn.allocate_address()
sousaedu80135b92021-02-17 15:05:18 +0100825 self.conn.associate_address(
826 allocation_id=eip.allocation_id,
827 network_interface_id=net_intr.id,
828 )
ahmadsa61ccd642017-04-20 18:17:28 -0700829
830 if index == 0:
aticig4e86d0c2022-06-02 20:03:13 +0300831 try:
832 reservation = self.conn.run_instances(
833 image_id,
834 key_name=self.key_pair,
835 instance_type=flavor_id,
836 security_groups=self.security_groups,
837 network_interfaces=interfaces,
838 user_data=userdata,
839 )
840 except Exception as instance_create_error:
841 self.logger.debug(traceback.format_exc())
842 self.format_vimconn_exception(instance_create_error)
843
844 if index > 0:
845 try:
846 if reservation:
847 instance_id = self.wait_for_instance_id(reservation)
848 if instance_id and self.wait_for_vm(
849 instance_id, "running"
850 ):
851 self.conn.attach_network_interface(
852 network_interface_id=net_intr.id,
853 instance_id=instance_id,
854 device_index=index,
855 )
856 except Exception as attach_network_error:
857 self.logger.debug(traceback.format_exc())
858 self.format_vimconn_exception(attach_network_error)
859
860 if instance_id := self.wait_for_instance_id(reservation):
861 time.sleep(30)
862 instance_status = self.refresh_vms_status(instance_id)
863 refreshed_instance_status = instance_status.get(instance_id)
864 instance_interfaces = refreshed_instance_status.get(
865 "interfaces"
ahmadsa61ccd642017-04-20 18:17:28 -0700866 )
aticig4e86d0c2022-06-02 20:03:13 +0300867 for idx, interface in enumerate(instance_interfaces):
868 if idx == index:
869 net_list[index]["vim_id"] = instance_interfaces[
870 idx
871 ].get("vim_interface_id")
sousaedu80135b92021-02-17 15:05:18 +0100872
aticig4e86d0c2022-06-02 20:03:13 +0300873 instance_id = self.wait_for_instance_id(reservation)
874 created_items["vm_id:" + str(instance_id)] = True
montesmoreno3b299bb2017-10-02 18:19:05 +0200875
aticig4e86d0c2022-06-02 20:03:13 +0300876 return instance_id, created_items
ahmadsa61ccd642017-04-20 18:17:28 -0700877 except Exception as e:
aticig4e86d0c2022-06-02 20:03:13 +0300878 self.logger.debug(traceback.format_exc())
ahmadsa61ccd642017-04-20 18:17:28 -0700879 self.format_vimconn_exception(e)
880
881 def get_vminstance(self, vm_id):
882 """Returns the VM instance information from VIM"""
ahmadsa61ccd642017-04-20 18:17:28 -0700883 try:
884 self._reload_connection()
885 reservation = self.conn.get_all_instances(vm_id)
sousaedu80135b92021-02-17 15:05:18 +0100886
ahmadsa61ccd642017-04-20 18:17:28 -0700887 return reservation[0].instances[0].__dict__
888 except Exception as e:
889 self.format_vimconn_exception(e)
890
garciadeblas89598d42022-06-30 13:57:43 +0200891 def delete_vminstance(self, vm_id, created_items=None, volumes_to_hold=None):
ahmadsa61ccd642017-04-20 18:17:28 -0700892 """Removes a VM instance from VIM
893 Returns the instance identifier"""
ahmadsa61ccd642017-04-20 18:17:28 -0700894 try:
895 self._reload_connection()
896 self.logger.debug("DELETING VM_ID: " + str(vm_id))
aticig4e86d0c2022-06-02 20:03:13 +0300897 reservation = self.conn.get_all_instances(vm_id)[0]
898 if hasattr(reservation, "instances"):
899 instance = reservation.instances[0]
sousaedu80135b92021-02-17 15:05:18 +0100900
aticig4e86d0c2022-06-02 20:03:13 +0300901 self.conn.terminate_instances(vm_id)
902 if self.wait_for_vm(vm_id, "terminated"):
903 for interface in instance.interfaces:
904 self.conn_vpc.delete_network_interface(
905 network_interface_id=interface.id,
906 )
907 if self.network_delete_on_termination:
908 for net in self.network_delete_on_termination:
909 try:
910 self.conn_vpc.delete_subnet(net)
911 except Exception as net_delete_error:
912 if isinstance(net_delete_error, EC2ResponseError):
913 self.logger.warning(f"Deleting network {net}: failed")
914 else:
915 self.format_vimconn_exception(net_delete_error)
916
917 return vm_id
ahmadsa61ccd642017-04-20 18:17:28 -0700918 except Exception as e:
919 self.format_vimconn_exception(e)
920
aticig4e86d0c2022-06-02 20:03:13 +0300921 def wait_for_instance_id(self, reservation):
922 if not reservation:
923 return False
924
925 self._reload_connection()
926 elapsed_time = 0
927 while elapsed_time < 30:
928 if reservation.instances:
929 instance_id = reservation.instances[0].id
930 return instance_id
931 time.sleep(5)
932 elapsed_time += 5
933 else:
934 raise vimconn.VimConnException(
935 "Failed to get instance_id for reservation",
aticig4e86d0c2022-06-02 20:03:13 +0300936 http_code=vimconn.HTTP_Request_Timeout,
937 )
938
939 def wait_for_vm(self, vm_id, status):
940 """wait until vm is in the desired status and return True.
941 If the timeout is reached generate an exception"""
942
943 self._reload_connection()
944
945 elapsed_time = 0
946 while elapsed_time < self.server_timeout:
947 if self.conn.get_all_instances(vm_id):
948 reservation = self.conn.get_all_instances(vm_id)[0]
949 if hasattr(reservation, "instances"):
950 instance = reservation.instances[0]
951 if instance.state == status:
952 return True
953 time.sleep(5)
954 elapsed_time += 5
955
956 # if we exceeded the timeout
957 else:
958 raise vimconn.VimConnException(
959 "Timeout waiting for instance " + vm_id + " to get " + status,
960 http_code=vimconn.HTTP_Request_Timeout,
961 )
962
ahmadsa61ccd642017-04-20 18:17:28 -0700963 def refresh_vms_status(self, vm_list):
sousaedu80135b92021-02-17 15:05:18 +0100964 """Get the status of the virtual machines and their interfaces/ports
ahmadsa61ccd642017-04-20 18:17:28 -0700965 Params: the list of VM identifiers
966 Returns a dictionary with:
967 vm_id: #VIM id of this Virtual Machine
968 status: #Mandatory. Text with one of:
969 # DELETED (not found at vim)
970 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
971 # OTHER (Vim reported other status not understood)
972 # ERROR (VIM indicates an ERROR status)
973 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
974 # BUILD (on building process), ERROR
975 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
976 #
977 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
978 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
979 interfaces: list with interface info. Each item a dictionary with:
980 vim_interface_id - The ID of the ENI.
981 vim_net_id - The ID of the VPC subnet.
982 mac_address - The MAC address of the interface.
983 ip_address - The IP address of the interface within the subnet.
984 """
985 self.logger.debug("Getting VM instance information from VIM")
sousaedu80135b92021-02-17 15:05:18 +0100986
ahmadsa61ccd642017-04-20 18:17:28 -0700987 try:
988 self._reload_connection()
aticig4e86d0c2022-06-02 20:03:13 +0300989 elapsed_time = 0
990 while elapsed_time < self.server_timeout:
991 reservation = self.conn.get_all_instances(vm_list)[0]
992 if reservation:
993 break
994 time.sleep(5)
995 elapsed_time += 5
996
997 # if we exceeded the timeout
998 else:
999 raise vimconn.VimConnException(
1000 vm_list + "could not be gathered, refresh vm status failed",
1001 http_code=vimconn.HTTP_Request_Timeout,
1002 )
1003
ahmadsa61ccd642017-04-20 18:17:28 -07001004 instances = {}
1005 instance_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001006
ahmadsa61ccd642017-04-20 18:17:28 -07001007 for instance in reservation.instances:
aticig4e86d0c2022-06-02 20:03:13 +03001008 if hasattr(instance, "id"):
ahmadsa61ccd642017-04-20 18:17:28 -07001009 try:
aticig4e86d0c2022-06-02 20:03:13 +03001010 if instance.state in ("pending"):
1011 instance_dict["status"] = "BUILD"
1012 elif instance.state in ("available", "running", "up"):
1013 instance_dict["status"] = "ACTIVE"
1014 else:
1015 instance_dict["status"] = "ERROR"
sousaedu80135b92021-02-17 15:05:18 +01001016
aticig4e86d0c2022-06-02 20:03:13 +03001017 instance_dict["error_msg"] = ""
1018 instance_dict["interfaces"] = []
1019
1020 for interface in instance.interfaces:
1021 interface_dict = {
1022 "vim_interface_id": interface.id,
1023 "vim_net_id": interface.subnet_id,
1024 "mac_address": interface.mac_address,
1025 }
1026
1027 if (
1028 hasattr(interface, "publicIp")
1029 and interface.publicIp is not None
1030 ):
1031 interface_dict["ip_address"] = (
1032 interface.publicIp
1033 + ";"
1034 + interface.private_ip_address
1035 )
1036 else:
1037 interface_dict[
1038 "ip_address"
1039 ] = interface.private_ip_address
1040
1041 instance_dict["interfaces"].append(interface_dict)
1042 except Exception as e:
1043 self.logger.error(
1044 "Exception getting vm status: %s", str(e), exc_info=True
1045 )
1046 instance_dict["status"] = "DELETED"
1047 instance_dict["error_msg"] = str(e)
1048 finally:
1049 instance_dictionary = vars(instance)
1050 cleared_instance_dict = {
1051 key: instance_dictionary[key]
1052 for key in instance_dictionary
1053 if not (isinstance(instance_dictionary[key], object))
1054 }
1055 instance_dict["vim_info"] = cleared_instance_dict
1056
1057 instances[instance.id] = instance_dict
sousaedu80135b92021-02-17 15:05:18 +01001058
ahmadsa61ccd642017-04-20 18:17:28 -07001059 return instances
1060 except Exception as e:
1061 self.logger.error("Exception getting vm status: %s", str(e), exc_info=True)
1062 self.format_vimconn_exception(e)
1063
tierno98e909c2017-10-14 13:27:03 +02001064 def action_vminstance(self, vm_id, action_dict, created_items={}):
ahmadsa61ccd642017-04-20 18:17:28 -07001065 """Send and action over a VM instance from VIM
1066 Returns the vm_id if the action was successfully sent to the VIM"""
1067
1068 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1069 try:
1070 self._reload_connection()
1071 if "start" in action_dict:
1072 self.conn.start_instances(vm_id)
1073 elif "stop" in action_dict or "stop" in action_dict:
1074 self.conn.stop_instances(vm_id)
1075 elif "terminate" in action_dict:
1076 self.conn.terminate_instances(vm_id)
1077 elif "reboot" in action_dict:
1078 self.conn.reboot_instances(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01001079
tierno98e909c2017-10-14 13:27:03 +02001080 return None
ahmadsa61ccd642017-04-20 18:17:28 -07001081 except Exception as e:
1082 self.format_vimconn_exception(e)
elumalai8658c2c2022-04-28 19:09:31 +05301083
1084 def migrate_instance(self, vm_id, compute_host=None):
1085 """
1086 Migrate a vdu
1087 param:
1088 vm_id: ID of an instance
1089 compute_host: Host to migrate the vdu to
1090 """
1091 # TODO: Add support for migration
1092 raise vimconn.VimConnNotImplemented("Not implemented")
sritharan29a4c1a2022-05-05 12:15:04 +00001093
1094 def resize_instance(self, vm_id, flavor_id=None):
1095 """
1096 resize a vdu
1097 param:
1098 vm_id: ID of an instance
1099 flavor_id: flavor to resize the vdu
1100 """
1101 # TODO: Add support for resize
1102 raise vimconn.VimConnNotImplemented("Not implemented")