blob: f225be4551aadc406158764ab6f38f82de9ea4f3 [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
tiernob5cef372017-06-19 15:52:22 +020044from glanceclient import client as glClient
tierno7edb6752016-03-21 17:37:52 +010045import glanceclient.exc as gl1Exceptions
sousaedu049cbb12022-01-05 11:39:35 +000046from keystoneauth1 import session
47from keystoneauth1.identity import v2, v3
48import keystoneclient.exceptions as ksExceptions
49import keystoneclient.v2_0.client as ksClient_v2
50import keystoneclient.v3.client as ksClient_v3
51import netaddr
tierno7edb6752016-03-21 17:37:52 +010052from neutronclient.common import exceptions as neExceptions
sousaedu049cbb12022-01-05 11:39:35 +000053from neutronclient.neutron import client as neClient
54from novaclient import client as nClient, exceptions as nvExceptions
55from osm_ro_plugin import vimconn
tierno7edb6752016-03-21 17:37:52 +010056from requests.exceptions import ConnectionError
sousaedu049cbb12022-01-05 11:39:35 +000057import yaml
tierno7edb6752016-03-21 17:37:52 +010058
tierno1ec592d2020-06-16 15:29:47 +000059__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C., Eduardo Sousa"
60__date__ = "$22-sep-2017 23:59:59$"
tierno40e1bce2017-08-09 09:12:04 +020061
62"""contain the openstack virtual machine status to openmano status"""
sousaedu80135b92021-02-17 15:05:18 +010063vmStatus2manoFormat = {
64 "ACTIVE": "ACTIVE",
65 "PAUSED": "PAUSED",
66 "SUSPENDED": "SUSPENDED",
67 "SHUTOFF": "INACTIVE",
68 "BUILD": "BUILD",
69 "ERROR": "ERROR",
70 "DELETED": "DELETED",
71}
72netStatus2manoFormat = {
73 "ACTIVE": "ACTIVE",
74 "PAUSED": "PAUSED",
75 "INACTIVE": "INACTIVE",
76 "BUILD": "BUILD",
77 "ERROR": "ERROR",
78 "DELETED": "DELETED",
79}
tierno7edb6752016-03-21 17:37:52 +010080
sousaedu80135b92021-02-17 15:05:18 +010081supportedClassificationTypes = ["legacy_flow_classifier"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000082
tierno1ec592d2020-06-16 15:29:47 +000083# global var to have a timeout creating and deleting volumes
garciadeblas64b39c52020-05-21 08:07:25 +000084volume_timeout = 1800
85server_timeout = 1800
montesmoreno0c8def02016-12-22 12:16:23 +000086
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010087
88class SafeDumper(yaml.SafeDumper):
89 def represent_data(self, data):
90 # Openstack APIs use custom subclasses of dict and YAML safe dumper
91 # is designed to not handle that (reference issue 142 of pyyaml)
92 if isinstance(data, dict) and data.__class__ != dict:
93 # A simple solution is to convert those items back to dicts
94 data = dict(data.items())
95
96 return super(SafeDumper, self).represent_data(data)
97
98
tierno72774862020-05-04 11:44:15 +000099class vimconnector(vimconn.VimConnector):
sousaedu80135b92021-02-17 15:05:18 +0100100 def __init__(
101 self,
102 uuid,
103 name,
104 tenant_id,
105 tenant_name,
106 url,
107 url_admin=None,
108 user=None,
109 passwd=None,
110 log_level=None,
111 config={},
112 persistent_info={},
113 ):
tierno1ec592d2020-06-16 15:29:47 +0000114 """using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +0100115 'url' is the keystone authorization url,
116 'url_admin' is not use
tierno1ec592d2020-06-16 15:29:47 +0000117 """
sousaedu80135b92021-02-17 15:05:18 +0100118 api_version = config.get("APIversion")
kate721d79b2017-06-24 04:21:38 -0700119
sousaedu80135b92021-02-17 15:05:18 +0100120 if api_version and api_version not in ("v3.3", "v2.0", "2", "3"):
121 raise vimconn.VimConnException(
122 "Invalid value '{}' for config:APIversion. "
123 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version)
124 )
125
126 vim_type = config.get("vim_type")
127
128 if vim_type and vim_type not in ("vio", "VIO"):
129 raise vimconn.VimConnException(
130 "Invalid value '{}' for config:vim_type."
131 "Allowed values are 'vio' or 'VIO'".format(vim_type)
132 )
133
134 if config.get("dataplane_net_vlan_range") is not None:
tierno1ec592d2020-06-16 15:29:47 +0000135 # validate vlan ranges provided by user
sousaedu80135b92021-02-17 15:05:18 +0100136 self._validate_vlan_ranges(
137 config.get("dataplane_net_vlan_range"), "dataplane_net_vlan_range"
138 )
garciadeblasebd66722019-01-31 16:01:31 +0000139
sousaedu80135b92021-02-17 15:05:18 +0100140 if config.get("multisegment_vlan_range") is not None:
tierno1ec592d2020-06-16 15:29:47 +0000141 # validate vlan ranges provided by user
sousaedu80135b92021-02-17 15:05:18 +0100142 self._validate_vlan_ranges(
143 config.get("multisegment_vlan_range"), "multisegment_vlan_range"
144 )
kate721d79b2017-06-24 04:21:38 -0700145
sousaedu80135b92021-02-17 15:05:18 +0100146 vimconn.VimConnector.__init__(
147 self,
148 uuid,
149 name,
150 tenant_id,
151 tenant_name,
152 url,
153 url_admin,
154 user,
155 passwd,
156 log_level,
157 config,
158 )
tiernob3d36742017-03-03 23:51:05 +0100159
tierno4d1ce222018-04-06 10:41:06 +0200160 if self.config.get("insecure") and self.config.get("ca_cert"):
sousaedu80135b92021-02-17 15:05:18 +0100161 raise vimconn.VimConnException(
162 "options insecure and ca_cert are mutually exclusive"
163 )
164
tierno4d1ce222018-04-06 10:41:06 +0200165 self.verify = True
sousaedu80135b92021-02-17 15:05:18 +0100166
tierno4d1ce222018-04-06 10:41:06 +0200167 if self.config.get("insecure"):
168 self.verify = False
sousaedu80135b92021-02-17 15:05:18 +0100169
tierno4d1ce222018-04-06 10:41:06 +0200170 if self.config.get("ca_cert"):
171 self.verify = self.config.get("ca_cert")
tierno4d1ce222018-04-06 10:41:06 +0200172
tierno7edb6752016-03-21 17:37:52 +0100173 if not url:
sousaedu80135b92021-02-17 15:05:18 +0100174 raise TypeError("url param can not be NoneType")
175
tiernob5cef372017-06-19 15:52:22 +0200176 self.persistent_info = persistent_info
sousaedu80135b92021-02-17 15:05:18 +0100177 self.availability_zone = persistent_info.get("availability_zone", None)
178 self.session = persistent_info.get("session", {"reload_client": True})
179 self.my_tenant_id = self.session.get("my_tenant_id")
180 self.nova = self.session.get("nova")
181 self.neutron = self.session.get("neutron")
182 self.cinder = self.session.get("cinder")
183 self.glance = self.session.get("glance")
184 # self.glancev1 = self.session.get("glancev1")
185 self.keystone = self.session.get("keystone")
186 self.api_version3 = self.session.get("api_version3")
kate721d79b2017-06-24 04:21:38 -0700187 self.vim_type = self.config.get("vim_type")
sousaedu80135b92021-02-17 15:05:18 +0100188
kate721d79b2017-06-24 04:21:38 -0700189 if self.vim_type:
190 self.vim_type = self.vim_type.upper()
sousaedu80135b92021-02-17 15:05:18 +0100191
kate721d79b2017-06-24 04:21:38 -0700192 if self.config.get("use_internal_endpoint"):
193 self.endpoint_type = "internalURL"
194 else:
195 self.endpoint_type = None
montesmoreno0c8def02016-12-22 12:16:23 +0000196
sousaedu80135b92021-02-17 15:05:18 +0100197 logging.getLogger("urllib3").setLevel(logging.WARNING)
198 logging.getLogger("keystoneauth").setLevel(logging.WARNING)
199 logging.getLogger("novaclient").setLevel(logging.WARNING)
200 self.logger = logging.getLogger("ro.vim.openstack")
kate721d79b2017-06-24 04:21:38 -0700201
tiernoa05b65a2019-02-01 12:30:27 +0000202 # allow security_groups to be a list or a single string
sousaedu80135b92021-02-17 15:05:18 +0100203 if isinstance(self.config.get("security_groups"), str):
204 self.config["security_groups"] = [self.config["security_groups"]]
205
tiernoa05b65a2019-02-01 12:30:27 +0000206 self.security_groups_id = None
207
tierno1ec592d2020-06-16 15:29:47 +0000208 # ###### VIO Specific Changes #########
kate721d79b2017-06-24 04:21:38 -0700209 if self.vim_type == "VIO":
sousaedu80135b92021-02-17 15:05:18 +0100210 self.logger = logging.getLogger("ro.vim.vio")
kate721d79b2017-06-24 04:21:38 -0700211
tiernofe789902016-09-29 14:20:44 +0000212 if log_level:
tierno1ec592d2020-06-16 15:29:47 +0000213 self.logger.setLevel(getattr(logging, log_level))
tiernof716aea2017-06-21 18:01:40 +0200214
215 def __getitem__(self, index):
216 """Get individuals parameters.
217 Throw KeyError"""
sousaedu80135b92021-02-17 15:05:18 +0100218 if index == "project_domain_id":
tiernof716aea2017-06-21 18:01:40 +0200219 return self.config.get("project_domain_id")
sousaedu80135b92021-02-17 15:05:18 +0100220 elif index == "user_domain_id":
tiernof716aea2017-06-21 18:01:40 +0200221 return self.config.get("user_domain_id")
222 else:
tierno72774862020-05-04 11:44:15 +0000223 return vimconn.VimConnector.__getitem__(self, index)
tiernof716aea2017-06-21 18:01:40 +0200224
225 def __setitem__(self, index, value):
226 """Set individuals parameters and it is marked as dirty so to force connection reload.
227 Throw KeyError"""
sousaedu80135b92021-02-17 15:05:18 +0100228 if index == "project_domain_id":
tiernof716aea2017-06-21 18:01:40 +0200229 self.config["project_domain_id"] = value
sousaedu80135b92021-02-17 15:05:18 +0100230 elif index == "user_domain_id":
tierno1ec592d2020-06-16 15:29:47 +0000231 self.config["user_domain_id"] = value
tiernof716aea2017-06-21 18:01:40 +0200232 else:
tierno72774862020-05-04 11:44:15 +0000233 vimconn.VimConnector.__setitem__(self, index, value)
sousaedu80135b92021-02-17 15:05:18 +0100234
235 self.session["reload_client"] = True
tiernof716aea2017-06-21 18:01:40 +0200236
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100237 def serialize(self, value):
238 """Serialization of python basic types.
239
240 In the case value is not serializable a message will be logged and a
241 simple representation of the data that cannot be converted back to
242 python is returned.
243 """
tierno7d782ef2019-10-04 12:56:31 +0000244 if isinstance(value, str):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100245 return value
246
247 try:
sousaedu80135b92021-02-17 15:05:18 +0100248 return yaml.dump(
249 value, Dumper=SafeDumper, default_flow_style=True, width=256
250 )
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100251 except yaml.representer.RepresenterError:
sousaedu80135b92021-02-17 15:05:18 +0100252 self.logger.debug(
253 "The following entity cannot be serialized in YAML:\n\n%s\n\n",
254 pformat(value),
255 exc_info=True,
256 )
257
tierno1ec592d2020-06-16 15:29:47 +0000258 return str(value)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100259
tierno7edb6752016-03-21 17:37:52 +0100260 def _reload_connection(self):
tierno1ec592d2020-06-16 15:29:47 +0000261 """Called before any operation, it check if credentials has changed
tierno7edb6752016-03-21 17:37:52 +0100262 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
tierno1ec592d2020-06-16 15:29:47 +0000263 """
264 # 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 +0100265 if self.session["reload_client"]:
266 if self.config.get("APIversion"):
267 self.api_version3 = (
268 self.config["APIversion"] == "v3.3"
269 or self.config["APIversion"] == "3"
270 )
tiernof716aea2017-06-21 18:01:40 +0200271 else: # get from ending auth_url that end with v3 or with v2.0
sousaedu80135b92021-02-17 15:05:18 +0100272 self.api_version3 = self.url.endswith("/v3") or self.url.endswith(
273 "/v3/"
274 )
275
276 self.session["api_version3"] = self.api_version3
277
tiernof716aea2017-06-21 18:01:40 +0200278 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100279 if self.config.get("project_domain_id") or self.config.get(
280 "project_domain_name"
281 ):
tierno3cb8dc32017-10-24 18:13:19 +0200282 project_domain_id_default = None
283 else:
sousaedu80135b92021-02-17 15:05:18 +0100284 project_domain_id_default = "default"
285
286 if self.config.get("user_domain_id") or self.config.get(
287 "user_domain_name"
288 ):
tierno3cb8dc32017-10-24 18:13:19 +0200289 user_domain_id_default = None
290 else:
sousaedu80135b92021-02-17 15:05:18 +0100291 user_domain_id_default = "default"
292 auth = v3.Password(
293 auth_url=self.url,
294 username=self.user,
295 password=self.passwd,
296 project_name=self.tenant_name,
297 project_id=self.tenant_id,
298 project_domain_id=self.config.get(
299 "project_domain_id", project_domain_id_default
300 ),
301 user_domain_id=self.config.get(
302 "user_domain_id", user_domain_id_default
303 ),
304 project_domain_name=self.config.get("project_domain_name"),
305 user_domain_name=self.config.get("user_domain_name"),
306 )
ahmadsa95baa272016-11-30 09:14:11 +0500307 else:
sousaedu80135b92021-02-17 15:05:18 +0100308 auth = v2.Password(
309 auth_url=self.url,
310 username=self.user,
311 password=self.passwd,
312 tenant_name=self.tenant_name,
313 tenant_id=self.tenant_id,
314 )
315
tierno4d1ce222018-04-06 10:41:06 +0200316 sess = session.Session(auth=auth, verify=self.verify)
tierno1ec592d2020-06-16 15:29:47 +0000317 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River
318 # Titanium cloud and StarlingX
sousaedu80135b92021-02-17 15:05:18 +0100319 region_name = self.config.get("region_name")
320
tiernof716aea2017-06-21 18:01:40 +0200321 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100322 self.keystone = ksClient_v3.Client(
323 session=sess,
324 endpoint_type=self.endpoint_type,
325 region_name=region_name,
326 )
tiernof716aea2017-06-21 18:01:40 +0200327 else:
sousaedu80135b92021-02-17 15:05:18 +0100328 self.keystone = ksClient_v2.Client(
329 session=sess, endpoint_type=self.endpoint_type
330 )
331
332 self.session["keystone"] = self.keystone
333 # In order to enable microversion functionality an explicit microversion must be specified in "config".
montesmoreno9317d302017-08-16 12:48:23 +0200334 # This implementation approach is due to the warning message in
335 # https://developer.openstack.org/api-guide/compute/microversions.html
336 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
337 # always require an specific microversion.
sousaedu80135b92021-02-17 15:05:18 +0100338 # To be able to use "device role tagging" functionality define "microversion: 2.32" in datacenter config
montesmoreno9317d302017-08-16 12:48:23 +0200339 version = self.config.get("microversion")
sousaedu80135b92021-02-17 15:05:18 +0100340
montesmoreno9317d302017-08-16 12:48:23 +0200341 if not version:
vegallc53829d2023-06-01 00:47:44 -0500342 version = "2.60"
sousaedu80135b92021-02-17 15:05:18 +0100343
tierno1ec592d2020-06-16 15:29:47 +0000344 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River
345 # Titanium cloud and StarlingX
sousaedu80135b92021-02-17 15:05:18 +0100346 self.nova = self.session["nova"] = nClient.Client(
347 str(version),
348 session=sess,
349 endpoint_type=self.endpoint_type,
350 region_name=region_name,
351 )
352 self.neutron = self.session["neutron"] = neClient.Client(
353 "2.0",
354 session=sess,
355 endpoint_type=self.endpoint_type,
356 region_name=region_name,
357 )
Lovejeet Singh778f3cc2023-02-13 16:15:40 +0530358
359 if sess.get_all_version_data(service_type="volumev2"):
360 self.cinder = self.session["cinder"] = cClient.Client(
361 2,
362 session=sess,
363 endpoint_type=self.endpoint_type,
364 region_name=region_name,
365 )
366 else:
367 self.cinder = self.session["cinder"] = cClient.Client(
368 3,
369 session=sess,
370 endpoint_type=self.endpoint_type,
371 region_name=region_name,
372 )
sousaedu80135b92021-02-17 15:05:18 +0100373
tiernoa05b65a2019-02-01 12:30:27 +0000374 try:
sousaedu80135b92021-02-17 15:05:18 +0100375 self.my_tenant_id = self.session["my_tenant_id"] = sess.get_project_id()
tierno1ec592d2020-06-16 15:29:47 +0000376 except Exception:
tiernoa05b65a2019-02-01 12:30:27 +0000377 self.logger.error("Cannot get project_id from session", exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +0100378
kate721d79b2017-06-24 04:21:38 -0700379 if self.endpoint_type == "internalURL":
380 glance_service_id = self.keystone.services.list(name="glance")[0].id
sousaedu80135b92021-02-17 15:05:18 +0100381 glance_endpoint = self.keystone.endpoints.list(
382 glance_service_id, interface="internal"
383 )[0].url
kate721d79b2017-06-24 04:21:38 -0700384 else:
385 glance_endpoint = None
sousaedu80135b92021-02-17 15:05:18 +0100386
387 self.glance = self.session["glance"] = glClient.Client(
388 2, session=sess, endpoint=glance_endpoint
389 )
tiernoa05b65a2019-02-01 12:30:27 +0000390 # using version 1 of glance client in new_image()
sousaedu80135b92021-02-17 15:05:18 +0100391 # self.glancev1 = self.session["glancev1"] = glClient.Client("1", session=sess,
tierno1beea862018-07-11 15:47:37 +0200392 # endpoint=glance_endpoint)
sousaedu80135b92021-02-17 15:05:18 +0100393 self.session["reload_client"] = False
394 self.persistent_info["session"] = self.session
mirabal29356312017-07-27 12:21:22 +0200395 # add availablity zone info inside self.persistent_info
396 self._set_availablity_zones()
sousaedu80135b92021-02-17 15:05:18 +0100397 self.persistent_info["availability_zone"] = self.availability_zone
398 # force to get again security_groups_ids next time they are needed
399 self.security_groups_id = None
ahmadsa95baa272016-11-30 09:14:11 +0500400
tierno7edb6752016-03-21 17:37:52 +0100401 def __net_os2mano(self, net_list_dict):
tierno1ec592d2020-06-16 15:29:47 +0000402 """Transform the net openstack format to mano format
403 net_list_dict can be a list of dict or a single dict"""
tierno7edb6752016-03-21 17:37:52 +0100404 if type(net_list_dict) is dict:
tierno1ec592d2020-06-16 15:29:47 +0000405 net_list_ = (net_list_dict,)
tierno7edb6752016-03-21 17:37:52 +0100406 elif type(net_list_dict) is list:
tierno1ec592d2020-06-16 15:29:47 +0000407 net_list_ = net_list_dict
tierno7edb6752016-03-21 17:37:52 +0100408 else:
409 raise TypeError("param net_list_dict must be a list or a dictionary")
410 for net in net_list_:
sousaedu80135b92021-02-17 15:05:18 +0100411 if net.get("provider:network_type") == "vlan":
412 net["type"] = "data"
tierno7edb6752016-03-21 17:37:52 +0100413 else:
sousaedu80135b92021-02-17 15:05:18 +0100414 net["type"] = "bridge"
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200415
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000416 def __classification_os2mano(self, class_list_dict):
417 """Transform the openstack format (Flow Classifier) to mano format
418 (Classification) class_list_dict can be a list of dict or a single dict
419 """
420 if isinstance(class_list_dict, dict):
421 class_list_ = [class_list_dict]
422 elif isinstance(class_list_dict, list):
423 class_list_ = class_list_dict
424 else:
tierno1ec592d2020-06-16 15:29:47 +0000425 raise TypeError("param class_list_dict must be a list or a dictionary")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000426 for classification in class_list_:
sousaedu80135b92021-02-17 15:05:18 +0100427 id = classification.pop("id")
428 name = classification.pop("name")
429 description = classification.pop("description")
430 project_id = classification.pop("project_id")
431 tenant_id = classification.pop("tenant_id")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000432 original_classification = copy.deepcopy(classification)
433 classification.clear()
sousaedu80135b92021-02-17 15:05:18 +0100434 classification["ctype"] = "legacy_flow_classifier"
435 classification["definition"] = original_classification
436 classification["id"] = id
437 classification["name"] = name
438 classification["description"] = description
439 classification["project_id"] = project_id
440 classification["tenant_id"] = tenant_id
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000441
442 def __sfi_os2mano(self, sfi_list_dict):
443 """Transform the openstack format (Port Pair) to mano format (SFI)
444 sfi_list_dict can be a list of dict or a single dict
445 """
446 if isinstance(sfi_list_dict, dict):
447 sfi_list_ = [sfi_list_dict]
448 elif isinstance(sfi_list_dict, list):
449 sfi_list_ = sfi_list_dict
450 else:
sousaedu80135b92021-02-17 15:05:18 +0100451 raise TypeError("param sfi_list_dict must be a list or a dictionary")
452
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000453 for sfi in sfi_list_:
sousaedu80135b92021-02-17 15:05:18 +0100454 sfi["ingress_ports"] = []
455 sfi["egress_ports"] = []
456
457 if sfi.get("ingress"):
458 sfi["ingress_ports"].append(sfi["ingress"])
459
460 if sfi.get("egress"):
461 sfi["egress_ports"].append(sfi["egress"])
462
463 del sfi["ingress"]
464 del sfi["egress"]
465 params = sfi.get("service_function_parameters")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000466 sfc_encap = False
sousaedu80135b92021-02-17 15:05:18 +0100467
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000468 if params:
sousaedu80135b92021-02-17 15:05:18 +0100469 correlation = params.get("correlation")
470
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000471 if correlation:
472 sfc_encap = True
sousaedu80135b92021-02-17 15:05:18 +0100473
474 sfi["sfc_encap"] = sfc_encap
475 del sfi["service_function_parameters"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000476
477 def __sf_os2mano(self, sf_list_dict):
478 """Transform the openstack format (Port Pair Group) to mano format (SF)
479 sf_list_dict can be a list of dict or a single dict
480 """
481 if isinstance(sf_list_dict, dict):
482 sf_list_ = [sf_list_dict]
483 elif isinstance(sf_list_dict, list):
484 sf_list_ = sf_list_dict
485 else:
sousaedu80135b92021-02-17 15:05:18 +0100486 raise TypeError("param sf_list_dict must be a list or a dictionary")
487
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000488 for sf in sf_list_:
sousaedu80135b92021-02-17 15:05:18 +0100489 del sf["port_pair_group_parameters"]
490 sf["sfis"] = sf["port_pairs"]
491 del sf["port_pairs"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000492
493 def __sfp_os2mano(self, sfp_list_dict):
494 """Transform the openstack format (Port Chain) to mano format (SFP)
495 sfp_list_dict can be a list of dict or a single dict
496 """
497 if isinstance(sfp_list_dict, dict):
498 sfp_list_ = [sfp_list_dict]
499 elif isinstance(sfp_list_dict, list):
500 sfp_list_ = sfp_list_dict
501 else:
sousaedu80135b92021-02-17 15:05:18 +0100502 raise TypeError("param sfp_list_dict must be a list or a dictionary")
503
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000504 for sfp in sfp_list_:
sousaedu80135b92021-02-17 15:05:18 +0100505 params = sfp.pop("chain_parameters")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000506 sfc_encap = False
sousaedu80135b92021-02-17 15:05:18 +0100507
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000508 if params:
sousaedu80135b92021-02-17 15:05:18 +0100509 correlation = params.get("correlation")
510
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000511 if correlation:
512 sfc_encap = True
sousaedu80135b92021-02-17 15:05:18 +0100513
514 sfp["sfc_encap"] = sfc_encap
515 sfp["spi"] = sfp.pop("chain_id")
516 sfp["classifications"] = sfp.pop("flow_classifiers")
517 sfp["service_functions"] = sfp.pop("port_pair_groups")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000518
519 # placeholder for now; read TODO note below
520 def _validate_classification(self, type, definition):
521 # only legacy_flow_classifier Type is supported at this point
522 return True
523 # TODO(igordcard): this method should be an abstract method of an
524 # abstract Classification class to be implemented by the specific
525 # Types. Also, abstract vimconnector should call the validation
526 # method before the implemented VIM connectors are called.
527
tiernoae4a8d12016-07-08 12:30:39 +0200528 def _format_exception(self, exception):
tierno69647792020-03-05 16:45:48 +0000529 """Transform a keystone, nova, neutron exception into a vimconn exception discovering the cause"""
tierno69647792020-03-05 16:45:48 +0000530 message_error = str(exception)
tierno5ad826a2020-08-11 11:19:44 +0000531 tip = ""
tiernode12f782019-04-05 12:46:42 +0000532
sousaedu80135b92021-02-17 15:05:18 +0100533 if isinstance(
534 exception,
535 (
536 neExceptions.NetworkNotFoundClient,
537 nvExceptions.NotFound,
538 ksExceptions.NotFound,
539 gl1Exceptions.HTTPNotFound,
540 ),
541 ):
542 raise vimconn.VimConnNotFoundException(
543 type(exception).__name__ + ": " + message_error
544 )
545 elif isinstance(
546 exception,
547 (
548 HTTPException,
549 gl1Exceptions.HTTPException,
550 gl1Exceptions.CommunicationError,
551 ConnectionError,
552 ksExceptions.ConnectionError,
553 neExceptions.ConnectionFailed,
554 ),
555 ):
tierno5ad826a2020-08-11 11:19:44 +0000556 if type(exception).__name__ == "SSLError":
557 tip = " (maybe option 'insecure' must be added to the VIM)"
sousaedu80135b92021-02-17 15:05:18 +0100558
559 raise vimconn.VimConnConnectionException(
560 "Invalid URL or credentials{}: {}".format(tip, message_error)
561 )
562 elif isinstance(
563 exception,
564 (
565 KeyError,
566 nvExceptions.BadRequest,
567 ksExceptions.BadRequest,
568 ),
569 ):
Patricia Reinoso17852162023-06-15 07:33:04 +0000570 if message_error == "OS-EXT-SRV-ATTR:host":
571 tip = " (If the user does not have non-admin credentials, this attribute will be missing)"
572 raise vimconn.VimConnInsufficientCredentials(
573 type(exception).__name__ + ": " + message_error + tip
574 )
sousaedu80135b92021-02-17 15:05:18 +0100575 raise vimconn.VimConnException(
576 type(exception).__name__ + ": " + message_error
577 )
Patricia Reinoso17852162023-06-15 07:33:04 +0000578
sousaedu80135b92021-02-17 15:05:18 +0100579 elif isinstance(
580 exception,
581 (
582 nvExceptions.ClientException,
583 ksExceptions.ClientException,
584 neExceptions.NeutronException,
585 ),
586 ):
587 raise vimconn.VimConnUnexpectedResponse(
588 type(exception).__name__ + ": " + message_error
589 )
tiernoae4a8d12016-07-08 12:30:39 +0200590 elif isinstance(exception, nvExceptions.Conflict):
sousaedu80135b92021-02-17 15:05:18 +0100591 raise vimconn.VimConnConflictException(
592 type(exception).__name__ + ": " + message_error
593 )
tierno72774862020-05-04 11:44:15 +0000594 elif isinstance(exception, vimconn.VimConnException):
tierno41a69812018-02-16 14:34:33 +0100595 raise exception
tiernof716aea2017-06-21 18:01:40 +0200596 else: # ()
tiernode12f782019-04-05 12:46:42 +0000597 self.logger.error("General Exception " + message_error, exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +0100598
599 raise vimconn.VimConnConnectionException(
600 type(exception).__name__ + ": " + message_error
601 )
tiernoae4a8d12016-07-08 12:30:39 +0200602
tiernoa05b65a2019-02-01 12:30:27 +0000603 def _get_ids_from_name(self):
604 """
605 Obtain ids from name of tenant and security_groups. Store at self .security_groups_id"
606 :return: None
607 """
608 # get tenant_id if only tenant_name is supplied
609 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100610
tiernoa05b65a2019-02-01 12:30:27 +0000611 if not self.my_tenant_id:
sousaedu80135b92021-02-17 15:05:18 +0100612 raise vimconn.VimConnConnectionException(
613 "Error getting tenant information from name={} id={}".format(
614 self.tenant_name, self.tenant_id
615 )
616 )
617
618 if self.config.get("security_groups") and not self.security_groups_id:
tiernoa05b65a2019-02-01 12:30:27 +0000619 # convert from name to id
sousaedu80135b92021-02-17 15:05:18 +0100620 neutron_sg_list = self.neutron.list_security_groups(
621 tenant_id=self.my_tenant_id
622 )["security_groups"]
tiernoa05b65a2019-02-01 12:30:27 +0000623
624 self.security_groups_id = []
sousaedu80135b92021-02-17 15:05:18 +0100625 for sg in self.config.get("security_groups"):
tiernoa05b65a2019-02-01 12:30:27 +0000626 for neutron_sg in neutron_sg_list:
627 if sg in (neutron_sg["id"], neutron_sg["name"]):
628 self.security_groups_id.append(neutron_sg["id"])
629 break
630 else:
631 self.security_groups_id = None
sousaedu80135b92021-02-17 15:05:18 +0100632
633 raise vimconn.VimConnConnectionException(
634 "Not found security group {} for this tenant".format(sg)
635 )
tiernoa05b65a2019-02-01 12:30:27 +0000636
vegallc53829d2023-06-01 00:47:44 -0500637 def _find_nova_server(self, vm_id):
638 """
639 Returns the VM instance from Openstack and completes it with flavor ID
640 Do not call nova.servers.find directly, as it does not return flavor ID with microversion>=2.47
641 """
642 try:
643 self._reload_connection()
644 server = self.nova.servers.find(id=vm_id)
645 # TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
646 server_dict = server.to_dict()
647 try:
648 server_dict["flavor"]["id"] = self.nova.flavors.find(
649 name=server_dict["flavor"]["original_name"]
650 ).id
651 except nClient.exceptions.NotFound as e:
652 self.logger.warning(str(e.message))
653 return server_dict
654 except (
655 ksExceptions.ClientException,
656 nvExceptions.ClientException,
657 nvExceptions.NotFound,
658 ConnectionError,
659 ) as e:
660 self._format_exception(e)
661
tierno5509c2e2019-07-04 16:23:20 +0000662 def check_vim_connectivity(self):
663 # just get network list to check connectivity and credentials
664 self.get_network_list(filter_dict={})
665
tiernoae4a8d12016-07-08 12:30:39 +0200666 def get_tenant_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +0000667 """Obtain tenants of VIM
tiernoae4a8d12016-07-08 12:30:39 +0200668 filter_dict can contain the following keys:
669 name: filter by tenant name
670 id: filter by tenant uuid/id
671 <other VIM specific>
672 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
tierno1ec592d2020-06-16 15:29:47 +0000673 """
ahmadsa95baa272016-11-30 09:14:11 +0500674 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
sousaedu80135b92021-02-17 15:05:18 +0100675
tiernoae4a8d12016-07-08 12:30:39 +0200676 try:
677 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100678
tiernof716aea2017-06-21 18:01:40 +0200679 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100680 project_class_list = self.keystone.projects.list(
681 name=filter_dict.get("name")
682 )
ahmadsa95baa272016-11-30 09:14:11 +0500683 else:
tiernof716aea2017-06-21 18:01:40 +0200684 project_class_list = self.keystone.tenants.findall(**filter_dict)
sousaedu80135b92021-02-17 15:05:18 +0100685
tierno1ec592d2020-06-16 15:29:47 +0000686 project_list = []
sousaedu80135b92021-02-17 15:05:18 +0100687
ahmadsa95baa272016-11-30 09:14:11 +0500688 for project in project_class_list:
sousaedu80135b92021-02-17 15:05:18 +0100689 if filter_dict.get("id") and filter_dict["id"] != project.id:
tiernof716aea2017-06-21 18:01:40 +0200690 continue
sousaedu80135b92021-02-17 15:05:18 +0100691
ahmadsa95baa272016-11-30 09:14:11 +0500692 project_list.append(project.to_dict())
sousaedu80135b92021-02-17 15:05:18 +0100693
ahmadsa95baa272016-11-30 09:14:11 +0500694 return project_list
sousaedu80135b92021-02-17 15:05:18 +0100695 except (
696 ksExceptions.ConnectionError,
697 ksExceptions.ClientException,
698 ConnectionError,
699 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200700 self._format_exception(e)
701
702 def new_tenant(self, tenant_name, tenant_description):
tierno1ec592d2020-06-16 15:29:47 +0000703 """Adds a new tenant to openstack VIM. Returns the tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200704 self.logger.debug("Adding a new tenant name: %s", tenant_name)
sousaedu80135b92021-02-17 15:05:18 +0100705
tiernoae4a8d12016-07-08 12:30:39 +0200706 try:
707 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100708
tiernof716aea2017-06-21 18:01:40 +0200709 if self.api_version3:
sousaedu80135b92021-02-17 15:05:18 +0100710 project = self.keystone.projects.create(
711 tenant_name,
712 self.config.get("project_domain_id", "default"),
713 description=tenant_description,
714 is_domain=False,
715 )
ahmadsa95baa272016-11-30 09:14:11 +0500716 else:
tiernof716aea2017-06-21 18:01:40 +0200717 project = self.keystone.tenants.create(tenant_name, tenant_description)
sousaedu80135b92021-02-17 15:05:18 +0100718
ahmadsa95baa272016-11-30 09:14:11 +0500719 return project.id
sousaedu80135b92021-02-17 15:05:18 +0100720 except (
721 ksExceptions.ConnectionError,
722 ksExceptions.ClientException,
723 ksExceptions.BadRequest,
724 ConnectionError,
725 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200726 self._format_exception(e)
727
728 def delete_tenant(self, tenant_id):
tierno1ec592d2020-06-16 15:29:47 +0000729 """Delete a tenant from openstack VIM. Returns the old tenant identifier"""
tiernoae4a8d12016-07-08 12:30:39 +0200730 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100731
tiernoae4a8d12016-07-08 12:30:39 +0200732 try:
733 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100734
tiernof716aea2017-06-21 18:01:40 +0200735 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500736 self.keystone.projects.delete(tenant_id)
737 else:
738 self.keystone.tenants.delete(tenant_id)
sousaedu80135b92021-02-17 15:05:18 +0100739
tiernoae4a8d12016-07-08 12:30:39 +0200740 return tenant_id
sousaedu80135b92021-02-17 15:05:18 +0100741 except (
742 ksExceptions.ConnectionError,
743 ksExceptions.ClientException,
744 ksExceptions.NotFound,
745 ConnectionError,
746 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200747 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500748
sousaedu80135b92021-02-17 15:05:18 +0100749 def new_network(
750 self,
751 net_name,
752 net_type,
753 ip_profile=None,
754 shared=False,
755 provider_network_profile=None,
756 ):
garciadeblasebd66722019-01-31 16:01:31 +0000757 """Adds a tenant network to VIM
758 Params:
759 'net_name': name of the network
760 'net_type': one of:
761 'bridge': overlay isolated network
762 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
763 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
764 'ip_profile': is a dict containing the IP parameters of the network
765 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
766 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
767 'gateway_address': (Optional) ip_schema, that is X.X.X.X
768 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
769 'dhcp_enabled': True or False
770 'dhcp_start_address': ip_schema, first IP to grant
771 'dhcp_count': number of IPs to grant.
772 'shared': if this network can be seen/use by other tenants/organization
garciadeblas4af0d542020-02-18 16:01:13 +0100773 'provider_network_profile': (optional) contains {segmentation-id: vlan, network-type: vlan|vxlan,
774 physical-network: physnet-label}
garciadeblasebd66722019-01-31 16:01:31 +0000775 Returns a tuple with the network identifier and created_items, or raises an exception on error
776 created_items can be None or a dictionary where this method can include key-values that will be passed to
777 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
778 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
779 as not present.
780 """
sousaedu80135b92021-02-17 15:05:18 +0100781 self.logger.debug(
782 "Adding a new network to VIM name '%s', type '%s'", net_name, net_type
783 )
garciadeblasebd66722019-01-31 16:01:31 +0000784 # self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
kbsuba85c54d2019-10-17 16:30:32 +0000785
tierno7edb6752016-03-21 17:37:52 +0100786 try:
kbsuba85c54d2019-10-17 16:30:32 +0000787 vlan = None
sousaedu80135b92021-02-17 15:05:18 +0100788
kbsuba85c54d2019-10-17 16:30:32 +0000789 if provider_network_profile:
790 vlan = provider_network_profile.get("segmentation-id")
sousaedu80135b92021-02-17 15:05:18 +0100791
garciadeblasedca7b32016-09-29 14:01:52 +0000792 new_net = None
garciadeblasebd66722019-01-31 16:01:31 +0000793 created_items = {}
tierno7edb6752016-03-21 17:37:52 +0100794 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +0100795 network_dict = {"name": net_name, "admin_state_up": True}
796
Gabriel Cuba0d8ce072022-12-14 18:33:50 -0500797 if net_type in ("data", "ptp") or provider_network_profile:
tierno6869ae72020-01-09 17:37:34 +0000798 provider_physical_network = None
sousaedu80135b92021-02-17 15:05:18 +0100799
800 if provider_network_profile and provider_network_profile.get(
801 "physical-network"
802 ):
803 provider_physical_network = provider_network_profile.get(
804 "physical-network"
805 )
806
tierno6869ae72020-01-09 17:37:34 +0000807 # provider-network must be one of the dataplane_physcial_netowrk if this is a list. If it is string
808 # or not declared, just ignore the checking
sousaedu80135b92021-02-17 15:05:18 +0100809 if (
810 isinstance(
811 self.config.get("dataplane_physical_net"), (tuple, list)
812 )
813 and provider_physical_network
814 not in self.config["dataplane_physical_net"]
815 ):
tierno72774862020-05-04 11:44:15 +0000816 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100817 "Invalid parameter 'provider-network:physical-network' "
818 "for network creation. '{}' is not one of the declared "
819 "list at VIM_config:dataplane_physical_net".format(
820 provider_physical_network
821 )
822 )
823
824 # use the default dataplane_physical_net
825 if not provider_physical_network:
826 provider_physical_network = self.config.get(
827 "dataplane_physical_net"
828 )
829
tierno6869ae72020-01-09 17:37:34 +0000830 # 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 +0100831 if (
832 isinstance(provider_physical_network, (tuple, list))
833 and provider_physical_network
834 ):
tierno6869ae72020-01-09 17:37:34 +0000835 provider_physical_network = provider_physical_network[0]
836
837 if not provider_physical_network:
tierno5ad826a2020-08-11 11:19:44 +0000838 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100839 "missing information needed for underlay networks. Provide "
840 "'dataplane_physical_net' configuration at VIM or use the NS "
841 "instantiation parameter 'provider-network.physical-network'"
842 " for the VLD"
843 )
tierno6869ae72020-01-09 17:37:34 +0000844
sousaedu80135b92021-02-17 15:05:18 +0100845 if not self.config.get("multisegment_support"):
846 network_dict[
847 "provider:physical_network"
848 ] = provider_physical_network
849
850 if (
851 provider_network_profile
852 and "network-type" in provider_network_profile
853 ):
854 network_dict[
855 "provider:network_type"
856 ] = provider_network_profile["network-type"]
garciadeblas4af0d542020-02-18 16:01:13 +0100857 else:
sousaedu80135b92021-02-17 15:05:18 +0100858 network_dict["provider:network_type"] = self.config.get(
859 "dataplane_network_type", "vlan"
860 )
861
tierno6869ae72020-01-09 17:37:34 +0000862 if vlan:
863 network_dict["provider:segmentation_id"] = vlan
garciadeblasebd66722019-01-31 16:01:31 +0000864 else:
tierno6869ae72020-01-09 17:37:34 +0000865 # Multi-segment case
garciadeblasebd66722019-01-31 16:01:31 +0000866 segment_list = []
tierno6869ae72020-01-09 17:37:34 +0000867 segment1_dict = {
sousaedu80135b92021-02-17 15:05:18 +0100868 "provider:physical_network": "",
869 "provider:network_type": "vxlan",
tierno6869ae72020-01-09 17:37:34 +0000870 }
garciadeblasebd66722019-01-31 16:01:31 +0000871 segment_list.append(segment1_dict)
tierno6869ae72020-01-09 17:37:34 +0000872 segment2_dict = {
873 "provider:physical_network": provider_physical_network,
sousaedu80135b92021-02-17 15:05:18 +0100874 "provider:network_type": "vlan",
tierno6869ae72020-01-09 17:37:34 +0000875 }
sousaedu80135b92021-02-17 15:05:18 +0100876
tierno6869ae72020-01-09 17:37:34 +0000877 if vlan:
878 segment2_dict["provider:segmentation_id"] = vlan
sousaedu80135b92021-02-17 15:05:18 +0100879 elif self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +0000880 vlanID = self._generate_multisegment_vlanID()
881 segment2_dict["provider:segmentation_id"] = vlanID
sousaedu80135b92021-02-17 15:05:18 +0100882
garciadeblasebd66722019-01-31 16:01:31 +0000883 # else
tierno72774862020-05-04 11:44:15 +0000884 # raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100885 # "You must provide "multisegment_vlan_range" at config dict before creating a multisegment
tierno1ec592d2020-06-16 15:29:47 +0000886 # network")
garciadeblasebd66722019-01-31 16:01:31 +0000887 segment_list.append(segment2_dict)
888 network_dict["segments"] = segment_list
kate721d79b2017-06-24 04:21:38 -0700889
tierno6869ae72020-01-09 17:37:34 +0000890 # VIO Specific Changes. It needs a concrete VLAN
891 if self.vim_type == "VIO" and vlan is None:
sousaedu80135b92021-02-17 15:05:18 +0100892 if self.config.get("dataplane_net_vlan_range") is None:
tierno72774862020-05-04 11:44:15 +0000893 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +0100894 "You must provide 'dataplane_net_vlan_range' in format "
895 "[start_ID - end_ID] at VIM_config for creating underlay "
896 "networks"
897 )
898
tierno6869ae72020-01-09 17:37:34 +0000899 network_dict["provider:segmentation_id"] = self._generate_vlanID()
kate721d79b2017-06-24 04:21:38 -0700900
garciadeblasebd66722019-01-31 16:01:31 +0000901 network_dict["shared"] = shared
sousaedu80135b92021-02-17 15:05:18 +0100902
anwarsff168192019-05-06 11:23:07 +0530903 if self.config.get("disable_network_port_security"):
904 network_dict["port_security_enabled"] = False
sousaedu80135b92021-02-17 15:05:18 +0100905
sousaedu2aa5f802021-06-17 15:39:29 +0100906 if self.config.get("neutron_availability_zone_hints"):
907 hints = self.config.get("neutron_availability_zone_hints")
908
909 if isinstance(hints, str):
910 hints = [hints]
911
912 network_dict["availability_zone_hints"] = hints
913
sousaedu80135b92021-02-17 15:05:18 +0100914 new_net = self.neutron.create_network({"network": network_dict})
garciadeblasebd66722019-01-31 16:01:31 +0000915 # print new_net
916 # create subnetwork, even if there is no profile
sousaedu80135b92021-02-17 15:05:18 +0100917
garciadeblas9f8456e2016-09-05 05:02:59 +0200918 if not ip_profile:
919 ip_profile = {}
sousaedu80135b92021-02-17 15:05:18 +0100920
921 if not ip_profile.get("subnet_address"):
tierno1ec592d2020-06-16 15:29:47 +0000922 # Fake subnet is required
elumalai51e72a02023-04-28 19:41:49 +0530923 subnet_rand = random.SystemRandom().randint(0, 255)
sousaedu80135b92021-02-17 15:05:18 +0100924 ip_profile["subnet_address"] = "192.168.{}.0/24".format(subnet_rand)
925
926 if "ip_version" not in ip_profile:
927 ip_profile["ip_version"] = "IPv4"
928
929 subnet = {
930 "name": net_name + "-subnet",
931 "network_id": new_net["network"]["id"],
932 "ip_version": 4 if ip_profile["ip_version"] == "IPv4" else 6,
933 "cidr": ip_profile["subnet_address"],
934 }
935
tiernoa1fb4462017-06-30 12:25:50 +0200936 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
sousaedu80135b92021-02-17 15:05:18 +0100937 if ip_profile.get("gateway_address"):
938 subnet["gateway_ip"] = ip_profile["gateway_address"]
tierno55d234c2018-07-04 18:29:21 +0200939 else:
sousaedu80135b92021-02-17 15:05:18 +0100940 subnet["gateway_ip"] = None
941
942 if ip_profile.get("dns_address"):
943 subnet["dns_nameservers"] = ip_profile["dns_address"].split(";")
944
945 if "dhcp_enabled" in ip_profile:
946 subnet["enable_dhcp"] = (
947 False
948 if ip_profile["dhcp_enabled"] == "false"
949 or ip_profile["dhcp_enabled"] is False
950 else True
951 )
952
953 if ip_profile.get("dhcp_start_address"):
954 subnet["allocation_pools"] = []
955 subnet["allocation_pools"].append(dict())
956 subnet["allocation_pools"][0]["start"] = ip_profile[
957 "dhcp_start_address"
958 ]
959
960 if ip_profile.get("dhcp_count"):
961 # parts = ip_profile["dhcp_start_address"].split(".")
tierno1ec592d2020-06-16 15:29:47 +0000962 # ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
sousaedu80135b92021-02-17 15:05:18 +0100963 ip_int = int(netaddr.IPAddress(ip_profile["dhcp_start_address"]))
964 ip_int += ip_profile["dhcp_count"] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200965 ip_str = str(netaddr.IPAddress(ip_int))
sousaedu80135b92021-02-17 15:05:18 +0100966 subnet["allocation_pools"][0]["end"] = ip_str
967
Gabriel Cubab3dbfca2023-03-14 10:58:39 -0500968 if (
969 ip_profile.get("ipv6_address_mode")
970 and ip_profile["ip_version"] != "IPv4"
971 ):
972 subnet["ipv6_address_mode"] = ip_profile["ipv6_address_mode"]
973 # ipv6_ra_mode can be set to the same value for most use cases, see documentation:
974 # https://docs.openstack.org/neutron/latest/admin/config-ipv6.html#ipv6-ra-mode-and-ipv6-address-mode-combinations
975 subnet["ipv6_ra_mode"] = ip_profile["ipv6_address_mode"]
976
tierno1ec592d2020-06-16 15:29:47 +0000977 # self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
978 self.neutron.create_subnet({"subnet": subnet})
garciadeblasebd66722019-01-31 16:01:31 +0000979
sousaedu80135b92021-02-17 15:05:18 +0100980 if net_type == "data" and self.config.get("multisegment_support"):
981 if self.config.get("l2gw_support"):
garciadeblasebd66722019-01-31 16:01:31 +0000982 l2gw_list = self.neutron.list_l2_gateways().get("l2_gateways", ())
983 for l2gw in l2gw_list:
tierno1ec592d2020-06-16 15:29:47 +0000984 l2gw_conn = {
985 "l2_gateway_id": l2gw["id"],
986 "network_id": new_net["network"]["id"],
987 "segmentation_id": str(vlanID),
988 }
sousaedu80135b92021-02-17 15:05:18 +0100989 new_l2gw_conn = self.neutron.create_l2_gateway_connection(
990 {"l2_gateway_connection": l2gw_conn}
991 )
992 created_items[
993 "l2gwconn:"
994 + str(new_l2gw_conn["l2_gateway_connection"]["id"])
995 ] = True
996
garciadeblasebd66722019-01-31 16:01:31 +0000997 return new_net["network"]["id"], created_items
tierno41a69812018-02-16 14:34:33 +0100998 except Exception as e:
tierno1ec592d2020-06-16 15:29:47 +0000999 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001000 for k, v in created_items.items():
1001 if not v: # skip already deleted
1002 continue
sousaedu80135b92021-02-17 15:05:18 +01001003
garciadeblasebd66722019-01-31 16:01:31 +00001004 try:
1005 k_item, _, k_id = k.partition(":")
sousaedu80135b92021-02-17 15:05:18 +01001006
garciadeblasebd66722019-01-31 16:01:31 +00001007 if k_item == "l2gwconn":
1008 self.neutron.delete_l2_gateway_connection(k_id)
1009 except Exception as e2:
sousaedu80135b92021-02-17 15:05:18 +01001010 self.logger.error(
1011 "Error deleting l2 gateway connection: {}: {}".format(
1012 type(e2).__name__, e2
1013 )
1014 )
1015
garciadeblasedca7b32016-09-29 14:01:52 +00001016 if new_net:
sousaedu80135b92021-02-17 15:05:18 +01001017 self.neutron.delete_network(new_net["network"]["id"])
1018
tiernoae4a8d12016-07-08 12:30:39 +02001019 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001020
1021 def get_network_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001022 """Obtain tenant networks of VIM
tierno7edb6752016-03-21 17:37:52 +01001023 Filter_dict can be:
1024 name: network name
1025 id: network uuid
1026 shared: boolean
1027 tenant_id: tenant
1028 admin_state_up: boolean
1029 status: 'ACTIVE'
1030 Returns the network list of dictionaries
tierno1ec592d2020-06-16 15:29:47 +00001031 """
tiernoae4a8d12016-07-08 12:30:39 +02001032 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
sousaedu80135b92021-02-17 15:05:18 +01001033
tierno7edb6752016-03-21 17:37:52 +01001034 try:
1035 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001036 filter_dict_os = filter_dict.copy()
sousaedu80135b92021-02-17 15:05:18 +01001037
tierno69b590e2018-03-13 18:52:23 +01001038 if self.api_version3 and "tenant_id" in filter_dict_os:
sousaedu80135b92021-02-17 15:05:18 +01001039 # TODO check
1040 filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id")
1041
tierno69b590e2018-03-13 18:52:23 +01001042 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +01001043 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +01001044 self.__net_os2mano(net_list)
sousaedu80135b92021-02-17 15:05:18 +01001045
tiernoae4a8d12016-07-08 12:30:39 +02001046 return net_list
sousaedu80135b92021-02-17 15:05:18 +01001047 except (
1048 neExceptions.ConnectionFailed,
1049 ksExceptions.ClientException,
1050 neExceptions.NeutronException,
1051 ConnectionError,
1052 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001053 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001054
tiernoae4a8d12016-07-08 12:30:39 +02001055 def get_network(self, net_id):
tierno1ec592d2020-06-16 15:29:47 +00001056 """Obtain details of network from VIM
1057 Returns the network information from a network id"""
tiernoae4a8d12016-07-08 12:30:39 +02001058 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno1ec592d2020-06-16 15:29:47 +00001059 filter_dict = {"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +02001060 net_list = self.get_network_list(filter_dict)
sousaedu80135b92021-02-17 15:05:18 +01001061
tierno1ec592d2020-06-16 15:29:47 +00001062 if len(net_list) == 0:
sousaedu80135b92021-02-17 15:05:18 +01001063 raise vimconn.VimConnNotFoundException(
1064 "Network '{}' not found".format(net_id)
1065 )
tierno1ec592d2020-06-16 15:29:47 +00001066 elif len(net_list) > 1:
sousaedu80135b92021-02-17 15:05:18 +01001067 raise vimconn.VimConnConflictException(
1068 "Found more than one network with this criteria"
1069 )
1070
tierno7edb6752016-03-21 17:37:52 +01001071 net = net_list[0]
tierno1ec592d2020-06-16 15:29:47 +00001072 subnets = []
1073 for subnet_id in net.get("subnets", ()):
tierno7edb6752016-03-21 17:37:52 +01001074 try:
1075 subnet = self.neutron.show_subnet(subnet_id)
1076 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001077 self.logger.error(
1078 "osconnector.get_network(): Error getting subnet %s %s"
1079 % (net_id, str(e))
1080 )
tiernoae4a8d12016-07-08 12:30:39 +02001081 subnet = {"id": subnet_id, "fault": str(e)}
sousaedu80135b92021-02-17 15:05:18 +01001082
tierno7edb6752016-03-21 17:37:52 +01001083 subnets.append(subnet)
sousaedu80135b92021-02-17 15:05:18 +01001084
tierno7edb6752016-03-21 17:37:52 +01001085 net["subnets"] = subnets
sousaedu80135b92021-02-17 15:05:18 +01001086 net["encapsulation"] = net.get("provider:network_type")
1087 net["encapsulation_type"] = net.get("provider:network_type")
1088 net["segmentation_id"] = net.get("provider:segmentation_id")
1089 net["encapsulation_id"] = net.get("provider:segmentation_id")
1090
tiernoae4a8d12016-07-08 12:30:39 +02001091 return net
tierno7edb6752016-03-21 17:37:52 +01001092
garciadeblasebd66722019-01-31 16:01:31 +00001093 def delete_network(self, net_id, created_items=None):
1094 """
1095 Removes a tenant network from VIM and its associated elements
1096 :param net_id: VIM identifier of the network, provided by method new_network
1097 :param created_items: dictionary with extra items to be deleted. provided by method new_network
1098 Returns the network identifier or raises an exception upon error or when network is not found
1099 """
tiernoae4a8d12016-07-08 12:30:39 +02001100 self.logger.debug("Deleting network '%s' from VIM", net_id)
sousaedu80135b92021-02-17 15:05:18 +01001101
tierno1ec592d2020-06-16 15:29:47 +00001102 if created_items is None:
garciadeblasebd66722019-01-31 16:01:31 +00001103 created_items = {}
sousaedu80135b92021-02-17 15:05:18 +01001104
tierno7edb6752016-03-21 17:37:52 +01001105 try:
1106 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001107 # delete l2gw connections (if any) before deleting the network
garciadeblasebd66722019-01-31 16:01:31 +00001108 for k, v in created_items.items():
1109 if not v: # skip already deleted
1110 continue
sousaedu80135b92021-02-17 15:05:18 +01001111
garciadeblasebd66722019-01-31 16:01:31 +00001112 try:
1113 k_item, _, k_id = k.partition(":")
1114 if k_item == "l2gwconn":
1115 self.neutron.delete_l2_gateway_connection(k_id)
1116 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001117 self.logger.error(
1118 "Error deleting l2 gateway connection: {}: {}".format(
1119 type(e).__name__, e
1120 )
1121 )
1122
tierno1ec592d2020-06-16 15:29:47 +00001123 # delete VM ports attached to this networks before the network
tierno7edb6752016-03-21 17:37:52 +01001124 ports = self.neutron.list_ports(network_id=net_id)
sousaedu80135b92021-02-17 15:05:18 +01001125 for p in ports["ports"]:
tierno7edb6752016-03-21 17:37:52 +01001126 try:
1127 self.neutron.delete_port(p["id"])
1128 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001129 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
sousaedu80135b92021-02-17 15:05:18 +01001130
tierno7edb6752016-03-21 17:37:52 +01001131 self.neutron.delete_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001132
tiernoae4a8d12016-07-08 12:30:39 +02001133 return net_id
sousaedu80135b92021-02-17 15:05:18 +01001134 except (
1135 neExceptions.ConnectionFailed,
1136 neExceptions.NetworkNotFoundClient,
1137 neExceptions.NeutronException,
1138 ksExceptions.ClientException,
1139 neExceptions.NeutronException,
1140 ConnectionError,
1141 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001142 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001143
tiernoae4a8d12016-07-08 12:30:39 +02001144 def refresh_nets_status(self, net_list):
tierno1ec592d2020-06-16 15:29:47 +00001145 """Get the status of the networks
sousaedu80135b92021-02-17 15:05:18 +01001146 Params: the list of network identifiers
1147 Returns a dictionary with:
1148 net_id: #VIM id of this network
1149 status: #Mandatory. Text with one of:
1150 # DELETED (not found at vim)
1151 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1152 # OTHER (Vim reported other status not understood)
1153 # ERROR (VIM indicates an ERROR status)
1154 # ACTIVE, INACTIVE, DOWN (admin down),
1155 # BUILD (on building process)
1156 #
1157 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1158 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
tierno1ec592d2020-06-16 15:29:47 +00001159 """
1160 net_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001161
tiernoae4a8d12016-07-08 12:30:39 +02001162 for net_id in net_list:
1163 net = {}
sousaedu80135b92021-02-17 15:05:18 +01001164
tiernoae4a8d12016-07-08 12:30:39 +02001165 try:
1166 net_vim = self.get_network(net_id)
sousaedu80135b92021-02-17 15:05:18 +01001167
1168 if net_vim["status"] in netStatus2manoFormat:
1169 net["status"] = netStatus2manoFormat[net_vim["status"]]
tiernoae4a8d12016-07-08 12:30:39 +02001170 else:
1171 net["status"] = "OTHER"
sousaedu80135b92021-02-17 15:05:18 +01001172 net["error_msg"] = "VIM status reported " + net_vim["status"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001173
sousaedu80135b92021-02-17 15:05:18 +01001174 if net["status"] == "ACTIVE" and not net_vim["admin_state_up"]:
1175 net["status"] = "DOWN"
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001176
sousaedu80135b92021-02-17 15:05:18 +01001177 net["vim_info"] = self.serialize(net_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001178
sousaedu80135b92021-02-17 15:05:18 +01001179 if net_vim.get("fault"): # TODO
1180 net["error_msg"] = str(net_vim["fault"])
tierno72774862020-05-04 11:44:15 +00001181 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001182 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001183 net["status"] = "DELETED"
1184 net["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00001185 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02001186 self.logger.error("Exception getting net status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01001187 net["status"] = "VIM_ERROR"
1188 net["error_msg"] = str(e)
tiernoae4a8d12016-07-08 12:30:39 +02001189 net_dict[net_id] = net
1190 return net_dict
1191
1192 def get_flavor(self, flavor_id):
tierno1ec592d2020-06-16 15:29:47 +00001193 """Obtain flavor details from the VIM. Returns the flavor dict details"""
tiernoae4a8d12016-07-08 12:30:39 +02001194 self.logger.debug("Getting flavor '%s'", flavor_id)
sousaedu80135b92021-02-17 15:05:18 +01001195
tierno7edb6752016-03-21 17:37:52 +01001196 try:
1197 self._reload_connection()
1198 flavor = self.nova.flavors.find(id=flavor_id)
tierno1ec592d2020-06-16 15:29:47 +00001199 # TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
sousaedu80135b92021-02-17 15:05:18 +01001200
tiernoae4a8d12016-07-08 12:30:39 +02001201 return flavor.to_dict()
sousaedu80135b92021-02-17 15:05:18 +01001202 except (
1203 nvExceptions.NotFound,
1204 nvExceptions.ClientException,
1205 ksExceptions.ClientException,
1206 ConnectionError,
1207 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001208 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001209
tiernocf157a82017-01-30 14:07:06 +01001210 def get_flavor_id_from_data(self, flavor_dict):
1211 """Obtain flavor id that match the flavor description
sousaedu80135b92021-02-17 15:05:18 +01001212 Returns the flavor_id or raises a vimconnNotFoundException
1213 flavor_dict: contains the required ram, vcpus, disk
1214 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
1215 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
1216 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +01001217 """
sousaedu80135b92021-02-17 15:05:18 +01001218 exact_match = False if self.config.get("use_existing_flavors") else True
1219
tiernocf157a82017-01-30 14:07:06 +01001220 try:
1221 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +02001222 flavor_candidate_id = None
1223 flavor_candidate_data = (10000, 10000, 10000)
sousaedu80135b92021-02-17 15:05:18 +01001224 flavor_target = (
1225 flavor_dict["ram"],
1226 flavor_dict["vcpus"],
1227 flavor_dict["disk"],
sousaedu648ee3d2021-11-22 14:09:15 +00001228 flavor_dict.get("ephemeral", 0),
1229 flavor_dict.get("swap", 0),
sousaedu80135b92021-02-17 15:05:18 +01001230 )
tiernoe26fc7a2017-05-30 14:43:03 +02001231 # numa=None
anwarsae5f52c2019-04-22 10:35:27 +05301232 extended = flavor_dict.get("extended", {})
1233 if extended:
tierno1ec592d2020-06-16 15:29:47 +00001234 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001235 raise vimconn.VimConnNotFoundException(
1236 "Flavor with EPA still not implemented"
1237 )
tiernocf157a82017-01-30 14:07:06 +01001238 # if len(numas) > 1:
tierno72774862020-05-04 11:44:15 +00001239 # raise vimconn.VimConnNotFoundException("Cannot find any flavor with more than one numa")
tiernocf157a82017-01-30 14:07:06 +01001240 # numa=numas[0]
1241 # numas = extended.get("numas")
1242 for flavor in self.nova.flavors.list():
1243 epa = flavor.get_keys()
sousaedu80135b92021-02-17 15:05:18 +01001244
tiernocf157a82017-01-30 14:07:06 +01001245 if epa:
1246 continue
tiernoe26fc7a2017-05-30 14:43:03 +02001247 # TODO
sousaedu80135b92021-02-17 15:05:18 +01001248
sousaedu648ee3d2021-11-22 14:09:15 +00001249 flavor_data = (
1250 flavor.ram,
1251 flavor.vcpus,
1252 flavor.disk,
1253 flavor.ephemeral,
preethika.pebaba1f2022-01-20 07:24:18 +00001254 flavor.swap if isinstance(flavor.swap, int) else 0,
sousaedu648ee3d2021-11-22 14:09:15 +00001255 )
tiernoe26fc7a2017-05-30 14:43:03 +02001256 if flavor_data == flavor_target:
1257 return flavor.id
sousaedu80135b92021-02-17 15:05:18 +01001258 elif (
1259 not exact_match
1260 and flavor_target < flavor_data < flavor_candidate_data
1261 ):
tiernoe26fc7a2017-05-30 14:43:03 +02001262 flavor_candidate_id = flavor.id
1263 flavor_candidate_data = flavor_data
sousaedu80135b92021-02-17 15:05:18 +01001264
tiernoe26fc7a2017-05-30 14:43:03 +02001265 if not exact_match and flavor_candidate_id:
1266 return flavor_candidate_id
sousaedu80135b92021-02-17 15:05:18 +01001267
1268 raise vimconn.VimConnNotFoundException(
1269 "Cannot find any flavor matching '{}'".format(flavor_dict)
1270 )
1271 except (
1272 nvExceptions.NotFound,
1273 nvExceptions.ClientException,
1274 ksExceptions.ClientException,
1275 ConnectionError,
1276 ) as e:
tiernocf157a82017-01-30 14:07:06 +01001277 self._format_exception(e)
1278
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001279 @staticmethod
1280 def process_resource_quota(quota: dict, prefix: str, extra_specs: dict) -> None:
1281 """Process resource quota and fill up extra_specs.
1282 Args:
1283 quota (dict): Keeping the quota of resurces
1284 prefix (str) Prefix
1285 extra_specs (dict) Dict to be filled to be used during flavor creation
1286
anwarsae5f52c2019-04-22 10:35:27 +05301287 """
sousaedu80135b92021-02-17 15:05:18 +01001288 if "limit" in quota:
1289 extra_specs["quota:" + prefix + "_limit"] = quota["limit"]
1290
1291 if "reserve" in quota:
1292 extra_specs["quota:" + prefix + "_reservation"] = quota["reserve"]
1293
1294 if "shares" in quota:
anwarsae5f52c2019-04-22 10:35:27 +05301295 extra_specs["quota:" + prefix + "_shares_level"] = "custom"
sousaedu80135b92021-02-17 15:05:18 +01001296 extra_specs["quota:" + prefix + "_shares_share"] = quota["shares"]
anwarsae5f52c2019-04-22 10:35:27 +05301297
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001298 @staticmethod
1299 def process_numa_memory(
1300 numa: dict, node_id: Optional[int], extra_specs: dict
1301 ) -> None:
1302 """Set the memory in extra_specs.
1303 Args:
1304 numa (dict): A dictionary which includes numa information
1305 node_id (int): ID of numa node
1306 extra_specs (dict): To be filled.
1307
1308 """
1309 if not numa.get("memory"):
1310 return
1311 memory_mb = numa["memory"] * 1024
1312 memory = "hw:numa_mem.{}".format(node_id)
1313 extra_specs[memory] = int(memory_mb)
1314
1315 @staticmethod
1316 def process_numa_vcpu(numa: dict, node_id: int, extra_specs: dict) -> None:
1317 """Set the cpu in extra_specs.
1318 Args:
1319 numa (dict): A dictionary which includes numa information
1320 node_id (int): ID of numa node
1321 extra_specs (dict): To be filled.
1322
1323 """
1324 if not numa.get("vcpu"):
1325 return
1326 vcpu = numa["vcpu"]
1327 cpu = "hw:numa_cpus.{}".format(node_id)
1328 vcpu = ",".join(map(str, vcpu))
1329 extra_specs[cpu] = vcpu
1330
1331 @staticmethod
1332 def process_numa_paired_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1333 """Fill up extra_specs if numa has paired-threads.
1334 Args:
1335 numa (dict): A dictionary which includes numa information
1336 extra_specs (dict): To be filled.
1337
1338 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001339 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001340
1341 """
1342 if not numa.get("paired-threads"):
1343 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001344
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001345 # cpu_thread_policy "require" implies that compute node must have an STM architecture
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001346 threads = numa["paired-threads"] * 2
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001347 extra_specs["hw:cpu_thread_policy"] = "require"
1348 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001349 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001350
1351 @staticmethod
1352 def process_numa_cores(numa: dict, extra_specs: dict) -> Optional[int]:
1353 """Fill up extra_specs if numa has cores.
1354 Args:
1355 numa (dict): A dictionary which includes numa information
1356 extra_specs (dict): To be filled.
1357
1358 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001359 cores (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001360
1361 """
1362 # cpu_thread_policy "isolate" implies that the host must not have an SMT
1363 # architecture, or a non-SMT architecture will be emulated
1364 if not numa.get("cores"):
1365 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001366 cores = numa["cores"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001367 extra_specs["hw:cpu_thread_policy"] = "isolate"
1368 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001369 return cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001370
1371 @staticmethod
1372 def process_numa_threads(numa: dict, extra_specs: dict) -> Optional[int]:
1373 """Fill up extra_specs if numa has threads.
1374 Args:
1375 numa (dict): A dictionary which includes numa information
1376 extra_specs (dict): To be filled.
1377
1378 Returns:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001379 threads (int) Number of virtual cpus
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001380
1381 """
1382 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
1383 if not numa.get("threads"):
1384 return
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001385 threads = numa["threads"]
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001386 extra_specs["hw:cpu_thread_policy"] = "prefer"
1387 extra_specs["hw:cpu_policy"] = "dedicated"
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001388 return threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001389
1390 def _process_numa_parameters_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001391 self, numas: List, extra_specs: Dict
1392 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001393 """Process numa parameters and fill up extra_specs.
1394
1395 Args:
1396 numas (list): List of dictionary which includes numa information
1397 extra_specs (dict): To be filled.
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001398
1399 """
1400 numa_nodes = len(numas)
1401 extra_specs["hw:numa_nodes"] = str(numa_nodes)
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001402 cpu_cores, cpu_threads = 0, 0
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001403
1404 if self.vim_type == "VIO":
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001405 self.process_vio_numa_nodes(numa_nodes, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001406
1407 for numa in numas:
1408 if "id" in numa:
1409 node_id = numa["id"]
1410 # overwrite ram and vcpus
1411 # check if key "memory" is present in numa else use ram value at flavor
1412 self.process_numa_memory(numa, node_id, extra_specs)
1413 self.process_numa_vcpu(numa, node_id, extra_specs)
1414
1415 # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
1416 extra_specs["hw:cpu_sockets"] = str(numa_nodes)
1417
1418 if "paired-threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001419 threads = self.process_numa_paired_threads(numa, extra_specs)
1420 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001421
1422 elif "cores" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001423 cores = self.process_numa_cores(numa, extra_specs)
1424 cpu_cores += cores
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001425
1426 elif "threads" in numa:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001427 threads = self.process_numa_threads(numa, extra_specs)
1428 cpu_threads += threads
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001429
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001430 if cpu_cores:
1431 extra_specs["hw:cpu_cores"] = str(cpu_cores)
1432 if cpu_threads:
1433 extra_specs["hw:cpu_threads"] = str(cpu_threads)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001434
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001435 @staticmethod
1436 def process_vio_numa_nodes(numa_nodes: int, extra_specs: Dict) -> None:
1437 """According to number of numa nodes, updates the extra_specs for VIO.
1438
1439 Args:
1440
1441 numa_nodes (int): List keeps the numa node numbers
1442 extra_specs (dict): Extra specs dict to be updated
1443
1444 """
Gulsum Aticid0571fe2022-11-14 13:06:06 +03001445 # If there are several numas, we do not define specific affinity.
1446 extra_specs["vmware:latency_sensitivity_level"] = "high"
1447
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001448 def _change_flavor_name(
1449 self, name: str, name_suffix: int, flavor_data: dict
1450 ) -> str:
1451 """Change the flavor name if the name already exists.
1452
1453 Args:
1454 name (str): Flavor name to be checked
1455 name_suffix (int): Suffix to be appended to name
1456 flavor_data (dict): Flavor dict
1457
1458 Returns:
1459 name (str): New flavor name to be used
1460
1461 """
1462 # Get used names
1463 fl = self.nova.flavors.list()
1464 fl_names = [f.name for f in fl]
1465
1466 while name in fl_names:
1467 name_suffix += 1
1468 name = flavor_data["name"] + "-" + str(name_suffix)
1469
1470 return name
1471
1472 def _process_extended_config_of_flavor(
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001473 self, extended: dict, extra_specs: dict
1474 ) -> None:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001475 """Process the extended dict to fill up extra_specs.
1476 Args:
1477
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001478 extended (dict): Keeping the extra specification of flavor
1479 extra_specs (dict) Dict to be filled to be used during flavor creation
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001480
1481 """
1482 quotas = {
1483 "cpu-quota": "cpu",
1484 "mem-quota": "memory",
1485 "vif-quota": "vif",
1486 "disk-io-quota": "disk_io",
1487 }
1488
1489 page_sizes = {
1490 "LARGE": "large",
1491 "SMALL": "small",
1492 "SIZE_2MB": "2MB",
1493 "SIZE_1GB": "1GB",
1494 "PREFER_LARGE": "any",
1495 }
1496
1497 policies = {
1498 "cpu-pinning-policy": "hw:cpu_policy",
1499 "cpu-thread-pinning-policy": "hw:cpu_thread_policy",
1500 "mem-policy": "hw:numa_mempolicy",
1501 }
1502
1503 numas = extended.get("numas")
1504 if numas:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001505 self._process_numa_parameters_of_flavor(numas, extra_specs)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001506
1507 for quota, item in quotas.items():
1508 if quota in extended.keys():
1509 self.process_resource_quota(extended.get(quota), item, extra_specs)
1510
1511 # Set the mempage size as specified in the descriptor
1512 if extended.get("mempage-size"):
1513 if extended["mempage-size"] in page_sizes.keys():
1514 extra_specs["hw:mem_page_size"] = page_sizes[extended["mempage-size"]]
1515 else:
1516 # Normally, validations in NBI should not allow to this condition.
1517 self.logger.debug(
1518 "Invalid mempage-size %s. Will be ignored",
1519 extended.get("mempage-size"),
1520 )
1521
1522 for policy, hw_policy in policies.items():
1523 if extended.get(policy):
1524 extra_specs[hw_policy] = extended[policy].lower()
1525
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001526 @staticmethod
1527 def _get_flavor_details(flavor_data: dict) -> Tuple:
1528 """Returns the details of flavor
1529 Args:
1530 flavor_data (dict): Dictionary that includes required flavor details
1531
1532 Returns:
1533 ram, vcpus, extra_specs, extended (tuple): Main items of required flavor
1534
1535 """
1536 return (
1537 flavor_data.get("ram", 64),
1538 flavor_data.get("vcpus", 1),
1539 {},
1540 flavor_data.get("extended"),
1541 )
1542
1543 def new_flavor(self, flavor_data: dict, change_name_if_used: bool = True) -> str:
1544 """Adds a tenant flavor to openstack VIM.
1545 if change_name_if_used is True, it will change name in case of conflict,
1546 because it is not supported name repetition.
1547
1548 Args:
1549 flavor_data (dict): Flavor details to be processed
1550 change_name_if_used (bool): Change name in case of conflict
1551
1552 Returns:
1553 flavor_id (str): flavor identifier
1554
tierno1ec592d2020-06-16 15:29:47 +00001555 """
tiernoae4a8d12016-07-08 12:30:39 +02001556 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno1ec592d2020-06-16 15:29:47 +00001557 retry = 0
1558 max_retries = 3
tierno7edb6752016-03-21 17:37:52 +01001559 name_suffix = 0
sousaedu80135b92021-02-17 15:05:18 +01001560
anwarsc76a3ee2018-10-04 14:05:32 +05301561 try:
sousaedu80135b92021-02-17 15:05:18 +01001562 name = flavor_data["name"]
tierno1ec592d2020-06-16 15:29:47 +00001563 while retry < max_retries:
1564 retry += 1
anwarsc76a3ee2018-10-04 14:05:32 +05301565 try:
1566 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001567
anwarsc76a3ee2018-10-04 14:05:32 +05301568 if change_name_if_used:
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001569 name = self._change_flavor_name(name, name_suffix, flavor_data)
sousaedu80135b92021-02-17 15:05:18 +01001570
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001571 ram, vcpus, extra_specs, extended = self._get_flavor_details(
1572 flavor_data
1573 )
anwarsc76a3ee2018-10-04 14:05:32 +05301574 if extended:
Gulsum Atici6a6e3342023-01-23 16:22:59 +03001575 self._process_extended_config_of_flavor(extended, extra_specs)
sousaedu80135b92021-02-17 15:05:18 +01001576
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001577 # Create flavor
sousaedu80135b92021-02-17 15:05:18 +01001578
sousaedu80135b92021-02-17 15:05:18 +01001579 new_flavor = self.nova.flavors.create(
sousaeduf524da82021-11-22 14:02:17 +00001580 name=name,
1581 ram=ram,
1582 vcpus=vcpus,
1583 disk=flavor_data.get("disk", 0),
1584 ephemeral=flavor_data.get("ephemeral", 0),
sousaedu648ee3d2021-11-22 14:09:15 +00001585 swap=flavor_data.get("swap", 0),
sousaedu80135b92021-02-17 15:05:18 +01001586 is_public=flavor_data.get("is_public", True),
1587 )
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001588
1589 # Add metadata
anwarsae5f52c2019-04-22 10:35:27 +05301590 if extra_specs:
1591 new_flavor.set_keys(extra_specs)
sousaedu80135b92021-02-17 15:05:18 +01001592
anwarsc76a3ee2018-10-04 14:05:32 +05301593 return new_flavor.id
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001594
anwarsc76a3ee2018-10-04 14:05:32 +05301595 except nvExceptions.Conflict as e:
1596 if change_name_if_used and retry < max_retries:
1597 continue
sousaedu80135b92021-02-17 15:05:18 +01001598
anwarsc76a3ee2018-10-04 14:05:32 +05301599 self._format_exception(e)
Gulsum Atici4415c4c2023-01-19 12:44:06 +03001600
sousaedu80135b92021-02-17 15:05:18 +01001601 except (
1602 ksExceptions.ClientException,
1603 nvExceptions.ClientException,
1604 ConnectionError,
1605 KeyError,
1606 ) as e:
anwarsc76a3ee2018-10-04 14:05:32 +05301607 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001608
tierno1ec592d2020-06-16 15:29:47 +00001609 def delete_flavor(self, flavor_id):
sousaedu80135b92021-02-17 15:05:18 +01001610 """Deletes a tenant flavor from openstack VIM. Returns the old flavor_id"""
tiernoae4a8d12016-07-08 12:30:39 +02001611 try:
1612 self._reload_connection()
1613 self.nova.flavors.delete(flavor_id)
sousaedu80135b92021-02-17 15:05:18 +01001614
tiernoae4a8d12016-07-08 12:30:39 +02001615 return flavor_id
tierno1ec592d2020-06-16 15:29:47 +00001616 # except nvExceptions.BadRequest as e:
sousaedu80135b92021-02-17 15:05:18 +01001617 except (
1618 nvExceptions.NotFound,
1619 ksExceptions.ClientException,
1620 nvExceptions.ClientException,
1621 ConnectionError,
1622 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001623 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001624
tierno1ec592d2020-06-16 15:29:47 +00001625 def new_image(self, image_dict):
1626 """
tiernoae4a8d12016-07-08 12:30:39 +02001627 Adds a tenant image to VIM. imge_dict is a dictionary with:
1628 name: name
1629 disk_format: qcow2, vhd, vmdk, raw (by default), ...
1630 location: path or URI
1631 public: "yes" or "no"
1632 metadata: metadata of the image
1633 Returns the image_id
tierno1ec592d2020-06-16 15:29:47 +00001634 """
1635 retry = 0
1636 max_retries = 3
sousaedu80135b92021-02-17 15:05:18 +01001637
tierno1ec592d2020-06-16 15:29:47 +00001638 while retry < max_retries:
1639 retry += 1
tierno7edb6752016-03-21 17:37:52 +01001640 try:
1641 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001642
tierno1ec592d2020-06-16 15:29:47 +00001643 # determine format http://docs.openstack.org/developer/glance/formats.html
tierno7edb6752016-03-21 17:37:52 +01001644 if "disk_format" in image_dict:
tierno1ec592d2020-06-16 15:29:47 +00001645 disk_format = image_dict["disk_format"]
1646 else: # autodiscover based on extension
sousaedu80135b92021-02-17 15:05:18 +01001647 if image_dict["location"].endswith(".qcow2"):
tierno1ec592d2020-06-16 15:29:47 +00001648 disk_format = "qcow2"
sousaedu80135b92021-02-17 15:05:18 +01001649 elif image_dict["location"].endswith(".vhd"):
tierno1ec592d2020-06-16 15:29:47 +00001650 disk_format = "vhd"
sousaedu80135b92021-02-17 15:05:18 +01001651 elif image_dict["location"].endswith(".vmdk"):
tierno1ec592d2020-06-16 15:29:47 +00001652 disk_format = "vmdk"
sousaedu80135b92021-02-17 15:05:18 +01001653 elif image_dict["location"].endswith(".vdi"):
tierno1ec592d2020-06-16 15:29:47 +00001654 disk_format = "vdi"
sousaedu80135b92021-02-17 15:05:18 +01001655 elif image_dict["location"].endswith(".iso"):
tierno1ec592d2020-06-16 15:29:47 +00001656 disk_format = "iso"
sousaedu80135b92021-02-17 15:05:18 +01001657 elif image_dict["location"].endswith(".aki"):
tierno1ec592d2020-06-16 15:29:47 +00001658 disk_format = "aki"
sousaedu80135b92021-02-17 15:05:18 +01001659 elif image_dict["location"].endswith(".ari"):
tierno1ec592d2020-06-16 15:29:47 +00001660 disk_format = "ari"
sousaedu80135b92021-02-17 15:05:18 +01001661 elif image_dict["location"].endswith(".ami"):
tierno1ec592d2020-06-16 15:29:47 +00001662 disk_format = "ami"
tierno7edb6752016-03-21 17:37:52 +01001663 else:
tierno1ec592d2020-06-16 15:29:47 +00001664 disk_format = "raw"
sousaedu80135b92021-02-17 15:05:18 +01001665
1666 self.logger.debug(
1667 "new_image: '%s' loading from '%s'",
1668 image_dict["name"],
1669 image_dict["location"],
1670 )
shashankjain3c83a212018-10-04 13:05:46 +05301671 if self.vim_type == "VIO":
1672 container_format = "bare"
sousaedu80135b92021-02-17 15:05:18 +01001673 if "container_format" in image_dict:
1674 container_format = image_dict["container_format"]
1675
1676 new_image = self.glance.images.create(
1677 name=image_dict["name"],
1678 container_format=container_format,
1679 disk_format=disk_format,
1680 )
shashankjain3c83a212018-10-04 13:05:46 +05301681 else:
sousaedu80135b92021-02-17 15:05:18 +01001682 new_image = self.glance.images.create(name=image_dict["name"])
1683
1684 if image_dict["location"].startswith("http"):
tierno1beea862018-07-11 15:47:37 +02001685 # TODO there is not a method to direct download. It must be downloaded locally with requests
tierno72774862020-05-04 11:44:15 +00001686 raise vimconn.VimConnNotImplemented("Cannot create image from URL")
tierno1ec592d2020-06-16 15:29:47 +00001687 else: # local path
sousaedu80135b92021-02-17 15:05:18 +01001688 with open(image_dict["location"]) as fimage:
tierno1beea862018-07-11 15:47:37 +02001689 self.glance.images.upload(new_image.id, fimage)
sousaedu80135b92021-02-17 15:05:18 +01001690 # new_image = self.glancev1.images.create(name=image_dict["name"], is_public=
1691 # image_dict.get("public","yes")=="yes",
tierno1beea862018-07-11 15:47:37 +02001692 # container_format="bare", data=fimage, disk_format=disk_format)
sousaedu80135b92021-02-17 15:05:18 +01001693
1694 metadata_to_load = image_dict.get("metadata")
1695
1696 # TODO location is a reserved word for current openstack versions. fixed for VIO please check
tierno1ec592d2020-06-16 15:29:47 +00001697 # for openstack
shashankjain3c83a212018-10-04 13:05:46 +05301698 if self.vim_type == "VIO":
sousaedu80135b92021-02-17 15:05:18 +01001699 metadata_to_load["upload_location"] = image_dict["location"]
shashankjain3c83a212018-10-04 13:05:46 +05301700 else:
sousaedu80135b92021-02-17 15:05:18 +01001701 metadata_to_load["location"] = image_dict["location"]
1702
tierno1beea862018-07-11 15:47:37 +02001703 self.glance.images.update(new_image.id, **metadata_to_load)
sousaedu80135b92021-02-17 15:05:18 +01001704
tiernoae4a8d12016-07-08 12:30:39 +02001705 return new_image.id
sousaedu80135b92021-02-17 15:05:18 +01001706 except (
1707 nvExceptions.Conflict,
1708 ksExceptions.ClientException,
1709 nvExceptions.ClientException,
1710 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001711 self._format_exception(e)
sousaedu80135b92021-02-17 15:05:18 +01001712 except (
1713 HTTPException,
1714 gl1Exceptions.HTTPException,
1715 gl1Exceptions.CommunicationError,
1716 ConnectionError,
1717 ) as e:
tierno1ec592d2020-06-16 15:29:47 +00001718 if retry == max_retries:
tiernoae4a8d12016-07-08 12:30:39 +02001719 continue
sousaedu80135b92021-02-17 15:05:18 +01001720
tiernoae4a8d12016-07-08 12:30:39 +02001721 self._format_exception(e)
tierno1ec592d2020-06-16 15:29:47 +00001722 except IOError as e: # can not open the file
sousaedu80135b92021-02-17 15:05:18 +01001723 raise vimconn.VimConnConnectionException(
1724 "{}: {} for {}".format(type(e).__name__, e, image_dict["location"]),
1725 http_code=vimconn.HTTP_Bad_Request,
1726 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001727
tiernoae4a8d12016-07-08 12:30:39 +02001728 def delete_image(self, image_id):
sousaedu80135b92021-02-17 15:05:18 +01001729 """Deletes a tenant image from openstack VIM. Returns the old id"""
tiernoae4a8d12016-07-08 12:30:39 +02001730 try:
1731 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001732 self.glance.images.delete(image_id)
sousaedu80135b92021-02-17 15:05:18 +01001733
tiernoae4a8d12016-07-08 12:30:39 +02001734 return image_id
sousaedu80135b92021-02-17 15:05:18 +01001735 except (
1736 nvExceptions.NotFound,
1737 ksExceptions.ClientException,
1738 nvExceptions.ClientException,
1739 gl1Exceptions.CommunicationError,
1740 gl1Exceptions.HTTPNotFound,
1741 ConnectionError,
1742 ) as e: # TODO remove
tiernoae4a8d12016-07-08 12:30:39 +02001743 self._format_exception(e)
1744
1745 def get_image_id_from_path(self, path):
tierno1ec592d2020-06-16 15:29:47 +00001746 """Get the image id from image path in the VIM database. Returns the image_id"""
tiernoae4a8d12016-07-08 12:30:39 +02001747 try:
1748 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001749 images = self.glance.images.list()
sousaedu80135b92021-02-17 15:05:18 +01001750
tiernoae4a8d12016-07-08 12:30:39 +02001751 for image in images:
tierno1ec592d2020-06-16 15:29:47 +00001752 if image.metadata.get("location") == path:
tiernoae4a8d12016-07-08 12:30:39 +02001753 return image.id
sousaedu80135b92021-02-17 15:05:18 +01001754
1755 raise vimconn.VimConnNotFoundException(
1756 "image with location '{}' not found".format(path)
1757 )
1758 except (
1759 ksExceptions.ClientException,
1760 nvExceptions.ClientException,
1761 gl1Exceptions.CommunicationError,
1762 ConnectionError,
1763 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001764 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001765
garciadeblasb69fa9f2016-09-28 12:04:10 +02001766 def get_image_list(self, filter_dict={}):
tierno1ec592d2020-06-16 15:29:47 +00001767 """Obtain tenant images from VIM
garciadeblasb69fa9f2016-09-28 12:04:10 +02001768 Filter_dict can be:
1769 id: image id
1770 name: image name
1771 checksum: image checksum
1772 Returns the image list of dictionaries:
1773 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1774 List can be empty
tierno1ec592d2020-06-16 15:29:47 +00001775 """
garciadeblasb69fa9f2016-09-28 12:04:10 +02001776 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
sousaedu80135b92021-02-17 15:05:18 +01001777
garciadeblasb69fa9f2016-09-28 12:04:10 +02001778 try:
1779 self._reload_connection()
tierno1ec592d2020-06-16 15:29:47 +00001780 # filter_dict_os = filter_dict.copy()
1781 # First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +02001782 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +02001783 filtered_list = []
sousaedu80135b92021-02-17 15:05:18 +01001784
garciadeblasb69fa9f2016-09-28 12:04:10 +02001785 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +02001786 try:
tierno1beea862018-07-11 15:47:37 +02001787 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
1788 continue
sousaedu80135b92021-02-17 15:05:18 +01001789
tierno1beea862018-07-11 15:47:37 +02001790 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
1791 continue
sousaedu80135b92021-02-17 15:05:18 +01001792
1793 if (
1794 filter_dict.get("checksum")
1795 and image["checksum"] != filter_dict["checksum"]
1796 ):
tierno1beea862018-07-11 15:47:37 +02001797 continue
1798
1799 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +02001800 except gl1Exceptions.HTTPNotFound:
1801 pass
sousaedu80135b92021-02-17 15:05:18 +01001802
garciadeblasb69fa9f2016-09-28 12:04:10 +02001803 return filtered_list
sousaedu80135b92021-02-17 15:05:18 +01001804 except (
1805 ksExceptions.ClientException,
1806 nvExceptions.ClientException,
1807 gl1Exceptions.CommunicationError,
1808 ConnectionError,
1809 ) as e:
garciadeblasb69fa9f2016-09-28 12:04:10 +02001810 self._format_exception(e)
1811
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001812 def __wait_for_vm(self, vm_id, status):
1813 """wait until vm is in the desired status and return True.
1814 If the VM gets in ERROR status, return false.
1815 If the timeout is reached generate an exception"""
1816 elapsed_time = 0
1817 while elapsed_time < server_timeout:
1818 vm_status = self.nova.servers.get(vm_id).status
sousaedu80135b92021-02-17 15:05:18 +01001819
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001820 if vm_status == status:
1821 return True
sousaedu80135b92021-02-17 15:05:18 +01001822
1823 if vm_status == "ERROR":
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001824 return False
sousaedu80135b92021-02-17 15:05:18 +01001825
tierno1df468d2018-07-06 14:25:16 +02001826 time.sleep(5)
1827 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001828
1829 # if we exceeded the timeout rollback
1830 if elapsed_time >= server_timeout:
sousaedu80135b92021-02-17 15:05:18 +01001831 raise vimconn.VimConnException(
1832 "Timeout waiting for instance " + vm_id + " to get " + status,
1833 http_code=vimconn.HTTP_Request_Timeout,
1834 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001835
mirabal29356312017-07-27 12:21:22 +02001836 def _get_openstack_availablity_zones(self):
1837 """
1838 Get from openstack availability zones available
1839 :return:
1840 """
1841 try:
1842 openstack_availability_zone = self.nova.availability_zones.list()
sousaedu80135b92021-02-17 15:05:18 +01001843 openstack_availability_zone = [
1844 str(zone.zoneName)
1845 for zone in openstack_availability_zone
1846 if zone.zoneName != "internal"
1847 ]
1848
mirabal29356312017-07-27 12:21:22 +02001849 return openstack_availability_zone
tierno1ec592d2020-06-16 15:29:47 +00001850 except Exception:
mirabal29356312017-07-27 12:21:22 +02001851 return None
1852
1853 def _set_availablity_zones(self):
1854 """
1855 Set vim availablity zone
1856 :return:
1857 """
sousaedu80135b92021-02-17 15:05:18 +01001858 if "availability_zone" in self.config:
1859 vim_availability_zones = self.config.get("availability_zone")
mirabal29356312017-07-27 12:21:22 +02001860
mirabal29356312017-07-27 12:21:22 +02001861 if isinstance(vim_availability_zones, str):
1862 self.availability_zone = [vim_availability_zones]
1863 elif isinstance(vim_availability_zones, list):
1864 self.availability_zone = vim_availability_zones
1865 else:
1866 self.availability_zone = self._get_openstack_availablity_zones()
1867
sousaedu80135b92021-02-17 15:05:18 +01001868 def _get_vm_availability_zone(
1869 self, availability_zone_index, availability_zone_list
1870 ):
mirabal29356312017-07-27 12:21:22 +02001871 """
tierno5a3273c2017-08-29 11:43:46 +02001872 Return thge availability zone to be used by the created VM.
1873 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +02001874 """
tierno5a3273c2017-08-29 11:43:46 +02001875 if availability_zone_index is None:
sousaedu80135b92021-02-17 15:05:18 +01001876 if not self.config.get("availability_zone"):
tierno5a3273c2017-08-29 11:43:46 +02001877 return None
sousaedu80135b92021-02-17 15:05:18 +01001878 elif isinstance(self.config.get("availability_zone"), str):
1879 return self.config["availability_zone"]
tierno5a3273c2017-08-29 11:43:46 +02001880 else:
1881 # TODO consider using a different parameter at config for default AV and AV list match
sousaedu80135b92021-02-17 15:05:18 +01001882 return self.config["availability_zone"][0]
mirabal29356312017-07-27 12:21:22 +02001883
tierno5a3273c2017-08-29 11:43:46 +02001884 vim_availability_zones = self.availability_zone
1885 # check if VIM offer enough availability zones describe in the VNFD
sousaedu80135b92021-02-17 15:05:18 +01001886 if vim_availability_zones and len(availability_zone_list) <= len(
1887 vim_availability_zones
1888 ):
tierno5a3273c2017-08-29 11:43:46 +02001889 # check if all the names of NFV AV match VIM AV names
1890 match_by_index = False
1891 for av in availability_zone_list:
1892 if av not in vim_availability_zones:
1893 match_by_index = True
1894 break
sousaedu80135b92021-02-17 15:05:18 +01001895
tierno5a3273c2017-08-29 11:43:46 +02001896 if match_by_index:
1897 return vim_availability_zones[availability_zone_index]
1898 else:
1899 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +02001900 else:
sousaedu80135b92021-02-17 15:05:18 +01001901 raise vimconn.VimConnConflictException(
1902 "No enough availability zones at VIM for this deployment"
1903 )
mirabal29356312017-07-27 12:21:22 +02001904
Gulsum Atici26f73662022-10-27 15:18:27 +03001905 def _prepare_port_dict_security_groups(self, net: dict, port_dict: dict) -> None:
1906 """Fill up the security_groups in the port_dict.
1907
1908 Args:
1909 net (dict): Network details
1910 port_dict (dict): Port details
1911
1912 """
1913 if (
1914 self.config.get("security_groups")
1915 and net.get("port_security") is not False
1916 and not self.config.get("no_port_security_extension")
1917 ):
1918 if not self.security_groups_id:
1919 self._get_ids_from_name()
1920
1921 port_dict["security_groups"] = self.security_groups_id
1922
1923 def _prepare_port_dict_binding(self, net: dict, port_dict: dict) -> None:
1924 """Fill up the network binding depending on network type in the port_dict.
1925
1926 Args:
1927 net (dict): Network details
1928 port_dict (dict): Port details
1929
1930 """
1931 if not net.get("type"):
1932 raise vimconn.VimConnException("Type is missing in the network details.")
1933
1934 if net["type"] == "virtual":
1935 pass
1936
1937 # For VF
1938 elif net["type"] == "VF" or net["type"] == "SR-IOV":
Gulsum Atici26f73662022-10-27 15:18:27 +03001939 port_dict["binding:vnic_type"] = "direct"
1940
1941 # VIO specific Changes
1942 if self.vim_type == "VIO":
1943 # Need to create port with port_security_enabled = False and no-security-groups
1944 port_dict["port_security_enabled"] = False
1945 port_dict["provider_security_groups"] = []
1946 port_dict["security_groups"] = []
1947
1948 else:
1949 # For PT PCI-PASSTHROUGH
1950 port_dict["binding:vnic_type"] = "direct-physical"
1951
1952 @staticmethod
1953 def _set_fixed_ip(new_port: dict, net: dict) -> None:
1954 """Set the "ip" parameter in net dictionary.
1955
1956 Args:
1957 new_port (dict): New created port
1958 net (dict): Network details
1959
1960 """
1961 fixed_ips = new_port["port"].get("fixed_ips")
1962
1963 if fixed_ips:
1964 net["ip"] = fixed_ips[0].get("ip_address")
1965 else:
1966 net["ip"] = None
1967
1968 @staticmethod
1969 def _prepare_port_dict_mac_ip_addr(net: dict, port_dict: dict) -> None:
1970 """Fill up the mac_address and fixed_ips in port_dict.
1971
1972 Args:
1973 net (dict): Network details
1974 port_dict (dict): Port details
1975
1976 """
1977 if net.get("mac_address"):
1978 port_dict["mac_address"] = net["mac_address"]
1979
elumalai370e36b2023-04-25 16:22:56 +05301980 ip_dual_list = []
1981 if ip_list := net.get("ip_address"):
1982 if not isinstance(ip_list, list):
1983 ip_list = [ip_list]
1984 for ip in ip_list:
1985 ip_dict = {"ip_address": ip}
1986 ip_dual_list.append(ip_dict)
1987 port_dict["fixed_ips"] = ip_dual_list
Gulsum Atici26f73662022-10-27 15:18:27 +03001988 # TODO add "subnet_id": <subnet_id>
1989
1990 def _create_new_port(self, port_dict: dict, created_items: dict, net: dict) -> Dict:
1991 """Create new port using neutron.
1992
1993 Args:
1994 port_dict (dict): Port details
1995 created_items (dict): All created items
1996 net (dict): Network details
1997
1998 Returns:
1999 new_port (dict): New created port
2000
2001 """
2002 new_port = self.neutron.create_port({"port": port_dict})
2003 created_items["port:" + str(new_port["port"]["id"])] = True
elumalaie17cd942023-04-28 18:04:24 +05302004 net["mac_address"] = new_port["port"]["mac_address"]
Gulsum Atici26f73662022-10-27 15:18:27 +03002005 net["vim_id"] = new_port["port"]["id"]
2006
2007 return new_port
2008
2009 def _create_port(
2010 self, net: dict, name: str, created_items: dict
2011 ) -> Tuple[dict, dict]:
2012 """Create port using net details.
2013
2014 Args:
2015 net (dict): Network details
2016 name (str): Name to be used as network name if net dict does not include name
2017 created_items (dict): All created items
2018
2019 Returns:
2020 new_port, port New created port, port dictionary
2021
2022 """
2023
2024 port_dict = {
2025 "network_id": net["net_id"],
2026 "name": net.get("name"),
2027 "admin_state_up": True,
2028 }
2029
2030 if not port_dict["name"]:
2031 port_dict["name"] = name
2032
2033 self._prepare_port_dict_security_groups(net, port_dict)
2034
2035 self._prepare_port_dict_binding(net, port_dict)
2036
2037 vimconnector._prepare_port_dict_mac_ip_addr(net, port_dict)
2038
2039 new_port = self._create_new_port(port_dict, created_items, net)
2040
2041 vimconnector._set_fixed_ip(new_port, net)
2042
2043 port = {"port-id": new_port["port"]["id"]}
2044
2045 if float(self.nova.api_version.get_string()) >= 2.32:
2046 port["tag"] = new_port["port"]["name"]
2047
2048 return new_port, port
2049
2050 def _prepare_network_for_vminstance(
2051 self,
2052 name: str,
2053 net_list: list,
2054 created_items: dict,
2055 net_list_vim: list,
2056 external_network: list,
2057 no_secured_ports: list,
2058 ) -> None:
2059 """Create port and fill up net dictionary for new VM instance creation.
2060
2061 Args:
2062 name (str): Name of network
2063 net_list (list): List of networks
2064 created_items (dict): All created items belongs to a VM
2065 net_list_vim (list): List of ports
2066 external_network (list): List of external-networks
2067 no_secured_ports (list): Port security disabled ports
2068 """
2069
2070 self._reload_connection()
2071
2072 for net in net_list:
2073 # Skip non-connected iface
2074 if not net.get("net_id"):
2075 continue
2076
2077 new_port, port = self._create_port(net, name, created_items)
2078
2079 net_list_vim.append(port)
2080
2081 if net.get("floating_ip", False):
2082 net["exit_on_floating_ip_error"] = True
2083 external_network.append(net)
2084
2085 elif net["use"] == "mgmt" and self.config.get("use_floating_ip"):
2086 net["exit_on_floating_ip_error"] = False
2087 external_network.append(net)
2088 net["floating_ip"] = self.config.get("use_floating_ip")
2089
2090 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic
2091 # is dropped. As a workaround we wait until the VM is active and then disable the port-security
2092 if net.get("port_security") is False and not self.config.get(
2093 "no_port_security_extension"
2094 ):
2095 no_secured_ports.append(
2096 (
2097 new_port["port"]["id"],
2098 net.get("port_security_disable_strategy"),
2099 )
2100 )
2101
2102 def _prepare_persistent_root_volumes(
2103 self,
2104 name: str,
2105 vm_av_zone: list,
2106 disk: dict,
2107 base_disk_index: int,
2108 block_device_mapping: dict,
2109 existing_vim_volumes: list,
2110 created_items: dict,
2111 ) -> Optional[str]:
2112 """Prepare persistent root volumes for new VM instance.
2113
2114 Args:
2115 name (str): Name of VM instance
2116 vm_av_zone (list): List of availability zones
2117 disk (dict): Disk details
2118 base_disk_index (int): Disk index
2119 block_device_mapping (dict): Block device details
2120 existing_vim_volumes (list): Existing disk details
2121 created_items (dict): All created items belongs to VM
2122
2123 Returns:
2124 boot_volume_id (str): ID of boot volume
2125
2126 """
2127 # Disk may include only vim_volume_id or only vim_id."
2128 # Use existing persistent root volume finding with volume_id or vim_id
2129 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
2130
2131 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002132 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2133 existing_vim_volumes.append({"id": disk[key_id]})
2134
2135 else:
2136 # Create persistent root volume
2137 volume = self.cinder.volumes.create(
2138 size=disk["size"],
2139 name=name + "vd" + chr(base_disk_index),
2140 imageRef=disk["image_id"],
2141 # Make sure volume is in the same AZ as the VM to be attached to
2142 availability_zone=vm_av_zone,
2143 )
2144 boot_volume_id = volume.id
aticig2f4ab6c2022-09-03 18:15:20 +03002145 self.update_block_device_mapping(
2146 volume=volume,
2147 block_device_mapping=block_device_mapping,
2148 base_disk_index=base_disk_index,
2149 disk=disk,
2150 created_items=created_items,
2151 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002152
2153 return boot_volume_id
2154
aticig2f4ab6c2022-09-03 18:15:20 +03002155 @staticmethod
2156 def update_block_device_mapping(
2157 volume: object,
2158 block_device_mapping: dict,
2159 base_disk_index: int,
2160 disk: dict,
2161 created_items: dict,
2162 ) -> None:
2163 """Add volume information to block device mapping dict.
2164 Args:
2165 volume (object): Created volume object
2166 block_device_mapping (dict): Block device details
2167 base_disk_index (int): Disk index
2168 disk (dict): Disk details
2169 created_items (dict): All created items belongs to VM
2170 """
2171 if not volume:
2172 raise vimconn.VimConnException("Volume is empty.")
2173
2174 if not hasattr(volume, "id"):
2175 raise vimconn.VimConnException(
2176 "Created volume is not valid, does not have id attribute."
2177 )
2178
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002179 block_device_mapping["vd" + chr(base_disk_index)] = volume.id
2180 if disk.get("multiattach"): # multiattach volumes do not belong to VDUs
2181 return
aticig2f4ab6c2022-09-03 18:15:20 +03002182 volume_txt = "volume:" + str(volume.id)
2183 if disk.get("keep"):
2184 volume_txt += ":keep"
2185 created_items[volume_txt] = True
aticig2f4ab6c2022-09-03 18:15:20 +03002186
vegall364627c2023-03-17 15:09:50 +00002187 def new_shared_volumes(self, shared_volume_data) -> (str, str):
2188 try:
2189 volume = self.cinder.volumes.create(
2190 size=shared_volume_data["size"],
2191 name=shared_volume_data["name"],
2192 volume_type="multiattach",
2193 )
2194 return (volume.name, volume.id)
2195 except (ConnectionError, KeyError) as e:
2196 self._format_exception(e)
2197
2198 def _prepare_shared_volumes(
2199 self,
2200 name: str,
2201 disk: dict,
2202 base_disk_index: int,
2203 block_device_mapping: dict,
2204 existing_vim_volumes: list,
2205 created_items: dict,
2206 ):
2207 volumes = {volume.name: volume.id for volume in self.cinder.volumes.list()}
2208 if volumes.get(disk["name"]):
2209 sv_id = volumes[disk["name"]]
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05002210 max_retries = 3
2211 vol_status = ""
2212 # If this is not the first VM to attach the volume, volume status may be "reserved" for a short time
2213 while max_retries:
2214 max_retries -= 1
2215 volume = self.cinder.volumes.get(sv_id)
2216 vol_status = volume.status
2217 if volume.status not in ("in-use", "available"):
2218 time.sleep(5)
2219 continue
2220 self.update_block_device_mapping(
2221 volume=volume,
2222 block_device_mapping=block_device_mapping,
2223 base_disk_index=base_disk_index,
2224 disk=disk,
2225 created_items=created_items,
2226 )
2227 return
2228 raise vimconn.VimConnException(
2229 "Shared volume is not prepared, status is: {}".format(vol_status),
2230 http_code=vimconn.HTTP_Internal_Server_Error,
vegall364627c2023-03-17 15:09:50 +00002231 )
2232
Gulsum Atici26f73662022-10-27 15:18:27 +03002233 def _prepare_non_root_persistent_volumes(
2234 self,
2235 name: str,
2236 disk: dict,
2237 vm_av_zone: list,
2238 block_device_mapping: dict,
2239 base_disk_index: int,
2240 existing_vim_volumes: list,
2241 created_items: dict,
2242 ) -> None:
2243 """Prepare persistent volumes for new VM instance.
2244
2245 Args:
2246 name (str): Name of VM instance
2247 disk (dict): Disk details
2248 vm_av_zone (list): List of availability zones
2249 block_device_mapping (dict): Block device details
2250 base_disk_index (int): Disk index
2251 existing_vim_volumes (list): Existing disk details
2252 created_items (dict): All created items belongs to VM
2253 """
2254 # Non-root persistent volumes
2255 # Disk may include only vim_volume_id or only vim_id."
2256 key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
Gulsum Atici26f73662022-10-27 15:18:27 +03002257 if disk.get(key_id):
Gulsum Atici26f73662022-10-27 15:18:27 +03002258 # Use existing persistent volume
2259 block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
2260 existing_vim_volumes.append({"id": disk[key_id]})
Gulsum Atici26f73662022-10-27 15:18:27 +03002261 else:
vegall364627c2023-03-17 15:09:50 +00002262 volume_name = f"{name}vd{chr(base_disk_index)}"
Gulsum Atici26f73662022-10-27 15:18:27 +03002263 volume = self.cinder.volumes.create(
2264 size=disk["size"],
vegall364627c2023-03-17 15:09:50 +00002265 name=volume_name,
Gulsum Atici26f73662022-10-27 15:18:27 +03002266 # Make sure volume is in the same AZ as the VM to be attached to
2267 availability_zone=vm_av_zone,
2268 )
aticig2f4ab6c2022-09-03 18:15:20 +03002269 self.update_block_device_mapping(
2270 volume=volume,
2271 block_device_mapping=block_device_mapping,
2272 base_disk_index=base_disk_index,
2273 disk=disk,
2274 created_items=created_items,
2275 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002276
2277 def _wait_for_created_volumes_availability(
2278 self, elapsed_time: int, created_items: dict
2279 ) -> Optional[int]:
2280 """Wait till created volumes become available.
2281
2282 Args:
2283 elapsed_time (int): Passed time while waiting
2284 created_items (dict): All created items belongs to VM
2285
2286 Returns:
2287 elapsed_time (int): Time spent while waiting
2288
2289 """
Gulsum Atici26f73662022-10-27 15:18:27 +03002290 while elapsed_time < volume_timeout:
2291 for created_item in created_items:
aticig2f4ab6c2022-09-03 18:15:20 +03002292 v, volume_id = (
2293 created_item.split(":")[0],
2294 created_item.split(":")[1],
2295 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002296 if v == "volume":
vegall364627c2023-03-17 15:09:50 +00002297 volume = self.cinder.volumes.get(volume_id)
2298 if (
2299 volume.volume_type == "multiattach"
2300 and volume.status == "in-use"
2301 ):
2302 return elapsed_time
2303 elif volume.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002304 break
2305 else:
2306 # All ready: break from while
2307 break
2308
2309 time.sleep(5)
2310 elapsed_time += 5
2311
2312 return elapsed_time
2313
2314 def _wait_for_existing_volumes_availability(
2315 self, elapsed_time: int, existing_vim_volumes: list
2316 ) -> Optional[int]:
2317 """Wait till existing volumes become available.
2318
2319 Args:
2320 elapsed_time (int): Passed time while waiting
2321 existing_vim_volumes (list): Existing volume details
2322
2323 Returns:
2324 elapsed_time (int): Time spent while waiting
2325
2326 """
2327
2328 while elapsed_time < volume_timeout:
2329 for volume in existing_vim_volumes:
vegall364627c2023-03-17 15:09:50 +00002330 v = self.cinder.volumes.get(volume["id"])
2331 if v.volume_type == "multiattach" and v.status == "in-use":
2332 return elapsed_time
2333 elif v.status != "available":
Gulsum Atici26f73662022-10-27 15:18:27 +03002334 break
2335 else: # all ready: break from while
2336 break
2337
2338 time.sleep(5)
2339 elapsed_time += 5
2340
2341 return elapsed_time
2342
2343 def _prepare_disk_for_vminstance(
2344 self,
2345 name: str,
2346 existing_vim_volumes: list,
2347 created_items: dict,
2348 vm_av_zone: list,
Gulsum Atici13d02322022-11-18 00:10:15 +03002349 block_device_mapping: dict,
Gulsum Atici26f73662022-10-27 15:18:27 +03002350 disk_list: list = None,
2351 ) -> None:
2352 """Prepare all volumes for new VM instance.
2353
2354 Args:
2355 name (str): Name of Instance
2356 existing_vim_volumes (list): List of existing volumes
2357 created_items (dict): All created items belongs to VM
2358 vm_av_zone (list): VM availability zone
Gulsum Atici13d02322022-11-18 00:10:15 +03002359 block_device_mapping (dict): Block devices to be attached to VM
Gulsum Atici26f73662022-10-27 15:18:27 +03002360 disk_list (list): List of disks
2361
2362 """
2363 # Create additional volumes in case these are present in disk_list
2364 base_disk_index = ord("b")
2365 boot_volume_id = None
2366 elapsed_time = 0
Gulsum Atici26f73662022-10-27 15:18:27 +03002367 for disk in disk_list:
2368 if "image_id" in disk:
2369 # Root persistent volume
2370 base_disk_index = ord("a")
2371 boot_volume_id = self._prepare_persistent_root_volumes(
2372 name=name,
2373 vm_av_zone=vm_av_zone,
2374 disk=disk,
2375 base_disk_index=base_disk_index,
2376 block_device_mapping=block_device_mapping,
2377 existing_vim_volumes=existing_vim_volumes,
2378 created_items=created_items,
2379 )
vegall364627c2023-03-17 15:09:50 +00002380 elif disk.get("multiattach"):
2381 self._prepare_shared_volumes(
2382 name=name,
2383 disk=disk,
2384 base_disk_index=base_disk_index,
2385 block_device_mapping=block_device_mapping,
2386 existing_vim_volumes=existing_vim_volumes,
2387 created_items=created_items,
2388 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002389 else:
2390 # Non-root persistent volume
2391 self._prepare_non_root_persistent_volumes(
2392 name=name,
2393 disk=disk,
2394 vm_av_zone=vm_av_zone,
2395 block_device_mapping=block_device_mapping,
2396 base_disk_index=base_disk_index,
2397 existing_vim_volumes=existing_vim_volumes,
2398 created_items=created_items,
2399 )
2400 base_disk_index += 1
2401
2402 # Wait until created volumes are with status available
2403 elapsed_time = self._wait_for_created_volumes_availability(
2404 elapsed_time, created_items
2405 )
2406 # Wait until existing volumes in vim are with status available
2407 elapsed_time = self._wait_for_existing_volumes_availability(
2408 elapsed_time, existing_vim_volumes
2409 )
2410 # If we exceeded the timeout rollback
2411 if elapsed_time >= volume_timeout:
2412 raise vimconn.VimConnException(
2413 "Timeout creating volumes for instance " + name,
2414 http_code=vimconn.HTTP_Request_Timeout,
2415 )
2416 if boot_volume_id:
2417 self.cinder.volumes.set_bootable(boot_volume_id, True)
2418
2419 def _find_the_external_network_for_floating_ip(self):
2420 """Get the external network ip in order to create floating IP.
2421
2422 Returns:
2423 pool_id (str): External network pool ID
2424
2425 """
2426
2427 # Find the external network
2428 external_nets = list()
2429
2430 for net in self.neutron.list_networks()["networks"]:
2431 if net["router:external"]:
2432 external_nets.append(net)
2433
2434 if len(external_nets) == 0:
2435 raise vimconn.VimConnException(
2436 "Cannot create floating_ip automatically since "
2437 "no external network is present",
2438 http_code=vimconn.HTTP_Conflict,
2439 )
2440
2441 if len(external_nets) > 1:
2442 raise vimconn.VimConnException(
2443 "Cannot create floating_ip automatically since "
2444 "multiple external networks are present",
2445 http_code=vimconn.HTTP_Conflict,
2446 )
2447
2448 # Pool ID
2449 return external_nets[0].get("id")
2450
2451 def _neutron_create_float_ip(self, param: dict, created_items: dict) -> None:
2452 """Trigger neutron to create a new floating IP using external network ID.
2453
2454 Args:
2455 param (dict): Input parameters to create a floating IP
2456 created_items (dict): All created items belongs to new VM instance
2457
2458 Raises:
2459
2460 VimConnException
2461 """
2462 try:
2463 self.logger.debug("Creating floating IP")
2464 new_floating_ip = self.neutron.create_floatingip(param)
2465 free_floating_ip = new_floating_ip["floatingip"]["id"]
2466 created_items["floating_ip:" + str(free_floating_ip)] = True
2467
2468 except Exception as e:
2469 raise vimconn.VimConnException(
2470 type(e).__name__ + ": Cannot create new floating_ip " + str(e),
2471 http_code=vimconn.HTTP_Conflict,
2472 )
2473
2474 def _create_floating_ip(
2475 self, floating_network: dict, server: object, created_items: dict
2476 ) -> None:
2477 """Get the available Pool ID and create a new floating IP.
2478
2479 Args:
2480 floating_network (dict): Dict including external network ID
2481 server (object): Server object
2482 created_items (dict): All created items belongs to new VM instance
2483
2484 """
2485
2486 # Pool_id is available
2487 if (
2488 isinstance(floating_network["floating_ip"], str)
2489 and floating_network["floating_ip"].lower() != "true"
2490 ):
2491 pool_id = floating_network["floating_ip"]
2492
2493 # Find the Pool_id
2494 else:
2495 pool_id = self._find_the_external_network_for_floating_ip()
2496
2497 param = {
2498 "floatingip": {
2499 "floating_network_id": pool_id,
2500 "tenant_id": server.tenant_id,
2501 }
2502 }
2503
2504 self._neutron_create_float_ip(param, created_items)
2505
2506 def _find_floating_ip(
2507 self,
2508 server: object,
2509 floating_ips: list,
2510 floating_network: dict,
2511 ) -> Optional[str]:
2512 """Find the available free floating IPs if there are.
2513
2514 Args:
2515 server (object): Server object
2516 floating_ips (list): List of floating IPs
2517 floating_network (dict): Details of floating network such as ID
2518
2519 Returns:
2520 free_floating_ip (str): Free floating ip address
2521
2522 """
2523 for fip in floating_ips:
2524 if fip.get("port_id") or fip.get("tenant_id") != server.tenant_id:
2525 continue
2526
2527 if isinstance(floating_network["floating_ip"], str):
2528 if fip.get("floating_network_id") != floating_network["floating_ip"]:
2529 continue
2530
2531 return fip["id"]
2532
2533 def _assign_floating_ip(
2534 self, free_floating_ip: str, floating_network: dict
2535 ) -> Dict:
2536 """Assign the free floating ip address to port.
2537
2538 Args:
2539 free_floating_ip (str): Floating IP to be assigned
2540 floating_network (dict): ID of floating network
2541
2542 Returns:
2543 fip (dict) (dict): Floating ip details
2544
2545 """
2546 # The vim_id key contains the neutron.port_id
2547 self.neutron.update_floatingip(
2548 free_floating_ip,
2549 {"floatingip": {"port_id": floating_network["vim_id"]}},
2550 )
2551 # For race condition ensure not re-assigned to other VM after 5 seconds
2552 time.sleep(5)
2553
2554 return self.neutron.show_floatingip(free_floating_ip)
2555
2556 def _get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002557 self, server: object, floating_network: dict
Gulsum Atici26f73662022-10-27 15:18:27 +03002558 ) -> Optional[str]:
2559 """Get the free floating IP address.
2560
2561 Args:
2562 server (object): Server Object
2563 floating_network (dict): Floating network details
Gulsum Atici26f73662022-10-27 15:18:27 +03002564
2565 Returns:
2566 free_floating_ip (str): Free floating ip addr
2567
2568 """
2569
2570 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
2571
2572 # Randomize
2573 random.shuffle(floating_ips)
2574
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002575 return self._find_floating_ip(server, floating_ips, floating_network)
Gulsum Atici26f73662022-10-27 15:18:27 +03002576
2577 def _prepare_external_network_for_vminstance(
2578 self,
2579 external_network: list,
2580 server: object,
2581 created_items: dict,
2582 vm_start_time: float,
2583 ) -> None:
2584 """Assign floating IP address for VM instance.
2585
2586 Args:
2587 external_network (list): ID of External network
2588 server (object): Server Object
2589 created_items (dict): All created items belongs to new VM instance
2590 vm_start_time (float): Time as a floating point number expressed in seconds since the epoch, in UTC
2591
2592 Raises:
2593 VimConnException
2594
2595 """
2596 for floating_network in external_network:
2597 try:
2598 assigned = False
2599 floating_ip_retries = 3
2600 # In case of RO in HA there can be conflicts, two RO trying to assign same floating IP, so retry
2601 # several times
2602 while not assigned:
Gulsum Atici26f73662022-10-27 15:18:27 +03002603 free_floating_ip = self._get_free_floating_ip(
Gulsum Atici9e76ebb2022-12-12 19:21:25 +03002604 server, floating_network
Gulsum Atici26f73662022-10-27 15:18:27 +03002605 )
2606
2607 if not free_floating_ip:
2608 self._create_floating_ip(
2609 floating_network, server, created_items
2610 )
2611
2612 try:
2613 # For race condition ensure not already assigned
2614 fip = self.neutron.show_floatingip(free_floating_ip)
2615
2616 if fip["floatingip"].get("port_id"):
2617 continue
2618
2619 # Assign floating ip
2620 fip = self._assign_floating_ip(
2621 free_floating_ip, floating_network
2622 )
2623
2624 if fip["floatingip"]["port_id"] != floating_network["vim_id"]:
2625 self.logger.warning(
2626 "floating_ip {} re-assigned to other port".format(
2627 free_floating_ip
2628 )
2629 )
2630 continue
2631
2632 self.logger.debug(
2633 "Assigned floating_ip {} to VM {}".format(
2634 free_floating_ip, server.id
2635 )
2636 )
2637
2638 assigned = True
2639
2640 except Exception as e:
2641 # Openstack need some time after VM creation to assign an IP. So retry if fails
2642 vm_status = self.nova.servers.get(server.id).status
2643
2644 if vm_status not in ("ACTIVE", "ERROR"):
2645 if time.time() - vm_start_time < server_timeout:
2646 time.sleep(5)
2647 continue
2648 elif floating_ip_retries > 0:
2649 floating_ip_retries -= 1
2650 continue
2651
2652 raise vimconn.VimConnException(
2653 "Cannot create floating_ip: {} {}".format(
2654 type(e).__name__, e
2655 ),
2656 http_code=vimconn.HTTP_Conflict,
2657 )
2658
2659 except Exception as e:
2660 if not floating_network["exit_on_floating_ip_error"]:
2661 self.logger.error("Cannot create floating_ip. %s", str(e))
2662 continue
2663
2664 raise
2665
2666 def _update_port_security_for_vminstance(
2667 self,
2668 no_secured_ports: list,
2669 server: object,
2670 ) -> None:
2671 """Updates the port security according to no_secured_ports list.
2672
2673 Args:
2674 no_secured_ports (list): List of ports that security will be disabled
2675 server (object): Server Object
2676
2677 Raises:
2678 VimConnException
2679
2680 """
2681 # Wait until the VM is active and then disable the port-security
2682 if no_secured_ports:
2683 self.__wait_for_vm(server.id, "ACTIVE")
2684
2685 for port in no_secured_ports:
2686 port_update = {
2687 "port": {"port_security_enabled": False, "security_groups": None}
2688 }
2689
2690 if port[1] == "allow-address-pairs":
2691 port_update = {
2692 "port": {"allowed_address_pairs": [{"ip_address": "0.0.0.0/0"}]}
2693 }
2694
2695 try:
2696 self.neutron.update_port(port[0], port_update)
2697
2698 except Exception:
Gulsum Atici26f73662022-10-27 15:18:27 +03002699 raise vimconn.VimConnException(
2700 "It was not possible to disable port security for port {}".format(
2701 port[0]
2702 )
2703 )
2704
sousaedu80135b92021-02-17 15:05:18 +01002705 def new_vminstance(
2706 self,
Gulsum Atici26f73662022-10-27 15:18:27 +03002707 name: str,
2708 description: str,
2709 start: bool,
2710 image_id: str,
2711 flavor_id: str,
2712 affinity_group_list: list,
2713 net_list: list,
sousaedu80135b92021-02-17 15:05:18 +01002714 cloud_config=None,
2715 disk_list=None,
2716 availability_zone_index=None,
2717 availability_zone_list=None,
Gulsum Atici26f73662022-10-27 15:18:27 +03002718 ) -> tuple:
2719 """Adds a VM instance to VIM.
2720
2721 Args:
2722 name (str): name of VM
2723 description (str): description
2724 start (bool): indicates if VM must start or boot in pause mode. Ignored
2725 image_id (str) image uuid
2726 flavor_id (str) flavor uuid
2727 affinity_group_list (list): list of affinity groups, each one is a dictionary.Ignore if empty.
2728 net_list (list): list of interfaces, each one is a dictionary with:
2729 name: name of network
2730 net_id: network uuid to connect
2731 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
2732 model: interface model, ignored #TODO
2733 mac_address: used for SR-IOV ifaces #TODO for other types
2734 use: 'data', 'bridge', 'mgmt'
2735 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
2736 vim_id: filled/added by this function
2737 floating_ip: True/False (or it can be None)
2738 port_security: True/False
2739 cloud_config (dict): (optional) dictionary with:
2740 key-pairs: (optional) list of strings with the public key to be inserted to the default user
2741 users: (optional) list of users to be inserted, each item is a dict with:
2742 name: (mandatory) user name,
2743 key-pairs: (optional) list of strings with the public key to be inserted to the user
2744 user-data: (optional) string is a text script to be passed directly to cloud-init
2745 config-files: (optional). List of files to be transferred. Each item is a dict with:
2746 dest: (mandatory) string with the destination absolute path
2747 encoding: (optional, by default text). Can be one of:
tierno1d213f42020-04-24 14:02:51 +00002748 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
Gulsum Atici26f73662022-10-27 15:18:27 +03002749 content : (mandatory) string with the content of the file
2750 permissions: (optional) string with file permissions, typically octal notation '0644'
2751 owner: (optional) file owner, string with the format 'owner:group'
2752 boot-data-drive: boolean to indicate if user-data must be passed using a boot drive (hard disk)
2753 disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
2754 image_id: (optional). VIM id of an existing image. If not provided an empty disk must be mounted
2755 size: (mandatory) string with the size of the disk in GB
2756 vim_id: (optional) should use this existing volume id
2757 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
2758 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
tierno5a3273c2017-08-29 11:43:46 +02002759 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01002760 #TODO ip, security groups
Gulsum Atici26f73662022-10-27 15:18:27 +03002761
2762 Returns:
2763 A tuple with the instance identifier and created_items or raises an exception on error
tierno98e909c2017-10-14 13:27:03 +02002764 created_items can be None or a dictionary where this method can include key-values that will be passed to
2765 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
2766 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
2767 as not present.
aticig2f4ab6c2022-09-03 18:15:20 +03002768
tierno98e909c2017-10-14 13:27:03 +02002769 """
sousaedu80135b92021-02-17 15:05:18 +01002770 self.logger.debug(
2771 "new_vminstance input: image='%s' flavor='%s' nics='%s'",
2772 image_id,
2773 flavor_id,
2774 str(net_list),
2775 )
2776
tierno7edb6752016-03-21 17:37:52 +01002777 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002778 server = None
tierno98e909c2017-10-14 13:27:03 +02002779 created_items = {}
tierno98e909c2017-10-14 13:27:03 +02002780 net_list_vim = []
Gulsum Atici26f73662022-10-27 15:18:27 +03002781 # list of external networks to be connected to instance, later on used to create floating_ip
tierno1ec592d2020-06-16 15:29:47 +00002782 external_network = []
Gulsum Atici26f73662022-10-27 15:18:27 +03002783 # List of ports with port-security disabled
2784 no_secured_ports = []
Gulsum Atici13d02322022-11-18 00:10:15 +03002785 block_device_mapping = {}
Gulsum Atici26f73662022-10-27 15:18:27 +03002786 existing_vim_volumes = []
2787 server_group_id = None
2788 scheduller_hints = {}
tiernoa05b65a2019-02-01 12:30:27 +00002789
Gulsum Atici26f73662022-10-27 15:18:27 +03002790 # Check the Openstack Connection
2791 self._reload_connection()
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02002792
Gulsum Atici26f73662022-10-27 15:18:27 +03002793 # Prepare network list
2794 self._prepare_network_for_vminstance(
2795 name=name,
2796 net_list=net_list,
2797 created_items=created_items,
2798 net_list_vim=net_list_vim,
2799 external_network=external_network,
2800 no_secured_ports=no_secured_ports,
sousaedu80135b92021-02-17 15:05:18 +01002801 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002802
Gulsum Atici26f73662022-10-27 15:18:27 +03002803 # Cloud config
tierno0a1437e2017-10-02 00:17:43 +02002804 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00002805
Gulsum Atici26f73662022-10-27 15:18:27 +03002806 # Get availability Zone
Alexis Romero247cc432022-05-12 13:23:25 +02002807 vm_av_zone = self._get_vm_availability_zone(
2808 availability_zone_index, availability_zone_list
2809 )
2810
tierno1df468d2018-07-06 14:25:16 +02002811 if disk_list:
Gulsum Atici26f73662022-10-27 15:18:27 +03002812 # Prepare disks
2813 self._prepare_disk_for_vminstance(
2814 name=name,
2815 existing_vim_volumes=existing_vim_volumes,
2816 created_items=created_items,
2817 vm_av_zone=vm_av_zone,
Gulsum Atici13d02322022-11-18 00:10:15 +03002818 block_device_mapping=block_device_mapping,
Gulsum Atici26f73662022-10-27 15:18:27 +03002819 disk_list=disk_list,
2820 )
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002821
2822 if affinity_group_list:
2823 # Only first id on the list will be used. Openstack restriction
2824 server_group_id = affinity_group_list[0]["affinity_group_id"]
2825 scheduller_hints["group"] = server_group_id
2826
sousaedu80135b92021-02-17 15:05:18 +01002827 self.logger.debug(
2828 "nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
2829 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002830 "block_device_mapping={}, server_group={})".format(
sousaedu80135b92021-02-17 15:05:18 +01002831 name,
2832 image_id,
2833 flavor_id,
2834 net_list_vim,
2835 self.config.get("security_groups"),
2836 vm_av_zone,
2837 self.config.get("keypair"),
2838 userdata,
2839 config_drive,
2840 block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002841 server_group_id,
sousaedu80135b92021-02-17 15:05:18 +01002842 )
2843 )
Gulsum Atici26f73662022-10-27 15:18:27 +03002844 # Create VM
sousaedu80135b92021-02-17 15:05:18 +01002845 server = self.nova.servers.create(
aticigcf14bb12022-05-19 13:03:17 +03002846 name=name,
2847 image=image_id,
2848 flavor=flavor_id,
sousaedu80135b92021-02-17 15:05:18 +01002849 nics=net_list_vim,
2850 security_groups=self.config.get("security_groups"),
2851 # TODO remove security_groups in future versions. Already at neutron port
2852 availability_zone=vm_av_zone,
2853 key_name=self.config.get("keypair"),
2854 userdata=userdata,
2855 config_drive=config_drive,
2856 block_device_mapping=block_device_mapping,
Alexis Romerob70f4ed2022-03-11 18:00:49 +01002857 scheduler_hints=scheduller_hints,
Gulsum Atici26f73662022-10-27 15:18:27 +03002858 )
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002859
tierno326fd5e2018-02-22 11:58:59 +01002860 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002861
Gulsum Atici26f73662022-10-27 15:18:27 +03002862 self._update_port_security_for_vminstance(no_secured_ports, server)
bravof7a1f5252020-10-20 10:27:42 -03002863
Gulsum Atici26f73662022-10-27 15:18:27 +03002864 self._prepare_external_network_for_vminstance(
2865 external_network=external_network,
2866 server=server,
2867 created_items=created_items,
2868 vm_start_time=vm_start_time,
2869 )
montesmoreno2a1fc4e2017-01-09 16:46:04 +00002870
tierno98e909c2017-10-14 13:27:03 +02002871 return server.id, created_items
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002872
2873 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02002874 server_id = None
2875 if server:
2876 server_id = server.id
sousaedu80135b92021-02-17 15:05:18 +01002877
tierno98e909c2017-10-14 13:27:03 +02002878 try:
aticig2f4ab6c2022-09-03 18:15:20 +03002879 created_items = self.remove_keep_tag_from_persistent_volumes(
2880 created_items
2881 )
2882
tierno98e909c2017-10-14 13:27:03 +02002883 self.delete_vminstance(server_id, created_items)
Gulsum Atici26f73662022-10-27 15:18:27 +03002884
tierno98e909c2017-10-14 13:27:03 +02002885 except Exception as e2:
2886 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02002887
tiernoae4a8d12016-07-08 12:30:39 +02002888 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01002889
aticig2f4ab6c2022-09-03 18:15:20 +03002890 @staticmethod
2891 def remove_keep_tag_from_persistent_volumes(created_items: Dict) -> Dict:
2892 """Removes the keep flag from persistent volumes. So, those volumes could be removed.
2893
2894 Args:
2895 created_items (dict): All created items belongs to VM
2896
2897 Returns:
2898 updated_created_items (dict): Dict which does not include keep flag for volumes.
2899
2900 """
2901 return {
2902 key.replace(":keep", ""): value for (key, value) in created_items.items()
2903 }
2904
tierno1ec592d2020-06-16 15:29:47 +00002905 def get_vminstance(self, vm_id):
2906 """Returns the VM instance information from VIM"""
vegallc53829d2023-06-01 00:47:44 -05002907 return self._find_nova_server(vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02002908
tierno1ec592d2020-06-16 15:29:47 +00002909 def get_vminstance_console(self, vm_id, console_type="vnc"):
2910 """
tierno7edb6752016-03-21 17:37:52 +01002911 Get a console for the virtual machine
2912 Params:
2913 vm_id: uuid of the VM
2914 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002915 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01002916 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02002917 Returns dict with the console parameters:
2918 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01002919 server: usually ip address
2920 port: the http, ssh, ... port
2921 suffix: extra text, e.g. the http path and query string
tierno1ec592d2020-06-16 15:29:47 +00002922 """
tiernoae4a8d12016-07-08 12:30:39 +02002923 self.logger.debug("Getting VM CONSOLE from VIM")
sousaedu80135b92021-02-17 15:05:18 +01002924
tierno7edb6752016-03-21 17:37:52 +01002925 try:
2926 self._reload_connection()
2927 server = self.nova.servers.find(id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01002928
tierno1ec592d2020-06-16 15:29:47 +00002929 if console_type is None or console_type == "novnc":
tierno7edb6752016-03-21 17:37:52 +01002930 console_dict = server.get_vnc_console("novnc")
2931 elif console_type == "xvpvnc":
2932 console_dict = server.get_vnc_console(console_type)
2933 elif console_type == "rdp-html5":
2934 console_dict = server.get_rdp_console(console_type)
2935 elif console_type == "spice-html5":
2936 console_dict = server.get_spice_console(console_type)
2937 else:
sousaedu80135b92021-02-17 15:05:18 +01002938 raise vimconn.VimConnException(
2939 "console type '{}' not allowed".format(console_type),
2940 http_code=vimconn.HTTP_Bad_Request,
2941 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002942
tierno7edb6752016-03-21 17:37:52 +01002943 console_dict1 = console_dict.get("console")
sousaedu80135b92021-02-17 15:05:18 +01002944
tierno7edb6752016-03-21 17:37:52 +01002945 if console_dict1:
2946 console_url = console_dict1.get("url")
sousaedu80135b92021-02-17 15:05:18 +01002947
tierno7edb6752016-03-21 17:37:52 +01002948 if console_url:
tierno1ec592d2020-06-16 15:29:47 +00002949 # parse console_url
tierno7edb6752016-03-21 17:37:52 +01002950 protocol_index = console_url.find("//")
sousaedu80135b92021-02-17 15:05:18 +01002951 suffix_index = (
2952 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
2953 )
2954 port_index = (
2955 console_url[protocol_index + 2 : suffix_index].find(":")
2956 + protocol_index
2957 + 2
2958 )
2959
tierno1ec592d2020-06-16 15:29:47 +00002960 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
sousaedu80135b92021-02-17 15:05:18 +01002961 return (
2962 -vimconn.HTTP_Internal_Server_Error,
2963 "Unexpected response from VIM",
2964 )
2965
2966 console_dict = {
2967 "protocol": console_url[0:protocol_index],
2968 "server": console_url[protocol_index + 2 : port_index],
2969 "port": console_url[port_index:suffix_index],
2970 "suffix": console_url[suffix_index + 1 :],
2971 }
tierno7edb6752016-03-21 17:37:52 +01002972 protocol_index += 2
sousaedu80135b92021-02-17 15:05:18 +01002973
tiernoae4a8d12016-07-08 12:30:39 +02002974 return console_dict
tierno72774862020-05-04 11:44:15 +00002975 raise vimconn.VimConnUnexpectedResponse("Unexpected response from VIM")
sousaedu80135b92021-02-17 15:05:18 +01002976 except (
2977 nvExceptions.NotFound,
2978 ksExceptions.ClientException,
2979 nvExceptions.ClientException,
2980 nvExceptions.BadRequest,
2981 ConnectionError,
2982 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02002983 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01002984
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03002985 def _delete_ports_by_id_wth_neutron(self, k_id: str) -> None:
2986 """Neutron delete ports by id.
2987 Args:
2988 k_id (str): Port id in the VIM
2989 """
2990 try:
limon878f8692023-07-24 15:53:41 +02002991 self.neutron.delete_port(k_id)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03002992
2993 except Exception as e:
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03002994 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
2995
vegall364627c2023-03-17 15:09:50 +00002996 def delete_shared_volumes(self, shared_volume_vim_id: str) -> bool:
2997 """Cinder delete volume by id.
2998 Args:
2999 shared_volume_vim_id (str): ID of shared volume in VIM
3000 """
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003001 elapsed_time = 0
vegall364627c2023-03-17 15:09:50 +00003002 try:
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003003 while elapsed_time < server_timeout:
3004 vol_status = self.cinder.volumes.get(shared_volume_vim_id).status
3005 if vol_status == "available":
3006 self.cinder.volumes.delete(shared_volume_vim_id)
3007 return True
vegall364627c2023-03-17 15:09:50 +00003008
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003009 time.sleep(5)
3010 elapsed_time += 5
3011
3012 if elapsed_time >= server_timeout:
3013 raise vimconn.VimConnException(
3014 "Timeout waiting for volume "
3015 + shared_volume_vim_id
3016 + " to be available",
3017 http_code=vimconn.HTTP_Request_Timeout,
3018 )
vegall364627c2023-03-17 15:09:50 +00003019
3020 except Exception as e:
3021 self.logger.error(
3022 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3023 )
Gabriel Cuba1fd411b2023-06-14 00:50:57 -05003024 self._format_exception(e)
vegall364627c2023-03-17 15:09:50 +00003025
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003026 def _delete_volumes_by_id_wth_cinder(
3027 self, k: str, k_id: str, volumes_to_hold: list, created_items: dict
3028 ) -> bool:
3029 """Cinder delete volume by id.
3030 Args:
3031 k (str): Full item name in created_items
3032 k_id (str): ID of floating ip in VIM
3033 volumes_to_hold (list): Volumes not to delete
3034 created_items (dict): All created items belongs to VM
3035 """
3036 try:
3037 if k_id in volumes_to_hold:
3038 return
3039
3040 if self.cinder.volumes.get(k_id).status != "available":
3041 return True
3042
3043 else:
3044 self.cinder.volumes.delete(k_id)
3045 created_items[k] = None
3046
3047 except Exception as e:
3048 self.logger.error(
3049 "Error deleting volume: {}: {}".format(type(e).__name__, e)
3050 )
3051
3052 def _delete_floating_ip_by_id(self, k: str, k_id: str, created_items: dict) -> None:
3053 """Neutron delete floating ip by id.
3054 Args:
3055 k (str): Full item name in created_items
3056 k_id (str): ID of floating ip in VIM
3057 created_items (dict): All created items belongs to VM
3058 """
3059 try:
3060 self.neutron.delete_floatingip(k_id)
3061 created_items[k] = None
3062
3063 except Exception as e:
3064 self.logger.error(
3065 "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
3066 )
3067
3068 @staticmethod
3069 def _get_item_name_id(k: str) -> Tuple[str, str]:
3070 k_item, _, k_id = k.partition(":")
3071 return k_item, k_id
3072
3073 def _delete_vm_ports_attached_to_network(self, created_items: dict) -> None:
3074 """Delete VM ports attached to the networks before deleting virtual machine.
3075 Args:
3076 created_items (dict): All created items belongs to VM
3077 """
3078
3079 for k, v in created_items.items():
3080 if not v: # skip already deleted
3081 continue
3082
3083 try:
3084 k_item, k_id = self._get_item_name_id(k)
3085 if k_item == "port":
3086 self._delete_ports_by_id_wth_neutron(k_id)
3087
3088 except Exception as e:
3089 self.logger.error(
3090 "Error deleting port: {}: {}".format(type(e).__name__, e)
3091 )
3092
3093 def _delete_created_items(
3094 self, created_items: dict, volumes_to_hold: list, keep_waiting: bool
3095 ) -> bool:
3096 """Delete Volumes and floating ip if they exist in created_items."""
3097 for k, v in created_items.items():
3098 if not v: # skip already deleted
3099 continue
3100
3101 try:
3102 k_item, k_id = self._get_item_name_id(k)
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003103 if k_item == "volume":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003104 unavailable_vol = self._delete_volumes_by_id_wth_cinder(
3105 k, k_id, volumes_to_hold, created_items
3106 )
3107
3108 if unavailable_vol:
3109 keep_waiting = True
3110
3111 elif k_item == "floating_ip":
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003112 self._delete_floating_ip_by_id(k, k_id, created_items)
3113
3114 except Exception as e:
3115 self.logger.error("Error deleting {}: {}".format(k, e))
3116
3117 return keep_waiting
3118
aticig2f4ab6c2022-09-03 18:15:20 +03003119 @staticmethod
3120 def _extract_items_wth_keep_flag_from_created_items(created_items: dict) -> dict:
3121 """Remove the volumes which has key flag from created_items
3122
3123 Args:
3124 created_items (dict): All created items belongs to VM
3125
3126 Returns:
3127 created_items (dict): Persistent volumes eliminated created_items
3128 """
3129 return {
3130 key: value
3131 for (key, value) in created_items.items()
3132 if len(key.split(":")) == 2
3133 }
3134
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003135 def delete_vminstance(
3136 self, vm_id: str, created_items: dict = None, volumes_to_hold: list = None
3137 ) -> None:
3138 """Removes a VM instance from VIM. Returns the old identifier.
3139 Args:
3140 vm_id (str): Identifier of VM instance
3141 created_items (dict): All created items belongs to VM
3142 volumes_to_hold (list): Volumes_to_hold
3143 """
tierno1ec592d2020-06-16 15:29:47 +00003144 if created_items is None:
tierno98e909c2017-10-14 13:27:03 +02003145 created_items = {}
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003146 if volumes_to_hold is None:
3147 volumes_to_hold = []
sousaedu80135b92021-02-17 15:05:18 +01003148
tierno7edb6752016-03-21 17:37:52 +01003149 try:
aticig2f4ab6c2022-09-03 18:15:20 +03003150 created_items = self._extract_items_wth_keep_flag_from_created_items(
3151 created_items
3152 )
3153
tierno7edb6752016-03-21 17:37:52 +01003154 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01003155
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003156 # Delete VM ports attached to the networks before the virtual machine
3157 if created_items:
3158 self._delete_vm_ports_attached_to_network(created_items)
montesmoreno0c8def02016-12-22 12:16:23 +00003159
tierno98e909c2017-10-14 13:27:03 +02003160 if vm_id:
3161 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00003162
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003163 # Although having detached, volumes should have in active status before deleting.
3164 # We ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00003165 keep_waiting = True
3166 elapsed_time = 0
sousaedu80135b92021-02-17 15:05:18 +01003167
montesmoreno0c8def02016-12-22 12:16:23 +00003168 while keep_waiting and elapsed_time < volume_timeout:
3169 keep_waiting = False
sousaedu80135b92021-02-17 15:05:18 +01003170
Gulsum Atici4bc8eb92022-11-21 14:11:02 +03003171 # Delete volumes and floating IP.
3172 keep_waiting = self._delete_created_items(
3173 created_items, volumes_to_hold, keep_waiting
3174 )
sousaedu80135b92021-02-17 15:05:18 +01003175
montesmoreno0c8def02016-12-22 12:16:23 +00003176 if keep_waiting:
3177 time.sleep(1)
3178 elapsed_time += 1
sousaedu80135b92021-02-17 15:05:18 +01003179
sousaedu80135b92021-02-17 15:05:18 +01003180 except (
3181 nvExceptions.NotFound,
3182 ksExceptions.ClientException,
3183 nvExceptions.ClientException,
3184 ConnectionError,
3185 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02003186 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01003187
tiernoae4a8d12016-07-08 12:30:39 +02003188 def refresh_vms_status(self, vm_list):
tierno1ec592d2020-06-16 15:29:47 +00003189 """Get the status of the virtual machines and their interfaces/ports
sousaedu80135b92021-02-17 15:05:18 +01003190 Params: the list of VM identifiers
3191 Returns a dictionary with:
3192 vm_id: #VIM id of this Virtual Machine
3193 status: #Mandatory. Text with one of:
3194 # DELETED (not found at vim)
3195 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
3196 # OTHER (Vim reported other status not understood)
3197 # ERROR (VIM indicates an ERROR status)
3198 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
3199 # CREATING (on building process), ERROR
3200 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
3201 #
3202 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
3203 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3204 interfaces:
3205 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
3206 mac_address: #Text format XX:XX:XX:XX:XX:XX
3207 vim_net_id: #network id where this interface is connected
3208 vim_interface_id: #interface/port VIM id
3209 ip_address: #null, or text with IPv4, IPv6 address
3210 compute_node: #identification of compute node where PF,VF interface is allocated
3211 pci: #PCI address of the NIC that hosts the PF,VF
3212 vlan: #physical VLAN used for VF
tierno1ec592d2020-06-16 15:29:47 +00003213 """
3214 vm_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01003215 self.logger.debug(
3216 "refresh_vms status: Getting tenant VM instance information from VIM"
3217 )
3218
tiernoae4a8d12016-07-08 12:30:39 +02003219 for vm_id in vm_list:
tierno1ec592d2020-06-16 15:29:47 +00003220 vm = {}
sousaedu80135b92021-02-17 15:05:18 +01003221
tiernoae4a8d12016-07-08 12:30:39 +02003222 try:
3223 vm_vim = self.get_vminstance(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003224
3225 if vm_vim["status"] in vmStatus2manoFormat:
3226 vm["status"] = vmStatus2manoFormat[vm_vim["status"]]
tierno7edb6752016-03-21 17:37:52 +01003227 else:
sousaedu80135b92021-02-17 15:05:18 +01003228 vm["status"] = "OTHER"
3229 vm["error_msg"] = "VIM status reported " + vm_vim["status"]
3230
tierno70eeb182020-10-19 16:38:00 +00003231 vm_vim.pop("OS-EXT-SRV-ATTR:user_data", None)
3232 vm_vim.pop("user_data", None)
sousaedu80135b92021-02-17 15:05:18 +01003233 vm["vim_info"] = self.serialize(vm_vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003234
tiernoae4a8d12016-07-08 12:30:39 +02003235 vm["interfaces"] = []
sousaedu80135b92021-02-17 15:05:18 +01003236 if vm_vim.get("fault"):
3237 vm["error_msg"] = str(vm_vim["fault"])
3238
tierno1ec592d2020-06-16 15:29:47 +00003239 # get interfaces
tierno7edb6752016-03-21 17:37:52 +01003240 try:
tiernoae4a8d12016-07-08 12:30:39 +02003241 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02003242 port_dict = self.neutron.list_ports(device_id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003243
tiernoae4a8d12016-07-08 12:30:39 +02003244 for port in port_dict["ports"]:
tierno1ec592d2020-06-16 15:29:47 +00003245 interface = {}
sousaedu80135b92021-02-17 15:05:18 +01003246 interface["vim_info"] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02003247 interface["mac_address"] = port.get("mac_address")
3248 interface["vim_net_id"] = port["network_id"]
3249 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003250 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003251 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003252
3253 if vm_vim.get("OS-EXT-SRV-ATTR:host"):
3254 interface["compute_node"] = vm_vim["OS-EXT-SRV-ATTR:host"]
3255
tierno867ffe92017-03-27 12:50:34 +02003256 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04003257
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01003258 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04003259 # in case of non-admin credentials, it will be missing
sousaedu80135b92021-02-17 15:05:18 +01003260 if port.get("binding:profile"):
3261 if port["binding:profile"].get("pci_slot"):
tierno1ec592d2020-06-16 15:29:47 +00003262 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting
3263 # the slot to 0x00
Mike Marchetti5b9da422017-05-02 15:35:47 -04003264 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
3265 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
sousaedu80135b92021-02-17 15:05:18 +01003266 pci = port["binding:profile"]["pci_slot"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04003267 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
3268 interface["pci"] = pci
sousaedu80135b92021-02-17 15:05:18 +01003269
tierno867ffe92017-03-27 12:50:34 +02003270 interface["vlan"] = None
sousaedu80135b92021-02-17 15:05:18 +01003271
3272 if port.get("binding:vif_details"):
3273 interface["vlan"] = port["binding:vif_details"].get("vlan")
3274
tierno1dfe9932020-06-18 08:50:10 +00003275 # Get vlan from network in case not present in port for those old openstacks and cases where
3276 # it is needed vlan at PT
3277 if not interface["vlan"]:
3278 # if network is of type vlan and port is of type direct (sr-iov) then set vlan id
3279 network = self.neutron.show_network(port["network_id"])
sousaedu80135b92021-02-17 15:05:18 +01003280
3281 if (
3282 network["network"].get("provider:network_type")
3283 == "vlan"
3284 ):
tierno1dfe9932020-06-18 08:50:10 +00003285 # and port.get("binding:vnic_type") in ("direct", "direct-physical"):
sousaedu80135b92021-02-17 15:05:18 +01003286 interface["vlan"] = network["network"].get(
3287 "provider:segmentation_id"
3288 )
3289
tierno1ec592d2020-06-16 15:29:47 +00003290 ips = []
3291 # look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02003292 try:
sousaedu80135b92021-02-17 15:05:18 +01003293 floating_ip_dict = self.neutron.list_floatingips(
3294 port_id=port["id"]
3295 )
3296
tiernob42fd9b2018-06-20 10:44:32 +02003297 if floating_ip_dict.get("floatingips"):
sousaedu80135b92021-02-17 15:05:18 +01003298 ips.append(
3299 floating_ip_dict["floatingips"][0].get(
3300 "floating_ip_address"
3301 )
3302 )
tiernob42fd9b2018-06-20 10:44:32 +02003303 except Exception:
3304 pass
tierno7edb6752016-03-21 17:37:52 +01003305
tiernoae4a8d12016-07-08 12:30:39 +02003306 for subnet in port["fixed_ips"]:
3307 ips.append(subnet["ip_address"])
sousaedu80135b92021-02-17 15:05:18 +01003308
tiernoae4a8d12016-07-08 12:30:39 +02003309 interface["ip_address"] = ";".join(ips)
3310 vm["interfaces"].append(interface)
3311 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01003312 self.logger.error(
3313 "Error getting vm interface information {}: {}".format(
3314 type(e).__name__, e
3315 ),
3316 exc_info=True,
3317 )
tierno72774862020-05-04 11:44:15 +00003318 except vimconn.VimConnNotFoundException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003319 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003320 vm["status"] = "DELETED"
3321 vm["error_msg"] = str(e)
tierno72774862020-05-04 11:44:15 +00003322 except vimconn.VimConnException as e:
tiernoae4a8d12016-07-08 12:30:39 +02003323 self.logger.error("Exception getting vm status: %s", str(e))
sousaedu80135b92021-02-17 15:05:18 +01003324 vm["status"] = "VIM_ERROR"
3325 vm["error_msg"] = str(e)
3326
tiernoae4a8d12016-07-08 12:30:39 +02003327 vm_dict[vm_id] = vm
sousaedu80135b92021-02-17 15:05:18 +01003328
tiernoae4a8d12016-07-08 12:30:39 +02003329 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003330
tierno98e909c2017-10-14 13:27:03 +02003331 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno1ec592d2020-06-16 15:29:47 +00003332 """Send and action over a VM instance from VIM
Gulsum Atici21c55d62023-02-02 20:41:00 +03003333 Returns None or the console dict if the action was successfully sent to the VIM
3334 """
tiernoae4a8d12016-07-08 12:30:39 +02003335 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
sousaedu80135b92021-02-17 15:05:18 +01003336
tierno7edb6752016-03-21 17:37:52 +01003337 try:
3338 self._reload_connection()
3339 server = self.nova.servers.find(id=vm_id)
sousaedu80135b92021-02-17 15:05:18 +01003340
tierno7edb6752016-03-21 17:37:52 +01003341 if "start" in action_dict:
tierno1ec592d2020-06-16 15:29:47 +00003342 if action_dict["start"] == "rebuild":
tierno7edb6752016-03-21 17:37:52 +01003343 server.rebuild()
3344 else:
tierno1ec592d2020-06-16 15:29:47 +00003345 if server.status == "PAUSED":
tierno7edb6752016-03-21 17:37:52 +01003346 server.unpause()
tierno1ec592d2020-06-16 15:29:47 +00003347 elif server.status == "SUSPENDED":
tierno7edb6752016-03-21 17:37:52 +01003348 server.resume()
tierno1ec592d2020-06-16 15:29:47 +00003349 elif server.status == "SHUTOFF":
tierno7edb6752016-03-21 17:37:52 +01003350 server.start()
k4.rahul78f474e2022-05-02 15:47:57 +00003351 else:
3352 self.logger.debug(
3353 "ERROR : Instance is not in SHUTOFF/PAUSE/SUSPEND state"
3354 )
3355 raise vimconn.VimConnException(
3356 "Cannot 'start' instance while it is in active state",
3357 http_code=vimconn.HTTP_Bad_Request,
3358 )
3359
tierno7edb6752016-03-21 17:37:52 +01003360 elif "pause" in action_dict:
3361 server.pause()
3362 elif "resume" in action_dict:
3363 server.resume()
3364 elif "shutoff" in action_dict or "shutdown" in action_dict:
k4.rahul78f474e2022-05-02 15:47:57 +00003365 self.logger.debug("server status %s", server.status)
3366 if server.status == "ACTIVE":
3367 server.stop()
3368 else:
3369 self.logger.debug("ERROR: VM is not in Active state")
3370 raise vimconn.VimConnException(
3371 "VM is not in active state, stop operation is not allowed",
3372 http_code=vimconn.HTTP_Bad_Request,
3373 )
tierno7edb6752016-03-21 17:37:52 +01003374 elif "forceOff" in action_dict:
tierno1ec592d2020-06-16 15:29:47 +00003375 server.stop() # TODO
tierno7edb6752016-03-21 17:37:52 +01003376 elif "terminate" in action_dict:
3377 server.delete()
3378 elif "createImage" in action_dict:
3379 server.create_image()
tierno1ec592d2020-06-16 15:29:47 +00003380 # "path":path_schema,
3381 # "description":description_schema,
3382 # "name":name_schema,
3383 # "metadata":metadata_schema,
3384 # "imageRef": id_schema,
3385 # "disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
tierno7edb6752016-03-21 17:37:52 +01003386 elif "rebuild" in action_dict:
sousaedu80135b92021-02-17 15:05:18 +01003387 server.rebuild(server.image["id"])
tierno7edb6752016-03-21 17:37:52 +01003388 elif "reboot" in action_dict:
sousaedu80135b92021-02-17 15:05:18 +01003389 server.reboot() # reboot_type="SOFT"
tierno7edb6752016-03-21 17:37:52 +01003390 elif "console" in action_dict:
3391 console_type = action_dict["console"]
sousaedu80135b92021-02-17 15:05:18 +01003392
tierno1ec592d2020-06-16 15:29:47 +00003393 if console_type is None or console_type == "novnc":
tierno7edb6752016-03-21 17:37:52 +01003394 console_dict = server.get_vnc_console("novnc")
3395 elif console_type == "xvpvnc":
3396 console_dict = server.get_vnc_console(console_type)
3397 elif console_type == "rdp-html5":
3398 console_dict = server.get_rdp_console(console_type)
3399 elif console_type == "spice-html5":
3400 console_dict = server.get_spice_console(console_type)
3401 else:
sousaedu80135b92021-02-17 15:05:18 +01003402 raise vimconn.VimConnException(
3403 "console type '{}' not allowed".format(console_type),
3404 http_code=vimconn.HTTP_Bad_Request,
3405 )
3406
tierno7edb6752016-03-21 17:37:52 +01003407 try:
3408 console_url = console_dict["console"]["url"]
tierno1ec592d2020-06-16 15:29:47 +00003409 # parse console_url
tierno7edb6752016-03-21 17:37:52 +01003410 protocol_index = console_url.find("//")
sousaedu80135b92021-02-17 15:05:18 +01003411 suffix_index = (
3412 console_url[protocol_index + 2 :].find("/") + protocol_index + 2
3413 )
3414 port_index = (
3415 console_url[protocol_index + 2 : suffix_index].find(":")
3416 + protocol_index
3417 + 2
3418 )
3419
tierno1ec592d2020-06-16 15:29:47 +00003420 if protocol_index < 0 or port_index < 0 or suffix_index < 0:
sousaedu80135b92021-02-17 15:05:18 +01003421 raise vimconn.VimConnException(
3422 "Unexpected response from VIM " + str(console_dict)
3423 )
3424
3425 console_dict2 = {
3426 "protocol": console_url[0:protocol_index],
3427 "server": console_url[protocol_index + 2 : port_index],
3428 "port": int(console_url[port_index + 1 : suffix_index]),
3429 "suffix": console_url[suffix_index + 1 :],
3430 }
3431
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003432 return console_dict2
tierno1ec592d2020-06-16 15:29:47 +00003433 except Exception:
sousaedu80135b92021-02-17 15:05:18 +01003434 raise vimconn.VimConnException(
3435 "Unexpected response from VIM " + str(console_dict)
3436 )
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003437
tierno98e909c2017-10-14 13:27:03 +02003438 return None
sousaedu80135b92021-02-17 15:05:18 +01003439 except (
3440 ksExceptions.ClientException,
3441 nvExceptions.ClientException,
3442 nvExceptions.NotFound,
3443 ConnectionError,
3444 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +02003445 self._format_exception(e)
tierno1ec592d2020-06-16 15:29:47 +00003446 # TODO insert exception vimconn.HTTP_Unauthorized
tiernoae4a8d12016-07-08 12:30:39 +02003447
tierno1ec592d2020-06-16 15:29:47 +00003448 # ###### VIO Specific Changes #########
garciadeblasebd66722019-01-31 16:01:31 +00003449 def _generate_vlanID(self):
kate721d79b2017-06-24 04:21:38 -07003450 """
sousaedu80135b92021-02-17 15:05:18 +01003451 Method to get unused vlanID
kate721d79b2017-06-24 04:21:38 -07003452 Args:
3453 None
3454 Returns:
3455 vlanID
3456 """
tierno1ec592d2020-06-16 15:29:47 +00003457 # Get used VLAN IDs
kate721d79b2017-06-24 04:21:38 -07003458 usedVlanIDs = []
3459 networks = self.get_network_list()
sousaedu80135b92021-02-17 15:05:18 +01003460
kate721d79b2017-06-24 04:21:38 -07003461 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003462 if net.get("provider:segmentation_id"):
3463 usedVlanIDs.append(net.get("provider:segmentation_id"))
3464
kate721d79b2017-06-24 04:21:38 -07003465 used_vlanIDs = set(usedVlanIDs)
3466
tierno1ec592d2020-06-16 15:29:47 +00003467 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003468 for vlanID_range in self.config.get("dataplane_net_vlan_range"):
kate721d79b2017-06-24 04:21:38 -07003469 try:
sousaedu80135b92021-02-17 15:05:18 +01003470 start_vlanid, end_vlanid = map(
3471 int, vlanID_range.replace(" ", "").split("-")
3472 )
3473
tierno7d782ef2019-10-04 12:56:31 +00003474 for vlanID in range(start_vlanid, end_vlanid + 1):
kate721d79b2017-06-24 04:21:38 -07003475 if vlanID not in used_vlanIDs:
3476 return vlanID
3477 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003478 raise vimconn.VimConnException(
3479 "Exception {} occurred while generating VLAN ID.".format(exp)
3480 )
kate721d79b2017-06-24 04:21:38 -07003481 else:
tierno1ec592d2020-06-16 15:29:47 +00003482 raise vimconn.VimConnConflictException(
3483 "Unable to create the SRIOV VLAN network. All given Vlan IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003484 self.config.get("dataplane_net_vlan_range")
3485 )
3486 )
kate721d79b2017-06-24 04:21:38 -07003487
garciadeblasebd66722019-01-31 16:01:31 +00003488 def _generate_multisegment_vlanID(self):
3489 """
sousaedu80135b92021-02-17 15:05:18 +01003490 Method to get unused vlanID
3491 Args:
3492 None
3493 Returns:
3494 vlanID
garciadeblasebd66722019-01-31 16:01:31 +00003495 """
tierno6869ae72020-01-09 17:37:34 +00003496 # Get used VLAN IDs
garciadeblasebd66722019-01-31 16:01:31 +00003497 usedVlanIDs = []
3498 networks = self.get_network_list()
3499 for net in networks:
sousaedu80135b92021-02-17 15:05:18 +01003500 if net.get("provider:network_type") == "vlan" and net.get(
3501 "provider:segmentation_id"
3502 ):
3503 usedVlanIDs.append(net.get("provider:segmentation_id"))
3504 elif net.get("segments"):
3505 for segment in net.get("segments"):
3506 if segment.get("provider:network_type") == "vlan" and segment.get(
3507 "provider:segmentation_id"
3508 ):
3509 usedVlanIDs.append(segment.get("provider:segmentation_id"))
3510
garciadeblasebd66722019-01-31 16:01:31 +00003511 used_vlanIDs = set(usedVlanIDs)
3512
tierno6869ae72020-01-09 17:37:34 +00003513 # find unused VLAN ID
sousaedu80135b92021-02-17 15:05:18 +01003514 for vlanID_range in self.config.get("multisegment_vlan_range"):
garciadeblasebd66722019-01-31 16:01:31 +00003515 try:
sousaedu80135b92021-02-17 15:05:18 +01003516 start_vlanid, end_vlanid = map(
3517 int, vlanID_range.replace(" ", "").split("-")
3518 )
3519
tierno7d782ef2019-10-04 12:56:31 +00003520 for vlanID in range(start_vlanid, end_vlanid + 1):
garciadeblasebd66722019-01-31 16:01:31 +00003521 if vlanID not in used_vlanIDs:
3522 return vlanID
3523 except Exception as exp:
sousaedu80135b92021-02-17 15:05:18 +01003524 raise vimconn.VimConnException(
3525 "Exception {} occurred while generating VLAN ID.".format(exp)
3526 )
garciadeblasebd66722019-01-31 16:01:31 +00003527 else:
tierno1ec592d2020-06-16 15:29:47 +00003528 raise vimconn.VimConnConflictException(
3529 "Unable to create the VLAN segment. All VLAN IDs {} are in use.".format(
sousaedu80135b92021-02-17 15:05:18 +01003530 self.config.get("multisegment_vlan_range")
3531 )
3532 )
garciadeblasebd66722019-01-31 16:01:31 +00003533
3534 def _validate_vlan_ranges(self, input_vlan_range, text_vlan_range):
kate721d79b2017-06-24 04:21:38 -07003535 """
3536 Method to validate user given vlanID ranges
3537 Args: None
3538 Returns: None
3539 """
garciadeblasebd66722019-01-31 16:01:31 +00003540 for vlanID_range in input_vlan_range:
kate721d79b2017-06-24 04:21:38 -07003541 vlan_range = vlanID_range.replace(" ", "")
tierno1ec592d2020-06-16 15:29:47 +00003542 # validate format
sousaedu80135b92021-02-17 15:05:18 +01003543 vlanID_pattern = r"(\d)*-(\d)*$"
kate721d79b2017-06-24 04:21:38 -07003544 match_obj = re.match(vlanID_pattern, vlan_range)
3545 if not match_obj:
tierno1ec592d2020-06-16 15:29:47 +00003546 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003547 "Invalid VLAN range for {}: {}.You must provide "
3548 "'{}' in format [start_ID - end_ID].".format(
3549 text_vlan_range, vlanID_range, text_vlan_range
3550 )
3551 )
kate721d79b2017-06-24 04:21:38 -07003552
tierno1ec592d2020-06-16 15:29:47 +00003553 start_vlanid, end_vlanid = map(int, vlan_range.split("-"))
3554 if start_vlanid <= 0:
3555 raise vimconn.VimConnConflictException(
3556 "Invalid VLAN range for {}: {}. Start ID can not be zero. For VLAN "
sousaedu80135b92021-02-17 15:05:18 +01003557 "networks valid IDs are 1 to 4094 ".format(
3558 text_vlan_range, vlanID_range
3559 )
3560 )
3561
tierno1ec592d2020-06-16 15:29:47 +00003562 if end_vlanid > 4094:
3563 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003564 "Invalid VLAN range for {}: {}. End VLAN ID can not be "
3565 "greater than 4094. For VLAN networks valid IDs are 1 to 4094 ".format(
3566 text_vlan_range, vlanID_range
3567 )
3568 )
kate721d79b2017-06-24 04:21:38 -07003569
3570 if start_vlanid > end_vlanid:
tierno1ec592d2020-06-16 15:29:47 +00003571 raise vimconn.VimConnConflictException(
sousaedu80135b92021-02-17 15:05:18 +01003572 "Invalid VLAN range for {}: {}. You must provide '{}'"
3573 " in format start_ID - end_ID and start_ID < end_ID ".format(
3574 text_vlan_range, vlanID_range, text_vlan_range
3575 )
3576 )
kate721d79b2017-06-24 04:21:38 -07003577
tierno7edb6752016-03-21 17:37:52 +01003578 def get_hosts_info(self):
tierno1ec592d2020-06-16 15:29:47 +00003579 """Get the information of deployed hosts
3580 Returns the hosts content"""
tierno7edb6752016-03-21 17:37:52 +01003581 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003582 print("osconnector: Getting Host info from VIM")
sousaedu80135b92021-02-17 15:05:18 +01003583
tierno7edb6752016-03-21 17:37:52 +01003584 try:
tierno1ec592d2020-06-16 15:29:47 +00003585 h_list = []
tierno7edb6752016-03-21 17:37:52 +01003586 self._reload_connection()
3587 hypervisors = self.nova.hypervisors.list()
sousaedu80135b92021-02-17 15:05:18 +01003588
tierno7edb6752016-03-21 17:37:52 +01003589 for hype in hypervisors:
tierno1ec592d2020-06-16 15:29:47 +00003590 h_list.append(hype.to_dict())
sousaedu80135b92021-02-17 15:05:18 +01003591
tierno1ec592d2020-06-16 15:29:47 +00003592 return 1, {"hosts": h_list}
tierno7edb6752016-03-21 17:37:52 +01003593 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003594 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003595 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003596 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003597 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003598 error_text = (
3599 type(e).__name__
3600 + ": "
3601 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3602 )
3603
tierno1ec592d2020-06-16 15:29:47 +00003604 # TODO insert exception vimconn.HTTP_Unauthorized
3605 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003606 self.logger.debug("get_hosts_info " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003607
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003608 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003609
3610 def get_hosts(self, vim_tenant):
tierno1ec592d2020-06-16 15:29:47 +00003611 """Get the hosts and deployed instances
3612 Returns the hosts content"""
tierno7edb6752016-03-21 17:37:52 +01003613 r, hype_dict = self.get_hosts_info()
sousaedu80135b92021-02-17 15:05:18 +01003614
tierno1ec592d2020-06-16 15:29:47 +00003615 if r < 0:
tierno7edb6752016-03-21 17:37:52 +01003616 return r, hype_dict
sousaedu80135b92021-02-17 15:05:18 +01003617
tierno7edb6752016-03-21 17:37:52 +01003618 hypervisors = hype_dict["hosts"]
sousaedu80135b92021-02-17 15:05:18 +01003619
tierno7edb6752016-03-21 17:37:52 +01003620 try:
3621 servers = self.nova.servers.list()
3622 for hype in hypervisors:
3623 for server in servers:
sousaedu80135b92021-02-17 15:05:18 +01003624 if (
3625 server.to_dict()["OS-EXT-SRV-ATTR:hypervisor_hostname"]
3626 == hype["hypervisor_hostname"]
3627 ):
3628 if "vm" in hype:
3629 hype["vm"].append(server.id)
tierno7edb6752016-03-21 17:37:52 +01003630 else:
sousaedu80135b92021-02-17 15:05:18 +01003631 hype["vm"] = [server.id]
3632
tierno7edb6752016-03-21 17:37:52 +01003633 return 1, hype_dict
3634 except nvExceptions.NotFound as e:
tierno1ec592d2020-06-16 15:29:47 +00003635 error_value = -vimconn.HTTP_Not_Found
sousaedu80135b92021-02-17 15:05:18 +01003636 error_text = str(e) if len(e.args) == 0 else str(e.args[0])
tierno7edb6752016-03-21 17:37:52 +01003637 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
tierno1ec592d2020-06-16 15:29:47 +00003638 error_value = -vimconn.HTTP_Bad_Request
sousaedu80135b92021-02-17 15:05:18 +01003639 error_text = (
3640 type(e).__name__
3641 + ": "
3642 + (str(e) if len(e.args) == 0 else str(e.args[0]))
3643 )
3644
tierno1ec592d2020-06-16 15:29:47 +00003645 # TODO insert exception vimconn.HTTP_Unauthorized
3646 # if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01003647 self.logger.debug("get_hosts " + error_text)
sousaedu80135b92021-02-17 15:05:18 +01003648
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00003649 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01003650
Alexis Romerob70f4ed2022-03-11 18:00:49 +01003651 def new_affinity_group(self, affinity_group_data):
3652 """Adds a server group to VIM
3653 affinity_group_data contains a dictionary with information, keys:
3654 name: name in VIM for the server group
3655 type: affinity or anti-affinity
3656 scope: Only nfvi-node allowed
3657 Returns the server group identifier"""
3658 self.logger.debug("Adding Server Group '%s'", str(affinity_group_data))
3659
3660 try:
3661 name = affinity_group_data["name"]
3662 policy = affinity_group_data["type"]
3663
3664 self._reload_connection()
3665 new_server_group = self.nova.server_groups.create(name, policy)
3666
3667 return new_server_group.id
3668 except (
3669 ksExceptions.ClientException,
3670 nvExceptions.ClientException,
3671 ConnectionError,
3672 KeyError,
3673 ) as e:
3674 self._format_exception(e)
3675
3676 def get_affinity_group(self, affinity_group_id):
3677 """Obtain server group details from the VIM. Returns the server group detais as a dict"""
3678 self.logger.debug("Getting flavor '%s'", affinity_group_id)
3679 try:
3680 self._reload_connection()
3681 server_group = self.nova.server_groups.find(id=affinity_group_id)
3682
3683 return server_group.to_dict()
3684 except (
3685 nvExceptions.NotFound,
3686 nvExceptions.ClientException,
3687 ksExceptions.ClientException,
3688 ConnectionError,
3689 ) as e:
3690 self._format_exception(e)
3691
3692 def delete_affinity_group(self, affinity_group_id):
3693 """Deletes a server group from the VIM. Returns the old affinity_group_id"""
3694 self.logger.debug("Getting server group '%s'", affinity_group_id)
3695 try:
3696 self._reload_connection()
3697 self.nova.server_groups.delete(affinity_group_id)
3698
3699 return affinity_group_id
3700 except (
3701 nvExceptions.NotFound,
3702 ksExceptions.ClientException,
3703 nvExceptions.ClientException,
3704 ConnectionError,
3705 ) as e:
3706 self._format_exception(e)
elumalai8658c2c2022-04-28 19:09:31 +05303707
Patricia Reinoso17852162023-06-15 07:33:04 +00003708 def get_vdu_state(self, vm_id, host_is_required=False) -> list:
3709 """Getting the state of a VDU.
3710 Args:
3711 vm_id (str): ID of an instance
3712 host_is_required (Boolean): If the VIM account is non-admin, host info does not appear in server_dict
3713 and if this is set to True, it raises KeyError.
3714 Returns:
3715 vdu_data (list): VDU details including state, flavor, host_info, AZ
elumalai8658c2c2022-04-28 19:09:31 +05303716 """
3717 self.logger.debug("Getting the status of VM")
3718 self.logger.debug("VIM VM ID %s", vm_id)
Patricia Reinoso17852162023-06-15 07:33:04 +00003719 try:
3720 self._reload_connection()
3721 server_dict = self._find_nova_server(vm_id)
3722 srv_attr = "OS-EXT-SRV-ATTR:host"
3723 host_info = (
3724 server_dict[srv_attr] if host_is_required else server_dict.get(srv_attr)
3725 )
3726 vdu_data = [
3727 server_dict["status"],
3728 server_dict["flavor"]["id"],
3729 host_info,
3730 server_dict["OS-EXT-AZ:availability_zone"],
3731 ]
3732 self.logger.debug("vdu_data %s", vdu_data)
3733 return vdu_data
3734
3735 except Exception as e:
3736 self._format_exception(e)
elumalai8658c2c2022-04-28 19:09:31 +05303737
3738 def check_compute_availability(self, host, server_flavor_details):
3739 self._reload_connection()
3740 hypervisor_search = self.nova.hypervisors.search(
3741 hypervisor_match=host, servers=True
3742 )
3743 for hypervisor in hypervisor_search:
3744 hypervisor_id = hypervisor.to_dict()["id"]
3745 hypervisor_details = self.nova.hypervisors.get(hypervisor=hypervisor_id)
3746 hypervisor_dict = hypervisor_details.to_dict()
3747 hypervisor_temp = json.dumps(hypervisor_dict)
3748 hypervisor_json = json.loads(hypervisor_temp)
3749 resources_available = [
3750 hypervisor_json["free_ram_mb"],
3751 hypervisor_json["disk_available_least"],
3752 hypervisor_json["vcpus"] - hypervisor_json["vcpus_used"],
3753 ]
3754 compute_available = all(
3755 x > y for x, y in zip(resources_available, server_flavor_details)
3756 )
3757 if compute_available:
3758 return host
3759
3760 def check_availability_zone(
3761 self, old_az, server_flavor_details, old_host, host=None
3762 ):
3763 self._reload_connection()
3764 az_check = {"zone_check": False, "compute_availability": None}
3765 aggregates_list = self.nova.aggregates.list()
3766 for aggregate in aggregates_list:
3767 aggregate_details = aggregate.to_dict()
3768 aggregate_temp = json.dumps(aggregate_details)
3769 aggregate_json = json.loads(aggregate_temp)
3770 if aggregate_json["availability_zone"] == old_az:
3771 hosts_list = aggregate_json["hosts"]
3772 if host is not None:
3773 if host in hosts_list:
3774 az_check["zone_check"] = True
3775 available_compute_id = self.check_compute_availability(
3776 host, server_flavor_details
3777 )
3778 if available_compute_id is not None:
3779 az_check["compute_availability"] = available_compute_id
3780 else:
3781 for check_host in hosts_list:
3782 if check_host != old_host:
3783 available_compute_id = self.check_compute_availability(
3784 check_host, server_flavor_details
3785 )
3786 if available_compute_id is not None:
3787 az_check["zone_check"] = True
3788 az_check["compute_availability"] = available_compute_id
3789 break
3790 else:
3791 az_check["zone_check"] = True
3792 return az_check
3793
3794 def migrate_instance(self, vm_id, compute_host=None):
3795 """
3796 Migrate a vdu
3797 param:
3798 vm_id: ID of an instance
3799 compute_host: Host to migrate the vdu to
3800 """
3801 self._reload_connection()
3802 vm_state = False
Patricia Reinoso17852162023-06-15 07:33:04 +00003803 instance_state = self.get_vdu_state(vm_id, host_is_required=True)
elumalai8658c2c2022-04-28 19:09:31 +05303804 server_flavor_id = instance_state[1]
3805 server_hypervisor_name = instance_state[2]
3806 server_availability_zone = instance_state[3]
3807 try:
3808 server_flavor = self.nova.flavors.find(id=server_flavor_id).to_dict()
3809 server_flavor_details = [
3810 server_flavor["ram"],
3811 server_flavor["disk"],
3812 server_flavor["vcpus"],
3813 ]
3814 if compute_host == server_hypervisor_name:
3815 raise vimconn.VimConnException(
3816 "Unable to migrate instance '{}' to the same host '{}'".format(
3817 vm_id, compute_host
3818 ),
3819 http_code=vimconn.HTTP_Bad_Request,
3820 )
3821 az_status = self.check_availability_zone(
3822 server_availability_zone,
3823 server_flavor_details,
3824 server_hypervisor_name,
3825 compute_host,
3826 )
3827 availability_zone_check = az_status["zone_check"]
3828 available_compute_id = az_status.get("compute_availability")
3829
3830 if availability_zone_check is False:
3831 raise vimconn.VimConnException(
3832 "Unable to migrate instance '{}' to a different availability zone".format(
3833 vm_id
3834 ),
3835 http_code=vimconn.HTTP_Bad_Request,
3836 )
3837 if available_compute_id is not None:
Patricia Reinoso17852162023-06-15 07:33:04 +00003838 # disk_over_commit parameter for live_migrate method is not valid for Nova API version >= 2.25
elumalai8658c2c2022-04-28 19:09:31 +05303839 self.nova.servers.live_migrate(
3840 server=vm_id,
3841 host=available_compute_id,
3842 block_migration=True,
elumalai8658c2c2022-04-28 19:09:31 +05303843 )
3844 state = "MIGRATING"
3845 changed_compute_host = ""
3846 if state == "MIGRATING":
3847 vm_state = self.__wait_for_vm(vm_id, "ACTIVE")
Patricia Reinoso17852162023-06-15 07:33:04 +00003848 changed_compute_host = self.get_vdu_state(
3849 vm_id, host_is_required=True
3850 )[2]
elumalai8658c2c2022-04-28 19:09:31 +05303851 if vm_state and changed_compute_host == available_compute_id:
3852 self.logger.debug(
3853 "Instance '{}' migrated to the new compute host '{}'".format(
3854 vm_id, changed_compute_host
3855 )
3856 )
3857 return state, available_compute_id
3858 else:
3859 raise vimconn.VimConnException(
3860 "Migration Failed. Instance '{}' not moved to the new host {}".format(
3861 vm_id, available_compute_id
3862 ),
3863 http_code=vimconn.HTTP_Bad_Request,
3864 )
3865 else:
3866 raise vimconn.VimConnException(
3867 "Compute '{}' not available or does not have enough resources to migrate the instance".format(
3868 available_compute_id
3869 ),
3870 http_code=vimconn.HTTP_Bad_Request,
3871 )
3872 except (
3873 nvExceptions.BadRequest,
3874 nvExceptions.ClientException,
3875 nvExceptions.NotFound,
3876 ) as e:
3877 self._format_exception(e)
sritharan29a4c1a2022-05-05 12:15:04 +00003878
3879 def resize_instance(self, vm_id, new_flavor_id):
3880 """
3881 For resizing the vm based on the given
3882 flavor details
3883 param:
3884 vm_id : ID of an instance
3885 new_flavor_id : Flavor id to be resized
3886 Return the status of a resized instance
3887 """
3888 self._reload_connection()
3889 self.logger.debug("resize the flavor of an instance")
3890 instance_status, old_flavor_id, compute_host, az = self.get_vdu_state(vm_id)
3891 old_flavor_disk = self.nova.flavors.find(id=old_flavor_id).to_dict()["disk"]
3892 new_flavor_disk = self.nova.flavors.find(id=new_flavor_id).to_dict()["disk"]
3893 try:
3894 if instance_status == "ACTIVE" or instance_status == "SHUTOFF":
3895 if old_flavor_disk > new_flavor_disk:
3896 raise nvExceptions.BadRequest(
3897 400,
3898 message="Server disk resize failed. Resize to lower disk flavor is not allowed",
3899 )
3900 else:
3901 self.nova.servers.resize(server=vm_id, flavor=new_flavor_id)
3902 vm_state = self.__wait_for_vm(vm_id, "VERIFY_RESIZE")
3903 if vm_state:
3904 instance_resized_status = self.confirm_resize(vm_id)
3905 return instance_resized_status
3906 else:
3907 raise nvExceptions.BadRequest(
3908 409,
3909 message="Cannot 'resize' vm_state is in ERROR",
3910 )
3911
3912 else:
3913 self.logger.debug("ERROR : Instance is not in ACTIVE or SHUTOFF state")
3914 raise nvExceptions.BadRequest(
3915 409,
3916 message="Cannot 'resize' instance while it is in vm_state resized",
3917 )
3918 except (
3919 nvExceptions.BadRequest,
3920 nvExceptions.ClientException,
3921 nvExceptions.NotFound,
3922 ) as e:
3923 self._format_exception(e)
3924
3925 def confirm_resize(self, vm_id):
3926 """
3927 Confirm the resize of an instance
3928 param:
3929 vm_id: ID of an instance
3930 """
3931 self._reload_connection()
3932 self.nova.servers.confirm_resize(server=vm_id)
3933 if self.get_vdu_state(vm_id)[0] == "VERIFY_RESIZE":
3934 self.__wait_for_vm(vm_id, "ACTIVE")
3935 instance_status = self.get_vdu_state(vm_id)[0]
3936 return instance_status
Gulsum Aticid586d892023-02-13 18:40:03 +03003937
3938 def get_monitoring_data(self):
3939 try:
3940 self.logger.debug("Getting servers and ports data from Openstack VIMs.")
3941 self._reload_connection()
3942 all_servers = self.nova.servers.list(detailed=True)
vegallc53829d2023-06-01 00:47:44 -05003943 try:
3944 for server in all_servers:
3945 server.flavor["id"] = self.nova.flavors.find(
3946 name=server.flavor["original_name"]
3947 ).id
3948 except nClient.exceptions.NotFound as e:
3949 self.logger.warning(str(e.message))
Gulsum Aticid586d892023-02-13 18:40:03 +03003950 all_ports = self.neutron.list_ports()
3951 return all_servers, all_ports
3952 except (
3953 vimconn.VimConnException,
3954 vimconn.VimConnNotFoundException,
3955 vimconn.VimConnConnectionException,
3956 ) as e:
3957 raise vimconn.VimConnException(
3958 f"Exception in monitoring while getting VMs and ports status: {str(e)}"
3959 )