blob: 0ab4d6d4ebe625ca91b727a71695a42826de3923 [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)
garciadeblas95906272024-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:
garciadeblas95906272024-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:
garciadeblas95906272024-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
garciadeblas95906272024-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:
garciadeblas95906272024-11-28 17:46:13 +0100393 self.logger.debug(
394 f"Service type not found. Using Cinder client v3 for VIM {self.id}"
395 )
Lovejeet Singh778f3cc2023-02-13 16:15:40 +0530396 self.cinder = self.session["cinder"] = cClient.Client(
397 3,
398 session=sess,
399 endpoint_type=self.endpoint_type,
400 region_name=region_name,
401 )
sousaedu80135b92021-02-17 15:05:18 +0100402
tiernoa05b65a2019-02-01 12:30:27 +0000403 try:
sousaedu80135b92021-02-17 15:05:18 +0100404 self.my_tenant_id = self.session["my_tenant_id"] = sess.get_project_id()
tierno1ec592d2020-06-16 15:29:47 +0000405 except Exception:
tiernoa05b65a2019-02-01 12:30:27 +0000406 self.logger.error("Cannot get project_id from session", exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +0100407
kate721d79b2017-06-24 04:21:38 -0700408 if self.endpoint_type == "internalURL":
409 glance_service_id = self.keystone.services.list(name="glance")[0].id
sousaedu80135b92021-02-17 15:05:18 +0100410 glance_endpoint = self.keystone.endpoints.list(
411 glance_service_id, interface="internal"
412 )[0].url
kate721d79b2017-06-24 04:21:38 -0700413 else:
414 glance_endpoint = None
sousaedu80135b92021-02-17 15:05:18 +0100415
416 self.glance = self.session["glance"] = glClient.Client(
417 2, session=sess, endpoint=glance_endpoint
418 )
tiernoa05b65a2019-02-01 12:30:27 +0000419 # using version 1 of glance client in new_image()
sousaedu80135b92021-02-17 15:05:18 +0100420 # self.glancev1 = self.session["glancev1"] = glClient.Client("1", session=sess,
tierno1beea862018-07-11 15:47:37 +0200421 # endpoint=glance_endpoint)
sousaedu80135b92021-02-17 15:05:18 +0100422 self.session["reload_client"] = False
423 self.persistent_info["session"] = self.session
mirabal29356312017-07-27 12:21:22 +0200424 # add availablity zone info inside self.persistent_info
425 self._set_availablity_zones()
sousaedu80135b92021-02-17 15:05:18 +0100426 self.persistent_info["availability_zone"] = self.availability_zone
427 # force to get again security_groups_ids next time they are needed
428 self.security_groups_id = None
ahmadsa95baa272016-11-30 09:14:11 +0500429
tierno7edb6752016-03-21 17:37:52 +0100430 def __net_os2mano(self, net_list_dict):
tierno1ec592d2020-06-16 15:29:47 +0000431 """Transform the net openstack format to mano format
432 net_list_dict can be a list of dict or a single dict"""
tierno7edb6752016-03-21 17:37:52 +0100433 if type(net_list_dict) is dict:
tierno1ec592d2020-06-16 15:29:47 +0000434 net_list_ = (net_list_dict,)
tierno7edb6752016-03-21 17:37:52 +0100435 elif type(net_list_dict) is list:
tierno1ec592d2020-06-16 15:29:47 +0000436 net_list_ = net_list_dict
tierno7edb6752016-03-21 17:37:52 +0100437 else:
438 raise TypeError("param net_list_dict must be a list or a dictionary")
439 for net in net_list_:
sousaedu80135b92021-02-17 15:05:18 +0100440 if net.get("provider:network_type") == "vlan":
441 net["type"] = "data"
tierno7edb6752016-03-21 17:37:52 +0100442 else:
sousaedu80135b92021-02-17 15:05:18 +0100443 net["type"] = "bridge"
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200444
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000445 def __classification_os2mano(self, class_list_dict):
446 """Transform the openstack format (Flow Classifier) to mano format
447 (Classification) class_list_dict can be a list of dict or a single dict
448 """
449 if isinstance(class_list_dict, dict):
450 class_list_ = [class_list_dict]
451 elif isinstance(class_list_dict, list):
452 class_list_ = class_list_dict
453 else:
tierno1ec592d2020-06-16 15:29:47 +0000454 raise TypeError("param class_list_dict must be a list or a dictionary")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000455 for classification in class_list_:
sousaedu80135b92021-02-17 15:05:18 +0100456 id = classification.pop("id")
457 name = classification.pop("name")
458 description = classification.pop("description")
459 project_id = classification.pop("project_id")
460 tenant_id = classification.pop("tenant_id")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000461 original_classification = copy.deepcopy(classification)
462 classification.clear()
sousaedu80135b92021-02-17 15:05:18 +0100463 classification["ctype"] = "legacy_flow_classifier"
464 classification["definition"] = original_classification
465 classification["id"] = id
466 classification["name"] = name
467 classification["description"] = description
468 classification["project_id"] = project_id
469 classification["tenant_id"] = tenant_id
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000470
471 def __sfi_os2mano(self, sfi_list_dict):
472 """Transform the openstack format (Port Pair) to mano format (SFI)
473 sfi_list_dict can be a list of dict or a single dict
474 """
475 if isinstance(sfi_list_dict, dict):
476 sfi_list_ = [sfi_list_dict]
477 elif isinstance(sfi_list_dict, list):
478 sfi_list_ = sfi_list_dict
479 else:
sousaedu80135b92021-02-17 15:05:18 +0100480 raise TypeError("param sfi_list_dict must be a list or a dictionary")
481
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000482 for sfi in sfi_list_:
sousaedu80135b92021-02-17 15:05:18 +0100483 sfi["ingress_ports"] = []
484 sfi["egress_ports"] = []
485
486 if sfi.get("ingress"):
487 sfi["ingress_ports"].append(sfi["ingress"])
488
489 if sfi.get("egress"):
490 sfi["egress_ports"].append(sfi["egress"])
491
492 del sfi["ingress"]
493 del sfi["egress"]
494 params = sfi.get("service_function_parameters")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000495 sfc_encap = False
sousaedu80135b92021-02-17 15:05:18 +0100496
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000497 if params:
sousaedu80135b92021-02-17 15:05:18 +0100498 correlation = params.get("correlation")
499
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000500 if correlation:
501 sfc_encap = True
sousaedu80135b92021-02-17 15:05:18 +0100502
503 sfi["sfc_encap"] = sfc_encap
504 del sfi["service_function_parameters"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000505
506 def __sf_os2mano(self, sf_list_dict):
507 """Transform the openstack format (Port Pair Group) to mano format (SF)
508 sf_list_dict can be a list of dict or a single dict
509 """
510 if isinstance(sf_list_dict, dict):
511 sf_list_ = [sf_list_dict]
512 elif isinstance(sf_list_dict, list):
513 sf_list_ = sf_list_dict
514 else:
sousaedu80135b92021-02-17 15:05:18 +0100515 raise TypeError("param sf_list_dict must be a list or a dictionary")
516
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000517 for sf in sf_list_:
sousaedu80135b92021-02-17 15:05:18 +0100518 del sf["port_pair_group_parameters"]
519 sf["sfis"] = sf["port_pairs"]
520 del sf["port_pairs"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000521
522 def __sfp_os2mano(self, sfp_list_dict):
523 """Transform the openstack format (Port Chain) to mano format (SFP)
524 sfp_list_dict can be a list of dict or a single dict
525 """
526 if isinstance(sfp_list_dict, dict):
527 sfp_list_ = [sfp_list_dict]
528 elif isinstance(sfp_list_dict, list):
529 sfp_list_ = sfp_list_dict
530 else:
sousaedu80135b92021-02-17 15:05:18 +0100531 raise TypeError("param sfp_list_dict must be a list or a dictionary")
532
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000533 for sfp in sfp_list_:
sousaedu80135b92021-02-17 15:05:18 +0100534 params = sfp.pop("chain_parameters")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000535 sfc_encap = False
sousaedu80135b92021-02-17 15:05:18 +0100536
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000537 if params:
sousaedu80135b92021-02-17 15:05:18 +0100538 correlation = params.get("correlation")
539
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000540 if correlation:
541 sfc_encap = True
sousaedu80135b92021-02-17 15:05:18 +0100542
543 sfp["sfc_encap"] = sfc_encap
544 sfp["spi"] = sfp.pop("chain_id")
545 sfp["classifications"] = sfp.pop("flow_classifiers")
546 sfp["service_functions"] = sfp.pop("port_pair_groups")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000547
548 # placeholder for now; read TODO note below
549 def _validate_classification(self, type, definition):
550 # only legacy_flow_classifier Type is supported at this point
551 return True
552 # TODO(igordcard): this method should be an abstract method of an
553 # abstract Classification class to be implemented by the specific
554 # Types. Also, abstract vimconnector should call the validation
555 # method before the implemented VIM connectors are called.
556
gatici335a06a2023-07-26 00:34:04 +0300557 @staticmethod
558 def _format_exception(exception):
tierno69647792020-03-05 16:45:48 +0000559 """Transform a keystone, nova, neutron exception into a vimconn exception discovering the cause"""
tierno69647792020-03-05 16:45:48 +0000560 message_error = str(exception)
tierno5ad826a2020-08-11 11:19:44 +0000561 tip = ""
tiernode12f782019-04-05 12:46:42 +0000562
sousaedu80135b92021-02-17 15:05:18 +0100563 if isinstance(
564 exception,
565 (
566 neExceptions.NetworkNotFoundClient,
567 nvExceptions.NotFound,
gatici335a06a2023-07-26 00:34:04 +0300568 nvExceptions.ResourceNotFound,
sousaedu80135b92021-02-17 15:05:18 +0100569 ksExceptions.NotFound,
570 gl1Exceptions.HTTPNotFound,
gatici335a06a2023-07-26 00:34:04 +0300571 cExceptions.NotFound,
sousaedu80135b92021-02-17 15:05:18 +0100572 ),
573 ):
574 raise vimconn.VimConnNotFoundException(
575 type(exception).__name__ + ": " + message_error
576 )
577 elif isinstance(
578 exception,
579 (
580 HTTPException,
581 gl1Exceptions.HTTPException,
582 gl1Exceptions.CommunicationError,
583 ConnectionError,
584 ksExceptions.ConnectionError,
585 neExceptions.ConnectionFailed,
gatici335a06a2023-07-26 00:34:04 +0300586 cExceptions.ConnectionError,
sousaedu80135b92021-02-17 15:05:18 +0100587 ),
588 ):
tierno5ad826a2020-08-11 11:19:44 +0000589 if type(exception).__name__ == "SSLError":
590 tip = " (maybe option 'insecure' must be added to the VIM)"
sousaedu80135b92021-02-17 15:05:18 +0100591
592 raise vimconn.VimConnConnectionException(
593 "Invalid URL or credentials{}: {}".format(tip, message_error)
594 )
595 elif isinstance(
596 exception,
597 (
598 KeyError,
599 nvExceptions.BadRequest,
600 ksExceptions.BadRequest,
gatici335a06a2023-07-26 00:34:04 +0300601 gl1Exceptions.BadRequest,
602 cExceptions.BadRequest,
sousaedu80135b92021-02-17 15:05:18 +0100603 ),
604 ):
Patricia Reinoso17852162023-06-15 07:33:04 +0000605 if message_error == "OS-EXT-SRV-ATTR:host":
606 tip = " (If the user does not have non-admin credentials, this attribute will be missing)"
607 raise vimconn.VimConnInsufficientCredentials(
608 type(exception).__name__ + ": " + message_error + tip
609 )
sousaedu80135b92021-02-17 15:05:18 +0100610 raise vimconn.VimConnException(
611 type(exception).__name__ + ": " + message_error
612 )
Patricia Reinoso17852162023-06-15 07:33:04 +0000613
sousaedu80135b92021-02-17 15:05:18 +0100614 elif isinstance(
615 exception,
616 (
617 nvExceptions.ClientException,
618 ksExceptions.ClientException,
619 neExceptions.NeutronException,
gatici335a06a2023-07-26 00:34:04 +0300620 cExceptions.ClientException,
sousaedu80135b92021-02-17 15:05:18 +0100621 ),
622 ):
623 raise vimconn.VimConnUnexpectedResponse(
624 type(exception).__name__ + ": " + message_error
625 )
tiernoae4a8d12016-07-08 12:30:39 +0200626 elif isinstance(exception, nvExceptions.Conflict):
sousaedu80135b92021-02-17 15:05:18 +0100627 raise vimconn.VimConnConflictException(
628 type(exception).__name__ + ": " + message_error
629 )
tierno72774862020-05-04 11:44:15 +0000630 elif isinstance(exception, vimconn.VimConnException):
tierno41a69812018-02-16 14:34:33 +0100631 raise exception
tiernof716aea2017-06-21 18:01:40 +0200632 else: # ()
gatici335a06a2023-07-26 00:34:04 +0300633 logger = logging.getLogger("ro.vim.openstack")
634 logger.error("General Exception " + message_error, exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +0100635
gatici335a06a2023-07-26 00:34:04 +0300636 raise vimconn.VimConnException(
sousaedu80135b92021-02-17 15:05:18 +0100637 type(exception).__name__ + ": " + message_error
638 )
tiernoae4a8d12016-07-08 12:30:39 +0200639
tiernoa05b65a2019-02-01 12:30:27 +0000640 def _get_ids_from_name(self):
641 """
642 Obtain ids from name of tenant and security_groups. Store at self .security_groups_id"
643 :return: None
644 """
645 # get tenant_id if only tenant_name is supplied
646 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100647
tiernoa05b65a2019-02-01 12:30:27 +0000648 if not self.my_tenant_id:
sousaedu80135b92021-02-17 15:05:18 +0100649 raise vimconn.VimConnConnectionException(
650 "Error getting tenant information from name={} id={}".format(
651 self.tenant_name, self.tenant_id
652 )
653 )
654
655 if self.config.get("security_groups") and not self.security_groups_id:
tiernoa05b65a2019-02-01 12:30:27 +0000656 # convert from name to id
sousaedu80135b92021-02-17 15:05:18 +0100657 neutron_sg_list = self.neutron.list_security_groups(
658 tenant_id=self.my_tenant_id
659 )["security_groups"]
tiernoa05b65a2019-02-01 12:30:27 +0000660
661 self.security_groups_id = []
sousaedu80135b92021-02-17 15:05:18 +0100662 for sg in self.config.get("security_groups"):
tiernoa05b65a2019-02-01 12:30:27 +0000663 for neutron_sg in neutron_sg_list:
664 if sg in (neutron_sg["id"], neutron_sg["name"]):
665 self.security_groups_id.append(neutron_sg["id"])
666 break
667 else:
668 self.security_groups_id = None
sousaedu80135b92021-02-17 15:05:18 +0100669
670 raise vimconn.VimConnConnectionException(
671 "Not found security group {} for this tenant".format(sg)
672 )
tiernoa05b65a2019-02-01 12:30:27 +0000673
vegallc53829d2023-06-01 00:47:44 -0500674 def _find_nova_server(self, vm_id):
675 """
676 Returns the VM instance from Openstack and completes it with flavor ID
677 Do not call nova.servers.find directly, as it does not return flavor ID with microversion>=2.47
678 """
679 try:
680 self._reload_connection()
681 server = self.nova.servers.find(id=vm_id)
682 # TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
683 server_dict = server.to_dict()
684 try:
Luis Vegad6577d82023-07-26 20:49:12 +0000685 if server_dict["flavor"].get("original_name"):
686 server_dict["flavor"]["id"] = self.nova.flavors.find(
687 name=server_dict["flavor"]["original_name"]
688 ).id
vegallc53829d2023-06-01 00:47:44 -0500689 except nClient.exceptions.NotFound as e:
690 self.logger.warning(str(e.message))
691 return server_dict
692 except (
693 ksExceptions.ClientException,
694 nvExceptions.ClientException,
695 nvExceptions.NotFound,
696 ConnectionError,
697 ) as e:
698 self._format_exception(e)
699
tierno5509c2e2019-07-04 16:23:20 +0000700 def check_vim_connectivity(self):
701 # just get network list to check connectivity and credentials
702 self.get_network_list(filter_dict={})
703
tiernoae4a8d12016-07-08 12:30:39 +0200704 def get_tenant_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +0000705 """Obtain tenants of VIM
tiernoae4a8d12016-07-08 12:30:39 +0200706 filter_dict can contain the following keys:
707 name: filter by tenant name
708 id: filter by tenant uuid/id
709 <other VIM specific>
710 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
tierno1ec592d2020-06-16 15:29:47 +0000711 """
ahmadsa95baa272016-11-30 09:14:11 +0500712 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200713 try:
714 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100715
tiernof716aea2017-06-21 18:01:40 +0200716 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100717 project_class_list = self.keystone.projects.list(
718 name=filter_dict.get("name")
719 )
ahmadsa95baa272016-11-30 09:14:11 +0500720 else:
tiernof716aea2017-06-21 18:01:40 +0200721 project_class_list = self.keystone.tenants.findall(**filter_dict)
sousaedu80135b92021-02-17 15:05:18 +0100722
tierno1ec592d2020-06-16 15:29:47 +0000723 project_list = []
sousaedu80135b92021-02-17 15:05:18 +0100724
ahmadsa95baa272016-11-30 09:14:11 +0500725 for project in project_class_list:
sousaedu80135b92021-02-17 15:05:18 +0100726 if filter_dict.get("id") and filter_dict["id"] != project.id:
tiernof716aea2017-06-21 18:01:40 +0200727 continue
sousaedu80135b92021-02-17 15:05:18 +0100728
ahmadsa95baa272016-11-30 09:14:11 +0500729 project_list.append(project.to_dict())
sousaedu80135b92021-02-17 15:05:18 +0100730
ahmadsa95baa272016-11-30 09:14:11 +0500731 return project_list
sousaedu80135b92021-02-17 15:05:18 +0100732 except (
733 ksExceptions.ConnectionError,
734 ksExceptions.ClientException,
735 ConnectionError,
736 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200737 self._format_exception(e)
738
739 def new_tenant(self, tenant_name, tenant_description):
tierno1ec592d2020-06-16 15:29:47 +0000740 """Adds a new tenant to openstack VIM. Returns the tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200741 self.logger.debug("Adding a new tenant name: %s", tenant_name)
742 try:
743 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100744
tiernof716aea2017-06-21 18:01:40 +0200745 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100746 project = self.keystone.projects.create(
747 tenant_name,
748 self.config.get("project_domain_id", "default"),
749 description=tenant_description,
750 is_domain=False,
751 )
ahmadsa95baa272016-11-30 09:14:11 +0500752 else:
tiernof716aea2017-06-21 18:01:40 +0200753 project = self.keystone.tenants.create(tenant_name, tenant_description)
sousaedu80135b92021-02-17 15:05:18 +0100754
ahmadsa95baa272016-11-30 09:14:11 +0500755 return project.id
sousaedu80135b92021-02-17 15:05:18 +0100756 except (
757 ksExceptions.ConnectionError,
758 ksExceptions.ClientException,
759 ksExceptions.BadRequest,
760 ConnectionError,
761 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200762 self._format_exception(e)
763
764 def delete_tenant(self, tenant_id):
tierno1ec592d2020-06-16 15:29:47 +0000765 """Delete a tenant from openstack VIM. Returns the old tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200766 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
767 try:
768 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100769
tiernof716aea2017-06-21 18:01:40 +0200770 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500771 self.keystone.projects.delete(tenant_id)
772 else:
773 self.keystone.tenants.delete(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100774
tiernoae4a8d12016-07-08 12:30:39 +0200775 return tenant_id
gatici335a06a2023-07-26 00:34:04 +0300776
sousaedu80135b92021-02-17 15:05:18 +0100777 except (
778 ksExceptions.ConnectionError,
779 ksExceptions.ClientException,
780 ksExceptions.NotFound,
781 ConnectionError,
782 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200783 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500784
sousaedu80135b92021-02-17 15:05:18 +0100785 def new_network(
786 self,
787 net_name,
788 net_type,
789 ip_profile=None,
790 shared=False,
791 provider_network_profile=None,
792 ):
garciadeblasebd66722019-01-31 16:01:31 +0000793 """Adds a tenant network to VIM
794 Params:
795 'net_name': name of the network
796 'net_type': one of:
797 'bridge': overlay isolated network
798 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
799 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
800 'ip_profile': is a dict containing the IP parameters of the network
801 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
802 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
803 'gateway_address': (Optional) ip_schema, that is X.X.X.X
804 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
805 'dhcp_enabled': True or False
806 'dhcp_start_address': ip_schema, first IP to grant
807 'dhcp_count': number of IPs to grant.
808 'shared': if this network can be seen/use by other tenants/organization
garciadeblas4af0d542020-02-18 16:01:13 +0100809 'provider_network_profile': (optional) contains {segmentation-id: vlan, network-type: vlan|vxlan,
810 physical-network: physnet-label}
garciadeblasebd66722019-01-31 16:01:31 +0000811 Returns a tuple with the network identifier and created_items, or raises an exception on error
812 created_items can be None or a dictionary where this method can include key-values that will be passed to
813 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
814 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
815 as not present.
816 """
sousaedu80135b92021-02-17 15:05:18 +0100817 self.logger.debug(
818 "Adding a new network to VIM name '%s', type '%s'", net_name, net_type
819 )
garciadeblasebd66722019-01-31 16:01:31 +0000820 # self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
kbsuba85c54d2019-10-17 16:30:32 +0000821
tierno7edb6752016-03-21 17:37:52 +0100822 try:
kbsuba85c54d2019-10-17 16:30:32 +0000823 vlan = None
sousaedu80135b92021-02-17 15:05:18 +0100824
kbsuba85c54d2019-10-17 16:30:32 +0000825 if provider_network_profile:
826 vlan = provider_network_profile.get("segmentation-id")
sousaedu80135b92021-02-17 15:05:18 +0100827
garciadeblasedca7b32016-09-29 14:01:52 +0000828 new_net = None
garciadeblasebd66722019-01-31 16:01:31 +0000829 created_items = {}
tierno7edb6752016-03-21 17:37:52 +0100830 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100831 network_dict = {"name": net_name, "admin_state_up": True}
832
Gabriel Cuba0d8ce072022-12-14 18:33:50 -0500833 if net_type in ("data", "ptp") or provider_network_profile:
tierno6869ae72020-01-09 17:37:34 +0000834 provider_physical_network = None
sousaedu80135b92021-02-17 15:05:18 +0100835
836 if provider_network_profile and provider_network_profile.get(
837 "physical-network"
838 ):
839 provider_physical_network = provider_network_profile.get(
840 "physical-network"
841 )
842
tierno6869ae72020-01-09 17:37:34 +0000843 # provider-network must be one of the dataplane_physcial_netowrk if this is a list. If it is string
844 # or not declared, just ignore the checking
sousaedu80135b92021-02-17 15:05:18 +0100845 if (
846 isinstance(
847 self.config.get("dataplane_physical_net"), (tuple, list)
848 )
849 and provider_physical_network
850 not in self.config["dataplane_physical_net"]
851 ):
tierno72774862020-05-04 11:44:15 +0000852 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100853 "Invalid parameter 'provider-network:physical-network' "
854 "for network creation. '{}' is not one of the declared "
855 "list at VIM_config:dataplane_physical_net".format(
856 provider_physical_network
857 )
858 )
859
860 # use the default dataplane_physical_net
861 if not provider_physical_network:
862 provider_physical_network = self.config.get(
863 "dataplane_physical_net"
864 )
865
gatici335a06a2023-07-26 00:34:04 +0300866 # if it is non-empty list, use the first value. If it is a string use the value directly
sousaedu80135b92021-02-17 15:05:18 +0100867 if (
868 isinstance(provider_physical_network, (tuple, list))
869 and provider_physical_network
870 ):
tierno6869ae72020-01-09 17:37:34 +0000871 provider_physical_network = provider_physical_network[0]
872
873 if not provider_physical_network:
tierno5ad826a2020-08-11 11:19:44 +0000874 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100875 "missing information needed for underlay networks. Provide "
876 "'dataplane_physical_net' configuration at VIM or use the NS "
877 "instantiation parameter 'provider-network.physical-network'"
878 " for the VLD"
879 )
tierno6869ae72020-01-09 17:37:34 +0000880
sousaedu80135b92021-02-17 15:05:18 +0100881 if not self.config.get("multisegment_support"):
882 network_dict[
883 "provider:physical_network"
884 ] = provider_physical_network
885
886 if (
887 provider_network_profile
888 and "network-type" in provider_network_profile
889 ):
890 network_dict[
891 "provider:network_type"
892 ] = provider_network_profile["network-type"]
garciadeblas4af0d542020-02-18 16:01:13 +0100893 else:
sousaedu80135b92021-02-17 15:05:18 +0100894 network_dict["provider:network_type"] = self.config.get(
895 "dataplane_network_type", "vlan"
896 )
897
tierno6869ae72020-01-09 17:37:34 +0000898 if vlan:
899 network_dict["provider:segmentation_id"] = vlan
garciadeblasebd66722019-01-31 16:01:31 +0000900 else:
tierno6869ae72020-01-09 17:37:34 +0000901 # Multi-segment case
garciadeblasebd66722019-01-31 16:01:31 +0000902 segment_list = []
tierno6869ae72020-01-09 17:37:34 +0000903 segment1_dict = {
sousaedu80135b92021-02-17 15:05:18 +0100904 "provider:physical_network": "",
905 "provider:network_type": "vxlan",
tierno6869ae72020-01-09 17:37:34 +0000906 }
garciadeblasebd66722019-01-31 16:01:31 +0000907 segment_list.append(segment1_dict)
tierno6869ae72020-01-09 17:37:34 +0000908 segment2_dict = {
909 "provider:physical_network": provider_physical_network,
sousaedu80135b92021-02-17 15:05:18 +0100910 "provider:network_type": "vlan",
tierno6869ae72020-01-09 17:37:34 +0000911 }
sousaedu80135b92021-02-17 15:05:18 +0100912
tierno6869ae72020-01-09 17:37:34 +0000913 if vlan:
914 segment2_dict["provider:segmentation_id"] = vlan
sousaedu80135b92021-02-17 15:05:18 +0100915 elif self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +0000916 vlanID = self._generate_multisegment_vlanID()
917 segment2_dict["provider:segmentation_id"] = vlanID
sousaedu80135b92021-02-17 15:05:18 +0100918
garciadeblasebd66722019-01-31 16:01:31 +0000919 # else
tierno72774862020-05-04 11:44:15 +0000920 # raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100921 # "You must provide "multisegment_vlan_range" at config dict before creating a multisegment
tierno1ec592d2020-06-16 15:29:47 +0000922 # network")
garciadeblasebd66722019-01-31 16:01:31 +0000923 segment_list.append(segment2_dict)
924 network_dict["segments"] = segment_list
kate721d79b2017-06-24 04:21:38 -0700925
tierno6869ae72020-01-09 17:37:34 +0000926 # VIO Specific Changes. It needs a concrete VLAN
927 if self.vim_type == "VIO" and vlan is None:
sousaedu80135b92021-02-17 15:05:18 +0100928 if self.config.get("dataplane_net_vlan_range") is None:
tierno72774862020-05-04 11:44:15 +0000929 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100930 "You must provide 'dataplane_net_vlan_range' in format "
931 "[start_ID - end_ID] at VIM_config for creating underlay "
932 "networks"
933 )
934
tierno6869ae72020-01-09 17:37:34 +0000935 network_dict["provider:segmentation_id"] = self._generate_vlanID()
kate721d79b2017-06-24 04:21:38 -0700936
garciadeblasebd66722019-01-31 16:01:31 +0000937 network_dict["shared"] = shared
sousaedu80135b92021-02-17 15:05:18 +0100938
anwarsff168192019-05-06 11:23:07 +0530939 if self.config.get("disable_network_port_security"):
940 network_dict["port_security_enabled"] = False
sousaedu80135b92021-02-17 15:05:18 +0100941
sousaedu2aa5f802021-06-17 15:39:29 +0100942 if self.config.get("neutron_availability_zone_hints"):
943 hints = self.config.get("neutron_availability_zone_hints")
944
945 if isinstance(hints, str):
946 hints = [hints]
947
948 network_dict["availability_zone_hints"] = hints
949
sousaedu80135b92021-02-17 15:05:18 +0100950 new_net = self.neutron.create_network({"network": network_dict})
garciadeblasebd66722019-01-31 16:01:31 +0000951 # print new_net
952 # create subnetwork, even if there is no profile
sousaedu80135b92021-02-17 15:05:18 +0100953
garciadeblas9f8456e2016-09-05 05:02:59 +0200954 if not ip_profile:
955 ip_profile = {}
sousaedu80135b92021-02-17 15:05:18 +0100956
957 if not ip_profile.get("subnet_address"):
tierno1ec592d2020-06-16 15:29:47 +0000958 # Fake subnet is required
elumalai51e72a02023-04-28 19:41:49 +0530959 subnet_rand = random.SystemRandom().randint(0, 255)
sousaedu80135b92021-02-17 15:05:18 +0100960 ip_profile["subnet_address"] = "192.168.{}.0/24".format(subnet_rand)
961
962 if "ip_version" not in ip_profile:
963 ip_profile["ip_version"] = "IPv4"
964
965 subnet = {
966 "name": net_name + "-subnet",
967 "network_id": new_net["network"]["id"],
968 "ip_version": 4 if ip_profile["ip_version"] == "IPv4" else 6,
969 "cidr": ip_profile["subnet_address"],
970 }
971
tiernoa1fb4462017-06-30 12:25:50 +0200972 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
sousaedu80135b92021-02-17 15:05:18 +0100973 if ip_profile.get("gateway_address"):
974 subnet["gateway_ip"] = ip_profile["gateway_address"]
tierno55d234c2018-07-04 18:29:21 +0200975 else:
sousaedu80135b92021-02-17 15:05:18 +0100976 subnet["gateway_ip"] = None
977
978 if ip_profile.get("dns_address"):
979 subnet["dns_nameservers"] = ip_profile["dns_address"].split(";")
980
981 if "dhcp_enabled" in ip_profile:
982 subnet["enable_dhcp"] = (
983 False
984 if ip_profile["dhcp_enabled"] == "false"
985 or ip_profile["dhcp_enabled"] is False
986 else True
987 )
988
989 if ip_profile.get("dhcp_start_address"):
990 subnet["allocation_pools"] = []
991 subnet["allocation_pools"].append(dict())
992 subnet["allocation_pools"][0]["start"] = ip_profile[
993 "dhcp_start_address"
994 ]
995
996 if ip_profile.get("dhcp_count"):
997 # parts = ip_profile["dhcp_start_address"].split(".")
tierno1ec592d2020-06-16 15:29:47 +0000998 # ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
sousaedu80135b92021-02-17 15:05:18 +0100999 ip_int = int(netaddr.IPAddress(ip_profile["dhcp_start_address"]))
1000 ip_int += ip_profile["dhcp_count"] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +02001001 ip_str = str(netaddr.IPAddress(ip_int))
sousaedu80135b92021-02-17 15:05:18 +01001002 subnet["allocation_pools"][0]["end"] = ip_str
1003
Gabriel Cubab3dbfca2023-03-14 10:58:39 -05001004 if (
1005 ip_profile.get("ipv6_address_mode")
1006 and ip_profile["ip_version"] != "IPv4"
1007 ):
1008 subnet["ipv6_address_mode"] = ip_profile["ipv6_address_mode"]
1009 # ipv6_ra_mode can be set to the same value for most use cases, see documentation:
1010 # https://docs.openstack.org/neutron/latest/admin/config-ipv6.html#ipv6-ra-mode-and-ipv6-address-mode-combinations
1011 subnet["ipv6_ra_mode"] = ip_profile["ipv6_address_mode"]
1012
tierno1ec592d2020-06-16 15:29:47 +00001013 # self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
1014 self.neutron.create_subnet({"subnet": subnet})
garciadeblasebd66722019-01-31 16:01:31 +00001015
sousaedu80135b92021-02-17 15:05:18 +01001016 if net_type == "data" and self.config.get("multisegment_support"):
1017 if self.config.get("l2gw_support"):
garciadeblasebd66722019-01-31 16:01:31 +00001018 l2gw_list = self.neutron.list_l2_gateways().get("l2_gateways", ())
1019 for l2gw in l2gw_list:
tierno1ec592d2020-06-16 15:29:47 +00001020 l2gw_conn = {
1021 "l2_gateway_id": l2gw["id"],
1022 "network_id": new_net["network"]["id"],
1023 "segmentation_id": str(vlanID),
1024 }
sousaedu80135b92021-02-17 15:05:18 +01001025 new_l2gw_conn = self.neutron.create_l2_gateway_connection(
1026 {"l2_gateway_connection": l2gw_conn}
1027 )
1028 created_items[
1029 "l2gwconn:"
1030 + str(new_l2gw_conn["l2_gateway_connection"]["id"])
1031 ] = True
1032
garciadeblasebd66722019-01-31 16:01:31 +00001033 return new_net["network"]["id"], created_items
tierno41a69812018-02-16 14:34:33 +01001034 except Exception as e:
tierno1ec592d2020-06-16 15:29:47 +00001035 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001036 for k, v in created_items.items():
1037 if not v: # skip already deleted
1038 continue
sousaedu80135b92021-02-17 15:05:18 +01001039
garciadeblasebd66722019-01-31 16:01:31 +00001040 try:
1041 k_item, _, k_id = k.partition(":")
sousaedu80135b92021-02-17 15:05:18 +01001042
garciadeblasebd66722019-01-31 16:01:31 +00001043 if k_item == "l2gwconn":
1044 self.neutron.delete_l2_gateway_connection(k_id)
gatici335a06a2023-07-26 00:34:04 +03001045
1046 except (neExceptions.ConnectionFailed, ConnectionError) as e2:
1047 self.logger.error(
1048 "Error deleting l2 gateway connection: {}: {}".format(
1049 type(e2).__name__, e2
1050 )
1051 )
1052 self._format_exception(e2)
garciadeblasebd66722019-01-31 16:01:31 +00001053 except Exception as e2:
sousaedu80135b92021-02-17 15:05:18 +01001054 self.logger.error(
1055 "Error deleting l2 gateway connection: {}: {}".format(
1056 type(e2).__name__, e2
1057 )
1058 )
1059
garciadeblasedca7b32016-09-29 14:01:52 +00001060 if new_net:
sousaedu80135b92021-02-17 15:05:18 +01001061 self.neutron.delete_network(new_net["network"]["id"])
1062
tiernoae4a8d12016-07-08 12:30:39 +02001063 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001064
1065 def get_network_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001066 """Obtain tenant networks of VIM
tierno7edb6752016-03-21 17:37:52 +01001067 Filter_dict can be:
1068 name: network name
1069 id: network uuid
1070 shared: boolean
1071 tenant_id: tenant
1072 admin_state_up: boolean
1073 status: 'ACTIVE'
1074 Returns the network list of dictionaries
tierno1ec592d2020-06-16 15:29:47 +00001075 """
tiernoae4a8d12016-07-08 12:30:39 +02001076 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +01001077 try:
1078 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001079 filter_dict_os = filter_dict.copy()
sousaedu80135b92021-02-17 15:05:18 +01001080
tierno69b590e2018-03-13 18:52:23 +01001081 if self.api_version3 and "tenant_id" in filter_dict_os:
sousaedu80135b92021-02-17 15:05:18 +01001082 # TODO check
1083 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
1084
tierno69b590e2018-03-13 18:52:23 +01001085 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +01001086 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +01001087 self.__net_os2mano(net_list)
sousaedu80135b92021-02-17 15:05:18 +01001088
tiernoae4a8d12016-07-08 12:30:39 +02001089 return net_list
sousaedu80135b92021-02-17 15:05:18 +01001090 except (
1091 neExceptions.ConnectionFailed,
1092 ksExceptions.ClientException,
1093 neExceptions.NeutronException,
1094 ConnectionError,
1095 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001096 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001097
tiernoae4a8d12016-07-08 12:30:39 +02001098 def get_network(self, net_id):
tierno1ec592d2020-06-16 15:29:47 +00001099 """Obtain details of network from VIM
1100 Returns the network information from a network id"""
tiernoae4a8d12016-07-08 12:30:39 +02001101 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno1ec592d2020-06-16 15:29:47 +00001102 filter_dict = {"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +02001103 net_list = self.get_network_list(filter_dict)
sousaedu80135b92021-02-17 15:05:18 +01001104
tierno1ec592d2020-06-16 15:29:47 +00001105 if len(net_list) == 0:
sousaedu80135b92021-02-17 15:05:18 +01001106 raise vimconn.VimConnNotFoundException(
1107 "Network '{}' not found".format(net_id)
1108 )
tierno1ec592d2020-06-16 15:29:47 +00001109 elif len(net_list) > 1:
sousaedu80135b92021-02-17 15:05:18 +01001110 raise vimconn.VimConnConflictException(
1111 "Found more than one network with this criteria"
1112 )
1113
tierno7edb6752016-03-21 17:37:52 +01001114 net = net_list[0]
tierno1ec592d2020-06-16 15:29:47 +00001115 subnets = []
1116 for subnet_id in net.get("subnets", ()):
tierno7edb6752016-03-21 17:37:52 +01001117 try:
1118 subnet = self.neutron.show_subnet(subnet_id)
1119 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001120 self.logger.error(
1121 "osconnector.get_network(): Error getting subnet %s %s"
1122 % (net_id, str(e))
1123 )
tiernoae4a8d12016-07-08 12:30:39 +02001124 subnet = {"id": subnet_id, "fault": str(e)}
sousaedu80135b92021-02-17 15:05:18 +01001125
tierno7edb6752016-03-21 17:37:52 +01001126 subnets.append(subnet)
sousaedu80135b92021-02-17 15:05:18 +01001127
tierno7edb6752016-03-21 17:37:52 +01001128 net["subnets"] = subnets
sousaedu80135b92021-02-17 15:05:18 +01001129 net["encapsulation"] = net.get("provider:network_type")
1130 net["encapsulation_type"] = net.get("provider:network_type")
1131 net["segmentation_id"] = net.get("provider:segmentation_id")
1132 net["encapsulation_id"] = net.get("provider:segmentation_id")
1133
tiernoae4a8d12016-07-08 12:30:39 +02001134 return net
tierno7edb6752016-03-21 17:37:52 +01001135
gatici335a06a2023-07-26 00:34:04 +03001136 @catch_any_exception
garciadeblasebd66722019-01-31 16:01:31 +00001137 def delete_network(self, net_id, created_items=None):
1138 """
1139 Removes a tenant network from VIM and its associated elements
1140 :param net_id: VIM identifier of the network, provided by method new_network
1141 :param created_items: dictionary with extra items to be deleted. provided by method new_network
1142 Returns the network identifier or raises an exception upon error or when network is not found
1143 """
tiernoae4a8d12016-07-08 12:30:39 +02001144 self.logger.debug("Deleting network '%s' from VIM", net_id)
sousaedu80135b92021-02-17 15:05:18 +01001145
tierno1ec592d2020-06-16 15:29:47 +00001146 if created_items is None:
garciadeblasebd66722019-01-31 16:01:31 +00001147 created_items = {}
sousaedu80135b92021-02-17 15:05:18 +01001148
tierno7edb6752016-03-21 17:37:52 +01001149 try:
1150 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001151 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001152 for k, v in created_items.items():
1153 if not v: # skip already deleted
1154 continue
sousaedu80135b92021-02-17 15:05:18 +01001155
garciadeblasebd66722019-01-31 16:01:31 +00001156 try:
1157 k_item, _, k_id = k.partition(":")
1158 if k_item == "l2gwconn":
1159 self.neutron.delete_l2_gateway_connection(k_id)
gatici335a06a2023-07-26 00:34:04 +03001160
1161 except (neExceptions.ConnectionFailed, ConnectionError) as e:
1162 self.logger.error(
1163 "Error deleting l2 gateway connection: {}: {}".format(
1164 type(e).__name__, e
1165 )
1166 )
1167 self._format_exception(e)
garciadeblasebd66722019-01-31 16:01:31 +00001168 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001169 self.logger.error(
1170 "Error deleting l2 gateway connection: {}: {}".format(
1171 type(e).__name__, e
1172 )
1173 )
1174
tierno1ec592d2020-06-16 15:29:47 +00001175 # delete VM ports attached to this networks before the network
tierno7edb6752016-03-21 17:37:52 +01001176 ports = self.neutron.list_ports(network_id=net_id)
sousaedu80135b92021-02-17 15:05:18 +01001177 for p in ports["ports"]:
tierno7edb6752016-03-21 17:37:52 +01001178 try:
1179 self.neutron.delete_port(p["id"])
gatici335a06a2023-07-26 00:34:04 +03001180
1181 except (neExceptions.ConnectionFailed, ConnectionError) as e:
1182 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
1183 # If there is connection error, it raises.
1184 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001185 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001186 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
sousaedu80135b92021-02-17 15:05:18 +01001187
tierno7edb6752016-03-21 17:37:52 +01001188 self.neutron.delete_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001189
tiernoae4a8d12016-07-08 12:30:39 +02001190 return net_id
gatici335a06a2023-07-26 00:34:04 +03001191 except (neExceptions.NetworkNotFoundClient, neExceptions.NotFound) as e:
1192 # If network to be deleted is not found, it does not raise.
1193 self.logger.warning(
1194 f"Error deleting network: {net_id} is not found, {str(e)}"
1195 )
tierno7edb6752016-03-21 17:37:52 +01001196
tiernoae4a8d12016-07-08 12:30:39 +02001197 def refresh_nets_status(self, net_list):
tierno1ec592d2020-06-16 15:29:47 +00001198 """Get the status of the networks
sousaedu80135b92021-02-17 15:05:18 +01001199 Params: the list of network identifiers
1200 Returns a dictionary with:
1201 net_id: #VIM id of this network
1202 status: #Mandatory. Text with one of:
1203 # DELETED (not found at vim)
1204 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1205 # OTHER (Vim reported other status not understood)
1206 # ERROR (VIM indicates an ERROR status)
1207 # ACTIVE, INACTIVE, DOWN (admin down),
1208 # BUILD (on building process)
1209 #
1210 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1211 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
tierno1ec592d2020-06-16 15:29:47 +00001212 """
1213 net_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001214
tiernoae4a8d12016-07-08 12:30:39 +02001215 for net_id in net_list:
1216 net = {}
sousaedu80135b92021-02-17 15:05:18 +01001217
tiernoae4a8d12016-07-08 12:30:39 +02001218 try:
1219 net_vim = self.get_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001220
1221 if net_vim["status"] in netStatus2manoFormat:
1222 net["status"] = netStatus2manoFormat[net_vim["status"]]
tiernoae4a8d12016-07-08 12:30:39 +02001223 else:
1224 net["status"] = "OTHER"
sousaedu80135b92021-02-17 15:05:18 +01001225 net["error_msg"] = "VIM status reported " + net_vim["status"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001226
sousaedu80135b92021-02-17 15:05:18 +01001227 if net["status"] == "ACTIVE" and not net_vim["admin_state_up"]:
1228 net["status"] = "DOWN"
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001229
sousaedu80135b92021-02-17 15:05:18 +01001230 net["vim_info"] = self.serialize(net_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001231
sousaedu80135b92021-02-17 15:05:18 +01001232 if net_vim.get("fault"): # TODO
1233 net["error_msg"] = str(net_vim["fault"])
tierno72774862020-05-04 11:44:15 +00001234 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001235 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001236 net["status"] = "DELETED"
1237 net["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00001238 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001239 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001240 net["status"] = "VIM_ERROR"
1241 net["error_msg"] = str(e)
tiernoae4a8d12016-07-08 12:30:39 +02001242 net_dict[net_id] = net
1243 return net_dict
1244
1245 def get_flavor(self, flavor_id):
tierno1ec592d2020-06-16 15:29:47 +00001246 """Obtain flavor details from the VIM. Returns the flavor dict details"""
tiernoae4a8d12016-07-08 12:30:39 +02001247 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +01001248 try:
1249 self._reload_connection()
1250 flavor = self.nova.flavors.find(id=flavor_id)
tiernoae4a8d12016-07-08 12:30:39 +02001251 return flavor.to_dict()
gatici335a06a2023-07-26 00:34:04 +03001252
sousaedu80135b92021-02-17 15:05:18 +01001253 except (
1254 nvExceptions.NotFound,
1255 nvExceptions.ClientException,
1256 ksExceptions.ClientException,
1257 ConnectionError,
1258 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001259 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001260
tiernocf157a82017-01-30 14:07:06 +01001261 def get_flavor_id_from_data(self, flavor_dict):
1262 """Obtain flavor id that match the flavor description
sousaedu80135b92021-02-17 15:05:18 +01001263 Returns the flavor_id or raises a vimconnNotFoundException
1264 flavor_dict: contains the required ram, vcpus, disk
1265 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
1266 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
1267 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +01001268 """
sousaedu80135b92021-02-17 15:05:18 +01001269 exact_match = False if self.config.get("use_existing_flavors") else True
1270
tiernocf157a82017-01-30 14:07:06 +01001271 try:
1272 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +02001273 flavor_candidate_id = None
1274 flavor_candidate_data = (10000, 10000, 10000)
sousaedu80135b92021-02-17 15:05:18 +01001275 flavor_target = (
1276 flavor_dict["ram"],
1277 flavor_dict["vcpus"],
1278 flavor_dict["disk"],
sousaedu648ee3d2021-11-22 14:09:15 +00001279 flavor_dict.get("ephemeral", 0),
1280 flavor_dict.get("swap", 0),
sousaedu80135b92021-02-17 15:05:18 +01001281 )
tiernoe26fc7a2017-05-30 14:43:03 +02001282 # numa=None
anwarsae5f52c2019-04-22 10:35:27 +05301283 extended = flavor_dict.get("extended", {})
1284 if extended:
tierno1ec592d2020-06-16 15:29:47 +00001285 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001286 raise vimconn.VimConnNotFoundException(
1287 "Flavor with EPA still not implemented"
1288 )
tiernocf157a82017-01-30 14:07:06 +01001289 # if len(numas) > 1:
tierno72774862020-05-04 11:44:15 +00001290 # raise vimconn.VimConnNotFoundException("Cannot find any flavor with more than one numa")
tiernocf157a82017-01-30 14:07:06 +01001291 # numa=numas[0]
1292 # numas = extended.get("numas")
1293 for flavor in self.nova.flavors.list():
1294 epa = flavor.get_keys()
sousaedu80135b92021-02-17 15:05:18 +01001295
tiernocf157a82017-01-30 14:07:06 +01001296 if epa:
1297 continue
tiernoe26fc7a2017-05-30 14:43:03 +02001298 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001299
sousaedu648ee3d2021-11-22 14:09:15 +00001300 flavor_data = (
1301 flavor.ram,
1302 flavor.vcpus,
1303 flavor.disk,
1304 flavor.ephemeral,
preethika.pebaba1f2022-01-20 07:24:18 +00001305 flavor.swap if isinstance(flavor.swap, int) else 0,
sousaedu648ee3d2021-11-22 14:09:15 +00001306 )
tiernoe26fc7a2017-05-30 14:43:03 +02001307 if flavor_data == flavor_target:
1308 return flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001309 elif (
1310 not exact_match
1311 and flavor_target < flavor_data < flavor_candidate_data
1312 ):
tiernoe26fc7a2017-05-30 14:43:03 +02001313 flavor_candidate_id = flavor.id
1314 flavor_candidate_data = flavor_data
sousaedu80135b92021-02-17 15:05:18 +01001315
tiernoe26fc7a2017-05-30 14:43:03 +02001316 if not exact_match and flavor_candidate_id:
1317 return flavor_candidate_id
sousaedu80135b92021-02-17 15:05:18 +01001318
1319 raise vimconn.VimConnNotFoundException(
1320 "Cannot find any flavor matching '{}'".format(flavor_dict)
1321 )
1322 except (
1323 nvExceptions.NotFound,
gatici335a06a2023-07-26 00:34:04 +03001324 nvExceptions.BadRequest,
sousaedu80135b92021-02-17 15:05:18 +01001325 nvExceptions.ClientException,
1326 ksExceptions.ClientException,
1327 ConnectionError,
1328 ) as e:
tiernocf157a82017-01-30 14:07:06 +01001329 self._format_exception(e)
1330
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001331 @staticmethod
1332 def process_resource_quota(quota: dict, prefix: str, extra_specs: dict) -> None:
1333 """Process resource quota and fill up extra_specs.
1334 Args:
1335 quota (dict): Keeping the quota of resurces
1336 prefix (str) Prefix
1337 extra_specs (dict) Dict to be filled to be used during flavor creation
1338
anwarsae5f52c2019-04-22 10:35:27 +05301339 """
sousaedu80135b92021-02-17 15:05:18 +01001340 if "limit" in quota:
1341 extra_specs["quota:" + prefix + "_limit"] = quota["limit"]
1342
1343 if "reserve" in quota:
1344 extra_specs["quota:" + prefix + "_reservation"] = quota["reserve"]
1345
1346 if "shares" in quota:
anwarsae5f52c2019-04-22 10:35:27 +05301347 extra_specs["quota:" + prefix + "_shares_level"] = "custom"
sousaedu80135b92021-02-17 15:05:18 +01001348 extra_specs["quota:" + prefix + "_shares_share"] = quota["shares"]
anwarsae5f52c2019-04-22 10:35:27 +05301349
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001350 @staticmethod
1351 def process_numa_memory(
1352 numa: dict, node_id: Optional[int], extra_specs: dict
1353 ) -> None:
1354 """Set the memory in extra_specs.
1355 Args:
1356 numa (dict): A dictionary which includes numa information
1357 node_id (int): ID of numa node
1358 extra_specs (dict): To be filled.
1359
1360 """
1361 if not numa.get("memory"):
1362 return
1363 memory_mb = numa["memory"] * 1024
1364 memory = "hw:numa_mem.{}".format(node_id)
1365 extra_specs[memory] = int(memory_mb)
1366
1367 @staticmethod
1368 def process_numa_vcpu(numa: dict, node_id: int, extra_specs: dict) -> None:
1369 """Set the cpu in extra_specs.
1370 Args:
1371 numa (dict): A dictionary which includes numa information
1372 node_id (int): ID of numa node
1373 extra_specs (dict): To be filled.
1374
1375 """
1376 if not numa.get("vcpu"):
1377 return
1378 vcpu = numa["vcpu"]
1379 cpu = "hw:numa_cpus.{}".format(node_id)
1380 vcpu = ",".join(map(str, vcpu))
1381 extra_specs[cpu] = vcpu
1382
1383 @staticmethod
1384 def process_numa_paired_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1385 """Fill up extra_specs if numa has paired-threads.
1386 Args:
1387 numa (dict): A dictionary which includes numa information
1388 extra_specs (dict): To be filled.
1389
1390 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001391 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001392
1393 """
1394 if not numa.get("paired-threads"):
1395 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001396
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001397 # cpu_thread_policy "require" implies that compute node must have an STM architecture
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001398 threads = numa["paired-threads"] * 2
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001399 extra_specs["hw:cpu_thread_policy"] = "require"
1400 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001401 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001402
1403 @staticmethod
1404 def process_numa_cores(numa: dict, extra_specs: dict) -> Optional[int]:
1405 """Fill up extra_specs if numa has cores.
1406 Args:
1407 numa (dict): A dictionary which includes numa information
1408 extra_specs (dict): To be filled.
1409
1410 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001411 cores (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001412
1413 """
1414 # cpu_thread_policy "isolate" implies that the host must not have an SMT
1415 # architecture, or a non-SMT architecture will be emulated
1416 if not numa.get("cores"):
1417 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001418 cores = numa["cores"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001419 extra_specs["hw:cpu_thread_policy"] = "isolate"
1420 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001421 return cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001422
1423 @staticmethod
1424 def process_numa_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1425 """Fill up extra_specs if numa has threads.
1426 Args:
1427 numa (dict): A dictionary which includes numa information
1428 extra_specs (dict): To be filled.
1429
1430 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001431 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001432
1433 """
1434 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
1435 if not numa.get("threads"):
1436 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001437 threads = numa["threads"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001438 extra_specs["hw:cpu_thread_policy"] = "prefer"
1439 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001440 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001441
1442 def _process_numa_parameters_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001443 self, numas: List, extra_specs: Dict
1444 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001445 """Process numa parameters and fill up extra_specs.
1446
1447 Args:
1448 numas (list): List of dictionary which includes numa information
1449 extra_specs (dict): To be filled.
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001450
1451 """
1452 numa_nodes = len(numas)
1453 extra_specs["hw:numa_nodes"] = str(numa_nodes)
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001454 cpu_cores, cpu_threads = 0, 0
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001455
1456 if self.vim_type == "VIO":
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001457 self.process_vio_numa_nodes(numa_nodes, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001458
1459 for numa in numas:
1460 if "id" in numa:
1461 node_id = numa["id"]
1462 # overwrite ram and vcpus
1463 # check if key "memory" is present in numa else use ram value at flavor
1464 self.process_numa_memory(numa, node_id, extra_specs)
1465 self.process_numa_vcpu(numa, node_id, extra_specs)
1466
1467 # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
1468 extra_specs["hw:cpu_sockets"] = str(numa_nodes)
1469
1470 if "paired-threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001471 threads = self.process_numa_paired_threads(numa, extra_specs)
1472 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001473
1474 elif "cores" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001475 cores = self.process_numa_cores(numa, extra_specs)
1476 cpu_cores += cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001477
1478 elif "threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001479 threads = self.process_numa_threads(numa, extra_specs)
1480 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001481
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001482 if cpu_cores:
1483 extra_specs["hw:cpu_cores"] = str(cpu_cores)
1484 if cpu_threads:
1485 extra_specs["hw:cpu_threads"] = str(cpu_threads)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001486
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001487 @staticmethod
1488 def process_vio_numa_nodes(numa_nodes: int, extra_specs: Dict) -> None:
1489 """According to number of numa nodes, updates the extra_specs for VIO.
1490
1491 Args:
1492
1493 numa_nodes (int): List keeps the numa node numbers
1494 extra_specs (dict): Extra specs dict to be updated
1495
1496 """
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001497 # If there are several numas, we do not define specific affinity.
1498 extra_specs["vmware:latency_sensitivity_level"] = "high"
1499
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001500 def _change_flavor_name(
1501 self, name: str, name_suffix: int, flavor_data: dict
1502 ) -> str:
1503 """Change the flavor name if the name already exists.
1504
1505 Args:
1506 name (str): Flavor name to be checked
1507 name_suffix (int): Suffix to be appended to name
1508 flavor_data (dict): Flavor dict
1509
1510 Returns:
1511 name (str): New flavor name to be used
1512
1513 """
1514 # Get used names
1515 fl = self.nova.flavors.list()
1516 fl_names = [f.name for f in fl]
1517
1518 while name in fl_names:
1519 name_suffix += 1
1520 name = flavor_data["name"] + "-" + str(name_suffix)
1521
1522 return name
1523
1524 def _process_extended_config_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001525 self, extended: dict, extra_specs: dict
1526 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001527 """Process the extended dict to fill up extra_specs.
1528 Args:
1529
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001530 extended (dict): Keeping the extra specification of flavor
1531 extra_specs (dict) Dict to be filled to be used during flavor creation
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001532
1533 """
1534 quotas = {
1535 "cpu-quota": "cpu",
1536 "mem-quota": "memory",
1537 "vif-quota": "vif",
1538 "disk-io-quota": "disk_io",
1539 }
1540
1541 page_sizes = {
1542 "LARGE": "large",
1543 "SMALL": "small",
1544 "SIZE_2MB": "2MB",
1545 "SIZE_1GB": "1GB",
1546 "PREFER_LARGE": "any",
1547 }
1548
1549 policies = {
1550 "cpu-pinning-policy": "hw:cpu_policy",
1551 "cpu-thread-pinning-policy": "hw:cpu_thread_policy",
1552 "mem-policy": "hw:numa_mempolicy",
1553 }
1554
1555 numas = extended.get("numas")
1556 if numas:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001557 self._process_numa_parameters_of_flavor(numas, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001558
1559 for quota, item in quotas.items():
1560 if quota in extended.keys():
1561 self.process_resource_quota(extended.get(quota), item, extra_specs)
1562
1563 # Set the mempage size as specified in the descriptor
1564 if extended.get("mempage-size"):
1565 if extended["mempage-size"] in page_sizes.keys():
1566 extra_specs["hw:mem_page_size"] = page_sizes[extended["mempage-size"]]
1567 else:
1568 # Normally, validations in NBI should not allow to this condition.
1569 self.logger.debug(
1570 "Invalid mempage-size %s. Will be ignored",
1571 extended.get("mempage-size"),
1572 )
1573
1574 for policy, hw_policy in policies.items():
1575 if extended.get(policy):
1576 extra_specs[hw_policy] = extended[policy].lower()
1577
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001578 @staticmethod
1579 def _get_flavor_details(flavor_data: dict) -> Tuple:
1580 """Returns the details of flavor
1581 Args:
1582 flavor_data (dict): Dictionary that includes required flavor details
1583
1584 Returns:
1585 ram, vcpus, extra_specs, extended (tuple): Main items of required flavor
1586
1587 """
1588 return (
1589 flavor_data.get("ram", 64),
1590 flavor_data.get("vcpus", 1),
1591 {},
1592 flavor_data.get("extended"),
1593 )
1594
gatici335a06a2023-07-26 00:34:04 +03001595 @catch_any_exception
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001596 def new_flavor(self, flavor_data: dict, change_name_if_used: bool = True) -> str:
1597 """Adds a tenant flavor to openstack VIM.
1598 if change_name_if_used is True, it will change name in case of conflict,
1599 because it is not supported name repetition.
1600
1601 Args:
1602 flavor_data (dict): Flavor details to be processed
1603 change_name_if_used (bool): Change name in case of conflict
1604
1605 Returns:
1606 flavor_id (str): flavor identifier
1607
tierno1ec592d2020-06-16 15:29:47 +00001608 """
tiernoae4a8d12016-07-08 12:30:39 +02001609 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno1ec592d2020-06-16 15:29:47 +00001610 retry = 0
1611 max_retries = 3
tierno7edb6752016-03-21 17:37:52 +01001612 name_suffix = 0
gatici335a06a2023-07-26 00:34:04 +03001613 name = flavor_data["name"]
1614 while retry < max_retries:
1615 retry += 1
1616 try:
1617 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001618
gatici335a06a2023-07-26 00:34:04 +03001619 if change_name_if_used:
1620 name = self._change_flavor_name(name, name_suffix, flavor_data)
sousaedu80135b92021-02-17 15:05:18 +01001621
gatici335a06a2023-07-26 00:34:04 +03001622 ram, vcpus, extra_specs, extended = self._get_flavor_details(
1623 flavor_data
1624 )
1625 if extended:
1626 self._process_extended_config_of_flavor(extended, extra_specs)
sousaedu80135b92021-02-17 15:05:18 +01001627
gatici335a06a2023-07-26 00:34:04 +03001628 # Create flavor
sousaedu80135b92021-02-17 15:05:18 +01001629
gatici335a06a2023-07-26 00:34:04 +03001630 new_flavor = self.nova.flavors.create(
1631 name=name,
1632 ram=ram,
1633 vcpus=vcpus,
1634 disk=flavor_data.get("disk", 0),
1635 ephemeral=flavor_data.get("ephemeral", 0),
1636 swap=flavor_data.get("swap", 0),
1637 is_public=flavor_data.get("is_public", True),
1638 )
sousaedu80135b92021-02-17 15:05:18 +01001639
gatici335a06a2023-07-26 00:34:04 +03001640 # Add metadata
1641 if extra_specs:
1642 new_flavor.set_keys(extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001643
gatici335a06a2023-07-26 00:34:04 +03001644 return new_flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001645
gatici335a06a2023-07-26 00:34:04 +03001646 except nvExceptions.Conflict as e:
1647 if change_name_if_used and retry < max_retries:
1648 continue
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001649
gatici335a06a2023-07-26 00:34:04 +03001650 self._format_exception(e)
sousaedu80135b92021-02-17 15:05:18 +01001651
gatici335a06a2023-07-26 00:34:04 +03001652 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00001653 def delete_flavor(self, flavor_id):
sousaedu80135b92021-02-17 15:05:18 +01001654 """Deletes a tenant flavor from openstack VIM. Returns the old flavor_id"""
tiernoae4a8d12016-07-08 12:30:39 +02001655 try:
1656 self._reload_connection()
1657 self.nova.flavors.delete(flavor_id)
1658 return flavor_id
gatici335a06a2023-07-26 00:34:04 +03001659
1660 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
1661 # If flavor is not found, it does not raise.
1662 self.logger.warning(
1663 f"Error deleting flavor: {flavor_id} is not found, {str(e.message)}"
1664 )
tierno7edb6752016-03-21 17:37:52 +01001665
tierno1ec592d2020-06-16 15:29:47 +00001666 def new_image(self, image_dict):
1667 """
tiernoae4a8d12016-07-08 12:30:39 +02001668 Adds a tenant image to VIM. imge_dict is a dictionary with:
1669 name: name
1670 disk_format: qcow2, vhd, vmdk, raw (by default), ...
1671 location: path or URI
1672 public: "yes" or "no"
1673 metadata: metadata of the image
1674 Returns the image_id
tierno1ec592d2020-06-16 15:29:47 +00001675 """
1676 retry = 0
1677 max_retries = 3
sousaedu80135b92021-02-17 15:05:18 +01001678
tierno1ec592d2020-06-16 15:29:47 +00001679 while retry < max_retries:
1680 retry += 1
tierno7edb6752016-03-21 17:37:52 +01001681 try:
1682 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001683
tierno1ec592d2020-06-16 15:29:47 +00001684 # determine format http://docs.openstack.org/developer/glance/formats.html
tierno7edb6752016-03-21 17:37:52 +01001685 if "disk_format" in image_dict:
tierno1ec592d2020-06-16 15:29:47 +00001686 disk_format = image_dict["disk_format"]
1687 else: # autodiscover based on extension
sousaedu80135b92021-02-17 15:05:18 +01001688 if image_dict["location"].endswith(".qcow2"):
tierno1ec592d2020-06-16 15:29:47 +00001689 disk_format = "qcow2"
sousaedu80135b92021-02-17 15:05:18 +01001690 elif image_dict["location"].endswith(".vhd"):
tierno1ec592d2020-06-16 15:29:47 +00001691 disk_format = "vhd"
sousaedu80135b92021-02-17 15:05:18 +01001692 elif image_dict["location"].endswith(".vmdk"):
tierno1ec592d2020-06-16 15:29:47 +00001693 disk_format = "vmdk"
sousaedu80135b92021-02-17 15:05:18 +01001694 elif image_dict["location"].endswith(".vdi"):
tierno1ec592d2020-06-16 15:29:47 +00001695 disk_format = "vdi"
sousaedu80135b92021-02-17 15:05:18 +01001696 elif image_dict["location"].endswith(".iso"):
tierno1ec592d2020-06-16 15:29:47 +00001697 disk_format = "iso"
sousaedu80135b92021-02-17 15:05:18 +01001698 elif image_dict["location"].endswith(".aki"):
tierno1ec592d2020-06-16 15:29:47 +00001699 disk_format = "aki"
sousaedu80135b92021-02-17 15:05:18 +01001700 elif image_dict["location"].endswith(".ari"):
tierno1ec592d2020-06-16 15:29:47 +00001701 disk_format = "ari"
sousaedu80135b92021-02-17 15:05:18 +01001702 elif image_dict["location"].endswith(".ami"):
tierno1ec592d2020-06-16 15:29:47 +00001703 disk_format = "ami"
tierno7edb6752016-03-21 17:37:52 +01001704 else:
tierno1ec592d2020-06-16 15:29:47 +00001705 disk_format = "raw"
sousaedu80135b92021-02-17 15:05:18 +01001706
1707 self.logger.debug(
1708 "new_image: '%s' loading from '%s'",
1709 image_dict["name"],
1710 image_dict["location"],
1711 )
shashankjain3c83a212018-10-04 13:05:46 +05301712 if self.vim_type == "VIO":
1713 container_format = "bare"
sousaedu80135b92021-02-17 15:05:18 +01001714 if "container_format" in image_dict:
1715 container_format = image_dict["container_format"]
1716
1717 new_image = self.glance.images.create(
1718 name=image_dict["name"],
1719 container_format=container_format,
1720 disk_format=disk_format,
1721 )
shashankjain3c83a212018-10-04 13:05:46 +05301722 else:
sousaedu80135b92021-02-17 15:05:18 +01001723 new_image = self.glance.images.create(name=image_dict["name"])
1724
1725 if image_dict["location"].startswith("http"):
tierno1beea862018-07-11 15:47:37 +02001726 # TODO there is not a method to direct download. It must be downloaded locally with requests
tierno72774862020-05-04 11:44:15 +00001727 raise vimconn.VimConnNotImplemented("Cannot create image from URL")
tierno1ec592d2020-06-16 15:29:47 +00001728 else: # local path
sousaedu80135b92021-02-17 15:05:18 +01001729 with open(image_dict["location"]) as fimage:
tierno1beea862018-07-11 15:47:37 +02001730 self.glance.images.upload(new_image.id, fimage)
sousaedu80135b92021-02-17 15:05:18 +01001731 # new_image = self.glancev1.images.create(name=image_dict["name"], is_public=
1732 # image_dict.get("public","yes")=="yes",
tierno1beea862018-07-11 15:47:37 +02001733 # container_format="bare", data=fimage, disk_format=disk_format)
sousaedu80135b92021-02-17 15:05:18 +01001734
1735 metadata_to_load = image_dict.get("metadata")
1736
1737 # TODO location is a reserved word for current openstack versions. fixed for VIO please check
tierno1ec592d2020-06-16 15:29:47 +00001738 # for openstack
shashankjain3c83a212018-10-04 13:05:46 +05301739 if self.vim_type == "VIO":
sousaedu80135b92021-02-17 15:05:18 +01001740 metadata_to_load["upload_location"] = image_dict["location"]
shashankjain3c83a212018-10-04 13:05:46 +05301741 else:
sousaedu80135b92021-02-17 15:05:18 +01001742 metadata_to_load["location"] = image_dict["location"]
1743
tierno1beea862018-07-11 15:47:37 +02001744 self.glance.images.update(new_image.id, **metadata_to_load)
sousaedu80135b92021-02-17 15:05:18 +01001745
tiernoae4a8d12016-07-08 12:30:39 +02001746 return new_image.id
sousaedu80135b92021-02-17 15:05:18 +01001747 except (
sousaedu80135b92021-02-17 15:05:18 +01001748 HTTPException,
1749 gl1Exceptions.HTTPException,
1750 gl1Exceptions.CommunicationError,
1751 ConnectionError,
1752 ) as e:
tierno1ec592d2020-06-16 15:29:47 +00001753 if retry == max_retries:
tiernoae4a8d12016-07-08 12:30:39 +02001754 continue
sousaedu80135b92021-02-17 15:05:18 +01001755
tiernoae4a8d12016-07-08 12:30:39 +02001756 self._format_exception(e)
tierno1ec592d2020-06-16 15:29:47 +00001757 except IOError as e: # can not open the file
sousaedu80135b92021-02-17 15:05:18 +01001758 raise vimconn.VimConnConnectionException(
1759 "{}: {} for {}".format(type(e).__name__, e, image_dict["location"]),
1760 http_code=vimconn.HTTP_Bad_Request,
1761 )
gatici335a06a2023-07-26 00:34:04 +03001762 except Exception as e:
1763 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001764
gatici335a06a2023-07-26 00:34:04 +03001765 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001766 def delete_image(self, image_id):
sousaedu80135b92021-02-17 15:05:18 +01001767 """Deletes a tenant image from openstack VIM. Returns the old id"""
tiernoae4a8d12016-07-08 12:30:39 +02001768 try:
1769 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001770 self.glance.images.delete(image_id)
sousaedu80135b92021-02-17 15:05:18 +01001771
tiernoae4a8d12016-07-08 12:30:39 +02001772 return image_id
gatici335a06a2023-07-26 00:34:04 +03001773 except gl1Exceptions.NotFound as e:
1774 # If image is not found, it does not raise.
1775 self.logger.warning(
1776 f"Error deleting image: {image_id} is not found, {str(e)}"
1777 )
tiernoae4a8d12016-07-08 12:30:39 +02001778
gatici335a06a2023-07-26 00:34:04 +03001779 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001780 def get_image_id_from_path(self, path):
tierno1ec592d2020-06-16 15:29:47 +00001781 """Get the image id from image path in the VIM database. Returns the image_id"""
gatici335a06a2023-07-26 00:34:04 +03001782 self._reload_connection()
1783 images = self.glance.images.list()
sousaedu80135b92021-02-17 15:05:18 +01001784
gatici335a06a2023-07-26 00:34:04 +03001785 for image in images:
1786 if image.metadata.get("location") == path:
1787 return image.id
sousaedu80135b92021-02-17 15:05:18 +01001788
gatici335a06a2023-07-26 00:34:04 +03001789 raise vimconn.VimConnNotFoundException(
1790 "image with location '{}' not found".format(path)
1791 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001792
garciadeblasb69fa9f2016-09-28 12:04:10 +02001793 def get_image_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001794 """Obtain tenant images from VIM
garciadeblasb69fa9f2016-09-28 12:04:10 +02001795 Filter_dict can be:
1796 id: image id
1797 name: image name
1798 checksum: image checksum
1799 Returns the image list of dictionaries:
1800 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1801 List can be empty
tierno1ec592d2020-06-16 15:29:47 +00001802 """
garciadeblasb69fa9f2016-09-28 12:04:10 +02001803 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
1804 try:
1805 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001806 # filter_dict_os = filter_dict.copy()
1807 # First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +02001808 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +02001809 filtered_list = []
sousaedu80135b92021-02-17 15:05:18 +01001810
garciadeblasb69fa9f2016-09-28 12:04:10 +02001811 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +02001812 try:
tierno1beea862018-07-11 15:47:37 +02001813 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
1814 continue
sousaedu80135b92021-02-17 15:05:18 +01001815
tierno1beea862018-07-11 15:47:37 +02001816 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
1817 continue
sousaedu80135b92021-02-17 15:05:18 +01001818
1819 if (
1820 filter_dict.get("checksum")
1821 and image["checksum"] != filter_dict["checksum"]
1822 ):
tierno1beea862018-07-11 15:47:37 +02001823 continue
1824
1825 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +02001826 except gl1Exceptions.HTTPNotFound:
1827 pass
sousaedu80135b92021-02-17 15:05:18 +01001828
garciadeblasb69fa9f2016-09-28 12:04:10 +02001829 return filtered_list
gatici335a06a2023-07-26 00:34:04 +03001830
sousaedu80135b92021-02-17 15:05:18 +01001831 except (
1832 ksExceptions.ClientException,
1833 nvExceptions.ClientException,
1834 gl1Exceptions.CommunicationError,
1835 ConnectionError,
1836 ) as e:
garciadeblasb69fa9f2016-09-28 12:04:10 +02001837 self._format_exception(e)
1838
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001839 def __wait_for_vm(self, vm_id, status):
1840 """wait until vm is in the desired status and return True.
1841 If the VM gets in ERROR status, return false.
1842 If the timeout is reached generate an exception"""
1843 elapsed_time = 0
1844 while elapsed_time < server_timeout:
1845 vm_status = self.nova.servers.get(vm_id).status
sousaedu80135b92021-02-17 15:05:18 +01001846
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001847 if vm_status == status:
1848 return True
sousaedu80135b92021-02-17 15:05:18 +01001849
1850 if vm_status == "ERROR":
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001851 return False
sousaedu80135b92021-02-17 15:05:18 +01001852
tierno1df468d2018-07-06 14:25:16 +02001853 time.sleep(5)
1854 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001855
1856 # if we exceeded the timeout rollback
1857 if elapsed_time >= server_timeout:
sousaedu80135b92021-02-17 15:05:18 +01001858 raise vimconn.VimConnException(
1859 "Timeout waiting for instance " + vm_id + " to get " + status,
1860 http_code=vimconn.HTTP_Request_Timeout,
1861 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001862
mirabal29356312017-07-27 12:21:22 +02001863 def _get_openstack_availablity_zones(self):
1864 """
1865 Get from openstack availability zones available
1866 :return:
1867 """
1868 try:
1869 openstack_availability_zone = self.nova.availability_zones.list()
sousaedu80135b92021-02-17 15:05:18 +01001870 openstack_availability_zone = [
1871 str(zone.zoneName)
1872 for zone in openstack_availability_zone
1873 if zone.zoneName != "internal"
1874 ]
1875
mirabal29356312017-07-27 12:21:22 +02001876 return openstack_availability_zone
tierno1ec592d2020-06-16 15:29:47 +00001877 except Exception:
mirabal29356312017-07-27 12:21:22 +02001878 return None
1879
1880 def _set_availablity_zones(self):
1881 """
1882 Set vim availablity zone
1883 :return:
1884 """
sousaedu80135b92021-02-17 15:05:18 +01001885 if "availability_zone" in self.config:
1886 vim_availability_zones = self.config.get("availability_zone")
mirabal29356312017-07-27 12:21:22 +02001887
mirabal29356312017-07-27 12:21:22 +02001888 if isinstance(vim_availability_zones, str):
1889 self.availability_zone = [vim_availability_zones]
1890 elif isinstance(vim_availability_zones, list):
1891 self.availability_zone = vim_availability_zones
1892 else:
1893 self.availability_zone = self._get_openstack_availablity_zones()
Luis Vega25bc6382023-10-05 23:22:04 +00001894 if "storage_availability_zone" in self.config:
1895 self.storage_availability_zone = self.config.get(
1896 "storage_availability_zone"
1897 )
mirabal29356312017-07-27 12:21:22 +02001898
sousaedu80135b92021-02-17 15:05:18 +01001899 def _get_vm_availability_zone(
1900 self, availability_zone_index, availability_zone_list
1901 ):
mirabal29356312017-07-27 12:21:22 +02001902 """
tierno5a3273c2017-08-29 11:43:46 +02001903 Return thge availability zone to be used by the created VM.
1904 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +02001905 """
tierno5a3273c2017-08-29 11:43:46 +02001906 if availability_zone_index is None:
sousaedu80135b92021-02-17 15:05:18 +01001907 if not self.config.get("availability_zone"):
tierno5a3273c2017-08-29 11:43:46 +02001908 return None
sousaedu80135b92021-02-17 15:05:18 +01001909 elif isinstance(self.config.get("availability_zone"), str):
1910 return self.config["availability_zone"]
tierno5a3273c2017-08-29 11:43:46 +02001911 else:
1912 # TODO consider using a different parameter at config for default AV and AV list match
sousaedu80135b92021-02-17 15:05:18 +01001913 return self.config["availability_zone"][0]
mirabal29356312017-07-27 12:21:22 +02001914
tierno5a3273c2017-08-29 11:43:46 +02001915 vim_availability_zones = self.availability_zone
1916 # check if VIM offer enough availability zones describe in the VNFD
sousaedu80135b92021-02-17 15:05:18 +01001917 if vim_availability_zones and len(availability_zone_list) <= len(
1918 vim_availability_zones
1919 ):
tierno5a3273c2017-08-29 11:43:46 +02001920 # check if all the names of NFV AV match VIM AV names
1921 match_by_index = False
1922 for av in availability_zone_list:
1923 if av not in vim_availability_zones:
1924 match_by_index = True
1925 break
sousaedu80135b92021-02-17 15:05:18 +01001926
tierno5a3273c2017-08-29 11:43:46 +02001927 if match_by_index:
1928 return vim_availability_zones[availability_zone_index]
1929 else:
1930 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +02001931 else:
sousaedu80135b92021-02-17 15:05:18 +01001932 raise vimconn.VimConnConflictException(
1933 "No enough availability zones at VIM for this deployment"
1934 )
mirabal29356312017-07-27 12:21:22 +02001935
Gulsum Atici26f73662022-10-27 15:18:27 +03001936 def _prepare_port_dict_security_groups(self, net: dict, port_dict: dict) -> None:
1937 """Fill up the security_groups in the port_dict.
1938
1939 Args:
1940 net (dict): Network details
1941 port_dict (dict): Port details
1942
1943 """
1944 if (
1945 self.config.get("security_groups")
1946 and net.get("port_security") is not False
1947 and not self.config.get("no_port_security_extension")
1948 ):
1949 if not self.security_groups_id:
1950 self._get_ids_from_name()
1951
1952 port_dict["security_groups"] = self.security_groups_id
1953
1954 def _prepare_port_dict_binding(self, net: dict, port_dict: dict) -> None:
1955 """Fill up the network binding depending on network type in the port_dict.
1956
1957 Args:
1958 net (dict): Network details
1959 port_dict (dict): Port details
1960
1961 """
1962 if not net.get("type"):
1963 raise vimconn.VimConnException("Type is missing in the network details.")
1964
1965 if net["type"] == "virtual":
1966 pass
1967
1968 # For VF
1969 elif net["type"] == "VF" or net["type"] == "SR-IOV":
Gulsum Atici26f73662022-10-27 15:18:27 +03001970 port_dict["binding:vnic_type"] = "direct"
1971
1972 # VIO specific Changes
1973 if self.vim_type == "VIO":
1974 # Need to create port with port_security_enabled = False and no-security-groups
1975 port_dict["port_security_enabled"] = False
1976 port_dict["provider_security_groups"] = []
1977 port_dict["security_groups"] = []
1978
1979 else:
1980 # For PT PCI-PASSTHROUGH
1981 port_dict["binding:vnic_type"] = "direct-physical"
1982
1983 @staticmethod
1984 def _set_fixed_ip(new_port: dict, net: dict) -> None:
1985 """Set the "ip" parameter in net dictionary.
1986
1987 Args:
1988 new_port (dict): New created port
1989 net (dict): Network details
1990
1991 """
1992 fixed_ips = new_port["port"].get("fixed_ips")
1993
1994 if fixed_ips:
1995 net["ip"] = fixed_ips[0].get("ip_address")
1996 else:
1997 net["ip"] = None
1998
1999 @staticmethod
2000 def _prepare_port_dict_mac_ip_addr(net: dict, port_dict: dict) -> None:
2001 """Fill up the mac_address and fixed_ips in port_dict.
2002
2003 Args:
2004 net (dict): Network details
2005 port_dict (dict): Port details
2006
2007 """
2008 if net.get("mac_address"):
2009 port_dict["mac_address"] = net["mac_address"]
2010
elumalai370e36b2023-04-25 16:22:56 +05302011 ip_dual_list = []
2012 if ip_list := net.get("ip_address"):
2013 if not isinstance(ip_list, list):
2014 ip_list = [ip_list]
2015 for ip in ip_list:
2016 ip_dict = {"ip_address": ip}
2017 ip_dual_list.append(ip_dict)
2018 port_dict["fixed_ips"] = ip_dual_list
Gulsum Atici26f73662022-10-27 15:18:27 +03002019 # TODO add "subnet_id": <subnet_id>
2020
2021 def _create_new_port(self, port_dict: dict, created_items: dict, net: dict) -> Dict:
2022 """Create new port using neutron.
2023
2024 Args:
2025 port_dict (dict): Port details
2026 created_items (dict): All created items
2027 net (dict): Network details
2028
2029 Returns:
2030 new_port (dict): New created port
2031
2032 """
2033 new_port = self.neutron.create_port({"port": port_dict})
2034 created_items["port:" + str(new_port["port"]["id"])] = True
elumalaie17cd942023-04-28 18:04:24 +05302035 net["mac_address"] = new_port["port"]["mac_address"]
Gulsum Atici26f73662022-10-27 15:18:27 +03002036 net["vim_id"] = new_port["port"]["id"]
2037
2038 return new_port
2039
2040 def _create_port(
2041 self, net: dict, name: str, created_items: dict
2042 ) -> Tuple[dict, dict]:
2043 """Create port using net details.
2044
2045 Args:
2046 net (dict): Network details
2047 name (str): Name to be used as network name if net dict does not include name
2048 created_items (dict): All created items
2049
2050 Returns:
2051 new_port, port New created port, port dictionary
2052
2053 """
2054
2055 port_dict = {
2056 "network_id": net["net_id"],
2057 "name": net.get("name"),
2058 "admin_state_up": True,
2059 }
2060
2061 if not port_dict["name"]:
2062 port_dict["name"] = name
2063
2064 self._prepare_port_dict_security_groups(net, port_dict)
2065
2066 self._prepare_port_dict_binding(net, port_dict)
2067
2068 vimconnector._prepare_port_dict_mac_ip_addr(net, port_dict)
2069
2070 new_port = self._create_new_port(port_dict, created_items, net)
2071
2072 vimconnector._set_fixed_ip(new_port, net)
2073
2074 port = {"port-id": new_port["port"]["id"]}
2075
2076 if float(self.nova.api_version.get_string()) >= 2.32:
2077 port["tag"] = new_port["port"]["name"]
2078
2079 return new_port, port
2080
2081 def _prepare_network_for_vminstance(
2082 self,
2083 name: str,
2084 net_list: list,
2085 created_items: dict,
2086 net_list_vim: list,
2087 external_network: list,
2088 no_secured_ports: list,
2089 ) -> None:
2090 """Create port and fill up net dictionary for new VM instance creation.
2091
2092 Args:
2093 name (str): Name of network
2094 net_list (list): List of networks
2095 created_items (dict): All created items belongs to a VM
2096 net_list_vim (list): List of ports
2097 external_network (list): List of external-networks
2098 no_secured_ports (list): Port security disabled ports
2099 """
2100
2101 self._reload_connection()
2102
2103 for net in net_list:
2104 # Skip non-connected iface
2105 if not net.get("net_id"):
2106 continue
2107
2108 new_port, port = self._create_port(net, name, created_items)
2109
2110 net_list_vim.append(port)
2111
2112 if net.get("floating_ip", False):
2113 net["exit_on_floating_ip_error"] = True
2114 external_network.append(net)
2115
2116 elif net["use"] == "mgmt" and self.config.get("use_floating_ip"):
2117 net["exit_on_floating_ip_error"] = False
2118 external_network.append(net)
2119 net["floating_ip"] = self.config.get("use_floating_ip")
2120
2121 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic
2122 # is dropped. As a workaround we wait until the VM is active and then disable the port-security
2123 if net.get("port_security") is False and not self.config.get(
2124 "no_port_security_extension"
2125 ):
2126 no_secured_ports.append(
2127 (
2128 new_port["port"]["id"],
2129 net.get("port_security_disable_strategy"),
2130 )
2131 )
2132
2133 def _prepare_persistent_root_volumes(
2134 self,
2135 name: str,
Luis Vega25bc6382023-10-05 23:22:04 +00002136 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002137 disk: dict,
2138 base_disk_index: int,
2139 block_device_mapping: dict,
2140 existing_vim_volumes: list,
2141 created_items: dict,
2142 ) -> Optional[str]:
2143 """Prepare persistent root volumes for new VM instance.
2144
2145 Args:
2146 name (str): Name of VM instance
Luis Vega25bc6382023-10-05 23:22:04 +00002147 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002148 disk (dict): Disk details
2149 base_disk_index (int): Disk index
2150 block_device_mapping (dict): Block device details
2151 existing_vim_volumes (list): Existing disk details
2152 created_items (dict): All created items belongs to VM
2153
2154 Returns:
2155 boot_volume_id (str): ID of boot volume
2156
2157 """
2158 # Disk may include only vim_volume_id or only vim_id."
2159 # Use existing persistent root volume finding with volume_id or vim_id
2160 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002161 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002162 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2163 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002164 else:
2165 # Create persistent root volume
2166 volume = self.cinder.volumes.create(
2167 size=disk["size"],
2168 name=name + "vd" + chr(base_disk_index),
2169 imageRef=disk["image_id"],
2170 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002171 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002172 )
2173 boot_volume_id = volume.id
aticig2f4ab6c2022-09-03 18:15:20 +03002174 self.update_block_device_mapping(
2175 volume=volume,
2176 block_device_mapping=block_device_mapping,
2177 base_disk_index=base_disk_index,
2178 disk=disk,
2179 created_items=created_items,
2180 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002181
2182 return boot_volume_id
2183
aticig2f4ab6c2022-09-03 18:15:20 +03002184 @staticmethod
2185 def update_block_device_mapping(
2186 volume: object,
2187 block_device_mapping: dict,
2188 base_disk_index: int,
2189 disk: dict,
2190 created_items: dict,
2191 ) -> None:
2192 """Add volume information to block device mapping dict.
2193 Args:
2194 volume (object): Created volume object
2195 block_device_mapping (dict): Block device details
2196 base_disk_index (int): Disk index
2197 disk (dict): Disk details
2198 created_items (dict): All created items belongs to VM
2199 """
2200 if not volume:
2201 raise vimconn.VimConnException("Volume is empty.")
2202
2203 if not hasattr(volume, "id"):
2204 raise vimconn.VimConnException(
2205 "Created volume is not valid, does not have id attribute."
2206 )
2207
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002208 block_device_mapping["vd" + chr(base_disk_index)] = volume.id
2209 if disk.get("multiattach"): # multiattach volumes do not belong to VDUs
2210 return
aticig2f4ab6c2022-09-03 18:15:20 +03002211 volume_txt = "volume:" + str(volume.id)
2212 if disk.get("keep"):
2213 volume_txt += ":keep"
2214 created_items[volume_txt] = True
aticig2f4ab6c2022-09-03 18:15:20 +03002215
gatici335a06a2023-07-26 00:34:04 +03002216 @catch_any_exception
vegall364627c2023-03-17 15:09:50 +00002217 def new_shared_volumes(self, shared_volume_data) -> (str, str):
Luis Vega25bc6382023-10-05 23:22:04 +00002218 availability_zone = (
2219 self.storage_availability_zone
2220 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002221 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002222 )
gatici335a06a2023-07-26 00:34:04 +03002223 volume = self.cinder.volumes.create(
2224 size=shared_volume_data["size"],
2225 name=shared_volume_data["name"],
2226 volume_type="multiattach",
Luis Vega25bc6382023-10-05 23:22:04 +00002227 availability_zone=availability_zone,
gatici335a06a2023-07-26 00:34:04 +03002228 )
2229 return volume.name, volume.id
vegall364627c2023-03-17 15:09:50 +00002230
2231 def _prepare_shared_volumes(
2232 self,
2233 name: str,
2234 disk: dict,
2235 base_disk_index: int,
2236 block_device_mapping: dict,
2237 existing_vim_volumes: list,
2238 created_items: dict,
2239 ):
2240 volumes = {volume.name: volume.id for volume in self.cinder.volumes.list()}
2241 if volumes.get(disk["name"]):
2242 sv_id = volumes[disk["name"]]
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002243 max_retries = 3
2244 vol_status = ""
2245 # If this is not the first VM to attach the volume, volume status may be "reserved" for a short time
2246 while max_retries:
2247 max_retries -= 1
2248 volume = self.cinder.volumes.get(sv_id)
2249 vol_status = volume.status
2250 if volume.status not in ("in-use", "available"):
2251 time.sleep(5)
2252 continue
2253 self.update_block_device_mapping(
2254 volume=volume,
2255 block_device_mapping=block_device_mapping,
2256 base_disk_index=base_disk_index,
2257 disk=disk,
2258 created_items=created_items,
2259 )
2260 return
2261 raise vimconn.VimConnException(
2262 "Shared volume is not prepared, status is: {}".format(vol_status),
2263 http_code=vimconn.HTTP_Internal_Server_Error,
vegall364627c2023-03-17 15:09:50 +00002264 )
2265
Gulsum Atici26f73662022-10-27 15:18:27 +03002266 def _prepare_non_root_persistent_volumes(
2267 self,
2268 name: str,
2269 disk: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002270 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002271 block_device_mapping: dict,
2272 base_disk_index: int,
2273 existing_vim_volumes: list,
2274 created_items: dict,
2275 ) -> None:
2276 """Prepare persistent volumes for new VM instance.
2277
2278 Args:
2279 name (str): Name of VM instance
2280 disk (dict): Disk details
Luis Vega25bc6382023-10-05 23:22:04 +00002281 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002282 block_device_mapping (dict): Block device details
2283 base_disk_index (int): Disk index
2284 existing_vim_volumes (list): Existing disk details
2285 created_items (dict): All created items belongs to VM
2286 """
2287 # Non-root persistent volumes
2288 # Disk may include only vim_volume_id or only vim_id."
2289 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002290 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002291 # Use existing persistent volume
2292 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2293 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002294 else:
vegall364627c2023-03-17 15:09:50 +00002295 volume_name = f"{name}vd{chr(base_disk_index)}"
Gulsum Atici26f73662022-10-27 15:18:27 +03002296 volume = self.cinder.volumes.create(
2297 size=disk["size"],
vegall364627c2023-03-17 15:09:50 +00002298 name=volume_name,
Gulsum Atici26f73662022-10-27 15:18:27 +03002299 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002300 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002301 )
aticig2f4ab6c2022-09-03 18:15:20 +03002302 self.update_block_device_mapping(
2303 volume=volume,
2304 block_device_mapping=block_device_mapping,
2305 base_disk_index=base_disk_index,
2306 disk=disk,
2307 created_items=created_items,
2308 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002309
2310 def _wait_for_created_volumes_availability(
2311 self, elapsed_time: int, created_items: dict
2312 ) -> Optional[int]:
2313 """Wait till created volumes become available.
2314
2315 Args:
2316 elapsed_time (int): Passed time while waiting
2317 created_items (dict): All created items belongs to VM
2318
2319 Returns:
2320 elapsed_time (int): Time spent while waiting
2321
2322 """
Gulsum Atici26f73662022-10-27 15:18:27 +03002323 while elapsed_time < volume_timeout:
2324 for created_item in created_items:
aticig2f4ab6c2022-09-03 18:15:20 +03002325 v, volume_id = (
2326 created_item.split(":")[0],
2327 created_item.split(":")[1],
2328 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002329 if v == "volume":
vegall364627c2023-03-17 15:09:50 +00002330 volume = self.cinder.volumes.get(volume_id)
2331 if (
2332 volume.volume_type == "multiattach"
2333 and volume.status == "in-use"
2334 ):
2335 return elapsed_time
2336 elif volume.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002337 break
2338 else:
2339 # All ready: break from while
2340 break
2341
2342 time.sleep(5)
2343 elapsed_time += 5
2344
2345 return elapsed_time
2346
2347 def _wait_for_existing_volumes_availability(
2348 self, elapsed_time: int, existing_vim_volumes: list
2349 ) -> Optional[int]:
2350 """Wait till existing volumes become available.
2351
2352 Args:
2353 elapsed_time (int): Passed time while waiting
2354 existing_vim_volumes (list): Existing volume details
2355
2356 Returns:
2357 elapsed_time (int): Time spent while waiting
2358
2359 """
2360
2361 while elapsed_time < volume_timeout:
2362 for volume in existing_vim_volumes:
vegall364627c2023-03-17 15:09:50 +00002363 v = self.cinder.volumes.get(volume["id"])
2364 if v.volume_type == "multiattach" and v.status == "in-use":
2365 return elapsed_time
2366 elif v.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002367 break
2368 else: # all ready: break from while
2369 break
2370
2371 time.sleep(5)
2372 elapsed_time += 5
2373
2374 return elapsed_time
2375
2376 def _prepare_disk_for_vminstance(
2377 self,
2378 name: str,
2379 existing_vim_volumes: list,
2380 created_items: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002381 storage_av_zone: list,
Gulsum Atici13d02322022-11-18 00:10:15 +03002382 block_device_mapping: dict,
Gulsum Atici26f73662022-10-27 15:18:27 +03002383 disk_list: list = None,
2384 ) -> None:
2385 """Prepare all volumes for new VM instance.
2386
2387 Args:
2388 name (str): Name of Instance
2389 existing_vim_volumes (list): List of existing volumes
2390 created_items (dict): All created items belongs to VM
Luis Vega25bc6382023-10-05 23:22:04 +00002391 storage_av_zone (list): Storage availability zone
Gulsum Atici13d02322022-11-18 00:10:15 +03002392 block_device_mapping (dict): Block devices to be attached to VM
Gulsum Atici26f73662022-10-27 15:18:27 +03002393 disk_list (list): List of disks
2394
2395 """
2396 # Create additional volumes in case these are present in disk_list
2397 base_disk_index = ord("b")
2398 boot_volume_id = None
2399 elapsed_time = 0
Gulsum Atici26f73662022-10-27 15:18:27 +03002400 for disk in disk_list:
2401 if "image_id" in disk:
2402 # Root persistent volume
2403 base_disk_index = ord("a")
2404 boot_volume_id = self._prepare_persistent_root_volumes(
2405 name=name,
Luis Vega25bc6382023-10-05 23:22:04 +00002406 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002407 disk=disk,
2408 base_disk_index=base_disk_index,
2409 block_device_mapping=block_device_mapping,
2410 existing_vim_volumes=existing_vim_volumes,
2411 created_items=created_items,
2412 )
vegall364627c2023-03-17 15:09:50 +00002413 elif disk.get("multiattach"):
2414 self._prepare_shared_volumes(
2415 name=name,
2416 disk=disk,
2417 base_disk_index=base_disk_index,
2418 block_device_mapping=block_device_mapping,
2419 existing_vim_volumes=existing_vim_volumes,
2420 created_items=created_items,
2421 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002422 else:
2423 # Non-root persistent volume
2424 self._prepare_non_root_persistent_volumes(
2425 name=name,
2426 disk=disk,
Luis Vega25bc6382023-10-05 23:22:04 +00002427 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002428 block_device_mapping=block_device_mapping,
2429 base_disk_index=base_disk_index,
2430 existing_vim_volumes=existing_vim_volumes,
2431 created_items=created_items,
2432 )
2433 base_disk_index += 1
2434
2435 # Wait until created volumes are with status available
2436 elapsed_time = self._wait_for_created_volumes_availability(
2437 elapsed_time, created_items
2438 )
2439 # Wait until existing volumes in vim are with status available
2440 elapsed_time = self._wait_for_existing_volumes_availability(
2441 elapsed_time, existing_vim_volumes
2442 )
2443 # If we exceeded the timeout rollback
2444 if elapsed_time >= volume_timeout:
2445 raise vimconn.VimConnException(
2446 "Timeout creating volumes for instance " + name,
2447 http_code=vimconn.HTTP_Request_Timeout,
2448 )
2449 if boot_volume_id:
2450 self.cinder.volumes.set_bootable(boot_volume_id, True)
2451
2452 def _find_the_external_network_for_floating_ip(self):
2453 """Get the external network ip in order to create floating IP.
2454
2455 Returns:
2456 pool_id (str): External network pool ID
2457
2458 """
2459
2460 # Find the external network
2461 external_nets = list()
2462
2463 for net in self.neutron.list_networks()["networks"]:
2464 if net["router:external"]:
2465 external_nets.append(net)
2466
2467 if len(external_nets) == 0:
2468 raise vimconn.VimConnException(
2469 "Cannot create floating_ip automatically since "
2470 "no external network is present",
2471 http_code=vimconn.HTTP_Conflict,
2472 )
2473
2474 if len(external_nets) > 1:
2475 raise vimconn.VimConnException(
2476 "Cannot create floating_ip automatically since "
2477 "multiple external networks are present",
2478 http_code=vimconn.HTTP_Conflict,
2479 )
2480
2481 # Pool ID
2482 return external_nets[0].get("id")
2483
2484 def _neutron_create_float_ip(self, param: dict, created_items: dict) -> None:
2485 """Trigger neutron to create a new floating IP using external network ID.
2486
2487 Args:
2488 param (dict): Input parameters to create a floating IP
2489 created_items (dict): All created items belongs to new VM instance
2490
2491 Raises:
2492
2493 VimConnException
2494 """
2495 try:
2496 self.logger.debug("Creating floating IP")
2497 new_floating_ip = self.neutron.create_floatingip(param)
2498 free_floating_ip = new_floating_ip["floatingip"]["id"]
2499 created_items["floating_ip:" + str(free_floating_ip)] = True
2500
2501 except Exception as e:
2502 raise vimconn.VimConnException(
2503 type(e).__name__ + ": Cannot create new floating_ip " + str(e),
2504 http_code=vimconn.HTTP_Conflict,
2505 )
2506
2507 def _create_floating_ip(
2508 self, floating_network: dict, server: object, created_items: dict
2509 ) -> None:
2510 """Get the available Pool ID and create a new floating IP.
2511
2512 Args:
2513 floating_network (dict): Dict including external network ID
2514 server (object): Server object
2515 created_items (dict): All created items belongs to new VM instance
2516
2517 """
2518
2519 # Pool_id is available
2520 if (
2521 isinstance(floating_network["floating_ip"], str)
2522 and floating_network["floating_ip"].lower() != "true"
2523 ):
2524 pool_id = floating_network["floating_ip"]
2525
2526 # Find the Pool_id
2527 else:
2528 pool_id = self._find_the_external_network_for_floating_ip()
2529
2530 param = {
2531 "floatingip": {
2532 "floating_network_id": pool_id,
2533 "tenant_id": server.tenant_id,
2534 }
2535 }
2536
2537 self._neutron_create_float_ip(param, created_items)
2538
2539 def _find_floating_ip(
2540 self,
2541 server: object,
2542 floating_ips: list,
2543 floating_network: dict,
2544 ) -> Optional[str]:
2545 """Find the available free floating IPs if there are.
2546
2547 Args:
2548 server (object): Server object
2549 floating_ips (list): List of floating IPs
2550 floating_network (dict): Details of floating network such as ID
2551
2552 Returns:
2553 free_floating_ip (str): Free floating ip address
2554
2555 """
2556 for fip in floating_ips:
2557 if fip.get("port_id") or fip.get("tenant_id") != server.tenant_id:
2558 continue
2559
2560 if isinstance(floating_network["floating_ip"], str):
2561 if fip.get("floating_network_id") != floating_network["floating_ip"]:
2562 continue
2563
2564 return fip["id"]
2565
2566 def _assign_floating_ip(
2567 self, free_floating_ip: str, floating_network: dict
2568 ) -> Dict:
2569 """Assign the free floating ip address to port.
2570
2571 Args:
2572 free_floating_ip (str): Floating IP to be assigned
2573 floating_network (dict): ID of floating network
2574
2575 Returns:
2576 fip (dict) (dict): Floating ip details
2577
2578 """
2579 # The vim_id key contains the neutron.port_id
2580 self.neutron.update_floatingip(
2581 free_floating_ip,
2582 {"floatingip": {"port_id": floating_network["vim_id"]}},
2583 )
2584 # For race condition ensure not re-assigned to other VM after 5 seconds
2585 time.sleep(5)
2586
2587 return self.neutron.show_floatingip(free_floating_ip)
2588
2589 def _get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002590 self, server: object, floating_network: dict
Gulsum Atici26f73662022-10-27 15:18:27 +03002591 ) -> Optional[str]:
2592 """Get the free floating IP address.
2593
2594 Args:
2595 server (object): Server Object
2596 floating_network (dict): Floating network details
Gulsum Atici26f73662022-10-27 15:18:27 +03002597
2598 Returns:
2599 free_floating_ip (str): Free floating ip addr
2600
2601 """
2602
2603 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
2604
2605 # Randomize
2606 random.shuffle(floating_ips)
2607
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002608 return self._find_floating_ip(server, floating_ips, floating_network)
Gulsum Atici26f73662022-10-27 15:18:27 +03002609
2610 def _prepare_external_network_for_vminstance(
2611 self,
2612 external_network: list,
2613 server: object,
2614 created_items: dict,
2615 vm_start_time: float,
2616 ) -> None:
2617 """Assign floating IP address for VM instance.
2618
2619 Args:
2620 external_network (list): ID of External network
2621 server (object): Server Object
2622 created_items (dict): All created items belongs to new VM instance
2623 vm_start_time (float): Time as a floating point number expressed in seconds since the epoch, in UTC
2624
2625 Raises:
2626 VimConnException
2627
2628 """
2629 for floating_network in external_network:
2630 try:
2631 assigned = False
2632 floating_ip_retries = 3
2633 # In case of RO in HA there can be conflicts, two RO trying to assign same floating IP, so retry
2634 # several times
2635 while not assigned:
Gulsum Atici26f73662022-10-27 15:18:27 +03002636 free_floating_ip = self._get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002637 server, floating_network
Gulsum Atici26f73662022-10-27 15:18:27 +03002638 )
2639
2640 if not free_floating_ip:
2641 self._create_floating_ip(
2642 floating_network, server, created_items
2643 )
2644
2645 try:
2646 # For race condition ensure not already assigned
2647 fip = self.neutron.show_floatingip(free_floating_ip)
2648
2649 if fip["floatingip"].get("port_id"):
2650 continue
2651
2652 # Assign floating ip
2653 fip = self._assign_floating_ip(
2654 free_floating_ip, floating_network
2655 )
2656
2657 if fip["floatingip"]["port_id"] != floating_network["vim_id"]:
2658 self.logger.warning(
2659 "floating_ip {} re-assigned to other port".format(
2660 free_floating_ip
2661 )
2662 )
2663 continue
2664
2665 self.logger.debug(
2666 "Assigned floating_ip {} to VM {}".format(
2667 free_floating_ip, server.id
2668 )
2669 )
2670
2671 assigned = True
2672
2673 except Exception as e:
2674 # Openstack need some time after VM creation to assign an IP. So retry if fails
2675 vm_status = self.nova.servers.get(server.id).status
2676
2677 if vm_status not in ("ACTIVE", "ERROR"):
2678 if time.time() - vm_start_time < server_timeout:
2679 time.sleep(5)
2680 continue
2681 elif floating_ip_retries > 0:
2682 floating_ip_retries -= 1
2683 continue
2684
2685 raise vimconn.VimConnException(
2686 "Cannot create floating_ip: {} {}".format(
2687 type(e).__name__, e
2688 ),
2689 http_code=vimconn.HTTP_Conflict,
2690 )
2691
2692 except Exception as e:
2693 if not floating_network["exit_on_floating_ip_error"]:
2694 self.logger.error("Cannot create floating_ip. %s", str(e))
2695 continue
2696
2697 raise
2698
2699 def _update_port_security_for_vminstance(
2700 self,
2701 no_secured_ports: list,
2702 server: object,
2703 ) -> None:
2704 """Updates the port security according to no_secured_ports list.
2705
2706 Args:
2707 no_secured_ports (list): List of ports that security will be disabled
2708 server (object): Server Object
2709
2710 Raises:
2711 VimConnException
2712
2713 """
2714 # Wait until the VM is active and then disable the port-security
2715 if no_secured_ports:
2716 self.__wait_for_vm(server.id, "ACTIVE")
2717
2718 for port in no_secured_ports:
2719 port_update = {
2720 "port": {"port_security_enabled": False, "security_groups": None}
2721 }
2722
2723 if port[1] == "allow-address-pairs":
2724 port_update = {
2725 "port": {"allowed_address_pairs": [{"ip_address": "0.0.0.0/0"}]}
2726 }
2727
2728 try:
2729 self.neutron.update_port(port[0], port_update)
2730
2731 except Exception:
Gulsum Atici26f73662022-10-27 15:18:27 +03002732 raise vimconn.VimConnException(
2733 "It was not possible to disable port security for port {}".format(
2734 port[0]
2735 )
2736 )
2737
sousaedu80135b92021-02-17 15:05:18 +01002738 def new_vminstance(
2739 self,
Gulsum Atici26f73662022-10-27 15:18:27 +03002740 name: str,
2741 description: str,
2742 start: bool,
2743 image_id: str,
2744 flavor_id: str,
2745 affinity_group_list: list,
2746 net_list: list,
sousaedu80135b92021-02-17 15:05:18 +01002747 cloud_config=None,
2748 disk_list=None,
2749 availability_zone_index=None,
2750 availability_zone_list=None,
Gulsum Atici26f73662022-10-27 15:18:27 +03002751 ) -> tuple:
2752 """Adds a VM instance to VIM.
2753
2754 Args:
2755 name (str): name of VM
2756 description (str): description
2757 start (bool): indicates if VM must start or boot in pause mode. Ignored
2758 image_id (str) image uuid
2759 flavor_id (str) flavor uuid
2760 affinity_group_list (list): list of affinity groups, each one is a dictionary.Ignore if empty.
2761 net_list (list): list of interfaces, each one is a dictionary with:
2762 name: name of network
2763 net_id: network uuid to connect
2764 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
2765 model: interface model, ignored #TODO
2766 mac_address: used for SR-IOV ifaces #TODO for other types
2767 use: 'data', 'bridge', 'mgmt'
2768 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
2769 vim_id: filled/added by this function
2770 floating_ip: True/False (or it can be None)
2771 port_security: True/False
2772 cloud_config (dict): (optional) dictionary with:
2773 key-pairs: (optional) list of strings with the public key to be inserted to the default user
2774 users: (optional) list of users to be inserted, each item is a dict with:
2775 name: (mandatory) user name,
2776 key-pairs: (optional) list of strings with the public key to be inserted to the user
2777 user-data: (optional) string is a text script to be passed directly to cloud-init
2778 config-files: (optional). List of files to be transferred. Each item is a dict with:
2779 dest: (mandatory) string with the destination absolute path
2780 encoding: (optional, by default text). Can be one of:
tierno1d213f42020-04-24 14:02:51 +00002781 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
Gulsum Atici26f73662022-10-27 15:18:27 +03002782 content : (mandatory) string with the content of the file
2783 permissions: (optional) string with file permissions, typically octal notation '0644'
2784 owner: (optional) file owner, string with the format 'owner:group'
2785 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
2786 disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
2787 image_id: (optional). VIM id of an existing image. If not provided an empty disk must be mounted
2788 size: (mandatory) string with the size of the disk in GB
2789 vim_id: (optional) should use this existing volume id
2790 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
2791 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
tierno5a3273c2017-08-29 11:43:46 +02002792 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01002793 #TODO ip, security groups
Gulsum Atici26f73662022-10-27 15:18:27 +03002794
2795 Returns:
2796 A tuple with the instance identifier and created_items or raises an exception on error
tierno98e909c2017-10-14 13:27:03 +02002797 created_items can be None or a dictionary where this method can include key-values that will be passed to
2798 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
2799 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
2800 as not present.
aticig2f4ab6c2022-09-03 18:15:20 +03002801
tierno98e909c2017-10-14 13:27:03 +02002802 """
sousaedu80135b92021-02-17 15:05:18 +01002803 self.logger.debug(
2804 "new_vminstance input: image='%s' flavor='%s' nics='%s'",
2805 image_id,
2806 flavor_id,
2807 str(net_list),
2808 )
gatici335a06a2023-07-26 00:34:04 +03002809 server = None
2810 created_items = {}
2811 net_list_vim = []
2812 # list of external networks to be connected to instance, later on used to create floating_ip
2813 external_network = []
2814 # List of ports with port-security disabled
2815 no_secured_ports = []
2816 block_device_mapping = {}
2817 existing_vim_volumes = []
2818 server_group_id = None
2819 scheduller_hints = {}
sousaedu80135b92021-02-17 15:05:18 +01002820
tierno7edb6752016-03-21 17:37:52 +01002821 try:
Gulsum Atici26f73662022-10-27 15:18:27 +03002822 # Check the Openstack Connection
2823 self._reload_connection()
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02002824
Gulsum Atici26f73662022-10-27 15:18:27 +03002825 # Prepare network list
2826 self._prepare_network_for_vminstance(
2827 name=name,
2828 net_list=net_list,
2829 created_items=created_items,
2830 net_list_vim=net_list_vim,
2831 external_network=external_network,
2832 no_secured_ports=no_secured_ports,
sousaedu80135b92021-02-17 15:05:18 +01002833 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002834
Gulsum Atici26f73662022-10-27 15:18:27 +03002835 # Cloud config
tierno0a1437e2017-10-02 00:17:43 +02002836 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00002837
Gulsum Atici26f73662022-10-27 15:18:27 +03002838 # Get availability Zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002839 self.vm_av_zone = self._get_vm_availability_zone(
Alexis Romero247cc432022-05-12 13:23:25 +02002840 availability_zone_index, availability_zone_list
2841 )
2842
Luis Vega25bc6382023-10-05 23:22:04 +00002843 storage_av_zone = (
2844 self.storage_availability_zone
2845 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002846 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002847 )
2848
tierno1df468d2018-07-06 14:25:16 +02002849 if disk_list:
Gulsum Atici26f73662022-10-27 15:18:27 +03002850 # Prepare disks
2851 self._prepare_disk_for_vminstance(
2852 name=name,
2853 existing_vim_volumes=existing_vim_volumes,
2854 created_items=created_items,
Luis Vega25bc6382023-10-05 23:22:04 +00002855 storage_av_zone=storage_av_zone,
Gulsum Atici13d02322022-11-18 00:10:15 +03002856 block_device_mapping=block_device_mapping,
Gulsum Atici26f73662022-10-27 15:18:27 +03002857 disk_list=disk_list,
2858 )
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002859
2860 if affinity_group_list:
2861 # Only first id on the list will be used. Openstack restriction
2862 server_group_id = affinity_group_list[0]["affinity_group_id"]
2863 scheduller_hints["group"] = server_group_id
2864
sousaedu80135b92021-02-17 15:05:18 +01002865 self.logger.debug(
2866 "nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
2867 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002868 "block_device_mapping={}, server_group={})".format(
sousaedu80135b92021-02-17 15:05:18 +01002869 name,
2870 image_id,
2871 flavor_id,
2872 net_list_vim,
2873 self.config.get("security_groups"),
Luis Vegaafe8df22023-12-01 01:02:12 +00002874 self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002875 self.config.get("keypair"),
2876 userdata,
2877 config_drive,
2878 block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002879 server_group_id,
sousaedu80135b92021-02-17 15:05:18 +01002880 )
2881 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002882 # Create VM
sousaedu80135b92021-02-17 15:05:18 +01002883 server = self.nova.servers.create(
aticigcf14bb12022-05-19 13:03:17 +03002884 name=name,
2885 image=image_id,
2886 flavor=flavor_id,
sousaedu80135b92021-02-17 15:05:18 +01002887 nics=net_list_vim,
2888 security_groups=self.config.get("security_groups"),
2889 # TODO remove security_groups in future versions. Already at neutron port
Luis Vegaafe8df22023-12-01 01:02:12 +00002890 availability_zone=self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002891 key_name=self.config.get("keypair"),
2892 userdata=userdata,
2893 config_drive=config_drive,
2894 block_device_mapping=block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002895 scheduler_hints=scheduller_hints,
Gulsum Atici26f73662022-10-27 15:18:27 +03002896 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002897
tierno326fd5e2018-02-22 11:58:59 +01002898 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002899
Gulsum Atici26f73662022-10-27 15:18:27 +03002900 self._update_port_security_for_vminstance(no_secured_ports, server)
bravof7a1f5252020-10-20 10:27:42 -03002901
Gulsum Atici26f73662022-10-27 15:18:27 +03002902 self._prepare_external_network_for_vminstance(
2903 external_network=external_network,
2904 server=server,
2905 created_items=created_items,
2906 vm_start_time=vm_start_time,
2907 )
montesmoreno2a1fc4e2017-01-09 16:46:04 +00002908
tierno98e909c2017-10-14 13:27:03 +02002909 return server.id, created_items
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002910
2911 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02002912 server_id = None
2913 if server:
2914 server_id = server.id
sousaedu80135b92021-02-17 15:05:18 +01002915
tierno98e909c2017-10-14 13:27:03 +02002916 try:
aticig2f4ab6c2022-09-03 18:15:20 +03002917 created_items = self.remove_keep_tag_from_persistent_volumes(
2918 created_items
2919 )
2920
tierno98e909c2017-10-14 13:27:03 +02002921 self.delete_vminstance(server_id, created_items)
Gulsum Atici26f73662022-10-27 15:18:27 +03002922
tierno98e909c2017-10-14 13:27:03 +02002923 except Exception as e2:
2924 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002925
tiernoae4a8d12016-07-08 12:30:39 +02002926 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01002927
aticig2f4ab6c2022-09-03 18:15:20 +03002928 @staticmethod
2929 def remove_keep_tag_from_persistent_volumes(created_items: Dict) -> Dict:
2930 """Removes the keep flag from persistent volumes. So, those volumes could be removed.
2931
2932 Args:
2933 created_items (dict): All created items belongs to VM
2934
2935 Returns:
2936 updated_created_items (dict): Dict which does not include keep flag for volumes.
2937
2938 """
2939 return {
2940 key.replace(":keep", ""): value for (key, value) in created_items.items()
2941 }
2942
tierno1ec592d2020-06-16 15:29:47 +00002943 def get_vminstance(self, vm_id):
2944 """Returns the VM instance information from VIM"""
vegallc53829d2023-06-01 00:47:44 -05002945 return self._find_nova_server(vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02002946
gatici335a06a2023-07-26 00:34:04 +03002947 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00002948 def get_vminstance_console(self, vm_id, console_type="vnc"):
2949 """
tierno7edb6752016-03-21 17:37:52 +01002950 Get a console for the virtual machine
2951 Params:
2952 vm_id: uuid of the VM
2953 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002954 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01002955 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02002956 Returns dict with the console parameters:
2957 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002958 server: usually ip address
2959 port: the http, ssh, ... port
2960 suffix: extra text, e.g. the http path and query string
tierno1ec592d2020-06-16 15:29:47 +00002961 """
tiernoae4a8d12016-07-08 12:30:39 +02002962 self.logger.debug("Getting VM CONSOLE from VIM")
gatici335a06a2023-07-26 00:34:04 +03002963 self._reload_connection()
2964 server = self.nova.servers.find(id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01002965
gatici335a06a2023-07-26 00:34:04 +03002966 if console_type is None or console_type == "novnc":
2967 console_dict = server.get_vnc_console("novnc")
2968 elif console_type == "xvpvnc":
2969 console_dict = server.get_vnc_console(console_type)
2970 elif console_type == "rdp-html5":
2971 console_dict = server.get_rdp_console(console_type)
2972 elif console_type == "spice-html5":
2973 console_dict = server.get_spice_console(console_type)
2974 else:
2975 raise vimconn.VimConnException(
2976 "console type '{}' not allowed".format(console_type),
2977 http_code=vimconn.HTTP_Bad_Request,
2978 )
sousaedu80135b92021-02-17 15:05:18 +01002979
gatici335a06a2023-07-26 00:34:04 +03002980 console_dict1 = console_dict.get("console")
2981
2982 if console_dict1:
2983 console_url = console_dict1.get("url")
2984
2985 if console_url:
2986 # parse console_url
2987 protocol_index = console_url.find("//")
2988 suffix_index = (
2989 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
2990 )
2991 port_index = (
2992 console_url[protocol_index + 2 : suffix_index].find(":")
2993 + protocol_index
2994 + 2
sousaedu80135b92021-02-17 15:05:18 +01002995 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002996
gatici335a06a2023-07-26 00:34:04 +03002997 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
2998 return (
2999 -vimconn.HTTP_Internal_Server_Error,
3000 "Unexpected response from VIM",
sousaedu80135b92021-02-17 15:05:18 +01003001 )
3002
gatici335a06a2023-07-26 00:34:04 +03003003 console_dict = {
3004 "protocol": console_url[0:protocol_index],
3005 "server": console_url[protocol_index + 2 : port_index],
3006 "port": console_url[port_index:suffix_index],
3007 "suffix": console_url[suffix_index + 1 :],
3008 }
3009 protocol_index += 2
sousaedu80135b92021-02-17 15:05:18 +01003010
gatici335a06a2023-07-26 00:34:04 +03003011 return console_dict
3012 raise vimconn.VimConnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01003013
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003014 def _delete_ports_by_id_wth_neutron(self, k_id: str) -> None:
3015 """Neutron delete ports by id.
3016 Args:
3017 k_id (str): Port id in the VIM
3018 """
3019 try:
limon878f8692023-07-24 15:53:41 +02003020 self.neutron.delete_port(k_id)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003021
gatici335a06a2023-07-26 00:34:04 +03003022 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3023 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3024 # If there is connection error, raise.
3025 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003026 except Exception as e:
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003027 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3028
vegall364627c2023-03-17 15:09:50 +00003029 def delete_shared_volumes(self, shared_volume_vim_id: str) -> bool:
3030 """Cinder delete volume by id.
3031 Args:
3032 shared_volume_vim_id (str): ID of shared volume in VIM
3033 """
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003034 elapsed_time = 0
vegall364627c2023-03-17 15:09:50 +00003035 try:
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003036 while elapsed_time < server_timeout:
3037 vol_status = self.cinder.volumes.get(shared_volume_vim_id).status
3038 if vol_status == "available":
3039 self.cinder.volumes.delete(shared_volume_vim_id)
3040 return True
vegall364627c2023-03-17 15:09:50 +00003041
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003042 time.sleep(5)
3043 elapsed_time += 5
3044
3045 if elapsed_time >= server_timeout:
3046 raise vimconn.VimConnException(
3047 "Timeout waiting for volume "
3048 + shared_volume_vim_id
3049 + " to be available",
3050 http_code=vimconn.HTTP_Request_Timeout,
3051 )
vegall364627c2023-03-17 15:09:50 +00003052
3053 except Exception as e:
3054 self.logger.error(
3055 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3056 )
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003057 self._format_exception(e)
vegall364627c2023-03-17 15:09:50 +00003058
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003059 def _delete_volumes_by_id_wth_cinder(
3060 self, k: str, k_id: str, volumes_to_hold: list, created_items: dict
3061 ) -> bool:
3062 """Cinder delete volume by id.
3063 Args:
3064 k (str): Full item name in created_items
3065 k_id (str): ID of floating ip in VIM
3066 volumes_to_hold (list): Volumes not to delete
3067 created_items (dict): All created items belongs to VM
3068 """
3069 try:
3070 if k_id in volumes_to_hold:
gatici335a06a2023-07-26 00:34:04 +03003071 return False
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003072
3073 if self.cinder.volumes.get(k_id).status != "available":
3074 return True
3075
3076 else:
3077 self.cinder.volumes.delete(k_id)
3078 created_items[k] = None
3079
gatici335a06a2023-07-26 00:34:04 +03003080 except (cExceptions.ConnectionError, ConnectionError) as e:
3081 self.logger.error(
3082 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3083 )
3084 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003085 except Exception as e:
3086 self.logger.error(
3087 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3088 )
3089
3090 def _delete_floating_ip_by_id(self, k: str, k_id: str, created_items: dict) -> None:
3091 """Neutron delete floating ip by id.
3092 Args:
3093 k (str): Full item name in created_items
3094 k_id (str): ID of floating ip in VIM
3095 created_items (dict): All created items belongs to VM
3096 """
3097 try:
3098 self.neutron.delete_floatingip(k_id)
3099 created_items[k] = None
3100
gatici335a06a2023-07-26 00:34:04 +03003101 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3102 self.logger.error(
3103 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3104 )
3105 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003106 except Exception as e:
3107 self.logger.error(
3108 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3109 )
3110
3111 @staticmethod
3112 def _get_item_name_id(k: str) -> Tuple[str, str]:
3113 k_item, _, k_id = k.partition(":")
3114 return k_item, k_id
3115
3116 def _delete_vm_ports_attached_to_network(self, created_items: dict) -> None:
3117 """Delete VM ports attached to the networks before deleting virtual machine.
3118 Args:
3119 created_items (dict): All created items belongs to VM
3120 """
3121
3122 for k, v in created_items.items():
3123 if not v: # skip already deleted
3124 continue
3125
3126 try:
3127 k_item, k_id = self._get_item_name_id(k)
3128 if k_item == "port":
3129 self._delete_ports_by_id_wth_neutron(k_id)
3130
gatici335a06a2023-07-26 00:34:04 +03003131 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3132 self.logger.error(
3133 "Error deleting port: {}: {}".format(type(e).__name__, e)
3134 )
3135 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003136 except Exception as e:
3137 self.logger.error(
3138 "Error deleting port: {}: {}".format(type(e).__name__, e)
3139 )
3140
3141 def _delete_created_items(
3142 self, created_items: dict, volumes_to_hold: list, keep_waiting: bool
3143 ) -> bool:
3144 """Delete Volumes and floating ip if they exist in created_items."""
3145 for k, v in created_items.items():
3146 if not v: # skip already deleted
3147 continue
3148
3149 try:
3150 k_item, k_id = self._get_item_name_id(k)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003151 if k_item == "volume":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003152 unavailable_vol = self._delete_volumes_by_id_wth_cinder(
3153 k, k_id, volumes_to_hold, created_items
3154 )
3155
3156 if unavailable_vol:
3157 keep_waiting = True
3158
3159 elif k_item == "floating_ip":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003160 self._delete_floating_ip_by_id(k, k_id, created_items)
3161
gatici335a06a2023-07-26 00:34:04 +03003162 except (
3163 cExceptions.ConnectionError,
3164 neExceptions.ConnectionFailed,
3165 ConnectionError,
3166 AttributeError,
3167 TypeError,
3168 ) as e:
3169 self.logger.error("Error deleting {}: {}".format(k, e))
3170 self._format_exception(e)
3171
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003172 except Exception as e:
3173 self.logger.error("Error deleting {}: {}".format(k, e))
3174
3175 return keep_waiting
3176
aticig2f4ab6c2022-09-03 18:15:20 +03003177 @staticmethod
3178 def _extract_items_wth_keep_flag_from_created_items(created_items: dict) -> dict:
3179 """Remove the volumes which has key flag from created_items
3180
3181 Args:
3182 created_items (dict): All created items belongs to VM
3183
3184 Returns:
3185 created_items (dict): Persistent volumes eliminated created_items
3186 """
3187 return {
3188 key: value
3189 for (key, value) in created_items.items()
3190 if len(key.split(":")) == 2
3191 }
3192
gatici335a06a2023-07-26 00:34:04 +03003193 @catch_any_exception
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003194 def delete_vminstance(
3195 self, vm_id: str, created_items: dict = None, volumes_to_hold: list = None
3196 ) -> None:
3197 """Removes a VM instance from VIM. Returns the old identifier.
3198 Args:
3199 vm_id (str): Identifier of VM instance
3200 created_items (dict): All created items belongs to VM
3201 volumes_to_hold (list): Volumes_to_hold
3202 """
tierno1ec592d2020-06-16 15:29:47 +00003203 if created_items is None:
tierno98e909c2017-10-14 13:27:03 +02003204 created_items = {}
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003205 if volumes_to_hold is None:
3206 volumes_to_hold = []
sousaedu80135b92021-02-17 15:05:18 +01003207
tierno7edb6752016-03-21 17:37:52 +01003208 try:
aticig2f4ab6c2022-09-03 18:15:20 +03003209 created_items = self._extract_items_wth_keep_flag_from_created_items(
3210 created_items
3211 )
3212
tierno7edb6752016-03-21 17:37:52 +01003213 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01003214
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003215 # Delete VM ports attached to the networks before the virtual machine
3216 if created_items:
3217 self._delete_vm_ports_attached_to_network(created_items)
montesmoreno0c8def02016-12-22 12:16:23 +00003218
tierno98e909c2017-10-14 13:27:03 +02003219 if vm_id:
3220 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00003221
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003222 # Although having detached, volumes should have in active status before deleting.
3223 # We ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00003224 keep_waiting = True
3225 elapsed_time = 0
sousaedu80135b92021-02-17 15:05:18 +01003226
montesmoreno0c8def02016-12-22 12:16:23 +00003227 while keep_waiting and elapsed_time < volume_timeout:
3228 keep_waiting = False
sousaedu80135b92021-02-17 15:05:18 +01003229
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003230 # Delete volumes and floating IP.
3231 keep_waiting = self._delete_created_items(
3232 created_items, volumes_to_hold, keep_waiting
3233 )
sousaedu80135b92021-02-17 15:05:18 +01003234
montesmoreno0c8def02016-12-22 12:16:23 +00003235 if keep_waiting:
3236 time.sleep(1)
3237 elapsed_time += 1
gatici335a06a2023-07-26 00:34:04 +03003238 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
3239 # If VM does not exist, it does not raise
3240 self.logger.warning(f"Error deleting VM: {vm_id} is not found, {str(e)}")
tierno7edb6752016-03-21 17:37:52 +01003241
tiernoae4a8d12016-07-08 12:30:39 +02003242 def refresh_vms_status(self, vm_list):
tierno1ec592d2020-06-16 15:29:47 +00003243 """Get the status of the virtual machines and their interfaces/ports
sousaedu80135b92021-02-17 15:05:18 +01003244 Params: the list of VM identifiers
3245 Returns a dictionary with:
3246 vm_id: #VIM id of this Virtual Machine
3247 status: #Mandatory. Text with one of:
3248 # DELETED (not found at vim)
3249 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
3250 # OTHER (Vim reported other status not understood)
3251 # ERROR (VIM indicates an ERROR status)
3252 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
3253 # CREATING (on building process), ERROR
3254 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
3255 #
3256 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
3257 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3258 interfaces:
3259 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3260 mac_address: #Text format XX:XX:XX:XX:XX:XX
3261 vim_net_id: #network id where this interface is connected
3262 vim_interface_id: #interface/port VIM id
3263 ip_address: #null, or text with IPv4, IPv6 address
3264 compute_node: #identification of compute node where PF,VF interface is allocated
3265 pci: #PCI address of the NIC that hosts the PF,VF
3266 vlan: #physical VLAN used for VF
tierno1ec592d2020-06-16 15:29:47 +00003267 """
3268 vm_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01003269 self.logger.debug(
3270 "refresh_vms status: Getting tenant VM instance information from VIM"
3271 )
tiernoae4a8d12016-07-08 12:30:39 +02003272 for vm_id in vm_list:
tierno1ec592d2020-06-16 15:29:47 +00003273 vm = {}
sousaedu80135b92021-02-17 15:05:18 +01003274
tiernoae4a8d12016-07-08 12:30:39 +02003275 try:
3276 vm_vim = self.get_vminstance(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003277
3278 if vm_vim["status"] in vmStatus2manoFormat:
3279 vm["status"] = vmStatus2manoFormat[vm_vim["status"]]
tierno7edb6752016-03-21 17:37:52 +01003280 else:
sousaedu80135b92021-02-17 15:05:18 +01003281 vm["status"] = "OTHER"
3282 vm["error_msg"] = "VIM status reported " + vm_vim["status"]
3283
tierno70eeb182020-10-19 16:38:00 +00003284 vm_vim.pop("OS-EXT-SRV-ATTR:user_data", None)
3285 vm_vim.pop("user_data", None)
sousaedu80135b92021-02-17 15:05:18 +01003286 vm["vim_info"] = self.serialize(vm_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003287
tiernoae4a8d12016-07-08 12:30:39 +02003288 vm["interfaces"] = []
sousaedu80135b92021-02-17 15:05:18 +01003289 if vm_vim.get("fault"):
3290 vm["error_msg"] = str(vm_vim["fault"])
3291
tierno1ec592d2020-06-16 15:29:47 +00003292 # get interfaces
tierno7edb6752016-03-21 17:37:52 +01003293 try:
tiernoae4a8d12016-07-08 12:30:39 +02003294 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02003295 port_dict = self.neutron.list_ports(device_id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003296
tiernoae4a8d12016-07-08 12:30:39 +02003297 for port in port_dict["ports"]:
tierno1ec592d2020-06-16 15:29:47 +00003298 interface = {}
sousaedu80135b92021-02-17 15:05:18 +01003299 interface["vim_info"] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02003300 interface["mac_address"] = port.get("mac_address")
3301 interface["vim_net_id"] = port["network_id"]
3302 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003303 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003304 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003305
3306 if vm_vim.get("OS-EXT-SRV-ATTR:host"):
3307 interface["compute_node"] = vm_vim["OS-EXT-SRV-ATTR:host"]
3308
tierno867ffe92017-03-27 12:50:34 +02003309 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04003310
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003311 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003312 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003313 if port.get("binding:profile"):
3314 if port["binding:profile"].get("pci_slot"):
tierno1ec592d2020-06-16 15:29:47 +00003315 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting
3316 # the slot to 0x00
Mike Marchetti5b9da422017-05-02 15:35:47 -04003317 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
3318 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
sousaedu80135b92021-02-17 15:05:18 +01003319 pci = port["binding:profile"]["pci_slot"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04003320 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
3321 interface["pci"] = pci
sousaedu80135b92021-02-17 15:05:18 +01003322
tierno867ffe92017-03-27 12:50:34 +02003323 interface["vlan"] = None
sousaedu80135b92021-02-17 15:05:18 +01003324
3325 if port.get("binding:vif_details"):
3326 interface["vlan"] = port["binding:vif_details"].get("vlan")
3327
tierno1dfe9932020-06-18 08:50:10 +00003328 # Get vlan from network in case not present in port for those old openstacks and cases where
3329 # it is needed vlan at PT
3330 if not interface["vlan"]:
3331 # if network is of type vlan and port is of type direct (sr-iov) then set vlan id
3332 network = self.neutron.show_network(port["network_id"])
sousaedu80135b92021-02-17 15:05:18 +01003333
3334 if (
3335 network["network"].get("provider:network_type")
3336 == "vlan"
3337 ):
tierno1dfe9932020-06-18 08:50:10 +00003338 # and port.get("binding:vnic_type") in ("direct", "direct-physical"):
sousaedu80135b92021-02-17 15:05:18 +01003339 interface["vlan"] = network["network"].get(
3340 "provider:segmentation_id"
3341 )
3342
tierno1ec592d2020-06-16 15:29:47 +00003343 ips = []
3344 # look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02003345 try:
sousaedu80135b92021-02-17 15:05:18 +01003346 floating_ip_dict = self.neutron.list_floatingips(
3347 port_id=port["id"]
3348 )
3349
tiernob42fd9b2018-06-20 10:44:32 +02003350 if floating_ip_dict.get("floatingips"):
sousaedu80135b92021-02-17 15:05:18 +01003351 ips.append(
3352 floating_ip_dict["floatingips"][0].get(
3353 "floating_ip_address"
3354 )
3355 )
tiernob42fd9b2018-06-20 10:44:32 +02003356 except Exception:
3357 pass
tierno7edb6752016-03-21 17:37:52 +01003358
tiernoae4a8d12016-07-08 12:30:39 +02003359 for subnet in port["fixed_ips"]:
3360 ips.append(subnet["ip_address"])
sousaedu80135b92021-02-17 15:05:18 +01003361
tiernoae4a8d12016-07-08 12:30:39 +02003362 interface["ip_address"] = ";".join(ips)
3363 vm["interfaces"].append(interface)
3364 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01003365 self.logger.error(
3366 "Error getting vm interface information {}: {}".format(
3367 type(e).__name__, e
3368 ),
3369 exc_info=True,
3370 )
tierno72774862020-05-04 11:44:15 +00003371 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003372 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003373 vm["status"] = "DELETED"
3374 vm["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00003375 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003376 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003377 vm["status"] = "VIM_ERROR"
3378 vm["error_msg"] = str(e)
3379
tiernoae4a8d12016-07-08 12:30:39 +02003380 vm_dict[vm_id] = vm
sousaedu80135b92021-02-17 15:05:18 +01003381
tiernoae4a8d12016-07-08 12:30:39 +02003382 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003383
gatici335a06a2023-07-26 00:34:04 +03003384 @catch_any_exception
tierno98e909c2017-10-14 13:27:03 +02003385 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno1ec592d2020-06-16 15:29:47 +00003386 """Send and action over a VM instance from VIM
Gulsum Atici21c55d62023-02-02 20:41:00 +03003387 Returns None or the console dict if the action was successfully sent to the VIM
3388 """
tiernoae4a8d12016-07-08 12:30:39 +02003389 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
gatici335a06a2023-07-26 00:34:04 +03003390 self._reload_connection()
3391 server = self.nova.servers.find(id=vm_id)
3392 if "start" in action_dict:
3393 if action_dict["start"] == "rebuild":
3394 server.rebuild()
3395 else:
3396 if server.status == "PAUSED":
3397 server.unpause()
3398 elif server.status == "SUSPENDED":
3399 server.resume()
3400 elif server.status == "SHUTOFF":
3401 server.start()
tierno7edb6752016-03-21 17:37:52 +01003402 else:
gatici335a06a2023-07-26 00:34:04 +03003403 self.logger.debug(
3404 "ERROR : Instance is not in SHUTOFF/PAUSE/SUSPEND state"
3405 )
k4.rahul78f474e2022-05-02 15:47:57 +00003406 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03003407 "Cannot 'start' instance while it is in active state",
k4.rahul78f474e2022-05-02 15:47:57 +00003408 http_code=vimconn.HTTP_Bad_Request,
3409 )
gatici335a06a2023-07-26 00:34:04 +03003410 elif "pause" in action_dict:
3411 server.pause()
3412 elif "resume" in action_dict:
3413 server.resume()
3414 elif "shutoff" in action_dict or "shutdown" in action_dict:
3415 self.logger.debug("server status %s", server.status)
3416 if server.status == "ACTIVE":
3417 server.stop()
3418 else:
3419 self.logger.debug("ERROR: VM is not in Active state")
3420 raise vimconn.VimConnException(
3421 "VM is not in active state, stop operation is not allowed",
3422 http_code=vimconn.HTTP_Bad_Request,
3423 )
3424 elif "forceOff" in action_dict:
3425 server.stop() # TODO
3426 elif "terminate" in action_dict:
3427 server.delete()
3428 elif "createImage" in action_dict:
3429 server.create_image()
3430 # "path":path_schema,
3431 # "description":description_schema,
3432 # "name":name_schema,
3433 # "metadata":metadata_schema,
3434 # "imageRef": id_schema,
3435 # "disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
3436 elif "rebuild" in action_dict:
3437 server.rebuild(server.image["id"])
3438 elif "reboot" in action_dict:
3439 server.reboot() # reboot_type="SOFT"
3440 elif "console" in action_dict:
3441 console_type = action_dict["console"]
sousaedu80135b92021-02-17 15:05:18 +01003442
gatici335a06a2023-07-26 00:34:04 +03003443 if console_type is None or console_type == "novnc":
3444 console_dict = server.get_vnc_console("novnc")
3445 elif console_type == "xvpvnc":
3446 console_dict = server.get_vnc_console(console_type)
3447 elif console_type == "rdp-html5":
3448 console_dict = server.get_rdp_console(console_type)
3449 elif console_type == "spice-html5":
3450 console_dict = server.get_spice_console(console_type)
3451 else:
3452 raise vimconn.VimConnException(
3453 "console type '{}' not allowed".format(console_type),
3454 http_code=vimconn.HTTP_Bad_Request,
3455 )
sousaedu80135b92021-02-17 15:05:18 +01003456
gatici335a06a2023-07-26 00:34:04 +03003457 try:
3458 console_url = console_dict["console"]["url"]
3459 # parse console_url
3460 protocol_index = console_url.find("//")
3461 suffix_index = (
3462 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3463 )
3464 port_index = (
3465 console_url[protocol_index + 2 : suffix_index].find(":")
3466 + protocol_index
3467 + 2
3468 )
sousaedu80135b92021-02-17 15:05:18 +01003469
gatici335a06a2023-07-26 00:34:04 +03003470 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
sousaedu80135b92021-02-17 15:05:18 +01003471 raise vimconn.VimConnException(
3472 "Unexpected response from VIM " + str(console_dict)
3473 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003474
gatici335a06a2023-07-26 00:34:04 +03003475 console_dict2 = {
3476 "protocol": console_url[0:protocol_index],
3477 "server": console_url[protocol_index + 2 : port_index],
3478 "port": int(console_url[port_index + 1 : suffix_index]),
3479 "suffix": console_url[suffix_index + 1 :],
3480 }
3481
3482 return console_dict2
3483 except Exception:
3484 raise vimconn.VimConnException(
3485 "Unexpected response from VIM " + str(console_dict)
3486 )
3487
3488 return None
tiernoae4a8d12016-07-08 12:30:39 +02003489
tierno1ec592d2020-06-16 15:29:47 +00003490 # ###### VIO Specific Changes #########
garciadeblasebd66722019-01-31 16:01:31 +00003491 def _generate_vlanID(self):
kate721d79b2017-06-24 04:21:38 -07003492 """
sousaedu80135b92021-02-17 15:05:18 +01003493 Method to get unused vlanID
kate721d79b2017-06-24 04:21:38 -07003494 Args:
3495 None
3496 Returns:
3497 vlanID
3498 """
tierno1ec592d2020-06-16 15:29:47 +00003499 # Get used VLAN IDs
kate721d79b2017-06-24 04:21:38 -07003500 usedVlanIDs = []
3501 networks = self.get_network_list()
sousaedu80135b92021-02-17 15:05:18 +01003502
kate721d79b2017-06-24 04:21:38 -07003503 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003504 if net.get("provider:segmentation_id"):
3505 usedVlanIDs.append(net.get("provider:segmentation_id"))
3506
kate721d79b2017-06-24 04:21:38 -07003507 used_vlanIDs = set(usedVlanIDs)
3508
tierno1ec592d2020-06-16 15:29:47 +00003509 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003510 for vlanID_range in self.config.get("dataplane_net_vlan_range"):
kate721d79b2017-06-24 04:21:38 -07003511 try:
sousaedu80135b92021-02-17 15:05:18 +01003512 start_vlanid, end_vlanid = map(
3513 int, vlanID_range.replace(" ", "").split("-")
3514 )
3515
tierno7d782ef2019-10-04 12:56:31 +00003516 for vlanID in range(start_vlanid, end_vlanid + 1):
kate721d79b2017-06-24 04:21:38 -07003517 if vlanID not in used_vlanIDs:
3518 return vlanID
3519 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003520 raise vimconn.VimConnException(
3521 "Exception {} occurred while generating VLAN ID.".format(exp)
3522 )
kate721d79b2017-06-24 04:21:38 -07003523 else:
tierno1ec592d2020-06-16 15:29:47 +00003524 raise vimconn.VimConnConflictException(
3525 "Unable to create the SRIOV VLAN network. All given Vlan IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003526 self.config.get("dataplane_net_vlan_range")
3527 )
3528 )
kate721d79b2017-06-24 04:21:38 -07003529
garciadeblasebd66722019-01-31 16:01:31 +00003530 def _generate_multisegment_vlanID(self):
3531 """
sousaedu80135b92021-02-17 15:05:18 +01003532 Method to get unused vlanID
3533 Args:
3534 None
3535 Returns:
3536 vlanID
garciadeblasebd66722019-01-31 16:01:31 +00003537 """
tierno6869ae72020-01-09 17:37:34 +00003538 # Get used VLAN IDs
garciadeblasebd66722019-01-31 16:01:31 +00003539 usedVlanIDs = []
3540 networks = self.get_network_list()
3541 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003542 if net.get("provider:network_type") == "vlan" and net.get(
3543 "provider:segmentation_id"
3544 ):
3545 usedVlanIDs.append(net.get("provider:segmentation_id"))
3546 elif net.get("segments"):
3547 for segment in net.get("segments"):
3548 if segment.get("provider:network_type") == "vlan" and segment.get(
3549 "provider:segmentation_id"
3550 ):
3551 usedVlanIDs.append(segment.get("provider:segmentation_id"))
3552
garciadeblasebd66722019-01-31 16:01:31 +00003553 used_vlanIDs = set(usedVlanIDs)
3554
tierno6869ae72020-01-09 17:37:34 +00003555 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003556 for vlanID_range in self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +00003557 try:
sousaedu80135b92021-02-17 15:05:18 +01003558 start_vlanid, end_vlanid = map(
3559 int, vlanID_range.replace(" ", "").split("-")
3560 )
3561
tierno7d782ef2019-10-04 12:56:31 +00003562 for vlanID in range(start_vlanid, end_vlanid + 1):
garciadeblasebd66722019-01-31 16:01:31 +00003563 if vlanID not in used_vlanIDs:
3564 return vlanID
3565 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003566 raise vimconn.VimConnException(
3567 "Exception {} occurred while generating VLAN ID.".format(exp)
3568 )
garciadeblasebd66722019-01-31 16:01:31 +00003569 else:
tierno1ec592d2020-06-16 15:29:47 +00003570 raise vimconn.VimConnConflictException(
3571 "Unable to create the VLAN segment. All VLAN IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003572 self.config.get("multisegment_vlan_range")
3573 )
3574 )
garciadeblasebd66722019-01-31 16:01:31 +00003575
3576 def _validate_vlan_ranges(self, input_vlan_range, text_vlan_range):
kate721d79b2017-06-24 04:21:38 -07003577 """
3578 Method to validate user given vlanID ranges
3579 Args: None
3580 Returns: None
3581 """
garciadeblasebd66722019-01-31 16:01:31 +00003582 for vlanID_range in input_vlan_range:
kate721d79b2017-06-24 04:21:38 -07003583 vlan_range = vlanID_range.replace(" ", "")
tierno1ec592d2020-06-16 15:29:47 +00003584 # validate format
sousaedu80135b92021-02-17 15:05:18 +01003585 vlanID_pattern = r"(\d)*-(\d)*$"
kate721d79b2017-06-24 04:21:38 -07003586 match_obj = re.match(vlanID_pattern, vlan_range)
3587 if not match_obj:
tierno1ec592d2020-06-16 15:29:47 +00003588 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003589 "Invalid VLAN range for {}: {}.You must provide "
3590 "'{}' in format [start_ID - end_ID].".format(
3591 text_vlan_range, vlanID_range, text_vlan_range
3592 )
3593 )
kate721d79b2017-06-24 04:21:38 -07003594
tierno1ec592d2020-06-16 15:29:47 +00003595 start_vlanid, end_vlanid = map(int, vlan_range.split("-"))
3596 if start_vlanid <= 0:
3597 raise vimconn.VimConnConflictException(
3598 "Invalid VLAN range for {}: {}. Start ID can not be zero. For VLAN "
sousaedu80135b92021-02-17 15:05:18 +01003599 "networks valid IDs are 1 to 4094 ".format(
3600 text_vlan_range, vlanID_range
3601 )
3602 )
3603
tierno1ec592d2020-06-16 15:29:47 +00003604 if end_vlanid > 4094:
3605 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003606 "Invalid VLAN range for {}: {}. End VLAN ID can not be "
3607 "greater than 4094. For VLAN networks valid IDs are 1 to 4094 ".format(
3608 text_vlan_range, vlanID_range
3609 )
3610 )
kate721d79b2017-06-24 04:21:38 -07003611
3612 if start_vlanid > end_vlanid:
tierno1ec592d2020-06-16 15:29:47 +00003613 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003614 "Invalid VLAN range for {}: {}. You must provide '{}'"
3615 " in format start_ID - end_ID and start_ID < end_ID ".format(
3616 text_vlan_range, vlanID_range, text_vlan_range
3617 )
3618 )
kate721d79b2017-06-24 04:21:38 -07003619
tierno7edb6752016-03-21 17:37:52 +01003620 def get_hosts_info(self):
tierno1ec592d2020-06-16 15:29:47 +00003621 """Get the information of deployed hosts
3622 Returns the hosts content"""
tierno7edb6752016-03-21 17:37:52 +01003623 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003624 print("osconnector: Getting Host info from VIM")
sousaedu80135b92021-02-17 15:05:18 +01003625
tierno7edb6752016-03-21 17:37:52 +01003626 try:
tierno1ec592d2020-06-16 15:29:47 +00003627 h_list = []
tierno7edb6752016-03-21 17:37:52 +01003628 self._reload_connection()
3629 hypervisors = self.nova.hypervisors.list()
sousaedu80135b92021-02-17 15:05:18 +01003630
tierno7edb6752016-03-21 17:37:52 +01003631 for hype in hypervisors:
tierno1ec592d2020-06-16 15:29:47 +00003632 h_list.append(hype.to_dict())
sousaedu80135b92021-02-17 15:05:18 +01003633
tierno1ec592d2020-06-16 15:29:47 +00003634 return 1, {"hosts": h_list}
tierno7edb6752016-03-21 17:37:52 +01003635 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003636 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003637 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003638 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003639 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003640 error_text = (
3641 type(e).__name__
3642 + ": "
3643 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3644 )
3645
tierno1ec592d2020-06-16 15:29:47 +00003646 # TODO insert exception vimconn.HTTP_Unauthorized
3647 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003648 self.logger.debug("get_hosts_info " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003649
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003650 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003651
3652 def get_hosts(self, vim_tenant):
tierno1ec592d2020-06-16 15:29:47 +00003653 """Get the hosts and deployed instances
3654 Returns the hosts content"""
tierno7edb6752016-03-21 17:37:52 +01003655 r, hype_dict = self.get_hosts_info()
sousaedu80135b92021-02-17 15:05:18 +01003656
tierno1ec592d2020-06-16 15:29:47 +00003657 if r < 0:
tierno7edb6752016-03-21 17:37:52 +01003658 return r, hype_dict
sousaedu80135b92021-02-17 15:05:18 +01003659
tierno7edb6752016-03-21 17:37:52 +01003660 hypervisors = hype_dict["hosts"]
sousaedu80135b92021-02-17 15:05:18 +01003661
tierno7edb6752016-03-21 17:37:52 +01003662 try:
3663 servers = self.nova.servers.list()
3664 for hype in hypervisors:
3665 for server in servers:
sousaedu80135b92021-02-17 15:05:18 +01003666 if (
3667 server.to_dict()["OS-EXT-SRV-ATTR:hypervisor_hostname"]
3668 == hype["hypervisor_hostname"]
3669 ):
3670 if "vm" in hype:
3671 hype["vm"].append(server.id)
tierno7edb6752016-03-21 17:37:52 +01003672 else:
sousaedu80135b92021-02-17 15:05:18 +01003673 hype["vm"] = [server.id]
3674
tierno7edb6752016-03-21 17:37:52 +01003675 return 1, hype_dict
3676 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003677 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003678 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003679 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003680 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003681 error_text = (
3682 type(e).__name__
3683 + ": "
3684 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3685 )
3686
tierno1ec592d2020-06-16 15:29:47 +00003687 # TODO insert exception vimconn.HTTP_Unauthorized
3688 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003689 self.logger.debug("get_hosts " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003690
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003691 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003692
Lovejeet Singhdf486552023-05-09 22:41:09 +05303693 def new_classification(self, name, ctype, definition):
3694 self.logger.debug(
3695 "Adding a new (Traffic) Classification to VIM, named %s", name
3696 )
3697
3698 try:
3699 new_class = None
3700 self._reload_connection()
3701
3702 if ctype not in supportedClassificationTypes:
3703 raise vimconn.VimConnNotSupportedException(
3704 "OpenStack VIM connector does not support provided "
3705 "Classification Type {}, supported ones are: {}".format(
3706 ctype, supportedClassificationTypes
3707 )
3708 )
3709
3710 if not self._validate_classification(ctype, definition):
3711 raise vimconn.VimConnException(
3712 "Incorrect Classification definition for the type specified."
3713 )
3714
3715 classification_dict = definition
3716 classification_dict["name"] = name
3717
3718 self.logger.info(
3719 "Adding a new (Traffic) Classification to VIM, named {} and {}.".format(
3720 name, classification_dict
3721 )
3722 )
3723 new_class = self.neutron.create_sfc_flow_classifier(
3724 {"flow_classifier": classification_dict}
3725 )
3726
3727 return new_class["flow_classifier"]["id"]
3728 except (
3729 neExceptions.ConnectionFailed,
3730 ksExceptions.ClientException,
3731 neExceptions.NeutronException,
3732 ConnectionError,
3733 ) as e:
3734 self.logger.error("Creation of Classification failed.")
3735 self._format_exception(e)
3736
3737 def get_classification(self, class_id):
3738 self.logger.debug(" Getting Classification %s from VIM", class_id)
3739 filter_dict = {"id": class_id}
3740 class_list = self.get_classification_list(filter_dict)
3741
3742 if len(class_list) == 0:
3743 raise vimconn.VimConnNotFoundException(
3744 "Classification '{}' not found".format(class_id)
3745 )
3746 elif len(class_list) > 1:
3747 raise vimconn.VimConnConflictException(
3748 "Found more than one Classification with this criteria"
3749 )
3750
3751 classification = class_list[0]
3752
3753 return classification
3754
3755 def get_classification_list(self, filter_dict={}):
3756 self.logger.debug(
3757 "Getting Classifications from VIM filter: '%s'", str(filter_dict)
3758 )
3759
3760 try:
3761 filter_dict_os = filter_dict.copy()
3762 self._reload_connection()
3763
3764 if self.api_version3 and "tenant_id" in filter_dict_os:
3765 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3766
3767 classification_dict = self.neutron.list_sfc_flow_classifiers(
3768 **filter_dict_os
3769 )
3770 classification_list = classification_dict["flow_classifiers"]
3771 self.__classification_os2mano(classification_list)
3772
3773 return classification_list
3774 except (
3775 neExceptions.ConnectionFailed,
3776 ksExceptions.ClientException,
3777 neExceptions.NeutronException,
3778 ConnectionError,
3779 ) as e:
3780 self._format_exception(e)
3781
3782 def delete_classification(self, class_id):
3783 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
3784
3785 try:
3786 self._reload_connection()
3787 self.neutron.delete_sfc_flow_classifier(class_id)
3788
3789 return class_id
3790 except (
3791 neExceptions.ConnectionFailed,
3792 neExceptions.NeutronException,
3793 ksExceptions.ClientException,
3794 neExceptions.NeutronException,
3795 ConnectionError,
3796 ) as e:
3797 self._format_exception(e)
3798
3799 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
3800 self.logger.debug(
3801 "Adding a new Service Function Instance to VIM, named '%s'", name
3802 )
3803
3804 try:
3805 new_sfi = None
3806 self._reload_connection()
3807 correlation = None
3808
3809 if sfc_encap:
3810 correlation = "nsh"
3811
3812 if len(ingress_ports) != 1:
3813 raise vimconn.VimConnNotSupportedException(
3814 "OpenStack VIM connector can only have 1 ingress port per SFI"
3815 )
3816
3817 if len(egress_ports) != 1:
3818 raise vimconn.VimConnNotSupportedException(
3819 "OpenStack VIM connector can only have 1 egress port per SFI"
3820 )
3821
3822 sfi_dict = {
3823 "name": name,
3824 "ingress": ingress_ports[0],
3825 "egress": egress_ports[0],
3826 "service_function_parameters": {"correlation": correlation},
3827 }
3828 self.logger.info("Adding a new SFI to VIM, {}.".format(sfi_dict))
3829 new_sfi = self.neutron.create_sfc_port_pair({"port_pair": sfi_dict})
3830
3831 return new_sfi["port_pair"]["id"]
3832 except (
3833 neExceptions.ConnectionFailed,
3834 ksExceptions.ClientException,
3835 neExceptions.NeutronException,
3836 ConnectionError,
3837 ) as e:
3838 if new_sfi:
3839 try:
3840 self.neutron.delete_sfc_port_pair(new_sfi["port_pair"]["id"])
3841 except Exception:
3842 self.logger.error(
3843 "Creation of Service Function Instance failed, with "
3844 "subsequent deletion failure as well."
3845 )
3846
3847 self._format_exception(e)
3848
3849 def get_sfi(self, sfi_id):
3850 self.logger.debug("Getting Service Function Instance %s from VIM", sfi_id)
3851 filter_dict = {"id": sfi_id}
3852 sfi_list = self.get_sfi_list(filter_dict)
3853
3854 if len(sfi_list) == 0:
3855 raise vimconn.VimConnNotFoundException(
3856 "Service Function Instance '{}' not found".format(sfi_id)
3857 )
3858 elif len(sfi_list) > 1:
3859 raise vimconn.VimConnConflictException(
3860 "Found more than one Service Function Instance with this criteria"
3861 )
3862
3863 sfi = sfi_list[0]
3864
3865 return sfi
3866
3867 def get_sfi_list(self, filter_dict={}):
3868 self.logger.debug(
3869 "Getting Service Function Instances from VIM filter: '%s'", str(filter_dict)
3870 )
3871
3872 try:
3873 self._reload_connection()
3874 filter_dict_os = filter_dict.copy()
3875
3876 if self.api_version3 and "tenant_id" in filter_dict_os:
3877 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3878
3879 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
3880 sfi_list = sfi_dict["port_pairs"]
3881 self.__sfi_os2mano(sfi_list)
3882
3883 return sfi_list
3884 except (
3885 neExceptions.ConnectionFailed,
3886 ksExceptions.ClientException,
3887 neExceptions.NeutronException,
3888 ConnectionError,
3889 ) as e:
3890 self._format_exception(e)
3891
3892 def delete_sfi(self, sfi_id):
3893 self.logger.debug("Deleting Service Function Instance '%s' from VIM", sfi_id)
3894
3895 try:
3896 self._reload_connection()
3897 self.neutron.delete_sfc_port_pair(sfi_id)
3898
3899 return sfi_id
3900 except (
3901 neExceptions.ConnectionFailed,
3902 neExceptions.NeutronException,
3903 ksExceptions.ClientException,
3904 neExceptions.NeutronException,
3905 ConnectionError,
3906 ) as e:
3907 self._format_exception(e)
3908
3909 def new_sf(self, name, sfis, sfc_encap=True):
3910 self.logger.debug("Adding a new Service Function to VIM, named '%s'", name)
3911
3912 new_sf = None
3913
3914 try:
3915 self._reload_connection()
3916
3917 for instance in sfis:
3918 sfi = self.get_sfi(instance)
3919
3920 if sfi.get("sfc_encap") != sfc_encap:
3921 raise vimconn.VimConnNotSupportedException(
3922 "OpenStack VIM connector requires all SFIs of the "
3923 "same SF to share the same SFC Encapsulation"
3924 )
3925
3926 sf_dict = {"name": name, "port_pairs": sfis}
3927
3928 self.logger.info("Adding a new SF to VIM, {}.".format(sf_dict))
3929 new_sf = self.neutron.create_sfc_port_pair_group(
3930 {"port_pair_group": sf_dict}
3931 )
3932
3933 return new_sf["port_pair_group"]["id"]
3934 except (
3935 neExceptions.ConnectionFailed,
3936 ksExceptions.ClientException,
3937 neExceptions.NeutronException,
3938 ConnectionError,
3939 ) as e:
3940 if new_sf:
3941 try:
3942 new_sf_id = new_sf.get("port_pair_group").get("id")
3943 self.neutron.delete_sfc_port_pair_group(new_sf_id)
3944 except Exception:
3945 self.logger.error(
3946 "Creation of Service Function failed, with "
3947 "subsequent deletion failure as well."
3948 )
3949
3950 self._format_exception(e)
3951
3952 def get_sf(self, sf_id):
3953 self.logger.debug("Getting Service Function %s from VIM", sf_id)
3954 filter_dict = {"id": sf_id}
3955 sf_list = self.get_sf_list(filter_dict)
3956
3957 if len(sf_list) == 0:
3958 raise vimconn.VimConnNotFoundException(
3959 "Service Function '{}' not found".format(sf_id)
3960 )
3961 elif len(sf_list) > 1:
3962 raise vimconn.VimConnConflictException(
3963 "Found more than one Service Function with this criteria"
3964 )
3965
3966 sf = sf_list[0]
3967
3968 return sf
3969
3970 def get_sf_list(self, filter_dict={}):
3971 self.logger.debug(
3972 "Getting Service Function from VIM filter: '%s'", str(filter_dict)
3973 )
3974
3975 try:
3976 self._reload_connection()
3977 filter_dict_os = filter_dict.copy()
3978
3979 if self.api_version3 and "tenant_id" in filter_dict_os:
3980 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3981
3982 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
3983 sf_list = sf_dict["port_pair_groups"]
3984 self.__sf_os2mano(sf_list)
3985
3986 return sf_list
3987 except (
3988 neExceptions.ConnectionFailed,
3989 ksExceptions.ClientException,
3990 neExceptions.NeutronException,
3991 ConnectionError,
3992 ) as e:
3993 self._format_exception(e)
3994
3995 def delete_sf(self, sf_id):
3996 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
3997
3998 try:
3999 self._reload_connection()
4000 self.neutron.delete_sfc_port_pair_group(sf_id)
4001
4002 return sf_id
4003 except (
4004 neExceptions.ConnectionFailed,
4005 neExceptions.NeutronException,
4006 ksExceptions.ClientException,
4007 neExceptions.NeutronException,
4008 ConnectionError,
4009 ) as e:
4010 self._format_exception(e)
4011
4012 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
4013 self.logger.debug("Adding a new Service Function Path to VIM, named '%s'", name)
4014
4015 new_sfp = None
4016
4017 try:
4018 self._reload_connection()
4019 # In networking-sfc the MPLS encapsulation is legacy
4020 # should be used when no full SFC Encapsulation is intended
4021 correlation = "mpls"
4022
4023 if sfc_encap:
4024 correlation = "nsh"
4025
4026 sfp_dict = {
4027 "name": name,
4028 "flow_classifiers": classifications,
4029 "port_pair_groups": sfs,
4030 "chain_parameters": {"correlation": correlation},
4031 }
4032
4033 if spi:
4034 sfp_dict["chain_id"] = spi
4035
4036 self.logger.info("Adding a new SFP to VIM, {}.".format(sfp_dict))
4037 new_sfp = self.neutron.create_sfc_port_chain({"port_chain": sfp_dict})
4038
4039 return new_sfp["port_chain"]["id"]
4040 except (
4041 neExceptions.ConnectionFailed,
4042 ksExceptions.ClientException,
4043 neExceptions.NeutronException,
4044 ConnectionError,
4045 ) as e:
4046 if new_sfp:
4047 try:
4048 new_sfp_id = new_sfp.get("port_chain").get("id")
4049 self.neutron.delete_sfc_port_chain(new_sfp_id)
4050 except Exception:
4051 self.logger.error(
4052 "Creation of Service Function Path failed, with "
4053 "subsequent deletion failure as well."
4054 )
4055
4056 self._format_exception(e)
4057
4058 def get_sfp(self, sfp_id):
4059 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
4060
4061 filter_dict = {"id": sfp_id}
4062 sfp_list = self.get_sfp_list(filter_dict)
4063
4064 if len(sfp_list) == 0:
4065 raise vimconn.VimConnNotFoundException(
4066 "Service Function Path '{}' not found".format(sfp_id)
4067 )
4068 elif len(sfp_list) > 1:
4069 raise vimconn.VimConnConflictException(
4070 "Found more than one Service Function Path with this criteria"
4071 )
4072
4073 sfp = sfp_list[0]
4074
4075 return sfp
4076
4077 def get_sfp_list(self, filter_dict={}):
4078 self.logger.debug(
4079 "Getting Service Function Paths from VIM filter: '%s'", str(filter_dict)
4080 )
4081
4082 try:
4083 self._reload_connection()
4084 filter_dict_os = filter_dict.copy()
4085
4086 if self.api_version3 and "tenant_id" in filter_dict_os:
4087 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
4088
4089 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
4090 sfp_list = sfp_dict["port_chains"]
4091 self.__sfp_os2mano(sfp_list)
4092
4093 return sfp_list
4094 except (
4095 neExceptions.ConnectionFailed,
4096 ksExceptions.ClientException,
4097 neExceptions.NeutronException,
4098 ConnectionError,
4099 ) as e:
4100 self._format_exception(e)
4101
4102 def delete_sfp(self, sfp_id):
4103 self.logger.debug("Deleting Service Function Path '%s' from VIM", sfp_id)
4104
4105 try:
4106 self._reload_connection()
4107 self.neutron.delete_sfc_port_chain(sfp_id)
4108
4109 return sfp_id
4110 except (
4111 neExceptions.ConnectionFailed,
4112 neExceptions.NeutronException,
4113 ksExceptions.ClientException,
4114 neExceptions.NeutronException,
4115 ConnectionError,
4116 ) as e:
4117 self._format_exception(e)
4118
4119 def refresh_sfps_status(self, sfp_list):
4120 """Get the status of the service function path
4121 Params: the list of sfp identifiers
4122 Returns a dictionary with:
4123 vm_id: #VIM id of this service function path
4124 status: #Mandatory. Text with one of:
4125 # DELETED (not found at vim)
4126 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4127 # OTHER (Vim reported other status not understood)
4128 # ERROR (VIM indicates an ERROR status)
4129 # ACTIVE,
4130 # CREATING (on building process)
4131 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4132 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)F
4133 """
4134 sfp_dict = {}
4135 self.logger.debug(
4136 "refresh_sfps status: Getting tenant SFP information from VIM"
4137 )
4138
4139 for sfp_id in sfp_list:
4140 sfp = {}
4141
4142 try:
4143 sfp_vim = self.get_sfp(sfp_id)
4144
4145 if sfp_vim["spi"]:
4146 sfp["status"] = vmStatus2manoFormat["ACTIVE"]
4147 else:
4148 sfp["status"] = "OTHER"
4149 sfp["error_msg"] = "VIM status reported " + sfp["status"]
4150
4151 sfp["vim_info"] = self.serialize(sfp_vim)
4152
4153 if sfp_vim.get("fault"):
4154 sfp["error_msg"] = str(sfp_vim["fault"])
4155 except vimconn.VimConnNotFoundException as e:
4156 self.logger.error("Exception getting sfp status: %s", str(e))
4157 sfp["status"] = "DELETED"
4158 sfp["error_msg"] = str(e)
4159 except vimconn.VimConnException as e:
4160 self.logger.error("Exception getting sfp status: %s", str(e))
4161 sfp["status"] = "VIM_ERROR"
4162 sfp["error_msg"] = str(e)
4163
4164 sfp_dict[sfp_id] = sfp
4165
4166 return sfp_dict
4167
4168 def refresh_sfis_status(self, sfi_list):
4169 """Get the status of the service function instances
4170 Params: the list of sfi identifiers
4171 Returns a dictionary with:
4172 vm_id: #VIM id of this service function instance
4173 status: #Mandatory. Text with one of:
4174 # DELETED (not found at vim)
4175 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4176 # OTHER (Vim reported other status not understood)
4177 # ERROR (VIM indicates an ERROR status)
4178 # ACTIVE,
4179 # CREATING (on building process)
4180 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4181 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4182 """
4183 sfi_dict = {}
4184 self.logger.debug(
4185 "refresh_sfis status: Getting tenant sfi information from VIM"
4186 )
4187
4188 for sfi_id in sfi_list:
4189 sfi = {}
4190
4191 try:
4192 sfi_vim = self.get_sfi(sfi_id)
4193
4194 if sfi_vim:
4195 sfi["status"] = vmStatus2manoFormat["ACTIVE"]
4196 else:
4197 sfi["status"] = "OTHER"
4198 sfi["error_msg"] = "VIM status reported " + sfi["status"]
4199
4200 sfi["vim_info"] = self.serialize(sfi_vim)
4201
4202 if sfi_vim.get("fault"):
4203 sfi["error_msg"] = str(sfi_vim["fault"])
4204 except vimconn.VimConnNotFoundException as e:
4205 self.logger.error("Exception getting sfi status: %s", str(e))
4206 sfi["status"] = "DELETED"
4207 sfi["error_msg"] = str(e)
4208 except vimconn.VimConnException as e:
4209 self.logger.error("Exception getting sfi status: %s", str(e))
4210 sfi["status"] = "VIM_ERROR"
4211 sfi["error_msg"] = str(e)
4212
4213 sfi_dict[sfi_id] = sfi
4214
4215 return sfi_dict
4216
4217 def refresh_sfs_status(self, sf_list):
4218 """Get the status of the service functions
4219 Params: the list of sf identifiers
4220 Returns a dictionary with:
4221 vm_id: #VIM id of this service function
4222 status: #Mandatory. Text with one of:
4223 # DELETED (not found at vim)
4224 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4225 # OTHER (Vim reported other status not understood)
4226 # ERROR (VIM indicates an ERROR status)
4227 # ACTIVE,
4228 # CREATING (on building process)
4229 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4230 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4231 """
4232 sf_dict = {}
4233 self.logger.debug("refresh_sfs status: Getting tenant sf information from VIM")
4234
4235 for sf_id in sf_list:
4236 sf = {}
4237
4238 try:
4239 sf_vim = self.get_sf(sf_id)
4240
4241 if sf_vim:
4242 sf["status"] = vmStatus2manoFormat["ACTIVE"]
4243 else:
4244 sf["status"] = "OTHER"
4245 sf["error_msg"] = "VIM status reported " + sf_vim["status"]
4246
4247 sf["vim_info"] = self.serialize(sf_vim)
4248
4249 if sf_vim.get("fault"):
4250 sf["error_msg"] = str(sf_vim["fault"])
4251 except vimconn.VimConnNotFoundException as e:
4252 self.logger.error("Exception getting sf status: %s", str(e))
4253 sf["status"] = "DELETED"
4254 sf["error_msg"] = str(e)
4255 except vimconn.VimConnException as e:
4256 self.logger.error("Exception getting sf status: %s", str(e))
4257 sf["status"] = "VIM_ERROR"
4258 sf["error_msg"] = str(e)
4259
4260 sf_dict[sf_id] = sf
4261
4262 return sf_dict
4263
4264 def refresh_classifications_status(self, classification_list):
4265 """Get the status of the classifications
4266 Params: the list of classification identifiers
4267 Returns a dictionary with:
4268 vm_id: #VIM id of this classifier
4269 status: #Mandatory. Text with one of:
4270 # DELETED (not found at vim)
4271 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4272 # OTHER (Vim reported other status not understood)
4273 # ERROR (VIM indicates an ERROR status)
4274 # ACTIVE,
4275 # CREATING (on building process)
4276 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4277 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4278 """
4279 classification_dict = {}
4280 self.logger.debug(
4281 "refresh_classifications status: Getting tenant classification information from VIM"
4282 )
4283
4284 for classification_id in classification_list:
4285 classification = {}
4286
4287 try:
4288 classification_vim = self.get_classification(classification_id)
4289
4290 if classification_vim:
4291 classification["status"] = vmStatus2manoFormat["ACTIVE"]
4292 else:
4293 classification["status"] = "OTHER"
4294 classification["error_msg"] = (
4295 "VIM status reported " + classification["status"]
4296 )
4297
4298 classification["vim_info"] = self.serialize(classification_vim)
4299
4300 if classification_vim.get("fault"):
4301 classification["error_msg"] = str(classification_vim["fault"])
4302 except vimconn.VimConnNotFoundException as e:
4303 self.logger.error("Exception getting classification status: %s", str(e))
4304 classification["status"] = "DELETED"
4305 classification["error_msg"] = str(e)
4306 except vimconn.VimConnException as e:
4307 self.logger.error("Exception getting classification status: %s", str(e))
4308 classification["status"] = "VIM_ERROR"
4309 classification["error_msg"] = str(e)
4310
4311 classification_dict[classification_id] = classification
4312
4313 return classification_dict
4314
gatici335a06a2023-07-26 00:34:04 +03004315 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004316 def new_affinity_group(self, affinity_group_data):
4317 """Adds a server group to VIM
4318 affinity_group_data contains a dictionary with information, keys:
4319 name: name in VIM for the server group
4320 type: affinity or anti-affinity
4321 scope: Only nfvi-node allowed
4322 Returns the server group identifier"""
4323 self.logger.debug("Adding Server Group '%s'", str(affinity_group_data))
gatici335a06a2023-07-26 00:34:04 +03004324 name = affinity_group_data["name"]
4325 policy = affinity_group_data["type"]
4326 self._reload_connection()
4327 new_server_group = self.nova.server_groups.create(name, policy)
4328 return new_server_group.id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004329
gatici335a06a2023-07-26 00:34:04 +03004330 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004331 def get_affinity_group(self, affinity_group_id):
4332 """Obtain server group details from the VIM. Returns the server group detais as a dict"""
4333 self.logger.debug("Getting flavor '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004334 self._reload_connection()
4335 server_group = self.nova.server_groups.find(id=affinity_group_id)
4336 return server_group.to_dict()
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004337
gatici335a06a2023-07-26 00:34:04 +03004338 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004339 def delete_affinity_group(self, affinity_group_id):
4340 """Deletes a server group from the VIM. Returns the old affinity_group_id"""
4341 self.logger.debug("Getting server group '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004342 self._reload_connection()
4343 self.nova.server_groups.delete(affinity_group_id)
4344 return affinity_group_id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004345
gatici335a06a2023-07-26 00:34:04 +03004346 @catch_any_exception
Patricia Reinoso17852162023-06-15 07:33:04 +00004347 def get_vdu_state(self, vm_id, host_is_required=False) -> list:
4348 """Getting the state of a VDU.
4349 Args:
4350 vm_id (str): ID of an instance
4351 host_is_required (Boolean): If the VIM account is non-admin, host info does not appear in server_dict
4352 and if this is set to True, it raises KeyError.
4353 Returns:
4354 vdu_data (list): VDU details including state, flavor, host_info, AZ
elumalai8658c2c2022-04-28 19:09:31 +05304355 """
4356 self.logger.debug("Getting the status of VM")
4357 self.logger.debug("VIM VM ID %s", vm_id)
gatici335a06a2023-07-26 00:34:04 +03004358 self._reload_connection()
4359 server_dict = self._find_nova_server(vm_id)
4360 srv_attr = "OS-EXT-SRV-ATTR:host"
4361 host_info = (
4362 server_dict[srv_attr] if host_is_required else server_dict.get(srv_attr)
4363 )
4364 vdu_data = [
4365 server_dict["status"],
4366 server_dict["flavor"]["id"],
4367 host_info,
4368 server_dict["OS-EXT-AZ:availability_zone"],
4369 ]
4370 self.logger.debug("vdu_data %s", vdu_data)
4371 return vdu_data
elumalai8658c2c2022-04-28 19:09:31 +05304372
4373 def check_compute_availability(self, host, server_flavor_details):
4374 self._reload_connection()
4375 hypervisor_search = self.nova.hypervisors.search(
4376 hypervisor_match=host, servers=True
4377 )
4378 for hypervisor in hypervisor_search:
4379 hypervisor_id = hypervisor.to_dict()["id"]
4380 hypervisor_details = self.nova.hypervisors.get(hypervisor=hypervisor_id)
4381 hypervisor_dict = hypervisor_details.to_dict()
4382 hypervisor_temp = json.dumps(hypervisor_dict)
4383 hypervisor_json = json.loads(hypervisor_temp)
4384 resources_available = [
4385 hypervisor_json["free_ram_mb"],
4386 hypervisor_json["disk_available_least"],
4387 hypervisor_json["vcpus"] - hypervisor_json["vcpus_used"],
4388 ]
4389 compute_available = all(
4390 x > y for x, y in zip(resources_available, server_flavor_details)
4391 )
4392 if compute_available:
4393 return host
4394
4395 def check_availability_zone(
4396 self, old_az, server_flavor_details, old_host, host=None
4397 ):
4398 self._reload_connection()
4399 az_check = {"zone_check": False, "compute_availability": None}
4400 aggregates_list = self.nova.aggregates.list()
4401 for aggregate in aggregates_list:
4402 aggregate_details = aggregate.to_dict()
4403 aggregate_temp = json.dumps(aggregate_details)
4404 aggregate_json = json.loads(aggregate_temp)
4405 if aggregate_json["availability_zone"] == old_az:
4406 hosts_list = aggregate_json["hosts"]
4407 if host is not None:
4408 if host in hosts_list:
4409 az_check["zone_check"] = True
4410 available_compute_id = self.check_compute_availability(
4411 host, server_flavor_details
4412 )
4413 if available_compute_id is not None:
4414 az_check["compute_availability"] = available_compute_id
4415 else:
4416 for check_host in hosts_list:
4417 if check_host != old_host:
4418 available_compute_id = self.check_compute_availability(
4419 check_host, server_flavor_details
4420 )
4421 if available_compute_id is not None:
4422 az_check["zone_check"] = True
4423 az_check["compute_availability"] = available_compute_id
4424 break
4425 else:
4426 az_check["zone_check"] = True
4427 return az_check
4428
gatici335a06a2023-07-26 00:34:04 +03004429 @catch_any_exception
elumalai8658c2c2022-04-28 19:09:31 +05304430 def migrate_instance(self, vm_id, compute_host=None):
4431 """
4432 Migrate a vdu
4433 param:
4434 vm_id: ID of an instance
4435 compute_host: Host to migrate the vdu to
4436 """
4437 self._reload_connection()
4438 vm_state = False
Patricia Reinoso17852162023-06-15 07:33:04 +00004439 instance_state = self.get_vdu_state(vm_id, host_is_required=True)
elumalai8658c2c2022-04-28 19:09:31 +05304440 server_flavor_id = instance_state[1]
4441 server_hypervisor_name = instance_state[2]
4442 server_availability_zone = instance_state[3]
gatici335a06a2023-07-26 00:34:04 +03004443 server_flavor = self.nova.flavors.find(id=server_flavor_id).to_dict()
4444 server_flavor_details = [
4445 server_flavor["ram"],
4446 server_flavor["disk"],
4447 server_flavor["vcpus"],
4448 ]
4449 if compute_host == server_hypervisor_name:
4450 raise vimconn.VimConnException(
4451 "Unable to migrate instance '{}' to the same host '{}'".format(
4452 vm_id, compute_host
4453 ),
4454 http_code=vimconn.HTTP_Bad_Request,
elumalai8658c2c2022-04-28 19:09:31 +05304455 )
gatici335a06a2023-07-26 00:34:04 +03004456 az_status = self.check_availability_zone(
4457 server_availability_zone,
4458 server_flavor_details,
4459 server_hypervisor_name,
4460 compute_host,
4461 )
4462 availability_zone_check = az_status["zone_check"]
4463 available_compute_id = az_status.get("compute_availability")
elumalai8658c2c2022-04-28 19:09:31 +05304464
gatici335a06a2023-07-26 00:34:04 +03004465 if availability_zone_check is False:
4466 raise vimconn.VimConnException(
4467 "Unable to migrate instance '{}' to a different availability zone".format(
4468 vm_id
4469 ),
4470 http_code=vimconn.HTTP_Bad_Request,
4471 )
4472 if available_compute_id is not None:
4473 # disk_over_commit parameter for live_migrate method is not valid for Nova API version >= 2.25
4474 self.nova.servers.live_migrate(
4475 server=vm_id,
4476 host=available_compute_id,
4477 block_migration=True,
4478 )
4479 state = "MIGRATING"
4480 changed_compute_host = ""
4481 if state == "MIGRATING":
4482 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
4483 changed_compute_host = self.get_vdu_state(vm_id, host_is_required=True)[
4484 2
4485 ]
4486 if vm_state and changed_compute_host == available_compute_id:
4487 self.logger.debug(
4488 "Instance '{}' migrated to the new compute host '{}'".format(
4489 vm_id, changed_compute_host
elumalai8658c2c2022-04-28 19:09:31 +05304490 )
gatici335a06a2023-07-26 00:34:04 +03004491 )
4492 return state, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304493 else:
4494 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03004495 "Migration Failed. Instance '{}' not moved to the new host {}".format(
4496 vm_id, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304497 ),
4498 http_code=vimconn.HTTP_Bad_Request,
4499 )
gatici335a06a2023-07-26 00:34:04 +03004500 else:
4501 raise vimconn.VimConnException(
4502 "Compute '{}' not available or does not have enough resources to migrate the instance".format(
4503 available_compute_id
4504 ),
4505 http_code=vimconn.HTTP_Bad_Request,
4506 )
sritharan29a4c1a2022-05-05 12:15:04 +00004507
gatici335a06a2023-07-26 00:34:04 +03004508 @catch_any_exception
sritharan29a4c1a2022-05-05 12:15:04 +00004509 def resize_instance(self, vm_id, new_flavor_id):
4510 """
4511 For resizing the vm based on the given
4512 flavor details
4513 param:
4514 vm_id : ID of an instance
4515 new_flavor_id : Flavor id to be resized
4516 Return the status of a resized instance
4517 """
4518 self._reload_connection()
4519 self.logger.debug("resize the flavor of an instance")
4520 instance_status, old_flavor_id, compute_host, az = self.get_vdu_state(vm_id)
4521 old_flavor_disk = self.nova.flavors.find(id=old_flavor_id).to_dict()["disk"]
4522 new_flavor_disk = self.nova.flavors.find(id=new_flavor_id).to_dict()["disk"]
gatici335a06a2023-07-26 00:34:04 +03004523 if instance_status == "ACTIVE" or instance_status == "SHUTOFF":
4524 if old_flavor_disk > new_flavor_disk:
sritharan29a4c1a2022-05-05 12:15:04 +00004525 raise nvExceptions.BadRequest(
gatici335a06a2023-07-26 00:34:04 +03004526 400,
4527 message="Server disk resize failed. Resize to lower disk flavor is not allowed",
sritharan29a4c1a2022-05-05 12:15:04 +00004528 )
gatici335a06a2023-07-26 00:34:04 +03004529 else:
4530 self.nova.servers.resize(server=vm_id, flavor=new_flavor_id)
4531 vm_state = self.__wait_for_vm(vm_id, "VERIFY_RESIZE")
4532 if vm_state:
4533 instance_resized_status = self.confirm_resize(vm_id)
4534 return instance_resized_status
4535 else:
4536 raise nvExceptions.BadRequest(
4537 409,
4538 message="Cannot 'resize' vm_state is in ERROR",
4539 )
4540
4541 else:
4542 self.logger.debug("ERROR : Instance is not in ACTIVE or SHUTOFF state")
4543 raise nvExceptions.BadRequest(
4544 409,
4545 message="Cannot 'resize' instance while it is in vm_state resized",
4546 )
sritharan29a4c1a2022-05-05 12:15:04 +00004547
4548 def confirm_resize(self, vm_id):
4549 """
4550 Confirm the resize of an instance
4551 param:
4552 vm_id: ID of an instance
4553 """
4554 self._reload_connection()
4555 self.nova.servers.confirm_resize(server=vm_id)
4556 if self.get_vdu_state(vm_id)[0] == "VERIFY_RESIZE":
4557 self.__wait_for_vm(vm_id, "ACTIVE")
4558 instance_status = self.get_vdu_state(vm_id)[0]
4559 return instance_status
Gulsum Aticid586d892023-02-13 18:40:03 +03004560
4561 def get_monitoring_data(self):
4562 try:
4563 self.logger.debug("Getting servers and ports data from Openstack VIMs.")
4564 self._reload_connection()
4565 all_servers = self.nova.servers.list(detailed=True)
vegallc53829d2023-06-01 00:47:44 -05004566 try:
4567 for server in all_servers:
Luis Vegad6577d82023-07-26 20:49:12 +00004568 if server.flavor.get("original_name"):
4569 server.flavor["id"] = self.nova.flavors.find(
4570 name=server.flavor["original_name"]
4571 ).id
vegallc53829d2023-06-01 00:47:44 -05004572 except nClient.exceptions.NotFound as e:
4573 self.logger.warning(str(e.message))
Gulsum Aticid586d892023-02-13 18:40:03 +03004574 all_ports = self.neutron.list_ports()
4575 return all_servers, all_ports
gatici335a06a2023-07-26 00:34:04 +03004576 except Exception as e:
Gulsum Aticid586d892023-02-13 18:40:03 +03004577 raise vimconn.VimConnException(
4578 f"Exception in monitoring while getting VMs and ports status: {str(e)}"
4579 )