blob: e9d406201193babef83ebd4989b3d798d60adbb0 [file] [log] [blame]
tierno7edb6752016-03-21 17:37:52 +01001# -*- coding: utf-8 -*-
2
3##
tierno92021022018-09-12 16:29:23 +02004# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
tierno7edb6752016-03-21 17:37:52 +01005# 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.
tierno7edb6752016-03-21 17:37:52 +010019##
20
tierno1ec592d2020-06-16 15:29:47 +000021"""
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000022osconnector implements all the methods to interact with openstack using the python-neutronclient.
23
24For the VNF forwarding graph, The OpenStack VIM connector calls the
25networking-sfc Neutron extension methods, whose resources are mapped
26to the VIM connector's SFC resources as follows:
27- Classification (OSM) -> Flow Classifier (Neutron)
28- Service Function Instance (OSM) -> Port Pair (Neutron)
29- Service Function (OSM) -> Port Pair Group (Neutron)
30- Service Function Path (OSM) -> Port Chain (Neutron)
tierno1ec592d2020-06-16 15:29:47 +000031"""
tierno7edb6752016-03-21 17:37:52 +010032
sousaedu049cbb12022-01-05 11:39:35 +000033import copy
34from http.client import HTTPException
elumalai8658c2c2022-04-28 19:09:31 +053035import json
tiernoae4a8d12016-07-08 12:30:39 +020036import logging
sousaedu049cbb12022-01-05 11:39:35 +000037from pprint import pformat
garciadeblas2299e3b2017-01-26 14:35:55 +000038import random
kate721d79b2017-06-24 04:21:38 -070039import re
sousaedu049cbb12022-01-05 11:39:35 +000040import time
Gulsum Atici4415c4c2023-01-19 12:44:06 +030041from typing import Dict, List, Optional, Tuple
sousaedu049cbb12022-01-05 11:39:35 +000042
43from cinderclient import client as cClient
gatici335a06a2023-07-26 00:34:04 +030044import cinderclient.exceptions as cExceptions
tiernob5cef372017-06-19 15:52:22 +020045from glanceclient import client as glClient
tierno7edb6752016-03-21 17:37:52 +010046import glanceclient.exc as gl1Exceptions
sousaedu049cbb12022-01-05 11:39:35 +000047from keystoneauth1 import session
48from keystoneauth1.identity import v2, v3
49import keystoneclient.exceptions as ksExceptions
50import keystoneclient.v2_0.client as ksClient_v2
51import keystoneclient.v3.client as ksClient_v3
52import netaddr
tierno7edb6752016-03-21 17:37:52 +010053from neutronclient.common import exceptions as neExceptions
sousaedu049cbb12022-01-05 11:39:35 +000054from neutronclient.neutron import client as neClient
55from novaclient import client as nClient, exceptions as nvExceptions
56from osm_ro_plugin import vimconn
tierno7edb6752016-03-21 17:37:52 +010057from requests.exceptions import ConnectionError
sousaedu049cbb12022-01-05 11:39:35 +000058import yaml
tierno7edb6752016-03-21 17:37:52 +010059
tierno1ec592d2020-06-16 15:29:47 +000060__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C., Eduardo Sousa"
61__date__ = "$22-sep-2017 23:59:59$"
tierno40e1bce2017-08-09 09:12:04 +020062
63"""contain the openstack virtual machine status to openmano status"""
sousaedu80135b92021-02-17 15:05:18 +010064vmStatus2manoFormat = {
65 "ACTIVE": "ACTIVE",
66 "PAUSED": "PAUSED",
67 "SUSPENDED": "SUSPENDED",
68 "SHUTOFF": "INACTIVE",
69 "BUILD": "BUILD",
70 "ERROR": "ERROR",
71 "DELETED": "DELETED",
72}
73netStatus2manoFormat = {
74 "ACTIVE": "ACTIVE",
75 "PAUSED": "PAUSED",
76 "INACTIVE": "INACTIVE",
77 "BUILD": "BUILD",
78 "ERROR": "ERROR",
79 "DELETED": "DELETED",
80}
tierno7edb6752016-03-21 17:37:52 +010081
sousaedu80135b92021-02-17 15:05:18 +010082supportedClassificationTypes = ["legacy_flow_classifier"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000083
tierno1ec592d2020-06-16 15:29:47 +000084# global var to have a timeout creating and deleting volumes
garciadeblas64b39c52020-05-21 08:07:25 +000085volume_timeout = 1800
86server_timeout = 1800
montesmoreno0c8def02016-12-22 12:16:23 +000087
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010088
gatici335a06a2023-07-26 00:34:04 +030089def catch_any_exception(func):
90 def format_exception(*args, **kwargs):
91 try:
92 return func(*args, *kwargs)
93 except Exception as e:
94 vimconnector._format_exception(e)
95
96 return format_exception
97
98
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010099class SafeDumper(yaml.SafeDumper):
100 def represent_data(self, data):
101 # Openstack APIs use custom subclasses of dict and YAML safe dumper
102 # is designed to not handle that (reference issue 142 of pyyaml)
103 if isinstance(data, dict) and data.__class__ != dict:
104 # A simple solution is to convert those items back to dicts
105 data = dict(data.items())
106
107 return super(SafeDumper, self).represent_data(data)
108
109
tierno72774862020-05-04 11:44:15 +0000110class vimconnector(vimconn.VimConnector):
sousaedu80135b92021-02-17 15:05:18 +0100111 def __init__(
112 self,
113 uuid,
114 name,
115 tenant_id,
116 tenant_name,
117 url,
118 url_admin=None,
119 user=None,
120 passwd=None,
121 log_level=None,
122 config={},
123 persistent_info={},
124 ):
tierno1ec592d2020-06-16 15:29:47 +0000125 """using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +0100126 'url' is the keystone authorization url,
127 'url_admin' is not use
tierno1ec592d2020-06-16 15:29:47 +0000128 """
sousaedu80135b92021-02-17 15:05:18 +0100129 api_version = config.get("APIversion")
kate721d79b2017-06-24 04:21:38 -0700130
sousaedu80135b92021-02-17 15:05:18 +0100131 if api_version and api_version not in ("v3.3", "v2.0", "2", "3"):
132 raise vimconn.VimConnException(
133 "Invalid value '{}' for config:APIversion. "
134 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version)
135 )
136
137 vim_type = config.get("vim_type")
138
139 if vim_type and vim_type not in ("vio", "VIO"):
140 raise vimconn.VimConnException(
141 "Invalid value '{}' for config:vim_type."
142 "Allowed values are 'vio' or 'VIO'".format(vim_type)
143 )
144
145 if config.get("dataplane_net_vlan_range") is not None:
tierno1ec592d2020-06-16 15:29:47 +0000146 # validate vlan ranges provided by user
sousaedu80135b92021-02-17 15:05:18 +0100147 self._validate_vlan_ranges(
148 config.get("dataplane_net_vlan_range"), "dataplane_net_vlan_range"
149 )
garciadeblasebd66722019-01-31 16:01:31 +0000150
sousaedu80135b92021-02-17 15:05:18 +0100151 if config.get("multisegment_vlan_range") is not None:
tierno1ec592d2020-06-16 15:29:47 +0000152 # validate vlan ranges provided by user
sousaedu80135b92021-02-17 15:05:18 +0100153 self._validate_vlan_ranges(
154 config.get("multisegment_vlan_range"), "multisegment_vlan_range"
155 )
kate721d79b2017-06-24 04:21:38 -0700156
sousaedu80135b92021-02-17 15:05:18 +0100157 vimconn.VimConnector.__init__(
158 self,
159 uuid,
160 name,
161 tenant_id,
162 tenant_name,
163 url,
164 url_admin,
165 user,
166 passwd,
167 log_level,
168 config,
169 )
tiernob3d36742017-03-03 23:51:05 +0100170
tierno4d1ce222018-04-06 10:41:06 +0200171 if self.config.get("insecure") and self.config.get("ca_cert"):
sousaedu80135b92021-02-17 15:05:18 +0100172 raise vimconn.VimConnException(
173 "options insecure and ca_cert are mutually exclusive"
174 )
175
tierno4d1ce222018-04-06 10:41:06 +0200176 self.verify = True
sousaedu80135b92021-02-17 15:05:18 +0100177
tierno4d1ce222018-04-06 10:41:06 +0200178 if self.config.get("insecure"):
179 self.verify = False
sousaedu80135b92021-02-17 15:05:18 +0100180
tierno4d1ce222018-04-06 10:41:06 +0200181 if self.config.get("ca_cert"):
182 self.verify = self.config.get("ca_cert")
tierno4d1ce222018-04-06 10:41:06 +0200183
tierno7edb6752016-03-21 17:37:52 +0100184 if not url:
sousaedu80135b92021-02-17 15:05:18 +0100185 raise TypeError("url param can not be NoneType")
186
tiernob5cef372017-06-19 15:52:22 +0200187 self.persistent_info = persistent_info
sousaedu80135b92021-02-17 15:05:18 +0100188 self.availability_zone = persistent_info.get("availability_zone", None)
Luis Vega25bc6382023-10-05 23:22:04 +0000189 self.storage_availability_zone = None
Luis Vegaafe8df22023-12-01 01:02:12 +0000190 self.vm_av_zone = None
sousaedu80135b92021-02-17 15:05:18 +0100191 self.session = persistent_info.get("session", {"reload_client": True})
192 self.my_tenant_id = self.session.get("my_tenant_id")
193 self.nova = self.session.get("nova")
194 self.neutron = self.session.get("neutron")
195 self.cinder = self.session.get("cinder")
196 self.glance = self.session.get("glance")
197 # self.glancev1 = self.session.get("glancev1")
198 self.keystone = self.session.get("keystone")
199 self.api_version3 = self.session.get("api_version3")
kate721d79b2017-06-24 04:21:38 -0700200 self.vim_type = self.config.get("vim_type")
sousaedu80135b92021-02-17 15:05:18 +0100201
kate721d79b2017-06-24 04:21:38 -0700202 if self.vim_type:
203 self.vim_type = self.vim_type.upper()
sousaedu80135b92021-02-17 15:05:18 +0100204
kate721d79b2017-06-24 04:21:38 -0700205 if self.config.get("use_internal_endpoint"):
206 self.endpoint_type = "internalURL"
207 else:
208 self.endpoint_type = None
montesmoreno0c8def02016-12-22 12:16:23 +0000209
sousaedu80135b92021-02-17 15:05:18 +0100210 logging.getLogger("urllib3").setLevel(logging.WARNING)
211 logging.getLogger("keystoneauth").setLevel(logging.WARNING)
212 logging.getLogger("novaclient").setLevel(logging.WARNING)
213 self.logger = logging.getLogger("ro.vim.openstack")
kate721d79b2017-06-24 04:21:38 -0700214
tiernoa05b65a2019-02-01 12:30:27 +0000215 # allow security_groups to be a list or a single string
sousaedu80135b92021-02-17 15:05:18 +0100216 if isinstance(self.config.get("security_groups"), str):
217 self.config["security_groups"] = [self.config["security_groups"]]
218
tiernoa05b65a2019-02-01 12:30:27 +0000219 self.security_groups_id = None
220
tierno1ec592d2020-06-16 15:29:47 +0000221 # ###### VIO Specific Changes #########
kate721d79b2017-06-24 04:21:38 -0700222 if self.vim_type == "VIO":
sousaedu80135b92021-02-17 15:05:18 +0100223 self.logger = logging.getLogger("ro.vim.vio")
kate721d79b2017-06-24 04:21:38 -0700224
tiernofe789902016-09-29 14:20:44 +0000225 if log_level:
tierno1ec592d2020-06-16 15:29:47 +0000226 self.logger.setLevel(getattr(logging, log_level))
tiernof716aea2017-06-21 18:01:40 +0200227
228 def __getitem__(self, index):
229 """Get individuals parameters.
230 Throw KeyError"""
sousaedu80135b92021-02-17 15:05:18 +0100231 if index == "project_domain_id":
tiernof716aea2017-06-21 18:01:40 +0200232 return self.config.get("project_domain_id")
sousaedu80135b92021-02-17 15:05:18 +0100233 elif index == "user_domain_id":
tiernof716aea2017-06-21 18:01:40 +0200234 return self.config.get("user_domain_id")
235 else:
tierno72774862020-05-04 11:44:15 +0000236 return vimconn.VimConnector.__getitem__(self, index)
tiernof716aea2017-06-21 18:01:40 +0200237
238 def __setitem__(self, index, value):
239 """Set individuals parameters and it is marked as dirty so to force connection reload.
240 Throw KeyError"""
sousaedu80135b92021-02-17 15:05:18 +0100241 if index == "project_domain_id":
tiernof716aea2017-06-21 18:01:40 +0200242 self.config["project_domain_id"] = value
sousaedu80135b92021-02-17 15:05:18 +0100243 elif index == "user_domain_id":
tierno1ec592d2020-06-16 15:29:47 +0000244 self.config["user_domain_id"] = value
tiernof716aea2017-06-21 18:01:40 +0200245 else:
tierno72774862020-05-04 11:44:15 +0000246 vimconn.VimConnector.__setitem__(self, index, value)
sousaedu80135b92021-02-17 15:05:18 +0100247
248 self.session["reload_client"] = True
tiernof716aea2017-06-21 18:01:40 +0200249
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100250 def serialize(self, value):
251 """Serialization of python basic types.
252
253 In the case value is not serializable a message will be logged and a
254 simple representation of the data that cannot be converted back to
255 python is returned.
256 """
tierno7d782ef2019-10-04 12:56:31 +0000257 if isinstance(value, str):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100258 return value
259
260 try:
sousaedu80135b92021-02-17 15:05:18 +0100261 return yaml.dump(
262 value, Dumper=SafeDumper, default_flow_style=True, width=256
263 )
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100264 except yaml.representer.RepresenterError:
sousaedu80135b92021-02-17 15:05:18 +0100265 self.logger.debug(
266 "The following entity cannot be serialized in YAML:\n\n%s\n\n",
267 pformat(value),
268 exc_info=True,
269 )
270
tierno1ec592d2020-06-16 15:29:47 +0000271 return str(value)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100272
tierno7edb6752016-03-21 17:37:52 +0100273 def _reload_connection(self):
tierno1ec592d2020-06-16 15:29:47 +0000274 """Called before any operation, it check if credentials has changed
tierno7edb6752016-03-21 17:37:52 +0100275 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
tierno1ec592d2020-06-16 15:29:47 +0000276 """
277 # TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
sousaedu80135b92021-02-17 15:05:18 +0100278 if self.session["reload_client"]:
279 if self.config.get("APIversion"):
280 self.api_version3 = (
281 self.config["APIversion"] == "v3.3"
282 or self.config["APIversion"] == "3"
283 )
tiernof716aea2017-06-21 18:01:40 +0200284 else: # get from ending auth_url that end with v3 or with v2.0
sousaedu80135b92021-02-17 15:05:18 +0100285 self.api_version3 = self.url.endswith("/v3") or self.url.endswith(
286 "/v3/"
287 )
288
289 self.session["api_version3"] = self.api_version3
290
tiernof716aea2017-06-21 18:01:40 +0200291 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100292 if self.config.get("project_domain_id") or self.config.get(
293 "project_domain_name"
294 ):
tierno3cb8dc32017-10-24 18:13:19 +0200295 project_domain_id_default = None
296 else:
sousaedu80135b92021-02-17 15:05:18 +0100297 project_domain_id_default = "default"
298
299 if self.config.get("user_domain_id") or self.config.get(
300 "user_domain_name"
301 ):
tierno3cb8dc32017-10-24 18:13:19 +0200302 user_domain_id_default = None
303 else:
sousaedu80135b92021-02-17 15:05:18 +0100304 user_domain_id_default = "default"
305 auth = v3.Password(
306 auth_url=self.url,
307 username=self.user,
308 password=self.passwd,
309 project_name=self.tenant_name,
310 project_id=self.tenant_id,
311 project_domain_id=self.config.get(
312 "project_domain_id", project_domain_id_default
313 ),
314 user_domain_id=self.config.get(
315 "user_domain_id", user_domain_id_default
316 ),
317 project_domain_name=self.config.get("project_domain_name"),
318 user_domain_name=self.config.get("user_domain_name"),
319 )
ahmadsa95baa272016-11-30 09:14:11 +0500320 else:
sousaedu80135b92021-02-17 15:05:18 +0100321 auth = v2.Password(
322 auth_url=self.url,
323 username=self.user,
324 password=self.passwd,
325 tenant_name=self.tenant_name,
326 tenant_id=self.tenant_id,
327 )
328
tierno4d1ce222018-04-06 10:41:06 +0200329 sess = session.Session(auth=auth, verify=self.verify)
garciadeblas92651a12024-11-28 17:46:13 +0100330 # added region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River
tierno1ec592d2020-06-16 15:29:47 +0000331 # Titanium cloud and StarlingX
sousaedu80135b92021-02-17 15:05:18 +0100332 region_name = self.config.get("region_name")
333
tiernof716aea2017-06-21 18:01:40 +0200334 if self.api_version3:
garciadeblas92651a12024-11-28 17:46:13 +0100335 self.logger.debug(f"Using Keystone client v3 for VIM {self.id}")
sousaedu80135b92021-02-17 15:05:18 +0100336 self.keystone = ksClient_v3.Client(
337 session=sess,
338 endpoint_type=self.endpoint_type,
339 region_name=region_name,
340 )
tiernof716aea2017-06-21 18:01:40 +0200341 else:
garciadeblas92651a12024-11-28 17:46:13 +0100342 self.logger.debug(f"Using Keystone client v2 for VIM {self.id}")
sousaedu80135b92021-02-17 15:05:18 +0100343 self.keystone = ksClient_v2.Client(
344 session=sess, endpoint_type=self.endpoint_type
345 )
346
347 self.session["keystone"] = self.keystone
348 # In order to enable microversion functionality an explicit microversion must be specified in "config".
montesmoreno9317d302017-08-16 12:48:23 +0200349 # This implementation approach is due to the warning message in
350 # https://developer.openstack.org/api-guide/compute/microversions.html
351 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
352 # always require an specific microversion.
sousaedu80135b92021-02-17 15:05:18 +0100353 # To be able to use "device role tagging" functionality define "microversion: 2.32" in datacenter config
montesmoreno9317d302017-08-16 12:48:23 +0200354 version = self.config.get("microversion")
sousaedu80135b92021-02-17 15:05:18 +0100355
montesmoreno9317d302017-08-16 12:48:23 +0200356 if not version:
vegallc53829d2023-06-01 00:47:44 -0500357 version = "2.60"
sousaedu80135b92021-02-17 15:05:18 +0100358
tierno1ec592d2020-06-16 15:29:47 +0000359 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River
360 # Titanium cloud and StarlingX
sousaedu80135b92021-02-17 15:05:18 +0100361 self.nova = self.session["nova"] = nClient.Client(
362 str(version),
363 session=sess,
364 endpoint_type=self.endpoint_type,
365 region_name=region_name,
366 )
367 self.neutron = self.session["neutron"] = neClient.Client(
368 "2.0",
369 session=sess,
370 endpoint_type=self.endpoint_type,
371 region_name=region_name,
372 )
Lovejeet Singh778f3cc2023-02-13 16:15:40 +0530373
garciadeblas92651a12024-11-28 17:46:13 +0100374 if sess.get_all_version_data(service_type="volumev3"):
375 self.logger.debug(f"Using Cinder client v3 for VIM {self.id}")
376 self.cinder = self.session["cinder"] = cClient.Client(
377 3,
378 session=sess,
379 endpoint_type=self.endpoint_type,
380 region_name=region_name,
381 )
382 elif sess.get_all_version_data(service_type="volumev2"):
383 self.logger.debug(
384 f"Service type volumev3 not found. Using Cinder client v2 for VIM {self.id}"
385 )
Lovejeet Singh778f3cc2023-02-13 16:15:40 +0530386 self.cinder = self.session["cinder"] = cClient.Client(
387 2,
388 session=sess,
389 endpoint_type=self.endpoint_type,
390 region_name=region_name,
391 )
392 else:
garciadeblas92651a12024-11-28 17:46:13 +0100393 self.logger.debug(
394 f"Service type not found. Using Cinder client v3 for VIM {self.id}"
395 )
Lovejeet Singh778f3cc2023-02-13 16:15:40 +0530396 self.cinder = self.session["cinder"] = cClient.Client(
397 3,
398 session=sess,
399 endpoint_type=self.endpoint_type,
400 region_name=region_name,
401 )
sousaedu80135b92021-02-17 15:05:18 +0100402
tiernoa05b65a2019-02-01 12:30:27 +0000403 try:
sousaedu80135b92021-02-17 15:05:18 +0100404 self.my_tenant_id = self.session["my_tenant_id"] = sess.get_project_id()
tierno1ec592d2020-06-16 15:29:47 +0000405 except Exception:
tiernoa05b65a2019-02-01 12:30:27 +0000406 self.logger.error("Cannot get project_id from session", exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +0100407
kate721d79b2017-06-24 04:21:38 -0700408 if self.endpoint_type == "internalURL":
409 glance_service_id = self.keystone.services.list(name="glance")[0].id
sousaedu80135b92021-02-17 15:05:18 +0100410 glance_endpoint = self.keystone.endpoints.list(
411 glance_service_id, interface="internal"
412 )[0].url
kate721d79b2017-06-24 04:21:38 -0700413 else:
414 glance_endpoint = None
sousaedu80135b92021-02-17 15:05:18 +0100415
416 self.glance = self.session["glance"] = glClient.Client(
417 2, session=sess, endpoint=glance_endpoint
418 )
tiernoa05b65a2019-02-01 12:30:27 +0000419 # using version 1 of glance client in new_image()
sousaedu80135b92021-02-17 15:05:18 +0100420 # self.glancev1 = self.session["glancev1"] = glClient.Client("1", session=sess,
tierno1beea862018-07-11 15:47:37 +0200421 # endpoint=glance_endpoint)
sousaedu80135b92021-02-17 15:05:18 +0100422 self.session["reload_client"] = False
423 self.persistent_info["session"] = self.session
mirabal29356312017-07-27 12:21:22 +0200424 # add availablity zone info inside self.persistent_info
425 self._set_availablity_zones()
sousaedu80135b92021-02-17 15:05:18 +0100426 self.persistent_info["availability_zone"] = self.availability_zone
427 # force to get again security_groups_ids next time they are needed
428 self.security_groups_id = None
ahmadsa95baa272016-11-30 09:14:11 +0500429
tierno7edb6752016-03-21 17:37:52 +0100430 def __net_os2mano(self, net_list_dict):
tierno1ec592d2020-06-16 15:29:47 +0000431 """Transform the net openstack format to mano format
432 net_list_dict can be a list of dict or a single dict"""
tierno7edb6752016-03-21 17:37:52 +0100433 if type(net_list_dict) is dict:
tierno1ec592d2020-06-16 15:29:47 +0000434 net_list_ = (net_list_dict,)
tierno7edb6752016-03-21 17:37:52 +0100435 elif type(net_list_dict) is list:
tierno1ec592d2020-06-16 15:29:47 +0000436 net_list_ = net_list_dict
tierno7edb6752016-03-21 17:37:52 +0100437 else:
438 raise TypeError("param net_list_dict must be a list or a dictionary")
439 for net in net_list_:
sousaedu80135b92021-02-17 15:05:18 +0100440 if net.get("provider:network_type") == "vlan":
441 net["type"] = "data"
tierno7edb6752016-03-21 17:37:52 +0100442 else:
sousaedu80135b92021-02-17 15:05:18 +0100443 net["type"] = "bridge"
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200444
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000445 def __classification_os2mano(self, class_list_dict):
446 """Transform the openstack format (Flow Classifier) to mano format
447 (Classification) class_list_dict can be a list of dict or a single dict
448 """
449 if isinstance(class_list_dict, dict):
450 class_list_ = [class_list_dict]
451 elif isinstance(class_list_dict, list):
452 class_list_ = class_list_dict
453 else:
tierno1ec592d2020-06-16 15:29:47 +0000454 raise TypeError("param class_list_dict must be a list or a dictionary")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000455 for classification in class_list_:
sousaedu80135b92021-02-17 15:05:18 +0100456 id = classification.pop("id")
457 name = classification.pop("name")
458 description = classification.pop("description")
459 project_id = classification.pop("project_id")
460 tenant_id = classification.pop("tenant_id")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000461 original_classification = copy.deepcopy(classification)
462 classification.clear()
sousaedu80135b92021-02-17 15:05:18 +0100463 classification["ctype"] = "legacy_flow_classifier"
464 classification["definition"] = original_classification
465 classification["id"] = id
466 classification["name"] = name
467 classification["description"] = description
468 classification["project_id"] = project_id
469 classification["tenant_id"] = tenant_id
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000470
471 def __sfi_os2mano(self, sfi_list_dict):
472 """Transform the openstack format (Port Pair) to mano format (SFI)
473 sfi_list_dict can be a list of dict or a single dict
474 """
475 if isinstance(sfi_list_dict, dict):
476 sfi_list_ = [sfi_list_dict]
477 elif isinstance(sfi_list_dict, list):
478 sfi_list_ = sfi_list_dict
479 else:
sousaedu80135b92021-02-17 15:05:18 +0100480 raise TypeError("param sfi_list_dict must be a list or a dictionary")
481
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000482 for sfi in sfi_list_:
sousaedu80135b92021-02-17 15:05:18 +0100483 sfi["ingress_ports"] = []
484 sfi["egress_ports"] = []
485
486 if sfi.get("ingress"):
487 sfi["ingress_ports"].append(sfi["ingress"])
488
489 if sfi.get("egress"):
490 sfi["egress_ports"].append(sfi["egress"])
491
492 del sfi["ingress"]
493 del sfi["egress"]
494 params = sfi.get("service_function_parameters")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000495 sfc_encap = False
sousaedu80135b92021-02-17 15:05:18 +0100496
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000497 if params:
sousaedu80135b92021-02-17 15:05:18 +0100498 correlation = params.get("correlation")
499
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000500 if correlation:
501 sfc_encap = True
sousaedu80135b92021-02-17 15:05:18 +0100502
503 sfi["sfc_encap"] = sfc_encap
504 del sfi["service_function_parameters"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000505
506 def __sf_os2mano(self, sf_list_dict):
507 """Transform the openstack format (Port Pair Group) to mano format (SF)
508 sf_list_dict can be a list of dict or a single dict
509 """
510 if isinstance(sf_list_dict, dict):
511 sf_list_ = [sf_list_dict]
512 elif isinstance(sf_list_dict, list):
513 sf_list_ = sf_list_dict
514 else:
sousaedu80135b92021-02-17 15:05:18 +0100515 raise TypeError("param sf_list_dict must be a list or a dictionary")
516
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000517 for sf in sf_list_:
sousaedu80135b92021-02-17 15:05:18 +0100518 del sf["port_pair_group_parameters"]
519 sf["sfis"] = sf["port_pairs"]
520 del sf["port_pairs"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000521
522 def __sfp_os2mano(self, sfp_list_dict):
523 """Transform the openstack format (Port Chain) to mano format (SFP)
524 sfp_list_dict can be a list of dict or a single dict
525 """
526 if isinstance(sfp_list_dict, dict):
527 sfp_list_ = [sfp_list_dict]
528 elif isinstance(sfp_list_dict, list):
529 sfp_list_ = sfp_list_dict
530 else:
sousaedu80135b92021-02-17 15:05:18 +0100531 raise TypeError("param sfp_list_dict must be a list or a dictionary")
532
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000533 for sfp in sfp_list_:
sousaedu80135b92021-02-17 15:05:18 +0100534 params = sfp.pop("chain_parameters")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000535 sfc_encap = False
sousaedu80135b92021-02-17 15:05:18 +0100536
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000537 if params:
sousaedu80135b92021-02-17 15:05:18 +0100538 correlation = params.get("correlation")
539
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000540 if correlation:
541 sfc_encap = True
sousaedu80135b92021-02-17 15:05:18 +0100542
543 sfp["sfc_encap"] = sfc_encap
544 sfp["spi"] = sfp.pop("chain_id")
545 sfp["classifications"] = sfp.pop("flow_classifiers")
546 sfp["service_functions"] = sfp.pop("port_pair_groups")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000547
548 # placeholder for now; read TODO note below
549 def _validate_classification(self, type, definition):
550 # only legacy_flow_classifier Type is supported at this point
551 return True
552 # TODO(igordcard): this method should be an abstract method of an
553 # abstract Classification class to be implemented by the specific
554 # Types. Also, abstract vimconnector should call the validation
555 # method before the implemented VIM connectors are called.
556
gatici335a06a2023-07-26 00:34:04 +0300557 @staticmethod
558 def _format_exception(exception):
tierno69647792020-03-05 16:45:48 +0000559 """Transform a keystone, nova, neutron exception into a vimconn exception discovering the cause"""
tierno69647792020-03-05 16:45:48 +0000560 message_error = str(exception)
tierno5ad826a2020-08-11 11:19:44 +0000561 tip = ""
tiernode12f782019-04-05 12:46:42 +0000562
sousaedu80135b92021-02-17 15:05:18 +0100563 if isinstance(
564 exception,
565 (
566 neExceptions.NetworkNotFoundClient,
567 nvExceptions.NotFound,
gatici335a06a2023-07-26 00:34:04 +0300568 nvExceptions.ResourceNotFound,
sousaedu80135b92021-02-17 15:05:18 +0100569 ksExceptions.NotFound,
570 gl1Exceptions.HTTPNotFound,
gatici335a06a2023-07-26 00:34:04 +0300571 cExceptions.NotFound,
sousaedu80135b92021-02-17 15:05:18 +0100572 ),
573 ):
574 raise vimconn.VimConnNotFoundException(
575 type(exception).__name__ + ": " + message_error
576 )
577 elif isinstance(
578 exception,
579 (
580 HTTPException,
581 gl1Exceptions.HTTPException,
582 gl1Exceptions.CommunicationError,
583 ConnectionError,
584 ksExceptions.ConnectionError,
585 neExceptions.ConnectionFailed,
gatici335a06a2023-07-26 00:34:04 +0300586 cExceptions.ConnectionError,
sousaedu80135b92021-02-17 15:05:18 +0100587 ),
588 ):
tierno5ad826a2020-08-11 11:19:44 +0000589 if type(exception).__name__ == "SSLError":
590 tip = " (maybe option 'insecure' must be added to the VIM)"
sousaedu80135b92021-02-17 15:05:18 +0100591
592 raise vimconn.VimConnConnectionException(
593 "Invalid URL or credentials{}: {}".format(tip, message_error)
594 )
595 elif isinstance(
596 exception,
597 (
598 KeyError,
599 nvExceptions.BadRequest,
600 ksExceptions.BadRequest,
gatici335a06a2023-07-26 00:34:04 +0300601 gl1Exceptions.BadRequest,
602 cExceptions.BadRequest,
sousaedu80135b92021-02-17 15:05:18 +0100603 ),
604 ):
Patricia Reinoso17852162023-06-15 07:33:04 +0000605 if message_error == "OS-EXT-SRV-ATTR:host":
606 tip = " (If the user does not have non-admin credentials, this attribute will be missing)"
607 raise vimconn.VimConnInsufficientCredentials(
608 type(exception).__name__ + ": " + message_error + tip
609 )
sousaedu80135b92021-02-17 15:05:18 +0100610 raise vimconn.VimConnException(
611 type(exception).__name__ + ": " + message_error
612 )
Patricia Reinoso17852162023-06-15 07:33:04 +0000613
sousaedu80135b92021-02-17 15:05:18 +0100614 elif isinstance(
615 exception,
616 (
617 nvExceptions.ClientException,
618 ksExceptions.ClientException,
619 neExceptions.NeutronException,
gatici335a06a2023-07-26 00:34:04 +0300620 cExceptions.ClientException,
sousaedu80135b92021-02-17 15:05:18 +0100621 ),
622 ):
623 raise vimconn.VimConnUnexpectedResponse(
624 type(exception).__name__ + ": " + message_error
625 )
tiernoae4a8d12016-07-08 12:30:39 +0200626 elif isinstance(exception, nvExceptions.Conflict):
sousaedu80135b92021-02-17 15:05:18 +0100627 raise vimconn.VimConnConflictException(
628 type(exception).__name__ + ": " + message_error
629 )
tierno72774862020-05-04 11:44:15 +0000630 elif isinstance(exception, vimconn.VimConnException):
tierno41a69812018-02-16 14:34:33 +0100631 raise exception
tiernof716aea2017-06-21 18:01:40 +0200632 else: # ()
gatici335a06a2023-07-26 00:34:04 +0300633 logger = logging.getLogger("ro.vim.openstack")
634 logger.error("General Exception " + message_error, exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +0100635
gatici335a06a2023-07-26 00:34:04 +0300636 raise vimconn.VimConnException(
sousaedu80135b92021-02-17 15:05:18 +0100637 type(exception).__name__ + ": " + message_error
638 )
tiernoae4a8d12016-07-08 12:30:39 +0200639
tiernoa05b65a2019-02-01 12:30:27 +0000640 def _get_ids_from_name(self):
641 """
642 Obtain ids from name of tenant and security_groups. Store at self .security_groups_id"
643 :return: None
644 """
645 # get tenant_id if only tenant_name is supplied
646 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100647
tiernoa05b65a2019-02-01 12:30:27 +0000648 if not self.my_tenant_id:
sousaedu80135b92021-02-17 15:05:18 +0100649 raise vimconn.VimConnConnectionException(
650 "Error getting tenant information from name={} id={}".format(
651 self.tenant_name, self.tenant_id
652 )
653 )
654
655 if self.config.get("security_groups") and not self.security_groups_id:
tiernoa05b65a2019-02-01 12:30:27 +0000656 # convert from name to id
sousaedu80135b92021-02-17 15:05:18 +0100657 neutron_sg_list = self.neutron.list_security_groups(
658 tenant_id=self.my_tenant_id
659 )["security_groups"]
tiernoa05b65a2019-02-01 12:30:27 +0000660
661 self.security_groups_id = []
sousaedu80135b92021-02-17 15:05:18 +0100662 for sg in self.config.get("security_groups"):
tiernoa05b65a2019-02-01 12:30:27 +0000663 for neutron_sg in neutron_sg_list:
664 if sg in (neutron_sg["id"], neutron_sg["name"]):
665 self.security_groups_id.append(neutron_sg["id"])
666 break
667 else:
668 self.security_groups_id = None
sousaedu80135b92021-02-17 15:05:18 +0100669
670 raise vimconn.VimConnConnectionException(
671 "Not found security group {} for this tenant".format(sg)
672 )
tiernoa05b65a2019-02-01 12:30:27 +0000673
vegallc53829d2023-06-01 00:47:44 -0500674 def _find_nova_server(self, vm_id):
675 """
676 Returns the VM instance from Openstack and completes it with flavor ID
677 Do not call nova.servers.find directly, as it does not return flavor ID with microversion>=2.47
678 """
679 try:
680 self._reload_connection()
681 server = self.nova.servers.find(id=vm_id)
682 # TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
683 server_dict = server.to_dict()
684 try:
Luis Vegad6577d82023-07-26 20:49:12 +0000685 if server_dict["flavor"].get("original_name"):
686 server_dict["flavor"]["id"] = self.nova.flavors.find(
687 name=server_dict["flavor"]["original_name"]
688 ).id
vegallc53829d2023-06-01 00:47:44 -0500689 except nClient.exceptions.NotFound as e:
690 self.logger.warning(str(e.message))
691 return server_dict
692 except (
693 ksExceptions.ClientException,
694 nvExceptions.ClientException,
695 nvExceptions.NotFound,
696 ConnectionError,
697 ) as e:
698 self._format_exception(e)
699
tierno5509c2e2019-07-04 16:23:20 +0000700 def check_vim_connectivity(self):
701 # just get network list to check connectivity and credentials
702 self.get_network_list(filter_dict={})
703
tiernoae4a8d12016-07-08 12:30:39 +0200704 def get_tenant_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +0000705 """Obtain tenants of VIM
tiernoae4a8d12016-07-08 12:30:39 +0200706 filter_dict can contain the following keys:
707 name: filter by tenant name
708 id: filter by tenant uuid/id
709 <other VIM specific>
710 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
tierno1ec592d2020-06-16 15:29:47 +0000711 """
ahmadsa95baa272016-11-30 09:14:11 +0500712 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200713 try:
714 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100715
tiernof716aea2017-06-21 18:01:40 +0200716 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100717 project_class_list = self.keystone.projects.list(
718 name=filter_dict.get("name")
719 )
ahmadsa95baa272016-11-30 09:14:11 +0500720 else:
tiernof716aea2017-06-21 18:01:40 +0200721 project_class_list = self.keystone.tenants.findall(**filter_dict)
sousaedu80135b92021-02-17 15:05:18 +0100722
tierno1ec592d2020-06-16 15:29:47 +0000723 project_list = []
sousaedu80135b92021-02-17 15:05:18 +0100724
ahmadsa95baa272016-11-30 09:14:11 +0500725 for project in project_class_list:
sousaedu80135b92021-02-17 15:05:18 +0100726 if filter_dict.get("id") and filter_dict["id"] != project.id:
tiernof716aea2017-06-21 18:01:40 +0200727 continue
sousaedu80135b92021-02-17 15:05:18 +0100728
ahmadsa95baa272016-11-30 09:14:11 +0500729 project_list.append(project.to_dict())
sousaedu80135b92021-02-17 15:05:18 +0100730
ahmadsa95baa272016-11-30 09:14:11 +0500731 return project_list
sousaedu80135b92021-02-17 15:05:18 +0100732 except (
733 ksExceptions.ConnectionError,
734 ksExceptions.ClientException,
735 ConnectionError,
736 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200737 self._format_exception(e)
738
739 def new_tenant(self, tenant_name, tenant_description):
tierno1ec592d2020-06-16 15:29:47 +0000740 """Adds a new tenant to openstack VIM. Returns the tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200741 self.logger.debug("Adding a new tenant name: %s", tenant_name)
742 try:
743 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100744
tiernof716aea2017-06-21 18:01:40 +0200745 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100746 project = self.keystone.projects.create(
747 tenant_name,
748 self.config.get("project_domain_id", "default"),
749 description=tenant_description,
750 is_domain=False,
751 )
ahmadsa95baa272016-11-30 09:14:11 +0500752 else:
tiernof716aea2017-06-21 18:01:40 +0200753 project = self.keystone.tenants.create(tenant_name, tenant_description)
sousaedu80135b92021-02-17 15:05:18 +0100754
ahmadsa95baa272016-11-30 09:14:11 +0500755 return project.id
sousaedu80135b92021-02-17 15:05:18 +0100756 except (
757 ksExceptions.ConnectionError,
758 ksExceptions.ClientException,
759 ksExceptions.BadRequest,
760 ConnectionError,
761 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200762 self._format_exception(e)
763
764 def delete_tenant(self, tenant_id):
tierno1ec592d2020-06-16 15:29:47 +0000765 """Delete a tenant from openstack VIM. Returns the old tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200766 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
767 try:
768 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100769
tiernof716aea2017-06-21 18:01:40 +0200770 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500771 self.keystone.projects.delete(tenant_id)
772 else:
773 self.keystone.tenants.delete(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100774
tiernoae4a8d12016-07-08 12:30:39 +0200775 return tenant_id
gatici335a06a2023-07-26 00:34:04 +0300776
sousaedu80135b92021-02-17 15:05:18 +0100777 except (
778 ksExceptions.ConnectionError,
779 ksExceptions.ClientException,
780 ksExceptions.NotFound,
781 ConnectionError,
782 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200783 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500784
sousaedu80135b92021-02-17 15:05:18 +0100785 def new_network(
786 self,
787 net_name,
788 net_type,
789 ip_profile=None,
790 shared=False,
791 provider_network_profile=None,
792 ):
garciadeblasebd66722019-01-31 16:01:31 +0000793 """Adds a tenant network to VIM
794 Params:
795 'net_name': name of the network
796 'net_type': one of:
797 'bridge': overlay isolated network
798 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
799 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
800 'ip_profile': is a dict containing the IP parameters of the network
801 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
802 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
803 'gateway_address': (Optional) ip_schema, that is X.X.X.X
804 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
805 'dhcp_enabled': True or False
806 'dhcp_start_address': ip_schema, first IP to grant
807 'dhcp_count': number of IPs to grant.
808 'shared': if this network can be seen/use by other tenants/organization
garciadeblas4af0d542020-02-18 16:01:13 +0100809 'provider_network_profile': (optional) contains {segmentation-id: vlan, network-type: vlan|vxlan,
810 physical-network: physnet-label}
garciadeblasebd66722019-01-31 16:01:31 +0000811 Returns a tuple with the network identifier and created_items, or raises an exception on error
812 created_items can be None or a dictionary where this method can include key-values that will be passed to
813 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
814 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
815 as not present.
816 """
sousaedu80135b92021-02-17 15:05:18 +0100817 self.logger.debug(
818 "Adding a new network to VIM name '%s', type '%s'", net_name, net_type
819 )
garciadeblasebd66722019-01-31 16:01:31 +0000820 # self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
kbsuba85c54d2019-10-17 16:30:32 +0000821
tierno7edb6752016-03-21 17:37:52 +0100822 try:
kbsuba85c54d2019-10-17 16:30:32 +0000823 vlan = None
sousaedu80135b92021-02-17 15:05:18 +0100824
kbsuba85c54d2019-10-17 16:30:32 +0000825 if provider_network_profile:
826 vlan = provider_network_profile.get("segmentation-id")
sousaedu80135b92021-02-17 15:05:18 +0100827
garciadeblasedca7b32016-09-29 14:01:52 +0000828 new_net = None
garciadeblasebd66722019-01-31 16:01:31 +0000829 created_items = {}
tierno7edb6752016-03-21 17:37:52 +0100830 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100831 network_dict = {"name": net_name, "admin_state_up": True}
832
Gabriel Cuba0d8ce072022-12-14 18:33:50 -0500833 if net_type in ("data", "ptp") or provider_network_profile:
tierno6869ae72020-01-09 17:37:34 +0000834 provider_physical_network = None
sousaedu80135b92021-02-17 15:05:18 +0100835
836 if provider_network_profile and provider_network_profile.get(
837 "physical-network"
838 ):
839 provider_physical_network = provider_network_profile.get(
840 "physical-network"
841 )
842
tierno6869ae72020-01-09 17:37:34 +0000843 # provider-network must be one of the dataplane_physcial_netowrk if this is a list. If it is string
844 # or not declared, just ignore the checking
sousaedu80135b92021-02-17 15:05:18 +0100845 if (
846 isinstance(
847 self.config.get("dataplane_physical_net"), (tuple, list)
848 )
849 and provider_physical_network
850 not in self.config["dataplane_physical_net"]
851 ):
tierno72774862020-05-04 11:44:15 +0000852 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100853 "Invalid parameter 'provider-network:physical-network' "
854 "for network creation. '{}' is not one of the declared "
855 "list at VIM_config:dataplane_physical_net".format(
856 provider_physical_network
857 )
858 )
859
860 # use the default dataplane_physical_net
861 if not provider_physical_network:
862 provider_physical_network = self.config.get(
863 "dataplane_physical_net"
864 )
865
gatici335a06a2023-07-26 00:34:04 +0300866 # if it is non-empty list, use the first value. If it is a string use the value directly
sousaedu80135b92021-02-17 15:05:18 +0100867 if (
868 isinstance(provider_physical_network, (tuple, list))
869 and provider_physical_network
870 ):
tierno6869ae72020-01-09 17:37:34 +0000871 provider_physical_network = provider_physical_network[0]
872
873 if not provider_physical_network:
tierno5ad826a2020-08-11 11:19:44 +0000874 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100875 "missing information needed for underlay networks. Provide "
876 "'dataplane_physical_net' configuration at VIM or use the NS "
877 "instantiation parameter 'provider-network.physical-network'"
878 " for the VLD"
879 )
tierno6869ae72020-01-09 17:37:34 +0000880
sousaedu80135b92021-02-17 15:05:18 +0100881 if not self.config.get("multisegment_support"):
garciadeblasaca8cb52023-12-21 16:28:15 +0100882 network_dict["provider:physical_network"] = (
883 provider_physical_network
884 )
sousaedu80135b92021-02-17 15:05:18 +0100885
886 if (
887 provider_network_profile
888 and "network-type" in provider_network_profile
889 ):
garciadeblasaca8cb52023-12-21 16:28:15 +0100890 network_dict["provider:network_type"] = (
891 provider_network_profile["network-type"]
892 )
garciadeblas4af0d542020-02-18 16:01:13 +0100893 else:
sousaedu80135b92021-02-17 15:05:18 +0100894 network_dict["provider:network_type"] = self.config.get(
895 "dataplane_network_type", "vlan"
896 )
897
tierno6869ae72020-01-09 17:37:34 +0000898 if vlan:
899 network_dict["provider:segmentation_id"] = vlan
garciadeblasebd66722019-01-31 16:01:31 +0000900 else:
tierno6869ae72020-01-09 17:37:34 +0000901 # Multi-segment case
garciadeblasebd66722019-01-31 16:01:31 +0000902 segment_list = []
tierno6869ae72020-01-09 17:37:34 +0000903 segment1_dict = {
sousaedu80135b92021-02-17 15:05:18 +0100904 "provider:physical_network": "",
905 "provider:network_type": "vxlan",
tierno6869ae72020-01-09 17:37:34 +0000906 }
garciadeblasebd66722019-01-31 16:01:31 +0000907 segment_list.append(segment1_dict)
tierno6869ae72020-01-09 17:37:34 +0000908 segment2_dict = {
909 "provider:physical_network": provider_physical_network,
sousaedu80135b92021-02-17 15:05:18 +0100910 "provider:network_type": "vlan",
tierno6869ae72020-01-09 17:37:34 +0000911 }
sousaedu80135b92021-02-17 15:05:18 +0100912
tierno6869ae72020-01-09 17:37:34 +0000913 if vlan:
914 segment2_dict["provider:segmentation_id"] = vlan
sousaedu80135b92021-02-17 15:05:18 +0100915 elif self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +0000916 vlanID = self._generate_multisegment_vlanID()
917 segment2_dict["provider:segmentation_id"] = vlanID
sousaedu80135b92021-02-17 15:05:18 +0100918
garciadeblasebd66722019-01-31 16:01:31 +0000919 # else
tierno72774862020-05-04 11:44:15 +0000920 # raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100921 # "You must provide "multisegment_vlan_range" at config dict before creating a multisegment
tierno1ec592d2020-06-16 15:29:47 +0000922 # network")
garciadeblasebd66722019-01-31 16:01:31 +0000923 segment_list.append(segment2_dict)
924 network_dict["segments"] = segment_list
kate721d79b2017-06-24 04:21:38 -0700925
tierno6869ae72020-01-09 17:37:34 +0000926 # VIO Specific Changes. It needs a concrete VLAN
927 if self.vim_type == "VIO" and vlan is None:
sousaedu80135b92021-02-17 15:05:18 +0100928 if self.config.get("dataplane_net_vlan_range") is None:
tierno72774862020-05-04 11:44:15 +0000929 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100930 "You must provide 'dataplane_net_vlan_range' in format "
931 "[start_ID - end_ID] at VIM_config for creating underlay "
932 "networks"
933 )
934
tierno6869ae72020-01-09 17:37:34 +0000935 network_dict["provider:segmentation_id"] = self._generate_vlanID()
kate721d79b2017-06-24 04:21:38 -0700936
garciadeblasebd66722019-01-31 16:01:31 +0000937 network_dict["shared"] = shared
sousaedu80135b92021-02-17 15:05:18 +0100938
anwarsff168192019-05-06 11:23:07 +0530939 if self.config.get("disable_network_port_security"):
940 network_dict["port_security_enabled"] = False
sousaedu80135b92021-02-17 15:05:18 +0100941
sousaedu2aa5f802021-06-17 15:39:29 +0100942 if self.config.get("neutron_availability_zone_hints"):
943 hints = self.config.get("neutron_availability_zone_hints")
944
945 if isinstance(hints, str):
946 hints = [hints]
947
948 network_dict["availability_zone_hints"] = hints
949
sousaedu80135b92021-02-17 15:05:18 +0100950 new_net = self.neutron.create_network({"network": network_dict})
garciadeblasebd66722019-01-31 16:01:31 +0000951 # print new_net
952 # create subnetwork, even if there is no profile
sousaedu80135b92021-02-17 15:05:18 +0100953
garciadeblas9f8456e2016-09-05 05:02:59 +0200954 if not ip_profile:
955 ip_profile = {}
sousaedu80135b92021-02-17 15:05:18 +0100956
957 if not ip_profile.get("subnet_address"):
tierno1ec592d2020-06-16 15:29:47 +0000958 # Fake subnet is required
elumalai51e72a02023-04-28 19:41:49 +0530959 subnet_rand = random.SystemRandom().randint(0, 255)
sousaedu80135b92021-02-17 15:05:18 +0100960 ip_profile["subnet_address"] = "192.168.{}.0/24".format(subnet_rand)
961
962 if "ip_version" not in ip_profile:
963 ip_profile["ip_version"] = "IPv4"
964
965 subnet = {
966 "name": net_name + "-subnet",
967 "network_id": new_net["network"]["id"],
968 "ip_version": 4 if ip_profile["ip_version"] == "IPv4" else 6,
969 "cidr": ip_profile["subnet_address"],
970 }
971
tiernoa1fb4462017-06-30 12:25:50 +0200972 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
sousaedu80135b92021-02-17 15:05:18 +0100973 if ip_profile.get("gateway_address"):
974 subnet["gateway_ip"] = ip_profile["gateway_address"]
tierno55d234c2018-07-04 18:29:21 +0200975 else:
sousaedu80135b92021-02-17 15:05:18 +0100976 subnet["gateway_ip"] = None
977
978 if ip_profile.get("dns_address"):
979 subnet["dns_nameservers"] = ip_profile["dns_address"].split(";")
980
981 if "dhcp_enabled" in ip_profile:
982 subnet["enable_dhcp"] = (
983 False
984 if ip_profile["dhcp_enabled"] == "false"
985 or ip_profile["dhcp_enabled"] is False
986 else True
987 )
988
989 if ip_profile.get("dhcp_start_address"):
990 subnet["allocation_pools"] = []
991 subnet["allocation_pools"].append(dict())
992 subnet["allocation_pools"][0]["start"] = ip_profile[
993 "dhcp_start_address"
994 ]
995
996 if ip_profile.get("dhcp_count"):
997 # parts = ip_profile["dhcp_start_address"].split(".")
tierno1ec592d2020-06-16 15:29:47 +0000998 # ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
sousaedu80135b92021-02-17 15:05:18 +0100999 ip_int = int(netaddr.IPAddress(ip_profile["dhcp_start_address"]))
1000 ip_int += ip_profile["dhcp_count"] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +02001001 ip_str = str(netaddr.IPAddress(ip_int))
sousaedu80135b92021-02-17 15:05:18 +01001002 subnet["allocation_pools"][0]["end"] = ip_str
1003
Gabriel Cubab3dbfca2023-03-14 10:58:39 -05001004 if (
1005 ip_profile.get("ipv6_address_mode")
1006 and ip_profile["ip_version"] != "IPv4"
1007 ):
1008 subnet["ipv6_address_mode"] = ip_profile["ipv6_address_mode"]
1009 # ipv6_ra_mode can be set to the same value for most use cases, see documentation:
1010 # https://docs.openstack.org/neutron/latest/admin/config-ipv6.html#ipv6-ra-mode-and-ipv6-address-mode-combinations
1011 subnet["ipv6_ra_mode"] = ip_profile["ipv6_address_mode"]
1012
tierno1ec592d2020-06-16 15:29:47 +00001013 # self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
1014 self.neutron.create_subnet({"subnet": subnet})
garciadeblasebd66722019-01-31 16:01:31 +00001015
sousaedu80135b92021-02-17 15:05:18 +01001016 if net_type == "data" and self.config.get("multisegment_support"):
1017 if self.config.get("l2gw_support"):
garciadeblasebd66722019-01-31 16:01:31 +00001018 l2gw_list = self.neutron.list_l2_gateways().get("l2_gateways", ())
1019 for l2gw in l2gw_list:
tierno1ec592d2020-06-16 15:29:47 +00001020 l2gw_conn = {
1021 "l2_gateway_id": l2gw["id"],
1022 "network_id": new_net["network"]["id"],
1023 "segmentation_id": str(vlanID),
1024 }
sousaedu80135b92021-02-17 15:05:18 +01001025 new_l2gw_conn = self.neutron.create_l2_gateway_connection(
1026 {"l2_gateway_connection": l2gw_conn}
1027 )
1028 created_items[
1029 "l2gwconn:"
1030 + str(new_l2gw_conn["l2_gateway_connection"]["id"])
1031 ] = True
1032
garciadeblasebd66722019-01-31 16:01:31 +00001033 return new_net["network"]["id"], created_items
tierno41a69812018-02-16 14:34:33 +01001034 except Exception as e:
tierno1ec592d2020-06-16 15:29:47 +00001035 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001036 for k, v in created_items.items():
1037 if not v: # skip already deleted
1038 continue
sousaedu80135b92021-02-17 15:05:18 +01001039
garciadeblasebd66722019-01-31 16:01:31 +00001040 try:
1041 k_item, _, k_id = k.partition(":")
sousaedu80135b92021-02-17 15:05:18 +01001042
garciadeblasebd66722019-01-31 16:01:31 +00001043 if k_item == "l2gwconn":
1044 self.neutron.delete_l2_gateway_connection(k_id)
gatici335a06a2023-07-26 00:34:04 +03001045
1046 except (neExceptions.ConnectionFailed, ConnectionError) as e2:
1047 self.logger.error(
1048 "Error deleting l2 gateway connection: {}: {}".format(
1049 type(e2).__name__, e2
1050 )
1051 )
1052 self._format_exception(e2)
garciadeblasebd66722019-01-31 16:01:31 +00001053 except Exception as e2:
sousaedu80135b92021-02-17 15:05:18 +01001054 self.logger.error(
1055 "Error deleting l2 gateway connection: {}: {}".format(
1056 type(e2).__name__, e2
1057 )
1058 )
1059
garciadeblasedca7b32016-09-29 14:01:52 +00001060 if new_net:
sousaedu80135b92021-02-17 15:05:18 +01001061 self.neutron.delete_network(new_net["network"]["id"])
1062
tiernoae4a8d12016-07-08 12:30:39 +02001063 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001064
1065 def get_network_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001066 """Obtain tenant networks of VIM
tierno7edb6752016-03-21 17:37:52 +01001067 Filter_dict can be:
1068 name: network name
1069 id: network uuid
1070 shared: boolean
1071 tenant_id: tenant
1072 admin_state_up: boolean
1073 status: 'ACTIVE'
1074 Returns the network list of dictionaries
tierno1ec592d2020-06-16 15:29:47 +00001075 """
tiernoae4a8d12016-07-08 12:30:39 +02001076 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +01001077 try:
1078 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001079 filter_dict_os = filter_dict.copy()
sousaedu80135b92021-02-17 15:05:18 +01001080
tierno69b590e2018-03-13 18:52:23 +01001081 if self.api_version3 and "tenant_id" in filter_dict_os:
sousaedu80135b92021-02-17 15:05:18 +01001082 # TODO check
1083 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
1084
tierno69b590e2018-03-13 18:52:23 +01001085 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +01001086 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +01001087 self.__net_os2mano(net_list)
sousaedu80135b92021-02-17 15:05:18 +01001088
tiernoae4a8d12016-07-08 12:30:39 +02001089 return net_list
sousaedu80135b92021-02-17 15:05:18 +01001090 except (
1091 neExceptions.ConnectionFailed,
1092 ksExceptions.ClientException,
1093 neExceptions.NeutronException,
1094 ConnectionError,
1095 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001096 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001097
tiernoae4a8d12016-07-08 12:30:39 +02001098 def get_network(self, net_id):
tierno1ec592d2020-06-16 15:29:47 +00001099 """Obtain details of network from VIM
1100 Returns the network information from a network id"""
tiernoae4a8d12016-07-08 12:30:39 +02001101 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno1ec592d2020-06-16 15:29:47 +00001102 filter_dict = {"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +02001103 net_list = self.get_network_list(filter_dict)
sousaedu80135b92021-02-17 15:05:18 +01001104
tierno1ec592d2020-06-16 15:29:47 +00001105 if len(net_list) == 0:
sousaedu80135b92021-02-17 15:05:18 +01001106 raise vimconn.VimConnNotFoundException(
1107 "Network '{}' not found".format(net_id)
1108 )
tierno1ec592d2020-06-16 15:29:47 +00001109 elif len(net_list) > 1:
sousaedu80135b92021-02-17 15:05:18 +01001110 raise vimconn.VimConnConflictException(
1111 "Found more than one network with this criteria"
1112 )
1113
tierno7edb6752016-03-21 17:37:52 +01001114 net = net_list[0]
tierno1ec592d2020-06-16 15:29:47 +00001115 subnets = []
1116 for subnet_id in net.get("subnets", ()):
tierno7edb6752016-03-21 17:37:52 +01001117 try:
1118 subnet = self.neutron.show_subnet(subnet_id)
1119 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001120 self.logger.error(
1121 "osconnector.get_network(): Error getting subnet %s %s"
1122 % (net_id, str(e))
1123 )
tiernoae4a8d12016-07-08 12:30:39 +02001124 subnet = {"id": subnet_id, "fault": str(e)}
sousaedu80135b92021-02-17 15:05:18 +01001125
tierno7edb6752016-03-21 17:37:52 +01001126 subnets.append(subnet)
sousaedu80135b92021-02-17 15:05:18 +01001127
tierno7edb6752016-03-21 17:37:52 +01001128 net["subnets"] = subnets
sousaedu80135b92021-02-17 15:05:18 +01001129 net["encapsulation"] = net.get("provider:network_type")
1130 net["encapsulation_type"] = net.get("provider:network_type")
1131 net["segmentation_id"] = net.get("provider:segmentation_id")
1132 net["encapsulation_id"] = net.get("provider:segmentation_id")
1133
tiernoae4a8d12016-07-08 12:30:39 +02001134 return net
tierno7edb6752016-03-21 17:37:52 +01001135
gatici335a06a2023-07-26 00:34:04 +03001136 @catch_any_exception
garciadeblasebd66722019-01-31 16:01:31 +00001137 def delete_network(self, net_id, created_items=None):
1138 """
1139 Removes a tenant network from VIM and its associated elements
1140 :param net_id: VIM identifier of the network, provided by method new_network
1141 :param created_items: dictionary with extra items to be deleted. provided by method new_network
1142 Returns the network identifier or raises an exception upon error or when network is not found
1143 """
tiernoae4a8d12016-07-08 12:30:39 +02001144 self.logger.debug("Deleting network '%s' from VIM", net_id)
sousaedu80135b92021-02-17 15:05:18 +01001145
tierno1ec592d2020-06-16 15:29:47 +00001146 if created_items is None:
garciadeblasebd66722019-01-31 16:01:31 +00001147 created_items = {}
sousaedu80135b92021-02-17 15:05:18 +01001148
tierno7edb6752016-03-21 17:37:52 +01001149 try:
1150 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001151 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001152 for k, v in created_items.items():
1153 if not v: # skip already deleted
1154 continue
sousaedu80135b92021-02-17 15:05:18 +01001155
garciadeblasebd66722019-01-31 16:01:31 +00001156 try:
1157 k_item, _, k_id = k.partition(":")
1158 if k_item == "l2gwconn":
1159 self.neutron.delete_l2_gateway_connection(k_id)
gatici335a06a2023-07-26 00:34:04 +03001160
1161 except (neExceptions.ConnectionFailed, ConnectionError) as e:
1162 self.logger.error(
1163 "Error deleting l2 gateway connection: {}: {}".format(
1164 type(e).__name__, e
1165 )
1166 )
1167 self._format_exception(e)
garciadeblasebd66722019-01-31 16:01:31 +00001168 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001169 self.logger.error(
1170 "Error deleting l2 gateway connection: {}: {}".format(
1171 type(e).__name__, e
1172 )
1173 )
1174
tierno1ec592d2020-06-16 15:29:47 +00001175 # delete VM ports attached to this networks before the network
tierno7edb6752016-03-21 17:37:52 +01001176 ports = self.neutron.list_ports(network_id=net_id)
sousaedu80135b92021-02-17 15:05:18 +01001177 for p in ports["ports"]:
tierno7edb6752016-03-21 17:37:52 +01001178 try:
1179 self.neutron.delete_port(p["id"])
gatici335a06a2023-07-26 00:34:04 +03001180
1181 except (neExceptions.ConnectionFailed, ConnectionError) as e:
1182 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
1183 # If there is connection error, it raises.
1184 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001185 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001186 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
sousaedu80135b92021-02-17 15:05:18 +01001187
tierno7edb6752016-03-21 17:37:52 +01001188 self.neutron.delete_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001189
tiernoae4a8d12016-07-08 12:30:39 +02001190 return net_id
gatici335a06a2023-07-26 00:34:04 +03001191 except (neExceptions.NetworkNotFoundClient, neExceptions.NotFound) as e:
1192 # If network to be deleted is not found, it does not raise.
1193 self.logger.warning(
1194 f"Error deleting network: {net_id} is not found, {str(e)}"
1195 )
tierno7edb6752016-03-21 17:37:52 +01001196
tiernoae4a8d12016-07-08 12:30:39 +02001197 def refresh_nets_status(self, net_list):
tierno1ec592d2020-06-16 15:29:47 +00001198 """Get the status of the networks
sousaedu80135b92021-02-17 15:05:18 +01001199 Params: the list of network identifiers
1200 Returns a dictionary with:
1201 net_id: #VIM id of this network
1202 status: #Mandatory. Text with one of:
1203 # DELETED (not found at vim)
1204 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1205 # OTHER (Vim reported other status not understood)
1206 # ERROR (VIM indicates an ERROR status)
1207 # ACTIVE, INACTIVE, DOWN (admin down),
1208 # BUILD (on building process)
1209 #
1210 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1211 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
tierno1ec592d2020-06-16 15:29:47 +00001212 """
1213 net_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001214
tiernoae4a8d12016-07-08 12:30:39 +02001215 for net_id in net_list:
1216 net = {}
sousaedu80135b92021-02-17 15:05:18 +01001217
tiernoae4a8d12016-07-08 12:30:39 +02001218 try:
1219 net_vim = self.get_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001220
1221 if net_vim["status"] in netStatus2manoFormat:
1222 net["status"] = netStatus2manoFormat[net_vim["status"]]
tiernoae4a8d12016-07-08 12:30:39 +02001223 else:
1224 net["status"] = "OTHER"
sousaedu80135b92021-02-17 15:05:18 +01001225 net["error_msg"] = "VIM status reported " + net_vim["status"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001226
sousaedu80135b92021-02-17 15:05:18 +01001227 if net["status"] == "ACTIVE" and not net_vim["admin_state_up"]:
1228 net["status"] = "DOWN"
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001229
sousaedu80135b92021-02-17 15:05:18 +01001230 net["vim_info"] = self.serialize(net_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001231
sousaedu80135b92021-02-17 15:05:18 +01001232 if net_vim.get("fault"): # TODO
1233 net["error_msg"] = str(net_vim["fault"])
tierno72774862020-05-04 11:44:15 +00001234 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001235 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001236 net["status"] = "DELETED"
1237 net["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00001238 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001239 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001240 net["status"] = "VIM_ERROR"
1241 net["error_msg"] = str(e)
tiernoae4a8d12016-07-08 12:30:39 +02001242 net_dict[net_id] = net
1243 return net_dict
1244
1245 def get_flavor(self, flavor_id):
tierno1ec592d2020-06-16 15:29:47 +00001246 """Obtain flavor details from the VIM. Returns the flavor dict details"""
tiernoae4a8d12016-07-08 12:30:39 +02001247 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +01001248 try:
1249 self._reload_connection()
1250 flavor = self.nova.flavors.find(id=flavor_id)
tiernoae4a8d12016-07-08 12:30:39 +02001251 return flavor.to_dict()
gatici335a06a2023-07-26 00:34:04 +03001252
sousaedu80135b92021-02-17 15:05:18 +01001253 except (
1254 nvExceptions.NotFound,
1255 nvExceptions.ClientException,
1256 ksExceptions.ClientException,
1257 ConnectionError,
1258 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001259 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001260
tiernocf157a82017-01-30 14:07:06 +01001261 def get_flavor_id_from_data(self, flavor_dict):
1262 """Obtain flavor id that match the flavor description
sousaedu80135b92021-02-17 15:05:18 +01001263 Returns the flavor_id or raises a vimconnNotFoundException
1264 flavor_dict: contains the required ram, vcpus, disk
1265 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
1266 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
1267 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +01001268 """
sousaedu80135b92021-02-17 15:05:18 +01001269 exact_match = False if self.config.get("use_existing_flavors") else True
1270
tiernocf157a82017-01-30 14:07:06 +01001271 try:
1272 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +02001273 flavor_candidate_id = None
1274 flavor_candidate_data = (10000, 10000, 10000)
sousaedu80135b92021-02-17 15:05:18 +01001275 flavor_target = (
1276 flavor_dict["ram"],
1277 flavor_dict["vcpus"],
1278 flavor_dict["disk"],
sousaedu648ee3d2021-11-22 14:09:15 +00001279 flavor_dict.get("ephemeral", 0),
1280 flavor_dict.get("swap", 0),
sousaedu80135b92021-02-17 15:05:18 +01001281 )
tiernoe26fc7a2017-05-30 14:43:03 +02001282 # numa=None
anwarsae5f52c2019-04-22 10:35:27 +05301283 extended = flavor_dict.get("extended", {})
1284 if extended:
tierno1ec592d2020-06-16 15:29:47 +00001285 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001286 raise vimconn.VimConnNotFoundException(
1287 "Flavor with EPA still not implemented"
1288 )
tiernocf157a82017-01-30 14:07:06 +01001289 # if len(numas) > 1:
tierno72774862020-05-04 11:44:15 +00001290 # raise vimconn.VimConnNotFoundException("Cannot find any flavor with more than one numa")
tiernocf157a82017-01-30 14:07:06 +01001291 # numa=numas[0]
1292 # numas = extended.get("numas")
1293 for flavor in self.nova.flavors.list():
1294 epa = flavor.get_keys()
sousaedu80135b92021-02-17 15:05:18 +01001295
tiernocf157a82017-01-30 14:07:06 +01001296 if epa:
1297 continue
tiernoe26fc7a2017-05-30 14:43:03 +02001298 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001299
sousaedu648ee3d2021-11-22 14:09:15 +00001300 flavor_data = (
1301 flavor.ram,
1302 flavor.vcpus,
1303 flavor.disk,
1304 flavor.ephemeral,
preethika.pebaba1f2022-01-20 07:24:18 +00001305 flavor.swap if isinstance(flavor.swap, int) else 0,
sousaedu648ee3d2021-11-22 14:09:15 +00001306 )
tiernoe26fc7a2017-05-30 14:43:03 +02001307 if flavor_data == flavor_target:
1308 return flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001309 elif (
1310 not exact_match
1311 and flavor_target < flavor_data < flavor_candidate_data
1312 ):
tiernoe26fc7a2017-05-30 14:43:03 +02001313 flavor_candidate_id = flavor.id
1314 flavor_candidate_data = flavor_data
sousaedu80135b92021-02-17 15:05:18 +01001315
tiernoe26fc7a2017-05-30 14:43:03 +02001316 if not exact_match and flavor_candidate_id:
1317 return flavor_candidate_id
sousaedu80135b92021-02-17 15:05:18 +01001318
1319 raise vimconn.VimConnNotFoundException(
1320 "Cannot find any flavor matching '{}'".format(flavor_dict)
1321 )
1322 except (
1323 nvExceptions.NotFound,
gatici335a06a2023-07-26 00:34:04 +03001324 nvExceptions.BadRequest,
sousaedu80135b92021-02-17 15:05:18 +01001325 nvExceptions.ClientException,
1326 ksExceptions.ClientException,
1327 ConnectionError,
1328 ) as e:
tiernocf157a82017-01-30 14:07:06 +01001329 self._format_exception(e)
1330
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001331 @staticmethod
1332 def process_resource_quota(quota: dict, prefix: str, extra_specs: dict) -> None:
1333 """Process resource quota and fill up extra_specs.
1334 Args:
1335 quota (dict): Keeping the quota of resurces
1336 prefix (str) Prefix
1337 extra_specs (dict) Dict to be filled to be used during flavor creation
1338
anwarsae5f52c2019-04-22 10:35:27 +05301339 """
sousaedu80135b92021-02-17 15:05:18 +01001340 if "limit" in quota:
1341 extra_specs["quota:" + prefix + "_limit"] = quota["limit"]
1342
1343 if "reserve" in quota:
1344 extra_specs["quota:" + prefix + "_reservation"] = quota["reserve"]
1345
1346 if "shares" in quota:
anwarsae5f52c2019-04-22 10:35:27 +05301347 extra_specs["quota:" + prefix + "_shares_level"] = "custom"
sousaedu80135b92021-02-17 15:05:18 +01001348 extra_specs["quota:" + prefix + "_shares_share"] = quota["shares"]
anwarsae5f52c2019-04-22 10:35:27 +05301349
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001350 @staticmethod
1351 def process_numa_memory(
1352 numa: dict, node_id: Optional[int], extra_specs: dict
1353 ) -> None:
1354 """Set the memory in extra_specs.
1355 Args:
1356 numa (dict): A dictionary which includes numa information
1357 node_id (int): ID of numa node
1358 extra_specs (dict): To be filled.
1359
1360 """
1361 if not numa.get("memory"):
1362 return
1363 memory_mb = numa["memory"] * 1024
1364 memory = "hw:numa_mem.{}".format(node_id)
1365 extra_specs[memory] = int(memory_mb)
1366
1367 @staticmethod
1368 def process_numa_vcpu(numa: dict, node_id: int, extra_specs: dict) -> None:
1369 """Set the cpu in extra_specs.
1370 Args:
1371 numa (dict): A dictionary which includes numa information
1372 node_id (int): ID of numa node
1373 extra_specs (dict): To be filled.
1374
1375 """
1376 if not numa.get("vcpu"):
1377 return
1378 vcpu = numa["vcpu"]
1379 cpu = "hw:numa_cpus.{}".format(node_id)
1380 vcpu = ",".join(map(str, vcpu))
1381 extra_specs[cpu] = vcpu
1382
1383 @staticmethod
1384 def process_numa_paired_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1385 """Fill up extra_specs if numa has paired-threads.
1386 Args:
1387 numa (dict): A dictionary which includes numa information
1388 extra_specs (dict): To be filled.
1389
1390 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001391 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001392
1393 """
1394 if not numa.get("paired-threads"):
1395 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001396
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001397 # cpu_thread_policy "require" implies that compute node must have an STM architecture
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001398 threads = numa["paired-threads"] * 2
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001399 extra_specs["hw:cpu_thread_policy"] = "require"
1400 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001401 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001402
1403 @staticmethod
1404 def process_numa_cores(numa: dict, extra_specs: dict) -> Optional[int]:
1405 """Fill up extra_specs if numa has cores.
1406 Args:
1407 numa (dict): A dictionary which includes numa information
1408 extra_specs (dict): To be filled.
1409
1410 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001411 cores (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001412
1413 """
1414 # cpu_thread_policy "isolate" implies that the host must not have an SMT
1415 # architecture, or a non-SMT architecture will be emulated
1416 if not numa.get("cores"):
1417 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001418 cores = numa["cores"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001419 extra_specs["hw:cpu_thread_policy"] = "isolate"
1420 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001421 return cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001422
1423 @staticmethod
1424 def process_numa_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1425 """Fill up extra_specs if numa has threads.
1426 Args:
1427 numa (dict): A dictionary which includes numa information
1428 extra_specs (dict): To be filled.
1429
1430 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001431 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001432
1433 """
1434 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
1435 if not numa.get("threads"):
1436 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001437 threads = numa["threads"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001438 extra_specs["hw:cpu_thread_policy"] = "prefer"
1439 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001440 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001441
1442 def _process_numa_parameters_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001443 self, numas: List, extra_specs: Dict
1444 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001445 """Process numa parameters and fill up extra_specs.
1446
1447 Args:
1448 numas (list): List of dictionary which includes numa information
1449 extra_specs (dict): To be filled.
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001450
1451 """
1452 numa_nodes = len(numas)
1453 extra_specs["hw:numa_nodes"] = str(numa_nodes)
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001454 cpu_cores, cpu_threads = 0, 0
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001455
1456 if self.vim_type == "VIO":
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001457 self.process_vio_numa_nodes(numa_nodes, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001458
1459 for numa in numas:
1460 if "id" in numa:
1461 node_id = numa["id"]
1462 # overwrite ram and vcpus
1463 # check if key "memory" is present in numa else use ram value at flavor
1464 self.process_numa_memory(numa, node_id, extra_specs)
1465 self.process_numa_vcpu(numa, node_id, extra_specs)
1466
1467 # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
1468 extra_specs["hw:cpu_sockets"] = str(numa_nodes)
1469
1470 if "paired-threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001471 threads = self.process_numa_paired_threads(numa, extra_specs)
1472 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001473
1474 elif "cores" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001475 cores = self.process_numa_cores(numa, extra_specs)
1476 cpu_cores += cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001477
1478 elif "threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001479 threads = self.process_numa_threads(numa, extra_specs)
1480 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001481
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001482 if cpu_cores:
1483 extra_specs["hw:cpu_cores"] = str(cpu_cores)
1484 if cpu_threads:
1485 extra_specs["hw:cpu_threads"] = str(cpu_threads)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001486
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001487 @staticmethod
1488 def process_vio_numa_nodes(numa_nodes: int, extra_specs: Dict) -> None:
1489 """According to number of numa nodes, updates the extra_specs for VIO.
1490
1491 Args:
1492
1493 numa_nodes (int): List keeps the numa node numbers
1494 extra_specs (dict): Extra specs dict to be updated
1495
1496 """
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001497 # If there are several numas, we do not define specific affinity.
1498 extra_specs["vmware:latency_sensitivity_level"] = "high"
1499
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001500 def _change_flavor_name(
1501 self, name: str, name_suffix: int, flavor_data: dict
1502 ) -> str:
1503 """Change the flavor name if the name already exists.
1504
1505 Args:
1506 name (str): Flavor name to be checked
1507 name_suffix (int): Suffix to be appended to name
1508 flavor_data (dict): Flavor dict
1509
1510 Returns:
1511 name (str): New flavor name to be used
1512
1513 """
1514 # Get used names
1515 fl = self.nova.flavors.list()
1516 fl_names = [f.name for f in fl]
1517
1518 while name in fl_names:
1519 name_suffix += 1
1520 name = flavor_data["name"] + "-" + str(name_suffix)
1521
1522 return name
1523
1524 def _process_extended_config_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001525 self, extended: dict, extra_specs: dict
1526 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001527 """Process the extended dict to fill up extra_specs.
1528 Args:
1529
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001530 extended (dict): Keeping the extra specification of flavor
1531 extra_specs (dict) Dict to be filled to be used during flavor creation
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001532
1533 """
1534 quotas = {
1535 "cpu-quota": "cpu",
1536 "mem-quota": "memory",
1537 "vif-quota": "vif",
1538 "disk-io-quota": "disk_io",
1539 }
1540
1541 page_sizes = {
1542 "LARGE": "large",
1543 "SMALL": "small",
1544 "SIZE_2MB": "2MB",
1545 "SIZE_1GB": "1GB",
1546 "PREFER_LARGE": "any",
1547 }
1548
1549 policies = {
1550 "cpu-pinning-policy": "hw:cpu_policy",
1551 "cpu-thread-pinning-policy": "hw:cpu_thread_policy",
1552 "mem-policy": "hw:numa_mempolicy",
1553 }
1554
1555 numas = extended.get("numas")
1556 if numas:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001557 self._process_numa_parameters_of_flavor(numas, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001558
1559 for quota, item in quotas.items():
1560 if quota in extended.keys():
1561 self.process_resource_quota(extended.get(quota), item, extra_specs)
1562
1563 # Set the mempage size as specified in the descriptor
1564 if extended.get("mempage-size"):
1565 if extended["mempage-size"] in page_sizes.keys():
1566 extra_specs["hw:mem_page_size"] = page_sizes[extended["mempage-size"]]
1567 else:
1568 # Normally, validations in NBI should not allow to this condition.
1569 self.logger.debug(
1570 "Invalid mempage-size %s. Will be ignored",
1571 extended.get("mempage-size"),
1572 )
1573
1574 for policy, hw_policy in policies.items():
1575 if extended.get(policy):
1576 extra_specs[hw_policy] = extended[policy].lower()
1577
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001578 @staticmethod
1579 def _get_flavor_details(flavor_data: dict) -> Tuple:
1580 """Returns the details of flavor
1581 Args:
1582 flavor_data (dict): Dictionary that includes required flavor details
1583
1584 Returns:
1585 ram, vcpus, extra_specs, extended (tuple): Main items of required flavor
1586
1587 """
1588 return (
1589 flavor_data.get("ram", 64),
1590 flavor_data.get("vcpus", 1),
1591 {},
1592 flavor_data.get("extended"),
1593 )
1594
gatici335a06a2023-07-26 00:34:04 +03001595 @catch_any_exception
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001596 def new_flavor(self, flavor_data: dict, change_name_if_used: bool = True) -> str:
1597 """Adds a tenant flavor to openstack VIM.
1598 if change_name_if_used is True, it will change name in case of conflict,
1599 because it is not supported name repetition.
1600
1601 Args:
1602 flavor_data (dict): Flavor details to be processed
1603 change_name_if_used (bool): Change name in case of conflict
1604
1605 Returns:
1606 flavor_id (str): flavor identifier
1607
tierno1ec592d2020-06-16 15:29:47 +00001608 """
tiernoae4a8d12016-07-08 12:30:39 +02001609 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno1ec592d2020-06-16 15:29:47 +00001610 retry = 0
1611 max_retries = 3
tierno7edb6752016-03-21 17:37:52 +01001612 name_suffix = 0
gatici335a06a2023-07-26 00:34:04 +03001613 name = flavor_data["name"]
1614 while retry < max_retries:
1615 retry += 1
1616 try:
1617 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001618
gatici335a06a2023-07-26 00:34:04 +03001619 if change_name_if_used:
1620 name = self._change_flavor_name(name, name_suffix, flavor_data)
sousaedu80135b92021-02-17 15:05:18 +01001621
gatici335a06a2023-07-26 00:34:04 +03001622 ram, vcpus, extra_specs, extended = self._get_flavor_details(
1623 flavor_data
1624 )
1625 if extended:
1626 self._process_extended_config_of_flavor(extended, extra_specs)
sousaedu80135b92021-02-17 15:05:18 +01001627
gatici335a06a2023-07-26 00:34:04 +03001628 # Create flavor
sousaedu80135b92021-02-17 15:05:18 +01001629
gatici335a06a2023-07-26 00:34:04 +03001630 new_flavor = self.nova.flavors.create(
1631 name=name,
1632 ram=ram,
1633 vcpus=vcpus,
1634 disk=flavor_data.get("disk", 0),
1635 ephemeral=flavor_data.get("ephemeral", 0),
1636 swap=flavor_data.get("swap", 0),
1637 is_public=flavor_data.get("is_public", True),
1638 )
sousaedu80135b92021-02-17 15:05:18 +01001639
gatici335a06a2023-07-26 00:34:04 +03001640 # Add metadata
1641 if extra_specs:
1642 new_flavor.set_keys(extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001643
gatici335a06a2023-07-26 00:34:04 +03001644 return new_flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001645
gatici335a06a2023-07-26 00:34:04 +03001646 except nvExceptions.Conflict as e:
1647 if change_name_if_used and retry < max_retries:
1648 continue
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001649
gatici335a06a2023-07-26 00:34:04 +03001650 self._format_exception(e)
sousaedu80135b92021-02-17 15:05:18 +01001651
gatici335a06a2023-07-26 00:34:04 +03001652 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00001653 def delete_flavor(self, flavor_id):
sousaedu80135b92021-02-17 15:05:18 +01001654 """Deletes a tenant flavor from openstack VIM. Returns the old flavor_id"""
tiernoae4a8d12016-07-08 12:30:39 +02001655 try:
1656 self._reload_connection()
1657 self.nova.flavors.delete(flavor_id)
1658 return flavor_id
gatici335a06a2023-07-26 00:34:04 +03001659
1660 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
1661 # If flavor is not found, it does not raise.
1662 self.logger.warning(
1663 f"Error deleting flavor: {flavor_id} is not found, {str(e.message)}"
1664 )
tierno7edb6752016-03-21 17:37:52 +01001665
tierno1ec592d2020-06-16 15:29:47 +00001666 def new_image(self, image_dict):
1667 """
tiernoae4a8d12016-07-08 12:30:39 +02001668 Adds a tenant image to VIM. imge_dict is a dictionary with:
1669 name: name
1670 disk_format: qcow2, vhd, vmdk, raw (by default), ...
1671 location: path or URI
1672 public: "yes" or "no"
1673 metadata: metadata of the image
1674 Returns the image_id
tierno1ec592d2020-06-16 15:29:47 +00001675 """
1676 retry = 0
1677 max_retries = 3
sousaedu80135b92021-02-17 15:05:18 +01001678
tierno1ec592d2020-06-16 15:29:47 +00001679 while retry < max_retries:
1680 retry += 1
tierno7edb6752016-03-21 17:37:52 +01001681 try:
1682 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001683
tierno1ec592d2020-06-16 15:29:47 +00001684 # determine format http://docs.openstack.org/developer/glance/formats.html
tierno7edb6752016-03-21 17:37:52 +01001685 if "disk_format" in image_dict:
tierno1ec592d2020-06-16 15:29:47 +00001686 disk_format = image_dict["disk_format"]
1687 else: # autodiscover based on extension
sousaedu80135b92021-02-17 15:05:18 +01001688 if image_dict["location"].endswith(".qcow2"):
tierno1ec592d2020-06-16 15:29:47 +00001689 disk_format = "qcow2"
sousaedu80135b92021-02-17 15:05:18 +01001690 elif image_dict["location"].endswith(".vhd"):
tierno1ec592d2020-06-16 15:29:47 +00001691 disk_format = "vhd"
sousaedu80135b92021-02-17 15:05:18 +01001692 elif image_dict["location"].endswith(".vmdk"):
tierno1ec592d2020-06-16 15:29:47 +00001693 disk_format = "vmdk"
sousaedu80135b92021-02-17 15:05:18 +01001694 elif image_dict["location"].endswith(".vdi"):
tierno1ec592d2020-06-16 15:29:47 +00001695 disk_format = "vdi"
sousaedu80135b92021-02-17 15:05:18 +01001696 elif image_dict["location"].endswith(".iso"):
tierno1ec592d2020-06-16 15:29:47 +00001697 disk_format = "iso"
sousaedu80135b92021-02-17 15:05:18 +01001698 elif image_dict["location"].endswith(".aki"):
tierno1ec592d2020-06-16 15:29:47 +00001699 disk_format = "aki"
sousaedu80135b92021-02-17 15:05:18 +01001700 elif image_dict["location"].endswith(".ari"):
tierno1ec592d2020-06-16 15:29:47 +00001701 disk_format = "ari"
sousaedu80135b92021-02-17 15:05:18 +01001702 elif image_dict["location"].endswith(".ami"):
tierno1ec592d2020-06-16 15:29:47 +00001703 disk_format = "ami"
tierno7edb6752016-03-21 17:37:52 +01001704 else:
tierno1ec592d2020-06-16 15:29:47 +00001705 disk_format = "raw"
sousaedu80135b92021-02-17 15:05:18 +01001706
1707 self.logger.debug(
1708 "new_image: '%s' loading from '%s'",
1709 image_dict["name"],
1710 image_dict["location"],
1711 )
shashankjain3c83a212018-10-04 13:05:46 +05301712 if self.vim_type == "VIO":
1713 container_format = "bare"
sousaedu80135b92021-02-17 15:05:18 +01001714 if "container_format" in image_dict:
1715 container_format = image_dict["container_format"]
1716
1717 new_image = self.glance.images.create(
1718 name=image_dict["name"],
1719 container_format=container_format,
1720 disk_format=disk_format,
1721 )
shashankjain3c83a212018-10-04 13:05:46 +05301722 else:
sousaedu80135b92021-02-17 15:05:18 +01001723 new_image = self.glance.images.create(name=image_dict["name"])
1724
1725 if image_dict["location"].startswith("http"):
tierno1beea862018-07-11 15:47:37 +02001726 # TODO there is not a method to direct download. It must be downloaded locally with requests
tierno72774862020-05-04 11:44:15 +00001727 raise vimconn.VimConnNotImplemented("Cannot create image from URL")
tierno1ec592d2020-06-16 15:29:47 +00001728 else: # local path
sousaedu80135b92021-02-17 15:05:18 +01001729 with open(image_dict["location"]) as fimage:
tierno1beea862018-07-11 15:47:37 +02001730 self.glance.images.upload(new_image.id, fimage)
sousaedu80135b92021-02-17 15:05:18 +01001731 # new_image = self.glancev1.images.create(name=image_dict["name"], is_public=
1732 # image_dict.get("public","yes")=="yes",
tierno1beea862018-07-11 15:47:37 +02001733 # container_format="bare", data=fimage, disk_format=disk_format)
sousaedu80135b92021-02-17 15:05:18 +01001734
1735 metadata_to_load = image_dict.get("metadata")
1736
1737 # TODO location is a reserved word for current openstack versions. fixed for VIO please check
tierno1ec592d2020-06-16 15:29:47 +00001738 # for openstack
shashankjain3c83a212018-10-04 13:05:46 +05301739 if self.vim_type == "VIO":
sousaedu80135b92021-02-17 15:05:18 +01001740 metadata_to_load["upload_location"] = image_dict["location"]
shashankjain3c83a212018-10-04 13:05:46 +05301741 else:
sousaedu80135b92021-02-17 15:05:18 +01001742 metadata_to_load["location"] = image_dict["location"]
1743
tierno1beea862018-07-11 15:47:37 +02001744 self.glance.images.update(new_image.id, **metadata_to_load)
sousaedu80135b92021-02-17 15:05:18 +01001745
tiernoae4a8d12016-07-08 12:30:39 +02001746 return new_image.id
sousaedu80135b92021-02-17 15:05:18 +01001747 except (
sousaedu80135b92021-02-17 15:05:18 +01001748 HTTPException,
1749 gl1Exceptions.HTTPException,
1750 gl1Exceptions.CommunicationError,
1751 ConnectionError,
1752 ) as e:
tierno1ec592d2020-06-16 15:29:47 +00001753 if retry == max_retries:
tiernoae4a8d12016-07-08 12:30:39 +02001754 continue
sousaedu80135b92021-02-17 15:05:18 +01001755
tiernoae4a8d12016-07-08 12:30:39 +02001756 self._format_exception(e)
tierno1ec592d2020-06-16 15:29:47 +00001757 except IOError as e: # can not open the file
sousaedu80135b92021-02-17 15:05:18 +01001758 raise vimconn.VimConnConnectionException(
1759 "{}: {} for {}".format(type(e).__name__, e, image_dict["location"]),
1760 http_code=vimconn.HTTP_Bad_Request,
1761 )
gatici335a06a2023-07-26 00:34:04 +03001762 except Exception as e:
1763 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001764
gatici335a06a2023-07-26 00:34:04 +03001765 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001766 def delete_image(self, image_id):
sousaedu80135b92021-02-17 15:05:18 +01001767 """Deletes a tenant image from openstack VIM. Returns the old id"""
tiernoae4a8d12016-07-08 12:30:39 +02001768 try:
1769 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001770 self.glance.images.delete(image_id)
sousaedu80135b92021-02-17 15:05:18 +01001771
tiernoae4a8d12016-07-08 12:30:39 +02001772 return image_id
gatici335a06a2023-07-26 00:34:04 +03001773 except gl1Exceptions.NotFound as e:
1774 # If image is not found, it does not raise.
1775 self.logger.warning(
1776 f"Error deleting image: {image_id} is not found, {str(e)}"
1777 )
tiernoae4a8d12016-07-08 12:30:39 +02001778
gatici335a06a2023-07-26 00:34:04 +03001779 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001780 def get_image_id_from_path(self, path):
tierno1ec592d2020-06-16 15:29:47 +00001781 """Get the image id from image path in the VIM database. Returns the image_id"""
gatici335a06a2023-07-26 00:34:04 +03001782 self._reload_connection()
1783 images = self.glance.images.list()
sousaedu80135b92021-02-17 15:05:18 +01001784
gatici335a06a2023-07-26 00:34:04 +03001785 for image in images:
1786 if image.metadata.get("location") == path:
1787 return image.id
sousaedu80135b92021-02-17 15:05:18 +01001788
gatici335a06a2023-07-26 00:34:04 +03001789 raise vimconn.VimConnNotFoundException(
1790 "image with location '{}' not found".format(path)
1791 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001792
garciadeblasb69fa9f2016-09-28 12:04:10 +02001793 def get_image_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001794 """Obtain tenant images from VIM
garciadeblasb69fa9f2016-09-28 12:04:10 +02001795 Filter_dict can be:
1796 id: image id
1797 name: image name
1798 checksum: image checksum
1799 Returns the image list of dictionaries:
1800 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1801 List can be empty
tierno1ec592d2020-06-16 15:29:47 +00001802 """
garciadeblasb69fa9f2016-09-28 12:04:10 +02001803 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
1804 try:
1805 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001806 # filter_dict_os = filter_dict.copy()
1807 # First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +02001808 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +02001809 filtered_list = []
sousaedu80135b92021-02-17 15:05:18 +01001810
garciadeblasb69fa9f2016-09-28 12:04:10 +02001811 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +02001812 try:
tierno1beea862018-07-11 15:47:37 +02001813 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
1814 continue
sousaedu80135b92021-02-17 15:05:18 +01001815
tierno1beea862018-07-11 15:47:37 +02001816 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
1817 continue
sousaedu80135b92021-02-17 15:05:18 +01001818
1819 if (
1820 filter_dict.get("checksum")
1821 and image["checksum"] != filter_dict["checksum"]
1822 ):
tierno1beea862018-07-11 15:47:37 +02001823 continue
1824
1825 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +02001826 except gl1Exceptions.HTTPNotFound:
1827 pass
sousaedu80135b92021-02-17 15:05:18 +01001828
garciadeblasb69fa9f2016-09-28 12:04:10 +02001829 return filtered_list
gatici335a06a2023-07-26 00:34:04 +03001830
sousaedu80135b92021-02-17 15:05:18 +01001831 except (
1832 ksExceptions.ClientException,
1833 nvExceptions.ClientException,
1834 gl1Exceptions.CommunicationError,
1835 ConnectionError,
1836 ) as e:
garciadeblasb69fa9f2016-09-28 12:04:10 +02001837 self._format_exception(e)
1838
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001839 def __wait_for_vm(self, vm_id, status):
1840 """wait until vm is in the desired status and return True.
1841 If the VM gets in ERROR status, return false.
1842 If the timeout is reached generate an exception"""
1843 elapsed_time = 0
1844 while elapsed_time < server_timeout:
1845 vm_status = self.nova.servers.get(vm_id).status
sousaedu80135b92021-02-17 15:05:18 +01001846
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001847 if vm_status == status:
1848 return True
sousaedu80135b92021-02-17 15:05:18 +01001849
1850 if vm_status == "ERROR":
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001851 return False
sousaedu80135b92021-02-17 15:05:18 +01001852
tierno1df468d2018-07-06 14:25:16 +02001853 time.sleep(5)
1854 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001855
1856 # if we exceeded the timeout rollback
1857 if elapsed_time >= server_timeout:
sousaedu80135b92021-02-17 15:05:18 +01001858 raise vimconn.VimConnException(
1859 "Timeout waiting for instance " + vm_id + " to get " + status,
1860 http_code=vimconn.HTTP_Request_Timeout,
1861 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001862
mirabal29356312017-07-27 12:21:22 +02001863 def _get_openstack_availablity_zones(self):
1864 """
1865 Get from openstack availability zones available
1866 :return:
1867 """
1868 try:
1869 openstack_availability_zone = self.nova.availability_zones.list()
sousaedu80135b92021-02-17 15:05:18 +01001870 openstack_availability_zone = [
1871 str(zone.zoneName)
1872 for zone in openstack_availability_zone
1873 if zone.zoneName != "internal"
1874 ]
1875
mirabal29356312017-07-27 12:21:22 +02001876 return openstack_availability_zone
tierno1ec592d2020-06-16 15:29:47 +00001877 except Exception:
mirabal29356312017-07-27 12:21:22 +02001878 return None
1879
1880 def _set_availablity_zones(self):
1881 """
1882 Set vim availablity zone
1883 :return:
1884 """
sousaedu80135b92021-02-17 15:05:18 +01001885 if "availability_zone" in self.config:
1886 vim_availability_zones = self.config.get("availability_zone")
mirabal29356312017-07-27 12:21:22 +02001887
mirabal29356312017-07-27 12:21:22 +02001888 if isinstance(vim_availability_zones, str):
1889 self.availability_zone = [vim_availability_zones]
1890 elif isinstance(vim_availability_zones, list):
1891 self.availability_zone = vim_availability_zones
1892 else:
1893 self.availability_zone = self._get_openstack_availablity_zones()
Luis Vega25bc6382023-10-05 23:22:04 +00001894 if "storage_availability_zone" in self.config:
1895 self.storage_availability_zone = self.config.get(
1896 "storage_availability_zone"
1897 )
mirabal29356312017-07-27 12:21:22 +02001898
sousaedu80135b92021-02-17 15:05:18 +01001899 def _get_vm_availability_zone(
1900 self, availability_zone_index, availability_zone_list
1901 ):
mirabal29356312017-07-27 12:21:22 +02001902 """
tierno5a3273c2017-08-29 11:43:46 +02001903 Return thge availability zone to be used by the created VM.
1904 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +02001905 """
tierno5a3273c2017-08-29 11:43:46 +02001906 if availability_zone_index is None:
sousaedu80135b92021-02-17 15:05:18 +01001907 if not self.config.get("availability_zone"):
tierno5a3273c2017-08-29 11:43:46 +02001908 return None
sousaedu80135b92021-02-17 15:05:18 +01001909 elif isinstance(self.config.get("availability_zone"), str):
1910 return self.config["availability_zone"]
tierno5a3273c2017-08-29 11:43:46 +02001911 else:
1912 # TODO consider using a different parameter at config for default AV and AV list match
sousaedu80135b92021-02-17 15:05:18 +01001913 return self.config["availability_zone"][0]
mirabal29356312017-07-27 12:21:22 +02001914
tierno5a3273c2017-08-29 11:43:46 +02001915 vim_availability_zones = self.availability_zone
1916 # check if VIM offer enough availability zones describe in the VNFD
sousaedu80135b92021-02-17 15:05:18 +01001917 if vim_availability_zones and len(availability_zone_list) <= len(
1918 vim_availability_zones
1919 ):
tierno5a3273c2017-08-29 11:43:46 +02001920 # check if all the names of NFV AV match VIM AV names
1921 match_by_index = False
1922 for av in availability_zone_list:
1923 if av not in vim_availability_zones:
1924 match_by_index = True
1925 break
sousaedu80135b92021-02-17 15:05:18 +01001926
tierno5a3273c2017-08-29 11:43:46 +02001927 if match_by_index:
1928 return vim_availability_zones[availability_zone_index]
1929 else:
1930 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +02001931 else:
sousaedu80135b92021-02-17 15:05:18 +01001932 raise vimconn.VimConnConflictException(
1933 "No enough availability zones at VIM for this deployment"
1934 )
mirabal29356312017-07-27 12:21:22 +02001935
Gulsum Atici26f73662022-10-27 15:18:27 +03001936 def _prepare_port_dict_security_groups(self, net: dict, port_dict: dict) -> None:
1937 """Fill up the security_groups in the port_dict.
1938
1939 Args:
1940 net (dict): Network details
1941 port_dict (dict): Port details
1942
1943 """
1944 if (
1945 self.config.get("security_groups")
1946 and net.get("port_security") is not False
1947 and not self.config.get("no_port_security_extension")
1948 ):
1949 if not self.security_groups_id:
1950 self._get_ids_from_name()
1951
1952 port_dict["security_groups"] = self.security_groups_id
1953
1954 def _prepare_port_dict_binding(self, net: dict, port_dict: dict) -> None:
1955 """Fill up the network binding depending on network type in the port_dict.
1956
1957 Args:
1958 net (dict): Network details
1959 port_dict (dict): Port details
1960
1961 """
1962 if not net.get("type"):
1963 raise vimconn.VimConnException("Type is missing in the network details.")
1964
1965 if net["type"] == "virtual":
1966 pass
1967
1968 # For VF
1969 elif net["type"] == "VF" or net["type"] == "SR-IOV":
Gulsum Atici26f73662022-10-27 15:18:27 +03001970 port_dict["binding:vnic_type"] = "direct"
1971
1972 # VIO specific Changes
1973 if self.vim_type == "VIO":
1974 # Need to create port with port_security_enabled = False and no-security-groups
1975 port_dict["port_security_enabled"] = False
1976 port_dict["provider_security_groups"] = []
1977 port_dict["security_groups"] = []
1978
1979 else:
1980 # For PT PCI-PASSTHROUGH
1981 port_dict["binding:vnic_type"] = "direct-physical"
1982
1983 @staticmethod
1984 def _set_fixed_ip(new_port: dict, net: dict) -> None:
1985 """Set the "ip" parameter in net dictionary.
1986
1987 Args:
1988 new_port (dict): New created port
1989 net (dict): Network details
1990
1991 """
1992 fixed_ips = new_port["port"].get("fixed_ips")
1993
1994 if fixed_ips:
1995 net["ip"] = fixed_ips[0].get("ip_address")
1996 else:
1997 net["ip"] = None
1998
1999 @staticmethod
2000 def _prepare_port_dict_mac_ip_addr(net: dict, port_dict: dict) -> None:
2001 """Fill up the mac_address and fixed_ips in port_dict.
2002
2003 Args:
2004 net (dict): Network details
2005 port_dict (dict): Port details
2006
2007 """
2008 if net.get("mac_address"):
2009 port_dict["mac_address"] = net["mac_address"]
2010
elumalai370e36b2023-04-25 16:22:56 +05302011 ip_dual_list = []
2012 if ip_list := net.get("ip_address"):
2013 if not isinstance(ip_list, list):
2014 ip_list = [ip_list]
2015 for ip in ip_list:
2016 ip_dict = {"ip_address": ip}
2017 ip_dual_list.append(ip_dict)
2018 port_dict["fixed_ips"] = ip_dual_list
Gulsum Atici26f73662022-10-27 15:18:27 +03002019 # TODO add "subnet_id": <subnet_id>
2020
2021 def _create_new_port(self, port_dict: dict, created_items: dict, net: dict) -> Dict:
2022 """Create new port using neutron.
2023
2024 Args:
2025 port_dict (dict): Port details
2026 created_items (dict): All created items
2027 net (dict): Network details
2028
2029 Returns:
2030 new_port (dict): New created port
2031
2032 """
2033 new_port = self.neutron.create_port({"port": port_dict})
2034 created_items["port:" + str(new_port["port"]["id"])] = True
elumalaie17cd942023-04-28 18:04:24 +05302035 net["mac_address"] = new_port["port"]["mac_address"]
Gulsum Atici26f73662022-10-27 15:18:27 +03002036 net["vim_id"] = new_port["port"]["id"]
2037
2038 return new_port
2039
2040 def _create_port(
2041 self, net: dict, name: str, created_items: dict
2042 ) -> Tuple[dict, dict]:
2043 """Create port using net details.
2044
2045 Args:
2046 net (dict): Network details
2047 name (str): Name to be used as network name if net dict does not include name
2048 created_items (dict): All created items
2049
2050 Returns:
2051 new_port, port New created port, port dictionary
2052
2053 """
2054
2055 port_dict = {
2056 "network_id": net["net_id"],
2057 "name": net.get("name"),
2058 "admin_state_up": True,
2059 }
2060
2061 if not port_dict["name"]:
2062 port_dict["name"] = name
2063
2064 self._prepare_port_dict_security_groups(net, port_dict)
2065
2066 self._prepare_port_dict_binding(net, port_dict)
2067
2068 vimconnector._prepare_port_dict_mac_ip_addr(net, port_dict)
2069
2070 new_port = self._create_new_port(port_dict, created_items, net)
2071
2072 vimconnector._set_fixed_ip(new_port, net)
2073
2074 port = {"port-id": new_port["port"]["id"]}
2075
2076 if float(self.nova.api_version.get_string()) >= 2.32:
2077 port["tag"] = new_port["port"]["name"]
2078
2079 return new_port, port
2080
2081 def _prepare_network_for_vminstance(
2082 self,
2083 name: str,
2084 net_list: list,
2085 created_items: dict,
2086 net_list_vim: list,
2087 external_network: list,
2088 no_secured_ports: list,
2089 ) -> None:
2090 """Create port and fill up net dictionary for new VM instance creation.
2091
2092 Args:
2093 name (str): Name of network
2094 net_list (list): List of networks
2095 created_items (dict): All created items belongs to a VM
2096 net_list_vim (list): List of ports
2097 external_network (list): List of external-networks
2098 no_secured_ports (list): Port security disabled ports
2099 """
2100
2101 self._reload_connection()
2102
2103 for net in net_list:
2104 # Skip non-connected iface
2105 if not net.get("net_id"):
2106 continue
2107
2108 new_port, port = self._create_port(net, name, created_items)
2109
2110 net_list_vim.append(port)
2111
2112 if net.get("floating_ip", False):
2113 net["exit_on_floating_ip_error"] = True
2114 external_network.append(net)
2115
2116 elif net["use"] == "mgmt" and self.config.get("use_floating_ip"):
2117 net["exit_on_floating_ip_error"] = False
2118 external_network.append(net)
2119 net["floating_ip"] = self.config.get("use_floating_ip")
2120
2121 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic
2122 # is dropped. As a workaround we wait until the VM is active and then disable the port-security
2123 if net.get("port_security") is False and not self.config.get(
2124 "no_port_security_extension"
2125 ):
2126 no_secured_ports.append(
2127 (
2128 new_port["port"]["id"],
2129 net.get("port_security_disable_strategy"),
2130 )
2131 )
2132
2133 def _prepare_persistent_root_volumes(
2134 self,
2135 name: str,
Luis Vega25bc6382023-10-05 23:22:04 +00002136 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002137 disk: dict,
2138 base_disk_index: int,
2139 block_device_mapping: dict,
2140 existing_vim_volumes: list,
2141 created_items: dict,
2142 ) -> Optional[str]:
2143 """Prepare persistent root volumes for new VM instance.
2144
2145 Args:
2146 name (str): Name of VM instance
Luis Vega25bc6382023-10-05 23:22:04 +00002147 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002148 disk (dict): Disk details
2149 base_disk_index (int): Disk index
2150 block_device_mapping (dict): Block device details
2151 existing_vim_volumes (list): Existing disk details
2152 created_items (dict): All created items belongs to VM
2153
2154 Returns:
2155 boot_volume_id (str): ID of boot volume
2156
2157 """
garciadeblas6a0147f2024-08-19 08:02:04 +00002158 self.logger.debug("Preparing root persistent volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002159 # Disk may include only vim_volume_id or only vim_id."
2160 # Use existing persistent root volume finding with volume_id or vim_id
2161 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002162 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002163 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2164 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002165 else:
2166 # Create persistent root volume
2167 volume = self.cinder.volumes.create(
2168 size=disk["size"],
2169 name=name + "vd" + chr(base_disk_index),
2170 imageRef=disk["image_id"],
2171 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002172 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002173 )
2174 boot_volume_id = volume.id
aticig2f4ab6c2022-09-03 18:15:20 +03002175 self.update_block_device_mapping(
2176 volume=volume,
2177 block_device_mapping=block_device_mapping,
2178 base_disk_index=base_disk_index,
2179 disk=disk,
2180 created_items=created_items,
2181 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002182
2183 return boot_volume_id
2184
aticig2f4ab6c2022-09-03 18:15:20 +03002185 @staticmethod
2186 def update_block_device_mapping(
2187 volume: object,
2188 block_device_mapping: dict,
2189 base_disk_index: int,
2190 disk: dict,
2191 created_items: dict,
2192 ) -> None:
2193 """Add volume information to block device mapping dict.
2194 Args:
2195 volume (object): Created volume object
2196 block_device_mapping (dict): Block device details
2197 base_disk_index (int): Disk index
2198 disk (dict): Disk details
2199 created_items (dict): All created items belongs to VM
2200 """
2201 if not volume:
2202 raise vimconn.VimConnException("Volume is empty.")
2203
2204 if not hasattr(volume, "id"):
2205 raise vimconn.VimConnException(
2206 "Created volume is not valid, does not have id attribute."
2207 )
2208
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002209 block_device_mapping["vd" + chr(base_disk_index)] = volume.id
2210 if disk.get("multiattach"): # multiattach volumes do not belong to VDUs
2211 return
aticig2f4ab6c2022-09-03 18:15:20 +03002212 volume_txt = "volume:" + str(volume.id)
2213 if disk.get("keep"):
2214 volume_txt += ":keep"
2215 created_items[volume_txt] = True
aticig2f4ab6c2022-09-03 18:15:20 +03002216
gatici335a06a2023-07-26 00:34:04 +03002217 @catch_any_exception
vegall364627c2023-03-17 15:09:50 +00002218 def new_shared_volumes(self, shared_volume_data) -> (str, str):
garciadeblas6a0147f2024-08-19 08:02:04 +00002219 self.logger.debug("Creating new shared volume")
Luis Vega25bc6382023-10-05 23:22:04 +00002220 availability_zone = (
2221 self.storage_availability_zone
2222 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002223 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002224 )
gatici335a06a2023-07-26 00:34:04 +03002225 volume = self.cinder.volumes.create(
2226 size=shared_volume_data["size"],
2227 name=shared_volume_data["name"],
2228 volume_type="multiattach",
Luis Vega25bc6382023-10-05 23:22:04 +00002229 availability_zone=availability_zone,
gatici335a06a2023-07-26 00:34:04 +03002230 )
2231 return volume.name, volume.id
vegall364627c2023-03-17 15:09:50 +00002232
2233 def _prepare_shared_volumes(
2234 self,
2235 name: str,
2236 disk: dict,
2237 base_disk_index: int,
2238 block_device_mapping: dict,
2239 existing_vim_volumes: list,
2240 created_items: dict,
2241 ):
garciadeblas6a0147f2024-08-19 08:02:04 +00002242 self.logger.debug("Preparing shared volumes")
vegall364627c2023-03-17 15:09:50 +00002243 volumes = {volume.name: volume.id for volume in self.cinder.volumes.list()}
2244 if volumes.get(disk["name"]):
2245 sv_id = volumes[disk["name"]]
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002246 max_retries = 3
2247 vol_status = ""
2248 # If this is not the first VM to attach the volume, volume status may be "reserved" for a short time
2249 while max_retries:
2250 max_retries -= 1
2251 volume = self.cinder.volumes.get(sv_id)
2252 vol_status = volume.status
2253 if volume.status not in ("in-use", "available"):
2254 time.sleep(5)
2255 continue
2256 self.update_block_device_mapping(
2257 volume=volume,
2258 block_device_mapping=block_device_mapping,
2259 base_disk_index=base_disk_index,
2260 disk=disk,
2261 created_items=created_items,
2262 )
2263 return
2264 raise vimconn.VimConnException(
2265 "Shared volume is not prepared, status is: {}".format(vol_status),
2266 http_code=vimconn.HTTP_Internal_Server_Error,
vegall364627c2023-03-17 15:09:50 +00002267 )
2268
Gulsum Atici26f73662022-10-27 15:18:27 +03002269 def _prepare_non_root_persistent_volumes(
2270 self,
2271 name: str,
2272 disk: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002273 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002274 block_device_mapping: dict,
2275 base_disk_index: int,
2276 existing_vim_volumes: list,
2277 created_items: dict,
2278 ) -> None:
2279 """Prepare persistent volumes for new VM instance.
2280
2281 Args:
2282 name (str): Name of VM instance
2283 disk (dict): Disk details
Luis Vega25bc6382023-10-05 23:22:04 +00002284 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002285 block_device_mapping (dict): Block device details
2286 base_disk_index (int): Disk index
2287 existing_vim_volumes (list): Existing disk details
2288 created_items (dict): All created items belongs to VM
2289 """
2290 # Non-root persistent volumes
2291 # Disk may include only vim_volume_id or only vim_id."
garciadeblas6a0147f2024-08-19 08:02:04 +00002292 self.logger.debug("Preparing non-root persistent volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002293 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002294 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002295 # Use existing persistent volume
2296 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2297 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002298 else:
vegall364627c2023-03-17 15:09:50 +00002299 volume_name = f"{name}vd{chr(base_disk_index)}"
Gulsum Atici26f73662022-10-27 15:18:27 +03002300 volume = self.cinder.volumes.create(
2301 size=disk["size"],
vegall364627c2023-03-17 15:09:50 +00002302 name=volume_name,
Gulsum Atici26f73662022-10-27 15:18:27 +03002303 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002304 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002305 )
aticig2f4ab6c2022-09-03 18:15:20 +03002306 self.update_block_device_mapping(
2307 volume=volume,
2308 block_device_mapping=block_device_mapping,
2309 base_disk_index=base_disk_index,
2310 disk=disk,
2311 created_items=created_items,
2312 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002313
2314 def _wait_for_created_volumes_availability(
2315 self, elapsed_time: int, created_items: dict
2316 ) -> Optional[int]:
2317 """Wait till created volumes become available.
2318
2319 Args:
2320 elapsed_time (int): Passed time while waiting
2321 created_items (dict): All created items belongs to VM
2322
2323 Returns:
2324 elapsed_time (int): Time spent while waiting
2325
2326 """
garciadeblas6a0147f2024-08-19 08:02:04 +00002327 self.logger.debug("Waiting for all created volumes to become available")
Gulsum Atici26f73662022-10-27 15:18:27 +03002328 while elapsed_time < volume_timeout:
garciadeblas6a0147f2024-08-19 08:02:04 +00002329 self.logger.debug("Checking disk availability for created volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002330 for created_item in created_items:
aticig2f4ab6c2022-09-03 18:15:20 +03002331 v, volume_id = (
2332 created_item.split(":")[0],
2333 created_item.split(":")[1],
2334 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002335 if v == "volume":
garciadeblas6a0147f2024-08-19 08:02:04 +00002336 self.logger.debug(f"Checking volume: {volume_id}")
vegall364627c2023-03-17 15:09:50 +00002337 volume = self.cinder.volumes.get(volume_id)
2338 if (
2339 volume.volume_type == "multiattach"
2340 and volume.status == "in-use"
2341 ):
2342 return elapsed_time
2343 elif volume.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002344 break
2345 else:
2346 # All ready: break from while
2347 break
2348
2349 time.sleep(5)
2350 elapsed_time += 5
2351
2352 return elapsed_time
2353
2354 def _wait_for_existing_volumes_availability(
2355 self, elapsed_time: int, existing_vim_volumes: list
2356 ) -> Optional[int]:
2357 """Wait till existing volumes become available.
2358
2359 Args:
2360 elapsed_time (int): Passed time while waiting
2361 existing_vim_volumes (list): Existing volume details
2362
2363 Returns:
2364 elapsed_time (int): Time spent while waiting
2365
2366 """
2367
garciadeblas6a0147f2024-08-19 08:02:04 +00002368 self.logger.debug("Waiting for all existing volumes to become available")
Gulsum Atici26f73662022-10-27 15:18:27 +03002369 while elapsed_time < volume_timeout:
garciadeblas6a0147f2024-08-19 08:02:04 +00002370 self.logger.debug("Checking disk availability for existing volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002371 for volume in existing_vim_volumes:
garciadeblas6a0147f2024-08-19 08:02:04 +00002372 self.logger.debug(f"Checking existing volume: {volume}")
vegall364627c2023-03-17 15:09:50 +00002373 v = self.cinder.volumes.get(volume["id"])
2374 if v.volume_type == "multiattach" and v.status == "in-use":
2375 return elapsed_time
2376 elif v.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002377 break
2378 else: # all ready: break from while
2379 break
2380
2381 time.sleep(5)
2382 elapsed_time += 5
2383
2384 return elapsed_time
2385
2386 def _prepare_disk_for_vminstance(
2387 self,
2388 name: str,
2389 existing_vim_volumes: list,
2390 created_items: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002391 storage_av_zone: list,
Gulsum Atici13d02322022-11-18 00:10:15 +03002392 block_device_mapping: dict,
Gulsum Atici26f73662022-10-27 15:18:27 +03002393 disk_list: list = None,
2394 ) -> None:
2395 """Prepare all volumes for new VM instance.
2396
2397 Args:
2398 name (str): Name of Instance
2399 existing_vim_volumes (list): List of existing volumes
2400 created_items (dict): All created items belongs to VM
Luis Vega25bc6382023-10-05 23:22:04 +00002401 storage_av_zone (list): Storage availability zone
Gulsum Atici13d02322022-11-18 00:10:15 +03002402 block_device_mapping (dict): Block devices to be attached to VM
Gulsum Atici26f73662022-10-27 15:18:27 +03002403 disk_list (list): List of disks
2404
2405 """
2406 # Create additional volumes in case these are present in disk_list
garciadeblas6a0147f2024-08-19 08:02:04 +00002407 self.logger.debug("Preparing disks for VM instances")
Gulsum Atici26f73662022-10-27 15:18:27 +03002408 base_disk_index = ord("b")
2409 boot_volume_id = None
2410 elapsed_time = 0
Gulsum Atici26f73662022-10-27 15:18:27 +03002411 for disk in disk_list:
garciadeblas6a0147f2024-08-19 08:02:04 +00002412 self.logger.debug(f"Disk: {disk}")
Gulsum Atici26f73662022-10-27 15:18:27 +03002413 if "image_id" in disk:
2414 # Root persistent volume
2415 base_disk_index = ord("a")
2416 boot_volume_id = self._prepare_persistent_root_volumes(
2417 name=name,
Luis Vega25bc6382023-10-05 23:22:04 +00002418 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002419 disk=disk,
2420 base_disk_index=base_disk_index,
2421 block_device_mapping=block_device_mapping,
2422 existing_vim_volumes=existing_vim_volumes,
2423 created_items=created_items,
2424 )
vegall364627c2023-03-17 15:09:50 +00002425 elif disk.get("multiattach"):
2426 self._prepare_shared_volumes(
2427 name=name,
2428 disk=disk,
2429 base_disk_index=base_disk_index,
2430 block_device_mapping=block_device_mapping,
2431 existing_vim_volumes=existing_vim_volumes,
2432 created_items=created_items,
2433 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002434 else:
2435 # Non-root persistent volume
2436 self._prepare_non_root_persistent_volumes(
2437 name=name,
2438 disk=disk,
Luis Vega25bc6382023-10-05 23:22:04 +00002439 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002440 block_device_mapping=block_device_mapping,
2441 base_disk_index=base_disk_index,
2442 existing_vim_volumes=existing_vim_volumes,
2443 created_items=created_items,
2444 )
2445 base_disk_index += 1
2446
2447 # Wait until created volumes are with status available
2448 elapsed_time = self._wait_for_created_volumes_availability(
2449 elapsed_time, created_items
2450 )
2451 # Wait until existing volumes in vim are with status available
2452 elapsed_time = self._wait_for_existing_volumes_availability(
2453 elapsed_time, existing_vim_volumes
2454 )
2455 # If we exceeded the timeout rollback
2456 if elapsed_time >= volume_timeout:
2457 raise vimconn.VimConnException(
2458 "Timeout creating volumes for instance " + name,
2459 http_code=vimconn.HTTP_Request_Timeout,
2460 )
2461 if boot_volume_id:
2462 self.cinder.volumes.set_bootable(boot_volume_id, True)
2463
2464 def _find_the_external_network_for_floating_ip(self):
2465 """Get the external network ip in order to create floating IP.
2466
2467 Returns:
2468 pool_id (str): External network pool ID
2469
2470 """
2471
2472 # Find the external network
2473 external_nets = list()
2474
2475 for net in self.neutron.list_networks()["networks"]:
2476 if net["router:external"]:
2477 external_nets.append(net)
2478
2479 if len(external_nets) == 0:
2480 raise vimconn.VimConnException(
2481 "Cannot create floating_ip automatically since "
2482 "no external network is present",
2483 http_code=vimconn.HTTP_Conflict,
2484 )
2485
2486 if len(external_nets) > 1:
2487 raise vimconn.VimConnException(
2488 "Cannot create floating_ip automatically since "
2489 "multiple external networks are present",
2490 http_code=vimconn.HTTP_Conflict,
2491 )
2492
2493 # Pool ID
2494 return external_nets[0].get("id")
2495
2496 def _neutron_create_float_ip(self, param: dict, created_items: dict) -> None:
2497 """Trigger neutron to create a new floating IP using external network ID.
2498
2499 Args:
2500 param (dict): Input parameters to create a floating IP
2501 created_items (dict): All created items belongs to new VM instance
2502
2503 Raises:
2504
2505 VimConnException
2506 """
2507 try:
2508 self.logger.debug("Creating floating IP")
2509 new_floating_ip = self.neutron.create_floatingip(param)
2510 free_floating_ip = new_floating_ip["floatingip"]["id"]
2511 created_items["floating_ip:" + str(free_floating_ip)] = True
2512
2513 except Exception as e:
2514 raise vimconn.VimConnException(
2515 type(e).__name__ + ": Cannot create new floating_ip " + str(e),
2516 http_code=vimconn.HTTP_Conflict,
2517 )
2518
2519 def _create_floating_ip(
2520 self, floating_network: dict, server: object, created_items: dict
2521 ) -> None:
2522 """Get the available Pool ID and create a new floating IP.
2523
2524 Args:
2525 floating_network (dict): Dict including external network ID
2526 server (object): Server object
2527 created_items (dict): All created items belongs to new VM instance
2528
2529 """
2530
2531 # Pool_id is available
2532 if (
2533 isinstance(floating_network["floating_ip"], str)
2534 and floating_network["floating_ip"].lower() != "true"
2535 ):
2536 pool_id = floating_network["floating_ip"]
2537
2538 # Find the Pool_id
2539 else:
2540 pool_id = self._find_the_external_network_for_floating_ip()
2541
2542 param = {
2543 "floatingip": {
2544 "floating_network_id": pool_id,
2545 "tenant_id": server.tenant_id,
2546 }
2547 }
2548
2549 self._neutron_create_float_ip(param, created_items)
2550
2551 def _find_floating_ip(
2552 self,
2553 server: object,
2554 floating_ips: list,
2555 floating_network: dict,
2556 ) -> Optional[str]:
2557 """Find the available free floating IPs if there are.
2558
2559 Args:
2560 server (object): Server object
2561 floating_ips (list): List of floating IPs
2562 floating_network (dict): Details of floating network such as ID
2563
2564 Returns:
2565 free_floating_ip (str): Free floating ip address
2566
2567 """
2568 for fip in floating_ips:
2569 if fip.get("port_id") or fip.get("tenant_id") != server.tenant_id:
2570 continue
2571
2572 if isinstance(floating_network["floating_ip"], str):
2573 if fip.get("floating_network_id") != floating_network["floating_ip"]:
2574 continue
2575
2576 return fip["id"]
2577
2578 def _assign_floating_ip(
2579 self, free_floating_ip: str, floating_network: dict
2580 ) -> Dict:
2581 """Assign the free floating ip address to port.
2582
2583 Args:
2584 free_floating_ip (str): Floating IP to be assigned
2585 floating_network (dict): ID of floating network
2586
2587 Returns:
2588 fip (dict) (dict): Floating ip details
2589
2590 """
2591 # The vim_id key contains the neutron.port_id
2592 self.neutron.update_floatingip(
2593 free_floating_ip,
2594 {"floatingip": {"port_id": floating_network["vim_id"]}},
2595 )
2596 # For race condition ensure not re-assigned to other VM after 5 seconds
2597 time.sleep(5)
2598
2599 return self.neutron.show_floatingip(free_floating_ip)
2600
2601 def _get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002602 self, server: object, floating_network: dict
Gulsum Atici26f73662022-10-27 15:18:27 +03002603 ) -> Optional[str]:
2604 """Get the free floating IP address.
2605
2606 Args:
2607 server (object): Server Object
2608 floating_network (dict): Floating network details
Gulsum Atici26f73662022-10-27 15:18:27 +03002609
2610 Returns:
2611 free_floating_ip (str): Free floating ip addr
2612
2613 """
2614
2615 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
2616
2617 # Randomize
2618 random.shuffle(floating_ips)
2619
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002620 return self._find_floating_ip(server, floating_ips, floating_network)
Gulsum Atici26f73662022-10-27 15:18:27 +03002621
2622 def _prepare_external_network_for_vminstance(
2623 self,
2624 external_network: list,
2625 server: object,
2626 created_items: dict,
2627 vm_start_time: float,
2628 ) -> None:
2629 """Assign floating IP address for VM instance.
2630
2631 Args:
2632 external_network (list): ID of External network
2633 server (object): Server Object
2634 created_items (dict): All created items belongs to new VM instance
2635 vm_start_time (float): Time as a floating point number expressed in seconds since the epoch, in UTC
2636
2637 Raises:
2638 VimConnException
2639
2640 """
2641 for floating_network in external_network:
2642 try:
2643 assigned = False
2644 floating_ip_retries = 3
2645 # In case of RO in HA there can be conflicts, two RO trying to assign same floating IP, so retry
2646 # several times
2647 while not assigned:
Gulsum Atici26f73662022-10-27 15:18:27 +03002648 free_floating_ip = self._get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002649 server, floating_network
Gulsum Atici26f73662022-10-27 15:18:27 +03002650 )
2651
2652 if not free_floating_ip:
2653 self._create_floating_ip(
2654 floating_network, server, created_items
2655 )
2656
2657 try:
2658 # For race condition ensure not already assigned
2659 fip = self.neutron.show_floatingip(free_floating_ip)
2660
2661 if fip["floatingip"].get("port_id"):
2662 continue
2663
2664 # Assign floating ip
2665 fip = self._assign_floating_ip(
2666 free_floating_ip, floating_network
2667 )
2668
2669 if fip["floatingip"]["port_id"] != floating_network["vim_id"]:
2670 self.logger.warning(
2671 "floating_ip {} re-assigned to other port".format(
2672 free_floating_ip
2673 )
2674 )
2675 continue
2676
2677 self.logger.debug(
2678 "Assigned floating_ip {} to VM {}".format(
2679 free_floating_ip, server.id
2680 )
2681 )
2682
2683 assigned = True
2684
2685 except Exception as e:
2686 # Openstack need some time after VM creation to assign an IP. So retry if fails
2687 vm_status = self.nova.servers.get(server.id).status
2688
2689 if vm_status not in ("ACTIVE", "ERROR"):
2690 if time.time() - vm_start_time < server_timeout:
2691 time.sleep(5)
2692 continue
2693 elif floating_ip_retries > 0:
2694 floating_ip_retries -= 1
2695 continue
2696
2697 raise vimconn.VimConnException(
2698 "Cannot create floating_ip: {} {}".format(
2699 type(e).__name__, e
2700 ),
2701 http_code=vimconn.HTTP_Conflict,
2702 )
2703
2704 except Exception as e:
2705 if not floating_network["exit_on_floating_ip_error"]:
2706 self.logger.error("Cannot create floating_ip. %s", str(e))
2707 continue
2708
2709 raise
2710
2711 def _update_port_security_for_vminstance(
2712 self,
2713 no_secured_ports: list,
2714 server: object,
2715 ) -> None:
2716 """Updates the port security according to no_secured_ports list.
2717
2718 Args:
2719 no_secured_ports (list): List of ports that security will be disabled
2720 server (object): Server Object
2721
2722 Raises:
2723 VimConnException
2724
2725 """
2726 # Wait until the VM is active and then disable the port-security
2727 if no_secured_ports:
2728 self.__wait_for_vm(server.id, "ACTIVE")
2729
2730 for port in no_secured_ports:
2731 port_update = {
2732 "port": {"port_security_enabled": False, "security_groups": None}
2733 }
2734
2735 if port[1] == "allow-address-pairs":
2736 port_update = {
2737 "port": {"allowed_address_pairs": [{"ip_address": "0.0.0.0/0"}]}
2738 }
2739
2740 try:
2741 self.neutron.update_port(port[0], port_update)
2742
2743 except Exception:
Gulsum Atici26f73662022-10-27 15:18:27 +03002744 raise vimconn.VimConnException(
2745 "It was not possible to disable port security for port {}".format(
2746 port[0]
2747 )
2748 )
2749
sousaedu80135b92021-02-17 15:05:18 +01002750 def new_vminstance(
2751 self,
Gulsum Atici26f73662022-10-27 15:18:27 +03002752 name: str,
2753 description: str,
2754 start: bool,
2755 image_id: str,
2756 flavor_id: str,
2757 affinity_group_list: list,
2758 net_list: list,
sousaedu80135b92021-02-17 15:05:18 +01002759 cloud_config=None,
2760 disk_list=None,
2761 availability_zone_index=None,
2762 availability_zone_list=None,
Gulsum Atici26f73662022-10-27 15:18:27 +03002763 ) -> tuple:
2764 """Adds a VM instance to VIM.
2765
2766 Args:
2767 name (str): name of VM
2768 description (str): description
2769 start (bool): indicates if VM must start or boot in pause mode. Ignored
2770 image_id (str) image uuid
2771 flavor_id (str) flavor uuid
2772 affinity_group_list (list): list of affinity groups, each one is a dictionary.Ignore if empty.
2773 net_list (list): list of interfaces, each one is a dictionary with:
2774 name: name of network
2775 net_id: network uuid to connect
2776 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
2777 model: interface model, ignored #TODO
2778 mac_address: used for SR-IOV ifaces #TODO for other types
2779 use: 'data', 'bridge', 'mgmt'
2780 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
2781 vim_id: filled/added by this function
2782 floating_ip: True/False (or it can be None)
2783 port_security: True/False
2784 cloud_config (dict): (optional) dictionary with:
2785 key-pairs: (optional) list of strings with the public key to be inserted to the default user
2786 users: (optional) list of users to be inserted, each item is a dict with:
2787 name: (mandatory) user name,
2788 key-pairs: (optional) list of strings with the public key to be inserted to the user
2789 user-data: (optional) string is a text script to be passed directly to cloud-init
2790 config-files: (optional). List of files to be transferred. Each item is a dict with:
2791 dest: (mandatory) string with the destination absolute path
2792 encoding: (optional, by default text). Can be one of:
tierno1d213f42020-04-24 14:02:51 +00002793 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
Gulsum Atici26f73662022-10-27 15:18:27 +03002794 content : (mandatory) string with the content of the file
2795 permissions: (optional) string with file permissions, typically octal notation '0644'
2796 owner: (optional) file owner, string with the format 'owner:group'
2797 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
2798 disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
2799 image_id: (optional). VIM id of an existing image. If not provided an empty disk must be mounted
2800 size: (mandatory) string with the size of the disk in GB
2801 vim_id: (optional) should use this existing volume id
2802 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
2803 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
tierno5a3273c2017-08-29 11:43:46 +02002804 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01002805 #TODO ip, security groups
Gulsum Atici26f73662022-10-27 15:18:27 +03002806
2807 Returns:
2808 A tuple with the instance identifier and created_items or raises an exception on error
tierno98e909c2017-10-14 13:27:03 +02002809 created_items can be None or a dictionary where this method can include key-values that will be passed to
2810 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
2811 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
2812 as not present.
aticig2f4ab6c2022-09-03 18:15:20 +03002813
tierno98e909c2017-10-14 13:27:03 +02002814 """
sousaedu80135b92021-02-17 15:05:18 +01002815 self.logger.debug(
2816 "new_vminstance input: image='%s' flavor='%s' nics='%s'",
2817 image_id,
2818 flavor_id,
2819 str(net_list),
2820 )
gatici335a06a2023-07-26 00:34:04 +03002821 server = None
2822 created_items = {}
2823 net_list_vim = []
2824 # list of external networks to be connected to instance, later on used to create floating_ip
2825 external_network = []
2826 # List of ports with port-security disabled
2827 no_secured_ports = []
2828 block_device_mapping = {}
2829 existing_vim_volumes = []
2830 server_group_id = None
2831 scheduller_hints = {}
sousaedu80135b92021-02-17 15:05:18 +01002832
tierno7edb6752016-03-21 17:37:52 +01002833 try:
Gulsum Atici26f73662022-10-27 15:18:27 +03002834 # Check the Openstack Connection
2835 self._reload_connection()
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02002836
Gulsum Atici26f73662022-10-27 15:18:27 +03002837 # Prepare network list
2838 self._prepare_network_for_vminstance(
2839 name=name,
2840 net_list=net_list,
2841 created_items=created_items,
2842 net_list_vim=net_list_vim,
2843 external_network=external_network,
2844 no_secured_ports=no_secured_ports,
sousaedu80135b92021-02-17 15:05:18 +01002845 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002846
Gulsum Atici26f73662022-10-27 15:18:27 +03002847 # Cloud config
tierno0a1437e2017-10-02 00:17:43 +02002848 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00002849
Gulsum Atici26f73662022-10-27 15:18:27 +03002850 # Get availability Zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002851 self.vm_av_zone = self._get_vm_availability_zone(
Alexis Romero247cc432022-05-12 13:23:25 +02002852 availability_zone_index, availability_zone_list
2853 )
2854
Luis Vega25bc6382023-10-05 23:22:04 +00002855 storage_av_zone = (
2856 self.storage_availability_zone
2857 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002858 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002859 )
2860
tierno1df468d2018-07-06 14:25:16 +02002861 if disk_list:
Gulsum Atici26f73662022-10-27 15:18:27 +03002862 # Prepare disks
2863 self._prepare_disk_for_vminstance(
2864 name=name,
2865 existing_vim_volumes=existing_vim_volumes,
2866 created_items=created_items,
Luis Vega25bc6382023-10-05 23:22:04 +00002867 storage_av_zone=storage_av_zone,
Gulsum Atici13d02322022-11-18 00:10:15 +03002868 block_device_mapping=block_device_mapping,
Gulsum Atici26f73662022-10-27 15:18:27 +03002869 disk_list=disk_list,
2870 )
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002871
2872 if affinity_group_list:
2873 # Only first id on the list will be used. Openstack restriction
2874 server_group_id = affinity_group_list[0]["affinity_group_id"]
2875 scheduller_hints["group"] = server_group_id
2876
sousaedu80135b92021-02-17 15:05:18 +01002877 self.logger.debug(
2878 "nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
2879 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002880 "block_device_mapping={}, server_group={})".format(
sousaedu80135b92021-02-17 15:05:18 +01002881 name,
2882 image_id,
2883 flavor_id,
2884 net_list_vim,
2885 self.config.get("security_groups"),
Luis Vegaafe8df22023-12-01 01:02:12 +00002886 self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002887 self.config.get("keypair"),
2888 userdata,
2889 config_drive,
2890 block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002891 server_group_id,
sousaedu80135b92021-02-17 15:05:18 +01002892 )
2893 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002894 # Create VM
sousaedu80135b92021-02-17 15:05:18 +01002895 server = self.nova.servers.create(
aticigcf14bb12022-05-19 13:03:17 +03002896 name=name,
2897 image=image_id,
2898 flavor=flavor_id,
sousaedu80135b92021-02-17 15:05:18 +01002899 nics=net_list_vim,
2900 security_groups=self.config.get("security_groups"),
2901 # TODO remove security_groups in future versions. Already at neutron port
Luis Vegaafe8df22023-12-01 01:02:12 +00002902 availability_zone=self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002903 key_name=self.config.get("keypair"),
2904 userdata=userdata,
2905 config_drive=config_drive,
2906 block_device_mapping=block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002907 scheduler_hints=scheduller_hints,
Gulsum Atici26f73662022-10-27 15:18:27 +03002908 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002909
tierno326fd5e2018-02-22 11:58:59 +01002910 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002911
Gulsum Atici26f73662022-10-27 15:18:27 +03002912 self._update_port_security_for_vminstance(no_secured_ports, server)
bravof7a1f5252020-10-20 10:27:42 -03002913
Gulsum Atici26f73662022-10-27 15:18:27 +03002914 self._prepare_external_network_for_vminstance(
2915 external_network=external_network,
2916 server=server,
2917 created_items=created_items,
2918 vm_start_time=vm_start_time,
2919 )
montesmoreno2a1fc4e2017-01-09 16:46:04 +00002920
tierno98e909c2017-10-14 13:27:03 +02002921 return server.id, created_items
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002922
2923 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02002924 server_id = None
2925 if server:
2926 server_id = server.id
sousaedu80135b92021-02-17 15:05:18 +01002927
tierno98e909c2017-10-14 13:27:03 +02002928 try:
aticig2f4ab6c2022-09-03 18:15:20 +03002929 created_items = self.remove_keep_tag_from_persistent_volumes(
2930 created_items
2931 )
2932
tierno98e909c2017-10-14 13:27:03 +02002933 self.delete_vminstance(server_id, created_items)
Gulsum Atici26f73662022-10-27 15:18:27 +03002934
tierno98e909c2017-10-14 13:27:03 +02002935 except Exception as e2:
2936 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002937
tiernoae4a8d12016-07-08 12:30:39 +02002938 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01002939
aticig2f4ab6c2022-09-03 18:15:20 +03002940 @staticmethod
2941 def remove_keep_tag_from_persistent_volumes(created_items: Dict) -> Dict:
2942 """Removes the keep flag from persistent volumes. So, those volumes could be removed.
2943
2944 Args:
2945 created_items (dict): All created items belongs to VM
2946
2947 Returns:
2948 updated_created_items (dict): Dict which does not include keep flag for volumes.
2949
2950 """
2951 return {
2952 key.replace(":keep", ""): value for (key, value) in created_items.items()
2953 }
2954
tierno1ec592d2020-06-16 15:29:47 +00002955 def get_vminstance(self, vm_id):
2956 """Returns the VM instance information from VIM"""
vegallc53829d2023-06-01 00:47:44 -05002957 return self._find_nova_server(vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02002958
gatici335a06a2023-07-26 00:34:04 +03002959 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00002960 def get_vminstance_console(self, vm_id, console_type="vnc"):
2961 """
tierno7edb6752016-03-21 17:37:52 +01002962 Get a console for the virtual machine
2963 Params:
2964 vm_id: uuid of the VM
2965 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002966 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01002967 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02002968 Returns dict with the console parameters:
2969 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002970 server: usually ip address
2971 port: the http, ssh, ... port
2972 suffix: extra text, e.g. the http path and query string
tierno1ec592d2020-06-16 15:29:47 +00002973 """
tiernoae4a8d12016-07-08 12:30:39 +02002974 self.logger.debug("Getting VM CONSOLE from VIM")
gatici335a06a2023-07-26 00:34:04 +03002975 self._reload_connection()
2976 server = self.nova.servers.find(id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01002977
gatici335a06a2023-07-26 00:34:04 +03002978 if console_type is None or console_type == "novnc":
2979 console_dict = server.get_vnc_console("novnc")
2980 elif console_type == "xvpvnc":
2981 console_dict = server.get_vnc_console(console_type)
2982 elif console_type == "rdp-html5":
2983 console_dict = server.get_rdp_console(console_type)
2984 elif console_type == "spice-html5":
2985 console_dict = server.get_spice_console(console_type)
2986 else:
2987 raise vimconn.VimConnException(
2988 "console type '{}' not allowed".format(console_type),
2989 http_code=vimconn.HTTP_Bad_Request,
2990 )
sousaedu80135b92021-02-17 15:05:18 +01002991
gatici335a06a2023-07-26 00:34:04 +03002992 console_dict1 = console_dict.get("console")
2993
2994 if console_dict1:
2995 console_url = console_dict1.get("url")
2996
2997 if console_url:
2998 # parse console_url
2999 protocol_index = console_url.find("//")
3000 suffix_index = (
3001 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3002 )
3003 port_index = (
3004 console_url[protocol_index + 2 : suffix_index].find(":")
3005 + protocol_index
3006 + 2
sousaedu80135b92021-02-17 15:05:18 +01003007 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003008
gatici335a06a2023-07-26 00:34:04 +03003009 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
3010 return (
3011 -vimconn.HTTP_Internal_Server_Error,
3012 "Unexpected response from VIM",
sousaedu80135b92021-02-17 15:05:18 +01003013 )
3014
gatici335a06a2023-07-26 00:34:04 +03003015 console_dict = {
3016 "protocol": console_url[0:protocol_index],
3017 "server": console_url[protocol_index + 2 : port_index],
3018 "port": console_url[port_index:suffix_index],
3019 "suffix": console_url[suffix_index + 1 :],
3020 }
3021 protocol_index += 2
sousaedu80135b92021-02-17 15:05:18 +01003022
gatici335a06a2023-07-26 00:34:04 +03003023 return console_dict
3024 raise vimconn.VimConnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01003025
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003026 def _delete_ports_by_id_wth_neutron(self, k_id: str) -> None:
3027 """Neutron delete ports by id.
3028 Args:
3029 k_id (str): Port id in the VIM
3030 """
3031 try:
limon878f8692023-07-24 15:53:41 +02003032 self.neutron.delete_port(k_id)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003033
gatici335a06a2023-07-26 00:34:04 +03003034 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3035 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3036 # If there is connection error, raise.
3037 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003038 except Exception as e:
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003039 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3040
vegall364627c2023-03-17 15:09:50 +00003041 def delete_shared_volumes(self, shared_volume_vim_id: str) -> bool:
3042 """Cinder delete volume by id.
3043 Args:
3044 shared_volume_vim_id (str): ID of shared volume in VIM
3045 """
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003046 elapsed_time = 0
vegall364627c2023-03-17 15:09:50 +00003047 try:
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003048 while elapsed_time < server_timeout:
3049 vol_status = self.cinder.volumes.get(shared_volume_vim_id).status
3050 if vol_status == "available":
3051 self.cinder.volumes.delete(shared_volume_vim_id)
3052 return True
vegall364627c2023-03-17 15:09:50 +00003053
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003054 time.sleep(5)
3055 elapsed_time += 5
3056
3057 if elapsed_time >= server_timeout:
3058 raise vimconn.VimConnException(
3059 "Timeout waiting for volume "
3060 + shared_volume_vim_id
3061 + " to be available",
3062 http_code=vimconn.HTTP_Request_Timeout,
3063 )
vegall364627c2023-03-17 15:09:50 +00003064
3065 except Exception as e:
3066 self.logger.error(
3067 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3068 )
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003069 self._format_exception(e)
vegall364627c2023-03-17 15:09:50 +00003070
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003071 def _delete_volumes_by_id_wth_cinder(
3072 self, k: str, k_id: str, volumes_to_hold: list, created_items: dict
3073 ) -> bool:
3074 """Cinder delete volume by id.
3075 Args:
3076 k (str): Full item name in created_items
3077 k_id (str): ID of floating ip in VIM
3078 volumes_to_hold (list): Volumes not to delete
3079 created_items (dict): All created items belongs to VM
3080 """
3081 try:
3082 if k_id in volumes_to_hold:
gatici335a06a2023-07-26 00:34:04 +03003083 return False
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003084
3085 if self.cinder.volumes.get(k_id).status != "available":
3086 return True
3087
3088 else:
3089 self.cinder.volumes.delete(k_id)
3090 created_items[k] = None
3091
gatici335a06a2023-07-26 00:34:04 +03003092 except (cExceptions.ConnectionError, ConnectionError) as e:
3093 self.logger.error(
3094 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3095 )
3096 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003097 except Exception as e:
3098 self.logger.error(
3099 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3100 )
3101
3102 def _delete_floating_ip_by_id(self, k: str, k_id: str, created_items: dict) -> None:
3103 """Neutron delete floating ip by id.
3104 Args:
3105 k (str): Full item name in created_items
3106 k_id (str): ID of floating ip in VIM
3107 created_items (dict): All created items belongs to VM
3108 """
3109 try:
3110 self.neutron.delete_floatingip(k_id)
3111 created_items[k] = None
3112
gatici335a06a2023-07-26 00:34:04 +03003113 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3114 self.logger.error(
3115 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3116 )
3117 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003118 except Exception as e:
3119 self.logger.error(
3120 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3121 )
3122
3123 @staticmethod
3124 def _get_item_name_id(k: str) -> Tuple[str, str]:
3125 k_item, _, k_id = k.partition(":")
3126 return k_item, k_id
3127
3128 def _delete_vm_ports_attached_to_network(self, created_items: dict) -> None:
3129 """Delete VM ports attached to the networks before deleting virtual machine.
3130 Args:
3131 created_items (dict): All created items belongs to VM
3132 """
3133
3134 for k, v in created_items.items():
3135 if not v: # skip already deleted
3136 continue
3137
3138 try:
3139 k_item, k_id = self._get_item_name_id(k)
3140 if k_item == "port":
3141 self._delete_ports_by_id_wth_neutron(k_id)
3142
gatici335a06a2023-07-26 00:34:04 +03003143 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3144 self.logger.error(
3145 "Error deleting port: {}: {}".format(type(e).__name__, e)
3146 )
3147 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003148 except Exception as e:
3149 self.logger.error(
3150 "Error deleting port: {}: {}".format(type(e).__name__, e)
3151 )
3152
3153 def _delete_created_items(
3154 self, created_items: dict, volumes_to_hold: list, keep_waiting: bool
3155 ) -> bool:
3156 """Delete Volumes and floating ip if they exist in created_items."""
3157 for k, v in created_items.items():
3158 if not v: # skip already deleted
3159 continue
3160
3161 try:
3162 k_item, k_id = self._get_item_name_id(k)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003163 if k_item == "volume":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003164 unavailable_vol = self._delete_volumes_by_id_wth_cinder(
3165 k, k_id, volumes_to_hold, created_items
3166 )
3167
3168 if unavailable_vol:
3169 keep_waiting = True
3170
3171 elif k_item == "floating_ip":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003172 self._delete_floating_ip_by_id(k, k_id, created_items)
3173
gatici335a06a2023-07-26 00:34:04 +03003174 except (
3175 cExceptions.ConnectionError,
3176 neExceptions.ConnectionFailed,
3177 ConnectionError,
3178 AttributeError,
3179 TypeError,
3180 ) as e:
3181 self.logger.error("Error deleting {}: {}".format(k, e))
3182 self._format_exception(e)
3183
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003184 except Exception as e:
3185 self.logger.error("Error deleting {}: {}".format(k, e))
3186
3187 return keep_waiting
3188
aticig2f4ab6c2022-09-03 18:15:20 +03003189 @staticmethod
3190 def _extract_items_wth_keep_flag_from_created_items(created_items: dict) -> dict:
3191 """Remove the volumes which has key flag from created_items
3192
3193 Args:
3194 created_items (dict): All created items belongs to VM
3195
3196 Returns:
3197 created_items (dict): Persistent volumes eliminated created_items
3198 """
3199 return {
3200 key: value
3201 for (key, value) in created_items.items()
3202 if len(key.split(":")) == 2
3203 }
3204
gatici335a06a2023-07-26 00:34:04 +03003205 @catch_any_exception
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003206 def delete_vminstance(
3207 self, vm_id: str, created_items: dict = None, volumes_to_hold: list = None
3208 ) -> None:
3209 """Removes a VM instance from VIM. Returns the old identifier.
3210 Args:
3211 vm_id (str): Identifier of VM instance
3212 created_items (dict): All created items belongs to VM
3213 volumes_to_hold (list): Volumes_to_hold
3214 """
tierno1ec592d2020-06-16 15:29:47 +00003215 if created_items is None:
tierno98e909c2017-10-14 13:27:03 +02003216 created_items = {}
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003217 if volumes_to_hold is None:
3218 volumes_to_hold = []
sousaedu80135b92021-02-17 15:05:18 +01003219
tierno7edb6752016-03-21 17:37:52 +01003220 try:
aticig2f4ab6c2022-09-03 18:15:20 +03003221 created_items = self._extract_items_wth_keep_flag_from_created_items(
3222 created_items
3223 )
3224
tierno7edb6752016-03-21 17:37:52 +01003225 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01003226
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003227 # Delete VM ports attached to the networks before the virtual machine
3228 if created_items:
3229 self._delete_vm_ports_attached_to_network(created_items)
montesmoreno0c8def02016-12-22 12:16:23 +00003230
tierno98e909c2017-10-14 13:27:03 +02003231 if vm_id:
3232 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00003233
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003234 # Although having detached, volumes should have in active status before deleting.
3235 # We ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00003236 keep_waiting = True
3237 elapsed_time = 0
sousaedu80135b92021-02-17 15:05:18 +01003238
montesmoreno0c8def02016-12-22 12:16:23 +00003239 while keep_waiting and elapsed_time < volume_timeout:
3240 keep_waiting = False
sousaedu80135b92021-02-17 15:05:18 +01003241
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003242 # Delete volumes and floating IP.
3243 keep_waiting = self._delete_created_items(
3244 created_items, volumes_to_hold, keep_waiting
3245 )
sousaedu80135b92021-02-17 15:05:18 +01003246
montesmoreno0c8def02016-12-22 12:16:23 +00003247 if keep_waiting:
3248 time.sleep(1)
3249 elapsed_time += 1
gatici335a06a2023-07-26 00:34:04 +03003250 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
3251 # If VM does not exist, it does not raise
3252 self.logger.warning(f"Error deleting VM: {vm_id} is not found, {str(e)}")
tierno7edb6752016-03-21 17:37:52 +01003253
tiernoae4a8d12016-07-08 12:30:39 +02003254 def refresh_vms_status(self, vm_list):
tierno1ec592d2020-06-16 15:29:47 +00003255 """Get the status of the virtual machines and their interfaces/ports
sousaedu80135b92021-02-17 15:05:18 +01003256 Params: the list of VM identifiers
3257 Returns a dictionary with:
3258 vm_id: #VIM id of this Virtual Machine
3259 status: #Mandatory. Text with one of:
3260 # DELETED (not found at vim)
3261 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
3262 # OTHER (Vim reported other status not understood)
3263 # ERROR (VIM indicates an ERROR status)
3264 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
3265 # CREATING (on building process), ERROR
3266 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
3267 #
3268 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
3269 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3270 interfaces:
3271 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3272 mac_address: #Text format XX:XX:XX:XX:XX:XX
3273 vim_net_id: #network id where this interface is connected
3274 vim_interface_id: #interface/port VIM id
3275 ip_address: #null, or text with IPv4, IPv6 address
3276 compute_node: #identification of compute node where PF,VF interface is allocated
3277 pci: #PCI address of the NIC that hosts the PF,VF
3278 vlan: #physical VLAN used for VF
tierno1ec592d2020-06-16 15:29:47 +00003279 """
3280 vm_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01003281 self.logger.debug(
3282 "refresh_vms status: Getting tenant VM instance information from VIM"
3283 )
tiernoae4a8d12016-07-08 12:30:39 +02003284 for vm_id in vm_list:
tierno1ec592d2020-06-16 15:29:47 +00003285 vm = {}
sousaedu80135b92021-02-17 15:05:18 +01003286
tiernoae4a8d12016-07-08 12:30:39 +02003287 try:
3288 vm_vim = self.get_vminstance(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003289
3290 if vm_vim["status"] in vmStatus2manoFormat:
3291 vm["status"] = vmStatus2manoFormat[vm_vim["status"]]
tierno7edb6752016-03-21 17:37:52 +01003292 else:
sousaedu80135b92021-02-17 15:05:18 +01003293 vm["status"] = "OTHER"
3294 vm["error_msg"] = "VIM status reported " + vm_vim["status"]
3295
tierno70eeb182020-10-19 16:38:00 +00003296 vm_vim.pop("OS-EXT-SRV-ATTR:user_data", None)
3297 vm_vim.pop("user_data", None)
sousaedu80135b92021-02-17 15:05:18 +01003298 vm["vim_info"] = self.serialize(vm_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003299
tiernoae4a8d12016-07-08 12:30:39 +02003300 vm["interfaces"] = []
sousaedu80135b92021-02-17 15:05:18 +01003301 if vm_vim.get("fault"):
3302 vm["error_msg"] = str(vm_vim["fault"])
3303
tierno1ec592d2020-06-16 15:29:47 +00003304 # get interfaces
tierno7edb6752016-03-21 17:37:52 +01003305 try:
tiernoae4a8d12016-07-08 12:30:39 +02003306 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02003307 port_dict = self.neutron.list_ports(device_id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003308
tiernoae4a8d12016-07-08 12:30:39 +02003309 for port in port_dict["ports"]:
tierno1ec592d2020-06-16 15:29:47 +00003310 interface = {}
sousaedu80135b92021-02-17 15:05:18 +01003311 interface["vim_info"] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02003312 interface["mac_address"] = port.get("mac_address")
3313 interface["vim_net_id"] = port["network_id"]
3314 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003315 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003316 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003317
3318 if vm_vim.get("OS-EXT-SRV-ATTR:host"):
3319 interface["compute_node"] = vm_vim["OS-EXT-SRV-ATTR:host"]
3320
tierno867ffe92017-03-27 12:50:34 +02003321 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04003322
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003323 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003324 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003325 if port.get("binding:profile"):
3326 if port["binding:profile"].get("pci_slot"):
tierno1ec592d2020-06-16 15:29:47 +00003327 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting
3328 # the slot to 0x00
Mike Marchetti5b9da422017-05-02 15:35:47 -04003329 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
3330 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
sousaedu80135b92021-02-17 15:05:18 +01003331 pci = port["binding:profile"]["pci_slot"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04003332 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
3333 interface["pci"] = pci
sousaedu80135b92021-02-17 15:05:18 +01003334
tierno867ffe92017-03-27 12:50:34 +02003335 interface["vlan"] = None
sousaedu80135b92021-02-17 15:05:18 +01003336
3337 if port.get("binding:vif_details"):
3338 interface["vlan"] = port["binding:vif_details"].get("vlan")
3339
tierno1dfe9932020-06-18 08:50:10 +00003340 # Get vlan from network in case not present in port for those old openstacks and cases where
3341 # it is needed vlan at PT
3342 if not interface["vlan"]:
3343 # if network is of type vlan and port is of type direct (sr-iov) then set vlan id
3344 network = self.neutron.show_network(port["network_id"])
sousaedu80135b92021-02-17 15:05:18 +01003345
3346 if (
3347 network["network"].get("provider:network_type")
3348 == "vlan"
3349 ):
tierno1dfe9932020-06-18 08:50:10 +00003350 # and port.get("binding:vnic_type") in ("direct", "direct-physical"):
sousaedu80135b92021-02-17 15:05:18 +01003351 interface["vlan"] = network["network"].get(
3352 "provider:segmentation_id"
3353 )
3354
tierno1ec592d2020-06-16 15:29:47 +00003355 ips = []
3356 # look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02003357 try:
sousaedu80135b92021-02-17 15:05:18 +01003358 floating_ip_dict = self.neutron.list_floatingips(
3359 port_id=port["id"]
3360 )
3361
tiernob42fd9b2018-06-20 10:44:32 +02003362 if floating_ip_dict.get("floatingips"):
sousaedu80135b92021-02-17 15:05:18 +01003363 ips.append(
3364 floating_ip_dict["floatingips"][0].get(
3365 "floating_ip_address"
3366 )
3367 )
tiernob42fd9b2018-06-20 10:44:32 +02003368 except Exception:
3369 pass
tierno7edb6752016-03-21 17:37:52 +01003370
tiernoae4a8d12016-07-08 12:30:39 +02003371 for subnet in port["fixed_ips"]:
3372 ips.append(subnet["ip_address"])
sousaedu80135b92021-02-17 15:05:18 +01003373
tiernoae4a8d12016-07-08 12:30:39 +02003374 interface["ip_address"] = ";".join(ips)
3375 vm["interfaces"].append(interface)
3376 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01003377 self.logger.error(
3378 "Error getting vm interface information {}: {}".format(
3379 type(e).__name__, e
3380 ),
3381 exc_info=True,
3382 )
tierno72774862020-05-04 11:44:15 +00003383 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003384 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003385 vm["status"] = "DELETED"
3386 vm["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00003387 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003388 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003389 vm["status"] = "VIM_ERROR"
3390 vm["error_msg"] = str(e)
3391
tiernoae4a8d12016-07-08 12:30:39 +02003392 vm_dict[vm_id] = vm
sousaedu80135b92021-02-17 15:05:18 +01003393
tiernoae4a8d12016-07-08 12:30:39 +02003394 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003395
gatici335a06a2023-07-26 00:34:04 +03003396 @catch_any_exception
tierno98e909c2017-10-14 13:27:03 +02003397 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno1ec592d2020-06-16 15:29:47 +00003398 """Send and action over a VM instance from VIM
Gulsum Atici21c55d62023-02-02 20:41:00 +03003399 Returns None or the console dict if the action was successfully sent to the VIM
3400 """
tiernoae4a8d12016-07-08 12:30:39 +02003401 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
gatici335a06a2023-07-26 00:34:04 +03003402 self._reload_connection()
3403 server = self.nova.servers.find(id=vm_id)
3404 if "start" in action_dict:
3405 if action_dict["start"] == "rebuild":
3406 server.rebuild()
Rahul Kumar8875f912023-11-08 06:48:12 +00003407 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
3408 if not vm_state:
3409 raise nvExceptions.BadRequest(
3410 409,
3411 message="Cannot 'REBUILD' vm_state is in ERROR",
3412 )
gatici335a06a2023-07-26 00:34:04 +03003413 else:
3414 if server.status == "PAUSED":
3415 server.unpause()
3416 elif server.status == "SUSPENDED":
3417 server.resume()
3418 elif server.status == "SHUTOFF":
3419 server.start()
Rahul Kumar8875f912023-11-08 06:48:12 +00003420 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
3421 if not vm_state:
3422 raise nvExceptions.BadRequest(
3423 409,
3424 message="Cannot 'START' vm_state is in ERROR",
3425 )
tierno7edb6752016-03-21 17:37:52 +01003426 else:
gatici335a06a2023-07-26 00:34:04 +03003427 self.logger.debug(
3428 "ERROR : Instance is not in SHUTOFF/PAUSE/SUSPEND state"
3429 )
k4.rahul78f474e2022-05-02 15:47:57 +00003430 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03003431 "Cannot 'start' instance while it is in active state",
k4.rahul78f474e2022-05-02 15:47:57 +00003432 http_code=vimconn.HTTP_Bad_Request,
3433 )
gatici335a06a2023-07-26 00:34:04 +03003434 elif "pause" in action_dict:
3435 server.pause()
3436 elif "resume" in action_dict:
3437 server.resume()
3438 elif "shutoff" in action_dict or "shutdown" in action_dict:
3439 self.logger.debug("server status %s", server.status)
3440 if server.status == "ACTIVE":
3441 server.stop()
Rahul Kumar8875f912023-11-08 06:48:12 +00003442 vm_state = self.__wait_for_vm(vm_id, "SHUTOFF")
3443 if not vm_state:
3444 raise nvExceptions.BadRequest(
3445 409,
3446 message="Cannot 'STOP' vm_state is in ERROR",
3447 )
gatici335a06a2023-07-26 00:34:04 +03003448 else:
3449 self.logger.debug("ERROR: VM is not in Active state")
3450 raise vimconn.VimConnException(
3451 "VM is not in active state, stop operation is not allowed",
3452 http_code=vimconn.HTTP_Bad_Request,
3453 )
3454 elif "forceOff" in action_dict:
3455 server.stop() # TODO
3456 elif "terminate" in action_dict:
3457 server.delete()
3458 elif "createImage" in action_dict:
3459 server.create_image()
3460 # "path":path_schema,
3461 # "description":description_schema,
3462 # "name":name_schema,
3463 # "metadata":metadata_schema,
3464 # "imageRef": id_schema,
3465 # "disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
3466 elif "rebuild" in action_dict:
3467 server.rebuild(server.image["id"])
3468 elif "reboot" in action_dict:
3469 server.reboot() # reboot_type="SOFT"
3470 elif "console" in action_dict:
3471 console_type = action_dict["console"]
sousaedu80135b92021-02-17 15:05:18 +01003472
gatici335a06a2023-07-26 00:34:04 +03003473 if console_type is None or console_type == "novnc":
3474 console_dict = server.get_vnc_console("novnc")
3475 elif console_type == "xvpvnc":
3476 console_dict = server.get_vnc_console(console_type)
3477 elif console_type == "rdp-html5":
3478 console_dict = server.get_rdp_console(console_type)
3479 elif console_type == "spice-html5":
3480 console_dict = server.get_spice_console(console_type)
3481 else:
3482 raise vimconn.VimConnException(
3483 "console type '{}' not allowed".format(console_type),
3484 http_code=vimconn.HTTP_Bad_Request,
3485 )
sousaedu80135b92021-02-17 15:05:18 +01003486
gatici335a06a2023-07-26 00:34:04 +03003487 try:
3488 console_url = console_dict["console"]["url"]
3489 # parse console_url
3490 protocol_index = console_url.find("//")
3491 suffix_index = (
3492 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3493 )
3494 port_index = (
3495 console_url[protocol_index + 2 : suffix_index].find(":")
3496 + protocol_index
3497 + 2
3498 )
sousaedu80135b92021-02-17 15:05:18 +01003499
gatici335a06a2023-07-26 00:34:04 +03003500 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
sousaedu80135b92021-02-17 15:05:18 +01003501 raise vimconn.VimConnException(
3502 "Unexpected response from VIM " + str(console_dict)
3503 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003504
gatici335a06a2023-07-26 00:34:04 +03003505 console_dict2 = {
3506 "protocol": console_url[0:protocol_index],
3507 "server": console_url[protocol_index + 2 : port_index],
3508 "port": int(console_url[port_index + 1 : suffix_index]),
3509 "suffix": console_url[suffix_index + 1 :],
3510 }
3511
3512 return console_dict2
3513 except Exception:
3514 raise vimconn.VimConnException(
3515 "Unexpected response from VIM " + str(console_dict)
3516 )
3517
3518 return None
tiernoae4a8d12016-07-08 12:30:39 +02003519
tierno1ec592d2020-06-16 15:29:47 +00003520 # ###### VIO Specific Changes #########
garciadeblasebd66722019-01-31 16:01:31 +00003521 def _generate_vlanID(self):
kate721d79b2017-06-24 04:21:38 -07003522 """
sousaedu80135b92021-02-17 15:05:18 +01003523 Method to get unused vlanID
kate721d79b2017-06-24 04:21:38 -07003524 Args:
3525 None
3526 Returns:
3527 vlanID
3528 """
tierno1ec592d2020-06-16 15:29:47 +00003529 # Get used VLAN IDs
kate721d79b2017-06-24 04:21:38 -07003530 usedVlanIDs = []
3531 networks = self.get_network_list()
sousaedu80135b92021-02-17 15:05:18 +01003532
kate721d79b2017-06-24 04:21:38 -07003533 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003534 if net.get("provider:segmentation_id"):
3535 usedVlanIDs.append(net.get("provider:segmentation_id"))
3536
kate721d79b2017-06-24 04:21:38 -07003537 used_vlanIDs = set(usedVlanIDs)
3538
tierno1ec592d2020-06-16 15:29:47 +00003539 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003540 for vlanID_range in self.config.get("dataplane_net_vlan_range"):
kate721d79b2017-06-24 04:21:38 -07003541 try:
sousaedu80135b92021-02-17 15:05:18 +01003542 start_vlanid, end_vlanid = map(
3543 int, vlanID_range.replace(" ", "").split("-")
3544 )
3545
tierno7d782ef2019-10-04 12:56:31 +00003546 for vlanID in range(start_vlanid, end_vlanid + 1):
kate721d79b2017-06-24 04:21:38 -07003547 if vlanID not in used_vlanIDs:
3548 return vlanID
3549 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003550 raise vimconn.VimConnException(
3551 "Exception {} occurred while generating VLAN ID.".format(exp)
3552 )
kate721d79b2017-06-24 04:21:38 -07003553 else:
tierno1ec592d2020-06-16 15:29:47 +00003554 raise vimconn.VimConnConflictException(
3555 "Unable to create the SRIOV VLAN network. All given Vlan IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003556 self.config.get("dataplane_net_vlan_range")
3557 )
3558 )
kate721d79b2017-06-24 04:21:38 -07003559
garciadeblasebd66722019-01-31 16:01:31 +00003560 def _generate_multisegment_vlanID(self):
3561 """
sousaedu80135b92021-02-17 15:05:18 +01003562 Method to get unused vlanID
3563 Args:
3564 None
3565 Returns:
3566 vlanID
garciadeblasebd66722019-01-31 16:01:31 +00003567 """
tierno6869ae72020-01-09 17:37:34 +00003568 # Get used VLAN IDs
garciadeblasebd66722019-01-31 16:01:31 +00003569 usedVlanIDs = []
3570 networks = self.get_network_list()
3571 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003572 if net.get("provider:network_type") == "vlan" and net.get(
3573 "provider:segmentation_id"
3574 ):
3575 usedVlanIDs.append(net.get("provider:segmentation_id"))
3576 elif net.get("segments"):
3577 for segment in net.get("segments"):
3578 if segment.get("provider:network_type") == "vlan" and segment.get(
3579 "provider:segmentation_id"
3580 ):
3581 usedVlanIDs.append(segment.get("provider:segmentation_id"))
3582
garciadeblasebd66722019-01-31 16:01:31 +00003583 used_vlanIDs = set(usedVlanIDs)
3584
tierno6869ae72020-01-09 17:37:34 +00003585 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003586 for vlanID_range in self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +00003587 try:
sousaedu80135b92021-02-17 15:05:18 +01003588 start_vlanid, end_vlanid = map(
3589 int, vlanID_range.replace(" ", "").split("-")
3590 )
3591
tierno7d782ef2019-10-04 12:56:31 +00003592 for vlanID in range(start_vlanid, end_vlanid + 1):
garciadeblasebd66722019-01-31 16:01:31 +00003593 if vlanID not in used_vlanIDs:
3594 return vlanID
3595 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003596 raise vimconn.VimConnException(
3597 "Exception {} occurred while generating VLAN ID.".format(exp)
3598 )
garciadeblasebd66722019-01-31 16:01:31 +00003599 else:
tierno1ec592d2020-06-16 15:29:47 +00003600 raise vimconn.VimConnConflictException(
3601 "Unable to create the VLAN segment. All VLAN IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003602 self.config.get("multisegment_vlan_range")
3603 )
3604 )
garciadeblasebd66722019-01-31 16:01:31 +00003605
3606 def _validate_vlan_ranges(self, input_vlan_range, text_vlan_range):
kate721d79b2017-06-24 04:21:38 -07003607 """
3608 Method to validate user given vlanID ranges
3609 Args: None
3610 Returns: None
3611 """
garciadeblasebd66722019-01-31 16:01:31 +00003612 for vlanID_range in input_vlan_range:
kate721d79b2017-06-24 04:21:38 -07003613 vlan_range = vlanID_range.replace(" ", "")
tierno1ec592d2020-06-16 15:29:47 +00003614 # validate format
sousaedu80135b92021-02-17 15:05:18 +01003615 vlanID_pattern = r"(\d)*-(\d)*$"
kate721d79b2017-06-24 04:21:38 -07003616 match_obj = re.match(vlanID_pattern, vlan_range)
3617 if not match_obj:
tierno1ec592d2020-06-16 15:29:47 +00003618 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003619 "Invalid VLAN range for {}: {}.You must provide "
3620 "'{}' in format [start_ID - end_ID].".format(
3621 text_vlan_range, vlanID_range, text_vlan_range
3622 )
3623 )
kate721d79b2017-06-24 04:21:38 -07003624
tierno1ec592d2020-06-16 15:29:47 +00003625 start_vlanid, end_vlanid = map(int, vlan_range.split("-"))
3626 if start_vlanid <= 0:
3627 raise vimconn.VimConnConflictException(
3628 "Invalid VLAN range for {}: {}. Start ID can not be zero. For VLAN "
sousaedu80135b92021-02-17 15:05:18 +01003629 "networks valid IDs are 1 to 4094 ".format(
3630 text_vlan_range, vlanID_range
3631 )
3632 )
3633
tierno1ec592d2020-06-16 15:29:47 +00003634 if end_vlanid > 4094:
3635 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003636 "Invalid VLAN range for {}: {}. End VLAN ID can not be "
3637 "greater than 4094. For VLAN networks valid IDs are 1 to 4094 ".format(
3638 text_vlan_range, vlanID_range
3639 )
3640 )
kate721d79b2017-06-24 04:21:38 -07003641
3642 if start_vlanid > end_vlanid:
tierno1ec592d2020-06-16 15:29:47 +00003643 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003644 "Invalid VLAN range for {}: {}. You must provide '{}'"
3645 " in format start_ID - end_ID and start_ID < end_ID ".format(
3646 text_vlan_range, vlanID_range, text_vlan_range
3647 )
3648 )
kate721d79b2017-06-24 04:21:38 -07003649
tierno7edb6752016-03-21 17:37:52 +01003650 def get_hosts_info(self):
tierno1ec592d2020-06-16 15:29:47 +00003651 """Get the information of deployed hosts
3652 Returns the hosts content"""
garciadeblas6a0147f2024-08-19 08:02:04 +00003653 self.logger.debug("osconnector: Getting Host info from VIM")
sousaedu80135b92021-02-17 15:05:18 +01003654
tierno7edb6752016-03-21 17:37:52 +01003655 try:
tierno1ec592d2020-06-16 15:29:47 +00003656 h_list = []
tierno7edb6752016-03-21 17:37:52 +01003657 self._reload_connection()
3658 hypervisors = self.nova.hypervisors.list()
sousaedu80135b92021-02-17 15:05:18 +01003659
tierno7edb6752016-03-21 17:37:52 +01003660 for hype in hypervisors:
tierno1ec592d2020-06-16 15:29:47 +00003661 h_list.append(hype.to_dict())
sousaedu80135b92021-02-17 15:05:18 +01003662
tierno1ec592d2020-06-16 15:29:47 +00003663 return 1, {"hosts": h_list}
tierno7edb6752016-03-21 17:37:52 +01003664 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003665 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003666 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003667 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003668 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003669 error_text = (
3670 type(e).__name__
3671 + ": "
3672 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3673 )
3674
tierno1ec592d2020-06-16 15:29:47 +00003675 # TODO insert exception vimconn.HTTP_Unauthorized
3676 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003677 self.logger.debug("get_hosts_info " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003678
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003679 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003680
3681 def get_hosts(self, vim_tenant):
tierno1ec592d2020-06-16 15:29:47 +00003682 """Get the hosts and deployed instances
3683 Returns the hosts content"""
tierno7edb6752016-03-21 17:37:52 +01003684 r, hype_dict = self.get_hosts_info()
sousaedu80135b92021-02-17 15:05:18 +01003685
tierno1ec592d2020-06-16 15:29:47 +00003686 if r < 0:
tierno7edb6752016-03-21 17:37:52 +01003687 return r, hype_dict
sousaedu80135b92021-02-17 15:05:18 +01003688
tierno7edb6752016-03-21 17:37:52 +01003689 hypervisors = hype_dict["hosts"]
sousaedu80135b92021-02-17 15:05:18 +01003690
tierno7edb6752016-03-21 17:37:52 +01003691 try:
3692 servers = self.nova.servers.list()
3693 for hype in hypervisors:
3694 for server in servers:
sousaedu80135b92021-02-17 15:05:18 +01003695 if (
3696 server.to_dict()["OS-EXT-SRV-ATTR:hypervisor_hostname"]
3697 == hype["hypervisor_hostname"]
3698 ):
3699 if "vm" in hype:
3700 hype["vm"].append(server.id)
tierno7edb6752016-03-21 17:37:52 +01003701 else:
sousaedu80135b92021-02-17 15:05:18 +01003702 hype["vm"] = [server.id]
3703
tierno7edb6752016-03-21 17:37:52 +01003704 return 1, hype_dict
3705 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003706 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003707 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003708 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003709 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003710 error_text = (
3711 type(e).__name__
3712 + ": "
3713 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3714 )
3715
tierno1ec592d2020-06-16 15:29:47 +00003716 # TODO insert exception vimconn.HTTP_Unauthorized
3717 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003718 self.logger.debug("get_hosts " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003719
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003720 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003721
Lovejeet Singhdf486552023-05-09 22:41:09 +05303722 def new_classification(self, name, ctype, definition):
3723 self.logger.debug(
3724 "Adding a new (Traffic) Classification to VIM, named %s", name
3725 )
3726
3727 try:
3728 new_class = None
3729 self._reload_connection()
3730
3731 if ctype not in supportedClassificationTypes:
3732 raise vimconn.VimConnNotSupportedException(
3733 "OpenStack VIM connector does not support provided "
3734 "Classification Type {}, supported ones are: {}".format(
3735 ctype, supportedClassificationTypes
3736 )
3737 )
3738
3739 if not self._validate_classification(ctype, definition):
3740 raise vimconn.VimConnException(
3741 "Incorrect Classification definition for the type specified."
3742 )
3743
3744 classification_dict = definition
3745 classification_dict["name"] = name
3746
3747 self.logger.info(
3748 "Adding a new (Traffic) Classification to VIM, named {} and {}.".format(
3749 name, classification_dict
3750 )
3751 )
3752 new_class = self.neutron.create_sfc_flow_classifier(
3753 {"flow_classifier": classification_dict}
3754 )
3755
3756 return new_class["flow_classifier"]["id"]
3757 except (
3758 neExceptions.ConnectionFailed,
3759 ksExceptions.ClientException,
3760 neExceptions.NeutronException,
3761 ConnectionError,
3762 ) as e:
3763 self.logger.error("Creation of Classification failed.")
3764 self._format_exception(e)
3765
3766 def get_classification(self, class_id):
3767 self.logger.debug(" Getting Classification %s from VIM", class_id)
3768 filter_dict = {"id": class_id}
3769 class_list = self.get_classification_list(filter_dict)
3770
3771 if len(class_list) == 0:
3772 raise vimconn.VimConnNotFoundException(
3773 "Classification '{}' not found".format(class_id)
3774 )
3775 elif len(class_list) > 1:
3776 raise vimconn.VimConnConflictException(
3777 "Found more than one Classification with this criteria"
3778 )
3779
3780 classification = class_list[0]
3781
3782 return classification
3783
3784 def get_classification_list(self, filter_dict={}):
3785 self.logger.debug(
3786 "Getting Classifications from VIM filter: '%s'", str(filter_dict)
3787 )
3788
3789 try:
3790 filter_dict_os = filter_dict.copy()
3791 self._reload_connection()
3792
3793 if self.api_version3 and "tenant_id" in filter_dict_os:
3794 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3795
3796 classification_dict = self.neutron.list_sfc_flow_classifiers(
3797 **filter_dict_os
3798 )
3799 classification_list = classification_dict["flow_classifiers"]
3800 self.__classification_os2mano(classification_list)
3801
3802 return classification_list
3803 except (
3804 neExceptions.ConnectionFailed,
3805 ksExceptions.ClientException,
3806 neExceptions.NeutronException,
3807 ConnectionError,
3808 ) as e:
3809 self._format_exception(e)
3810
3811 def delete_classification(self, class_id):
3812 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
3813
3814 try:
3815 self._reload_connection()
3816 self.neutron.delete_sfc_flow_classifier(class_id)
3817
3818 return class_id
3819 except (
3820 neExceptions.ConnectionFailed,
3821 neExceptions.NeutronException,
3822 ksExceptions.ClientException,
3823 neExceptions.NeutronException,
3824 ConnectionError,
3825 ) as e:
3826 self._format_exception(e)
3827
3828 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
3829 self.logger.debug(
3830 "Adding a new Service Function Instance to VIM, named '%s'", name
3831 )
3832
3833 try:
3834 new_sfi = None
3835 self._reload_connection()
3836 correlation = None
3837
3838 if sfc_encap:
3839 correlation = "nsh"
3840
3841 if len(ingress_ports) != 1:
3842 raise vimconn.VimConnNotSupportedException(
3843 "OpenStack VIM connector can only have 1 ingress port per SFI"
3844 )
3845
3846 if len(egress_ports) != 1:
3847 raise vimconn.VimConnNotSupportedException(
3848 "OpenStack VIM connector can only have 1 egress port per SFI"
3849 )
3850
3851 sfi_dict = {
3852 "name": name,
3853 "ingress": ingress_ports[0],
3854 "egress": egress_ports[0],
3855 "service_function_parameters": {"correlation": correlation},
3856 }
3857 self.logger.info("Adding a new SFI to VIM, {}.".format(sfi_dict))
3858 new_sfi = self.neutron.create_sfc_port_pair({"port_pair": sfi_dict})
3859
3860 return new_sfi["port_pair"]["id"]
3861 except (
3862 neExceptions.ConnectionFailed,
3863 ksExceptions.ClientException,
3864 neExceptions.NeutronException,
3865 ConnectionError,
3866 ) as e:
3867 if new_sfi:
3868 try:
3869 self.neutron.delete_sfc_port_pair(new_sfi["port_pair"]["id"])
3870 except Exception:
3871 self.logger.error(
3872 "Creation of Service Function Instance failed, with "
3873 "subsequent deletion failure as well."
3874 )
3875
3876 self._format_exception(e)
3877
3878 def get_sfi(self, sfi_id):
3879 self.logger.debug("Getting Service Function Instance %s from VIM", sfi_id)
3880 filter_dict = {"id": sfi_id}
3881 sfi_list = self.get_sfi_list(filter_dict)
3882
3883 if len(sfi_list) == 0:
3884 raise vimconn.VimConnNotFoundException(
3885 "Service Function Instance '{}' not found".format(sfi_id)
3886 )
3887 elif len(sfi_list) > 1:
3888 raise vimconn.VimConnConflictException(
3889 "Found more than one Service Function Instance with this criteria"
3890 )
3891
3892 sfi = sfi_list[0]
3893
3894 return sfi
3895
3896 def get_sfi_list(self, filter_dict={}):
3897 self.logger.debug(
3898 "Getting Service Function Instances from VIM filter: '%s'", str(filter_dict)
3899 )
3900
3901 try:
3902 self._reload_connection()
3903 filter_dict_os = filter_dict.copy()
3904
3905 if self.api_version3 and "tenant_id" in filter_dict_os:
3906 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3907
3908 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
3909 sfi_list = sfi_dict["port_pairs"]
3910 self.__sfi_os2mano(sfi_list)
3911
3912 return sfi_list
3913 except (
3914 neExceptions.ConnectionFailed,
3915 ksExceptions.ClientException,
3916 neExceptions.NeutronException,
3917 ConnectionError,
3918 ) as e:
3919 self._format_exception(e)
3920
3921 def delete_sfi(self, sfi_id):
3922 self.logger.debug("Deleting Service Function Instance '%s' from VIM", sfi_id)
3923
3924 try:
3925 self._reload_connection()
3926 self.neutron.delete_sfc_port_pair(sfi_id)
3927
3928 return sfi_id
3929 except (
3930 neExceptions.ConnectionFailed,
3931 neExceptions.NeutronException,
3932 ksExceptions.ClientException,
3933 neExceptions.NeutronException,
3934 ConnectionError,
3935 ) as e:
3936 self._format_exception(e)
3937
3938 def new_sf(self, name, sfis, sfc_encap=True):
3939 self.logger.debug("Adding a new Service Function to VIM, named '%s'", name)
3940
3941 new_sf = None
3942
3943 try:
3944 self._reload_connection()
3945
3946 for instance in sfis:
3947 sfi = self.get_sfi(instance)
3948
3949 if sfi.get("sfc_encap") != sfc_encap:
3950 raise vimconn.VimConnNotSupportedException(
3951 "OpenStack VIM connector requires all SFIs of the "
3952 "same SF to share the same SFC Encapsulation"
3953 )
3954
3955 sf_dict = {"name": name, "port_pairs": sfis}
3956
3957 self.logger.info("Adding a new SF to VIM, {}.".format(sf_dict))
3958 new_sf = self.neutron.create_sfc_port_pair_group(
3959 {"port_pair_group": sf_dict}
3960 )
3961
3962 return new_sf["port_pair_group"]["id"]
3963 except (
3964 neExceptions.ConnectionFailed,
3965 ksExceptions.ClientException,
3966 neExceptions.NeutronException,
3967 ConnectionError,
3968 ) as e:
3969 if new_sf:
3970 try:
3971 new_sf_id = new_sf.get("port_pair_group").get("id")
3972 self.neutron.delete_sfc_port_pair_group(new_sf_id)
3973 except Exception:
3974 self.logger.error(
3975 "Creation of Service Function failed, with "
3976 "subsequent deletion failure as well."
3977 )
3978
3979 self._format_exception(e)
3980
3981 def get_sf(self, sf_id):
3982 self.logger.debug("Getting Service Function %s from VIM", sf_id)
3983 filter_dict = {"id": sf_id}
3984 sf_list = self.get_sf_list(filter_dict)
3985
3986 if len(sf_list) == 0:
3987 raise vimconn.VimConnNotFoundException(
3988 "Service Function '{}' not found".format(sf_id)
3989 )
3990 elif len(sf_list) > 1:
3991 raise vimconn.VimConnConflictException(
3992 "Found more than one Service Function with this criteria"
3993 )
3994
3995 sf = sf_list[0]
3996
3997 return sf
3998
3999 def get_sf_list(self, filter_dict={}):
4000 self.logger.debug(
4001 "Getting Service Function from VIM filter: '%s'", str(filter_dict)
4002 )
4003
4004 try:
4005 self._reload_connection()
4006 filter_dict_os = filter_dict.copy()
4007
4008 if self.api_version3 and "tenant_id" in filter_dict_os:
4009 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
4010
4011 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
4012 sf_list = sf_dict["port_pair_groups"]
4013 self.__sf_os2mano(sf_list)
4014
4015 return sf_list
4016 except (
4017 neExceptions.ConnectionFailed,
4018 ksExceptions.ClientException,
4019 neExceptions.NeutronException,
4020 ConnectionError,
4021 ) as e:
4022 self._format_exception(e)
4023
4024 def delete_sf(self, sf_id):
4025 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
4026
4027 try:
4028 self._reload_connection()
4029 self.neutron.delete_sfc_port_pair_group(sf_id)
4030
4031 return sf_id
4032 except (
4033 neExceptions.ConnectionFailed,
4034 neExceptions.NeutronException,
4035 ksExceptions.ClientException,
4036 neExceptions.NeutronException,
4037 ConnectionError,
4038 ) as e:
4039 self._format_exception(e)
4040
4041 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
4042 self.logger.debug("Adding a new Service Function Path to VIM, named '%s'", name)
4043
4044 new_sfp = None
4045
4046 try:
4047 self._reload_connection()
4048 # In networking-sfc the MPLS encapsulation is legacy
4049 # should be used when no full SFC Encapsulation is intended
4050 correlation = "mpls"
4051
4052 if sfc_encap:
4053 correlation = "nsh"
4054
4055 sfp_dict = {
4056 "name": name,
4057 "flow_classifiers": classifications,
4058 "port_pair_groups": sfs,
4059 "chain_parameters": {"correlation": correlation},
4060 }
4061
4062 if spi:
4063 sfp_dict["chain_id"] = spi
4064
4065 self.logger.info("Adding a new SFP to VIM, {}.".format(sfp_dict))
4066 new_sfp = self.neutron.create_sfc_port_chain({"port_chain": sfp_dict})
4067
4068 return new_sfp["port_chain"]["id"]
4069 except (
4070 neExceptions.ConnectionFailed,
4071 ksExceptions.ClientException,
4072 neExceptions.NeutronException,
4073 ConnectionError,
4074 ) as e:
4075 if new_sfp:
4076 try:
4077 new_sfp_id = new_sfp.get("port_chain").get("id")
4078 self.neutron.delete_sfc_port_chain(new_sfp_id)
4079 except Exception:
4080 self.logger.error(
4081 "Creation of Service Function Path failed, with "
4082 "subsequent deletion failure as well."
4083 )
4084
4085 self._format_exception(e)
4086
4087 def get_sfp(self, sfp_id):
4088 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
4089
4090 filter_dict = {"id": sfp_id}
4091 sfp_list = self.get_sfp_list(filter_dict)
4092
4093 if len(sfp_list) == 0:
4094 raise vimconn.VimConnNotFoundException(
4095 "Service Function Path '{}' not found".format(sfp_id)
4096 )
4097 elif len(sfp_list) > 1:
4098 raise vimconn.VimConnConflictException(
4099 "Found more than one Service Function Path with this criteria"
4100 )
4101
4102 sfp = sfp_list[0]
4103
4104 return sfp
4105
4106 def get_sfp_list(self, filter_dict={}):
4107 self.logger.debug(
4108 "Getting Service Function Paths from VIM filter: '%s'", str(filter_dict)
4109 )
4110
4111 try:
4112 self._reload_connection()
4113 filter_dict_os = filter_dict.copy()
4114
4115 if self.api_version3 and "tenant_id" in filter_dict_os:
4116 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
4117
4118 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
4119 sfp_list = sfp_dict["port_chains"]
4120 self.__sfp_os2mano(sfp_list)
4121
4122 return sfp_list
4123 except (
4124 neExceptions.ConnectionFailed,
4125 ksExceptions.ClientException,
4126 neExceptions.NeutronException,
4127 ConnectionError,
4128 ) as e:
4129 self._format_exception(e)
4130
4131 def delete_sfp(self, sfp_id):
4132 self.logger.debug("Deleting Service Function Path '%s' from VIM", sfp_id)
4133
4134 try:
4135 self._reload_connection()
4136 self.neutron.delete_sfc_port_chain(sfp_id)
4137
4138 return sfp_id
4139 except (
4140 neExceptions.ConnectionFailed,
4141 neExceptions.NeutronException,
4142 ksExceptions.ClientException,
4143 neExceptions.NeutronException,
4144 ConnectionError,
4145 ) as e:
4146 self._format_exception(e)
4147
4148 def refresh_sfps_status(self, sfp_list):
4149 """Get the status of the service function path
4150 Params: the list of sfp identifiers
4151 Returns a dictionary with:
4152 vm_id: #VIM id of this service function path
4153 status: #Mandatory. Text with one of:
4154 # DELETED (not found at vim)
4155 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4156 # OTHER (Vim reported other status not understood)
4157 # ERROR (VIM indicates an ERROR status)
4158 # ACTIVE,
4159 # CREATING (on building process)
4160 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4161 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)F
4162 """
4163 sfp_dict = {}
4164 self.logger.debug(
4165 "refresh_sfps status: Getting tenant SFP information from VIM"
4166 )
4167
4168 for sfp_id in sfp_list:
4169 sfp = {}
4170
4171 try:
4172 sfp_vim = self.get_sfp(sfp_id)
4173
4174 if sfp_vim["spi"]:
4175 sfp["status"] = vmStatus2manoFormat["ACTIVE"]
4176 else:
4177 sfp["status"] = "OTHER"
4178 sfp["error_msg"] = "VIM status reported " + sfp["status"]
4179
4180 sfp["vim_info"] = self.serialize(sfp_vim)
4181
4182 if sfp_vim.get("fault"):
4183 sfp["error_msg"] = str(sfp_vim["fault"])
4184 except vimconn.VimConnNotFoundException as e:
4185 self.logger.error("Exception getting sfp status: %s", str(e))
4186 sfp["status"] = "DELETED"
4187 sfp["error_msg"] = str(e)
4188 except vimconn.VimConnException as e:
4189 self.logger.error("Exception getting sfp status: %s", str(e))
4190 sfp["status"] = "VIM_ERROR"
4191 sfp["error_msg"] = str(e)
4192
4193 sfp_dict[sfp_id] = sfp
4194
4195 return sfp_dict
4196
4197 def refresh_sfis_status(self, sfi_list):
4198 """Get the status of the service function instances
4199 Params: the list of sfi identifiers
4200 Returns a dictionary with:
4201 vm_id: #VIM id of this service function instance
4202 status: #Mandatory. Text with one of:
4203 # DELETED (not found at vim)
4204 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4205 # OTHER (Vim reported other status not understood)
4206 # ERROR (VIM indicates an ERROR status)
4207 # ACTIVE,
4208 # CREATING (on building process)
4209 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4210 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4211 """
4212 sfi_dict = {}
4213 self.logger.debug(
4214 "refresh_sfis status: Getting tenant sfi information from VIM"
4215 )
4216
4217 for sfi_id in sfi_list:
4218 sfi = {}
4219
4220 try:
4221 sfi_vim = self.get_sfi(sfi_id)
4222
4223 if sfi_vim:
4224 sfi["status"] = vmStatus2manoFormat["ACTIVE"]
4225 else:
4226 sfi["status"] = "OTHER"
4227 sfi["error_msg"] = "VIM status reported " + sfi["status"]
4228
4229 sfi["vim_info"] = self.serialize(sfi_vim)
4230
4231 if sfi_vim.get("fault"):
4232 sfi["error_msg"] = str(sfi_vim["fault"])
4233 except vimconn.VimConnNotFoundException as e:
4234 self.logger.error("Exception getting sfi status: %s", str(e))
4235 sfi["status"] = "DELETED"
4236 sfi["error_msg"] = str(e)
4237 except vimconn.VimConnException as e:
4238 self.logger.error("Exception getting sfi status: %s", str(e))
4239 sfi["status"] = "VIM_ERROR"
4240 sfi["error_msg"] = str(e)
4241
4242 sfi_dict[sfi_id] = sfi
4243
4244 return sfi_dict
4245
4246 def refresh_sfs_status(self, sf_list):
4247 """Get the status of the service functions
4248 Params: the list of sf identifiers
4249 Returns a dictionary with:
4250 vm_id: #VIM id of this service function
4251 status: #Mandatory. Text with one of:
4252 # DELETED (not found at vim)
4253 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4254 # OTHER (Vim reported other status not understood)
4255 # ERROR (VIM indicates an ERROR status)
4256 # ACTIVE,
4257 # CREATING (on building process)
4258 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4259 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4260 """
4261 sf_dict = {}
4262 self.logger.debug("refresh_sfs status: Getting tenant sf information from VIM")
4263
4264 for sf_id in sf_list:
4265 sf = {}
4266
4267 try:
4268 sf_vim = self.get_sf(sf_id)
4269
4270 if sf_vim:
4271 sf["status"] = vmStatus2manoFormat["ACTIVE"]
4272 else:
4273 sf["status"] = "OTHER"
4274 sf["error_msg"] = "VIM status reported " + sf_vim["status"]
4275
4276 sf["vim_info"] = self.serialize(sf_vim)
4277
4278 if sf_vim.get("fault"):
4279 sf["error_msg"] = str(sf_vim["fault"])
4280 except vimconn.VimConnNotFoundException as e:
4281 self.logger.error("Exception getting sf status: %s", str(e))
4282 sf["status"] = "DELETED"
4283 sf["error_msg"] = str(e)
4284 except vimconn.VimConnException as e:
4285 self.logger.error("Exception getting sf status: %s", str(e))
4286 sf["status"] = "VIM_ERROR"
4287 sf["error_msg"] = str(e)
4288
4289 sf_dict[sf_id] = sf
4290
4291 return sf_dict
4292
4293 def refresh_classifications_status(self, classification_list):
4294 """Get the status of the classifications
4295 Params: the list of classification identifiers
4296 Returns a dictionary with:
4297 vm_id: #VIM id of this classifier
4298 status: #Mandatory. Text with one of:
4299 # DELETED (not found at vim)
4300 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4301 # OTHER (Vim reported other status not understood)
4302 # ERROR (VIM indicates an ERROR status)
4303 # ACTIVE,
4304 # CREATING (on building process)
4305 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4306 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4307 """
4308 classification_dict = {}
4309 self.logger.debug(
4310 "refresh_classifications status: Getting tenant classification information from VIM"
4311 )
4312
4313 for classification_id in classification_list:
4314 classification = {}
4315
4316 try:
4317 classification_vim = self.get_classification(classification_id)
4318
4319 if classification_vim:
4320 classification["status"] = vmStatus2manoFormat["ACTIVE"]
4321 else:
4322 classification["status"] = "OTHER"
4323 classification["error_msg"] = (
4324 "VIM status reported " + classification["status"]
4325 )
4326
4327 classification["vim_info"] = self.serialize(classification_vim)
4328
4329 if classification_vim.get("fault"):
4330 classification["error_msg"] = str(classification_vim["fault"])
4331 except vimconn.VimConnNotFoundException as e:
4332 self.logger.error("Exception getting classification status: %s", str(e))
4333 classification["status"] = "DELETED"
4334 classification["error_msg"] = str(e)
4335 except vimconn.VimConnException as e:
4336 self.logger.error("Exception getting classification status: %s", str(e))
4337 classification["status"] = "VIM_ERROR"
4338 classification["error_msg"] = str(e)
4339
4340 classification_dict[classification_id] = classification
4341
4342 return classification_dict
4343
gatici335a06a2023-07-26 00:34:04 +03004344 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004345 def new_affinity_group(self, affinity_group_data):
4346 """Adds a server group to VIM
4347 affinity_group_data contains a dictionary with information, keys:
4348 name: name in VIM for the server group
4349 type: affinity or anti-affinity
4350 scope: Only nfvi-node allowed
4351 Returns the server group identifier"""
4352 self.logger.debug("Adding Server Group '%s'", str(affinity_group_data))
gatici335a06a2023-07-26 00:34:04 +03004353 name = affinity_group_data["name"]
4354 policy = affinity_group_data["type"]
4355 self._reload_connection()
4356 new_server_group = self.nova.server_groups.create(name, policy)
4357 return new_server_group.id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004358
gatici335a06a2023-07-26 00:34:04 +03004359 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004360 def get_affinity_group(self, affinity_group_id):
4361 """Obtain server group details from the VIM. Returns the server group detais as a dict"""
4362 self.logger.debug("Getting flavor '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004363 self._reload_connection()
4364 server_group = self.nova.server_groups.find(id=affinity_group_id)
4365 return server_group.to_dict()
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004366
gatici335a06a2023-07-26 00:34:04 +03004367 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004368 def delete_affinity_group(self, affinity_group_id):
4369 """Deletes a server group from the VIM. Returns the old affinity_group_id"""
4370 self.logger.debug("Getting server group '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004371 self._reload_connection()
4372 self.nova.server_groups.delete(affinity_group_id)
4373 return affinity_group_id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004374
gatici335a06a2023-07-26 00:34:04 +03004375 @catch_any_exception
Patricia Reinoso17852162023-06-15 07:33:04 +00004376 def get_vdu_state(self, vm_id, host_is_required=False) -> list:
4377 """Getting the state of a VDU.
4378 Args:
4379 vm_id (str): ID of an instance
4380 host_is_required (Boolean): If the VIM account is non-admin, host info does not appear in server_dict
4381 and if this is set to True, it raises KeyError.
4382 Returns:
4383 vdu_data (list): VDU details including state, flavor, host_info, AZ
elumalai8658c2c2022-04-28 19:09:31 +05304384 """
4385 self.logger.debug("Getting the status of VM")
4386 self.logger.debug("VIM VM ID %s", vm_id)
gatici335a06a2023-07-26 00:34:04 +03004387 self._reload_connection()
4388 server_dict = self._find_nova_server(vm_id)
4389 srv_attr = "OS-EXT-SRV-ATTR:host"
4390 host_info = (
4391 server_dict[srv_attr] if host_is_required else server_dict.get(srv_attr)
4392 )
4393 vdu_data = [
4394 server_dict["status"],
4395 server_dict["flavor"]["id"],
4396 host_info,
4397 server_dict["OS-EXT-AZ:availability_zone"],
4398 ]
4399 self.logger.debug("vdu_data %s", vdu_data)
4400 return vdu_data
elumalai8658c2c2022-04-28 19:09:31 +05304401
4402 def check_compute_availability(self, host, server_flavor_details):
4403 self._reload_connection()
4404 hypervisor_search = self.nova.hypervisors.search(
4405 hypervisor_match=host, servers=True
4406 )
4407 for hypervisor in hypervisor_search:
4408 hypervisor_id = hypervisor.to_dict()["id"]
4409 hypervisor_details = self.nova.hypervisors.get(hypervisor=hypervisor_id)
4410 hypervisor_dict = hypervisor_details.to_dict()
4411 hypervisor_temp = json.dumps(hypervisor_dict)
4412 hypervisor_json = json.loads(hypervisor_temp)
4413 resources_available = [
4414 hypervisor_json["free_ram_mb"],
4415 hypervisor_json["disk_available_least"],
4416 hypervisor_json["vcpus"] - hypervisor_json["vcpus_used"],
4417 ]
4418 compute_available = all(
4419 x > y for x, y in zip(resources_available, server_flavor_details)
4420 )
4421 if compute_available:
4422 return host
4423
4424 def check_availability_zone(
4425 self, old_az, server_flavor_details, old_host, host=None
4426 ):
4427 self._reload_connection()
4428 az_check = {"zone_check": False, "compute_availability": None}
4429 aggregates_list = self.nova.aggregates.list()
4430 for aggregate in aggregates_list:
4431 aggregate_details = aggregate.to_dict()
4432 aggregate_temp = json.dumps(aggregate_details)
4433 aggregate_json = json.loads(aggregate_temp)
4434 if aggregate_json["availability_zone"] == old_az:
4435 hosts_list = aggregate_json["hosts"]
4436 if host is not None:
4437 if host in hosts_list:
4438 az_check["zone_check"] = True
4439 available_compute_id = self.check_compute_availability(
4440 host, server_flavor_details
4441 )
4442 if available_compute_id is not None:
4443 az_check["compute_availability"] = available_compute_id
4444 else:
4445 for check_host in hosts_list:
4446 if check_host != old_host:
4447 available_compute_id = self.check_compute_availability(
4448 check_host, server_flavor_details
4449 )
4450 if available_compute_id is not None:
4451 az_check["zone_check"] = True
4452 az_check["compute_availability"] = available_compute_id
4453 break
4454 else:
4455 az_check["zone_check"] = True
4456 return az_check
4457
gatici335a06a2023-07-26 00:34:04 +03004458 @catch_any_exception
elumalai8658c2c2022-04-28 19:09:31 +05304459 def migrate_instance(self, vm_id, compute_host=None):
4460 """
4461 Migrate a vdu
4462 param:
4463 vm_id: ID of an instance
4464 compute_host: Host to migrate the vdu to
4465 """
4466 self._reload_connection()
4467 vm_state = False
Patricia Reinoso17852162023-06-15 07:33:04 +00004468 instance_state = self.get_vdu_state(vm_id, host_is_required=True)
elumalai8658c2c2022-04-28 19:09:31 +05304469 server_flavor_id = instance_state[1]
4470 server_hypervisor_name = instance_state[2]
4471 server_availability_zone = instance_state[3]
gatici335a06a2023-07-26 00:34:04 +03004472 server_flavor = self.nova.flavors.find(id=server_flavor_id).to_dict()
4473 server_flavor_details = [
4474 server_flavor["ram"],
4475 server_flavor["disk"],
4476 server_flavor["vcpus"],
4477 ]
4478 if compute_host == server_hypervisor_name:
4479 raise vimconn.VimConnException(
4480 "Unable to migrate instance '{}' to the same host '{}'".format(
4481 vm_id, compute_host
4482 ),
4483 http_code=vimconn.HTTP_Bad_Request,
elumalai8658c2c2022-04-28 19:09:31 +05304484 )
gatici335a06a2023-07-26 00:34:04 +03004485 az_status = self.check_availability_zone(
4486 server_availability_zone,
4487 server_flavor_details,
4488 server_hypervisor_name,
4489 compute_host,
4490 )
4491 availability_zone_check = az_status["zone_check"]
4492 available_compute_id = az_status.get("compute_availability")
elumalai8658c2c2022-04-28 19:09:31 +05304493
gatici335a06a2023-07-26 00:34:04 +03004494 if availability_zone_check is False:
4495 raise vimconn.VimConnException(
4496 "Unable to migrate instance '{}' to a different availability zone".format(
4497 vm_id
4498 ),
4499 http_code=vimconn.HTTP_Bad_Request,
4500 )
4501 if available_compute_id is not None:
4502 # disk_over_commit parameter for live_migrate method is not valid for Nova API version >= 2.25
4503 self.nova.servers.live_migrate(
4504 server=vm_id,
4505 host=available_compute_id,
4506 block_migration=True,
4507 )
4508 state = "MIGRATING"
4509 changed_compute_host = ""
4510 if state == "MIGRATING":
4511 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
4512 changed_compute_host = self.get_vdu_state(vm_id, host_is_required=True)[
4513 2
4514 ]
4515 if vm_state and changed_compute_host == available_compute_id:
4516 self.logger.debug(
4517 "Instance '{}' migrated to the new compute host '{}'".format(
4518 vm_id, changed_compute_host
elumalai8658c2c2022-04-28 19:09:31 +05304519 )
gatici335a06a2023-07-26 00:34:04 +03004520 )
4521 return state, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304522 else:
4523 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03004524 "Migration Failed. Instance '{}' not moved to the new host {}".format(
4525 vm_id, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304526 ),
4527 http_code=vimconn.HTTP_Bad_Request,
4528 )
gatici335a06a2023-07-26 00:34:04 +03004529 else:
4530 raise vimconn.VimConnException(
4531 "Compute '{}' not available or does not have enough resources to migrate the instance".format(
4532 available_compute_id
4533 ),
4534 http_code=vimconn.HTTP_Bad_Request,
4535 )
sritharan29a4c1a2022-05-05 12:15:04 +00004536
gatici335a06a2023-07-26 00:34:04 +03004537 @catch_any_exception
sritharan29a4c1a2022-05-05 12:15:04 +00004538 def resize_instance(self, vm_id, new_flavor_id):
4539 """
4540 For resizing the vm based on the given
4541 flavor details
4542 param:
4543 vm_id : ID of an instance
4544 new_flavor_id : Flavor id to be resized
4545 Return the status of a resized instance
4546 """
4547 self._reload_connection()
4548 self.logger.debug("resize the flavor of an instance")
4549 instance_status, old_flavor_id, compute_host, az = self.get_vdu_state(vm_id)
4550 old_flavor_disk = self.nova.flavors.find(id=old_flavor_id).to_dict()["disk"]
4551 new_flavor_disk = self.nova.flavors.find(id=new_flavor_id).to_dict()["disk"]
gatici335a06a2023-07-26 00:34:04 +03004552 if instance_status == "ACTIVE" or instance_status == "SHUTOFF":
4553 if old_flavor_disk > new_flavor_disk:
sritharan29a4c1a2022-05-05 12:15:04 +00004554 raise nvExceptions.BadRequest(
gatici335a06a2023-07-26 00:34:04 +03004555 400,
4556 message="Server disk resize failed. Resize to lower disk flavor is not allowed",
sritharan29a4c1a2022-05-05 12:15:04 +00004557 )
gatici335a06a2023-07-26 00:34:04 +03004558 else:
4559 self.nova.servers.resize(server=vm_id, flavor=new_flavor_id)
4560 vm_state = self.__wait_for_vm(vm_id, "VERIFY_RESIZE")
4561 if vm_state:
deepika.e970f5552024-06-03 14:12:50 +05304562 instance_resized_status = self.confirm_resize(
4563 vm_id, instance_status
4564 )
gatici335a06a2023-07-26 00:34:04 +03004565 return instance_resized_status
4566 else:
4567 raise nvExceptions.BadRequest(
4568 409,
4569 message="Cannot 'resize' vm_state is in ERROR",
4570 )
4571
4572 else:
4573 self.logger.debug("ERROR : Instance is not in ACTIVE or SHUTOFF state")
4574 raise nvExceptions.BadRequest(
4575 409,
4576 message="Cannot 'resize' instance while it is in vm_state resized",
4577 )
sritharan29a4c1a2022-05-05 12:15:04 +00004578
deepika.e970f5552024-06-03 14:12:50 +05304579 def confirm_resize(self, vm_id, instance_state):
sritharan29a4c1a2022-05-05 12:15:04 +00004580 """
4581 Confirm the resize of an instance
4582 param:
4583 vm_id: ID of an instance
4584 """
4585 self._reload_connection()
4586 self.nova.servers.confirm_resize(server=vm_id)
4587 if self.get_vdu_state(vm_id)[0] == "VERIFY_RESIZE":
deepika.e970f5552024-06-03 14:12:50 +05304588 self.__wait_for_vm(vm_id, instance_state)
sritharan29a4c1a2022-05-05 12:15:04 +00004589 instance_status = self.get_vdu_state(vm_id)[0]
4590 return instance_status
Gulsum Aticid586d892023-02-13 18:40:03 +03004591
4592 def get_monitoring_data(self):
4593 try:
4594 self.logger.debug("Getting servers and ports data from Openstack VIMs.")
4595 self._reload_connection()
4596 all_servers = self.nova.servers.list(detailed=True)
vegallc53829d2023-06-01 00:47:44 -05004597 try:
4598 for server in all_servers:
Luis Vegad6577d82023-07-26 20:49:12 +00004599 if server.flavor.get("original_name"):
4600 server.flavor["id"] = self.nova.flavors.find(
4601 name=server.flavor["original_name"]
4602 ).id
vegallc53829d2023-06-01 00:47:44 -05004603 except nClient.exceptions.NotFound as e:
4604 self.logger.warning(str(e.message))
Gulsum Aticid586d892023-02-13 18:40:03 +03004605 all_ports = self.neutron.list_ports()
4606 return all_servers, all_ports
gatici335a06a2023-07-26 00:34:04 +03004607 except Exception as e:
Gulsum Aticid586d892023-02-13 18:40:03 +03004608 raise vimconn.VimConnException(
4609 f"Exception in monitoring while getting VMs and ports status: {str(e)}"
4610 )