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