blob: 7b27e251cf386ff046353d5a9bf752d7d02bee2a [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)
tierno1ec592d2020-06-16 15:29:47 +0000330 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River
331 # 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:
sousaedu80135b92021-02-17 15:05:18 +0100335 self.keystone = ksClient_v3.Client(
336 session=sess,
337 endpoint_type=self.endpoint_type,
338 region_name=region_name,
339 )
tiernof716aea2017-06-21 18:01:40 +0200340 else:
sousaedu80135b92021-02-17 15:05:18 +0100341 self.keystone = ksClient_v2.Client(
342 session=sess, endpoint_type=self.endpoint_type
343 )
344
345 self.session["keystone"] = self.keystone
346 # In order to enable microversion functionality an explicit microversion must be specified in "config".
montesmoreno9317d302017-08-16 12:48:23 +0200347 # This implementation approach is due to the warning message in
348 # https://developer.openstack.org/api-guide/compute/microversions.html
349 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
350 # always require an specific microversion.
sousaedu80135b92021-02-17 15:05:18 +0100351 # To be able to use "device role tagging" functionality define "microversion: 2.32" in datacenter config
montesmoreno9317d302017-08-16 12:48:23 +0200352 version = self.config.get("microversion")
sousaedu80135b92021-02-17 15:05:18 +0100353
montesmoreno9317d302017-08-16 12:48:23 +0200354 if not version:
vegallc53829d2023-06-01 00:47:44 -0500355 version = "2.60"
sousaedu80135b92021-02-17 15:05:18 +0100356
tierno1ec592d2020-06-16 15:29:47 +0000357 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River
358 # Titanium cloud and StarlingX
sousaedu80135b92021-02-17 15:05:18 +0100359 self.nova = self.session["nova"] = nClient.Client(
360 str(version),
361 session=sess,
362 endpoint_type=self.endpoint_type,
363 region_name=region_name,
364 )
365 self.neutron = self.session["neutron"] = neClient.Client(
366 "2.0",
367 session=sess,
368 endpoint_type=self.endpoint_type,
369 region_name=region_name,
370 )
Lovejeet Singh778f3cc2023-02-13 16:15:40 +0530371
372 if sess.get_all_version_data(service_type="volumev2"):
373 self.cinder = self.session["cinder"] = cClient.Client(
374 2,
375 session=sess,
376 endpoint_type=self.endpoint_type,
377 region_name=region_name,
378 )
379 else:
380 self.cinder = self.session["cinder"] = cClient.Client(
381 3,
382 session=sess,
383 endpoint_type=self.endpoint_type,
384 region_name=region_name,
385 )
sousaedu80135b92021-02-17 15:05:18 +0100386
tiernoa05b65a2019-02-01 12:30:27 +0000387 try:
sousaedu80135b92021-02-17 15:05:18 +0100388 self.my_tenant_id = self.session["my_tenant_id"] = sess.get_project_id()
tierno1ec592d2020-06-16 15:29:47 +0000389 except Exception:
tiernoa05b65a2019-02-01 12:30:27 +0000390 self.logger.error("Cannot get project_id from session", exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +0100391
kate721d79b2017-06-24 04:21:38 -0700392 if self.endpoint_type == "internalURL":
393 glance_service_id = self.keystone.services.list(name="glance")[0].id
sousaedu80135b92021-02-17 15:05:18 +0100394 glance_endpoint = self.keystone.endpoints.list(
395 glance_service_id, interface="internal"
396 )[0].url
kate721d79b2017-06-24 04:21:38 -0700397 else:
398 glance_endpoint = None
sousaedu80135b92021-02-17 15:05:18 +0100399
400 self.glance = self.session["glance"] = glClient.Client(
401 2, session=sess, endpoint=glance_endpoint
402 )
tiernoa05b65a2019-02-01 12:30:27 +0000403 # using version 1 of glance client in new_image()
sousaedu80135b92021-02-17 15:05:18 +0100404 # self.glancev1 = self.session["glancev1"] = glClient.Client("1", session=sess,
tierno1beea862018-07-11 15:47:37 +0200405 # endpoint=glance_endpoint)
sousaedu80135b92021-02-17 15:05:18 +0100406 self.session["reload_client"] = False
407 self.persistent_info["session"] = self.session
mirabal29356312017-07-27 12:21:22 +0200408 # add availablity zone info inside self.persistent_info
409 self._set_availablity_zones()
sousaedu80135b92021-02-17 15:05:18 +0100410 self.persistent_info["availability_zone"] = self.availability_zone
411 # force to get again security_groups_ids next time they are needed
412 self.security_groups_id = None
ahmadsa95baa272016-11-30 09:14:11 +0500413
tierno7edb6752016-03-21 17:37:52 +0100414 def __net_os2mano(self, net_list_dict):
tierno1ec592d2020-06-16 15:29:47 +0000415 """Transform the net openstack format to mano format
416 net_list_dict can be a list of dict or a single dict"""
tierno7edb6752016-03-21 17:37:52 +0100417 if type(net_list_dict) is dict:
tierno1ec592d2020-06-16 15:29:47 +0000418 net_list_ = (net_list_dict,)
tierno7edb6752016-03-21 17:37:52 +0100419 elif type(net_list_dict) is list:
tierno1ec592d2020-06-16 15:29:47 +0000420 net_list_ = net_list_dict
tierno7edb6752016-03-21 17:37:52 +0100421 else:
422 raise TypeError("param net_list_dict must be a list or a dictionary")
423 for net in net_list_:
sousaedu80135b92021-02-17 15:05:18 +0100424 if net.get("provider:network_type") == "vlan":
425 net["type"] = "data"
tierno7edb6752016-03-21 17:37:52 +0100426 else:
sousaedu80135b92021-02-17 15:05:18 +0100427 net["type"] = "bridge"
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200428
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000429 def __classification_os2mano(self, class_list_dict):
430 """Transform the openstack format (Flow Classifier) to mano format
431 (Classification) class_list_dict can be a list of dict or a single dict
432 """
433 if isinstance(class_list_dict, dict):
434 class_list_ = [class_list_dict]
435 elif isinstance(class_list_dict, list):
436 class_list_ = class_list_dict
437 else:
tierno1ec592d2020-06-16 15:29:47 +0000438 raise TypeError("param class_list_dict must be a list or a dictionary")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000439 for classification in class_list_:
sousaedu80135b92021-02-17 15:05:18 +0100440 id = classification.pop("id")
441 name = classification.pop("name")
442 description = classification.pop("description")
443 project_id = classification.pop("project_id")
444 tenant_id = classification.pop("tenant_id")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000445 original_classification = copy.deepcopy(classification)
446 classification.clear()
sousaedu80135b92021-02-17 15:05:18 +0100447 classification["ctype"] = "legacy_flow_classifier"
448 classification["definition"] = original_classification
449 classification["id"] = id
450 classification["name"] = name
451 classification["description"] = description
452 classification["project_id"] = project_id
453 classification["tenant_id"] = tenant_id
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000454
455 def __sfi_os2mano(self, sfi_list_dict):
456 """Transform the openstack format (Port Pair) to mano format (SFI)
457 sfi_list_dict can be a list of dict or a single dict
458 """
459 if isinstance(sfi_list_dict, dict):
460 sfi_list_ = [sfi_list_dict]
461 elif isinstance(sfi_list_dict, list):
462 sfi_list_ = sfi_list_dict
463 else:
sousaedu80135b92021-02-17 15:05:18 +0100464 raise TypeError("param sfi_list_dict must be a list or a dictionary")
465
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000466 for sfi in sfi_list_:
sousaedu80135b92021-02-17 15:05:18 +0100467 sfi["ingress_ports"] = []
468 sfi["egress_ports"] = []
469
470 if sfi.get("ingress"):
471 sfi["ingress_ports"].append(sfi["ingress"])
472
473 if sfi.get("egress"):
474 sfi["egress_ports"].append(sfi["egress"])
475
476 del sfi["ingress"]
477 del sfi["egress"]
478 params = sfi.get("service_function_parameters")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000479 sfc_encap = False
sousaedu80135b92021-02-17 15:05:18 +0100480
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000481 if params:
sousaedu80135b92021-02-17 15:05:18 +0100482 correlation = params.get("correlation")
483
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000484 if correlation:
485 sfc_encap = True
sousaedu80135b92021-02-17 15:05:18 +0100486
487 sfi["sfc_encap"] = sfc_encap
488 del sfi["service_function_parameters"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000489
490 def __sf_os2mano(self, sf_list_dict):
491 """Transform the openstack format (Port Pair Group) to mano format (SF)
492 sf_list_dict can be a list of dict or a single dict
493 """
494 if isinstance(sf_list_dict, dict):
495 sf_list_ = [sf_list_dict]
496 elif isinstance(sf_list_dict, list):
497 sf_list_ = sf_list_dict
498 else:
sousaedu80135b92021-02-17 15:05:18 +0100499 raise TypeError("param sf_list_dict must be a list or a dictionary")
500
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000501 for sf in sf_list_:
sousaedu80135b92021-02-17 15:05:18 +0100502 del sf["port_pair_group_parameters"]
503 sf["sfis"] = sf["port_pairs"]
504 del sf["port_pairs"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000505
506 def __sfp_os2mano(self, sfp_list_dict):
507 """Transform the openstack format (Port Chain) to mano format (SFP)
508 sfp_list_dict can be a list of dict or a single dict
509 """
510 if isinstance(sfp_list_dict, dict):
511 sfp_list_ = [sfp_list_dict]
512 elif isinstance(sfp_list_dict, list):
513 sfp_list_ = sfp_list_dict
514 else:
sousaedu80135b92021-02-17 15:05:18 +0100515 raise TypeError("param sfp_list_dict must be a list or a dictionary")
516
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000517 for sfp in sfp_list_:
sousaedu80135b92021-02-17 15:05:18 +0100518 params = sfp.pop("chain_parameters")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000519 sfc_encap = False
sousaedu80135b92021-02-17 15:05:18 +0100520
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000521 if params:
sousaedu80135b92021-02-17 15:05:18 +0100522 correlation = params.get("correlation")
523
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000524 if correlation:
525 sfc_encap = True
sousaedu80135b92021-02-17 15:05:18 +0100526
527 sfp["sfc_encap"] = sfc_encap
528 sfp["spi"] = sfp.pop("chain_id")
529 sfp["classifications"] = sfp.pop("flow_classifiers")
530 sfp["service_functions"] = sfp.pop("port_pair_groups")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000531
532 # placeholder for now; read TODO note below
533 def _validate_classification(self, type, definition):
534 # only legacy_flow_classifier Type is supported at this point
535 return True
536 # TODO(igordcard): this method should be an abstract method of an
537 # abstract Classification class to be implemented by the specific
538 # Types. Also, abstract vimconnector should call the validation
539 # method before the implemented VIM connectors are called.
540
gatici335a06a2023-07-26 00:34:04 +0300541 @staticmethod
542 def _format_exception(exception):
tierno69647792020-03-05 16:45:48 +0000543 """Transform a keystone, nova, neutron exception into a vimconn exception discovering the cause"""
tierno69647792020-03-05 16:45:48 +0000544 message_error = str(exception)
tierno5ad826a2020-08-11 11:19:44 +0000545 tip = ""
tiernode12f782019-04-05 12:46:42 +0000546
sousaedu80135b92021-02-17 15:05:18 +0100547 if isinstance(
548 exception,
549 (
550 neExceptions.NetworkNotFoundClient,
551 nvExceptions.NotFound,
gatici335a06a2023-07-26 00:34:04 +0300552 nvExceptions.ResourceNotFound,
sousaedu80135b92021-02-17 15:05:18 +0100553 ksExceptions.NotFound,
554 gl1Exceptions.HTTPNotFound,
gatici335a06a2023-07-26 00:34:04 +0300555 cExceptions.NotFound,
sousaedu80135b92021-02-17 15:05:18 +0100556 ),
557 ):
558 raise vimconn.VimConnNotFoundException(
559 type(exception).__name__ + ": " + message_error
560 )
561 elif isinstance(
562 exception,
563 (
564 HTTPException,
565 gl1Exceptions.HTTPException,
566 gl1Exceptions.CommunicationError,
567 ConnectionError,
568 ksExceptions.ConnectionError,
569 neExceptions.ConnectionFailed,
gatici335a06a2023-07-26 00:34:04 +0300570 cExceptions.ConnectionError,
sousaedu80135b92021-02-17 15:05:18 +0100571 ),
572 ):
tierno5ad826a2020-08-11 11:19:44 +0000573 if type(exception).__name__ == "SSLError":
574 tip = " (maybe option 'insecure' must be added to the VIM)"
sousaedu80135b92021-02-17 15:05:18 +0100575
576 raise vimconn.VimConnConnectionException(
577 "Invalid URL or credentials{}: {}".format(tip, message_error)
578 )
579 elif isinstance(
580 exception,
581 (
582 KeyError,
583 nvExceptions.BadRequest,
584 ksExceptions.BadRequest,
gatici335a06a2023-07-26 00:34:04 +0300585 gl1Exceptions.BadRequest,
586 cExceptions.BadRequest,
sousaedu80135b92021-02-17 15:05:18 +0100587 ),
588 ):
Patricia Reinoso17852162023-06-15 07:33:04 +0000589 if message_error == "OS-EXT-SRV-ATTR:host":
590 tip = " (If the user does not have non-admin credentials, this attribute will be missing)"
591 raise vimconn.VimConnInsufficientCredentials(
592 type(exception).__name__ + ": " + message_error + tip
593 )
sousaedu80135b92021-02-17 15:05:18 +0100594 raise vimconn.VimConnException(
595 type(exception).__name__ + ": " + message_error
596 )
Patricia Reinoso17852162023-06-15 07:33:04 +0000597
sousaedu80135b92021-02-17 15:05:18 +0100598 elif isinstance(
599 exception,
600 (
601 nvExceptions.ClientException,
602 ksExceptions.ClientException,
603 neExceptions.NeutronException,
gatici335a06a2023-07-26 00:34:04 +0300604 cExceptions.ClientException,
sousaedu80135b92021-02-17 15:05:18 +0100605 ),
606 ):
607 raise vimconn.VimConnUnexpectedResponse(
608 type(exception).__name__ + ": " + message_error
609 )
tiernoae4a8d12016-07-08 12:30:39 +0200610 elif isinstance(exception, nvExceptions.Conflict):
sousaedu80135b92021-02-17 15:05:18 +0100611 raise vimconn.VimConnConflictException(
612 type(exception).__name__ + ": " + message_error
613 )
tierno72774862020-05-04 11:44:15 +0000614 elif isinstance(exception, vimconn.VimConnException):
tierno41a69812018-02-16 14:34:33 +0100615 raise exception
tiernof716aea2017-06-21 18:01:40 +0200616 else: # ()
gatici335a06a2023-07-26 00:34:04 +0300617 logger = logging.getLogger("ro.vim.openstack")
618 logger.error("General Exception " + message_error, exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +0100619
gatici335a06a2023-07-26 00:34:04 +0300620 raise vimconn.VimConnException(
sousaedu80135b92021-02-17 15:05:18 +0100621 type(exception).__name__ + ": " + message_error
622 )
tiernoae4a8d12016-07-08 12:30:39 +0200623
tiernoa05b65a2019-02-01 12:30:27 +0000624 def _get_ids_from_name(self):
625 """
626 Obtain ids from name of tenant and security_groups. Store at self .security_groups_id"
627 :return: None
628 """
629 # get tenant_id if only tenant_name is supplied
630 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100631
tiernoa05b65a2019-02-01 12:30:27 +0000632 if not self.my_tenant_id:
sousaedu80135b92021-02-17 15:05:18 +0100633 raise vimconn.VimConnConnectionException(
634 "Error getting tenant information from name={} id={}".format(
635 self.tenant_name, self.tenant_id
636 )
637 )
638
639 if self.config.get("security_groups") and not self.security_groups_id:
tiernoa05b65a2019-02-01 12:30:27 +0000640 # convert from name to id
sousaedu80135b92021-02-17 15:05:18 +0100641 neutron_sg_list = self.neutron.list_security_groups(
642 tenant_id=self.my_tenant_id
643 )["security_groups"]
tiernoa05b65a2019-02-01 12:30:27 +0000644
645 self.security_groups_id = []
sousaedu80135b92021-02-17 15:05:18 +0100646 for sg in self.config.get("security_groups"):
tiernoa05b65a2019-02-01 12:30:27 +0000647 for neutron_sg in neutron_sg_list:
648 if sg in (neutron_sg["id"], neutron_sg["name"]):
649 self.security_groups_id.append(neutron_sg["id"])
650 break
651 else:
652 self.security_groups_id = None
sousaedu80135b92021-02-17 15:05:18 +0100653
654 raise vimconn.VimConnConnectionException(
655 "Not found security group {} for this tenant".format(sg)
656 )
tiernoa05b65a2019-02-01 12:30:27 +0000657
vegallc53829d2023-06-01 00:47:44 -0500658 def _find_nova_server(self, vm_id):
659 """
660 Returns the VM instance from Openstack and completes it with flavor ID
661 Do not call nova.servers.find directly, as it does not return flavor ID with microversion>=2.47
662 """
663 try:
664 self._reload_connection()
665 server = self.nova.servers.find(id=vm_id)
666 # TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
667 server_dict = server.to_dict()
668 try:
Luis Vegad6577d82023-07-26 20:49:12 +0000669 if server_dict["flavor"].get("original_name"):
670 server_dict["flavor"]["id"] = self.nova.flavors.find(
671 name=server_dict["flavor"]["original_name"]
672 ).id
vegallc53829d2023-06-01 00:47:44 -0500673 except nClient.exceptions.NotFound as e:
674 self.logger.warning(str(e.message))
675 return server_dict
676 except (
677 ksExceptions.ClientException,
678 nvExceptions.ClientException,
679 nvExceptions.NotFound,
680 ConnectionError,
681 ) as e:
682 self._format_exception(e)
683
tierno5509c2e2019-07-04 16:23:20 +0000684 def check_vim_connectivity(self):
685 # just get network list to check connectivity and credentials
686 self.get_network_list(filter_dict={})
687
tiernoae4a8d12016-07-08 12:30:39 +0200688 def get_tenant_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +0000689 """Obtain tenants of VIM
tiernoae4a8d12016-07-08 12:30:39 +0200690 filter_dict can contain the following keys:
691 name: filter by tenant name
692 id: filter by tenant uuid/id
693 <other VIM specific>
694 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
tierno1ec592d2020-06-16 15:29:47 +0000695 """
ahmadsa95baa272016-11-30 09:14:11 +0500696 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200697 try:
698 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100699
tiernof716aea2017-06-21 18:01:40 +0200700 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100701 project_class_list = self.keystone.projects.list(
702 name=filter_dict.get("name")
703 )
ahmadsa95baa272016-11-30 09:14:11 +0500704 else:
tiernof716aea2017-06-21 18:01:40 +0200705 project_class_list = self.keystone.tenants.findall(**filter_dict)
sousaedu80135b92021-02-17 15:05:18 +0100706
tierno1ec592d2020-06-16 15:29:47 +0000707 project_list = []
sousaedu80135b92021-02-17 15:05:18 +0100708
ahmadsa95baa272016-11-30 09:14:11 +0500709 for project in project_class_list:
sousaedu80135b92021-02-17 15:05:18 +0100710 if filter_dict.get("id") and filter_dict["id"] != project.id:
tiernof716aea2017-06-21 18:01:40 +0200711 continue
sousaedu80135b92021-02-17 15:05:18 +0100712
ahmadsa95baa272016-11-30 09:14:11 +0500713 project_list.append(project.to_dict())
sousaedu80135b92021-02-17 15:05:18 +0100714
ahmadsa95baa272016-11-30 09:14:11 +0500715 return project_list
sousaedu80135b92021-02-17 15:05:18 +0100716 except (
717 ksExceptions.ConnectionError,
718 ksExceptions.ClientException,
719 ConnectionError,
720 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200721 self._format_exception(e)
722
723 def new_tenant(self, tenant_name, tenant_description):
tierno1ec592d2020-06-16 15:29:47 +0000724 """Adds a new tenant to openstack VIM. Returns the tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200725 self.logger.debug("Adding a new tenant name: %s", tenant_name)
726 try:
727 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100728
tiernof716aea2017-06-21 18:01:40 +0200729 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100730 project = self.keystone.projects.create(
731 tenant_name,
732 self.config.get("project_domain_id", "default"),
733 description=tenant_description,
734 is_domain=False,
735 )
ahmadsa95baa272016-11-30 09:14:11 +0500736 else:
tiernof716aea2017-06-21 18:01:40 +0200737 project = self.keystone.tenants.create(tenant_name, tenant_description)
sousaedu80135b92021-02-17 15:05:18 +0100738
ahmadsa95baa272016-11-30 09:14:11 +0500739 return project.id
sousaedu80135b92021-02-17 15:05:18 +0100740 except (
741 ksExceptions.ConnectionError,
742 ksExceptions.ClientException,
743 ksExceptions.BadRequest,
744 ConnectionError,
745 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200746 self._format_exception(e)
747
748 def delete_tenant(self, tenant_id):
tierno1ec592d2020-06-16 15:29:47 +0000749 """Delete a tenant from openstack VIM. Returns the old tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200750 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
751 try:
752 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100753
tiernof716aea2017-06-21 18:01:40 +0200754 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500755 self.keystone.projects.delete(tenant_id)
756 else:
757 self.keystone.tenants.delete(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100758
tiernoae4a8d12016-07-08 12:30:39 +0200759 return tenant_id
gatici335a06a2023-07-26 00:34:04 +0300760
sousaedu80135b92021-02-17 15:05:18 +0100761 except (
762 ksExceptions.ConnectionError,
763 ksExceptions.ClientException,
764 ksExceptions.NotFound,
765 ConnectionError,
766 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200767 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500768
sousaedu80135b92021-02-17 15:05:18 +0100769 def new_network(
770 self,
771 net_name,
772 net_type,
773 ip_profile=None,
774 shared=False,
775 provider_network_profile=None,
776 ):
garciadeblasebd66722019-01-31 16:01:31 +0000777 """Adds a tenant network to VIM
778 Params:
779 'net_name': name of the network
780 'net_type': one of:
781 'bridge': overlay isolated network
782 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
783 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
784 'ip_profile': is a dict containing the IP parameters of the network
785 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
786 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
787 'gateway_address': (Optional) ip_schema, that is X.X.X.X
788 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
789 'dhcp_enabled': True or False
790 'dhcp_start_address': ip_schema, first IP to grant
791 'dhcp_count': number of IPs to grant.
792 'shared': if this network can be seen/use by other tenants/organization
garciadeblas4af0d542020-02-18 16:01:13 +0100793 'provider_network_profile': (optional) contains {segmentation-id: vlan, network-type: vlan|vxlan,
794 physical-network: physnet-label}
garciadeblasebd66722019-01-31 16:01:31 +0000795 Returns a tuple with the network identifier and created_items, or raises an exception on error
796 created_items can be None or a dictionary where this method can include key-values that will be passed to
797 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
798 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
799 as not present.
800 """
sousaedu80135b92021-02-17 15:05:18 +0100801 self.logger.debug(
802 "Adding a new network to VIM name '%s', type '%s'", net_name, net_type
803 )
garciadeblasebd66722019-01-31 16:01:31 +0000804 # self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
kbsuba85c54d2019-10-17 16:30:32 +0000805
tierno7edb6752016-03-21 17:37:52 +0100806 try:
kbsuba85c54d2019-10-17 16:30:32 +0000807 vlan = None
sousaedu80135b92021-02-17 15:05:18 +0100808
kbsuba85c54d2019-10-17 16:30:32 +0000809 if provider_network_profile:
810 vlan = provider_network_profile.get("segmentation-id")
sousaedu80135b92021-02-17 15:05:18 +0100811
garciadeblasedca7b32016-09-29 14:01:52 +0000812 new_net = None
garciadeblasebd66722019-01-31 16:01:31 +0000813 created_items = {}
tierno7edb6752016-03-21 17:37:52 +0100814 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100815 network_dict = {"name": net_name, "admin_state_up": True}
816
Gabriel Cuba0d8ce072022-12-14 18:33:50 -0500817 if net_type in ("data", "ptp") or provider_network_profile:
tierno6869ae72020-01-09 17:37:34 +0000818 provider_physical_network = None
sousaedu80135b92021-02-17 15:05:18 +0100819
820 if provider_network_profile and provider_network_profile.get(
821 "physical-network"
822 ):
823 provider_physical_network = provider_network_profile.get(
824 "physical-network"
825 )
826
tierno6869ae72020-01-09 17:37:34 +0000827 # provider-network must be one of the dataplane_physcial_netowrk if this is a list. If it is string
828 # or not declared, just ignore the checking
sousaedu80135b92021-02-17 15:05:18 +0100829 if (
830 isinstance(
831 self.config.get("dataplane_physical_net"), (tuple, list)
832 )
833 and provider_physical_network
834 not in self.config["dataplane_physical_net"]
835 ):
tierno72774862020-05-04 11:44:15 +0000836 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100837 "Invalid parameter 'provider-network:physical-network' "
838 "for network creation. '{}' is not one of the declared "
839 "list at VIM_config:dataplane_physical_net".format(
840 provider_physical_network
841 )
842 )
843
844 # use the default dataplane_physical_net
845 if not provider_physical_network:
846 provider_physical_network = self.config.get(
847 "dataplane_physical_net"
848 )
849
gatici335a06a2023-07-26 00:34:04 +0300850 # 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 +0100851 if (
852 isinstance(provider_physical_network, (tuple, list))
853 and provider_physical_network
854 ):
tierno6869ae72020-01-09 17:37:34 +0000855 provider_physical_network = provider_physical_network[0]
856
857 if not provider_physical_network:
tierno5ad826a2020-08-11 11:19:44 +0000858 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100859 "missing information needed for underlay networks. Provide "
860 "'dataplane_physical_net' configuration at VIM or use the NS "
861 "instantiation parameter 'provider-network.physical-network'"
862 " for the VLD"
863 )
tierno6869ae72020-01-09 17:37:34 +0000864
sousaedu80135b92021-02-17 15:05:18 +0100865 if not self.config.get("multisegment_support"):
garciadeblasaca8cb52023-12-21 16:28:15 +0100866 network_dict["provider:physical_network"] = (
867 provider_physical_network
868 )
sousaedu80135b92021-02-17 15:05:18 +0100869
870 if (
871 provider_network_profile
872 and "network-type" in provider_network_profile
873 ):
garciadeblasaca8cb52023-12-21 16:28:15 +0100874 network_dict["provider:network_type"] = (
875 provider_network_profile["network-type"]
876 )
garciadeblas4af0d542020-02-18 16:01:13 +0100877 else:
sousaedu80135b92021-02-17 15:05:18 +0100878 network_dict["provider:network_type"] = self.config.get(
879 "dataplane_network_type", "vlan"
880 )
881
tierno6869ae72020-01-09 17:37:34 +0000882 if vlan:
883 network_dict["provider:segmentation_id"] = vlan
garciadeblasebd66722019-01-31 16:01:31 +0000884 else:
tierno6869ae72020-01-09 17:37:34 +0000885 # Multi-segment case
garciadeblasebd66722019-01-31 16:01:31 +0000886 segment_list = []
tierno6869ae72020-01-09 17:37:34 +0000887 segment1_dict = {
sousaedu80135b92021-02-17 15:05:18 +0100888 "provider:physical_network": "",
889 "provider:network_type": "vxlan",
tierno6869ae72020-01-09 17:37:34 +0000890 }
garciadeblasebd66722019-01-31 16:01:31 +0000891 segment_list.append(segment1_dict)
tierno6869ae72020-01-09 17:37:34 +0000892 segment2_dict = {
893 "provider:physical_network": provider_physical_network,
sousaedu80135b92021-02-17 15:05:18 +0100894 "provider:network_type": "vlan",
tierno6869ae72020-01-09 17:37:34 +0000895 }
sousaedu80135b92021-02-17 15:05:18 +0100896
tierno6869ae72020-01-09 17:37:34 +0000897 if vlan:
898 segment2_dict["provider:segmentation_id"] = vlan
sousaedu80135b92021-02-17 15:05:18 +0100899 elif self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +0000900 vlanID = self._generate_multisegment_vlanID()
901 segment2_dict["provider:segmentation_id"] = vlanID
sousaedu80135b92021-02-17 15:05:18 +0100902
garciadeblasebd66722019-01-31 16:01:31 +0000903 # else
tierno72774862020-05-04 11:44:15 +0000904 # raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100905 # "You must provide "multisegment_vlan_range" at config dict before creating a multisegment
tierno1ec592d2020-06-16 15:29:47 +0000906 # network")
garciadeblasebd66722019-01-31 16:01:31 +0000907 segment_list.append(segment2_dict)
908 network_dict["segments"] = segment_list
kate721d79b2017-06-24 04:21:38 -0700909
tierno6869ae72020-01-09 17:37:34 +0000910 # VIO Specific Changes. It needs a concrete VLAN
911 if self.vim_type == "VIO" and vlan is None:
sousaedu80135b92021-02-17 15:05:18 +0100912 if self.config.get("dataplane_net_vlan_range") is None:
tierno72774862020-05-04 11:44:15 +0000913 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100914 "You must provide 'dataplane_net_vlan_range' in format "
915 "[start_ID - end_ID] at VIM_config for creating underlay "
916 "networks"
917 )
918
tierno6869ae72020-01-09 17:37:34 +0000919 network_dict["provider:segmentation_id"] = self._generate_vlanID()
kate721d79b2017-06-24 04:21:38 -0700920
garciadeblasebd66722019-01-31 16:01:31 +0000921 network_dict["shared"] = shared
sousaedu80135b92021-02-17 15:05:18 +0100922
anwarsff168192019-05-06 11:23:07 +0530923 if self.config.get("disable_network_port_security"):
924 network_dict["port_security_enabled"] = False
sousaedu80135b92021-02-17 15:05:18 +0100925
sousaedu2aa5f802021-06-17 15:39:29 +0100926 if self.config.get("neutron_availability_zone_hints"):
927 hints = self.config.get("neutron_availability_zone_hints")
928
929 if isinstance(hints, str):
930 hints = [hints]
931
932 network_dict["availability_zone_hints"] = hints
933
sousaedu80135b92021-02-17 15:05:18 +0100934 new_net = self.neutron.create_network({"network": network_dict})
garciadeblasebd66722019-01-31 16:01:31 +0000935 # print new_net
936 # create subnetwork, even if there is no profile
sousaedu80135b92021-02-17 15:05:18 +0100937
garciadeblas9f8456e2016-09-05 05:02:59 +0200938 if not ip_profile:
939 ip_profile = {}
sousaedu80135b92021-02-17 15:05:18 +0100940
941 if not ip_profile.get("subnet_address"):
tierno1ec592d2020-06-16 15:29:47 +0000942 # Fake subnet is required
elumalai51e72a02023-04-28 19:41:49 +0530943 subnet_rand = random.SystemRandom().randint(0, 255)
sousaedu80135b92021-02-17 15:05:18 +0100944 ip_profile["subnet_address"] = "192.168.{}.0/24".format(subnet_rand)
945
946 if "ip_version" not in ip_profile:
947 ip_profile["ip_version"] = "IPv4"
948
949 subnet = {
950 "name": net_name + "-subnet",
951 "network_id": new_net["network"]["id"],
952 "ip_version": 4 if ip_profile["ip_version"] == "IPv4" else 6,
953 "cidr": ip_profile["subnet_address"],
954 }
955
tiernoa1fb4462017-06-30 12:25:50 +0200956 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
sousaedu80135b92021-02-17 15:05:18 +0100957 if ip_profile.get("gateway_address"):
958 subnet["gateway_ip"] = ip_profile["gateway_address"]
tierno55d234c2018-07-04 18:29:21 +0200959 else:
sousaedu80135b92021-02-17 15:05:18 +0100960 subnet["gateway_ip"] = None
961
962 if ip_profile.get("dns_address"):
963 subnet["dns_nameservers"] = ip_profile["dns_address"].split(";")
964
965 if "dhcp_enabled" in ip_profile:
966 subnet["enable_dhcp"] = (
967 False
968 if ip_profile["dhcp_enabled"] == "false"
969 or ip_profile["dhcp_enabled"] is False
970 else True
971 )
972
973 if ip_profile.get("dhcp_start_address"):
974 subnet["allocation_pools"] = []
975 subnet["allocation_pools"].append(dict())
976 subnet["allocation_pools"][0]["start"] = ip_profile[
977 "dhcp_start_address"
978 ]
979
980 if ip_profile.get("dhcp_count"):
981 # parts = ip_profile["dhcp_start_address"].split(".")
tierno1ec592d2020-06-16 15:29:47 +0000982 # ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
sousaedu80135b92021-02-17 15:05:18 +0100983 ip_int = int(netaddr.IPAddress(ip_profile["dhcp_start_address"]))
984 ip_int += ip_profile["dhcp_count"] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200985 ip_str = str(netaddr.IPAddress(ip_int))
sousaedu80135b92021-02-17 15:05:18 +0100986 subnet["allocation_pools"][0]["end"] = ip_str
987
Gabriel Cubab3dbfca2023-03-14 10:58:39 -0500988 if (
989 ip_profile.get("ipv6_address_mode")
990 and ip_profile["ip_version"] != "IPv4"
991 ):
992 subnet["ipv6_address_mode"] = ip_profile["ipv6_address_mode"]
993 # ipv6_ra_mode can be set to the same value for most use cases, see documentation:
994 # https://docs.openstack.org/neutron/latest/admin/config-ipv6.html#ipv6-ra-mode-and-ipv6-address-mode-combinations
995 subnet["ipv6_ra_mode"] = ip_profile["ipv6_address_mode"]
996
tierno1ec592d2020-06-16 15:29:47 +0000997 # self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
998 self.neutron.create_subnet({"subnet": subnet})
garciadeblasebd66722019-01-31 16:01:31 +0000999
sousaedu80135b92021-02-17 15:05:18 +01001000 if net_type == "data" and self.config.get("multisegment_support"):
1001 if self.config.get("l2gw_support"):
garciadeblasebd66722019-01-31 16:01:31 +00001002 l2gw_list = self.neutron.list_l2_gateways().get("l2_gateways", ())
1003 for l2gw in l2gw_list:
tierno1ec592d2020-06-16 15:29:47 +00001004 l2gw_conn = {
1005 "l2_gateway_id": l2gw["id"],
1006 "network_id": new_net["network"]["id"],
1007 "segmentation_id": str(vlanID),
1008 }
sousaedu80135b92021-02-17 15:05:18 +01001009 new_l2gw_conn = self.neutron.create_l2_gateway_connection(
1010 {"l2_gateway_connection": l2gw_conn}
1011 )
1012 created_items[
1013 "l2gwconn:"
1014 + str(new_l2gw_conn["l2_gateway_connection"]["id"])
1015 ] = True
1016
garciadeblasebd66722019-01-31 16:01:31 +00001017 return new_net["network"]["id"], created_items
tierno41a69812018-02-16 14:34:33 +01001018 except Exception as e:
tierno1ec592d2020-06-16 15:29:47 +00001019 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001020 for k, v in created_items.items():
1021 if not v: # skip already deleted
1022 continue
sousaedu80135b92021-02-17 15:05:18 +01001023
garciadeblasebd66722019-01-31 16:01:31 +00001024 try:
1025 k_item, _, k_id = k.partition(":")
sousaedu80135b92021-02-17 15:05:18 +01001026
garciadeblasebd66722019-01-31 16:01:31 +00001027 if k_item == "l2gwconn":
1028 self.neutron.delete_l2_gateway_connection(k_id)
gatici335a06a2023-07-26 00:34:04 +03001029
1030 except (neExceptions.ConnectionFailed, ConnectionError) as e2:
1031 self.logger.error(
1032 "Error deleting l2 gateway connection: {}: {}".format(
1033 type(e2).__name__, e2
1034 )
1035 )
1036 self._format_exception(e2)
garciadeblasebd66722019-01-31 16:01:31 +00001037 except Exception as e2:
sousaedu80135b92021-02-17 15:05:18 +01001038 self.logger.error(
1039 "Error deleting l2 gateway connection: {}: {}".format(
1040 type(e2).__name__, e2
1041 )
1042 )
1043
garciadeblasedca7b32016-09-29 14:01:52 +00001044 if new_net:
sousaedu80135b92021-02-17 15:05:18 +01001045 self.neutron.delete_network(new_net["network"]["id"])
1046
tiernoae4a8d12016-07-08 12:30:39 +02001047 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001048
1049 def get_network_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001050 """Obtain tenant networks of VIM
tierno7edb6752016-03-21 17:37:52 +01001051 Filter_dict can be:
1052 name: network name
1053 id: network uuid
1054 shared: boolean
1055 tenant_id: tenant
1056 admin_state_up: boolean
1057 status: 'ACTIVE'
1058 Returns the network list of dictionaries
tierno1ec592d2020-06-16 15:29:47 +00001059 """
tiernoae4a8d12016-07-08 12:30:39 +02001060 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +01001061 try:
1062 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001063 filter_dict_os = filter_dict.copy()
sousaedu80135b92021-02-17 15:05:18 +01001064
tierno69b590e2018-03-13 18:52:23 +01001065 if self.api_version3 and "tenant_id" in filter_dict_os:
sousaedu80135b92021-02-17 15:05:18 +01001066 # TODO check
1067 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
1068
tierno69b590e2018-03-13 18:52:23 +01001069 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +01001070 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +01001071 self.__net_os2mano(net_list)
sousaedu80135b92021-02-17 15:05:18 +01001072
tiernoae4a8d12016-07-08 12:30:39 +02001073 return net_list
sousaedu80135b92021-02-17 15:05:18 +01001074 except (
1075 neExceptions.ConnectionFailed,
1076 ksExceptions.ClientException,
1077 neExceptions.NeutronException,
1078 ConnectionError,
1079 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001080 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001081
tiernoae4a8d12016-07-08 12:30:39 +02001082 def get_network(self, net_id):
tierno1ec592d2020-06-16 15:29:47 +00001083 """Obtain details of network from VIM
1084 Returns the network information from a network id"""
tiernoae4a8d12016-07-08 12:30:39 +02001085 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno1ec592d2020-06-16 15:29:47 +00001086 filter_dict = {"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +02001087 net_list = self.get_network_list(filter_dict)
sousaedu80135b92021-02-17 15:05:18 +01001088
tierno1ec592d2020-06-16 15:29:47 +00001089 if len(net_list) == 0:
sousaedu80135b92021-02-17 15:05:18 +01001090 raise vimconn.VimConnNotFoundException(
1091 "Network '{}' not found".format(net_id)
1092 )
tierno1ec592d2020-06-16 15:29:47 +00001093 elif len(net_list) > 1:
sousaedu80135b92021-02-17 15:05:18 +01001094 raise vimconn.VimConnConflictException(
1095 "Found more than one network with this criteria"
1096 )
1097
tierno7edb6752016-03-21 17:37:52 +01001098 net = net_list[0]
tierno1ec592d2020-06-16 15:29:47 +00001099 subnets = []
1100 for subnet_id in net.get("subnets", ()):
tierno7edb6752016-03-21 17:37:52 +01001101 try:
1102 subnet = self.neutron.show_subnet(subnet_id)
1103 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001104 self.logger.error(
1105 "osconnector.get_network(): Error getting subnet %s %s"
1106 % (net_id, str(e))
1107 )
tiernoae4a8d12016-07-08 12:30:39 +02001108 subnet = {"id": subnet_id, "fault": str(e)}
sousaedu80135b92021-02-17 15:05:18 +01001109
tierno7edb6752016-03-21 17:37:52 +01001110 subnets.append(subnet)
sousaedu80135b92021-02-17 15:05:18 +01001111
tierno7edb6752016-03-21 17:37:52 +01001112 net["subnets"] = subnets
sousaedu80135b92021-02-17 15:05:18 +01001113 net["encapsulation"] = net.get("provider:network_type")
1114 net["encapsulation_type"] = net.get("provider:network_type")
1115 net["segmentation_id"] = net.get("provider:segmentation_id")
1116 net["encapsulation_id"] = net.get("provider:segmentation_id")
1117
tiernoae4a8d12016-07-08 12:30:39 +02001118 return net
tierno7edb6752016-03-21 17:37:52 +01001119
gatici335a06a2023-07-26 00:34:04 +03001120 @catch_any_exception
garciadeblasebd66722019-01-31 16:01:31 +00001121 def delete_network(self, net_id, created_items=None):
1122 """
1123 Removes a tenant network from VIM and its associated elements
1124 :param net_id: VIM identifier of the network, provided by method new_network
1125 :param created_items: dictionary with extra items to be deleted. provided by method new_network
1126 Returns the network identifier or raises an exception upon error or when network is not found
1127 """
tiernoae4a8d12016-07-08 12:30:39 +02001128 self.logger.debug("Deleting network '%s' from VIM", net_id)
sousaedu80135b92021-02-17 15:05:18 +01001129
tierno1ec592d2020-06-16 15:29:47 +00001130 if created_items is None:
garciadeblasebd66722019-01-31 16:01:31 +00001131 created_items = {}
sousaedu80135b92021-02-17 15:05:18 +01001132
tierno7edb6752016-03-21 17:37:52 +01001133 try:
1134 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001135 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001136 for k, v in created_items.items():
1137 if not v: # skip already deleted
1138 continue
sousaedu80135b92021-02-17 15:05:18 +01001139
garciadeblasebd66722019-01-31 16:01:31 +00001140 try:
1141 k_item, _, k_id = k.partition(":")
1142 if k_item == "l2gwconn":
1143 self.neutron.delete_l2_gateway_connection(k_id)
gatici335a06a2023-07-26 00:34:04 +03001144
1145 except (neExceptions.ConnectionFailed, ConnectionError) as e:
1146 self.logger.error(
1147 "Error deleting l2 gateway connection: {}: {}".format(
1148 type(e).__name__, e
1149 )
1150 )
1151 self._format_exception(e)
garciadeblasebd66722019-01-31 16:01:31 +00001152 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001153 self.logger.error(
1154 "Error deleting l2 gateway connection: {}: {}".format(
1155 type(e).__name__, e
1156 )
1157 )
1158
tierno1ec592d2020-06-16 15:29:47 +00001159 # delete VM ports attached to this networks before the network
tierno7edb6752016-03-21 17:37:52 +01001160 ports = self.neutron.list_ports(network_id=net_id)
sousaedu80135b92021-02-17 15:05:18 +01001161 for p in ports["ports"]:
tierno7edb6752016-03-21 17:37:52 +01001162 try:
1163 self.neutron.delete_port(p["id"])
gatici335a06a2023-07-26 00:34:04 +03001164
1165 except (neExceptions.ConnectionFailed, ConnectionError) as e:
1166 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
1167 # If there is connection error, it raises.
1168 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001169 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001170 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
sousaedu80135b92021-02-17 15:05:18 +01001171
tierno7edb6752016-03-21 17:37:52 +01001172 self.neutron.delete_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001173
tiernoae4a8d12016-07-08 12:30:39 +02001174 return net_id
gatici335a06a2023-07-26 00:34:04 +03001175 except (neExceptions.NetworkNotFoundClient, neExceptions.NotFound) as e:
1176 # If network to be deleted is not found, it does not raise.
1177 self.logger.warning(
1178 f"Error deleting network: {net_id} is not found, {str(e)}"
1179 )
tierno7edb6752016-03-21 17:37:52 +01001180
tiernoae4a8d12016-07-08 12:30:39 +02001181 def refresh_nets_status(self, net_list):
tierno1ec592d2020-06-16 15:29:47 +00001182 """Get the status of the networks
sousaedu80135b92021-02-17 15:05:18 +01001183 Params: the list of network identifiers
1184 Returns a dictionary with:
1185 net_id: #VIM id of this network
1186 status: #Mandatory. Text with one of:
1187 # DELETED (not found at vim)
1188 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1189 # OTHER (Vim reported other status not understood)
1190 # ERROR (VIM indicates an ERROR status)
1191 # ACTIVE, INACTIVE, DOWN (admin down),
1192 # BUILD (on building process)
1193 #
1194 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1195 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
tierno1ec592d2020-06-16 15:29:47 +00001196 """
1197 net_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001198
tiernoae4a8d12016-07-08 12:30:39 +02001199 for net_id in net_list:
1200 net = {}
sousaedu80135b92021-02-17 15:05:18 +01001201
tiernoae4a8d12016-07-08 12:30:39 +02001202 try:
1203 net_vim = self.get_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001204
1205 if net_vim["status"] in netStatus2manoFormat:
1206 net["status"] = netStatus2manoFormat[net_vim["status"]]
tiernoae4a8d12016-07-08 12:30:39 +02001207 else:
1208 net["status"] = "OTHER"
sousaedu80135b92021-02-17 15:05:18 +01001209 net["error_msg"] = "VIM status reported " + net_vim["status"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001210
sousaedu80135b92021-02-17 15:05:18 +01001211 if net["status"] == "ACTIVE" and not net_vim["admin_state_up"]:
1212 net["status"] = "DOWN"
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001213
sousaedu80135b92021-02-17 15:05:18 +01001214 net["vim_info"] = self.serialize(net_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001215
sousaedu80135b92021-02-17 15:05:18 +01001216 if net_vim.get("fault"): # TODO
1217 net["error_msg"] = str(net_vim["fault"])
tierno72774862020-05-04 11:44:15 +00001218 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001219 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001220 net["status"] = "DELETED"
1221 net["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00001222 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001223 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001224 net["status"] = "VIM_ERROR"
1225 net["error_msg"] = str(e)
tiernoae4a8d12016-07-08 12:30:39 +02001226 net_dict[net_id] = net
1227 return net_dict
1228
1229 def get_flavor(self, flavor_id):
tierno1ec592d2020-06-16 15:29:47 +00001230 """Obtain flavor details from the VIM. Returns the flavor dict details"""
tiernoae4a8d12016-07-08 12:30:39 +02001231 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +01001232 try:
1233 self._reload_connection()
1234 flavor = self.nova.flavors.find(id=flavor_id)
tiernoae4a8d12016-07-08 12:30:39 +02001235 return flavor.to_dict()
gatici335a06a2023-07-26 00:34:04 +03001236
sousaedu80135b92021-02-17 15:05:18 +01001237 except (
1238 nvExceptions.NotFound,
1239 nvExceptions.ClientException,
1240 ksExceptions.ClientException,
1241 ConnectionError,
1242 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001243 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001244
tiernocf157a82017-01-30 14:07:06 +01001245 def get_flavor_id_from_data(self, flavor_dict):
1246 """Obtain flavor id that match the flavor description
sousaedu80135b92021-02-17 15:05:18 +01001247 Returns the flavor_id or raises a vimconnNotFoundException
1248 flavor_dict: contains the required ram, vcpus, disk
1249 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
1250 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
1251 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +01001252 """
sousaedu80135b92021-02-17 15:05:18 +01001253 exact_match = False if self.config.get("use_existing_flavors") else True
1254
tiernocf157a82017-01-30 14:07:06 +01001255 try:
1256 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +02001257 flavor_candidate_id = None
1258 flavor_candidate_data = (10000, 10000, 10000)
sousaedu80135b92021-02-17 15:05:18 +01001259 flavor_target = (
1260 flavor_dict["ram"],
1261 flavor_dict["vcpus"],
1262 flavor_dict["disk"],
sousaedu648ee3d2021-11-22 14:09:15 +00001263 flavor_dict.get("ephemeral", 0),
1264 flavor_dict.get("swap", 0),
sousaedu80135b92021-02-17 15:05:18 +01001265 )
tiernoe26fc7a2017-05-30 14:43:03 +02001266 # numa=None
anwarsae5f52c2019-04-22 10:35:27 +05301267 extended = flavor_dict.get("extended", {})
1268 if extended:
tierno1ec592d2020-06-16 15:29:47 +00001269 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001270 raise vimconn.VimConnNotFoundException(
1271 "Flavor with EPA still not implemented"
1272 )
tiernocf157a82017-01-30 14:07:06 +01001273 # if len(numas) > 1:
tierno72774862020-05-04 11:44:15 +00001274 # raise vimconn.VimConnNotFoundException("Cannot find any flavor with more than one numa")
tiernocf157a82017-01-30 14:07:06 +01001275 # numa=numas[0]
1276 # numas = extended.get("numas")
1277 for flavor in self.nova.flavors.list():
1278 epa = flavor.get_keys()
sousaedu80135b92021-02-17 15:05:18 +01001279
tiernocf157a82017-01-30 14:07:06 +01001280 if epa:
1281 continue
tiernoe26fc7a2017-05-30 14:43:03 +02001282 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001283
sousaedu648ee3d2021-11-22 14:09:15 +00001284 flavor_data = (
1285 flavor.ram,
1286 flavor.vcpus,
1287 flavor.disk,
1288 flavor.ephemeral,
preethika.pebaba1f2022-01-20 07:24:18 +00001289 flavor.swap if isinstance(flavor.swap, int) else 0,
sousaedu648ee3d2021-11-22 14:09:15 +00001290 )
tiernoe26fc7a2017-05-30 14:43:03 +02001291 if flavor_data == flavor_target:
1292 return flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001293 elif (
1294 not exact_match
1295 and flavor_target < flavor_data < flavor_candidate_data
1296 ):
tiernoe26fc7a2017-05-30 14:43:03 +02001297 flavor_candidate_id = flavor.id
1298 flavor_candidate_data = flavor_data
sousaedu80135b92021-02-17 15:05:18 +01001299
tiernoe26fc7a2017-05-30 14:43:03 +02001300 if not exact_match and flavor_candidate_id:
1301 return flavor_candidate_id
sousaedu80135b92021-02-17 15:05:18 +01001302
1303 raise vimconn.VimConnNotFoundException(
1304 "Cannot find any flavor matching '{}'".format(flavor_dict)
1305 )
1306 except (
1307 nvExceptions.NotFound,
gatici335a06a2023-07-26 00:34:04 +03001308 nvExceptions.BadRequest,
sousaedu80135b92021-02-17 15:05:18 +01001309 nvExceptions.ClientException,
1310 ksExceptions.ClientException,
1311 ConnectionError,
1312 ) as e:
tiernocf157a82017-01-30 14:07:06 +01001313 self._format_exception(e)
1314
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001315 @staticmethod
1316 def process_resource_quota(quota: dict, prefix: str, extra_specs: dict) -> None:
1317 """Process resource quota and fill up extra_specs.
1318 Args:
1319 quota (dict): Keeping the quota of resurces
1320 prefix (str) Prefix
1321 extra_specs (dict) Dict to be filled to be used during flavor creation
1322
anwarsae5f52c2019-04-22 10:35:27 +05301323 """
sousaedu80135b92021-02-17 15:05:18 +01001324 if "limit" in quota:
1325 extra_specs["quota:" + prefix + "_limit"] = quota["limit"]
1326
1327 if "reserve" in quota:
1328 extra_specs["quota:" + prefix + "_reservation"] = quota["reserve"]
1329
1330 if "shares" in quota:
anwarsae5f52c2019-04-22 10:35:27 +05301331 extra_specs["quota:" + prefix + "_shares_level"] = "custom"
sousaedu80135b92021-02-17 15:05:18 +01001332 extra_specs["quota:" + prefix + "_shares_share"] = quota["shares"]
anwarsae5f52c2019-04-22 10:35:27 +05301333
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001334 @staticmethod
1335 def process_numa_memory(
1336 numa: dict, node_id: Optional[int], extra_specs: dict
1337 ) -> None:
1338 """Set the memory in extra_specs.
1339 Args:
1340 numa (dict): A dictionary which includes numa information
1341 node_id (int): ID of numa node
1342 extra_specs (dict): To be filled.
1343
1344 """
1345 if not numa.get("memory"):
1346 return
1347 memory_mb = numa["memory"] * 1024
1348 memory = "hw:numa_mem.{}".format(node_id)
1349 extra_specs[memory] = int(memory_mb)
1350
1351 @staticmethod
1352 def process_numa_vcpu(numa: dict, node_id: int, extra_specs: dict) -> None:
1353 """Set the cpu in extra_specs.
1354 Args:
1355 numa (dict): A dictionary which includes numa information
1356 node_id (int): ID of numa node
1357 extra_specs (dict): To be filled.
1358
1359 """
1360 if not numa.get("vcpu"):
1361 return
1362 vcpu = numa["vcpu"]
1363 cpu = "hw:numa_cpus.{}".format(node_id)
1364 vcpu = ",".join(map(str, vcpu))
1365 extra_specs[cpu] = vcpu
1366
1367 @staticmethod
1368 def process_numa_paired_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1369 """Fill up extra_specs if numa has paired-threads.
1370 Args:
1371 numa (dict): A dictionary which includes numa information
1372 extra_specs (dict): To be filled.
1373
1374 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001375 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001376
1377 """
1378 if not numa.get("paired-threads"):
1379 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001380
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001381 # cpu_thread_policy "require" implies that compute node must have an STM architecture
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001382 threads = numa["paired-threads"] * 2
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001383 extra_specs["hw:cpu_thread_policy"] = "require"
1384 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001385 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001386
1387 @staticmethod
1388 def process_numa_cores(numa: dict, extra_specs: dict) -> Optional[int]:
1389 """Fill up extra_specs if numa has cores.
1390 Args:
1391 numa (dict): A dictionary which includes numa information
1392 extra_specs (dict): To be filled.
1393
1394 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001395 cores (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001396
1397 """
1398 # cpu_thread_policy "isolate" implies that the host must not have an SMT
1399 # architecture, or a non-SMT architecture will be emulated
1400 if not numa.get("cores"):
1401 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001402 cores = numa["cores"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001403 extra_specs["hw:cpu_thread_policy"] = "isolate"
1404 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001405 return cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001406
1407 @staticmethod
1408 def process_numa_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1409 """Fill up extra_specs if numa has threads.
1410 Args:
1411 numa (dict): A dictionary which includes numa information
1412 extra_specs (dict): To be filled.
1413
1414 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001415 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001416
1417 """
1418 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
1419 if not numa.get("threads"):
1420 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001421 threads = numa["threads"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001422 extra_specs["hw:cpu_thread_policy"] = "prefer"
1423 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001424 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001425
1426 def _process_numa_parameters_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001427 self, numas: List, extra_specs: Dict
1428 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001429 """Process numa parameters and fill up extra_specs.
1430
1431 Args:
1432 numas (list): List of dictionary which includes numa information
1433 extra_specs (dict): To be filled.
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001434
1435 """
1436 numa_nodes = len(numas)
1437 extra_specs["hw:numa_nodes"] = str(numa_nodes)
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001438 cpu_cores, cpu_threads = 0, 0
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001439
1440 if self.vim_type == "VIO":
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001441 self.process_vio_numa_nodes(numa_nodes, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001442
1443 for numa in numas:
1444 if "id" in numa:
1445 node_id = numa["id"]
1446 # overwrite ram and vcpus
1447 # check if key "memory" is present in numa else use ram value at flavor
1448 self.process_numa_memory(numa, node_id, extra_specs)
1449 self.process_numa_vcpu(numa, node_id, extra_specs)
1450
1451 # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
1452 extra_specs["hw:cpu_sockets"] = str(numa_nodes)
1453
1454 if "paired-threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001455 threads = self.process_numa_paired_threads(numa, extra_specs)
1456 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001457
1458 elif "cores" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001459 cores = self.process_numa_cores(numa, extra_specs)
1460 cpu_cores += cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001461
1462 elif "threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001463 threads = self.process_numa_threads(numa, extra_specs)
1464 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001465
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001466 if cpu_cores:
1467 extra_specs["hw:cpu_cores"] = str(cpu_cores)
1468 if cpu_threads:
1469 extra_specs["hw:cpu_threads"] = str(cpu_threads)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001470
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001471 @staticmethod
1472 def process_vio_numa_nodes(numa_nodes: int, extra_specs: Dict) -> None:
1473 """According to number of numa nodes, updates the extra_specs for VIO.
1474
1475 Args:
1476
1477 numa_nodes (int): List keeps the numa node numbers
1478 extra_specs (dict): Extra specs dict to be updated
1479
1480 """
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001481 # If there are several numas, we do not define specific affinity.
1482 extra_specs["vmware:latency_sensitivity_level"] = "high"
1483
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001484 def _change_flavor_name(
1485 self, name: str, name_suffix: int, flavor_data: dict
1486 ) -> str:
1487 """Change the flavor name if the name already exists.
1488
1489 Args:
1490 name (str): Flavor name to be checked
1491 name_suffix (int): Suffix to be appended to name
1492 flavor_data (dict): Flavor dict
1493
1494 Returns:
1495 name (str): New flavor name to be used
1496
1497 """
1498 # Get used names
1499 fl = self.nova.flavors.list()
1500 fl_names = [f.name for f in fl]
1501
1502 while name in fl_names:
1503 name_suffix += 1
1504 name = flavor_data["name"] + "-" + str(name_suffix)
1505
1506 return name
1507
1508 def _process_extended_config_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001509 self, extended: dict, extra_specs: dict
1510 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001511 """Process the extended dict to fill up extra_specs.
1512 Args:
1513
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001514 extended (dict): Keeping the extra specification of flavor
1515 extra_specs (dict) Dict to be filled to be used during flavor creation
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001516
1517 """
1518 quotas = {
1519 "cpu-quota": "cpu",
1520 "mem-quota": "memory",
1521 "vif-quota": "vif",
1522 "disk-io-quota": "disk_io",
1523 }
1524
1525 page_sizes = {
1526 "LARGE": "large",
1527 "SMALL": "small",
1528 "SIZE_2MB": "2MB",
1529 "SIZE_1GB": "1GB",
1530 "PREFER_LARGE": "any",
1531 }
1532
1533 policies = {
1534 "cpu-pinning-policy": "hw:cpu_policy",
1535 "cpu-thread-pinning-policy": "hw:cpu_thread_policy",
1536 "mem-policy": "hw:numa_mempolicy",
1537 }
1538
1539 numas = extended.get("numas")
1540 if numas:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001541 self._process_numa_parameters_of_flavor(numas, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001542
1543 for quota, item in quotas.items():
1544 if quota in extended.keys():
1545 self.process_resource_quota(extended.get(quota), item, extra_specs)
1546
1547 # Set the mempage size as specified in the descriptor
1548 if extended.get("mempage-size"):
1549 if extended["mempage-size"] in page_sizes.keys():
1550 extra_specs["hw:mem_page_size"] = page_sizes[extended["mempage-size"]]
1551 else:
1552 # Normally, validations in NBI should not allow to this condition.
1553 self.logger.debug(
1554 "Invalid mempage-size %s. Will be ignored",
1555 extended.get("mempage-size"),
1556 )
1557
1558 for policy, hw_policy in policies.items():
1559 if extended.get(policy):
1560 extra_specs[hw_policy] = extended[policy].lower()
1561
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001562 @staticmethod
1563 def _get_flavor_details(flavor_data: dict) -> Tuple:
1564 """Returns the details of flavor
1565 Args:
1566 flavor_data (dict): Dictionary that includes required flavor details
1567
1568 Returns:
1569 ram, vcpus, extra_specs, extended (tuple): Main items of required flavor
1570
1571 """
1572 return (
1573 flavor_data.get("ram", 64),
1574 flavor_data.get("vcpus", 1),
1575 {},
1576 flavor_data.get("extended"),
1577 )
1578
gatici335a06a2023-07-26 00:34:04 +03001579 @catch_any_exception
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001580 def new_flavor(self, flavor_data: dict, change_name_if_used: bool = True) -> str:
1581 """Adds a tenant flavor to openstack VIM.
1582 if change_name_if_used is True, it will change name in case of conflict,
1583 because it is not supported name repetition.
1584
1585 Args:
1586 flavor_data (dict): Flavor details to be processed
1587 change_name_if_used (bool): Change name in case of conflict
1588
1589 Returns:
1590 flavor_id (str): flavor identifier
1591
tierno1ec592d2020-06-16 15:29:47 +00001592 """
tiernoae4a8d12016-07-08 12:30:39 +02001593 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno1ec592d2020-06-16 15:29:47 +00001594 retry = 0
1595 max_retries = 3
tierno7edb6752016-03-21 17:37:52 +01001596 name_suffix = 0
gatici335a06a2023-07-26 00:34:04 +03001597 name = flavor_data["name"]
1598 while retry < max_retries:
1599 retry += 1
1600 try:
1601 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001602
gatici335a06a2023-07-26 00:34:04 +03001603 if change_name_if_used:
1604 name = self._change_flavor_name(name, name_suffix, flavor_data)
sousaedu80135b92021-02-17 15:05:18 +01001605
gatici335a06a2023-07-26 00:34:04 +03001606 ram, vcpus, extra_specs, extended = self._get_flavor_details(
1607 flavor_data
1608 )
1609 if extended:
1610 self._process_extended_config_of_flavor(extended, extra_specs)
sousaedu80135b92021-02-17 15:05:18 +01001611
gatici335a06a2023-07-26 00:34:04 +03001612 # Create flavor
sousaedu80135b92021-02-17 15:05:18 +01001613
gatici335a06a2023-07-26 00:34:04 +03001614 new_flavor = self.nova.flavors.create(
1615 name=name,
1616 ram=ram,
1617 vcpus=vcpus,
1618 disk=flavor_data.get("disk", 0),
1619 ephemeral=flavor_data.get("ephemeral", 0),
1620 swap=flavor_data.get("swap", 0),
1621 is_public=flavor_data.get("is_public", True),
1622 )
sousaedu80135b92021-02-17 15:05:18 +01001623
gatici335a06a2023-07-26 00:34:04 +03001624 # Add metadata
1625 if extra_specs:
1626 new_flavor.set_keys(extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001627
gatici335a06a2023-07-26 00:34:04 +03001628 return new_flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001629
gatici335a06a2023-07-26 00:34:04 +03001630 except nvExceptions.Conflict as e:
1631 if change_name_if_used and retry < max_retries:
1632 continue
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001633
gatici335a06a2023-07-26 00:34:04 +03001634 self._format_exception(e)
sousaedu80135b92021-02-17 15:05:18 +01001635
gatici335a06a2023-07-26 00:34:04 +03001636 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00001637 def delete_flavor(self, flavor_id):
sousaedu80135b92021-02-17 15:05:18 +01001638 """Deletes a tenant flavor from openstack VIM. Returns the old flavor_id"""
tiernoae4a8d12016-07-08 12:30:39 +02001639 try:
1640 self._reload_connection()
1641 self.nova.flavors.delete(flavor_id)
1642 return flavor_id
gatici335a06a2023-07-26 00:34:04 +03001643
1644 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
1645 # If flavor is not found, it does not raise.
1646 self.logger.warning(
1647 f"Error deleting flavor: {flavor_id} is not found, {str(e.message)}"
1648 )
tierno7edb6752016-03-21 17:37:52 +01001649
tierno1ec592d2020-06-16 15:29:47 +00001650 def new_image(self, image_dict):
1651 """
tiernoae4a8d12016-07-08 12:30:39 +02001652 Adds a tenant image to VIM. imge_dict is a dictionary with:
1653 name: name
1654 disk_format: qcow2, vhd, vmdk, raw (by default), ...
1655 location: path or URI
1656 public: "yes" or "no"
1657 metadata: metadata of the image
1658 Returns the image_id
tierno1ec592d2020-06-16 15:29:47 +00001659 """
1660 retry = 0
1661 max_retries = 3
sousaedu80135b92021-02-17 15:05:18 +01001662
tierno1ec592d2020-06-16 15:29:47 +00001663 while retry < max_retries:
1664 retry += 1
tierno7edb6752016-03-21 17:37:52 +01001665 try:
1666 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001667
tierno1ec592d2020-06-16 15:29:47 +00001668 # determine format http://docs.openstack.org/developer/glance/formats.html
tierno7edb6752016-03-21 17:37:52 +01001669 if "disk_format" in image_dict:
tierno1ec592d2020-06-16 15:29:47 +00001670 disk_format = image_dict["disk_format"]
1671 else: # autodiscover based on extension
sousaedu80135b92021-02-17 15:05:18 +01001672 if image_dict["location"].endswith(".qcow2"):
tierno1ec592d2020-06-16 15:29:47 +00001673 disk_format = "qcow2"
sousaedu80135b92021-02-17 15:05:18 +01001674 elif image_dict["location"].endswith(".vhd"):
tierno1ec592d2020-06-16 15:29:47 +00001675 disk_format = "vhd"
sousaedu80135b92021-02-17 15:05:18 +01001676 elif image_dict["location"].endswith(".vmdk"):
tierno1ec592d2020-06-16 15:29:47 +00001677 disk_format = "vmdk"
sousaedu80135b92021-02-17 15:05:18 +01001678 elif image_dict["location"].endswith(".vdi"):
tierno1ec592d2020-06-16 15:29:47 +00001679 disk_format = "vdi"
sousaedu80135b92021-02-17 15:05:18 +01001680 elif image_dict["location"].endswith(".iso"):
tierno1ec592d2020-06-16 15:29:47 +00001681 disk_format = "iso"
sousaedu80135b92021-02-17 15:05:18 +01001682 elif image_dict["location"].endswith(".aki"):
tierno1ec592d2020-06-16 15:29:47 +00001683 disk_format = "aki"
sousaedu80135b92021-02-17 15:05:18 +01001684 elif image_dict["location"].endswith(".ari"):
tierno1ec592d2020-06-16 15:29:47 +00001685 disk_format = "ari"
sousaedu80135b92021-02-17 15:05:18 +01001686 elif image_dict["location"].endswith(".ami"):
tierno1ec592d2020-06-16 15:29:47 +00001687 disk_format = "ami"
tierno7edb6752016-03-21 17:37:52 +01001688 else:
tierno1ec592d2020-06-16 15:29:47 +00001689 disk_format = "raw"
sousaedu80135b92021-02-17 15:05:18 +01001690
1691 self.logger.debug(
1692 "new_image: '%s' loading from '%s'",
1693 image_dict["name"],
1694 image_dict["location"],
1695 )
shashankjain3c83a212018-10-04 13:05:46 +05301696 if self.vim_type == "VIO":
1697 container_format = "bare"
sousaedu80135b92021-02-17 15:05:18 +01001698 if "container_format" in image_dict:
1699 container_format = image_dict["container_format"]
1700
1701 new_image = self.glance.images.create(
1702 name=image_dict["name"],
1703 container_format=container_format,
1704 disk_format=disk_format,
1705 )
shashankjain3c83a212018-10-04 13:05:46 +05301706 else:
sousaedu80135b92021-02-17 15:05:18 +01001707 new_image = self.glance.images.create(name=image_dict["name"])
1708
1709 if image_dict["location"].startswith("http"):
tierno1beea862018-07-11 15:47:37 +02001710 # TODO there is not a method to direct download. It must be downloaded locally with requests
tierno72774862020-05-04 11:44:15 +00001711 raise vimconn.VimConnNotImplemented("Cannot create image from URL")
tierno1ec592d2020-06-16 15:29:47 +00001712 else: # local path
sousaedu80135b92021-02-17 15:05:18 +01001713 with open(image_dict["location"]) as fimage:
tierno1beea862018-07-11 15:47:37 +02001714 self.glance.images.upload(new_image.id, fimage)
sousaedu80135b92021-02-17 15:05:18 +01001715 # new_image = self.glancev1.images.create(name=image_dict["name"], is_public=
1716 # image_dict.get("public","yes")=="yes",
tierno1beea862018-07-11 15:47:37 +02001717 # container_format="bare", data=fimage, disk_format=disk_format)
sousaedu80135b92021-02-17 15:05:18 +01001718
1719 metadata_to_load = image_dict.get("metadata")
1720
1721 # TODO location is a reserved word for current openstack versions. fixed for VIO please check
tierno1ec592d2020-06-16 15:29:47 +00001722 # for openstack
shashankjain3c83a212018-10-04 13:05:46 +05301723 if self.vim_type == "VIO":
sousaedu80135b92021-02-17 15:05:18 +01001724 metadata_to_load["upload_location"] = image_dict["location"]
shashankjain3c83a212018-10-04 13:05:46 +05301725 else:
sousaedu80135b92021-02-17 15:05:18 +01001726 metadata_to_load["location"] = image_dict["location"]
1727
tierno1beea862018-07-11 15:47:37 +02001728 self.glance.images.update(new_image.id, **metadata_to_load)
sousaedu80135b92021-02-17 15:05:18 +01001729
tiernoae4a8d12016-07-08 12:30:39 +02001730 return new_image.id
sousaedu80135b92021-02-17 15:05:18 +01001731 except (
sousaedu80135b92021-02-17 15:05:18 +01001732 HTTPException,
1733 gl1Exceptions.HTTPException,
1734 gl1Exceptions.CommunicationError,
1735 ConnectionError,
1736 ) as e:
tierno1ec592d2020-06-16 15:29:47 +00001737 if retry == max_retries:
tiernoae4a8d12016-07-08 12:30:39 +02001738 continue
sousaedu80135b92021-02-17 15:05:18 +01001739
tiernoae4a8d12016-07-08 12:30:39 +02001740 self._format_exception(e)
tierno1ec592d2020-06-16 15:29:47 +00001741 except IOError as e: # can not open the file
sousaedu80135b92021-02-17 15:05:18 +01001742 raise vimconn.VimConnConnectionException(
1743 "{}: {} for {}".format(type(e).__name__, e, image_dict["location"]),
1744 http_code=vimconn.HTTP_Bad_Request,
1745 )
gatici335a06a2023-07-26 00:34:04 +03001746 except Exception as e:
1747 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001748
gatici335a06a2023-07-26 00:34:04 +03001749 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001750 def delete_image(self, image_id):
sousaedu80135b92021-02-17 15:05:18 +01001751 """Deletes a tenant image from openstack VIM. Returns the old id"""
tiernoae4a8d12016-07-08 12:30:39 +02001752 try:
1753 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001754 self.glance.images.delete(image_id)
sousaedu80135b92021-02-17 15:05:18 +01001755
tiernoae4a8d12016-07-08 12:30:39 +02001756 return image_id
gatici335a06a2023-07-26 00:34:04 +03001757 except gl1Exceptions.NotFound as e:
1758 # If image is not found, it does not raise.
1759 self.logger.warning(
1760 f"Error deleting image: {image_id} is not found, {str(e)}"
1761 )
tiernoae4a8d12016-07-08 12:30:39 +02001762
gatici335a06a2023-07-26 00:34:04 +03001763 @catch_any_exception
tiernoae4a8d12016-07-08 12:30:39 +02001764 def get_image_id_from_path(self, path):
tierno1ec592d2020-06-16 15:29:47 +00001765 """Get the image id from image path in the VIM database. Returns the image_id"""
gatici335a06a2023-07-26 00:34:04 +03001766 self._reload_connection()
1767 images = self.glance.images.list()
sousaedu80135b92021-02-17 15:05:18 +01001768
gatici335a06a2023-07-26 00:34:04 +03001769 for image in images:
1770 if image.metadata.get("location") == path:
1771 return image.id
sousaedu80135b92021-02-17 15:05:18 +01001772
gatici335a06a2023-07-26 00:34:04 +03001773 raise vimconn.VimConnNotFoundException(
1774 "image with location '{}' not found".format(path)
1775 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001776
garciadeblasb69fa9f2016-09-28 12:04:10 +02001777 def get_image_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001778 """Obtain tenant images from VIM
garciadeblasb69fa9f2016-09-28 12:04:10 +02001779 Filter_dict can be:
1780 id: image id
1781 name: image name
1782 checksum: image checksum
1783 Returns the image list of dictionaries:
1784 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1785 List can be empty
tierno1ec592d2020-06-16 15:29:47 +00001786 """
garciadeblasb69fa9f2016-09-28 12:04:10 +02001787 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
1788 try:
1789 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001790 # filter_dict_os = filter_dict.copy()
1791 # First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +02001792 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +02001793 filtered_list = []
sousaedu80135b92021-02-17 15:05:18 +01001794
garciadeblasb69fa9f2016-09-28 12:04:10 +02001795 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +02001796 try:
tierno1beea862018-07-11 15:47:37 +02001797 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
1798 continue
sousaedu80135b92021-02-17 15:05:18 +01001799
tierno1beea862018-07-11 15:47:37 +02001800 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
1801 continue
sousaedu80135b92021-02-17 15:05:18 +01001802
1803 if (
1804 filter_dict.get("checksum")
1805 and image["checksum"] != filter_dict["checksum"]
1806 ):
tierno1beea862018-07-11 15:47:37 +02001807 continue
1808
1809 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +02001810 except gl1Exceptions.HTTPNotFound:
1811 pass
sousaedu80135b92021-02-17 15:05:18 +01001812
garciadeblasb69fa9f2016-09-28 12:04:10 +02001813 return filtered_list
gatici335a06a2023-07-26 00:34:04 +03001814
sousaedu80135b92021-02-17 15:05:18 +01001815 except (
1816 ksExceptions.ClientException,
1817 nvExceptions.ClientException,
1818 gl1Exceptions.CommunicationError,
1819 ConnectionError,
1820 ) as e:
garciadeblasb69fa9f2016-09-28 12:04:10 +02001821 self._format_exception(e)
1822
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001823 def __wait_for_vm(self, vm_id, status):
1824 """wait until vm is in the desired status and return True.
1825 If the VM gets in ERROR status, return false.
1826 If the timeout is reached generate an exception"""
1827 elapsed_time = 0
1828 while elapsed_time < server_timeout:
1829 vm_status = self.nova.servers.get(vm_id).status
sousaedu80135b92021-02-17 15:05:18 +01001830
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001831 if vm_status == status:
1832 return True
sousaedu80135b92021-02-17 15:05:18 +01001833
1834 if vm_status == "ERROR":
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001835 return False
sousaedu80135b92021-02-17 15:05:18 +01001836
tierno1df468d2018-07-06 14:25:16 +02001837 time.sleep(5)
1838 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001839
1840 # if we exceeded the timeout rollback
1841 if elapsed_time >= server_timeout:
sousaedu80135b92021-02-17 15:05:18 +01001842 raise vimconn.VimConnException(
1843 "Timeout waiting for instance " + vm_id + " to get " + status,
1844 http_code=vimconn.HTTP_Request_Timeout,
1845 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001846
mirabal29356312017-07-27 12:21:22 +02001847 def _get_openstack_availablity_zones(self):
1848 """
1849 Get from openstack availability zones available
1850 :return:
1851 """
1852 try:
1853 openstack_availability_zone = self.nova.availability_zones.list()
sousaedu80135b92021-02-17 15:05:18 +01001854 openstack_availability_zone = [
1855 str(zone.zoneName)
1856 for zone in openstack_availability_zone
1857 if zone.zoneName != "internal"
1858 ]
1859
mirabal29356312017-07-27 12:21:22 +02001860 return openstack_availability_zone
tierno1ec592d2020-06-16 15:29:47 +00001861 except Exception:
mirabal29356312017-07-27 12:21:22 +02001862 return None
1863
1864 def _set_availablity_zones(self):
1865 """
1866 Set vim availablity zone
1867 :return:
1868 """
sousaedu80135b92021-02-17 15:05:18 +01001869 if "availability_zone" in self.config:
1870 vim_availability_zones = self.config.get("availability_zone")
mirabal29356312017-07-27 12:21:22 +02001871
mirabal29356312017-07-27 12:21:22 +02001872 if isinstance(vim_availability_zones, str):
1873 self.availability_zone = [vim_availability_zones]
1874 elif isinstance(vim_availability_zones, list):
1875 self.availability_zone = vim_availability_zones
1876 else:
1877 self.availability_zone = self._get_openstack_availablity_zones()
Luis Vega25bc6382023-10-05 23:22:04 +00001878 if "storage_availability_zone" in self.config:
1879 self.storage_availability_zone = self.config.get(
1880 "storage_availability_zone"
1881 )
mirabal29356312017-07-27 12:21:22 +02001882
sousaedu80135b92021-02-17 15:05:18 +01001883 def _get_vm_availability_zone(
1884 self, availability_zone_index, availability_zone_list
1885 ):
mirabal29356312017-07-27 12:21:22 +02001886 """
tierno5a3273c2017-08-29 11:43:46 +02001887 Return thge availability zone to be used by the created VM.
1888 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +02001889 """
tierno5a3273c2017-08-29 11:43:46 +02001890 if availability_zone_index is None:
sousaedu80135b92021-02-17 15:05:18 +01001891 if not self.config.get("availability_zone"):
tierno5a3273c2017-08-29 11:43:46 +02001892 return None
sousaedu80135b92021-02-17 15:05:18 +01001893 elif isinstance(self.config.get("availability_zone"), str):
1894 return self.config["availability_zone"]
tierno5a3273c2017-08-29 11:43:46 +02001895 else:
1896 # TODO consider using a different parameter at config for default AV and AV list match
sousaedu80135b92021-02-17 15:05:18 +01001897 return self.config["availability_zone"][0]
mirabal29356312017-07-27 12:21:22 +02001898
tierno5a3273c2017-08-29 11:43:46 +02001899 vim_availability_zones = self.availability_zone
1900 # check if VIM offer enough availability zones describe in the VNFD
sousaedu80135b92021-02-17 15:05:18 +01001901 if vim_availability_zones and len(availability_zone_list) <= len(
1902 vim_availability_zones
1903 ):
tierno5a3273c2017-08-29 11:43:46 +02001904 # check if all the names of NFV AV match VIM AV names
1905 match_by_index = False
1906 for av in availability_zone_list:
1907 if av not in vim_availability_zones:
1908 match_by_index = True
1909 break
sousaedu80135b92021-02-17 15:05:18 +01001910
tierno5a3273c2017-08-29 11:43:46 +02001911 if match_by_index:
1912 return vim_availability_zones[availability_zone_index]
1913 else:
1914 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +02001915 else:
sousaedu80135b92021-02-17 15:05:18 +01001916 raise vimconn.VimConnConflictException(
1917 "No enough availability zones at VIM for this deployment"
1918 )
mirabal29356312017-07-27 12:21:22 +02001919
Gulsum Atici26f73662022-10-27 15:18:27 +03001920 def _prepare_port_dict_security_groups(self, net: dict, port_dict: dict) -> None:
1921 """Fill up the security_groups in the port_dict.
1922
1923 Args:
1924 net (dict): Network details
1925 port_dict (dict): Port details
1926
1927 """
1928 if (
1929 self.config.get("security_groups")
1930 and net.get("port_security") is not False
1931 and not self.config.get("no_port_security_extension")
1932 ):
1933 if not self.security_groups_id:
1934 self._get_ids_from_name()
1935
1936 port_dict["security_groups"] = self.security_groups_id
1937
1938 def _prepare_port_dict_binding(self, net: dict, port_dict: dict) -> None:
1939 """Fill up the network binding depending on network type in the port_dict.
1940
1941 Args:
1942 net (dict): Network details
1943 port_dict (dict): Port details
1944
1945 """
1946 if not net.get("type"):
1947 raise vimconn.VimConnException("Type is missing in the network details.")
1948
1949 if net["type"] == "virtual":
1950 pass
1951
1952 # For VF
1953 elif net["type"] == "VF" or net["type"] == "SR-IOV":
Gulsum Atici26f73662022-10-27 15:18:27 +03001954 port_dict["binding:vnic_type"] = "direct"
1955
1956 # VIO specific Changes
1957 if self.vim_type == "VIO":
1958 # Need to create port with port_security_enabled = False and no-security-groups
1959 port_dict["port_security_enabled"] = False
1960 port_dict["provider_security_groups"] = []
1961 port_dict["security_groups"] = []
1962
1963 else:
1964 # For PT PCI-PASSTHROUGH
1965 port_dict["binding:vnic_type"] = "direct-physical"
1966
1967 @staticmethod
1968 def _set_fixed_ip(new_port: dict, net: dict) -> None:
1969 """Set the "ip" parameter in net dictionary.
1970
1971 Args:
1972 new_port (dict): New created port
1973 net (dict): Network details
1974
1975 """
1976 fixed_ips = new_port["port"].get("fixed_ips")
1977
1978 if fixed_ips:
1979 net["ip"] = fixed_ips[0].get("ip_address")
1980 else:
1981 net["ip"] = None
1982
1983 @staticmethod
1984 def _prepare_port_dict_mac_ip_addr(net: dict, port_dict: dict) -> None:
1985 """Fill up the mac_address and fixed_ips in port_dict.
1986
1987 Args:
1988 net (dict): Network details
1989 port_dict (dict): Port details
1990
1991 """
1992 if net.get("mac_address"):
1993 port_dict["mac_address"] = net["mac_address"]
1994
elumalai370e36b2023-04-25 16:22:56 +05301995 ip_dual_list = []
1996 if ip_list := net.get("ip_address"):
1997 if not isinstance(ip_list, list):
1998 ip_list = [ip_list]
1999 for ip in ip_list:
2000 ip_dict = {"ip_address": ip}
2001 ip_dual_list.append(ip_dict)
2002 port_dict["fixed_ips"] = ip_dual_list
Gulsum Atici26f73662022-10-27 15:18:27 +03002003 # TODO add "subnet_id": <subnet_id>
2004
2005 def _create_new_port(self, port_dict: dict, created_items: dict, net: dict) -> Dict:
2006 """Create new port using neutron.
2007
2008 Args:
2009 port_dict (dict): Port details
2010 created_items (dict): All created items
2011 net (dict): Network details
2012
2013 Returns:
2014 new_port (dict): New created port
2015
2016 """
2017 new_port = self.neutron.create_port({"port": port_dict})
2018 created_items["port:" + str(new_port["port"]["id"])] = True
elumalaie17cd942023-04-28 18:04:24 +05302019 net["mac_address"] = new_port["port"]["mac_address"]
Gulsum Atici26f73662022-10-27 15:18:27 +03002020 net["vim_id"] = new_port["port"]["id"]
2021
2022 return new_port
2023
2024 def _create_port(
2025 self, net: dict, name: str, created_items: dict
2026 ) -> Tuple[dict, dict]:
2027 """Create port using net details.
2028
2029 Args:
2030 net (dict): Network details
2031 name (str): Name to be used as network name if net dict does not include name
2032 created_items (dict): All created items
2033
2034 Returns:
2035 new_port, port New created port, port dictionary
2036
2037 """
2038
2039 port_dict = {
2040 "network_id": net["net_id"],
2041 "name": net.get("name"),
2042 "admin_state_up": True,
2043 }
2044
2045 if not port_dict["name"]:
2046 port_dict["name"] = name
2047
2048 self._prepare_port_dict_security_groups(net, port_dict)
2049
2050 self._prepare_port_dict_binding(net, port_dict)
2051
2052 vimconnector._prepare_port_dict_mac_ip_addr(net, port_dict)
2053
2054 new_port = self._create_new_port(port_dict, created_items, net)
2055
2056 vimconnector._set_fixed_ip(new_port, net)
2057
2058 port = {"port-id": new_port["port"]["id"]}
2059
2060 if float(self.nova.api_version.get_string()) >= 2.32:
2061 port["tag"] = new_port["port"]["name"]
2062
2063 return new_port, port
2064
2065 def _prepare_network_for_vminstance(
2066 self,
2067 name: str,
2068 net_list: list,
2069 created_items: dict,
2070 net_list_vim: list,
2071 external_network: list,
2072 no_secured_ports: list,
2073 ) -> None:
2074 """Create port and fill up net dictionary for new VM instance creation.
2075
2076 Args:
2077 name (str): Name of network
2078 net_list (list): List of networks
2079 created_items (dict): All created items belongs to a VM
2080 net_list_vim (list): List of ports
2081 external_network (list): List of external-networks
2082 no_secured_ports (list): Port security disabled ports
2083 """
2084
2085 self._reload_connection()
2086
2087 for net in net_list:
2088 # Skip non-connected iface
2089 if not net.get("net_id"):
2090 continue
2091
2092 new_port, port = self._create_port(net, name, created_items)
2093
2094 net_list_vim.append(port)
2095
2096 if net.get("floating_ip", False):
2097 net["exit_on_floating_ip_error"] = True
2098 external_network.append(net)
2099
2100 elif net["use"] == "mgmt" and self.config.get("use_floating_ip"):
2101 net["exit_on_floating_ip_error"] = False
2102 external_network.append(net)
2103 net["floating_ip"] = self.config.get("use_floating_ip")
2104
2105 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic
2106 # is dropped. As a workaround we wait until the VM is active and then disable the port-security
2107 if net.get("port_security") is False and not self.config.get(
2108 "no_port_security_extension"
2109 ):
2110 no_secured_ports.append(
2111 (
2112 new_port["port"]["id"],
2113 net.get("port_security_disable_strategy"),
2114 )
2115 )
2116
2117 def _prepare_persistent_root_volumes(
2118 self,
2119 name: str,
Luis Vega25bc6382023-10-05 23:22:04 +00002120 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002121 disk: dict,
2122 base_disk_index: int,
2123 block_device_mapping: dict,
2124 existing_vim_volumes: list,
2125 created_items: dict,
2126 ) -> Optional[str]:
2127 """Prepare persistent root volumes for new VM instance.
2128
2129 Args:
2130 name (str): Name of VM instance
Luis Vega25bc6382023-10-05 23:22:04 +00002131 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002132 disk (dict): Disk details
2133 base_disk_index (int): Disk index
2134 block_device_mapping (dict): Block device details
2135 existing_vim_volumes (list): Existing disk details
2136 created_items (dict): All created items belongs to VM
2137
2138 Returns:
2139 boot_volume_id (str): ID of boot volume
2140
2141 """
garciadeblas6a0147f2024-08-19 08:02:04 +00002142 self.logger.debug("Preparing root persistent volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002143 # Disk may include only vim_volume_id or only vim_id."
2144 # Use existing persistent root volume finding with volume_id or vim_id
2145 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002146 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002147 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2148 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002149 else:
2150 # Create persistent root volume
2151 volume = self.cinder.volumes.create(
2152 size=disk["size"],
2153 name=name + "vd" + chr(base_disk_index),
2154 imageRef=disk["image_id"],
2155 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002156 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002157 )
2158 boot_volume_id = volume.id
aticig2f4ab6c2022-09-03 18:15:20 +03002159 self.update_block_device_mapping(
2160 volume=volume,
2161 block_device_mapping=block_device_mapping,
2162 base_disk_index=base_disk_index,
2163 disk=disk,
2164 created_items=created_items,
2165 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002166
2167 return boot_volume_id
2168
aticig2f4ab6c2022-09-03 18:15:20 +03002169 @staticmethod
2170 def update_block_device_mapping(
2171 volume: object,
2172 block_device_mapping: dict,
2173 base_disk_index: int,
2174 disk: dict,
2175 created_items: dict,
2176 ) -> None:
2177 """Add volume information to block device mapping dict.
2178 Args:
2179 volume (object): Created volume object
2180 block_device_mapping (dict): Block device details
2181 base_disk_index (int): Disk index
2182 disk (dict): Disk details
2183 created_items (dict): All created items belongs to VM
2184 """
2185 if not volume:
2186 raise vimconn.VimConnException("Volume is empty.")
2187
2188 if not hasattr(volume, "id"):
2189 raise vimconn.VimConnException(
2190 "Created volume is not valid, does not have id attribute."
2191 )
2192
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002193 block_device_mapping["vd" + chr(base_disk_index)] = volume.id
2194 if disk.get("multiattach"): # multiattach volumes do not belong to VDUs
2195 return
aticig2f4ab6c2022-09-03 18:15:20 +03002196 volume_txt = "volume:" + str(volume.id)
2197 if disk.get("keep"):
2198 volume_txt += ":keep"
2199 created_items[volume_txt] = True
aticig2f4ab6c2022-09-03 18:15:20 +03002200
gatici335a06a2023-07-26 00:34:04 +03002201 @catch_any_exception
vegall364627c2023-03-17 15:09:50 +00002202 def new_shared_volumes(self, shared_volume_data) -> (str, str):
garciadeblas6a0147f2024-08-19 08:02:04 +00002203 self.logger.debug("Creating new shared volume")
Luis Vega25bc6382023-10-05 23:22:04 +00002204 availability_zone = (
2205 self.storage_availability_zone
2206 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002207 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002208 )
gatici335a06a2023-07-26 00:34:04 +03002209 volume = self.cinder.volumes.create(
2210 size=shared_volume_data["size"],
2211 name=shared_volume_data["name"],
2212 volume_type="multiattach",
Luis Vega25bc6382023-10-05 23:22:04 +00002213 availability_zone=availability_zone,
gatici335a06a2023-07-26 00:34:04 +03002214 )
2215 return volume.name, volume.id
vegall364627c2023-03-17 15:09:50 +00002216
2217 def _prepare_shared_volumes(
2218 self,
2219 name: str,
2220 disk: dict,
2221 base_disk_index: int,
2222 block_device_mapping: dict,
2223 existing_vim_volumes: list,
2224 created_items: dict,
2225 ):
garciadeblas6a0147f2024-08-19 08:02:04 +00002226 self.logger.debug("Preparing shared volumes")
vegall364627c2023-03-17 15:09:50 +00002227 volumes = {volume.name: volume.id for volume in self.cinder.volumes.list()}
2228 if volumes.get(disk["name"]):
2229 sv_id = volumes[disk["name"]]
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002230 max_retries = 3
2231 vol_status = ""
2232 # If this is not the first VM to attach the volume, volume status may be "reserved" for a short time
2233 while max_retries:
2234 max_retries -= 1
2235 volume = self.cinder.volumes.get(sv_id)
2236 vol_status = volume.status
2237 if volume.status not in ("in-use", "available"):
2238 time.sleep(5)
2239 continue
2240 self.update_block_device_mapping(
2241 volume=volume,
2242 block_device_mapping=block_device_mapping,
2243 base_disk_index=base_disk_index,
2244 disk=disk,
2245 created_items=created_items,
2246 )
2247 return
2248 raise vimconn.VimConnException(
2249 "Shared volume is not prepared, status is: {}".format(vol_status),
2250 http_code=vimconn.HTTP_Internal_Server_Error,
vegall364627c2023-03-17 15:09:50 +00002251 )
2252
Gulsum Atici26f73662022-10-27 15:18:27 +03002253 def _prepare_non_root_persistent_volumes(
2254 self,
2255 name: str,
2256 disk: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002257 storage_av_zone: list,
Gulsum Atici26f73662022-10-27 15:18:27 +03002258 block_device_mapping: dict,
2259 base_disk_index: int,
2260 existing_vim_volumes: list,
2261 created_items: dict,
2262 ) -> None:
2263 """Prepare persistent volumes for new VM instance.
2264
2265 Args:
2266 name (str): Name of VM instance
2267 disk (dict): Disk details
Luis Vega25bc6382023-10-05 23:22:04 +00002268 storage_av_zone (list): Storage of availability zones
Gulsum Atici26f73662022-10-27 15:18:27 +03002269 block_device_mapping (dict): Block device details
2270 base_disk_index (int): Disk index
2271 existing_vim_volumes (list): Existing disk details
2272 created_items (dict): All created items belongs to VM
2273 """
2274 # Non-root persistent volumes
2275 # Disk may include only vim_volume_id or only vim_id."
garciadeblas6a0147f2024-08-19 08:02:04 +00002276 self.logger.debug("Preparing non-root persistent volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002277 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002278 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002279 # Use existing persistent volume
2280 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2281 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002282 else:
vegall364627c2023-03-17 15:09:50 +00002283 volume_name = f"{name}vd{chr(base_disk_index)}"
Gulsum Atici26f73662022-10-27 15:18:27 +03002284 volume = self.cinder.volumes.create(
2285 size=disk["size"],
vegall364627c2023-03-17 15:09:50 +00002286 name=volume_name,
Gulsum Atici26f73662022-10-27 15:18:27 +03002287 # Make sure volume is in the same AZ as the VM to be attached to
Luis Vega25bc6382023-10-05 23:22:04 +00002288 availability_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002289 )
aticig2f4ab6c2022-09-03 18:15:20 +03002290 self.update_block_device_mapping(
2291 volume=volume,
2292 block_device_mapping=block_device_mapping,
2293 base_disk_index=base_disk_index,
2294 disk=disk,
2295 created_items=created_items,
2296 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002297
2298 def _wait_for_created_volumes_availability(
2299 self, elapsed_time: int, created_items: dict
2300 ) -> Optional[int]:
2301 """Wait till created volumes become available.
2302
2303 Args:
2304 elapsed_time (int): Passed time while waiting
2305 created_items (dict): All created items belongs to VM
2306
2307 Returns:
2308 elapsed_time (int): Time spent while waiting
2309
2310 """
garciadeblas6a0147f2024-08-19 08:02:04 +00002311 self.logger.debug("Waiting for all created volumes to become available")
Gulsum Atici26f73662022-10-27 15:18:27 +03002312 while elapsed_time < volume_timeout:
garciadeblas6a0147f2024-08-19 08:02:04 +00002313 self.logger.debug("Checking disk availability for created volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002314 for created_item in created_items:
aticig2f4ab6c2022-09-03 18:15:20 +03002315 v, volume_id = (
2316 created_item.split(":")[0],
2317 created_item.split(":")[1],
2318 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002319 if v == "volume":
garciadeblas6a0147f2024-08-19 08:02:04 +00002320 self.logger.debug(f"Checking volume: {volume_id}")
vegall364627c2023-03-17 15:09:50 +00002321 volume = self.cinder.volumes.get(volume_id)
2322 if (
2323 volume.volume_type == "multiattach"
2324 and volume.status == "in-use"
2325 ):
2326 return elapsed_time
2327 elif volume.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002328 break
2329 else:
2330 # All ready: break from while
2331 break
2332
2333 time.sleep(5)
2334 elapsed_time += 5
2335
2336 return elapsed_time
2337
2338 def _wait_for_existing_volumes_availability(
2339 self, elapsed_time: int, existing_vim_volumes: list
2340 ) -> Optional[int]:
2341 """Wait till existing volumes become available.
2342
2343 Args:
2344 elapsed_time (int): Passed time while waiting
2345 existing_vim_volumes (list): Existing volume details
2346
2347 Returns:
2348 elapsed_time (int): Time spent while waiting
2349
2350 """
2351
garciadeblas6a0147f2024-08-19 08:02:04 +00002352 self.logger.debug("Waiting for all existing volumes to become available")
Gulsum Atici26f73662022-10-27 15:18:27 +03002353 while elapsed_time < volume_timeout:
garciadeblas6a0147f2024-08-19 08:02:04 +00002354 self.logger.debug("Checking disk availability for existing volumes")
Gulsum Atici26f73662022-10-27 15:18:27 +03002355 for volume in existing_vim_volumes:
garciadeblas6a0147f2024-08-19 08:02:04 +00002356 self.logger.debug(f"Checking existing volume: {volume}")
vegall364627c2023-03-17 15:09:50 +00002357 v = self.cinder.volumes.get(volume["id"])
2358 if v.volume_type == "multiattach" and v.status == "in-use":
2359 return elapsed_time
2360 elif v.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002361 break
2362 else: # all ready: break from while
2363 break
2364
2365 time.sleep(5)
2366 elapsed_time += 5
2367
2368 return elapsed_time
2369
2370 def _prepare_disk_for_vminstance(
2371 self,
2372 name: str,
2373 existing_vim_volumes: list,
2374 created_items: dict,
Luis Vega25bc6382023-10-05 23:22:04 +00002375 storage_av_zone: list,
Gulsum Atici13d02322022-11-18 00:10:15 +03002376 block_device_mapping: dict,
Gulsum Atici26f73662022-10-27 15:18:27 +03002377 disk_list: list = None,
2378 ) -> None:
2379 """Prepare all volumes for new VM instance.
2380
2381 Args:
2382 name (str): Name of Instance
2383 existing_vim_volumes (list): List of existing volumes
2384 created_items (dict): All created items belongs to VM
Luis Vega25bc6382023-10-05 23:22:04 +00002385 storage_av_zone (list): Storage availability zone
Gulsum Atici13d02322022-11-18 00:10:15 +03002386 block_device_mapping (dict): Block devices to be attached to VM
Gulsum Atici26f73662022-10-27 15:18:27 +03002387 disk_list (list): List of disks
2388
2389 """
2390 # Create additional volumes in case these are present in disk_list
garciadeblas6a0147f2024-08-19 08:02:04 +00002391 self.logger.debug("Preparing disks for VM instances")
Gulsum Atici26f73662022-10-27 15:18:27 +03002392 base_disk_index = ord("b")
2393 boot_volume_id = None
2394 elapsed_time = 0
Gulsum Atici26f73662022-10-27 15:18:27 +03002395 for disk in disk_list:
garciadeblas6a0147f2024-08-19 08:02:04 +00002396 self.logger.debug(f"Disk: {disk}")
Gulsum Atici26f73662022-10-27 15:18:27 +03002397 if "image_id" in disk:
2398 # Root persistent volume
2399 base_disk_index = ord("a")
2400 boot_volume_id = self._prepare_persistent_root_volumes(
2401 name=name,
Luis Vega25bc6382023-10-05 23:22:04 +00002402 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002403 disk=disk,
2404 base_disk_index=base_disk_index,
2405 block_device_mapping=block_device_mapping,
2406 existing_vim_volumes=existing_vim_volumes,
2407 created_items=created_items,
2408 )
vegall364627c2023-03-17 15:09:50 +00002409 elif disk.get("multiattach"):
2410 self._prepare_shared_volumes(
2411 name=name,
2412 disk=disk,
2413 base_disk_index=base_disk_index,
2414 block_device_mapping=block_device_mapping,
2415 existing_vim_volumes=existing_vim_volumes,
2416 created_items=created_items,
2417 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002418 else:
2419 # Non-root persistent volume
2420 self._prepare_non_root_persistent_volumes(
2421 name=name,
2422 disk=disk,
Luis Vega25bc6382023-10-05 23:22:04 +00002423 storage_av_zone=storage_av_zone,
Gulsum Atici26f73662022-10-27 15:18:27 +03002424 block_device_mapping=block_device_mapping,
2425 base_disk_index=base_disk_index,
2426 existing_vim_volumes=existing_vim_volumes,
2427 created_items=created_items,
2428 )
2429 base_disk_index += 1
2430
2431 # Wait until created volumes are with status available
2432 elapsed_time = self._wait_for_created_volumes_availability(
2433 elapsed_time, created_items
2434 )
2435 # Wait until existing volumes in vim are with status available
2436 elapsed_time = self._wait_for_existing_volumes_availability(
2437 elapsed_time, existing_vim_volumes
2438 )
2439 # If we exceeded the timeout rollback
2440 if elapsed_time >= volume_timeout:
2441 raise vimconn.VimConnException(
2442 "Timeout creating volumes for instance " + name,
2443 http_code=vimconn.HTTP_Request_Timeout,
2444 )
2445 if boot_volume_id:
2446 self.cinder.volumes.set_bootable(boot_volume_id, True)
2447
2448 def _find_the_external_network_for_floating_ip(self):
2449 """Get the external network ip in order to create floating IP.
2450
2451 Returns:
2452 pool_id (str): External network pool ID
2453
2454 """
2455
2456 # Find the external network
2457 external_nets = list()
2458
2459 for net in self.neutron.list_networks()["networks"]:
2460 if net["router:external"]:
2461 external_nets.append(net)
2462
2463 if len(external_nets) == 0:
2464 raise vimconn.VimConnException(
2465 "Cannot create floating_ip automatically since "
2466 "no external network is present",
2467 http_code=vimconn.HTTP_Conflict,
2468 )
2469
2470 if len(external_nets) > 1:
2471 raise vimconn.VimConnException(
2472 "Cannot create floating_ip automatically since "
2473 "multiple external networks are present",
2474 http_code=vimconn.HTTP_Conflict,
2475 )
2476
2477 # Pool ID
2478 return external_nets[0].get("id")
2479
2480 def _neutron_create_float_ip(self, param: dict, created_items: dict) -> None:
2481 """Trigger neutron to create a new floating IP using external network ID.
2482
2483 Args:
2484 param (dict): Input parameters to create a floating IP
2485 created_items (dict): All created items belongs to new VM instance
2486
2487 Raises:
2488
2489 VimConnException
2490 """
2491 try:
2492 self.logger.debug("Creating floating IP")
2493 new_floating_ip = self.neutron.create_floatingip(param)
2494 free_floating_ip = new_floating_ip["floatingip"]["id"]
2495 created_items["floating_ip:" + str(free_floating_ip)] = True
2496
2497 except Exception as e:
2498 raise vimconn.VimConnException(
2499 type(e).__name__ + ": Cannot create new floating_ip " + str(e),
2500 http_code=vimconn.HTTP_Conflict,
2501 )
2502
2503 def _create_floating_ip(
2504 self, floating_network: dict, server: object, created_items: dict
2505 ) -> None:
2506 """Get the available Pool ID and create a new floating IP.
2507
2508 Args:
2509 floating_network (dict): Dict including external network ID
2510 server (object): Server object
2511 created_items (dict): All created items belongs to new VM instance
2512
2513 """
2514
2515 # Pool_id is available
2516 if (
2517 isinstance(floating_network["floating_ip"], str)
2518 and floating_network["floating_ip"].lower() != "true"
2519 ):
2520 pool_id = floating_network["floating_ip"]
2521
2522 # Find the Pool_id
2523 else:
2524 pool_id = self._find_the_external_network_for_floating_ip()
2525
2526 param = {
2527 "floatingip": {
2528 "floating_network_id": pool_id,
2529 "tenant_id": server.tenant_id,
2530 }
2531 }
2532
2533 self._neutron_create_float_ip(param, created_items)
2534
2535 def _find_floating_ip(
2536 self,
2537 server: object,
2538 floating_ips: list,
2539 floating_network: dict,
2540 ) -> Optional[str]:
2541 """Find the available free floating IPs if there are.
2542
2543 Args:
2544 server (object): Server object
2545 floating_ips (list): List of floating IPs
2546 floating_network (dict): Details of floating network such as ID
2547
2548 Returns:
2549 free_floating_ip (str): Free floating ip address
2550
2551 """
2552 for fip in floating_ips:
2553 if fip.get("port_id") or fip.get("tenant_id") != server.tenant_id:
2554 continue
2555
2556 if isinstance(floating_network["floating_ip"], str):
2557 if fip.get("floating_network_id") != floating_network["floating_ip"]:
2558 continue
2559
2560 return fip["id"]
2561
2562 def _assign_floating_ip(
2563 self, free_floating_ip: str, floating_network: dict
2564 ) -> Dict:
2565 """Assign the free floating ip address to port.
2566
2567 Args:
2568 free_floating_ip (str): Floating IP to be assigned
2569 floating_network (dict): ID of floating network
2570
2571 Returns:
2572 fip (dict) (dict): Floating ip details
2573
2574 """
2575 # The vim_id key contains the neutron.port_id
2576 self.neutron.update_floatingip(
2577 free_floating_ip,
2578 {"floatingip": {"port_id": floating_network["vim_id"]}},
2579 )
2580 # For race condition ensure not re-assigned to other VM after 5 seconds
2581 time.sleep(5)
2582
2583 return self.neutron.show_floatingip(free_floating_ip)
2584
2585 def _get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002586 self, server: object, floating_network: dict
Gulsum Atici26f73662022-10-27 15:18:27 +03002587 ) -> Optional[str]:
2588 """Get the free floating IP address.
2589
2590 Args:
2591 server (object): Server Object
2592 floating_network (dict): Floating network details
Gulsum Atici26f73662022-10-27 15:18:27 +03002593
2594 Returns:
2595 free_floating_ip (str): Free floating ip addr
2596
2597 """
2598
2599 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
2600
2601 # Randomize
2602 random.shuffle(floating_ips)
2603
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002604 return self._find_floating_ip(server, floating_ips, floating_network)
Gulsum Atici26f73662022-10-27 15:18:27 +03002605
2606 def _prepare_external_network_for_vminstance(
2607 self,
2608 external_network: list,
2609 server: object,
2610 created_items: dict,
2611 vm_start_time: float,
2612 ) -> None:
2613 """Assign floating IP address for VM instance.
2614
2615 Args:
2616 external_network (list): ID of External network
2617 server (object): Server Object
2618 created_items (dict): All created items belongs to new VM instance
2619 vm_start_time (float): Time as a floating point number expressed in seconds since the epoch, in UTC
2620
2621 Raises:
2622 VimConnException
2623
2624 """
2625 for floating_network in external_network:
2626 try:
2627 assigned = False
2628 floating_ip_retries = 3
2629 # In case of RO in HA there can be conflicts, two RO trying to assign same floating IP, so retry
2630 # several times
2631 while not assigned:
Gulsum Atici26f73662022-10-27 15:18:27 +03002632 free_floating_ip = self._get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002633 server, floating_network
Gulsum Atici26f73662022-10-27 15:18:27 +03002634 )
2635
2636 if not free_floating_ip:
2637 self._create_floating_ip(
2638 floating_network, server, created_items
2639 )
2640
2641 try:
2642 # For race condition ensure not already assigned
2643 fip = self.neutron.show_floatingip(free_floating_ip)
2644
2645 if fip["floatingip"].get("port_id"):
2646 continue
2647
2648 # Assign floating ip
2649 fip = self._assign_floating_ip(
2650 free_floating_ip, floating_network
2651 )
2652
2653 if fip["floatingip"]["port_id"] != floating_network["vim_id"]:
2654 self.logger.warning(
2655 "floating_ip {} re-assigned to other port".format(
2656 free_floating_ip
2657 )
2658 )
2659 continue
2660
2661 self.logger.debug(
2662 "Assigned floating_ip {} to VM {}".format(
2663 free_floating_ip, server.id
2664 )
2665 )
2666
2667 assigned = True
2668
2669 except Exception as e:
2670 # Openstack need some time after VM creation to assign an IP. So retry if fails
2671 vm_status = self.nova.servers.get(server.id).status
2672
2673 if vm_status not in ("ACTIVE", "ERROR"):
2674 if time.time() - vm_start_time < server_timeout:
2675 time.sleep(5)
2676 continue
2677 elif floating_ip_retries > 0:
2678 floating_ip_retries -= 1
2679 continue
2680
2681 raise vimconn.VimConnException(
2682 "Cannot create floating_ip: {} {}".format(
2683 type(e).__name__, e
2684 ),
2685 http_code=vimconn.HTTP_Conflict,
2686 )
2687
2688 except Exception as e:
2689 if not floating_network["exit_on_floating_ip_error"]:
2690 self.logger.error("Cannot create floating_ip. %s", str(e))
2691 continue
2692
2693 raise
2694
2695 def _update_port_security_for_vminstance(
2696 self,
2697 no_secured_ports: list,
2698 server: object,
2699 ) -> None:
2700 """Updates the port security according to no_secured_ports list.
2701
2702 Args:
2703 no_secured_ports (list): List of ports that security will be disabled
2704 server (object): Server Object
2705
2706 Raises:
2707 VimConnException
2708
2709 """
2710 # Wait until the VM is active and then disable the port-security
2711 if no_secured_ports:
2712 self.__wait_for_vm(server.id, "ACTIVE")
2713
2714 for port in no_secured_ports:
2715 port_update = {
2716 "port": {"port_security_enabled": False, "security_groups": None}
2717 }
2718
2719 if port[1] == "allow-address-pairs":
2720 port_update = {
2721 "port": {"allowed_address_pairs": [{"ip_address": "0.0.0.0/0"}]}
2722 }
2723
2724 try:
2725 self.neutron.update_port(port[0], port_update)
2726
2727 except Exception:
Gulsum Atici26f73662022-10-27 15:18:27 +03002728 raise vimconn.VimConnException(
2729 "It was not possible to disable port security for port {}".format(
2730 port[0]
2731 )
2732 )
2733
sousaedu80135b92021-02-17 15:05:18 +01002734 def new_vminstance(
2735 self,
Gulsum Atici26f73662022-10-27 15:18:27 +03002736 name: str,
2737 description: str,
2738 start: bool,
2739 image_id: str,
2740 flavor_id: str,
2741 affinity_group_list: list,
2742 net_list: list,
sousaedu80135b92021-02-17 15:05:18 +01002743 cloud_config=None,
2744 disk_list=None,
2745 availability_zone_index=None,
2746 availability_zone_list=None,
Gulsum Atici26f73662022-10-27 15:18:27 +03002747 ) -> tuple:
2748 """Adds a VM instance to VIM.
2749
2750 Args:
2751 name (str): name of VM
2752 description (str): description
2753 start (bool): indicates if VM must start or boot in pause mode. Ignored
2754 image_id (str) image uuid
2755 flavor_id (str) flavor uuid
2756 affinity_group_list (list): list of affinity groups, each one is a dictionary.Ignore if empty.
2757 net_list (list): list of interfaces, each one is a dictionary with:
2758 name: name of network
2759 net_id: network uuid to connect
2760 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
2761 model: interface model, ignored #TODO
2762 mac_address: used for SR-IOV ifaces #TODO for other types
2763 use: 'data', 'bridge', 'mgmt'
2764 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
2765 vim_id: filled/added by this function
2766 floating_ip: True/False (or it can be None)
2767 port_security: True/False
2768 cloud_config (dict): (optional) dictionary with:
2769 key-pairs: (optional) list of strings with the public key to be inserted to the default user
2770 users: (optional) list of users to be inserted, each item is a dict with:
2771 name: (mandatory) user name,
2772 key-pairs: (optional) list of strings with the public key to be inserted to the user
2773 user-data: (optional) string is a text script to be passed directly to cloud-init
2774 config-files: (optional). List of files to be transferred. Each item is a dict with:
2775 dest: (mandatory) string with the destination absolute path
2776 encoding: (optional, by default text). Can be one of:
tierno1d213f42020-04-24 14:02:51 +00002777 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
Gulsum Atici26f73662022-10-27 15:18:27 +03002778 content : (mandatory) string with the content of the file
2779 permissions: (optional) string with file permissions, typically octal notation '0644'
2780 owner: (optional) file owner, string with the format 'owner:group'
2781 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
2782 disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
2783 image_id: (optional). VIM id of an existing image. If not provided an empty disk must be mounted
2784 size: (mandatory) string with the size of the disk in GB
2785 vim_id: (optional) should use this existing volume id
2786 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
2787 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
tierno5a3273c2017-08-29 11:43:46 +02002788 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01002789 #TODO ip, security groups
Gulsum Atici26f73662022-10-27 15:18:27 +03002790
2791 Returns:
2792 A tuple with the instance identifier and created_items or raises an exception on error
tierno98e909c2017-10-14 13:27:03 +02002793 created_items can be None or a dictionary where this method can include key-values that will be passed to
2794 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
2795 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
2796 as not present.
aticig2f4ab6c2022-09-03 18:15:20 +03002797
tierno98e909c2017-10-14 13:27:03 +02002798 """
sousaedu80135b92021-02-17 15:05:18 +01002799 self.logger.debug(
2800 "new_vminstance input: image='%s' flavor='%s' nics='%s'",
2801 image_id,
2802 flavor_id,
2803 str(net_list),
2804 )
gatici335a06a2023-07-26 00:34:04 +03002805 server = None
2806 created_items = {}
2807 net_list_vim = []
2808 # list of external networks to be connected to instance, later on used to create floating_ip
2809 external_network = []
2810 # List of ports with port-security disabled
2811 no_secured_ports = []
2812 block_device_mapping = {}
2813 existing_vim_volumes = []
2814 server_group_id = None
2815 scheduller_hints = {}
sousaedu80135b92021-02-17 15:05:18 +01002816
tierno7edb6752016-03-21 17:37:52 +01002817 try:
Gulsum Atici26f73662022-10-27 15:18:27 +03002818 # Check the Openstack Connection
2819 self._reload_connection()
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02002820
Gulsum Atici26f73662022-10-27 15:18:27 +03002821 # Prepare network list
2822 self._prepare_network_for_vminstance(
2823 name=name,
2824 net_list=net_list,
2825 created_items=created_items,
2826 net_list_vim=net_list_vim,
2827 external_network=external_network,
2828 no_secured_ports=no_secured_ports,
sousaedu80135b92021-02-17 15:05:18 +01002829 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002830
Gulsum Atici26f73662022-10-27 15:18:27 +03002831 # Cloud config
tierno0a1437e2017-10-02 00:17:43 +02002832 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00002833
Gulsum Atici26f73662022-10-27 15:18:27 +03002834 # Get availability Zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002835 self.vm_av_zone = self._get_vm_availability_zone(
Alexis Romero247cc432022-05-12 13:23:25 +02002836 availability_zone_index, availability_zone_list
2837 )
2838
Luis Vega25bc6382023-10-05 23:22:04 +00002839 storage_av_zone = (
2840 self.storage_availability_zone
2841 if self.storage_availability_zone
Luis Vegaafe8df22023-12-01 01:02:12 +00002842 else self.vm_av_zone
Luis Vega25bc6382023-10-05 23:22:04 +00002843 )
2844
tierno1df468d2018-07-06 14:25:16 +02002845 if disk_list:
Gulsum Atici26f73662022-10-27 15:18:27 +03002846 # Prepare disks
2847 self._prepare_disk_for_vminstance(
2848 name=name,
2849 existing_vim_volumes=existing_vim_volumes,
2850 created_items=created_items,
Luis Vega25bc6382023-10-05 23:22:04 +00002851 storage_av_zone=storage_av_zone,
Gulsum Atici13d02322022-11-18 00:10:15 +03002852 block_device_mapping=block_device_mapping,
Gulsum Atici26f73662022-10-27 15:18:27 +03002853 disk_list=disk_list,
2854 )
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002855
2856 if affinity_group_list:
2857 # Only first id on the list will be used. Openstack restriction
2858 server_group_id = affinity_group_list[0]["affinity_group_id"]
2859 scheduller_hints["group"] = server_group_id
2860
sousaedu80135b92021-02-17 15:05:18 +01002861 self.logger.debug(
2862 "nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
2863 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002864 "block_device_mapping={}, server_group={})".format(
sousaedu80135b92021-02-17 15:05:18 +01002865 name,
2866 image_id,
2867 flavor_id,
2868 net_list_vim,
2869 self.config.get("security_groups"),
Luis Vegaafe8df22023-12-01 01:02:12 +00002870 self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002871 self.config.get("keypair"),
2872 userdata,
2873 config_drive,
2874 block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002875 server_group_id,
sousaedu80135b92021-02-17 15:05:18 +01002876 )
2877 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002878 # Create VM
sousaedu80135b92021-02-17 15:05:18 +01002879 server = self.nova.servers.create(
aticigcf14bb12022-05-19 13:03:17 +03002880 name=name,
2881 image=image_id,
2882 flavor=flavor_id,
sousaedu80135b92021-02-17 15:05:18 +01002883 nics=net_list_vim,
2884 security_groups=self.config.get("security_groups"),
2885 # TODO remove security_groups in future versions. Already at neutron port
Luis Vegaafe8df22023-12-01 01:02:12 +00002886 availability_zone=self.vm_av_zone,
sousaedu80135b92021-02-17 15:05:18 +01002887 key_name=self.config.get("keypair"),
2888 userdata=userdata,
2889 config_drive=config_drive,
2890 block_device_mapping=block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002891 scheduler_hints=scheduller_hints,
Gulsum Atici26f73662022-10-27 15:18:27 +03002892 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002893
tierno326fd5e2018-02-22 11:58:59 +01002894 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002895
Gulsum Atici26f73662022-10-27 15:18:27 +03002896 self._update_port_security_for_vminstance(no_secured_ports, server)
bravof7a1f5252020-10-20 10:27:42 -03002897
Gulsum Atici26f73662022-10-27 15:18:27 +03002898 self._prepare_external_network_for_vminstance(
2899 external_network=external_network,
2900 server=server,
2901 created_items=created_items,
2902 vm_start_time=vm_start_time,
2903 )
montesmoreno2a1fc4e2017-01-09 16:46:04 +00002904
tierno98e909c2017-10-14 13:27:03 +02002905 return server.id, created_items
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002906
2907 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02002908 server_id = None
2909 if server:
2910 server_id = server.id
sousaedu80135b92021-02-17 15:05:18 +01002911
tierno98e909c2017-10-14 13:27:03 +02002912 try:
aticig2f4ab6c2022-09-03 18:15:20 +03002913 created_items = self.remove_keep_tag_from_persistent_volumes(
2914 created_items
2915 )
2916
tierno98e909c2017-10-14 13:27:03 +02002917 self.delete_vminstance(server_id, created_items)
Gulsum Atici26f73662022-10-27 15:18:27 +03002918
tierno98e909c2017-10-14 13:27:03 +02002919 except Exception as e2:
2920 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002921
tiernoae4a8d12016-07-08 12:30:39 +02002922 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01002923
aticig2f4ab6c2022-09-03 18:15:20 +03002924 @staticmethod
2925 def remove_keep_tag_from_persistent_volumes(created_items: Dict) -> Dict:
2926 """Removes the keep flag from persistent volumes. So, those volumes could be removed.
2927
2928 Args:
2929 created_items (dict): All created items belongs to VM
2930
2931 Returns:
2932 updated_created_items (dict): Dict which does not include keep flag for volumes.
2933
2934 """
2935 return {
2936 key.replace(":keep", ""): value for (key, value) in created_items.items()
2937 }
2938
tierno1ec592d2020-06-16 15:29:47 +00002939 def get_vminstance(self, vm_id):
2940 """Returns the VM instance information from VIM"""
vegallc53829d2023-06-01 00:47:44 -05002941 return self._find_nova_server(vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02002942
gatici335a06a2023-07-26 00:34:04 +03002943 @catch_any_exception
tierno1ec592d2020-06-16 15:29:47 +00002944 def get_vminstance_console(self, vm_id, console_type="vnc"):
2945 """
tierno7edb6752016-03-21 17:37:52 +01002946 Get a console for the virtual machine
2947 Params:
2948 vm_id: uuid of the VM
2949 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002950 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01002951 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02002952 Returns dict with the console parameters:
2953 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002954 server: usually ip address
2955 port: the http, ssh, ... port
2956 suffix: extra text, e.g. the http path and query string
tierno1ec592d2020-06-16 15:29:47 +00002957 """
tiernoae4a8d12016-07-08 12:30:39 +02002958 self.logger.debug("Getting VM CONSOLE from VIM")
gatici335a06a2023-07-26 00:34:04 +03002959 self._reload_connection()
2960 server = self.nova.servers.find(id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01002961
gatici335a06a2023-07-26 00:34:04 +03002962 if console_type is None or console_type == "novnc":
2963 console_dict = server.get_vnc_console("novnc")
2964 elif console_type == "xvpvnc":
2965 console_dict = server.get_vnc_console(console_type)
2966 elif console_type == "rdp-html5":
2967 console_dict = server.get_rdp_console(console_type)
2968 elif console_type == "spice-html5":
2969 console_dict = server.get_spice_console(console_type)
2970 else:
2971 raise vimconn.VimConnException(
2972 "console type '{}' not allowed".format(console_type),
2973 http_code=vimconn.HTTP_Bad_Request,
2974 )
sousaedu80135b92021-02-17 15:05:18 +01002975
gatici335a06a2023-07-26 00:34:04 +03002976 console_dict1 = console_dict.get("console")
2977
2978 if console_dict1:
2979 console_url = console_dict1.get("url")
2980
2981 if console_url:
2982 # parse console_url
2983 protocol_index = console_url.find("//")
2984 suffix_index = (
2985 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
2986 )
2987 port_index = (
2988 console_url[protocol_index + 2 : suffix_index].find(":")
2989 + protocol_index
2990 + 2
sousaedu80135b92021-02-17 15:05:18 +01002991 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002992
gatici335a06a2023-07-26 00:34:04 +03002993 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
2994 return (
2995 -vimconn.HTTP_Internal_Server_Error,
2996 "Unexpected response from VIM",
sousaedu80135b92021-02-17 15:05:18 +01002997 )
2998
gatici335a06a2023-07-26 00:34:04 +03002999 console_dict = {
3000 "protocol": console_url[0:protocol_index],
3001 "server": console_url[protocol_index + 2 : port_index],
3002 "port": console_url[port_index:suffix_index],
3003 "suffix": console_url[suffix_index + 1 :],
3004 }
3005 protocol_index += 2
sousaedu80135b92021-02-17 15:05:18 +01003006
gatici335a06a2023-07-26 00:34:04 +03003007 return console_dict
3008 raise vimconn.VimConnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01003009
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003010 def _delete_ports_by_id_wth_neutron(self, k_id: str) -> None:
3011 """Neutron delete ports by id.
3012 Args:
3013 k_id (str): Port id in the VIM
3014 """
3015 try:
limon878f8692023-07-24 15:53:41 +02003016 self.neutron.delete_port(k_id)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003017
gatici335a06a2023-07-26 00:34:04 +03003018 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3019 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3020 # If there is connection error, raise.
3021 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003022 except Exception as e:
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003023 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
3024
vegall364627c2023-03-17 15:09:50 +00003025 def delete_shared_volumes(self, shared_volume_vim_id: str) -> bool:
3026 """Cinder delete volume by id.
3027 Args:
3028 shared_volume_vim_id (str): ID of shared volume in VIM
3029 """
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003030 elapsed_time = 0
vegall364627c2023-03-17 15:09:50 +00003031 try:
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003032 while elapsed_time < server_timeout:
3033 vol_status = self.cinder.volumes.get(shared_volume_vim_id).status
3034 if vol_status == "available":
3035 self.cinder.volumes.delete(shared_volume_vim_id)
3036 return True
vegall364627c2023-03-17 15:09:50 +00003037
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003038 time.sleep(5)
3039 elapsed_time += 5
3040
3041 if elapsed_time >= server_timeout:
3042 raise vimconn.VimConnException(
3043 "Timeout waiting for volume "
3044 + shared_volume_vim_id
3045 + " to be available",
3046 http_code=vimconn.HTTP_Request_Timeout,
3047 )
vegall364627c2023-03-17 15:09:50 +00003048
3049 except Exception as e:
3050 self.logger.error(
3051 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3052 )
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003053 self._format_exception(e)
vegall364627c2023-03-17 15:09:50 +00003054
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003055 def _delete_volumes_by_id_wth_cinder(
3056 self, k: str, k_id: str, volumes_to_hold: list, created_items: dict
3057 ) -> bool:
3058 """Cinder delete volume by id.
3059 Args:
3060 k (str): Full item name in created_items
3061 k_id (str): ID of floating ip in VIM
3062 volumes_to_hold (list): Volumes not to delete
3063 created_items (dict): All created items belongs to VM
3064 """
3065 try:
3066 if k_id in volumes_to_hold:
gatici335a06a2023-07-26 00:34:04 +03003067 return False
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003068
3069 if self.cinder.volumes.get(k_id).status != "available":
3070 return True
3071
3072 else:
3073 self.cinder.volumes.delete(k_id)
3074 created_items[k] = None
3075
gatici335a06a2023-07-26 00:34:04 +03003076 except (cExceptions.ConnectionError, ConnectionError) as e:
3077 self.logger.error(
3078 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3079 )
3080 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003081 except Exception as e:
3082 self.logger.error(
3083 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3084 )
3085
3086 def _delete_floating_ip_by_id(self, k: str, k_id: str, created_items: dict) -> None:
3087 """Neutron delete floating ip by id.
3088 Args:
3089 k (str): Full item name in created_items
3090 k_id (str): ID of floating ip in VIM
3091 created_items (dict): All created items belongs to VM
3092 """
3093 try:
3094 self.neutron.delete_floatingip(k_id)
3095 created_items[k] = None
3096
gatici335a06a2023-07-26 00:34:04 +03003097 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3098 self.logger.error(
3099 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3100 )
3101 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003102 except Exception as e:
3103 self.logger.error(
3104 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3105 )
3106
3107 @staticmethod
3108 def _get_item_name_id(k: str) -> Tuple[str, str]:
3109 k_item, _, k_id = k.partition(":")
3110 return k_item, k_id
3111
3112 def _delete_vm_ports_attached_to_network(self, created_items: dict) -> None:
3113 """Delete VM ports attached to the networks before deleting virtual machine.
3114 Args:
3115 created_items (dict): All created items belongs to VM
3116 """
3117
3118 for k, v in created_items.items():
3119 if not v: # skip already deleted
3120 continue
3121
3122 try:
3123 k_item, k_id = self._get_item_name_id(k)
3124 if k_item == "port":
3125 self._delete_ports_by_id_wth_neutron(k_id)
3126
gatici335a06a2023-07-26 00:34:04 +03003127 except (neExceptions.ConnectionFailed, ConnectionError) as e:
3128 self.logger.error(
3129 "Error deleting port: {}: {}".format(type(e).__name__, e)
3130 )
3131 self._format_exception(e)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003132 except Exception as e:
3133 self.logger.error(
3134 "Error deleting port: {}: {}".format(type(e).__name__, e)
3135 )
3136
3137 def _delete_created_items(
3138 self, created_items: dict, volumes_to_hold: list, keep_waiting: bool
3139 ) -> bool:
3140 """Delete Volumes and floating ip if they exist in created_items."""
3141 for k, v in created_items.items():
3142 if not v: # skip already deleted
3143 continue
3144
3145 try:
3146 k_item, k_id = self._get_item_name_id(k)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003147 if k_item == "volume":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003148 unavailable_vol = self._delete_volumes_by_id_wth_cinder(
3149 k, k_id, volumes_to_hold, created_items
3150 )
3151
3152 if unavailable_vol:
3153 keep_waiting = True
3154
3155 elif k_item == "floating_ip":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003156 self._delete_floating_ip_by_id(k, k_id, created_items)
3157
gatici335a06a2023-07-26 00:34:04 +03003158 except (
3159 cExceptions.ConnectionError,
3160 neExceptions.ConnectionFailed,
3161 ConnectionError,
3162 AttributeError,
3163 TypeError,
3164 ) as e:
3165 self.logger.error("Error deleting {}: {}".format(k, e))
3166 self._format_exception(e)
3167
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003168 except Exception as e:
3169 self.logger.error("Error deleting {}: {}".format(k, e))
3170
3171 return keep_waiting
3172
aticig2f4ab6c2022-09-03 18:15:20 +03003173 @staticmethod
3174 def _extract_items_wth_keep_flag_from_created_items(created_items: dict) -> dict:
3175 """Remove the volumes which has key flag from created_items
3176
3177 Args:
3178 created_items (dict): All created items belongs to VM
3179
3180 Returns:
3181 created_items (dict): Persistent volumes eliminated created_items
3182 """
3183 return {
3184 key: value
3185 for (key, value) in created_items.items()
3186 if len(key.split(":")) == 2
3187 }
3188
gatici335a06a2023-07-26 00:34:04 +03003189 @catch_any_exception
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003190 def delete_vminstance(
3191 self, vm_id: str, created_items: dict = None, volumes_to_hold: list = None
3192 ) -> None:
3193 """Removes a VM instance from VIM. Returns the old identifier.
3194 Args:
3195 vm_id (str): Identifier of VM instance
3196 created_items (dict): All created items belongs to VM
3197 volumes_to_hold (list): Volumes_to_hold
3198 """
tierno1ec592d2020-06-16 15:29:47 +00003199 if created_items is None:
tierno98e909c2017-10-14 13:27:03 +02003200 created_items = {}
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003201 if volumes_to_hold is None:
3202 volumes_to_hold = []
sousaedu80135b92021-02-17 15:05:18 +01003203
tierno7edb6752016-03-21 17:37:52 +01003204 try:
aticig2f4ab6c2022-09-03 18:15:20 +03003205 created_items = self._extract_items_wth_keep_flag_from_created_items(
3206 created_items
3207 )
3208
tierno7edb6752016-03-21 17:37:52 +01003209 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01003210
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003211 # Delete VM ports attached to the networks before the virtual machine
3212 if created_items:
3213 self._delete_vm_ports_attached_to_network(created_items)
montesmoreno0c8def02016-12-22 12:16:23 +00003214
tierno98e909c2017-10-14 13:27:03 +02003215 if vm_id:
3216 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00003217
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003218 # Although having detached, volumes should have in active status before deleting.
3219 # We ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00003220 keep_waiting = True
3221 elapsed_time = 0
sousaedu80135b92021-02-17 15:05:18 +01003222
montesmoreno0c8def02016-12-22 12:16:23 +00003223 while keep_waiting and elapsed_time < volume_timeout:
3224 keep_waiting = False
sousaedu80135b92021-02-17 15:05:18 +01003225
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003226 # Delete volumes and floating IP.
3227 keep_waiting = self._delete_created_items(
3228 created_items, volumes_to_hold, keep_waiting
3229 )
sousaedu80135b92021-02-17 15:05:18 +01003230
montesmoreno0c8def02016-12-22 12:16:23 +00003231 if keep_waiting:
3232 time.sleep(1)
3233 elapsed_time += 1
gatici335a06a2023-07-26 00:34:04 +03003234 except (nvExceptions.NotFound, nvExceptions.ResourceNotFound) as e:
3235 # If VM does not exist, it does not raise
3236 self.logger.warning(f"Error deleting VM: {vm_id} is not found, {str(e)}")
tierno7edb6752016-03-21 17:37:52 +01003237
tiernoae4a8d12016-07-08 12:30:39 +02003238 def refresh_vms_status(self, vm_list):
tierno1ec592d2020-06-16 15:29:47 +00003239 """Get the status of the virtual machines and their interfaces/ports
sousaedu80135b92021-02-17 15:05:18 +01003240 Params: the list of VM identifiers
3241 Returns a dictionary with:
3242 vm_id: #VIM id of this Virtual Machine
3243 status: #Mandatory. Text with one of:
3244 # DELETED (not found at vim)
3245 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
3246 # OTHER (Vim reported other status not understood)
3247 # ERROR (VIM indicates an ERROR status)
3248 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
3249 # CREATING (on building process), ERROR
3250 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
3251 #
3252 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
3253 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3254 interfaces:
3255 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3256 mac_address: #Text format XX:XX:XX:XX:XX:XX
3257 vim_net_id: #network id where this interface is connected
3258 vim_interface_id: #interface/port VIM id
3259 ip_address: #null, or text with IPv4, IPv6 address
3260 compute_node: #identification of compute node where PF,VF interface is allocated
3261 pci: #PCI address of the NIC that hosts the PF,VF
3262 vlan: #physical VLAN used for VF
tierno1ec592d2020-06-16 15:29:47 +00003263 """
3264 vm_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01003265 self.logger.debug(
3266 "refresh_vms status: Getting tenant VM instance information from VIM"
3267 )
tiernoae4a8d12016-07-08 12:30:39 +02003268 for vm_id in vm_list:
tierno1ec592d2020-06-16 15:29:47 +00003269 vm = {}
sousaedu80135b92021-02-17 15:05:18 +01003270
tiernoae4a8d12016-07-08 12:30:39 +02003271 try:
3272 vm_vim = self.get_vminstance(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003273
3274 if vm_vim["status"] in vmStatus2manoFormat:
3275 vm["status"] = vmStatus2manoFormat[vm_vim["status"]]
tierno7edb6752016-03-21 17:37:52 +01003276 else:
sousaedu80135b92021-02-17 15:05:18 +01003277 vm["status"] = "OTHER"
3278 vm["error_msg"] = "VIM status reported " + vm_vim["status"]
3279
tierno70eeb182020-10-19 16:38:00 +00003280 vm_vim.pop("OS-EXT-SRV-ATTR:user_data", None)
3281 vm_vim.pop("user_data", None)
sousaedu80135b92021-02-17 15:05:18 +01003282 vm["vim_info"] = self.serialize(vm_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003283
tiernoae4a8d12016-07-08 12:30:39 +02003284 vm["interfaces"] = []
sousaedu80135b92021-02-17 15:05:18 +01003285 if vm_vim.get("fault"):
3286 vm["error_msg"] = str(vm_vim["fault"])
3287
tierno1ec592d2020-06-16 15:29:47 +00003288 # get interfaces
tierno7edb6752016-03-21 17:37:52 +01003289 try:
tiernoae4a8d12016-07-08 12:30:39 +02003290 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02003291 port_dict = self.neutron.list_ports(device_id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003292
tiernoae4a8d12016-07-08 12:30:39 +02003293 for port in port_dict["ports"]:
tierno1ec592d2020-06-16 15:29:47 +00003294 interface = {}
sousaedu80135b92021-02-17 15:05:18 +01003295 interface["vim_info"] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02003296 interface["mac_address"] = port.get("mac_address")
3297 interface["vim_net_id"] = port["network_id"]
3298 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003299 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003300 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003301
3302 if vm_vim.get("OS-EXT-SRV-ATTR:host"):
3303 interface["compute_node"] = vm_vim["OS-EXT-SRV-ATTR:host"]
3304
tierno867ffe92017-03-27 12:50:34 +02003305 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04003306
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003307 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003308 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003309 if port.get("binding:profile"):
3310 if port["binding:profile"].get("pci_slot"):
tierno1ec592d2020-06-16 15:29:47 +00003311 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting
3312 # the slot to 0x00
Mike Marchetti5b9da422017-05-02 15:35:47 -04003313 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
3314 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
sousaedu80135b92021-02-17 15:05:18 +01003315 pci = port["binding:profile"]["pci_slot"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04003316 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
3317 interface["pci"] = pci
sousaedu80135b92021-02-17 15:05:18 +01003318
tierno867ffe92017-03-27 12:50:34 +02003319 interface["vlan"] = None
sousaedu80135b92021-02-17 15:05:18 +01003320
3321 if port.get("binding:vif_details"):
3322 interface["vlan"] = port["binding:vif_details"].get("vlan")
3323
tierno1dfe9932020-06-18 08:50:10 +00003324 # Get vlan from network in case not present in port for those old openstacks and cases where
3325 # it is needed vlan at PT
3326 if not interface["vlan"]:
3327 # if network is of type vlan and port is of type direct (sr-iov) then set vlan id
3328 network = self.neutron.show_network(port["network_id"])
sousaedu80135b92021-02-17 15:05:18 +01003329
3330 if (
3331 network["network"].get("provider:network_type")
3332 == "vlan"
3333 ):
tierno1dfe9932020-06-18 08:50:10 +00003334 # and port.get("binding:vnic_type") in ("direct", "direct-physical"):
sousaedu80135b92021-02-17 15:05:18 +01003335 interface["vlan"] = network["network"].get(
3336 "provider:segmentation_id"
3337 )
3338
tierno1ec592d2020-06-16 15:29:47 +00003339 ips = []
3340 # look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02003341 try:
sousaedu80135b92021-02-17 15:05:18 +01003342 floating_ip_dict = self.neutron.list_floatingips(
3343 port_id=port["id"]
3344 )
3345
tiernob42fd9b2018-06-20 10:44:32 +02003346 if floating_ip_dict.get("floatingips"):
sousaedu80135b92021-02-17 15:05:18 +01003347 ips.append(
3348 floating_ip_dict["floatingips"][0].get(
3349 "floating_ip_address"
3350 )
3351 )
tiernob42fd9b2018-06-20 10:44:32 +02003352 except Exception:
3353 pass
tierno7edb6752016-03-21 17:37:52 +01003354
tiernoae4a8d12016-07-08 12:30:39 +02003355 for subnet in port["fixed_ips"]:
3356 ips.append(subnet["ip_address"])
sousaedu80135b92021-02-17 15:05:18 +01003357
tiernoae4a8d12016-07-08 12:30:39 +02003358 interface["ip_address"] = ";".join(ips)
3359 vm["interfaces"].append(interface)
3360 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01003361 self.logger.error(
3362 "Error getting vm interface information {}: {}".format(
3363 type(e).__name__, e
3364 ),
3365 exc_info=True,
3366 )
tierno72774862020-05-04 11:44:15 +00003367 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003368 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003369 vm["status"] = "DELETED"
3370 vm["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00003371 except vimconn.VimConnException 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"] = "VIM_ERROR"
3374 vm["error_msg"] = str(e)
3375
tiernoae4a8d12016-07-08 12:30:39 +02003376 vm_dict[vm_id] = vm
sousaedu80135b92021-02-17 15:05:18 +01003377
tiernoae4a8d12016-07-08 12:30:39 +02003378 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003379
gatici335a06a2023-07-26 00:34:04 +03003380 @catch_any_exception
tierno98e909c2017-10-14 13:27:03 +02003381 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno1ec592d2020-06-16 15:29:47 +00003382 """Send and action over a VM instance from VIM
Gulsum Atici21c55d62023-02-02 20:41:00 +03003383 Returns None or the console dict if the action was successfully sent to the VIM
3384 """
tiernoae4a8d12016-07-08 12:30:39 +02003385 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
gatici335a06a2023-07-26 00:34:04 +03003386 self._reload_connection()
3387 server = self.nova.servers.find(id=vm_id)
3388 if "start" in action_dict:
3389 if action_dict["start"] == "rebuild":
3390 server.rebuild()
Rahul Kumar8875f912023-11-08 06:48:12 +00003391 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
3392 if not vm_state:
3393 raise nvExceptions.BadRequest(
3394 409,
3395 message="Cannot 'REBUILD' vm_state is in ERROR",
3396 )
gatici335a06a2023-07-26 00:34:04 +03003397 else:
3398 if server.status == "PAUSED":
3399 server.unpause()
3400 elif server.status == "SUSPENDED":
3401 server.resume()
3402 elif server.status == "SHUTOFF":
3403 server.start()
Rahul Kumar8875f912023-11-08 06:48:12 +00003404 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
3405 if not vm_state:
3406 raise nvExceptions.BadRequest(
3407 409,
3408 message="Cannot 'START' vm_state is in ERROR",
3409 )
tierno7edb6752016-03-21 17:37:52 +01003410 else:
gatici335a06a2023-07-26 00:34:04 +03003411 self.logger.debug(
3412 "ERROR : Instance is not in SHUTOFF/PAUSE/SUSPEND state"
3413 )
k4.rahul78f474e2022-05-02 15:47:57 +00003414 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03003415 "Cannot 'start' instance while it is in active state",
k4.rahul78f474e2022-05-02 15:47:57 +00003416 http_code=vimconn.HTTP_Bad_Request,
3417 )
gatici335a06a2023-07-26 00:34:04 +03003418 elif "pause" in action_dict:
3419 server.pause()
3420 elif "resume" in action_dict:
3421 server.resume()
3422 elif "shutoff" in action_dict or "shutdown" in action_dict:
3423 self.logger.debug("server status %s", server.status)
3424 if server.status == "ACTIVE":
3425 server.stop()
Rahul Kumar8875f912023-11-08 06:48:12 +00003426 vm_state = self.__wait_for_vm(vm_id, "SHUTOFF")
3427 if not vm_state:
3428 raise nvExceptions.BadRequest(
3429 409,
3430 message="Cannot 'STOP' vm_state is in ERROR",
3431 )
gatici335a06a2023-07-26 00:34:04 +03003432 else:
3433 self.logger.debug("ERROR: VM is not in Active state")
3434 raise vimconn.VimConnException(
3435 "VM is not in active state, stop operation is not allowed",
3436 http_code=vimconn.HTTP_Bad_Request,
3437 )
3438 elif "forceOff" in action_dict:
3439 server.stop() # TODO
3440 elif "terminate" in action_dict:
3441 server.delete()
3442 elif "createImage" in action_dict:
3443 server.create_image()
3444 # "path":path_schema,
3445 # "description":description_schema,
3446 # "name":name_schema,
3447 # "metadata":metadata_schema,
3448 # "imageRef": id_schema,
3449 # "disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
3450 elif "rebuild" in action_dict:
3451 server.rebuild(server.image["id"])
3452 elif "reboot" in action_dict:
3453 server.reboot() # reboot_type="SOFT"
3454 elif "console" in action_dict:
3455 console_type = action_dict["console"]
sousaedu80135b92021-02-17 15:05:18 +01003456
gatici335a06a2023-07-26 00:34:04 +03003457 if console_type is None or console_type == "novnc":
3458 console_dict = server.get_vnc_console("novnc")
3459 elif console_type == "xvpvnc":
3460 console_dict = server.get_vnc_console(console_type)
3461 elif console_type == "rdp-html5":
3462 console_dict = server.get_rdp_console(console_type)
3463 elif console_type == "spice-html5":
3464 console_dict = server.get_spice_console(console_type)
3465 else:
3466 raise vimconn.VimConnException(
3467 "console type '{}' not allowed".format(console_type),
3468 http_code=vimconn.HTTP_Bad_Request,
3469 )
sousaedu80135b92021-02-17 15:05:18 +01003470
gatici335a06a2023-07-26 00:34:04 +03003471 try:
3472 console_url = console_dict["console"]["url"]
3473 # parse console_url
3474 protocol_index = console_url.find("//")
3475 suffix_index = (
3476 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3477 )
3478 port_index = (
3479 console_url[protocol_index + 2 : suffix_index].find(":")
3480 + protocol_index
3481 + 2
3482 )
sousaedu80135b92021-02-17 15:05:18 +01003483
gatici335a06a2023-07-26 00:34:04 +03003484 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
sousaedu80135b92021-02-17 15:05:18 +01003485 raise vimconn.VimConnException(
3486 "Unexpected response from VIM " + str(console_dict)
3487 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003488
gatici335a06a2023-07-26 00:34:04 +03003489 console_dict2 = {
3490 "protocol": console_url[0:protocol_index],
3491 "server": console_url[protocol_index + 2 : port_index],
3492 "port": int(console_url[port_index + 1 : suffix_index]),
3493 "suffix": console_url[suffix_index + 1 :],
3494 }
3495
3496 return console_dict2
3497 except Exception:
3498 raise vimconn.VimConnException(
3499 "Unexpected response from VIM " + str(console_dict)
3500 )
3501
3502 return None
tiernoae4a8d12016-07-08 12:30:39 +02003503
tierno1ec592d2020-06-16 15:29:47 +00003504 # ###### VIO Specific Changes #########
garciadeblasebd66722019-01-31 16:01:31 +00003505 def _generate_vlanID(self):
kate721d79b2017-06-24 04:21:38 -07003506 """
sousaedu80135b92021-02-17 15:05:18 +01003507 Method to get unused vlanID
kate721d79b2017-06-24 04:21:38 -07003508 Args:
3509 None
3510 Returns:
3511 vlanID
3512 """
tierno1ec592d2020-06-16 15:29:47 +00003513 # Get used VLAN IDs
kate721d79b2017-06-24 04:21:38 -07003514 usedVlanIDs = []
3515 networks = self.get_network_list()
sousaedu80135b92021-02-17 15:05:18 +01003516
kate721d79b2017-06-24 04:21:38 -07003517 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003518 if net.get("provider:segmentation_id"):
3519 usedVlanIDs.append(net.get("provider:segmentation_id"))
3520
kate721d79b2017-06-24 04:21:38 -07003521 used_vlanIDs = set(usedVlanIDs)
3522
tierno1ec592d2020-06-16 15:29:47 +00003523 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003524 for vlanID_range in self.config.get("dataplane_net_vlan_range"):
kate721d79b2017-06-24 04:21:38 -07003525 try:
sousaedu80135b92021-02-17 15:05:18 +01003526 start_vlanid, end_vlanid = map(
3527 int, vlanID_range.replace(" ", "").split("-")
3528 )
3529
tierno7d782ef2019-10-04 12:56:31 +00003530 for vlanID in range(start_vlanid, end_vlanid + 1):
kate721d79b2017-06-24 04:21:38 -07003531 if vlanID not in used_vlanIDs:
3532 return vlanID
3533 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003534 raise vimconn.VimConnException(
3535 "Exception {} occurred while generating VLAN ID.".format(exp)
3536 )
kate721d79b2017-06-24 04:21:38 -07003537 else:
tierno1ec592d2020-06-16 15:29:47 +00003538 raise vimconn.VimConnConflictException(
3539 "Unable to create the SRIOV VLAN network. All given Vlan IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003540 self.config.get("dataplane_net_vlan_range")
3541 )
3542 )
kate721d79b2017-06-24 04:21:38 -07003543
garciadeblasebd66722019-01-31 16:01:31 +00003544 def _generate_multisegment_vlanID(self):
3545 """
sousaedu80135b92021-02-17 15:05:18 +01003546 Method to get unused vlanID
3547 Args:
3548 None
3549 Returns:
3550 vlanID
garciadeblasebd66722019-01-31 16:01:31 +00003551 """
tierno6869ae72020-01-09 17:37:34 +00003552 # Get used VLAN IDs
garciadeblasebd66722019-01-31 16:01:31 +00003553 usedVlanIDs = []
3554 networks = self.get_network_list()
3555 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003556 if net.get("provider:network_type") == "vlan" and net.get(
3557 "provider:segmentation_id"
3558 ):
3559 usedVlanIDs.append(net.get("provider:segmentation_id"))
3560 elif net.get("segments"):
3561 for segment in net.get("segments"):
3562 if segment.get("provider:network_type") == "vlan" and segment.get(
3563 "provider:segmentation_id"
3564 ):
3565 usedVlanIDs.append(segment.get("provider:segmentation_id"))
3566
garciadeblasebd66722019-01-31 16:01:31 +00003567 used_vlanIDs = set(usedVlanIDs)
3568
tierno6869ae72020-01-09 17:37:34 +00003569 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003570 for vlanID_range in self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +00003571 try:
sousaedu80135b92021-02-17 15:05:18 +01003572 start_vlanid, end_vlanid = map(
3573 int, vlanID_range.replace(" ", "").split("-")
3574 )
3575
tierno7d782ef2019-10-04 12:56:31 +00003576 for vlanID in range(start_vlanid, end_vlanid + 1):
garciadeblasebd66722019-01-31 16:01:31 +00003577 if vlanID not in used_vlanIDs:
3578 return vlanID
3579 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003580 raise vimconn.VimConnException(
3581 "Exception {} occurred while generating VLAN ID.".format(exp)
3582 )
garciadeblasebd66722019-01-31 16:01:31 +00003583 else:
tierno1ec592d2020-06-16 15:29:47 +00003584 raise vimconn.VimConnConflictException(
3585 "Unable to create the VLAN segment. All VLAN IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003586 self.config.get("multisegment_vlan_range")
3587 )
3588 )
garciadeblasebd66722019-01-31 16:01:31 +00003589
3590 def _validate_vlan_ranges(self, input_vlan_range, text_vlan_range):
kate721d79b2017-06-24 04:21:38 -07003591 """
3592 Method to validate user given vlanID ranges
3593 Args: None
3594 Returns: None
3595 """
garciadeblasebd66722019-01-31 16:01:31 +00003596 for vlanID_range in input_vlan_range:
kate721d79b2017-06-24 04:21:38 -07003597 vlan_range = vlanID_range.replace(" ", "")
tierno1ec592d2020-06-16 15:29:47 +00003598 # validate format
sousaedu80135b92021-02-17 15:05:18 +01003599 vlanID_pattern = r"(\d)*-(\d)*$"
kate721d79b2017-06-24 04:21:38 -07003600 match_obj = re.match(vlanID_pattern, vlan_range)
3601 if not match_obj:
tierno1ec592d2020-06-16 15:29:47 +00003602 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003603 "Invalid VLAN range for {}: {}.You must provide "
3604 "'{}' in format [start_ID - end_ID].".format(
3605 text_vlan_range, vlanID_range, text_vlan_range
3606 )
3607 )
kate721d79b2017-06-24 04:21:38 -07003608
tierno1ec592d2020-06-16 15:29:47 +00003609 start_vlanid, end_vlanid = map(int, vlan_range.split("-"))
3610 if start_vlanid <= 0:
3611 raise vimconn.VimConnConflictException(
3612 "Invalid VLAN range for {}: {}. Start ID can not be zero. For VLAN "
sousaedu80135b92021-02-17 15:05:18 +01003613 "networks valid IDs are 1 to 4094 ".format(
3614 text_vlan_range, vlanID_range
3615 )
3616 )
3617
tierno1ec592d2020-06-16 15:29:47 +00003618 if end_vlanid > 4094:
3619 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003620 "Invalid VLAN range for {}: {}. End VLAN ID can not be "
3621 "greater than 4094. For VLAN networks valid IDs are 1 to 4094 ".format(
3622 text_vlan_range, vlanID_range
3623 )
3624 )
kate721d79b2017-06-24 04:21:38 -07003625
3626 if start_vlanid > end_vlanid:
tierno1ec592d2020-06-16 15:29:47 +00003627 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003628 "Invalid VLAN range for {}: {}. You must provide '{}'"
3629 " in format start_ID - end_ID and start_ID < end_ID ".format(
3630 text_vlan_range, vlanID_range, text_vlan_range
3631 )
3632 )
kate721d79b2017-06-24 04:21:38 -07003633
tierno7edb6752016-03-21 17:37:52 +01003634 def get_hosts_info(self):
tierno1ec592d2020-06-16 15:29:47 +00003635 """Get the information of deployed hosts
3636 Returns the hosts content"""
garciadeblas6a0147f2024-08-19 08:02:04 +00003637 self.logger.debug("osconnector: Getting Host info from VIM")
sousaedu80135b92021-02-17 15:05:18 +01003638
tierno7edb6752016-03-21 17:37:52 +01003639 try:
tierno1ec592d2020-06-16 15:29:47 +00003640 h_list = []
tierno7edb6752016-03-21 17:37:52 +01003641 self._reload_connection()
3642 hypervisors = self.nova.hypervisors.list()
sousaedu80135b92021-02-17 15:05:18 +01003643
tierno7edb6752016-03-21 17:37:52 +01003644 for hype in hypervisors:
tierno1ec592d2020-06-16 15:29:47 +00003645 h_list.append(hype.to_dict())
sousaedu80135b92021-02-17 15:05:18 +01003646
tierno1ec592d2020-06-16 15:29:47 +00003647 return 1, {"hosts": h_list}
tierno7edb6752016-03-21 17:37:52 +01003648 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003649 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003650 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003651 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003652 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003653 error_text = (
3654 type(e).__name__
3655 + ": "
3656 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3657 )
3658
tierno1ec592d2020-06-16 15:29:47 +00003659 # TODO insert exception vimconn.HTTP_Unauthorized
3660 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003661 self.logger.debug("get_hosts_info " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003662
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003663 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003664
3665 def get_hosts(self, vim_tenant):
tierno1ec592d2020-06-16 15:29:47 +00003666 """Get the hosts and deployed instances
3667 Returns the hosts content"""
tierno7edb6752016-03-21 17:37:52 +01003668 r, hype_dict = self.get_hosts_info()
sousaedu80135b92021-02-17 15:05:18 +01003669
tierno1ec592d2020-06-16 15:29:47 +00003670 if r < 0:
tierno7edb6752016-03-21 17:37:52 +01003671 return r, hype_dict
sousaedu80135b92021-02-17 15:05:18 +01003672
tierno7edb6752016-03-21 17:37:52 +01003673 hypervisors = hype_dict["hosts"]
sousaedu80135b92021-02-17 15:05:18 +01003674
tierno7edb6752016-03-21 17:37:52 +01003675 try:
3676 servers = self.nova.servers.list()
3677 for hype in hypervisors:
3678 for server in servers:
sousaedu80135b92021-02-17 15:05:18 +01003679 if (
3680 server.to_dict()["OS-EXT-SRV-ATTR:hypervisor_hostname"]
3681 == hype["hypervisor_hostname"]
3682 ):
3683 if "vm" in hype:
3684 hype["vm"].append(server.id)
tierno7edb6752016-03-21 17:37:52 +01003685 else:
sousaedu80135b92021-02-17 15:05:18 +01003686 hype["vm"] = [server.id]
3687
tierno7edb6752016-03-21 17:37:52 +01003688 return 1, hype_dict
3689 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003690 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003691 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003692 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003693 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003694 error_text = (
3695 type(e).__name__
3696 + ": "
3697 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3698 )
3699
tierno1ec592d2020-06-16 15:29:47 +00003700 # TODO insert exception vimconn.HTTP_Unauthorized
3701 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003702 self.logger.debug("get_hosts " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003703
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003704 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003705
Lovejeet Singhdf486552023-05-09 22:41:09 +05303706 def new_classification(self, name, ctype, definition):
3707 self.logger.debug(
3708 "Adding a new (Traffic) Classification to VIM, named %s", name
3709 )
3710
3711 try:
3712 new_class = None
3713 self._reload_connection()
3714
3715 if ctype not in supportedClassificationTypes:
3716 raise vimconn.VimConnNotSupportedException(
3717 "OpenStack VIM connector does not support provided "
3718 "Classification Type {}, supported ones are: {}".format(
3719 ctype, supportedClassificationTypes
3720 )
3721 )
3722
3723 if not self._validate_classification(ctype, definition):
3724 raise vimconn.VimConnException(
3725 "Incorrect Classification definition for the type specified."
3726 )
3727
3728 classification_dict = definition
3729 classification_dict["name"] = name
3730
3731 self.logger.info(
3732 "Adding a new (Traffic) Classification to VIM, named {} and {}.".format(
3733 name, classification_dict
3734 )
3735 )
3736 new_class = self.neutron.create_sfc_flow_classifier(
3737 {"flow_classifier": classification_dict}
3738 )
3739
3740 return new_class["flow_classifier"]["id"]
3741 except (
3742 neExceptions.ConnectionFailed,
3743 ksExceptions.ClientException,
3744 neExceptions.NeutronException,
3745 ConnectionError,
3746 ) as e:
3747 self.logger.error("Creation of Classification failed.")
3748 self._format_exception(e)
3749
3750 def get_classification(self, class_id):
3751 self.logger.debug(" Getting Classification %s from VIM", class_id)
3752 filter_dict = {"id": class_id}
3753 class_list = self.get_classification_list(filter_dict)
3754
3755 if len(class_list) == 0:
3756 raise vimconn.VimConnNotFoundException(
3757 "Classification '{}' not found".format(class_id)
3758 )
3759 elif len(class_list) > 1:
3760 raise vimconn.VimConnConflictException(
3761 "Found more than one Classification with this criteria"
3762 )
3763
3764 classification = class_list[0]
3765
3766 return classification
3767
3768 def get_classification_list(self, filter_dict={}):
3769 self.logger.debug(
3770 "Getting Classifications from VIM filter: '%s'", str(filter_dict)
3771 )
3772
3773 try:
3774 filter_dict_os = filter_dict.copy()
3775 self._reload_connection()
3776
3777 if self.api_version3 and "tenant_id" in filter_dict_os:
3778 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3779
3780 classification_dict = self.neutron.list_sfc_flow_classifiers(
3781 **filter_dict_os
3782 )
3783 classification_list = classification_dict["flow_classifiers"]
3784 self.__classification_os2mano(classification_list)
3785
3786 return classification_list
3787 except (
3788 neExceptions.ConnectionFailed,
3789 ksExceptions.ClientException,
3790 neExceptions.NeutronException,
3791 ConnectionError,
3792 ) as e:
3793 self._format_exception(e)
3794
3795 def delete_classification(self, class_id):
3796 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
3797
3798 try:
3799 self._reload_connection()
3800 self.neutron.delete_sfc_flow_classifier(class_id)
3801
3802 return class_id
3803 except (
3804 neExceptions.ConnectionFailed,
3805 neExceptions.NeutronException,
3806 ksExceptions.ClientException,
3807 neExceptions.NeutronException,
3808 ConnectionError,
3809 ) as e:
3810 self._format_exception(e)
3811
3812 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
3813 self.logger.debug(
3814 "Adding a new Service Function Instance to VIM, named '%s'", name
3815 )
3816
3817 try:
3818 new_sfi = None
3819 self._reload_connection()
3820 correlation = None
3821
3822 if sfc_encap:
3823 correlation = "nsh"
3824
3825 if len(ingress_ports) != 1:
3826 raise vimconn.VimConnNotSupportedException(
3827 "OpenStack VIM connector can only have 1 ingress port per SFI"
3828 )
3829
3830 if len(egress_ports) != 1:
3831 raise vimconn.VimConnNotSupportedException(
3832 "OpenStack VIM connector can only have 1 egress port per SFI"
3833 )
3834
3835 sfi_dict = {
3836 "name": name,
3837 "ingress": ingress_ports[0],
3838 "egress": egress_ports[0],
3839 "service_function_parameters": {"correlation": correlation},
3840 }
3841 self.logger.info("Adding a new SFI to VIM, {}.".format(sfi_dict))
3842 new_sfi = self.neutron.create_sfc_port_pair({"port_pair": sfi_dict})
3843
3844 return new_sfi["port_pair"]["id"]
3845 except (
3846 neExceptions.ConnectionFailed,
3847 ksExceptions.ClientException,
3848 neExceptions.NeutronException,
3849 ConnectionError,
3850 ) as e:
3851 if new_sfi:
3852 try:
3853 self.neutron.delete_sfc_port_pair(new_sfi["port_pair"]["id"])
3854 except Exception:
3855 self.logger.error(
3856 "Creation of Service Function Instance failed, with "
3857 "subsequent deletion failure as well."
3858 )
3859
3860 self._format_exception(e)
3861
3862 def get_sfi(self, sfi_id):
3863 self.logger.debug("Getting Service Function Instance %s from VIM", sfi_id)
3864 filter_dict = {"id": sfi_id}
3865 sfi_list = self.get_sfi_list(filter_dict)
3866
3867 if len(sfi_list) == 0:
3868 raise vimconn.VimConnNotFoundException(
3869 "Service Function Instance '{}' not found".format(sfi_id)
3870 )
3871 elif len(sfi_list) > 1:
3872 raise vimconn.VimConnConflictException(
3873 "Found more than one Service Function Instance with this criteria"
3874 )
3875
3876 sfi = sfi_list[0]
3877
3878 return sfi
3879
3880 def get_sfi_list(self, filter_dict={}):
3881 self.logger.debug(
3882 "Getting Service Function Instances from VIM filter: '%s'", str(filter_dict)
3883 )
3884
3885 try:
3886 self._reload_connection()
3887 filter_dict_os = filter_dict.copy()
3888
3889 if self.api_version3 and "tenant_id" in filter_dict_os:
3890 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3891
3892 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
3893 sfi_list = sfi_dict["port_pairs"]
3894 self.__sfi_os2mano(sfi_list)
3895
3896 return sfi_list
3897 except (
3898 neExceptions.ConnectionFailed,
3899 ksExceptions.ClientException,
3900 neExceptions.NeutronException,
3901 ConnectionError,
3902 ) as e:
3903 self._format_exception(e)
3904
3905 def delete_sfi(self, sfi_id):
3906 self.logger.debug("Deleting Service Function Instance '%s' from VIM", sfi_id)
3907
3908 try:
3909 self._reload_connection()
3910 self.neutron.delete_sfc_port_pair(sfi_id)
3911
3912 return sfi_id
3913 except (
3914 neExceptions.ConnectionFailed,
3915 neExceptions.NeutronException,
3916 ksExceptions.ClientException,
3917 neExceptions.NeutronException,
3918 ConnectionError,
3919 ) as e:
3920 self._format_exception(e)
3921
3922 def new_sf(self, name, sfis, sfc_encap=True):
3923 self.logger.debug("Adding a new Service Function to VIM, named '%s'", name)
3924
3925 new_sf = None
3926
3927 try:
3928 self._reload_connection()
3929
3930 for instance in sfis:
3931 sfi = self.get_sfi(instance)
3932
3933 if sfi.get("sfc_encap") != sfc_encap:
3934 raise vimconn.VimConnNotSupportedException(
3935 "OpenStack VIM connector requires all SFIs of the "
3936 "same SF to share the same SFC Encapsulation"
3937 )
3938
3939 sf_dict = {"name": name, "port_pairs": sfis}
3940
3941 self.logger.info("Adding a new SF to VIM, {}.".format(sf_dict))
3942 new_sf = self.neutron.create_sfc_port_pair_group(
3943 {"port_pair_group": sf_dict}
3944 )
3945
3946 return new_sf["port_pair_group"]["id"]
3947 except (
3948 neExceptions.ConnectionFailed,
3949 ksExceptions.ClientException,
3950 neExceptions.NeutronException,
3951 ConnectionError,
3952 ) as e:
3953 if new_sf:
3954 try:
3955 new_sf_id = new_sf.get("port_pair_group").get("id")
3956 self.neutron.delete_sfc_port_pair_group(new_sf_id)
3957 except Exception:
3958 self.logger.error(
3959 "Creation of Service Function failed, with "
3960 "subsequent deletion failure as well."
3961 )
3962
3963 self._format_exception(e)
3964
3965 def get_sf(self, sf_id):
3966 self.logger.debug("Getting Service Function %s from VIM", sf_id)
3967 filter_dict = {"id": sf_id}
3968 sf_list = self.get_sf_list(filter_dict)
3969
3970 if len(sf_list) == 0:
3971 raise vimconn.VimConnNotFoundException(
3972 "Service Function '{}' not found".format(sf_id)
3973 )
3974 elif len(sf_list) > 1:
3975 raise vimconn.VimConnConflictException(
3976 "Found more than one Service Function with this criteria"
3977 )
3978
3979 sf = sf_list[0]
3980
3981 return sf
3982
3983 def get_sf_list(self, filter_dict={}):
3984 self.logger.debug(
3985 "Getting Service Function from VIM filter: '%s'", str(filter_dict)
3986 )
3987
3988 try:
3989 self._reload_connection()
3990 filter_dict_os = filter_dict.copy()
3991
3992 if self.api_version3 and "tenant_id" in filter_dict_os:
3993 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
3994
3995 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
3996 sf_list = sf_dict["port_pair_groups"]
3997 self.__sf_os2mano(sf_list)
3998
3999 return sf_list
4000 except (
4001 neExceptions.ConnectionFailed,
4002 ksExceptions.ClientException,
4003 neExceptions.NeutronException,
4004 ConnectionError,
4005 ) as e:
4006 self._format_exception(e)
4007
4008 def delete_sf(self, sf_id):
4009 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
4010
4011 try:
4012 self._reload_connection()
4013 self.neutron.delete_sfc_port_pair_group(sf_id)
4014
4015 return sf_id
4016 except (
4017 neExceptions.ConnectionFailed,
4018 neExceptions.NeutronException,
4019 ksExceptions.ClientException,
4020 neExceptions.NeutronException,
4021 ConnectionError,
4022 ) as e:
4023 self._format_exception(e)
4024
4025 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
4026 self.logger.debug("Adding a new Service Function Path to VIM, named '%s'", name)
4027
4028 new_sfp = None
4029
4030 try:
4031 self._reload_connection()
4032 # In networking-sfc the MPLS encapsulation is legacy
4033 # should be used when no full SFC Encapsulation is intended
4034 correlation = "mpls"
4035
4036 if sfc_encap:
4037 correlation = "nsh"
4038
4039 sfp_dict = {
4040 "name": name,
4041 "flow_classifiers": classifications,
4042 "port_pair_groups": sfs,
4043 "chain_parameters": {"correlation": correlation},
4044 }
4045
4046 if spi:
4047 sfp_dict["chain_id"] = spi
4048
4049 self.logger.info("Adding a new SFP to VIM, {}.".format(sfp_dict))
4050 new_sfp = self.neutron.create_sfc_port_chain({"port_chain": sfp_dict})
4051
4052 return new_sfp["port_chain"]["id"]
4053 except (
4054 neExceptions.ConnectionFailed,
4055 ksExceptions.ClientException,
4056 neExceptions.NeutronException,
4057 ConnectionError,
4058 ) as e:
4059 if new_sfp:
4060 try:
4061 new_sfp_id = new_sfp.get("port_chain").get("id")
4062 self.neutron.delete_sfc_port_chain(new_sfp_id)
4063 except Exception:
4064 self.logger.error(
4065 "Creation of Service Function Path failed, with "
4066 "subsequent deletion failure as well."
4067 )
4068
4069 self._format_exception(e)
4070
4071 def get_sfp(self, sfp_id):
4072 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
4073
4074 filter_dict = {"id": sfp_id}
4075 sfp_list = self.get_sfp_list(filter_dict)
4076
4077 if len(sfp_list) == 0:
4078 raise vimconn.VimConnNotFoundException(
4079 "Service Function Path '{}' not found".format(sfp_id)
4080 )
4081 elif len(sfp_list) > 1:
4082 raise vimconn.VimConnConflictException(
4083 "Found more than one Service Function Path with this criteria"
4084 )
4085
4086 sfp = sfp_list[0]
4087
4088 return sfp
4089
4090 def get_sfp_list(self, filter_dict={}):
4091 self.logger.debug(
4092 "Getting Service Function Paths from VIM filter: '%s'", str(filter_dict)
4093 )
4094
4095 try:
4096 self._reload_connection()
4097 filter_dict_os = filter_dict.copy()
4098
4099 if self.api_version3 and "tenant_id" in filter_dict_os:
4100 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
4101
4102 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
4103 sfp_list = sfp_dict["port_chains"]
4104 self.__sfp_os2mano(sfp_list)
4105
4106 return sfp_list
4107 except (
4108 neExceptions.ConnectionFailed,
4109 ksExceptions.ClientException,
4110 neExceptions.NeutronException,
4111 ConnectionError,
4112 ) as e:
4113 self._format_exception(e)
4114
4115 def delete_sfp(self, sfp_id):
4116 self.logger.debug("Deleting Service Function Path '%s' from VIM", sfp_id)
4117
4118 try:
4119 self._reload_connection()
4120 self.neutron.delete_sfc_port_chain(sfp_id)
4121
4122 return sfp_id
4123 except (
4124 neExceptions.ConnectionFailed,
4125 neExceptions.NeutronException,
4126 ksExceptions.ClientException,
4127 neExceptions.NeutronException,
4128 ConnectionError,
4129 ) as e:
4130 self._format_exception(e)
4131
4132 def refresh_sfps_status(self, sfp_list):
4133 """Get the status of the service function path
4134 Params: the list of sfp identifiers
4135 Returns a dictionary with:
4136 vm_id: #VIM id of this service function path
4137 status: #Mandatory. Text with one of:
4138 # DELETED (not found at vim)
4139 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4140 # OTHER (Vim reported other status not understood)
4141 # ERROR (VIM indicates an ERROR status)
4142 # ACTIVE,
4143 # CREATING (on building process)
4144 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4145 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)F
4146 """
4147 sfp_dict = {}
4148 self.logger.debug(
4149 "refresh_sfps status: Getting tenant SFP information from VIM"
4150 )
4151
4152 for sfp_id in sfp_list:
4153 sfp = {}
4154
4155 try:
4156 sfp_vim = self.get_sfp(sfp_id)
4157
4158 if sfp_vim["spi"]:
4159 sfp["status"] = vmStatus2manoFormat["ACTIVE"]
4160 else:
4161 sfp["status"] = "OTHER"
4162 sfp["error_msg"] = "VIM status reported " + sfp["status"]
4163
4164 sfp["vim_info"] = self.serialize(sfp_vim)
4165
4166 if sfp_vim.get("fault"):
4167 sfp["error_msg"] = str(sfp_vim["fault"])
4168 except vimconn.VimConnNotFoundException as e:
4169 self.logger.error("Exception getting sfp status: %s", str(e))
4170 sfp["status"] = "DELETED"
4171 sfp["error_msg"] = str(e)
4172 except vimconn.VimConnException as e:
4173 self.logger.error("Exception getting sfp status: %s", str(e))
4174 sfp["status"] = "VIM_ERROR"
4175 sfp["error_msg"] = str(e)
4176
4177 sfp_dict[sfp_id] = sfp
4178
4179 return sfp_dict
4180
4181 def refresh_sfis_status(self, sfi_list):
4182 """Get the status of the service function instances
4183 Params: the list of sfi identifiers
4184 Returns a dictionary with:
4185 vm_id: #VIM id of this service function instance
4186 status: #Mandatory. Text with one of:
4187 # DELETED (not found at vim)
4188 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4189 # OTHER (Vim reported other status not understood)
4190 # ERROR (VIM indicates an ERROR status)
4191 # ACTIVE,
4192 # CREATING (on building process)
4193 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4194 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4195 """
4196 sfi_dict = {}
4197 self.logger.debug(
4198 "refresh_sfis status: Getting tenant sfi information from VIM"
4199 )
4200
4201 for sfi_id in sfi_list:
4202 sfi = {}
4203
4204 try:
4205 sfi_vim = self.get_sfi(sfi_id)
4206
4207 if sfi_vim:
4208 sfi["status"] = vmStatus2manoFormat["ACTIVE"]
4209 else:
4210 sfi["status"] = "OTHER"
4211 sfi["error_msg"] = "VIM status reported " + sfi["status"]
4212
4213 sfi["vim_info"] = self.serialize(sfi_vim)
4214
4215 if sfi_vim.get("fault"):
4216 sfi["error_msg"] = str(sfi_vim["fault"])
4217 except vimconn.VimConnNotFoundException as e:
4218 self.logger.error("Exception getting sfi status: %s", str(e))
4219 sfi["status"] = "DELETED"
4220 sfi["error_msg"] = str(e)
4221 except vimconn.VimConnException as e:
4222 self.logger.error("Exception getting sfi status: %s", str(e))
4223 sfi["status"] = "VIM_ERROR"
4224 sfi["error_msg"] = str(e)
4225
4226 sfi_dict[sfi_id] = sfi
4227
4228 return sfi_dict
4229
4230 def refresh_sfs_status(self, sf_list):
4231 """Get the status of the service functions
4232 Params: the list of sf identifiers
4233 Returns a dictionary with:
4234 vm_id: #VIM id of this service function
4235 status: #Mandatory. Text with one of:
4236 # DELETED (not found at vim)
4237 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4238 # OTHER (Vim reported other status not understood)
4239 # ERROR (VIM indicates an ERROR status)
4240 # ACTIVE,
4241 # CREATING (on building process)
4242 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4243 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4244 """
4245 sf_dict = {}
4246 self.logger.debug("refresh_sfs status: Getting tenant sf information from VIM")
4247
4248 for sf_id in sf_list:
4249 sf = {}
4250
4251 try:
4252 sf_vim = self.get_sf(sf_id)
4253
4254 if sf_vim:
4255 sf["status"] = vmStatus2manoFormat["ACTIVE"]
4256 else:
4257 sf["status"] = "OTHER"
4258 sf["error_msg"] = "VIM status reported " + sf_vim["status"]
4259
4260 sf["vim_info"] = self.serialize(sf_vim)
4261
4262 if sf_vim.get("fault"):
4263 sf["error_msg"] = str(sf_vim["fault"])
4264 except vimconn.VimConnNotFoundException as e:
4265 self.logger.error("Exception getting sf status: %s", str(e))
4266 sf["status"] = "DELETED"
4267 sf["error_msg"] = str(e)
4268 except vimconn.VimConnException as e:
4269 self.logger.error("Exception getting sf status: %s", str(e))
4270 sf["status"] = "VIM_ERROR"
4271 sf["error_msg"] = str(e)
4272
4273 sf_dict[sf_id] = sf
4274
4275 return sf_dict
4276
4277 def refresh_classifications_status(self, classification_list):
4278 """Get the status of the classifications
4279 Params: the list of classification identifiers
4280 Returns a dictionary with:
4281 vm_id: #VIM id of this classifier
4282 status: #Mandatory. Text with one of:
4283 # DELETED (not found at vim)
4284 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
4285 # OTHER (Vim reported other status not understood)
4286 # ERROR (VIM indicates an ERROR status)
4287 # ACTIVE,
4288 # CREATING (on building process)
4289 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
4290 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
4291 """
4292 classification_dict = {}
4293 self.logger.debug(
4294 "refresh_classifications status: Getting tenant classification information from VIM"
4295 )
4296
4297 for classification_id in classification_list:
4298 classification = {}
4299
4300 try:
4301 classification_vim = self.get_classification(classification_id)
4302
4303 if classification_vim:
4304 classification["status"] = vmStatus2manoFormat["ACTIVE"]
4305 else:
4306 classification["status"] = "OTHER"
4307 classification["error_msg"] = (
4308 "VIM status reported " + classification["status"]
4309 )
4310
4311 classification["vim_info"] = self.serialize(classification_vim)
4312
4313 if classification_vim.get("fault"):
4314 classification["error_msg"] = str(classification_vim["fault"])
4315 except vimconn.VimConnNotFoundException as e:
4316 self.logger.error("Exception getting classification status: %s", str(e))
4317 classification["status"] = "DELETED"
4318 classification["error_msg"] = str(e)
4319 except vimconn.VimConnException as e:
4320 self.logger.error("Exception getting classification status: %s", str(e))
4321 classification["status"] = "VIM_ERROR"
4322 classification["error_msg"] = str(e)
4323
4324 classification_dict[classification_id] = classification
4325
4326 return classification_dict
4327
gatici335a06a2023-07-26 00:34:04 +03004328 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004329 def new_affinity_group(self, affinity_group_data):
4330 """Adds a server group to VIM
4331 affinity_group_data contains a dictionary with information, keys:
4332 name: name in VIM for the server group
4333 type: affinity or anti-affinity
4334 scope: Only nfvi-node allowed
4335 Returns the server group identifier"""
4336 self.logger.debug("Adding Server Group '%s'", str(affinity_group_data))
gatici335a06a2023-07-26 00:34:04 +03004337 name = affinity_group_data["name"]
4338 policy = affinity_group_data["type"]
4339 self._reload_connection()
4340 new_server_group = self.nova.server_groups.create(name, policy)
4341 return new_server_group.id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004342
gatici335a06a2023-07-26 00:34:04 +03004343 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004344 def get_affinity_group(self, affinity_group_id):
4345 """Obtain server group details from the VIM. Returns the server group detais as a dict"""
4346 self.logger.debug("Getting flavor '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004347 self._reload_connection()
4348 server_group = self.nova.server_groups.find(id=affinity_group_id)
4349 return server_group.to_dict()
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004350
gatici335a06a2023-07-26 00:34:04 +03004351 @catch_any_exception
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004352 def delete_affinity_group(self, affinity_group_id):
4353 """Deletes a server group from the VIM. Returns the old affinity_group_id"""
4354 self.logger.debug("Getting server group '%s'", affinity_group_id)
gatici335a06a2023-07-26 00:34:04 +03004355 self._reload_connection()
4356 self.nova.server_groups.delete(affinity_group_id)
4357 return affinity_group_id
Alexis Romerob70f4ed2022-03-11 18:00:49 +01004358
gatici335a06a2023-07-26 00:34:04 +03004359 @catch_any_exception
Patricia Reinoso17852162023-06-15 07:33:04 +00004360 def get_vdu_state(self, vm_id, host_is_required=False) -> list:
4361 """Getting the state of a VDU.
4362 Args:
4363 vm_id (str): ID of an instance
4364 host_is_required (Boolean): If the VIM account is non-admin, host info does not appear in server_dict
4365 and if this is set to True, it raises KeyError.
4366 Returns:
4367 vdu_data (list): VDU details including state, flavor, host_info, AZ
elumalai8658c2c2022-04-28 19:09:31 +05304368 """
4369 self.logger.debug("Getting the status of VM")
4370 self.logger.debug("VIM VM ID %s", vm_id)
gatici335a06a2023-07-26 00:34:04 +03004371 self._reload_connection()
4372 server_dict = self._find_nova_server(vm_id)
4373 srv_attr = "OS-EXT-SRV-ATTR:host"
4374 host_info = (
4375 server_dict[srv_attr] if host_is_required else server_dict.get(srv_attr)
4376 )
4377 vdu_data = [
4378 server_dict["status"],
4379 server_dict["flavor"]["id"],
4380 host_info,
4381 server_dict["OS-EXT-AZ:availability_zone"],
4382 ]
4383 self.logger.debug("vdu_data %s", vdu_data)
4384 return vdu_data
elumalai8658c2c2022-04-28 19:09:31 +05304385
4386 def check_compute_availability(self, host, server_flavor_details):
4387 self._reload_connection()
4388 hypervisor_search = self.nova.hypervisors.search(
4389 hypervisor_match=host, servers=True
4390 )
4391 for hypervisor in hypervisor_search:
4392 hypervisor_id = hypervisor.to_dict()["id"]
4393 hypervisor_details = self.nova.hypervisors.get(hypervisor=hypervisor_id)
4394 hypervisor_dict = hypervisor_details.to_dict()
4395 hypervisor_temp = json.dumps(hypervisor_dict)
4396 hypervisor_json = json.loads(hypervisor_temp)
4397 resources_available = [
4398 hypervisor_json["free_ram_mb"],
4399 hypervisor_json["disk_available_least"],
4400 hypervisor_json["vcpus"] - hypervisor_json["vcpus_used"],
4401 ]
4402 compute_available = all(
4403 x > y for x, y in zip(resources_available, server_flavor_details)
4404 )
4405 if compute_available:
4406 return host
4407
4408 def check_availability_zone(
4409 self, old_az, server_flavor_details, old_host, host=None
4410 ):
4411 self._reload_connection()
4412 az_check = {"zone_check": False, "compute_availability": None}
4413 aggregates_list = self.nova.aggregates.list()
4414 for aggregate in aggregates_list:
4415 aggregate_details = aggregate.to_dict()
4416 aggregate_temp = json.dumps(aggregate_details)
4417 aggregate_json = json.loads(aggregate_temp)
4418 if aggregate_json["availability_zone"] == old_az:
4419 hosts_list = aggregate_json["hosts"]
4420 if host is not None:
4421 if host in hosts_list:
4422 az_check["zone_check"] = True
4423 available_compute_id = self.check_compute_availability(
4424 host, server_flavor_details
4425 )
4426 if available_compute_id is not None:
4427 az_check["compute_availability"] = available_compute_id
4428 else:
4429 for check_host in hosts_list:
4430 if check_host != old_host:
4431 available_compute_id = self.check_compute_availability(
4432 check_host, server_flavor_details
4433 )
4434 if available_compute_id is not None:
4435 az_check["zone_check"] = True
4436 az_check["compute_availability"] = available_compute_id
4437 break
4438 else:
4439 az_check["zone_check"] = True
4440 return az_check
4441
gatici335a06a2023-07-26 00:34:04 +03004442 @catch_any_exception
elumalai8658c2c2022-04-28 19:09:31 +05304443 def migrate_instance(self, vm_id, compute_host=None):
4444 """
4445 Migrate a vdu
4446 param:
4447 vm_id: ID of an instance
4448 compute_host: Host to migrate the vdu to
4449 """
4450 self._reload_connection()
4451 vm_state = False
Patricia Reinoso17852162023-06-15 07:33:04 +00004452 instance_state = self.get_vdu_state(vm_id, host_is_required=True)
elumalai8658c2c2022-04-28 19:09:31 +05304453 server_flavor_id = instance_state[1]
4454 server_hypervisor_name = instance_state[2]
4455 server_availability_zone = instance_state[3]
gatici335a06a2023-07-26 00:34:04 +03004456 server_flavor = self.nova.flavors.find(id=server_flavor_id).to_dict()
4457 server_flavor_details = [
4458 server_flavor["ram"],
4459 server_flavor["disk"],
4460 server_flavor["vcpus"],
4461 ]
4462 if compute_host == server_hypervisor_name:
4463 raise vimconn.VimConnException(
4464 "Unable to migrate instance '{}' to the same host '{}'".format(
4465 vm_id, compute_host
4466 ),
4467 http_code=vimconn.HTTP_Bad_Request,
elumalai8658c2c2022-04-28 19:09:31 +05304468 )
gatici335a06a2023-07-26 00:34:04 +03004469 az_status = self.check_availability_zone(
4470 server_availability_zone,
4471 server_flavor_details,
4472 server_hypervisor_name,
4473 compute_host,
4474 )
4475 availability_zone_check = az_status["zone_check"]
4476 available_compute_id = az_status.get("compute_availability")
elumalai8658c2c2022-04-28 19:09:31 +05304477
gatici335a06a2023-07-26 00:34:04 +03004478 if availability_zone_check is False:
4479 raise vimconn.VimConnException(
4480 "Unable to migrate instance '{}' to a different availability zone".format(
4481 vm_id
4482 ),
4483 http_code=vimconn.HTTP_Bad_Request,
4484 )
4485 if available_compute_id is not None:
4486 # disk_over_commit parameter for live_migrate method is not valid for Nova API version >= 2.25
4487 self.nova.servers.live_migrate(
4488 server=vm_id,
4489 host=available_compute_id,
4490 block_migration=True,
4491 )
4492 state = "MIGRATING"
4493 changed_compute_host = ""
4494 if state == "MIGRATING":
4495 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
4496 changed_compute_host = self.get_vdu_state(vm_id, host_is_required=True)[
4497 2
4498 ]
4499 if vm_state and changed_compute_host == available_compute_id:
4500 self.logger.debug(
4501 "Instance '{}' migrated to the new compute host '{}'".format(
4502 vm_id, changed_compute_host
elumalai8658c2c2022-04-28 19:09:31 +05304503 )
gatici335a06a2023-07-26 00:34:04 +03004504 )
4505 return state, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304506 else:
4507 raise vimconn.VimConnException(
gatici335a06a2023-07-26 00:34:04 +03004508 "Migration Failed. Instance '{}' not moved to the new host {}".format(
4509 vm_id, available_compute_id
elumalai8658c2c2022-04-28 19:09:31 +05304510 ),
4511 http_code=vimconn.HTTP_Bad_Request,
4512 )
gatici335a06a2023-07-26 00:34:04 +03004513 else:
4514 raise vimconn.VimConnException(
4515 "Compute '{}' not available or does not have enough resources to migrate the instance".format(
4516 available_compute_id
4517 ),
4518 http_code=vimconn.HTTP_Bad_Request,
4519 )
sritharan29a4c1a2022-05-05 12:15:04 +00004520
gatici335a06a2023-07-26 00:34:04 +03004521 @catch_any_exception
sritharan29a4c1a2022-05-05 12:15:04 +00004522 def resize_instance(self, vm_id, new_flavor_id):
4523 """
4524 For resizing the vm based on the given
4525 flavor details
4526 param:
4527 vm_id : ID of an instance
4528 new_flavor_id : Flavor id to be resized
4529 Return the status of a resized instance
4530 """
4531 self._reload_connection()
4532 self.logger.debug("resize the flavor of an instance")
4533 instance_status, old_flavor_id, compute_host, az = self.get_vdu_state(vm_id)
4534 old_flavor_disk = self.nova.flavors.find(id=old_flavor_id).to_dict()["disk"]
4535 new_flavor_disk = self.nova.flavors.find(id=new_flavor_id).to_dict()["disk"]
gatici335a06a2023-07-26 00:34:04 +03004536 if instance_status == "ACTIVE" or instance_status == "SHUTOFF":
4537 if old_flavor_disk > new_flavor_disk:
sritharan29a4c1a2022-05-05 12:15:04 +00004538 raise nvExceptions.BadRequest(
gatici335a06a2023-07-26 00:34:04 +03004539 400,
4540 message="Server disk resize failed. Resize to lower disk flavor is not allowed",
sritharan29a4c1a2022-05-05 12:15:04 +00004541 )
gatici335a06a2023-07-26 00:34:04 +03004542 else:
4543 self.nova.servers.resize(server=vm_id, flavor=new_flavor_id)
4544 vm_state = self.__wait_for_vm(vm_id, "VERIFY_RESIZE")
4545 if vm_state:
deepika.e970f5552024-06-03 14:12:50 +05304546 instance_resized_status = self.confirm_resize(
4547 vm_id, instance_status
4548 )
gatici335a06a2023-07-26 00:34:04 +03004549 return instance_resized_status
4550 else:
4551 raise nvExceptions.BadRequest(
4552 409,
4553 message="Cannot 'resize' vm_state is in ERROR",
4554 )
4555
4556 else:
4557 self.logger.debug("ERROR : Instance is not in ACTIVE or SHUTOFF state")
4558 raise nvExceptions.BadRequest(
4559 409,
4560 message="Cannot 'resize' instance while it is in vm_state resized",
4561 )
sritharan29a4c1a2022-05-05 12:15:04 +00004562
deepika.e970f5552024-06-03 14:12:50 +05304563 def confirm_resize(self, vm_id, instance_state):
sritharan29a4c1a2022-05-05 12:15:04 +00004564 """
4565 Confirm the resize of an instance
4566 param:
4567 vm_id: ID of an instance
4568 """
4569 self._reload_connection()
4570 self.nova.servers.confirm_resize(server=vm_id)
4571 if self.get_vdu_state(vm_id)[0] == "VERIFY_RESIZE":
deepika.e970f5552024-06-03 14:12:50 +05304572 self.__wait_for_vm(vm_id, instance_state)
sritharan29a4c1a2022-05-05 12:15:04 +00004573 instance_status = self.get_vdu_state(vm_id)[0]
4574 return instance_status
Gulsum Aticid586d892023-02-13 18:40:03 +03004575
4576 def get_monitoring_data(self):
4577 try:
4578 self.logger.debug("Getting servers and ports data from Openstack VIMs.")
4579 self._reload_connection()
4580 all_servers = self.nova.servers.list(detailed=True)
vegallc53829d2023-06-01 00:47:44 -05004581 try:
4582 for server in all_servers:
Luis Vegad6577d82023-07-26 20:49:12 +00004583 if server.flavor.get("original_name"):
4584 server.flavor["id"] = self.nova.flavors.find(
4585 name=server.flavor["original_name"]
4586 ).id
vegallc53829d2023-06-01 00:47:44 -05004587 except nClient.exceptions.NotFound as e:
4588 self.logger.warning(str(e.message))
Gulsum Aticid586d892023-02-13 18:40:03 +03004589 all_ports = self.neutron.list_ports()
4590 return all_servers, all_ports
gatici335a06a2023-07-26 00:34:04 +03004591 except Exception as e:
Gulsum Aticid586d892023-02-13 18:40:03 +03004592 raise vimconn.VimConnException(
4593 f"Exception in monitoring while getting VMs and ports status: {str(e)}"
4594 )