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