c78b0f9238a040ea0fcc451aa733599ff170bb26
[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 # REGION
233 if "region_name" in config:
234 self.region = config.get("region_name")
235 else:
236 raise vimconn.VimConnException(
237 "Azure region_name is not specified at config"
238 )
239
240 # RESOURCE_GROUP
241 if "resource_group" in config:
242 self.resource_group = config.get("resource_group")
243 else:
244 raise vimconn.VimConnException(
245 "Azure resource_group 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, 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._get_location_from_resource_group(self.resource_group)
552 try:
553 net_ifz = {"location": location}
554 net_ip_config = {
555 "name": nic_name + "-ipconfiguration",
556 "subnet": {"id": subnet_id},
557 }
558
559 if static_ip:
560 net_ip_config["privateIPAddress"] = static_ip
561 net_ip_config["privateIPAllocationMethod"] = "Static"
562
563 net_ifz["ip_configurations"] = [net_ip_config]
564 mac_address = net.get("mac_address")
565
566 if mac_address:
567 net_ifz["mac_address"] = mac_address
568
569 async_nic_creation = (
570 self.conn_vnet.network_interfaces.begin_create_or_update(
571 self.resource_group, nic_name, net_ifz
572 )
573 )
574 nic_data = async_nic_creation.result()
575 created_items[nic_data.id] = True
576 self.logger.debug("created nic name %s", nic_name)
577
578 public_ip = net.get("floating_ip")
579 if public_ip:
580 public_ip_address_params = {
581 "location": location,
582 "public_ip_allocation_method": "Dynamic",
583 }
584 public_ip_name = nic_name + "-public-ip"
585 async_public_ip = (
586 self.conn_vnet.public_ip_addresses.begin_create_or_update(
587 self.resource_group, public_ip_name, public_ip_address_params
588 )
589 )
590 public_ip = async_public_ip.result()
591 self.logger.debug("created public IP: {}".format(public_ip))
592
593 # Associate NIC to Public IP
594 nic_data = self.conn_vnet.network_interfaces.get(
595 self.resource_group, nic_name
596 )
597
598 nic_data.ip_configurations[0].public_ip_address = public_ip
599 created_items[public_ip.id] = True
600
601 self.conn_vnet.network_interfaces.begin_create_or_update(
602 self.resource_group, nic_name, nic_data
603 )
604
605 except Exception as e:
606 self._format_vimconn_exception(e)
607
608 return nic_data, created_items
609
610 def new_flavor(self, flavor_data):
611 """
612 It is not allowed to create new flavors in Azure, must always use an existing one
613 """
614 raise vimconn.VimConnAuthException(
615 "It is not possible to create new flavors in AZURE"
616 )
617
618 def new_tenant(self, tenant_name, tenant_description):
619 """
620 It is not allowed to create new tenants in azure
621 """
622 raise vimconn.VimConnAuthException(
623 "It is not possible to create a TENANT in AZURE"
624 )
625
626 def new_image(self, image_dict):
627 """
628 It is not allowed to create new images in Azure, must always use an existing one
629 """
630 raise vimconn.VimConnAuthException(
631 "It is not possible to create new images in AZURE"
632 )
633
634 def get_image_id_from_path(self, path):
635 """Get the image id from image path in the VIM database.
636 Returns the image_id or raises a vimconnNotFoundException
637 """
638 raise vimconn.VimConnAuthException(
639 "It is not possible to obtain image from path in AZURE"
640 )
641
642 def get_image_list(self, filter_dict={}):
643 """Obtain tenant images from VIM
644 Filter_dict can be:
645 name: image name with the format: publisher:offer:sku:version
646 If some part of the name is provide ex: publisher:offer it will search all availables skus and version
647 for the provided publisher and offer
648 id: image uuid, currently not supported for azure
649 Returns the image list of dictionaries:
650 [{<the fields at Filter_dict plus some VIM specific>}, ...]
651 List can be empty
652 """
653 self.logger.debug("get_image_list filter {}".format(filter_dict))
654
655 self._reload_connection()
656 try:
657 image_list = []
658 if filter_dict.get("name"):
659 # name will have the format "publisher:offer:sku:version"
660 # publisher is required, offer sku and version will be searched if not provided
661 params = filter_dict["name"].split(":")
662 publisher = params[0]
663 if publisher:
664 # obtain offer list
665 offer_list = self._get_offer_list(params, publisher)
666
667 for offer in offer_list:
668 # obtain skus
669 sku_list = self._get_sku_list(params, publisher, offer)
670
671 for sku in sku_list:
672 # if version is defined get directly version, else list images
673 if len(params) == 4 and params[3]:
674 version = params[3]
675 if version == "latest":
676 image_list = self._get_sku_image_list(
677 publisher, offer, sku
678 )
679 image_list = [image_list[-1]]
680 else:
681 image_list = self._get_version_image_list(
682 publisher, offer, sku, version
683 )
684 else:
685 image_list = self._get_sku_image_list(
686 publisher, offer, sku
687 )
688 else:
689 raise vimconn.VimConnAuthException(
690 "List images in Azure must include name param with at least publisher"
691 )
692 else:
693 raise vimconn.VimConnAuthException(
694 "List images in Azure must include name param with at"
695 " least publisher"
696 )
697
698 return image_list
699 except Exception as e:
700 self._format_vimconn_exception(e)
701
702 def _get_offer_list(self, params, publisher):
703 """
704 Helper method to obtain offer list for defined publisher
705 """
706 if len(params) >= 2 and params[1]:
707 return [params[1]]
708 else:
709 try:
710 # get list of offers from azure
711 result_offers = self.conn_compute.virtual_machine_images.list_offers(
712 self.region, publisher
713 )
714
715 return [offer.name for offer in result_offers]
716 except CloudError as e:
717 # azure raises CloudError when not found
718 self.logger.info(
719 "error listing offers for publisher {}, Error: {}".format(
720 publisher, e
721 )
722 )
723
724 return []
725
726 def _get_sku_list(self, params, publisher, offer):
727 """
728 Helper method to obtain sku list for defined publisher and offer
729 """
730 if len(params) >= 3 and params[2]:
731 return [params[2]]
732 else:
733 try:
734 # get list of skus from azure
735 result_skus = self.conn_compute.virtual_machine_images.list_skus(
736 self.region, publisher, offer
737 )
738
739 return [sku.name for sku in result_skus]
740 except CloudError as e:
741 # azure raises CloudError when not found
742 self.logger.info(
743 "error listing skus for publisher {}, offer {}, Error: {}".format(
744 publisher, offer, e
745 )
746 )
747
748 return []
749
750 def _get_sku_image_list(self, publisher, offer, sku):
751 """
752 Helper method to obtain image list for publisher, offer and sku
753 """
754 image_list = []
755 try:
756 result_images = self.conn_compute.virtual_machine_images.list(
757 self.region, publisher, offer, sku
758 )
759 for result_image in result_images:
760 image_list.append(
761 {
762 "id": str(result_image.id),
763 "name": ":".join([publisher, offer, sku, result_image.name]),
764 }
765 )
766 except CloudError as e:
767 self.logger.info(
768 "error listing skus for publisher {}, offer {}, Error: {}".format(
769 publisher, offer, e
770 )
771 )
772 image_list = []
773
774 return image_list
775
776 def _get_version_image_list(self, publisher, offer, sku, version):
777 image_list = []
778 try:
779 result_image = self.conn_compute.virtual_machine_images.get(
780 self.region, publisher, offer, sku, version
781 )
782
783 if result_image:
784 image_list.append(
785 {
786 "id": str(result_image.id),
787 "name": ":".join([publisher, offer, sku, version]),
788 }
789 )
790 except CloudError as e:
791 # azure gives CloudError when not found
792 self.logger.info(
793 "error listing images for publisher {}, offer {}, sku {}, version {} Error: {}".format(
794 publisher, offer, sku, version, e
795 )
796 )
797 image_list = []
798
799 return image_list
800
801 def get_network_list(self, filter_dict={}):
802 """Obtain tenant networks of VIM
803 Filter_dict can be:
804 name: network name
805 id: network id
806 shared: boolean, not implemented in Azure
807 tenant_id: tenant, not used in Azure, all networks same tenants
808 admin_state_up: boolean, not implemented in Azure
809 status: 'ACTIVE', not implemented in Azure #
810 Returns the network list of dictionaries
811 """
812 # self.logger.debug("getting network list for vim, filter %s", filter_dict)
813 try:
814 self._reload_connection()
815
816 vnet = self.conn_vnet.virtual_networks.get(
817 self.resource_group, self.vnet_name
818 )
819 subnet_list = []
820
821 for subnet in vnet.subnets:
822 if filter_dict:
823 if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
824 continue
825
826 if (
827 filter_dict.get("name")
828 and str(subnet.name) != filter_dict["name"]
829 ):
830 continue
831
832 name = self._get_resource_name_from_resource_id(subnet.id)
833
834 subnet_list.append(
835 {
836 "id": str(subnet.id),
837 "name": name,
838 "status": self.provision_state2osm[subnet.provisioning_state],
839 "cidr_block": str(subnet.address_prefix),
840 "type": "bridge",
841 "shared": False,
842 }
843 )
844
845 return subnet_list
846 except Exception as e:
847 self._format_vimconn_exception(e)
848
849 def new_vminstance(
850 self,
851 name,
852 description,
853 start,
854 image_id,
855 flavor_id,
856 net_list,
857 cloud_config=None,
858 disk_list=None,
859 availability_zone_index=None,
860 availability_zone_list=None,
861 ):
862 self.logger.debug(
863 "new vm instance name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, "
864 "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s",
865 name,
866 image_id,
867 flavor_id,
868 net_list,
869 cloud_config,
870 disk_list,
871 availability_zone_index,
872 availability_zone_list,
873 )
874 self._reload_connection()
875
876 # Validate input data is valid
877 # The virtual machine name must have less or 64 characters and it can not have the following
878 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
879 vm_name = self._check_vm_name(name)
880 # Obtain vm unused name
881 vm_name = self._get_unused_vm_name(vm_name)
882
883 # At least one network must be provided
884 if not net_list:
885 raise vimconn.VimConnException(
886 "At least one net must be provided to create a new VM"
887 )
888
889 # image_id are several fields of the image_id
890 image_reference = self._get_image_reference(image_id)
891
892 try:
893 virtual_machine = None
894 created_items = {}
895
896 # Create nics for each subnet
897 self._check_subnets_for_vm(net_list)
898 vm_nics = []
899
900 for idx, net in enumerate(net_list):
901 # Fault with subnet_id
902 # subnet_id=net["subnet_id"]
903 # subnet_id=net["net_id"]
904 nic_name = vm_name + "-nic-" + str(idx)
905 vm_nic, nic_items = self._create_nic(
906 net, nic_name, net.get("ip_address"), created_items
907 )
908 vm_nics.append({"id": str(vm_nic.id)})
909 net["vim_id"] = vm_nic.id
910
911 vm_parameters = {
912 "location": self.region,
913 "os_profile": self._build_os_profile(vm_name, cloud_config, image_id),
914 "hardware_profile": {"vm_size": flavor_id},
915 "storage_profile": {"image_reference": image_reference},
916 }
917
918 # If the machine has several networks one must be marked as primary
919 # As it is not indicated in the interface the first interface will be marked as primary
920 if len(vm_nics) > 1:
921 for idx, vm_nic in enumerate(vm_nics):
922 if idx == 0:
923 vm_nics[0]["Primary"] = True
924 else:
925 vm_nics[idx]["Primary"] = False
926
927 vm_parameters["network_profile"] = {"network_interfaces": vm_nics}
928
929 # Obtain zone information
930 vm_zone = self._get_vm_zone(availability_zone_index, availability_zone_list)
931 if vm_zone:
932 vm_parameters["zones"] = [vm_zone]
933
934 self.logger.debug("create vm name: %s", vm_name)
935 creation_result = self.conn_compute.virtual_machines.begin_create_or_update(
936 self.resource_group, vm_name, vm_parameters, polling=False
937 )
938 self.logger.debug("obtained creation result: %s", creation_result)
939 virtual_machine = creation_result.result()
940 self.logger.debug("created vm name: %s", vm_name)
941
942 """ Por ahora no hacer polling para ver si tarda menos
943 # Add disks if they are provided
944 if disk_list:
945 for disk_index, disk in enumerate(disk_list):
946 self.logger.debug(
947 "add disk size: %s, image: %s",
948 disk.get("size"),
949 disk.get("image"),
950 )
951 self._add_newvm_disk(
952 virtual_machine, vm_name, disk_index, disk, created_items
953 )
954
955 if start:
956 self.conn_compute.virtual_machines.start(self.resource_group, vm_name)
957 # start_result.wait()
958 """
959
960 return virtual_machine.id, created_items
961
962 # run_command_parameters = {
963 # "command_id": "RunShellScript", # For linux, don't change it
964 # "script": [
965 # "date > /tmp/test.txt"
966 # ]
967 # }
968 except Exception as e:
969 # Rollback vm creacion
970 vm_id = None
971
972 if virtual_machine:
973 vm_id = virtual_machine.id
974
975 try:
976 self.logger.debug("exception creating vm try to rollback")
977 self.delete_vminstance(vm_id, created_items)
978 except Exception as e2:
979 self.logger.error("new_vminstance rollback fail {}".format(e2))
980
981 self.logger.debug("Exception creating new vminstance: %s", e, exc_info=True)
982 self._format_vimconn_exception(e)
983
984 def _build_os_profile(self, vm_name, cloud_config, image_id):
985
986 # initial os_profile
987 os_profile = {"computer_name": vm_name}
988
989 # for azure os_profile admin_username is required
990 if cloud_config and cloud_config.get("users"):
991 admin_username = cloud_config.get("users")[0].get(
992 "name", self._get_default_admin_user(image_id)
993 )
994 else:
995 admin_username = self._get_default_admin_user(image_id)
996 os_profile["admin_username"] = admin_username
997
998 # if there is a cloud-init load it
999 if cloud_config:
1000 _, userdata = self._create_user_data(cloud_config)
1001 custom_data = base64.b64encode(userdata.encode("utf-8")).decode("latin-1")
1002 os_profile["custom_data"] = custom_data
1003
1004 # either password of ssh-keys are required
1005 # we will always use ssh-keys, in case it is not available we will generate it
1006 if cloud_config and cloud_config.get("key-pairs"):
1007 key_data = cloud_config.get("key-pairs")[0]
1008 else:
1009 _, key_data = self._generate_keys()
1010
1011 os_profile["linux_configuration"] = {
1012 "ssh": {
1013 "public_keys": [
1014 {
1015 "path": "/home/{}/.ssh/authorized_keys".format(admin_username),
1016 "key_data": key_data,
1017 }
1018 ]
1019 },
1020 }
1021
1022 return os_profile
1023
1024 def _generate_keys(self):
1025 """Method used to generate a pair of private/public keys.
1026 This method is used because to create a vm in Azure we always need a key or a password
1027 In some cases we may have a password in a cloud-init file but it may not be available
1028 """
1029 key = rsa.generate_private_key(
1030 backend=crypto_default_backend(), public_exponent=65537, key_size=2048
1031 )
1032 private_key = key.private_bytes(
1033 crypto_serialization.Encoding.PEM,
1034 crypto_serialization.PrivateFormat.PKCS8,
1035 crypto_serialization.NoEncryption(),
1036 )
1037 public_key = key.public_key().public_bytes(
1038 crypto_serialization.Encoding.OpenSSH,
1039 crypto_serialization.PublicFormat.OpenSSH,
1040 )
1041 private_key = private_key.decode("utf8")
1042 # Change first line because Paramiko needs a explicit start with 'BEGIN RSA PRIVATE KEY'
1043 i = private_key.find("\n")
1044 private_key = "-----BEGIN RSA PRIVATE KEY-----" + private_key[i:]
1045 public_key = public_key.decode("utf8")
1046
1047 return private_key, public_key
1048
1049 def _get_unused_vm_name(self, vm_name):
1050 """
1051 Checks the vm name and in case it is used adds a suffix to the name to allow creation
1052 :return:
1053 """
1054 all_vms = self.conn_compute.virtual_machines.list(self.resource_group)
1055 # Filter to vms starting with the indicated name
1056 vms = list(filter(lambda vm: (vm.name.startswith(vm_name)), all_vms))
1057 vm_names = [str(vm.name) for vm in vms]
1058
1059 # get the name with the first not used suffix
1060 name_suffix = 0
1061 # name = subnet_name + "-" + str(name_suffix)
1062 name = vm_name # first subnet created will have no prefix
1063
1064 while name in vm_names:
1065 name_suffix += 1
1066 name = vm_name + "-" + str(name_suffix)
1067
1068 return name
1069
1070 def _get_vm_zone(self, availability_zone_index, availability_zone_list):
1071 if availability_zone_index is None:
1072 return None
1073
1074 vim_availability_zones = self._get_azure_availability_zones()
1075 # check if VIM offer enough availability zones describe in the VNFD
1076 if vim_availability_zones and len(availability_zone_list) <= len(
1077 vim_availability_zones
1078 ):
1079 # check if all the names of NFV AV match VIM AV names
1080 match_by_index = False
1081
1082 if not availability_zone_list:
1083 match_by_index = True
1084 else:
1085 for av in availability_zone_list:
1086 if av not in vim_availability_zones:
1087 match_by_index = True
1088 break
1089
1090 if match_by_index:
1091 return vim_availability_zones[availability_zone_index]
1092 else:
1093 return availability_zone_list[availability_zone_index]
1094 else:
1095 raise vimconn.VimConnConflictException(
1096 "No enough availability zones at VIM for this deployment"
1097 )
1098
1099 def _get_azure_availability_zones(self):
1100 return self.AZURE_ZONES
1101
1102 def _add_newvm_disk(
1103 self, virtual_machine, vm_name, disk_index, disk, created_items={}
1104 ):
1105 disk_name = None
1106 data_disk = None
1107
1108 # Check if must create empty disk or from image
1109 if disk.get("vim_id"):
1110 # disk already exists, just get
1111 parsed_id = azure_tools.parse_resource_id(disk.get("vim_id"))
1112 disk_name = parsed_id.get("name")
1113 data_disk = self.conn_compute.disks.get(self.resource_group, disk_name)
1114 else:
1115 disk_name = vm_name + "_DataDisk_" + str(disk_index)
1116 if not disk.get("image_id"):
1117 self.logger.debug("create new data disk name: %s", disk_name)
1118 async_disk_creation = self.conn_compute.disks.begin_create_or_update(
1119 self.resource_group,
1120 disk_name,
1121 {
1122 "location": self.region,
1123 "disk_size_gb": disk.get("size"),
1124 "creation_data": {"create_option": DiskCreateOption.empty},
1125 },
1126 )
1127 data_disk = async_disk_creation.result()
1128 created_items[data_disk.id] = True
1129 else:
1130 image_id = disk.get("image_id")
1131
1132 if azure_tools.is_valid_resource_id(image_id):
1133 parsed_id = azure_tools.parse_resource_id(image_id)
1134
1135 # Check if image is snapshot or disk
1136 image_name = parsed_id.get("name")
1137 type = parsed_id.get("resource_type")
1138
1139 if type == "snapshots" or type == "disks":
1140 self.logger.debug("create disk from copy name: %s", image_name)
1141 # ┬┐Should check that snapshot exists?
1142 async_disk_creation = (
1143 self.conn_compute.disks.begin_create_or_update(
1144 self.resource_group,
1145 disk_name,
1146 {
1147 "location": self.region,
1148 "creation_data": {
1149 "create_option": "Copy",
1150 "source_uri": image_id,
1151 },
1152 },
1153 )
1154 )
1155 data_disk = async_disk_creation.result()
1156 created_items[data_disk.id] = True
1157 else:
1158 raise vimconn.VimConnNotFoundException(
1159 "Invalid image_id: %s ", image_id
1160 )
1161 else:
1162 raise vimconn.VimConnNotFoundException(
1163 "Invalid image_id: %s ", image_id
1164 )
1165
1166 # Attach the disk created
1167 virtual_machine.storage_profile.data_disks.append(
1168 {
1169 "lun": disk_index,
1170 "name": disk_name,
1171 "create_option": DiskCreateOption.attach,
1172 "managed_disk": {"id": data_disk.id},
1173 "disk_size_gb": disk.get("size"),
1174 }
1175 )
1176 self.logger.debug("attach disk name: %s", disk_name)
1177 self.conn_compute.virtual_machines.begin_create_or_update(
1178 self.resource_group, virtual_machine.name, virtual_machine
1179 )
1180
1181 # It is necesary extract from image_id data to create the VM with this format
1182 # "image_reference": {
1183 # "publisher": vm_reference["publisher"],
1184 # "offer": vm_reference["offer"],
1185 # "sku": vm_reference["sku"],
1186 # "version": vm_reference["version"]
1187 # },
1188 def _get_image_reference(self, image_id):
1189 try:
1190 # The data input format example:
1191 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
1192 # Publishers/Canonical/ArtifactTypes/VMImage/
1193 # Offers/UbuntuServer/
1194 # Skus/18.04-LTS/
1195 # Versions/18.04.201809110
1196 publisher = str(image_id.split("/")[8])
1197 offer = str(image_id.split("/")[12])
1198 sku = str(image_id.split("/")[14])
1199 version = str(image_id.split("/")[16])
1200
1201 return {
1202 "publisher": publisher,
1203 "offer": offer,
1204 "sku": sku,
1205 "version": version,
1206 }
1207 except Exception:
1208 raise vimconn.VimConnException(
1209 "Unable to get image_reference from invalid image_id format: '{}'".format(
1210 image_id
1211 )
1212 )
1213
1214 # Azure VM names can not have some special characters
1215 def _check_vm_name(self, vm_name):
1216 """
1217 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
1218 """
1219 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
1220
1221 # First: the VM name max length is 64 characters
1222 vm_name_aux = vm_name[:64]
1223
1224 # Second: replace not allowed characters
1225 for elem in chars_not_allowed_list:
1226 # Check if string is in the main string
1227 if elem in vm_name_aux:
1228 # self.logger.debug("Dentro del IF")
1229 # Replace the string
1230 vm_name_aux = vm_name_aux.replace(elem, "-")
1231
1232 return vm_name_aux
1233
1234 def get_flavor_id_from_data(self, flavor_dict):
1235 self.logger.debug("getting flavor id from data, flavor_dict: %s", flavor_dict)
1236 filter_dict = flavor_dict or {}
1237
1238 try:
1239 self._reload_connection()
1240 vm_sizes_list = [
1241 vm_size.as_dict()
1242 for vm_size in self.conn_compute.resource_skus.list(
1243 "location eq '{}'".format(self.region)
1244 )
1245 ]
1246
1247 cpus = filter_dict.get("vcpus") or 0
1248 memMB = filter_dict.get("ram") or 0
1249 numberInterfaces = len(filter_dict.get("interfaces", [])) or 0
1250
1251 # Filter
1252 filtered_sizes = []
1253 for size in vm_sizes_list:
1254 if size["resource_type"] == "virtualMachines":
1255 size_cpus = int(
1256 self._find_in_capabilities(size["capabilities"], "vCPUs")
1257 )
1258 size_memory = float(
1259 self._find_in_capabilities(size["capabilities"], "MemoryGB")
1260 )
1261 size_interfaces = self._find_in_capabilities(
1262 size["capabilities"], "MaxNetworkInterfaces"
1263 )
1264 if size_interfaces:
1265 size_interfaces = int(size_interfaces)
1266 else:
1267 self.logger.debug(
1268 "Flavor with no defined MaxNetworkInterfaces: {}".format(
1269 size["name"]
1270 )
1271 )
1272 continue
1273 if (
1274 size_cpus >= cpus
1275 and size_memory >= memMB / 1024
1276 and size_interfaces >= numberInterfaces
1277 ):
1278 if self._config.get("flavors_pattern"):
1279 if re.search(
1280 self._config.get("flavors_pattern"), size["name"]
1281 ):
1282 new_size = {
1283 e["name"]: e["value"] for e in size["capabilities"]
1284 }
1285 new_size["name"] = size["name"]
1286 filtered_sizes.append(new_size)
1287 else:
1288 new_size = {
1289 e["name"]: e["value"] for e in size["capabilities"]
1290 }
1291 new_size["name"] = size["name"]
1292 filtered_sizes.append(new_size)
1293
1294 # Sort
1295 listedFilteredSizes = sorted(
1296 filtered_sizes,
1297 key=lambda k: (
1298 int(k["vCPUs"]),
1299 float(k["MemoryGB"]),
1300 int(k["MaxNetworkInterfaces"]),
1301 int(k["MaxResourceVolumeMB"]),
1302 ),
1303 )
1304
1305 if listedFilteredSizes:
1306 return listedFilteredSizes[0]["name"]
1307
1308 raise vimconn.VimConnNotFoundException(
1309 "Cannot find any flavor matching '{}'".format(str(flavor_dict))
1310 )
1311 except Exception as e:
1312 self._format_vimconn_exception(e)
1313
1314 def _get_flavor_id_from_flavor_name(self, flavor_name):
1315 # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name))
1316 try:
1317 self._reload_connection()
1318 vm_sizes_list = [
1319 vm_size.as_dict()
1320 for vm_size in self.conn_compute.resource_skus.list(
1321 "location eq '{}'".format(self.region)
1322 )
1323 ]
1324
1325 output_flavor = None
1326 for size in vm_sizes_list:
1327 if size["name"] == flavor_name:
1328 output_flavor = size
1329
1330 # None is returned if not found anything
1331 return output_flavor
1332 except Exception as e:
1333 self._format_vimconn_exception(e)
1334
1335 def check_vim_connectivity(self):
1336 try:
1337 self._reload_connection()
1338 return True
1339 except Exception as e:
1340 raise vimconn.VimConnException(
1341 "Connectivity issue with Azure API: {}".format(e)
1342 )
1343
1344 def get_network(self, net_id):
1345 # self.logger.debug("get network id: {}".format(net_id))
1346 # res_name = self._get_resource_name_from_resource_id(net_id)
1347 self._reload_connection()
1348
1349 filter_dict = {"name": net_id}
1350 network_list = self.get_network_list(filter_dict)
1351
1352 if not network_list:
1353 raise vimconn.VimConnNotFoundException(
1354 "network '{}' not found".format(net_id)
1355 )
1356 else:
1357 return network_list[0]
1358
1359 def delete_network(self, net_id, created_items=None):
1360 self.logger.debug(
1361 "deleting network {} - {}".format(self.resource_group, net_id)
1362 )
1363
1364 self._reload_connection()
1365 res_name = self._get_resource_name_from_resource_id(net_id)
1366
1367 try:
1368 # Obtain subnets ant try to delete nic first
1369 subnet = self.conn_vnet.subnets.get(
1370 self.resource_group, self.vnet_name, res_name
1371 )
1372 if not subnet:
1373 raise vimconn.VimConnNotFoundException(
1374 "network '{}' not found".format(net_id)
1375 )
1376
1377 # TODO - for a quick-fix delete nics sequentially but should not wait
1378 # for each in turn
1379 if subnet.ip_configurations:
1380 for ip_configuration in subnet.ip_configurations:
1381 # obtain nic_name from ip_configuration
1382 parsed_id = azure_tools.parse_resource_id(ip_configuration.id)
1383 nic_name = parsed_id["name"]
1384 self.delete_inuse_nic(nic_name)
1385
1386 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
1387 # Put the initial virtual_network API
1388 async_delete = self.conn_vnet.subnets.begin_delete(
1389 self.resource_group, self.vnet_name, res_name
1390 )
1391 async_delete.wait()
1392
1393 return net_id
1394
1395 except ResourceNotFoundError:
1396 raise vimconn.VimConnNotFoundException(
1397 "network '{}' not found".format(net_id)
1398 )
1399 except CloudError as e:
1400 if e.error.error and "notfound" in e.error.error.lower():
1401 raise vimconn.VimConnNotFoundException(
1402 "network '{}' not found".format(net_id)
1403 )
1404 else:
1405 self._format_vimconn_exception(e)
1406 except Exception as e:
1407 self._format_vimconn_exception(e)
1408
1409 def delete_inuse_nic(self, nic_name):
1410
1411 # Obtain nic data
1412 nic_data = self.conn_vnet.network_interfaces.get(self.resource_group, nic_name)
1413
1414 # Obtain vm associated to nic in case it exists
1415 if nic_data.virtual_machine:
1416 vm_name = azure_tools.parse_resource_id(nic_data.virtual_machine.id)["name"]
1417 self.logger.debug("vm_name: {}".format(vm_name))
1418 virtual_machine = self.conn_compute.virtual_machines.get(
1419 self.resource_group, vm_name
1420 )
1421 self.logger.debug("obtained vm")
1422
1423 # Deattach nic from vm if it has netwolk machines attached
1424 network_interfaces = virtual_machine.network_profile.network_interfaces
1425 network_interfaces[:] = [
1426 interface
1427 for interface in network_interfaces
1428 if self._get_resource_name_from_resource_id(interface.id) != nic_name
1429 ]
1430
1431 # TODO - check if there is a public ip to delete and delete it
1432 if network_interfaces:
1433
1434 # Deallocate the vm
1435 async_vm_deallocate = (
1436 self.conn_compute.virtual_machines.begin_deallocate(
1437 self.resource_group, vm_name
1438 )
1439 )
1440 self.logger.debug("deallocating vm")
1441 async_vm_deallocate.wait()
1442 self.logger.debug("vm deallocated")
1443
1444 async_vm_update = (
1445 self.conn_compute.virtual_machines.begin_create_or_update(
1446 self.resource_group, vm_name, virtual_machine
1447 )
1448 )
1449 virtual_machine = async_vm_update.result()
1450 self.logger.debug("nic removed from interface")
1451
1452 else:
1453 self.logger.debug("There are no interfaces left, delete vm")
1454 self.delete_vminstance(virtual_machine.id)
1455 self.logger.debug("Delete vm")
1456
1457 # Delete nic
1458 self.logger.debug("delete NIC name: %s", nic_name)
1459 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
1460 self.resource_group, nic_name
1461 )
1462 nic_delete.wait()
1463 self.logger.debug("deleted NIC name: %s", nic_name)
1464
1465 def delete_vminstance(self, vm_id, created_items=None):
1466 """Deletes a vm instance from the vim."""
1467 self.logger.debug(
1468 "deleting VM instance {} - {}".format(self.resource_group, vm_id)
1469 )
1470 self._reload_connection()
1471
1472 created_items = created_items or {}
1473 try:
1474 # Check vm exists, we can call delete_vm to clean created_items
1475 if vm_id:
1476 res_name = self._get_resource_name_from_resource_id(vm_id)
1477 vm = self.conn_compute.virtual_machines.get(
1478 self.resource_group, res_name
1479 )
1480
1481 # Shuts down the virtual machine and releases the compute resources
1482 # vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1483 # vm_stop.wait()
1484
1485 vm_delete = self.conn_compute.virtual_machines.begin_delete(
1486 self.resource_group, res_name
1487 )
1488 vm_delete.wait()
1489 self.logger.debug("deleted VM name: %s", res_name)
1490
1491 # Delete OS Disk, check if exists, in case of error creating
1492 # it may not be fully created
1493 if vm.storage_profile.os_disk:
1494 os_disk_name = vm.storage_profile.os_disk.name
1495 self.logger.debug("delete OS DISK: %s", os_disk_name)
1496 async_disk_delete = self.conn_compute.disks.begin_delete(
1497 self.resource_group, os_disk_name
1498 )
1499 async_disk_delete.wait()
1500 # os disks are created always with the machine
1501 self.logger.debug("deleted OS DISK name: %s", os_disk_name)
1502
1503 for data_disk in vm.storage_profile.data_disks:
1504 self.logger.debug("delete data_disk: %s", data_disk.name)
1505 async_disk_delete = self.conn_compute.disks.begin_delete(
1506 self.resource_group, data_disk.name
1507 )
1508 async_disk_delete.wait()
1509 self._markdel_created_item(data_disk.managed_disk.id, created_items)
1510 self.logger.debug("deleted OS DISK name: %s", data_disk.name)
1511
1512 # After deleting VM, it is necessary to delete NIC, because if is not deleted delete_network
1513 # does not work because Azure says that is in use the subnet
1514 network_interfaces = vm.network_profile.network_interfaces
1515
1516 for network_interface in network_interfaces:
1517 nic_name = self._get_resource_name_from_resource_id(
1518 network_interface.id
1519 )
1520 nic_data = self.conn_vnet.network_interfaces.get(
1521 self.resource_group, nic_name
1522 )
1523
1524 public_ip_name = None
1525 exist_public_ip = nic_data.ip_configurations[0].public_ip_address
1526 if exist_public_ip:
1527 public_ip_id = nic_data.ip_configurations[
1528 0
1529 ].public_ip_address.id
1530
1531 # Delete public_ip
1532 public_ip_name = self._get_resource_name_from_resource_id(
1533 public_ip_id
1534 )
1535
1536 # Public ip must be deleted afterwards of nic that is attached
1537
1538 self.logger.debug("delete NIC name: %s", nic_name)
1539 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
1540 self.resource_group, nic_name
1541 )
1542 nic_delete.wait()
1543 self._markdel_created_item(network_interface.id, created_items)
1544 self.logger.debug("deleted NIC name: %s", nic_name)
1545
1546 # Delete list of public ips
1547 if public_ip_name:
1548 self.logger.debug("delete PUBLIC IP - " + public_ip_name)
1549 ip_delete = self.conn_vnet.public_ip_addresses.begin_delete(
1550 self.resource_group, public_ip_name
1551 )
1552 ip_delete.wait()
1553 self._markdel_created_item(public_ip_id, created_items)
1554
1555 # Delete created items
1556 self._delete_created_items(created_items)
1557
1558 except ResourceNotFoundError:
1559 raise vimconn.VimConnNotFoundException(
1560 "No vm instance found '{}'".format(vm_id)
1561 )
1562 except CloudError as e:
1563 if e.error.error and "notfound" in e.error.error.lower():
1564 raise vimconn.VimConnNotFoundException(
1565 "No vm instance found '{}'".format(vm_id)
1566 )
1567 else:
1568 self._format_vimconn_exception(e)
1569 except Exception as e:
1570 self._format_vimconn_exception(e)
1571
1572 def _markdel_created_item(self, item_id, created_items):
1573 if item_id in created_items:
1574 created_items[item_id] = False
1575
1576 def _delete_created_items(self, created_items):
1577 """Delete created_items elements that have not been deleted with the virtual machine
1578 Created_items may not be deleted correctly with the created machine if the
1579 virtual machine fails creating or in other cases of error
1580 """
1581 self.logger.debug("Created items: %s", created_items)
1582 # TODO - optimize - should not wait until it is deleted
1583 # Must delete in order first nics, then public_ips
1584 # As dictionaries don't preserve order, first get items to be deleted then delete them
1585 nics_to_delete = []
1586 publics_ip_to_delete = []
1587 disks_to_delete = []
1588 for item_id, v in created_items.items():
1589 if not v: # skip already deleted
1590 continue
1591
1592 # self.logger.debug("Must delete item id: %s", item_id)
1593 # Obtain type, supported nic, disk or public ip
1594 parsed_id = azure_tools.parse_resource_id(item_id)
1595 resource_type = parsed_id.get("resource_type")
1596 name = parsed_id.get("name")
1597
1598 if resource_type == "networkInterfaces":
1599 nics_to_delete.append(name)
1600 elif resource_type == "publicIPAddresses":
1601 publics_ip_to_delete.append(name)
1602 elif resource_type == "disks":
1603 disks_to_delete.append(name)
1604
1605 # Now delete
1606 for item_name in nics_to_delete:
1607 try:
1608 self.logger.debug("deleting nic name %s:", item_name)
1609 nic_delete = self.conn_vnet.network_interfaces.begin_delete(
1610 self.resource_group, item_name
1611 )
1612 nic_delete.wait()
1613 self.logger.debug("deleted nic name %s:", item_name)
1614 except Exception as e:
1615 self.logger.error(
1616 "Error deleting item: {}: {}".format(type(e).__name__, e)
1617 )
1618
1619 for item_name in publics_ip_to_delete:
1620 try:
1621 self.logger.debug("deleting public ip name %s:", item_name)
1622 ip_delete = self.conn_vnet.public_ip_addresses.begin_delete(
1623 self.resource_group, name
1624 )
1625 ip_delete.wait()
1626 self.logger.debug("deleted public ip name %s:", item_name)
1627 except Exception as e:
1628 self.logger.error(
1629 "Error deleting item: {}: {}".format(type(e).__name__, e)
1630 )
1631
1632 for item_name in disks_to_delete:
1633 try:
1634 self.logger.debug("deleting data disk name %s:", name)
1635 async_disk_delete = self.conn_compute.disks.begin_delete(
1636 self.resource_group, item_name
1637 )
1638 async_disk_delete.wait()
1639 self.logger.debug("deleted data disk name %s:", name)
1640 except Exception as e:
1641 self.logger.error(
1642 "Error deleting item: {}: {}".format(type(e).__name__, e)
1643 )
1644
1645 def action_vminstance(self, vm_id, action_dict, created_items={}):
1646 """Send and action over a VM instance from VIM
1647 Returns the vm_id if the action was successfully sent to the VIM
1648 """
1649 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1650
1651 try:
1652 self._reload_connection()
1653 resName = self._get_resource_name_from_resource_id(vm_id)
1654
1655 if "start" in action_dict:
1656 self.conn_compute.virtual_machines.begin_start(
1657 self.resource_group, resName
1658 )
1659 elif (
1660 "stop" in action_dict
1661 or "shutdown" in action_dict
1662 or "shutoff" in action_dict
1663 ):
1664 self.conn_compute.virtual_machines.begin_power_off(
1665 self.resource_group, resName
1666 )
1667 elif "terminate" in action_dict:
1668 self.conn_compute.virtual_machines.begin_delete(
1669 self.resource_group, resName
1670 )
1671 elif "reboot" in action_dict:
1672 self.conn_compute.virtual_machines.begin_restart(
1673 self.resource_group, resName
1674 )
1675
1676 return None
1677 except ResourceNotFoundError:
1678 raise vimconn.VimConnNotFoundException("No vm found '{}'".format(vm_id))
1679 except CloudError as e:
1680 if e.error.error and "notfound" in e.error.error.lower():
1681 raise vimconn.VimConnNotFoundException("No vm found '{}'".format(vm_id))
1682 else:
1683 self._format_vimconn_exception(e)
1684 except Exception as e:
1685 self._format_vimconn_exception(e)
1686
1687 def delete_flavor(self, flavor_id):
1688 raise vimconn.VimConnAuthException(
1689 "It is not possible to delete a FLAVOR in AZURE"
1690 )
1691
1692 def delete_tenant(self, tenant_id):
1693 raise vimconn.VimConnAuthException(
1694 "It is not possible to delete a TENANT in AZURE"
1695 )
1696
1697 def delete_image(self, image_id):
1698 raise vimconn.VimConnAuthException(
1699 "It is not possible to delete a IMAGE in AZURE"
1700 )
1701
1702 def get_vminstance(self, vm_id):
1703 """
1704 Obtaing the vm instance data from v_id
1705 """
1706 self.logger.debug("get vm instance: %s", vm_id)
1707 self._reload_connection()
1708 try:
1709 resName = self._get_resource_name_from_resource_id(vm_id)
1710 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
1711 except ResourceNotFoundError:
1712 raise vimconn.VimConnNotFoundException(
1713 "No vminstance found '{}'".format(vm_id)
1714 )
1715 except CloudError as e:
1716 if e.error.error and "notfound" in e.error.error.lower():
1717 raise vimconn.VimConnNotFoundException(
1718 "No vminstance found '{}'".format(vm_id)
1719 )
1720 else:
1721 self._format_vimconn_exception(e)
1722 except Exception as e:
1723 self._format_vimconn_exception(e)
1724
1725 return vm
1726
1727 def get_flavor(self, flavor_id):
1728 """
1729 Obtains the flavor_data from the flavor_id
1730 """
1731 self._reload_connection()
1732 self.logger.debug("get flavor from id: %s", flavor_id)
1733 flavor_data = self._get_flavor_id_from_flavor_name(flavor_id)
1734
1735 if flavor_data:
1736 flavor = {
1737 "id": flavor_id,
1738 "name": flavor_id,
1739 "ram": flavor_data["memoryInMB"],
1740 "vcpus": flavor_data["numberOfCores"],
1741 "disk": flavor_data["resourceDiskSizeInMB"] / 1024,
1742 }
1743
1744 return flavor
1745 else:
1746 raise vimconn.VimConnNotFoundException(
1747 "flavor '{}' not found".format(flavor_id)
1748 )
1749
1750 def get_tenant_list(self, filter_dict={}):
1751 """Obtains the list of tenants
1752 For the azure connector only the azure tenant will be returned if it is compatible
1753 with filter_dict
1754 """
1755 tenants_azure = [{"name": self.tenant, "id": self.tenant}]
1756 tenant_list = []
1757
1758 self.logger.debug("get tenant list: %s", filter_dict)
1759 for tenant_azure in tenants_azure:
1760 if filter_dict:
1761 if (
1762 filter_dict.get("id")
1763 and str(tenant_azure.get("id")) != filter_dict["id"]
1764 ):
1765 continue
1766
1767 if (
1768 filter_dict.get("name")
1769 and str(tenant_azure.get("name")) != filter_dict["name"]
1770 ):
1771 continue
1772
1773 tenant_list.append(tenant_azure)
1774
1775 return tenant_list
1776
1777 def refresh_nets_status(self, net_list):
1778 """Get the status of the networks
1779 Params: the list of network identifiers
1780 Returns a dictionary with:
1781 net_id: #VIM id of this network
1782 status: #Mandatory. Text with one of:
1783 # DELETED (not found at vim)
1784 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1785 # OTHER (Vim reported other status not understood)
1786 # ERROR (VIM indicates an ERROR status)
1787 # ACTIVE, INACTIVE, DOWN (admin down),
1788 # BUILD (on building process)
1789 #
1790 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1791 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1792 """
1793 out_nets = {}
1794 self._reload_connection()
1795
1796 self.logger.debug("reload nets status net_list: %s", net_list)
1797 for net_id in net_list:
1798 try:
1799 netName = self._get_net_name_from_resource_id(net_id)
1800 resName = self._get_resource_name_from_resource_id(net_id)
1801
1802 net = self.conn_vnet.subnets.get(self.resource_group, netName, resName)
1803
1804 out_nets[net_id] = {
1805 "status": self.provision_state2osm[net.provisioning_state],
1806 "vim_info": str(net),
1807 }
1808 except CloudError as e:
1809 if e.error.error and "notfound" in e.error.error.lower():
1810 self.logger.info(
1811 "Not found subnet net_name: %s, subnet_name: %s",
1812 netName,
1813 resName,
1814 )
1815 out_nets[net_id] = {"status": "DELETED", "error_msg": str(e)}
1816 else:
1817 self.logger.error(
1818 "CloudError Exception %s when searching subnet", e
1819 )
1820 out_nets[net_id] = {
1821 "status": "VIM_ERROR",
1822 "error_msg": str(e),
1823 }
1824 except vimconn.VimConnNotFoundException as e:
1825 self.logger.error(
1826 "VimConnNotFoundException %s when searching subnet", e
1827 )
1828 out_nets[net_id] = {
1829 "status": "DELETED",
1830 "error_msg": str(e),
1831 }
1832 except Exception as e:
1833 self.logger.error(
1834 "Exception %s when searching subnet", e, exc_info=True
1835 )
1836 out_nets[net_id] = {
1837 "status": "VIM_ERROR",
1838 "error_msg": str(e),
1839 }
1840
1841 return out_nets
1842
1843 def refresh_vms_status(self, vm_list):
1844 """Get the status of the virtual machines and their interfaces/ports
1845 Params: the list of VM identifiers
1846 Returns a dictionary with:
1847 vm_id: # VIM id of this Virtual Machine
1848 status: # Mandatory. Text with one of:
1849 # DELETED (not found at vim)
1850 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1851 # OTHER (Vim reported other status not understood)
1852 # ERROR (VIM indicates an ERROR status)
1853 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1854 # BUILD (on building process), ERROR
1855 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1856 # (ACTIVE:NoMgmtIP is not returned for Azure)
1857 #
1858 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1859 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1860 interfaces: list with interface info. Each item a dictionary with:
1861 vim_interface_id - The ID of the interface
1862 mac_address - The MAC address of the interface.
1863 ip_address - The IP address of the interface within the subnet.
1864 """
1865 out_vms = {}
1866 self._reload_connection()
1867
1868 self.logger.debug("refresh vm status vm_list: %s", vm_list)
1869 search_vm_list = vm_list or {}
1870
1871 for vm_id in search_vm_list:
1872 out_vm = {}
1873 try:
1874 res_name = self._get_resource_name_from_resource_id(vm_id)
1875
1876 vm = self.conn_compute.virtual_machines.get(
1877 self.resource_group, res_name
1878 )
1879 out_vm["vim_info"] = str(vm)
1880 out_vm["status"] = self.provision_state2osm.get(
1881 vm.provisioning_state, "OTHER"
1882 )
1883
1884 if vm.provisioning_state == "Succeeded":
1885 # check if machine is running or stopped
1886 instance_view = self.conn_compute.virtual_machines.instance_view(
1887 self.resource_group, res_name
1888 )
1889
1890 for status in instance_view.statuses:
1891 splitted_status = status.code.split("/")
1892 if (
1893 len(splitted_status) == 2
1894 and splitted_status[0] == "PowerState"
1895 ):
1896 out_vm["status"] = self.power_state2osm.get(
1897 splitted_status[1], "OTHER"
1898 )
1899
1900 network_interfaces = vm.network_profile.network_interfaces
1901 out_vm["interfaces"] = self._get_vm_interfaces_status(
1902 vm_id, network_interfaces
1903 )
1904
1905 except CloudError as e:
1906 if e.error.error and "notfound" in e.error.error.lower():
1907 self.logger.debug("Not found vm id: %s", vm_id)
1908 out_vm["status"] = "DELETED"
1909 out_vm["error_msg"] = str(e)
1910 out_vm["vim_info"] = None
1911 else:
1912 # maybe connection error or another type of error, return vim error
1913 self.logger.error("Exception %s refreshing vm_status", e)
1914 out_vm["status"] = "VIM_ERROR"
1915 out_vm["error_msg"] = str(e)
1916 out_vm["vim_info"] = None
1917 except Exception as e:
1918 self.logger.error("Exception %s refreshing vm_status", e, exc_info=True)
1919 out_vm["status"] = "VIM_ERROR"
1920 out_vm["error_msg"] = str(e)
1921 out_vm["vim_info"] = None
1922
1923 out_vms[vm_id] = out_vm
1924
1925 return out_vms
1926
1927 def _get_vm_interfaces_status(self, vm_id, interfaces):
1928 """
1929 Gets the interfaces detail for a vm
1930 :param interfaces: List of interfaces.
1931 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1932 """
1933 try:
1934 interface_list = []
1935 for network_interface in interfaces:
1936 interface_dict = {}
1937 nic_name = self._get_resource_name_from_resource_id(
1938 network_interface.id
1939 )
1940 interface_dict["vim_interface_id"] = network_interface.id
1941
1942 nic_data = self.conn_vnet.network_interfaces.get(
1943 self.resource_group,
1944 nic_name,
1945 )
1946
1947 ips = []
1948 if nic_data.ip_configurations[0].public_ip_address:
1949 self.logger.debug("Obtain public ip address")
1950 public_ip_name = self._get_resource_name_from_resource_id(
1951 nic_data.ip_configurations[0].public_ip_address.id
1952 )
1953 public_ip = self.conn_vnet.public_ip_addresses.get(
1954 self.resource_group, public_ip_name
1955 )
1956 self.logger.debug("Public ip address is: %s", public_ip.ip_address)
1957 ips.append(public_ip.ip_address)
1958
1959 private_ip = nic_data.ip_configurations[0].private_ip_address
1960 ips.append(private_ip)
1961
1962 interface_dict["mac_address"] = nic_data.mac_address
1963 interface_dict["ip_address"] = ";".join(ips)
1964 interface_list.append(interface_dict)
1965
1966 return interface_list
1967 except Exception as e:
1968 self.logger.error(
1969 "Exception %s obtaining interface data for vm: %s",
1970 e,
1971 vm_id,
1972 exc_info=True,
1973 )
1974 self._format_vimconn_exception(e)
1975
1976 def _get_default_admin_user(self, image_id):
1977 if "ubuntu" in image_id.lower():
1978 return "ubuntu"
1979 else:
1980 return self._default_admin_user
1981
1982
1983 if __name__ == "__main__":
1984 # Init logger
1985 log_format = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
1986 log_formatter = logging.Formatter(log_format, datefmt="%Y-%m-%dT%H:%M:%S")
1987 handler = logging.StreamHandler()
1988 handler.setFormatter(log_formatter)
1989 logger = logging.getLogger("ro.vim.azure")
1990 # logger.setLevel(level=logging.ERROR)
1991 # logger.setLevel(level=logging.INFO)
1992 logger.setLevel(level=logging.DEBUG)
1993 logger.addHandler(handler)
1994
1995 # Making some basic test
1996 vim_id = "azure"
1997 vim_name = "azure"
1998 needed_test_params = {
1999 "client_id": "AZURE_CLIENT_ID",
2000 "secret": "AZURE_SECRET",
2001 "tenant": "AZURE_TENANT",
2002 "resource_group": "AZURE_RESOURCE_GROUP",
2003 "subscription_id": "AZURE_SUBSCRIPTION_ID",
2004 "vnet_name": "AZURE_VNET_NAME",
2005 }
2006 test_params = {}
2007
2008 for param, env_var in needed_test_params.items():
2009 value = getenv(env_var)
2010
2011 if not value:
2012 raise Exception("Provide a valid value for env '{}'".format(env_var))
2013
2014 test_params[param] = value
2015
2016 config = {
2017 "region_name": getenv("AZURE_REGION_NAME", "northeurope"),
2018 "resource_group": getenv("AZURE_RESOURCE_GROUP"),
2019 "subscription_id": getenv("AZURE_SUBSCRIPTION_ID"),
2020 "pub_key": getenv("AZURE_PUB_KEY", None),
2021 "vnet_name": getenv("AZURE_VNET_NAME", "osm_vnet"),
2022 }
2023
2024 azure = vimconnector(
2025 vim_id,
2026 vim_name,
2027 tenant_id=test_params["tenant"],
2028 tenant_name=None,
2029 url=None,
2030 url_admin=None,
2031 user=test_params["client_id"],
2032 passwd=test_params["secret"],
2033 log_level=None,
2034 config=config,
2035 )
2036
2037 """
2038 logger.debug("List images")
2039 image = azure.get_image_list({"name": "Canonical:UbuntuServer:18.04-LTS:18.04.201809110"})
2040 logger.debug("image: {}".format(image))
2041
2042 logger.debug("List networks")
2043 network_list = azure.get_network_list({"name": "internal"})
2044 logger.debug("Network_list: {}".format(network_list))
2045
2046 logger.debug("List flavors")
2047 flavors = azure.get_flavor_id_from_data({"vcpus": 2})
2048 logger.debug("flavors: {}".format(flavors))
2049 """
2050
2051 """
2052 # Create network and test machine
2053 #new_network_id, _ = azure.new_network("testnet1", "data")
2054 new_network_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers")
2055 "/Microsoft.Network/virtualNetworks/osm_vnet/subnets/testnet1"
2056 ).format(test_params["resource_group"])
2057 logger.debug("new_network_id: {}".format(new_network_id))
2058
2059 logger.debug("Delete network")
2060 new_network_id = azure.delete_network(new_network_id)
2061 logger.debug("deleted network_id: {}".format(new_network_id))
2062 """
2063
2064 """
2065 logger.debug("List networks")
2066 network_list = azure.get_network_list({"name": "internal"})
2067 logger.debug("Network_list: {}".format(network_list))
2068
2069 logger.debug("Show machine isabelvm")
2070 vmachine = azure.get_vminstance( ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}"
2071 "/providers/Microsoft.Compute/virtualMachines/isabelVM"
2072 ).format(test_params["resource_group"])
2073 )
2074 logger.debug("Vmachine: {}".format(vmachine))
2075 """
2076
2077 """
2078 logger.debug("List images")
2079 image = azure.get_image_list({"name": "Canonical:UbuntuServer:16.04"})
2080 # image = azure.get_image_list({"name": "Canonical:UbuntuServer:18.04-LTS"})
2081 logger.debug("image: {}".format(image))
2082 """
2083
2084 """
2085 # Create network and test machine
2086 new_network_id, _ = azure.new_network("testnet1", "data")
2087 image_id = ("/Subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/Providers/Microsoft.Compute"
2088 "/Locations/northeurope/Publishers/Canonical/ArtifactTypes/VMImage/Offers/UbuntuServer"
2089 "/Skus/18.04-LTS/Versions/18.04.201809110")
2090 """
2091 """
2092
2093 network_id = ("subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}
2094 "/providers/Microsoft.Network/virtualNetworks/osm_vnet/subnets/internal"
2095 ).format(test_params["resource_group"])
2096 """
2097
2098 """
2099 logger.debug("Create machine")
2100 image_id = ("/Subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/Providers/Microsoft.Compute/Locations"
2101 "/northeurope/Publishers/Canonical/ArtifactTypes/VMImage/Offers/UbuntuServer/Skus/18.04-LTS"
2102 "/Versions/18.04.202103151")
2103 cloud_config = {"user-data": (
2104 "#cloud-config\n"
2105 "password: osm4u\n"
2106 "chpasswd: { expire: False }\n"
2107 "ssh_pwauth: True\n\n"
2108 "write_files:\n"
2109 "- content: |\n"
2110 " # My new helloworld file\n\n"
2111 " owner: root:root\n"
2112 " permissions: '0644'\n"
2113 " path: /root/helloworld.txt",
2114 "key-pairs": [
2115 ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/p7fuw/W0+6uhx9XNPY4dN/K2cXZweDfjJN8W/sQ1AhKvn"
2116 "j0MF+dbBdsd2tfq6XUhx5LiKoGTunRpRonOw249ivH7pSyNN7FYpdLaij7Krn3K+QRNEOahMI4eoqdglVftA3"
2117 "vlw4Oe/aZOU9BXPdRLxfr9hRKzg5zkK91/LBkEViAijpCwK6ODPZLDDUwY4iihYK9R5eZ3fmM4+3k3Jd0hPRk"
2118 "B5YbtDQOu8ASWRZ9iTAWqr1OwQmvNc6ohSVg1tbq3wSxj/5bbz0J24A7TTpY0giWctne8Qkl/F2e0ZSErvbBB"
2119 "GXKxfnq7sc23OK1hPxMAuS+ufzyXsnL1+fB4t2iF azureuser@osm-test-client\n"
2120 )]
2121 }
2122 network_id = ("subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers"
2123 "/Microsoft.Network/virtualNetworks/osm_vnet/subnets/internal"
2124 ).format(test_params["resource_group"])
2125 vm = azure.new_vminstance(name="isabelvm",
2126 description="testvm",
2127 start=True,
2128 image_id=image_id,
2129 flavor_id="Standard_B1ls",
2130 net_list = [{"net_id": network_id, "name": "internal", "use": "mgmt", "floating_ip":True}],
2131 cloud_config = cloud_config)
2132 logger.debug("vm: {}".format(vm))
2133 """
2134
2135 """
2136 # Delete nonexistent vm
2137 try:
2138 logger.debug("Delete machine")
2139 vm_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Compute/"
2140 "virtualMachines/isabelvm"
2141 ).format(test_params["resource_group"])
2142 created_items = {
2143 ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2144 "/networkInterfaces/isabelvm-nic-0"
2145 ).format(test_params["resource_group"]): True,
2146 ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2147 "/publicIPAddresses/isabelvm-nic-0-public-ip"
2148 ).format(test_params["resource_group"]): True
2149 }
2150 azure.delete_vminstance(vm_id, created_items)
2151 except vimconn.VimConnNotFoundException as e:
2152 print("Ok: excepcion no encontrada")
2153 """
2154
2155 """
2156 network_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2157 "/virtualNetworks/osm_vnet/subnets/hfcloudinit-internal-1"
2158 ).format(test_params["resource_group"])
2159 azure.delete_network(network_id)
2160 """