Fix bug 1859 for VNET supporting in a different resource group
[osm/RO.git] / RO-VIM-azure / osm_rovim_azure / vimconn_azure.py
1 # -*- coding: utf-8 -*-
2 ##
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 ##
15
16 import base64
17 import logging
18 from os import getenv
19 import re
20
21 from azure.core.exceptions import ResourceNotFoundError
22 from azure.identity import ClientSecretCredential
23 from azure.mgmt.compute import ComputeManagementClient
24 from azure.mgmt.compute.models import DiskCreateOption
25 from azure.mgmt.network import NetworkManagementClient
26 from azure.mgmt.resource import ResourceManagementClient
27 from azure.profiles import ProfileDefinition
28 from cryptography.hazmat.backends import default_backend as crypto_default_backend
29 from cryptography.hazmat.primitives import serialization as crypto_serialization
30 from cryptography.hazmat.primitives.asymmetric import rsa
31 from msrest.exceptions import AuthenticationError
32 from msrestazure.azure_exceptions import CloudError
33 import msrestazure.tools as azure_tools
34 import netaddr
35 from osm_ro_plugin import vimconn
36 from requests.exceptions import ConnectionError
37
38 __author__ = "Isabel Lloret, Sergio Gonzalez, Alfonso Tierno, Gerardo Garcia"
39 __date__ = "$18-apr-2019 23:59:59$"
40
41
42 if getenv("OSMRO_PDB_DEBUG"):
43 import sys
44
45 print(sys.path)
46 import pdb
47
48 pdb.set_trace()
49
50
51 def find_in_list(the_list, condition_lambda):
52 for item in the_list:
53 if condition_lambda(item):
54 return item
55 else:
56 return None
57
58
59 class vimconnector(vimconn.VimConnector):
60
61 # Translate azure provisioning state to OSM provision state
62 # The first three ones are the transitional status once a user initiated action has been requested
63 # Once the operation is complete, it will transition into the states Succeeded or Failed
64 # https://docs.microsoft.com/en-us/azure/virtual-machines/windows/states-lifecycle
65 provision_state2osm = {
66 "Creating": "BUILD",
67 "Updating": "BUILD",
68 "Deleting": "INACTIVE",
69 "Succeeded": "ACTIVE",
70 "Failed": "ERROR",
71 }
72
73 # Translate azure power state to OSM provision state
74 power_state2osm = {
75 "starting": "INACTIVE",
76 "running": "ACTIVE",
77 "stopping": "INACTIVE",
78 "stopped": "INACTIVE",
79 "unknown": "OTHER",
80 "deallocated": "BUILD",
81 "deallocating": "BUILD",
82 }
83
84 # TODO - review availability zones
85 AZURE_ZONES = ["1", "2", "3"]
86
87 AZURE_COMPUTE_MGMT_CLIENT_API_VERSION = "2021-03-01"
88 AZURE_COMPUTE_MGMT_PROFILE_TAG = "azure.mgmt.compute.ComputeManagementClient"
89 AZURE_COMPUTE_MGMT_PROFILE = ProfileDefinition(
90 {
91 AZURE_COMPUTE_MGMT_PROFILE_TAG: {
92 None: AZURE_COMPUTE_MGMT_CLIENT_API_VERSION,
93 "availability_sets": "2020-12-01",
94 "dedicated_host_groups": "2020-12-01",
95 "dedicated_hosts": "2020-12-01",
96 "disk_accesses": "2020-12-01",
97 "disk_encryption_sets": "2020-12-01",
98 "disk_restore_point": "2020-12-01",
99 "disks": "2020-12-01",
100 "galleries": "2020-09-30",
101 "gallery_application_versions": "2020-09-30",
102 "gallery_applications": "2020-09-30",
103 "gallery_image_versions": "2020-09-30",
104 "gallery_images": "2020-09-30",
105 "gallery_sharing_profile": "2020-09-30",
106 "images": "2020-12-01",
107 "log_analytics": "2020-12-01",
108 "operations": "2020-12-01",
109 "proximity_placement_groups": "2020-12-01",
110 "resource_skus": "2019-04-01",
111 "shared_galleries": "2020-09-30",
112 "shared_gallery_image_versions": "2020-09-30",
113 "shared_gallery_images": "2020-09-30",
114 "snapshots": "2020-12-01",
115 "ssh_public_keys": "2020-12-01",
116 "usage": "2020-12-01",
117 "virtual_machine_extension_images": "2020-12-01",
118 "virtual_machine_extensions": "2020-12-01",
119 "virtual_machine_images": "2020-12-01",
120 "virtual_machine_images_edge_zone": "2020-12-01",
121 "virtual_machine_run_commands": "2020-12-01",
122 "virtual_machine_scale_set_extensions": "2020-12-01",
123 "virtual_machine_scale_set_rolling_upgrades": "2020-12-01",
124 "virtual_machine_scale_set_vm_extensions": "2020-12-01",
125 "virtual_machine_scale_set_vm_run_commands": "2020-12-01",
126 "virtual_machine_scale_set_vms": "2020-12-01",
127 "virtual_machine_scale_sets": "2020-12-01",
128 "virtual_machine_sizes": "2020-12-01",
129 "virtual_machines": "2020-12-01",
130 }
131 },
132 AZURE_COMPUTE_MGMT_PROFILE_TAG + " osm",
133 )
134
135 AZURE_RESOURCE_MGMT_CLIENT_API_VERSION = "2020-10-01"
136 AZURE_RESOURCE_MGMT_PROFILE_TAG = (
137 "azure.mgmt.resource.resources.ResourceManagementClient"
138 )
139 AZURE_RESOURCE_MGMT_PROFILE = ProfileDefinition(
140 {
141 AZURE_RESOURCE_MGMT_PROFILE_TAG: {
142 None: AZURE_RESOURCE_MGMT_CLIENT_API_VERSION,
143 }
144 },
145 AZURE_RESOURCE_MGMT_PROFILE_TAG + " osm",
146 )
147
148 AZURE_NETWORK_MGMT_CLIENT_API_VERSION = "2020-11-01"
149 AZURE_NETWORK_MGMT_PROFILE_TAG = "azure.mgmt.network.NetworkManagementClient"
150 AZURE_NETWORK_MGMT_PROFILE = ProfileDefinition(
151 {
152 AZURE_NETWORK_MGMT_PROFILE_TAG: {
153 None: AZURE_NETWORK_MGMT_CLIENT_API_VERSION,
154 "firewall_policy_rule_groups": "2020-04-01",
155 "interface_endpoints": "2019-02-01",
156 "p2_svpn_server_configurations": "2019-07-01",
157 }
158 },
159 AZURE_NETWORK_MGMT_PROFILE_TAG + " osm",
160 )
161
162 def __init__(
163 self,
164 uuid,
165 name,
166 tenant_id,
167 tenant_name,
168 url,
169 url_admin=None,
170 user=None,
171 passwd=None,
172 log_level=None,
173 config={},
174 persistent_info={},
175 ):
176 """
177 Constructor of VIM. Raise an exception is some needed parameter is missing, but it must not do any connectivity
178 checking against the VIM
179 Using common constructor parameters.
180 In this case: config must include the following parameters:
181 subscription_id: assigned azure subscription identifier
182 region_name: current region for azure network
183 resource_group: used for all azure created resources
184 vnet_name: base vnet for azure, created networks will be subnets from this base network
185 config may also include the following parameter:
186 flavors_pattern: pattern that will be used to select a range of vm sizes, for example
187 "^((?!Standard_B).)*$" will filter out Standard_B range that is cheap but is very overused
188 "^Standard_B" will select a serie B maybe for test environment
189 """
190 vimconn.VimConnector.__init__(
191 self,
192 uuid,
193 name,
194 tenant_id,
195 tenant_name,
196 url,
197 url_admin,
198 user,
199 passwd,
200 log_level,
201 config,
202 persistent_info,
203 )
204
205 # Variable that indicates if client must be reloaded or initialized
206 self.reload_client = True
207
208 self.vnet_address_space = None
209
210 # LOGGER
211 self.logger = logging.getLogger("ro.vim.azure")
212 if log_level:
213 self.logger.setLevel(getattr(logging, log_level))
214
215 self.tenant = tenant_id or tenant_name
216
217 # Store config to create azure subscription later
218 self._config = {
219 "user": user,
220 "passwd": passwd,
221 "tenant": tenant_id or tenant_name,
222 }
223
224 # SUBSCRIPTION
225 if "subscription_id" in config:
226 self._config["subscription_id"] = config.get("subscription_id")
227 # self.logger.debug("Setting subscription to: %s", self.config["subscription_id"])
228 else:
229 raise vimconn.VimConnException("Subscription not specified")
230
231 # RESOURCE_GROUP
232 if "resource_group" in config:
233 self.resource_group = config.get("resource_group")
234 else:
235 raise vimconn.VimConnException(
236 "Azure resource_group is not specified at config"
237 )
238
239 # REGION
240 if "region_name" in config:
241 self.region = config.get("region_name")
242 else:
243 raise vimconn.VimConnException(
244 "Azure region_name is not specified at config"
245 )
246
247 # VNET_NAME
248 if "vnet_name" in config:
249 self.vnet_name = config["vnet_name"]
250
251 # VNET_RESOURCE_GROUP
252 self.vnet_resource_group = config.get("vnet_resource_group")
253
254 # TODO - not used, do anything about it?
255 # public ssh key
256 self.pub_key = config.get("pub_key")
257
258 # TODO - check default user for azure
259 # default admin user
260 self._default_admin_user = "azureuser"
261
262 # flavor pattern regex
263 if "flavors_pattern" in config:
264 self._config["flavors_pattern"] = config["flavors_pattern"]
265
266 def _find_in_capabilities(self, capabilities, name):
267 cap = find_in_list(capabilities, lambda c: c["name"] == name)
268 if cap:
269 return cap.get("value")
270 else:
271 return None
272
273 def _reload_connection(self):
274 """
275 Called before any operation, checks python azure clients
276 """
277 if self.reload_client:
278 self.logger.debug("reloading azure client")
279
280 try:
281 self.credentials = ClientSecretCredential(
282 client_id=self._config["user"],
283 client_secret=self._config["passwd"],
284 tenant_id=self._config["tenant"],
285 )
286 self.conn = ResourceManagementClient(
287 self.credentials,
288 self._config["subscription_id"],
289 profile=self.AZURE_RESOURCE_MGMT_PROFILE,
290 )
291 self.conn_compute = ComputeManagementClient(
292 self.credentials,
293 self._config["subscription_id"],
294 profile=self.AZURE_COMPUTE_MGMT_PROFILE,
295 )
296 self.conn_vnet = NetworkManagementClient(
297 self.credentials,
298 self._config["subscription_id"],
299 profile=self.AZURE_NETWORK_MGMT_PROFILE,
300 )
301 self._check_or_create_resource_group()
302 self._check_or_create_vnet()
303
304 # Set to client created
305 self.reload_client = False
306 except Exception as e:
307 self._format_vimconn_exception(e)
308
309 def _get_resource_name_from_resource_id(self, resource_id):
310 """
311 Obtains resource_name from the azure complete identifier: resource_name will always be last item
312 """
313 try:
314 resource = str(resource_id.split("/")[-1])
315
316 return resource
317 except Exception as e:
318 raise vimconn.VimConnException(
319 "Unable to get resource name from resource_id '{}' Error: '{}'".format(
320 resource_id, e
321 )
322 )
323
324 def _get_location_from_resource_group(self, resource_group_name):
325 try:
326 location = self.conn.resource_groups.get(resource_group_name).location
327
328 return location
329 except Exception:
330 raise vimconn.VimConnNotFoundException(
331 "Location '{}' not found".format(resource_group_name)
332 )
333
334 def _get_resource_group_name_from_resource_id(self, resource_id):
335 try:
336 rg = str(resource_id.split("/")[4])
337
338 return rg
339 except Exception:
340 raise vimconn.VimConnException(
341 "Unable to get resource group from invalid resource_id format '{}'".format(
342 resource_id
343 )
344 )
345
346 def _get_net_name_from_resource_id(self, resource_id):
347 try:
348 net_name = str(resource_id.split("/")[8])
349
350 return net_name
351 except Exception:
352 raise vimconn.VimConnException(
353 "Unable to get azure net_name from invalid resource_id format '{}'".format(
354 resource_id
355 )
356 )
357
358 def _check_subnets_for_vm(self, net_list):
359 # All subnets must belong to the same resource group and vnet
360 # All subnets must belong to the same resource group anded vnet
361 rg_vnet = set(
362 self._get_resource_group_name_from_resource_id(net["net_id"])
363 + self._get_net_name_from_resource_id(net["net_id"])
364 for net in net_list
365 )
366
367 if len(rg_vnet) != 1:
368 raise self._format_vimconn_exception(
369 "Azure VMs can only attach to subnets in same VNET"
370 )
371
372 def _format_vimconn_exception(self, e):
373 """
374 Transforms a generic or azure exception to a vimcommException
375 """
376 self.logger.error("Azure plugin error: {}".format(e))
377 if isinstance(e, vimconn.VimConnException):
378 raise e
379 elif isinstance(e, AuthenticationError):
380 raise vimconn.VimConnAuthException(type(e).__name__ + ": " + str(e))
381 elif isinstance(e, ConnectionError):
382 raise vimconn.VimConnConnectionException(type(e).__name__ + ": " + str(e))
383 else:
384 # In case of generic error recreate client
385 self.reload_client = True
386
387 raise vimconn.VimConnException(type(e).__name__ + ": " + str(e))
388
389 def _check_or_create_resource_group(self):
390 """
391 Creates the base resource group if it does not exist
392 """
393 try:
394 rg_exists = self.conn.resource_groups.check_existence(self.resource_group)
395
396 if not rg_exists:
397 self.logger.debug("create base rgroup: %s", self.resource_group)
398 self.conn.resource_groups.create_or_update(
399 self.resource_group, {"location": self.region}
400 )
401 except Exception as e:
402 self._format_vimconn_exception(e)
403
404 def _check_or_create_vnet(self):
405 """
406 Try to get existent base vnet, in case it does not exist it creates it
407 """
408 try:
409 vnet = self.conn_vnet.virtual_networks.get(
410 self.vnet_resource_group or self.resource_group, self.vnet_name
411 )
412 self.vnet_address_space = vnet.address_space.address_prefixes[0]
413 self.vnet_id = vnet.id
414
415 return
416 except CloudError as e:
417 if e.error.error and "notfound" in e.error.error.lower():
418 pass
419 # continue and create it
420 else:
421 self._format_vimconn_exception(e)
422
423 # if it does not exist, create it
424 try:
425 vnet_params = {
426 "location": self.region,
427 "address_space": {"address_prefixes": ["10.0.0.0/8"]},
428 }
429 self.vnet_address_space = "10.0.0.0/8"
430
431 self.logger.debug("create base vnet: %s", self.vnet_name)
432 self.conn_vnet.virtual_networks.begin_create_or_update(
433 self.vnet_resource_group or self.resource_group,
434 self.vnet_name,
435 vnet_params,
436 )
437 vnet = self.conn_vnet.virtual_networks.get(
438 self.vnet_resource_group or self.resource_group, self.vnet_name
439 )
440 self.vnet_id = vnet.id
441 except Exception as e:
442 self._format_vimconn_exception(e)
443
444 def new_network(
445 self,
446 net_name,
447 net_type,
448 ip_profile=None,
449 shared=False,
450 provider_network_profile=None,
451 ):
452 """
453 Adds a tenant network to VIM
454 :param net_name: name of the network
455 :param net_type: not used for azure networks
456 :param ip_profile: is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
457 'ip-version': can be one of ['IPv4','IPv6']
458 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
459 'gateway-address': (Optional) ip_schema, that is X.X.X.X, not implemented for azure connector
460 'dns-address': (Optional) ip_schema, not implemented for azure connector
461 'dhcp': (Optional) dict containing, not implemented for azure connector
462 'enabled': {'type': 'boolean'},
463 'start-address': ip_schema, first IP to grant
464 'count': number of IPs to grant.
465 :param shared: Not allowed for Azure Connector
466 :param provider_network_profile: (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk}
467 :return: a tuple with the network identifier and created_items, or raises an exception on error
468 created_items can be None or a dictionary where this method can include key-values that will be passed to
469 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
470 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
471 as not present.
472 """
473 return self._new_subnet(net_name, ip_profile)
474
475 def _new_subnet(self, net_name, ip_profile):
476 """
477 Adds a tenant network to VIM. It creates a new subnet at existing base vnet
478 :param net_name: subnet name
479 :param ip_profile:
480 subnet-address: if it is not provided a subnet/24 in the default vnet is created,
481 otherwise it creates a subnet in the indicated address
482 :return: a tuple with the network identifier and created_items, or raises an exception on error
483 """
484 self.logger.debug("create subnet name %s, ip_profile %s", net_name, ip_profile)
485 self._reload_connection()
486
487 if ip_profile is None:
488 # get a non used vnet ip range /24 and allocate automatically inside the range self.vnet_address_space
489 used_subnets = self.get_network_list()
490 for ip_range in netaddr.IPNetwork(self.vnet_address_space).subnet(24):
491 for used_subnet in used_subnets:
492 subnet_range = netaddr.IPNetwork(used_subnet["cidr_block"])
493
494 if subnet_range in ip_range or ip_range in subnet_range:
495 # this range overlaps with an existing subnet ip range. Breaks and look for another
496 break
497 else:
498 ip_profile = {"subnet_address": str(ip_range)}
499 self.logger.debug("dinamically obtained ip_profile: %s", ip_range)
500 break
501 else:
502 raise vimconn.VimConnException(
503 "Cannot find a non-used subnet range in {}".format(
504 self.vnet_address_space
505 )
506 )
507 else:
508 ip_profile = {"subnet_address": ip_profile["subnet_address"]}
509
510 try:
511 # subnet_name = "{}-{}".format(net_name[:24], uuid4())
512 subnet_params = {"address_prefix": ip_profile["subnet_address"]}
513 # Assign a not duplicated net name
514 subnet_name = self._get_unused_subnet_name(net_name)
515
516 self.logger.debug("creating subnet_name: {}".format(subnet_name))
517 async_creation = self.conn_vnet.subnets.begin_create_or_update(
518 self.vnet_resource_group or self.resource_group,
519 self.vnet_name,
520 subnet_name,
521 subnet_params,
522 )
523 async_creation.wait()
524 # TODO - do not wait here, check where it is used
525 self.logger.debug("created subnet_name: {}".format(subnet_name))
526
527 return "{}/subnets/{}".format(self.vnet_id, subnet_name), None
528 except Exception as e:
529 self._format_vimconn_exception(e)
530
531 def _get_unused_subnet_name(self, subnet_name):
532 """
533 Adds a prefix to the subnet_name with a number in case the indicated name is repeated
534 Checks subnets with the indicated name (without suffix) and adds a suffix with a number
535 """
536 all_subnets = self.conn_vnet.subnets.list(
537 self.vnet_resource_group or self.resource_group, self.vnet_name
538 )
539 # Filter to subnets starting with the indicated name
540 subnets = list(
541 filter(lambda subnet: (subnet.name.startswith(subnet_name)), all_subnets)
542 )
543 net_names = [str(subnet.name) for subnet in subnets]
544
545 # get the name with the first not used suffix
546 name_suffix = 0
547 # name = subnet_name + "-" + str(name_suffix)
548 name = subnet_name # first subnet created will have no prefix
549 while name in net_names:
550 name_suffix += 1
551 name = subnet_name + "-" + str(name_suffix)
552
553 return name
554
555 def _create_nic(self, net, nic_name, region=None, static_ip=None, created_items={}):
556 self.logger.debug("create nic name %s, net_name %s", nic_name, net)
557 self._reload_connection()
558
559 subnet_id = net["net_id"]
560 location = self.region or self._get_location_from_resource_group(
561 self.resource_group
562 )
563
564 try:
565 net_ifz = {"location": location}
566 net_ip_config = {
567 "name": nic_name + "-ipconfiguration",
568 "subnet": {"id": subnet_id},
569 }
570
571 if static_ip:
572 net_ip_config["privateIPAddress"] = static_ip
573 net_ip_config["privateIPAllocationMethod"] = "Static"
574
575 net_ifz["ip_configurations"] = [net_ip_config]
576 mac_address = net.get("mac_address")
577
578 if mac_address:
579 net_ifz["mac_address"] = mac_address
580
581 async_nic_creation = (
582 self.conn_vnet.network_interfaces.begin_create_or_update(
583 self.resource_group, nic_name, net_ifz
584 )
585 )
586 nic_data = async_nic_creation.result()
587 created_items[nic_data.id] = True
588 self.logger.debug("created nic name %s", nic_name)
589
590 public_ip = net.get("floating_ip")
591 if public_ip:
592 public_ip_address_params = {
593 "location": location,
594 "public_ip_allocation_method": "Dynamic",
595 }
596 public_ip_name = nic_name + "-public-ip"
597 async_public_ip = (
598 self.conn_vnet.public_ip_addresses.begin_create_or_update(
599 self.resource_group, public_ip_name, public_ip_address_params
600 )
601 )
602 public_ip = async_public_ip.result()
603 self.logger.debug("created public IP: {}".format(public_ip))
604
605 # Associate NIC to Public IP
606 nic_data = self.conn_vnet.network_interfaces.get(
607 self.resource_group, nic_name
608 )
609
610 nic_data.ip_configurations[0].public_ip_address = public_ip
611 created_items[public_ip.id] = True
612
613 self.conn_vnet.network_interfaces.begin_create_or_update(
614 self.resource_group, nic_name, nic_data
615 )
616
617 except Exception as e:
618 self._format_vimconn_exception(e)
619
620 return nic_data, created_items
621
622 def new_flavor(self, flavor_data):
623 """
624 It is not allowed to create new flavors in Azure, must always use an existing one
625 """
626 raise vimconn.VimConnAuthException(
627 "It is not possible to create new flavors in AZURE"
628 )
629
630 def new_tenant(self, tenant_name, tenant_description):
631 """
632 It is not allowed to create new tenants in azure
633 """
634 raise vimconn.VimConnAuthException(
635 "It is not possible to create a TENANT in AZURE"
636 )
637
638 def new_image(self, image_dict):
639 """
640 It is not allowed to create new images in Azure, must always use an existing one
641 """
642 raise vimconn.VimConnAuthException(
643 "It is not possible to create new images in AZURE"
644 )
645
646 def get_image_id_from_path(self, path):
647 """Get the image id from image path in the VIM database.
648 Returns the image_id or raises a vimconnNotFoundException
649 """
650 raise vimconn.VimConnAuthException(
651 "It is not possible to obtain image from path in AZURE"
652 )
653
654 def get_image_list(self, filter_dict={}):
655 """Obtain tenant images from VIM
656 Filter_dict can be:
657 name: image name with the format: publisher:offer:sku:version
658 If some part of the name is provide ex: publisher:offer it will search all availables skus and version
659 for the provided publisher and offer
660 id: image uuid, currently not supported for azure
661 Returns the image list of dictionaries:
662 [{<the fields at Filter_dict plus some VIM specific>}, ...]
663 List can be empty
664 """
665 self.logger.debug("get_image_list filter {}".format(filter_dict))
666
667 self._reload_connection()
668 try:
669 image_list = []
670 if filter_dict.get("name"):
671 # name will have the format "publisher:offer:sku:version"
672 # publisher is required, offer sku and version will be searched if not provided
673 params = filter_dict["name"].split(":")
674 publisher = params[0]
675 if publisher:
676 # obtain offer list
677 offer_list = self._get_offer_list(params, publisher)
678
679 for offer in offer_list:
680 # obtain skus
681 sku_list = self._get_sku_list(params, publisher, offer)
682
683 for sku in sku_list:
684 # if version is defined get directly version, else list images
685 if len(params) == 4 and params[3]:
686 version = params[3]
687 if version == "latest":
688 image_list = self._get_sku_image_list(
689 publisher, offer, sku
690 )
691 image_list = [image_list[-1]]
692 else:
693 image_list = self._get_version_image_list(
694 publisher, offer, sku, version
695 )
696 else:
697 image_list = self._get_sku_image_list(
698 publisher, offer, sku
699 )
700 else:
701 raise vimconn.VimConnAuthException(
702 "List images in Azure must include name param with at least publisher"
703 )
704 else:
705 raise vimconn.VimConnAuthException(
706 "List images in Azure must include name param with at"
707 " least publisher"
708 )
709
710 return image_list
711 except Exception as e:
712 self._format_vimconn_exception(e)
713
714 def _get_offer_list(self, params, publisher):
715 """
716 Helper method to obtain offer list for defined publisher
717 """
718 if len(params) >= 2 and params[1]:
719 return [params[1]]
720 else:
721 try:
722 # get list of offers from azure
723 result_offers = self.conn_compute.virtual_machine_images.list_offers(
724 self.region, publisher
725 )
726
727 return [offer.name for offer in result_offers]
728 except CloudError as e:
729 # azure raises CloudError when not found
730 self.logger.info(
731 "error listing offers for publisher {}, Error: {}".format(
732 publisher, e
733 )
734 )
735
736 return []
737
738 def _get_sku_list(self, params, publisher, offer):
739 """
740 Helper method to obtain sku list for defined publisher and offer
741 """
742 if len(params) >= 3 and params[2]:
743 return [params[2]]
744 else:
745 try:
746 # get list of skus from azure
747 result_skus = self.conn_compute.virtual_machine_images.list_skus(
748 self.region, publisher, offer
749 )
750
751 return [sku.name for sku in result_skus]
752 except CloudError as e:
753 # azure raises CloudError when not found
754 self.logger.info(
755 "error listing skus for publisher {}, offer {}, Error: {}".format(
756 publisher, offer, e
757 )
758 )
759
760 return []
761
762 def _get_sku_image_list(self, publisher, offer, sku):
763 """
764 Helper method to obtain image list for publisher, offer and sku
765 """
766 image_list = []
767 try:
768 result_images = self.conn_compute.virtual_machine_images.list(
769 self.region, publisher, offer, sku
770 )
771 for result_image in result_images:
772 image_list.append(
773 {
774 "id": str(result_image.id),
775 "name": ":".join([publisher, offer, sku, result_image.name]),
776 }
777 )
778 except CloudError as e:
779 self.logger.info(
780 "error listing skus for publisher {}, offer {}, Error: {}".format(
781 publisher, offer, e
782 )
783 )
784 image_list = []
785
786 return image_list
787
788 def _get_version_image_list(self, publisher, offer, sku, version):
789 image_list = []
790 try:
791 result_image = self.conn_compute.virtual_machine_images.get(
792 self.region, publisher, offer, sku, version
793 )
794
795 if result_image:
796 image_list.append(
797 {
798 "id": str(result_image.id),
799 "name": ":".join([publisher, offer, sku, version]),
800 }
801 )
802 except CloudError as e:
803 # azure gives CloudError when not found
804 self.logger.info(
805 "error listing images for publisher {}, offer {}, sku {}, version {} Error: {}".format(
806 publisher, offer, sku, version, e
807 )
808 )
809 image_list = []
810
811 return image_list
812
813 def get_network_list(self, filter_dict={}):
814 """Obtain tenant networks of VIM
815 Filter_dict can be:
816 name: network name
817 id: network id
818 shared: boolean, not implemented in Azure
819 tenant_id: tenant, not used in Azure, all networks same tenants
820 admin_state_up: boolean, not implemented in Azure
821 status: 'ACTIVE', not implemented in Azure #
822 Returns the network list of dictionaries
823 """
824 # self.logger.debug("getting network list for vim, filter %s", filter_dict)
825 try:
826 self._reload_connection()
827
828 vnet = self.conn_vnet.virtual_networks.get(
829 self.vnet_resource_group or self.resource_group, self.vnet_name
830 )
831 subnet_list = []
832
833 for subnet in vnet.subnets:
834 if filter_dict:
835 if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
836 continue
837
838 if (
839 filter_dict.get("name")
840 and str(subnet.name) != filter_dict["name"]
841 ):
842 continue
843
844 name = self._get_resource_name_from_resource_id(subnet.id)
845
846 subnet_list.append(
847 {
848 "id": str(subnet.id),
849 "name": name,
850 "status": self.provision_state2osm[subnet.provisioning_state],
851 "cidr_block": str(subnet.address_prefix),
852 "type": "bridge",
853 "shared": False,
854 }
855 )
856
857 return subnet_list
858 except Exception as e:
859 self._format_vimconn_exception(e)
860
861 def new_vminstance(
862 self,
863 name,
864 description,
865 start,
866 image_id,
867 flavor_id,
868 net_list,
869 cloud_config=None,
870 disk_list=None,
871 availability_zone_index=None,
872 availability_zone_list=None,
873 ):
874 self.logger.debug(
875 "new vm instance name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, "
876 "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s",
877 name,
878 image_id,
879 flavor_id,
880 net_list,
881 cloud_config,
882 disk_list,
883 availability_zone_index,
884 availability_zone_list,
885 )
886 self._reload_connection()
887
888 # Validate input data is valid
889 # The virtual machine name must have less or 64 characters and it can not have the following
890 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
891 vm_name = self._check_vm_name(name)
892 # Obtain vm unused name
893 vm_name = self._get_unused_vm_name(vm_name)
894
895 # At least one network must be provided
896 if not net_list:
897 raise vimconn.VimConnException(
898 "At least one net must be provided to create a new VM"
899 )
900
901 # image_id are several fields of the image_id
902 image_reference = self._get_image_reference(image_id)
903
904 try:
905 virtual_machine = None
906 created_items = {}
907
908 # Create nics for each subnet
909 self._check_subnets_for_vm(net_list)
910 vm_nics = []
911
912 for idx, net in enumerate(net_list):
913 # Fault with subnet_id
914 # subnet_id=net["subnet_id"]
915 # subnet_id=net["net_id"]
916 nic_name = vm_name + "-nic-" + str(idx)
917 vm_nic, nic_items = self._create_nic(
918 net, nic_name, self.region, net.get("ip_address"), created_items
919 )
920 vm_nics.append({"id": str(vm_nic.id)})
921 net["vim_id"] = vm_nic.id
922
923 vm_parameters = {
924 "location": self.region,
925 "os_profile": self._build_os_profile(vm_name, cloud_config, image_id),
926 "hardware_profile": {"vm_size": flavor_id},
927 "storage_profile": {"image_reference": image_reference},
928 }
929
930 # If the machine has several networks one must be marked as primary
931 # As it is not indicated in the interface the first interface will be marked as primary
932 if len(vm_nics) > 1:
933 for idx, vm_nic in enumerate(vm_nics):
934 if idx == 0:
935 vm_nics[0]["Primary"] = True
936 else:
937 vm_nics[idx]["Primary"] = False
938
939 vm_parameters["network_profile"] = {"network_interfaces": vm_nics}
940
941 # Obtain zone information
942 vm_zone = self._get_vm_zone(availability_zone_index, availability_zone_list)
943 if vm_zone:
944 vm_parameters["zones"] = [vm_zone]
945
946 self.logger.debug("create vm name: %s", vm_name)
947 creation_result = self.conn_compute.virtual_machines.begin_create_or_update(
948 self.resource_group, vm_name, vm_parameters, polling=False
949 )
950 self.logger.debug("obtained creation result: %s", creation_result)
951 virtual_machine = creation_result.result()
952 self.logger.debug("created vm name: %s", vm_name)
953
954 """ Por ahora no hacer polling para ver si tarda menos
955 # Add disks if they are provided
956 if disk_list:
957 for disk_index, disk in enumerate(disk_list):
958 self.logger.debug(
959 "add disk size: %s, image: %s",
960 disk.get("size"),
961 disk.get("image"),
962 )
963 self._add_newvm_disk(
964 virtual_machine, vm_name, disk_index, disk, created_items
965 )
966
967 if start:
968 self.conn_compute.virtual_machines.start(self.resource_group, vm_name)
969 # start_result.wait()
970 """
971
972 return virtual_machine.id, created_items
973
974 # run_command_parameters = {
975 # "command_id": "RunShellScript", # For linux, don't change it
976 # "script": [
977 # "date > /tmp/test.txt"
978 # ]
979 # }
980 except Exception as e:
981 # Rollback vm creacion
982 vm_id = None
983
984 if virtual_machine:
985 vm_id = virtual_machine.id
986
987 try:
988 self.logger.debug("exception creating vm try to rollback")
989 self.delete_vminstance(vm_id, created_items)
990 except Exception as e2:
991 self.logger.error("new_vminstance rollback fail {}".format(e2))
992
993 self.logger.debug("Exception creating new vminstance: %s", e, exc_info=True)
994 self._format_vimconn_exception(e)
995
996 def _build_os_profile(self, vm_name, cloud_config, image_id):
997
998 # initial os_profile
999 os_profile = {"computer_name": vm_name}
1000
1001 # for azure os_profile admin_username is required
1002 if cloud_config and cloud_config.get("users"):
1003 admin_username = cloud_config.get("users")[0].get(
1004 "name", self._get_default_admin_user(image_id)
1005 )
1006 else:
1007 admin_username = self._get_default_admin_user(image_id)
1008 os_profile["admin_username"] = admin_username
1009
1010 # if there is a cloud-init load it
1011 if cloud_config:
1012 _, userdata = self._create_user_data(cloud_config)
1013 custom_data = base64.b64encode(userdata.encode("utf-8")).decode("latin-1")
1014 os_profile["custom_data"] = custom_data
1015
1016 # either password of ssh-keys are required
1017 # we will always use ssh-keys, in case it is not available we will generate it
1018 if cloud_config and cloud_config.get("key-pairs"):
1019 key_data = cloud_config.get("key-pairs")[0]
1020 else:
1021 _, key_data = self._generate_keys()
1022
1023 os_profile["linux_configuration"] = {
1024 "ssh": {
1025 "public_keys": [
1026 {
1027 "path": "/home/{}/.ssh/authorized_keys".format(admin_username),
1028 "key_data": key_data,
1029 }
1030 ]
1031 },
1032 }
1033
1034 return os_profile
1035
1036 def _generate_keys(self):
1037 """Method used to generate a pair of private/public keys.
1038 This method is used because to create a vm in Azure we always need a key or a password
1039 In some cases we may have a password in a cloud-init file but it may not be available
1040 """
1041 key = rsa.generate_private_key(
1042 backend=crypto_default_backend(), public_exponent=65537, key_size=2048
1043 )
1044 private_key = key.private_bytes(
1045 crypto_serialization.Encoding.PEM,
1046 crypto_serialization.PrivateFormat.PKCS8,
1047 crypto_serialization.NoEncryption(),
1048 )
1049 public_key = key.public_key().public_bytes(
1050 crypto_serialization.Encoding.OpenSSH,
1051 crypto_serialization.PublicFormat.OpenSSH,
1052 )
1053 private_key = private_key.decode("utf8")
1054 # Change first line because Paramiko needs a explicit start with 'BEGIN RSA PRIVATE KEY'
1055 i = private_key.find("\n")
1056 private_key = "-----BEGIN RSA PRIVATE KEY-----" + private_key[i:]
1057 public_key = public_key.decode("utf8")
1058
1059 return private_key, public_key
1060
1061 def _get_unused_vm_name(self, vm_name):
1062 """
1063 Checks the vm name and in case it is used adds a suffix to the name to allow creation
1064 :return:
1065 """
1066 all_vms = self.conn_compute.virtual_machines.list(self.resource_group)
1067 # Filter to vms starting with the indicated name
1068 vms = list(filter(lambda vm: (vm.name.startswith(vm_name)), all_vms))
1069 vm_names = [str(vm.name) for vm in vms]
1070
1071 # get the name with the first not used suffix
1072 name_suffix = 0
1073 # name = subnet_name + "-" + str(name_suffix)
1074 name = vm_name # first subnet created will have no prefix
1075
1076 while name in vm_names:
1077 name_suffix += 1
1078 name = vm_name + "-" + str(name_suffix)
1079
1080 return name
1081
1082 def _get_vm_zone(self, availability_zone_index, availability_zone_list):
1083 if availability_zone_index is None:
1084 return None
1085
1086 vim_availability_zones = self._get_azure_availability_zones()
1087 # check if VIM offer enough availability zones describe in the VNFD
1088 if vim_availability_zones and len(availability_zone_list) <= len(
1089 vim_availability_zones
1090 ):
1091 # check if all the names of NFV AV match VIM AV names
1092 match_by_index = False
1093
1094 if not availability_zone_list:
1095 match_by_index = True
1096 else:
1097 for av in availability_zone_list:
1098 if av not in vim_availability_zones:
1099 match_by_index = True
1100 break
1101
1102 if match_by_index:
1103 return vim_availability_zones[availability_zone_index]
1104 else:
1105 return availability_zone_list[availability_zone_index]
1106 else:
1107 raise vimconn.VimConnConflictException(
1108 "No enough availability zones at VIM for this deployment"
1109 )
1110
1111 def _get_azure_availability_zones(self):
1112 return self.AZURE_ZONES
1113
1114 def _add_newvm_disk(
1115 self, virtual_machine, vm_name, disk_index, disk, created_items={}
1116 ):
1117 disk_name = None
1118 data_disk = None
1119
1120 # Check if must create empty disk or from image
1121 if disk.get("vim_id"):
1122 # disk already exists, just get
1123 parsed_id = azure_tools.parse_resource_id(disk.get("vim_id"))
1124 disk_name = parsed_id.get("name")
1125 data_disk = self.conn_compute.disks.get(self.resource_group, disk_name)
1126 else:
1127 disk_name = vm_name + "_DataDisk_" + str(disk_index)
1128 if not disk.get("image_id"):
1129 self.logger.debug("create new data disk name: %s", disk_name)
1130 async_disk_creation = self.conn_compute.disks.begin_create_or_update(
1131 self.resource_group,
1132 disk_name,
1133 {
1134 "location": self.region,
1135 "disk_size_gb": disk.get("size"),
1136 "creation_data": {"create_option": DiskCreateOption.empty},
1137 },
1138 )
1139 data_disk = async_disk_creation.result()
1140 created_items[data_disk.id] = True
1141 else:
1142 image_id = disk.get("image_id")
1143
1144 if azure_tools.is_valid_resource_id(image_id):
1145 parsed_id = azure_tools.parse_resource_id(image_id)
1146
1147 # Check if image is snapshot or disk
1148 image_name = parsed_id.get("name")
1149 type = parsed_id.get("resource_type")
1150
1151 if type == "snapshots" or type == "disks":
1152 self.logger.debug("create disk from copy name: %s", image_name)
1153 # ¿Should check that snapshot exists?
1154 async_disk_creation = (
1155 self.conn_compute.disks.begin_create_or_update(
1156 self.resource_group,
1157 disk_name,
1158 {
1159 "location": self.region,
1160 "creation_data": {
1161 "create_option": "Copy",
1162 "source_uri": image_id,
1163 },
1164 },
1165 )
1166 )
1167 data_disk = async_disk_creation.result()
1168 created_items[data_disk.id] = True
1169 else:
1170 raise vimconn.VimConnNotFoundException(
1171 "Invalid image_id: %s ", image_id
1172 )
1173 else:
1174 raise vimconn.VimConnNotFoundException(
1175 "Invalid image_id: %s ", image_id
1176 )
1177
1178 # Attach the disk created
1179 virtual_machine.storage_profile.data_disks.append(
1180 {
1181 "lun": disk_index,
1182 "name": disk_name,
1183 "create_option": DiskCreateOption.attach,
1184 "managed_disk": {"id": data_disk.id},
1185 "disk_size_gb": disk.get("size"),
1186 }
1187 )
1188 self.logger.debug("attach disk name: %s", disk_name)
1189 self.conn_compute.virtual_machines.begin_create_or_update(
1190 self.resource_group, virtual_machine.name, virtual_machine
1191 )
1192
1193 # It is necesary extract from image_id data to create the VM with this format
1194 # "image_reference": {
1195 # "publisher": vm_reference["publisher"],
1196 # "offer": vm_reference["offer"],
1197 # "sku": vm_reference["sku"],
1198 # "version": vm_reference["version"]
1199 # },
1200 def _get_image_reference(self, image_id):
1201 try:
1202 # The data input format example:
1203 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
1204 # Publishers/Canonical/ArtifactTypes/VMImage/
1205 # Offers/UbuntuServer/
1206 # Skus/18.04-LTS/
1207 # Versions/18.04.201809110
1208 publisher = str(image_id.split("/")[8])
1209 offer = str(image_id.split("/")[12])
1210 sku = str(image_id.split("/")[14])
1211 version = str(image_id.split("/")[16])
1212
1213 return {
1214 "publisher": publisher,
1215 "offer": offer,
1216 "sku": sku,
1217 "version": version,
1218 }
1219 except Exception:
1220 raise vimconn.VimConnException(
1221 "Unable to get image_reference from invalid image_id format: '{}'".format(
1222 image_id
1223 )
1224 )
1225
1226 # Azure VM names can not have some special characters
1227 def _check_vm_name(self, vm_name):
1228 """
1229 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
1230 """
1231 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
1232
1233 # First: the VM name max length is 64 characters
1234 vm_name_aux = vm_name[:64]
1235
1236 # Second: replace not allowed characters
1237 for elem in chars_not_allowed_list:
1238 # Check if string is in the main string
1239 if elem in vm_name_aux:
1240 # self.logger.debug("Dentro del IF")
1241 # Replace the string
1242 vm_name_aux = vm_name_aux.replace(elem, "-")
1243
1244 return vm_name_aux
1245
1246 def get_flavor_id_from_data(self, flavor_dict):
1247 self.logger.debug("getting flavor id from data, flavor_dict: %s", flavor_dict)
1248 filter_dict = flavor_dict or {}
1249
1250 try:
1251 self._reload_connection()
1252 vm_sizes_list = [
1253 vm_size.as_dict()
1254 for vm_size in self.conn_compute.resource_skus.list(
1255 "location eq '{}'".format(self.region)
1256 )
1257 ]
1258
1259 cpus = filter_dict.get("vcpus") or 0
1260 memMB = filter_dict.get("ram") or 0
1261 numberInterfaces = len(filter_dict.get("interfaces", [])) or 0
1262
1263 # Filter
1264 filtered_sizes = []
1265 for size in vm_sizes_list:
1266 if size["resource_type"] == "virtualMachines":
1267 size_cpus = int(
1268 self._find_in_capabilities(size["capabilities"], "vCPUs")
1269 )
1270 size_memory = float(
1271 self._find_in_capabilities(size["capabilities"], "MemoryGB")
1272 )
1273 size_interfaces = self._find_in_capabilities(
1274 size["capabilities"], "MaxNetworkInterfaces"
1275 )
1276 if size_interfaces:
1277 size_interfaces = int(size_interfaces)
1278 else:
1279 self.logger.debug(
1280 "Flavor with no defined MaxNetworkInterfaces: {}".format(
1281 size["name"]
1282 )
1283 )
1284 continue
1285 if (
1286 size_cpus >= cpus
1287 and size_memory >= memMB / 1024
1288 and size_interfaces >= numberInterfaces
1289 ):
1290 if self._config.get("flavors_pattern"):
1291 if re.search(
1292 self._config.get("flavors_pattern"), size["name"]
1293 ):
1294 new_size = {
1295 e["name"]: e["value"] for e in size["capabilities"]
1296 }
1297 new_size["name"] = size["name"]
1298 filtered_sizes.append(new_size)
1299 else:
1300 new_size = {
1301 e["name"]: e["value"] for e in size["capabilities"]
1302 }
1303 new_size["name"] = size["name"]
1304 filtered_sizes.append(new_size)
1305
1306 # Sort
1307 listedFilteredSizes = sorted(
1308 filtered_sizes,
1309 key=lambda k: (
1310 int(k["vCPUs"]),
1311 float(k["MemoryGB"]),
1312 int(k["MaxNetworkInterfaces"]),
1313 int(k["MaxResourceVolumeMB"]),
1314 ),
1315 )
1316
1317 if listedFilteredSizes:
1318 return listedFilteredSizes[0]["name"]
1319
1320 raise vimconn.VimConnNotFoundException(
1321 "Cannot find any flavor matching '{}'".format(str(flavor_dict))
1322 )
1323 except Exception as e:
1324 self._format_vimconn_exception(e)
1325
1326 def _get_flavor_id_from_flavor_name(self, flavor_name):
1327 # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name))
1328 try:
1329 self._reload_connection()
1330 vm_sizes_list = [
1331 vm_size.as_dict()
1332 for vm_size in self.conn_compute.resource_skus.list(
1333 "location eq '{}'".format(self.region)
1334 )
1335 ]
1336
1337 output_flavor = None
1338 for size in vm_sizes_list:
1339 if size["name"] == flavor_name:
1340 output_flavor = size
1341
1342 # None is returned if not found anything
1343 return output_flavor
1344 except Exception as e:
1345 self._format_vimconn_exception(e)
1346
1347 def check_vim_connectivity(self):
1348 try:
1349 self._reload_connection()
1350 return True
1351 except Exception as e:
1352 raise vimconn.VimConnException(
1353 "Connectivity issue with Azure API: {}".format(e)
1354 )
1355
1356 def get_network(self, net_id):
1357 # self.logger.debug("get network id: {}".format(net_id))
1358 # res_name = self._get_resource_name_from_resource_id(net_id)
1359 self._reload_connection()
1360
1361 filter_dict = {"name": net_id}
1362 network_list = self.get_network_list(filter_dict)
1363
1364 if not network_list:
1365 raise vimconn.VimConnNotFoundException(
1366 "network '{}' not found".format(net_id)
1367 )
1368 else:
1369 return network_list[0]
1370
1371 def delete_network(self, net_id, created_items=None):
1372 self.logger.debug(
1373 "deleting network {} - {}".format(
1374 self.vnet_resource_group or self.resource_group, net_id
1375 )
1376 )
1377
1378 self._reload_connection()
1379 res_name = self._get_resource_name_from_resource_id(net_id)
1380
1381 try:
1382 # Obtain subnets ant try to delete nic first
1383 subnet = self.conn_vnet.subnets.get(
1384 self.vnet_resource_group or self.resource_group,
1385 self.vnet_name,
1386 res_name,
1387 )
1388 if not subnet:
1389 raise vimconn.VimConnNotFoundException(
1390 "network '{}' not found".format(net_id)
1391 )
1392
1393 # TODO - for a quick-fix delete nics sequentially but should not wait
1394 # for each in turn
1395 if subnet.ip_configurations:
1396 for ip_configuration in subnet.ip_configurations:
1397 # obtain nic_name from ip_configuration
1398 parsed_id = azure_tools.parse_resource_id(ip_configuration.id)
1399 nic_name = parsed_id["name"]
1400 self.delete_inuse_nic(nic_name)
1401
1402 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
1403 # Put the initial virtual_network API
1404 async_delete = self.conn_vnet.subnets.begin_delete(
1405 self.vnet_resource_group or self.resource_group,
1406 self.vnet_name,
1407 res_name,
1408 )
1409 async_delete.wait()
1410
1411 return net_id
1412
1413 except ResourceNotFoundError:
1414 raise vimconn.VimConnNotFoundException(
1415 "network '{}' not found".format(net_id)
1416 )
1417 except CloudError as e:
1418 if e.error.error and "notfound" in e.error.error.lower():
1419 raise vimconn.VimConnNotFoundException(
1420 "network '{}' not found".format(net_id)
1421 )
1422 else:
1423 self._format_vimconn_exception(e)
1424 except Exception as e:
1425 self._format_vimconn_exception(e)
1426
1427 def delete_inuse_nic(self, nic_name):
1428
1429 # Obtain nic data
1430 nic_data = self.conn_vnet.network_interfaces.get(self.resource_group, nic_name)
1431
1432 # Obtain vm associated to nic in case it exists
1433 if nic_data.virtual_machine:
1434 vm_name = azure_tools.parse_resource_id(nic_data.virtual_machine.id)["name"]
1435 self.logger.debug("vm_name: {}".format(vm_name))
1436 virtual_machine = self.conn_compute.virtual_machines.get(
1437 self.resource_group, vm_name
1438 )
1439 self.logger.debug("obtained vm")
1440
1441 # Deattach nic from vm if it has netwolk machines attached
1442 network_interfaces = virtual_machine.network_profile.network_interfaces
1443 network_interfaces[:] = [
1444 interface
1445 for interface in network_interfaces
1446 if self._get_resource_name_from_resource_id(interface.id) != nic_name
1447 ]
1448
1449 # TODO - check if there is a public ip to delete and delete it
1450 if network_interfaces:
1451
1452 # Deallocate the vm
1453 async_vm_deallocate = (
1454 self.conn_compute.virtual_machines.begin_deallocate(
1455 self.resource_group, vm_name
1456 )
1457 )
1458 self.logger.debug("deallocating vm")
1459 async_vm_deallocate.wait()
1460 self.logger.debug("vm deallocated")
1461
1462 async_vm_update = (
1463 self.conn_compute.virtual_machines.begin_create_or_update(
1464 self.resource_group, vm_name, virtual_machine
1465 )
1466 )
1467 virtual_machine = async_vm_update.result()
1468 self.logger.debug("nic removed from interface")
1469
1470 else:
1471 self.logger.debug("There are no interfaces left, delete vm")
1472 self.delete_vminstance(virtual_machine.id)
1473 self.logger.debug("Delete vm")
1474
1475 # Delete nic
1476 self.logger.debug("delete NIC name: %s", nic_name)
1477 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
1478 self.resource_group, nic_name
1479 )
1480 nic_delete.wait()
1481 self.logger.debug("deleted NIC name: %s", nic_name)
1482
1483 def delete_vminstance(self, vm_id, created_items=None):
1484 """Deletes a vm instance from the vim."""
1485 self.logger.debug(
1486 "deleting VM instance {} - {}".format(self.resource_group, vm_id)
1487 )
1488 self._reload_connection()
1489
1490 created_items = created_items or {}
1491 try:
1492 # Check vm exists, we can call delete_vm to clean created_items
1493 if vm_id:
1494 res_name = self._get_resource_name_from_resource_id(vm_id)
1495 vm = self.conn_compute.virtual_machines.get(
1496 self.resource_group, res_name
1497 )
1498
1499 # Shuts down the virtual machine and releases the compute resources
1500 # vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1501 # vm_stop.wait()
1502
1503 vm_delete = self.conn_compute.virtual_machines.begin_delete(
1504 self.resource_group, res_name
1505 )
1506 vm_delete.wait()
1507 self.logger.debug("deleted VM name: %s", res_name)
1508
1509 # Delete OS Disk, check if exists, in case of error creating
1510 # it may not be fully created
1511 if vm.storage_profile.os_disk:
1512 os_disk_name = vm.storage_profile.os_disk.name
1513 self.logger.debug("delete OS DISK: %s", os_disk_name)
1514 async_disk_delete = self.conn_compute.disks.begin_delete(
1515 self.resource_group, os_disk_name
1516 )
1517 async_disk_delete.wait()
1518 # os disks are created always with the machine
1519 self.logger.debug("deleted OS DISK name: %s", os_disk_name)
1520
1521 for data_disk in vm.storage_profile.data_disks:
1522 self.logger.debug("delete data_disk: %s", data_disk.name)
1523 async_disk_delete = self.conn_compute.disks.begin_delete(
1524 self.resource_group, data_disk.name
1525 )
1526 async_disk_delete.wait()
1527 self._markdel_created_item(data_disk.managed_disk.id, created_items)
1528 self.logger.debug("deleted OS DISK name: %s", data_disk.name)
1529
1530 # After deleting VM, it is necessary to delete NIC, because if is not deleted delete_network
1531 # does not work because Azure says that is in use the subnet
1532 network_interfaces = vm.network_profile.network_interfaces
1533
1534 for network_interface in network_interfaces:
1535 nic_name = self._get_resource_name_from_resource_id(
1536 network_interface.id
1537 )
1538 nic_data = self.conn_vnet.network_interfaces.get(
1539 self.resource_group, nic_name
1540 )
1541
1542 public_ip_name = None
1543 exist_public_ip = nic_data.ip_configurations[0].public_ip_address
1544 if exist_public_ip:
1545 public_ip_id = nic_data.ip_configurations[
1546 0
1547 ].public_ip_address.id
1548
1549 # Delete public_ip
1550 public_ip_name = self._get_resource_name_from_resource_id(
1551 public_ip_id
1552 )
1553
1554 # Public ip must be deleted afterwards of nic that is attached
1555
1556 self.logger.debug("delete NIC name: %s", nic_name)
1557 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
1558 self.resource_group, nic_name
1559 )
1560 nic_delete.wait()
1561 self._markdel_created_item(network_interface.id, created_items)
1562 self.logger.debug("deleted NIC name: %s", nic_name)
1563
1564 # Delete list of public ips
1565 if public_ip_name:
1566 self.logger.debug("delete PUBLIC IP - " + public_ip_name)
1567 ip_delete = self.conn_vnet.public_ip_addresses.begin_delete(
1568 self.resource_group, public_ip_name
1569 )
1570 ip_delete.wait()
1571 self._markdel_created_item(public_ip_id, created_items)
1572
1573 # Delete created items
1574 self._delete_created_items(created_items)
1575
1576 except ResourceNotFoundError:
1577 raise vimconn.VimConnNotFoundException(
1578 "No vm instance found '{}'".format(vm_id)
1579 )
1580 except CloudError as e:
1581 if e.error.error and "notfound" in e.error.error.lower():
1582 raise vimconn.VimConnNotFoundException(
1583 "No vm instance found '{}'".format(vm_id)
1584 )
1585 else:
1586 self._format_vimconn_exception(e)
1587 except Exception as e:
1588 self._format_vimconn_exception(e)
1589
1590 def _markdel_created_item(self, item_id, created_items):
1591 if item_id in created_items:
1592 created_items[item_id] = False
1593
1594 def _delete_created_items(self, created_items):
1595 """Delete created_items elements that have not been deleted with the virtual machine
1596 Created_items may not be deleted correctly with the created machine if the
1597 virtual machine fails creating or in other cases of error
1598 """
1599 self.logger.debug("Created items: %s", created_items)
1600 # TODO - optimize - should not wait until it is deleted
1601 # Must delete in order first nics, then public_ips
1602 # As dictionaries don't preserve order, first get items to be deleted then delete them
1603 nics_to_delete = []
1604 publics_ip_to_delete = []
1605 disks_to_delete = []
1606 for item_id, v in created_items.items():
1607 if not v: # skip already deleted
1608 continue
1609
1610 # self.logger.debug("Must delete item id: %s", item_id)
1611 # Obtain type, supported nic, disk or public ip
1612 parsed_id = azure_tools.parse_resource_id(item_id)
1613 resource_type = parsed_id.get("resource_type")
1614 name = parsed_id.get("name")
1615
1616 if resource_type == "networkInterfaces":
1617 nics_to_delete.append(name)
1618 elif resource_type == "publicIPAddresses":
1619 publics_ip_to_delete.append(name)
1620 elif resource_type == "disks":
1621 disks_to_delete.append(name)
1622
1623 # Now delete
1624 for item_name in nics_to_delete:
1625 try:
1626 self.logger.debug("deleting nic name %s:", item_name)
1627 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
1628 self.resource_group, item_name
1629 )
1630 nic_delete.wait()
1631 self.logger.debug("deleted nic name %s:", item_name)
1632 except Exception as e:
1633 self.logger.error(
1634 "Error deleting item: {}: {}".format(type(e).__name__, e)
1635 )
1636
1637 for item_name in publics_ip_to_delete:
1638 try:
1639 self.logger.debug("deleting public ip name %s:", item_name)
1640 ip_delete = self.conn_vnet.public_ip_addresses.begin_delete(
1641 self.resource_group, name
1642 )
1643 ip_delete.wait()
1644 self.logger.debug("deleted public ip name %s:", item_name)
1645 except Exception as e:
1646 self.logger.error(
1647 "Error deleting item: {}: {}".format(type(e).__name__, e)
1648 )
1649
1650 for item_name in disks_to_delete:
1651 try:
1652 self.logger.debug("deleting data disk name %s:", name)
1653 async_disk_delete = self.conn_compute.disks.begin_delete(
1654 self.resource_group, item_name
1655 )
1656 async_disk_delete.wait()
1657 self.logger.debug("deleted data disk name %s:", name)
1658 except Exception as e:
1659 self.logger.error(
1660 "Error deleting item: {}: {}".format(type(e).__name__, e)
1661 )
1662
1663 def action_vminstance(self, vm_id, action_dict, created_items={}):
1664 """Send and action over a VM instance from VIM
1665 Returns the vm_id if the action was successfully sent to the VIM
1666 """
1667 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1668
1669 try:
1670 self._reload_connection()
1671 resName = self._get_resource_name_from_resource_id(vm_id)
1672
1673 if "start" in action_dict:
1674 self.conn_compute.virtual_machines.begin_start(
1675 self.resource_group, resName
1676 )
1677 elif (
1678 "stop" in action_dict
1679 or "shutdown" in action_dict
1680 or "shutoff" in action_dict
1681 ):
1682 self.conn_compute.virtual_machines.begin_power_off(
1683 self.resource_group, resName
1684 )
1685 elif "terminate" in action_dict:
1686 self.conn_compute.virtual_machines.begin_delete(
1687 self.resource_group, resName
1688 )
1689 elif "reboot" in action_dict:
1690 self.conn_compute.virtual_machines.begin_restart(
1691 self.resource_group, resName
1692 )
1693
1694 return None
1695 except ResourceNotFoundError:
1696 raise vimconn.VimConnNotFoundException("No vm found '{}'".format(vm_id))
1697 except CloudError as e:
1698 if e.error.error and "notfound" in e.error.error.lower():
1699 raise vimconn.VimConnNotFoundException("No vm found '{}'".format(vm_id))
1700 else:
1701 self._format_vimconn_exception(e)
1702 except Exception as e:
1703 self._format_vimconn_exception(e)
1704
1705 def delete_flavor(self, flavor_id):
1706 raise vimconn.VimConnAuthException(
1707 "It is not possible to delete a FLAVOR in AZURE"
1708 )
1709
1710 def delete_tenant(self, tenant_id):
1711 raise vimconn.VimConnAuthException(
1712 "It is not possible to delete a TENANT in AZURE"
1713 )
1714
1715 def delete_image(self, image_id):
1716 raise vimconn.VimConnAuthException(
1717 "It is not possible to delete a IMAGE in AZURE"
1718 )
1719
1720 def get_vminstance(self, vm_id):
1721 """
1722 Obtaing the vm instance data from v_id
1723 """
1724 self.logger.debug("get vm instance: %s", vm_id)
1725 self._reload_connection()
1726 try:
1727 resName = self._get_resource_name_from_resource_id(vm_id)
1728 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
1729 except ResourceNotFoundError:
1730 raise vimconn.VimConnNotFoundException(
1731 "No vminstance found '{}'".format(vm_id)
1732 )
1733 except CloudError as e:
1734 if e.error.error and "notfound" in e.error.error.lower():
1735 raise vimconn.VimConnNotFoundException(
1736 "No vminstance found '{}'".format(vm_id)
1737 )
1738 else:
1739 self._format_vimconn_exception(e)
1740 except Exception as e:
1741 self._format_vimconn_exception(e)
1742
1743 return vm
1744
1745 def get_flavor(self, flavor_id):
1746 """
1747 Obtains the flavor_data from the flavor_id
1748 """
1749 self._reload_connection()
1750 self.logger.debug("get flavor from id: %s", flavor_id)
1751 flavor_data = self._get_flavor_id_from_flavor_name(flavor_id)
1752
1753 if flavor_data:
1754 flavor = {
1755 "id": flavor_id,
1756 "name": flavor_id,
1757 "ram": flavor_data["memoryInMB"],
1758 "vcpus": flavor_data["numberOfCores"],
1759 "disk": flavor_data["resourceDiskSizeInMB"] / 1024,
1760 }
1761
1762 return flavor
1763 else:
1764 raise vimconn.VimConnNotFoundException(
1765 "flavor '{}' not found".format(flavor_id)
1766 )
1767
1768 def get_tenant_list(self, filter_dict={}):
1769 """Obtains the list of tenants
1770 For the azure connector only the azure tenant will be returned if it is compatible
1771 with filter_dict
1772 """
1773 tenants_azure = [{"name": self.tenant, "id": self.tenant}]
1774 tenant_list = []
1775
1776 self.logger.debug("get tenant list: %s", filter_dict)
1777 for tenant_azure in tenants_azure:
1778 if filter_dict:
1779 if (
1780 filter_dict.get("id")
1781 and str(tenant_azure.get("id")) != filter_dict["id"]
1782 ):
1783 continue
1784
1785 if (
1786 filter_dict.get("name")
1787 and str(tenant_azure.get("name")) != filter_dict["name"]
1788 ):
1789 continue
1790
1791 tenant_list.append(tenant_azure)
1792
1793 return tenant_list
1794
1795 def refresh_nets_status(self, net_list):
1796 """Get the status of the networks
1797 Params: the list of network identifiers
1798 Returns a dictionary with:
1799 net_id: #VIM id of this network
1800 status: #Mandatory. Text with one of:
1801 # DELETED (not found at vim)
1802 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1803 # OTHER (Vim reported other status not understood)
1804 # ERROR (VIM indicates an ERROR status)
1805 # ACTIVE, INACTIVE, DOWN (admin down),
1806 # BUILD (on building process)
1807 #
1808 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1809 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1810 """
1811 out_nets = {}
1812 self._reload_connection()
1813
1814 self.logger.debug("reload nets status net_list: %s", net_list)
1815 for net_id in net_list:
1816 try:
1817 netName = self._get_net_name_from_resource_id(net_id)
1818 resName = self._get_resource_name_from_resource_id(net_id)
1819
1820 net = self.conn_vnet.subnets.get(
1821 self.vnet_resource_group or self.resource_group, netName, resName
1822 )
1823
1824 out_nets[net_id] = {
1825 "status": self.provision_state2osm[net.provisioning_state],
1826 "vim_info": str(net),
1827 }
1828 except CloudError as e:
1829 if e.error.error and "notfound" in e.error.error.lower():
1830 self.logger.info(
1831 "Not found subnet net_name: %s, subnet_name: %s",
1832 netName,
1833 resName,
1834 )
1835 out_nets[net_id] = {"status": "DELETED", "error_msg": str(e)}
1836 else:
1837 self.logger.error(
1838 "CloudError Exception %s when searching subnet", e
1839 )
1840 out_nets[net_id] = {
1841 "status": "VIM_ERROR",
1842 "error_msg": str(e),
1843 }
1844 except vimconn.VimConnNotFoundException as e:
1845 self.logger.error(
1846 "VimConnNotFoundException %s when searching subnet", e
1847 )
1848 out_nets[net_id] = {
1849 "status": "DELETED",
1850 "error_msg": str(e),
1851 }
1852 except Exception as e:
1853 self.logger.error(
1854 "Exception %s when searching subnet", e, exc_info=True
1855 )
1856 out_nets[net_id] = {
1857 "status": "VIM_ERROR",
1858 "error_msg": str(e),
1859 }
1860
1861 return out_nets
1862
1863 def refresh_vms_status(self, vm_list):
1864 """Get the status of the virtual machines and their interfaces/ports
1865 Params: the list of VM identifiers
1866 Returns a dictionary with:
1867 vm_id: # VIM id of this Virtual Machine
1868 status: # Mandatory. Text with one of:
1869 # DELETED (not found at vim)
1870 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1871 # OTHER (Vim reported other status not understood)
1872 # ERROR (VIM indicates an ERROR status)
1873 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1874 # BUILD (on building process), ERROR
1875 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1876 # (ACTIVE:NoMgmtIP is not returned for Azure)
1877 #
1878 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1879 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1880 interfaces: list with interface info. Each item a dictionary with:
1881 vim_interface_id - The ID of the interface
1882 mac_address - The MAC address of the interface.
1883 ip_address - The IP address of the interface within the subnet.
1884 """
1885 out_vms = {}
1886 self._reload_connection()
1887
1888 self.logger.debug("refresh vm status vm_list: %s", vm_list)
1889 search_vm_list = vm_list or {}
1890
1891 for vm_id in search_vm_list:
1892 out_vm = {}
1893 try:
1894 res_name = self._get_resource_name_from_resource_id(vm_id)
1895
1896 vm = self.conn_compute.virtual_machines.get(
1897 self.resource_group, res_name
1898 )
1899 out_vm["vim_info"] = str(vm)
1900 out_vm["status"] = self.provision_state2osm.get(
1901 vm.provisioning_state, "OTHER"
1902 )
1903
1904 if vm.provisioning_state == "Succeeded":
1905 # check if machine is running or stopped
1906 instance_view = self.conn_compute.virtual_machines.instance_view(
1907 self.resource_group, res_name
1908 )
1909
1910 for status in instance_view.statuses:
1911 splitted_status = status.code.split("/")
1912 if (
1913 len(splitted_status) == 2
1914 and splitted_status[0] == "PowerState"
1915 ):
1916 out_vm["status"] = self.power_state2osm.get(
1917 splitted_status[1], "OTHER"
1918 )
1919
1920 network_interfaces = vm.network_profile.network_interfaces
1921 out_vm["interfaces"] = self._get_vm_interfaces_status(
1922 vm_id, network_interfaces
1923 )
1924
1925 except CloudError as e:
1926 if e.error.error and "notfound" in e.error.error.lower():
1927 self.logger.debug("Not found vm id: %s", vm_id)
1928 out_vm["status"] = "DELETED"
1929 out_vm["error_msg"] = str(e)
1930 out_vm["vim_info"] = None
1931 else:
1932 # maybe connection error or another type of error, return vim error
1933 self.logger.error("Exception %s refreshing vm_status", e)
1934 out_vm["status"] = "VIM_ERROR"
1935 out_vm["error_msg"] = str(e)
1936 out_vm["vim_info"] = None
1937 except Exception as e:
1938 self.logger.error("Exception %s refreshing vm_status", e, exc_info=True)
1939 out_vm["status"] = "VIM_ERROR"
1940 out_vm["error_msg"] = str(e)
1941 out_vm["vim_info"] = None
1942
1943 out_vms[vm_id] = out_vm
1944
1945 return out_vms
1946
1947 def _get_vm_interfaces_status(self, vm_id, interfaces):
1948 """
1949 Gets the interfaces detail for a vm
1950 :param interfaces: List of interfaces.
1951 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1952 """
1953 try:
1954 interface_list = []
1955 for network_interface in interfaces:
1956 interface_dict = {}
1957 nic_name = self._get_resource_name_from_resource_id(
1958 network_interface.id
1959 )
1960 interface_dict["vim_interface_id"] = network_interface.id
1961
1962 nic_data = self.conn_vnet.network_interfaces.get(
1963 self.resource_group,
1964 nic_name,
1965 )
1966
1967 ips = []
1968 if nic_data.ip_configurations[0].public_ip_address:
1969 self.logger.debug("Obtain public ip address")
1970 public_ip_name = self._get_resource_name_from_resource_id(
1971 nic_data.ip_configurations[0].public_ip_address.id
1972 )
1973 public_ip = self.conn_vnet.public_ip_addresses.get(
1974 self.resource_group, public_ip_name
1975 )
1976 self.logger.debug("Public ip address is: %s", public_ip.ip_address)
1977 ips.append(public_ip.ip_address)
1978
1979 private_ip = nic_data.ip_configurations[0].private_ip_address
1980 ips.append(private_ip)
1981
1982 interface_dict["mac_address"] = nic_data.mac_address
1983 interface_dict["ip_address"] = ";".join(ips)
1984 interface_list.append(interface_dict)
1985
1986 return interface_list
1987 except Exception as e:
1988 self.logger.error(
1989 "Exception %s obtaining interface data for vm: %s",
1990 e,
1991 vm_id,
1992 exc_info=True,
1993 )
1994 self._format_vimconn_exception(e)
1995
1996 def _get_default_admin_user(self, image_id):
1997 if "ubuntu" in image_id.lower():
1998 return "ubuntu"
1999 else:
2000 return self._default_admin_user
2001
2002
2003 if __name__ == "__main__":
2004 # Init logger
2005 log_format = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
2006 log_formatter = logging.Formatter(log_format, datefmt="%Y-%m-%dT%H:%M:%S")
2007 handler = logging.StreamHandler()
2008 handler.setFormatter(log_formatter)
2009 logger = logging.getLogger("ro.vim.azure")
2010 # logger.setLevel(level=logging.ERROR)
2011 # logger.setLevel(level=logging.INFO)
2012 logger.setLevel(level=logging.DEBUG)
2013 logger.addHandler(handler)
2014
2015 # Making some basic test
2016 vim_id = "azure"
2017 vim_name = "azure"
2018 needed_test_params = {
2019 "client_id": "AZURE_CLIENT_ID",
2020 "secret": "AZURE_SECRET",
2021 "tenant": "AZURE_TENANT",
2022 "resource_group": "AZURE_RESOURCE_GROUP",
2023 "subscription_id": "AZURE_SUBSCRIPTION_ID",
2024 "vnet_name": "AZURE_VNET_NAME",
2025 }
2026 test_params = {}
2027
2028 for param, env_var in needed_test_params.items():
2029 value = getenv(env_var)
2030
2031 if not value:
2032 raise Exception("Provide a valid value for env '{}'".format(env_var))
2033
2034 test_params[param] = value
2035
2036 config = {
2037 "region_name": getenv("AZURE_REGION_NAME", "northeurope"),
2038 "resource_group": getenv("AZURE_RESOURCE_GROUP"),
2039 "subscription_id": getenv("AZURE_SUBSCRIPTION_ID"),
2040 "pub_key": getenv("AZURE_PUB_KEY", None),
2041 "vnet_name": getenv("AZURE_VNET_NAME", "osm_vnet"),
2042 }
2043
2044 azure = vimconnector(
2045 vim_id,
2046 vim_name,
2047 tenant_id=test_params["tenant"],
2048 tenant_name=None,
2049 url=None,
2050 url_admin=None,
2051 user=test_params["client_id"],
2052 passwd=test_params["secret"],
2053 log_level=None,
2054 config=config,
2055 )
2056
2057 """
2058 logger.debug("List images")
2059 image = azure.get_image_list({"name": "Canonical:UbuntuServer:18.04-LTS:18.04.201809110"})
2060 logger.debug("image: {}".format(image))
2061
2062 logger.debug("List networks")
2063 network_list = azure.get_network_list({"name": "internal"})
2064 logger.debug("Network_list: {}".format(network_list))
2065
2066 logger.debug("List flavors")
2067 flavors = azure.get_flavor_id_from_data({"vcpus": 2})
2068 logger.debug("flavors: {}".format(flavors))
2069 """
2070
2071 """
2072 # Create network and test machine
2073 #new_network_id, _ = azure.new_network("testnet1", "data")
2074 new_network_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers")
2075 "/Microsoft.Network/virtualNetworks/osm_vnet/subnets/testnet1"
2076 ).format(test_params["resource_group"])
2077 logger.debug("new_network_id: {}".format(new_network_id))
2078
2079 logger.debug("Delete network")
2080 new_network_id = azure.delete_network(new_network_id)
2081 logger.debug("deleted network_id: {}".format(new_network_id))
2082 """
2083
2084 """
2085 logger.debug("List networks")
2086 network_list = azure.get_network_list({"name": "internal"})
2087 logger.debug("Network_list: {}".format(network_list))
2088
2089 logger.debug("Show machine isabelvm")
2090 vmachine = azure.get_vminstance( ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}"
2091 "/providers/Microsoft.Compute/virtualMachines/isabelVM"
2092 ).format(test_params["resource_group"])
2093 )
2094 logger.debug("Vmachine: {}".format(vmachine))
2095 """
2096
2097 """
2098 logger.debug("List images")
2099 image = azure.get_image_list({"name": "Canonical:UbuntuServer:16.04"})
2100 # image = azure.get_image_list({"name": "Canonical:UbuntuServer:18.04-LTS"})
2101 logger.debug("image: {}".format(image))
2102 """
2103
2104 """
2105 # Create network and test machine
2106 new_network_id, _ = azure.new_network("testnet1", "data")
2107 image_id = ("/Subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/Providers/Microsoft.Compute"
2108 "/Locations/northeurope/Publishers/Canonical/ArtifactTypes/VMImage/Offers/UbuntuServer"
2109 "/Skus/18.04-LTS/Versions/18.04.201809110")
2110 """
2111 """
2112
2113 network_id = ("subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}
2114 "/providers/Microsoft.Network/virtualNetworks/osm_vnet/subnets/internal"
2115 ).format(test_params["resource_group"])
2116 """
2117
2118 """
2119 logger.debug("Create machine")
2120 image_id = ("/Subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/Providers/Microsoft.Compute/Locations"
2121 "/northeurope/Publishers/Canonical/ArtifactTypes/VMImage/Offers/UbuntuServer/Skus/18.04-LTS"
2122 "/Versions/18.04.202103151")
2123 cloud_config = {"user-data": (
2124 "#cloud-config\n"
2125 "password: osm4u\n"
2126 "chpasswd: { expire: False }\n"
2127 "ssh_pwauth: True\n\n"
2128 "write_files:\n"
2129 "- content: |\n"
2130 " # My new helloworld file\n\n"
2131 " owner: root:root\n"
2132 " permissions: '0644'\n"
2133 " path: /root/helloworld.txt",
2134 "key-pairs": [
2135 ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/p7fuw/W0+6uhx9XNPY4dN/K2cXZweDfjJN8W/sQ1AhKvn"
2136 "j0MF+dbBdsd2tfq6XUhx5LiKoGTunRpRonOw249ivH7pSyNN7FYpdLaij7Krn3K+QRNEOahMI4eoqdglVftA3"
2137 "vlw4Oe/aZOU9BXPdRLxfr9hRKzg5zkK91/LBkEViAijpCwK6ODPZLDDUwY4iihYK9R5eZ3fmM4+3k3Jd0hPRk"
2138 "B5YbtDQOu8ASWRZ9iTAWqr1OwQmvNc6ohSVg1tbq3wSxj/5bbz0J24A7TTpY0giWctne8Qkl/F2e0ZSErvbBB"
2139 "GXKxfnq7sc23OK1hPxMAuS+ufzyXsnL1+fB4t2iF azureuser@osm-test-client\n"
2140 )]
2141 }
2142 network_id = ("subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers"
2143 "/Microsoft.Network/virtualNetworks/osm_vnet/subnets/internal"
2144 ).format(test_params["resource_group"])
2145 vm = azure.new_vminstance(name="isabelvm",
2146 description="testvm",
2147 start=True,
2148 image_id=image_id,
2149 flavor_id="Standard_B1ls",
2150 net_list = [{"net_id": network_id, "name": "internal", "use": "mgmt", "floating_ip":True}],
2151 cloud_config = cloud_config)
2152 logger.debug("vm: {}".format(vm))
2153 """
2154
2155 """
2156 # Delete nonexistent vm
2157 try:
2158 logger.debug("Delete machine")
2159 vm_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Compute/"
2160 "virtualMachines/isabelvm"
2161 ).format(test_params["resource_group"])
2162 created_items = {
2163 ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2164 "/networkInterfaces/isabelvm-nic-0"
2165 ).format(test_params["resource_group"]): True,
2166 ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2167 "/publicIPAddresses/isabelvm-nic-0-public-ip"
2168 ).format(test_params["resource_group"]): True
2169 }
2170 azure.delete_vminstance(vm_id, created_items)
2171 except vimconn.VimConnNotFoundException as e:
2172 print("Ok: excepcion no encontrada")
2173 """
2174
2175 """
2176 network_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2177 "/virtualNetworks/osm_vnet/subnets/hfcloudinit-internal-1"
2178 ).format(test_params["resource_group"])
2179 azure.delete_network(network_id)
2180 """