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
.network
import NetworkManagementClient
25 from azure
.mgmt
.resource
import ResourceManagementClient
26 from azure
.profiles
import ProfileDefinition
27 from cryptography
.hazmat
.backends
import default_backend
as crypto_default_backend
28 from cryptography
.hazmat
.primitives
import serialization
as crypto_serialization
29 from cryptography
.hazmat
.primitives
.asymmetric
import rsa
30 from msrest
.exceptions
import AuthenticationError
31 from msrestazure
.azure_exceptions
import CloudError
32 import msrestazure
.tools
as azure_tools
34 from osm_ro_plugin
import vimconn
35 from requests
.exceptions
import ConnectionError
37 __author__
= "Isabel Lloret, Sergio Gonzalez, Alfonso Tierno, Gerardo Garcia"
38 __date__
= "$18-apr-2019 23:59:59$"
41 if getenv("OSMRO_PDB_DEBUG"):
50 def find_in_list(the_list
, condition_lambda
):
52 if condition_lambda(item
):
58 class vimconnector(vimconn
.VimConnector
):
59 # Translate azure provisioning state to OSM provision state
60 # The first three ones are the transitional status once a user initiated action has been requested
61 # Once the operation is complete, it will transition into the states Succeeded or Failed
62 # https://docs.microsoft.com/en-us/azure/virtual-machines/windows/states-lifecycle
63 provision_state2osm
= {
66 "Deleting": "INACTIVE",
67 "Succeeded": "ACTIVE",
71 # Translate azure power state to OSM provision state
73 "starting": "INACTIVE",
75 "stopping": "INACTIVE",
76 "stopped": "INACTIVE",
78 "deallocated": "BUILD",
79 "deallocating": "BUILD",
82 # TODO - review availability zones
83 AZURE_ZONES
= ["1", "2", "3"]
85 AZURE_COMPUTE_MGMT_CLIENT_API_VERSION
= "2021-03-01"
86 AZURE_COMPUTE_MGMT_PROFILE_TAG
= "azure.mgmt.compute.ComputeManagementClient"
87 AZURE_COMPUTE_MGMT_PROFILE
= ProfileDefinition(
89 AZURE_COMPUTE_MGMT_PROFILE_TAG
: {
90 None: AZURE_COMPUTE_MGMT_CLIENT_API_VERSION
,
91 "availability_sets": "2020-12-01",
92 "dedicated_host_groups": "2020-12-01",
93 "dedicated_hosts": "2020-12-01",
94 "disk_accesses": "2020-12-01",
95 "disk_encryption_sets": "2020-12-01",
96 "disk_restore_point": "2020-12-01",
97 "disks": "2020-12-01",
98 "galleries": "2020-09-30",
99 "gallery_application_versions": "2020-09-30",
100 "gallery_applications": "2020-09-30",
101 "gallery_image_versions": "2020-09-30",
102 "gallery_images": "2020-09-30",
103 "gallery_sharing_profile": "2020-09-30",
104 "images": "2020-12-01",
105 "log_analytics": "2020-12-01",
106 "operations": "2020-12-01",
107 "proximity_placement_groups": "2020-12-01",
108 "resource_skus": "2019-04-01",
109 "shared_galleries": "2020-09-30",
110 "shared_gallery_image_versions": "2020-09-30",
111 "shared_gallery_images": "2020-09-30",
112 "snapshots": "2020-12-01",
113 "ssh_public_keys": "2020-12-01",
114 "usage": "2020-12-01",
115 "virtual_machine_extension_images": "2020-12-01",
116 "virtual_machine_extensions": "2020-12-01",
117 "virtual_machine_images": "2020-12-01",
118 "virtual_machine_images_edge_zone": "2020-12-01",
119 "virtual_machine_run_commands": "2020-12-01",
120 "virtual_machine_scale_set_extensions": "2020-12-01",
121 "virtual_machine_scale_set_rolling_upgrades": "2020-12-01",
122 "virtual_machine_scale_set_vm_extensions": "2020-12-01",
123 "virtual_machine_scale_set_vm_run_commands": "2020-12-01",
124 "virtual_machine_scale_set_vms": "2020-12-01",
125 "virtual_machine_scale_sets": "2020-12-01",
126 "virtual_machine_sizes": "2020-12-01",
127 "virtual_machines": "2020-12-01",
130 AZURE_COMPUTE_MGMT_PROFILE_TAG
+ " osm",
133 AZURE_RESOURCE_MGMT_CLIENT_API_VERSION
= "2020-10-01"
134 AZURE_RESOURCE_MGMT_PROFILE_TAG
= (
135 "azure.mgmt.resource.resources.ResourceManagementClient"
137 AZURE_RESOURCE_MGMT_PROFILE
= ProfileDefinition(
139 AZURE_RESOURCE_MGMT_PROFILE_TAG
: {
140 None: AZURE_RESOURCE_MGMT_CLIENT_API_VERSION
,
143 AZURE_RESOURCE_MGMT_PROFILE_TAG
+ " osm",
146 AZURE_NETWORK_MGMT_CLIENT_API_VERSION
= "2020-11-01"
147 AZURE_NETWORK_MGMT_PROFILE_TAG
= "azure.mgmt.network.NetworkManagementClient"
148 AZURE_NETWORK_MGMT_PROFILE
= ProfileDefinition(
150 AZURE_NETWORK_MGMT_PROFILE_TAG
: {
151 None: AZURE_NETWORK_MGMT_CLIENT_API_VERSION
,
152 "firewall_policy_rule_groups": "2020-04-01",
153 "interface_endpoints": "2019-02-01",
154 "p2_svpn_server_configurations": "2019-07-01",
157 AZURE_NETWORK_MGMT_PROFILE_TAG
+ " osm",
175 Constructor of VIM. Raise an exception is some needed parameter is missing, but it must not do any connectivity
176 checking against the VIM
177 Using common constructor parameters.
178 In this case: config must include the following parameters:
179 subscription_id: assigned azure subscription identifier
180 region_name: current region for azure network
181 resource_group: used for all azure created resources
182 vnet_name: base vnet for azure, created networks will be subnets from this base network
183 config may also include the following parameter:
184 flavors_pattern: pattern that will be used to select a range of vm sizes, for example
185 "^((?!Standard_B).)*$" will filter out Standard_B range that is cheap but is very overused
186 "^Standard_B" will select a serie B maybe for test environment
188 vimconn
.VimConnector
.__init
__(
203 # Variable that indicates if client must be reloaded or initialized
204 self
.reload_client
= True
206 self
.vnet_address_space
= []
209 self
.logger
= logging
.getLogger("ro.vim.azure")
211 self
.logger
.setLevel(getattr(logging
, log_level
))
213 self
.tenant
= tenant_id
or tenant_name
215 # Store config to create azure subscription later
219 "tenant": tenant_id
or tenant_name
,
223 if "subscription_id" in config
:
224 self
._config
["subscription_id"] = config
.get("subscription_id")
225 # self.logger.debug("Setting subscription to: %s", self.config["subscription_id"])
227 raise vimconn
.VimConnException("Subscription not specified")
230 if "resource_group" in config
:
231 self
.resource_group
= config
.get("resource_group")
233 raise vimconn
.VimConnException(
234 "Azure resource_group is not specified at config"
238 if "region_name" in config
:
239 self
.region
= config
.get("region_name")
241 raise vimconn
.VimConnException(
242 "Azure region_name is not specified at config"
246 if "vnet_name" in config
:
247 self
.vnet_name
= config
["vnet_name"]
249 # VNET_RESOURCE_GROUP
250 self
.vnet_resource_group
= config
.get("vnet_resource_group")
252 # TODO - not used, do anything about it?
254 self
.pub_key
= config
.get("pub_key")
256 # TODO - check default user for azure
258 self
._default
_admin
_user
= "azureuser"
260 # flavor pattern regex
261 if "flavors_pattern" in config
:
262 self
._config
["flavors_pattern"] = config
["flavors_pattern"]
264 def _find_in_capabilities(self
, capabilities
, name
):
265 cap
= find_in_list(capabilities
, lambda c
: c
["name"] == name
)
267 return cap
.get("value")
271 def _reload_connection(self
):
273 Called before any operation, checks python azure clients
275 if self
.reload_client
:
276 self
.logger
.debug("reloading azure client")
279 self
.credentials
= ClientSecretCredential(
280 client_id
=self
._config
["user"],
281 client_secret
=self
._config
["passwd"],
282 tenant_id
=self
._config
["tenant"],
284 self
.conn
= ResourceManagementClient(
286 self
._config
["subscription_id"],
287 profile
=self
.AZURE_RESOURCE_MGMT_PROFILE
,
289 self
.conn_compute
= ComputeManagementClient(
291 self
._config
["subscription_id"],
292 profile
=self
.AZURE_COMPUTE_MGMT_PROFILE
,
294 self
.conn_vnet
= NetworkManagementClient(
296 self
._config
["subscription_id"],
297 profile
=self
.AZURE_NETWORK_MGMT_PROFILE
,
299 self
._check
_or
_create
_resource
_group
()
300 self
._check
_or
_create
_vnet
()
302 # Set to client created
303 self
.reload_client
= False
304 except Exception as e
:
305 self
._format
_vimconn
_exception
(e
)
307 def _get_resource_name_from_resource_id(self
, resource_id
):
309 Obtains resource_name from the azure complete identifier: resource_name will always be last item
312 resource
= str(resource_id
.split("/")[-1])
315 except Exception as e
:
316 raise vimconn
.VimConnException(
317 "Unable to get resource name from resource_id '{}' Error: '{}'".format(
322 def _get_location_from_resource_group(self
, resource_group_name
):
324 location
= self
.conn
.resource_groups
.get(resource_group_name
).location
328 raise vimconn
.VimConnNotFoundException(
329 "Location '{}' not found".format(resource_group_name
)
332 def _get_resource_group_name_from_resource_id(self
, resource_id
):
334 rg
= str(resource_id
.split("/")[4])
338 raise vimconn
.VimConnException(
339 "Unable to get resource group from invalid resource_id format '{}'".format(
344 def _get_net_name_from_resource_id(self
, resource_id
):
346 net_name
= str(resource_id
.split("/")[8])
350 raise vimconn
.VimConnException(
351 "Unable to get azure net_name from invalid resource_id format '{}'".format(
356 def _check_subnets_for_vm(self
, net_list
):
357 # All subnets must belong to the same resource group and vnet
358 # All subnets must belong to the same resource group anded vnet
360 self
._get
_resource
_group
_name
_from
_resource
_id
(net
["net_id"])
361 + self
._get
_net
_name
_from
_resource
_id
(net
["net_id"])
365 if len(rg_vnet
) != 1:
366 raise self
._format
_vimconn
_exception
(
367 "Azure VMs can only attach to subnets in same VNET"
370 def _format_vimconn_exception(self
, e
):
372 Transforms a generic or azure exception to a vimcommException
374 self
.logger
.error("Azure plugin error: {}".format(e
))
375 if isinstance(e
, vimconn
.VimConnException
):
377 elif isinstance(e
, AuthenticationError
):
378 raise vimconn
.VimConnAuthException(type(e
).__name
__ + ": " + str(e
))
379 elif isinstance(e
, ConnectionError
):
380 raise vimconn
.VimConnConnectionException(type(e
).__name
__ + ": " + str(e
))
382 # In case of generic error recreate client
383 self
.reload_client
= True
385 raise vimconn
.VimConnException(type(e
).__name
__ + ": " + str(e
))
387 def _check_or_create_resource_group(self
):
389 Creates the base resource group if it does not exist
392 rg_exists
= self
.conn
.resource_groups
.check_existence(self
.resource_group
)
395 self
.logger
.debug("create base rgroup: %s", self
.resource_group
)
396 self
.conn
.resource_groups
.create_or_update(
397 self
.resource_group
, {"location": self
.region
}
399 except Exception as e
:
400 self
._format
_vimconn
_exception
(e
)
402 def _check_or_create_vnet(self
):
404 Try to get existent base vnet, in case it does not exist it creates it
407 vnet
= self
.conn_vnet
.virtual_networks
.get(
408 self
.vnet_resource_group
or self
.resource_group
, self
.vnet_name
410 self
.vnet_address_space
= vnet
.address_space
.address_prefixes
411 self
.vnet_id
= vnet
.id
414 except CloudError
as e
:
415 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
416 self
.logger
.exception("CloudError Exception occured.")
417 # continue and create it
419 self
._format
_vimconn
_exception
(e
)
421 # if it does not exist, create it
424 "location": self
.region
,
425 "address_space": {"address_prefixes": ["10.0.0.0/8"]},
427 self
.vnet_address_space
= ["10.0.0.0/8"]
429 self
.logger
.debug("create base vnet: %s", self
.vnet_name
)
430 self
.conn_vnet
.virtual_networks
.begin_create_or_update(
431 self
.vnet_resource_group
or self
.resource_group
,
435 vnet
= self
.conn_vnet
.virtual_networks
.get(
436 self
.vnet_resource_group
or self
.resource_group
, self
.vnet_name
438 self
.vnet_id
= vnet
.id
439 except Exception as e
:
440 self
._format
_vimconn
_exception
(e
)
448 provider_network_profile
=None,
451 Adds a tenant network to VIM
452 :param net_name: name of the network
453 :param net_type: not used for azure networks
454 :param ip_profile: is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
455 'ip-version': can be one of ['IPv4','IPv6']
456 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
457 'gateway-address': (Optional) ip_schema, that is X.X.X.X, not implemented for azure connector
458 'dns-address': (Optional) ip_schema, not implemented for azure connector
459 'dhcp': (Optional) dict containing, not implemented for azure connector
460 'enabled': {'type': 'boolean'},
461 'start-address': ip_schema, first IP to grant
462 'count': number of IPs to grant.
463 :param shared: Not allowed for Azure Connector
464 :param provider_network_profile: (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk}
465 :return: a tuple with the network identifier and created_items, or raises an exception on error
466 created_items can be None or a dictionary where this method can include key-values that will be passed to
467 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
468 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
471 return self
._new
_subnet
(net_name
, ip_profile
)
473 def _new_subnet(self
, net_name
, ip_profile
):
475 Adds a tenant network to VIM. It creates a new subnet at existing base vnet
476 :param net_name: subnet name
478 subnet-address: if it is not provided a subnet/24 in the default vnet is created,
479 otherwise it creates a subnet in the indicated address
480 :return: a tuple with the network identifier and created_items, or raises an exception on error
482 self
.logger
.debug("create subnet name %s, ip_profile %s", net_name
, ip_profile
)
483 self
._reload
_connection
()
485 if ip_profile
is None:
486 # get a non used vnet ip range /24 and allocate automatically inside the range self.vnet_address_space
487 used_subnets
= self
.get_network_list()
488 for space
in self
.vnet_address_space
:
489 for ip_range
in netaddr
.IPNetwork(space
).subnet(24):
490 for used_subnet
in used_subnets
:
491 subnet_range
= netaddr
.IPNetwork(used_subnet
["cidr_block"])
493 if subnet_range
in ip_range
or ip_range
in subnet_range
:
494 # this range overlaps with an existing subnet ip range. Breaks and look for another
497 ip_profile
= {"subnet_address": str(ip_range
)}
499 "dinamically obtained ip_profile: %s", ip_range
502 if ip_profile
is not None:
505 raise vimconn
.VimConnException(
506 "Cannot find a non-used subnet range in {}".format(
507 self
.vnet_address_space
511 ip_profile
= {"subnet_address": ip_profile
["subnet_address"]}
514 # subnet_name = "{}-{}".format(net_name[:24], uuid4())
515 subnet_params
= {"address_prefix": ip_profile
["subnet_address"]}
516 # Assign a not duplicated net name
517 subnet_name
= self
._get
_unused
_subnet
_name
(net_name
)
519 self
.logger
.debug("creating subnet_name: {}".format(subnet_name
))
520 async_creation
= self
.conn_vnet
.subnets
.begin_create_or_update(
521 self
.vnet_resource_group
or self
.resource_group
,
526 async_creation
.wait()
527 # TODO - do not wait here, check where it is used
528 self
.logger
.debug("created subnet_name: {}".format(subnet_name
))
530 return "{}/subnets/{}".format(self
.vnet_id
, subnet_name
), None
531 except Exception as e
:
532 self
._format
_vimconn
_exception
(e
)
534 def _get_unused_subnet_name(self
, subnet_name
):
536 Adds a prefix to the subnet_name with a number in case the indicated name is repeated
537 Checks subnets with the indicated name (without suffix) and adds a suffix with a number
539 all_subnets
= self
.conn_vnet
.subnets
.list(
540 self
.vnet_resource_group
or self
.resource_group
, self
.vnet_name
542 # Filter to subnets starting with the indicated name
544 filter(lambda subnet
: (subnet
.name
.startswith(subnet_name
)), all_subnets
)
546 net_names
= [str(subnet
.name
) for subnet
in subnets
]
548 # get the name with the first not used suffix
550 # name = subnet_name + "-" + str(name_suffix)
551 name
= subnet_name
# first subnet created will have no prefix
552 while name
in net_names
:
554 name
= subnet_name
+ "-" + str(name_suffix
)
558 def _create_nic(self
, net
, nic_name
, region
=None, static_ip
=None, created_items
={}):
559 self
.logger
.debug("create nic name %s, net_name %s", nic_name
, net
)
560 self
._reload
_connection
()
562 subnet_id
= net
["net_id"]
563 location
= self
.region
or self
._get
_location
_from
_resource
_group
(
568 net_ifz
= {"location": location
}
570 "name": nic_name
+ "-ipconfiguration",
571 "subnet": {"id": subnet_id
},
575 net_ip_config
["privateIPAddress"] = static_ip
576 net_ip_config
["privateIPAllocationMethod"] = "Static"
578 net_ifz
["ip_configurations"] = [net_ip_config
]
579 mac_address
= net
.get("mac_address")
582 net_ifz
["mac_address"] = mac_address
584 async_nic_creation
= (
585 self
.conn_vnet
.network_interfaces
.begin_create_or_update(
586 self
.resource_group
, nic_name
, net_ifz
589 nic_data
= async_nic_creation
.result()
590 created_items
[nic_data
.id] = True
591 self
.logger
.debug("created nic name %s", nic_name
)
593 public_ip
= net
.get("floating_ip")
595 public_ip_address_params
= {
596 "location": location
,
597 "public_ip_allocation_method": "Dynamic",
599 public_ip_name
= nic_name
+ "-public-ip"
601 self
.conn_vnet
.public_ip_addresses
.begin_create_or_update(
602 self
.resource_group
, public_ip_name
, public_ip_address_params
605 public_ip
= async_public_ip
.result()
606 self
.logger
.debug("created public IP: {}".format(public_ip
))
608 # Associate NIC to Public IP
609 nic_data
= self
.conn_vnet
.network_interfaces
.get(
610 self
.resource_group
, nic_name
613 nic_data
.ip_configurations
[0].public_ip_address
= public_ip
614 created_items
[public_ip
.id] = True
616 self
.conn_vnet
.network_interfaces
.begin_create_or_update(
617 self
.resource_group
, nic_name
, nic_data
620 except Exception as e
:
621 self
._format
_vimconn
_exception
(e
)
623 return nic_data
, created_items
625 def new_flavor(self
, flavor_data
):
627 It is not allowed to create new flavors in Azure, must always use an existing one
629 raise vimconn
.VimConnAuthException(
630 "It is not possible to create new flavors in AZURE"
633 def new_tenant(self
, tenant_name
, tenant_description
):
635 It is not allowed to create new tenants in azure
637 raise vimconn
.VimConnAuthException(
638 "It is not possible to create a TENANT in AZURE"
641 def new_image(self
, image_dict
):
643 It is not allowed to create new images in Azure, must always use an existing one
645 raise vimconn
.VimConnAuthException(
646 "It is not possible to create new images in AZURE"
649 def get_image_id_from_path(self
, path
):
650 """Get the image id from image path in the VIM database.
651 Returns the image_id or raises a vimconnNotFoundException
653 raise vimconn
.VimConnAuthException(
654 "It is not possible to obtain image from path in AZURE"
657 def get_image_list(self
, filter_dict
={}):
658 """Obtain tenant images from VIM
660 name: image name with the format: publisher:offer:sku:version
661 If some part of the name is provide ex: publisher:offer it will search all availables skus and version
662 for the provided publisher and offer
663 id: image uuid, currently not supported for azure
664 Returns the image list of dictionaries:
665 [{<the fields at Filter_dict plus some VIM specific>}, ...]
668 self
.logger
.debug("get_image_list filter {}".format(filter_dict
))
670 self
._reload
_connection
()
673 if filter_dict
.get("name"):
674 # name will have the format "publisher:offer:sku:version"
675 # publisher is required, offer sku and version will be searched if not provided
676 params
= filter_dict
["name"].split(":")
677 publisher
= params
[0]
680 offer_list
= self
._get
_offer
_list
(params
, publisher
)
682 for offer
in offer_list
:
684 sku_list
= self
._get
_sku
_list
(params
, publisher
, offer
)
687 # if version is defined get directly version, else list images
688 if len(params
) == 4 and params
[3]:
690 if version
== "latest":
691 image_list
= self
._get
_sku
_image
_list
(
692 publisher
, offer
, sku
694 image_list
= [image_list
[-1]]
696 image_list
= self
._get
_version
_image
_list
(
697 publisher
, offer
, sku
, version
700 image_list
= self
._get
_sku
_image
_list
(
701 publisher
, offer
, sku
704 raise vimconn
.VimConnAuthException(
705 "List images in Azure must include name param with at least publisher"
708 raise vimconn
.VimConnAuthException(
709 "List images in Azure must include name param with at"
714 except Exception as e
:
715 self
._format
_vimconn
_exception
(e
)
717 def _get_offer_list(self
, params
, publisher
):
719 Helper method to obtain offer list for defined publisher
721 if len(params
) >= 2 and params
[1]:
725 # get list of offers from azure
726 result_offers
= self
.conn_compute
.virtual_machine_images
.list_offers(
727 self
.region
, publisher
730 return [offer
.name
for offer
in result_offers
]
731 except CloudError
as e
:
732 # azure raises CloudError when not found
734 "error listing offers for publisher {}, Error: {}".format(
741 def _get_sku_list(self
, params
, publisher
, offer
):
743 Helper method to obtain sku list for defined publisher and offer
745 if len(params
) >= 3 and params
[2]:
749 # get list of skus from azure
750 result_skus
= self
.conn_compute
.virtual_machine_images
.list_skus(
751 self
.region
, publisher
, offer
754 return [sku
.name
for sku
in result_skus
]
755 except CloudError
as e
:
756 # azure raises CloudError when not found
758 "error listing skus for publisher {}, offer {}, Error: {}".format(
765 def _get_sku_image_list(self
, publisher
, offer
, sku
):
767 Helper method to obtain image list for publisher, offer and sku
771 result_images
= self
.conn_compute
.virtual_machine_images
.list(
772 self
.region
, publisher
, offer
, sku
774 for result_image
in result_images
:
777 "id": str(result_image
.id),
778 "name": ":".join([publisher
, offer
, sku
, result_image
.name
]),
781 except CloudError
as e
:
783 "error listing skus for publisher {}, offer {}, Error: {}".format(
791 def _get_version_image_list(self
, publisher
, offer
, sku
, version
):
794 result_image
= self
.conn_compute
.virtual_machine_images
.get(
795 self
.region
, publisher
, offer
, sku
, version
801 "id": str(result_image
.id),
802 "name": ":".join([publisher
, offer
, sku
, version
]),
805 except CloudError
as e
:
806 # azure gives CloudError when not found
808 "error listing images for publisher {}, offer {}, sku {}, version {} Error: {}".format(
809 publisher
, offer
, sku
, version
, e
816 def get_network_list(self
, filter_dict
={}):
817 """Obtain tenant networks of VIM
821 shared: boolean, not implemented in Azure
822 tenant_id: tenant, not used in Azure, all networks same tenants
823 admin_state_up: boolean, not implemented in Azure
824 status: 'ACTIVE', not implemented in Azure #
825 Returns the network list of dictionaries
827 # self.logger.debug("getting network list for vim, filter %s", filter_dict)
829 self
._reload
_connection
()
831 vnet
= self
.conn_vnet
.virtual_networks
.get(
832 self
.vnet_resource_group
or self
.resource_group
, self
.vnet_name
836 for subnet
in vnet
.subnets
:
838 if filter_dict
.get("id") and str(subnet
.id) != filter_dict
["id"]:
842 filter_dict
.get("name")
843 and str(subnet
.name
) != filter_dict
["name"]
847 name
= self
._get
_resource
_name
_from
_resource
_id
(subnet
.id)
851 "id": str(subnet
.id),
853 "status": self
.provision_state2osm
[subnet
.provisioning_state
],
854 "cidr_block": str(subnet
.address_prefix
),
861 except Exception as e
:
862 self
._format
_vimconn
_exception
(e
)
875 availability_zone_index
=None,
876 availability_zone_list
=None,
879 "new vm instance name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, "
880 "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s",
887 availability_zone_index
,
888 availability_zone_list
,
890 self
._reload
_connection
()
892 # Validate input data is valid
893 # The virtual machine name must have less or 64 characters and it can not have the following
894 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
895 vm_name
= self
._check
_vm
_name
(name
)
896 # Obtain vm unused name
897 vm_name
= self
._get
_unused
_vm
_name
(vm_name
)
899 # At least one network must be provided
901 raise vimconn
.VimConnException(
902 "At least one net must be provided to create a new VM"
905 # image_id are several fields of the image_id
906 image_reference
= self
._get
_image
_reference
(image_id
)
909 virtual_machine
= None
912 # Create nics for each subnet
913 self
._check
_subnets
_for
_vm
(net_list
)
916 for idx
, net
in enumerate(net_list
):
917 # Fault with subnet_id
918 # subnet_id=net["subnet_id"]
919 # subnet_id=net["net_id"]
920 nic_name
= vm_name
+ "-nic-" + str(idx
)
921 vm_nic
, nic_items
= self
._create
_nic
(
922 net
, nic_name
, self
.region
, net
.get("ip_address"), created_items
924 vm_nics
.append({"id": str(vm_nic
.id)})
925 net
["vim_id"] = vm_nic
.id
928 "location": self
.region
,
929 "os_profile": self
._build
_os
_profile
(vm_name
, cloud_config
, image_id
),
930 "hardware_profile": {"vm_size": flavor_id
},
931 "storage_profile": {"image_reference": image_reference
},
934 # If the machine has several networks one must be marked as primary
935 # As it is not indicated in the interface the first interface will be marked as primary
937 for idx
, vm_nic
in enumerate(vm_nics
):
939 vm_nics
[0]["Primary"] = True
941 vm_nics
[idx
]["Primary"] = False
943 vm_parameters
["network_profile"] = {"network_interfaces": vm_nics
}
945 # Obtain zone information
946 vm_zone
= self
._get
_vm
_zone
(availability_zone_index
, availability_zone_list
)
948 vm_parameters
["zones"] = [vm_zone
]
950 self
.logger
.debug("create vm name: %s", vm_name
)
951 creation_result
= self
.conn_compute
.virtual_machines
.begin_create_or_update(
952 self
.resource_group
, vm_name
, vm_parameters
, polling
=False
954 self
.logger
.debug("obtained creation result: %s", creation_result
)
955 virtual_machine
= creation_result
.result()
956 self
.logger
.debug("created vm name: %s", vm_name
)
958 return virtual_machine
.id, created_items
960 except Exception as e
:
961 # Rollback vm creacion
965 vm_id
= virtual_machine
.id
968 self
.logger
.debug("exception creating vm try to rollback")
969 self
.delete_vminstance(vm_id
, created_items
)
970 except Exception as e2
:
971 self
.logger
.error("new_vminstance rollback fail {}".format(e2
))
973 self
.logger
.debug("Exception creating new vminstance: %s", e
, exc_info
=True)
974 self
._format
_vimconn
_exception
(e
)
976 def _build_os_profile(self
, vm_name
, cloud_config
, image_id
):
978 os_profile
= {"computer_name": vm_name
}
980 # for azure os_profile admin_username is required
981 if cloud_config
and cloud_config
.get("users"):
982 admin_username
= cloud_config
.get("users")[0].get(
983 "name", self
._get
_default
_admin
_user
(image_id
)
986 admin_username
= self
._get
_default
_admin
_user
(image_id
)
987 os_profile
["admin_username"] = admin_username
989 # if there is a cloud-init load it
991 _
, userdata
= self
._create
_user
_data
(cloud_config
)
992 custom_data
= base64
.b64encode(userdata
.encode("utf-8")).decode("latin-1")
993 os_profile
["custom_data"] = custom_data
995 # either password of ssh-keys are required
996 # we will always use ssh-keys, in case it is not available we will generate it
997 if cloud_config
and cloud_config
.get("key-pairs"):
998 key_data
= cloud_config
.get("key-pairs")[0]
1000 _
, key_data
= self
._generate
_keys
()
1002 os_profile
["linux_configuration"] = {
1006 "path": "/home/{}/.ssh/authorized_keys".format(admin_username
),
1007 "key_data": key_data
,
1015 def _generate_keys(self
):
1016 """Method used to generate a pair of private/public keys.
1017 This method is used because to create a vm in Azure we always need a key or a password
1018 In some cases we may have a password in a cloud-init file but it may not be available
1020 key
= rsa
.generate_private_key(
1021 backend
=crypto_default_backend(), public_exponent
=65537, key_size
=2048
1023 private_key
= key
.private_bytes(
1024 crypto_serialization
.Encoding
.PEM
,
1025 crypto_serialization
.PrivateFormat
.PKCS8
,
1026 crypto_serialization
.NoEncryption(),
1028 public_key
= key
.public_key().public_bytes(
1029 crypto_serialization
.Encoding
.OpenSSH
,
1030 crypto_serialization
.PublicFormat
.OpenSSH
,
1032 private_key
= private_key
.decode("utf8")
1033 # Change first line because Paramiko needs a explicit start with 'BEGIN RSA PRIVATE KEY'
1034 i
= private_key
.find("\n")
1035 private_key
= "-----BEGIN RSA PRIVATE KEY-----" + private_key
[i
:]
1036 public_key
= public_key
.decode("utf8")
1038 return private_key
, public_key
1040 def _get_unused_vm_name(self
, vm_name
):
1042 Checks the vm name and in case it is used adds a suffix to the name to allow creation
1045 all_vms
= self
.conn_compute
.virtual_machines
.list(self
.resource_group
)
1046 # Filter to vms starting with the indicated name
1047 vms
= list(filter(lambda vm
: (vm
.name
.startswith(vm_name
)), all_vms
))
1048 vm_names
= [str(vm
.name
) for vm
in vms
]
1050 # get the name with the first not used suffix
1052 # name = subnet_name + "-" + str(name_suffix)
1053 name
= vm_name
# first subnet created will have no prefix
1055 while name
in vm_names
:
1057 name
= vm_name
+ "-" + str(name_suffix
)
1061 def _get_vm_zone(self
, availability_zone_index
, availability_zone_list
):
1062 if availability_zone_index
is None:
1065 vim_availability_zones
= self
._get
_azure
_availability
_zones
()
1066 # check if VIM offer enough availability zones describe in the VNFD
1067 if vim_availability_zones
and len(availability_zone_list
) <= len(
1068 vim_availability_zones
1070 # check if all the names of NFV AV match VIM AV names
1071 match_by_index
= False
1073 if not availability_zone_list
:
1074 match_by_index
= True
1076 for av
in availability_zone_list
:
1077 if av
not in vim_availability_zones
:
1078 match_by_index
= True
1082 return vim_availability_zones
[availability_zone_index
]
1084 return availability_zone_list
[availability_zone_index
]
1086 raise vimconn
.VimConnConflictException(
1087 "No enough availability zones at VIM for this deployment"
1090 def _get_azure_availability_zones(self
):
1091 return self
.AZURE_ZONES
1093 def _get_image_reference(self
, image_id
):
1095 # The data input format example:
1096 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
1097 # Publishers/Canonical/ArtifactTypes/VMImage/
1098 # Offers/UbuntuServer/
1100 # Versions/18.04.201809110
1101 publisher
= str(image_id
.split("/")[8])
1102 offer
= str(image_id
.split("/")[12])
1103 sku
= str(image_id
.split("/")[14])
1104 version
= str(image_id
.split("/")[16])
1107 "publisher": publisher
,
1113 raise vimconn
.VimConnException(
1114 "Unable to get image_reference from invalid image_id format: '{}'".format(
1119 # Azure VM names can not have some special characters
1120 def _check_vm_name(self
, vm_name
):
1122 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
1124 chars_not_allowed_list
= "~!@#$%^&*()=+_[]{}|;:<>/?."
1126 # First: the VM name max length is 64 characters
1127 vm_name_aux
= vm_name
[:64]
1129 # Second: replace not allowed characters
1130 for elem
in chars_not_allowed_list
:
1131 # Check if string is in the main string
1132 if elem
in vm_name_aux
:
1133 # self.logger.debug("Dentro del IF")
1134 # Replace the string
1135 vm_name_aux
= vm_name_aux
.replace(elem
, "-")
1139 def get_flavor_id_from_data(self
, flavor_dict
):
1140 self
.logger
.debug("getting flavor id from data, flavor_dict: %s", flavor_dict
)
1141 filter_dict
= flavor_dict
or {}
1144 self
._reload
_connection
()
1147 for vm_size
in self
.conn_compute
.resource_skus
.list(
1148 filter="location eq '{}'".format(self
.region
)
1152 cpus
= filter_dict
.get("vcpus") or 0
1153 memMB
= filter_dict
.get("ram") or 0
1154 numberInterfaces
= len(filter_dict
.get("interfaces", [])) or 0
1158 for size
in vm_sizes_list
:
1159 if size
["resource_type"] == "virtualMachines":
1161 self
._find
_in
_capabilities
(size
["capabilities"], "vCPUs")
1163 size_memory
= float(
1164 self
._find
_in
_capabilities
(size
["capabilities"], "MemoryGB")
1166 size_interfaces
= self
._find
_in
_capabilities
(
1167 size
["capabilities"], "MaxNetworkInterfaces"
1170 size_interfaces
= int(size_interfaces
)
1173 "Flavor with no defined MaxNetworkInterfaces: {}".format(
1180 and size_memory
>= memMB
/ 1024
1181 and size_interfaces
>= numberInterfaces
1183 if self
._config
.get("flavors_pattern"):
1185 self
._config
.get("flavors_pattern"), size
["name"]
1188 e
["name"]: e
["value"] for e
in size
["capabilities"]
1190 new_size
["name"] = size
["name"]
1191 filtered_sizes
.append(new_size
)
1194 e
["name"]: e
["value"] for e
in size
["capabilities"]
1196 new_size
["name"] = size
["name"]
1197 filtered_sizes
.append(new_size
)
1200 listedFilteredSizes
= sorted(
1204 float(k
["MemoryGB"]),
1205 int(k
["MaxNetworkInterfaces"]),
1206 int(k
["MaxResourceVolumeMB"]),
1210 if listedFilteredSizes
:
1211 return listedFilteredSizes
[0]["name"]
1213 raise vimconn
.VimConnNotFoundException(
1214 "Cannot find any flavor matching '{}'".format(str(flavor_dict
))
1216 except Exception as e
:
1217 self
._format
_vimconn
_exception
(e
)
1219 def _get_flavor_id_from_flavor_name(self
, flavor_name
):
1220 # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name))
1222 self
._reload
_connection
()
1225 for vm_size
in self
.conn_compute
.resource_skus
.list(
1226 filter="location eq '{}'".format(self
.region
)
1230 output_flavor
= None
1231 for size
in vm_sizes_list
:
1232 if size
["name"] == flavor_name
:
1233 output_flavor
= size
1235 # None is returned if not found anything
1236 return output_flavor
1237 except Exception as e
:
1238 self
._format
_vimconn
_exception
(e
)
1240 def check_vim_connectivity(self
):
1242 self
._reload
_connection
()
1244 except Exception as e
:
1245 raise vimconn
.VimConnException(
1246 "Connectivity issue with Azure API: {}".format(e
)
1249 def get_network(self
, net_id
):
1250 # self.logger.debug("get network id: {}".format(net_id))
1251 # res_name = self._get_resource_name_from_resource_id(net_id)
1252 self
._reload
_connection
()
1254 filter_dict
= {"name": net_id
}
1255 network_list
= self
.get_network_list(filter_dict
)
1257 if not network_list
:
1258 raise vimconn
.VimConnNotFoundException(
1259 "network '{}' not found".format(net_id
)
1262 return network_list
[0]
1264 def delete_network(self
, net_id
, created_items
=None):
1266 "deleting network {} - {}".format(
1267 self
.vnet_resource_group
or self
.resource_group
, net_id
1271 self
._reload
_connection
()
1272 res_name
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
1275 # Obtain subnets ant try to delete nic first
1276 subnet
= self
.conn_vnet
.subnets
.get(
1277 self
.vnet_resource_group
or self
.resource_group
,
1282 raise vimconn
.VimConnNotFoundException(
1283 "network '{}' not found".format(net_id
)
1286 # TODO - for a quick-fix delete nics sequentially but should not wait
1288 if subnet
.ip_configurations
:
1289 for ip_configuration
in subnet
.ip_configurations
:
1290 # obtain nic_name from ip_configuration
1291 parsed_id
= azure_tools
.parse_resource_id(ip_configuration
.id)
1292 nic_name
= parsed_id
["name"]
1293 self
.delete_inuse_nic(nic_name
)
1295 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
1296 # Put the initial virtual_network API
1297 async_delete
= self
.conn_vnet
.subnets
.begin_delete(
1298 self
.vnet_resource_group
or self
.resource_group
,
1306 except ResourceNotFoundError
:
1307 raise vimconn
.VimConnNotFoundException(
1308 "network '{}' not found".format(net_id
)
1310 except CloudError
as e
:
1311 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1312 raise vimconn
.VimConnNotFoundException(
1313 "network '{}' not found".format(net_id
)
1316 self
._format
_vimconn
_exception
(e
)
1317 except Exception as e
:
1318 self
._format
_vimconn
_exception
(e
)
1320 def delete_inuse_nic(self
, nic_name
):
1322 nic_data
= self
.conn_vnet
.network_interfaces
.get(self
.resource_group
, nic_name
)
1324 # Obtain vm associated to nic in case it exists
1325 if nic_data
.virtual_machine
:
1326 vm_name
= azure_tools
.parse_resource_id(nic_data
.virtual_machine
.id)["name"]
1327 self
.logger
.debug("vm_name: {}".format(vm_name
))
1328 virtual_machine
= self
.conn_compute
.virtual_machines
.get(
1329 self
.resource_group
, vm_name
1331 self
.logger
.debug("obtained vm")
1333 # Deattach nic from vm if it has netwolk machines attached
1334 network_interfaces
= virtual_machine
.network_profile
.network_interfaces
1335 network_interfaces
[:] = [
1337 for interface
in network_interfaces
1338 if self
._get
_resource
_name
_from
_resource
_id
(interface
.id) != nic_name
1341 # TODO - check if there is a public ip to delete and delete it
1342 if network_interfaces
:
1344 async_vm_deallocate
= (
1345 self
.conn_compute
.virtual_machines
.begin_deallocate(
1346 self
.resource_group
, vm_name
1349 self
.logger
.debug("deallocating vm")
1350 async_vm_deallocate
.wait()
1351 self
.logger
.debug("vm deallocated")
1354 self
.conn_compute
.virtual_machines
.begin_create_or_update(
1355 self
.resource_group
, vm_name
, virtual_machine
1358 virtual_machine
= async_vm_update
.result()
1359 self
.logger
.debug("nic removed from interface")
1362 self
.logger
.debug("There are no interfaces left, delete vm")
1363 self
.delete_vminstance(virtual_machine
.id)
1364 self
.logger
.debug("Delete vm")
1367 self
.logger
.debug("delete NIC name: %s", nic_name
)
1368 nic_delete
= self
.conn_vnet
.network_interfaces
.begin_delete(
1369 self
.resource_group
, nic_name
1372 self
.logger
.debug("deleted NIC name: %s", nic_name
)
1374 def delete_vminstance(self
, vm_id
, created_items
=None, volumes_to_hold
=None):
1375 """Deletes a vm instance from the vim."""
1377 "deleting VM instance {} - {}".format(self
.resource_group
, vm_id
)
1379 self
._reload
_connection
()
1381 created_items
= created_items
or {}
1383 # Check vm exists, we can call delete_vm to clean created_items
1385 res_name
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1386 vm
= self
.conn_compute
.virtual_machines
.get(
1387 self
.resource_group
, res_name
1390 # Shuts down the virtual machine and releases the compute resources
1391 # vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1394 vm_delete
= self
.conn_compute
.virtual_machines
.begin_delete(
1395 self
.resource_group
, res_name
1398 self
.logger
.debug("deleted VM name: %s", res_name
)
1400 # Delete OS Disk, check if exists, in case of error creating
1401 # it may not be fully created
1402 if vm
.storage_profile
.os_disk
:
1403 os_disk_name
= vm
.storage_profile
.os_disk
.name
1404 self
.logger
.debug("delete OS DISK: %s", os_disk_name
)
1405 async_disk_delete
= self
.conn_compute
.disks
.begin_delete(
1406 self
.resource_group
, os_disk_name
1408 async_disk_delete
.wait()
1409 # os disks are created always with the machine
1410 self
.logger
.debug("deleted OS DISK name: %s", os_disk_name
)
1412 for data_disk
in vm
.storage_profile
.data_disks
:
1413 self
.logger
.debug("delete data_disk: %s", data_disk
.name
)
1414 async_disk_delete
= self
.conn_compute
.disks
.begin_delete(
1415 self
.resource_group
, data_disk
.name
1417 async_disk_delete
.wait()
1418 self
._markdel
_created
_item
(data_disk
.managed_disk
.id, created_items
)
1419 self
.logger
.debug("deleted OS DISK name: %s", data_disk
.name
)
1421 # After deleting VM, it is necessary to delete NIC, because if is not deleted delete_network
1422 # does not work because Azure says that is in use the subnet
1423 network_interfaces
= vm
.network_profile
.network_interfaces
1425 for network_interface
in network_interfaces
:
1426 nic_name
= self
._get
_resource
_name
_from
_resource
_id
(
1427 network_interface
.id
1429 nic_data
= self
.conn_vnet
.network_interfaces
.get(
1430 self
.resource_group
, nic_name
1433 public_ip_name
= None
1434 exist_public_ip
= nic_data
.ip_configurations
[0].public_ip_address
1436 public_ip_id
= nic_data
.ip_configurations
[
1438 ].public_ip_address
.id
1441 public_ip_name
= self
._get
_resource
_name
_from
_resource
_id
(
1445 # Public ip must be deleted afterwards of nic that is attached
1447 self
.logger
.debug("delete NIC name: %s", nic_name
)
1448 nic_delete
= self
.conn_vnet
.network_interfaces
.begin_delete(
1449 self
.resource_group
, nic_name
1452 self
._markdel
_created
_item
(network_interface
.id, created_items
)
1453 self
.logger
.debug("deleted NIC name: %s", nic_name
)
1455 # Delete list of public ips
1457 self
.logger
.debug("delete PUBLIC IP - " + public_ip_name
)
1458 ip_delete
= self
.conn_vnet
.public_ip_addresses
.begin_delete(
1459 self
.resource_group
, public_ip_name
1462 self
._markdel
_created
_item
(public_ip_id
, created_items
)
1464 # Delete created items
1465 self
._delete
_created
_items
(created_items
)
1467 except ResourceNotFoundError
:
1468 raise vimconn
.VimConnNotFoundException(
1469 "No vm instance found '{}'".format(vm_id
)
1471 except CloudError
as e
:
1472 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1473 raise vimconn
.VimConnNotFoundException(
1474 "No vm instance found '{}'".format(vm_id
)
1477 self
._format
_vimconn
_exception
(e
)
1478 except Exception as e
:
1479 self
._format
_vimconn
_exception
(e
)
1481 def _markdel_created_item(self
, item_id
, created_items
):
1482 if item_id
in created_items
:
1483 created_items
[item_id
] = False
1485 def _delete_created_items(self
, created_items
):
1486 """Delete created_items elements that have not been deleted with the virtual machine
1487 Created_items may not be deleted correctly with the created machine if the
1488 virtual machine fails creating or in other cases of error
1490 self
.logger
.debug("Created items: %s", created_items
)
1491 # TODO - optimize - should not wait until it is deleted
1492 # Must delete in order first nics, then public_ips
1493 # As dictionaries don't preserve order, first get items to be deleted then delete them
1495 publics_ip_to_delete
= []
1496 disks_to_delete
= []
1497 for item_id
, v
in created_items
.items():
1498 if not v
: # skip already deleted
1501 # self.logger.debug("Must delete item id: %s", item_id)
1502 # Obtain type, supported nic, disk or public ip
1503 parsed_id
= azure_tools
.parse_resource_id(item_id
)
1504 resource_type
= parsed_id
.get("resource_type")
1505 name
= parsed_id
.get("name")
1507 if resource_type
== "networkInterfaces":
1508 nics_to_delete
.append(name
)
1509 elif resource_type
== "publicIPAddresses":
1510 publics_ip_to_delete
.append(name
)
1511 elif resource_type
== "disks":
1512 disks_to_delete
.append(name
)
1515 for item_name
in nics_to_delete
:
1517 self
.logger
.debug("deleting nic name %s:", item_name
)
1518 nic_delete
= self
.conn_vnet
.network_interfaces
.begin_delete(
1519 self
.resource_group
, item_name
1522 self
.logger
.debug("deleted nic name %s:", item_name
)
1523 except Exception as e
:
1525 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
1528 for item_name
in publics_ip_to_delete
:
1530 self
.logger
.debug("deleting public ip name %s:", item_name
)
1531 ip_delete
= self
.conn_vnet
.public_ip_addresses
.begin_delete(
1532 self
.resource_group
, name
1535 self
.logger
.debug("deleted public ip name %s:", item_name
)
1536 except Exception as e
:
1538 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
1541 for item_name
in disks_to_delete
:
1543 self
.logger
.debug("deleting data disk name %s:", name
)
1544 async_disk_delete
= self
.conn_compute
.disks
.begin_delete(
1545 self
.resource_group
, item_name
1547 async_disk_delete
.wait()
1548 self
.logger
.debug("deleted data disk name %s:", name
)
1549 except Exception as e
:
1551 "Error deleting item: {}: {}".format(type(e
).__name
__, e
)
1554 def action_vminstance(self
, vm_id
, action_dict
, created_items
={}):
1555 """Send and action over a VM instance from VIM
1556 Returns the vm_id if the action was successfully sent to the VIM
1558 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1561 self
._reload
_connection
()
1562 resName
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1564 if "start" in action_dict
:
1565 self
.conn_compute
.virtual_machines
.begin_start(
1566 self
.resource_group
, resName
1569 "stop" in action_dict
1570 or "shutdown" in action_dict
1571 or "shutoff" in action_dict
1573 self
.conn_compute
.virtual_machines
.begin_power_off(
1574 self
.resource_group
, resName
1576 elif "terminate" in action_dict
:
1577 self
.conn_compute
.virtual_machines
.begin_delete(
1578 self
.resource_group
, resName
1580 elif "reboot" in action_dict
:
1581 self
.conn_compute
.virtual_machines
.begin_restart(
1582 self
.resource_group
, resName
1586 except ResourceNotFoundError
:
1587 raise vimconn
.VimConnNotFoundException("No vm found '{}'".format(vm_id
))
1588 except CloudError
as e
:
1589 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1590 raise vimconn
.VimConnNotFoundException("No vm found '{}'".format(vm_id
))
1592 self
._format
_vimconn
_exception
(e
)
1593 except Exception as e
:
1594 self
._format
_vimconn
_exception
(e
)
1596 def delete_flavor(self
, flavor_id
):
1597 raise vimconn
.VimConnAuthException(
1598 "It is not possible to delete a FLAVOR in AZURE"
1601 def delete_tenant(self
, tenant_id
):
1602 raise vimconn
.VimConnAuthException(
1603 "It is not possible to delete a TENANT in AZURE"
1606 def delete_image(self
, image_id
):
1607 raise vimconn
.VimConnAuthException(
1608 "It is not possible to delete a IMAGE in AZURE"
1611 def get_vminstance(self
, vm_id
):
1613 Obtaing the vm instance data from v_id
1615 self
.logger
.debug("get vm instance: %s", vm_id
)
1616 self
._reload
_connection
()
1618 resName
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1619 vm
= self
.conn_compute
.virtual_machines
.get(self
.resource_group
, resName
)
1620 except ResourceNotFoundError
:
1621 raise vimconn
.VimConnNotFoundException(
1622 "No vminstance found '{}'".format(vm_id
)
1624 except CloudError
as e
:
1625 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1626 raise vimconn
.VimConnNotFoundException(
1627 "No vminstance found '{}'".format(vm_id
)
1630 self
._format
_vimconn
_exception
(e
)
1631 except Exception as e
:
1632 self
._format
_vimconn
_exception
(e
)
1636 def get_flavor(self
, flavor_id
):
1638 Obtains the flavor_data from the flavor_id
1640 self
._reload
_connection
()
1641 self
.logger
.debug("get flavor from id: %s", flavor_id
)
1642 flavor_data
= self
._get
_flavor
_id
_from
_flavor
_name
(flavor_id
)
1648 "ram": flavor_data
["memoryInMB"],
1649 "vcpus": flavor_data
["numberOfCores"],
1650 "disk": flavor_data
["resourceDiskSizeInMB"] / 1024,
1655 raise vimconn
.VimConnNotFoundException(
1656 "flavor '{}' not found".format(flavor_id
)
1659 def get_tenant_list(self
, filter_dict
={}):
1660 """Obtains the list of tenants
1661 For the azure connector only the azure tenant will be returned if it is compatible
1664 tenants_azure
= [{"name": self
.tenant
, "id": self
.tenant
}]
1667 self
.logger
.debug("get tenant list: %s", filter_dict
)
1668 for tenant_azure
in tenants_azure
:
1671 filter_dict
.get("id")
1672 and str(tenant_azure
.get("id")) != filter_dict
["id"]
1677 filter_dict
.get("name")
1678 and str(tenant_azure
.get("name")) != filter_dict
["name"]
1682 tenant_list
.append(tenant_azure
)
1686 def refresh_nets_status(self
, net_list
):
1687 """Get the status of the networks
1688 Params: the list of network identifiers
1689 Returns a dictionary with:
1690 net_id: #VIM id of this network
1691 status: #Mandatory. Text with one of:
1692 # DELETED (not found at vim)
1693 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1694 # OTHER (Vim reported other status not understood)
1695 # ERROR (VIM indicates an ERROR status)
1696 # ACTIVE, INACTIVE, DOWN (admin down),
1697 # BUILD (on building process)
1699 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1700 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1703 self
._reload
_connection
()
1705 self
.logger
.debug("reload nets status net_list: %s", net_list
)
1706 for net_id
in net_list
:
1708 netName
= self
._get
_net
_name
_from
_resource
_id
(net_id
)
1709 resName
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
1711 net
= self
.conn_vnet
.subnets
.get(
1712 self
.vnet_resource_group
or self
.resource_group
, netName
, resName
1715 out_nets
[net_id
] = {
1716 "status": self
.provision_state2osm
[net
.provisioning_state
],
1717 "vim_info": str(net
),
1719 except CloudError
as e
:
1720 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1722 "Not found subnet net_name: %s, subnet_name: %s",
1726 out_nets
[net_id
] = {"status": "DELETED", "error_msg": str(e
)}
1729 "CloudError Exception %s when searching subnet", e
1731 out_nets
[net_id
] = {
1732 "status": "VIM_ERROR",
1733 "error_msg": str(e
),
1735 except vimconn
.VimConnNotFoundException
as e
:
1737 "VimConnNotFoundException %s when searching subnet", e
1739 out_nets
[net_id
] = {
1740 "status": "DELETED",
1741 "error_msg": str(e
),
1743 except Exception as e
:
1745 "Exception %s when searching subnet", e
, exc_info
=True
1747 out_nets
[net_id
] = {
1748 "status": "VIM_ERROR",
1749 "error_msg": str(e
),
1754 def refresh_vms_status(self
, vm_list
):
1755 """Get the status of the virtual machines and their interfaces/ports
1756 Params: the list of VM identifiers
1757 Returns a dictionary with:
1758 vm_id: # VIM id of this Virtual Machine
1759 status: # Mandatory. Text with one of:
1760 # DELETED (not found at vim)
1761 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1762 # OTHER (Vim reported other status not understood)
1763 # ERROR (VIM indicates an ERROR status)
1764 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1765 # BUILD (on building process), ERROR
1766 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1767 # (ACTIVE:NoMgmtIP is not returned for Azure)
1769 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1770 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1771 interfaces: list with interface info. Each item a dictionary with:
1772 vim_interface_id - The ID of the interface
1773 mac_address - The MAC address of the interface.
1774 ip_address - The IP address of the interface within the subnet.
1777 self
._reload
_connection
()
1779 self
.logger
.debug("refresh vm status vm_list: %s", vm_list
)
1780 search_vm_list
= vm_list
or {}
1782 for vm_id
in search_vm_list
:
1785 res_name
= self
._get
_resource
_name
_from
_resource
_id
(vm_id
)
1787 vm
= self
.conn_compute
.virtual_machines
.get(
1788 self
.resource_group
, res_name
1790 img
= vm
.storage_profile
.image_reference
1791 images
= self
._get
_version
_image
_list
(
1792 img
.publisher
, img
.offer
, img
.sku
, img
.version
1797 "location": vm
.location
,
1798 "provisioning_state": vm
.provisioning_state
,
1801 "flavor": {"id": vm
.hardware_profile
.vm_size
},
1804 out_vm
["vim_info"] = str(vim_info
)
1805 out_vm
["status"] = self
.provision_state2osm
.get(
1806 vm
.provisioning_state
, "OTHER"
1809 if vm
.provisioning_state
== "Succeeded":
1810 # check if machine is running or stopped
1811 instance_view
= self
.conn_compute
.virtual_machines
.instance_view(
1812 self
.resource_group
, res_name
1815 for status
in instance_view
.statuses
:
1816 splitted_status
= status
.code
.split("/")
1818 len(splitted_status
) == 2
1819 and splitted_status
[0] == "PowerState"
1821 out_vm
["status"] = self
.power_state2osm
.get(
1822 splitted_status
[1], "OTHER"
1825 network_interfaces
= vm
.network_profile
.network_interfaces
1826 out_vm
["interfaces"] = self
._get
_vm
_interfaces
_status
(
1827 vm_id
, network_interfaces
1830 except CloudError
as e
:
1831 if e
.error
.error
and "notfound" in e
.error
.error
.lower():
1832 self
.logger
.debug("Not found vm id: %s", vm_id
)
1833 out_vm
["status"] = "DELETED"
1834 out_vm
["error_msg"] = str(e
)
1835 out_vm
["vim_info"] = None
1837 # maybe connection error or another type of error, return vim error
1838 self
.logger
.error("Exception %s refreshing vm_status", e
)
1839 out_vm
["status"] = "VIM_ERROR"
1840 out_vm
["error_msg"] = str(e
)
1841 out_vm
["vim_info"] = None
1842 except Exception as e
:
1843 self
.logger
.error("Exception %s refreshing vm_status", e
, exc_info
=True)
1844 out_vm
["status"] = "VIM_ERROR"
1845 out_vm
["error_msg"] = str(e
)
1846 out_vm
["vim_info"] = None
1848 out_vms
[vm_id
] = out_vm
1852 def _get_vm_interfaces_status(self
, vm_id
, interfaces
):
1854 Gets the interfaces detail for a vm
1855 :param interfaces: List of interfaces.
1856 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1860 for network_interface
in interfaces
:
1862 nic_name
= self
._get
_resource
_name
_from
_resource
_id
(
1863 network_interface
.id
1865 interface_dict
["vim_interface_id"] = network_interface
.id
1867 nic_data
= self
.conn_vnet
.network_interfaces
.get(
1868 self
.resource_group
,
1873 if nic_data
.ip_configurations
[0].public_ip_address
:
1874 self
.logger
.debug("Obtain public ip address")
1875 public_ip_name
= self
._get
_resource
_name
_from
_resource
_id
(
1876 nic_data
.ip_configurations
[0].public_ip_address
.id
1878 public_ip
= self
.conn_vnet
.public_ip_addresses
.get(
1879 self
.resource_group
, public_ip_name
1881 self
.logger
.debug("Public ip address is: %s", public_ip
.ip_address
)
1882 ips
.append(public_ip
.ip_address
)
1884 subnet
= nic_data
.ip_configurations
[0].subnet
.id
1886 interface_dict
["vim_net_id"] = subnet
1888 private_ip
= nic_data
.ip_configurations
[0].private_ip_address
1889 ips
.append(private_ip
)
1891 interface_dict
["mac_address"] = nic_data
.mac_address
1892 interface_dict
["ip_address"] = ";".join(ips
)
1893 interface_list
.append(interface_dict
)
1895 return interface_list
1896 except Exception as e
:
1898 "Exception %s obtaining interface data for vm: %s",
1903 self
._format
_vimconn
_exception
(e
)
1905 def _get_default_admin_user(self
, image_id
):
1906 if "ubuntu" in image_id
.lower():
1909 return self
._default
_admin
_user
1911 def migrate_instance(self
, vm_id
, compute_host
=None):
1915 vm_id: ID of an instance
1916 compute_host: Host to migrate the vdu to
1918 # TODO: Add support for migration
1919 raise vimconn
.VimConnNotImplemented("Not implemented")
1921 def resize_instance(self
, vm_id
, flavor_id
=None):
1925 vm_id: ID of an instance
1926 flavor_id: flavor id to resize the vdu
1928 # TODO: Add support for resize
1929 raise vimconn
.VimConnNotImplemented("Not implemented")
1932 if __name__
== "__main__":
1934 log_format
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
1935 log_formatter
= logging
.Formatter(log_format
, datefmt
="%Y-%m-%dT%H:%M:%S")
1936 handler
= logging
.StreamHandler()
1937 handler
.setFormatter(log_formatter
)
1938 logger
= logging
.getLogger("ro.vim.azure")
1939 # logger.setLevel(level=logging.ERROR)
1940 # logger.setLevel(level=logging.INFO)
1941 logger
.setLevel(level
=logging
.DEBUG
)
1942 logger
.addHandler(handler
)
1944 # Making some basic test
1947 needed_test_params
= {
1948 "client_id": "AZURE_CLIENT_ID",
1949 "secret": "AZURE_SECRET",
1950 "tenant": "AZURE_TENANT",
1951 "resource_group": "AZURE_RESOURCE_GROUP",
1952 "subscription_id": "AZURE_SUBSCRIPTION_ID",
1953 "vnet_name": "AZURE_VNET_NAME",
1957 for param
, env_var
in needed_test_params
.items():
1958 value
= getenv(env_var
)
1961 raise Exception("Provide a valid value for env '{}'".format(env_var
))
1963 test_params
[param
] = value
1966 "region_name": getenv("AZURE_REGION_NAME", "northeurope"),
1967 "resource_group": getenv("AZURE_RESOURCE_GROUP"),
1968 "subscription_id": getenv("AZURE_SUBSCRIPTION_ID"),
1969 "pub_key": getenv("AZURE_PUB_KEY", None),
1970 "vnet_name": getenv("AZURE_VNET_NAME", "osm_vnet"),
1973 azure
= vimconnector(
1976 tenant_id
=test_params
["tenant"],
1980 user
=test_params
["client_id"],
1981 passwd
=test_params
["secret"],