blob: 501c6924e17dff76e3c3316ea03e0624740dbd76 [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
kayal2001342ee282024-11-28 11:56:49 +0530640 def _get_ids_from_name(self, security_group_name=None):
tiernoa05b65a2019-02-01 12:30:27 +0000641 """
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
kayal2001342ee282024-11-28 11:56:49 +0530655 neutron_sg_list = self.neutron.list_security_groups(
656 tenant_id=self.my_tenant_id
657 )["security_groups"]
658
sousaedu80135b92021-02-17 15:05:18 +0100659 if self.config.get("security_groups") and not self.security_groups_id:
tiernoa05b65a2019-02-01 12:30:27 +0000660 # convert from name to id
kayal2001342ee282024-11-28 11:56:49 +0530661 # neutron_sg_list = self.neutron.list_security_groups(
662 # tenant_id=self.my_tenant_id
663 # )["security_groups"]
tiernoa05b65a2019-02-01 12:30:27 +0000664
665 self.security_groups_id = []
sousaedu80135b92021-02-17 15:05:18 +0100666 for sg in self.config.get("security_groups"):
tiernoa05b65a2019-02-01 12:30:27 +0000667 for neutron_sg in neutron_sg_list:
668 if sg in (neutron_sg["id"], neutron_sg["name"]):
669 self.security_groups_id.append(neutron_sg["id"])
670 break
671 else:
672 self.security_groups_id = None
sousaedu80135b92021-02-17 15:05:18 +0100673
674 raise vimconn.VimConnConnectionException(
675 "Not found security group {} for this tenant".format(sg)
676 )
tiernoa05b65a2019-02-01 12:30:27 +0000677
kayal2001342ee282024-11-28 11:56:49 +0530678 if security_group_name is not None:
679 self.security_groups_id = []
680 for neutron_sg in neutron_sg_list:
681 if security_group_name in (neutron_sg["id"], neutron_sg["name"]):
682 self.security_groups_id.append(neutron_sg["id"])
683 break
684 else:
685 self.security_groups_id = None
686 raise vimconn.VimConnConnectionException(
687 "Not found security group {} for this tenant".format(sg)
688 )
689
vegallc53829d2023-06-01 00:47:44 -0500690 def _find_nova_server(self, vm_id):
691 """
692 Returns the VM instance from Openstack and completes it with flavor ID
693 Do not call nova.servers.find directly, as it does not return flavor ID with microversion>=2.47
694 """
695 try:
696 self._reload_connection()
697 server = self.nova.servers.find(id=vm_id)
698 # TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
699 server_dict = server.to_dict()
700 try:
Luis Vegad6577d82023-07-26 20:49:12 +0000701 if server_dict["flavor"].get("original_name"):
702 server_dict["flavor"]["id"] = self.nova.flavors.find(
703 name=server_dict["flavor"]["original_name"]
704 ).id
vegallc53829d2023-06-01 00:47:44 -0500705 except nClient.exceptions.NotFound as e:
706 self.logger.warning(str(e.message))
707 return server_dict
708 except (
709 ksExceptions.ClientException,
710 nvExceptions.ClientException,
711 nvExceptions.NotFound,
712 ConnectionError,
713 ) as e:
714 self._format_exception(e)
715
tierno5509c2e2019-07-04 16:23:20 +0000716 def check_vim_connectivity(self):
717 # just get network list to check connectivity and credentials
718 self.get_network_list(filter_dict={})
719
tiernoae4a8d12016-07-08 12:30:39 +0200720 def get_tenant_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +0000721 """Obtain tenants of VIM
tiernoae4a8d12016-07-08 12:30:39 +0200722 filter_dict can contain the following keys:
723 name: filter by tenant name
724 id: filter by tenant uuid/id
725 <other VIM specific>
726 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
tierno1ec592d2020-06-16 15:29:47 +0000727 """
ahmadsa95baa272016-11-30 09:14:11 +0500728 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200729 try:
730 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100731
tiernof716aea2017-06-21 18:01:40 +0200732 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100733 project_class_list = self.keystone.projects.list(
734 name=filter_dict.get("name")
735 )
ahmadsa95baa272016-11-30 09:14:11 +0500736 else:
tiernof716aea2017-06-21 18:01:40 +0200737 project_class_list = self.keystone.tenants.findall(**filter_dict)
sousaedu80135b92021-02-17 15:05:18 +0100738
tierno1ec592d2020-06-16 15:29:47 +0000739 project_list = []
sousaedu80135b92021-02-17 15:05:18 +0100740
ahmadsa95baa272016-11-30 09:14:11 +0500741 for project in project_class_list:
sousaedu80135b92021-02-17 15:05:18 +0100742 if filter_dict.get("id") and filter_dict["id"] != project.id:
tiernof716aea2017-06-21 18:01:40 +0200743 continue
sousaedu80135b92021-02-17 15:05:18 +0100744
ahmadsa95baa272016-11-30 09:14:11 +0500745 project_list.append(project.to_dict())
sousaedu80135b92021-02-17 15:05:18 +0100746
ahmadsa95baa272016-11-30 09:14:11 +0500747 return project_list
sousaedu80135b92021-02-17 15:05:18 +0100748 except (
749 ksExceptions.ConnectionError,
750 ksExceptions.ClientException,
751 ConnectionError,
752 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200753 self._format_exception(e)
754
755 def new_tenant(self, tenant_name, tenant_description):
tierno1ec592d2020-06-16 15:29:47 +0000756 """Adds a new tenant to openstack VIM. Returns the tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200757 self.logger.debug("Adding a new tenant name: %s", tenant_name)
758 try:
759 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100760
tiernof716aea2017-06-21 18:01:40 +0200761 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100762 project = self.keystone.projects.create(
763 tenant_name,
764 self.config.get("project_domain_id", "default"),
765 description=tenant_description,
766 is_domain=False,
767 )
ahmadsa95baa272016-11-30 09:14:11 +0500768 else:
tiernof716aea2017-06-21 18:01:40 +0200769 project = self.keystone.tenants.create(tenant_name, tenant_description)
sousaedu80135b92021-02-17 15:05:18 +0100770
ahmadsa95baa272016-11-30 09:14:11 +0500771 return project.id
sousaedu80135b92021-02-17 15:05:18 +0100772 except (
773 ksExceptions.ConnectionError,
774 ksExceptions.ClientException,
775 ksExceptions.BadRequest,
776 ConnectionError,
777 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200778 self._format_exception(e)
779
780 def delete_tenant(self, tenant_id):
tierno1ec592d2020-06-16 15:29:47 +0000781 """Delete a tenant from openstack VIM. Returns the old tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200782 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
783 try:
784 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100785
tiernof716aea2017-06-21 18:01:40 +0200786 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500787 self.keystone.projects.delete(tenant_id)
788 else:
789 self.keystone.tenants.delete(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100790
tiernoae4a8d12016-07-08 12:30:39 +0200791 return tenant_id
gatici335a06a2023-07-26 00:34:04 +0300792
sousaedu80135b92021-02-17 15:05:18 +0100793 except (
794 ksExceptions.ConnectionError,
795 ksExceptions.ClientException,
796 ksExceptions.NotFound,
797 ConnectionError,
798 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200799 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500800
sousaedu80135b92021-02-17 15:05:18 +0100801 def new_network(
802 self,
803 net_name,
804 net_type,
805 ip_profile=None,
806 shared=False,
807 provider_network_profile=None,
808 ):
garciadeblasebd66722019-01-31 16:01:31 +0000809 """Adds a tenant network to VIM
810 Params:
811 'net_name': name of the network
812 'net_type': one of:
813 'bridge': overlay isolated network
814 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
815 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
816 'ip_profile': is a dict containing the IP parameters of the network
817 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
818 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
819 'gateway_address': (Optional) ip_schema, that is X.X.X.X
820 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
821 'dhcp_enabled': True or False
822 'dhcp_start_address': ip_schema, first IP to grant
823 'dhcp_count': number of IPs to grant.
824 'shared': if this network can be seen/use by other tenants/organization
garciadeblas4af0d542020-02-18 16:01:13 +0100825 'provider_network_profile': (optional) contains {segmentation-id: vlan, network-type: vlan|vxlan,
826 physical-network: physnet-label}
garciadeblasebd66722019-01-31 16:01:31 +0000827 Returns a tuple with the network identifier and created_items, or raises an exception on error
828 created_items can be None or a dictionary where this method can include key-values that will be passed to
829 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
830 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
831 as not present.
832 """
sousaedu80135b92021-02-17 15:05:18 +0100833 self.logger.debug(
834 "Adding a new network to VIM name '%s', type '%s'", net_name, net_type
835 )
garciadeblasebd66722019-01-31 16:01:31 +0000836 # self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
kbsuba85c54d2019-10-17 16:30:32 +0000837
tierno7edb6752016-03-21 17:37:52 +0100838 try:
kbsuba85c54d2019-10-17 16:30:32 +0000839 vlan = None
sousaedu80135b92021-02-17 15:05:18 +0100840
kbsuba85c54d2019-10-17 16:30:32 +0000841 if provider_network_profile:
842 vlan = provider_network_profile.get("segmentation-id")
sousaedu80135b92021-02-17 15:05:18 +0100843
garciadeblasedca7b32016-09-29 14:01:52 +0000844 new_net = None
garciadeblasebd66722019-01-31 16:01:31 +0000845 created_items = {}
tierno7edb6752016-03-21 17:37:52 +0100846 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100847 network_dict = {"name": net_name, "admin_state_up": True}
848
Gabriel Cuba0d8ce072022-12-14 18:33:50 -0500849 if net_type in ("data", "ptp") or provider_network_profile:
tierno6869ae72020-01-09 17:37:34 +0000850 provider_physical_network = None
sousaedu80135b92021-02-17 15:05:18 +0100851
852 if provider_network_profile and provider_network_profile.get(
853 "physical-network"
854 ):
855 provider_physical_network = provider_network_profile.get(
856 "physical-network"
857 )
858
tierno6869ae72020-01-09 17:37:34 +0000859 # provider-network must be one of the dataplane_physcial_netowrk if this is a list. If it is string
860 # or not declared, just ignore the checking
sousaedu80135b92021-02-17 15:05:18 +0100861 if (
862 isinstance(
863 self.config.get("dataplane_physical_net"), (tuple, list)
864 )
865 and provider_physical_network
866 not in self.config["dataplane_physical_net"]
867 ):
tierno72774862020-05-04 11:44:15 +0000868 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100869 "Invalid parameter 'provider-network:physical-network' "
870 "for network creation. '{}' is not one of the declared "
871 "list at VIM_config:dataplane_physical_net".format(
872 provider_physical_network
873 )
874 )
875
876 # use the default dataplane_physical_net
877 if not provider_physical_network:
878 provider_physical_network = self.config.get(
879 "dataplane_physical_net"
880 )
881
gatici335a06a2023-07-26 00:34:04 +0300882 # 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 +0100883 if (
884 isinstance(provider_physical_network, (tuple, list))
885 and provider_physical_network
886 ):
tierno6869ae72020-01-09 17:37:34 +0000887 provider_physical_network = provider_physical_network[0]
888
889 if not provider_physical_network:
tierno5ad826a2020-08-11 11:19:44 +0000890 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100891 "missing information needed for underlay networks. Provide "
892 "'dataplane_physical_net' configuration at VIM or use the NS "
893 "instantiation parameter 'provider-network.physical-network'"
894 " for the VLD"
895 )
tierno6869ae72020-01-09 17:37:34 +0000896
sousaedu80135b92021-02-17 15:05:18 +0100897 if not self.config.get("multisegment_support"):
garciadeblasaca8cb52023-12-21 16:28:15 +0100898 network_dict["provider:physical_network"] = (
899 provider_physical_network
900 )
sousaedu80135b92021-02-17 15:05:18 +0100901
902 if (
903 provider_network_profile
904 and "network-type" in provider_network_profile
905 ):
garciadeblasaca8cb52023-12-21 16:28:15 +0100906 network_dict["provider:network_type"] = (
907 provider_network_profile["network-type"]
908 )
garciadeblas4af0d542020-02-18 16:01:13 +0100909 else:
sousaedu80135b92021-02-17 15:05:18 +0100910 network_dict["provider:network_type"] = self.config.get(
911 "dataplane_network_type", "vlan"
912 )
913
tierno6869ae72020-01-09 17:37:34 +0000914 if vlan:
915 network_dict["provider:segmentation_id"] = vlan
garciadeblasebd66722019-01-31 16:01:31 +0000916 else:
tierno6869ae72020-01-09 17:37:34 +0000917 # Multi-segment case
garciadeblasebd66722019-01-31 16:01:31 +0000918 segment_list = []
tierno6869ae72020-01-09 17:37:34 +0000919 segment1_dict = {
sousaedu80135b92021-02-17 15:05:18 +0100920 "provider:physical_network": "",
921 "provider:network_type": "vxlan",
tierno6869ae72020-01-09 17:37:34 +0000922 }
garciadeblasebd66722019-01-31 16:01:31 +0000923 segment_list.append(segment1_dict)
tierno6869ae72020-01-09 17:37:34 +0000924 segment2_dict = {
925 "provider:physical_network": provider_physical_network,
sousaedu80135b92021-02-17 15:05:18 +0100926 "provider:network_type": "vlan",
tierno6869ae72020-01-09 17:37:34 +0000927 }
sousaedu80135b92021-02-17 15:05:18 +0100928
tierno6869ae72020-01-09 17:37:34 +0000929 if vlan:
930 segment2_dict["provider:segmentation_id"] = vlan
sousaedu80135b92021-02-17 15:05:18 +0100931 elif self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +0000932 vlanID = self._generate_multisegment_vlanID()
933 segment2_dict["provider:segmentation_id"] = vlanID
sousaedu80135b92021-02-17 15:05:18 +0100934
garciadeblasebd66722019-01-31 16:01:31 +0000935 # else
tierno72774862020-05-04 11:44:15 +0000936 # raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100937 # "You must provide "multisegment_vlan_range" at config dict before creating a multisegment
tierno1ec592d2020-06-16 15:29:47 +0000938 # network")
garciadeblasebd66722019-01-31 16:01:31 +0000939 segment_list.append(segment2_dict)
940 network_dict["segments"] = segment_list
kate721d79b2017-06-24 04:21:38 -0700941
tierno6869ae72020-01-09 17:37:34 +0000942 # VIO Specific Changes. It needs a concrete VLAN
943 if self.vim_type == "VIO" and vlan is None:
sousaedu80135b92021-02-17 15:05:18 +0100944 if self.config.get("dataplane_net_vlan_range") is None:
tierno72774862020-05-04 11:44:15 +0000945 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100946 "You must provide 'dataplane_net_vlan_range' in format "
947 "[start_ID - end_ID] at VIM_config for creating underlay "
948 "networks"
949 )
950
tierno6869ae72020-01-09 17:37:34 +0000951 network_dict["provider:segmentation_id"] = self._generate_vlanID()
kate721d79b2017-06-24 04:21:38 -0700952
garciadeblasebd66722019-01-31 16:01:31 +0000953 network_dict["shared"] = shared
sousaedu80135b92021-02-17 15:05:18 +0100954
anwarsff168192019-05-06 11:23:07 +0530955 if self.config.get("disable_network_port_security"):
956 network_dict["port_security_enabled"] = False
sousaedu80135b92021-02-17 15:05:18 +0100957
sousaedu2aa5f802021-06-17 15:39:29 +0100958 if self.config.get("neutron_availability_zone_hints"):
959 hints = self.config.get("neutron_availability_zone_hints")
960
961 if isinstance(hints, str):
962 hints = [hints]
963
964 network_dict["availability_zone_hints"] = hints
965
sousaedu80135b92021-02-17 15:05:18 +0100966 new_net = self.neutron.create_network({"network": network_dict})
garciadeblasebd66722019-01-31 16:01:31 +0000967 # print new_net
968 # create subnetwork, even if there is no profile
sousaedu80135b92021-02-17 15:05:18 +0100969
garciadeblas9f8456e2016-09-05 05:02:59 +0200970 if not ip_profile:
971 ip_profile = {}
sousaedu80135b92021-02-17 15:05:18 +0100972
973 if not ip_profile.get("subnet_address"):
tierno1ec592d2020-06-16 15:29:47 +0000974 # Fake subnet is required
elumalai51e72a02023-04-28 19:41:49 +0530975 subnet_rand = random.SystemRandom().randint(0, 255)
sousaedu80135b92021-02-17 15:05:18 +0100976 ip_profile["subnet_address"] = "192.168.{}.0/24".format(subnet_rand)
977
978 if "ip_version" not in ip_profile:
979 ip_profile["ip_version"] = "IPv4"
980
981 subnet = {
982 "name": net_name + "-subnet",
983 "network_id": new_net["network"]["id"],
984 "ip_version": 4 if ip_profile["ip_version"] == "IPv4" else 6,
985 "cidr": ip_profile["subnet_address"],
986 }
987
tiernoa1fb4462017-06-30 12:25:50 +0200988 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
sousaedu80135b92021-02-17 15:05:18 +0100989 if ip_profile.get("gateway_address"):
990 subnet["gateway_ip"] = ip_profile["gateway_address"]
tierno55d234c2018-07-04 18:29:21 +0200991 else:
sousaedu80135b92021-02-17 15:05:18 +0100992 subnet["gateway_ip"] = None
993
994 if ip_profile.get("dns_address"):
995 subnet["dns_nameservers"] = ip_profile["dns_address"].split(";")
996
997 if "dhcp_enabled" in ip_profile:
998 subnet["enable_dhcp"] = (
999 False
1000 if ip_profile["dhcp_enabled"] == "false"
1001 or ip_profile["dhcp_enabled"] is False
1002 else True
1003 )
1004
1005 if ip_profile.get("dhcp_start_address"):
1006 subnet["allocation_pools"] = []
1007 subnet["allocation_pools"].append(dict())
1008 subnet["allocation_pools"][0]["start"] = ip_profile[
1009 "dhcp_start_address"
1010 ]
1011
1012 if ip_profile.get("dhcp_count"):
1013 # parts = ip_profile["dhcp_start_address"].split(".")
tierno1ec592d2020-06-16 15:29:47 +00001014 # ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
sousaedu80135b92021-02-17 15:05:18 +01001015 ip_int = int(netaddr.IPAddress(ip_profile["dhcp_start_address"]))
1016 ip_int += ip_profile["dhcp_count"] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +02001017 ip_str = str(netaddr.IPAddress(ip_int))
sousaedu80135b92021-02-17 15:05:18 +01001018 subnet["allocation_pools"][0]["end"] = ip_str
1019
Gabriel Cubab3dbfca2023-03-14 10:58:39 -05001020 if (
1021 ip_profile.get("ipv6_address_mode")
1022 and ip_profile["ip_version"] != "IPv4"
1023 ):
1024 subnet["ipv6_address_mode"] = ip_profile["ipv6_address_mode"]
1025 # ipv6_ra_mode can be set to the same value for most use cases, see documentation:
1026 # https://docs.openstack.org/neutron/latest/admin/config-ipv6.html#ipv6-ra-mode-and-ipv6-address-mode-combinations
1027 subnet["ipv6_ra_mode"] = ip_profile["ipv6_address_mode"]
1028
tierno1ec592d2020-06-16 15:29:47 +00001029 # self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
1030 self.neutron.create_subnet({"subnet": subnet})
garciadeblasebd66722019-01-31 16:01:31 +00001031
sousaedu80135b92021-02-17 15:05:18 +01001032 if net_type == "data" and self.config.get("multisegment_support"):
1033 if self.config.get("l2gw_support"):
garciadeblasebd66722019-01-31 16:01:31 +00001034 l2gw_list = self.neutron.list_l2_gateways().get("l2_gateways", ())
1035 for l2gw in l2gw_list:
tierno1ec592d2020-06-16 15:29:47 +00001036 l2gw_conn = {
1037 "l2_gateway_id": l2gw["id"],
1038 "network_id": new_net["network"]["id"],
1039 "segmentation_id": str(vlanID),
1040 }
sousaedu80135b92021-02-17 15:05:18 +01001041 new_l2gw_conn = self.neutron.create_l2_gateway_connection(
1042 {"l2_gateway_connection": l2gw_conn}
1043 )
1044 created_items[
1045 "l2gwconn:"
1046 + str(new_l2gw_conn["l2_gateway_connection"]["id"])
1047 ] = True
1048
garciadeblasebd66722019-01-31 16:01:31 +00001049 return new_net["network"]["id"], created_items
tierno41a69812018-02-16 14:34:33 +01001050 except Exception as e:
tierno1ec592d2020-06-16 15:29:47 +00001051 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001052 for k, v in created_items.items():
1053 if not v: # skip already deleted
1054 continue
sousaedu80135b92021-02-17 15:05:18 +01001055
garciadeblasebd66722019-01-31 16:01:31 +00001056 try:
1057 k_item, _, k_id = k.partition(":")
sousaedu80135b92021-02-17 15:05:18 +01001058
garciadeblasebd66722019-01-31 16:01:31 +00001059 if k_item == "l2gwconn":
1060 self.neutron.delete_l2_gateway_connection(k_id)
gatici335a06a2023-07-26 00:34:04 +03001061
1062 except (neExceptions.ConnectionFailed, ConnectionError) as e2:
1063 self.logger.error(
1064 "Error deleting l2 gateway connection: {}: {}".format(
1065 type(e2).__name__, e2
1066 )
1067 )
1068 self._format_exception(e2)
garciadeblasebd66722019-01-31 16:01:31 +00001069 except Exception as e2:
sousaedu80135b92021-02-17 15:05:18 +01001070 self.logger.error(
1071 "Error deleting l2 gateway connection: {}: {}".format(
1072 type(e2).__name__, e2
1073 )
1074 )
1075
garciadeblasedca7b32016-09-29 14:01:52 +00001076 if new_net:
sousaedu80135b92021-02-17 15:05:18 +01001077 self.neutron.delete_network(new_net["network"]["id"])
1078
tiernoae4a8d12016-07-08 12:30:39 +02001079 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001080
1081 def get_network_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001082 """Obtain tenant networks of VIM
tierno7edb6752016-03-21 17:37:52 +01001083 Filter_dict can be:
1084 name: network name
1085 id: network uuid
1086 shared: boolean
1087 tenant_id: tenant
1088 admin_state_up: boolean
1089 status: 'ACTIVE'
1090 Returns the network list of dictionaries
tierno1ec592d2020-06-16 15:29:47 +00001091 """
tiernoae4a8d12016-07-08 12:30:39 +02001092 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +01001093 try:
1094 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001095 filter_dict_os = filter_dict.copy()
sousaedu80135b92021-02-17 15:05:18 +01001096
tierno69b590e2018-03-13 18:52:23 +01001097 if self.api_version3 and "tenant_id" in filter_dict_os:
sousaedu80135b92021-02-17 15:05:18 +01001098 # TODO check
1099 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
1100
tierno69b590e2018-03-13 18:52:23 +01001101 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +01001102 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +01001103 self.__net_os2mano(net_list)
sousaedu80135b92021-02-17 15:05:18 +01001104
tiernoae4a8d12016-07-08 12:30:39 +02001105 return net_list
sousaedu80135b92021-02-17 15:05:18 +01001106 except (
1107 neExceptions.ConnectionFailed,
1108 ksExceptions.ClientException,
1109 neExceptions.NeutronException,
1110 ConnectionError,
1111 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001112 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001113
tiernoae4a8d12016-07-08 12:30:39 +02001114 def get_network(self, net_id):
tierno1ec592d2020-06-16 15:29:47 +00001115 """Obtain details of network from VIM
1116 Returns the network information from a network id"""
tiernoae4a8d12016-07-08 12:30:39 +02001117 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno1ec592d2020-06-16 15:29:47 +00001118 filter_dict = {"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +02001119 net_list = self.get_network_list(filter_dict)
sousaedu80135b92021-02-17 15:05:18 +01001120
tierno1ec592d2020-06-16 15:29:47 +00001121 if len(net_list) == 0:
sousaedu80135b92021-02-17 15:05:18 +01001122 raise vimconn.VimConnNotFoundException(
1123 "Network '{}' not found".format(net_id)
1124 )
tierno1ec592d2020-06-16 15:29:47 +00001125 elif len(net_list) > 1:
sousaedu80135b92021-02-17 15:05:18 +01001126 raise vimconn.VimConnConflictException(
1127 "Found more than one network with this criteria"
1128 )
1129
tierno7edb6752016-03-21 17:37:52 +01001130 net = net_list[0]
tierno1ec592d2020-06-16 15:29:47 +00001131 subnets = []
1132 for subnet_id in net.get("subnets", ()):
tierno7edb6752016-03-21 17:37:52 +01001133 try:
1134 subnet = self.neutron.show_subnet(subnet_id)
1135 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001136 self.logger.error(
1137 "osconnector.get_network(): Error getting subnet %s %s"
1138 % (net_id, str(e))
1139 )
tiernoae4a8d12016-07-08 12:30:39 +02001140 subnet = {"id": subnet_id, "fault": str(e)}
sousaedu80135b92021-02-17 15:05:18 +01001141
tierno7edb6752016-03-21 17:37:52 +01001142 subnets.append(subnet)
sousaedu80135b92021-02-17 15:05:18 +01001143
tierno7edb6752016-03-21 17:37:52 +01001144 net["subnets"] = subnets
sousaedu80135b92021-02-17 15:05:18 +01001145 net["encapsulation"] = net.get("provider:network_type")
1146 net["encapsulation_type"] = net.get("provider:network_type")
1147 net["segmentation_id"] = net.get("provider:segmentation_id")
1148 net["encapsulation_id"] = net.get("provider:segmentation_id")
1149
tiernoae4a8d12016-07-08 12:30:39 +02001150 return net
tierno7edb6752016-03-21 17:37:52 +01001151
gatici335a06a2023-07-26 00:34:04 +03001152 @catch_any_exception
garciadeblasebd66722019-01-31 16:01:31 +00001153 def delete_network(self, net_id, created_items=None):
1154 """
1155 Removes a tenant network from VIM and its associated elements
1156 :param net_id: VIM identifier of the network, provided by method new_network
1157 :param created_items: dictionary with extra items to be deleted. provided by method new_network
1158 Returns the network identifier or raises an exception upon error or when network is not found
1159 """
tiernoae4a8d12016-07-08 12:30:39 +02001160 self.logger.debug("Deleting network '%s' from VIM", net_id)
sousaedu80135b92021-02-17 15:05:18 +01001161
tierno1ec592d2020-06-16 15:29:47 +00001162 if created_items is None:
garciadeblasebd66722019-01-31 16:01:31 +00001163 created_items = {}
sousaedu80135b92021-02-17 15:05:18 +01001164
tierno7edb6752016-03-21 17:37:52 +01001165 try:
1166 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001167 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001168 for k, v in created_items.items():
1169 if not v: # skip already deleted
1170 continue
sousaedu80135b92021-02-17 15:05:18 +01001171
garciadeblasebd66722019-01-31 16:01:31 +00001172 try:
1173 k_item, _, k_id = k.partition(":")
1174 if k_item == "l2gwconn":
1175 self.neutron.delete_l2_gateway_connection(k_id)
gatici335a06a2023-07-26 00:34:04 +03001176
1177 except (neExceptions.ConnectionFailed, ConnectionError) as e:
1178 self.logger.error(
1179 "Error deleting l2 gateway connection: {}: {}".format(
1180 type(e).__name__, e
1181 )
1182 )
1183 self._format_exception(e)
garciadeblasebd66722019-01-31 16:01:31 +00001184 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001185 self.logger.error(
1186 "Error deleting l2 gateway connection: {}: {}".format(
1187 type(e).__name__, e
1188 )
1189 )
1190
tierno1ec592d2020-06-16 15:29:47 +00001191 # delete VM ports attached to this networks before the network
tierno7edb6752016-03-21 17:37:52 +01001192 ports = self.neutron.list_ports(network_id=net_id)
sousaedu80135b92021-02-17 15:05:18 +01001193 for p in ports["ports"]:
tierno7edb6752016-03-21 17:37:52 +01001194 try:
1195 self.neutron.delete_port(p["id"])
gatici335a06a2023-07-26 00:34:04 +03001196
1197 except (neExceptions.ConnectionFailed, ConnectionError) as e:
1198 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
1199 # If there is connection error, it raises.
1200 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001201 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001202 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
sousaedu80135b92021-02-17 15:05:18 +01001203
tierno7edb6752016-03-21 17:37:52 +01001204 self.neutron.delete_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001205
tiernoae4a8d12016-07-08 12:30:39 +02001206 return net_id
gatici335a06a2023-07-26 00:34:04 +03001207 except (neExceptions.NetworkNotFoundClient, neExceptions.NotFound) as e:
1208 # If network to be deleted is not found, it does not raise.
1209 self.logger.warning(
1210 f"Error deleting network: {net_id} is not found, {str(e)}"
1211 )
tierno7edb6752016-03-21 17:37:52 +01001212
tiernoae4a8d12016-07-08 12:30:39 +02001213 def refresh_nets_status(self, net_list):
tierno1ec592d2020-06-16 15:29:47 +00001214 """Get the status of the networks
sousaedu80135b92021-02-17 15:05:18 +01001215 Params: the list of network identifiers
1216 Returns a dictionary with:
1217 net_id: #VIM id of this network
1218 status: #Mandatory. Text with one of:
1219 # DELETED (not found at vim)
1220 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1221 # OTHER (Vim reported other status not understood)
1222 # ERROR (VIM indicates an ERROR status)
1223 # ACTIVE, INACTIVE, DOWN (admin down),
1224 # BUILD (on building process)
1225 #
1226 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1227 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
tierno1ec592d2020-06-16 15:29:47 +00001228 """
1229 net_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001230
tiernoae4a8d12016-07-08 12:30:39 +02001231 for net_id in net_list:
1232 net = {}
sousaedu80135b92021-02-17 15:05:18 +01001233
tiernoae4a8d12016-07-08 12:30:39 +02001234 try:
1235 net_vim = self.get_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001236
1237 if net_vim["status"] in netStatus2manoFormat:
1238 net["status"] = netStatus2manoFormat[net_vim["status"]]
tiernoae4a8d12016-07-08 12:30:39 +02001239 else:
1240 net["status"] = "OTHER"
sousaedu80135b92021-02-17 15:05:18 +01001241 net["error_msg"] = "VIM status reported " + net_vim["status"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001242
sousaedu80135b92021-02-17 15:05:18 +01001243 if net["status"] == "ACTIVE" and not net_vim["admin_state_up"]:
1244 net["status"] = "DOWN"
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001245
sousaedu80135b92021-02-17 15:05:18 +01001246 net["vim_info"] = self.serialize(net_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001247
sousaedu80135b92021-02-17 15:05:18 +01001248 if net_vim.get("fault"): # TODO
1249 net["error_msg"] = str(net_vim["fault"])
tierno72774862020-05-04 11:44:15 +00001250 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001251 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001252 net["status"] = "DELETED"
1253 net["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00001254 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001255 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001256 net["status"] = "VIM_ERROR"
1257 net["error_msg"] = str(e)
tiernoae4a8d12016-07-08 12:30:39 +02001258 net_dict[net_id] = net
1259 return net_dict
1260
1261 def get_flavor(self, flavor_id):
tierno1ec592d2020-06-16 15:29:47 +00001262 """Obtain flavor details from the VIM. Returns the flavor dict details"""
tiernoae4a8d12016-07-08 12:30:39 +02001263 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +01001264 try:
1265 self._reload_connection()
1266 flavor = self.nova.flavors.find(id=flavor_id)
tiernoae4a8d12016-07-08 12:30:39 +02001267 return flavor.to_dict()
gatici335a06a2023-07-26 00:34:04 +03001268
sousaedu80135b92021-02-17 15:05:18 +01001269 except (
1270 nvExceptions.NotFound,
1271 nvExceptions.ClientException,
1272 ksExceptions.ClientException,
1273 ConnectionError,
1274 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001275 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001276
tiernocf157a82017-01-30 14:07:06 +01001277 def get_flavor_id_from_data(self, flavor_dict):
1278 """Obtain flavor id that match the flavor description
sousaedu80135b92021-02-17 15:05:18 +01001279 Returns the flavor_id or raises a vimconnNotFoundException
1280 flavor_dict: contains the required ram, vcpus, disk
1281 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
1282 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
1283 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +01001284 """
sousaedu80135b92021-02-17 15:05:18 +01001285 exact_match = False if self.config.get("use_existing_flavors") else True
1286
tiernocf157a82017-01-30 14:07:06 +01001287 try:
1288 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +02001289 flavor_candidate_id = None
1290 flavor_candidate_data = (10000, 10000, 10000)
sousaedu80135b92021-02-17 15:05:18 +01001291 flavor_target = (
1292 flavor_dict["ram"],
1293 flavor_dict["vcpus"],
1294 flavor_dict["disk"],
sousaedu648ee3d2021-11-22 14:09:15 +00001295 flavor_dict.get("ephemeral", 0),
1296 flavor_dict.get("swap", 0),
sousaedu80135b92021-02-17 15:05:18 +01001297 )
tiernoe26fc7a2017-05-30 14:43:03 +02001298 # numa=None
anwarsae5f52c2019-04-22 10:35:27 +05301299 extended = flavor_dict.get("extended", {})
1300 if extended:
tierno1ec592d2020-06-16 15:29:47 +00001301 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001302 raise vimconn.VimConnNotFoundException(
1303 "Flavor with EPA still not implemented"
1304 )
tiernocf157a82017-01-30 14:07:06 +01001305 # if len(numas) > 1:
tierno72774862020-05-04 11:44:15 +00001306 # raise vimconn.VimConnNotFoundException("Cannot find any flavor with more than one numa")
tiernocf157a82017-01-30 14:07:06 +01001307 # numa=numas[0]
1308 # numas = extended.get("numas")
1309 for flavor in self.nova.flavors.list():
1310 epa = flavor.get_keys()
sousaedu80135b92021-02-17 15:05:18 +01001311
tiernocf157a82017-01-30 14:07:06 +01001312 if epa:
1313 continue
tiernoe26fc7a2017-05-30 14:43:03 +02001314 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001315
sousaedu648ee3d2021-11-22 14:09:15 +00001316 flavor_data = (
1317 flavor.ram,
1318 flavor.vcpus,
1319 flavor.disk,
1320 flavor.ephemeral,
preethika.pebaba1f2022-01-20 07:24:18 +00001321 flavor.swap if isinstance(flavor.swap, int) else 0,
sousaedu648ee3d2021-11-22 14:09:15 +00001322 )
tiernoe26fc7a2017-05-30 14:43:03 +02001323 if flavor_data == flavor_target:
1324 return flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001325 elif (
1326 not exact_match
1327 and flavor_target < flavor_data < flavor_candidate_data
1328 ):
tiernoe26fc7a2017-05-30 14:43:03 +02001329 flavor_candidate_id = flavor.id
1330 flavor_candidate_data = flavor_data
sousaedu80135b92021-02-17 15:05:18 +01001331
tiernoe26fc7a2017-05-30 14:43:03 +02001332 if not exact_match and flavor_candidate_id:
1333 return flavor_candidate_id
sousaedu80135b92021-02-17 15:05:18 +01001334
1335 raise vimconn.VimConnNotFoundException(
1336 "Cannot find any flavor matching '{}'".format(flavor_dict)
1337 )
1338 except (
1339 nvExceptions.NotFound,
gatici335a06a2023-07-26 00:34:04 +03001340 nvExceptions.BadRequest,
sousaedu80135b92021-02-17 15:05:18 +01001341 nvExceptions.ClientException,
1342 ksExceptions.ClientException,
1343 ConnectionError,
1344 ) as e:
tiernocf157a82017-01-30 14:07:06 +01001345 self._format_exception(e)
1346
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001347 @staticmethod
1348 def process_resource_quota(quota: dict, prefix: str, extra_specs: dict) -> None:
1349 """Process resource quota and fill up extra_specs.
1350 Args:
1351 quota (dict): Keeping the quota of resurces
1352 prefix (str) Prefix
1353 extra_specs (dict) Dict to be filled to be used during flavor creation
1354
anwarsae5f52c2019-04-22 10:35:27 +05301355 """
sousaedu80135b92021-02-17 15:05:18 +01001356 if "limit" in quota:
1357 extra_specs["quota:" + prefix + "_limit"] = quota["limit"]
1358
1359 if "reserve" in quota:
1360 extra_specs["quota:" + prefix + "_reservation"] = quota["reserve"]
1361
1362 if "shares" in quota:
anwarsae5f52c2019-04-22 10:35:27 +05301363 extra_specs["quota:" + prefix + "_shares_level"] = "custom"
sousaedu80135b92021-02-17 15:05:18 +01001364 extra_specs["quota:" + prefix + "_shares_share"] = quota["shares"]
anwarsae5f52c2019-04-22 10:35:27 +05301365
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001366 @staticmethod
1367 def process_numa_memory(
1368 numa: dict, node_id: Optional[int], extra_specs: dict
1369 ) -> None:
1370 """Set the memory in extra_specs.
1371 Args:
1372 numa (dict): A dictionary which includes numa information
1373 node_id (int): ID of numa node
1374 extra_specs (dict): To be filled.
1375
1376 """
1377 if not numa.get("memory"):
1378 return
1379 memory_mb = numa["memory"] * 1024
1380 memory = "hw:numa_mem.{}".format(node_id)
1381 extra_specs[memory] = int(memory_mb)
1382
1383 @staticmethod
1384 def process_numa_vcpu(numa: dict, node_id: int, extra_specs: dict) -> None:
1385 """Set the cpu in extra_specs.
1386 Args:
1387 numa (dict): A dictionary which includes numa information
1388 node_id (int): ID of numa node
1389 extra_specs (dict): To be filled.
1390
1391 """
1392 if not numa.get("vcpu"):
1393 return
1394 vcpu = numa["vcpu"]
1395 cpu = "hw:numa_cpus.{}".format(node_id)
1396 vcpu = ",".join(map(str, vcpu))
1397 extra_specs[cpu] = vcpu
1398
1399 @staticmethod
1400 def process_numa_paired_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1401 """Fill up extra_specs if numa has paired-threads.
1402 Args:
1403 numa (dict): A dictionary which includes numa information
1404 extra_specs (dict): To be filled.
1405
1406 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001407 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001408
1409 """
1410 if not numa.get("paired-threads"):
1411 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001412
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001413 # cpu_thread_policy "require" implies that compute node must have an STM architecture
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001414 threads = numa["paired-threads"] * 2
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001415 extra_specs["hw:cpu_thread_policy"] = "require"
1416 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001417 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001418
1419 @staticmethod
1420 def process_numa_cores(numa: dict, extra_specs: dict) -> Optional[int]:
1421 """Fill up extra_specs if numa has cores.
1422 Args:
1423 numa (dict): A dictionary which includes numa information
1424 extra_specs (dict): To be filled.
1425
1426 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001427 cores (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001428
1429 """
1430 # cpu_thread_policy "isolate" implies that the host must not have an SMT
1431 # architecture, or a non-SMT architecture will be emulated
1432 if not numa.get("cores"):
1433 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001434 cores = numa["cores"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001435 extra_specs["hw:cpu_thread_policy"] = "isolate"
1436 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001437 return cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001438
1439 @staticmethod
1440 def process_numa_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1441 """Fill up extra_specs if numa has threads.
1442 Args:
1443 numa (dict): A dictionary which includes numa information
1444 extra_specs (dict): To be filled.
1445
1446 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001447 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001448
1449 """
1450 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
1451 if not numa.get("threads"):
1452 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001453 threads = numa["threads"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001454 extra_specs["hw:cpu_thread_policy"] = "prefer"
1455 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001456 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001457
1458 def _process_numa_parameters_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001459 self, numas: List, extra_specs: Dict
1460 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001461 """Process numa parameters and fill up extra_specs.
1462
1463 Args:
1464 numas (list): List of dictionary which includes numa information
1465 extra_specs (dict): To be filled.
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001466
1467 """
1468 numa_nodes = len(numas)
1469 extra_specs["hw:numa_nodes"] = str(numa_nodes)
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001470 cpu_cores, cpu_threads = 0, 0
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001471
1472 if self.vim_type == "VIO":
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001473 self.process_vio_numa_nodes(numa_nodes, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001474
1475 for numa in numas:
1476 if "id" in numa:
1477 node_id = numa["id"]
1478 # overwrite ram and vcpus
1479 # check if key "memory" is present in numa else use ram value at flavor
1480 self.process_numa_memory(numa, node_id, extra_specs)
1481 self.process_numa_vcpu(numa, node_id, extra_specs)
1482
1483 # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
1484 extra_specs["hw:cpu_sockets"] = str(numa_nodes)
1485
1486 if "paired-threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001487 threads = self.process_numa_paired_threads(numa, extra_specs)
1488 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001489
1490 elif "cores" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001491 cores = self.process_numa_cores(numa, extra_specs)
1492 cpu_cores += cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001493
1494 elif "threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001495 threads = self.process_numa_threads(numa, extra_specs)
1496 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001497
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001498 if cpu_cores:
1499 extra_specs["hw:cpu_cores"] = str(cpu_cores)
1500 if cpu_threads:
1501 extra_specs["hw:cpu_threads"] = str(cpu_threads)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001502
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001503 @staticmethod
1504 def process_vio_numa_nodes(numa_nodes: int, extra_specs: Dict) -> None:
1505 """According to number of numa nodes, updates the extra_specs for VIO.
1506
1507 Args:
1508
1509 numa_nodes (int): List keeps the numa node numbers
1510 extra_specs (dict): Extra specs dict to be updated
1511
1512 """
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001513 # If there are several numas, we do not define specific affinity.
1514 extra_specs["vmware:latency_sensitivity_level"] = "high"
1515
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001516 def _change_flavor_name(
1517 self, name: str, name_suffix: int, flavor_data: dict
1518 ) -> str:
1519 """Change the flavor name if the name already exists.
1520
1521 Args:
1522 name (str): Flavor name to be checked
1523 name_suffix (int): Suffix to be appended to name
1524 flavor_data (dict): Flavor dict
1525
1526 Returns:
1527 name (str): New flavor name to be used
1528
1529 """
1530 # Get used names
1531 fl = self.nova.flavors.list()
1532 fl_names = [f.name for f in fl]
1533
1534 while name in fl_names:
1535 name_suffix += 1
1536 name = flavor_data["name"] + "-" + str(name_suffix)
1537
1538 return name
1539
1540 def _process_extended_config_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001541 self, extended: dict, extra_specs: dict
1542 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001543 """Process the extended dict to fill up extra_specs.
1544 Args:
1545
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001546 extended (dict): Keeping the extra specification of flavor
1547 extra_specs (dict) Dict to be filled to be used during flavor creation
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001548
1549 """
1550 quotas = {
1551 "cpu-quota": "cpu",
1552 "mem-quota": "memory",
1553 "vif-quota": "vif",
1554 "disk-io-quota": "disk_io",
1555 }
1556
1557 page_sizes = {
1558 "LARGE": "large",
1559 "SMALL": "small",
1560 "SIZE_2MB": "2MB",
1561 "SIZE_1GB": "1GB",
1562 "PREFER_LARGE": "any",
1563 }
1564
1565 policies = {
1566 "cpu-pinning-policy": "hw:cpu_policy",
1567 "cpu-thread-pinning-policy": "hw:cpu_thread_policy",
1568 "mem-policy": "hw:numa_mempolicy",
1569 }
1570
1571 numas = extended.get("numas")
1572 if numas:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001573 self._process_numa_parameters_of_flavor(numas, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001574
1575 for quota, item in quotas.items():
1576 if quota in extended.keys():
1577 self.process_resource_quota(extended.get(quota), item, extra_specs)
1578
1579 # Set the mempage size as specified in the descriptor
1580 if extended.get("mempage-size"):
1581 if extended["mempage-size"] in page_sizes.keys():
1582 extra_specs["hw:mem_page_size"] = page_sizes[extended["mempage-size"]]
1583 else:
1584 # Normally, validations in NBI should not allow to this condition.
1585 self.logger.debug(
1586 "Invalid mempage-size %s. Will be ignored",
1587 extended.get("mempage-size"),
1588 )
1589
1590 for policy, hw_policy in policies.items():
1591 if extended.get(policy):
1592 extra_specs[hw_policy] = extended[policy].lower()
1593
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001594 @staticmethod
1595 def _get_flavor_details(flavor_data: dict) -> Tuple:
1596 """Returns the details of flavor
1597 Args:
1598 flavor_data (dict): Dictionary that includes required flavor details
1599
1600 Returns:
1601 ram, vcpus, extra_specs, extended (tuple): Main items of required flavor
1602
1603 """
1604 return (
1605 flavor_data.get("ram", 64),
1606 flavor_data.get("vcpus", 1),
1607 {},
1608 flavor_data.get("extended"),
1609 )
1610
gatici335a06a2023-07-26 00:34:04 +03001611 @catch_any_exception
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001612 def new_flavor(self, flavor_data: dict, change_name_if_used: bool = True) -> str:
1613 """Adds a tenant flavor to openstack VIM.
1614 if change_name_if_used is True, it will change name in case of conflict,
1615 because it is not supported name repetition.
1616
1617 Args:
1618 flavor_data (dict): Flavor details to be processed
1619 change_name_if_used (bool): Change name in case of conflict
1620
1621 Returns:
1622 flavor_id (str): flavor identifier
1623
tierno1ec592d2020-06-16 15:29:47 +00001624 """
tiernoae4a8d12016-07-08 12:30:39 +02001625 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno1ec592d2020-06-16 15:29:47 +00001626 retry = 0
1627 max_retries = 3
tierno7edb6752016-03-21 17:37:52 +01001628 name_suffix = 0
gatici335a06a2023-07-26 00:34:04 +03001629 name = flavor_data["name"]
1630 while retry < max_retries:
1631 retry += 1
1632 try:
1633 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001634
gatici335a06a2023-07-26 00:34:04 +03001635 if change_name_if_used:
1636 name = self._change_flavor_name(name, name_suffix, flavor_data)
sousaedu80135b92021-02-17 15:05:18 +01001637
gatici335a06a2023-07-26 00:34:04 +03001638 ram, vcpus, extra_specs, extended = self._get_flavor_details(
1639 flavor_data
1640 )
1641 if extended:
1642 self._process_extended_config_of_flavor(extended, extra_specs)
sousaedu80135b92021-02-17 15:05:18 +01001643
gatici335a06a2023-07-26 00:34:04 +03001644 # Create flavor
sousaedu80135b92021-02-17 15:05:18 +01001645
gatici335a06a2023-07-26 00:34:04 +03001646 new_flavor = self.nova.flavors.create(
1647 name=name,
1648 ram=ram,
1649 vcpus=vcpus,
1650 disk=flavor_data.get("disk", 0),
1651 ephemeral=flavor_data.get("ephemeral", 0),
1652 swap=flavor_data.get("swap", 0),
1653 is_public=flavor_data.get("is_public", True),
1654 )
sousaedu80135b92021-02-17 15:05:18 +01001655
gatici335a06a2023-07-26 00:34:04 +03001656 # Add metadata
1657 if extra_specs:
1658 new_flavor.set_keys(extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001659
gatici335a06a2023-07-26 00:34:04 +03001660 return new_flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001661
gatici335a06a2023-07-26 00:34:04 +03001662 except nvExceptions.Conflict as e:
1663 if change_name_if_used and retry < max_retries:
1664 continue
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001665
gatici335a06a2023-07-26 00:34:04 +03001666 self._format_exception(e)
sousaedu80135b92021-02-17 15:05:18 +01001667
gatici335a06a2023-07-26 00:34:04 +03001668 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00001669 def delete_flavor(self, flavor_id):
sousaedu80135b92021-02-17 15:05:18 +01001670 """Deletes a tenant flavor from openstack VIM. Returns the old flavor_id"""
tiernoae4a8d12016-07-08 12:30:39 +02001671 try:
1672 self._reload_connection()
1673 self.nova.flavors.delete(flavor_id)
1674 return flavor_id
gatici335a06a2023-07-26 00:34:04 +03001675
1676 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
1677 # If flavor is not found, it does not raise.
1678 self.logger.warning(
1679 f"Error deleting flavor: {flavor_id} is not found, {str(e.message)}"
1680 )
tierno7edb6752016-03-21 17:37:52 +01001681
tierno1ec592d2020-06-16 15:29:47 +00001682 def new_image(self, image_dict):
1683 """
tiernoae4a8d12016-07-08 12:30:39 +02001684 Adds a tenant image to VIM. imge_dict is a dictionary with:
1685 name: name
1686 disk_format: qcow2, vhd, vmdk, raw (by default), ...
1687 location: path or URI
1688 public: "yes" or "no"
1689 metadata: metadata of the image
1690 Returns the image_id
tierno1ec592d2020-06-16 15:29:47 +00001691 """
1692 retry = 0
1693 max_retries = 3
sousaedu80135b92021-02-17 15:05:18 +01001694
tierno1ec592d2020-06-16 15:29:47 +00001695 while retry < max_retries:
1696 retry += 1
tierno7edb6752016-03-21 17:37:52 +01001697 try:
1698 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001699
tierno1ec592d2020-06-16 15:29:47 +00001700 # determine format http://docs.openstack.org/developer/glance/formats.html
tierno7edb6752016-03-21 17:37:52 +01001701 if "disk_format" in image_dict:
tierno1ec592d2020-06-16 15:29:47 +00001702 disk_format = image_dict["disk_format"]
1703 else: # autodiscover based on extension
sousaedu80135b92021-02-17 15:05:18 +01001704 if image_dict["location"].endswith(".qcow2"):
tierno1ec592d2020-06-16 15:29:47 +00001705 disk_format = "qcow2"
sousaedu80135b92021-02-17 15:05:18 +01001706 elif image_dict["location"].endswith(".vhd"):
tierno1ec592d2020-06-16 15:29:47 +00001707 disk_format = "vhd"
sousaedu80135b92021-02-17 15:05:18 +01001708 elif image_dict["location"].endswith(".vmdk"):
tierno1ec592d2020-06-16 15:29:47 +00001709 disk_format = "vmdk"
sousaedu80135b92021-02-17 15:05:18 +01001710 elif image_dict["location"].endswith(".vdi"):
tierno1ec592d2020-06-16 15:29:47 +00001711 disk_format = "vdi"
sousaedu80135b92021-02-17 15:05:18 +01001712 elif image_dict["location"].endswith(".iso"):
tierno1ec592d2020-06-16 15:29:47 +00001713 disk_format = "iso"
sousaedu80135b92021-02-17 15:05:18 +01001714 elif image_dict["location"].endswith(".aki"):
tierno1ec592d2020-06-16 15:29:47 +00001715 disk_format = "aki"
sousaedu80135b92021-02-17 15:05:18 +01001716 elif image_dict["location"].endswith(".ari"):
tierno1ec592d2020-06-16 15:29:47 +00001717 disk_format = "ari"
sousaedu80135b92021-02-17 15:05:18 +01001718 elif image_dict["location"].endswith(".ami"):
tierno1ec592d2020-06-16 15:29:47 +00001719 disk_format = "ami"
tierno7edb6752016-03-21 17:37:52 +01001720 else:
tierno1ec592d2020-06-16 15:29:47 +00001721 disk_format = "raw"
sousaedu80135b92021-02-17 15:05:18 +01001722
1723 self.logger.debug(
1724 "new_image: '%s' loading from '%s'",
1725 image_dict["name"],
1726 image_dict["location"],
1727 )
shashankjain3c83a212018-10-04 13:05:46 +05301728 if self.vim_type == "VIO":
1729 container_format = "bare"
sousaedu80135b92021-02-17 15:05:18 +01001730 if "container_format" in image_dict:
1731 container_format = image_dict["container_format"]
1732
1733 new_image = self.glance.images.create(
1734 name=image_dict["name"],
1735 container_format=container_format,
1736 disk_format=disk_format,
1737 )
shashankjain3c83a212018-10-04 13:05:46 +05301738 else:
sousaedu80135b92021-02-17 15:05:18 +01001739 new_image = self.glance.images.create(name=image_dict["name"])
1740
1741 if image_dict["location"].startswith("http"):
tierno1beea862018-07-11 15:47:37 +02001742 # TODO there is not a method to direct download. It must be downloaded locally with requests
tierno72774862020-05-04 11:44:15 +00001743 raise vimconn.VimConnNotImplemented("Cannot create image from URL")
tierno1ec592d2020-06-16 15:29:47 +00001744 else: # local path
sousaedu80135b92021-02-17 15:05:18 +01001745 with open(image_dict["location"]) as fimage:
tierno1beea862018-07-11 15:47:37 +02001746 self.glance.images.upload(new_image.id, fimage)
sousaedu80135b92021-02-17 15:05:18 +01001747 # new_image = self.glancev1.images.create(name=image_dict["name"], is_public=
1748 # image_dict.get("public","yes")=="yes",
tierno1beea862018-07-11 15:47:37 +02001749 # container_format="bare", data=fimage, disk_format=disk_format)
sousaedu80135b92021-02-17 15:05:18 +01001750
1751 metadata_to_load = image_dict.get("metadata")
1752
1753 # TODO location is a reserved word for current openstack versions. fixed for VIO please check
tierno1ec592d2020-06-16 15:29:47 +00001754 # for openstack
shashankjain3c83a212018-10-04 13:05:46 +05301755 if self.vim_type == "VIO":
sousaedu80135b92021-02-17 15:05:18 +01001756 metadata_to_load["upload_location"] = image_dict["location"]
shashankjain3c83a212018-10-04 13:05:46 +05301757 else:
sousaedu80135b92021-02-17 15:05:18 +01001758 metadata_to_load["location"] = image_dict["location"]
1759
tierno1beea862018-07-11 15:47:37 +02001760 self.glance.images.update(new_image.id, **metadata_to_load)
sousaedu80135b92021-02-17 15:05:18 +01001761
tiernoae4a8d12016-07-08 12:30:39 +02001762 return new_image.id
sousaedu80135b92021-02-17 15:05:18 +01001763 except (
sousaedu80135b92021-02-17 15:05:18 +01001764 HTTPException,
1765 gl1Exceptions.HTTPException,
1766 gl1Exceptions.CommunicationError,
1767 ConnectionError,
1768 ) as e:
tierno1ec592d2020-06-16 15:29:47 +00001769 if retry == max_retries:
tiernoae4a8d12016-07-08 12:30:39 +02001770 continue
sousaedu80135b92021-02-17 15:05:18 +01001771
tiernoae4a8d12016-07-08 12:30:39 +02001772 self._format_exception(e)
tierno1ec592d2020-06-16 15:29:47 +00001773 except IOError as e: # can not open the file
sousaedu80135b92021-02-17 15:05:18 +01001774 raise vimconn.VimConnConnectionException(
1775 "{}: {} for {}".format(type(e).__name__, e, image_dict["location"]),
1776 http_code=vimconn.HTTP_Bad_Request,
1777 )
gatici335a06a2023-07-26 00:34:04 +03001778 except Exception as e:
1779 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001780
gatici335a06a2023-07-26 00:34:04 +03001781 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001782 def delete_image(self, image_id):
sousaedu80135b92021-02-17 15:05:18 +01001783 """Deletes a tenant image from openstack VIM. Returns the old id"""
tiernoae4a8d12016-07-08 12:30:39 +02001784 try:
1785 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001786 self.glance.images.delete(image_id)
sousaedu80135b92021-02-17 15:05:18 +01001787
tiernoae4a8d12016-07-08 12:30:39 +02001788 return image_id
gatici335a06a2023-07-26 00:34:04 +03001789 except gl1Exceptions.NotFound as e:
1790 # If image is not found, it does not raise.
1791 self.logger.warning(
1792 f"Error deleting image: {image_id} is not found, {str(e)}"
1793 )
tiernoae4a8d12016-07-08 12:30:39 +02001794
gatici335a06a2023-07-26 00:34:04 +03001795 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001796 def get_image_id_from_path(self, path):
tierno1ec592d2020-06-16 15:29:47 +00001797 """Get the image id from image path in the VIM database. Returns the image_id"""
gatici335a06a2023-07-26 00:34:04 +03001798 self._reload_connection()
1799 images = self.glance.images.list()
sousaedu80135b92021-02-17 15:05:18 +01001800
gatici335a06a2023-07-26 00:34:04 +03001801 for image in images:
1802 if image.metadata.get("location") == path:
1803 return image.id
sousaedu80135b92021-02-17 15:05:18 +01001804
gatici335a06a2023-07-26 00:34:04 +03001805 raise vimconn.VimConnNotFoundException(
1806 "image with location '{}' not found".format(path)
1807 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001808
garciadeblasb69fa9f2016-09-28 12:04:10 +02001809 def get_image_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001810 """Obtain tenant images from VIM
garciadeblasb69fa9f2016-09-28 12:04:10 +02001811 Filter_dict can be:
1812 id: image id
1813 name: image name
1814 checksum: image checksum
1815 Returns the image list of dictionaries:
1816 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1817 List can be empty
tierno1ec592d2020-06-16 15:29:47 +00001818 """
garciadeblasb69fa9f2016-09-28 12:04:10 +02001819 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
1820 try:
1821 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001822 # filter_dict_os = filter_dict.copy()
1823 # First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +02001824 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +02001825 filtered_list = []
sousaedu80135b92021-02-17 15:05:18 +01001826
garciadeblasb69fa9f2016-09-28 12:04:10 +02001827 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +02001828 try:
tierno1beea862018-07-11 15:47:37 +02001829 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
1830 continue
sousaedu80135b92021-02-17 15:05:18 +01001831
tierno1beea862018-07-11 15:47:37 +02001832 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
1833 continue
sousaedu80135b92021-02-17 15:05:18 +01001834
1835 if (
1836 filter_dict.get("checksum")
1837 and image["checksum"] != filter_dict["checksum"]
1838 ):
tierno1beea862018-07-11 15:47:37 +02001839 continue
1840
1841 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +02001842 except gl1Exceptions.HTTPNotFound:
1843 pass
sousaedu80135b92021-02-17 15:05:18 +01001844
garciadeblasb69fa9f2016-09-28 12:04:10 +02001845 return filtered_list
gatici335a06a2023-07-26 00:34:04 +03001846
sousaedu80135b92021-02-17 15:05:18 +01001847 except (
1848 ksExceptions.ClientException,
1849 nvExceptions.ClientException,
1850 gl1Exceptions.CommunicationError,
1851 ConnectionError,
1852 ) as e:
garciadeblasb69fa9f2016-09-28 12:04:10 +02001853 self._format_exception(e)
1854
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001855 def __wait_for_vm(self, vm_id, status):
1856 """wait until vm is in the desired status and return True.
1857 If the VM gets in ERROR status, return false.
1858 If the timeout is reached generate an exception"""
1859 elapsed_time = 0
1860 while elapsed_time < server_timeout:
1861 vm_status = self.nova.servers.get(vm_id).status
sousaedu80135b92021-02-17 15:05:18 +01001862
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001863 if vm_status == status:
1864 return True
sousaedu80135b92021-02-17 15:05:18 +01001865
1866 if vm_status == "ERROR":
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001867 return False
sousaedu80135b92021-02-17 15:05:18 +01001868
tierno1df468d2018-07-06 14:25:16 +02001869 time.sleep(5)
1870 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001871
1872 # if we exceeded the timeout rollback
1873 if elapsed_time >= server_timeout:
sousaedu80135b92021-02-17 15:05:18 +01001874 raise vimconn.VimConnException(
1875 "Timeout waiting for instance " + vm_id + " to get " + status,
1876 http_code=vimconn.HTTP_Request_Timeout,
1877 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001878
mirabal29356312017-07-27 12:21:22 +02001879 def _get_openstack_availablity_zones(self):
1880 """
1881 Get from openstack availability zones available
1882 :return:
1883 """
1884 try:
1885 openstack_availability_zone = self.nova.availability_zones.list()
sousaedu80135b92021-02-17 15:05:18 +01001886 openstack_availability_zone = [
1887 str(zone.zoneName)
1888 for zone in openstack_availability_zone
1889 if zone.zoneName != "internal"
1890 ]
1891
mirabal29356312017-07-27 12:21:22 +02001892 return openstack_availability_zone
tierno1ec592d2020-06-16 15:29:47 +00001893 except Exception:
mirabal29356312017-07-27 12:21:22 +02001894 return None
1895
1896 def _set_availablity_zones(self):
1897 """
1898 Set vim availablity zone
1899 :return:
1900 """
sousaedu80135b92021-02-17 15:05:18 +01001901 if "availability_zone" in self.config:
1902 vim_availability_zones = self.config.get("availability_zone")
mirabal29356312017-07-27 12:21:22 +02001903
mirabal29356312017-07-27 12:21:22 +02001904 if isinstance(vim_availability_zones, str):
1905 self.availability_zone = [vim_availability_zones]
1906 elif isinstance(vim_availability_zones, list):
1907 self.availability_zone = vim_availability_zones
1908 else:
1909 self.availability_zone = self._get_openstack_availablity_zones()
Luis Vega25bc6382023-10-05 23:22:04 +00001910 if "storage_availability_zone" in self.config:
1911 self.storage_availability_zone = self.config.get(
1912 "storage_availability_zone"
1913 )
mirabal29356312017-07-27 12:21:22 +02001914
sousaedu80135b92021-02-17 15:05:18 +01001915 def _get_vm_availability_zone(
1916 self, availability_zone_index, availability_zone_list
1917 ):
mirabal29356312017-07-27 12:21:22 +02001918 """
tierno5a3273c2017-08-29 11:43:46 +02001919 Return thge availability zone to be used by the created VM.
1920 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +02001921 """
tierno5a3273c2017-08-29 11:43:46 +02001922 if availability_zone_index is None:
sousaedu80135b92021-02-17 15:05:18 +01001923 if not self.config.get("availability_zone"):
tierno5a3273c2017-08-29 11:43:46 +02001924 return None
sousaedu80135b92021-02-17 15:05:18 +01001925 elif isinstance(self.config.get("availability_zone"), str):
1926 return self.config["availability_zone"]
tierno5a3273c2017-08-29 11:43:46 +02001927 else:
1928 # TODO consider using a different parameter at config for default AV and AV list match
sousaedu80135b92021-02-17 15:05:18 +01001929 return self.config["availability_zone"][0]
mirabal29356312017-07-27 12:21:22 +02001930
tierno5a3273c2017-08-29 11:43:46 +02001931 vim_availability_zones = self.availability_zone
1932 # check if VIM offer enough availability zones describe in the VNFD
sousaedu80135b92021-02-17 15:05:18 +01001933 if vim_availability_zones and len(availability_zone_list) <= len(
1934 vim_availability_zones
1935 ):
tierno5a3273c2017-08-29 11:43:46 +02001936 # check if all the names of NFV AV match VIM AV names
1937 match_by_index = False
1938 for av in availability_zone_list:
1939 if av not in vim_availability_zones:
1940 match_by_index = True
1941 break
sousaedu80135b92021-02-17 15:05:18 +01001942
tierno5a3273c2017-08-29 11:43:46 +02001943 if match_by_index:
1944 return vim_availability_zones[availability_zone_index]
1945 else:
1946 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +02001947 else:
sousaedu80135b92021-02-17 15:05:18 +01001948 raise vimconn.VimConnConflictException(
1949 "No enough availability zones at VIM for this deployment"
1950 )
mirabal29356312017-07-27 12:21:22 +02001951
kayal2001342ee282024-11-28 11:56:49 +05301952 def _prepare_port_dict_security_groups(
1953 self, net: dict, port_dict: dict, security_group_name=None
1954 ) -> None:
Gulsum Atici26f73662022-10-27 15:18:27 +03001955 """Fill up the security_groups in the port_dict.
1956
1957 Args:
1958 net (dict): Network details
1959 port_dict (dict): Port details
1960
1961 """
1962 if (
1963 self.config.get("security_groups")
1964 and net.get("port_security") is not False
1965 and not self.config.get("no_port_security_extension")
1966 ):
1967 if not self.security_groups_id:
1968 self._get_ids_from_name()
1969
1970 port_dict["security_groups"] = self.security_groups_id
1971
kayal2001342ee282024-11-28 11:56:49 +05301972 if security_group_name is not None:
1973 self._get_ids_from_name(security_group_name)
1974 port_dict["security_groups"] = self.security_groups_id
1975
Gulsum Atici26f73662022-10-27 15:18:27 +03001976 def _prepare_port_dict_binding(self, net: dict, port_dict: dict) -> None:
1977 """Fill up the network binding depending on network type in the port_dict.
1978
1979 Args:
1980 net (dict): Network details
1981 port_dict (dict): Port details
1982
1983 """
1984 if not net.get("type"):
1985 raise vimconn.VimConnException("Type is missing in the network details.")
1986
1987 if net["type"] == "virtual":
1988 pass
1989
1990 # For VF
1991 elif net["type"] == "VF" or net["type"] == "SR-IOV":
Gulsum Atici26f73662022-10-27 15:18:27 +03001992 port_dict["binding:vnic_type"] = "direct"
1993
1994 # VIO specific Changes
1995 if self.vim_type == "VIO":
1996 # Need to create port with port_security_enabled = False and no-security-groups
1997 port_dict["port_security_enabled"] = False
1998 port_dict["provider_security_groups"] = []
1999 port_dict["security_groups"] = []
2000
2001 else:
2002 # For PT PCI-PASSTHROUGH
2003 port_dict["binding:vnic_type"] = "direct-physical"
2004
2005 @staticmethod
2006 def _set_fixed_ip(new_port: dict, net: dict) -> None:
2007 """Set the "ip" parameter in net dictionary.
2008
2009 Args:
2010 new_port (dict): New created port
2011 net (dict): Network details
2012
2013 """
2014 fixed_ips = new_port["port"].get("fixed_ips")
2015
2016 if fixed_ips:
2017 net["ip"] = fixed_ips[0].get("ip_address")
2018 else:
2019 net["ip"] = None
2020
2021 @staticmethod
2022 def _prepare_port_dict_mac_ip_addr(net: dict, port_dict: dict) -> None:
2023 """Fill up the mac_address and fixed_ips in port_dict.
2024
2025 Args:
2026 net (dict): Network details
2027 port_dict (dict): Port details
2028
2029 """
2030 if net.get("mac_address"):
2031 port_dict["mac_address"] = net["mac_address"]
2032
elumalai370e36b2023-04-25 16:22:56 +05302033 ip_dual_list = []
2034 if ip_list := net.get("ip_address"):
2035 if not isinstance(ip_list, list):
2036 ip_list = [ip_list]
2037 for ip in ip_list:
2038 ip_dict = {"ip_address": ip}
2039 ip_dual_list.append(ip_dict)
2040 port_dict["fixed_ips"] = ip_dual_list
Gulsum Atici26f73662022-10-27 15:18:27 +03002041 # TODO add "subnet_id": <subnet_id>
2042
2043 def _create_new_port(self, port_dict: dict, created_items: dict, net: dict) -> Dict:
2044 """Create new port using neutron.
2045
2046 Args:
2047 port_dict (dict): Port details
2048 created_items (dict): All created items
2049 net (dict): Network details
2050
2051 Returns:
2052 new_port (dict): New created port
2053
2054 """
2055 new_port = self.neutron.create_port({"port": port_dict})
2056 created_items["port:" + str(new_port["port"]["id"])] = True
elumalaie17cd942023-04-28 18:04:24 +05302057 net["mac_address"] = new_port["port"]["mac_address"]
Gulsum Atici26f73662022-10-27 15:18:27 +03002058 net["vim_id"] = new_port["port"]["id"]
2059
2060 return new_port
2061
2062 def _create_port(
kayal2001342ee282024-11-28 11:56:49 +05302063 self, net: dict, name: str, created_items: dict, security_group_name=None
Gulsum Atici26f73662022-10-27 15:18:27 +03002064 ) -> Tuple[dict, dict]:
2065 """Create port using net details.
2066
2067 Args:
2068 net (dict): Network details
2069 name (str): Name to be used as network name if net dict does not include name
2070 created_items (dict): All created items
2071
2072 Returns:
2073 new_port, port New created port, port dictionary
2074
2075 """
2076
2077 port_dict = {
2078 "network_id": net["net_id"],
2079 "name": net.get("name"),
2080 "admin_state_up": True,
2081 }
2082
2083 if not port_dict["name"]:
2084 port_dict["name"] = name
2085
kayal2001342ee282024-11-28 11:56:49 +05302086 self._prepare_port_dict_security_groups(net, port_dict, security_group_name)
Gulsum Atici26f73662022-10-27 15:18:27 +03002087
2088 self._prepare_port_dict_binding(net, port_dict)
2089
2090 vimconnector._prepare_port_dict_mac_ip_addr(net, port_dict)
2091
2092 new_port = self._create_new_port(port_dict, created_items, net)
2093
2094 vimconnector._set_fixed_ip(new_port, net)
2095
2096 port = {"port-id": new_port["port"]["id"]}
2097
2098 if float(self.nova.api_version.get_string()) >= 2.32:
2099 port["tag"] = new_port["port"]["name"]
2100
2101 return new_port, port
2102
2103 def _prepare_network_for_vminstance(
2104 self,
2105 name: str,
2106 net_list: list,
2107 created_items: dict,
2108 net_list_vim: list,
2109 external_network: list,
2110 no_secured_ports: list,
kayal2001342ee282024-11-28 11:56:49 +05302111 security_group_name=None,
Gulsum Atici26f73662022-10-27 15:18:27 +03002112 ) -> None:
2113 """Create port and fill up net dictionary for new VM instance creation.
2114
2115 Args:
2116 name (str): Name of network
2117 net_list (list): List of networks
2118 created_items (dict): All created items belongs to a VM
2119 net_list_vim (list): List of ports
2120 external_network (list): List of external-networks
2121 no_secured_ports (list): Port security disabled ports
2122 """
2123
2124 self._reload_connection()
2125
2126 for net in net_list:
2127 # Skip non-connected iface
2128 if not net.get("net_id"):
2129 continue
2130
kayal2001342ee282024-11-28 11:56:49 +05302131 new_port, port = self._create_port(
2132 net, name, created_items, security_group_name
2133 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002134
2135 net_list_vim.append(port)
2136
2137 if net.get("floating_ip", False):
2138 net["exit_on_floating_ip_error"] = True
2139 external_network.append(net)
2140
2141 elif net["use"] == "mgmt" and self.config.get("use_floating_ip"):
2142 net["exit_on_floating_ip_error"] = False
2143 external_network.append(net)
2144 net["floating_ip"] = self.config.get("use_floating_ip")
2145
2146 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic
2147 # is dropped. As a workaround we wait until the VM is active and then disable the port-security
2148 if net.get("port_security") is False and not self.config.get(
2149 "no_port_security_extension"
2150 ):
2151 no_secured_ports.append(
2152 (
2153 new_port["port"]["id"],
2154 net.get("port_security_disable_strategy"),
2155 )
2156 )
2157
2158 def _prepare_persistent_root_volumes(
2159 self,
2160 name: str,
Luis Vega25bc6382023-10-05 23:22:04 +00002161 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002162 disk: dict,
2163 base_disk_index: int,
2164 block_device_mapping: dict,
2165 existing_vim_volumes: list,
2166 created_items: dict,
2167 ) -> Optional[str]:
2168 """Prepare persistent root volumes for new VM instance.
2169
2170 Args:
2171 name (str): Name of VM instance
Luis Vega25bc6382023-10-05 23:22:04 +00002172 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002173 disk (dict): Disk details
2174 base_disk_index (int): Disk index
2175 block_device_mapping (dict): Block device details
2176 existing_vim_volumes (list): Existing disk details
2177 created_items (dict): All created items belongs to VM
2178
2179 Returns:
2180 boot_volume_id (str): ID of boot volume
2181
2182 """
garciadeblas6a0147f2024-08-19 08:02:04 +00002183 self.logger.debug("Preparing root persistent volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002184 # Disk may include only vim_volume_id or only vim_id."
2185 # Use existing persistent root volume finding with volume_id or vim_id
2186 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002187 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002188 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2189 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002190 else:
2191 # Create persistent root volume
2192 volume = self.cinder.volumes.create(
2193 size=disk["size"],
2194 name=name + "vd" + chr(base_disk_index),
2195 imageRef=disk["image_id"],
2196 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002197 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002198 )
2199 boot_volume_id = volume.id
aticig2f4ab6c2022-09-03 18:15:20 +03002200 self.update_block_device_mapping(
2201 volume=volume,
2202 block_device_mapping=block_device_mapping,
2203 base_disk_index=base_disk_index,
2204 disk=disk,
2205 created_items=created_items,
2206 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002207
2208 return boot_volume_id
2209
aticig2f4ab6c2022-09-03 18:15:20 +03002210 @staticmethod
2211 def update_block_device_mapping(
2212 volume: object,
2213 block_device_mapping: dict,
2214 base_disk_index: int,
2215 disk: dict,
2216 created_items: dict,
2217 ) -> None:
2218 """Add volume information to block device mapping dict.
2219 Args:
2220 volume (object): Created volume object
2221 block_device_mapping (dict): Block device details
2222 base_disk_index (int): Disk index
2223 disk (dict): Disk details
2224 created_items (dict): All created items belongs to VM
2225 """
2226 if not volume:
2227 raise vimconn.VimConnException("Volume is empty.")
2228
2229 if not hasattr(volume, "id"):
2230 raise vimconn.VimConnException(
2231 "Created volume is not valid, does not have id attribute."
2232 )
2233
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002234 block_device_mapping["vd" + chr(base_disk_index)] = volume.id
2235 if disk.get("multiattach"): # multiattach volumes do not belong to VDUs
2236 return
aticig2f4ab6c2022-09-03 18:15:20 +03002237 volume_txt = "volume:" + str(volume.id)
2238 if disk.get("keep"):
2239 volume_txt += ":keep"
2240 created_items[volume_txt] = True
aticig2f4ab6c2022-09-03 18:15:20 +03002241
gatici335a06a2023-07-26 00:34:04 +03002242 @catch_any_exception
vegall364627c2023-03-17 15:09:50 +00002243 def new_shared_volumes(self, shared_volume_data) -> (str, str):
garciadeblas6a0147f2024-08-19 08:02:04 +00002244 self.logger.debug("Creating new shared volume")
Luis Vega25bc6382023-10-05 23:22:04 +00002245 availability_zone = (
2246 self.storage_availability_zone
2247 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002248 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002249 )
gatici335a06a2023-07-26 00:34:04 +03002250 volume = self.cinder.volumes.create(
2251 size=shared_volume_data["size"],
2252 name=shared_volume_data["name"],
2253 volume_type="multiattach",
Luis Vega25bc6382023-10-05 23:22:04 +00002254 availability_zone=availability_zone,
gatici335a06a2023-07-26 00:34:04 +03002255 )
2256 return volume.name, volume.id
vegall364627c2023-03-17 15:09:50 +00002257
2258 def _prepare_shared_volumes(
2259 self,
2260 name: str,
2261 disk: dict,
2262 base_disk_index: int,
2263 block_device_mapping: dict,
2264 existing_vim_volumes: list,
2265 created_items: dict,
2266 ):
garciadeblas6a0147f2024-08-19 08:02:04 +00002267 self.logger.debug("Preparing shared volumes")
vegall364627c2023-03-17 15:09:50 +00002268 volumes = {volume.name: volume.id for volume in self.cinder.volumes.list()}
2269 if volumes.get(disk["name"]):
2270 sv_id = volumes[disk["name"]]
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002271 max_retries = 3
2272 vol_status = ""
2273 # If this is not the first VM to attach the volume, volume status may be "reserved" for a short time
2274 while max_retries:
2275 max_retries -= 1
2276 volume = self.cinder.volumes.get(sv_id)
2277 vol_status = volume.status
2278 if volume.status not in ("in-use", "available"):
2279 time.sleep(5)
2280 continue
2281 self.update_block_device_mapping(
2282 volume=volume,
2283 block_device_mapping=block_device_mapping,
2284 base_disk_index=base_disk_index,
2285 disk=disk,
2286 created_items=created_items,
2287 )
2288 return
2289 raise vimconn.VimConnException(
2290 "Shared volume is not prepared, status is: {}".format(vol_status),
2291 http_code=vimconn.HTTP_Internal_Server_Error,
vegall364627c2023-03-17 15:09:50 +00002292 )
2293
Gulsum Atici26f73662022-10-27 15:18:27 +03002294 def _prepare_non_root_persistent_volumes(
2295 self,
2296 name: str,
2297 disk: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002298 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002299 block_device_mapping: dict,
2300 base_disk_index: int,
2301 existing_vim_volumes: list,
2302 created_items: dict,
2303 ) -> None:
2304 """Prepare persistent volumes for new VM instance.
2305
2306 Args:
2307 name (str): Name of VM instance
2308 disk (dict): Disk details
Luis Vega25bc6382023-10-05 23:22:04 +00002309 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002310 block_device_mapping (dict): Block device details
2311 base_disk_index (int): Disk index
2312 existing_vim_volumes (list): Existing disk details
2313 created_items (dict): All created items belongs to VM
2314 """
2315 # Non-root persistent volumes
2316 # Disk may include only vim_volume_id or only vim_id."
garciadeblas6a0147f2024-08-19 08:02:04 +00002317 self.logger.debug("Preparing non-root persistent volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002318 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002319 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002320 # Use existing persistent volume
2321 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2322 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002323 else:
vegall364627c2023-03-17 15:09:50 +00002324 volume_name = f"{name}vd{chr(base_disk_index)}"
Gulsum Atici26f73662022-10-27 15:18:27 +03002325 volume = self.cinder.volumes.create(
2326 size=disk["size"],
vegall364627c2023-03-17 15:09:50 +00002327 name=volume_name,
Gulsum Atici26f73662022-10-27 15:18:27 +03002328 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002329 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002330 )
aticig2f4ab6c2022-09-03 18:15:20 +03002331 self.update_block_device_mapping(
2332 volume=volume,
2333 block_device_mapping=block_device_mapping,
2334 base_disk_index=base_disk_index,
2335 disk=disk,
2336 created_items=created_items,
2337 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002338
2339 def _wait_for_created_volumes_availability(
2340 self, elapsed_time: int, created_items: dict
2341 ) -> Optional[int]:
2342 """Wait till created volumes become available.
2343
2344 Args:
2345 elapsed_time (int): Passed time while waiting
2346 created_items (dict): All created items belongs to VM
2347
2348 Returns:
2349 elapsed_time (int): Time spent while waiting
2350
2351 """
garciadeblas6a0147f2024-08-19 08:02:04 +00002352 self.logger.debug("Waiting for all created volumes to become available")
Gulsum Atici26f73662022-10-27 15:18:27 +03002353 while elapsed_time < volume_timeout:
garciadeblas6a0147f2024-08-19 08:02:04 +00002354 self.logger.debug("Checking disk availability for created volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002355 for created_item in created_items:
aticig2f4ab6c2022-09-03 18:15:20 +03002356 v, volume_id = (
2357 created_item.split(":")[0],
2358 created_item.split(":")[1],
2359 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002360 if v == "volume":
garciadeblas6a0147f2024-08-19 08:02:04 +00002361 self.logger.debug(f"Checking volume: {volume_id}")
vegall364627c2023-03-17 15:09:50 +00002362 volume = self.cinder.volumes.get(volume_id)
2363 if (
2364 volume.volume_type == "multiattach"
2365 and volume.status == "in-use"
2366 ):
2367 return elapsed_time
2368 elif volume.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002369 break
2370 else:
2371 # All ready: break from while
2372 break
2373
2374 time.sleep(5)
2375 elapsed_time += 5
2376
2377 return elapsed_time
2378
2379 def _wait_for_existing_volumes_availability(
2380 self, elapsed_time: int, existing_vim_volumes: list
2381 ) -> Optional[int]:
2382 """Wait till existing volumes become available.
2383
2384 Args:
2385 elapsed_time (int): Passed time while waiting
2386 existing_vim_volumes (list): Existing volume details
2387
2388 Returns:
2389 elapsed_time (int): Time spent while waiting
2390
2391 """
2392
garciadeblas6a0147f2024-08-19 08:02:04 +00002393 self.logger.debug("Waiting for all existing volumes to become available")
Gulsum Atici26f73662022-10-27 15:18:27 +03002394 while elapsed_time < volume_timeout:
garciadeblas6a0147f2024-08-19 08:02:04 +00002395 self.logger.debug("Checking disk availability for existing volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002396 for volume in existing_vim_volumes:
garciadeblas6a0147f2024-08-19 08:02:04 +00002397 self.logger.debug(f"Checking existing volume: {volume}")
vegall364627c2023-03-17 15:09:50 +00002398 v = self.cinder.volumes.get(volume["id"])
2399 if v.volume_type == "multiattach" and v.status == "in-use":
2400 return elapsed_time
2401 elif v.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002402 break
2403 else: # all ready: break from while
2404 break
2405
2406 time.sleep(5)
2407 elapsed_time += 5
2408
2409 return elapsed_time
2410
2411 def _prepare_disk_for_vminstance(
2412 self,
2413 name: str,
2414 existing_vim_volumes: list,
2415 created_items: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002416 storage_av_zone: list,
Gulsum Atici13d02322022-11-18 00:10:15 +03002417 block_device_mapping: dict,
Gulsum Atici26f73662022-10-27 15:18:27 +03002418 disk_list: list = None,
2419 ) -> None:
2420 """Prepare all volumes for new VM instance.
2421
2422 Args:
2423 name (str): Name of Instance
2424 existing_vim_volumes (list): List of existing volumes
2425 created_items (dict): All created items belongs to VM
Luis Vega25bc6382023-10-05 23:22:04 +00002426 storage_av_zone (list): Storage availability zone
Gulsum Atici13d02322022-11-18 00:10:15 +03002427 block_device_mapping (dict): Block devices to be attached to VM
Gulsum Atici26f73662022-10-27 15:18:27 +03002428 disk_list (list): List of disks
2429
2430 """
2431 # Create additional volumes in case these are present in disk_list
garciadeblas6a0147f2024-08-19 08:02:04 +00002432 self.logger.debug("Preparing disks for VM instances")
Gulsum Atici26f73662022-10-27 15:18:27 +03002433 base_disk_index = ord("b")
2434 boot_volume_id = None
2435 elapsed_time = 0
Gulsum Atici26f73662022-10-27 15:18:27 +03002436 for disk in disk_list:
garciadeblas6a0147f2024-08-19 08:02:04 +00002437 self.logger.debug(f"Disk: {disk}")
Gulsum Atici26f73662022-10-27 15:18:27 +03002438 if "image_id" in disk:
2439 # Root persistent volume
2440 base_disk_index = ord("a")
2441 boot_volume_id = self._prepare_persistent_root_volumes(
2442 name=name,
Luis Vega25bc6382023-10-05 23:22:04 +00002443 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002444 disk=disk,
2445 base_disk_index=base_disk_index,
2446 block_device_mapping=block_device_mapping,
2447 existing_vim_volumes=existing_vim_volumes,
2448 created_items=created_items,
2449 )
vegall364627c2023-03-17 15:09:50 +00002450 elif disk.get("multiattach"):
2451 self._prepare_shared_volumes(
2452 name=name,
2453 disk=disk,
2454 base_disk_index=base_disk_index,
2455 block_device_mapping=block_device_mapping,
2456 existing_vim_volumes=existing_vim_volumes,
2457 created_items=created_items,
2458 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002459 else:
2460 # Non-root persistent volume
2461 self._prepare_non_root_persistent_volumes(
2462 name=name,
2463 disk=disk,
Luis Vega25bc6382023-10-05 23:22:04 +00002464 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002465 block_device_mapping=block_device_mapping,
2466 base_disk_index=base_disk_index,
2467 existing_vim_volumes=existing_vim_volumes,
2468 created_items=created_items,
2469 )
2470 base_disk_index += 1
2471
2472 # Wait until created volumes are with status available
2473 elapsed_time = self._wait_for_created_volumes_availability(
2474 elapsed_time, created_items
2475 )
2476 # Wait until existing volumes in vim are with status available
2477 elapsed_time = self._wait_for_existing_volumes_availability(
2478 elapsed_time, existing_vim_volumes
2479 )
2480 # If we exceeded the timeout rollback
2481 if elapsed_time >= volume_timeout:
2482 raise vimconn.VimConnException(
2483 "Timeout creating volumes for instance " + name,
2484 http_code=vimconn.HTTP_Request_Timeout,
2485 )
2486 if boot_volume_id:
2487 self.cinder.volumes.set_bootable(boot_volume_id, True)
2488
2489 def _find_the_external_network_for_floating_ip(self):
2490 """Get the external network ip in order to create floating IP.
2491
2492 Returns:
2493 pool_id (str): External network pool ID
2494
2495 """
2496
2497 # Find the external network
2498 external_nets = list()
2499
2500 for net in self.neutron.list_networks()["networks"]:
2501 if net["router:external"]:
2502 external_nets.append(net)
2503
2504 if len(external_nets) == 0:
2505 raise vimconn.VimConnException(
2506 "Cannot create floating_ip automatically since "
2507 "no external network is present",
2508 http_code=vimconn.HTTP_Conflict,
2509 )
2510
2511 if len(external_nets) > 1:
2512 raise vimconn.VimConnException(
2513 "Cannot create floating_ip automatically since "
2514 "multiple external networks are present",
2515 http_code=vimconn.HTTP_Conflict,
2516 )
2517
2518 # Pool ID
2519 return external_nets[0].get("id")
2520
2521 def _neutron_create_float_ip(self, param: dict, created_items: dict) -> None:
2522 """Trigger neutron to create a new floating IP using external network ID.
2523
2524 Args:
2525 param (dict): Input parameters to create a floating IP
2526 created_items (dict): All created items belongs to new VM instance
2527
2528 Raises:
2529
2530 VimConnException
2531 """
2532 try:
2533 self.logger.debug("Creating floating IP")
2534 new_floating_ip = self.neutron.create_floatingip(param)
2535 free_floating_ip = new_floating_ip["floatingip"]["id"]
2536 created_items["floating_ip:" + str(free_floating_ip)] = True
2537
2538 except Exception as e:
2539 raise vimconn.VimConnException(
2540 type(e).__name__ + ": Cannot create new floating_ip " + str(e),
2541 http_code=vimconn.HTTP_Conflict,
2542 )
2543
2544 def _create_floating_ip(
2545 self, floating_network: dict, server: object, created_items: dict
2546 ) -> None:
2547 """Get the available Pool ID and create a new floating IP.
2548
2549 Args:
2550 floating_network (dict): Dict including external network ID
2551 server (object): Server object
2552 created_items (dict): All created items belongs to new VM instance
2553
2554 """
2555
2556 # Pool_id is available
2557 if (
2558 isinstance(floating_network["floating_ip"], str)
2559 and floating_network["floating_ip"].lower() != "true"
2560 ):
2561 pool_id = floating_network["floating_ip"]
2562
2563 # Find the Pool_id
2564 else:
2565 pool_id = self._find_the_external_network_for_floating_ip()
2566
2567 param = {
2568 "floatingip": {
2569 "floating_network_id": pool_id,
2570 "tenant_id": server.tenant_id,
2571 }
2572 }
2573
2574 self._neutron_create_float_ip(param, created_items)
2575
2576 def _find_floating_ip(
2577 self,
2578 server: object,
2579 floating_ips: list,
2580 floating_network: dict,
2581 ) -> Optional[str]:
2582 """Find the available free floating IPs if there are.
2583
2584 Args:
2585 server (object): Server object
2586 floating_ips (list): List of floating IPs
2587 floating_network (dict): Details of floating network such as ID
2588
2589 Returns:
2590 free_floating_ip (str): Free floating ip address
2591
2592 """
2593 for fip in floating_ips:
2594 if fip.get("port_id") or fip.get("tenant_id") != server.tenant_id:
2595 continue
2596
2597 if isinstance(floating_network["floating_ip"], str):
2598 if fip.get("floating_network_id") != floating_network["floating_ip"]:
2599 continue
2600
2601 return fip["id"]
2602
2603 def _assign_floating_ip(
2604 self, free_floating_ip: str, floating_network: dict
2605 ) -> Dict:
2606 """Assign the free floating ip address to port.
2607
2608 Args:
2609 free_floating_ip (str): Floating IP to be assigned
2610 floating_network (dict): ID of floating network
2611
2612 Returns:
2613 fip (dict) (dict): Floating ip details
2614
2615 """
2616 # The vim_id key contains the neutron.port_id
2617 self.neutron.update_floatingip(
2618 free_floating_ip,
2619 {"floatingip": {"port_id": floating_network["vim_id"]}},
2620 )
2621 # For race condition ensure not re-assigned to other VM after 5 seconds
2622 time.sleep(5)
2623
2624 return self.neutron.show_floatingip(free_floating_ip)
2625
2626 def _get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002627 self, server: object, floating_network: dict
Gulsum Atici26f73662022-10-27 15:18:27 +03002628 ) -> Optional[str]:
2629 """Get the free floating IP address.
2630
2631 Args:
2632 server (object): Server Object
2633 floating_network (dict): Floating network details
Gulsum Atici26f73662022-10-27 15:18:27 +03002634
2635 Returns:
2636 free_floating_ip (str): Free floating ip addr
2637
2638 """
2639
2640 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
2641
2642 # Randomize
2643 random.shuffle(floating_ips)
2644
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002645 return self._find_floating_ip(server, floating_ips, floating_network)
Gulsum Atici26f73662022-10-27 15:18:27 +03002646
2647 def _prepare_external_network_for_vminstance(
2648 self,
2649 external_network: list,
2650 server: object,
2651 created_items: dict,
2652 vm_start_time: float,
2653 ) -> None:
2654 """Assign floating IP address for VM instance.
2655
2656 Args:
2657 external_network (list): ID of External network
2658 server (object): Server Object
2659 created_items (dict): All created items belongs to new VM instance
2660 vm_start_time (float): Time as a floating point number expressed in seconds since the epoch, in UTC
2661
2662 Raises:
2663 VimConnException
2664
2665 """
2666 for floating_network in external_network:
2667 try:
2668 assigned = False
2669 floating_ip_retries = 3
2670 # In case of RO in HA there can be conflicts, two RO trying to assign same floating IP, so retry
2671 # several times
2672 while not assigned:
Gulsum Atici26f73662022-10-27 15:18:27 +03002673 free_floating_ip = self._get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002674 server, floating_network
Gulsum Atici26f73662022-10-27 15:18:27 +03002675 )
2676
2677 if not free_floating_ip:
2678 self._create_floating_ip(
2679 floating_network, server, created_items
2680 )
2681
2682 try:
2683 # For race condition ensure not already assigned
2684 fip = self.neutron.show_floatingip(free_floating_ip)
2685
2686 if fip["floatingip"].get("port_id"):
2687 continue
2688
2689 # Assign floating ip
2690 fip = self._assign_floating_ip(
2691 free_floating_ip, floating_network
2692 )
2693
2694 if fip["floatingip"]["port_id"] != floating_network["vim_id"]:
2695 self.logger.warning(
2696 "floating_ip {} re-assigned to other port".format(
2697 free_floating_ip
2698 )
2699 )
2700 continue
2701
2702 self.logger.debug(
2703 "Assigned floating_ip {} to VM {}".format(
2704 free_floating_ip, server.id
2705 )
2706 )
2707
2708 assigned = True
2709
2710 except Exception as e:
2711 # Openstack need some time after VM creation to assign an IP. So retry if fails
2712 vm_status = self.nova.servers.get(server.id).status
2713
2714 if vm_status not in ("ACTIVE", "ERROR"):
2715 if time.time() - vm_start_time < server_timeout:
2716 time.sleep(5)
2717 continue
2718 elif floating_ip_retries > 0:
2719 floating_ip_retries -= 1
2720 continue
2721
2722 raise vimconn.VimConnException(
2723 "Cannot create floating_ip: {} {}".format(
2724 type(e).__name__, e
2725 ),
2726 http_code=vimconn.HTTP_Conflict,
2727 )
2728
2729 except Exception as e:
2730 if not floating_network["exit_on_floating_ip_error"]:
2731 self.logger.error("Cannot create floating_ip. %s", str(e))
2732 continue
2733
2734 raise
2735
2736 def _update_port_security_for_vminstance(
2737 self,
2738 no_secured_ports: list,
2739 server: object,
2740 ) -> None:
2741 """Updates the port security according to no_secured_ports list.
2742
2743 Args:
2744 no_secured_ports (list): List of ports that security will be disabled
2745 server (object): Server Object
2746
2747 Raises:
2748 VimConnException
2749
2750 """
2751 # Wait until the VM is active and then disable the port-security
2752 if no_secured_ports:
2753 self.__wait_for_vm(server.id, "ACTIVE")
2754
2755 for port in no_secured_ports:
2756 port_update = {
2757 "port": {"port_security_enabled": False, "security_groups": None}
2758 }
2759
2760 if port[1] == "allow-address-pairs":
2761 port_update = {
2762 "port": {"allowed_address_pairs": [{"ip_address": "0.0.0.0/0"}]}
2763 }
2764
2765 try:
2766 self.neutron.update_port(port[0], port_update)
2767
2768 except Exception:
Gulsum Atici26f73662022-10-27 15:18:27 +03002769 raise vimconn.VimConnException(
2770 "It was not possible to disable port security for port {}".format(
2771 port[0]
2772 )
2773 )
2774
sousaedu80135b92021-02-17 15:05:18 +01002775 def new_vminstance(
2776 self,
Gulsum Atici26f73662022-10-27 15:18:27 +03002777 name: str,
2778 description: str,
2779 start: bool,
2780 image_id: str,
2781 flavor_id: str,
2782 affinity_group_list: list,
2783 net_list: list,
sousaedu80135b92021-02-17 15:05:18 +01002784 cloud_config=None,
2785 disk_list=None,
2786 availability_zone_index=None,
2787 availability_zone_list=None,
kayal2001342ee282024-11-28 11:56:49 +05302788 security_group_name=None,
Gulsum Atici26f73662022-10-27 15:18:27 +03002789 ) -> tuple:
2790 """Adds a VM instance to VIM.
2791
2792 Args:
2793 name (str): name of VM
2794 description (str): description
2795 start (bool): indicates if VM must start or boot in pause mode. Ignored
2796 image_id (str) image uuid
2797 flavor_id (str) flavor uuid
2798 affinity_group_list (list): list of affinity groups, each one is a dictionary.Ignore if empty.
2799 net_list (list): list of interfaces, each one is a dictionary with:
2800 name: name of network
2801 net_id: network uuid to connect
2802 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
2803 model: interface model, ignored #TODO
2804 mac_address: used for SR-IOV ifaces #TODO for other types
2805 use: 'data', 'bridge', 'mgmt'
2806 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
2807 vim_id: filled/added by this function
2808 floating_ip: True/False (or it can be None)
2809 port_security: True/False
2810 cloud_config (dict): (optional) dictionary with:
2811 key-pairs: (optional) list of strings with the public key to be inserted to the default user
2812 users: (optional) list of users to be inserted, each item is a dict with:
2813 name: (mandatory) user name,
2814 key-pairs: (optional) list of strings with the public key to be inserted to the user
2815 user-data: (optional) string is a text script to be passed directly to cloud-init
2816 config-files: (optional). List of files to be transferred. Each item is a dict with:
2817 dest: (mandatory) string with the destination absolute path
2818 encoding: (optional, by default text). Can be one of:
tierno1d213f42020-04-24 14:02:51 +00002819 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
Gulsum Atici26f73662022-10-27 15:18:27 +03002820 content : (mandatory) string with the content of the file
2821 permissions: (optional) string with file permissions, typically octal notation '0644'
2822 owner: (optional) file owner, string with the format 'owner:group'
2823 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
2824 disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
2825 image_id: (optional). VIM id of an existing image. If not provided an empty disk must be mounted
2826 size: (mandatory) string with the size of the disk in GB
2827 vim_id: (optional) should use this existing volume id
2828 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
2829 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
tierno5a3273c2017-08-29 11:43:46 +02002830 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01002831 #TODO ip, security groups
Gulsum Atici26f73662022-10-27 15:18:27 +03002832
2833 Returns:
2834 A tuple with the instance identifier and created_items or raises an exception on error
tierno98e909c2017-10-14 13:27:03 +02002835 created_items can be None or a dictionary where this method can include key-values that will be passed to
2836 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
2837 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
2838 as not present.
aticig2f4ab6c2022-09-03 18:15:20 +03002839
tierno98e909c2017-10-14 13:27:03 +02002840 """
sousaedu80135b92021-02-17 15:05:18 +01002841 self.logger.debug(
2842 "new_vminstance input: image='%s' flavor='%s' nics='%s'",
2843 image_id,
2844 flavor_id,
2845 str(net_list),
2846 )
gatici335a06a2023-07-26 00:34:04 +03002847 server = None
2848 created_items = {}
2849 net_list_vim = []
2850 # list of external networks to be connected to instance, later on used to create floating_ip
2851 external_network = []
2852 # List of ports with port-security disabled
2853 no_secured_ports = []
2854 block_device_mapping = {}
2855 existing_vim_volumes = []
2856 server_group_id = None
2857 scheduller_hints = {}
sousaedu80135b92021-02-17 15:05:18 +01002858
tierno7edb6752016-03-21 17:37:52 +01002859 try:
Gulsum Atici26f73662022-10-27 15:18:27 +03002860 # Check the Openstack Connection
2861 self._reload_connection()
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02002862
Gulsum Atici26f73662022-10-27 15:18:27 +03002863 # Prepare network list
2864 self._prepare_network_for_vminstance(
2865 name=name,
2866 net_list=net_list,
2867 created_items=created_items,
2868 net_list_vim=net_list_vim,
2869 external_network=external_network,
2870 no_secured_ports=no_secured_ports,
kayal2001342ee282024-11-28 11:56:49 +05302871 security_group_name=security_group_name,
sousaedu80135b92021-02-17 15:05:18 +01002872 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002873
Gulsum Atici26f73662022-10-27 15:18:27 +03002874 # Cloud config
tierno0a1437e2017-10-02 00:17:43 +02002875 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00002876
Gulsum Atici26f73662022-10-27 15:18:27 +03002877 # Get availability Zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002878 self.vm_av_zone = self._get_vm_availability_zone(
Alexis Romero247cc432022-05-12 13:23:25 +02002879 availability_zone_index, availability_zone_list
2880 )
2881
Luis Vega25bc6382023-10-05 23:22:04 +00002882 storage_av_zone = (
2883 self.storage_availability_zone
2884 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002885 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002886 )
2887
tierno1df468d2018-07-06 14:25:16 +02002888 if disk_list:
Gulsum Atici26f73662022-10-27 15:18:27 +03002889 # Prepare disks
2890 self._prepare_disk_for_vminstance(
2891 name=name,
2892 existing_vim_volumes=existing_vim_volumes,
2893 created_items=created_items,
Luis Vega25bc6382023-10-05 23:22:04 +00002894 storage_av_zone=storage_av_zone,
Gulsum Atici13d02322022-11-18 00:10:15 +03002895 block_device_mapping=block_device_mapping,
Gulsum Atici26f73662022-10-27 15:18:27 +03002896 disk_list=disk_list,
2897 )
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002898
2899 if affinity_group_list:
2900 # Only first id on the list will be used. Openstack restriction
2901 server_group_id = affinity_group_list[0]["affinity_group_id"]
2902 scheduller_hints["group"] = server_group_id
2903
sousaedu80135b92021-02-17 15:05:18 +01002904 self.logger.debug(
2905 "nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
2906 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002907 "block_device_mapping={}, server_group={})".format(
sousaedu80135b92021-02-17 15:05:18 +01002908 name,
2909 image_id,
2910 flavor_id,
2911 net_list_vim,
2912 self.config.get("security_groups"),
Luis Vegaafe8df22023-12-01 01:02:12 +00002913 self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002914 self.config.get("keypair"),
2915 userdata,
2916 config_drive,
2917 block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002918 server_group_id,
sousaedu80135b92021-02-17 15:05:18 +01002919 )
2920 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002921 # Create VM
sousaedu80135b92021-02-17 15:05:18 +01002922 server = self.nova.servers.create(
aticigcf14bb12022-05-19 13:03:17 +03002923 name=name,
2924 image=image_id,
2925 flavor=flavor_id,
sousaedu80135b92021-02-17 15:05:18 +01002926 nics=net_list_vim,
2927 security_groups=self.config.get("security_groups"),
2928 # TODO remove security_groups in future versions. Already at neutron port
Luis Vegaafe8df22023-12-01 01:02:12 +00002929 availability_zone=self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002930 key_name=self.config.get("keypair"),
2931 userdata=userdata,
2932 config_drive=config_drive,
2933 block_device_mapping=block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002934 scheduler_hints=scheduller_hints,
Gulsum Atici26f73662022-10-27 15:18:27 +03002935 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002936
tierno326fd5e2018-02-22 11:58:59 +01002937 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002938
Gulsum Atici26f73662022-10-27 15:18:27 +03002939 self._update_port_security_for_vminstance(no_secured_ports, server)
bravof7a1f5252020-10-20 10:27:42 -03002940
Gulsum Atici26f73662022-10-27 15:18:27 +03002941 self._prepare_external_network_for_vminstance(
2942 external_network=external_network,
2943 server=server,
2944 created_items=created_items,
2945 vm_start_time=vm_start_time,
2946 )
montesmoreno2a1fc4e2017-01-09 16:46:04 +00002947
tierno98e909c2017-10-14 13:27:03 +02002948 return server.id, created_items
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002949
2950 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02002951 server_id = None
2952 if server:
2953 server_id = server.id
sousaedu80135b92021-02-17 15:05:18 +01002954
tierno98e909c2017-10-14 13:27:03 +02002955 try:
aticig2f4ab6c2022-09-03 18:15:20 +03002956 created_items = self.remove_keep_tag_from_persistent_volumes(
2957 created_items
2958 )
2959
tierno98e909c2017-10-14 13:27:03 +02002960 self.delete_vminstance(server_id, created_items)
Gulsum Atici26f73662022-10-27 15:18:27 +03002961
tierno98e909c2017-10-14 13:27:03 +02002962 except Exception as e2:
2963 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002964
tiernoae4a8d12016-07-08 12:30:39 +02002965 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01002966
aticig2f4ab6c2022-09-03 18:15:20 +03002967 @staticmethod
2968 def remove_keep_tag_from_persistent_volumes(created_items: Dict) -> Dict:
2969 """Removes the keep flag from persistent volumes. So, those volumes could be removed.
2970
2971 Args:
2972 created_items (dict): All created items belongs to VM
2973
2974 Returns:
2975 updated_created_items (dict): Dict which does not include keep flag for volumes.
2976
2977 """
2978 return {
2979 key.replace(":keep", ""): value for (key, value) in created_items.items()
2980 }
2981
tierno1ec592d2020-06-16 15:29:47 +00002982 def get_vminstance(self, vm_id):
2983 """Returns the VM instance information from VIM"""
vegallc53829d2023-06-01 00:47:44 -05002984 return self._find_nova_server(vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02002985
gatici335a06a2023-07-26 00:34:04 +03002986 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00002987 def get_vminstance_console(self, vm_id, console_type="vnc"):
2988 """
tierno7edb6752016-03-21 17:37:52 +01002989 Get a console for the virtual machine
2990 Params:
2991 vm_id: uuid of the VM
2992 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002993 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01002994 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02002995 Returns dict with the console parameters:
2996 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002997 server: usually ip address
2998 port: the http, ssh, ... port
2999 suffix: extra text, e.g. the http path and query string
tierno1ec592d2020-06-16 15:29:47 +00003000 """
tiernoae4a8d12016-07-08 12:30:39 +02003001 self.logger.debug("Getting VM CONSOLE from VIM")
gatici335a06a2023-07-26 00:34:04 +03003002 self._reload_connection()
3003 server = self.nova.servers.find(id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003004
gatici335a06a2023-07-26 00:34:04 +03003005 if console_type is None or console_type == "novnc":
3006 console_dict = server.get_vnc_console("novnc")
3007 elif console_type == "xvpvnc":
3008 console_dict = server.get_vnc_console(console_type)
3009 elif console_type == "rdp-html5":
3010 console_dict = server.get_rdp_console(console_type)
3011 elif console_type == "spice-html5":
3012 console_dict = server.get_spice_console(console_type)
3013 else:
3014 raise vimconn.VimConnException(
3015 "console type '{}' not allowed".format(console_type),
3016 http_code=vimconn.HTTP_Bad_Request,
3017 )
sousaedu80135b92021-02-17 15:05:18 +01003018
gatici335a06a2023-07-26 00:34:04 +03003019 console_dict1 = console_dict.get("console")
3020
3021 if console_dict1:
3022 console_url = console_dict1.get("url")
3023
3024 if console_url:
3025 # parse console_url
3026 protocol_index = console_url.find("//")
3027 suffix_index = (
3028 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3029 )
3030 port_index = (
3031 console_url[protocol_index + 2 : suffix_index].find(":")
3032 + protocol_index
3033 + 2
sousaedu80135b92021-02-17 15:05:18 +01003034 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003035
gatici335a06a2023-07-26 00:34:04 +03003036 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
3037 return (
3038 -vimconn.HTTP_Internal_Server_Error,
3039 "Unexpected response from VIM",
sousaedu80135b92021-02-17 15:05:18 +01003040 )
3041
gatici335a06a2023-07-26 00:34:04 +03003042 console_dict = {
3043 "protocol": console_url[0:protocol_index],
3044 "server": console_url[protocol_index + 2 : port_index],
3045 "port": console_url[port_index:suffix_index],
3046 "suffix": console_url[suffix_index + 1 :],
3047 }
3048 protocol_index += 2
sousaedu80135b92021-02-17 15:05:18 +01003049
gatici335a06a2023-07-26 00:34:04 +03003050 return console_dict
3051 raise vimconn.VimConnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01003052
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003053 def _delete_ports_by_id_wth_neutron(self, k_id: str) -> None:
3054 """Neutron delete ports by id.
3055 Args:
3056 k_id (str): Port id in the VIM
3057 """
3058 try:
limon878f8692023-07-24 15:53:41 +02003059 self.neutron.delete_port(k_id)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003060
gatici335a06a2023-07-26 00:34:04 +03003061 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3062 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3063 # If there is connection error, raise.
3064 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003065 except Exception as e:
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003066 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3067
vegall364627c2023-03-17 15:09:50 +00003068 def delete_shared_volumes(self, shared_volume_vim_id: str) -> bool:
3069 """Cinder delete volume by id.
3070 Args:
3071 shared_volume_vim_id (str): ID of shared volume in VIM
3072 """
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003073 elapsed_time = 0
vegall364627c2023-03-17 15:09:50 +00003074 try:
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003075 while elapsed_time < server_timeout:
3076 vol_status = self.cinder.volumes.get(shared_volume_vim_id).status
3077 if vol_status == "available":
3078 self.cinder.volumes.delete(shared_volume_vim_id)
3079 return True
vegall364627c2023-03-17 15:09:50 +00003080
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003081 time.sleep(5)
3082 elapsed_time += 5
3083
3084 if elapsed_time >= server_timeout:
3085 raise vimconn.VimConnException(
3086 "Timeout waiting for volume "
3087 + shared_volume_vim_id
3088 + " to be available",
3089 http_code=vimconn.HTTP_Request_Timeout,
3090 )
vegall364627c2023-03-17 15:09:50 +00003091
3092 except Exception as e:
3093 self.logger.error(
3094 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3095 )
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003096 self._format_exception(e)
vegall364627c2023-03-17 15:09:50 +00003097
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003098 def _delete_volumes_by_id_wth_cinder(
3099 self, k: str, k_id: str, volumes_to_hold: list, created_items: dict
3100 ) -> bool:
3101 """Cinder delete volume by id.
3102 Args:
3103 k (str): Full item name in created_items
3104 k_id (str): ID of floating ip in VIM
3105 volumes_to_hold (list): Volumes not to delete
3106 created_items (dict): All created items belongs to VM
3107 """
3108 try:
3109 if k_id in volumes_to_hold:
gatici335a06a2023-07-26 00:34:04 +03003110 return False
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003111
3112 if self.cinder.volumes.get(k_id).status != "available":
3113 return True
3114
3115 else:
3116 self.cinder.volumes.delete(k_id)
3117 created_items[k] = None
3118
gatici335a06a2023-07-26 00:34:04 +03003119 except (cExceptions.ConnectionError, ConnectionError) as e:
3120 self.logger.error(
3121 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3122 )
3123 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003124 except Exception as e:
3125 self.logger.error(
3126 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3127 )
3128
3129 def _delete_floating_ip_by_id(self, k: str, k_id: str, created_items: dict) -> None:
3130 """Neutron delete floating ip by id.
3131 Args:
3132 k (str): Full item name in created_items
3133 k_id (str): ID of floating ip in VIM
3134 created_items (dict): All created items belongs to VM
3135 """
3136 try:
3137 self.neutron.delete_floatingip(k_id)
3138 created_items[k] = None
3139
gatici335a06a2023-07-26 00:34:04 +03003140 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3141 self.logger.error(
3142 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3143 )
3144 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003145 except Exception as e:
3146 self.logger.error(
3147 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3148 )
3149
3150 @staticmethod
3151 def _get_item_name_id(k: str) -> Tuple[str, str]:
3152 k_item, _, k_id = k.partition(":")
3153 return k_item, k_id
3154
3155 def _delete_vm_ports_attached_to_network(self, created_items: dict) -> None:
3156 """Delete VM ports attached to the networks before deleting virtual machine.
3157 Args:
3158 created_items (dict): All created items belongs to VM
3159 """
3160
3161 for k, v in created_items.items():
3162 if not v: # skip already deleted
3163 continue
3164
3165 try:
3166 k_item, k_id = self._get_item_name_id(k)
3167 if k_item == "port":
3168 self._delete_ports_by_id_wth_neutron(k_id)
3169
gatici335a06a2023-07-26 00:34:04 +03003170 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3171 self.logger.error(
3172 "Error deleting port: {}: {}".format(type(e).__name__, e)
3173 )
3174 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003175 except Exception as e:
3176 self.logger.error(
3177 "Error deleting port: {}: {}".format(type(e).__name__, e)
3178 )
3179
3180 def _delete_created_items(
3181 self, created_items: dict, volumes_to_hold: list, keep_waiting: bool
3182 ) -> bool:
3183 """Delete Volumes and floating ip if they exist in created_items."""
3184 for k, v in created_items.items():
3185 if not v: # skip already deleted
3186 continue
3187
3188 try:
3189 k_item, k_id = self._get_item_name_id(k)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003190 if k_item == "volume":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003191 unavailable_vol = self._delete_volumes_by_id_wth_cinder(
3192 k, k_id, volumes_to_hold, created_items
3193 )
3194
3195 if unavailable_vol:
3196 keep_waiting = True
3197
3198 elif k_item == "floating_ip":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003199 self._delete_floating_ip_by_id(k, k_id, created_items)
3200
gatici335a06a2023-07-26 00:34:04 +03003201 except (
3202 cExceptions.ConnectionError,
3203 neExceptions.ConnectionFailed,
3204 ConnectionError,
3205 AttributeError,
3206 TypeError,
3207 ) as e:
3208 self.logger.error("Error deleting {}: {}".format(k, e))
3209 self._format_exception(e)
3210
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003211 except Exception as e:
3212 self.logger.error("Error deleting {}: {}".format(k, e))
3213
3214 return keep_waiting
3215
aticig2f4ab6c2022-09-03 18:15:20 +03003216 @staticmethod
3217 def _extract_items_wth_keep_flag_from_created_items(created_items: dict) -> dict:
3218 """Remove the volumes which has key flag from created_items
3219
3220 Args:
3221 created_items (dict): All created items belongs to VM
3222
3223 Returns:
3224 created_items (dict): Persistent volumes eliminated created_items
3225 """
3226 return {
3227 key: value
3228 for (key, value) in created_items.items()
3229 if len(key.split(":")) == 2
3230 }
3231
gatici335a06a2023-07-26 00:34:04 +03003232 @catch_any_exception
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003233 def delete_vminstance(
3234 self, vm_id: str, created_items: dict = None, volumes_to_hold: list = None
3235 ) -> None:
3236 """Removes a VM instance from VIM. Returns the old identifier.
3237 Args:
3238 vm_id (str): Identifier of VM instance
3239 created_items (dict): All created items belongs to VM
3240 volumes_to_hold (list): Volumes_to_hold
3241 """
tierno1ec592d2020-06-16 15:29:47 +00003242 if created_items is None:
tierno98e909c2017-10-14 13:27:03 +02003243 created_items = {}
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003244 if volumes_to_hold is None:
3245 volumes_to_hold = []
sousaedu80135b92021-02-17 15:05:18 +01003246
tierno7edb6752016-03-21 17:37:52 +01003247 try:
aticig2f4ab6c2022-09-03 18:15:20 +03003248 created_items = self._extract_items_wth_keep_flag_from_created_items(
3249 created_items
3250 )
3251
tierno7edb6752016-03-21 17:37:52 +01003252 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01003253
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003254 # Delete VM ports attached to the networks before the virtual machine
3255 if created_items:
3256 self._delete_vm_ports_attached_to_network(created_items)
montesmoreno0c8def02016-12-22 12:16:23 +00003257
tierno98e909c2017-10-14 13:27:03 +02003258 if vm_id:
3259 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00003260
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003261 # Although having detached, volumes should have in active status before deleting.
3262 # We ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00003263 keep_waiting = True
3264 elapsed_time = 0
sousaedu80135b92021-02-17 15:05:18 +01003265
montesmoreno0c8def02016-12-22 12:16:23 +00003266 while keep_waiting and elapsed_time < volume_timeout:
3267 keep_waiting = False
sousaedu80135b92021-02-17 15:05:18 +01003268
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003269 # Delete volumes and floating IP.
3270 keep_waiting = self._delete_created_items(
3271 created_items, volumes_to_hold, keep_waiting
3272 )
sousaedu80135b92021-02-17 15:05:18 +01003273
montesmoreno0c8def02016-12-22 12:16:23 +00003274 if keep_waiting:
3275 time.sleep(1)
3276 elapsed_time += 1
gatici335a06a2023-07-26 00:34:04 +03003277 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
3278 # If VM does not exist, it does not raise
3279 self.logger.warning(f"Error deleting VM: {vm_id} is not found, {str(e)}")
tierno7edb6752016-03-21 17:37:52 +01003280
tiernoae4a8d12016-07-08 12:30:39 +02003281 def refresh_vms_status(self, vm_list):
tierno1ec592d2020-06-16 15:29:47 +00003282 """Get the status of the virtual machines and their interfaces/ports
sousaedu80135b92021-02-17 15:05:18 +01003283 Params: the list of VM identifiers
3284 Returns a dictionary with:
3285 vm_id: #VIM id of this Virtual Machine
3286 status: #Mandatory. Text with one of:
3287 # DELETED (not found at vim)
3288 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
3289 # OTHER (Vim reported other status not understood)
3290 # ERROR (VIM indicates an ERROR status)
3291 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
3292 # CREATING (on building process), ERROR
3293 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
3294 #
3295 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
3296 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3297 interfaces:
3298 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3299 mac_address: #Text format XX:XX:XX:XX:XX:XX
3300 vim_net_id: #network id where this interface is connected
3301 vim_interface_id: #interface/port VIM id
3302 ip_address: #null, or text with IPv4, IPv6 address
3303 compute_node: #identification of compute node where PF,VF interface is allocated
3304 pci: #PCI address of the NIC that hosts the PF,VF
3305 vlan: #physical VLAN used for VF
tierno1ec592d2020-06-16 15:29:47 +00003306 """
3307 vm_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01003308 self.logger.debug(
3309 "refresh_vms status: Getting tenant VM instance information from VIM"
3310 )
tiernoae4a8d12016-07-08 12:30:39 +02003311 for vm_id in vm_list:
tierno1ec592d2020-06-16 15:29:47 +00003312 vm = {}
sousaedu80135b92021-02-17 15:05:18 +01003313
tiernoae4a8d12016-07-08 12:30:39 +02003314 try:
3315 vm_vim = self.get_vminstance(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003316
3317 if vm_vim["status"] in vmStatus2manoFormat:
3318 vm["status"] = vmStatus2manoFormat[vm_vim["status"]]
tierno7edb6752016-03-21 17:37:52 +01003319 else:
sousaedu80135b92021-02-17 15:05:18 +01003320 vm["status"] = "OTHER"
3321 vm["error_msg"] = "VIM status reported " + vm_vim["status"]
3322
tierno70eeb182020-10-19 16:38:00 +00003323 vm_vim.pop("OS-EXT-SRV-ATTR:user_data", None)
3324 vm_vim.pop("user_data", None)
sousaedu80135b92021-02-17 15:05:18 +01003325 vm["vim_info"] = self.serialize(vm_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003326
tiernoae4a8d12016-07-08 12:30:39 +02003327 vm["interfaces"] = []
sousaedu80135b92021-02-17 15:05:18 +01003328 if vm_vim.get("fault"):
3329 vm["error_msg"] = str(vm_vim["fault"])
3330
tierno1ec592d2020-06-16 15:29:47 +00003331 # get interfaces
tierno7edb6752016-03-21 17:37:52 +01003332 try:
tiernoae4a8d12016-07-08 12:30:39 +02003333 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02003334 port_dict = self.neutron.list_ports(device_id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003335
tiernoae4a8d12016-07-08 12:30:39 +02003336 for port in port_dict["ports"]:
tierno1ec592d2020-06-16 15:29:47 +00003337 interface = {}
sousaedu80135b92021-02-17 15:05:18 +01003338 interface["vim_info"] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02003339 interface["mac_address"] = port.get("mac_address")
3340 interface["vim_net_id"] = port["network_id"]
3341 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003342 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003343 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003344
3345 if vm_vim.get("OS-EXT-SRV-ATTR:host"):
3346 interface["compute_node"] = vm_vim["OS-EXT-SRV-ATTR:host"]
3347
tierno867ffe92017-03-27 12:50:34 +02003348 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04003349
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003350 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003351 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003352 if port.get("binding:profile"):
3353 if port["binding:profile"].get("pci_slot"):
tierno1ec592d2020-06-16 15:29:47 +00003354 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting
3355 # the slot to 0x00
Mike Marchetti5b9da422017-05-02 15:35:47 -04003356 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
3357 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
sousaedu80135b92021-02-17 15:05:18 +01003358 pci = port["binding:profile"]["pci_slot"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04003359 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
3360 interface["pci"] = pci
sousaedu80135b92021-02-17 15:05:18 +01003361
tierno867ffe92017-03-27 12:50:34 +02003362 interface["vlan"] = None
sousaedu80135b92021-02-17 15:05:18 +01003363
3364 if port.get("binding:vif_details"):
3365 interface["vlan"] = port["binding:vif_details"].get("vlan")
3366
tierno1dfe9932020-06-18 08:50:10 +00003367 # Get vlan from network in case not present in port for those old openstacks and cases where
3368 # it is needed vlan at PT
3369 if not interface["vlan"]:
3370 # if network is of type vlan and port is of type direct (sr-iov) then set vlan id
3371 network = self.neutron.show_network(port["network_id"])
sousaedu80135b92021-02-17 15:05:18 +01003372
3373 if (
3374 network["network"].get("provider:network_type")
3375 == "vlan"
3376 ):
tierno1dfe9932020-06-18 08:50:10 +00003377 # and port.get("binding:vnic_type") in ("direct", "direct-physical"):
sousaedu80135b92021-02-17 15:05:18 +01003378 interface["vlan"] = network["network"].get(
3379 "provider:segmentation_id"
3380 )
3381
tierno1ec592d2020-06-16 15:29:47 +00003382 ips = []
3383 # look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02003384 try:
sousaedu80135b92021-02-17 15:05:18 +01003385 floating_ip_dict = self.neutron.list_floatingips(
3386 port_id=port["id"]
3387 )
3388
tiernob42fd9b2018-06-20 10:44:32 +02003389 if floating_ip_dict.get("floatingips"):
sousaedu80135b92021-02-17 15:05:18 +01003390 ips.append(
3391 floating_ip_dict["floatingips"][0].get(
3392 "floating_ip_address"
3393 )
3394 )
tiernob42fd9b2018-06-20 10:44:32 +02003395 except Exception:
3396 pass
tierno7edb6752016-03-21 17:37:52 +01003397
tiernoae4a8d12016-07-08 12:30:39 +02003398 for subnet in port["fixed_ips"]:
3399 ips.append(subnet["ip_address"])
sousaedu80135b92021-02-17 15:05:18 +01003400
tiernoae4a8d12016-07-08 12:30:39 +02003401 interface["ip_address"] = ";".join(ips)
3402 vm["interfaces"].append(interface)
3403 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01003404 self.logger.error(
3405 "Error getting vm interface information {}: {}".format(
3406 type(e).__name__, e
3407 ),
3408 exc_info=True,
3409 )
tierno72774862020-05-04 11:44:15 +00003410 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003411 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003412 vm["status"] = "DELETED"
3413 vm["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00003414 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003415 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003416 vm["status"] = "VIM_ERROR"
3417 vm["error_msg"] = str(e)
3418
tiernoae4a8d12016-07-08 12:30:39 +02003419 vm_dict[vm_id] = vm
sousaedu80135b92021-02-17 15:05:18 +01003420
tiernoae4a8d12016-07-08 12:30:39 +02003421 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003422
gatici335a06a2023-07-26 00:34:04 +03003423 @catch_any_exception
tierno98e909c2017-10-14 13:27:03 +02003424 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno1ec592d2020-06-16 15:29:47 +00003425 """Send and action over a VM instance from VIM
Gulsum Atici21c55d62023-02-02 20:41:00 +03003426 Returns None or the console dict if the action was successfully sent to the VIM
3427 """
tiernoae4a8d12016-07-08 12:30:39 +02003428 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
gatici335a06a2023-07-26 00:34:04 +03003429 self._reload_connection()
3430 server = self.nova.servers.find(id=vm_id)
3431 if "start" in action_dict:
3432 if action_dict["start"] == "rebuild":
3433 server.rebuild()
Rahul Kumar8875f912023-11-08 06:48:12 +00003434 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
3435 if not vm_state:
3436 raise nvExceptions.BadRequest(
3437 409,
3438 message="Cannot 'REBUILD' vm_state is in ERROR",
3439 )
gatici335a06a2023-07-26 00:34:04 +03003440 else:
3441 if server.status == "PAUSED":
3442 server.unpause()
3443 elif server.status == "SUSPENDED":
3444 server.resume()
3445 elif server.status == "SHUTOFF":
3446 server.start()
Rahul Kumar8875f912023-11-08 06:48:12 +00003447 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
3448 if not vm_state:
3449 raise nvExceptions.BadRequest(
3450 409,
3451 message="Cannot 'START' vm_state is in ERROR",
3452 )
tierno7edb6752016-03-21 17:37:52 +01003453 else:
gatici335a06a2023-07-26 00:34:04 +03003454 self.logger.debug(
3455 "ERROR : Instance is not in SHUTOFF/PAUSE/SUSPEND state"
3456 )
k4.rahul78f474e2022-05-02 15:47:57 +00003457 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03003458 "Cannot 'start' instance while it is in active state",
k4.rahul78f474e2022-05-02 15:47:57 +00003459 http_code=vimconn.HTTP_Bad_Request,
3460 )
gatici335a06a2023-07-26 00:34:04 +03003461 elif "pause" in action_dict:
3462 server.pause()
3463 elif "resume" in action_dict:
3464 server.resume()
3465 elif "shutoff" in action_dict or "shutdown" in action_dict:
3466 self.logger.debug("server status %s", server.status)
3467 if server.status == "ACTIVE":
3468 server.stop()
Rahul Kumar8875f912023-11-08 06:48:12 +00003469 vm_state = self.__wait_for_vm(vm_id, "SHUTOFF")
3470 if not vm_state:
3471 raise nvExceptions.BadRequest(
3472 409,
3473 message="Cannot 'STOP' vm_state is in ERROR",
3474 )
gatici335a06a2023-07-26 00:34:04 +03003475 else:
3476 self.logger.debug("ERROR: VM is not in Active state")
3477 raise vimconn.VimConnException(
3478 "VM is not in active state, stop operation is not allowed",
3479 http_code=vimconn.HTTP_Bad_Request,
3480 )
3481 elif "forceOff" in action_dict:
3482 server.stop() # TODO
3483 elif "terminate" in action_dict:
3484 server.delete()
3485 elif "createImage" in action_dict:
3486 server.create_image()
3487 # "path":path_schema,
3488 # "description":description_schema,
3489 # "name":name_schema,
3490 # "metadata":metadata_schema,
3491 # "imageRef": id_schema,
3492 # "disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
3493 elif "rebuild" in action_dict:
3494 server.rebuild(server.image["id"])
3495 elif "reboot" in action_dict:
3496 server.reboot() # reboot_type="SOFT"
3497 elif "console" in action_dict:
3498 console_type = action_dict["console"]
sousaedu80135b92021-02-17 15:05:18 +01003499
gatici335a06a2023-07-26 00:34:04 +03003500 if console_type is None or console_type == "novnc":
3501 console_dict = server.get_vnc_console("novnc")
3502 elif console_type == "xvpvnc":
3503 console_dict = server.get_vnc_console(console_type)
3504 elif console_type == "rdp-html5":
3505 console_dict = server.get_rdp_console(console_type)
3506 elif console_type == "spice-html5":
3507 console_dict = server.get_spice_console(console_type)
3508 else:
3509 raise vimconn.VimConnException(
3510 "console type '{}' not allowed".format(console_type),
3511 http_code=vimconn.HTTP_Bad_Request,
3512 )
sousaedu80135b92021-02-17 15:05:18 +01003513
gatici335a06a2023-07-26 00:34:04 +03003514 try:
3515 console_url = console_dict["console"]["url"]
3516 # parse console_url
3517 protocol_index = console_url.find("//")
3518 suffix_index = (
3519 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3520 )
3521 port_index = (
3522 console_url[protocol_index + 2 : suffix_index].find(":")
3523 + protocol_index
3524 + 2
3525 )
sousaedu80135b92021-02-17 15:05:18 +01003526
gatici335a06a2023-07-26 00:34:04 +03003527 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
sousaedu80135b92021-02-17 15:05:18 +01003528 raise vimconn.VimConnException(
3529 "Unexpected response from VIM " + str(console_dict)
3530 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003531
gatici335a06a2023-07-26 00:34:04 +03003532 console_dict2 = {
3533 "protocol": console_url[0:protocol_index],
3534 "server": console_url[protocol_index + 2 : port_index],
3535 "port": int(console_url[port_index + 1 : suffix_index]),
3536 "suffix": console_url[suffix_index + 1 :],
3537 }
3538
3539 return console_dict2
3540 except Exception:
3541 raise vimconn.VimConnException(
3542 "Unexpected response from VIM " + str(console_dict)
3543 )
3544
3545 return None
tiernoae4a8d12016-07-08 12:30:39 +02003546
tierno1ec592d2020-06-16 15:29:47 +00003547 # ###### VIO Specific Changes #########
garciadeblasebd66722019-01-31 16:01:31 +00003548 def _generate_vlanID(self):
kate721d79b2017-06-24 04:21:38 -07003549 """
sousaedu80135b92021-02-17 15:05:18 +01003550 Method to get unused vlanID
kate721d79b2017-06-24 04:21:38 -07003551 Args:
3552 None
3553 Returns:
3554 vlanID
3555 """
tierno1ec592d2020-06-16 15:29:47 +00003556 # Get used VLAN IDs
kate721d79b2017-06-24 04:21:38 -07003557 usedVlanIDs = []
3558 networks = self.get_network_list()
sousaedu80135b92021-02-17 15:05:18 +01003559
kate721d79b2017-06-24 04:21:38 -07003560 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003561 if net.get("provider:segmentation_id"):
3562 usedVlanIDs.append(net.get("provider:segmentation_id"))
3563
kate721d79b2017-06-24 04:21:38 -07003564 used_vlanIDs = set(usedVlanIDs)
3565
tierno1ec592d2020-06-16 15:29:47 +00003566 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003567 for vlanID_range in self.config.get("dataplane_net_vlan_range"):
kate721d79b2017-06-24 04:21:38 -07003568 try:
sousaedu80135b92021-02-17 15:05:18 +01003569 start_vlanid, end_vlanid = map(
3570 int, vlanID_range.replace(" ", "").split("-")
3571 )
3572
tierno7d782ef2019-10-04 12:56:31 +00003573 for vlanID in range(start_vlanid, end_vlanid + 1):
kate721d79b2017-06-24 04:21:38 -07003574 if vlanID not in used_vlanIDs:
3575 return vlanID
3576 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003577 raise vimconn.VimConnException(
3578 "Exception {} occurred while generating VLAN ID.".format(exp)
3579 )
kate721d79b2017-06-24 04:21:38 -07003580 else:
tierno1ec592d2020-06-16 15:29:47 +00003581 raise vimconn.VimConnConflictException(
3582 "Unable to create the SRIOV VLAN network. All given Vlan IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003583 self.config.get("dataplane_net_vlan_range")
3584 )
3585 )
kate721d79b2017-06-24 04:21:38 -07003586
garciadeblasebd66722019-01-31 16:01:31 +00003587 def _generate_multisegment_vlanID(self):
3588 """
sousaedu80135b92021-02-17 15:05:18 +01003589 Method to get unused vlanID
3590 Args:
3591 None
3592 Returns:
3593 vlanID
garciadeblasebd66722019-01-31 16:01:31 +00003594 """
tierno6869ae72020-01-09 17:37:34 +00003595 # Get used VLAN IDs
garciadeblasebd66722019-01-31 16:01:31 +00003596 usedVlanIDs = []
3597 networks = self.get_network_list()
3598 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003599 if net.get("provider:network_type") == "vlan" and net.get(
3600 "provider:segmentation_id"
3601 ):
3602 usedVlanIDs.append(net.get("provider:segmentation_id"))
3603 elif net.get("segments"):
3604 for segment in net.get("segments"):
3605 if segment.get("provider:network_type") == "vlan" and segment.get(
3606 "provider:segmentation_id"
3607 ):
3608 usedVlanIDs.append(segment.get("provider:segmentation_id"))
3609
garciadeblasebd66722019-01-31 16:01:31 +00003610 used_vlanIDs = set(usedVlanIDs)
3611
tierno6869ae72020-01-09 17:37:34 +00003612 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003613 for vlanID_range in self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +00003614 try:
sousaedu80135b92021-02-17 15:05:18 +01003615 start_vlanid, end_vlanid = map(
3616 int, vlanID_range.replace(" ", "").split("-")
3617 )
3618
tierno7d782ef2019-10-04 12:56:31 +00003619 for vlanID in range(start_vlanid, end_vlanid + 1):
garciadeblasebd66722019-01-31 16:01:31 +00003620 if vlanID not in used_vlanIDs:
3621 return vlanID
3622 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003623 raise vimconn.VimConnException(
3624 "Exception {} occurred while generating VLAN ID.".format(exp)
3625 )
garciadeblasebd66722019-01-31 16:01:31 +00003626 else:
tierno1ec592d2020-06-16 15:29:47 +00003627 raise vimconn.VimConnConflictException(
3628 "Unable to create the VLAN segment. All VLAN IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003629 self.config.get("multisegment_vlan_range")
3630 )
3631 )
garciadeblasebd66722019-01-31 16:01:31 +00003632
3633 def _validate_vlan_ranges(self, input_vlan_range, text_vlan_range):
kate721d79b2017-06-24 04:21:38 -07003634 """
3635 Method to validate user given vlanID ranges
3636 Args: None
3637 Returns: None
3638 """
garciadeblasebd66722019-01-31 16:01:31 +00003639 for vlanID_range in input_vlan_range:
kate721d79b2017-06-24 04:21:38 -07003640 vlan_range = vlanID_range.replace(" ", "")
tierno1ec592d2020-06-16 15:29:47 +00003641 # validate format
sousaedu80135b92021-02-17 15:05:18 +01003642 vlanID_pattern = r"(\d)*-(\d)*$"
kate721d79b2017-06-24 04:21:38 -07003643 match_obj = re.match(vlanID_pattern, vlan_range)
3644 if not match_obj:
tierno1ec592d2020-06-16 15:29:47 +00003645 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003646 "Invalid VLAN range for {}: {}.You must provide "
3647 "'{}' in format [start_ID - end_ID].".format(
3648 text_vlan_range, vlanID_range, text_vlan_range
3649 )
3650 )
kate721d79b2017-06-24 04:21:38 -07003651
tierno1ec592d2020-06-16 15:29:47 +00003652 start_vlanid, end_vlanid = map(int, vlan_range.split("-"))
3653 if start_vlanid <= 0:
3654 raise vimconn.VimConnConflictException(
3655 "Invalid VLAN range for {}: {}. Start ID can not be zero. For VLAN "
sousaedu80135b92021-02-17 15:05:18 +01003656 "networks valid IDs are 1 to 4094 ".format(
3657 text_vlan_range, vlanID_range
3658 )
3659 )
3660
tierno1ec592d2020-06-16 15:29:47 +00003661 if end_vlanid > 4094:
3662 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003663 "Invalid VLAN range for {}: {}. End VLAN ID can not be "
3664 "greater than 4094. For VLAN networks valid IDs are 1 to 4094 ".format(
3665 text_vlan_range, vlanID_range
3666 )
3667 )
kate721d79b2017-06-24 04:21:38 -07003668
3669 if start_vlanid > end_vlanid:
tierno1ec592d2020-06-16 15:29:47 +00003670 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003671 "Invalid VLAN range for {}: {}. You must provide '{}'"
3672 " in format start_ID - end_ID and start_ID < end_ID ".format(
3673 text_vlan_range, vlanID_range, text_vlan_range
3674 )
3675 )
kate721d79b2017-06-24 04:21:38 -07003676
tierno7edb6752016-03-21 17:37:52 +01003677 def get_hosts_info(self):
tierno1ec592d2020-06-16 15:29:47 +00003678 """Get the information of deployed hosts
3679 Returns the hosts content"""
garciadeblas6a0147f2024-08-19 08:02:04 +00003680 self.logger.debug("osconnector: Getting Host info from VIM")
sousaedu80135b92021-02-17 15:05:18 +01003681
tierno7edb6752016-03-21 17:37:52 +01003682 try:
tierno1ec592d2020-06-16 15:29:47 +00003683 h_list = []
tierno7edb6752016-03-21 17:37:52 +01003684 self._reload_connection()
3685 hypervisors = self.nova.hypervisors.list()
sousaedu80135b92021-02-17 15:05:18 +01003686
tierno7edb6752016-03-21 17:37:52 +01003687 for hype in hypervisors:
tierno1ec592d2020-06-16 15:29:47 +00003688 h_list.append(hype.to_dict())
sousaedu80135b92021-02-17 15:05:18 +01003689
tierno1ec592d2020-06-16 15:29:47 +00003690 return 1, {"hosts": h_list}
tierno7edb6752016-03-21 17:37:52 +01003691 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003692 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003693 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003694 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003695 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003696 error_text = (
3697 type(e).__name__
3698 + ": "
3699 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3700 )
3701
tierno1ec592d2020-06-16 15:29:47 +00003702 # TODO insert exception vimconn.HTTP_Unauthorized
3703 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003704 self.logger.debug("get_hosts_info " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003705
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003706 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003707
3708 def get_hosts(self, vim_tenant):
tierno1ec592d2020-06-16 15:29:47 +00003709 """Get the hosts and deployed instances
3710 Returns the hosts content"""
tierno7edb6752016-03-21 17:37:52 +01003711 r, hype_dict = self.get_hosts_info()
sousaedu80135b92021-02-17 15:05:18 +01003712
tierno1ec592d2020-06-16 15:29:47 +00003713 if r < 0:
tierno7edb6752016-03-21 17:37:52 +01003714 return r, hype_dict
sousaedu80135b92021-02-17 15:05:18 +01003715
tierno7edb6752016-03-21 17:37:52 +01003716 hypervisors = hype_dict["hosts"]
sousaedu80135b92021-02-17 15:05:18 +01003717
tierno7edb6752016-03-21 17:37:52 +01003718 try:
3719 servers = self.nova.servers.list()
3720 for hype in hypervisors:
3721 for server in servers:
sousaedu80135b92021-02-17 15:05:18 +01003722 if (
3723 server.to_dict()["OS-EXT-SRV-ATTR:hypervisor_hostname"]
3724 == hype["hypervisor_hostname"]
3725 ):
3726 if "vm" in hype:
3727 hype["vm"].append(server.id)
tierno7edb6752016-03-21 17:37:52 +01003728 else:
sousaedu80135b92021-02-17 15:05:18 +01003729 hype["vm"] = [server.id]
3730
tierno7edb6752016-03-21 17:37:52 +01003731 return 1, hype_dict
3732 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003733 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003734 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003735 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003736 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003737 error_text = (
3738 type(e).__name__
3739 + ": "
3740 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3741 )
3742
tierno1ec592d2020-06-16 15:29:47 +00003743 # TODO insert exception vimconn.HTTP_Unauthorized
3744 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003745 self.logger.debug("get_hosts " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003746
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003747 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003748
Lovejeet Singhdf486552023-05-09 22:41:09 +05303749 def new_classification(self, name, ctype, definition):
3750 self.logger.debug(
3751 "Adding a new (Traffic) Classification to VIM, named %s", name
3752 )
3753
3754 try:
3755 new_class = None
3756 self._reload_connection()
3757
3758 if ctype not in supportedClassificationTypes:
3759 raise vimconn.VimConnNotSupportedException(
3760 "OpenStack VIM connector does not support provided "
3761 "Classification Type {}, supported ones are: {}".format(
3762 ctype, supportedClassificationTypes
3763 )
3764 )
3765
3766 if not self._validate_classification(ctype, definition):
3767 raise vimconn.VimConnException(
3768 "Incorrect Classification definition for the type specified."
3769 )
3770
3771 classification_dict = definition
3772 classification_dict["name"] = name
3773
3774 self.logger.info(
3775 "Adding a new (Traffic) Classification to VIM, named {} and {}.".format(
3776 name, classification_dict
3777 )
3778 )
3779 new_class = self.neutron.create_sfc_flow_classifier(
3780 {"flow_classifier": classification_dict}
3781 )
3782
3783 return new_class["flow_classifier"]["id"]
3784 except (
3785 neExceptions.ConnectionFailed,
3786 ksExceptions.ClientException,
3787 neExceptions.NeutronException,
3788 ConnectionError,
3789 ) as e:
3790 self.logger.error("Creation of Classification failed.")
3791 self._format_exception(e)
3792
3793 def get_classification(self, class_id):
3794 self.logger.debug(" Getting Classification %s from VIM", class_id)
3795 filter_dict = {"id": class_id}
3796 class_list = self.get_classification_list(filter_dict)
3797
3798 if len(class_list) == 0:
3799 raise vimconn.VimConnNotFoundException(
3800 "Classification '{}' not found".format(class_id)
3801 )
3802 elif len(class_list) > 1:
3803 raise vimconn.VimConnConflictException(
3804 "Found more than one Classification with this criteria"
3805 )
3806
3807 classification = class_list[0]
3808
3809 return classification
3810
3811 def get_classification_list(self, filter_dict={}):
3812 self.logger.debug(
3813 "Getting Classifications from VIM filter: '%s'", str(filter_dict)
3814 )
3815
3816 try:
3817 filter_dict_os = filter_dict.copy()
3818 self._reload_connection()
3819
3820 if self.api_version3 and "tenant_id" in filter_dict_os:
3821 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3822
3823 classification_dict = self.neutron.list_sfc_flow_classifiers(
3824 **filter_dict_os
3825 )
3826 classification_list = classification_dict["flow_classifiers"]
3827 self.__classification_os2mano(classification_list)
3828
3829 return classification_list
3830 except (
3831 neExceptions.ConnectionFailed,
3832 ksExceptions.ClientException,
3833 neExceptions.NeutronException,
3834 ConnectionError,
3835 ) as e:
3836 self._format_exception(e)
3837
3838 def delete_classification(self, class_id):
3839 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
3840
3841 try:
3842 self._reload_connection()
3843 self.neutron.delete_sfc_flow_classifier(class_id)
3844
3845 return class_id
3846 except (
3847 neExceptions.ConnectionFailed,
3848 neExceptions.NeutronException,
3849 ksExceptions.ClientException,
3850 neExceptions.NeutronException,
3851 ConnectionError,
3852 ) as e:
3853 self._format_exception(e)
3854
3855 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
3856 self.logger.debug(
3857 "Adding a new Service Function Instance to VIM, named '%s'", name
3858 )
3859
3860 try:
3861 new_sfi = None
3862 self._reload_connection()
3863 correlation = None
3864
3865 if sfc_encap:
3866 correlation = "nsh"
3867
3868 if len(ingress_ports) != 1:
3869 raise vimconn.VimConnNotSupportedException(
3870 "OpenStack VIM connector can only have 1 ingress port per SFI"
3871 )
3872
3873 if len(egress_ports) != 1:
3874 raise vimconn.VimConnNotSupportedException(
3875 "OpenStack VIM connector can only have 1 egress port per SFI"
3876 )
3877
3878 sfi_dict = {
3879 "name": name,
3880 "ingress": ingress_ports[0],
3881 "egress": egress_ports[0],
3882 "service_function_parameters": {"correlation": correlation},
3883 }
3884 self.logger.info("Adding a new SFI to VIM, {}.".format(sfi_dict))
3885 new_sfi = self.neutron.create_sfc_port_pair({"port_pair": sfi_dict})
3886
3887 return new_sfi["port_pair"]["id"]
3888 except (
3889 neExceptions.ConnectionFailed,
3890 ksExceptions.ClientException,
3891 neExceptions.NeutronException,
3892 ConnectionError,
3893 ) as e:
3894 if new_sfi:
3895 try:
3896 self.neutron.delete_sfc_port_pair(new_sfi["port_pair"]["id"])
3897 except Exception:
3898 self.logger.error(
3899 "Creation of Service Function Instance failed, with "
3900 "subsequent deletion failure as well."
3901 )
3902
3903 self._format_exception(e)
3904
3905 def get_sfi(self, sfi_id):
3906 self.logger.debug("Getting Service Function Instance %s from VIM", sfi_id)
3907 filter_dict = {"id": sfi_id}
3908 sfi_list = self.get_sfi_list(filter_dict)
3909
3910 if len(sfi_list) == 0:
3911 raise vimconn.VimConnNotFoundException(
3912 "Service Function Instance '{}' not found".format(sfi_id)
3913 )
3914 elif len(sfi_list) > 1:
3915 raise vimconn.VimConnConflictException(
3916 "Found more than one Service Function Instance with this criteria"
3917 )
3918
3919 sfi = sfi_list[0]
3920
3921 return sfi
3922
3923 def get_sfi_list(self, filter_dict={}):
3924 self.logger.debug(
3925 "Getting Service Function Instances from VIM filter: '%s'", str(filter_dict)
3926 )
3927
3928 try:
3929 self._reload_connection()
3930 filter_dict_os = filter_dict.copy()
3931
3932 if self.api_version3 and "tenant_id" in filter_dict_os:
3933 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3934
3935 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
3936 sfi_list = sfi_dict["port_pairs"]
3937 self.__sfi_os2mano(sfi_list)
3938
3939 return sfi_list
3940 except (
3941 neExceptions.ConnectionFailed,
3942 ksExceptions.ClientException,
3943 neExceptions.NeutronException,
3944 ConnectionError,
3945 ) as e:
3946 self._format_exception(e)
3947
3948 def delete_sfi(self, sfi_id):
3949 self.logger.debug("Deleting Service Function Instance '%s' from VIM", sfi_id)
3950
3951 try:
3952 self._reload_connection()
3953 self.neutron.delete_sfc_port_pair(sfi_id)
3954
3955 return sfi_id
3956 except (
3957 neExceptions.ConnectionFailed,
3958 neExceptions.NeutronException,
3959 ksExceptions.ClientException,
3960 neExceptions.NeutronException,
3961 ConnectionError,
3962 ) as e:
3963 self._format_exception(e)
3964
3965 def new_sf(self, name, sfis, sfc_encap=True):
3966 self.logger.debug("Adding a new Service Function to VIM, named '%s'", name)
3967
3968 new_sf = None
3969
3970 try:
3971 self._reload_connection()
3972
3973 for instance in sfis:
3974 sfi = self.get_sfi(instance)
3975
3976 if sfi.get("sfc_encap") != sfc_encap:
3977 raise vimconn.VimConnNotSupportedException(
3978 "OpenStack VIM connector requires all SFIs of the "
3979 "same SF to share the same SFC Encapsulation"
3980 )
3981
3982 sf_dict = {"name": name, "port_pairs": sfis}
3983
3984 self.logger.info("Adding a new SF to VIM, {}.".format(sf_dict))
3985 new_sf = self.neutron.create_sfc_port_pair_group(
3986 {"port_pair_group": sf_dict}
3987 )
3988
3989 return new_sf["port_pair_group"]["id"]
3990 except (
3991 neExceptions.ConnectionFailed,
3992 ksExceptions.ClientException,
3993 neExceptions.NeutronException,
3994 ConnectionError,
3995 ) as e:
3996 if new_sf:
3997 try:
3998 new_sf_id = new_sf.get("port_pair_group").get("id")
3999 self.neutron.delete_sfc_port_pair_group(new_sf_id)
4000 except Exception:
4001 self.logger.error(
4002 "Creation of Service Function failed, with "
4003 "subsequent deletion failure as well."
4004 )
4005
4006 self._format_exception(e)
4007
4008 def get_sf(self, sf_id):
4009 self.logger.debug("Getting Service Function %s from VIM", sf_id)
4010 filter_dict = {"id": sf_id}
4011 sf_list = self.get_sf_list(filter_dict)
4012
4013 if len(sf_list) == 0:
4014 raise vimconn.VimConnNotFoundException(
4015 "Service Function '{}' not found".format(sf_id)
4016 )
4017 elif len(sf_list) > 1:
4018 raise vimconn.VimConnConflictException(
4019 "Found more than one Service Function with this criteria"
4020 )
4021
4022 sf = sf_list[0]
4023
4024 return sf
4025
4026 def get_sf_list(self, filter_dict={}):
4027 self.logger.debug(
4028 "Getting Service Function from VIM filter: '%s'", str(filter_dict)
4029 )
4030
4031 try:
4032 self._reload_connection()
4033 filter_dict_os = filter_dict.copy()
4034
4035 if self.api_version3 and "tenant_id" in filter_dict_os:
4036 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
4037
4038 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
4039 sf_list = sf_dict["port_pair_groups"]
4040 self.__sf_os2mano(sf_list)
4041
4042 return sf_list
4043 except (
4044 neExceptions.ConnectionFailed,
4045 ksExceptions.ClientException,
4046 neExceptions.NeutronException,
4047 ConnectionError,
4048 ) as e:
4049 self._format_exception(e)
4050
4051 def delete_sf(self, sf_id):
4052 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
4053
4054 try:
4055 self._reload_connection()
4056 self.neutron.delete_sfc_port_pair_group(sf_id)
4057
4058 return sf_id
4059 except (
4060 neExceptions.ConnectionFailed,
4061 neExceptions.NeutronException,
4062 ksExceptions.ClientException,
4063 neExceptions.NeutronException,
4064 ConnectionError,
4065 ) as e:
4066 self._format_exception(e)
4067
4068 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
4069 self.logger.debug("Adding a new Service Function Path to VIM, named '%s'", name)
4070
4071 new_sfp = None
4072
4073 try:
4074 self._reload_connection()
4075 # In networking-sfc the MPLS encapsulation is legacy
4076 # should be used when no full SFC Encapsulation is intended
4077 correlation = "mpls"
4078
4079 if sfc_encap:
4080 correlation = "nsh"
4081
4082 sfp_dict = {
4083 "name": name,
4084 "flow_classifiers": classifications,
4085 "port_pair_groups": sfs,
4086 "chain_parameters": {"correlation": correlation},
4087 }
4088
4089 if spi:
4090 sfp_dict["chain_id"] = spi
4091
4092 self.logger.info("Adding a new SFP to VIM, {}.".format(sfp_dict))
4093 new_sfp = self.neutron.create_sfc_port_chain({"port_chain": sfp_dict})
4094
4095 return new_sfp["port_chain"]["id"]
4096 except (
4097 neExceptions.ConnectionFailed,
4098 ksExceptions.ClientException,
4099 neExceptions.NeutronException,
4100 ConnectionError,
4101 ) as e:
4102 if new_sfp:
4103 try:
4104 new_sfp_id = new_sfp.get("port_chain").get("id")
4105 self.neutron.delete_sfc_port_chain(new_sfp_id)
4106 except Exception:
4107 self.logger.error(
4108 "Creation of Service Function Path failed, with "
4109 "subsequent deletion failure as well."
4110 )
4111
4112 self._format_exception(e)
4113
4114 def get_sfp(self, sfp_id):
4115 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
4116
4117 filter_dict = {"id": sfp_id}
4118 sfp_list = self.get_sfp_list(filter_dict)
4119
4120 if len(sfp_list) == 0:
4121 raise vimconn.VimConnNotFoundException(
4122 "Service Function Path '{}' not found".format(sfp_id)
4123 )
4124 elif len(sfp_list) > 1:
4125 raise vimconn.VimConnConflictException(
4126 "Found more than one Service Function Path with this criteria"
4127 )
4128
4129 sfp = sfp_list[0]
4130
4131 return sfp
4132
4133 def get_sfp_list(self, filter_dict={}):
4134 self.logger.debug(
4135 "Getting Service Function Paths from VIM filter: '%s'", str(filter_dict)
4136 )
4137
4138 try:
4139 self._reload_connection()
4140 filter_dict_os = filter_dict.copy()
4141
4142 if self.api_version3 and "tenant_id" in filter_dict_os:
4143 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
4144
4145 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
4146 sfp_list = sfp_dict["port_chains"]
4147 self.__sfp_os2mano(sfp_list)
4148
4149 return sfp_list
4150 except (
4151 neExceptions.ConnectionFailed,
4152 ksExceptions.ClientException,
4153 neExceptions.NeutronException,
4154 ConnectionError,
4155 ) as e:
4156 self._format_exception(e)
4157
4158 def delete_sfp(self, sfp_id):
4159 self.logger.debug("Deleting Service Function Path '%s' from VIM", sfp_id)
4160
4161 try:
4162 self._reload_connection()
4163 self.neutron.delete_sfc_port_chain(sfp_id)
4164
4165 return sfp_id
4166 except (
4167 neExceptions.ConnectionFailed,
4168 neExceptions.NeutronException,
4169 ksExceptions.ClientException,
4170 neExceptions.NeutronException,
4171 ConnectionError,
4172 ) as e:
4173 self._format_exception(e)
4174
4175 def refresh_sfps_status(self, sfp_list):
4176 """Get the status of the service function path
4177 Params: the list of sfp identifiers
4178 Returns a dictionary with:
4179 vm_id: #VIM id of this service function path
4180 status: #Mandatory. Text with one of:
4181 # DELETED (not found at vim)
4182 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4183 # OTHER (Vim reported other status not understood)
4184 # ERROR (VIM indicates an ERROR status)
4185 # ACTIVE,
4186 # CREATING (on building process)
4187 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4188 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)F
4189 """
4190 sfp_dict = {}
4191 self.logger.debug(
4192 "refresh_sfps status: Getting tenant SFP information from VIM"
4193 )
4194
4195 for sfp_id in sfp_list:
4196 sfp = {}
4197
4198 try:
4199 sfp_vim = self.get_sfp(sfp_id)
4200
4201 if sfp_vim["spi"]:
4202 sfp["status"] = vmStatus2manoFormat["ACTIVE"]
4203 else:
4204 sfp["status"] = "OTHER"
4205 sfp["error_msg"] = "VIM status reported " + sfp["status"]
4206
4207 sfp["vim_info"] = self.serialize(sfp_vim)
4208
4209 if sfp_vim.get("fault"):
4210 sfp["error_msg"] = str(sfp_vim["fault"])
4211 except vimconn.VimConnNotFoundException as e:
4212 self.logger.error("Exception getting sfp status: %s", str(e))
4213 sfp["status"] = "DELETED"
4214 sfp["error_msg"] = str(e)
4215 except vimconn.VimConnException as e:
4216 self.logger.error("Exception getting sfp status: %s", str(e))
4217 sfp["status"] = "VIM_ERROR"
4218 sfp["error_msg"] = str(e)
4219
4220 sfp_dict[sfp_id] = sfp
4221
4222 return sfp_dict
4223
4224 def refresh_sfis_status(self, sfi_list):
4225 """Get the status of the service function instances
4226 Params: the list of sfi identifiers
4227 Returns a dictionary with:
4228 vm_id: #VIM id of this service function instance
4229 status: #Mandatory. Text with one of:
4230 # DELETED (not found at vim)
4231 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4232 # OTHER (Vim reported other status not understood)
4233 # ERROR (VIM indicates an ERROR status)
4234 # ACTIVE,
4235 # CREATING (on building process)
4236 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4237 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4238 """
4239 sfi_dict = {}
4240 self.logger.debug(
4241 "refresh_sfis status: Getting tenant sfi information from VIM"
4242 )
4243
4244 for sfi_id in sfi_list:
4245 sfi = {}
4246
4247 try:
4248 sfi_vim = self.get_sfi(sfi_id)
4249
4250 if sfi_vim:
4251 sfi["status"] = vmStatus2manoFormat["ACTIVE"]
4252 else:
4253 sfi["status"] = "OTHER"
4254 sfi["error_msg"] = "VIM status reported " + sfi["status"]
4255
4256 sfi["vim_info"] = self.serialize(sfi_vim)
4257
4258 if sfi_vim.get("fault"):
4259 sfi["error_msg"] = str(sfi_vim["fault"])
4260 except vimconn.VimConnNotFoundException as e:
4261 self.logger.error("Exception getting sfi status: %s", str(e))
4262 sfi["status"] = "DELETED"
4263 sfi["error_msg"] = str(e)
4264 except vimconn.VimConnException as e:
4265 self.logger.error("Exception getting sfi status: %s", str(e))
4266 sfi["status"] = "VIM_ERROR"
4267 sfi["error_msg"] = str(e)
4268
4269 sfi_dict[sfi_id] = sfi
4270
4271 return sfi_dict
4272
4273 def refresh_sfs_status(self, sf_list):
4274 """Get the status of the service functions
4275 Params: the list of sf identifiers
4276 Returns a dictionary with:
4277 vm_id: #VIM id of this service function
4278 status: #Mandatory. Text with one of:
4279 # DELETED (not found at vim)
4280 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4281 # OTHER (Vim reported other status not understood)
4282 # ERROR (VIM indicates an ERROR status)
4283 # ACTIVE,
4284 # CREATING (on building process)
4285 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4286 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4287 """
4288 sf_dict = {}
4289 self.logger.debug("refresh_sfs status: Getting tenant sf information from VIM")
4290
4291 for sf_id in sf_list:
4292 sf = {}
4293
4294 try:
4295 sf_vim = self.get_sf(sf_id)
4296
4297 if sf_vim:
4298 sf["status"] = vmStatus2manoFormat["ACTIVE"]
4299 else:
4300 sf["status"] = "OTHER"
4301 sf["error_msg"] = "VIM status reported " + sf_vim["status"]
4302
4303 sf["vim_info"] = self.serialize(sf_vim)
4304
4305 if sf_vim.get("fault"):
4306 sf["error_msg"] = str(sf_vim["fault"])
4307 except vimconn.VimConnNotFoundException as e:
4308 self.logger.error("Exception getting sf status: %s", str(e))
4309 sf["status"] = "DELETED"
4310 sf["error_msg"] = str(e)
4311 except vimconn.VimConnException as e:
4312 self.logger.error("Exception getting sf status: %s", str(e))
4313 sf["status"] = "VIM_ERROR"
4314 sf["error_msg"] = str(e)
4315
4316 sf_dict[sf_id] = sf
4317
4318 return sf_dict
4319
4320 def refresh_classifications_status(self, classification_list):
4321 """Get the status of the classifications
4322 Params: the list of classification identifiers
4323 Returns a dictionary with:
4324 vm_id: #VIM id of this classifier
4325 status: #Mandatory. Text with one of:
4326 # DELETED (not found at vim)
4327 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4328 # OTHER (Vim reported other status not understood)
4329 # ERROR (VIM indicates an ERROR status)
4330 # ACTIVE,
4331 # CREATING (on building process)
4332 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4333 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4334 """
4335 classification_dict = {}
4336 self.logger.debug(
4337 "refresh_classifications status: Getting tenant classification information from VIM"
4338 )
4339
4340 for classification_id in classification_list:
4341 classification = {}
4342
4343 try:
4344 classification_vim = self.get_classification(classification_id)
4345
4346 if classification_vim:
4347 classification["status"] = vmStatus2manoFormat["ACTIVE"]
4348 else:
4349 classification["status"] = "OTHER"
4350 classification["error_msg"] = (
4351 "VIM status reported " + classification["status"]
4352 )
4353
4354 classification["vim_info"] = self.serialize(classification_vim)
4355
4356 if classification_vim.get("fault"):
4357 classification["error_msg"] = str(classification_vim["fault"])
4358 except vimconn.VimConnNotFoundException as e:
4359 self.logger.error("Exception getting classification status: %s", str(e))
4360 classification["status"] = "DELETED"
4361 classification["error_msg"] = str(e)
4362 except vimconn.VimConnException as e:
4363 self.logger.error("Exception getting classification status: %s", str(e))
4364 classification["status"] = "VIM_ERROR"
4365 classification["error_msg"] = str(e)
4366
4367 classification_dict[classification_id] = classification
4368
4369 return classification_dict
4370
gatici335a06a2023-07-26 00:34:04 +03004371 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004372 def new_affinity_group(self, affinity_group_data):
4373 """Adds a server group to VIM
4374 affinity_group_data contains a dictionary with information, keys:
4375 name: name in VIM for the server group
4376 type: affinity or anti-affinity
4377 scope: Only nfvi-node allowed
4378 Returns the server group identifier"""
4379 self.logger.debug("Adding Server Group '%s'", str(affinity_group_data))
gatici335a06a2023-07-26 00:34:04 +03004380 name = affinity_group_data["name"]
4381 policy = affinity_group_data["type"]
4382 self._reload_connection()
4383 new_server_group = self.nova.server_groups.create(name, policy)
4384 return new_server_group.id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004385
gatici335a06a2023-07-26 00:34:04 +03004386 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004387 def get_affinity_group(self, affinity_group_id):
4388 """Obtain server group details from the VIM. Returns the server group detais as a dict"""
4389 self.logger.debug("Getting flavor '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004390 self._reload_connection()
4391 server_group = self.nova.server_groups.find(id=affinity_group_id)
4392 return server_group.to_dict()
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004393
gatici335a06a2023-07-26 00:34:04 +03004394 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004395 def delete_affinity_group(self, affinity_group_id):
4396 """Deletes a server group from the VIM. Returns the old affinity_group_id"""
4397 self.logger.debug("Getting server group '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004398 self._reload_connection()
4399 self.nova.server_groups.delete(affinity_group_id)
4400 return affinity_group_id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004401
gatici335a06a2023-07-26 00:34:04 +03004402 @catch_any_exception
Patricia Reinoso17852162023-06-15 07:33:04 +00004403 def get_vdu_state(self, vm_id, host_is_required=False) -> list:
4404 """Getting the state of a VDU.
4405 Args:
4406 vm_id (str): ID of an instance
4407 host_is_required (Boolean): If the VIM account is non-admin, host info does not appear in server_dict
4408 and if this is set to True, it raises KeyError.
4409 Returns:
4410 vdu_data (list): VDU details including state, flavor, host_info, AZ
elumalai8658c2c2022-04-28 19:09:31 +05304411 """
4412 self.logger.debug("Getting the status of VM")
4413 self.logger.debug("VIM VM ID %s", vm_id)
gatici335a06a2023-07-26 00:34:04 +03004414 self._reload_connection()
4415 server_dict = self._find_nova_server(vm_id)
4416 srv_attr = "OS-EXT-SRV-ATTR:host"
4417 host_info = (
4418 server_dict[srv_attr] if host_is_required else server_dict.get(srv_attr)
4419 )
4420 vdu_data = [
4421 server_dict["status"],
4422 server_dict["flavor"]["id"],
4423 host_info,
4424 server_dict["OS-EXT-AZ:availability_zone"],
4425 ]
4426 self.logger.debug("vdu_data %s", vdu_data)
4427 return vdu_data
elumalai8658c2c2022-04-28 19:09:31 +05304428
4429 def check_compute_availability(self, host, server_flavor_details):
4430 self._reload_connection()
4431 hypervisor_search = self.nova.hypervisors.search(
4432 hypervisor_match=host, servers=True
4433 )
4434 for hypervisor in hypervisor_search:
4435 hypervisor_id = hypervisor.to_dict()["id"]
4436 hypervisor_details = self.nova.hypervisors.get(hypervisor=hypervisor_id)
4437 hypervisor_dict = hypervisor_details.to_dict()
4438 hypervisor_temp = json.dumps(hypervisor_dict)
4439 hypervisor_json = json.loads(hypervisor_temp)
4440 resources_available = [
4441 hypervisor_json["free_ram_mb"],
4442 hypervisor_json["disk_available_least"],
4443 hypervisor_json["vcpus"] - hypervisor_json["vcpus_used"],
4444 ]
4445 compute_available = all(
4446 x > y for x, y in zip(resources_available, server_flavor_details)
4447 )
4448 if compute_available:
4449 return host
4450
4451 def check_availability_zone(
4452 self, old_az, server_flavor_details, old_host, host=None
4453 ):
4454 self._reload_connection()
4455 az_check = {"zone_check": False, "compute_availability": None}
4456 aggregates_list = self.nova.aggregates.list()
4457 for aggregate in aggregates_list:
4458 aggregate_details = aggregate.to_dict()
4459 aggregate_temp = json.dumps(aggregate_details)
4460 aggregate_json = json.loads(aggregate_temp)
4461 if aggregate_json["availability_zone"] == old_az:
4462 hosts_list = aggregate_json["hosts"]
4463 if host is not None:
4464 if host in hosts_list:
4465 az_check["zone_check"] = True
4466 available_compute_id = self.check_compute_availability(
4467 host, server_flavor_details
4468 )
4469 if available_compute_id is not None:
4470 az_check["compute_availability"] = available_compute_id
4471 else:
4472 for check_host in hosts_list:
4473 if check_host != old_host:
4474 available_compute_id = self.check_compute_availability(
4475 check_host, server_flavor_details
4476 )
4477 if available_compute_id is not None:
4478 az_check["zone_check"] = True
4479 az_check["compute_availability"] = available_compute_id
4480 break
4481 else:
4482 az_check["zone_check"] = True
4483 return az_check
4484
gatici335a06a2023-07-26 00:34:04 +03004485 @catch_any_exception
elumalai8658c2c2022-04-28 19:09:31 +05304486 def migrate_instance(self, vm_id, compute_host=None):
4487 """
4488 Migrate a vdu
4489 param:
4490 vm_id: ID of an instance
4491 compute_host: Host to migrate the vdu to
4492 """
4493 self._reload_connection()
4494 vm_state = False
Patricia Reinoso17852162023-06-15 07:33:04 +00004495 instance_state = self.get_vdu_state(vm_id, host_is_required=True)
elumalai8658c2c2022-04-28 19:09:31 +05304496 server_flavor_id = instance_state[1]
4497 server_hypervisor_name = instance_state[2]
4498 server_availability_zone = instance_state[3]
gatici335a06a2023-07-26 00:34:04 +03004499 server_flavor = self.nova.flavors.find(id=server_flavor_id).to_dict()
4500 server_flavor_details = [
4501 server_flavor["ram"],
4502 server_flavor["disk"],
4503 server_flavor["vcpus"],
4504 ]
4505 if compute_host == server_hypervisor_name:
4506 raise vimconn.VimConnException(
4507 "Unable to migrate instance '{}' to the same host '{}'".format(
4508 vm_id, compute_host
4509 ),
4510 http_code=vimconn.HTTP_Bad_Request,
elumalai8658c2c2022-04-28 19:09:31 +05304511 )
gatici335a06a2023-07-26 00:34:04 +03004512 az_status = self.check_availability_zone(
4513 server_availability_zone,
4514 server_flavor_details,
4515 server_hypervisor_name,
4516 compute_host,
4517 )
4518 availability_zone_check = az_status["zone_check"]
4519 available_compute_id = az_status.get("compute_availability")
elumalai8658c2c2022-04-28 19:09:31 +05304520
gatici335a06a2023-07-26 00:34:04 +03004521 if availability_zone_check is False:
4522 raise vimconn.VimConnException(
4523 "Unable to migrate instance '{}' to a different availability zone".format(
4524 vm_id
4525 ),
4526 http_code=vimconn.HTTP_Bad_Request,
4527 )
4528 if available_compute_id is not None:
4529 # disk_over_commit parameter for live_migrate method is not valid for Nova API version >= 2.25
4530 self.nova.servers.live_migrate(
4531 server=vm_id,
4532 host=available_compute_id,
4533 block_migration=True,
4534 )
4535 state = "MIGRATING"
4536 changed_compute_host = ""
4537 if state == "MIGRATING":
4538 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
4539 changed_compute_host = self.get_vdu_state(vm_id, host_is_required=True)[
4540 2
4541 ]
4542 if vm_state and changed_compute_host == available_compute_id:
4543 self.logger.debug(
4544 "Instance '{}' migrated to the new compute host '{}'".format(
4545 vm_id, changed_compute_host
elumalai8658c2c2022-04-28 19:09:31 +05304546 )
gatici335a06a2023-07-26 00:34:04 +03004547 )
4548 return state, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304549 else:
4550 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03004551 "Migration Failed. Instance '{}' not moved to the new host {}".format(
4552 vm_id, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304553 ),
4554 http_code=vimconn.HTTP_Bad_Request,
4555 )
gatici335a06a2023-07-26 00:34:04 +03004556 else:
4557 raise vimconn.VimConnException(
4558 "Compute '{}' not available or does not have enough resources to migrate the instance".format(
4559 available_compute_id
4560 ),
4561 http_code=vimconn.HTTP_Bad_Request,
4562 )
sritharan29a4c1a2022-05-05 12:15:04 +00004563
gatici335a06a2023-07-26 00:34:04 +03004564 @catch_any_exception
sritharan29a4c1a2022-05-05 12:15:04 +00004565 def resize_instance(self, vm_id, new_flavor_id):
4566 """
4567 For resizing the vm based on the given
4568 flavor details
4569 param:
4570 vm_id : ID of an instance
4571 new_flavor_id : Flavor id to be resized
4572 Return the status of a resized instance
4573 """
4574 self._reload_connection()
4575 self.logger.debug("resize the flavor of an instance")
4576 instance_status, old_flavor_id, compute_host, az = self.get_vdu_state(vm_id)
4577 old_flavor_disk = self.nova.flavors.find(id=old_flavor_id).to_dict()["disk"]
4578 new_flavor_disk = self.nova.flavors.find(id=new_flavor_id).to_dict()["disk"]
gatici335a06a2023-07-26 00:34:04 +03004579 if instance_status == "ACTIVE" or instance_status == "SHUTOFF":
4580 if old_flavor_disk > new_flavor_disk:
sritharan29a4c1a2022-05-05 12:15:04 +00004581 raise nvExceptions.BadRequest(
gatici335a06a2023-07-26 00:34:04 +03004582 400,
4583 message="Server disk resize failed. Resize to lower disk flavor is not allowed",
sritharan29a4c1a2022-05-05 12:15:04 +00004584 )
gatici335a06a2023-07-26 00:34:04 +03004585 else:
4586 self.nova.servers.resize(server=vm_id, flavor=new_flavor_id)
4587 vm_state = self.__wait_for_vm(vm_id, "VERIFY_RESIZE")
4588 if vm_state:
deepika.e970f5552024-06-03 14:12:50 +05304589 instance_resized_status = self.confirm_resize(
4590 vm_id, instance_status
4591 )
gatici335a06a2023-07-26 00:34:04 +03004592 return instance_resized_status
4593 else:
4594 raise nvExceptions.BadRequest(
4595 409,
4596 message="Cannot 'resize' vm_state is in ERROR",
4597 )
4598
4599 else:
4600 self.logger.debug("ERROR : Instance is not in ACTIVE or SHUTOFF state")
4601 raise nvExceptions.BadRequest(
4602 409,
4603 message="Cannot 'resize' instance while it is in vm_state resized",
4604 )
sritharan29a4c1a2022-05-05 12:15:04 +00004605
deepika.e970f5552024-06-03 14:12:50 +05304606 def confirm_resize(self, vm_id, instance_state):
sritharan29a4c1a2022-05-05 12:15:04 +00004607 """
4608 Confirm the resize of an instance
4609 param:
4610 vm_id: ID of an instance
4611 """
4612 self._reload_connection()
4613 self.nova.servers.confirm_resize(server=vm_id)
4614 if self.get_vdu_state(vm_id)[0] == "VERIFY_RESIZE":
deepika.e970f5552024-06-03 14:12:50 +05304615 self.__wait_for_vm(vm_id, instance_state)
sritharan29a4c1a2022-05-05 12:15:04 +00004616 instance_status = self.get_vdu_state(vm_id)[0]
4617 return instance_status
Gulsum Aticid586d892023-02-13 18:40:03 +03004618
4619 def get_monitoring_data(self):
4620 try:
4621 self.logger.debug("Getting servers and ports data from Openstack VIMs.")
4622 self._reload_connection()
4623 all_servers = self.nova.servers.list(detailed=True)
vegallc53829d2023-06-01 00:47:44 -05004624 try:
4625 for server in all_servers:
Luis Vegad6577d82023-07-26 20:49:12 +00004626 if server.flavor.get("original_name"):
4627 server.flavor["id"] = self.nova.flavors.find(
4628 name=server.flavor["original_name"]
4629 ).id
vegallc53829d2023-06-01 00:47:44 -05004630 except nClient.exceptions.NotFound as e:
4631 self.logger.warning(str(e.message))
Gulsum Aticid586d892023-02-13 18:40:03 +03004632 all_ports = self.neutron.list_ports()
4633 return all_servers, all_ports
gatici335a06a2023-07-26 00:34:04 +03004634 except Exception as e:
Gulsum Aticid586d892023-02-13 18:40:03 +03004635 raise vimconn.VimConnException(
4636 f"Exception in monitoring while getting VMs and ports status: {str(e)}"
4637 )