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