blob: 78af39da1415624c1f44b12d98a8624355c3edf3 [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
kayal20019b81cb62024-11-28 11:05:24 +05301261 def get_flavor(self, flavor_id=None, flavor_name=None):
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()
kayal20019b81cb62024-11-28 11:05:24 +05301266 if flavor_id:
1267 flavor = self.nova.flavors.find(id=flavor_id)
1268 else:
1269 flavor = self.nova.flavors.find(name=flavor_name)
tiernoae4a8d12016-07-08 12:30:39 +02001270 return flavor.to_dict()
gatici335a06a2023-07-26 00:34:04 +03001271
sousaedu80135b92021-02-17 15:05:18 +01001272 except (
1273 nvExceptions.NotFound,
1274 nvExceptions.ClientException,
1275 ksExceptions.ClientException,
1276 ConnectionError,
1277 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001278 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001279
tiernocf157a82017-01-30 14:07:06 +01001280 def get_flavor_id_from_data(self, flavor_dict):
1281 """Obtain flavor id that match the flavor description
sousaedu80135b92021-02-17 15:05:18 +01001282 Returns the flavor_id or raises a vimconnNotFoundException
1283 flavor_dict: contains the required ram, vcpus, disk
1284 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
1285 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
1286 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +01001287 """
sousaedu80135b92021-02-17 15:05:18 +01001288 exact_match = False if self.config.get("use_existing_flavors") else True
1289
tiernocf157a82017-01-30 14:07:06 +01001290 try:
1291 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +02001292 flavor_candidate_id = None
1293 flavor_candidate_data = (10000, 10000, 10000)
sousaedu80135b92021-02-17 15:05:18 +01001294 flavor_target = (
1295 flavor_dict["ram"],
1296 flavor_dict["vcpus"],
1297 flavor_dict["disk"],
sousaedu648ee3d2021-11-22 14:09:15 +00001298 flavor_dict.get("ephemeral", 0),
1299 flavor_dict.get("swap", 0),
sousaedu80135b92021-02-17 15:05:18 +01001300 )
tiernoe26fc7a2017-05-30 14:43:03 +02001301 # numa=None
anwarsae5f52c2019-04-22 10:35:27 +05301302 extended = flavor_dict.get("extended", {})
1303 if extended:
tierno1ec592d2020-06-16 15:29:47 +00001304 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001305 raise vimconn.VimConnNotFoundException(
1306 "Flavor with EPA still not implemented"
1307 )
tiernocf157a82017-01-30 14:07:06 +01001308 # if len(numas) > 1:
tierno72774862020-05-04 11:44:15 +00001309 # raise vimconn.VimConnNotFoundException("Cannot find any flavor with more than one numa")
tiernocf157a82017-01-30 14:07:06 +01001310 # numa=numas[0]
1311 # numas = extended.get("numas")
1312 for flavor in self.nova.flavors.list():
1313 epa = flavor.get_keys()
sousaedu80135b92021-02-17 15:05:18 +01001314
tiernocf157a82017-01-30 14:07:06 +01001315 if epa:
1316 continue
tiernoe26fc7a2017-05-30 14:43:03 +02001317 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001318
sousaedu648ee3d2021-11-22 14:09:15 +00001319 flavor_data = (
1320 flavor.ram,
1321 flavor.vcpus,
1322 flavor.disk,
1323 flavor.ephemeral,
preethika.pebaba1f2022-01-20 07:24:18 +00001324 flavor.swap if isinstance(flavor.swap, int) else 0,
sousaedu648ee3d2021-11-22 14:09:15 +00001325 )
tiernoe26fc7a2017-05-30 14:43:03 +02001326 if flavor_data == flavor_target:
1327 return flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001328 elif (
1329 not exact_match
1330 and flavor_target < flavor_data < flavor_candidate_data
1331 ):
tiernoe26fc7a2017-05-30 14:43:03 +02001332 flavor_candidate_id = flavor.id
1333 flavor_candidate_data = flavor_data
sousaedu80135b92021-02-17 15:05:18 +01001334
tiernoe26fc7a2017-05-30 14:43:03 +02001335 if not exact_match and flavor_candidate_id:
1336 return flavor_candidate_id
sousaedu80135b92021-02-17 15:05:18 +01001337
1338 raise vimconn.VimConnNotFoundException(
1339 "Cannot find any flavor matching '{}'".format(flavor_dict)
1340 )
1341 except (
1342 nvExceptions.NotFound,
gatici335a06a2023-07-26 00:34:04 +03001343 nvExceptions.BadRequest,
sousaedu80135b92021-02-17 15:05:18 +01001344 nvExceptions.ClientException,
1345 ksExceptions.ClientException,
1346 ConnectionError,
1347 ) as e:
tiernocf157a82017-01-30 14:07:06 +01001348 self._format_exception(e)
1349
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001350 @staticmethod
1351 def process_resource_quota(quota: dict, prefix: str, extra_specs: dict) -> None:
1352 """Process resource quota and fill up extra_specs.
1353 Args:
1354 quota (dict): Keeping the quota of resurces
1355 prefix (str) Prefix
1356 extra_specs (dict) Dict to be filled to be used during flavor creation
1357
anwarsae5f52c2019-04-22 10:35:27 +05301358 """
sousaedu80135b92021-02-17 15:05:18 +01001359 if "limit" in quota:
1360 extra_specs["quota:" + prefix + "_limit"] = quota["limit"]
1361
1362 if "reserve" in quota:
1363 extra_specs["quota:" + prefix + "_reservation"] = quota["reserve"]
1364
1365 if "shares" in quota:
anwarsae5f52c2019-04-22 10:35:27 +05301366 extra_specs["quota:" + prefix + "_shares_level"] = "custom"
sousaedu80135b92021-02-17 15:05:18 +01001367 extra_specs["quota:" + prefix + "_shares_share"] = quota["shares"]
anwarsae5f52c2019-04-22 10:35:27 +05301368
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001369 @staticmethod
1370 def process_numa_memory(
1371 numa: dict, node_id: Optional[int], extra_specs: dict
1372 ) -> None:
1373 """Set the memory in extra_specs.
1374 Args:
1375 numa (dict): A dictionary which includes numa information
1376 node_id (int): ID of numa node
1377 extra_specs (dict): To be filled.
1378
1379 """
1380 if not numa.get("memory"):
1381 return
1382 memory_mb = numa["memory"] * 1024
1383 memory = "hw:numa_mem.{}".format(node_id)
1384 extra_specs[memory] = int(memory_mb)
1385
1386 @staticmethod
1387 def process_numa_vcpu(numa: dict, node_id: int, extra_specs: dict) -> None:
1388 """Set the cpu in extra_specs.
1389 Args:
1390 numa (dict): A dictionary which includes numa information
1391 node_id (int): ID of numa node
1392 extra_specs (dict): To be filled.
1393
1394 """
1395 if not numa.get("vcpu"):
1396 return
1397 vcpu = numa["vcpu"]
1398 cpu = "hw:numa_cpus.{}".format(node_id)
1399 vcpu = ",".join(map(str, vcpu))
1400 extra_specs[cpu] = vcpu
1401
1402 @staticmethod
1403 def process_numa_paired_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1404 """Fill up extra_specs if numa has paired-threads.
1405 Args:
1406 numa (dict): A dictionary which includes numa information
1407 extra_specs (dict): To be filled.
1408
1409 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001410 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001411
1412 """
1413 if not numa.get("paired-threads"):
1414 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001415
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001416 # cpu_thread_policy "require" implies that compute node must have an STM architecture
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001417 threads = numa["paired-threads"] * 2
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001418 extra_specs["hw:cpu_thread_policy"] = "require"
1419 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001420 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001421
1422 @staticmethod
1423 def process_numa_cores(numa: dict, extra_specs: dict) -> Optional[int]:
1424 """Fill up extra_specs if numa has cores.
1425 Args:
1426 numa (dict): A dictionary which includes numa information
1427 extra_specs (dict): To be filled.
1428
1429 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001430 cores (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001431
1432 """
1433 # cpu_thread_policy "isolate" implies that the host must not have an SMT
1434 # architecture, or a non-SMT architecture will be emulated
1435 if not numa.get("cores"):
1436 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001437 cores = numa["cores"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001438 extra_specs["hw:cpu_thread_policy"] = "isolate"
1439 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001440 return cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001441
1442 @staticmethod
1443 def process_numa_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1444 """Fill up extra_specs if numa has threads.
1445 Args:
1446 numa (dict): A dictionary which includes numa information
1447 extra_specs (dict): To be filled.
1448
1449 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001450 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001451
1452 """
1453 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
1454 if not numa.get("threads"):
1455 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001456 threads = numa["threads"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001457 extra_specs["hw:cpu_thread_policy"] = "prefer"
1458 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001459 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001460
1461 def _process_numa_parameters_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001462 self, numas: List, extra_specs: Dict
1463 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001464 """Process numa parameters and fill up extra_specs.
1465
1466 Args:
1467 numas (list): List of dictionary which includes numa information
1468 extra_specs (dict): To be filled.
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001469
1470 """
1471 numa_nodes = len(numas)
1472 extra_specs["hw:numa_nodes"] = str(numa_nodes)
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001473 cpu_cores, cpu_threads = 0, 0
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001474
1475 if self.vim_type == "VIO":
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001476 self.process_vio_numa_nodes(numa_nodes, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001477
1478 for numa in numas:
1479 if "id" in numa:
1480 node_id = numa["id"]
1481 # overwrite ram and vcpus
1482 # check if key "memory" is present in numa else use ram value at flavor
1483 self.process_numa_memory(numa, node_id, extra_specs)
1484 self.process_numa_vcpu(numa, node_id, extra_specs)
1485
1486 # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
1487 extra_specs["hw:cpu_sockets"] = str(numa_nodes)
1488
1489 if "paired-threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001490 threads = self.process_numa_paired_threads(numa, extra_specs)
1491 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001492
1493 elif "cores" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001494 cores = self.process_numa_cores(numa, extra_specs)
1495 cpu_cores += cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001496
1497 elif "threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001498 threads = self.process_numa_threads(numa, extra_specs)
1499 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001500
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001501 if cpu_cores:
1502 extra_specs["hw:cpu_cores"] = str(cpu_cores)
1503 if cpu_threads:
1504 extra_specs["hw:cpu_threads"] = str(cpu_threads)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001505
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001506 @staticmethod
1507 def process_vio_numa_nodes(numa_nodes: int, extra_specs: Dict) -> None:
1508 """According to number of numa nodes, updates the extra_specs for VIO.
1509
1510 Args:
1511
1512 numa_nodes (int): List keeps the numa node numbers
1513 extra_specs (dict): Extra specs dict to be updated
1514
1515 """
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001516 # If there are several numas, we do not define specific affinity.
1517 extra_specs["vmware:latency_sensitivity_level"] = "high"
1518
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001519 def _change_flavor_name(
1520 self, name: str, name_suffix: int, flavor_data: dict
1521 ) -> str:
1522 """Change the flavor name if the name already exists.
1523
1524 Args:
1525 name (str): Flavor name to be checked
1526 name_suffix (int): Suffix to be appended to name
1527 flavor_data (dict): Flavor dict
1528
1529 Returns:
1530 name (str): New flavor name to be used
1531
1532 """
1533 # Get used names
1534 fl = self.nova.flavors.list()
1535 fl_names = [f.name for f in fl]
1536
1537 while name in fl_names:
1538 name_suffix += 1
1539 name = flavor_data["name"] + "-" + str(name_suffix)
1540
1541 return name
1542
1543 def _process_extended_config_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001544 self, extended: dict, extra_specs: dict
1545 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001546 """Process the extended dict to fill up extra_specs.
1547 Args:
1548
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001549 extended (dict): Keeping the extra specification of flavor
1550 extra_specs (dict) Dict to be filled to be used during flavor creation
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001551
1552 """
1553 quotas = {
1554 "cpu-quota": "cpu",
1555 "mem-quota": "memory",
1556 "vif-quota": "vif",
1557 "disk-io-quota": "disk_io",
1558 }
1559
1560 page_sizes = {
1561 "LARGE": "large",
1562 "SMALL": "small",
1563 "SIZE_2MB": "2MB",
1564 "SIZE_1GB": "1GB",
1565 "PREFER_LARGE": "any",
1566 }
1567
1568 policies = {
1569 "cpu-pinning-policy": "hw:cpu_policy",
1570 "cpu-thread-pinning-policy": "hw:cpu_thread_policy",
1571 "mem-policy": "hw:numa_mempolicy",
1572 }
1573
1574 numas = extended.get("numas")
1575 if numas:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001576 self._process_numa_parameters_of_flavor(numas, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001577
1578 for quota, item in quotas.items():
1579 if quota in extended.keys():
1580 self.process_resource_quota(extended.get(quota), item, extra_specs)
1581
1582 # Set the mempage size as specified in the descriptor
1583 if extended.get("mempage-size"):
1584 if extended["mempage-size"] in page_sizes.keys():
1585 extra_specs["hw:mem_page_size"] = page_sizes[extended["mempage-size"]]
1586 else:
1587 # Normally, validations in NBI should not allow to this condition.
1588 self.logger.debug(
1589 "Invalid mempage-size %s. Will be ignored",
1590 extended.get("mempage-size"),
1591 )
1592
1593 for policy, hw_policy in policies.items():
1594 if extended.get(policy):
1595 extra_specs[hw_policy] = extended[policy].lower()
1596
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001597 @staticmethod
1598 def _get_flavor_details(flavor_data: dict) -> Tuple:
1599 """Returns the details of flavor
1600 Args:
1601 flavor_data (dict): Dictionary that includes required flavor details
1602
1603 Returns:
1604 ram, vcpus, extra_specs, extended (tuple): Main items of required flavor
1605
1606 """
1607 return (
1608 flavor_data.get("ram", 64),
1609 flavor_data.get("vcpus", 1),
1610 {},
1611 flavor_data.get("extended"),
1612 )
1613
gatici335a06a2023-07-26 00:34:04 +03001614 @catch_any_exception
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001615 def new_flavor(self, flavor_data: dict, change_name_if_used: bool = True) -> str:
1616 """Adds a tenant flavor to openstack VIM.
1617 if change_name_if_used is True, it will change name in case of conflict,
1618 because it is not supported name repetition.
1619
1620 Args:
1621 flavor_data (dict): Flavor details to be processed
1622 change_name_if_used (bool): Change name in case of conflict
1623
1624 Returns:
1625 flavor_id (str): flavor identifier
1626
tierno1ec592d2020-06-16 15:29:47 +00001627 """
tiernoae4a8d12016-07-08 12:30:39 +02001628 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno1ec592d2020-06-16 15:29:47 +00001629 retry = 0
1630 max_retries = 3
tierno7edb6752016-03-21 17:37:52 +01001631 name_suffix = 0
gatici335a06a2023-07-26 00:34:04 +03001632 name = flavor_data["name"]
1633 while retry < max_retries:
1634 retry += 1
1635 try:
1636 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001637
gatici335a06a2023-07-26 00:34:04 +03001638 if change_name_if_used:
1639 name = self._change_flavor_name(name, name_suffix, flavor_data)
sousaedu80135b92021-02-17 15:05:18 +01001640
gatici335a06a2023-07-26 00:34:04 +03001641 ram, vcpus, extra_specs, extended = self._get_flavor_details(
1642 flavor_data
1643 )
1644 if extended:
1645 self._process_extended_config_of_flavor(extended, extra_specs)
sousaedu80135b92021-02-17 15:05:18 +01001646
gatici335a06a2023-07-26 00:34:04 +03001647 # Create flavor
sousaedu80135b92021-02-17 15:05:18 +01001648
gatici335a06a2023-07-26 00:34:04 +03001649 new_flavor = self.nova.flavors.create(
1650 name=name,
1651 ram=ram,
1652 vcpus=vcpus,
1653 disk=flavor_data.get("disk", 0),
1654 ephemeral=flavor_data.get("ephemeral", 0),
1655 swap=flavor_data.get("swap", 0),
1656 is_public=flavor_data.get("is_public", True),
1657 )
sousaedu80135b92021-02-17 15:05:18 +01001658
gatici335a06a2023-07-26 00:34:04 +03001659 # Add metadata
1660 if extra_specs:
1661 new_flavor.set_keys(extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001662
gatici335a06a2023-07-26 00:34:04 +03001663 return new_flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001664
gatici335a06a2023-07-26 00:34:04 +03001665 except nvExceptions.Conflict as e:
1666 if change_name_if_used and retry < max_retries:
1667 continue
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001668
gatici335a06a2023-07-26 00:34:04 +03001669 self._format_exception(e)
sousaedu80135b92021-02-17 15:05:18 +01001670
gatici335a06a2023-07-26 00:34:04 +03001671 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00001672 def delete_flavor(self, flavor_id):
sousaedu80135b92021-02-17 15:05:18 +01001673 """Deletes a tenant flavor from openstack VIM. Returns the old flavor_id"""
tiernoae4a8d12016-07-08 12:30:39 +02001674 try:
1675 self._reload_connection()
1676 self.nova.flavors.delete(flavor_id)
1677 return flavor_id
gatici335a06a2023-07-26 00:34:04 +03001678
1679 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
1680 # If flavor is not found, it does not raise.
1681 self.logger.warning(
1682 f"Error deleting flavor: {flavor_id} is not found, {str(e.message)}"
1683 )
tierno7edb6752016-03-21 17:37:52 +01001684
tierno1ec592d2020-06-16 15:29:47 +00001685 def new_image(self, image_dict):
1686 """
tiernoae4a8d12016-07-08 12:30:39 +02001687 Adds a tenant image to VIM. imge_dict is a dictionary with:
1688 name: name
1689 disk_format: qcow2, vhd, vmdk, raw (by default), ...
1690 location: path or URI
1691 public: "yes" or "no"
1692 metadata: metadata of the image
1693 Returns the image_id
tierno1ec592d2020-06-16 15:29:47 +00001694 """
1695 retry = 0
1696 max_retries = 3
sousaedu80135b92021-02-17 15:05:18 +01001697
tierno1ec592d2020-06-16 15:29:47 +00001698 while retry < max_retries:
1699 retry += 1
tierno7edb6752016-03-21 17:37:52 +01001700 try:
1701 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001702
tierno1ec592d2020-06-16 15:29:47 +00001703 # determine format http://docs.openstack.org/developer/glance/formats.html
tierno7edb6752016-03-21 17:37:52 +01001704 if "disk_format" in image_dict:
tierno1ec592d2020-06-16 15:29:47 +00001705 disk_format = image_dict["disk_format"]
1706 else: # autodiscover based on extension
sousaedu80135b92021-02-17 15:05:18 +01001707 if image_dict["location"].endswith(".qcow2"):
tierno1ec592d2020-06-16 15:29:47 +00001708 disk_format = "qcow2"
sousaedu80135b92021-02-17 15:05:18 +01001709 elif image_dict["location"].endswith(".vhd"):
tierno1ec592d2020-06-16 15:29:47 +00001710 disk_format = "vhd"
sousaedu80135b92021-02-17 15:05:18 +01001711 elif image_dict["location"].endswith(".vmdk"):
tierno1ec592d2020-06-16 15:29:47 +00001712 disk_format = "vmdk"
sousaedu80135b92021-02-17 15:05:18 +01001713 elif image_dict["location"].endswith(".vdi"):
tierno1ec592d2020-06-16 15:29:47 +00001714 disk_format = "vdi"
sousaedu80135b92021-02-17 15:05:18 +01001715 elif image_dict["location"].endswith(".iso"):
tierno1ec592d2020-06-16 15:29:47 +00001716 disk_format = "iso"
sousaedu80135b92021-02-17 15:05:18 +01001717 elif image_dict["location"].endswith(".aki"):
tierno1ec592d2020-06-16 15:29:47 +00001718 disk_format = "aki"
sousaedu80135b92021-02-17 15:05:18 +01001719 elif image_dict["location"].endswith(".ari"):
tierno1ec592d2020-06-16 15:29:47 +00001720 disk_format = "ari"
sousaedu80135b92021-02-17 15:05:18 +01001721 elif image_dict["location"].endswith(".ami"):
tierno1ec592d2020-06-16 15:29:47 +00001722 disk_format = "ami"
tierno7edb6752016-03-21 17:37:52 +01001723 else:
tierno1ec592d2020-06-16 15:29:47 +00001724 disk_format = "raw"
sousaedu80135b92021-02-17 15:05:18 +01001725
1726 self.logger.debug(
1727 "new_image: '%s' loading from '%s'",
1728 image_dict["name"],
1729 image_dict["location"],
1730 )
shashankjain3c83a212018-10-04 13:05:46 +05301731 if self.vim_type == "VIO":
1732 container_format = "bare"
sousaedu80135b92021-02-17 15:05:18 +01001733 if "container_format" in image_dict:
1734 container_format = image_dict["container_format"]
1735
1736 new_image = self.glance.images.create(
1737 name=image_dict["name"],
1738 container_format=container_format,
1739 disk_format=disk_format,
1740 )
shashankjain3c83a212018-10-04 13:05:46 +05301741 else:
sousaedu80135b92021-02-17 15:05:18 +01001742 new_image = self.glance.images.create(name=image_dict["name"])
1743
1744 if image_dict["location"].startswith("http"):
tierno1beea862018-07-11 15:47:37 +02001745 # TODO there is not a method to direct download. It must be downloaded locally with requests
tierno72774862020-05-04 11:44:15 +00001746 raise vimconn.VimConnNotImplemented("Cannot create image from URL")
tierno1ec592d2020-06-16 15:29:47 +00001747 else: # local path
sousaedu80135b92021-02-17 15:05:18 +01001748 with open(image_dict["location"]) as fimage:
tierno1beea862018-07-11 15:47:37 +02001749 self.glance.images.upload(new_image.id, fimage)
sousaedu80135b92021-02-17 15:05:18 +01001750 # new_image = self.glancev1.images.create(name=image_dict["name"], is_public=
1751 # image_dict.get("public","yes")=="yes",
tierno1beea862018-07-11 15:47:37 +02001752 # container_format="bare", data=fimage, disk_format=disk_format)
sousaedu80135b92021-02-17 15:05:18 +01001753
1754 metadata_to_load = image_dict.get("metadata")
1755
1756 # TODO location is a reserved word for current openstack versions. fixed for VIO please check
tierno1ec592d2020-06-16 15:29:47 +00001757 # for openstack
shashankjain3c83a212018-10-04 13:05:46 +05301758 if self.vim_type == "VIO":
sousaedu80135b92021-02-17 15:05:18 +01001759 metadata_to_load["upload_location"] = image_dict["location"]
shashankjain3c83a212018-10-04 13:05:46 +05301760 else:
sousaedu80135b92021-02-17 15:05:18 +01001761 metadata_to_load["location"] = image_dict["location"]
1762
tierno1beea862018-07-11 15:47:37 +02001763 self.glance.images.update(new_image.id, **metadata_to_load)
sousaedu80135b92021-02-17 15:05:18 +01001764
tiernoae4a8d12016-07-08 12:30:39 +02001765 return new_image.id
sousaedu80135b92021-02-17 15:05:18 +01001766 except (
sousaedu80135b92021-02-17 15:05:18 +01001767 HTTPException,
1768 gl1Exceptions.HTTPException,
1769 gl1Exceptions.CommunicationError,
1770 ConnectionError,
1771 ) as e:
tierno1ec592d2020-06-16 15:29:47 +00001772 if retry == max_retries:
tiernoae4a8d12016-07-08 12:30:39 +02001773 continue
sousaedu80135b92021-02-17 15:05:18 +01001774
tiernoae4a8d12016-07-08 12:30:39 +02001775 self._format_exception(e)
tierno1ec592d2020-06-16 15:29:47 +00001776 except IOError as e: # can not open the file
sousaedu80135b92021-02-17 15:05:18 +01001777 raise vimconn.VimConnConnectionException(
1778 "{}: {} for {}".format(type(e).__name__, e, image_dict["location"]),
1779 http_code=vimconn.HTTP_Bad_Request,
1780 )
gatici335a06a2023-07-26 00:34:04 +03001781 except Exception as e:
1782 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001783
gatici335a06a2023-07-26 00:34:04 +03001784 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001785 def delete_image(self, image_id):
sousaedu80135b92021-02-17 15:05:18 +01001786 """Deletes a tenant image from openstack VIM. Returns the old id"""
tiernoae4a8d12016-07-08 12:30:39 +02001787 try:
1788 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001789 self.glance.images.delete(image_id)
sousaedu80135b92021-02-17 15:05:18 +01001790
tiernoae4a8d12016-07-08 12:30:39 +02001791 return image_id
gatici335a06a2023-07-26 00:34:04 +03001792 except gl1Exceptions.NotFound as e:
1793 # If image is not found, it does not raise.
1794 self.logger.warning(
1795 f"Error deleting image: {image_id} is not found, {str(e)}"
1796 )
tiernoae4a8d12016-07-08 12:30:39 +02001797
gatici335a06a2023-07-26 00:34:04 +03001798 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001799 def get_image_id_from_path(self, path):
tierno1ec592d2020-06-16 15:29:47 +00001800 """Get the image id from image path in the VIM database. Returns the image_id"""
gatici335a06a2023-07-26 00:34:04 +03001801 self._reload_connection()
1802 images = self.glance.images.list()
sousaedu80135b92021-02-17 15:05:18 +01001803
gatici335a06a2023-07-26 00:34:04 +03001804 for image in images:
1805 if image.metadata.get("location") == path:
1806 return image.id
sousaedu80135b92021-02-17 15:05:18 +01001807
gatici335a06a2023-07-26 00:34:04 +03001808 raise vimconn.VimConnNotFoundException(
1809 "image with location '{}' not found".format(path)
1810 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001811
garciadeblasb69fa9f2016-09-28 12:04:10 +02001812 def get_image_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001813 """Obtain tenant images from VIM
garciadeblasb69fa9f2016-09-28 12:04:10 +02001814 Filter_dict can be:
1815 id: image id
1816 name: image name
1817 checksum: image checksum
1818 Returns the image list of dictionaries:
1819 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1820 List can be empty
tierno1ec592d2020-06-16 15:29:47 +00001821 """
garciadeblasb69fa9f2016-09-28 12:04:10 +02001822 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
1823 try:
1824 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001825 # filter_dict_os = filter_dict.copy()
1826 # First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +02001827 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +02001828 filtered_list = []
sousaedu80135b92021-02-17 15:05:18 +01001829
garciadeblasb69fa9f2016-09-28 12:04:10 +02001830 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +02001831 try:
tierno1beea862018-07-11 15:47:37 +02001832 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
1833 continue
sousaedu80135b92021-02-17 15:05:18 +01001834
tierno1beea862018-07-11 15:47:37 +02001835 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
1836 continue
sousaedu80135b92021-02-17 15:05:18 +01001837
1838 if (
1839 filter_dict.get("checksum")
1840 and image["checksum"] != filter_dict["checksum"]
1841 ):
tierno1beea862018-07-11 15:47:37 +02001842 continue
1843
1844 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +02001845 except gl1Exceptions.HTTPNotFound:
1846 pass
sousaedu80135b92021-02-17 15:05:18 +01001847
garciadeblasb69fa9f2016-09-28 12:04:10 +02001848 return filtered_list
gatici335a06a2023-07-26 00:34:04 +03001849
sousaedu80135b92021-02-17 15:05:18 +01001850 except (
1851 ksExceptions.ClientException,
1852 nvExceptions.ClientException,
1853 gl1Exceptions.CommunicationError,
1854 ConnectionError,
1855 ) as e:
garciadeblasb69fa9f2016-09-28 12:04:10 +02001856 self._format_exception(e)
1857
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001858 def __wait_for_vm(self, vm_id, status):
1859 """wait until vm is in the desired status and return True.
1860 If the VM gets in ERROR status, return false.
1861 If the timeout is reached generate an exception"""
1862 elapsed_time = 0
1863 while elapsed_time < server_timeout:
1864 vm_status = self.nova.servers.get(vm_id).status
sousaedu80135b92021-02-17 15:05:18 +01001865
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001866 if vm_status == status:
1867 return True
sousaedu80135b92021-02-17 15:05:18 +01001868
1869 if vm_status == "ERROR":
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001870 return False
sousaedu80135b92021-02-17 15:05:18 +01001871
tierno1df468d2018-07-06 14:25:16 +02001872 time.sleep(5)
1873 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001874
1875 # if we exceeded the timeout rollback
1876 if elapsed_time >= server_timeout:
sousaedu80135b92021-02-17 15:05:18 +01001877 raise vimconn.VimConnException(
1878 "Timeout waiting for instance " + vm_id + " to get " + status,
1879 http_code=vimconn.HTTP_Request_Timeout,
1880 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001881
mirabal29356312017-07-27 12:21:22 +02001882 def _get_openstack_availablity_zones(self):
1883 """
1884 Get from openstack availability zones available
1885 :return:
1886 """
1887 try:
1888 openstack_availability_zone = self.nova.availability_zones.list()
sousaedu80135b92021-02-17 15:05:18 +01001889 openstack_availability_zone = [
1890 str(zone.zoneName)
1891 for zone in openstack_availability_zone
1892 if zone.zoneName != "internal"
1893 ]
1894
mirabal29356312017-07-27 12:21:22 +02001895 return openstack_availability_zone
tierno1ec592d2020-06-16 15:29:47 +00001896 except Exception:
mirabal29356312017-07-27 12:21:22 +02001897 return None
1898
1899 def _set_availablity_zones(self):
1900 """
1901 Set vim availablity zone
1902 :return:
1903 """
sousaedu80135b92021-02-17 15:05:18 +01001904 if "availability_zone" in self.config:
1905 vim_availability_zones = self.config.get("availability_zone")
mirabal29356312017-07-27 12:21:22 +02001906
mirabal29356312017-07-27 12:21:22 +02001907 if isinstance(vim_availability_zones, str):
1908 self.availability_zone = [vim_availability_zones]
1909 elif isinstance(vim_availability_zones, list):
1910 self.availability_zone = vim_availability_zones
1911 else:
1912 self.availability_zone = self._get_openstack_availablity_zones()
Luis Vega25bc6382023-10-05 23:22:04 +00001913 if "storage_availability_zone" in self.config:
1914 self.storage_availability_zone = self.config.get(
1915 "storage_availability_zone"
1916 )
mirabal29356312017-07-27 12:21:22 +02001917
sousaedu80135b92021-02-17 15:05:18 +01001918 def _get_vm_availability_zone(
1919 self, availability_zone_index, availability_zone_list
1920 ):
mirabal29356312017-07-27 12:21:22 +02001921 """
tierno5a3273c2017-08-29 11:43:46 +02001922 Return thge availability zone to be used by the created VM.
1923 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +02001924 """
tierno5a3273c2017-08-29 11:43:46 +02001925 if availability_zone_index is None:
sousaedu80135b92021-02-17 15:05:18 +01001926 if not self.config.get("availability_zone"):
tierno5a3273c2017-08-29 11:43:46 +02001927 return None
sousaedu80135b92021-02-17 15:05:18 +01001928 elif isinstance(self.config.get("availability_zone"), str):
1929 return self.config["availability_zone"]
tierno5a3273c2017-08-29 11:43:46 +02001930 else:
1931 # TODO consider using a different parameter at config for default AV and AV list match
sousaedu80135b92021-02-17 15:05:18 +01001932 return self.config["availability_zone"][0]
mirabal29356312017-07-27 12:21:22 +02001933
tierno5a3273c2017-08-29 11:43:46 +02001934 vim_availability_zones = self.availability_zone
1935 # check if VIM offer enough availability zones describe in the VNFD
sousaedu80135b92021-02-17 15:05:18 +01001936 if vim_availability_zones and len(availability_zone_list) <= len(
1937 vim_availability_zones
1938 ):
tierno5a3273c2017-08-29 11:43:46 +02001939 # check if all the names of NFV AV match VIM AV names
1940 match_by_index = False
1941 for av in availability_zone_list:
1942 if av not in vim_availability_zones:
1943 match_by_index = True
1944 break
sousaedu80135b92021-02-17 15:05:18 +01001945
tierno5a3273c2017-08-29 11:43:46 +02001946 if match_by_index:
1947 return vim_availability_zones[availability_zone_index]
1948 else:
1949 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +02001950 else:
sousaedu80135b92021-02-17 15:05:18 +01001951 raise vimconn.VimConnConflictException(
1952 "No enough availability zones at VIM for this deployment"
1953 )
mirabal29356312017-07-27 12:21:22 +02001954
kayal2001342ee282024-11-28 11:56:49 +05301955 def _prepare_port_dict_security_groups(
1956 self, net: dict, port_dict: dict, security_group_name=None
1957 ) -> None:
Gulsum Atici26f73662022-10-27 15:18:27 +03001958 """Fill up the security_groups in the port_dict.
1959
1960 Args:
1961 net (dict): Network details
1962 port_dict (dict): Port details
1963
1964 """
1965 if (
1966 self.config.get("security_groups")
1967 and net.get("port_security") is not False
1968 and not self.config.get("no_port_security_extension")
1969 ):
1970 if not self.security_groups_id:
1971 self._get_ids_from_name()
1972
1973 port_dict["security_groups"] = self.security_groups_id
1974
kayal200155f836d2025-07-20 13:36:08 +05301975 if security_group_name is not None:
1976 self._get_ids_from_name(security_group_name)
1977 port_dict["security_groups"] = self.security_groups_id
kayal2001342ee282024-11-28 11:56:49 +05301978
Gulsum Atici26f73662022-10-27 15:18:27 +03001979 def _prepare_port_dict_binding(self, net: dict, port_dict: dict) -> None:
1980 """Fill up the network binding depending on network type in the port_dict.
1981
1982 Args:
1983 net (dict): Network details
1984 port_dict (dict): Port details
1985
1986 """
1987 if not net.get("type"):
1988 raise vimconn.VimConnException("Type is missing in the network details.")
1989
1990 if net["type"] == "virtual":
1991 pass
1992
1993 # For VF
1994 elif net["type"] == "VF" or net["type"] == "SR-IOV":
Gulsum Atici26f73662022-10-27 15:18:27 +03001995 port_dict["binding:vnic_type"] = "direct"
1996
1997 # VIO specific Changes
1998 if self.vim_type == "VIO":
1999 # Need to create port with port_security_enabled = False and no-security-groups
2000 port_dict["port_security_enabled"] = False
2001 port_dict["provider_security_groups"] = []
2002 port_dict["security_groups"] = []
2003
2004 else:
2005 # For PT PCI-PASSTHROUGH
2006 port_dict["binding:vnic_type"] = "direct-physical"
2007
2008 @staticmethod
2009 def _set_fixed_ip(new_port: dict, net: dict) -> None:
2010 """Set the "ip" parameter in net dictionary.
2011
2012 Args:
2013 new_port (dict): New created port
2014 net (dict): Network details
2015
2016 """
2017 fixed_ips = new_port["port"].get("fixed_ips")
2018
2019 if fixed_ips:
2020 net["ip"] = fixed_ips[0].get("ip_address")
2021 else:
2022 net["ip"] = None
2023
2024 @staticmethod
2025 def _prepare_port_dict_mac_ip_addr(net: dict, port_dict: dict) -> None:
2026 """Fill up the mac_address and fixed_ips in port_dict.
2027
2028 Args:
2029 net (dict): Network details
2030 port_dict (dict): Port details
2031
2032 """
2033 if net.get("mac_address"):
2034 port_dict["mac_address"] = net["mac_address"]
2035
elumalai370e36b2023-04-25 16:22:56 +05302036 ip_dual_list = []
2037 if ip_list := net.get("ip_address"):
2038 if not isinstance(ip_list, list):
2039 ip_list = [ip_list]
2040 for ip in ip_list:
2041 ip_dict = {"ip_address": ip}
2042 ip_dual_list.append(ip_dict)
2043 port_dict["fixed_ips"] = ip_dual_list
Gulsum Atici26f73662022-10-27 15:18:27 +03002044 # TODO add "subnet_id": <subnet_id>
2045
2046 def _create_new_port(self, port_dict: dict, created_items: dict, net: dict) -> Dict:
2047 """Create new port using neutron.
2048
2049 Args:
2050 port_dict (dict): Port details
2051 created_items (dict): All created items
2052 net (dict): Network details
2053
2054 Returns:
2055 new_port (dict): New created port
2056
2057 """
2058 new_port = self.neutron.create_port({"port": port_dict})
2059 created_items["port:" + str(new_port["port"]["id"])] = True
elumalaie17cd942023-04-28 18:04:24 +05302060 net["mac_address"] = new_port["port"]["mac_address"]
Gulsum Atici26f73662022-10-27 15:18:27 +03002061 net["vim_id"] = new_port["port"]["id"]
2062
2063 return new_port
2064
2065 def _create_port(
kayal2001342ee282024-11-28 11:56:49 +05302066 self, net: dict, name: str, created_items: dict, security_group_name=None
Gulsum Atici26f73662022-10-27 15:18:27 +03002067 ) -> Tuple[dict, dict]:
2068 """Create port using net details.
2069
2070 Args:
2071 net (dict): Network details
2072 name (str): Name to be used as network name if net dict does not include name
2073 created_items (dict): All created items
2074
2075 Returns:
2076 new_port, port New created port, port dictionary
2077
2078 """
2079
2080 port_dict = {
2081 "network_id": net["net_id"],
2082 "name": net.get("name"),
2083 "admin_state_up": True,
2084 }
2085
2086 if not port_dict["name"]:
2087 port_dict["name"] = name
2088
kayal2001342ee282024-11-28 11:56:49 +05302089 self._prepare_port_dict_security_groups(net, port_dict, security_group_name)
Gulsum Atici26f73662022-10-27 15:18:27 +03002090
2091 self._prepare_port_dict_binding(net, port_dict)
2092
2093 vimconnector._prepare_port_dict_mac_ip_addr(net, port_dict)
2094
2095 new_port = self._create_new_port(port_dict, created_items, net)
2096
2097 vimconnector._set_fixed_ip(new_port, net)
2098
2099 port = {"port-id": new_port["port"]["id"]}
2100
2101 if float(self.nova.api_version.get_string()) >= 2.32:
2102 port["tag"] = new_port["port"]["name"]
2103
2104 return new_port, port
2105
2106 def _prepare_network_for_vminstance(
2107 self,
2108 name: str,
2109 net_list: list,
2110 created_items: dict,
2111 net_list_vim: list,
2112 external_network: list,
2113 no_secured_ports: list,
kayal2001342ee282024-11-28 11:56:49 +05302114 security_group_name=None,
Gulsum Atici26f73662022-10-27 15:18:27 +03002115 ) -> None:
2116 """Create port and fill up net dictionary for new VM instance creation.
2117
2118 Args:
2119 name (str): Name of network
2120 net_list (list): List of networks
2121 created_items (dict): All created items belongs to a VM
2122 net_list_vim (list): List of ports
2123 external_network (list): List of external-networks
2124 no_secured_ports (list): Port security disabled ports
2125 """
2126
2127 self._reload_connection()
2128
2129 for net in net_list:
2130 # Skip non-connected iface
2131 if not net.get("net_id"):
2132 continue
2133
kayal2001342ee282024-11-28 11:56:49 +05302134 new_port, port = self._create_port(
2135 net, name, created_items, security_group_name
2136 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002137
2138 net_list_vim.append(port)
2139
2140 if net.get("floating_ip", False):
2141 net["exit_on_floating_ip_error"] = True
2142 external_network.append(net)
2143
2144 elif net["use"] == "mgmt" and self.config.get("use_floating_ip"):
2145 net["exit_on_floating_ip_error"] = False
2146 external_network.append(net)
2147 net["floating_ip"] = self.config.get("use_floating_ip")
2148
2149 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic
2150 # is dropped. As a workaround we wait until the VM is active and then disable the port-security
2151 if net.get("port_security") is False and not self.config.get(
2152 "no_port_security_extension"
2153 ):
2154 no_secured_ports.append(
2155 (
2156 new_port["port"]["id"],
2157 net.get("port_security_disable_strategy"),
2158 )
2159 )
2160
2161 def _prepare_persistent_root_volumes(
2162 self,
2163 name: str,
Luis Vega25bc6382023-10-05 23:22:04 +00002164 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002165 disk: dict,
2166 base_disk_index: int,
2167 block_device_mapping: dict,
2168 existing_vim_volumes: list,
2169 created_items: dict,
2170 ) -> Optional[str]:
2171 """Prepare persistent root volumes for new VM instance.
2172
2173 Args:
2174 name (str): Name of VM instance
Luis Vega25bc6382023-10-05 23:22:04 +00002175 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002176 disk (dict): Disk details
2177 base_disk_index (int): Disk index
2178 block_device_mapping (dict): Block device details
2179 existing_vim_volumes (list): Existing disk details
2180 created_items (dict): All created items belongs to VM
2181
2182 Returns:
2183 boot_volume_id (str): ID of boot volume
2184
2185 """
garciadeblas6a0147f2024-08-19 08:02:04 +00002186 self.logger.debug("Preparing root persistent volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002187 # Disk may include only vim_volume_id or only vim_id."
2188 # Use existing persistent root volume finding with volume_id or vim_id
2189 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002190 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002191 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2192 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002193 else:
2194 # Create persistent root volume
2195 volume = self.cinder.volumes.create(
2196 size=disk["size"],
2197 name=name + "vd" + chr(base_disk_index),
2198 imageRef=disk["image_id"],
2199 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002200 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002201 )
2202 boot_volume_id = volume.id
aticig2f4ab6c2022-09-03 18:15:20 +03002203 self.update_block_device_mapping(
2204 volume=volume,
2205 block_device_mapping=block_device_mapping,
2206 base_disk_index=base_disk_index,
2207 disk=disk,
2208 created_items=created_items,
2209 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002210
2211 return boot_volume_id
2212
aticig2f4ab6c2022-09-03 18:15:20 +03002213 @staticmethod
2214 def update_block_device_mapping(
2215 volume: object,
2216 block_device_mapping: dict,
2217 base_disk_index: int,
2218 disk: dict,
2219 created_items: dict,
2220 ) -> None:
2221 """Add volume information to block device mapping dict.
2222 Args:
2223 volume (object): Created volume object
2224 block_device_mapping (dict): Block device details
2225 base_disk_index (int): Disk index
2226 disk (dict): Disk details
2227 created_items (dict): All created items belongs to VM
2228 """
2229 if not volume:
2230 raise vimconn.VimConnException("Volume is empty.")
2231
2232 if not hasattr(volume, "id"):
2233 raise vimconn.VimConnException(
2234 "Created volume is not valid, does not have id attribute."
2235 )
2236
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002237 block_device_mapping["vd" + chr(base_disk_index)] = volume.id
2238 if disk.get("multiattach"): # multiattach volumes do not belong to VDUs
2239 return
aticig2f4ab6c2022-09-03 18:15:20 +03002240 volume_txt = "volume:" + str(volume.id)
2241 if disk.get("keep"):
2242 volume_txt += ":keep"
2243 created_items[volume_txt] = True
aticig2f4ab6c2022-09-03 18:15:20 +03002244
gatici335a06a2023-07-26 00:34:04 +03002245 @catch_any_exception
vegall364627c2023-03-17 15:09:50 +00002246 def new_shared_volumes(self, shared_volume_data) -> (str, str):
garciadeblas6a0147f2024-08-19 08:02:04 +00002247 self.logger.debug("Creating new shared volume")
Luis Vega25bc6382023-10-05 23:22:04 +00002248 availability_zone = (
2249 self.storage_availability_zone
2250 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002251 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002252 )
gatici335a06a2023-07-26 00:34:04 +03002253 volume = self.cinder.volumes.create(
2254 size=shared_volume_data["size"],
2255 name=shared_volume_data["name"],
2256 volume_type="multiattach",
Luis Vega25bc6382023-10-05 23:22:04 +00002257 availability_zone=availability_zone,
gatici335a06a2023-07-26 00:34:04 +03002258 )
2259 return volume.name, volume.id
vegall364627c2023-03-17 15:09:50 +00002260
2261 def _prepare_shared_volumes(
2262 self,
2263 name: str,
2264 disk: dict,
2265 base_disk_index: int,
2266 block_device_mapping: dict,
2267 existing_vim_volumes: list,
2268 created_items: dict,
2269 ):
garciadeblas6a0147f2024-08-19 08:02:04 +00002270 self.logger.debug("Preparing shared volumes")
vegall364627c2023-03-17 15:09:50 +00002271 volumes = {volume.name: volume.id for volume in self.cinder.volumes.list()}
2272 if volumes.get(disk["name"]):
2273 sv_id = volumes[disk["name"]]
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002274 max_retries = 3
2275 vol_status = ""
2276 # If this is not the first VM to attach the volume, volume status may be "reserved" for a short time
2277 while max_retries:
2278 max_retries -= 1
2279 volume = self.cinder.volumes.get(sv_id)
2280 vol_status = volume.status
2281 if volume.status not in ("in-use", "available"):
2282 time.sleep(5)
2283 continue
2284 self.update_block_device_mapping(
2285 volume=volume,
2286 block_device_mapping=block_device_mapping,
2287 base_disk_index=base_disk_index,
2288 disk=disk,
2289 created_items=created_items,
2290 )
2291 return
2292 raise vimconn.VimConnException(
2293 "Shared volume is not prepared, status is: {}".format(vol_status),
2294 http_code=vimconn.HTTP_Internal_Server_Error,
vegall364627c2023-03-17 15:09:50 +00002295 )
2296
Gulsum Atici26f73662022-10-27 15:18:27 +03002297 def _prepare_non_root_persistent_volumes(
2298 self,
2299 name: str,
2300 disk: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002301 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002302 block_device_mapping: dict,
2303 base_disk_index: int,
2304 existing_vim_volumes: list,
2305 created_items: dict,
2306 ) -> None:
2307 """Prepare persistent volumes for new VM instance.
2308
2309 Args:
2310 name (str): Name of VM instance
2311 disk (dict): Disk details
Luis Vega25bc6382023-10-05 23:22:04 +00002312 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002313 block_device_mapping (dict): Block device details
2314 base_disk_index (int): Disk index
2315 existing_vim_volumes (list): Existing disk details
2316 created_items (dict): All created items belongs to VM
2317 """
2318 # Non-root persistent volumes
2319 # Disk may include only vim_volume_id or only vim_id."
garciadeblas6a0147f2024-08-19 08:02:04 +00002320 self.logger.debug("Preparing non-root persistent volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002321 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002322 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002323 # Use existing persistent volume
2324 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2325 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002326 else:
vegall364627c2023-03-17 15:09:50 +00002327 volume_name = f"{name}vd{chr(base_disk_index)}"
Gulsum Atici26f73662022-10-27 15:18:27 +03002328 volume = self.cinder.volumes.create(
2329 size=disk["size"],
vegall364627c2023-03-17 15:09:50 +00002330 name=volume_name,
Gulsum Atici26f73662022-10-27 15:18:27 +03002331 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002332 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002333 )
aticig2f4ab6c2022-09-03 18:15:20 +03002334 self.update_block_device_mapping(
2335 volume=volume,
2336 block_device_mapping=block_device_mapping,
2337 base_disk_index=base_disk_index,
2338 disk=disk,
2339 created_items=created_items,
2340 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002341
2342 def _wait_for_created_volumes_availability(
2343 self, elapsed_time: int, created_items: dict
2344 ) -> Optional[int]:
2345 """Wait till created volumes become available.
2346
2347 Args:
2348 elapsed_time (int): Passed time while waiting
2349 created_items (dict): All created items belongs to VM
2350
2351 Returns:
2352 elapsed_time (int): Time spent while waiting
2353
2354 """
garciadeblas6a0147f2024-08-19 08:02:04 +00002355 self.logger.debug("Waiting for all created volumes to become available")
Gulsum Atici26f73662022-10-27 15:18:27 +03002356 while elapsed_time < volume_timeout:
garciadeblas6a0147f2024-08-19 08:02:04 +00002357 self.logger.debug("Checking disk availability for created volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002358 for created_item in created_items:
aticig2f4ab6c2022-09-03 18:15:20 +03002359 v, volume_id = (
2360 created_item.split(":")[0],
2361 created_item.split(":")[1],
2362 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002363 if v == "volume":
garciadeblas6a0147f2024-08-19 08:02:04 +00002364 self.logger.debug(f"Checking volume: {volume_id}")
vegall364627c2023-03-17 15:09:50 +00002365 volume = self.cinder.volumes.get(volume_id)
2366 if (
2367 volume.volume_type == "multiattach"
2368 and volume.status == "in-use"
2369 ):
2370 return elapsed_time
2371 elif volume.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002372 break
2373 else:
2374 # All ready: break from while
2375 break
2376
2377 time.sleep(5)
2378 elapsed_time += 5
2379
2380 return elapsed_time
2381
2382 def _wait_for_existing_volumes_availability(
2383 self, elapsed_time: int, existing_vim_volumes: list
2384 ) -> Optional[int]:
2385 """Wait till existing volumes become available.
2386
2387 Args:
2388 elapsed_time (int): Passed time while waiting
2389 existing_vim_volumes (list): Existing volume details
2390
2391 Returns:
2392 elapsed_time (int): Time spent while waiting
2393
2394 """
2395
garciadeblas6a0147f2024-08-19 08:02:04 +00002396 self.logger.debug("Waiting for all existing volumes to become available")
Gulsum Atici26f73662022-10-27 15:18:27 +03002397 while elapsed_time < volume_timeout:
garciadeblas6a0147f2024-08-19 08:02:04 +00002398 self.logger.debug("Checking disk availability for existing volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002399 for volume in existing_vim_volumes:
garciadeblas6a0147f2024-08-19 08:02:04 +00002400 self.logger.debug(f"Checking existing volume: {volume}")
vegall364627c2023-03-17 15:09:50 +00002401 v = self.cinder.volumes.get(volume["id"])
2402 if v.volume_type == "multiattach" and v.status == "in-use":
2403 return elapsed_time
2404 elif v.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002405 break
2406 else: # all ready: break from while
2407 break
2408
2409 time.sleep(5)
2410 elapsed_time += 5
2411
2412 return elapsed_time
2413
2414 def _prepare_disk_for_vminstance(
2415 self,
2416 name: str,
2417 existing_vim_volumes: list,
2418 created_items: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002419 storage_av_zone: list,
Gulsum Atici13d02322022-11-18 00:10:15 +03002420 block_device_mapping: dict,
Gulsum Atici26f73662022-10-27 15:18:27 +03002421 disk_list: list = None,
2422 ) -> None:
2423 """Prepare all volumes for new VM instance.
2424
2425 Args:
2426 name (str): Name of Instance
2427 existing_vim_volumes (list): List of existing volumes
2428 created_items (dict): All created items belongs to VM
Luis Vega25bc6382023-10-05 23:22:04 +00002429 storage_av_zone (list): Storage availability zone
Gulsum Atici13d02322022-11-18 00:10:15 +03002430 block_device_mapping (dict): Block devices to be attached to VM
Gulsum Atici26f73662022-10-27 15:18:27 +03002431 disk_list (list): List of disks
2432
2433 """
2434 # Create additional volumes in case these are present in disk_list
garciadeblas6a0147f2024-08-19 08:02:04 +00002435 self.logger.debug("Preparing disks for VM instances")
Gulsum Atici26f73662022-10-27 15:18:27 +03002436 base_disk_index = ord("b")
2437 boot_volume_id = None
2438 elapsed_time = 0
Gulsum Atici26f73662022-10-27 15:18:27 +03002439 for disk in disk_list:
garciadeblas6a0147f2024-08-19 08:02:04 +00002440 self.logger.debug(f"Disk: {disk}")
Gulsum Atici26f73662022-10-27 15:18:27 +03002441 if "image_id" in disk:
2442 # Root persistent volume
2443 base_disk_index = ord("a")
2444 boot_volume_id = self._prepare_persistent_root_volumes(
2445 name=name,
Luis Vega25bc6382023-10-05 23:22:04 +00002446 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002447 disk=disk,
2448 base_disk_index=base_disk_index,
2449 block_device_mapping=block_device_mapping,
2450 existing_vim_volumes=existing_vim_volumes,
2451 created_items=created_items,
2452 )
vegall364627c2023-03-17 15:09:50 +00002453 elif disk.get("multiattach"):
2454 self._prepare_shared_volumes(
2455 name=name,
2456 disk=disk,
2457 base_disk_index=base_disk_index,
2458 block_device_mapping=block_device_mapping,
2459 existing_vim_volumes=existing_vim_volumes,
2460 created_items=created_items,
2461 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002462 else:
2463 # Non-root persistent volume
2464 self._prepare_non_root_persistent_volumes(
2465 name=name,
2466 disk=disk,
Luis Vega25bc6382023-10-05 23:22:04 +00002467 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002468 block_device_mapping=block_device_mapping,
2469 base_disk_index=base_disk_index,
2470 existing_vim_volumes=existing_vim_volumes,
2471 created_items=created_items,
2472 )
2473 base_disk_index += 1
2474
2475 # Wait until created volumes are with status available
2476 elapsed_time = self._wait_for_created_volumes_availability(
2477 elapsed_time, created_items
2478 )
2479 # Wait until existing volumes in vim are with status available
2480 elapsed_time = self._wait_for_existing_volumes_availability(
2481 elapsed_time, existing_vim_volumes
2482 )
2483 # If we exceeded the timeout rollback
2484 if elapsed_time >= volume_timeout:
2485 raise vimconn.VimConnException(
2486 "Timeout creating volumes for instance " + name,
2487 http_code=vimconn.HTTP_Request_Timeout,
2488 )
2489 if boot_volume_id:
2490 self.cinder.volumes.set_bootable(boot_volume_id, True)
2491
2492 def _find_the_external_network_for_floating_ip(self):
2493 """Get the external network ip in order to create floating IP.
2494
2495 Returns:
2496 pool_id (str): External network pool ID
2497
2498 """
2499
2500 # Find the external network
2501 external_nets = list()
2502
2503 for net in self.neutron.list_networks()["networks"]:
2504 if net["router:external"]:
2505 external_nets.append(net)
2506
2507 if len(external_nets) == 0:
2508 raise vimconn.VimConnException(
2509 "Cannot create floating_ip automatically since "
2510 "no external network is present",
2511 http_code=vimconn.HTTP_Conflict,
2512 )
2513
2514 if len(external_nets) > 1:
2515 raise vimconn.VimConnException(
2516 "Cannot create floating_ip automatically since "
2517 "multiple external networks are present",
2518 http_code=vimconn.HTTP_Conflict,
2519 )
2520
2521 # Pool ID
2522 return external_nets[0].get("id")
2523
2524 def _neutron_create_float_ip(self, param: dict, created_items: dict) -> None:
2525 """Trigger neutron to create a new floating IP using external network ID.
2526
2527 Args:
2528 param (dict): Input parameters to create a floating IP
2529 created_items (dict): All created items belongs to new VM instance
2530
2531 Raises:
2532
2533 VimConnException
2534 """
2535 try:
2536 self.logger.debug("Creating floating IP")
2537 new_floating_ip = self.neutron.create_floatingip(param)
2538 free_floating_ip = new_floating_ip["floatingip"]["id"]
2539 created_items["floating_ip:" + str(free_floating_ip)] = True
2540
2541 except Exception as e:
2542 raise vimconn.VimConnException(
2543 type(e).__name__ + ": Cannot create new floating_ip " + str(e),
2544 http_code=vimconn.HTTP_Conflict,
2545 )
2546
2547 def _create_floating_ip(
2548 self, floating_network: dict, server: object, created_items: dict
2549 ) -> None:
2550 """Get the available Pool ID and create a new floating IP.
2551
2552 Args:
2553 floating_network (dict): Dict including external network ID
2554 server (object): Server object
2555 created_items (dict): All created items belongs to new VM instance
2556
2557 """
2558
2559 # Pool_id is available
2560 if (
2561 isinstance(floating_network["floating_ip"], str)
2562 and floating_network["floating_ip"].lower() != "true"
2563 ):
2564 pool_id = floating_network["floating_ip"]
2565
2566 # Find the Pool_id
2567 else:
2568 pool_id = self._find_the_external_network_for_floating_ip()
2569
2570 param = {
2571 "floatingip": {
2572 "floating_network_id": pool_id,
2573 "tenant_id": server.tenant_id,
2574 }
2575 }
2576
2577 self._neutron_create_float_ip(param, created_items)
2578
2579 def _find_floating_ip(
2580 self,
2581 server: object,
2582 floating_ips: list,
2583 floating_network: dict,
2584 ) -> Optional[str]:
2585 """Find the available free floating IPs if there are.
2586
2587 Args:
2588 server (object): Server object
2589 floating_ips (list): List of floating IPs
2590 floating_network (dict): Details of floating network such as ID
2591
2592 Returns:
2593 free_floating_ip (str): Free floating ip address
2594
2595 """
2596 for fip in floating_ips:
2597 if fip.get("port_id") or fip.get("tenant_id") != server.tenant_id:
2598 continue
2599
2600 if isinstance(floating_network["floating_ip"], str):
2601 if fip.get("floating_network_id") != floating_network["floating_ip"]:
2602 continue
2603
2604 return fip["id"]
2605
2606 def _assign_floating_ip(
2607 self, free_floating_ip: str, floating_network: dict
2608 ) -> Dict:
2609 """Assign the free floating ip address to port.
2610
2611 Args:
2612 free_floating_ip (str): Floating IP to be assigned
2613 floating_network (dict): ID of floating network
2614
2615 Returns:
2616 fip (dict) (dict): Floating ip details
2617
2618 """
2619 # The vim_id key contains the neutron.port_id
2620 self.neutron.update_floatingip(
2621 free_floating_ip,
2622 {"floatingip": {"port_id": floating_network["vim_id"]}},
2623 )
2624 # For race condition ensure not re-assigned to other VM after 5 seconds
2625 time.sleep(5)
2626
2627 return self.neutron.show_floatingip(free_floating_ip)
2628
2629 def _get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002630 self, server: object, floating_network: dict
Gulsum Atici26f73662022-10-27 15:18:27 +03002631 ) -> Optional[str]:
2632 """Get the free floating IP address.
2633
2634 Args:
2635 server (object): Server Object
2636 floating_network (dict): Floating network details
Gulsum Atici26f73662022-10-27 15:18:27 +03002637
2638 Returns:
2639 free_floating_ip (str): Free floating ip addr
2640
2641 """
2642
2643 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
2644
2645 # Randomize
2646 random.shuffle(floating_ips)
2647
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002648 return self._find_floating_ip(server, floating_ips, floating_network)
Gulsum Atici26f73662022-10-27 15:18:27 +03002649
2650 def _prepare_external_network_for_vminstance(
2651 self,
2652 external_network: list,
2653 server: object,
2654 created_items: dict,
2655 vm_start_time: float,
2656 ) -> None:
2657 """Assign floating IP address for VM instance.
2658
2659 Args:
2660 external_network (list): ID of External network
2661 server (object): Server Object
2662 created_items (dict): All created items belongs to new VM instance
2663 vm_start_time (float): Time as a floating point number expressed in seconds since the epoch, in UTC
2664
2665 Raises:
2666 VimConnException
2667
2668 """
2669 for floating_network in external_network:
2670 try:
2671 assigned = False
2672 floating_ip_retries = 3
2673 # In case of RO in HA there can be conflicts, two RO trying to assign same floating IP, so retry
2674 # several times
2675 while not assigned:
Gulsum Atici26f73662022-10-27 15:18:27 +03002676 free_floating_ip = self._get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002677 server, floating_network
Gulsum Atici26f73662022-10-27 15:18:27 +03002678 )
2679
2680 if not free_floating_ip:
2681 self._create_floating_ip(
2682 floating_network, server, created_items
2683 )
2684
2685 try:
2686 # For race condition ensure not already assigned
2687 fip = self.neutron.show_floatingip(free_floating_ip)
2688
2689 if fip["floatingip"].get("port_id"):
2690 continue
2691
2692 # Assign floating ip
2693 fip = self._assign_floating_ip(
2694 free_floating_ip, floating_network
2695 )
2696
2697 if fip["floatingip"]["port_id"] != floating_network["vim_id"]:
2698 self.logger.warning(
2699 "floating_ip {} re-assigned to other port".format(
2700 free_floating_ip
2701 )
2702 )
2703 continue
2704
2705 self.logger.debug(
2706 "Assigned floating_ip {} to VM {}".format(
2707 free_floating_ip, server.id
2708 )
2709 )
2710
2711 assigned = True
2712
2713 except Exception as e:
2714 # Openstack need some time after VM creation to assign an IP. So retry if fails
2715 vm_status = self.nova.servers.get(server.id).status
2716
2717 if vm_status not in ("ACTIVE", "ERROR"):
2718 if time.time() - vm_start_time < server_timeout:
2719 time.sleep(5)
2720 continue
2721 elif floating_ip_retries > 0:
2722 floating_ip_retries -= 1
2723 continue
2724
2725 raise vimconn.VimConnException(
2726 "Cannot create floating_ip: {} {}".format(
2727 type(e).__name__, e
2728 ),
2729 http_code=vimconn.HTTP_Conflict,
2730 )
2731
2732 except Exception as e:
2733 if not floating_network["exit_on_floating_ip_error"]:
2734 self.logger.error("Cannot create floating_ip. %s", str(e))
2735 continue
2736
2737 raise
2738
2739 def _update_port_security_for_vminstance(
2740 self,
2741 no_secured_ports: list,
2742 server: object,
2743 ) -> None:
2744 """Updates the port security according to no_secured_ports list.
2745
2746 Args:
2747 no_secured_ports (list): List of ports that security will be disabled
2748 server (object): Server Object
2749
2750 Raises:
2751 VimConnException
2752
2753 """
2754 # Wait until the VM is active and then disable the port-security
2755 if no_secured_ports:
2756 self.__wait_for_vm(server.id, "ACTIVE")
2757
2758 for port in no_secured_ports:
2759 port_update = {
2760 "port": {"port_security_enabled": False, "security_groups": None}
2761 }
2762
2763 if port[1] == "allow-address-pairs":
2764 port_update = {
2765 "port": {"allowed_address_pairs": [{"ip_address": "0.0.0.0/0"}]}
2766 }
2767
2768 try:
2769 self.neutron.update_port(port[0], port_update)
2770
2771 except Exception:
Gulsum Atici26f73662022-10-27 15:18:27 +03002772 raise vimconn.VimConnException(
2773 "It was not possible to disable port security for port {}".format(
2774 port[0]
2775 )
2776 )
2777
sousaedu80135b92021-02-17 15:05:18 +01002778 def new_vminstance(
2779 self,
Gulsum Atici26f73662022-10-27 15:18:27 +03002780 name: str,
2781 description: str,
2782 start: bool,
2783 image_id: str,
2784 flavor_id: str,
2785 affinity_group_list: list,
2786 net_list: list,
sousaedu80135b92021-02-17 15:05:18 +01002787 cloud_config=None,
2788 disk_list=None,
2789 availability_zone_index=None,
2790 availability_zone_list=None,
kayal2001342ee282024-11-28 11:56:49 +05302791 security_group_name=None,
Gulsum Atici26f73662022-10-27 15:18:27 +03002792 ) -> tuple:
2793 """Adds a VM instance to VIM.
2794
2795 Args:
2796 name (str): name of VM
2797 description (str): description
2798 start (bool): indicates if VM must start or boot in pause mode. Ignored
2799 image_id (str) image uuid
2800 flavor_id (str) flavor uuid
2801 affinity_group_list (list): list of affinity groups, each one is a dictionary.Ignore if empty.
2802 net_list (list): list of interfaces, each one is a dictionary with:
2803 name: name of network
2804 net_id: network uuid to connect
2805 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
2806 model: interface model, ignored #TODO
2807 mac_address: used for SR-IOV ifaces #TODO for other types
2808 use: 'data', 'bridge', 'mgmt'
2809 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
2810 vim_id: filled/added by this function
2811 floating_ip: True/False (or it can be None)
2812 port_security: True/False
2813 cloud_config (dict): (optional) dictionary with:
2814 key-pairs: (optional) list of strings with the public key to be inserted to the default user
2815 users: (optional) list of users to be inserted, each item is a dict with:
2816 name: (mandatory) user name,
2817 key-pairs: (optional) list of strings with the public key to be inserted to the user
2818 user-data: (optional) string is a text script to be passed directly to cloud-init
2819 config-files: (optional). List of files to be transferred. Each item is a dict with:
2820 dest: (mandatory) string with the destination absolute path
2821 encoding: (optional, by default text). Can be one of:
tierno1d213f42020-04-24 14:02:51 +00002822 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
Gulsum Atici26f73662022-10-27 15:18:27 +03002823 content : (mandatory) string with the content of the file
2824 permissions: (optional) string with file permissions, typically octal notation '0644'
2825 owner: (optional) file owner, string with the format 'owner:group'
2826 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
2827 disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
2828 image_id: (optional). VIM id of an existing image. If not provided an empty disk must be mounted
2829 size: (mandatory) string with the size of the disk in GB
2830 vim_id: (optional) should use this existing volume id
2831 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
2832 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
tierno5a3273c2017-08-29 11:43:46 +02002833 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01002834 #TODO ip, security groups
Gulsum Atici26f73662022-10-27 15:18:27 +03002835
2836 Returns:
2837 A tuple with the instance identifier and created_items or raises an exception on error
tierno98e909c2017-10-14 13:27:03 +02002838 created_items can be None or a dictionary where this method can include key-values that will be passed to
2839 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
2840 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
2841 as not present.
aticig2f4ab6c2022-09-03 18:15:20 +03002842
tierno98e909c2017-10-14 13:27:03 +02002843 """
sousaedu80135b92021-02-17 15:05:18 +01002844 self.logger.debug(
2845 "new_vminstance input: image='%s' flavor='%s' nics='%s'",
2846 image_id,
2847 flavor_id,
2848 str(net_list),
2849 )
gatici335a06a2023-07-26 00:34:04 +03002850 server = None
2851 created_items = {}
2852 net_list_vim = []
2853 # list of external networks to be connected to instance, later on used to create floating_ip
2854 external_network = []
2855 # List of ports with port-security disabled
2856 no_secured_ports = []
2857 block_device_mapping = {}
2858 existing_vim_volumes = []
2859 server_group_id = None
2860 scheduller_hints = {}
sousaedu80135b92021-02-17 15:05:18 +01002861
tierno7edb6752016-03-21 17:37:52 +01002862 try:
Gulsum Atici26f73662022-10-27 15:18:27 +03002863 # Check the Openstack Connection
2864 self._reload_connection()
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02002865
Gulsum Atici26f73662022-10-27 15:18:27 +03002866 # Prepare network list
2867 self._prepare_network_for_vminstance(
2868 name=name,
2869 net_list=net_list,
2870 created_items=created_items,
2871 net_list_vim=net_list_vim,
2872 external_network=external_network,
2873 no_secured_ports=no_secured_ports,
kayal2001342ee282024-11-28 11:56:49 +05302874 security_group_name=security_group_name,
sousaedu80135b92021-02-17 15:05:18 +01002875 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002876
Gulsum Atici26f73662022-10-27 15:18:27 +03002877 # Cloud config
tierno0a1437e2017-10-02 00:17:43 +02002878 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00002879
Gulsum Atici26f73662022-10-27 15:18:27 +03002880 # Get availability Zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002881 self.vm_av_zone = self._get_vm_availability_zone(
Alexis Romero247cc432022-05-12 13:23:25 +02002882 availability_zone_index, availability_zone_list
2883 )
2884
Luis Vega25bc6382023-10-05 23:22:04 +00002885 storage_av_zone = (
2886 self.storage_availability_zone
2887 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002888 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002889 )
2890
tierno1df468d2018-07-06 14:25:16 +02002891 if disk_list:
Gulsum Atici26f73662022-10-27 15:18:27 +03002892 # Prepare disks
2893 self._prepare_disk_for_vminstance(
2894 name=name,
2895 existing_vim_volumes=existing_vim_volumes,
2896 created_items=created_items,
Luis Vega25bc6382023-10-05 23:22:04 +00002897 storage_av_zone=storage_av_zone,
Gulsum Atici13d02322022-11-18 00:10:15 +03002898 block_device_mapping=block_device_mapping,
Gulsum Atici26f73662022-10-27 15:18:27 +03002899 disk_list=disk_list,
2900 )
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002901
2902 if affinity_group_list:
2903 # Only first id on the list will be used. Openstack restriction
2904 server_group_id = affinity_group_list[0]["affinity_group_id"]
2905 scheduller_hints["group"] = server_group_id
2906
sousaedu80135b92021-02-17 15:05:18 +01002907 self.logger.debug(
2908 "nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
2909 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002910 "block_device_mapping={}, server_group={})".format(
sousaedu80135b92021-02-17 15:05:18 +01002911 name,
2912 image_id,
2913 flavor_id,
2914 net_list_vim,
2915 self.config.get("security_groups"),
Luis Vegaafe8df22023-12-01 01:02:12 +00002916 self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002917 self.config.get("keypair"),
2918 userdata,
2919 config_drive,
2920 block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002921 server_group_id,
sousaedu80135b92021-02-17 15:05:18 +01002922 )
2923 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002924 # Create VM
sousaedu80135b92021-02-17 15:05:18 +01002925 server = self.nova.servers.create(
aticigcf14bb12022-05-19 13:03:17 +03002926 name=name,
2927 image=image_id,
2928 flavor=flavor_id,
sousaedu80135b92021-02-17 15:05:18 +01002929 nics=net_list_vim,
2930 security_groups=self.config.get("security_groups"),
2931 # TODO remove security_groups in future versions. Already at neutron port
Luis Vegaafe8df22023-12-01 01:02:12 +00002932 availability_zone=self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002933 key_name=self.config.get("keypair"),
2934 userdata=userdata,
2935 config_drive=config_drive,
2936 block_device_mapping=block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002937 scheduler_hints=scheduller_hints,
Gulsum Atici26f73662022-10-27 15:18:27 +03002938 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002939
tierno326fd5e2018-02-22 11:58:59 +01002940 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002941
Gulsum Atici26f73662022-10-27 15:18:27 +03002942 self._update_port_security_for_vminstance(no_secured_ports, server)
bravof7a1f5252020-10-20 10:27:42 -03002943
Gulsum Atici26f73662022-10-27 15:18:27 +03002944 self._prepare_external_network_for_vminstance(
2945 external_network=external_network,
2946 server=server,
2947 created_items=created_items,
2948 vm_start_time=vm_start_time,
2949 )
montesmoreno2a1fc4e2017-01-09 16:46:04 +00002950
tierno98e909c2017-10-14 13:27:03 +02002951 return server.id, created_items
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002952
2953 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02002954 server_id = None
2955 if server:
2956 server_id = server.id
sousaedu80135b92021-02-17 15:05:18 +01002957
tierno98e909c2017-10-14 13:27:03 +02002958 try:
aticig2f4ab6c2022-09-03 18:15:20 +03002959 created_items = self.remove_keep_tag_from_persistent_volumes(
2960 created_items
2961 )
2962
tierno98e909c2017-10-14 13:27:03 +02002963 self.delete_vminstance(server_id, created_items)
Gulsum Atici26f73662022-10-27 15:18:27 +03002964
tierno98e909c2017-10-14 13:27:03 +02002965 except Exception as e2:
2966 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002967
tiernoae4a8d12016-07-08 12:30:39 +02002968 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01002969
aticig2f4ab6c2022-09-03 18:15:20 +03002970 @staticmethod
2971 def remove_keep_tag_from_persistent_volumes(created_items: Dict) -> Dict:
2972 """Removes the keep flag from persistent volumes. So, those volumes could be removed.
2973
2974 Args:
2975 created_items (dict): All created items belongs to VM
2976
2977 Returns:
2978 updated_created_items (dict): Dict which does not include keep flag for volumes.
2979
2980 """
2981 return {
2982 key.replace(":keep", ""): value for (key, value) in created_items.items()
2983 }
2984
tierno1ec592d2020-06-16 15:29:47 +00002985 def get_vminstance(self, vm_id):
2986 """Returns the VM instance information from VIM"""
vegallc53829d2023-06-01 00:47:44 -05002987 return self._find_nova_server(vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02002988
gatici335a06a2023-07-26 00:34:04 +03002989 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00002990 def get_vminstance_console(self, vm_id, console_type="vnc"):
2991 """
tierno7edb6752016-03-21 17:37:52 +01002992 Get a console for the virtual machine
2993 Params:
2994 vm_id: uuid of the VM
2995 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002996 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01002997 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02002998 Returns dict with the console parameters:
2999 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003000 server: usually ip address
3001 port: the http, ssh, ... port
3002 suffix: extra text, e.g. the http path and query string
tierno1ec592d2020-06-16 15:29:47 +00003003 """
tiernoae4a8d12016-07-08 12:30:39 +02003004 self.logger.debug("Getting VM CONSOLE from VIM")
gatici335a06a2023-07-26 00:34:04 +03003005 self._reload_connection()
3006 server = self.nova.servers.find(id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003007
gatici335a06a2023-07-26 00:34:04 +03003008 if console_type is None or console_type == "novnc":
3009 console_dict = server.get_vnc_console("novnc")
3010 elif console_type == "xvpvnc":
3011 console_dict = server.get_vnc_console(console_type)
3012 elif console_type == "rdp-html5":
3013 console_dict = server.get_rdp_console(console_type)
3014 elif console_type == "spice-html5":
3015 console_dict = server.get_spice_console(console_type)
3016 else:
3017 raise vimconn.VimConnException(
3018 "console type '{}' not allowed".format(console_type),
3019 http_code=vimconn.HTTP_Bad_Request,
3020 )
sousaedu80135b92021-02-17 15:05:18 +01003021
gatici335a06a2023-07-26 00:34:04 +03003022 console_dict1 = console_dict.get("console")
3023
3024 if console_dict1:
3025 console_url = console_dict1.get("url")
3026
3027 if console_url:
3028 # parse console_url
3029 protocol_index = console_url.find("//")
3030 suffix_index = (
3031 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3032 )
3033 port_index = (
3034 console_url[protocol_index + 2 : suffix_index].find(":")
3035 + protocol_index
3036 + 2
sousaedu80135b92021-02-17 15:05:18 +01003037 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003038
gatici335a06a2023-07-26 00:34:04 +03003039 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
3040 return (
3041 -vimconn.HTTP_Internal_Server_Error,
3042 "Unexpected response from VIM",
sousaedu80135b92021-02-17 15:05:18 +01003043 )
3044
gatici335a06a2023-07-26 00:34:04 +03003045 console_dict = {
3046 "protocol": console_url[0:protocol_index],
3047 "server": console_url[protocol_index + 2 : port_index],
3048 "port": console_url[port_index:suffix_index],
3049 "suffix": console_url[suffix_index + 1 :],
3050 }
3051 protocol_index += 2
sousaedu80135b92021-02-17 15:05:18 +01003052
gatici335a06a2023-07-26 00:34:04 +03003053 return console_dict
3054 raise vimconn.VimConnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01003055
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003056 def _delete_ports_by_id_wth_neutron(self, k_id: str) -> None:
3057 """Neutron delete ports by id.
3058 Args:
3059 k_id (str): Port id in the VIM
3060 """
3061 try:
limon878f8692023-07-24 15:53:41 +02003062 self.neutron.delete_port(k_id)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003063
gatici335a06a2023-07-26 00:34:04 +03003064 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3065 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3066 # If there is connection error, raise.
3067 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003068 except Exception as e:
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003069 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3070
vegall364627c2023-03-17 15:09:50 +00003071 def delete_shared_volumes(self, shared_volume_vim_id: str) -> bool:
3072 """Cinder delete volume by id.
3073 Args:
3074 shared_volume_vim_id (str): ID of shared volume in VIM
3075 """
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003076 elapsed_time = 0
vegall364627c2023-03-17 15:09:50 +00003077 try:
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003078 while elapsed_time < server_timeout:
3079 vol_status = self.cinder.volumes.get(shared_volume_vim_id).status
3080 if vol_status == "available":
3081 self.cinder.volumes.delete(shared_volume_vim_id)
3082 return True
vegall364627c2023-03-17 15:09:50 +00003083
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003084 time.sleep(5)
3085 elapsed_time += 5
3086
3087 if elapsed_time >= server_timeout:
3088 raise vimconn.VimConnException(
3089 "Timeout waiting for volume "
3090 + shared_volume_vim_id
3091 + " to be available",
3092 http_code=vimconn.HTTP_Request_Timeout,
3093 )
vegall364627c2023-03-17 15:09:50 +00003094
3095 except Exception as e:
3096 self.logger.error(
3097 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3098 )
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003099 self._format_exception(e)
vegall364627c2023-03-17 15:09:50 +00003100
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003101 def _delete_volumes_by_id_wth_cinder(
3102 self, k: str, k_id: str, volumes_to_hold: list, created_items: dict
3103 ) -> bool:
3104 """Cinder delete volume by id.
3105 Args:
3106 k (str): Full item name in created_items
3107 k_id (str): ID of floating ip in VIM
3108 volumes_to_hold (list): Volumes not to delete
3109 created_items (dict): All created items belongs to VM
3110 """
3111 try:
3112 if k_id in volumes_to_hold:
gatici335a06a2023-07-26 00:34:04 +03003113 return False
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003114
3115 if self.cinder.volumes.get(k_id).status != "available":
3116 return True
3117
3118 else:
3119 self.cinder.volumes.delete(k_id)
3120 created_items[k] = None
3121
gatici335a06a2023-07-26 00:34:04 +03003122 except (cExceptions.ConnectionError, ConnectionError) as e:
3123 self.logger.error(
3124 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3125 )
3126 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003127 except Exception as e:
3128 self.logger.error(
3129 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3130 )
3131
3132 def _delete_floating_ip_by_id(self, k: str, k_id: str, created_items: dict) -> None:
3133 """Neutron delete floating ip by id.
3134 Args:
3135 k (str): Full item name in created_items
3136 k_id (str): ID of floating ip in VIM
3137 created_items (dict): All created items belongs to VM
3138 """
3139 try:
3140 self.neutron.delete_floatingip(k_id)
3141 created_items[k] = None
3142
gatici335a06a2023-07-26 00:34:04 +03003143 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3144 self.logger.error(
3145 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3146 )
3147 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003148 except Exception as e:
3149 self.logger.error(
3150 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3151 )
3152
3153 @staticmethod
3154 def _get_item_name_id(k: str) -> Tuple[str, str]:
3155 k_item, _, k_id = k.partition(":")
3156 return k_item, k_id
3157
3158 def _delete_vm_ports_attached_to_network(self, created_items: dict) -> None:
3159 """Delete VM ports attached to the networks before deleting virtual machine.
3160 Args:
3161 created_items (dict): All created items belongs to VM
3162 """
3163
3164 for k, v in created_items.items():
3165 if not v: # skip already deleted
3166 continue
3167
3168 try:
3169 k_item, k_id = self._get_item_name_id(k)
3170 if k_item == "port":
3171 self._delete_ports_by_id_wth_neutron(k_id)
3172
gatici335a06a2023-07-26 00:34:04 +03003173 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3174 self.logger.error(
3175 "Error deleting port: {}: {}".format(type(e).__name__, e)
3176 )
3177 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003178 except Exception as e:
3179 self.logger.error(
3180 "Error deleting port: {}: {}".format(type(e).__name__, e)
3181 )
3182
3183 def _delete_created_items(
3184 self, created_items: dict, volumes_to_hold: list, keep_waiting: bool
3185 ) -> bool:
3186 """Delete Volumes and floating ip if they exist in created_items."""
3187 for k, v in created_items.items():
3188 if not v: # skip already deleted
3189 continue
3190
3191 try:
3192 k_item, k_id = self._get_item_name_id(k)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003193 if k_item == "volume":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003194 unavailable_vol = self._delete_volumes_by_id_wth_cinder(
3195 k, k_id, volumes_to_hold, created_items
3196 )
3197
3198 if unavailable_vol:
3199 keep_waiting = True
3200
3201 elif k_item == "floating_ip":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003202 self._delete_floating_ip_by_id(k, k_id, created_items)
3203
gatici335a06a2023-07-26 00:34:04 +03003204 except (
3205 cExceptions.ConnectionError,
3206 neExceptions.ConnectionFailed,
3207 ConnectionError,
3208 AttributeError,
3209 TypeError,
3210 ) as e:
3211 self.logger.error("Error deleting {}: {}".format(k, e))
3212 self._format_exception(e)
3213
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003214 except Exception as e:
3215 self.logger.error("Error deleting {}: {}".format(k, e))
3216
3217 return keep_waiting
3218
aticig2f4ab6c2022-09-03 18:15:20 +03003219 @staticmethod
3220 def _extract_items_wth_keep_flag_from_created_items(created_items: dict) -> dict:
3221 """Remove the volumes which has key flag from created_items
3222
3223 Args:
3224 created_items (dict): All created items belongs to VM
3225
3226 Returns:
3227 created_items (dict): Persistent volumes eliminated created_items
3228 """
3229 return {
3230 key: value
3231 for (key, value) in created_items.items()
3232 if len(key.split(":")) == 2
3233 }
3234
gatici335a06a2023-07-26 00:34:04 +03003235 @catch_any_exception
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003236 def delete_vminstance(
3237 self, vm_id: str, created_items: dict = None, volumes_to_hold: list = None
3238 ) -> None:
3239 """Removes a VM instance from VIM. Returns the old identifier.
3240 Args:
3241 vm_id (str): Identifier of VM instance
3242 created_items (dict): All created items belongs to VM
3243 volumes_to_hold (list): Volumes_to_hold
3244 """
tierno1ec592d2020-06-16 15:29:47 +00003245 if created_items is None:
tierno98e909c2017-10-14 13:27:03 +02003246 created_items = {}
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003247 if volumes_to_hold is None:
3248 volumes_to_hold = []
sousaedu80135b92021-02-17 15:05:18 +01003249
tierno7edb6752016-03-21 17:37:52 +01003250 try:
aticig2f4ab6c2022-09-03 18:15:20 +03003251 created_items = self._extract_items_wth_keep_flag_from_created_items(
3252 created_items
3253 )
3254
tierno7edb6752016-03-21 17:37:52 +01003255 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01003256
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003257 # Delete VM ports attached to the networks before the virtual machine
3258 if created_items:
3259 self._delete_vm_ports_attached_to_network(created_items)
montesmoreno0c8def02016-12-22 12:16:23 +00003260
tierno98e909c2017-10-14 13:27:03 +02003261 if vm_id:
3262 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00003263
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003264 # Although having detached, volumes should have in active status before deleting.
3265 # We ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00003266 keep_waiting = True
3267 elapsed_time = 0
sousaedu80135b92021-02-17 15:05:18 +01003268
montesmoreno0c8def02016-12-22 12:16:23 +00003269 while keep_waiting and elapsed_time < volume_timeout:
3270 keep_waiting = False
sousaedu80135b92021-02-17 15:05:18 +01003271
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003272 # Delete volumes and floating IP.
3273 keep_waiting = self._delete_created_items(
3274 created_items, volumes_to_hold, keep_waiting
3275 )
sousaedu80135b92021-02-17 15:05:18 +01003276
montesmoreno0c8def02016-12-22 12:16:23 +00003277 if keep_waiting:
3278 time.sleep(1)
3279 elapsed_time += 1
gatici335a06a2023-07-26 00:34:04 +03003280 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
3281 # If VM does not exist, it does not raise
3282 self.logger.warning(f"Error deleting VM: {vm_id} is not found, {str(e)}")
tierno7edb6752016-03-21 17:37:52 +01003283
tiernoae4a8d12016-07-08 12:30:39 +02003284 def refresh_vms_status(self, vm_list):
tierno1ec592d2020-06-16 15:29:47 +00003285 """Get the status of the virtual machines and their interfaces/ports
sousaedu80135b92021-02-17 15:05:18 +01003286 Params: the list of VM identifiers
3287 Returns a dictionary with:
3288 vm_id: #VIM id of this Virtual Machine
3289 status: #Mandatory. Text with one of:
3290 # DELETED (not found at vim)
3291 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
3292 # OTHER (Vim reported other status not understood)
3293 # ERROR (VIM indicates an ERROR status)
3294 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
3295 # CREATING (on building process), ERROR
3296 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
3297 #
3298 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
3299 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3300 interfaces:
3301 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3302 mac_address: #Text format XX:XX:XX:XX:XX:XX
3303 vim_net_id: #network id where this interface is connected
3304 vim_interface_id: #interface/port VIM id
3305 ip_address: #null, or text with IPv4, IPv6 address
3306 compute_node: #identification of compute node where PF,VF interface is allocated
3307 pci: #PCI address of the NIC that hosts the PF,VF
3308 vlan: #physical VLAN used for VF
tierno1ec592d2020-06-16 15:29:47 +00003309 """
3310 vm_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01003311 self.logger.debug(
3312 "refresh_vms status: Getting tenant VM instance information from VIM"
3313 )
tiernoae4a8d12016-07-08 12:30:39 +02003314 for vm_id in vm_list:
tierno1ec592d2020-06-16 15:29:47 +00003315 vm = {}
sousaedu80135b92021-02-17 15:05:18 +01003316
tiernoae4a8d12016-07-08 12:30:39 +02003317 try:
3318 vm_vim = self.get_vminstance(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003319
3320 if vm_vim["status"] in vmStatus2manoFormat:
3321 vm["status"] = vmStatus2manoFormat[vm_vim["status"]]
tierno7edb6752016-03-21 17:37:52 +01003322 else:
sousaedu80135b92021-02-17 15:05:18 +01003323 vm["status"] = "OTHER"
3324 vm["error_msg"] = "VIM status reported " + vm_vim["status"]
3325
tierno70eeb182020-10-19 16:38:00 +00003326 vm_vim.pop("OS-EXT-SRV-ATTR:user_data", None)
3327 vm_vim.pop("user_data", None)
sousaedu80135b92021-02-17 15:05:18 +01003328 vm["vim_info"] = self.serialize(vm_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003329
tiernoae4a8d12016-07-08 12:30:39 +02003330 vm["interfaces"] = []
sousaedu80135b92021-02-17 15:05:18 +01003331 if vm_vim.get("fault"):
3332 vm["error_msg"] = str(vm_vim["fault"])
3333
tierno1ec592d2020-06-16 15:29:47 +00003334 # get interfaces
tierno7edb6752016-03-21 17:37:52 +01003335 try:
tiernoae4a8d12016-07-08 12:30:39 +02003336 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02003337 port_dict = self.neutron.list_ports(device_id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003338
tiernoae4a8d12016-07-08 12:30:39 +02003339 for port in port_dict["ports"]:
tierno1ec592d2020-06-16 15:29:47 +00003340 interface = {}
sousaedu80135b92021-02-17 15:05:18 +01003341 interface["vim_info"] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02003342 interface["mac_address"] = port.get("mac_address")
3343 interface["vim_net_id"] = port["network_id"]
3344 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003345 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003346 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003347
3348 if vm_vim.get("OS-EXT-SRV-ATTR:host"):
3349 interface["compute_node"] = vm_vim["OS-EXT-SRV-ATTR:host"]
3350
tierno867ffe92017-03-27 12:50:34 +02003351 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04003352
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003353 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003354 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003355 if port.get("binding:profile"):
3356 if port["binding:profile"].get("pci_slot"):
tierno1ec592d2020-06-16 15:29:47 +00003357 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting
3358 # the slot to 0x00
Mike Marchetti5b9da422017-05-02 15:35:47 -04003359 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
3360 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
sousaedu80135b92021-02-17 15:05:18 +01003361 pci = port["binding:profile"]["pci_slot"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04003362 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
3363 interface["pci"] = pci
sousaedu80135b92021-02-17 15:05:18 +01003364
tierno867ffe92017-03-27 12:50:34 +02003365 interface["vlan"] = None
sousaedu80135b92021-02-17 15:05:18 +01003366
3367 if port.get("binding:vif_details"):
3368 interface["vlan"] = port["binding:vif_details"].get("vlan")
3369
tierno1dfe9932020-06-18 08:50:10 +00003370 # Get vlan from network in case not present in port for those old openstacks and cases where
3371 # it is needed vlan at PT
3372 if not interface["vlan"]:
3373 # if network is of type vlan and port is of type direct (sr-iov) then set vlan id
3374 network = self.neutron.show_network(port["network_id"])
sousaedu80135b92021-02-17 15:05:18 +01003375
3376 if (
3377 network["network"].get("provider:network_type")
3378 == "vlan"
3379 ):
tierno1dfe9932020-06-18 08:50:10 +00003380 # and port.get("binding:vnic_type") in ("direct", "direct-physical"):
sousaedu80135b92021-02-17 15:05:18 +01003381 interface["vlan"] = network["network"].get(
3382 "provider:segmentation_id"
3383 )
3384
tierno1ec592d2020-06-16 15:29:47 +00003385 ips = []
3386 # look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02003387 try:
sousaedu80135b92021-02-17 15:05:18 +01003388 floating_ip_dict = self.neutron.list_floatingips(
3389 port_id=port["id"]
3390 )
3391
tiernob42fd9b2018-06-20 10:44:32 +02003392 if floating_ip_dict.get("floatingips"):
sousaedu80135b92021-02-17 15:05:18 +01003393 ips.append(
3394 floating_ip_dict["floatingips"][0].get(
3395 "floating_ip_address"
3396 )
3397 )
tiernob42fd9b2018-06-20 10:44:32 +02003398 except Exception:
3399 pass
tierno7edb6752016-03-21 17:37:52 +01003400
tiernoae4a8d12016-07-08 12:30:39 +02003401 for subnet in port["fixed_ips"]:
3402 ips.append(subnet["ip_address"])
sousaedu80135b92021-02-17 15:05:18 +01003403
tiernoae4a8d12016-07-08 12:30:39 +02003404 interface["ip_address"] = ";".join(ips)
3405 vm["interfaces"].append(interface)
3406 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01003407 self.logger.error(
3408 "Error getting vm interface information {}: {}".format(
3409 type(e).__name__, e
3410 ),
3411 exc_info=True,
3412 )
tierno72774862020-05-04 11:44:15 +00003413 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003414 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003415 vm["status"] = "DELETED"
3416 vm["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00003417 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003418 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003419 vm["status"] = "VIM_ERROR"
3420 vm["error_msg"] = str(e)
3421
tiernoae4a8d12016-07-08 12:30:39 +02003422 vm_dict[vm_id] = vm
sousaedu80135b92021-02-17 15:05:18 +01003423
tiernoae4a8d12016-07-08 12:30:39 +02003424 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003425
gatici335a06a2023-07-26 00:34:04 +03003426 @catch_any_exception
tierno98e909c2017-10-14 13:27:03 +02003427 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno1ec592d2020-06-16 15:29:47 +00003428 """Send and action over a VM instance from VIM
Gulsum Atici21c55d62023-02-02 20:41:00 +03003429 Returns None or the console dict if the action was successfully sent to the VIM
3430 """
tiernoae4a8d12016-07-08 12:30:39 +02003431 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
gatici335a06a2023-07-26 00:34:04 +03003432 self._reload_connection()
3433 server = self.nova.servers.find(id=vm_id)
3434 if "start" in action_dict:
3435 if action_dict["start"] == "rebuild":
3436 server.rebuild()
Rahul Kumar8875f912023-11-08 06:48:12 +00003437 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
3438 if not vm_state:
3439 raise nvExceptions.BadRequest(
3440 409,
3441 message="Cannot 'REBUILD' vm_state is in ERROR",
3442 )
gatici335a06a2023-07-26 00:34:04 +03003443 else:
3444 if server.status == "PAUSED":
3445 server.unpause()
3446 elif server.status == "SUSPENDED":
3447 server.resume()
3448 elif server.status == "SHUTOFF":
3449 server.start()
Rahul Kumar8875f912023-11-08 06:48:12 +00003450 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
3451 if not vm_state:
3452 raise nvExceptions.BadRequest(
3453 409,
3454 message="Cannot 'START' vm_state is in ERROR",
3455 )
tierno7edb6752016-03-21 17:37:52 +01003456 else:
gatici335a06a2023-07-26 00:34:04 +03003457 self.logger.debug(
3458 "ERROR : Instance is not in SHUTOFF/PAUSE/SUSPEND state"
3459 )
k4.rahul78f474e2022-05-02 15:47:57 +00003460 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03003461 "Cannot 'start' instance while it is in active state",
k4.rahul78f474e2022-05-02 15:47:57 +00003462 http_code=vimconn.HTTP_Bad_Request,
3463 )
gatici335a06a2023-07-26 00:34:04 +03003464 elif "pause" in action_dict:
3465 server.pause()
3466 elif "resume" in action_dict:
3467 server.resume()
3468 elif "shutoff" in action_dict or "shutdown" in action_dict:
3469 self.logger.debug("server status %s", server.status)
3470 if server.status == "ACTIVE":
3471 server.stop()
Rahul Kumar8875f912023-11-08 06:48:12 +00003472 vm_state = self.__wait_for_vm(vm_id, "SHUTOFF")
3473 if not vm_state:
3474 raise nvExceptions.BadRequest(
3475 409,
3476 message="Cannot 'STOP' vm_state is in ERROR",
3477 )
gatici335a06a2023-07-26 00:34:04 +03003478 else:
3479 self.logger.debug("ERROR: VM is not in Active state")
3480 raise vimconn.VimConnException(
3481 "VM is not in active state, stop operation is not allowed",
3482 http_code=vimconn.HTTP_Bad_Request,
3483 )
3484 elif "forceOff" in action_dict:
3485 server.stop() # TODO
3486 elif "terminate" in action_dict:
3487 server.delete()
3488 elif "createImage" in action_dict:
3489 server.create_image()
3490 # "path":path_schema,
3491 # "description":description_schema,
3492 # "name":name_schema,
3493 # "metadata":metadata_schema,
3494 # "imageRef": id_schema,
3495 # "disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
3496 elif "rebuild" in action_dict:
3497 server.rebuild(server.image["id"])
3498 elif "reboot" in action_dict:
3499 server.reboot() # reboot_type="SOFT"
3500 elif "console" in action_dict:
3501 console_type = action_dict["console"]
sousaedu80135b92021-02-17 15:05:18 +01003502
gatici335a06a2023-07-26 00:34:04 +03003503 if console_type is None or console_type == "novnc":
3504 console_dict = server.get_vnc_console("novnc")
3505 elif console_type == "xvpvnc":
3506 console_dict = server.get_vnc_console(console_type)
3507 elif console_type == "rdp-html5":
3508 console_dict = server.get_rdp_console(console_type)
3509 elif console_type == "spice-html5":
3510 console_dict = server.get_spice_console(console_type)
3511 else:
3512 raise vimconn.VimConnException(
3513 "console type '{}' not allowed".format(console_type),
3514 http_code=vimconn.HTTP_Bad_Request,
3515 )
sousaedu80135b92021-02-17 15:05:18 +01003516
gatici335a06a2023-07-26 00:34:04 +03003517 try:
3518 console_url = console_dict["console"]["url"]
3519 # parse console_url
3520 protocol_index = console_url.find("//")
3521 suffix_index = (
3522 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3523 )
3524 port_index = (
3525 console_url[protocol_index + 2 : suffix_index].find(":")
3526 + protocol_index
3527 + 2
3528 )
sousaedu80135b92021-02-17 15:05:18 +01003529
gatici335a06a2023-07-26 00:34:04 +03003530 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
sousaedu80135b92021-02-17 15:05:18 +01003531 raise vimconn.VimConnException(
3532 "Unexpected response from VIM " + str(console_dict)
3533 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003534
gatici335a06a2023-07-26 00:34:04 +03003535 console_dict2 = {
3536 "protocol": console_url[0:protocol_index],
3537 "server": console_url[protocol_index + 2 : port_index],
3538 "port": int(console_url[port_index + 1 : suffix_index]),
3539 "suffix": console_url[suffix_index + 1 :],
3540 }
3541
3542 return console_dict2
3543 except Exception:
3544 raise vimconn.VimConnException(
3545 "Unexpected response from VIM " + str(console_dict)
3546 )
3547
3548 return None
tiernoae4a8d12016-07-08 12:30:39 +02003549
tierno1ec592d2020-06-16 15:29:47 +00003550 # ###### VIO Specific Changes #########
garciadeblasebd66722019-01-31 16:01:31 +00003551 def _generate_vlanID(self):
kate721d79b2017-06-24 04:21:38 -07003552 """
sousaedu80135b92021-02-17 15:05:18 +01003553 Method to get unused vlanID
kate721d79b2017-06-24 04:21:38 -07003554 Args:
3555 None
3556 Returns:
3557 vlanID
3558 """
tierno1ec592d2020-06-16 15:29:47 +00003559 # Get used VLAN IDs
kate721d79b2017-06-24 04:21:38 -07003560 usedVlanIDs = []
3561 networks = self.get_network_list()
sousaedu80135b92021-02-17 15:05:18 +01003562
kate721d79b2017-06-24 04:21:38 -07003563 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003564 if net.get("provider:segmentation_id"):
3565 usedVlanIDs.append(net.get("provider:segmentation_id"))
3566
kate721d79b2017-06-24 04:21:38 -07003567 used_vlanIDs = set(usedVlanIDs)
3568
tierno1ec592d2020-06-16 15:29:47 +00003569 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003570 for vlanID_range in self.config.get("dataplane_net_vlan_range"):
kate721d79b2017-06-24 04:21:38 -07003571 try:
sousaedu80135b92021-02-17 15:05:18 +01003572 start_vlanid, end_vlanid = map(
3573 int, vlanID_range.replace(" ", "").split("-")
3574 )
3575
tierno7d782ef2019-10-04 12:56:31 +00003576 for vlanID in range(start_vlanid, end_vlanid + 1):
kate721d79b2017-06-24 04:21:38 -07003577 if vlanID not in used_vlanIDs:
3578 return vlanID
3579 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003580 raise vimconn.VimConnException(
3581 "Exception {} occurred while generating VLAN ID.".format(exp)
3582 )
kate721d79b2017-06-24 04:21:38 -07003583 else:
tierno1ec592d2020-06-16 15:29:47 +00003584 raise vimconn.VimConnConflictException(
3585 "Unable to create the SRIOV VLAN network. All given Vlan IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003586 self.config.get("dataplane_net_vlan_range")
3587 )
3588 )
kate721d79b2017-06-24 04:21:38 -07003589
garciadeblasebd66722019-01-31 16:01:31 +00003590 def _generate_multisegment_vlanID(self):
3591 """
sousaedu80135b92021-02-17 15:05:18 +01003592 Method to get unused vlanID
3593 Args:
3594 None
3595 Returns:
3596 vlanID
garciadeblasebd66722019-01-31 16:01:31 +00003597 """
tierno6869ae72020-01-09 17:37:34 +00003598 # Get used VLAN IDs
garciadeblasebd66722019-01-31 16:01:31 +00003599 usedVlanIDs = []
3600 networks = self.get_network_list()
3601 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003602 if net.get("provider:network_type") == "vlan" and net.get(
3603 "provider:segmentation_id"
3604 ):
3605 usedVlanIDs.append(net.get("provider:segmentation_id"))
3606 elif net.get("segments"):
3607 for segment in net.get("segments"):
3608 if segment.get("provider:network_type") == "vlan" and segment.get(
3609 "provider:segmentation_id"
3610 ):
3611 usedVlanIDs.append(segment.get("provider:segmentation_id"))
3612
garciadeblasebd66722019-01-31 16:01:31 +00003613 used_vlanIDs = set(usedVlanIDs)
3614
tierno6869ae72020-01-09 17:37:34 +00003615 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003616 for vlanID_range in self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +00003617 try:
sousaedu80135b92021-02-17 15:05:18 +01003618 start_vlanid, end_vlanid = map(
3619 int, vlanID_range.replace(" ", "").split("-")
3620 )
3621
tierno7d782ef2019-10-04 12:56:31 +00003622 for vlanID in range(start_vlanid, end_vlanid + 1):
garciadeblasebd66722019-01-31 16:01:31 +00003623 if vlanID not in used_vlanIDs:
3624 return vlanID
3625 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003626 raise vimconn.VimConnException(
3627 "Exception {} occurred while generating VLAN ID.".format(exp)
3628 )
garciadeblasebd66722019-01-31 16:01:31 +00003629 else:
tierno1ec592d2020-06-16 15:29:47 +00003630 raise vimconn.VimConnConflictException(
3631 "Unable to create the VLAN segment. All VLAN IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003632 self.config.get("multisegment_vlan_range")
3633 )
3634 )
garciadeblasebd66722019-01-31 16:01:31 +00003635
3636 def _validate_vlan_ranges(self, input_vlan_range, text_vlan_range):
kate721d79b2017-06-24 04:21:38 -07003637 """
3638 Method to validate user given vlanID ranges
3639 Args: None
3640 Returns: None
3641 """
garciadeblasebd66722019-01-31 16:01:31 +00003642 for vlanID_range in input_vlan_range:
kate721d79b2017-06-24 04:21:38 -07003643 vlan_range = vlanID_range.replace(" ", "")
tierno1ec592d2020-06-16 15:29:47 +00003644 # validate format
sousaedu80135b92021-02-17 15:05:18 +01003645 vlanID_pattern = r"(\d)*-(\d)*$"
kate721d79b2017-06-24 04:21:38 -07003646 match_obj = re.match(vlanID_pattern, vlan_range)
3647 if not match_obj:
tierno1ec592d2020-06-16 15:29:47 +00003648 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003649 "Invalid VLAN range for {}: {}.You must provide "
3650 "'{}' in format [start_ID - end_ID].".format(
3651 text_vlan_range, vlanID_range, text_vlan_range
3652 )
3653 )
kate721d79b2017-06-24 04:21:38 -07003654
tierno1ec592d2020-06-16 15:29:47 +00003655 start_vlanid, end_vlanid = map(int, vlan_range.split("-"))
3656 if start_vlanid <= 0:
3657 raise vimconn.VimConnConflictException(
3658 "Invalid VLAN range for {}: {}. Start ID can not be zero. For VLAN "
sousaedu80135b92021-02-17 15:05:18 +01003659 "networks valid IDs are 1 to 4094 ".format(
3660 text_vlan_range, vlanID_range
3661 )
3662 )
3663
tierno1ec592d2020-06-16 15:29:47 +00003664 if end_vlanid > 4094:
3665 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003666 "Invalid VLAN range for {}: {}. End VLAN ID can not be "
3667 "greater than 4094. For VLAN networks valid IDs are 1 to 4094 ".format(
3668 text_vlan_range, vlanID_range
3669 )
3670 )
kate721d79b2017-06-24 04:21:38 -07003671
3672 if start_vlanid > end_vlanid:
tierno1ec592d2020-06-16 15:29:47 +00003673 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003674 "Invalid VLAN range for {}: {}. You must provide '{}'"
3675 " in format start_ID - end_ID and start_ID < end_ID ".format(
3676 text_vlan_range, vlanID_range, text_vlan_range
3677 )
3678 )
kate721d79b2017-06-24 04:21:38 -07003679
tierno7edb6752016-03-21 17:37:52 +01003680 def get_hosts_info(self):
tierno1ec592d2020-06-16 15:29:47 +00003681 """Get the information of deployed hosts
3682 Returns the hosts content"""
garciadeblas6a0147f2024-08-19 08:02:04 +00003683 self.logger.debug("osconnector: Getting Host info from VIM")
sousaedu80135b92021-02-17 15:05:18 +01003684
tierno7edb6752016-03-21 17:37:52 +01003685 try:
tierno1ec592d2020-06-16 15:29:47 +00003686 h_list = []
tierno7edb6752016-03-21 17:37:52 +01003687 self._reload_connection()
3688 hypervisors = self.nova.hypervisors.list()
sousaedu80135b92021-02-17 15:05:18 +01003689
tierno7edb6752016-03-21 17:37:52 +01003690 for hype in hypervisors:
tierno1ec592d2020-06-16 15:29:47 +00003691 h_list.append(hype.to_dict())
sousaedu80135b92021-02-17 15:05:18 +01003692
tierno1ec592d2020-06-16 15:29:47 +00003693 return 1, {"hosts": h_list}
tierno7edb6752016-03-21 17:37:52 +01003694 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003695 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003696 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003697 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003698 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003699 error_text = (
3700 type(e).__name__
3701 + ": "
3702 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3703 )
3704
tierno1ec592d2020-06-16 15:29:47 +00003705 # TODO insert exception vimconn.HTTP_Unauthorized
3706 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003707 self.logger.debug("get_hosts_info " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003708
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003709 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003710
3711 def get_hosts(self, vim_tenant):
tierno1ec592d2020-06-16 15:29:47 +00003712 """Get the hosts and deployed instances
3713 Returns the hosts content"""
tierno7edb6752016-03-21 17:37:52 +01003714 r, hype_dict = self.get_hosts_info()
sousaedu80135b92021-02-17 15:05:18 +01003715
tierno1ec592d2020-06-16 15:29:47 +00003716 if r < 0:
tierno7edb6752016-03-21 17:37:52 +01003717 return r, hype_dict
sousaedu80135b92021-02-17 15:05:18 +01003718
tierno7edb6752016-03-21 17:37:52 +01003719 hypervisors = hype_dict["hosts"]
sousaedu80135b92021-02-17 15:05:18 +01003720
tierno7edb6752016-03-21 17:37:52 +01003721 try:
3722 servers = self.nova.servers.list()
3723 for hype in hypervisors:
3724 for server in servers:
sousaedu80135b92021-02-17 15:05:18 +01003725 if (
3726 server.to_dict()["OS-EXT-SRV-ATTR:hypervisor_hostname"]
3727 == hype["hypervisor_hostname"]
3728 ):
3729 if "vm" in hype:
3730 hype["vm"].append(server.id)
tierno7edb6752016-03-21 17:37:52 +01003731 else:
sousaedu80135b92021-02-17 15:05:18 +01003732 hype["vm"] = [server.id]
3733
tierno7edb6752016-03-21 17:37:52 +01003734 return 1, hype_dict
3735 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003736 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003737 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003738 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003739 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003740 error_text = (
3741 type(e).__name__
3742 + ": "
3743 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3744 )
3745
tierno1ec592d2020-06-16 15:29:47 +00003746 # TODO insert exception vimconn.HTTP_Unauthorized
3747 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003748 self.logger.debug("get_hosts " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003749
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003750 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003751
Lovejeet Singhdf486552023-05-09 22:41:09 +05303752 def new_classification(self, name, ctype, definition):
3753 self.logger.debug(
3754 "Adding a new (Traffic) Classification to VIM, named %s", name
3755 )
3756
3757 try:
3758 new_class = None
3759 self._reload_connection()
3760
3761 if ctype not in supportedClassificationTypes:
3762 raise vimconn.VimConnNotSupportedException(
3763 "OpenStack VIM connector does not support provided "
3764 "Classification Type {}, supported ones are: {}".format(
3765 ctype, supportedClassificationTypes
3766 )
3767 )
3768
3769 if not self._validate_classification(ctype, definition):
3770 raise vimconn.VimConnException(
3771 "Incorrect Classification definition for the type specified."
3772 )
3773
3774 classification_dict = definition
3775 classification_dict["name"] = name
3776
3777 self.logger.info(
3778 "Adding a new (Traffic) Classification to VIM, named {} and {}.".format(
3779 name, classification_dict
3780 )
3781 )
3782 new_class = self.neutron.create_sfc_flow_classifier(
3783 {"flow_classifier": classification_dict}
3784 )
3785
3786 return new_class["flow_classifier"]["id"]
3787 except (
3788 neExceptions.ConnectionFailed,
3789 ksExceptions.ClientException,
3790 neExceptions.NeutronException,
3791 ConnectionError,
3792 ) as e:
3793 self.logger.error("Creation of Classification failed.")
3794 self._format_exception(e)
3795
3796 def get_classification(self, class_id):
3797 self.logger.debug(" Getting Classification %s from VIM", class_id)
3798 filter_dict = {"id": class_id}
3799 class_list = self.get_classification_list(filter_dict)
3800
3801 if len(class_list) == 0:
3802 raise vimconn.VimConnNotFoundException(
3803 "Classification '{}' not found".format(class_id)
3804 )
3805 elif len(class_list) > 1:
3806 raise vimconn.VimConnConflictException(
3807 "Found more than one Classification with this criteria"
3808 )
3809
3810 classification = class_list[0]
3811
3812 return classification
3813
3814 def get_classification_list(self, filter_dict={}):
3815 self.logger.debug(
3816 "Getting Classifications from VIM filter: '%s'", str(filter_dict)
3817 )
3818
3819 try:
3820 filter_dict_os = filter_dict.copy()
3821 self._reload_connection()
3822
3823 if self.api_version3 and "tenant_id" in filter_dict_os:
3824 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3825
3826 classification_dict = self.neutron.list_sfc_flow_classifiers(
3827 **filter_dict_os
3828 )
3829 classification_list = classification_dict["flow_classifiers"]
3830 self.__classification_os2mano(classification_list)
3831
3832 return classification_list
3833 except (
3834 neExceptions.ConnectionFailed,
3835 ksExceptions.ClientException,
3836 neExceptions.NeutronException,
3837 ConnectionError,
3838 ) as e:
3839 self._format_exception(e)
3840
3841 def delete_classification(self, class_id):
3842 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
3843
3844 try:
3845 self._reload_connection()
3846 self.neutron.delete_sfc_flow_classifier(class_id)
3847
3848 return class_id
3849 except (
3850 neExceptions.ConnectionFailed,
3851 neExceptions.NeutronException,
3852 ksExceptions.ClientException,
3853 neExceptions.NeutronException,
3854 ConnectionError,
3855 ) as e:
3856 self._format_exception(e)
3857
3858 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
3859 self.logger.debug(
3860 "Adding a new Service Function Instance to VIM, named '%s'", name
3861 )
3862
3863 try:
3864 new_sfi = None
3865 self._reload_connection()
3866 correlation = None
3867
3868 if sfc_encap:
3869 correlation = "nsh"
3870
3871 if len(ingress_ports) != 1:
3872 raise vimconn.VimConnNotSupportedException(
3873 "OpenStack VIM connector can only have 1 ingress port per SFI"
3874 )
3875
3876 if len(egress_ports) != 1:
3877 raise vimconn.VimConnNotSupportedException(
3878 "OpenStack VIM connector can only have 1 egress port per SFI"
3879 )
3880
3881 sfi_dict = {
3882 "name": name,
3883 "ingress": ingress_ports[0],
3884 "egress": egress_ports[0],
3885 "service_function_parameters": {"correlation": correlation},
3886 }
3887 self.logger.info("Adding a new SFI to VIM, {}.".format(sfi_dict))
3888 new_sfi = self.neutron.create_sfc_port_pair({"port_pair": sfi_dict})
3889
3890 return new_sfi["port_pair"]["id"]
3891 except (
3892 neExceptions.ConnectionFailed,
3893 ksExceptions.ClientException,
3894 neExceptions.NeutronException,
3895 ConnectionError,
3896 ) as e:
3897 if new_sfi:
3898 try:
3899 self.neutron.delete_sfc_port_pair(new_sfi["port_pair"]["id"])
3900 except Exception:
3901 self.logger.error(
3902 "Creation of Service Function Instance failed, with "
3903 "subsequent deletion failure as well."
3904 )
3905
3906 self._format_exception(e)
3907
3908 def get_sfi(self, sfi_id):
3909 self.logger.debug("Getting Service Function Instance %s from VIM", sfi_id)
3910 filter_dict = {"id": sfi_id}
3911 sfi_list = self.get_sfi_list(filter_dict)
3912
3913 if len(sfi_list) == 0:
3914 raise vimconn.VimConnNotFoundException(
3915 "Service Function Instance '{}' not found".format(sfi_id)
3916 )
3917 elif len(sfi_list) > 1:
3918 raise vimconn.VimConnConflictException(
3919 "Found more than one Service Function Instance with this criteria"
3920 )
3921
3922 sfi = sfi_list[0]
3923
3924 return sfi
3925
3926 def get_sfi_list(self, filter_dict={}):
3927 self.logger.debug(
3928 "Getting Service Function Instances from VIM filter: '%s'", str(filter_dict)
3929 )
3930
3931 try:
3932 self._reload_connection()
3933 filter_dict_os = filter_dict.copy()
3934
3935 if self.api_version3 and "tenant_id" in filter_dict_os:
3936 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3937
3938 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
3939 sfi_list = sfi_dict["port_pairs"]
3940 self.__sfi_os2mano(sfi_list)
3941
3942 return sfi_list
3943 except (
3944 neExceptions.ConnectionFailed,
3945 ksExceptions.ClientException,
3946 neExceptions.NeutronException,
3947 ConnectionError,
3948 ) as e:
3949 self._format_exception(e)
3950
3951 def delete_sfi(self, sfi_id):
3952 self.logger.debug("Deleting Service Function Instance '%s' from VIM", sfi_id)
3953
3954 try:
3955 self._reload_connection()
3956 self.neutron.delete_sfc_port_pair(sfi_id)
3957
3958 return sfi_id
3959 except (
3960 neExceptions.ConnectionFailed,
3961 neExceptions.NeutronException,
3962 ksExceptions.ClientException,
3963 neExceptions.NeutronException,
3964 ConnectionError,
3965 ) as e:
3966 self._format_exception(e)
3967
3968 def new_sf(self, name, sfis, sfc_encap=True):
3969 self.logger.debug("Adding a new Service Function to VIM, named '%s'", name)
3970
3971 new_sf = None
3972
3973 try:
3974 self._reload_connection()
3975
3976 for instance in sfis:
3977 sfi = self.get_sfi(instance)
3978
3979 if sfi.get("sfc_encap") != sfc_encap:
3980 raise vimconn.VimConnNotSupportedException(
3981 "OpenStack VIM connector requires all SFIs of the "
3982 "same SF to share the same SFC Encapsulation"
3983 )
3984
3985 sf_dict = {"name": name, "port_pairs": sfis}
3986
3987 self.logger.info("Adding a new SF to VIM, {}.".format(sf_dict))
3988 new_sf = self.neutron.create_sfc_port_pair_group(
3989 {"port_pair_group": sf_dict}
3990 )
3991
3992 return new_sf["port_pair_group"]["id"]
3993 except (
3994 neExceptions.ConnectionFailed,
3995 ksExceptions.ClientException,
3996 neExceptions.NeutronException,
3997 ConnectionError,
3998 ) as e:
3999 if new_sf:
4000 try:
4001 new_sf_id = new_sf.get("port_pair_group").get("id")
4002 self.neutron.delete_sfc_port_pair_group(new_sf_id)
4003 except Exception:
4004 self.logger.error(
4005 "Creation of Service Function failed, with "
4006 "subsequent deletion failure as well."
4007 )
4008
4009 self._format_exception(e)
4010
4011 def get_sf(self, sf_id):
4012 self.logger.debug("Getting Service Function %s from VIM", sf_id)
4013 filter_dict = {"id": sf_id}
4014 sf_list = self.get_sf_list(filter_dict)
4015
4016 if len(sf_list) == 0:
4017 raise vimconn.VimConnNotFoundException(
4018 "Service Function '{}' not found".format(sf_id)
4019 )
4020 elif len(sf_list) > 1:
4021 raise vimconn.VimConnConflictException(
4022 "Found more than one Service Function with this criteria"
4023 )
4024
4025 sf = sf_list[0]
4026
4027 return sf
4028
4029 def get_sf_list(self, filter_dict={}):
4030 self.logger.debug(
4031 "Getting Service Function from VIM filter: '%s'", str(filter_dict)
4032 )
4033
4034 try:
4035 self._reload_connection()
4036 filter_dict_os = filter_dict.copy()
4037
4038 if self.api_version3 and "tenant_id" in filter_dict_os:
4039 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
4040
4041 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
4042 sf_list = sf_dict["port_pair_groups"]
4043 self.__sf_os2mano(sf_list)
4044
4045 return sf_list
4046 except (
4047 neExceptions.ConnectionFailed,
4048 ksExceptions.ClientException,
4049 neExceptions.NeutronException,
4050 ConnectionError,
4051 ) as e:
4052 self._format_exception(e)
4053
4054 def delete_sf(self, sf_id):
4055 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
4056
4057 try:
4058 self._reload_connection()
4059 self.neutron.delete_sfc_port_pair_group(sf_id)
4060
4061 return sf_id
4062 except (
4063 neExceptions.ConnectionFailed,
4064 neExceptions.NeutronException,
4065 ksExceptions.ClientException,
4066 neExceptions.NeutronException,
4067 ConnectionError,
4068 ) as e:
4069 self._format_exception(e)
4070
4071 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
4072 self.logger.debug("Adding a new Service Function Path to VIM, named '%s'", name)
4073
4074 new_sfp = None
4075
4076 try:
4077 self._reload_connection()
4078 # In networking-sfc the MPLS encapsulation is legacy
4079 # should be used when no full SFC Encapsulation is intended
4080 correlation = "mpls"
4081
4082 if sfc_encap:
4083 correlation = "nsh"
4084
4085 sfp_dict = {
4086 "name": name,
4087 "flow_classifiers": classifications,
4088 "port_pair_groups": sfs,
4089 "chain_parameters": {"correlation": correlation},
4090 }
4091
4092 if spi:
4093 sfp_dict["chain_id"] = spi
4094
4095 self.logger.info("Adding a new SFP to VIM, {}.".format(sfp_dict))
4096 new_sfp = self.neutron.create_sfc_port_chain({"port_chain": sfp_dict})
4097
4098 return new_sfp["port_chain"]["id"]
4099 except (
4100 neExceptions.ConnectionFailed,
4101 ksExceptions.ClientException,
4102 neExceptions.NeutronException,
4103 ConnectionError,
4104 ) as e:
4105 if new_sfp:
4106 try:
4107 new_sfp_id = new_sfp.get("port_chain").get("id")
4108 self.neutron.delete_sfc_port_chain(new_sfp_id)
4109 except Exception:
4110 self.logger.error(
4111 "Creation of Service Function Path failed, with "
4112 "subsequent deletion failure as well."
4113 )
4114
4115 self._format_exception(e)
4116
4117 def get_sfp(self, sfp_id):
4118 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
4119
4120 filter_dict = {"id": sfp_id}
4121 sfp_list = self.get_sfp_list(filter_dict)
4122
4123 if len(sfp_list) == 0:
4124 raise vimconn.VimConnNotFoundException(
4125 "Service Function Path '{}' not found".format(sfp_id)
4126 )
4127 elif len(sfp_list) > 1:
4128 raise vimconn.VimConnConflictException(
4129 "Found more than one Service Function Path with this criteria"
4130 )
4131
4132 sfp = sfp_list[0]
4133
4134 return sfp
4135
4136 def get_sfp_list(self, filter_dict={}):
4137 self.logger.debug(
4138 "Getting Service Function Paths from VIM filter: '%s'", str(filter_dict)
4139 )
4140
4141 try:
4142 self._reload_connection()
4143 filter_dict_os = filter_dict.copy()
4144
4145 if self.api_version3 and "tenant_id" in filter_dict_os:
4146 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
4147
4148 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
4149 sfp_list = sfp_dict["port_chains"]
4150 self.__sfp_os2mano(sfp_list)
4151
4152 return sfp_list
4153 except (
4154 neExceptions.ConnectionFailed,
4155 ksExceptions.ClientException,
4156 neExceptions.NeutronException,
4157 ConnectionError,
4158 ) as e:
4159 self._format_exception(e)
4160
4161 def delete_sfp(self, sfp_id):
4162 self.logger.debug("Deleting Service Function Path '%s' from VIM", sfp_id)
4163
4164 try:
4165 self._reload_connection()
4166 self.neutron.delete_sfc_port_chain(sfp_id)
4167
4168 return sfp_id
4169 except (
4170 neExceptions.ConnectionFailed,
4171 neExceptions.NeutronException,
4172 ksExceptions.ClientException,
4173 neExceptions.NeutronException,
4174 ConnectionError,
4175 ) as e:
4176 self._format_exception(e)
4177
4178 def refresh_sfps_status(self, sfp_list):
4179 """Get the status of the service function path
4180 Params: the list of sfp identifiers
4181 Returns a dictionary with:
4182 vm_id: #VIM id of this service function path
4183 status: #Mandatory. Text with one of:
4184 # DELETED (not found at vim)
4185 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4186 # OTHER (Vim reported other status not understood)
4187 # ERROR (VIM indicates an ERROR status)
4188 # ACTIVE,
4189 # CREATING (on building process)
4190 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4191 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)F
4192 """
4193 sfp_dict = {}
4194 self.logger.debug(
4195 "refresh_sfps status: Getting tenant SFP information from VIM"
4196 )
4197
4198 for sfp_id in sfp_list:
4199 sfp = {}
4200
4201 try:
4202 sfp_vim = self.get_sfp(sfp_id)
4203
4204 if sfp_vim["spi"]:
4205 sfp["status"] = vmStatus2manoFormat["ACTIVE"]
4206 else:
4207 sfp["status"] = "OTHER"
4208 sfp["error_msg"] = "VIM status reported " + sfp["status"]
4209
4210 sfp["vim_info"] = self.serialize(sfp_vim)
4211
4212 if sfp_vim.get("fault"):
4213 sfp["error_msg"] = str(sfp_vim["fault"])
4214 except vimconn.VimConnNotFoundException as e:
4215 self.logger.error("Exception getting sfp status: %s", str(e))
4216 sfp["status"] = "DELETED"
4217 sfp["error_msg"] = str(e)
4218 except vimconn.VimConnException as e:
4219 self.logger.error("Exception getting sfp status: %s", str(e))
4220 sfp["status"] = "VIM_ERROR"
4221 sfp["error_msg"] = str(e)
4222
4223 sfp_dict[sfp_id] = sfp
4224
4225 return sfp_dict
4226
4227 def refresh_sfis_status(self, sfi_list):
4228 """Get the status of the service function instances
4229 Params: the list of sfi identifiers
4230 Returns a dictionary with:
4231 vm_id: #VIM id of this service function instance
4232 status: #Mandatory. Text with one of:
4233 # DELETED (not found at vim)
4234 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4235 # OTHER (Vim reported other status not understood)
4236 # ERROR (VIM indicates an ERROR status)
4237 # ACTIVE,
4238 # CREATING (on building process)
4239 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4240 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4241 """
4242 sfi_dict = {}
4243 self.logger.debug(
4244 "refresh_sfis status: Getting tenant sfi information from VIM"
4245 )
4246
4247 for sfi_id in sfi_list:
4248 sfi = {}
4249
4250 try:
4251 sfi_vim = self.get_sfi(sfi_id)
4252
4253 if sfi_vim:
4254 sfi["status"] = vmStatus2manoFormat["ACTIVE"]
4255 else:
4256 sfi["status"] = "OTHER"
4257 sfi["error_msg"] = "VIM status reported " + sfi["status"]
4258
4259 sfi["vim_info"] = self.serialize(sfi_vim)
4260
4261 if sfi_vim.get("fault"):
4262 sfi["error_msg"] = str(sfi_vim["fault"])
4263 except vimconn.VimConnNotFoundException as e:
4264 self.logger.error("Exception getting sfi status: %s", str(e))
4265 sfi["status"] = "DELETED"
4266 sfi["error_msg"] = str(e)
4267 except vimconn.VimConnException as e:
4268 self.logger.error("Exception getting sfi status: %s", str(e))
4269 sfi["status"] = "VIM_ERROR"
4270 sfi["error_msg"] = str(e)
4271
4272 sfi_dict[sfi_id] = sfi
4273
4274 return sfi_dict
4275
4276 def refresh_sfs_status(self, sf_list):
4277 """Get the status of the service functions
4278 Params: the list of sf identifiers
4279 Returns a dictionary with:
4280 vm_id: #VIM id of this service function
4281 status: #Mandatory. Text with one of:
4282 # DELETED (not found at vim)
4283 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4284 # OTHER (Vim reported other status not understood)
4285 # ERROR (VIM indicates an ERROR status)
4286 # ACTIVE,
4287 # CREATING (on building process)
4288 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4289 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4290 """
4291 sf_dict = {}
4292 self.logger.debug("refresh_sfs status: Getting tenant sf information from VIM")
4293
4294 for sf_id in sf_list:
4295 sf = {}
4296
4297 try:
4298 sf_vim = self.get_sf(sf_id)
4299
4300 if sf_vim:
4301 sf["status"] = vmStatus2manoFormat["ACTIVE"]
4302 else:
4303 sf["status"] = "OTHER"
4304 sf["error_msg"] = "VIM status reported " + sf_vim["status"]
4305
4306 sf["vim_info"] = self.serialize(sf_vim)
4307
4308 if sf_vim.get("fault"):
4309 sf["error_msg"] = str(sf_vim["fault"])
4310 except vimconn.VimConnNotFoundException as e:
4311 self.logger.error("Exception getting sf status: %s", str(e))
4312 sf["status"] = "DELETED"
4313 sf["error_msg"] = str(e)
4314 except vimconn.VimConnException as e:
4315 self.logger.error("Exception getting sf status: %s", str(e))
4316 sf["status"] = "VIM_ERROR"
4317 sf["error_msg"] = str(e)
4318
4319 sf_dict[sf_id] = sf
4320
4321 return sf_dict
4322
4323 def refresh_classifications_status(self, classification_list):
4324 """Get the status of the classifications
4325 Params: the list of classification identifiers
4326 Returns a dictionary with:
4327 vm_id: #VIM id of this classifier
4328 status: #Mandatory. Text with one of:
4329 # DELETED (not found at vim)
4330 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4331 # OTHER (Vim reported other status not understood)
4332 # ERROR (VIM indicates an ERROR status)
4333 # ACTIVE,
4334 # CREATING (on building process)
4335 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4336 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4337 """
4338 classification_dict = {}
4339 self.logger.debug(
4340 "refresh_classifications status: Getting tenant classification information from VIM"
4341 )
4342
4343 for classification_id in classification_list:
4344 classification = {}
4345
4346 try:
4347 classification_vim = self.get_classification(classification_id)
4348
4349 if classification_vim:
4350 classification["status"] = vmStatus2manoFormat["ACTIVE"]
4351 else:
4352 classification["status"] = "OTHER"
4353 classification["error_msg"] = (
4354 "VIM status reported " + classification["status"]
4355 )
4356
4357 classification["vim_info"] = self.serialize(classification_vim)
4358
4359 if classification_vim.get("fault"):
4360 classification["error_msg"] = str(classification_vim["fault"])
4361 except vimconn.VimConnNotFoundException as e:
4362 self.logger.error("Exception getting classification status: %s", str(e))
4363 classification["status"] = "DELETED"
4364 classification["error_msg"] = str(e)
4365 except vimconn.VimConnException as e:
4366 self.logger.error("Exception getting classification status: %s", str(e))
4367 classification["status"] = "VIM_ERROR"
4368 classification["error_msg"] = str(e)
4369
4370 classification_dict[classification_id] = classification
4371
4372 return classification_dict
4373
gatici335a06a2023-07-26 00:34:04 +03004374 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004375 def new_affinity_group(self, affinity_group_data):
4376 """Adds a server group to VIM
4377 affinity_group_data contains a dictionary with information, keys:
4378 name: name in VIM for the server group
4379 type: affinity or anti-affinity
4380 scope: Only nfvi-node allowed
4381 Returns the server group identifier"""
4382 self.logger.debug("Adding Server Group '%s'", str(affinity_group_data))
gatici335a06a2023-07-26 00:34:04 +03004383 name = affinity_group_data["name"]
4384 policy = affinity_group_data["type"]
4385 self._reload_connection()
4386 new_server_group = self.nova.server_groups.create(name, policy)
4387 return new_server_group.id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004388
gatici335a06a2023-07-26 00:34:04 +03004389 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004390 def get_affinity_group(self, affinity_group_id):
4391 """Obtain server group details from the VIM. Returns the server group detais as a dict"""
4392 self.logger.debug("Getting flavor '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004393 self._reload_connection()
4394 server_group = self.nova.server_groups.find(id=affinity_group_id)
4395 return server_group.to_dict()
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004396
gatici335a06a2023-07-26 00:34:04 +03004397 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004398 def delete_affinity_group(self, affinity_group_id):
4399 """Deletes a server group from the VIM. Returns the old affinity_group_id"""
4400 self.logger.debug("Getting server group '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004401 self._reload_connection()
4402 self.nova.server_groups.delete(affinity_group_id)
4403 return affinity_group_id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004404
gatici335a06a2023-07-26 00:34:04 +03004405 @catch_any_exception
Patricia Reinoso17852162023-06-15 07:33:04 +00004406 def get_vdu_state(self, vm_id, host_is_required=False) -> list:
4407 """Getting the state of a VDU.
4408 Args:
4409 vm_id (str): ID of an instance
4410 host_is_required (Boolean): If the VIM account is non-admin, host info does not appear in server_dict
4411 and if this is set to True, it raises KeyError.
4412 Returns:
4413 vdu_data (list): VDU details including state, flavor, host_info, AZ
elumalai8658c2c2022-04-28 19:09:31 +05304414 """
4415 self.logger.debug("Getting the status of VM")
4416 self.logger.debug("VIM VM ID %s", vm_id)
gatici335a06a2023-07-26 00:34:04 +03004417 self._reload_connection()
4418 server_dict = self._find_nova_server(vm_id)
4419 srv_attr = "OS-EXT-SRV-ATTR:host"
4420 host_info = (
4421 server_dict[srv_attr] if host_is_required else server_dict.get(srv_attr)
4422 )
4423 vdu_data = [
4424 server_dict["status"],
4425 server_dict["flavor"]["id"],
4426 host_info,
4427 server_dict["OS-EXT-AZ:availability_zone"],
4428 ]
4429 self.logger.debug("vdu_data %s", vdu_data)
4430 return vdu_data
elumalai8658c2c2022-04-28 19:09:31 +05304431
4432 def check_compute_availability(self, host, server_flavor_details):
4433 self._reload_connection()
4434 hypervisor_search = self.nova.hypervisors.search(
4435 hypervisor_match=host, servers=True
4436 )
4437 for hypervisor in hypervisor_search:
4438 hypervisor_id = hypervisor.to_dict()["id"]
4439 hypervisor_details = self.nova.hypervisors.get(hypervisor=hypervisor_id)
4440 hypervisor_dict = hypervisor_details.to_dict()
4441 hypervisor_temp = json.dumps(hypervisor_dict)
4442 hypervisor_json = json.loads(hypervisor_temp)
4443 resources_available = [
4444 hypervisor_json["free_ram_mb"],
4445 hypervisor_json["disk_available_least"],
4446 hypervisor_json["vcpus"] - hypervisor_json["vcpus_used"],
4447 ]
4448 compute_available = all(
4449 x > y for x, y in zip(resources_available, server_flavor_details)
4450 )
4451 if compute_available:
4452 return host
4453
4454 def check_availability_zone(
4455 self, old_az, server_flavor_details, old_host, host=None
4456 ):
4457 self._reload_connection()
4458 az_check = {"zone_check": False, "compute_availability": None}
4459 aggregates_list = self.nova.aggregates.list()
4460 for aggregate in aggregates_list:
4461 aggregate_details = aggregate.to_dict()
4462 aggregate_temp = json.dumps(aggregate_details)
4463 aggregate_json = json.loads(aggregate_temp)
4464 if aggregate_json["availability_zone"] == old_az:
4465 hosts_list = aggregate_json["hosts"]
4466 if host is not None:
4467 if host in hosts_list:
4468 az_check["zone_check"] = True
4469 available_compute_id = self.check_compute_availability(
4470 host, server_flavor_details
4471 )
4472 if available_compute_id is not None:
4473 az_check["compute_availability"] = available_compute_id
4474 else:
4475 for check_host in hosts_list:
4476 if check_host != old_host:
4477 available_compute_id = self.check_compute_availability(
4478 check_host, server_flavor_details
4479 )
4480 if available_compute_id is not None:
4481 az_check["zone_check"] = True
4482 az_check["compute_availability"] = available_compute_id
4483 break
4484 else:
4485 az_check["zone_check"] = True
4486 return az_check
4487
gatici335a06a2023-07-26 00:34:04 +03004488 @catch_any_exception
elumalai8658c2c2022-04-28 19:09:31 +05304489 def migrate_instance(self, vm_id, compute_host=None):
4490 """
4491 Migrate a vdu
4492 param:
4493 vm_id: ID of an instance
4494 compute_host: Host to migrate the vdu to
4495 """
4496 self._reload_connection()
4497 vm_state = False
Patricia Reinoso17852162023-06-15 07:33:04 +00004498 instance_state = self.get_vdu_state(vm_id, host_is_required=True)
elumalai8658c2c2022-04-28 19:09:31 +05304499 server_flavor_id = instance_state[1]
4500 server_hypervisor_name = instance_state[2]
4501 server_availability_zone = instance_state[3]
gatici335a06a2023-07-26 00:34:04 +03004502 server_flavor = self.nova.flavors.find(id=server_flavor_id).to_dict()
4503 server_flavor_details = [
4504 server_flavor["ram"],
4505 server_flavor["disk"],
4506 server_flavor["vcpus"],
4507 ]
4508 if compute_host == server_hypervisor_name:
4509 raise vimconn.VimConnException(
4510 "Unable to migrate instance '{}' to the same host '{}'".format(
4511 vm_id, compute_host
4512 ),
4513 http_code=vimconn.HTTP_Bad_Request,
elumalai8658c2c2022-04-28 19:09:31 +05304514 )
gatici335a06a2023-07-26 00:34:04 +03004515 az_status = self.check_availability_zone(
4516 server_availability_zone,
4517 server_flavor_details,
4518 server_hypervisor_name,
4519 compute_host,
4520 )
4521 availability_zone_check = az_status["zone_check"]
4522 available_compute_id = az_status.get("compute_availability")
elumalai8658c2c2022-04-28 19:09:31 +05304523
gatici335a06a2023-07-26 00:34:04 +03004524 if availability_zone_check is False:
4525 raise vimconn.VimConnException(
4526 "Unable to migrate instance '{}' to a different availability zone".format(
4527 vm_id
4528 ),
4529 http_code=vimconn.HTTP_Bad_Request,
4530 )
4531 if available_compute_id is not None:
4532 # disk_over_commit parameter for live_migrate method is not valid for Nova API version >= 2.25
4533 self.nova.servers.live_migrate(
4534 server=vm_id,
4535 host=available_compute_id,
4536 block_migration=True,
4537 )
4538 state = "MIGRATING"
4539 changed_compute_host = ""
4540 if state == "MIGRATING":
4541 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
4542 changed_compute_host = self.get_vdu_state(vm_id, host_is_required=True)[
4543 2
4544 ]
4545 if vm_state and changed_compute_host == available_compute_id:
4546 self.logger.debug(
4547 "Instance '{}' migrated to the new compute host '{}'".format(
4548 vm_id, changed_compute_host
elumalai8658c2c2022-04-28 19:09:31 +05304549 )
gatici335a06a2023-07-26 00:34:04 +03004550 )
4551 return state, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304552 else:
4553 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03004554 "Migration Failed. Instance '{}' not moved to the new host {}".format(
4555 vm_id, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304556 ),
4557 http_code=vimconn.HTTP_Bad_Request,
4558 )
gatici335a06a2023-07-26 00:34:04 +03004559 else:
4560 raise vimconn.VimConnException(
4561 "Compute '{}' not available or does not have enough resources to migrate the instance".format(
4562 available_compute_id
4563 ),
4564 http_code=vimconn.HTTP_Bad_Request,
4565 )
sritharan29a4c1a2022-05-05 12:15:04 +00004566
gatici335a06a2023-07-26 00:34:04 +03004567 @catch_any_exception
sritharan29a4c1a2022-05-05 12:15:04 +00004568 def resize_instance(self, vm_id, new_flavor_id):
4569 """
4570 For resizing the vm based on the given
4571 flavor details
4572 param:
4573 vm_id : ID of an instance
4574 new_flavor_id : Flavor id to be resized
4575 Return the status of a resized instance
4576 """
4577 self._reload_connection()
4578 self.logger.debug("resize the flavor of an instance")
4579 instance_status, old_flavor_id, compute_host, az = self.get_vdu_state(vm_id)
4580 old_flavor_disk = self.nova.flavors.find(id=old_flavor_id).to_dict()["disk"]
4581 new_flavor_disk = self.nova.flavors.find(id=new_flavor_id).to_dict()["disk"]
gatici335a06a2023-07-26 00:34:04 +03004582 if instance_status == "ACTIVE" or instance_status == "SHUTOFF":
4583 if old_flavor_disk > new_flavor_disk:
sritharan29a4c1a2022-05-05 12:15:04 +00004584 raise nvExceptions.BadRequest(
gatici335a06a2023-07-26 00:34:04 +03004585 400,
4586 message="Server disk resize failed. Resize to lower disk flavor is not allowed",
sritharan29a4c1a2022-05-05 12:15:04 +00004587 )
gatici335a06a2023-07-26 00:34:04 +03004588 else:
4589 self.nova.servers.resize(server=vm_id, flavor=new_flavor_id)
4590 vm_state = self.__wait_for_vm(vm_id, "VERIFY_RESIZE")
4591 if vm_state:
deepika.e970f5552024-06-03 14:12:50 +05304592 instance_resized_status = self.confirm_resize(
4593 vm_id, instance_status
4594 )
gatici335a06a2023-07-26 00:34:04 +03004595 return instance_resized_status
4596 else:
4597 raise nvExceptions.BadRequest(
4598 409,
4599 message="Cannot 'resize' vm_state is in ERROR",
4600 )
4601
4602 else:
4603 self.logger.debug("ERROR : Instance is not in ACTIVE or SHUTOFF state")
4604 raise nvExceptions.BadRequest(
4605 409,
4606 message="Cannot 'resize' instance while it is in vm_state resized",
4607 )
sritharan29a4c1a2022-05-05 12:15:04 +00004608
deepika.e970f5552024-06-03 14:12:50 +05304609 def confirm_resize(self, vm_id, instance_state):
sritharan29a4c1a2022-05-05 12:15:04 +00004610 """
4611 Confirm the resize of an instance
4612 param:
4613 vm_id: ID of an instance
4614 """
4615 self._reload_connection()
4616 self.nova.servers.confirm_resize(server=vm_id)
4617 if self.get_vdu_state(vm_id)[0] == "VERIFY_RESIZE":
deepika.e970f5552024-06-03 14:12:50 +05304618 self.__wait_for_vm(vm_id, instance_state)
sritharan29a4c1a2022-05-05 12:15:04 +00004619 instance_status = self.get_vdu_state(vm_id)[0]
4620 return instance_status
Gulsum Aticid586d892023-02-13 18:40:03 +03004621
4622 def get_monitoring_data(self):
4623 try:
4624 self.logger.debug("Getting servers and ports data from Openstack VIMs.")
4625 self._reload_connection()
4626 all_servers = self.nova.servers.list(detailed=True)
vegallc53829d2023-06-01 00:47:44 -05004627 try:
4628 for server in all_servers:
Luis Vegad6577d82023-07-26 20:49:12 +00004629 if server.flavor.get("original_name"):
4630 server.flavor["id"] = self.nova.flavors.find(
4631 name=server.flavor["original_name"]
4632 ).id
vegallc53829d2023-06-01 00:47:44 -05004633 except nClient.exceptions.NotFound as e:
4634 self.logger.warning(str(e.message))
Gulsum Aticid586d892023-02-13 18:40:03 +03004635 all_ports = self.neutron.list_ports()
4636 return all_servers, all_ports
gatici335a06a2023-07-26 00:34:04 +03004637 except Exception as e:
Gulsum Aticid586d892023-02-13 18:40:03 +03004638 raise vimconn.VimConnException(
4639 f"Exception in monitoring while getting VMs and ports status: {str(e)}"
4640 )