blob: 06bc97031666e1a9c9a80c6cfb91032dacecc942 [file] [log] [blame]
seryio34478552019-05-23 14:50:49 +02001# -*- coding: utf-8 -*-
tiernob569e4b2019-11-21 16:10:32 +00002##
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14##
seryio34478552019-05-23 14:50:49 +020015
jamartinezv14a823d2019-08-01 11:45:15 +020016import base64
seryio34478552019-05-23 14:50:49 +020017import logging
sousaedu049cbb12022-01-05 11:39:35 +000018from os import getenv
lloretgalleg45220152019-10-29 11:53:49 +010019import re
seryio34478552019-05-23 14:50:49 +020020
sousaedu049cbb12022-01-05 11:39:35 +000021from azure.core.exceptions import ResourceNotFoundError
lloretgallegc8138e82021-03-31 07:12:06 +000022from azure.identity import ClientSecretCredential
seryio34478552019-05-23 14:50:49 +020023from azure.mgmt.compute import ComputeManagementClient
sousaedu049cbb12022-01-05 11:39:35 +000024from azure.mgmt.network import NetworkManagementClient
25from azure.mgmt.resource import ResourceManagementClient
lloretgallegc8138e82021-03-31 07:12:06 +000026from azure.profiles import ProfileDefinition
sousaedu049cbb12022-01-05 11:39:35 +000027from cryptography.hazmat.backends import default_backend as crypto_default_backend
lloretgallegc8138e82021-03-31 07:12:06 +000028from cryptography.hazmat.primitives import serialization as crypto_serialization
29from cryptography.hazmat.primitives.asymmetric import rsa
sousaedu049cbb12022-01-05 11:39:35 +000030from msrest.exceptions import AuthenticationError
31from msrestazure.azure_exceptions import CloudError
32import msrestazure.tools as azure_tools
33import netaddr
34from osm_ro_plugin import vimconn
35from requests.exceptions import ConnectionError
lloretgallegc8138e82021-03-31 07:12:06 +000036
37__author__ = "Isabel Lloret, Sergio Gonzalez, Alfonso Tierno, Gerardo Garcia"
sousaedu80135b92021-02-17 15:05:18 +010038__date__ = "$18-apr-2019 23:59:59$"
lloretgalleg45220152019-10-29 11:53:49 +010039
tiernodeb74b22019-05-27 10:24:50 +000040
sousaedu80135b92021-02-17 15:05:18 +010041if getenv("OSMRO_PDB_DEBUG"):
jamartinezv14a823d2019-08-01 11:45:15 +020042 import sys
sousaedu80135b92021-02-17 15:05:18 +010043
jamartinezv14a823d2019-08-01 11:45:15 +020044 print(sys.path)
45 import pdb
sousaedu80135b92021-02-17 15:05:18 +010046
jamartinezv14a823d2019-08-01 11:45:15 +020047 pdb.set_trace()
seryio34478552019-05-23 14:50:49 +020048
49
garciadeblasc878ac42021-05-27 12:00:30 +020050def find_in_list(the_list, condition_lambda):
51 for item in the_list:
52 if condition_lambda(item):
53 return item
54 else:
55 return None
56
57
tierno72774862020-05-04 11:44:15 +000058class vimconnector(vimconn.VimConnector):
lloretgalleg45220152019-10-29 11:53:49 +010059 # Translate azure provisioning state to OSM provision state
60 # The first three ones are the transitional status once a user initiated action has been requested
61 # Once the operation is complete, it will transition into the states Succeeded or Failed
62 # https://docs.microsoft.com/en-us/azure/virtual-machines/windows/states-lifecycle
tierno84efdc12019-05-29 09:29:01 +000063 provision_state2osm = {
lloretgalleg45220152019-10-29 11:53:49 +010064 "Creating": "BUILD",
tierno84efdc12019-05-29 09:29:01 +000065 "Updating": "BUILD",
lloretgalleg45220152019-10-29 11:53:49 +010066 "Deleting": "INACTIVE",
67 "Succeeded": "ACTIVE",
sousaedu80135b92021-02-17 15:05:18 +010068 "Failed": "ERROR",
lloretgalleg45220152019-10-29 11:53:49 +010069 }
70
71 # Translate azure power state to OSM provision state
72 power_state2osm = {
73 "starting": "INACTIVE",
74 "running": "ACTIVE",
75 "stopping": "INACTIVE",
76 "stopped": "INACTIVE",
77 "unknown": "OTHER",
78 "deallocated": "BUILD",
sousaedu80135b92021-02-17 15:05:18 +010079 "deallocating": "BUILD",
tierno84efdc12019-05-29 09:29:01 +000080 }
81
lloretgallegc8138e82021-03-31 07:12:06 +000082 # TODO - review availability zones
lloretgalleg064b05a2019-11-29 14:46:31 +010083 AZURE_ZONES = ["1", "2", "3"]
84
lloretgallegc8138e82021-03-31 07:12:06 +000085 AZURE_COMPUTE_MGMT_CLIENT_API_VERSION = "2021-03-01"
86 AZURE_COMPUTE_MGMT_PROFILE_TAG = "azure.mgmt.compute.ComputeManagementClient"
87 AZURE_COMPUTE_MGMT_PROFILE = ProfileDefinition(
88 {
89 AZURE_COMPUTE_MGMT_PROFILE_TAG: {
90 None: AZURE_COMPUTE_MGMT_CLIENT_API_VERSION,
91 "availability_sets": "2020-12-01",
92 "dedicated_host_groups": "2020-12-01",
93 "dedicated_hosts": "2020-12-01",
94 "disk_accesses": "2020-12-01",
95 "disk_encryption_sets": "2020-12-01",
96 "disk_restore_point": "2020-12-01",
97 "disks": "2020-12-01",
98 "galleries": "2020-09-30",
99 "gallery_application_versions": "2020-09-30",
100 "gallery_applications": "2020-09-30",
101 "gallery_image_versions": "2020-09-30",
102 "gallery_images": "2020-09-30",
103 "gallery_sharing_profile": "2020-09-30",
104 "images": "2020-12-01",
105 "log_analytics": "2020-12-01",
106 "operations": "2020-12-01",
107 "proximity_placement_groups": "2020-12-01",
108 "resource_skus": "2019-04-01",
109 "shared_galleries": "2020-09-30",
110 "shared_gallery_image_versions": "2020-09-30",
111 "shared_gallery_images": "2020-09-30",
112 "snapshots": "2020-12-01",
113 "ssh_public_keys": "2020-12-01",
114 "usage": "2020-12-01",
115 "virtual_machine_extension_images": "2020-12-01",
116 "virtual_machine_extensions": "2020-12-01",
117 "virtual_machine_images": "2020-12-01",
118 "virtual_machine_images_edge_zone": "2020-12-01",
119 "virtual_machine_run_commands": "2020-12-01",
120 "virtual_machine_scale_set_extensions": "2020-12-01",
121 "virtual_machine_scale_set_rolling_upgrades": "2020-12-01",
122 "virtual_machine_scale_set_vm_extensions": "2020-12-01",
123 "virtual_machine_scale_set_vm_run_commands": "2020-12-01",
124 "virtual_machine_scale_set_vms": "2020-12-01",
125 "virtual_machine_scale_sets": "2020-12-01",
126 "virtual_machine_sizes": "2020-12-01",
127 "virtual_machines": "2020-12-01",
128 }
129 },
130 AZURE_COMPUTE_MGMT_PROFILE_TAG + " osm",
131 )
132
133 AZURE_RESOURCE_MGMT_CLIENT_API_VERSION = "2020-10-01"
134 AZURE_RESOURCE_MGMT_PROFILE_TAG = (
135 "azure.mgmt.resource.resources.ResourceManagementClient"
136 )
137 AZURE_RESOURCE_MGMT_PROFILE = ProfileDefinition(
138 {
139 AZURE_RESOURCE_MGMT_PROFILE_TAG: {
140 None: AZURE_RESOURCE_MGMT_CLIENT_API_VERSION,
141 }
142 },
143 AZURE_RESOURCE_MGMT_PROFILE_TAG + " osm",
144 )
145
146 AZURE_NETWORK_MGMT_CLIENT_API_VERSION = "2020-11-01"
147 AZURE_NETWORK_MGMT_PROFILE_TAG = "azure.mgmt.network.NetworkManagementClient"
148 AZURE_NETWORK_MGMT_PROFILE = ProfileDefinition(
149 {
150 AZURE_NETWORK_MGMT_PROFILE_TAG: {
151 None: AZURE_NETWORK_MGMT_CLIENT_API_VERSION,
152 "firewall_policy_rule_groups": "2020-04-01",
153 "interface_endpoints": "2019-02-01",
154 "p2_svpn_server_configurations": "2019-07-01",
155 }
156 },
157 AZURE_NETWORK_MGMT_PROFILE_TAG + " osm",
158 )
159
sousaedu80135b92021-02-17 15:05:18 +0100160 def __init__(
161 self,
162 uuid,
163 name,
164 tenant_id,
165 tenant_name,
166 url,
167 url_admin=None,
168 user=None,
169 passwd=None,
170 log_level=None,
171 config={},
172 persistent_info={},
173 ):
lloretgalleg45220152019-10-29 11:53:49 +0100174 """
175 Constructor of VIM. Raise an exception is some needed parameter is missing, but it must not do any connectivity
176 checking against the VIM
177 Using common constructor parameters.
178 In this case: config must include the following parameters:
179 subscription_id: assigned azure subscription identifier
180 region_name: current region for azure network
181 resource_group: used for all azure created resources
182 vnet_name: base vnet for azure, created networks will be subnets from this base network
183 config may also include the following parameter:
184 flavors_pattern: pattern that will be used to select a range of vm sizes, for example
185 "^((?!Standard_B).)*$" will filter out Standard_B range that is cheap but is very overused
186 "^Standard_B" will select a serie B maybe for test environment
187 """
sousaedu80135b92021-02-17 15:05:18 +0100188 vimconn.VimConnector.__init__(
189 self,
190 uuid,
191 name,
192 tenant_id,
193 tenant_name,
194 url,
195 url_admin,
196 user,
197 passwd,
198 log_level,
199 config,
200 persistent_info,
201 )
seryio34478552019-05-23 14:50:49 +0200202
lloretgalleg45220152019-10-29 11:53:49 +0100203 # Variable that indicates if client must be reloaded or initialized
204 self.reload_client = True
205
aguilard5d27a332023-11-02 16:52:56 +0000206 self.vnet_address_space = []
lloretgallegc8138e82021-03-31 07:12:06 +0000207
seryio34478552019-05-23 14:50:49 +0200208 # LOGGER
sousaedu80135b92021-02-17 15:05:18 +0100209 self.logger = logging.getLogger("ro.vim.azure")
seryio34478552019-05-23 14:50:49 +0200210 if log_level:
seryio34478552019-05-23 14:50:49 +0200211 self.logger.setLevel(getattr(logging, log_level))
212
sousaedu80135b92021-02-17 15:05:18 +0100213 self.tenant = tenant_id or tenant_name
tierno30d0d6d2019-05-27 08:14:01 +0000214
lloretgalleg45220152019-10-29 11:53:49 +0100215 # Store config to create azure subscription later
tiernob569e4b2019-11-21 16:10:32 +0000216 self._config = {
217 "user": user,
218 "passwd": passwd,
sousaedu80135b92021-02-17 15:05:18 +0100219 "tenant": tenant_id or tenant_name,
tiernob569e4b2019-11-21 16:10:32 +0000220 }
tierno30d0d6d2019-05-27 08:14:01 +0000221
222 # SUBSCRIPTION
sousaedu80135b92021-02-17 15:05:18 +0100223 if "subscription_id" in config:
224 self._config["subscription_id"] = config.get("subscription_id")
225 # self.logger.debug("Setting subscription to: %s", self.config["subscription_id"])
seryio34478552019-05-23 14:50:49 +0200226 else:
sousaedu80135b92021-02-17 15:05:18 +0100227 raise vimconn.VimConnException("Subscription not specified")
lloretgalleg45220152019-10-29 11:53:49 +0100228
tierno30d0d6d2019-05-27 08:14:01 +0000229 # RESOURCE_GROUP
sousaedu80135b92021-02-17 15:05:18 +0100230 if "resource_group" in config:
231 self.resource_group = config.get("resource_group")
seryio34478552019-05-23 14:50:49 +0200232 else:
sousaedu80135b92021-02-17 15:05:18 +0100233 raise vimconn.VimConnException(
234 "Azure resource_group is not specified at config"
235 )
lloretgalleg45220152019-10-29 11:53:49 +0100236
garciadeblas66af6942021-10-26 18:38:49 +0200237 # REGION
238 if "region_name" in config:
239 self.region = config.get("region_name")
240 else:
241 raise vimconn.VimConnException(
242 "Azure region_name is not specified at config"
243 )
244
seryio34478552019-05-23 14:50:49 +0200245 # VNET_NAME
sousaedu80135b92021-02-17 15:05:18 +0100246 if "vnet_name" in config:
seryio34478552019-05-23 14:50:49 +0200247 self.vnet_name = config["vnet_name"]
sousaedu80135b92021-02-17 15:05:18 +0100248
gallardodd821752022-01-17 17:37:51 +0000249 # VNET_RESOURCE_GROUP
250 self.vnet_resource_group = config.get("vnet_resource_group")
251
lloretgallegc8138e82021-03-31 07:12:06 +0000252 # TODO - not used, do anything about it?
seryio34478552019-05-23 14:50:49 +0200253 # public ssh key
sousaedu80135b92021-02-17 15:05:18 +0100254 self.pub_key = config.get("pub_key")
lloretgalleg45220152019-10-29 11:53:49 +0100255
lloretgallegc8138e82021-03-31 07:12:06 +0000256 # TODO - check default user for azure
257 # default admin user
258 self._default_admin_user = "azureuser"
259
lloretgalleg45220152019-10-29 11:53:49 +0100260 # flavor pattern regex
sousaedu80135b92021-02-17 15:05:18 +0100261 if "flavors_pattern" in config:
262 self._config["flavors_pattern"] = config["flavors_pattern"]
263
garciadeblasc878ac42021-05-27 12:00:30 +0200264 def _find_in_capabilities(self, capabilities, name):
265 cap = find_in_list(capabilities, lambda c: c["name"] == name)
266 if cap:
267 return cap.get("value")
268 else:
269 return None
270
seryio34478552019-05-23 14:50:49 +0200271 def _reload_connection(self):
tiernodeb74b22019-05-27 10:24:50 +0000272 """
lloretgalleg45220152019-10-29 11:53:49 +0100273 Called before any operation, checks python azure clients
tiernodeb74b22019-05-27 10:24:50 +0000274 """
lloretgalleg45220152019-10-29 11:53:49 +0100275 if self.reload_client:
sousaedu80135b92021-02-17 15:05:18 +0100276 self.logger.debug("reloading azure client")
277
lloretgalleg45220152019-10-29 11:53:49 +0100278 try:
lloretgallegc8138e82021-03-31 07:12:06 +0000279 self.credentials = ClientSecretCredential(
lloretgalleg45220152019-10-29 11:53:49 +0100280 client_id=self._config["user"],
lloretgallegc8138e82021-03-31 07:12:06 +0000281 client_secret=self._config["passwd"],
282 tenant_id=self._config["tenant"],
lloretgalleg45220152019-10-29 11:53:49 +0100283 )
sousaedu80135b92021-02-17 15:05:18 +0100284 self.conn = ResourceManagementClient(
lloretgallegc8138e82021-03-31 07:12:06 +0000285 self.credentials,
286 self._config["subscription_id"],
287 profile=self.AZURE_RESOURCE_MGMT_PROFILE,
sousaedu80135b92021-02-17 15:05:18 +0100288 )
289 self.conn_compute = ComputeManagementClient(
lloretgallegc8138e82021-03-31 07:12:06 +0000290 self.credentials,
291 self._config["subscription_id"],
292 profile=self.AZURE_COMPUTE_MGMT_PROFILE,
sousaedu80135b92021-02-17 15:05:18 +0100293 )
294 self.conn_vnet = NetworkManagementClient(
lloretgallegc8138e82021-03-31 07:12:06 +0000295 self.credentials,
296 self._config["subscription_id"],
297 profile=self.AZURE_NETWORK_MGMT_PROFILE,
sousaedu80135b92021-02-17 15:05:18 +0100298 )
lloretgalleg45220152019-10-29 11:53:49 +0100299 self._check_or_create_resource_group()
300 self._check_or_create_vnet()
301
302 # Set to client created
303 self.reload_client = False
304 except Exception as e:
305 self._format_vimconn_exception(e)
seryio34478552019-05-23 14:50:49 +0200306
307 def _get_resource_name_from_resource_id(self, resource_id):
lloretgalleg45220152019-10-29 11:53:49 +0100308 """
309 Obtains resource_name from the azure complete identifier: resource_name will always be last item
310 """
jamartinezv14a823d2019-08-01 11:45:15 +0200311 try:
sousaedu80135b92021-02-17 15:05:18 +0100312 resource = str(resource_id.split("/")[-1])
313
jamartinezv14a823d2019-08-01 11:45:15 +0200314 return resource
315 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +0100316 raise vimconn.VimConnException(
317 "Unable to get resource name from resource_id '{}' Error: '{}'".format(
318 resource_id, e
319 )
320 )
seryio34478552019-05-23 14:50:49 +0200321
322 def _get_location_from_resource_group(self, resource_group_name):
jamartinezv14a823d2019-08-01 11:45:15 +0200323 try:
lloretgalleg45220152019-10-29 11:53:49 +0100324 location = self.conn.resource_groups.get(resource_group_name).location
sousaedu80135b92021-02-17 15:05:18 +0100325
jamartinezv14a823d2019-08-01 11:45:15 +0200326 return location
tierno1ec592d2020-06-16 15:29:47 +0000327 except Exception:
sousaedu80135b92021-02-17 15:05:18 +0100328 raise vimconn.VimConnNotFoundException(
329 "Location '{}' not found".format(resource_group_name)
330 )
jamartinezv14a823d2019-08-01 11:45:15 +0200331
seryio34478552019-05-23 14:50:49 +0200332 def _get_resource_group_name_from_resource_id(self, resource_id):
jamartinezv14a823d2019-08-01 11:45:15 +0200333 try:
sousaedu80135b92021-02-17 15:05:18 +0100334 rg = str(resource_id.split("/")[4])
335
jamartinezv14a823d2019-08-01 11:45:15 +0200336 return rg
tierno1ec592d2020-06-16 15:29:47 +0000337 except Exception:
sousaedu80135b92021-02-17 15:05:18 +0100338 raise vimconn.VimConnException(
339 "Unable to get resource group from invalid resource_id format '{}'".format(
340 resource_id
341 )
342 )
jamartinezv14a823d2019-08-01 11:45:15 +0200343
344 def _get_net_name_from_resource_id(self, resource_id):
jamartinezv14a823d2019-08-01 11:45:15 +0200345 try:
sousaedu80135b92021-02-17 15:05:18 +0100346 net_name = str(resource_id.split("/")[8])
347
jamartinezv14a823d2019-08-01 11:45:15 +0200348 return net_name
tierno1ec592d2020-06-16 15:29:47 +0000349 except Exception:
sousaedu80135b92021-02-17 15:05:18 +0100350 raise vimconn.VimConnException(
351 "Unable to get azure net_name from invalid resource_id format '{}'".format(
352 resource_id
353 )
354 )
seryio34478552019-05-23 14:50:49 +0200355
356 def _check_subnets_for_vm(self, net_list):
tierno30d0d6d2019-05-27 08:14:01 +0000357 # All subnets must belong to the same resource group and vnet
lloretgallegc8138e82021-03-31 07:12:06 +0000358 # All subnets must belong to the same resource group anded vnet
sousaedu80135b92021-02-17 15:05:18 +0100359 rg_vnet = set(
360 self._get_resource_group_name_from_resource_id(net["net_id"])
361 + self._get_net_name_from_resource_id(net["net_id"])
362 for net in net_list
363 )
seryio34478552019-05-23 14:50:49 +0200364
lloretgalleg45220152019-10-29 11:53:49 +0100365 if len(rg_vnet) != 1:
sousaedu80135b92021-02-17 15:05:18 +0100366 raise self._format_vimconn_exception(
367 "Azure VMs can only attach to subnets in same VNET"
368 )
seryio34478552019-05-23 14:50:49 +0200369
lloretgalleg45220152019-10-29 11:53:49 +0100370 def _format_vimconn_exception(self, e):
tiernodeb74b22019-05-27 10:24:50 +0000371 """
lloretgalleg45220152019-10-29 11:53:49 +0100372 Transforms a generic or azure exception to a vimcommException
tiernodeb74b22019-05-27 10:24:50 +0000373 """
lloretgallegc8138e82021-03-31 07:12:06 +0000374 self.logger.error("Azure plugin error: {}".format(e))
tierno72774862020-05-04 11:44:15 +0000375 if isinstance(e, vimconn.VimConnException):
lloretgallegc8138e82021-03-31 07:12:06 +0000376 raise e
lloretgalleg45220152019-10-29 11:53:49 +0100377 elif isinstance(e, AuthenticationError):
sousaedu80135b92021-02-17 15:05:18 +0100378 raise vimconn.VimConnAuthException(type(e).__name__ + ": " + str(e))
lloretgalleg45220152019-10-29 11:53:49 +0100379 elif isinstance(e, ConnectionError):
sousaedu80135b92021-02-17 15:05:18 +0100380 raise vimconn.VimConnConnectionException(type(e).__name__ + ": " + str(e))
lloretgalleg45220152019-10-29 11:53:49 +0100381 else:
382 # In case of generic error recreate client
383 self.reload_client = True
sousaedu80135b92021-02-17 15:05:18 +0100384
385 raise vimconn.VimConnException(type(e).__name__ + ": " + str(e))
seryio34478552019-05-23 14:50:49 +0200386
387 def _check_or_create_resource_group(self):
tiernodeb74b22019-05-27 10:24:50 +0000388 """
lloretgalleg45220152019-10-29 11:53:49 +0100389 Creates the base resource group if it does not exist
tiernodeb74b22019-05-27 10:24:50 +0000390 """
lloretgalleg45220152019-10-29 11:53:49 +0100391 try:
392 rg_exists = self.conn.resource_groups.check_existence(self.resource_group)
sousaedu80135b92021-02-17 15:05:18 +0100393
lloretgalleg45220152019-10-29 11:53:49 +0100394 if not rg_exists:
395 self.logger.debug("create base rgroup: %s", self.resource_group)
sousaedu80135b92021-02-17 15:05:18 +0100396 self.conn.resource_groups.create_or_update(
397 self.resource_group, {"location": self.region}
398 )
lloretgalleg45220152019-10-29 11:53:49 +0100399 except Exception as e:
400 self._format_vimconn_exception(e)
tiernodeb74b22019-05-27 10:24:50 +0000401
402 def _check_or_create_vnet(self):
lloretgalleg45220152019-10-29 11:53:49 +0100403 """
404 Try to get existent base vnet, in case it does not exist it creates it
405 """
tiernodeb74b22019-05-27 10:24:50 +0000406 try:
sousaedu80135b92021-02-17 15:05:18 +0100407 vnet = self.conn_vnet.virtual_networks.get(
gallardodd821752022-01-17 17:37:51 +0000408 self.vnet_resource_group or self.resource_group, self.vnet_name
sousaedu80135b92021-02-17 15:05:18 +0100409 )
aguilard5d27a332023-11-02 16:52:56 +0000410 self.vnet_address_space = vnet.address_space.address_prefixes
tierno24620412019-06-03 14:05:08 +0000411 self.vnet_id = vnet.id
sousaedu80135b92021-02-17 15:05:18 +0100412
tierno84efdc12019-05-29 09:29:01 +0000413 return
414 except CloudError as e:
lloretgalleg45220152019-10-29 11:53:49 +0100415 if e.error.error and "notfound" in e.error.error.lower():
aticig7b521f72022-07-15 00:43:09 +0300416 self.logger.exception("CloudError Exception occured.")
lloretgalleg45220152019-10-29 11:53:49 +0100417 # continue and create it
tierno84efdc12019-05-29 09:29:01 +0000418 else:
lloretgalleg45220152019-10-29 11:53:49 +0100419 self._format_vimconn_exception(e)
420
421 # if it does not exist, create it
tiernodeb74b22019-05-27 10:24:50 +0000422 try:
423 vnet_params = {
sousaedu80135b92021-02-17 15:05:18 +0100424 "location": self.region,
425 "address_space": {"address_prefixes": ["10.0.0.0/8"]},
tiernodeb74b22019-05-27 10:24:50 +0000426 }
aguilard5d27a332023-11-02 16:52:56 +0000427 self.vnet_address_space = ["10.0.0.0/8"]
jamartinezv14a823d2019-08-01 11:45:15 +0200428
lloretgalleg45220152019-10-29 11:53:49 +0100429 self.logger.debug("create base vnet: %s", self.vnet_name)
lloretgallegc8138e82021-03-31 07:12:06 +0000430 self.conn_vnet.virtual_networks.begin_create_or_update(
gallardodd821752022-01-17 17:37:51 +0000431 self.vnet_resource_group or self.resource_group,
432 self.vnet_name,
433 vnet_params,
sousaedu80135b92021-02-17 15:05:18 +0100434 )
435 vnet = self.conn_vnet.virtual_networks.get(
gallardodd821752022-01-17 17:37:51 +0000436 self.vnet_resource_group or self.resource_group, self.vnet_name
sousaedu80135b92021-02-17 15:05:18 +0100437 )
tierno24620412019-06-03 14:05:08 +0000438 self.vnet_id = vnet.id
tiernodeb74b22019-05-27 10:24:50 +0000439 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +0100440 self._format_vimconn_exception(e)
seryio34478552019-05-23 14:50:49 +0200441
sousaedu80135b92021-02-17 15:05:18 +0100442 def new_network(
443 self,
444 net_name,
445 net_type,
446 ip_profile=None,
447 shared=False,
448 provider_network_profile=None,
449 ):
tiernodeb74b22019-05-27 10:24:50 +0000450 """
451 Adds a tenant network to VIM
452 :param net_name: name of the network
lloretgalleg45220152019-10-29 11:53:49 +0100453 :param net_type: not used for azure networks
tiernodeb74b22019-05-27 10:24:50 +0000454 :param ip_profile: is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
seryio34478552019-05-23 14:50:49 +0200455 'ip-version': can be one of ['IPv4','IPv6']
456 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
lloretgalleg45220152019-10-29 11:53:49 +0100457 'gateway-address': (Optional) ip_schema, that is X.X.X.X, not implemented for azure connector
458 'dns-address': (Optional) ip_schema, not implemented for azure connector
459 'dhcp': (Optional) dict containing, not implemented for azure connector
seryio34478552019-05-23 14:50:49 +0200460 'enabled': {'type': 'boolean'},
461 'start-address': ip_schema, first IP to grant
462 'count': number of IPs to grant.
lloretgalleg45220152019-10-29 11:53:49 +0100463 :param shared: Not allowed for Azure Connector
kbsuba85c54d2019-10-17 16:30:32 +0000464 :param provider_network_profile: (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk}
tiernodeb74b22019-05-27 10:24:50 +0000465 :return: a tuple with the network identifier and created_items, or raises an exception on error
seryio34478552019-05-23 14:50:49 +0200466 created_items can be None or a dictionary where this method can include key-values that will be passed to
467 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
468 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
469 as not present.
tiernodeb74b22019-05-27 10:24:50 +0000470 """
seryio34478552019-05-23 14:50:49 +0200471 return self._new_subnet(net_name, ip_profile)
472
473 def _new_subnet(self, net_name, ip_profile):
tiernodeb74b22019-05-27 10:24:50 +0000474 """
lloretgalleg45220152019-10-29 11:53:49 +0100475 Adds a tenant network to VIM. It creates a new subnet at existing base vnet
476 :param net_name: subnet name
tiernodeb74b22019-05-27 10:24:50 +0000477 :param ip_profile:
lloretgalleg45220152019-10-29 11:53:49 +0100478 subnet-address: if it is not provided a subnet/24 in the default vnet is created,
479 otherwise it creates a subnet in the indicated address
480 :return: a tuple with the network identifier and created_items, or raises an exception on error
tiernodeb74b22019-05-27 10:24:50 +0000481 """
sousaedu80135b92021-02-17 15:05:18 +0100482 self.logger.debug("create subnet name %s, ip_profile %s", net_name, ip_profile)
seryio34478552019-05-23 14:50:49 +0200483 self._reload_connection()
seryio34478552019-05-23 14:50:49 +0200484
485 if ip_profile is None:
tierno24620412019-06-03 14:05:08 +0000486 # get a non used vnet ip range /24 and allocate automatically inside the range self.vnet_address_space
487 used_subnets = self.get_network_list()
aguilard5d27a332023-11-02 16:52:56 +0000488 for space in self.vnet_address_space:
489 for ip_range in netaddr.IPNetwork(space).subnet(24):
490 for used_subnet in used_subnets:
491 subnet_range = netaddr.IPNetwork(used_subnet["cidr_block"])
sousaedu80135b92021-02-17 15:05:18 +0100492
aguilard5d27a332023-11-02 16:52:56 +0000493 if subnet_range in ip_range or ip_range in subnet_range:
494 # this range overlaps with an existing subnet ip range. Breaks and look for another
495 break
496 else:
497 ip_profile = {"subnet_address": str(ip_range)}
498 self.logger.debug(
499 "dinamically obtained ip_profile: %s", ip_range
500 )
tierno24620412019-06-03 14:05:08 +0000501 break
aguilard5d27a332023-11-02 16:52:56 +0000502 if ip_profile is not None:
tierno24620412019-06-03 14:05:08 +0000503 break
504 else:
sousaedu80135b92021-02-17 15:05:18 +0100505 raise vimconn.VimConnException(
506 "Cannot find a non-used subnet range in {}".format(
507 self.vnet_address_space
508 )
509 )
jamartinezv14a823d2019-08-01 11:45:15 +0200510 else:
sousaedu80135b92021-02-17 15:05:18 +0100511 ip_profile = {"subnet_address": ip_profile["subnet_address"]}
seryio34478552019-05-23 14:50:49 +0200512
513 try:
tiernob569e4b2019-11-21 16:10:32 +0000514 # subnet_name = "{}-{}".format(net_name[:24], uuid4())
sousaedu80135b92021-02-17 15:05:18 +0100515 subnet_params = {"address_prefix": ip_profile["subnet_address"]}
lloretgalleg45220152019-10-29 11:53:49 +0100516 # Assign a not duplicated net name
517 subnet_name = self._get_unused_subnet_name(net_name)
seryio34478552019-05-23 14:50:49 +0200518
sousaedu80135b92021-02-17 15:05:18 +0100519 self.logger.debug("creating subnet_name: {}".format(subnet_name))
lloretgallegc8138e82021-03-31 07:12:06 +0000520 async_creation = self.conn_vnet.subnets.begin_create_or_update(
gallardodd821752022-01-17 17:37:51 +0000521 self.vnet_resource_group or self.resource_group,
522 self.vnet_name,
523 subnet_name,
524 subnet_params,
sousaedu80135b92021-02-17 15:05:18 +0100525 )
lloretgalleg45220152019-10-29 11:53:49 +0100526 async_creation.wait()
lloretgallegc8138e82021-03-31 07:12:06 +0000527 # TODO - do not wait here, check where it is used
sousaedu80135b92021-02-17 15:05:18 +0100528 self.logger.debug("created subnet_name: {}".format(subnet_name))
lloretgalleg45220152019-10-29 11:53:49 +0100529
jamartinezv14a823d2019-08-01 11:45:15 +0200530 return "{}/subnets/{}".format(self.vnet_id, subnet_name), None
seryio34478552019-05-23 14:50:49 +0200531 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +0100532 self._format_vimconn_exception(e)
533
534 def _get_unused_subnet_name(self, subnet_name):
535 """
536 Adds a prefix to the subnet_name with a number in case the indicated name is repeated
537 Checks subnets with the indicated name (without suffix) and adds a suffix with a number
538 """
gallardodd821752022-01-17 17:37:51 +0000539 all_subnets = self.conn_vnet.subnets.list(
540 self.vnet_resource_group or self.resource_group, self.vnet_name
541 )
lloretgalleg45220152019-10-29 11:53:49 +0100542 # Filter to subnets starting with the indicated name
sousaedu80135b92021-02-17 15:05:18 +0100543 subnets = list(
544 filter(lambda subnet: (subnet.name.startswith(subnet_name)), all_subnets)
545 )
lloretgalleg45220152019-10-29 11:53:49 +0100546 net_names = [str(subnet.name) for subnet in subnets]
547
548 # get the name with the first not used suffix
549 name_suffix = 0
tiernob569e4b2019-11-21 16:10:32 +0000550 # name = subnet_name + "-" + str(name_suffix)
551 name = subnet_name # first subnet created will have no prefix
lloretgalleg45220152019-10-29 11:53:49 +0100552 while name in net_names:
553 name_suffix += 1
554 name = subnet_name + "-" + str(name_suffix)
sousaedu80135b92021-02-17 15:05:18 +0100555
lloretgalleg45220152019-10-29 11:53:49 +0100556 return name
seryio34478552019-05-23 14:50:49 +0200557
garciadeblas66af6942021-10-26 18:38:49 +0200558 def _create_nic(self, net, nic_name, region=None, static_ip=None, created_items={}):
sousaedu80135b92021-02-17 15:05:18 +0100559 self.logger.debug("create nic name %s, net_name %s", nic_name, net)
seryio34478552019-05-23 14:50:49 +0200560 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +0200561
sousaedu80135b92021-02-17 15:05:18 +0100562 subnet_id = net["net_id"]
sousaedu89278b82021-11-19 01:01:32 +0000563 location = self.region or self._get_location_from_resource_group(
564 self.resource_group
565 )
gallardodd821752022-01-17 17:37:51 +0000566
jamartinezv14a823d2019-08-01 11:45:15 +0200567 try:
sousaedu80135b92021-02-17 15:05:18 +0100568 net_ifz = {"location": location}
569 net_ip_config = {
570 "name": nic_name + "-ipconfiguration",
571 "subnet": {"id": subnet_id},
572 }
lloretgallegbfc28b02019-11-21 09:31:16 +0100573
sousaedu80135b92021-02-17 15:05:18 +0100574 if static_ip:
575 net_ip_config["privateIPAddress"] = static_ip
576 net_ip_config["privateIPAllocationMethod"] = "Static"
577
578 net_ifz["ip_configurations"] = [net_ip_config]
579 mac_address = net.get("mac_address")
580
581 if mac_address:
582 net_ifz["mac_address"] = mac_address
583
lloretgallegc8138e82021-03-31 07:12:06 +0000584 async_nic_creation = (
585 self.conn_vnet.network_interfaces.begin_create_or_update(
586 self.resource_group, nic_name, net_ifz
587 )
sousaedu80135b92021-02-17 15:05:18 +0100588 )
lloretgalleg064b05a2019-11-29 14:46:31 +0100589 nic_data = async_nic_creation.result()
590 created_items[nic_data.id] = True
sousaedu80135b92021-02-17 15:05:18 +0100591 self.logger.debug("created nic name %s", nic_name)
jamartinezv14a823d2019-08-01 11:45:15 +0200592
sousaedu80135b92021-02-17 15:05:18 +0100593 public_ip = net.get("floating_ip")
lloretgalleg45220152019-10-29 11:53:49 +0100594 if public_ip:
595 public_ip_address_params = {
sousaedu80135b92021-02-17 15:05:18 +0100596 "location": location,
597 "public_ip_allocation_method": "Dynamic",
seryio34478552019-05-23 14:50:49 +0200598 }
sousaedu80135b92021-02-17 15:05:18 +0100599 public_ip_name = nic_name + "-public-ip"
lloretgallegc8138e82021-03-31 07:12:06 +0000600 async_public_ip = (
601 self.conn_vnet.public_ip_addresses.begin_create_or_update(
602 self.resource_group, public_ip_name, public_ip_address_params
603 )
jamartinezv14a823d2019-08-01 11:45:15 +0200604 )
lloretgalleg064b05a2019-11-29 14:46:31 +0100605 public_ip = async_public_ip.result()
sousaedu80135b92021-02-17 15:05:18 +0100606 self.logger.debug("created public IP: {}".format(public_ip))
jamartinezv14a823d2019-08-01 11:45:15 +0200607
tiernob569e4b2019-11-21 16:10:32 +0000608 # Associate NIC to Public IP
jamartinezv14a823d2019-08-01 11:45:15 +0200609 nic_data = self.conn_vnet.network_interfaces.get(
sousaedu80135b92021-02-17 15:05:18 +0100610 self.resource_group, nic_name
611 )
jamartinezv14a823d2019-08-01 11:45:15 +0200612
lloretgalleg064b05a2019-11-29 14:46:31 +0100613 nic_data.ip_configurations[0].public_ip_address = public_ip
614 created_items[public_ip.id] = True
jamartinezv14a823d2019-08-01 11:45:15 +0200615
lloretgallegc8138e82021-03-31 07:12:06 +0000616 self.conn_vnet.network_interfaces.begin_create_or_update(
sousaedu80135b92021-02-17 15:05:18 +0100617 self.resource_group, nic_name, nic_data
618 )
jamartinezv14a823d2019-08-01 11:45:15 +0200619
620 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +0100621 self._format_vimconn_exception(e)
seryio34478552019-05-23 14:50:49 +0200622
lloretgalleg064b05a2019-11-29 14:46:31 +0100623 return nic_data, created_items
seryio34478552019-05-23 14:50:49 +0200624
jamartinezv14a823d2019-08-01 11:45:15 +0200625 def new_flavor(self, flavor_data):
tiernodeb74b22019-05-27 10:24:50 +0000626 """
lloretgalleg45220152019-10-29 11:53:49 +0100627 It is not allowed to create new flavors in Azure, must always use an existing one
628 """
sousaedu80135b92021-02-17 15:05:18 +0100629 raise vimconn.VimConnAuthException(
630 "It is not possible to create new flavors in AZURE"
631 )
tiernodeb74b22019-05-27 10:24:50 +0000632
lloretgalleg45220152019-10-29 11:53:49 +0100633 def new_tenant(self, tenant_name, tenant_description):
tiernodeb74b22019-05-27 10:24:50 +0000634 """
lloretgalleg45220152019-10-29 11:53:49 +0100635 It is not allowed to create new tenants in azure
636 """
sousaedu80135b92021-02-17 15:05:18 +0100637 raise vimconn.VimConnAuthException(
638 "It is not possible to create a TENANT in AZURE"
639 )
jamartinezv14a823d2019-08-01 11:45:15 +0200640
641 def new_image(self, image_dict):
lloretgalleg45220152019-10-29 11:53:49 +0100642 """
643 It is not allowed to create new images in Azure, must always use an existing one
644 """
sousaedu80135b92021-02-17 15:05:18 +0100645 raise vimconn.VimConnAuthException(
646 "It is not possible to create new images in AZURE"
647 )
jamartinezv14a823d2019-08-01 11:45:15 +0200648
649 def get_image_id_from_path(self, path):
650 """Get the image id from image path in the VIM database.
sousaedu80135b92021-02-17 15:05:18 +0100651 Returns the image_id or raises a vimconnNotFoundException
jamartinezv14a823d2019-08-01 11:45:15 +0200652 """
sousaedu80135b92021-02-17 15:05:18 +0100653 raise vimconn.VimConnAuthException(
654 "It is not possible to obtain image from path in AZURE"
655 )
jamartinezv14a823d2019-08-01 11:45:15 +0200656
657 def get_image_list(self, filter_dict={}):
658 """Obtain tenant images from VIM
659 Filter_dict can be:
lloretgalleg45220152019-10-29 11:53:49 +0100660 name: image name with the format: publisher:offer:sku:version
661 If some part of the name is provide ex: publisher:offer it will search all availables skus and version
662 for the provided publisher and offer
663 id: image uuid, currently not supported for azure
jamartinezv14a823d2019-08-01 11:45:15 +0200664 Returns the image list of dictionaries:
665 [{<the fields at Filter_dict plus some VIM specific>}, ...]
666 List can be empty
667 """
lloretgalleg45220152019-10-29 11:53:49 +0100668 self.logger.debug("get_image_list filter {}".format(filter_dict))
tiernodeb74b22019-05-27 10:24:50 +0000669
670 self._reload_connection()
lloretgalleg45220152019-10-29 11:53:49 +0100671 try:
672 image_list = []
673 if filter_dict.get("name"):
sousaedu80135b92021-02-17 15:05:18 +0100674 # name will have the format "publisher:offer:sku:version"
lloretgalleg45220152019-10-29 11:53:49 +0100675 # publisher is required, offer sku and version will be searched if not provided
676 params = filter_dict["name"].split(":")
tiernodeb74b22019-05-27 10:24:50 +0000677 publisher = params[0]
lloretgalleg45220152019-10-29 11:53:49 +0100678 if publisher:
679 # obtain offer list
680 offer_list = self._get_offer_list(params, publisher)
sousaedu80135b92021-02-17 15:05:18 +0100681
lloretgalleg45220152019-10-29 11:53:49 +0100682 for offer in offer_list:
683 # obtain skus
684 sku_list = self._get_sku_list(params, publisher, offer)
sousaedu80135b92021-02-17 15:05:18 +0100685
lloretgalleg45220152019-10-29 11:53:49 +0100686 for sku in sku_list:
687 # if version is defined get directly version, else list images
688 if len(params) == 4 and params[3]:
689 version = params[3]
lloretgallegc8138e82021-03-31 07:12:06 +0000690 if version == "latest":
691 image_list = self._get_sku_image_list(
692 publisher, offer, sku
693 )
694 image_list = [image_list[-1]]
695 else:
696 image_list = self._get_version_image_list(
697 publisher, offer, sku, version
698 )
lloretgalleg45220152019-10-29 11:53:49 +0100699 else:
sousaedu80135b92021-02-17 15:05:18 +0100700 image_list = self._get_sku_image_list(
701 publisher, offer, sku
702 )
lloretgalleg45220152019-10-29 11:53:49 +0100703 else:
tierno72774862020-05-04 11:44:15 +0000704 raise vimconn.VimConnAuthException(
sousaedu80135b92021-02-17 15:05:18 +0100705 "List images in Azure must include name param with at least publisher"
706 )
lloretgalleg45220152019-10-29 11:53:49 +0100707 else:
sousaedu80135b92021-02-17 15:05:18 +0100708 raise vimconn.VimConnAuthException(
709 "List images in Azure must include name param with at"
710 " least publisher"
711 )
tiernodeb74b22019-05-27 10:24:50 +0000712
lloretgalleg45220152019-10-29 11:53:49 +0100713 return image_list
714 except Exception as e:
715 self._format_vimconn_exception(e)
tiernodeb74b22019-05-27 10:24:50 +0000716
lloretgalleg45220152019-10-29 11:53:49 +0100717 def _get_offer_list(self, params, publisher):
718 """
719 Helper method to obtain offer list for defined publisher
720 """
721 if len(params) >= 2 and params[1]:
722 return [params[1]]
723 else:
724 try:
725 # get list of offers from azure
sousaedu80135b92021-02-17 15:05:18 +0100726 result_offers = self.conn_compute.virtual_machine_images.list_offers(
727 self.region, publisher
728 )
729
lloretgalleg45220152019-10-29 11:53:49 +0100730 return [offer.name for offer in result_offers]
731 except CloudError as e:
732 # azure raises CloudError when not found
sousaedu80135b92021-02-17 15:05:18 +0100733 self.logger.info(
734 "error listing offers for publisher {}, Error: {}".format(
735 publisher, e
736 )
737 )
738
lloretgalleg45220152019-10-29 11:53:49 +0100739 return []
740
741 def _get_sku_list(self, params, publisher, offer):
742 """
743 Helper method to obtain sku list for defined publisher and offer
744 """
745 if len(params) >= 3 and params[2]:
746 return [params[2]]
747 else:
748 try:
749 # get list of skus from azure
sousaedu80135b92021-02-17 15:05:18 +0100750 result_skus = self.conn_compute.virtual_machine_images.list_skus(
751 self.region, publisher, offer
752 )
753
lloretgalleg45220152019-10-29 11:53:49 +0100754 return [sku.name for sku in result_skus]
755 except CloudError as e:
756 # azure raises CloudError when not found
sousaedu80135b92021-02-17 15:05:18 +0100757 self.logger.info(
758 "error listing skus for publisher {}, offer {}, Error: {}".format(
759 publisher, offer, e
760 )
761 )
762
lloretgalleg45220152019-10-29 11:53:49 +0100763 return []
764
765 def _get_sku_image_list(self, publisher, offer, sku):
766 """
767 Helper method to obtain image list for publisher, offer and sku
768 """
769 image_list = []
770 try:
sousaedu80135b92021-02-17 15:05:18 +0100771 result_images = self.conn_compute.virtual_machine_images.list(
772 self.region, publisher, offer, sku
773 )
lloretgalleg45220152019-10-29 11:53:49 +0100774 for result_image in result_images:
sousaedu80135b92021-02-17 15:05:18 +0100775 image_list.append(
776 {
777 "id": str(result_image.id),
778 "name": ":".join([publisher, offer, sku, result_image.name]),
779 }
780 )
lloretgalleg45220152019-10-29 11:53:49 +0100781 except CloudError as e:
782 self.logger.info(
sousaedu80135b92021-02-17 15:05:18 +0100783 "error listing skus for publisher {}, offer {}, Error: {}".format(
784 publisher, offer, e
785 )
786 )
lloretgalleg45220152019-10-29 11:53:49 +0100787 image_list = []
sousaedu80135b92021-02-17 15:05:18 +0100788
lloretgalleg45220152019-10-29 11:53:49 +0100789 return image_list
790
791 def _get_version_image_list(self, publisher, offer, sku, version):
792 image_list = []
793 try:
sousaedu80135b92021-02-17 15:05:18 +0100794 result_image = self.conn_compute.virtual_machine_images.get(
795 self.region, publisher, offer, sku, version
796 )
797
lloretgalleg45220152019-10-29 11:53:49 +0100798 if result_image:
sousaedu80135b92021-02-17 15:05:18 +0100799 image_list.append(
800 {
801 "id": str(result_image.id),
802 "name": ":".join([publisher, offer, sku, version]),
803 }
804 )
lloretgalleg45220152019-10-29 11:53:49 +0100805 except CloudError as e:
806 # azure gives CloudError when not found
sousaedu80135b92021-02-17 15:05:18 +0100807 self.logger.info(
808 "error listing images for publisher {}, offer {}, sku {}, version {} Error: {}".format(
809 publisher, offer, sku, version, e
810 )
811 )
lloretgalleg45220152019-10-29 11:53:49 +0100812 image_list = []
sousaedu80135b92021-02-17 15:05:18 +0100813
tiernodeb74b22019-05-27 10:24:50 +0000814 return image_list
815
seryio34478552019-05-23 14:50:49 +0200816 def get_network_list(self, filter_dict={}):
tiernodeb74b22019-05-27 10:24:50 +0000817 """Obtain tenant networks of VIM
seryio34478552019-05-23 14:50:49 +0200818 Filter_dict can be:
819 name: network name
lloretgalleg45220152019-10-29 11:53:49 +0100820 id: network id
821 shared: boolean, not implemented in Azure
822 tenant_id: tenant, not used in Azure, all networks same tenants
823 admin_state_up: boolean, not implemented in Azure
824 status: 'ACTIVE', not implemented in Azure #
seryio34478552019-05-23 14:50:49 +0200825 Returns the network list of dictionaries
tiernodeb74b22019-05-27 10:24:50 +0000826 """
sousaedu80135b92021-02-17 15:05:18 +0100827 # self.logger.debug("getting network list for vim, filter %s", filter_dict)
seryio34478552019-05-23 14:50:49 +0200828 try:
829 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +0200830
sousaedu80135b92021-02-17 15:05:18 +0100831 vnet = self.conn_vnet.virtual_networks.get(
gallardodd821752022-01-17 17:37:51 +0000832 self.vnet_resource_group or self.resource_group, self.vnet_name
sousaedu80135b92021-02-17 15:05:18 +0100833 )
seryio34478552019-05-23 14:50:49 +0200834 subnet_list = []
jamartinezv14a823d2019-08-01 11:45:15 +0200835
seryio34478552019-05-23 14:50:49 +0200836 for subnet in vnet.subnets:
seryio34478552019-05-23 14:50:49 +0200837 if filter_dict:
838 if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
839 continue
sousaedu80135b92021-02-17 15:05:18 +0100840
841 if (
842 filter_dict.get("name")
843 and str(subnet.name) != filter_dict["name"]
844 ):
seryio34478552019-05-23 14:50:49 +0200845 continue
846
jamartinezv14a823d2019-08-01 11:45:15 +0200847 name = self._get_resource_name_from_resource_id(subnet.id)
848
sousaedu80135b92021-02-17 15:05:18 +0100849 subnet_list.append(
850 {
851 "id": str(subnet.id),
852 "name": name,
853 "status": self.provision_state2osm[subnet.provisioning_state],
854 "cidr_block": str(subnet.address_prefix),
855 "type": "bridge",
856 "shared": False,
857 }
858 )
jamartinezv14a823d2019-08-01 11:45:15 +0200859
seryio34478552019-05-23 14:50:49 +0200860 return subnet_list
861 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +0100862 self._format_vimconn_exception(e)
seryio34478552019-05-23 14:50:49 +0200863
sousaedu80135b92021-02-17 15:05:18 +0100864 def new_vminstance(
865 self,
866 name,
867 description,
868 start,
869 image_id,
870 flavor_id,
Alexis Romerob70f4ed2022-03-11 18:00:49 +0100871 affinity_group_list,
sousaedu80135b92021-02-17 15:05:18 +0100872 net_list,
873 cloud_config=None,
874 disk_list=None,
875 availability_zone_index=None,
876 availability_zone_list=None,
877 ):
878 self.logger.debug(
879 "new vm instance name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, "
880 "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s",
881 name,
882 image_id,
883 flavor_id,
884 net_list,
885 cloud_config,
886 disk_list,
887 availability_zone_index,
888 availability_zone_list,
889 )
lloretgalleg45220152019-10-29 11:53:49 +0100890 self._reload_connection()
891
892 # Validate input data is valid
893 # The virtual machine name must have less or 64 characters and it can not have the following
894 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
895 vm_name = self._check_vm_name(name)
896 # Obtain vm unused name
897 vm_name = self._get_unused_vm_name(vm_name)
898
899 # At least one network must be provided
900 if not net_list:
sousaedu80135b92021-02-17 15:05:18 +0100901 raise vimconn.VimConnException(
902 "At least one net must be provided to create a new VM"
903 )
lloretgalleg45220152019-10-29 11:53:49 +0100904
905 # image_id are several fields of the image_id
906 image_reference = self._get_image_reference(image_id)
jamartinezv14a823d2019-08-01 11:45:15 +0200907
seryio34478552019-05-23 14:50:49 +0200908 try:
lloretgalleg064b05a2019-11-29 14:46:31 +0100909 virtual_machine = None
910 created_items = {}
911
912 # Create nics for each subnet
913 self._check_subnets_for_vm(net_list)
914 vm_nics = []
sousaedu80135b92021-02-17 15:05:18 +0100915
lloretgalleg064b05a2019-11-29 14:46:31 +0100916 for idx, net in enumerate(net_list):
917 # Fault with subnet_id
sousaedu80135b92021-02-17 15:05:18 +0100918 # subnet_id=net["subnet_id"]
919 # subnet_id=net["net_id"]
920 nic_name = vm_name + "-nic-" + str(idx)
921 vm_nic, nic_items = self._create_nic(
garciadeblas66af6942021-10-26 18:38:49 +0200922 net, nic_name, self.region, net.get("ip_address"), created_items
sousaedu80135b92021-02-17 15:05:18 +0100923 )
924 vm_nics.append({"id": str(vm_nic.id)})
925 net["vim_id"] = vm_nic.id
jamartinezv14a823d2019-08-01 11:45:15 +0200926
seryio34478552019-05-23 14:50:49 +0200927 vm_parameters = {
sousaedu80135b92021-02-17 15:05:18 +0100928 "location": self.region,
lloretgallegc8138e82021-03-31 07:12:06 +0000929 "os_profile": self._build_os_profile(vm_name, cloud_config, image_id),
sousaedu80135b92021-02-17 15:05:18 +0100930 "hardware_profile": {"vm_size": flavor_id},
931 "storage_profile": {"image_reference": image_reference},
seryio34478552019-05-23 14:50:49 +0200932 }
jamartinezv14a823d2019-08-01 11:45:15 +0200933
lloretgalleg45220152019-10-29 11:53:49 +0100934 # If the machine has several networks one must be marked as primary
935 # As it is not indicated in the interface the first interface will be marked as primary
936 if len(vm_nics) > 1:
937 for idx, vm_nic in enumerate(vm_nics):
938 if idx == 0:
sousaedu80135b92021-02-17 15:05:18 +0100939 vm_nics[0]["Primary"] = True
lloretgalleg45220152019-10-29 11:53:49 +0100940 else:
sousaedu80135b92021-02-17 15:05:18 +0100941 vm_nics[idx]["Primary"] = False
lloretgalleg45220152019-10-29 11:53:49 +0100942
sousaedu80135b92021-02-17 15:05:18 +0100943 vm_parameters["network_profile"] = {"network_interfaces": vm_nics}
lloretgalleg45220152019-10-29 11:53:49 +0100944
lloretgalleg064b05a2019-11-29 14:46:31 +0100945 # Obtain zone information
946 vm_zone = self._get_vm_zone(availability_zone_index, availability_zone_list)
947 if vm_zone:
sousaedu80135b92021-02-17 15:05:18 +0100948 vm_parameters["zones"] = [vm_zone]
lloretgalleg064b05a2019-11-29 14:46:31 +0100949
lloretgalleg45220152019-10-29 11:53:49 +0100950 self.logger.debug("create vm name: %s", vm_name)
lloretgallegc8138e82021-03-31 07:12:06 +0000951 creation_result = self.conn_compute.virtual_machines.begin_create_or_update(
952 self.resource_group, vm_name, vm_parameters, polling=False
seryio34478552019-05-23 14:50:49 +0200953 )
lloretgallegc8138e82021-03-31 07:12:06 +0000954 self.logger.debug("obtained creation result: %s", creation_result)
lloretgalleg064b05a2019-11-29 14:46:31 +0100955 virtual_machine = creation_result.result()
lloretgallegbfc28b02019-11-21 09:31:16 +0100956 self.logger.debug("created vm name: %s", vm_name)
jamartinezv14a823d2019-08-01 11:45:15 +0200957
lloretgalleg064b05a2019-11-29 14:46:31 +0100958 return virtual_machine.id, created_items
sousaedu80135b92021-02-17 15:05:18 +0100959
seryio34478552019-05-23 14:50:49 +0200960 except Exception as e:
lloretgalleg064b05a2019-11-29 14:46:31 +0100961 # Rollback vm creacion
962 vm_id = None
sousaedu80135b92021-02-17 15:05:18 +0100963
lloretgalleg064b05a2019-11-29 14:46:31 +0100964 if virtual_machine:
965 vm_id = virtual_machine.id
sousaedu80135b92021-02-17 15:05:18 +0100966
lloretgalleg064b05a2019-11-29 14:46:31 +0100967 try:
968 self.logger.debug("exception creating vm try to rollback")
969 self.delete_vminstance(vm_id, created_items)
970 except Exception as e2:
971 self.logger.error("new_vminstance rollback fail {}".format(e2))
972
sousaedu80135b92021-02-17 15:05:18 +0100973 self.logger.debug("Exception creating new vminstance: %s", e, exc_info=True)
lloretgalleg45220152019-10-29 11:53:49 +0100974 self._format_vimconn_exception(e)
975
lloretgallegc8138e82021-03-31 07:12:06 +0000976 def _build_os_profile(self, vm_name, cloud_config, image_id):
lloretgallegc8138e82021-03-31 07:12:06 +0000977 # initial os_profile
978 os_profile = {"computer_name": vm_name}
979
980 # for azure os_profile admin_username is required
981 if cloud_config and cloud_config.get("users"):
982 admin_username = cloud_config.get("users")[0].get(
983 "name", self._get_default_admin_user(image_id)
984 )
985 else:
986 admin_username = self._get_default_admin_user(image_id)
987 os_profile["admin_username"] = admin_username
988
989 # if there is a cloud-init load it
990 if cloud_config:
991 _, userdata = self._create_user_data(cloud_config)
992 custom_data = base64.b64encode(userdata.encode("utf-8")).decode("latin-1")
993 os_profile["custom_data"] = custom_data
994
995 # either password of ssh-keys are required
996 # we will always use ssh-keys, in case it is not available we will generate it
997 if cloud_config and cloud_config.get("key-pairs"):
998 key_data = cloud_config.get("key-pairs")[0]
999 else:
1000 _, key_data = self._generate_keys()
1001
1002 os_profile["linux_configuration"] = {
1003 "ssh": {
1004 "public_keys": [
1005 {
1006 "path": "/home/{}/.ssh/authorized_keys".format(admin_username),
1007 "key_data": key_data,
1008 }
1009 ]
1010 },
1011 }
1012
1013 return os_profile
1014
1015 def _generate_keys(self):
1016 """Method used to generate a pair of private/public keys.
1017 This method is used because to create a vm in Azure we always need a key or a password
1018 In some cases we may have a password in a cloud-init file but it may not be available
1019 """
1020 key = rsa.generate_private_key(
1021 backend=crypto_default_backend(), public_exponent=65537, key_size=2048
1022 )
1023 private_key = key.private_bytes(
1024 crypto_serialization.Encoding.PEM,
1025 crypto_serialization.PrivateFormat.PKCS8,
1026 crypto_serialization.NoEncryption(),
1027 )
1028 public_key = key.public_key().public_bytes(
1029 crypto_serialization.Encoding.OpenSSH,
1030 crypto_serialization.PublicFormat.OpenSSH,
1031 )
1032 private_key = private_key.decode("utf8")
1033 # Change first line because Paramiko needs a explicit start with 'BEGIN RSA PRIVATE KEY'
1034 i = private_key.find("\n")
1035 private_key = "-----BEGIN RSA PRIVATE KEY-----" + private_key[i:]
1036 public_key = public_key.decode("utf8")
1037
1038 return private_key, public_key
1039
lloretgalleg45220152019-10-29 11:53:49 +01001040 def _get_unused_vm_name(self, vm_name):
1041 """
1042 Checks the vm name and in case it is used adds a suffix to the name to allow creation
1043 :return:
1044 """
1045 all_vms = self.conn_compute.virtual_machines.list(self.resource_group)
1046 # Filter to vms starting with the indicated name
1047 vms = list(filter(lambda vm: (vm.name.startswith(vm_name)), all_vms))
1048 vm_names = [str(vm.name) for vm in vms]
1049
1050 # get the name with the first not used suffix
1051 name_suffix = 0
1052 # name = subnet_name + "-" + str(name_suffix)
1053 name = vm_name # first subnet created will have no prefix
sousaedu80135b92021-02-17 15:05:18 +01001054
lloretgalleg45220152019-10-29 11:53:49 +01001055 while name in vm_names:
1056 name_suffix += 1
1057 name = vm_name + "-" + str(name_suffix)
sousaedu80135b92021-02-17 15:05:18 +01001058
lloretgalleg45220152019-10-29 11:53:49 +01001059 return name
1060
lloretgalleg064b05a2019-11-29 14:46:31 +01001061 def _get_vm_zone(self, availability_zone_index, availability_zone_list):
lloretgalleg064b05a2019-11-29 14:46:31 +01001062 if availability_zone_index is None:
1063 return None
1064
1065 vim_availability_zones = self._get_azure_availability_zones()
1066 # check if VIM offer enough availability zones describe in the VNFD
sousaedu80135b92021-02-17 15:05:18 +01001067 if vim_availability_zones and len(availability_zone_list) <= len(
1068 vim_availability_zones
1069 ):
lloretgalleg064b05a2019-11-29 14:46:31 +01001070 # check if all the names of NFV AV match VIM AV names
1071 match_by_index = False
sousaedu80135b92021-02-17 15:05:18 +01001072
lloretgalleg064b05a2019-11-29 14:46:31 +01001073 if not availability_zone_list:
1074 match_by_index = True
1075 else:
1076 for av in availability_zone_list:
1077 if av not in vim_availability_zones:
1078 match_by_index = True
1079 break
sousaedu80135b92021-02-17 15:05:18 +01001080
lloretgalleg064b05a2019-11-29 14:46:31 +01001081 if match_by_index:
1082 return vim_availability_zones[availability_zone_index]
1083 else:
1084 return availability_zone_list[availability_zone_index]
1085 else:
sousaedu80135b92021-02-17 15:05:18 +01001086 raise vimconn.VimConnConflictException(
1087 "No enough availability zones at VIM for this deployment"
1088 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001089
1090 def _get_azure_availability_zones(self):
1091 return self.AZURE_ZONES
1092
lloretgalleg45220152019-10-29 11:53:49 +01001093 def _get_image_reference(self, image_id):
lloretgalleg45220152019-10-29 11:53:49 +01001094 try:
1095 # The data input format example:
1096 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
1097 # Publishers/Canonical/ArtifactTypes/VMImage/
1098 # Offers/UbuntuServer/
1099 # Skus/18.04-LTS/
1100 # Versions/18.04.201809110
sousaedu80135b92021-02-17 15:05:18 +01001101 publisher = str(image_id.split("/")[8])
1102 offer = str(image_id.split("/")[12])
1103 sku = str(image_id.split("/")[14])
1104 version = str(image_id.split("/")[16])
jamartinezv14a823d2019-08-01 11:45:15 +02001105
lloretgalleg45220152019-10-29 11:53:49 +01001106 return {
sousaedu80135b92021-02-17 15:05:18 +01001107 "publisher": publisher,
1108 "offer": offer,
1109 "sku": sku,
1110 "version": version,
lloretgalleg45220152019-10-29 11:53:49 +01001111 }
tierno1ec592d2020-06-16 15:29:47 +00001112 except Exception:
tierno72774862020-05-04 11:44:15 +00001113 raise vimconn.VimConnException(
sousaedu80135b92021-02-17 15:05:18 +01001114 "Unable to get image_reference from invalid image_id format: '{}'".format(
1115 image_id
1116 )
1117 )
jamartinezv14a823d2019-08-01 11:45:15 +02001118
1119 # Azure VM names can not have some special characters
lloretgalleg45220152019-10-29 11:53:49 +01001120 def _check_vm_name(self, vm_name):
1121 """
1122 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
1123 """
jamartinezv14a823d2019-08-01 11:45:15 +02001124 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
1125
1126 # First: the VM name max length is 64 characters
1127 vm_name_aux = vm_name[:64]
1128
1129 # Second: replace not allowed characters
lloretgalleg45220152019-10-29 11:53:49 +01001130 for elem in chars_not_allowed_list:
jamartinezv14a823d2019-08-01 11:45:15 +02001131 # Check if string is in the main string
lloretgalleg45220152019-10-29 11:53:49 +01001132 if elem in vm_name_aux:
sousaedu80135b92021-02-17 15:05:18 +01001133 # self.logger.debug("Dentro del IF")
jamartinezv14a823d2019-08-01 11:45:15 +02001134 # Replace the string
sousaedu80135b92021-02-17 15:05:18 +01001135 vm_name_aux = vm_name_aux.replace(elem, "-")
jamartinezv14a823d2019-08-01 11:45:15 +02001136
1137 return vm_name_aux
seryio34478552019-05-23 14:50:49 +02001138
1139 def get_flavor_id_from_data(self, flavor_dict):
lloretgalleg45220152019-10-29 11:53:49 +01001140 self.logger.debug("getting flavor id from data, flavor_dict: %s", flavor_dict)
1141 filter_dict = flavor_dict or {}
sousaedu80135b92021-02-17 15:05:18 +01001142
jamartinezv14a823d2019-08-01 11:45:15 +02001143 try:
1144 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001145 vm_sizes_list = [
garciadeblasc878ac42021-05-27 12:00:30 +02001146 vm_size.as_dict()
lloretgallegc8138e82021-03-31 07:12:06 +00001147 for vm_size in self.conn_compute.resource_skus.list(
aguilardb1aa4222023-10-09 14:05:53 +00001148 filter="location eq '{}'".format(self.region)
lloretgallegc8138e82021-03-31 07:12:06 +00001149 )
sousaedu80135b92021-02-17 15:05:18 +01001150 ]
tierno30d0d6d2019-05-27 08:14:01 +00001151
sousaedu80135b92021-02-17 15:05:18 +01001152 cpus = filter_dict.get("vcpus") or 0
1153 memMB = filter_dict.get("ram") or 0
lloretgallegc8138e82021-03-31 07:12:06 +00001154 numberInterfaces = len(filter_dict.get("interfaces", [])) or 0
tierno30d0d6d2019-05-27 08:14:01 +00001155
lloretgalleg45220152019-10-29 11:53:49 +01001156 # Filter
garciadeblasc878ac42021-05-27 12:00:30 +02001157 filtered_sizes = []
1158 for size in vm_sizes_list:
1159 if size["resource_type"] == "virtualMachines":
1160 size_cpus = int(
1161 self._find_in_capabilities(size["capabilities"], "vCPUs")
1162 )
1163 size_memory = float(
1164 self._find_in_capabilities(size["capabilities"], "MemoryGB")
1165 )
1166 size_interfaces = self._find_in_capabilities(
1167 size["capabilities"], "MaxNetworkInterfaces"
1168 )
1169 if size_interfaces:
1170 size_interfaces = int(size_interfaces)
1171 else:
1172 self.logger.debug(
1173 "Flavor with no defined MaxNetworkInterfaces: {}".format(
1174 size["name"]
1175 )
1176 )
1177 continue
1178 if (
1179 size_cpus >= cpus
1180 and size_memory >= memMB / 1024
1181 and size_interfaces >= numberInterfaces
1182 ):
1183 if self._config.get("flavors_pattern"):
1184 if re.search(
1185 self._config.get("flavors_pattern"), size["name"]
1186 ):
1187 new_size = {
1188 e["name"]: e["value"] for e in size["capabilities"]
1189 }
1190 new_size["name"] = size["name"]
1191 filtered_sizes.append(new_size)
1192 else:
1193 new_size = {
1194 e["name"]: e["value"] for e in size["capabilities"]
1195 }
1196 new_size["name"] = size["name"]
1197 filtered_sizes.append(new_size)
lloretgalleg45220152019-10-29 11:53:49 +01001198
1199 # Sort
sousaedu80135b92021-02-17 15:05:18 +01001200 listedFilteredSizes = sorted(
1201 filtered_sizes,
1202 key=lambda k: (
garciadeblasc878ac42021-05-27 12:00:30 +02001203 int(k["vCPUs"]),
1204 float(k["MemoryGB"]),
1205 int(k["MaxNetworkInterfaces"]),
1206 int(k["MaxResourceVolumeMB"]),
sousaedu80135b92021-02-17 15:05:18 +01001207 ),
1208 )
lloretgalleg45220152019-10-29 11:53:49 +01001209
1210 if listedFilteredSizes:
sousaedu80135b92021-02-17 15:05:18 +01001211 return listedFilteredSizes[0]["name"]
jamartinezv14a823d2019-08-01 11:45:15 +02001212
sousaedu80135b92021-02-17 15:05:18 +01001213 raise vimconn.VimConnNotFoundException(
1214 "Cannot find any flavor matching '{}'".format(str(flavor_dict))
1215 )
jamartinezv14a823d2019-08-01 11:45:15 +02001216 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +01001217 self._format_vimconn_exception(e)
jamartinezv14a823d2019-08-01 11:45:15 +02001218
1219 def _get_flavor_id_from_flavor_name(self, flavor_name):
tiernob569e4b2019-11-21 16:10:32 +00001220 # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name))
jamartinezv14a823d2019-08-01 11:45:15 +02001221 try:
1222 self._reload_connection()
sousaedu80135b92021-02-17 15:05:18 +01001223 vm_sizes_list = [
garciadeblasc878ac42021-05-27 12:00:30 +02001224 vm_size.as_dict()
lloretgallegc8138e82021-03-31 07:12:06 +00001225 for vm_size in self.conn_compute.resource_skus.list(
aguilardb1aa4222023-10-09 14:05:53 +00001226 filter="location eq '{}'".format(self.region)
lloretgallegc8138e82021-03-31 07:12:06 +00001227 )
sousaedu80135b92021-02-17 15:05:18 +01001228 ]
jamartinezv14a823d2019-08-01 11:45:15 +02001229
1230 output_flavor = None
1231 for size in vm_sizes_list:
sousaedu80135b92021-02-17 15:05:18 +01001232 if size["name"] == flavor_name:
jamartinezv14a823d2019-08-01 11:45:15 +02001233 output_flavor = size
1234
tiernob569e4b2019-11-21 16:10:32 +00001235 # None is returned if not found anything
jamartinezv14a823d2019-08-01 11:45:15 +02001236 return output_flavor
jamartinezv14a823d2019-08-01 11:45:15 +02001237 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +01001238 self._format_vimconn_exception(e)
tierno30d0d6d2019-05-27 08:14:01 +00001239
1240 def check_vim_connectivity(self):
seryio34478552019-05-23 14:50:49 +02001241 try:
1242 self._reload_connection()
tierno30d0d6d2019-05-27 08:14:01 +00001243 return True
1244 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001245 raise vimconn.VimConnException(
1246 "Connectivity issue with Azure API: {}".format(e)
1247 )
seryio34478552019-05-23 14:50:49 +02001248
1249 def get_network(self, net_id):
sousaedu80135b92021-02-17 15:05:18 +01001250 # self.logger.debug("get network id: {}".format(net_id))
tiernob569e4b2019-11-21 16:10:32 +00001251 # res_name = self._get_resource_name_from_resource_id(net_id)
seryio34478552019-05-23 14:50:49 +02001252 self._reload_connection()
seryio34478552019-05-23 14:50:49 +02001253
sousaedu80135b92021-02-17 15:05:18 +01001254 filter_dict = {"name": net_id}
jamartinezv14a823d2019-08-01 11:45:15 +02001255 network_list = self.get_network_list(filter_dict)
1256
1257 if not network_list:
sousaedu80135b92021-02-17 15:05:18 +01001258 raise vimconn.VimConnNotFoundException(
1259 "network '{}' not found".format(net_id)
1260 )
jamartinezv14a823d2019-08-01 11:45:15 +02001261 else:
1262 return network_list[0]
1263
jamartinezv14a823d2019-08-01 11:45:15 +02001264 def delete_network(self, net_id, created_items=None):
sousaedu80135b92021-02-17 15:05:18 +01001265 self.logger.debug(
gallardodd821752022-01-17 17:37:51 +00001266 "deleting network {} - {}".format(
1267 self.vnet_resource_group or self.resource_group, net_id
1268 )
sousaedu80135b92021-02-17 15:05:18 +01001269 )
jamartinezv14a823d2019-08-01 11:45:15 +02001270
seryio34478552019-05-23 14:50:49 +02001271 self._reload_connection()
lloretgalleg45220152019-10-29 11:53:49 +01001272 res_name = self._get_resource_name_from_resource_id(net_id)
jamartinezv14a823d2019-08-01 11:45:15 +02001273
1274 try:
lloretgallegc8138e82021-03-31 07:12:06 +00001275 # Obtain subnets ant try to delete nic first
1276 subnet = self.conn_vnet.subnets.get(
gallardodd821752022-01-17 17:37:51 +00001277 self.vnet_resource_group or self.resource_group,
1278 self.vnet_name,
1279 res_name,
lloretgallegc8138e82021-03-31 07:12:06 +00001280 )
1281 if not subnet:
1282 raise vimconn.VimConnNotFoundException(
1283 "network '{}' not found".format(net_id)
1284 )
1285
1286 # TODO - for a quick-fix delete nics sequentially but should not wait
1287 # for each in turn
1288 if subnet.ip_configurations:
1289 for ip_configuration in subnet.ip_configurations:
1290 # obtain nic_name from ip_configuration
1291 parsed_id = azure_tools.parse_resource_id(ip_configuration.id)
1292 nic_name = parsed_id["name"]
1293 self.delete_inuse_nic(nic_name)
1294
jamartinezv14a823d2019-08-01 11:45:15 +02001295 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
1296 # Put the initial virtual_network API
lloretgallegc8138e82021-03-31 07:12:06 +00001297 async_delete = self.conn_vnet.subnets.begin_delete(
gallardodd821752022-01-17 17:37:51 +00001298 self.vnet_resource_group or self.resource_group,
1299 self.vnet_name,
1300 res_name,
sousaedu80135b92021-02-17 15:05:18 +01001301 )
lloretgalleg45220152019-10-29 11:53:49 +01001302 async_delete.wait()
lloretgallegc8138e82021-03-31 07:12:06 +00001303
jamartinezv14a823d2019-08-01 11:45:15 +02001304 return net_id
1305
lloretgallegc8138e82021-03-31 07:12:06 +00001306 except ResourceNotFoundError:
1307 raise vimconn.VimConnNotFoundException(
1308 "network '{}' not found".format(net_id)
1309 )
jamartinezv14a823d2019-08-01 11:45:15 +02001310 except CloudError as e:
lloretgalleg45220152019-10-29 11:53:49 +01001311 if e.error.error and "notfound" in e.error.error.lower():
sousaedu80135b92021-02-17 15:05:18 +01001312 raise vimconn.VimConnNotFoundException(
1313 "network '{}' not found".format(net_id)
1314 )
jamartinezv14a823d2019-08-01 11:45:15 +02001315 else:
lloretgalleg45220152019-10-29 11:53:49 +01001316 self._format_vimconn_exception(e)
jamartinezv14a823d2019-08-01 11:45:15 +02001317 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +01001318 self._format_vimconn_exception(e)
jamartinezv14a823d2019-08-01 11:45:15 +02001319
lloretgallegc8138e82021-03-31 07:12:06 +00001320 def delete_inuse_nic(self, nic_name):
lloretgallegc8138e82021-03-31 07:12:06 +00001321 # Obtain nic data
1322 nic_data = self.conn_vnet.network_interfaces.get(self.resource_group, nic_name)
1323
1324 # Obtain vm associated to nic in case it exists
1325 if nic_data.virtual_machine:
1326 vm_name = azure_tools.parse_resource_id(nic_data.virtual_machine.id)["name"]
1327 self.logger.debug("vm_name: {}".format(vm_name))
1328 virtual_machine = self.conn_compute.virtual_machines.get(
1329 self.resource_group, vm_name
1330 )
1331 self.logger.debug("obtained vm")
1332
1333 # Deattach nic from vm if it has netwolk machines attached
1334 network_interfaces = virtual_machine.network_profile.network_interfaces
1335 network_interfaces[:] = [
1336 interface
1337 for interface in network_interfaces
1338 if self._get_resource_name_from_resource_id(interface.id) != nic_name
1339 ]
1340
1341 # TODO - check if there is a public ip to delete and delete it
1342 if network_interfaces:
lloretgallegc8138e82021-03-31 07:12:06 +00001343 # Deallocate the vm
1344 async_vm_deallocate = (
1345 self.conn_compute.virtual_machines.begin_deallocate(
1346 self.resource_group, vm_name
1347 )
1348 )
1349 self.logger.debug("deallocating vm")
1350 async_vm_deallocate.wait()
1351 self.logger.debug("vm deallocated")
1352
1353 async_vm_update = (
1354 self.conn_compute.virtual_machines.begin_create_or_update(
1355 self.resource_group, vm_name, virtual_machine
1356 )
1357 )
1358 virtual_machine = async_vm_update.result()
1359 self.logger.debug("nic removed from interface")
1360
1361 else:
1362 self.logger.debug("There are no interfaces left, delete vm")
1363 self.delete_vminstance(virtual_machine.id)
1364 self.logger.debug("Delete vm")
1365
1366 # Delete nic
1367 self.logger.debug("delete NIC name: %s", nic_name)
1368 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
1369 self.resource_group, nic_name
1370 )
1371 nic_delete.wait()
1372 self.logger.debug("deleted NIC name: %s", nic_name)
1373
garciadeblas89598d42022-06-30 13:57:43 +02001374 def delete_vminstance(self, vm_id, created_items=None, volumes_to_hold=None):
sousaedu80135b92021-02-17 15:05:18 +01001375 """Deletes a vm instance from the vim."""
1376 self.logger.debug(
1377 "deleting VM instance {} - {}".format(self.resource_group, vm_id)
1378 )
jamartinezv14a823d2019-08-01 11:45:15 +02001379 self._reload_connection()
1380
lloretgalleg064b05a2019-11-29 14:46:31 +01001381 created_items = created_items or {}
jamartinezv14a823d2019-08-01 11:45:15 +02001382 try:
lloretgalleg064b05a2019-11-29 14:46:31 +01001383 # Check vm exists, we can call delete_vm to clean created_items
1384 if vm_id:
1385 res_name = self._get_resource_name_from_resource_id(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01001386 vm = self.conn_compute.virtual_machines.get(
1387 self.resource_group, res_name
1388 )
jamartinezv14a823d2019-08-01 11:45:15 +02001389
lloretgalleg064b05a2019-11-29 14:46:31 +01001390 # Shuts down the virtual machine and releases the compute resources
1391 # vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1392 # vm_stop.wait()
jamartinezv14a823d2019-08-01 11:45:15 +02001393
lloretgallegc8138e82021-03-31 07:12:06 +00001394 vm_delete = self.conn_compute.virtual_machines.begin_delete(
sousaedu80135b92021-02-17 15:05:18 +01001395 self.resource_group, res_name
1396 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001397 vm_delete.wait()
sousaedu80135b92021-02-17 15:05:18 +01001398 self.logger.debug("deleted VM name: %s", res_name)
jamartinezv14a823d2019-08-01 11:45:15 +02001399
lloretgallegc8138e82021-03-31 07:12:06 +00001400 # Delete OS Disk, check if exists, in case of error creating
1401 # it may not be fully created
1402 if vm.storage_profile.os_disk:
1403 os_disk_name = vm.storage_profile.os_disk.name
1404 self.logger.debug("delete OS DISK: %s", os_disk_name)
1405 async_disk_delete = self.conn_compute.disks.begin_delete(
1406 self.resource_group, os_disk_name
1407 )
1408 async_disk_delete.wait()
1409 # os disks are created always with the machine
1410 self.logger.debug("deleted OS DISK name: %s", os_disk_name)
jamartinezv14a823d2019-08-01 11:45:15 +02001411
lloretgalleg064b05a2019-11-29 14:46:31 +01001412 for data_disk in vm.storage_profile.data_disks:
sousaedu80135b92021-02-17 15:05:18 +01001413 self.logger.debug("delete data_disk: %s", data_disk.name)
lloretgallegc8138e82021-03-31 07:12:06 +00001414 async_disk_delete = self.conn_compute.disks.begin_delete(
sousaedu80135b92021-02-17 15:05:18 +01001415 self.resource_group, data_disk.name
1416 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001417 async_disk_delete.wait()
1418 self._markdel_created_item(data_disk.managed_disk.id, created_items)
sousaedu80135b92021-02-17 15:05:18 +01001419 self.logger.debug("deleted OS DISK name: %s", data_disk.name)
jamartinezv14a823d2019-08-01 11:45:15 +02001420
lloretgalleg064b05a2019-11-29 14:46:31 +01001421 # After deleting VM, it is necessary to delete NIC, because if is not deleted delete_network
1422 # does not work because Azure says that is in use the subnet
1423 network_interfaces = vm.network_profile.network_interfaces
lloretgalleg45220152019-10-29 11:53:49 +01001424
lloretgalleg064b05a2019-11-29 14:46:31 +01001425 for network_interface in network_interfaces:
sousaedu80135b92021-02-17 15:05:18 +01001426 nic_name = self._get_resource_name_from_resource_id(
1427 network_interface.id
1428 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001429 nic_data = self.conn_vnet.network_interfaces.get(
sousaedu80135b92021-02-17 15:05:18 +01001430 self.resource_group, nic_name
1431 )
jamartinezv14a823d2019-08-01 11:45:15 +02001432
lloretgalleg064b05a2019-11-29 14:46:31 +01001433 public_ip_name = None
1434 exist_public_ip = nic_data.ip_configurations[0].public_ip_address
1435 if exist_public_ip:
sousaedu80135b92021-02-17 15:05:18 +01001436 public_ip_id = nic_data.ip_configurations[
1437 0
1438 ].public_ip_address.id
jamartinezv14a823d2019-08-01 11:45:15 +02001439
lloretgalleg064b05a2019-11-29 14:46:31 +01001440 # Delete public_ip
sousaedu80135b92021-02-17 15:05:18 +01001441 public_ip_name = self._get_resource_name_from_resource_id(
1442 public_ip_id
1443 )
jamartinezv14a823d2019-08-01 11:45:15 +02001444
lloretgalleg064b05a2019-11-29 14:46:31 +01001445 # Public ip must be deleted afterwards of nic that is attached
jamartinezv14a823d2019-08-01 11:45:15 +02001446
sousaedu80135b92021-02-17 15:05:18 +01001447 self.logger.debug("delete NIC name: %s", nic_name)
lloretgallegc8138e82021-03-31 07:12:06 +00001448 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
sousaedu80135b92021-02-17 15:05:18 +01001449 self.resource_group, nic_name
1450 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001451 nic_delete.wait()
1452 self._markdel_created_item(network_interface.id, created_items)
sousaedu80135b92021-02-17 15:05:18 +01001453 self.logger.debug("deleted NIC name: %s", nic_name)
jamartinezv14a823d2019-08-01 11:45:15 +02001454
lloretgalleg064b05a2019-11-29 14:46:31 +01001455 # Delete list of public ips
1456 if public_ip_name:
sousaedu80135b92021-02-17 15:05:18 +01001457 self.logger.debug("delete PUBLIC IP - " + public_ip_name)
lloretgallegc8138e82021-03-31 07:12:06 +00001458 ip_delete = self.conn_vnet.public_ip_addresses.begin_delete(
sousaedu80135b92021-02-17 15:05:18 +01001459 self.resource_group, public_ip_name
1460 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001461 ip_delete.wait()
1462 self._markdel_created_item(public_ip_id, created_items)
lloretgalleg45220152019-10-29 11:53:49 +01001463
lloretgalleg064b05a2019-11-29 14:46:31 +01001464 # Delete created items
1465 self._delete_created_items(created_items)
lloretgalleg45220152019-10-29 11:53:49 +01001466
lloretgallegc8138e82021-03-31 07:12:06 +00001467 except ResourceNotFoundError:
1468 raise vimconn.VimConnNotFoundException(
1469 "No vm instance found '{}'".format(vm_id)
1470 )
lloretgalleg45220152019-10-29 11:53:49 +01001471 except CloudError as e:
1472 if e.error.error and "notfound" in e.error.error.lower():
sousaedu80135b92021-02-17 15:05:18 +01001473 raise vimconn.VimConnNotFoundException(
1474 "No vm instance found '{}'".format(vm_id)
1475 )
lloretgalleg45220152019-10-29 11:53:49 +01001476 else:
1477 self._format_vimconn_exception(e)
1478 except Exception as e:
1479 self._format_vimconn_exception(e)
1480
lloretgalleg064b05a2019-11-29 14:46:31 +01001481 def _markdel_created_item(self, item_id, created_items):
1482 if item_id in created_items:
1483 created_items[item_id] = False
1484
1485 def _delete_created_items(self, created_items):
sousaedu80135b92021-02-17 15:05:18 +01001486 """Delete created_items elements that have not been deleted with the virtual machine
1487 Created_items may not be deleted correctly with the created machine if the
1488 virtual machine fails creating or in other cases of error
lloretgalleg064b05a2019-11-29 14:46:31 +01001489 """
1490 self.logger.debug("Created items: %s", created_items)
lloretgallegc8138e82021-03-31 07:12:06 +00001491 # TODO - optimize - should not wait until it is deleted
lloretgalleg064b05a2019-11-29 14:46:31 +01001492 # Must delete in order first nics, then public_ips
1493 # As dictionaries don't preserve order, first get items to be deleted then delete them
1494 nics_to_delete = []
1495 publics_ip_to_delete = []
1496 disks_to_delete = []
1497 for item_id, v in created_items.items():
1498 if not v: # skip already deleted
1499 continue
1500
tierno1ec592d2020-06-16 15:29:47 +00001501 # self.logger.debug("Must delete item id: %s", item_id)
lloretgalleg064b05a2019-11-29 14:46:31 +01001502 # Obtain type, supported nic, disk or public ip
1503 parsed_id = azure_tools.parse_resource_id(item_id)
1504 resource_type = parsed_id.get("resource_type")
1505 name = parsed_id.get("name")
1506
1507 if resource_type == "networkInterfaces":
1508 nics_to_delete.append(name)
1509 elif resource_type == "publicIPAddresses":
1510 publics_ip_to_delete.append(name)
1511 elif resource_type == "disks":
1512 disks_to_delete.append(name)
1513
1514 # Now delete
1515 for item_name in nics_to_delete:
1516 try:
1517 self.logger.debug("deleting nic name %s:", item_name)
lloretgallegc8138e82021-03-31 07:12:06 +00001518 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
sousaedu80135b92021-02-17 15:05:18 +01001519 self.resource_group, item_name
1520 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001521 nic_delete.wait()
1522 self.logger.debug("deleted nic name %s:", item_name)
1523 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001524 self.logger.error(
1525 "Error deleting item: {}: {}".format(type(e).__name__, e)
1526 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001527
1528 for item_name in publics_ip_to_delete:
1529 try:
1530 self.logger.debug("deleting public ip name %s:", item_name)
lloretgallegc8138e82021-03-31 07:12:06 +00001531 ip_delete = self.conn_vnet.public_ip_addresses.begin_delete(
sousaedu80135b92021-02-17 15:05:18 +01001532 self.resource_group, name
1533 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001534 ip_delete.wait()
1535 self.logger.debug("deleted public ip name %s:", item_name)
1536 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001537 self.logger.error(
1538 "Error deleting item: {}: {}".format(type(e).__name__, e)
1539 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001540
1541 for item_name in disks_to_delete:
1542 try:
1543 self.logger.debug("deleting data disk name %s:", name)
lloretgallegc8138e82021-03-31 07:12:06 +00001544 async_disk_delete = self.conn_compute.disks.begin_delete(
sousaedu80135b92021-02-17 15:05:18 +01001545 self.resource_group, item_name
1546 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001547 async_disk_delete.wait()
1548 self.logger.debug("deleted data disk name %s:", name)
1549 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001550 self.logger.error(
1551 "Error deleting item: {}: {}".format(type(e).__name__, e)
1552 )
lloretgalleg064b05a2019-11-29 14:46:31 +01001553
tiernob569e4b2019-11-21 16:10:32 +00001554 def action_vminstance(self, vm_id, action_dict, created_items={}):
jamartinezv14a823d2019-08-01 11:45:15 +02001555 """Send and action over a VM instance from VIM
lloretgalleg45220152019-10-29 11:53:49 +01001556 Returns the vm_id if the action was successfully sent to the VIM
1557 """
jamartinezv14a823d2019-08-01 11:45:15 +02001558 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
sousaedu80135b92021-02-17 15:05:18 +01001559
jamartinezv14a823d2019-08-01 11:45:15 +02001560 try:
1561 self._reload_connection()
1562 resName = self._get_resource_name_from_resource_id(vm_id)
sousaedu80135b92021-02-17 15:05:18 +01001563
jamartinezv14a823d2019-08-01 11:45:15 +02001564 if "start" in action_dict:
lloretgallegc8138e82021-03-31 07:12:06 +00001565 self.conn_compute.virtual_machines.begin_start(
1566 self.resource_group, resName
1567 )
sousaedu80135b92021-02-17 15:05:18 +01001568 elif (
1569 "stop" in action_dict
1570 or "shutdown" in action_dict
1571 or "shutoff" in action_dict
1572 ):
lloretgallegc8138e82021-03-31 07:12:06 +00001573 self.conn_compute.virtual_machines.begin_power_off(
sousaedu80135b92021-02-17 15:05:18 +01001574 self.resource_group, resName
1575 )
jamartinezv14a823d2019-08-01 11:45:15 +02001576 elif "terminate" in action_dict:
lloretgallegc8138e82021-03-31 07:12:06 +00001577 self.conn_compute.virtual_machines.begin_delete(
1578 self.resource_group, resName
1579 )
jamartinezv14a823d2019-08-01 11:45:15 +02001580 elif "reboot" in action_dict:
lloretgallegc8138e82021-03-31 07:12:06 +00001581 self.conn_compute.virtual_machines.begin_restart(
1582 self.resource_group, resName
1583 )
sousaedu80135b92021-02-17 15:05:18 +01001584
jamartinezv14a823d2019-08-01 11:45:15 +02001585 return None
lloretgallegc8138e82021-03-31 07:12:06 +00001586 except ResourceNotFoundError:
1587 raise vimconn.VimConnNotFoundException("No vm found '{}'".format(vm_id))
jamartinezv14a823d2019-08-01 11:45:15 +02001588 except CloudError as e:
lloretgalleg45220152019-10-29 11:53:49 +01001589 if e.error.error and "notfound" in e.error.error.lower():
tierno72774862020-05-04 11:44:15 +00001590 raise vimconn.VimConnNotFoundException("No vm found '{}'".format(vm_id))
jamartinezv14a823d2019-08-01 11:45:15 +02001591 else:
lloretgalleg45220152019-10-29 11:53:49 +01001592 self._format_vimconn_exception(e)
jamartinezv14a823d2019-08-01 11:45:15 +02001593 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +01001594 self._format_vimconn_exception(e)
jamartinezv14a823d2019-08-01 11:45:15 +02001595
1596 def delete_flavor(self, flavor_id):
sousaedu80135b92021-02-17 15:05:18 +01001597 raise vimconn.VimConnAuthException(
1598 "It is not possible to delete a FLAVOR in AZURE"
1599 )
jamartinezv14a823d2019-08-01 11:45:15 +02001600
sousaedu80135b92021-02-17 15:05:18 +01001601 def delete_tenant(self, tenant_id):
1602 raise vimconn.VimConnAuthException(
1603 "It is not possible to delete a TENANT in AZURE"
1604 )
jamartinezv14a823d2019-08-01 11:45:15 +02001605
1606 def delete_image(self, image_id):
sousaedu80135b92021-02-17 15:05:18 +01001607 raise vimconn.VimConnAuthException(
1608 "It is not possible to delete a IMAGE in AZURE"
1609 )
seryio34478552019-05-23 14:50:49 +02001610
1611 def get_vminstance(self, vm_id):
lloretgalleg45220152019-10-29 11:53:49 +01001612 """
1613 Obtaing the vm instance data from v_id
1614 """
1615 self.logger.debug("get vm instance: %s", vm_id)
seryio34478552019-05-23 14:50:49 +02001616 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +02001617 try:
1618 resName = self._get_resource_name_from_resource_id(vm_id)
tiernob569e4b2019-11-21 16:10:32 +00001619 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
lloretgallegc8138e82021-03-31 07:12:06 +00001620 except ResourceNotFoundError:
1621 raise vimconn.VimConnNotFoundException(
1622 "No vminstance found '{}'".format(vm_id)
1623 )
jamartinezv14a823d2019-08-01 11:45:15 +02001624 except CloudError as e:
lloretgalleg45220152019-10-29 11:53:49 +01001625 if e.error.error and "notfound" in e.error.error.lower():
sousaedu80135b92021-02-17 15:05:18 +01001626 raise vimconn.VimConnNotFoundException(
1627 "No vminstance found '{}'".format(vm_id)
1628 )
jamartinezv14a823d2019-08-01 11:45:15 +02001629 else:
lloretgalleg45220152019-10-29 11:53:49 +01001630 self._format_vimconn_exception(e)
jamartinezv14a823d2019-08-01 11:45:15 +02001631 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +01001632 self._format_vimconn_exception(e)
seryio34478552019-05-23 14:50:49 +02001633
1634 return vm
1635
1636 def get_flavor(self, flavor_id):
lloretgalleg45220152019-10-29 11:53:49 +01001637 """
1638 Obtains the flavor_data from the flavor_id
1639 """
seryio34478552019-05-23 14:50:49 +02001640 self._reload_connection()
lloretgalleg45220152019-10-29 11:53:49 +01001641 self.logger.debug("get flavor from id: %s", flavor_id)
jamartinezv14a823d2019-08-01 11:45:15 +02001642 flavor_data = self._get_flavor_id_from_flavor_name(flavor_id)
sousaedu80135b92021-02-17 15:05:18 +01001643
jamartinezv14a823d2019-08-01 11:45:15 +02001644 if flavor_data:
1645 flavor = {
sousaedu80135b92021-02-17 15:05:18 +01001646 "id": flavor_id,
1647 "name": flavor_id,
1648 "ram": flavor_data["memoryInMB"],
1649 "vcpus": flavor_data["numberOfCores"],
1650 "disk": flavor_data["resourceDiskSizeInMB"] / 1024,
jamartinezv14a823d2019-08-01 11:45:15 +02001651 }
sousaedu80135b92021-02-17 15:05:18 +01001652
jamartinezv14a823d2019-08-01 11:45:15 +02001653 return flavor
1654 else:
sousaedu80135b92021-02-17 15:05:18 +01001655 raise vimconn.VimConnNotFoundException(
1656 "flavor '{}' not found".format(flavor_id)
1657 )
seryio34478552019-05-23 14:50:49 +02001658
jamartinezv14a823d2019-08-01 11:45:15 +02001659 def get_tenant_list(self, filter_dict={}):
sousaedu80135b92021-02-17 15:05:18 +01001660 """Obtains the list of tenants
1661 For the azure connector only the azure tenant will be returned if it is compatible
1662 with filter_dict
lloretgalleg45220152019-10-29 11:53:49 +01001663 """
sousaedu80135b92021-02-17 15:05:18 +01001664 tenants_azure = [{"name": self.tenant, "id": self.tenant}]
tiernob569e4b2019-11-21 16:10:32 +00001665 tenant_list = []
seryio34478552019-05-23 14:50:49 +02001666
lloretgalleg45220152019-10-29 11:53:49 +01001667 self.logger.debug("get tenant list: %s", filter_dict)
jamartinezv14a823d2019-08-01 11:45:15 +02001668 for tenant_azure in tenants_azure:
1669 if filter_dict:
sousaedu80135b92021-02-17 15:05:18 +01001670 if (
1671 filter_dict.get("id")
1672 and str(tenant_azure.get("id")) != filter_dict["id"]
1673 ):
jamartinezv14a823d2019-08-01 11:45:15 +02001674 continue
sousaedu80135b92021-02-17 15:05:18 +01001675
1676 if (
1677 filter_dict.get("name")
1678 and str(tenant_azure.get("name")) != filter_dict["name"]
1679 ):
jamartinezv14a823d2019-08-01 11:45:15 +02001680 continue
1681
1682 tenant_list.append(tenant_azure)
1683
1684 return tenant_list
seryio34478552019-05-23 14:50:49 +02001685
tierno84efdc12019-05-29 09:29:01 +00001686 def refresh_nets_status(self, net_list):
lloretgalleg45220152019-10-29 11:53:49 +01001687 """Get the status of the networks
sousaedu80135b92021-02-17 15:05:18 +01001688 Params: the list of network identifiers
1689 Returns a dictionary with:
1690 net_id: #VIM id of this network
1691 status: #Mandatory. Text with one of:
1692 # DELETED (not found at vim)
1693 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1694 # OTHER (Vim reported other status not understood)
1695 # ERROR (VIM indicates an ERROR status)
1696 # ACTIVE, INACTIVE, DOWN (admin down),
1697 # BUILD (on building process)
1698 #
1699 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1700 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
lloretgalleg45220152019-10-29 11:53:49 +01001701 """
tierno84efdc12019-05-29 09:29:01 +00001702 out_nets = {}
1703 self._reload_connection()
lloretgalleg45220152019-10-29 11:53:49 +01001704
1705 self.logger.debug("reload nets status net_list: %s", net_list)
tierno84efdc12019-05-29 09:29:01 +00001706 for net_id in net_list:
1707 try:
jamartinezv14a823d2019-08-01 11:45:15 +02001708 netName = self._get_net_name_from_resource_id(net_id)
tierno84efdc12019-05-29 09:29:01 +00001709 resName = self._get_resource_name_from_resource_id(net_id)
seryio34478552019-05-23 14:50:49 +02001710
gallardodd821752022-01-17 17:37:51 +00001711 net = self.conn_vnet.subnets.get(
1712 self.vnet_resource_group or self.resource_group, netName, resName
1713 )
jamartinezv14a823d2019-08-01 11:45:15 +02001714
lloretgalleg45220152019-10-29 11:53:49 +01001715 out_nets[net_id] = {
jamartinezv14a823d2019-08-01 11:45:15 +02001716 "status": self.provision_state2osm[net.provisioning_state],
sousaedu80135b92021-02-17 15:05:18 +01001717 "vim_info": str(net),
tierno84efdc12019-05-29 09:29:01 +00001718 }
1719 except CloudError as e:
lloretgalleg45220152019-10-29 11:53:49 +01001720 if e.error.error and "notfound" in e.error.error.lower():
sousaedu80135b92021-02-17 15:05:18 +01001721 self.logger.info(
1722 "Not found subnet net_name: %s, subnet_name: %s",
1723 netName,
1724 resName,
1725 )
1726 out_nets[net_id] = {"status": "DELETED", "error_msg": str(e)}
tierno84efdc12019-05-29 09:29:01 +00001727 else:
sousaedu80135b92021-02-17 15:05:18 +01001728 self.logger.error(
1729 "CloudError Exception %s when searching subnet", e
1730 )
lloretgalleg45220152019-10-29 11:53:49 +01001731 out_nets[net_id] = {
1732 "status": "VIM_ERROR",
sousaedu80135b92021-02-17 15:05:18 +01001733 "error_msg": str(e),
lloretgalleg45220152019-10-29 11:53:49 +01001734 }
tierno72774862020-05-04 11:44:15 +00001735 except vimconn.VimConnNotFoundException as e:
sousaedu80135b92021-02-17 15:05:18 +01001736 self.logger.error(
1737 "VimConnNotFoundException %s when searching subnet", e
1738 )
jamartinezv14a823d2019-08-01 11:45:15 +02001739 out_nets[net_id] = {
1740 "status": "DELETED",
sousaedu80135b92021-02-17 15:05:18 +01001741 "error_msg": str(e),
jamartinezv14a823d2019-08-01 11:45:15 +02001742 }
tierno84efdc12019-05-29 09:29:01 +00001743 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001744 self.logger.error(
1745 "Exception %s when searching subnet", e, exc_info=True
1746 )
tierno84efdc12019-05-29 09:29:01 +00001747 out_nets[net_id] = {
1748 "status": "VIM_ERROR",
sousaedu80135b92021-02-17 15:05:18 +01001749 "error_msg": str(e),
tierno84efdc12019-05-29 09:29:01 +00001750 }
sousaedu80135b92021-02-17 15:05:18 +01001751
tierno84efdc12019-05-29 09:29:01 +00001752 return out_nets
1753
1754 def refresh_vms_status(self, vm_list):
sousaedu80135b92021-02-17 15:05:18 +01001755 """Get the status of the virtual machines and their interfaces/ports
lloretgalleg45220152019-10-29 11:53:49 +01001756 Params: the list of VM identifiers
1757 Returns a dictionary with:
1758 vm_id: # VIM id of this Virtual Machine
1759 status: # Mandatory. Text with one of:
1760 # DELETED (not found at vim)
1761 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1762 # OTHER (Vim reported other status not understood)
1763 # ERROR (VIM indicates an ERROR status)
1764 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1765 # BUILD (on building process), ERROR
1766 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1767 # (ACTIVE:NoMgmtIP is not returned for Azure)
1768 #
1769 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1770 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1771 interfaces: list with interface info. Each item a dictionary with:
1772 vim_interface_id - The ID of the interface
1773 mac_address - The MAC address of the interface.
1774 ip_address - The IP address of the interface within the subnet.
1775 """
tierno84efdc12019-05-29 09:29:01 +00001776 out_vms = {}
1777 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +02001778
lloretgalleg45220152019-10-29 11:53:49 +01001779 self.logger.debug("refresh vm status vm_list: %s", vm_list)
1780 search_vm_list = vm_list or {}
1781
1782 for vm_id in search_vm_list:
1783 out_vm = {}
tierno84efdc12019-05-29 09:29:01 +00001784 try:
lloretgalleg45220152019-10-29 11:53:49 +01001785 res_name = self._get_resource_name_from_resource_id(vm_id)
jamartinezv14a823d2019-08-01 11:45:15 +02001786
sousaedu80135b92021-02-17 15:05:18 +01001787 vm = self.conn_compute.virtual_machines.get(
1788 self.resource_group, res_name
1789 )
aguilard309b5bf2023-10-05 14:13:23 +00001790 img = vm.storage_profile.image_reference
1791 images = self._get_version_image_list(
1792 img.publisher, img.offer, img.sku, img.version
1793 )
1794 vim_info = {
1795 "id": vm.id,
1796 "name": vm.name,
1797 "location": vm.location,
1798 "provisioning_state": vm.provisioning_state,
1799 "vm_id": vm.vm_id,
1800 "type": vm.type,
1801 "flavor": {"id": vm.hardware_profile.vm_size},
1802 "image": images[0],
1803 }
1804 out_vm["vim_info"] = str(vim_info)
sousaedu80135b92021-02-17 15:05:18 +01001805 out_vm["status"] = self.provision_state2osm.get(
1806 vm.provisioning_state, "OTHER"
1807 )
1808
1809 if vm.provisioning_state == "Succeeded":
lloretgalleg45220152019-10-29 11:53:49 +01001810 # check if machine is running or stopped
sousaedu80135b92021-02-17 15:05:18 +01001811 instance_view = self.conn_compute.virtual_machines.instance_view(
1812 self.resource_group, res_name
1813 )
1814
lloretgalleg45220152019-10-29 11:53:49 +01001815 for status in instance_view.statuses:
1816 splitted_status = status.code.split("/")
sousaedu80135b92021-02-17 15:05:18 +01001817 if (
1818 len(splitted_status) == 2
1819 and splitted_status[0] == "PowerState"
1820 ):
1821 out_vm["status"] = self.power_state2osm.get(
1822 splitted_status[1], "OTHER"
1823 )
jamartinezv14a823d2019-08-01 11:45:15 +02001824
1825 network_interfaces = vm.network_profile.network_interfaces
sousaedu80135b92021-02-17 15:05:18 +01001826 out_vm["interfaces"] = self._get_vm_interfaces_status(
1827 vm_id, network_interfaces
1828 )
jamartinezv14a823d2019-08-01 11:45:15 +02001829
lloretgalleg45220152019-10-29 11:53:49 +01001830 except CloudError as e:
1831 if e.error.error and "notfound" in e.error.error.lower():
1832 self.logger.debug("Not found vm id: %s", vm_id)
sousaedu80135b92021-02-17 15:05:18 +01001833 out_vm["status"] = "DELETED"
1834 out_vm["error_msg"] = str(e)
1835 out_vm["vim_info"] = None
lloretgalleg45220152019-10-29 11:53:49 +01001836 else:
1837 # maybe connection error or another type of error, return vim error
1838 self.logger.error("Exception %s refreshing vm_status", e)
sousaedu80135b92021-02-17 15:05:18 +01001839 out_vm["status"] = "VIM_ERROR"
1840 out_vm["error_msg"] = str(e)
1841 out_vm["vim_info"] = None
tierno84efdc12019-05-29 09:29:01 +00001842 except Exception as e:
lloretgalleg45220152019-10-29 11:53:49 +01001843 self.logger.error("Exception %s refreshing vm_status", e, exc_info=True)
sousaedu80135b92021-02-17 15:05:18 +01001844 out_vm["status"] = "VIM_ERROR"
1845 out_vm["error_msg"] = str(e)
1846 out_vm["vim_info"] = None
jamartinezv14a823d2019-08-01 11:45:15 +02001847
lloretgalleg45220152019-10-29 11:53:49 +01001848 out_vms[vm_id] = out_vm
tierno84efdc12019-05-29 09:29:01 +00001849
1850 return out_vms
seryio07ad1362019-05-29 09:16:24 +02001851
lloretgalleg45220152019-10-29 11:53:49 +01001852 def _get_vm_interfaces_status(self, vm_id, interfaces):
1853 """
1854 Gets the interfaces detail for a vm
1855 :param interfaces: List of interfaces.
1856 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1857 """
1858 try:
1859 interface_list = []
1860 for network_interface in interfaces:
1861 interface_dict = {}
sousaedu80135b92021-02-17 15:05:18 +01001862 nic_name = self._get_resource_name_from_resource_id(
1863 network_interface.id
1864 )
1865 interface_dict["vim_interface_id"] = network_interface.id
lloretgalleg45220152019-10-29 11:53:49 +01001866
1867 nic_data = self.conn_vnet.network_interfaces.get(
1868 self.resource_group,
sousaedu80135b92021-02-17 15:05:18 +01001869 nic_name,
1870 )
lloretgalleg45220152019-10-29 11:53:49 +01001871
lloretgallegbfc28b02019-11-21 09:31:16 +01001872 ips = []
1873 if nic_data.ip_configurations[0].public_ip_address:
1874 self.logger.debug("Obtain public ip address")
tiernob569e4b2019-11-21 16:10:32 +00001875 public_ip_name = self._get_resource_name_from_resource_id(
sousaedu80135b92021-02-17 15:05:18 +01001876 nic_data.ip_configurations[0].public_ip_address.id
1877 )
1878 public_ip = self.conn_vnet.public_ip_addresses.get(
1879 self.resource_group, public_ip_name
1880 )
lloretgallegbfc28b02019-11-21 09:31:16 +01001881 self.logger.debug("Public ip address is: %s", public_ip.ip_address)
1882 ips.append(public_ip.ip_address)
1883
aguilard309b5bf2023-10-05 14:13:23 +00001884 subnet = nic_data.ip_configurations[0].subnet.id
1885 if subnet:
1886 interface_dict["vim_net_id"] = subnet
1887
lloretgalleg45220152019-10-29 11:53:49 +01001888 private_ip = nic_data.ip_configurations[0].private_ip_address
lloretgallegbfc28b02019-11-21 09:31:16 +01001889 ips.append(private_ip)
lloretgalleg45220152019-10-29 11:53:49 +01001890
sousaedu80135b92021-02-17 15:05:18 +01001891 interface_dict["mac_address"] = nic_data.mac_address
1892 interface_dict["ip_address"] = ";".join(ips)
lloretgalleg45220152019-10-29 11:53:49 +01001893 interface_list.append(interface_dict)
1894
1895 return interface_list
1896 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +01001897 self.logger.error(
lloretgallegc8138e82021-03-31 07:12:06 +00001898 "Exception %s obtaining interface data for vm: %s",
sousaedu80135b92021-02-17 15:05:18 +01001899 e,
lloretgallegc8138e82021-03-31 07:12:06 +00001900 vm_id,
sousaedu80135b92021-02-17 15:05:18 +01001901 exc_info=True,
1902 )
lloretgalleg45220152019-10-29 11:53:49 +01001903 self._format_vimconn_exception(e)
1904
lloretgallegc8138e82021-03-31 07:12:06 +00001905 def _get_default_admin_user(self, image_id):
1906 if "ubuntu" in image_id.lower():
1907 return "ubuntu"
1908 else:
1909 return self._default_admin_user
1910
elumalai8658c2c2022-04-28 19:09:31 +05301911 def migrate_instance(self, vm_id, compute_host=None):
1912 """
1913 Migrate a vdu
1914 param:
1915 vm_id: ID of an instance
1916 compute_host: Host to migrate the vdu to
1917 """
1918 # TODO: Add support for migration
1919 raise vimconn.VimConnNotImplemented("Not implemented")
1920
sritharan29a4c1a2022-05-05 12:15:04 +00001921 def resize_instance(self, vm_id, flavor_id=None):
1922 """
1923 resize a vdu
1924 param:
1925 vm_id: ID of an instance
1926 flavor_id: flavor id to resize the vdu
1927 """
1928 # TODO: Add support for resize
1929 raise vimconn.VimConnNotImplemented("Not implemented")
1930
seryio34478552019-05-23 14:50:49 +02001931
1932if __name__ == "__main__":
lloretgallegc8138e82021-03-31 07:12:06 +00001933 # Init logger
1934 log_format = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
1935 log_formatter = logging.Formatter(log_format, datefmt="%Y-%m-%dT%H:%M:%S")
1936 handler = logging.StreamHandler()
1937 handler.setFormatter(log_formatter)
1938 logger = logging.getLogger("ro.vim.azure")
1939 # logger.setLevel(level=logging.ERROR)
1940 # logger.setLevel(level=logging.INFO)
1941 logger.setLevel(level=logging.DEBUG)
1942 logger.addHandler(handler)
1943
seryio34478552019-05-23 14:50:49 +02001944 # Making some basic test
sousaedu80135b92021-02-17 15:05:18 +01001945 vim_id = "azure"
1946 vim_name = "azure"
seryio34478552019-05-23 14:50:49 +02001947 needed_test_params = {
tiernodeb74b22019-05-27 10:24:50 +00001948 "client_id": "AZURE_CLIENT_ID",
1949 "secret": "AZURE_SECRET",
1950 "tenant": "AZURE_TENANT",
1951 "resource_group": "AZURE_RESOURCE_GROUP",
1952 "subscription_id": "AZURE_SUBSCRIPTION_ID",
tierno30d0d6d2019-05-27 08:14:01 +00001953 "vnet_name": "AZURE_VNET_NAME",
seryio34478552019-05-23 14:50:49 +02001954 }
1955 test_params = {}
1956
1957 for param, env_var in needed_test_params.items():
1958 value = getenv(env_var)
sousaedu80135b92021-02-17 15:05:18 +01001959
seryio34478552019-05-23 14:50:49 +02001960 if not value:
1961 raise Exception("Provide a valid value for env '{}'".format(env_var))
sousaedu80135b92021-02-17 15:05:18 +01001962
seryio34478552019-05-23 14:50:49 +02001963 test_params[param] = value
1964
1965 config = {
lloretgallegc8138e82021-03-31 07:12:06 +00001966 "region_name": getenv("AZURE_REGION_NAME", "northeurope"),
sousaedu80135b92021-02-17 15:05:18 +01001967 "resource_group": getenv("AZURE_RESOURCE_GROUP"),
1968 "subscription_id": getenv("AZURE_SUBSCRIPTION_ID"),
1969 "pub_key": getenv("AZURE_PUB_KEY", None),
lloretgallegc8138e82021-03-31 07:12:06 +00001970 "vnet_name": getenv("AZURE_VNET_NAME", "osm_vnet"),
seryio34478552019-05-23 14:50:49 +02001971 }
tierno30d0d6d2019-05-27 08:14:01 +00001972
sousaedu80135b92021-02-17 15:05:18 +01001973 azure = vimconnector(
1974 vim_id,
1975 vim_name,
1976 tenant_id=test_params["tenant"],
1977 tenant_name=None,
1978 url=None,
1979 url_admin=None,
1980 user=test_params["client_id"],
1981 passwd=test_params["secret"],
1982 log_level=None,
1983 config=config,
1984 )