From: tierno Date: Thu, 21 Nov 2019 16:10:32 +0000 (+0000) Subject: Feature 7432 Merge branch 'Azure' X-Git-Tag: v7.0.0rc1~15 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=b569e4bf18d0c72093b82e5a84014e19094d0025;hp=0aa0e2fcec7f5c7dcd6d56e2a38f72db327cee50;p=osm%2FRO.git Feature 7432 Merge branch 'Azure' flake8 fix on vincomm_azure Change-Id: Ife862080a0ad1824e22a04d7ae2511f25ed90372 Signed-off-by: tierno --- diff --git a/LICENSE b/LICENSE index 8dada3ed..1be36763 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright Copyright 2019 ETSI Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/osm_ro/vimconn_azure.py b/osm_ro/vimconn_azure.py index 24a9878d..ad96da1a 100755 --- a/osm_ro/vimconn_azure.py +++ b/osm_ro/vimconn_azure.py @@ -1,385 +1,860 @@ # -*- coding: utf-8 -*- +## +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +## -__author__='Sergio Gonzalez' -__date__ ='$18-apr-2019 23:59:59$' - +import base64 import vimconn import logging +import netaddr +import re from os import getenv -from uuid import uuid4 - from azure.common.credentials import ServicePrincipalCredentials from azure.mgmt.resource import ResourceManagementClient from azure.mgmt.network import NetworkManagementClient from azure.mgmt.compute import ComputeManagementClient +from azure.mgmt.compute.models import DiskCreateOption +from msrestazure.azure_exceptions import CloudError +from msrest.exceptions import AuthenticationError +from requests.exceptions import ConnectionError + +__author__ = 'Isabel Lloret, Sergio Gonzalez, Alfonso Tierno' +__date__ = '$18-apr-2019 23:59:59$' + + +if getenv('OSMRO_PDB_DEBUG'): + import sys + print(sys.path) + import pdb + pdb.set_trace() class vimconnector(vimconn.vimconnector): + # Translate azure provisioning state to OSM provision state + # The first three ones are the transitional status once a user initiated action has been requested + # Once the operation is complete, it will transition into the states Succeeded or Failed + # https://docs.microsoft.com/en-us/azure/virtual-machines/windows/states-lifecycle + provision_state2osm = { + "Creating": "BUILD", + "Updating": "BUILD", + "Deleting": "INACTIVE", + "Succeeded": "ACTIVE", + "Failed": "ERROR" + } + + # Translate azure power state to OSM provision state + power_state2osm = { + "starting": "INACTIVE", + "running": "ACTIVE", + "stopping": "INACTIVE", + "stopped": "INACTIVE", + "unknown": "OTHER", + "deallocated": "BUILD", + "deallocating": "BUILD" + } + def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None, config={}, persistent_info={}): + """ + Constructor of VIM. Raise an exception is some needed parameter is missing, but it must not do any connectivity + checking against the VIM + Using common constructor parameters. + In this case: config must include the following parameters: + subscription_id: assigned azure subscription identifier + region_name: current region for azure network + resource_group: used for all azure created resources + vnet_name: base vnet for azure, created networks will be subnets from this base network + config may also include the following parameter: + flavors_pattern: pattern that will be used to select a range of vm sizes, for example + "^((?!Standard_B).)*$" will filter out Standard_B range that is cheap but is very overused + "^Standard_B" will select a serie B maybe for test environment + """ vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config, persistent_info) + # Variable that indicates if client must be reloaded or initialized + self.reload_client = True + + self.vnet_address_space = None # LOGGER self.logger = logging.getLogger('openmano.vim.azure') if log_level: logging.basicConfig() self.logger.setLevel(getattr(logging, log_level)) - # CREDENTIALS - self.credentials = ServicePrincipalCredentials( - client_id=user, - secret=passwd, - tenant=(tenant_id or tenant_name) - ) + self.tenant = (tenant_id or tenant_name) + + # Store config to create azure subscription later + self._config = { + "user": user, + "passwd": passwd, + "tenant": tenant_id or tenant_name + } # SUBSCRIPTION if 'subscription_id' in config: - self.subscription_id = config.get('subscription_id') - self.logger.debug('Setting subscription '+str(self.subscription_id)) + self._config["subscription_id"] = config.get('subscription_id') + # self.logger.debug('Setting subscription to: %s', self.config["subscription_id"]) else: raise vimconn.vimconnException('Subscription not specified') + # REGION if 'region_name' in config: self.region = config.get('region_name') else: raise vimconn.vimconnException('Azure region_name is not specified at config') + # RESOURCE_GROUP if 'resource_group' in config: self.resource_group = config.get('resource_group') else: raise vimconn.vimconnException('Azure resource_group is not specified at config') + # VNET_NAME if 'vnet_name' in config: self.vnet_name = config["vnet_name"] # public ssh key self.pub_key = config.get('pub_key') + + # flavor pattern regex + if 'flavors_pattern' in config: + self._config['flavors_pattern'] = config['flavors_pattern'] def _reload_connection(self): """ - Sets connections to work with Azure service APIs - :return: + Called before any operation, checks python azure clients """ - self.logger.debug('Reloading API Connection') - try: - self.conn = ResourceManagementClient(self.credentials, self.subscription_id) - self.conn_compute = ComputeManagementClient(self.credentials, self.subscription_id) - self.conn_vnet = NetworkManagementClient(self.credentials, self.subscription_id) - self._check_or_create_resource_group() - self._check_or_create_vnet() - except Exception as e: - self.format_vimconn_exception(e) + if self.reload_client: + self.logger.debug('reloading azure client') + try: + self.credentials = ServicePrincipalCredentials( + client_id=self._config["user"], + secret=self._config["passwd"], + tenant=self._config["tenant"] + ) + self.conn = ResourceManagementClient(self.credentials, self._config["subscription_id"]) + self.conn_compute = ComputeManagementClient(self.credentials, self._config["subscription_id"]) + self.conn_vnet = NetworkManagementClient(self.credentials, self._config["subscription_id"]) + self._check_or_create_resource_group() + self._check_or_create_vnet() + + # Set to client created + self.reload_client = False + except Exception as e: + self._format_vimconn_exception(e) def _get_resource_name_from_resource_id(self, resource_id): - return str(resource_id.split('/')[-1]) + """ + Obtains resource_name from the azure complete identifier: resource_name will always be last item + """ + try: + resource = str(resource_id.split('/')[-1]) + return resource + except Exception as e: + raise vimconn.vimconnException("Unable to get resource name from resource_id '{}' Error: '{}'". + format(resource_id, e)) def _get_location_from_resource_group(self, resource_group_name): - return self.conn.resource_groups.get(resource_group_name).location - + try: + location = self.conn.resource_groups.get(resource_group_name).location + return location + except Exception as e: + raise vimconn.vimconnNotFoundException("Location '{}' not found".format(resource_group_name)) + def _get_resource_group_name_from_resource_id(self, resource_id): - return str(resource_id.split('/')[4]) + + try: + rg = str(resource_id.split('/')[4]) + return rg + except Exception as e: + raise vimconn.vimconnException("Unable to get resource group from invalid resource_id format '{}'". + format(resource_id)) + + def _get_net_name_from_resource_id(self, resource_id): + + try: + net_name = str(resource_id.split('/')[8]) + return net_name + except Exception as e: + raise vimconn.vimconnException("Unable to get azure net_name from invalid resource_id format '{}'". + format(resource_id)) def _check_subnets_for_vm(self, net_list): # All subnets must belong to the same resource group and vnet - if len(set(self._get_resource_group_name_from_resource_id(net['id']) + - self._get_resource_name_from_resource_id(net['id']) for net in net_list)) != 1: - raise self.format_vimconn_exception('Azure VMs can only attach to subnets in same VNET') + rg_vnet = set(self._get_resource_group_name_from_resource_id(net['net_id']) + + self._get_net_name_from_resource_id(net['net_id']) for net in net_list) + + if len(rg_vnet) != 1: + raise self._format_vimconn_exception('Azure VMs can only attach to subnets in same VNET') - def format_vimconn_exception(self, e): + def _format_vimconn_exception(self, e): """ - Params: an Exception object - :param e: - :return: Raises the proper vimconnException + Transforms a generic or azure exception to a vimcommException """ - self.conn = None - self.conn_vnet = None - raise vimconn.vimconnConnectionException(type(e).__name__ + ': ' + str(e)) + if isinstance(e, vimconn.vimconnException): + raise + elif isinstance(e, AuthenticationError): + raise vimconn.vimconnAuthException(type(e).__name__ + ': ' + str(e)) + elif isinstance(e, ConnectionError): + raise vimconn.vimconnConnectionException(type(e).__name__ + ': ' + str(e)) + else: + # In case of generic error recreate client + self.reload_client = True + raise vimconn.vimconnException(type(e).__name__ + ': ' + str(e)) def _check_or_create_resource_group(self): """ - Creates a resource group in indicated region - :return: None + Creates the base resource group if it does not exist """ - self.logger.debug('Creating RG {} in location {}'.format(self.resource_group, self.region)) - self.conn.resource_groups.create_or_update(self.resource_group, {'location': self.region}) + try: + rg_exists = self.conn.resource_groups.check_existence(self.resource_group) + if not rg_exists: + self.logger.debug("create base rgroup: %s", self.resource_group) + self.conn.resource_groups.create_or_update(self.resource_group, {'location': self.region}) + except Exception as e: + self._format_vimconn_exception(e) def _check_or_create_vnet(self): + """ + Try to get existent base vnet, in case it does not exist it creates it + """ + try: + vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name) + self.vnet_address_space = vnet.address_space.address_prefixes[0] + self.vnet_id = vnet.id + return + except CloudError as e: + if e.error.error and "notfound" in e.error.error.lower(): + pass + # continue and create it + else: + self._format_vimconn_exception(e) + + # if it does not exist, create it try: vnet_params = { 'location': self.region, 'address_space': { - 'address_prefixes': "10.0.0.0/8" + 'address_prefixes': ["10.0.0.0/8"] }, } + self.vnet_address_space = "10.0.0.0/8" + + self.logger.debug("create base vnet: %s", self.vnet_name) self.conn_vnet.virtual_networks.create_or_update(self.resource_group, self.vnet_name, vnet_params) + vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name) + self.vnet_id = vnet.id except Exception as e: - self.format_vimconn_exception(e) + self._format_vimconn_exception(e) def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None): """ Adds a tenant network to VIM :param net_name: name of the network - :param net_type: + :param net_type: not used for azure networks :param ip_profile: is a dict containing the IP parameters of the network (Currently only IPv4 is implemented) 'ip-version': can be one of ['IPv4','IPv6'] 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y - 'gateway-address': (Optional) ip_schema, that is X.X.X.X - 'dns-address': (Optional) ip_schema, - 'dhcp': (Optional) dict containing + 'gateway-address': (Optional) ip_schema, that is X.X.X.X, not implemented for azure connector + 'dns-address': (Optional) ip_schema, not implemented for azure connector + 'dhcp': (Optional) dict containing, not implemented for azure connector 'enabled': {'type': 'boolean'}, 'start-address': ip_schema, first IP to grant 'count': number of IPs to grant. - :param shared: - :param vlan: + :param shared: Not allowed for Azure Connector + :param vlan: VLAN tagging is not allowed for Azure :return: a tuple with the network identifier and created_items, or raises an exception on error created_items can be None or a dictionary where this method can include key-values that will be passed to the method delete_network. Can be used to store created segments, created l2gw connections, etc. Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same as not present. """ - return self._new_subnet(net_name, ip_profile) def _new_subnet(self, net_name, ip_profile): """ - Adds a tenant network to VIM. It creates a new VNET with a single subnet - :param net_name: + Adds a tenant network to VIM. It creates a new subnet at existing base vnet + :param net_name: subnet name :param ip_profile: - :return: + subnet-address: if it is not provided a subnet/24 in the default vnet is created, + otherwise it creates a subnet in the indicated address + :return: a tuple with the network identifier and created_items, or raises an exception on error """ - self.logger.debug('Adding a subnet to VNET '+self.vnet_name) + self.logger.debug('create subnet name %s, ip_profile %s', net_name, ip_profile) self._reload_connection() if ip_profile is None: - # TODO get a non used vnet ip range /24 and allocate automatically - raise vimconn.vimconnException('Azure cannot create VNET with no CIDR') + # get a non used vnet ip range /24 and allocate automatically inside the range self.vnet_address_space + used_subnets = self.get_network_list() + for ip_range in netaddr.IPNetwork(self.vnet_address_space).subnet(24): + for used_subnet in used_subnets: + subnet_range = netaddr.IPNetwork(used_subnet["cidr_block"]) + if subnet_range in ip_range or ip_range in subnet_range: + # this range overlaps with an existing subnet ip range. Breaks and look for another + break + else: + ip_profile = {"subnet_address": str(ip_range)} + self.logger.debug('dinamically obtained ip_profile: %s', ip_range) + break + else: + raise vimconn.vimconnException("Cannot find a non-used subnet range in {}". + format(self.vnet_address_space)) + else: + ip_profile = {"subnet_address": ip_profile['subnet_address']} try: - vnet_params= { - 'location': self.region, - 'address_space': { - 'address_prefixes': [ip_profile['subnet_address']] - }, - 'subnets': [ - { - 'name': "{}-{}".format(net_name[:24], uuid4()), - 'address_prefix': ip_profile['subnet_address'] - } - ] + # subnet_name = "{}-{}".format(net_name[:24], uuid4()) + subnet_params = { + 'address_prefix': ip_profile['subnet_address'] } - self.conn_vnet.virtual_networks.create_or_update(self.resource_group, self.vnet_name, vnet_params) - # TODO return a tuple (subnet-ID, None) + # Assign a not duplicated net name + subnet_name = self._get_unused_subnet_name(net_name) + + self.logger.debug('creating subnet_name: {}'.format(subnet_name)) + async_creation = self.conn_vnet.subnets.create_or_update(self.resource_group, self.vnet_name, + subnet_name, subnet_params) + async_creation.wait() + self.logger.debug('created subnet_name: {}'.format(subnet_name)) + + return "{}/subnets/{}".format(self.vnet_id, subnet_name), None except Exception as e: - self.format_vimconn_exception(e) + self._format_vimconn_exception(e) + + def _get_unused_subnet_name(self, subnet_name): + """ + Adds a prefix to the subnet_name with a number in case the indicated name is repeated + Checks subnets with the indicated name (without suffix) and adds a suffix with a number + """ + all_subnets = self.conn_vnet.subnets.list(self.resource_group, self.vnet_name) + # Filter to subnets starting with the indicated name + subnets = list(filter(lambda subnet: (subnet.name.startswith(subnet_name)), all_subnets)) + net_names = [str(subnet.name) for subnet in subnets] - def _create_nic(self, subnet_id, nic_name, static_ip=None): + # get the name with the first not used suffix + name_suffix = 0 + # name = subnet_name + "-" + str(name_suffix) + name = subnet_name # first subnet created will have no prefix + while name in net_names: + name_suffix += 1 + name = subnet_name + "-" + str(name_suffix) + return name + + def _create_nic(self, net, nic_name, static_ip=None): + + self.logger.debug('create nic name %s, net_name %s', nic_name, net) self._reload_connection() - - resource_group_name=self._get_resource_group_name_from_resource_id(subnet_id) - location = self._get_location_from_resource_group(resource_group_name) - - if static_ip: - async_nic_creation = self.conn_vnet.network_interfaces.create_or_update( - resource_group_name, - nic_name, - { - 'location': location, - 'ip_configurations': [{ - 'name': nic_name + 'ipconfiguration', - 'privateIPAddress': static_ip, - 'privateIPAllocationMethod': 'Static', - 'subnet': { - 'id': subnet_id - } - }] - } - ) - else: - async_nic_creation = self.conn_vnet.network_interfaces.create_or_update( - resource_group_name, - nic_name, - { + + subnet_id = net['net_id'] + location = self._get_location_from_resource_group(self.resource_group) + try: + net_ifz = {'location': location} + net_ip_config = {'name': nic_name + '-ipconfiguration', 'subnet': {'id': subnet_id}} + if static_ip: + net_ip_config['privateIPAddress'] = static_ip + net_ip_config['privateIPAllocationMethod'] = 'Static' + net_ifz['ip_configurations'] = [net_ip_config] + mac_address = net.get('mac_address') + if mac_address: + net_ifz['mac_address'] = mac_address + + async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(self.resource_group, nic_name, + net_ifz) + async_nic_creation.wait() + self.logger.debug('created nic name %s', nic_name) + + public_ip = net.get('floating_ip') + if public_ip: + public_ip_address_params = { 'location': location, - 'ip_configurations': [{ - 'name': nic_name + 'ipconfiguration', - 'subnet': { - 'id': subnet_id - } - }] + 'public_ip_allocation_method': 'Dynamic' } - ) + public_ip_name = nic_name + '-public-ip' + public_ip = self.conn_vnet.public_ip_addresses.create_or_update( + self.resource_group, + public_ip_name, + public_ip_address_params + ) + self.logger.debug('created public IP: {}'.format(public_ip.result())) + + # Associate NIC to Public IP + nic_data = self.conn_vnet.network_interfaces.get( + self.resource_group, + nic_name) + + nic_data.ip_configurations[0].public_ip_address = public_ip.result() + + self.conn_vnet.network_interfaces.create_or_update( + self.resource_group, + nic_name, + nic_data) + + except Exception as e: + self._format_vimconn_exception(e) return async_nic_creation.result() - def get_image_list(self, filter_dict={}): + def new_flavor(self, flavor_data): """ - The urn contains for marketplace 'publisher:offer:sku:version' + It is not allowed to create new flavors in Azure, must always use an existing one + """ + raise vimconn.vimconnAuthException("It is not possible to create new flavors in AZURE") - :param filter_dict: - :return: + def new_tenant(self, tenant_name, tenant_description): """ - image_list = [] + It is not allowed to create new tenants in azure + """ + raise vimconn.vimconnAuthException("It is not possible to create a TENANT in AZURE") + + def new_image(self, image_dict): + """ + It is not allowed to create new images in Azure, must always use an existing one + """ + raise vimconn.vimconnAuthException("It is not possible to create new images in AZURE") + + def get_image_id_from_path(self, path): + """Get the image id from image path in the VIM database. + Returns the image_id or raises a vimconnNotFoundException + """ + raise vimconn.vimconnAuthException("It is not possible to obtain image from path in AZURE") + + def get_image_list(self, filter_dict={}): + """Obtain tenant images from VIM + Filter_dict can be: + name: image name with the format: publisher:offer:sku:version + If some part of the name is provide ex: publisher:offer it will search all availables skus and version + for the provided publisher and offer + id: image uuid, currently not supported for azure + Returns the image list of dictionaries: + [{}, ...] + List can be empty + """ + + self.logger.debug("get_image_list filter {}".format(filter_dict)) self._reload_connection() - if filter_dict.get("name"): - params = filter_dict["name"].split(":") - if len(params) >= 3: + try: + image_list = [] + if filter_dict.get("name"): + # name will have the format 'publisher:offer:sku:version' + # publisher is required, offer sku and version will be searched if not provided + params = filter_dict["name"].split(":") publisher = params[0] - offer = params[1] - sku = params[2] - version = None - if len(params) == 4: - version = params[3] - images = self.conn_compute.virtual_machine_images.list(self.region, publisher, offer, sku) - for image in images: - if version: - image_version = str(image.id).split("/")[-1] - if image_version != version: - continue - image_list.append({ - 'id': str(image.id), - 'name': self._get_resource_name_from_resource_id(image.id) - }) - return image_list - - images = self.conn_compute.virtual_machine_images.list() - - for image in images: - # TODO implement filter_dict - if filter_dict: - if filter_dict.get("id") and str(image.id) != filter_dict["id"]: - continue - if filter_dict.get("name") and \ - self._get_resource_name_from_resource_id(image.id) != filter_dict["name"]: - continue - # TODO add checksum - image_list.append({ - 'id': str(image.id), - 'name': self._get_resource_name_from_resource_id(image.id), - }) + if publisher: + # obtain offer list + offer_list = self._get_offer_list(params, publisher) + for offer in offer_list: + # obtain skus + sku_list = self._get_sku_list(params, publisher, offer) + for sku in sku_list: + # if version is defined get directly version, else list images + if len(params) == 4 and params[3]: + version = params[3] + image_list = self._get_version_image_list(publisher, offer, sku, version) + else: + image_list = self._get_sku_image_list(publisher, offer, sku) + else: + raise vimconn.vimconnAuthException( + "List images in Azure must include name param with at least publisher") + else: + raise vimconn.vimconnAuthException("List images in Azure must include name param with at" + " least publisher") + + return image_list + except Exception as e: + self._format_vimconn_exception(e) + + def _get_offer_list(self, params, publisher): + """ + Helper method to obtain offer list for defined publisher + """ + if len(params) >= 2 and params[1]: + return [params[1]] + else: + try: + # get list of offers from azure + result_offers = self.conn_compute.virtual_machine_images.list_offers(self.region, publisher) + return [offer.name for offer in result_offers] + except CloudError as e: + # azure raises CloudError when not found + self.logger.info("error listing offers for publisher {}, Error: {}".format(publisher, e)) + return [] + + def _get_sku_list(self, params, publisher, offer): + """ + Helper method to obtain sku list for defined publisher and offer + """ + if len(params) >= 3 and params[2]: + return [params[2]] + else: + try: + # get list of skus from azure + result_skus = self.conn_compute.virtual_machine_images.list_skus(self.region, publisher, offer) + return [sku.name for sku in result_skus] + except CloudError as e: + # azure raises CloudError when not found + self.logger.info("error listing skus for publisher {}, offer {}, Error: {}".format(publisher, offer, e)) + return [] + + def _get_sku_image_list(self, publisher, offer, sku): + """ + Helper method to obtain image list for publisher, offer and sku + """ + image_list = [] + try: + result_images = self.conn_compute.virtual_machine_images.list(self.region, publisher, offer, sku) + for result_image in result_images: + image_list.append({ + 'id': str(result_image.id), + 'name': ":".join([publisher, offer, sku, result_image.name]) + }) + except CloudError as e: + self.logger.info( + "error listing skus for publisher {}, offer {}, Error: {}".format(publisher, offer, e)) + image_list = [] + return image_list + + def _get_version_image_list(self, publisher, offer, sku, version): + image_list = [] + try: + result_image = self.conn_compute.virtual_machine_images.get(self.region, publisher, offer, sku, version) + if result_image: + image_list.append({ + 'id': str(result_image.id), + 'name': ":".join([publisher, offer, sku, version]) + }) + except CloudError as e: + # azure gives CloudError when not found + self.logger.info("error listing images for publisher {}, offer {}, sku {}, version {} Error: {}". + format(publisher, offer, sku, version, e)) + image_list = [] return image_list def get_network_list(self, filter_dict={}): """Obtain tenant networks of VIM Filter_dict can be: name: network name - id: network uuid - shared: boolean - tenant_id: tenant - admin_state_up: boolean - status: 'ACTIVE' + id: network id + shared: boolean, not implemented in Azure + tenant_id: tenant, not used in Azure, all networks same tenants + admin_state_up: boolean, not implemented in Azure + status: 'ACTIVE', not implemented in Azure # Returns the network list of dictionaries """ - self.logger.debug('Getting all subnets from VIM') + # self.logger.debug('getting network list for vim, filter %s', filter_dict) try: self._reload_connection() - vnet = self.conn_vnet.virtual_networks.get(self.config["resource_group"], self.vnet_name) + + vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name) subnet_list = [] - + for subnet in vnet.subnets: - # TODO implement filter_dict if filter_dict: if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]: continue if filter_dict.get("name") and \ - self._get_resource_name_from_resource_id(subnet.id) != filter_dict["name"]: + str(subnet.name) != filter_dict["name"]: continue + name = self._get_resource_name_from_resource_id(subnet.id) + subnet_list.append({ 'id': str(subnet.id), - 'name': self._get_resource_name_from_resource_id(subnet.id), - 'status': str(vnet.provisioning_state), # TODO Does subnet contains status??? - 'cidr_block': str(subnet.address_prefix) - } - ) + 'name': name, + 'status': self.provision_state2osm[subnet.provisioning_state], + 'cidr_block': str(subnet.address_prefix), + 'type': 'bridge', + 'shared': False + }) + return subnet_list except Exception as e: - self.format_vimconn_exception(e) + self._format_vimconn_exception(e) - def new_vminstance(self, vm_name, description, start, image_id, flavor_id, net_list, cloud_config=None, + def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None, availability_zone_index=None, availability_zone_list=None): - return self._new_vminstance(vm_name, image_id, flavor_id, net_list) - - def _new_vminstance(self, vm_name, image_id, flavor_id, net_list, cloud_config=None, disk_list=None, - availability_zone_index=None, availability_zone_list=None): - #Create NICs + self.logger.debug("new vm instance name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, " + "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s", + name, image_id, flavor_id, net_list, cloud_config, disk_list, + availability_zone_index, availability_zone_list) + + self._reload_connection() + + # Validate input data is valid + # The virtual machine name must have less or 64 characters and it can not have the following + # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.) + vm_name = self._check_vm_name(name) + # Obtain vm unused name + vm_name = self._get_unused_vm_name(vm_name) + + # At least one network must be provided + if not net_list: + raise vimconn.vimconnException("At least one net must be provided to create a new VM") + + # image_id are several fields of the image_id + image_reference = self._get_image_reference(image_id) + self._check_subnets_for_vm(net_list) vm_nics = [] for idx, net in enumerate(net_list): - subnet_id=net['subnet_id'] + # Fault with subnet_id + # subnet_id=net['subnet_id'] + # subnet_id=net['net_id'] nic_name = vm_name + '-nic-'+str(idx) - vm_nic = self._create_nic(subnet_id, nic_name) - vm_nics.append({ 'id': str(vm_nic.id)}) + vm_nic = self._create_nic(net, nic_name, net.get('ip_address')) + vm_nics.append({'id': str(vm_nic.id)}) + net['vim_id'] = vm_nic.id try: + + # cloud-init configuration + # cloud config + if cloud_config: + config_drive, userdata = self._create_user_data(cloud_config) + custom_data = base64.b64encode(userdata.encode('utf-8')).decode('latin-1') + key_data = None + key_pairs = cloud_config.get("key-pairs") + if key_pairs: + key_data = key_pairs[0] + + if cloud_config.get("users"): + user_name = cloud_config.get("users")[0].get("name", "osm") + else: + user_name = "osm" # DEFAULT USER IS OSM + + os_profile = { + 'computer_name': vm_name, + 'admin_username': user_name, + 'linux_configuration': { + "disable_password_authentication": True, + "ssh": { + "public_keys": [{ + "path": "/home/{}/.ssh/authorized_keys".format(user_name), + "key_data": key_data + }] + } + }, + 'custom_data': custom_data + } + else: + os_profile = { + 'computer_name': vm_name, + 'admin_username': 'osm', + 'admin_password': 'Osm4u!', + } + vm_parameters = { 'location': self.region, - 'os_profile': { - 'computer_name': vm_name, # TODO if vm_name cannot be repeated add uuid4() suffix - 'admin_username': 'sergio', # TODO is it mandatory??? - 'linuxConfiguration': { - 'disablePasswordAuthentication': 'true', - 'ssh': { - 'publicKeys': [ - { - 'path': '/home/sergio/.ssh/authorized_keys', - 'keyData': self.pub_key - } - ] - } - } - - }, + 'os_profile': os_profile, 'hardware_profile': { - 'vm_size':flavor_id + 'vm_size': flavor_id }, 'storage_profile': { - 'image_reference': image_id - }, - 'network_profile': { - 'network_interfaces': [ - vm_nics[0] - ] + 'image_reference': image_reference } } + + # Add data disks if they are provided + if disk_list: + data_disks = [] + for lun_name, disk in enumerate(disk_list): + self.logger.debug("add disk size: %s, image: %s", disk.get("size"), disk.get("image_id")) + if not disk.get("image_id"): + data_disks.append({ + 'lun': lun_name, # You choose the value, depending of what is available for you + 'name': vm_name + "_data_disk-" + str(lun_name), + 'create_option': DiskCreateOption.empty, + 'disk_size_gb': disk.get("size") + }) + else: + # self.logger.debug("currently not able to create data disks from image for azure, ignoring") + data_disks.append({ + 'lun': lun_name, # You choose the value, depending of what is available for you + 'name': vm_name + "_data_disk-" + str(lun_name), + 'create_option': 'Attach', + 'disk_size_gb': disk.get("size"), + 'managed_disk': { + 'id': disk.get("image_id") + } + }) + + if data_disks: + vm_parameters["storage_profile"]["data_disks"] = data_disks + + # If the machine has several networks one must be marked as primary + # As it is not indicated in the interface the first interface will be marked as primary + if len(vm_nics) > 1: + for idx, vm_nic in enumerate(vm_nics): + if idx == 0: + vm_nics[0]['Primary'] = True + else: + vm_nics[idx]['Primary'] = False + + vm_parameters['network_profile'] = {'network_interfaces': vm_nics} + + self.logger.debug("create vm name: %s", vm_name) creation_result = self.conn_compute.virtual_machines.create_or_update( self.resource_group, - vm_name, + vm_name, vm_parameters ) + # creation_result.wait() + result = creation_result.result() + self.logger.debug("created vm name: %s", vm_name) + + if start: + self.conn_compute.virtual_machines.start( + self.resource_group, + vm_name) + # start_result.wait() + + return result.id, None - run_command_parameters = { - 'command_id': 'RunShellScript', # For linux, don't change it - 'script': [ - 'date > /home/sergio/test.txt' - ] + # run_command_parameters = { + # 'command_id': 'RunShellScript', # For linux, don't change it + # 'script': [ + # 'date > /tmp/test.txt' + # ] + # } + except Exception as e: + self.logger.debug('Exception creating new vminstance: %s', e, exc_info=True) + self._format_vimconn_exception(e) + + def _get_unused_vm_name(self, vm_name): + """ + Checks the vm name and in case it is used adds a suffix to the name to allow creation + :return: + """ + all_vms = self.conn_compute.virtual_machines.list(self.resource_group) + # Filter to vms starting with the indicated name + vms = list(filter(lambda vm: (vm.name.startswith(vm_name)), all_vms)) + vm_names = [str(vm.name) for vm in vms] + + # get the name with the first not used suffix + name_suffix = 0 + # name = subnet_name + "-" + str(name_suffix) + name = vm_name # first subnet created will have no prefix + while name in vm_names: + name_suffix += 1 + name = vm_name + "-" + str(name_suffix) + return name + + # It is necesary extract from image_id data to create the VM with this format + # 'image_reference': { + # 'publisher': vm_reference['publisher'], + # 'offer': vm_reference['offer'], + # 'sku': vm_reference['sku'], + # 'version': vm_reference['version'] + # }, + def _get_image_reference(self, image_id): + + try: + # The data input format example: + # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/ + # Publishers/Canonical/ArtifactTypes/VMImage/ + # Offers/UbuntuServer/ + # Skus/18.04-LTS/ + # Versions/18.04.201809110 + publisher = str(image_id.split('/')[8]) + offer = str(image_id.split('/')[12]) + sku = str(image_id.split('/')[14]) + version = str(image_id.split('/')[16]) + + return { + 'publisher': publisher, + 'offer': offer, + 'sku': sku, + 'version': version } - poller = self.conn_compute.virtual_machines.run_command( - self.resource_group, - vm_name, - run_command_parameters - ) - # TODO return a tuple (vm-ID, None) except Exception as e: - self.format_vimconn_exception(e) + raise vimconn.vimconnException( + "Unable to get image_reference from invalid image_id format: '{}'".format(image_id)) + + # Azure VM names can not have some special characters + def _check_vm_name(self, vm_name): + """ + Checks vm name, in case the vm has not allowed characters they are removed, not error raised + """ + + chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?." + + # First: the VM name max length is 64 characters + vm_name_aux = vm_name[:64] + + # Second: replace not allowed characters + for elem in chars_not_allowed_list: + # Check if string is in the main string + if elem in vm_name_aux: + # self.logger.debug('Dentro del IF') + # Replace the string + vm_name_aux = vm_name_aux.replace(elem, '-') + + return vm_name_aux def get_flavor_id_from_data(self, flavor_dict): - self.logger.debug("Getting flavor id from data") - self._reload_connection() - vm_sizes_list = [vm_size.serialize() for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region)] - cpus = flavor_dict['vcpus'] - memMB = flavor_dict['ram'] + self.logger.debug("getting flavor id from data, flavor_dict: %s", flavor_dict) + filter_dict = flavor_dict or {} + try: + self._reload_connection() + vm_sizes_list = [vm_size.serialize() for vm_size in + self.conn_compute.virtual_machine_sizes.list(self.region)] + + cpus = filter_dict.get('vcpus') or 0 + memMB = filter_dict.get('ram') or 0 + + # Filter + if self._config.get("flavors_pattern"): + filtered_sizes = [size for size in vm_sizes_list if size['numberOfCores'] >= cpus and + size['memoryInMB'] >= memMB and + re.search(self._config.get("flavors_pattern"), size["name"])] + else: + filtered_sizes = [size for size in vm_sizes_list if size['numberOfCores'] >= cpus and + size['memoryInMB'] >= memMB] - filteredSizes = [size for size in vm_sizes_list if size['numberOfCores'] > cpus and size['memoryInMB'] > memMB] - listedFilteredSizes = sorted(filteredSizes, key=lambda k: k['numberOfCores']) + # Sort + listedFilteredSizes = sorted(filtered_sizes, key=lambda k: (k['numberOfCores'], k['memoryInMB'], + k['resourceDiskSizeInMB'])) - return listedFilteredSizes[0]['name'] + if listedFilteredSizes: + return listedFilteredSizes[0]['name'] + raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict))) + + except Exception as e: + self._format_vimconn_exception(e) + + def _get_flavor_id_from_flavor_name(self, flavor_name): + + # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name)) + try: + self._reload_connection() + vm_sizes_list = [vm_size.serialize() for vm_size in + self.conn_compute.virtual_machine_sizes.list(self.region)] + + output_flavor = None + for size in vm_sizes_list: + if size['name'] == flavor_name: + output_flavor = size + + # None is returned if not found anything + return output_flavor + + except Exception as e: + self._format_vimconn_exception(e) def check_vim_connectivity(self): try: @@ -389,53 +864,382 @@ class vimconnector(vimconn.vimconnector): raise vimconn.vimconnException("Connectivity issue with Azure API: {}".format(e)) def get_network(self, net_id): - resGroup = self._get_resource_group_name_from_resource_id(net_id) - resName = self._get_resource_name_from_resource_id(net_id) - + + # self.logger.debug('get network id: {}'.format(net_id)) + # res_name = self._get_resource_name_from_resource_id(net_id) self._reload_connection() - vnet = self.conn_vnet.virtual_networks.get(resGroup, resName) - return vnet + filter_dict = {'name': net_id} + network_list = self.get_network_list(filter_dict) + + if not network_list: + raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id)) + else: + return network_list[0] + + def delete_network(self, net_id, created_items=None): + + self.logger.debug('deleting network {} - {}'.format(self.resource_group, net_id)) - def delete_network(self, net_id): - resGroup = self._get_resource_group_name_from_resource_id(net_id) - resName = self._get_resource_name_from_resource_id(net_id) - self._reload_connection() - self.conn_vnet.virtual_networks.delete(resGroup, resName) + res_name = self._get_resource_name_from_resource_id(net_id) + filter_dict = {'name': res_name} + network_list = self.get_network_list(filter_dict) + if not network_list: + raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id)) + + try: + # Subnet API fails (CloudError: Azure Error: ResourceNotFound) + # Put the initial virtual_network API + async_delete = self.conn_vnet.subnets.delete(self.resource_group, self.vnet_name, res_name) + async_delete.wait() + return net_id + + except CloudError as e: + if e.error.error and "notfound" in e.error.error.lower(): + raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id)) + else: + self._format_vimconn_exception(e) + except Exception as e: + self._format_vimconn_exception(e) - def delete_vminstance(self, vm_id): - resGroup = self._get_resource_group_name_from_resource_id(net_id) - resName = self._get_resource_name_from_resource_id(net_id) - + def delete_vminstance(self, vm_id, created_items=None): + """ Deletes a vm instance from the vim. + """ + self.logger.debug('deleting VM instance {} - {}'.format(self.resource_group, vm_id)) self._reload_connection() - self.conn_compute.virtual_machines.delete(resGroup, resName) + + try: + + res_name = self._get_resource_name_from_resource_id(vm_id) + vm = self.conn_compute.virtual_machines.get(self.resource_group, res_name) + + # Shuts down the virtual machine and releases the compute resources + # vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName) + # vm_stop.wait() + + vm_delete = self.conn_compute.virtual_machines.delete(self.resource_group, res_name) + vm_delete.wait() + self.logger.debug('deleted VM name: %s', res_name) + + # Delete OS Disk + os_disk_name = vm.storage_profile.os_disk.name + self.logger.debug('delete OS DISK: %s', os_disk_name) + self.conn_compute.disks.delete(self.resource_group, os_disk_name) + self.logger.debug('deleted OS DISK name: %s', os_disk_name) + + for data_disk in vm.storage_profile.data_disks: + self.logger.debug('delete data_disk: %s', data_disk.name) + self.conn_compute.disks.delete(self.resource_group, data_disk.name) + self.logger.debug('deleted OS DISK name: %s', data_disk.name) + + # After deleting VM, it is necessary to delete NIC, because if is not deleted delete_network + # does not work because Azure says that is in use the subnet + network_interfaces = vm.network_profile.network_interfaces + + for network_interface in network_interfaces: + + nic_name = self._get_resource_name_from_resource_id(network_interface.id) + nic_data = self.conn_vnet.network_interfaces.get( + self.resource_group, + nic_name) + + public_ip_name = None + exist_public_ip = nic_data.ip_configurations[0].public_ip_address + if exist_public_ip: + public_ip_id = nic_data.ip_configurations[0].public_ip_address.id + + # Delete public_ip + public_ip_name = self._get_resource_name_from_resource_id(public_ip_id) + + # Public ip must be deleted afterwards of nic that is attached + + self.logger.debug('delete NIC name: %s', nic_name) + nic_delete = self.conn_vnet.network_interfaces.delete(self.resource_group, nic_name) + nic_delete.wait() + self.logger.debug('deleted NIC name: %s', nic_name) + + # Delete list of public ips + if public_ip_name: + self.logger.debug('delete PUBLIC IP - ' + public_ip_name) + self.conn_vnet.public_ip_addresses.delete(self.resource_group, public_ip_name) + + except CloudError as e: + if e.error.error and "notfound" in e.error.error.lower(): + raise vimconn.vimconnNotFoundException("No vm instance found '{}'".format(vm_id)) + else: + self._format_vimconn_exception(e) + except Exception as e: + self._format_vimconn_exception(e) + + def action_vminstance(self, vm_id, action_dict, created_items={}): + """Send and action over a VM instance from VIM + Returns the vm_id if the action was successfully sent to the VIM + """ + + self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict)) + try: + self._reload_connection() + resName = self._get_resource_name_from_resource_id(vm_id) + if "start" in action_dict: + self.conn_compute.virtual_machines.start(self.resource_group, resName) + elif "stop" in action_dict or "shutdown" in action_dict or "shutoff" in action_dict: + self.conn_compute.virtual_machines.power_off(self.resource_group, resName) + elif "terminate" in action_dict: + self.conn_compute.virtual_machines.delete(self.resource_group, resName) + elif "reboot" in action_dict: + self.conn_compute.virtual_machines.restart(self.resource_group, resName) + return None + except CloudError as e: + if e.error.error and "notfound" in e.error.error.lower(): + raise vimconn.vimconnNotFoundException("No vm found '{}'".format(vm_id)) + else: + self._format_vimconn_exception(e) + except Exception as e: + self._format_vimconn_exception(e) + + def delete_flavor(self, flavor_id): + raise vimconn.vimconnAuthException("It is not possible to delete a FLAVOR in AZURE") + + def delete_tenant(self, tenant_id,): + raise vimconn.vimconnAuthException("It is not possible to delete a TENANT in AZURE") + + def delete_image(self, image_id): + raise vimconn.vimconnAuthException("It is not possible to delete a IMAGE in AZURE") def get_vminstance(self, vm_id): - resGroup = self._get_resource_group_name_from_resource_id(net_id) - resName = self._get_resource_name_from_resource_id(net_id) - + """ + Obtaing the vm instance data from v_id + """ + self.logger.debug("get vm instance: %s", vm_id) self._reload_connection() - vm=self.conn_compute.virtual_machines.get(resGroup, resName) + try: + resName = self._get_resource_name_from_resource_id(vm_id) + vm = self.conn_compute.virtual_machines.get(self.resource_group, resName) + except CloudError as e: + if e.error.error and "notfound" in e.error.error.lower(): + raise vimconn.vimconnNotFoundException("No vminstance found '{}'".format(vm_id)) + else: + self._format_vimconn_exception(e) + except Exception as e: + self._format_vimconn_exception(e) return vm def get_flavor(self, flavor_id): + """ + Obtains the flavor_data from the flavor_id + """ self._reload_connection() - for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region): - if vm_size.name == flavor_id : - return vm_size + self.logger.debug("get flavor from id: %s", flavor_id) + flavor_data = self._get_flavor_id_from_flavor_name(flavor_id) + if flavor_data: + flavor = { + 'id': flavor_id, + 'name': flavor_id, + 'ram': flavor_data['memoryInMB'], + 'vcpus': flavor_data['numberOfCores'], + 'disk': flavor_data['resourceDiskSizeInMB']/1024 + } + return flavor + else: + raise vimconn.vimconnNotFoundException("flavor '{}' not found".format(flavor_id)) + + def get_tenant_list(self, filter_dict={}): + """ Obtains the list of tenants + For the azure connector only the azure tenant will be returned if it is compatible + with filter_dict + """ + tenants_azure = [{'name': self.tenant, 'id': self.tenant}] + tenant_list = [] + + self.logger.debug("get tenant list: %s", filter_dict) + for tenant_azure in tenants_azure: + if filter_dict: + if filter_dict.get("id") and str(tenant_azure.get("id")) != filter_dict["id"]: + continue + if filter_dict.get("name") and str(tenant_azure.get("name")) != filter_dict["name"]: + continue + + tenant_list.append(tenant_azure) + + return tenant_list + def refresh_nets_status(self, net_list): + """Get the status of the networks + Params: the list of network identifiers + Returns a dictionary with: + net_id: #VIM id of this network + status: #Mandatory. Text with one of: + # DELETED (not found at vim) + # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) + # OTHER (Vim reported other status not understood) + # ERROR (VIM indicates an ERROR status) + # ACTIVE, INACTIVE, DOWN (admin down), + # BUILD (on building process) + # + error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR + vim_info: #Text with plain information obtained from vim (yaml.safe_dump) + + """ + + out_nets = {} + self._reload_connection() + + self.logger.debug("reload nets status net_list: %s", net_list) + for net_id in net_list: + try: + netName = self._get_net_name_from_resource_id(net_id) + resName = self._get_resource_name_from_resource_id(net_id) + + net = self.conn_vnet.subnets.get(self.resource_group, netName, resName) + + out_nets[net_id] = { + "status": self.provision_state2osm[net.provisioning_state], + "vim_info": str(net) + } + except CloudError as e: + if e.error.error and "notfound" in e.error.error.lower(): + self.logger.info("Not found subnet net_name: %s, subnet_name: %s", netName, resName) + out_nets[net_id] = { + "status": "DELETED", + "error_msg": str(e) + } + else: + self.logger.error("CloudError Exception %s when searching subnet", e) + out_nets[net_id] = { + "status": "VIM_ERROR", + "error_msg": str(e) + } + except vimconn.vimconnNotFoundException as e: + self.logger.error("VimConnNotFoundException %s when searching subnet", e) + out_nets[net_id] = { + "status": "DELETED", + "error_msg": str(e) + } + except Exception as e: + self.logger.error("Exception %s when searching subnet", e, exc_info=True) + out_nets[net_id] = { + "status": "VIM_ERROR", + "error_msg": str(e) + } + return out_nets + + def refresh_vms_status(self, vm_list): + """ Get the status of the virtual machines and their interfaces/ports + Params: the list of VM identifiers + Returns a dictionary with: + vm_id: # VIM id of this Virtual Machine + status: # Mandatory. Text with one of: + # DELETED (not found at vim) + # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) + # OTHER (Vim reported other status not understood) + # ERROR (VIM indicates an ERROR status) + # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running), + # BUILD (on building process), ERROR + # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address + # (ACTIVE:NoMgmtIP is not returned for Azure) + # + error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR + vim_info: #Text with plain information obtained from vim (yaml.safe_dump) + interfaces: list with interface info. Each item a dictionary with: + vim_interface_id - The ID of the interface + mac_address - The MAC address of the interface. + ip_address - The IP address of the interface within the subnet. + """ + + out_vms = {} + self._reload_connection() + + self.logger.debug("refresh vm status vm_list: %s", vm_list) + search_vm_list = vm_list or {} + + for vm_id in search_vm_list: + out_vm = {} + try: + res_name = self._get_resource_name_from_resource_id(vm_id) + + vm = self.conn_compute.virtual_machines.get(self.resource_group, res_name) + out_vm['vim_info'] = str(vm) + out_vm['status'] = self.provision_state2osm.get(vm.provisioning_state, 'OTHER') + if vm.provisioning_state == 'Succeeded': + # check if machine is running or stopped + instance_view = self.conn_compute.virtual_machines.instance_view(self.resource_group, + res_name) + for status in instance_view.statuses: + splitted_status = status.code.split("/") + if len(splitted_status) == 2 and splitted_status[0] == 'PowerState': + out_vm['status'] = self.power_state2osm.get(splitted_status[1], 'OTHER') + + network_interfaces = vm.network_profile.network_interfaces + out_vm['interfaces'] = self._get_vm_interfaces_status(vm_id, network_interfaces) + + except CloudError as e: + if e.error.error and "notfound" in e.error.error.lower(): + self.logger.debug("Not found vm id: %s", vm_id) + out_vm['status'] = "DELETED" + out_vm['error_msg'] = str(e) + out_vm['vim_info'] = None + else: + # maybe connection error or another type of error, return vim error + self.logger.error("Exception %s refreshing vm_status", e) + out_vm['status'] = "VIM_ERROR" + out_vm['error_msg'] = str(e) + out_vm['vim_info'] = None + except Exception as e: + self.logger.error("Exception %s refreshing vm_status", e, exc_info=True) + out_vm['status'] = "VIM_ERROR" + out_vm['error_msg'] = str(e) + out_vm['vim_info'] = None + + out_vms[vm_id] = out_vm + + return out_vms + + def _get_vm_interfaces_status(self, vm_id, interfaces): + """ + Gets the interfaces detail for a vm + :param interfaces: List of interfaces. + :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address + """ + try: + interface_list = [] + for network_interface in interfaces: + interface_dict = {} + nic_name = self._get_resource_name_from_resource_id(network_interface.id) + interface_dict['vim_interface_id'] = network_interface.id + + nic_data = self.conn_vnet.network_interfaces.get( + self.resource_group, + nic_name) + + ips = [] + if nic_data.ip_configurations[0].public_ip_address: + self.logger.debug("Obtain public ip address") + public_ip_name = self._get_resource_name_from_resource_id( + nic_data.ip_configurations[0].public_ip_address.id) + public_ip = self.conn_vnet.public_ip_addresses.get(self.resource_group, public_ip_name) + self.logger.debug("Public ip address is: %s", public_ip.ip_address) + ips.append(public_ip.ip_address) + + private_ip = nic_data.ip_configurations[0].private_ip_address + ips.append(private_ip) + + interface_dict['mac_address'] = nic_data.mac_address + interface_dict['ip_address'] = ";".join(ips) + interface_list.append(interface_dict) + + return interface_list + except Exception as e: + self.logger.error("Exception %s obtaining interface data for vm: %s, error: %s", vm_id, e, exc_info=True) + self._format_vimconn_exception(e) -# TODO refresh_nets_status ver estado activo -# TODO refresh_vms_status ver estado activo -# TODO get_vminstance_console for getting console if __name__ == "__main__": # Making some basic test - vim_id='azure' - vim_name='azure' + vim_id = 'azure' + vim_name = 'azure' needed_test_params = { "client_id": "AZURE_CLIENT_ID", "secret": "AZURE_SECRET", @@ -453,11 +1257,11 @@ if __name__ == "__main__": test_params[param] = value config = { - 'region_name': getenv("AZURE_REGION_NAME", 'westeurope'), - 'resource_group': getenv("AZURE_RESOURCE_GROUP"), - 'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"), - 'pub_key': getenv("AZURE_PUB_KEY", None), - 'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'), + 'region_name': getenv("AZURE_REGION_NAME", 'westeurope'), + 'resource_group': getenv("AZURE_RESOURCE_GROUP"), + 'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"), + 'pub_key': getenv("AZURE_PUB_KEY", None), + 'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'), } virtualMachine = { @@ -480,7 +1284,7 @@ if __name__ == "__main__": vnet_config = { 'subnet_address': '10.1.2.0/24', - #'subnet_name': 'subnet-oam' + # 'subnet_name': 'subnet-oam' } ########################### @@ -492,4 +1296,9 @@ if __name__ == "__main__": # azure.new_vminstance(virtualMachine['name'], virtualMachine['description'], virtualMachine['status'], # virtualMachine['image'], virtualMachine['hardware_profile']['vm_size'], subnets) - azure.get_flavor("Standard_A11") \ No newline at end of file + azure.new_network("mynet", None) + net_id = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/Microsoft."\ + "Network/virtualNetworks/test" + net_id_not_found = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/"\ + "Microsoft.Network/virtualNetworks/testALF" + azure.refresh_nets_status([net_id, net_id_not_found]) diff --git a/test/test_RO.py b/test/test_RO.py index 932853c5..a777f693 100755 --- a/test/test_RO.py +++ b/test/test_RO.py @@ -20,6 +20,15 @@ # ## +# DEBUG WITH PDB +from os import getenv +if getenv('OSMRO_PDB_DEBUG'): + import sys + print(sys.path) + import pdb + pdb.set_trace() + + """ Module for testing openmano functionality. It uses openmanoclient.py for invoking openmano """ @@ -97,6 +106,7 @@ class test_VIM_datacenter_tenant_operations(test_base): self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index, inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 + logger.debug("Test create tenant") tenant = test_config["client"].create_tenant(name=self.__class__.tenant_name, description=self.__class__.tenant_name) logger.debug("{}".format(tenant)) @@ -291,12 +301,13 @@ class test_vimconn_connect(test_base): vca_object = test_config["vim_conn"].connect() logger.debug("{}".format(vca_object)) self.assertIsNotNone(vca_object) - elif test_config['vimtype'] == 'openstack': + elif test_config['vimtype'] in ('openstack', 'azure'): test_config["vim_conn"]._reload_connection() network_list = test_config["vim_conn"].get_network_list() logger.debug("{}".format(network_list)) self.assertIsNotNone(network_list) + class test_vimconn_new_network(test_base): network_name = None @@ -311,9 +322,10 @@ class test_vimconn_new_network(test_base): network, _ = test_config["vim_conn"].new_network(net_name=self.__class__.network_name, net_type=network_type) self.__class__.network_id = network - logger.debug("{}".format(network)) + logger.debug("Created network {}".format(network)) network_list = test_config["vim_conn"].get_network_list() + logger.debug("Network list {}".format(network_list)) for net in network_list: if self.__class__.network_name in net.get('name'): self.assertIn(self.__class__.network_name, net.get('name')) @@ -326,12 +338,17 @@ class test_vimconn_new_network(test_base): else: logger.info("Failed to delete network id {}".format(self.__class__.network_id)) + network_list = test_config["vim_conn"].get_network_list() + logger.debug("Network list after deletion {}".format(network_list)) + def test_010_new_network_by_types(self): delete_net_ids = [] network_types = ['data','bridge','mgmt'] self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index, inspect.currentframe().f_code.co_name) + network_list = test_config["vim_conn"].get_network_list() + logger.debug("Network list at start {}".format(network_list)) self.__class__.test_index += 1 for net_type in network_types: self.__class__.network_name = _get_random_string(20) @@ -357,6 +374,8 @@ class test_vimconn_new_network(test_base): logger.info("Network id {} sucessfully deleted".format(net_id)) else: logger.info("Failed to delete network id {}".format(net_id)) + network_list = test_config["vim_conn"].get_network_list() + logger.debug("Network list after test {}".format(network_list)) def test_020_new_network_by_ipprofile(self): test_directory_content = os.listdir(test_config["test_directory"]) @@ -371,15 +390,14 @@ class test_vimconn_new_network(test_base): with open(vnfd, 'r') as stream: vnf_descriptor = yaml.load(stream) - internal_connections_list = vnf_descriptor['vnf']['internal-connections'] + #internal_connections_list = vnf_descriptor['vnf']['internal-connections'] + internal_connections_list = vnf_descriptor['vnfd-catalog']['vnfd'][0]['ip-profiles'] for item in internal_connections_list: - if 'ip-profile' in item: - version = item['ip-profile']['ip-version'] - dhcp_count = item['ip-profile']['dhcp']['count'] - dhcp_enabled = item['ip-profile']['dhcp']['enabled'] - dhcp_start_address = item['ip-profile']['dhcp']['start-address'] - subnet_address = item['ip-profile']['subnet-address'] - + version = item['ip-version'] + dhcp_count = item['dhcp-params']['count'] + dhcp_enabled = item['dhcp-params']['enabled'] + dhcp_start_address = item['dhcp-params']['start-address'] + subnet_address = item['subnet-address'] self.__class__.network_name = _get_random_string(20) ip_profile = {'dhcp_count': dhcp_count, @@ -399,6 +417,7 @@ class test_vimconn_new_network(test_base): logger.debug("{}".format(network)) network_list = test_config["vim_conn"].get_network_list() + logger.debug("Created network by ip_profile {}".format(network_list)) for net in network_list: if self.__class__.network_name in net.get('name'): self.assertIn(self.__class__.network_name, net.get('name')) @@ -489,8 +508,17 @@ class test_vimconn_new_network(test_base): self.__class__.test_index += 1 # refresh net status + # if azure network name must have the following format + if test_config['vimtype'] == 'azure': + unknown_net_id = "/" + "/".join(["subscriptions", test_config["vim_conn"].subscription_id, + "resourceGroups", test_config["vim_conn"].resource_group, + "providers", "Microsoft.Network", + "virtualNetworks", test_config["vim_conn"].vnet_name, + "subnets", unknown_net_id]) + #unknown_net_id = "/subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/resourceGroups/osmRG/providers/Microsoft.Network/virtualNetworks/osm_vnet/subnets/unnkown_net" + net_dict = test_config["vim_conn"].refresh_nets_status([unknown_net_id]) - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): self.assertEqual(net_dict[unknown_net_id]['status'], 'DELETED') else: # TODO : Fix vmware connector to return status DELETED as per vimconn.py @@ -530,7 +558,8 @@ class test_vimconn_get_network_list(test_base): self.assertIn(self.__class__.network_name, net.get('name')) self.assertEqual(net.get('type'), self.__class__.net_type) self.assertEqual(net.get('status'), 'ACTIVE') - self.assertEqual(net.get('shared'), False) + if test_config['vimtype'] != 'azure': + self.assertEqual(net.get('shared'), False) def test_010_get_network_list_by_name(self): self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], @@ -538,7 +567,7 @@ class test_vimconn_get_network_list(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): network_name = test_config['vim_conn'].get_network(self.__class__.network_id)['name'] else: network_name = test_config['vim_conn'].get_network_name_by_id(self.__class__.network_id) @@ -572,7 +601,7 @@ class test_vimconn_get_network_list(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): network_name = test_config['vim_conn'].get_network(self.__class__.network_id)['name'] else: network_name = test_config['vim_conn'].get_network_name_by_id(self.__class__.network_id) @@ -592,7 +621,7 @@ class test_vimconn_get_network_list(test_base): self.__class__.test_index += 1 tenant_list = test_config["vim_conn"].get_tenant_list() - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): network_name = test_config['vim_conn'].get_network(self.__class__.network_id)['name'] else: network_name = test_config['vim_conn'].get_network_name_by_id(self.__class__.network_id) @@ -616,7 +645,7 @@ class test_vimconn_get_network_list(test_base): self.__class__.test_index += 1 status = 'ACTIVE' - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): network_name = test_config['vim_conn'].get_network(self.__class__.network_id)['name'] else: network_name = test_config['vim_conn'].get_network_name_by_id(self.__class__.network_id) @@ -788,17 +817,23 @@ class test_vimconn_new_flavor(test_base): flavor_id = None def test_000_new_flavor(self): - flavor_data = {'name': _get_random_string(20), 'ram': 1024, 'vpcus': 1, 'disk': 10} + flavor_data = {'name': _get_random_string(20), 'ram': 1024, 'vcpus': 1, 'disk': 10} self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index, inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - # create new flavor - self.__class__.flavor_id = test_config["vim_conn"].new_flavor(flavor_data) - self.assertIsInstance(self.__class__.flavor_id, (str, unicode)) - self.assertIsInstance(uuid.UUID(self.__class__.flavor_id), uuid.UUID) + if test_config['vimtype'] == 'azure': + with self.assertRaises(Exception) as context: + test_config["vim_conn"].new_flavor(flavor_data) + + self.assertEqual((context.exception).http_code, 401) + else: + # create new flavor + self.__class__.flavor_id = test_config["vim_conn"].new_flavor(flavor_data) + self.assertIsInstance(self.__class__.flavor_id, (str, unicode)) + self.assertIsInstance(uuid.UUID(self.__class__.flavor_id), uuid.UUID) def test_010_delete_flavor(self): self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], @@ -807,12 +842,18 @@ class test_vimconn_new_flavor(test_base): self.__class__.test_index += 1 # delete flavor - result = test_config["vim_conn"].delete_flavor(self.__class__.flavor_id) - if result: - logger.info("Flavor id {} sucessfully deleted".format(result)) + if test_config['vimtype'] == 'azure': + with self.assertRaises(Exception) as context: + test_config["vim_conn"].delete_flavor(self.__class__.flavor_id) + + self.assertEqual((context.exception).http_code, 401) else: - logger.error("Failed to delete flavor id {}".format(result)) - raise Exception ("Failed to delete created flavor") + result = test_config["vim_conn"].delete_flavor(self.__class__.flavor_id) + if result: + logger.info("Flavor id {} sucessfully deleted".format(result)) + else: + logger.error("Failed to delete flavor id {}".format(result)) + raise Exception ("Failed to delete created flavor") def test_020_new_flavor_negative(self): Invalid_flavor_data = {'ram': '1024', 'vcpus': 2.0, 'disk': 2.0} @@ -824,8 +865,10 @@ class test_vimconn_new_flavor(test_base): with self.assertRaises(Exception) as context: test_config["vim_conn"].new_flavor(Invalid_flavor_data) - - self.assertEqual((context.exception).http_code, 400) + if test_config['vimtype'] != 'azure': + self.assertEqual((context.exception).http_code, 400) + else: + self.assertEqual((context.exception).http_code, 401) def test_030_delete_flavor_negative(self): Non_exist_flavor_id = str(uuid.uuid4()) @@ -838,7 +881,10 @@ class test_vimconn_new_flavor(test_base): with self.assertRaises(Exception) as context: test_config["vim_conn"].delete_flavor(Non_exist_flavor_id) - self.assertEqual((context.exception).http_code, 404) + if test_config['vimtype'] != 'azure': + self.assertEqual((context.exception).http_code, 404) + else: + self.assertEqual((context.exception).http_code, 401) # class test_vimconn_new_image(test_base): # @@ -931,27 +977,38 @@ class test_vimconn_get_image_list(test_base): self.__class__.test_index, inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - image_list = test_config["vim_conn"].get_image_list() - for item in image_list: - if 'name' in item: - self.__class__.image_name = item['name'] - self.__class__.image_id = item['id'] - self.assertIsInstance(self.__class__.image_name, (str, unicode)) - self.assertIsInstance(self.__class__.image_id, (str, unicode)) + if test_config['vimtype'] != 'azure': + image_list = test_config["vim_conn"].get_image_list() + logger.debug("{}: Result image list: {}".format(self.__class__.test_text, image_list)) + + for item in image_list: + if 'name' in item: + self.__class__.image_name = item['name'] + self.__class__.image_id = item['id'] + self.assertIsInstance(self.__class__.image_name, (str, unicode)) + self.assertIsInstance(self.__class__.image_id, (str, unicode)) + else: + with self.assertRaises(Exception) as context: + image_list = test_config["vim_conn"].get_image_list() + self.assertEqual((context.exception).http_code, 401) + logger.debug(self.__class__.test_text + "Exception unauthorized: " + str(context.exception)) def test_010_get_image_list_by_name(self): self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index, inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 + self.__class__.image_name = test_config['image_name'] + logger.debug("{}: Image name: {}".format(self.__class__.test_text, self.__class__.image_name)) image_list = test_config["vim_conn"].get_image_list({'name': self.__class__.image_name}) + logger.debug("{}: Result image list: {}".format(self.__class__.test_text, image_list)) for item in image_list: self.assertIsInstance(item['id'], (str, unicode)) self.assertIsInstance(item['name'], (str, unicode)) - self.assertEqual(item['id'], self.__class__.image_id) + #self.assertEqual(item['id'], self.__class__.image_id) self.assertEqual(item['name'], self.__class__.image_name) def test_020_get_image_list_by_id(self): @@ -1031,9 +1088,13 @@ class test_vimconn_new_vminstance(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'vpci': vpci, 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] + net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'vpci': vpci, + 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] - self.__class__.instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, image_id=self.__class__.image_id, flavor_id=flavor_id, net_list=net_list) + self.__class__.instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', + start=False, + image_id=self.__class__.image_id, + flavor_id=flavor_id, net_list=net_list) self.assertIsInstance(self.__class__.instance_id, (str, unicode)) @@ -1050,9 +1111,12 @@ class test_vimconn_new_vminstance(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'port_security': True, 'model': model_name, 'type': 'virtual', 'net_id': self.__class__.network_id}] + net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'port_security': True, + 'model': model_name, 'type': 'virtual', 'net_id': self.__class__.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, image_id=self.__class__.image_id,flavor_id=flavor_id,net_list=net_list) + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=self.__class__.image_id, + flavor_id=flavor_id,net_list=net_list) self.assertIsInstance(instance_id, (str, unicode)) @@ -1074,11 +1138,12 @@ class test_vimconn_new_vminstance(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - net_list = [{'use': net_use, 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] + net_list = [{'use': net_use, 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', + 'net_id': self.__class__.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, image_id=self.__class__.image_id,disk_list=None, - flavor_id=flavor_id, - net_list=net_list) + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=self.__class__.image_id,disk_list=None, + flavor_id=flavor_id, net_list=net_list) self.assertIsInstance(instance_id, (str, unicode)) # Deleting created vm instance @@ -1108,7 +1173,7 @@ class test_vimconn_new_vminstance(test_base): net_list=net_list) self.assertEqual(type(instance_id),str) - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): # create network of type data network_name = _get_random_string(20) net_type = 'data' @@ -1119,9 +1184,9 @@ class test_vimconn_new_vminstance(test_base): 'type': _type, 'net_id': network_id}] instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, - image_id=self.__class__.image_id, disk_list=None, - flavor_id=flavor_id, - net_list=net_list) + image_id=self.__class__.image_id, disk_list=None, + flavor_id=flavor_id, + net_list=net_list) self.assertEqual(type(instance_id), unicode) @@ -1142,11 +1207,12 @@ class test_vimconn_new_vminstance(test_base): name = 'eth0' user_name = 'test_user' - key_pairs = ['ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy2w9GHMKKNkpCmrDK2ovc3XBYDETuLWwaW24S+feHhLBQiZlzh3gSQoINlA+2ycM9zYbxl4BGzEzpTVyCQFZv5PidG4m6ox7LR+KYkDcITMyjsVuQJKDvt6oZvRt6KbChcCi0n2JJD/oUiJbBFagDBlRslbaFI2mmqmhLlJ5TLDtmYxzBLpjuX4m4tv+pdmQVfg7DYHsoy0hllhjtcDlt1nn05WgWYRTu7mfQTWfVTavu+OjIX3e0WN6NW7yIBWZcE/Q9lC0II3W7PZDE3QaT55se4SPIO2JTdqsx6XGbekdG1n6adlduOI27sOU5m4doiyJ8554yVbuDB/z5lRBD alfonso.tiernosepulveda@telefonica.com'] + key_pairs = ['ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDtAjl5R+GSKP3gFrdFxgizKEUzhXKQbyjaxJH9thsK 0/fDiYlaNEjvijgPgiVZkfwvqgWeLprPcpzL2j4jvmmSJ3+7C8ihCwObWP0VUiuewmbIINBPAR0RqusjMRyPsa+q0asFBPOoZLx3Cv3vzmC1AA3mKuCNeT EuA0rlWhDIOVwMcU5sP1grnmuexQB8HcR7BdKcA9y08pTwnCQR8vmtW77SRkaxEGXm4Gnw5qw8Z27mHdk2wWS2SnbVH7aFwWvDXc6jjf5TpEWypdr/EAPC +eJipeS2Oa4FsntEqAu3Fz6gp/9ub8uNqgCgHfMzs6FhYpZpipwS0hXYyF6eVsSx osm@osm'] - users_data = [{'key-pairs': ['ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy2w9GHMKKNkpCmrDK2ovc3XBYDETuLWwaW24S+feHhLBQiZlzh3gSQoINlA+2ycM9zYbxl4BGzEzpTVyCQFZv5PidG4m6ox7LR+KYkDcITMyjsVuQJKDvt6oZvRt6KbChcCi0n2JJD/oUiJbBFagDBlRslbaFI2mmqmhLlJ5TLDtmYxzBLpjuX4m4tv+pdmQVfg7DYHsoy0hllhjtcDlt1nn05WgWYRTu7mfQTWfVTavu+OjIX3e0WN6NW7yIBWZcE/Q9lC0II3W7PZDE3QaT55se4SPIO2JTdqsx6XGbekdG1n6adlduOI27sOU5m4doiyJ8554yVbuDB/z5lRBD alfonso.tiernosepulveda@telefonica.com'], 'name': user_name}] + users_data = [{'key-pairs': ['ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDtAjl5R+GSKP3gFrdFxgizKEUzhXKQbyjaxJH9thsK0/fDiYlaNEjvijgPgiVZkfwvqgWeLprPcpzL2j4jvmmSJ3+7C8ihCwObWP0VUiuewmbIINBPAR0RqusjMRyPsa+q0asFBPOoZLx3Cv3vzmC1AA3mKuCNeTEuA0rlWhDIOVwMcU5sP1grnmuexQB8HcR7BdKcA9y08pTwnCQR8vmtW77SRkaxEGXm4Gnw5qw8Z27mHdk2wWS2SnbVH7aFwWvDXc6jjf5TpEWypdr/EAPC+eJipeS2Oa4FsntEqAu3Fz6gp/9ub8uNqgCgHfMzs6FhYpZpipwS0hXYyF6eVsSx osm@osm'], 'name': 'cloudinit'}] cloud_data = {'config-files': [{'content': 'auto enp0s3\niface enp0s3 inet dhcp\n', 'dest': '/etc/network/interfaces.d/enp0s3.cfg', 'owner': 'root:root', 'permissions': '0644'}, {'content': '#! /bin/bash\nls -al >> /var/log/osm.log\n', 'dest': '/etc/rc.local', 'permissions': '0755'}, {'content': 'file content', 'dest': '/etc/test_delete'}], 'boot-data-drive': True, 'key-pairs': key_pairs, 'users': users_data } + #cloud_data = {'users': users_data } # create new flavor flavor_id = test_config["vim_conn"].new_flavor(flavor_data) @@ -1156,10 +1222,13 @@ class test_vimconn_new_vminstance(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] + net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'port_security': True, + 'type': 'virtual', 'net_id': self.__class__.network_id}] instance_id, _ = test_config["vim_conn"].new_vminstance(name='Cloud_vm', description='', start=False, - image_id=self.__class__.image_id, flavor_id=flavor_id,net_list=net_list,cloud_config=cloud_data) + image_id=self.__class__.image_id, + flavor_id=flavor_id,net_list=net_list, + cloud_config=cloud_data) self.assertIsInstance(instance_id, (str, unicode)) @@ -1182,12 +1251,13 @@ class test_vimconn_new_vminstance(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] + net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'port_security': True, + 'type': 'virtual', 'net_id': self.__class__.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='VM_test1', description='', start=False, image_id=self.__class__.image_id, - flavor_id=flavor_id, - net_list=net_list, - disk_list=device_data) + instance_id, _ = test_config["vim_conn"].new_vminstance(name='VM_test1', description='', start=False, + image_id=self.__class__.image_id, + flavor_id=flavor_id, net_list=net_list, + disk_list=device_data) self.assertIsInstance(instance_id, (str, unicode)) # Deleting created vm instance @@ -1205,12 +1275,14 @@ class test_vimconn_new_vminstance(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] + net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'port_security': True, + 'type': 'virtual', 'net_id': self.__class__.network_id}] with self.assertRaises(Exception) as context: - test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, image_id=unknown_image_id, - flavor_id=unknown_flavor_id, - net_list=net_list) + test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=unknown_image_id, + flavor_id=unknown_flavor_id, + net_list=net_list) self.assertIn((context.exception).http_code, (400, 404)) @@ -1267,7 +1339,7 @@ class test_vimconn_new_vminstance(test_base): if attr == 'interfaces': self.assertEqual(type(vm_info[self.__class__.instance_id][attr]), list) - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): vpci = "0000:00:11.0" name = "eth0" @@ -1276,9 +1348,12 @@ class test_vimconn_new_vminstance(test_base): # create new flavor flavor_id = test_config["vim_conn"].new_flavor(flavor_data) # create new vm instance - net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'vpci': vpci, 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] + net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'vpci': vpci, + 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, image_id=self.__class__.image_id, flavor_id=flavor_id, net_list=net_list) + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=self.__class__.image_id, + flavor_id=flavor_id, net_list=net_list) time.sleep(30) vm_list = [] @@ -1311,7 +1386,7 @@ class test_vimconn_new_vminstance(test_base): if test_config['vimtype'] == 'vmware': self.assertEqual(vm_dict,{}) - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): self.assertEqual(vm_dict[unknown_id]['status'], 'DELETED') def test_110_action_vminstance(self): @@ -1328,7 +1403,7 @@ class test_vimconn_new_vminstance(test_base): {action: None}) self.assertEqual(instance_id, self.__class__.instance_id) - if test_config['vimtype'] == 'openstack': + if test_config['vimtype'] in ('openstack', 'azure'): # create new vm instance vpci = "0000:00:11.0" name = "eth0" @@ -1338,11 +1413,17 @@ class test_vimconn_new_vminstance(test_base): # create new flavor flavor_id = test_config["vim_conn"].new_flavor(flavor_data) - net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'vpci': vpci, 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] + net_list = [{'use': self.__class__.net_type, 'name': name, 'floating_ip': False, 'vpci': vpci, + 'port_security': True, 'type': 'virtual', 'net_id': self.__class__.network_id}] - new_instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, image_id=self.__class__.image_id, flavor_id=flavor_id, net_list=net_list) + new_instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', + start=False, image_id=self.__class__.image_id, + flavor_id=flavor_id, net_list=net_list) - action_list = ['shutdown','start','shutoff','rebuild','start','pause','start'] + if test_config['vimtype'] == 'openstack': + action_list = ['shutdown','start','shutoff','rebuild','start','pause','start'] + else: + action_list = ['shutdown','start','stop','start','shutoff','start','reboot'] # various action on vminstace for action in action_list: @@ -1402,9 +1483,12 @@ class test_vimconn_new_vminstance(test_base): self.assertEqual(sriov_net_name, list_item.get('name')) self.__class__.sriov_network_id = list_item.get('id') - net_list = [{'use': 'data', 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'VF', 'net_id': self.__class__.sriov_network_id}] + net_list = [{'use': 'data', 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'VF', + 'net_id': self.__class__.sriov_network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_sriov_vm', description='', start=False, image_id=self.__class__.image_id, flavor_id=flavor_id, net_list=net_list) + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_sriov_vm', description='', start=False, + image_id=self.__class__.image_id, flavor_id=flavor_id, + net_list=net_list) self.assertIsInstance(instance_id, (str, unicode)) @@ -1426,6 +1510,7 @@ class test_vimconn_get_tenant_list(test_base): # Getting tenant list tenant_list = test_config["vim_conn"].get_tenant_list() + logger.debug(self.__class__.test_text + "Tenant list: " + str(tenant_list)) for item in tenant_list: if test_config['tenant'] == item['name']: @@ -1441,6 +1526,7 @@ class test_vimconn_get_tenant_list(test_base): # Getting filter tenant list by its id filter_tenant_list = test_config["vim_conn"].get_tenant_list({'id': self.__class__.tenant_id}) + logger.debug(self.__class__.test_text + "Tenant list: " + str(filter_tenant_list)) for item in filter_tenant_list: self.assertIsInstance(item['id'], (str, unicode)) @@ -1454,6 +1540,7 @@ class test_vimconn_get_tenant_list(test_base): # Getting filter tenant list by its name filter_tenant_list = test_config["vim_conn"].get_tenant_list({'name': test_config['tenant']}) + logger.debug(self.__class__.test_text + "Tenant list: " + str(filter_tenant_list)) for item in filter_tenant_list: self.assertIsInstance(item['name'], (str, unicode)) @@ -1468,6 +1555,7 @@ class test_vimconn_get_tenant_list(test_base): # Getting filter tenant list by its name and id filter_tenant_list = test_config["vim_conn"].get_tenant_list({'name': test_config['tenant'], 'id': self.__class__.tenant_id}) + logger.debug(self.__class__.test_text + "Tenant list: " + str(filter_tenant_list)) for item in filter_tenant_list: self.assertIsInstance(item['name'], (str, unicode)) @@ -1485,6 +1573,7 @@ class test_vimconn_get_tenant_list(test_base): filter_tenant_list = test_config["vim_conn"].get_tenant_list({'name': non_exist_tenant_name, 'id': non_exist_tenant_id}) + logger.debug(self.__class__.test_text + "Tenant list: " + str(filter_tenant_list)) self.assertEqual(filter_tenant_list, []) @@ -1499,10 +1588,16 @@ class test_vimconn_new_tenant(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - self.__class__.tenant_id = test_config["vim_conn"].new_tenant(tenant_name, "") - time.sleep(15) + if test_config['vimtype'] != 'azure': + self.__class__.tenant_id = test_config["vim_conn"].new_tenant(tenant_name, "") + time.sleep(15) - self.assertIsInstance(self.__class__.tenant_id, (str, unicode)) + self.assertIsInstance(self.__class__.tenant_id, (str, unicode)) + else: + with self.assertRaises(Exception) as context: + test_config["vim_conn"].new_tenant(self.__class__.tenant_id, "") + self.assertEqual((context.exception).http_code, 401) + logger.debug(self.__class__.test_text + "Exception unauthorized: " + str(context.exception)) def test_010_new_tenant_negative(self): @@ -1515,7 +1610,11 @@ class test_vimconn_new_tenant(test_base): with self.assertRaises(Exception) as context: test_config["vim_conn"].new_tenant(Invalid_tenant_name, "") - self.assertEqual((context.exception).http_code, 400) + if test_config['vimtype'] != 'azure': + self.assertEqual((context.exception).http_code, 400) + else: + self.assertEqual((context.exception).http_code, 401) + logger.debug(self.__class__.test_text + "Exception unauthorized: " + str(context.exception)) def test_020_delete_tenant(self): @@ -1524,21 +1623,30 @@ class test_vimconn_new_tenant(test_base): inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 - tenant_id = test_config["vim_conn"].delete_tenant(self.__class__.tenant_id) - - self.assertIsInstance(tenant_id, (str, unicode)) + if test_config['vimtype'] != 'azure': + tenant_id = test_config["vim_conn"].delete_tenant(self.__class__.tenant_id) + self.assertIsInstance(tenant_id, (str, unicode)) + else: + with self.assertRaises(Exception) as context: + test_config["vim_conn"].delete_tenant(self.__class__.tenant_id) + self.assertEqual((context.exception).http_code, 401) + logger.debug(self.__class__.test_text + "Exception unauthorized: " + str(context.exception)) def test_030_delete_tenant_negative(self): - Non_exist_tenant_name = 'Test_30_tenant' + non_exist_tenant_name = 'Test_30_tenant' self.__class__.test_text = "{}.{}. TEST {}".format(test_config["test_number"], self.__class__.test_index, inspect.currentframe().f_code.co_name) self.__class__.test_index += 1 with self.assertRaises(Exception) as context: - test_config["vim_conn"].delete_tenant(Non_exist_tenant_name) + test_config["vim_conn"].delete_tenant(non_exist_tenant_name) - self.assertEqual((context.exception).http_code, 404) + if test_config['vimtype'] != 'azure': + self.assertEqual((context.exception).http_code, 404) + else: + self.assertEqual((context.exception).http_code, 401) + logger.debug(self.__class__.test_text + "Exception unauthorized: " + str(context.exception)) def get_image_id(): @@ -1789,7 +1897,8 @@ class test_vimconn_vminstance_by_existing_disk(test_base): net_list = [{'use': 'bridge', 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', image_id=image_id, + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=image_id, flavor_id=flavor_id, net_list=net_list,disk_list=disk_list) self.assertEqual(type(instance_id),str) @@ -1816,7 +1925,8 @@ class test_vimconn_vminstance_by_existing_disk(test_base): net_list = [{'use': 'bridge', 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', image_id=image_id, + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=image_id, flavor_id=flavor_id, net_list=net_list,disk_list=disk_list) self.assertEqual(type(instance_id),str) @@ -1847,7 +1957,8 @@ class test_vimconn_vminstance_by_existing_disk(test_base): net_list = [{'use': 'bridge', 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', image_id=image_id, + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=image_id, flavor_id=flavor_id, net_list=net_list,disk_list=disk_list ) self.assertEqual(type(instance_id),str) @@ -1897,7 +2008,8 @@ class test_vimconn_vminstance_by_affinity_anti_affinity(test_base): net_list = [{'use': 'bridge', 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', image_id=image_id, + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=image_id, flavor_id=flavor_id, net_list=net_list,availability_zone_index=1, availability_zone_list=['HG_174','HG_175']) @@ -1927,8 +2039,8 @@ class test_vimconn_vminstance_by_numa_affinity(test_base): def test_000_vminstance_by_numa_affinity(self): flavor_data = {'extended': {'numas': [{'paired-threads-id': [['1', '3'], ['2', '4']], - ' paired-threads': 2, 'memory': 1}]}, - 'ram': 1024, 'vcpus': 1, 'disk': 10} + ' paired-threads': 2, 'memory': 1}]}, + 'ram': 1024, 'vcpus': 1, 'disk': 10} name = "eth10" # create new flavor @@ -1945,7 +2057,8 @@ class test_vimconn_vminstance_by_numa_affinity(test_base): net_list = [{'use': 'bridge', 'name': name, 'floating_ip': False, 'port_security': True, 'type': 'virtual', 'net_id': self.network_id}] - instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', image_id=image_id, + instance_id, _ = test_config["vim_conn"].new_vminstance(name='Test1_vm', description='', start=False, + image_id=image_id, flavor_id=flavor_id, net_list=net_list) self.assertEqual(type(instance_id),str) @@ -2131,7 +2244,8 @@ def test_vimconnector(args): test_config['sriov_net_name'] = args.sriov_net_name # vmware connector obj - test_config['vim_conn'] = vim.vimconnector(name=org_name, tenant_name=tenant_name, user=org_user,passwd=org_passwd, url=vim_url, config=config_params) + test_config['vim_conn'] = vim.vimconnector(name=org_name, tenant_name=tenant_name, user=org_user, + passwd=org_passwd, url=vim_url, config=config_params) elif args.vimtype == "aws": import vimconn_aws as vim @@ -2163,6 +2277,33 @@ def test_vimconnector(args): elif args.vimtype == "openvim": import vimconn_openvim as vim + elif args.vimtype == "azure": + import vimconn_azure as vim + + test_config["test_directory"] = os.path.dirname(__file__) + "/RO_tests" + + tenant_name = args.tenant_name + test_config['tenant'] = tenant_name + config_params = yaml.load(args.config_param) + os_user = config_params.get('user') + os_passwd = config_params.get('passwd') + vim_url = args.endpoint_url + test_config['image_path'] = args.image_path + test_config['image_name'] = args.image_name + #test_config['sriov_net_name'] = args.sriov_net_name + args_log_level = "DEBUG" if args.debug else "INFO" + + # azure connector obj + vim_persistent_info = {} + test_config['vim_conn'] = vim.vimconnector( + uuid="test-uuid-1", name="VIO-azure", + tenant_id=None, tenant_name=tenant_name, + url=vim_url, url_admin=None, + user=os_user, passwd=os_passwd, log_level= args_log_level, + config=config_params, persistent_info=vim_persistent_info + ) + test_config['vim_conn'].debug = "true" + else: logger.critical("vimtype '{}' not supported".format(args.vimtype)) sys.exit(1) @@ -2468,7 +2609,7 @@ if __name__=="__main__": vimconn_parser.set_defaults(func=test_vimconnector) # Mandatory arguments mandatory_arguments = vimconn_parser.add_argument_group('mandatory arguments') - mandatory_arguments.add_argument('--vimtype', choices=['vmware', 'aws', 'openstack', 'openvim'], required=True, + mandatory_arguments.add_argument('--vimtype', choices=['vmware', 'aws', 'openstack', 'openvim','azure'], required=True, help='Set the vimconnector type to test') mandatory_arguments.add_argument('-c', '--config', dest='config_param', required=True, help='Set the vimconnector specific config parameters in dictionary format')