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