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