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():
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
)
871 availability_zone_index
=None,
872 availability_zone_list
=None,
875 "new vm instance name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, "
876 "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s",
883 availability_zone_index
,
884 availability_zone_list
,
886 self
._reload
_connection
()
888 # Validate input data is valid
889 # The virtual machine name must have less or 64 characters and it can not have the following
890 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
891 vm_name
= self
._check
_vm
_name
(name
)
892 # Obtain vm unused name
893 vm_name
= self
._get
_unused
_vm
_name
(vm_name
)
895 # At least one network must be provided
897 raise vimconn
.VimConnException(
898 "At least one net must be provided to create a new VM"
901 # image_id are several fields of the image_id
902 image_reference
= self
._get
_image
_reference
(image_id
)
905 virtual_machine
= None
908 # Create nics for each subnet
909 self
._check
_subnets
_for
_vm
(net_list
)
912 for idx
, net
in enumerate(net_list
):
913 # Fault with subnet_id
914 # subnet_id=net["subnet_id"]
915 # subnet_id=net["net_id"]
916 nic_name
= vm_name
+ "-nic-" + str(idx
)
917 vm_nic
, nic_items
= self
._create
_nic
(
918 net
, nic_name
, self
.region
, net
.get("ip_address"), created_items
920 vm_nics
.append({"id": str(vm_nic
.id)})
921 net
["vim_id"] = vm_nic
.id
924 "location": self
.region
,
925 "os_profile": self
._build
_os
_profile
(vm_name
, cloud_config
, image_id
),
926 "hardware_profile": {"vm_size": flavor_id
},
927 "storage_profile": {"image_reference": image_reference
},
930 # If the machine has several networks one must be marked as primary
931 # As it is not indicated in the interface the first interface will be marked as primary
933 for idx
, vm_nic
in enumerate(vm_nics
):
935 vm_nics
[0]["Primary"] = True
937 vm_nics
[idx
]["Primary"] = False
939 vm_parameters
["network_profile"] = {"network_interfaces": vm_nics
}
941 # Obtain zone information
942 vm_zone
= self
._get
_vm
_zone
(availability_zone_index
, availability_zone_list
)
944 vm_parameters
["zones"] = [vm_zone
]
946 self
.logger
.debug("create vm name: %s", vm_name
)
947 creation_result
= self
.conn_compute
.virtual_machines
.begin_create_or_update(
948 self
.resource_group
, vm_name
, vm_parameters
, polling
=False
950 self
.logger
.debug("obtained creation result: %s", creation_result
)
951 virtual_machine
= creation_result
.result()
952 self
.logger
.debug("created vm name: %s", vm_name
)
954 """ Por ahora no hacer polling para ver si tarda menos
955 # Add disks if they are provided
957 for disk_index, disk in enumerate(disk_list):
959 "add disk size: %s, image: %s",
963 self._add_newvm_disk(
964 virtual_machine, vm_name, disk_index, disk, created_items
968 self.conn_compute.virtual_machines.start(self.resource_group, vm_name)
969 # start_result.wait()
972 return virtual_machine
.id, created_items
974 # run_command_parameters = {
975 # "command_id": "RunShellScript", # For linux, don't change it
977 # "date > /tmp/test.txt"
980 except Exception as e
:
981 # Rollback vm creacion
985 vm_id
= virtual_machine
.id
988 self
.logger
.debug("exception creating vm try to rollback")
989 self
.delete_vminstance(vm_id
, created_items
)
990 except Exception as e2
:
991 self
.logger
.error("new_vminstance rollback fail {}".format(e2
))
993 self
.logger
.debug("Exception creating new vminstance: %s", e
, exc_info
=True)
994 self
._format
_vimconn
_exception
(e
)
996 def _build_os_profile(self
, vm_name
, cloud_config
, image_id
):
999 os_profile
= {"computer_name": vm_name
}
1001 # for azure os_profile admin_username is required
1002 if cloud_config
and cloud_config
.get("users"):
1003 admin_username
= cloud_config
.get("users")[0].get(
1004 "name", self
._get
_default
_admin
_user
(image_id
)
1007 admin_username
= self
._get
_default
_admin
_user
(image_id
)
1008 os_profile
["admin_username"] = admin_username
1010 # if there is a cloud-init load it
1012 _
, userdata
= self
._create
_user
_data
(cloud_config
)
1013 custom_data
= base64
.b64encode(userdata
.encode("utf-8")).decode("latin-1")
1014 os_profile
["custom_data"] = custom_data
1016 # either password of ssh-keys are required
1017 # we will always use ssh-keys, in case it is not available we will generate it
1018 if cloud_config
and cloud_config
.get("key-pairs"):
1019 key_data
= cloud_config
.get("key-pairs")[0]
1021 _
, key_data
= self
._generate
_keys
()
1023 os_profile
["linux_configuration"] = {
1027 "path": "/home/{}/.ssh/authorized_keys".format(admin_username
),
1028 "key_data": key_data
,
1036 def _generate_keys(self
):
1037 """Method used to generate a pair of private/public keys.
1038 This method is used because to create a vm in Azure we always need a key or a password
1039 In some cases we may have a password in a cloud-init file but it may not be available
1041 key
= rsa
.generate_private_key(
1042 backend
=crypto_default_backend(), public_exponent
=65537, key_size
=2048
1044 private_key
= key
.private_bytes(
1045 crypto_serialization
.Encoding
.PEM
,
1046 crypto_serialization
.PrivateFormat
.PKCS8
,
1047 crypto_serialization
.NoEncryption(),
1049 public_key
= key
.public_key().public_bytes(
1050 crypto_serialization
.Encoding
.OpenSSH
,
1051 crypto_serialization
.PublicFormat
.OpenSSH
,
1053 private_key
= private_key
.decode("utf8")
1054 # Change first line because Paramiko needs a explicit start with 'BEGIN RSA PRIVATE KEY'
1055 i
= private_key
.find("\n")
1056 private_key
= "-----BEGIN RSA PRIVATE KEY-----" + private_key
[i
:]
1057 public_key
= public_key
.decode("utf8")
1059 return private_key
, public_key
1061 def _get_unused_vm_name(self
, vm_name
):
1063 Checks the vm name and in case it is used adds a suffix to the name to allow creation
1066 all_vms
= self
.conn_compute
.virtual_machines
.list(self
.resource_group
)
1067 # Filter to vms starting with the indicated name
1068 vms
= list(filter(lambda vm
: (vm
.name
.startswith(vm_name
)), all_vms
))
1069 vm_names
= [str(vm
.name
) for vm
in vms
]
1071 # get the name with the first not used suffix
1073 # name = subnet_name + "-" + str(name_suffix)
1074 name
= vm_name
# first subnet created will have no prefix
1076 while name
in vm_names
:
1078 name
= vm_name
+ "-" + str(name_suffix
)
1082 def _get_vm_zone(self
, availability_zone_index
, availability_zone_list
):
1083 if availability_zone_index
is None:
1086 vim_availability_zones
= self
._get
_azure
_availability
_zones
()
1087 # check if VIM offer enough availability zones describe in the VNFD
1088 if vim_availability_zones
and len(availability_zone_list
) <= len(
1089 vim_availability_zones
1091 # check if all the names of NFV AV match VIM AV names
1092 match_by_index
= False
1094 if not availability_zone_list
:
1095 match_by_index
= True
1097 for av
in availability_zone_list
:
1098 if av
not in vim_availability_zones
:
1099 match_by_index
= True
1103 return vim_availability_zones
[availability_zone_index
]
1105 return availability_zone_list
[availability_zone_index
]
1107 raise vimconn
.VimConnConflictException(
1108 "No enough availability zones at VIM for this deployment"
1111 def _get_azure_availability_zones(self
):
1112 return self
.AZURE_ZONES
1114 def _add_newvm_disk(
1115 self
, virtual_machine
, vm_name
, disk_index
, disk
, created_items
={}
1120 # Check if must create empty disk or from image
1121 if disk
.get("vim_id"):
1122 # disk already exists, just get
1123 parsed_id
= azure_tools
.parse_resource_id(disk
.get("vim_id"))
1124 disk_name
= parsed_id
.get("name")
1125 data_disk
= self
.conn_compute
.disks
.get(self
.resource_group
, disk_name
)
1127 disk_name
= vm_name
+ "_DataDisk_" + str(disk_index
)
1128 if not disk
.get("image_id"):
1129 self
.logger
.debug("create new data disk name: %s", disk_name
)
1130 async_disk_creation
= self
.conn_compute
.disks
.begin_create_or_update(
1131 self
.resource_group
,
1134 "location": self
.region
,
1135 "disk_size_gb": disk
.get("size"),
1136 "creation_data": {"create_option": DiskCreateOption
.empty
},
1139 data_disk
= async_disk_creation
.result()
1140 created_items
[data_disk
.id] = True
1142 image_id
= disk
.get("image_id")
1144 if azure_tools
.is_valid_resource_id(image_id
):
1145 parsed_id
= azure_tools
.parse_resource_id(image_id
)
1147 # Check if image is snapshot or disk
1148 image_name
= parsed_id
.get("name")
1149 type = parsed_id
.get("resource_type")
1151 if type == "snapshots" or type == "disks":
1152 self
.logger
.debug("create disk from copy name: %s", image_name
)
1153 # ¿Should check that snapshot exists?
1154 async_disk_creation
= (
1155 self
.conn_compute
.disks
.begin_create_or_update(
1156 self
.resource_group
,
1159 "location": self
.region
,
1161 "create_option": "Copy",
1162 "source_uri": image_id
,
1167 data_disk
= async_disk_creation
.result()
1168 created_items
[data_disk
.id] = True
1170 raise vimconn
.VimConnNotFoundException(
1171 "Invalid image_id: %s ", image_id
1174 raise vimconn
.VimConnNotFoundException(
1175 "Invalid image_id: %s ", image_id
1178 # Attach the disk created
1179 virtual_machine
.storage_profile
.data_disks
.append(
1183 "create_option": DiskCreateOption
.attach
,
1184 "managed_disk": {"id": data_disk
.id},
1185 "disk_size_gb": disk
.get("size"),
1188 self
.logger
.debug("attach disk name: %s", disk_name
)
1189 self
.conn_compute
.virtual_machines
.begin_create_or_update(
1190 self
.resource_group
, virtual_machine
.name
, virtual_machine
1193 # It is necesary extract from image_id data to create the VM with this format
1194 # "image_reference": {
1195 # "publisher": vm_reference["publisher"],
1196 # "offer": vm_reference["offer"],
1197 # "sku": vm_reference["sku"],
1198 # "version": vm_reference["version"]
1200 def _get_image_reference(self
, image_id
):
1202 # The data input format example:
1203 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
1204 # Publishers/Canonical/ArtifactTypes/VMImage/
1205 # Offers/UbuntuServer/
1207 # Versions/18.04.201809110
1208 publisher
= str(image_id
.split("/")[8])
1209 offer
= str(image_id
.split("/")[12])
1210 sku
= str(image_id
.split("/")[14])
1211 version
= str(image_id
.split("/")[16])
1214 "publisher": publisher
,
1220 raise vimconn
.VimConnException(
1221 "Unable to get image_reference from invalid image_id format: '{}'".format(
1226 # Azure VM names can not have some special characters
1227 def _check_vm_name(self
, vm_name
):
1229 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
1231 chars_not_allowed_list
= "~!@#$%^&*()=+_[]{}|;:<>/?."
1233 # First: the VM name max length is 64 characters
1234 vm_name_aux
= vm_name
[:64]
1236 # Second: replace not allowed characters
1237 for elem
in chars_not_allowed_list
:
1238 # Check if string is in the main string
1239 if elem
in vm_name_aux
:
1240 # self.logger.debug("Dentro del IF")
1241 # Replace the string
1242 vm_name_aux
= vm_name_aux
.replace(elem
, "-")
1246 def get_flavor_id_from_data(self
, flavor_dict
):
1247 self
.logger
.debug("getting flavor id from data, flavor_dict: %s", flavor_dict
)
1248 filter_dict
= flavor_dict
or {}
1251 self
._reload
_connection
()
1254 for vm_size
in self
.conn_compute
.resource_skus
.list(
1255 "location eq '{}'".format(self
.region
)
1259 cpus
= filter_dict
.get("vcpus") or 0
1260 memMB
= filter_dict
.get("ram") or 0
1261 numberInterfaces
= len(filter_dict
.get("interfaces", [])) or 0
1265 for size
in vm_sizes_list
:
1266 if size
["resource_type"] == "virtualMachines":
1268 self
._find
_in
_capabilities
(size
["capabilities"], "vCPUs")
1270 size_memory
= float(
1271 self
._find
_in
_capabilities
(size
["capabilities"], "MemoryGB")
1273 size_interfaces
= self
._find
_in
_capabilities
(
1274 size
["capabilities"], "MaxNetworkInterfaces"
1277 size_interfaces
= int(size_interfaces
)
1280 "Flavor with no defined MaxNetworkInterfaces: {}".format(
1287 and size_memory
>= memMB
/ 1024
1288 and size_interfaces
>= numberInterfaces
1290 if self
._config
.get("flavors_pattern"):
1292 self
._config
.get("flavors_pattern"), size
["name"]
1295 e
["name"]: e
["value"] for e
in size
["capabilities"]
1297 new_size
["name"] = size
["name"]
1298 filtered_sizes
.append(new_size
)
1301 e
["name"]: e
["value"] for e
in size
["capabilities"]
1303 new_size
["name"] = size
["name"]
1304 filtered_sizes
.append(new_size
)
1307 listedFilteredSizes
= sorted(
1311 float(k
["MemoryGB"]),
1312 int(k
["MaxNetworkInterfaces"]),
1313 int(k
["MaxResourceVolumeMB"]),
1317 if listedFilteredSizes
:
1318 return listedFilteredSizes
[0]["name"]
1320 raise vimconn
.VimConnNotFoundException(
1321 "Cannot find any flavor matching '{}'".format(str(flavor_dict
))
1323 except Exception as e
:
1324 self
._format
_vimconn
_exception
(e
)
1326 def _get_flavor_id_from_flavor_name(self
, flavor_name
):
1327 # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name))
1329 self
._reload
_connection
()
1332 for vm_size
in self
.conn_compute
.resource_skus
.list(
1333 "location eq '{}'".format(self
.region
)
1337 output_flavor
= None
1338 for size
in vm_sizes_list
:
1339 if size
["name"] == flavor_name
:
1340 output_flavor
= size
1342 # None is returned if not found anything
1343 return output_flavor
1344 except Exception as e
:
1345 self
._format
_vimconn
_exception
(e
)
1347 def check_vim_connectivity(self
):
1349 self
._reload
_connection
()
1351 except Exception as e
:
1352 raise vimconn
.VimConnException(
1353 "Connectivity issue with Azure API: {}".format(e
)
1356 def get_network(self
, net_id
):
1357 # self.logger.debug("get network id: {}".format(net_id))
1358 # res_name = self._get_resource_name_from_resource_id(net_id)
1359 self
._reload
_connection
()
1361 filter_dict
= {"name": net_id
}
1362 network_list
= self
.get_network_list(filter_dict
)
1364 if not network_list
:
1365 raise vimconn
.VimConnNotFoundException(
1366 "network '{}' not found".format(net_id
)
1369 return network_list
[0]
1371 def delete_network(self
, net_id
, created_items
=None):
1373 "deleting network {} - {}".format(
1374 self
.vnet_resource_group
or self
.resource_group
, net_id
1378 self
._reload
_connection
()
1379 res_name
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
1382 # Obtain subnets ant try to delete nic first
1383 subnet
= self
.conn_vnet
.subnets
.get(
1384 self
.vnet_resource_group
or self
.resource_group
,
1389 raise vimconn
.VimConnNotFoundException(
1390 "network '{}' not found".format(net_id
)
1393 # TODO - for a quick-fix delete nics sequentially but should not wait
1395 if subnet
.ip_configurations
:
1396 for ip_configuration
in subnet
.ip_configurations
:
1397 # obtain nic_name from ip_configuration
1398 parsed_id
= azure_tools
.parse_resource_id(ip_configuration
.id)
1399 nic_name
= parsed_id
["name"]
1400 self
.delete_inuse_nic(nic_name
)
1402 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
1403 # Put the initial virtual_network API
1404 async_delete
= self
.conn_vnet
.subnets
.begin_delete(
1405 self
.vnet_resource_group
or self
.resource_group
,
1413 except ResourceNotFoundError
:
1414 raise vimconn
.VimConnNotFoundException(
1415 "network '{}' not found".format(net_id
)
1417 except CloudError
as e
:
1418 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1419 raise vimconn
.VimConnNotFoundException(
1420 "network '{}' not found".format(net_id
)
1423 self
._format
_vimconn
_exception
(e
)
1424 except Exception as e
:
1425 self
._format
_vimconn
_exception
(e
)
1427 def delete_inuse_nic(self
, nic_name
):
1430 nic_data
= self
.conn_vnet
.network_interfaces
.get(self
.resource_group
, nic_name
)
1432 # Obtain vm associated to nic in case it exists
1433 if nic_data
.virtual_machine
:
1434 vm_name
= azure_tools
.parse_resource_id(nic_data
.virtual_machine
.id)["name"]
1435 self
.logger
.debug("vm_name: {}".format(vm_name
))
1436 virtual_machine
= self
.conn_compute
.virtual_machines
.get(
1437 self
.resource_group
, vm_name
1439 self
.logger
.debug("obtained vm")
1441 # Deattach nic from vm if it has netwolk machines attached
1442 network_interfaces
= virtual_machine
.network_profile
.network_interfaces
1443 network_interfaces
[:] = [
1445 for interface
in network_interfaces
1446 if self
._get
_resource
_name
_from
_resource
_id
(interface
.id) != nic_name
1449 # TODO - check if there is a public ip to delete and delete it
1450 if network_interfaces
:
1453 async_vm_deallocate
= (
1454 self
.conn_compute
.virtual_machines
.begin_deallocate(
1455 self
.resource_group
, vm_name
1458 self
.logger
.debug("deallocating vm")
1459 async_vm_deallocate
.wait()
1460 self
.logger
.debug("vm deallocated")
1463 self
.conn_compute
.virtual_machines
.begin_create_or_update(
1464 self
.resource_group
, vm_name
, virtual_machine
1467 virtual_machine
= async_vm_update
.result()
1468 self
.logger
.debug("nic removed from interface")
1471 self
.logger
.debug("There are no interfaces left, delete vm")
1472 self
.delete_vminstance(virtual_machine
.id)
1473 self
.logger
.debug("Delete vm")
1476 self
.logger
.debug("delete NIC name: %s", nic_name
)
1477 nic_delete
= self
.conn_vnet
.network_interfaces
.begin_delete(
1478 self
.resource_group
, nic_name
1481 self
.logger
.debug("deleted NIC name: %s", nic_name
)
1483 def delete_vminstance(self
, vm_id
, created_items
=None):
1484 """Deletes a vm instance from the vim."""
1486 "deleting VM instance {} - {}".format(self
.resource_group
, vm_id
)
1488 self
._reload
_connection
()
1490 created_items
= created_items
or {}
1492 # Check vm exists, we can call delete_vm to clean created_items
1494 res_name
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1495 vm
= self
.conn_compute
.virtual_machines
.get(
1496 self
.resource_group
, res_name
1499 # Shuts down the virtual machine and releases the compute resources
1500 # vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1503 vm_delete
= self
.conn_compute
.virtual_machines
.begin_delete(
1504 self
.resource_group
, res_name
1507 self
.logger
.debug("deleted VM name: %s", res_name
)
1509 # Delete OS Disk, check if exists, in case of error creating
1510 # it may not be fully created
1511 if vm
.storage_profile
.os_disk
:
1512 os_disk_name
= vm
.storage_profile
.os_disk
.name
1513 self
.logger
.debug("delete OS DISK: %s", os_disk_name
)
1514 async_disk_delete
= self
.conn_compute
.disks
.begin_delete(
1515 self
.resource_group
, os_disk_name
1517 async_disk_delete
.wait()
1518 # os disks are created always with the machine
1519 self
.logger
.debug("deleted OS DISK name: %s", os_disk_name
)
1521 for data_disk
in vm
.storage_profile
.data_disks
:
1522 self
.logger
.debug("delete data_disk: %s", data_disk
.name
)
1523 async_disk_delete
= self
.conn_compute
.disks
.begin_delete(
1524 self
.resource_group
, data_disk
.name
1526 async_disk_delete
.wait()
1527 self
._markdel
_created
_item
(data_disk
.managed_disk
.id, created_items
)
1528 self
.logger
.debug("deleted OS DISK name: %s", data_disk
.name
)
1530 # After deleting VM, it is necessary to delete NIC, because if is not deleted delete_network
1531 # does not work because Azure says that is in use the subnet
1532 network_interfaces
= vm
.network_profile
.network_interfaces
1534 for network_interface
in network_interfaces
:
1535 nic_name
= self
._get
_resource
_name
_from
_resource
_id
(
1536 network_interface
.id
1538 nic_data
= self
.conn_vnet
.network_interfaces
.get(
1539 self
.resource_group
, nic_name
1542 public_ip_name
= None
1543 exist_public_ip
= nic_data
.ip_configurations
[0].public_ip_address
1545 public_ip_id
= nic_data
.ip_configurations
[
1547 ].public_ip_address
.id
1550 public_ip_name
= self
._get
_resource
_name
_from
_resource
_id
(
1554 # Public ip must be deleted afterwards of nic that is attached
1556 self
.logger
.debug("delete NIC name: %s", nic_name
)
1557 nic_delete
= self
.conn_vnet
.network_interfaces
.begin_delete(
1558 self
.resource_group
, nic_name
1561 self
._markdel
_created
_item
(network_interface
.id, created_items
)
1562 self
.logger
.debug("deleted NIC name: %s", nic_name
)
1564 # Delete list of public ips
1566 self
.logger
.debug("delete PUBLIC IP - " + public_ip_name
)
1567 ip_delete
= self
.conn_vnet
.public_ip_addresses
.begin_delete(
1568 self
.resource_group
, public_ip_name
1571 self
._markdel
_created
_item
(public_ip_id
, created_items
)
1573 # Delete created items
1574 self
._delete
_created
_items
(created_items
)
1576 except ResourceNotFoundError
:
1577 raise vimconn
.VimConnNotFoundException(
1578 "No vm instance found '{}'".format(vm_id
)
1580 except CloudError
as e
:
1581 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1582 raise vimconn
.VimConnNotFoundException(
1583 "No vm instance found '{}'".format(vm_id
)
1586 self
._format
_vimconn
_exception
(e
)
1587 except Exception as e
:
1588 self
._format
_vimconn
_exception
(e
)
1590 def _markdel_created_item(self
, item_id
, created_items
):
1591 if item_id
in created_items
:
1592 created_items
[item_id
] = False
1594 def _delete_created_items(self
, created_items
):
1595 """Delete created_items elements that have not been deleted with the virtual machine
1596 Created_items may not be deleted correctly with the created machine if the
1597 virtual machine fails creating or in other cases of error
1599 self
.logger
.debug("Created items: %s", created_items
)
1600 # TODO - optimize - should not wait until it is deleted
1601 # Must delete in order first nics, then public_ips
1602 # As dictionaries don't preserve order, first get items to be deleted then delete them
1604 publics_ip_to_delete
= []
1605 disks_to_delete
= []
1606 for item_id
, v
in created_items
.items():
1607 if not v
: # skip already deleted
1610 # self.logger.debug("Must delete item id: %s", item_id)
1611 # Obtain type, supported nic, disk or public ip
1612 parsed_id
= azure_tools
.parse_resource_id(item_id
)
1613 resource_type
= parsed_id
.get("resource_type")
1614 name
= parsed_id
.get("name")
1616 if resource_type
== "networkInterfaces":
1617 nics_to_delete
.append(name
)
1618 elif resource_type
== "publicIPAddresses":
1619 publics_ip_to_delete
.append(name
)
1620 elif resource_type
== "disks":
1621 disks_to_delete
.append(name
)
1624 for item_name
in nics_to_delete
:
1626 self
.logger
.debug("deleting nic name %s:", item_name
)
1627 nic_delete
= self
.conn_vnet
.network_interfaces
.begin_delete(
1628 self
.resource_group
, item_name
1631 self
.logger
.debug("deleted nic name %s:", item_name
)
1632 except Exception as e
:
1634 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
1637 for item_name
in publics_ip_to_delete
:
1639 self
.logger
.debug("deleting public ip name %s:", item_name
)
1640 ip_delete
= self
.conn_vnet
.public_ip_addresses
.begin_delete(
1641 self
.resource_group
, name
1644 self
.logger
.debug("deleted public ip name %s:", item_name
)
1645 except Exception as e
:
1647 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
1650 for item_name
in disks_to_delete
:
1652 self
.logger
.debug("deleting data disk name %s:", name
)
1653 async_disk_delete
= self
.conn_compute
.disks
.begin_delete(
1654 self
.resource_group
, item_name
1656 async_disk_delete
.wait()
1657 self
.logger
.debug("deleted data disk name %s:", name
)
1658 except Exception as e
:
1660 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
1663 def action_vminstance(self
, vm_id
, action_dict
, created_items
={}):
1664 """Send and action over a VM instance from VIM
1665 Returns the vm_id if the action was successfully sent to the VIM
1667 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1670 self
._reload
_connection
()
1671 resName
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1673 if "start" in action_dict
:
1674 self
.conn_compute
.virtual_machines
.begin_start(
1675 self
.resource_group
, resName
1678 "stop" in action_dict
1679 or "shutdown" in action_dict
1680 or "shutoff" in action_dict
1682 self
.conn_compute
.virtual_machines
.begin_power_off(
1683 self
.resource_group
, resName
1685 elif "terminate" in action_dict
:
1686 self
.conn_compute
.virtual_machines
.begin_delete(
1687 self
.resource_group
, resName
1689 elif "reboot" in action_dict
:
1690 self
.conn_compute
.virtual_machines
.begin_restart(
1691 self
.resource_group
, resName
1695 except ResourceNotFoundError
:
1696 raise vimconn
.VimConnNotFoundException("No vm found '{}'".format(vm_id
))
1697 except CloudError
as e
:
1698 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1699 raise vimconn
.VimConnNotFoundException("No vm found '{}'".format(vm_id
))
1701 self
._format
_vimconn
_exception
(e
)
1702 except Exception as e
:
1703 self
._format
_vimconn
_exception
(e
)
1705 def delete_flavor(self
, flavor_id
):
1706 raise vimconn
.VimConnAuthException(
1707 "It is not possible to delete a FLAVOR in AZURE"
1710 def delete_tenant(self
, tenant_id
):
1711 raise vimconn
.VimConnAuthException(
1712 "It is not possible to delete a TENANT in AZURE"
1715 def delete_image(self
, image_id
):
1716 raise vimconn
.VimConnAuthException(
1717 "It is not possible to delete a IMAGE in AZURE"
1720 def get_vminstance(self
, vm_id
):
1722 Obtaing the vm instance data from v_id
1724 self
.logger
.debug("get vm instance: %s", vm_id
)
1725 self
._reload
_connection
()
1727 resName
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1728 vm
= self
.conn_compute
.virtual_machines
.get(self
.resource_group
, resName
)
1729 except ResourceNotFoundError
:
1730 raise vimconn
.VimConnNotFoundException(
1731 "No vminstance found '{}'".format(vm_id
)
1733 except CloudError
as e
:
1734 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1735 raise vimconn
.VimConnNotFoundException(
1736 "No vminstance found '{}'".format(vm_id
)
1739 self
._format
_vimconn
_exception
(e
)
1740 except Exception as e
:
1741 self
._format
_vimconn
_exception
(e
)
1745 def get_flavor(self
, flavor_id
):
1747 Obtains the flavor_data from the flavor_id
1749 self
._reload
_connection
()
1750 self
.logger
.debug("get flavor from id: %s", flavor_id
)
1751 flavor_data
= self
._get
_flavor
_id
_from
_flavor
_name
(flavor_id
)
1757 "ram": flavor_data
["memoryInMB"],
1758 "vcpus": flavor_data
["numberOfCores"],
1759 "disk": flavor_data
["resourceDiskSizeInMB"] / 1024,
1764 raise vimconn
.VimConnNotFoundException(
1765 "flavor '{}' not found".format(flavor_id
)
1768 def get_tenant_list(self
, filter_dict
={}):
1769 """Obtains the list of tenants
1770 For the azure connector only the azure tenant will be returned if it is compatible
1773 tenants_azure
= [{"name": self
.tenant
, "id": self
.tenant
}]
1776 self
.logger
.debug("get tenant list: %s", filter_dict
)
1777 for tenant_azure
in tenants_azure
:
1780 filter_dict
.get("id")
1781 and str(tenant_azure
.get("id")) != filter_dict
["id"]
1786 filter_dict
.get("name")
1787 and str(tenant_azure
.get("name")) != filter_dict
["name"]
1791 tenant_list
.append(tenant_azure
)
1795 def refresh_nets_status(self
, net_list
):
1796 """Get the status of the networks
1797 Params: the list of network identifiers
1798 Returns a dictionary with:
1799 net_id: #VIM id of this network
1800 status: #Mandatory. Text with one of:
1801 # DELETED (not found at vim)
1802 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1803 # OTHER (Vim reported other status not understood)
1804 # ERROR (VIM indicates an ERROR status)
1805 # ACTIVE, INACTIVE, DOWN (admin down),
1806 # BUILD (on building process)
1808 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1809 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1812 self
._reload
_connection
()
1814 self
.logger
.debug("reload nets status net_list: %s", net_list
)
1815 for net_id
in net_list
:
1817 netName
= self
._get
_net
_name
_from
_resource
_id
(net_id
)
1818 resName
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
1820 net
= self
.conn_vnet
.subnets
.get(
1821 self
.vnet_resource_group
or self
.resource_group
, netName
, resName
1824 out_nets
[net_id
] = {
1825 "status": self
.provision_state2osm
[net
.provisioning_state
],
1826 "vim_info": str(net
),
1828 except CloudError
as e
:
1829 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1831 "Not found subnet net_name: %s, subnet_name: %s",
1835 out_nets
[net_id
] = {"status": "DELETED", "error_msg": str(e
)}
1838 "CloudError Exception %s when searching subnet", e
1840 out_nets
[net_id
] = {
1841 "status": "VIM_ERROR",
1842 "error_msg": str(e
),
1844 except vimconn
.VimConnNotFoundException
as e
:
1846 "VimConnNotFoundException %s when searching subnet", e
1848 out_nets
[net_id
] = {
1849 "status": "DELETED",
1850 "error_msg": str(e
),
1852 except Exception as e
:
1854 "Exception %s when searching subnet", e
, exc_info
=True
1856 out_nets
[net_id
] = {
1857 "status": "VIM_ERROR",
1858 "error_msg": str(e
),
1863 def refresh_vms_status(self
, vm_list
):
1864 """Get the status of the virtual machines and their interfaces/ports
1865 Params: the list of VM identifiers
1866 Returns a dictionary with:
1867 vm_id: # VIM id of this Virtual Machine
1868 status: # Mandatory. Text with one of:
1869 # DELETED (not found at vim)
1870 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1871 # OTHER (Vim reported other status not understood)
1872 # ERROR (VIM indicates an ERROR status)
1873 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1874 # BUILD (on building process), ERROR
1875 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1876 # (ACTIVE:NoMgmtIP is not returned for Azure)
1878 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1879 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1880 interfaces: list with interface info. Each item a dictionary with:
1881 vim_interface_id - The ID of the interface
1882 mac_address - The MAC address of the interface.
1883 ip_address - The IP address of the interface within the subnet.
1886 self
._reload
_connection
()
1888 self
.logger
.debug("refresh vm status vm_list: %s", vm_list
)
1889 search_vm_list
= vm_list
or {}
1891 for vm_id
in search_vm_list
:
1894 res_name
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1896 vm
= self
.conn_compute
.virtual_machines
.get(
1897 self
.resource_group
, res_name
1899 out_vm
["vim_info"] = str(vm
)
1900 out_vm
["status"] = self
.provision_state2osm
.get(
1901 vm
.provisioning_state
, "OTHER"
1904 if vm
.provisioning_state
== "Succeeded":
1905 # check if machine is running or stopped
1906 instance_view
= self
.conn_compute
.virtual_machines
.instance_view(
1907 self
.resource_group
, res_name
1910 for status
in instance_view
.statuses
:
1911 splitted_status
= status
.code
.split("/")
1913 len(splitted_status
) == 2
1914 and splitted_status
[0] == "PowerState"
1916 out_vm
["status"] = self
.power_state2osm
.get(
1917 splitted_status
[1], "OTHER"
1920 network_interfaces
= vm
.network_profile
.network_interfaces
1921 out_vm
["interfaces"] = self
._get
_vm
_interfaces
_status
(
1922 vm_id
, network_interfaces
1925 except CloudError
as e
:
1926 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1927 self
.logger
.debug("Not found vm id: %s", vm_id
)
1928 out_vm
["status"] = "DELETED"
1929 out_vm
["error_msg"] = str(e
)
1930 out_vm
["vim_info"] = None
1932 # maybe connection error or another type of error, return vim error
1933 self
.logger
.error("Exception %s refreshing vm_status", e
)
1934 out_vm
["status"] = "VIM_ERROR"
1935 out_vm
["error_msg"] = str(e
)
1936 out_vm
["vim_info"] = None
1937 except Exception as e
:
1938 self
.logger
.error("Exception %s refreshing vm_status", e
, exc_info
=True)
1939 out_vm
["status"] = "VIM_ERROR"
1940 out_vm
["error_msg"] = str(e
)
1941 out_vm
["vim_info"] = None
1943 out_vms
[vm_id
] = out_vm
1947 def _get_vm_interfaces_status(self
, vm_id
, interfaces
):
1949 Gets the interfaces detail for a vm
1950 :param interfaces: List of interfaces.
1951 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1955 for network_interface
in interfaces
:
1957 nic_name
= self
._get
_resource
_name
_from
_resource
_id
(
1958 network_interface
.id
1960 interface_dict
["vim_interface_id"] = network_interface
.id
1962 nic_data
= self
.conn_vnet
.network_interfaces
.get(
1963 self
.resource_group
,
1968 if nic_data
.ip_configurations
[0].public_ip_address
:
1969 self
.logger
.debug("Obtain public ip address")
1970 public_ip_name
= self
._get
_resource
_name
_from
_resource
_id
(
1971 nic_data
.ip_configurations
[0].public_ip_address
.id
1973 public_ip
= self
.conn_vnet
.public_ip_addresses
.get(
1974 self
.resource_group
, public_ip_name
1976 self
.logger
.debug("Public ip address is: %s", public_ip
.ip_address
)
1977 ips
.append(public_ip
.ip_address
)
1979 private_ip
= nic_data
.ip_configurations
[0].private_ip_address
1980 ips
.append(private_ip
)
1982 interface_dict
["mac_address"] = nic_data
.mac_address
1983 interface_dict
["ip_address"] = ";".join(ips
)
1984 interface_list
.append(interface_dict
)
1986 return interface_list
1987 except Exception as e
:
1989 "Exception %s obtaining interface data for vm: %s",
1994 self
._format
_vimconn
_exception
(e
)
1996 def _get_default_admin_user(self
, image_id
):
1997 if "ubuntu" in image_id
.lower():
2000 return self
._default
_admin
_user
2003 if __name__
== "__main__":
2005 log_format
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
2006 log_formatter
= logging
.Formatter(log_format
, datefmt
="%Y-%m-%dT%H:%M:%S")
2007 handler
= logging
.StreamHandler()
2008 handler
.setFormatter(log_formatter
)
2009 logger
= logging
.getLogger("ro.vim.azure")
2010 # logger.setLevel(level=logging.ERROR)
2011 # logger.setLevel(level=logging.INFO)
2012 logger
.setLevel(level
=logging
.DEBUG
)
2013 logger
.addHandler(handler
)
2015 # Making some basic test
2018 needed_test_params
= {
2019 "client_id": "AZURE_CLIENT_ID",
2020 "secret": "AZURE_SECRET",
2021 "tenant": "AZURE_TENANT",
2022 "resource_group": "AZURE_RESOURCE_GROUP",
2023 "subscription_id": "AZURE_SUBSCRIPTION_ID",
2024 "vnet_name": "AZURE_VNET_NAME",
2028 for param
, env_var
in needed_test_params
.items():
2029 value
= getenv(env_var
)
2032 raise Exception("Provide a valid value for env '{}'".format(env_var
))
2034 test_params
[param
] = value
2037 "region_name": getenv("AZURE_REGION_NAME", "northeurope"),
2038 "resource_group": getenv("AZURE_RESOURCE_GROUP"),
2039 "subscription_id": getenv("AZURE_SUBSCRIPTION_ID"),
2040 "pub_key": getenv("AZURE_PUB_KEY", None),
2041 "vnet_name": getenv("AZURE_VNET_NAME", "osm_vnet"),
2044 azure
= vimconnector(
2047 tenant_id
=test_params
["tenant"],
2051 user
=test_params
["client_id"],
2052 passwd
=test_params
["secret"],
2058 logger.debug("List images")
2059 image = azure.get_image_list({"name": "Canonical:UbuntuServer:18.04-LTS:18.04.201809110"})
2060 logger.debug("image: {}".format(image))
2062 logger.debug("List networks")
2063 network_list = azure.get_network_list({"name": "internal"})
2064 logger.debug("Network_list: {}".format(network_list))
2066 logger.debug("List flavors")
2067 flavors = azure.get_flavor_id_from_data({"vcpus": 2})
2068 logger.debug("flavors: {}".format(flavors))
2072 # Create network and test machine
2073 #new_network_id, _ = azure.new_network("testnet1", "data")
2074 new_network_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers")
2075 "/Microsoft.Network/virtualNetworks/osm_vnet/subnets/testnet1"
2076 ).format(test_params["resource_group"])
2077 logger.debug("new_network_id: {}".format(new_network_id))
2079 logger.debug("Delete network")
2080 new_network_id = azure.delete_network(new_network_id)
2081 logger.debug("deleted network_id: {}".format(new_network_id))
2085 logger.debug("List networks")
2086 network_list = azure.get_network_list({"name": "internal"})
2087 logger.debug("Network_list: {}".format(network_list))
2089 logger.debug("Show machine isabelvm")
2090 vmachine = azure.get_vminstance( ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}"
2091 "/providers/Microsoft.Compute/virtualMachines/isabelVM"
2092 ).format(test_params["resource_group"])
2094 logger.debug("Vmachine: {}".format(vmachine))
2098 logger.debug("List images")
2099 image = azure.get_image_list({"name": "Canonical:UbuntuServer:16.04"})
2100 # image = azure.get_image_list({"name": "Canonical:UbuntuServer:18.04-LTS"})
2101 logger.debug("image: {}".format(image))
2105 # Create network and test machine
2106 new_network_id, _ = azure.new_network("testnet1", "data")
2107 image_id = ("/Subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/Providers/Microsoft.Compute"
2108 "/Locations/northeurope/Publishers/Canonical/ArtifactTypes/VMImage/Offers/UbuntuServer"
2109 "/Skus/18.04-LTS/Versions/18.04.201809110")
2113 network_id = ("subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}
2114 "/providers/Microsoft.Network/virtualNetworks/osm_vnet/subnets/internal"
2115 ).format(test_params["resource_group"])
2119 logger.debug("Create machine")
2120 image_id = ("/Subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/Providers/Microsoft.Compute/Locations"
2121 "/northeurope/Publishers/Canonical/ArtifactTypes/VMImage/Offers/UbuntuServer/Skus/18.04-LTS"
2122 "/Versions/18.04.202103151")
2123 cloud_config = {"user-data": (
2126 "chpasswd: { expire: False }\n"
2127 "ssh_pwauth: True\n\n"
2130 " # My new helloworld file\n\n"
2131 " owner: root:root\n"
2132 " permissions: '0644'\n"
2133 " path: /root/helloworld.txt",
2135 ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/p7fuw/W0+6uhx9XNPY4dN/K2cXZweDfjJN8W/sQ1AhKvn"
2136 "j0MF+dbBdsd2tfq6XUhx5LiKoGTunRpRonOw249ivH7pSyNN7FYpdLaij7Krn3K+QRNEOahMI4eoqdglVftA3"
2137 "vlw4Oe/aZOU9BXPdRLxfr9hRKzg5zkK91/LBkEViAijpCwK6ODPZLDDUwY4iihYK9R5eZ3fmM4+3k3Jd0hPRk"
2138 "B5YbtDQOu8ASWRZ9iTAWqr1OwQmvNc6ohSVg1tbq3wSxj/5bbz0J24A7TTpY0giWctne8Qkl/F2e0ZSErvbBB"
2139 "GXKxfnq7sc23OK1hPxMAuS+ufzyXsnL1+fB4t2iF azureuser@osm-test-client\n"
2142 network_id = ("subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers"
2143 "/Microsoft.Network/virtualNetworks/osm_vnet/subnets/internal"
2144 ).format(test_params["resource_group"])
2145 vm = azure.new_vminstance(name="isabelvm",
2146 description="testvm",
2149 flavor_id="Standard_B1ls",
2150 net_list = [{"net_id": network_id, "name": "internal", "use": "mgmt", "floating_ip":True}],
2151 cloud_config = cloud_config)
2152 logger.debug("vm: {}".format(vm))
2156 # Delete nonexistent vm
2158 logger.debug("Delete machine")
2159 vm_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Compute/"
2160 "virtualMachines/isabelvm"
2161 ).format(test_params["resource_group"])
2163 ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2164 "/networkInterfaces/isabelvm-nic-0"
2165 ).format(test_params["resource_group"]): True,
2166 ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2167 "/publicIPAddresses/isabelvm-nic-0-public-ip"
2168 ).format(test_params["resource_group"]): True
2170 azure.delete_vminstance(vm_id, created_items)
2171 except vimconn.VimConnNotFoundException as e:
2172 print("Ok: excepcion no encontrada")
2176 network_id = ("/subscriptions/5c1a2458-dfde-4adf-a4e3-08fa0e21d171/resourceGroups/{}/providers/Microsoft.Network"
2177 "/virtualNetworks/osm_vnet/subnets/hfcloudinit-internal-1"
2178 ).format(test_params["resource_group"])
2179 azure.delete_network(network_id)