1 # -*- coding: utf-8 -*-
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
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
35 from osm_ro_plugin
import vimconn
36 from requests
.exceptions
import ConnectionError
38 __author__
= "Isabel Lloret, Sergio Gonzalez, Alfonso Tierno, Gerardo Garcia"
39 __date__
= "$18-apr-2019 23:59:59$"
42 if getenv("OSMRO_PDB_DEBUG"):
51 def find_in_list(the_list
, condition_lambda
):
53 if condition_lambda(item
):
59 class vimconnector(vimconn
.VimConnector
):
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
= {
68 "Deleting": "INACTIVE",
69 "Succeeded": "ACTIVE",
73 # Translate azure power state to OSM provision state
75 "starting": "INACTIVE",
77 "stopping": "INACTIVE",
78 "stopped": "INACTIVE",
80 "deallocated": "BUILD",
81 "deallocating": "BUILD",
84 # TODO - review availability zones
85 AZURE_ZONES
= ["1", "2", "3"]
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(
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",
132 AZURE_COMPUTE_MGMT_PROFILE_TAG
+ " osm",
135 AZURE_RESOURCE_MGMT_CLIENT_API_VERSION
= "2020-10-01"
136 AZURE_RESOURCE_MGMT_PROFILE_TAG
= (
137 "azure.mgmt.resource.resources.ResourceManagementClient"
139 AZURE_RESOURCE_MGMT_PROFILE
= ProfileDefinition(
141 AZURE_RESOURCE_MGMT_PROFILE_TAG
: {
142 None: AZURE_RESOURCE_MGMT_CLIENT_API_VERSION
,
145 AZURE_RESOURCE_MGMT_PROFILE_TAG
+ " osm",
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(
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",
159 AZURE_NETWORK_MGMT_PROFILE_TAG
+ " osm",
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
190 vimconn
.VimConnector
.__init
__(
205 # Variable that indicates if client must be reloaded or initialized
206 self
.reload_client
= True
208 self
.vnet_address_space
= None
211 self
.logger
= logging
.getLogger("ro.vim.azure")
213 self
.logger
.setLevel(getattr(logging
, log_level
))
215 self
.tenant
= tenant_id
or tenant_name
217 # Store config to create azure subscription later
221 "tenant": tenant_id
or tenant_name
,
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"])
229 raise vimconn
.VimConnException("Subscription not specified")
232 if "resource_group" in config
:
233 self
.resource_group
= config
.get("resource_group")
235 raise vimconn
.VimConnException(
236 "Azure resource_group is not specified at config"
240 if "region_name" in config
:
241 self
.region
= config
.get("region_name")
243 raise vimconn
.VimConnException(
244 "Azure region_name is not specified at config"
248 if "vnet_name" in config
:
249 self
.vnet_name
= config
["vnet_name"]
251 # VNET_RESOURCE_GROUP
252 self
.vnet_resource_group
= config
.get("vnet_resource_group")
254 # TODO - not used, do anything about it?
256 self
.pub_key
= config
.get("pub_key")
258 # TODO - check default user for azure
260 self
._default
_admin
_user
= "azureuser"
262 # flavor pattern regex
263 if "flavors_pattern" in config
:
264 self
._config
["flavors_pattern"] = config
["flavors_pattern"]
266 def _find_in_capabilities(self
, capabilities
, name
):
267 cap
= find_in_list(capabilities
, lambda c
: c
["name"] == name
)
269 return cap
.get("value")
273 def _reload_connection(self
):
275 Called before any operation, checks python azure clients
277 if self
.reload_client
:
278 self
.logger
.debug("reloading azure client")
281 self
.credentials
= ClientSecretCredential(
282 client_id
=self
._config
["user"],
283 client_secret
=self
._config
["passwd"],
284 tenant_id
=self
._config
["tenant"],
286 self
.conn
= ResourceManagementClient(
288 self
._config
["subscription_id"],
289 profile
=self
.AZURE_RESOURCE_MGMT_PROFILE
,
291 self
.conn_compute
= ComputeManagementClient(
293 self
._config
["subscription_id"],
294 profile
=self
.AZURE_COMPUTE_MGMT_PROFILE
,
296 self
.conn_vnet
= NetworkManagementClient(
298 self
._config
["subscription_id"],
299 profile
=self
.AZURE_NETWORK_MGMT_PROFILE
,
301 self
._check
_or
_create
_resource
_group
()
302 self
._check
_or
_create
_vnet
()
304 # Set to client created
305 self
.reload_client
= False
306 except Exception as e
:
307 self
._format
_vimconn
_exception
(e
)
309 def _get_resource_name_from_resource_id(self
, resource_id
):
311 Obtains resource_name from the azure complete identifier: resource_name will always be last item
314 resource
= str(resource_id
.split("/")[-1])
317 except Exception as e
:
318 raise vimconn
.VimConnException(
319 "Unable to get resource name from resource_id '{}' Error: '{}'".format(
324 def _get_location_from_resource_group(self
, resource_group_name
):
326 location
= self
.conn
.resource_groups
.get(resource_group_name
).location
330 raise vimconn
.VimConnNotFoundException(
331 "Location '{}' not found".format(resource_group_name
)
334 def _get_resource_group_name_from_resource_id(self
, resource_id
):
336 rg
= str(resource_id
.split("/")[4])
340 raise vimconn
.VimConnException(
341 "Unable to get resource group from invalid resource_id format '{}'".format(
346 def _get_net_name_from_resource_id(self
, resource_id
):
348 net_name
= str(resource_id
.split("/")[8])
352 raise vimconn
.VimConnException(
353 "Unable to get azure net_name from invalid resource_id format '{}'".format(
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
362 self
._get
_resource
_group
_name
_from
_resource
_id
(net
["net_id"])
363 + self
._get
_net
_name
_from
_resource
_id
(net
["net_id"])
367 if len(rg_vnet
) != 1:
368 raise self
._format
_vimconn
_exception
(
369 "Azure VMs can only attach to subnets in same VNET"
372 def _format_vimconn_exception(self
, e
):
374 Transforms a generic or azure exception to a vimcommException
376 self
.logger
.error("Azure plugin error: {}".format(e
))
377 if isinstance(e
, vimconn
.VimConnException
):
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
))
384 # In case of generic error recreate client
385 self
.reload_client
= True
387 raise vimconn
.VimConnException(type(e
).__name
__ + ": " + str(e
))
389 def _check_or_create_resource_group(self
):
391 Creates the base resource group if it does not exist
394 rg_exists
= self
.conn
.resource_groups
.check_existence(self
.resource_group
)
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
}
401 except Exception as e
:
402 self
._format
_vimconn
_exception
(e
)
404 def _check_or_create_vnet(self
):
406 Try to get existent base vnet, in case it does not exist it creates it
409 vnet
= self
.conn_vnet
.virtual_networks
.get(
410 self
.vnet_resource_group
or self
.resource_group
, self
.vnet_name
412 self
.vnet_address_space
= vnet
.address_space
.address_prefixes
[0]
413 self
.vnet_id
= vnet
.id
416 except CloudError
as e
:
417 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
418 self
.logger
.exception("CloudError Exception occured.")
419 # continue and create it
421 self
._format
_vimconn
_exception
(e
)
423 # if it does not exist, create it
426 "location": self
.region
,
427 "address_space": {"address_prefixes": ["10.0.0.0/8"]},
429 self
.vnet_address_space
= "10.0.0.0/8"
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
,
437 vnet
= self
.conn_vnet
.virtual_networks
.get(
438 self
.vnet_resource_group
or self
.resource_group
, self
.vnet_name
440 self
.vnet_id
= vnet
.id
441 except Exception as e
:
442 self
._format
_vimconn
_exception
(e
)
450 provider_network_profile
=None,
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
473 return self
._new
_subnet
(net_name
, ip_profile
)
475 def _new_subnet(self
, net_name
, ip_profile
):
477 Adds a tenant network to VIM. It creates a new subnet at existing base vnet
478 :param net_name: subnet name
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
484 self
.logger
.debug("create subnet name %s, ip_profile %s", net_name
, ip_profile
)
485 self
._reload
_connection
()
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"])
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
498 ip_profile
= {"subnet_address": str(ip_range
)}
499 self
.logger
.debug("dinamically obtained ip_profile: %s", ip_range
)
502 raise vimconn
.VimConnException(
503 "Cannot find a non-used subnet range in {}".format(
504 self
.vnet_address_space
508 ip_profile
= {"subnet_address": ip_profile
["subnet_address"]}
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
)
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
,
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
))
527 return "{}/subnets/{}".format(self
.vnet_id
, subnet_name
), None
528 except Exception as e
:
529 self
._format
_vimconn
_exception
(e
)
531 def _get_unused_subnet_name(self
, subnet_name
):
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
536 all_subnets
= self
.conn_vnet
.subnets
.list(
537 self
.vnet_resource_group
or self
.resource_group
, self
.vnet_name
539 # Filter to subnets starting with the indicated name
541 filter(lambda subnet
: (subnet
.name
.startswith(subnet_name
)), all_subnets
)
543 net_names
= [str(subnet
.name
) for subnet
in subnets
]
545 # get the name with the first not used suffix
547 # name = subnet_name + "-" + str(name_suffix)
548 name
= subnet_name
# first subnet created will have no prefix
549 while name
in net_names
:
551 name
= subnet_name
+ "-" + str(name_suffix
)
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
()
559 subnet_id
= net
["net_id"]
560 location
= self
.region
or self
._get
_location
_from
_resource
_group
(
565 net_ifz
= {"location": location
}
567 "name": nic_name
+ "-ipconfiguration",
568 "subnet": {"id": subnet_id
},
572 net_ip_config
["privateIPAddress"] = static_ip
573 net_ip_config
["privateIPAllocationMethod"] = "Static"
575 net_ifz
["ip_configurations"] = [net_ip_config
]
576 mac_address
= net
.get("mac_address")
579 net_ifz
["mac_address"] = mac_address
581 async_nic_creation
= (
582 self
.conn_vnet
.network_interfaces
.begin_create_or_update(
583 self
.resource_group
, nic_name
, net_ifz
586 nic_data
= async_nic_creation
.result()
587 created_items
[nic_data
.id] = True
588 self
.logger
.debug("created nic name %s", nic_name
)
590 public_ip
= net
.get("floating_ip")
592 public_ip_address_params
= {
593 "location": location
,
594 "public_ip_allocation_method": "Dynamic",
596 public_ip_name
= nic_name
+ "-public-ip"
598 self
.conn_vnet
.public_ip_addresses
.begin_create_or_update(
599 self
.resource_group
, public_ip_name
, public_ip_address_params
602 public_ip
= async_public_ip
.result()
603 self
.logger
.debug("created public IP: {}".format(public_ip
))
605 # Associate NIC to Public IP
606 nic_data
= self
.conn_vnet
.network_interfaces
.get(
607 self
.resource_group
, nic_name
610 nic_data
.ip_configurations
[0].public_ip_address
= public_ip
611 created_items
[public_ip
.id] = True
613 self
.conn_vnet
.network_interfaces
.begin_create_or_update(
614 self
.resource_group
, nic_name
, nic_data
617 except Exception as e
:
618 self
._format
_vimconn
_exception
(e
)
620 return nic_data
, created_items
622 def new_flavor(self
, flavor_data
):
624 It is not allowed to create new flavors in Azure, must always use an existing one
626 raise vimconn
.VimConnAuthException(
627 "It is not possible to create new flavors in AZURE"
630 def new_tenant(self
, tenant_name
, tenant_description
):
632 It is not allowed to create new tenants in azure
634 raise vimconn
.VimConnAuthException(
635 "It is not possible to create a TENANT in AZURE"
638 def new_image(self
, image_dict
):
640 It is not allowed to create new images in Azure, must always use an existing one
642 raise vimconn
.VimConnAuthException(
643 "It is not possible to create new images in AZURE"
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
650 raise vimconn
.VimConnAuthException(
651 "It is not possible to obtain image from path in AZURE"
654 def get_image_list(self
, filter_dict
={}):
655 """Obtain tenant images from VIM
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>}, ...]
665 self
.logger
.debug("get_image_list filter {}".format(filter_dict
))
667 self
._reload
_connection
()
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]
677 offer_list
= self
._get
_offer
_list
(params
, publisher
)
679 for offer
in offer_list
:
681 sku_list
= self
._get
_sku
_list
(params
, publisher
, offer
)
684 # if version is defined get directly version, else list images
685 if len(params
) == 4 and params
[3]:
687 if version
== "latest":
688 image_list
= self
._get
_sku
_image
_list
(
689 publisher
, offer
, sku
691 image_list
= [image_list
[-1]]
693 image_list
= self
._get
_version
_image
_list
(
694 publisher
, offer
, sku
, version
697 image_list
= self
._get
_sku
_image
_list
(
698 publisher
, offer
, sku
701 raise vimconn
.VimConnAuthException(
702 "List images in Azure must include name param with at least publisher"
705 raise vimconn
.VimConnAuthException(
706 "List images in Azure must include name param with at"
711 except Exception as e
:
712 self
._format
_vimconn
_exception
(e
)
714 def _get_offer_list(self
, params
, publisher
):
716 Helper method to obtain offer list for defined publisher
718 if len(params
) >= 2 and params
[1]:
722 # get list of offers from azure
723 result_offers
= self
.conn_compute
.virtual_machine_images
.list_offers(
724 self
.region
, publisher
727 return [offer
.name
for offer
in result_offers
]
728 except CloudError
as e
:
729 # azure raises CloudError when not found
731 "error listing offers for publisher {}, Error: {}".format(
738 def _get_sku_list(self
, params
, publisher
, offer
):
740 Helper method to obtain sku list for defined publisher and offer
742 if len(params
) >= 3 and params
[2]:
746 # get list of skus from azure
747 result_skus
= self
.conn_compute
.virtual_machine_images
.list_skus(
748 self
.region
, publisher
, offer
751 return [sku
.name
for sku
in result_skus
]
752 except CloudError
as e
:
753 # azure raises CloudError when not found
755 "error listing skus for publisher {}, offer {}, Error: {}".format(
762 def _get_sku_image_list(self
, publisher
, offer
, sku
):
764 Helper method to obtain image list for publisher, offer and sku
768 result_images
= self
.conn_compute
.virtual_machine_images
.list(
769 self
.region
, publisher
, offer
, sku
771 for result_image
in result_images
:
774 "id": str(result_image
.id),
775 "name": ":".join([publisher
, offer
, sku
, result_image
.name
]),
778 except CloudError
as e
:
780 "error listing skus for publisher {}, offer {}, Error: {}".format(
788 def _get_version_image_list(self
, publisher
, offer
, sku
, version
):
791 result_image
= self
.conn_compute
.virtual_machine_images
.get(
792 self
.region
, publisher
, offer
, sku
, version
798 "id": str(result_image
.id),
799 "name": ":".join([publisher
, offer
, sku
, version
]),
802 except CloudError
as e
:
803 # azure gives CloudError when not found
805 "error listing images for publisher {}, offer {}, sku {}, version {} Error: {}".format(
806 publisher
, offer
, sku
, version
, e
813 def get_network_list(self
, filter_dict
={}):
814 """Obtain tenant networks of VIM
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
824 # self.logger.debug("getting network list for vim, filter %s", filter_dict)
826 self
._reload
_connection
()
828 vnet
= self
.conn_vnet
.virtual_networks
.get(
829 self
.vnet_resource_group
or self
.resource_group
, self
.vnet_name
833 for subnet
in vnet
.subnets
:
835 if filter_dict
.get("id") and str(subnet
.id) != filter_dict
["id"]:
839 filter_dict
.get("name")
840 and str(subnet
.name
) != filter_dict
["name"]
844 name
= self
._get
_resource
_name
_from
_resource
_id
(subnet
.id)
848 "id": str(subnet
.id),
850 "status": self
.provision_state2osm
[subnet
.provisioning_state
],
851 "cidr_block": str(subnet
.address_prefix
),
858 except Exception as e
:
859 self
._format
_vimconn
_exception
(e
)
872 availability_zone_index
=None,
873 availability_zone_list
=None,
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",
884 availability_zone_index
,
885 availability_zone_list
,
887 self
._reload
_connection
()
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
)
896 # At least one network must be provided
898 raise vimconn
.VimConnException(
899 "At least one net must be provided to create a new VM"
902 # image_id are several fields of the image_id
903 image_reference
= self
._get
_image
_reference
(image_id
)
906 virtual_machine
= None
909 # Create nics for each subnet
910 self
._check
_subnets
_for
_vm
(net_list
)
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
921 vm_nics
.append({"id": str(vm_nic
.id)})
922 net
["vim_id"] = vm_nic
.id
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
},
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
934 for idx
, vm_nic
in enumerate(vm_nics
):
936 vm_nics
[0]["Primary"] = True
938 vm_nics
[idx
]["Primary"] = False
940 vm_parameters
["network_profile"] = {"network_interfaces": vm_nics
}
942 # Obtain zone information
943 vm_zone
= self
._get
_vm
_zone
(availability_zone_index
, availability_zone_list
)
945 vm_parameters
["zones"] = [vm_zone
]
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
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
)
955 """ Por ahora no hacer polling para ver si tarda menos
956 # Add disks if they are provided
958 for disk_index, disk in enumerate(disk_list):
960 "add disk size: %s, image: %s",
964 self._add_newvm_disk(
965 virtual_machine, vm_name, disk_index, disk, created_items
969 self.conn_compute.virtual_machines.start(self.resource_group, vm_name)
970 # start_result.wait()
973 return virtual_machine
.id, created_items
975 # run_command_parameters = {
976 # "command_id": "RunShellScript", # For linux, don't change it
978 # "date > /tmp/test.txt"
981 except Exception as e
:
982 # Rollback vm creacion
986 vm_id
= virtual_machine
.id
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
))
994 self
.logger
.debug("Exception creating new vminstance: %s", e
, exc_info
=True)
995 self
._format
_vimconn
_exception
(e
)
997 def _build_os_profile(self
, vm_name
, cloud_config
, image_id
):
1000 os_profile
= {"computer_name": vm_name
}
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
)
1008 admin_username
= self
._get
_default
_admin
_user
(image_id
)
1009 os_profile
["admin_username"] = admin_username
1011 # if there is a cloud-init load it
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
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]
1022 _
, key_data
= self
._generate
_keys
()
1024 os_profile
["linux_configuration"] = {
1028 "path": "/home/{}/.ssh/authorized_keys".format(admin_username
),
1029 "key_data": key_data
,
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
1042 key
= rsa
.generate_private_key(
1043 backend
=crypto_default_backend(), public_exponent
=65537, key_size
=2048
1045 private_key
= key
.private_bytes(
1046 crypto_serialization
.Encoding
.PEM
,
1047 crypto_serialization
.PrivateFormat
.PKCS8
,
1048 crypto_serialization
.NoEncryption(),
1050 public_key
= key
.public_key().public_bytes(
1051 crypto_serialization
.Encoding
.OpenSSH
,
1052 crypto_serialization
.PublicFormat
.OpenSSH
,
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")
1060 return private_key
, public_key
1062 def _get_unused_vm_name(self
, vm_name
):
1064 Checks the vm name and in case it is used adds a suffix to the name to allow creation
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
]
1072 # get the name with the first not used suffix
1074 # name = subnet_name + "-" + str(name_suffix)
1075 name
= vm_name
# first subnet created will have no prefix
1077 while name
in vm_names
:
1079 name
= vm_name
+ "-" + str(name_suffix
)
1083 def _get_vm_zone(self
, availability_zone_index
, availability_zone_list
):
1084 if availability_zone_index
is None:
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
1092 # check if all the names of NFV AV match VIM AV names
1093 match_by_index
= False
1095 if not availability_zone_list
:
1096 match_by_index
= True
1098 for av
in availability_zone_list
:
1099 if av
not in vim_availability_zones
:
1100 match_by_index
= True
1104 return vim_availability_zones
[availability_zone_index
]
1106 return availability_zone_list
[availability_zone_index
]
1108 raise vimconn
.VimConnConflictException(
1109 "No enough availability zones at VIM for this deployment"
1112 def _get_azure_availability_zones(self
):
1113 return self
.AZURE_ZONES
1115 def _add_newvm_disk(
1116 self
, virtual_machine
, vm_name
, disk_index
, disk
, created_items
={}
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
)
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
,
1135 "location": self
.region
,
1136 "disk_size_gb": disk
.get("size"),
1137 "creation_data": {"create_option": DiskCreateOption
.empty
},
1140 data_disk
= async_disk_creation
.result()
1141 created_items
[data_disk
.id] = True
1143 image_id
= disk
.get("image_id")
1145 if azure_tools
.is_valid_resource_id(image_id
):
1146 parsed_id
= azure_tools
.parse_resource_id(image_id
)
1148 # Check if image is snapshot or disk
1149 image_name
= parsed_id
.get("name")
1150 type = parsed_id
.get("resource_type")
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
,
1160 "location": self
.region
,
1162 "create_option": "Copy",
1163 "source_uri": image_id
,
1168 data_disk
= async_disk_creation
.result()
1169 created_items
[data_disk
.id] = True
1171 raise vimconn
.VimConnNotFoundException(
1172 "Invalid image_id: %s ", image_id
1175 raise vimconn
.VimConnNotFoundException(
1176 "Invalid image_id: %s ", image_id
1179 # Attach the disk created
1180 virtual_machine
.storage_profile
.data_disks
.append(
1184 "create_option": DiskCreateOption
.attach
,
1185 "managed_disk": {"id": data_disk
.id},
1186 "disk_size_gb": disk
.get("size"),
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
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"]
1201 def _get_image_reference(self
, image_id
):
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/
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])
1215 "publisher": publisher
,
1221 raise vimconn
.VimConnException(
1222 "Unable to get image_reference from invalid image_id format: '{}'".format(
1227 # Azure VM names can not have some special characters
1228 def _check_vm_name(self
, vm_name
):
1230 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
1232 chars_not_allowed_list
= "~!@#$%^&*()=+_[]{}|;:<>/?."
1234 # First: the VM name max length is 64 characters
1235 vm_name_aux
= vm_name
[:64]
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
, "-")
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 {}
1252 self
._reload
_connection
()
1255 for vm_size
in self
.conn_compute
.resource_skus
.list(
1256 "location eq '{}'".format(self
.region
)
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
1266 for size
in vm_sizes_list
:
1267 if size
["resource_type"] == "virtualMachines":
1269 self
._find
_in
_capabilities
(size
["capabilities"], "vCPUs")
1271 size_memory
= float(
1272 self
._find
_in
_capabilities
(size
["capabilities"], "MemoryGB")
1274 size_interfaces
= self
._find
_in
_capabilities
(
1275 size
["capabilities"], "MaxNetworkInterfaces"
1278 size_interfaces
= int(size_interfaces
)
1281 "Flavor with no defined MaxNetworkInterfaces: {}".format(
1288 and size_memory
>= memMB
/ 1024
1289 and size_interfaces
>= numberInterfaces
1291 if self
._config
.get("flavors_pattern"):
1293 self
._config
.get("flavors_pattern"), size
["name"]
1296 e
["name"]: e
["value"] for e
in size
["capabilities"]
1298 new_size
["name"] = size
["name"]
1299 filtered_sizes
.append(new_size
)
1302 e
["name"]: e
["value"] for e
in size
["capabilities"]
1304 new_size
["name"] = size
["name"]
1305 filtered_sizes
.append(new_size
)
1308 listedFilteredSizes
= sorted(
1312 float(k
["MemoryGB"]),
1313 int(k
["MaxNetworkInterfaces"]),
1314 int(k
["MaxResourceVolumeMB"]),
1318 if listedFilteredSizes
:
1319 return listedFilteredSizes
[0]["name"]
1321 raise vimconn
.VimConnNotFoundException(
1322 "Cannot find any flavor matching '{}'".format(str(flavor_dict
))
1324 except Exception as e
:
1325 self
._format
_vimconn
_exception
(e
)
1327 def _get_flavor_id_from_flavor_name(self
, flavor_name
):
1328 # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name))
1330 self
._reload
_connection
()
1333 for vm_size
in self
.conn_compute
.resource_skus
.list(
1334 "location eq '{}'".format(self
.region
)
1338 output_flavor
= None
1339 for size
in vm_sizes_list
:
1340 if size
["name"] == flavor_name
:
1341 output_flavor
= size
1343 # None is returned if not found anything
1344 return output_flavor
1345 except Exception as e
:
1346 self
._format
_vimconn
_exception
(e
)
1348 def check_vim_connectivity(self
):
1350 self
._reload
_connection
()
1352 except Exception as e
:
1353 raise vimconn
.VimConnException(
1354 "Connectivity issue with Azure API: {}".format(e
)
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
()
1362 filter_dict
= {"name": net_id
}
1363 network_list
= self
.get_network_list(filter_dict
)
1365 if not network_list
:
1366 raise vimconn
.VimConnNotFoundException(
1367 "network '{}' not found".format(net_id
)
1370 return network_list
[0]
1372 def delete_network(self
, net_id
, created_items
=None):
1374 "deleting network {} - {}".format(
1375 self
.vnet_resource_group
or self
.resource_group
, net_id
1379 self
._reload
_connection
()
1380 res_name
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
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
,
1390 raise vimconn
.VimConnNotFoundException(
1391 "network '{}' not found".format(net_id
)
1394 # TODO - for a quick-fix delete nics sequentially but should not wait
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
)
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
,
1414 except ResourceNotFoundError
:
1415 raise vimconn
.VimConnNotFoundException(
1416 "network '{}' not found".format(net_id
)
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
)
1424 self
._format
_vimconn
_exception
(e
)
1425 except Exception as e
:
1426 self
._format
_vimconn
_exception
(e
)
1428 def delete_inuse_nic(self
, nic_name
):
1431 nic_data
= self
.conn_vnet
.network_interfaces
.get(self
.resource_group
, nic_name
)
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
1440 self
.logger
.debug("obtained vm")
1442 # Deattach nic from vm if it has netwolk machines attached
1443 network_interfaces
= virtual_machine
.network_profile
.network_interfaces
1444 network_interfaces
[:] = [
1446 for interface
in network_interfaces
1447 if self
._get
_resource
_name
_from
_resource
_id
(interface
.id) != nic_name
1450 # TODO - check if there is a public ip to delete and delete it
1451 if network_interfaces
:
1454 async_vm_deallocate
= (
1455 self
.conn_compute
.virtual_machines
.begin_deallocate(
1456 self
.resource_group
, vm_name
1459 self
.logger
.debug("deallocating vm")
1460 async_vm_deallocate
.wait()
1461 self
.logger
.debug("vm deallocated")
1464 self
.conn_compute
.virtual_machines
.begin_create_or_update(
1465 self
.resource_group
, vm_name
, virtual_machine
1468 virtual_machine
= async_vm_update
.result()
1469 self
.logger
.debug("nic removed from interface")
1472 self
.logger
.debug("There are no interfaces left, delete vm")
1473 self
.delete_vminstance(virtual_machine
.id)
1474 self
.logger
.debug("Delete vm")
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
1482 self
.logger
.debug("deleted NIC name: %s", nic_name
)
1484 def delete_vminstance(self
, vm_id
, created_items
=None, volumes_to_hold
=None):
1485 """Deletes a vm instance from the vim."""
1487 "deleting VM instance {} - {}".format(self
.resource_group
, vm_id
)
1489 self
._reload
_connection
()
1491 created_items
= created_items
or {}
1493 # Check vm exists, we can call delete_vm to clean created_items
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
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)
1504 vm_delete
= self
.conn_compute
.virtual_machines
.begin_delete(
1505 self
.resource_group
, res_name
1508 self
.logger
.debug("deleted VM name: %s", res_name
)
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
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
)
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
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
)
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
1535 for network_interface
in network_interfaces
:
1536 nic_name
= self
._get
_resource
_name
_from
_resource
_id
(
1537 network_interface
.id
1539 nic_data
= self
.conn_vnet
.network_interfaces
.get(
1540 self
.resource_group
, nic_name
1543 public_ip_name
= None
1544 exist_public_ip
= nic_data
.ip_configurations
[0].public_ip_address
1546 public_ip_id
= nic_data
.ip_configurations
[
1548 ].public_ip_address
.id
1551 public_ip_name
= self
._get
_resource
_name
_from
_resource
_id
(
1555 # Public ip must be deleted afterwards of nic that is attached
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
1562 self
._markdel
_created
_item
(network_interface
.id, created_items
)
1563 self
.logger
.debug("deleted NIC name: %s", nic_name
)
1565 # Delete list of public ips
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
1572 self
._markdel
_created
_item
(public_ip_id
, created_items
)
1574 # Delete created items
1575 self
._delete
_created
_items
(created_items
)
1577 except ResourceNotFoundError
:
1578 raise vimconn
.VimConnNotFoundException(
1579 "No vm instance found '{}'".format(vm_id
)
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
)
1587 self
._format
_vimconn
_exception
(e
)
1588 except Exception as e
:
1589 self
._format
_vimconn
_exception
(e
)
1591 def _markdel_created_item(self
, item_id
, created_items
):
1592 if item_id
in created_items
:
1593 created_items
[item_id
] = False
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
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
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
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")
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
)
1625 for item_name
in nics_to_delete
:
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
1632 self
.logger
.debug("deleted nic name %s:", item_name
)
1633 except Exception as e
:
1635 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
1638 for item_name
in publics_ip_to_delete
:
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
1645 self
.logger
.debug("deleted public ip name %s:", item_name
)
1646 except Exception as e
:
1648 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
1651 for item_name
in disks_to_delete
:
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
1657 async_disk_delete
.wait()
1658 self
.logger
.debug("deleted data disk name %s:", name
)
1659 except Exception as e
:
1661 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
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
1668 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1671 self
._reload
_connection
()
1672 resName
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1674 if "start" in action_dict
:
1675 self
.conn_compute
.virtual_machines
.begin_start(
1676 self
.resource_group
, resName
1679 "stop" in action_dict
1680 or "shutdown" in action_dict
1681 or "shutoff" in action_dict
1683 self
.conn_compute
.virtual_machines
.begin_power_off(
1684 self
.resource_group
, resName
1686 elif "terminate" in action_dict
:
1687 self
.conn_compute
.virtual_machines
.begin_delete(
1688 self
.resource_group
, resName
1690 elif "reboot" in action_dict
:
1691 self
.conn_compute
.virtual_machines
.begin_restart(
1692 self
.resource_group
, resName
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
))
1702 self
._format
_vimconn
_exception
(e
)
1703 except Exception as e
:
1704 self
._format
_vimconn
_exception
(e
)
1706 def delete_flavor(self
, flavor_id
):
1707 raise vimconn
.VimConnAuthException(
1708 "It is not possible to delete a FLAVOR in AZURE"
1711 def delete_tenant(self
, tenant_id
):
1712 raise vimconn
.VimConnAuthException(
1713 "It is not possible to delete a TENANT in AZURE"
1716 def delete_image(self
, image_id
):
1717 raise vimconn
.VimConnAuthException(
1718 "It is not possible to delete a IMAGE in AZURE"
1721 def get_vminstance(self
, vm_id
):
1723 Obtaing the vm instance data from v_id
1725 self
.logger
.debug("get vm instance: %s", vm_id
)
1726 self
._reload
_connection
()
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
)
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
)
1740 self
._format
_vimconn
_exception
(e
)
1741 except Exception as e
:
1742 self
._format
_vimconn
_exception
(e
)
1746 def get_flavor(self
, flavor_id
):
1748 Obtains the flavor_data from the flavor_id
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
)
1758 "ram": flavor_data
["memoryInMB"],
1759 "vcpus": flavor_data
["numberOfCores"],
1760 "disk": flavor_data
["resourceDiskSizeInMB"] / 1024,
1765 raise vimconn
.VimConnNotFoundException(
1766 "flavor '{}' not found".format(flavor_id
)
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
1774 tenants_azure
= [{"name": self
.tenant
, "id": self
.tenant
}]
1777 self
.logger
.debug("get tenant list: %s", filter_dict
)
1778 for tenant_azure
in tenants_azure
:
1781 filter_dict
.get("id")
1782 and str(tenant_azure
.get("id")) != filter_dict
["id"]
1787 filter_dict
.get("name")
1788 and str(tenant_azure
.get("name")) != filter_dict
["name"]
1792 tenant_list
.append(tenant_azure
)
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)
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)
1813 self
._reload
_connection
()
1815 self
.logger
.debug("reload nets status net_list: %s", net_list
)
1816 for net_id
in net_list
:
1818 netName
= self
._get
_net
_name
_from
_resource
_id
(net_id
)
1819 resName
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
1821 net
= self
.conn_vnet
.subnets
.get(
1822 self
.vnet_resource_group
or self
.resource_group
, netName
, resName
1825 out_nets
[net_id
] = {
1826 "status": self
.provision_state2osm
[net
.provisioning_state
],
1827 "vim_info": str(net
),
1829 except CloudError
as e
:
1830 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1832 "Not found subnet net_name: %s, subnet_name: %s",
1836 out_nets
[net_id
] = {"status": "DELETED", "error_msg": str(e
)}
1839 "CloudError Exception %s when searching subnet", e
1841 out_nets
[net_id
] = {
1842 "status": "VIM_ERROR",
1843 "error_msg": str(e
),
1845 except vimconn
.VimConnNotFoundException
as e
:
1847 "VimConnNotFoundException %s when searching subnet", e
1849 out_nets
[net_id
] = {
1850 "status": "DELETED",
1851 "error_msg": str(e
),
1853 except Exception as e
:
1855 "Exception %s when searching subnet", e
, exc_info
=True
1857 out_nets
[net_id
] = {
1858 "status": "VIM_ERROR",
1859 "error_msg": str(e
),
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)
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.
1887 self
._reload
_connection
()
1889 self
.logger
.debug("refresh vm status vm_list: %s", vm_list
)
1890 search_vm_list
= vm_list
or {}
1892 for vm_id
in search_vm_list
:
1895 res_name
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1897 vm
= self
.conn_compute
.virtual_machines
.get(
1898 self
.resource_group
, res_name
1900 out_vm
["vim_info"] = str(vm
)
1901 out_vm
["status"] = self
.provision_state2osm
.get(
1902 vm
.provisioning_state
, "OTHER"
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
1911 for status
in instance_view
.statuses
:
1912 splitted_status
= status
.code
.split("/")
1914 len(splitted_status
) == 2
1915 and splitted_status
[0] == "PowerState"
1917 out_vm
["status"] = self
.power_state2osm
.get(
1918 splitted_status
[1], "OTHER"
1921 network_interfaces
= vm
.network_profile
.network_interfaces
1922 out_vm
["interfaces"] = self
._get
_vm
_interfaces
_status
(
1923 vm_id
, network_interfaces
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
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
1944 out_vms
[vm_id
] = out_vm
1948 def _get_vm_interfaces_status(self
, vm_id
, interfaces
):
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
1956 for network_interface
in interfaces
:
1958 nic_name
= self
._get
_resource
_name
_from
_resource
_id
(
1959 network_interface
.id
1961 interface_dict
["vim_interface_id"] = network_interface
.id
1963 nic_data
= self
.conn_vnet
.network_interfaces
.get(
1964 self
.resource_group
,
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
1974 public_ip
= self
.conn_vnet
.public_ip_addresses
.get(
1975 self
.resource_group
, public_ip_name
1977 self
.logger
.debug("Public ip address is: %s", public_ip
.ip_address
)
1978 ips
.append(public_ip
.ip_address
)
1980 private_ip
= nic_data
.ip_configurations
[0].private_ip_address
1981 ips
.append(private_ip
)
1983 interface_dict
["mac_address"] = nic_data
.mac_address
1984 interface_dict
["ip_address"] = ";".join(ips
)
1985 interface_list
.append(interface_dict
)
1987 return interface_list
1988 except Exception as e
:
1990 "Exception %s obtaining interface data for vm: %s",
1995 self
._format
_vimconn
_exception
(e
)
1997 def _get_default_admin_user(self
, image_id
):
1998 if "ubuntu" in image_id
.lower():
2001 return self
._default
_admin
_user
2003 def migrate_instance(self
, vm_id
, compute_host
=None):
2007 vm_id: ID of an instance
2008 compute_host: Host to migrate the vdu to
2010 # TODO: Add support for migration
2011 raise vimconn
.VimConnNotImplemented("Not implemented")
2013 def resize_instance(self
, vm_id
, flavor_id
=None):
2017 vm_id: ID of an instance
2018 flavor_id: flavor id to resize the vdu
2020 # TODO: Add support for resize
2021 raise vimconn
.VimConnNotImplemented("Not implemented")
2024 if __name__
== "__main__":
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
)
2036 # Making some basic test
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",
2049 for param
, env_var
in needed_test_params
.items():
2050 value
= getenv(env_var
)
2053 raise Exception("Provide a valid value for env '{}'".format(env_var
))
2055 test_params
[param
] = value
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"),
2065 azure
= vimconnector(
2068 tenant_id
=test_params
["tenant"],
2072 user
=test_params
["client_id"],
2073 passwd
=test_params
["secret"],
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))
2083 logger.debug("List networks")
2084 network_list = azure.get_network_list({"name": "internal"})
2085 logger.debug("Network_list: {}".format(network_list))
2087 logger.debug("List flavors")
2088 flavors = azure.get_flavor_id_from_data({"vcpus": 2})
2089 logger.debug("flavors: {}".format(flavors))
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))
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))
2106 logger.debug("List networks")
2107 network_list = azure.get_network_list({"name": "internal"})
2108 logger.debug("Network_list: {}".format(network_list))
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"])
2115 logger.debug("Vmachine: {}".format(vmachine))
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))
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")
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"])
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": (
2147 "chpasswd: { expire: False }\n"
2148 "ssh_pwauth: True\n\n"
2151 " # My new helloworld file\n\n"
2152 " owner: root:root\n"
2153 " permissions: '0644'\n"
2154 " path: /root/helloworld.txt",
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"
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",
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))
2177 # Delete nonexistent vm
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"])
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
2191 azure.delete_vminstance(vm_id, created_items)
2192 except vimconn.VimConnNotFoundException as e:
2193 print("Ok: excepcion no encontrada")
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)