X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=vimconn_openstack.py;fp=vimconn_openstack.py;h=0000000000000000000000000000000000000000;hb=2c290ca4088492a3c32bb6ab218d0004da68f6ea;hp=b501d9da28ca3605d4b7a3600e538936aa28d7f4;hpb=06e6c396413630640cafae3488442a0869f1642d;p=osm%2FRO.git diff --git a/vimconn_openstack.py b/vimconn_openstack.py deleted file mode 100644 index b501d9da..00000000 --- a/vimconn_openstack.py +++ /dev/null @@ -1,1336 +0,0 @@ -# -*- coding: utf-8 -*- - -## -# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. -# This file is part of openmano -# All Rights Reserved. -# -# 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. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact with: nfvlabs@tid.es -## - -''' -osconnector implements all the methods to interact with openstack using the python-client. -''' -__author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research" -__date__ ="$22-jun-2014 11:19:29$" - -import vimconn -import json -import yaml -import logging -import netaddr -import time -import yaml -import random - -from novaclient import client as nClient_v2, exceptions as nvExceptions -from novaclient import api_versions -import keystoneclient.v2_0.client as ksClient_v2 -from novaclient.v2.client import Client as nClient -import keystoneclient.v3.client as ksClient -import keystoneclient.exceptions as ksExceptions -import glanceclient.v2.client as glClient -import glanceclient.client as gl1Client -import glanceclient.exc as gl1Exceptions -import cinderclient.v2.client as cClient_v2 -from httplib import HTTPException -from neutronclient.neutron import client as neClient_v2 -from neutronclient.v2_0 import client as neClient -from neutronclient.common import exceptions as neExceptions -from requests.exceptions import ConnectionError - -'''contain the openstack virtual machine status to openmano status''' -vmStatus2manoFormat={'ACTIVE':'ACTIVE', - 'PAUSED':'PAUSED', - 'SUSPENDED': 'SUSPENDED', - 'SHUTOFF':'INACTIVE', - 'BUILD':'BUILD', - 'ERROR':'ERROR','DELETED':'DELETED' - } -netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED' - } - -#global var to have a timeout creating and deleting volumes -volume_timeout = 60 -server_timeout = 60 - -class vimconnector(vimconn.vimconnector): - def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, - log_level=None, config={}, persistent_info={}): - '''using common constructor parameters. In this case - 'url' is the keystone authorization url, - 'url_admin' is not use - ''' - self.osc_api_version = 'v2.0' - if config.get('APIversion') == 'v3.3': - self.osc_api_version = 'v3.3' - vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config) - - self.persistent_info = persistent_info - self.k_creds={} - self.n_creds={} - if self.config.get("insecure"): - self.k_creds["insecure"] = True - self.n_creds["insecure"] = True - if not url: - raise TypeError, 'url param can not be NoneType' - self.k_creds['auth_url'] = url - self.n_creds['auth_url'] = url - if tenant_name: - self.k_creds['tenant_name'] = tenant_name - self.n_creds['project_id'] = tenant_name - if tenant_id: - self.k_creds['tenant_id'] = tenant_id - self.n_creds['tenant_id'] = tenant_id - if user: - self.k_creds['username'] = user - self.n_creds['username'] = user - if passwd: - self.k_creds['password'] = passwd - self.n_creds['api_key'] = passwd - if self.osc_api_version == 'v3.3': - self.k_creds['project_name'] = tenant_name - self.k_creds['project_id'] = tenant_id - if config.get('region_name'): - self.k_creds['region_name'] = config.get('region_name') - self.n_creds['region_name'] = config.get('region_name') - - self.reload_client = True - self.logger = logging.getLogger('openmano.vim.openstack') - if log_level: - self.logger.setLevel( getattr(logging, log_level) ) - - def __setitem__(self,index, value): - '''Set individuals parameters - Throw TypeError, KeyError - ''' - if index=='tenant_id': - self.reload_client=True - self.tenant_id = value - if self.osc_api_version == 'v3.3': - if value: - self.k_creds['project_id'] = value - self.n_creds['project_id'] = value - else: - del self.k_creds['project_id'] - del self.n_creds['project_id'] - else: - if value: - self.k_creds['tenant_id'] = value - self.n_creds['tenant_id'] = value - else: - del self.k_creds['tenant_id'] - del self.n_creds['tenant_id'] - elif index=='tenant_name': - self.reload_client=True - self.tenant_name = value - if self.osc_api_version == 'v3.3': - if value: - self.k_creds['project_name'] = value - self.n_creds['project_name'] = value - else: - del self.k_creds['project_name'] - del self.n_creds['project_name'] - else: - if value: - self.k_creds['tenant_name'] = value - self.n_creds['project_id'] = value - else: - del self.k_creds['tenant_name'] - del self.n_creds['project_id'] - elif index=='user': - self.reload_client=True - self.user = value - if value: - self.k_creds['username'] = value - self.n_creds['username'] = value - else: - del self.k_creds['username'] - del self.n_creds['username'] - elif index=='passwd': - self.reload_client=True - self.passwd = value - if value: - self.k_creds['password'] = value - self.n_creds['api_key'] = value - else: - del self.k_creds['password'] - del self.n_creds['api_key'] - elif index=='url': - self.reload_client=True - self.url = value - if value: - self.k_creds['auth_url'] = value - self.n_creds['auth_url'] = value - else: - raise TypeError, 'url param can not be NoneType' - else: - vimconn.vimconnector.__setitem__(self,index, value) - - def _reload_connection(self): - '''Called before any operation, it check if credentials has changed - Throw keystoneclient.apiclient.exceptions.AuthorizationFailure - ''' - #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-) - if self.reload_client: - #test valid params - if len(self.n_creds) <4: - raise ksExceptions.ClientException("Not enough parameters to connect to openstack") - if self.osc_api_version == 'v3.3': - self.nova = nClient(api_version=api_versions.APIVersion(version_str='2.0'), **self.n_creds) - #TODO To be updated for v3 - #self.cinder = cClient.Client(**self.n_creds) - self.keystone = ksClient.Client(**self.k_creds) - self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL') - self.neutron = neClient.Client(api_version=api_versions.APIVersion(version_str='2.0'), endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds) - else: - self.nova = nClient_v2.Client(version='2', **self.n_creds) - self.cinder = cClient_v2.Client(**self.n_creds) - self.keystone = ksClient_v2.Client(**self.k_creds) - self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL') - self.neutron = neClient_v2.Client('2.0', endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds) - self.glance_endpoint = self.keystone.service_catalog.url_for(service_type='image', endpoint_type='publicURL') - self.glance = glClient.Client(self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds - self.reload_client = False - - def __net_os2mano(self, net_list_dict): - '''Transform the net openstack format to mano format - net_list_dict can be a list of dict or a single dict''' - if type(net_list_dict) is dict: - net_list_=(net_list_dict,) - elif type(net_list_dict) is list: - net_list_=net_list_dict - else: - raise TypeError("param net_list_dict must be a list or a dictionary") - for net in net_list_: - if net.get('provider:network_type') == "vlan": - net['type']='data' - else: - net['type']='bridge' - - - - def _format_exception(self, exception): - '''Transform a keystone, nova, neutron exception into a vimconn exception''' - if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, - ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed - )): - raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception)) - elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException, - neExceptions.NeutronException, nvExceptions.BadRequest)): - raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception)) - elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)): - raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception)) - elif isinstance(exception, nvExceptions.Conflict): - raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception)) - else: # () - raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception)) - - def get_tenant_list(self, filter_dict={}): - '''Obtain tenants of VIM - filter_dict can contain the following keys: - name: filter by tenant name - id: filter by tenant uuid/id - - Returns the tenant list of dictionaries: [{'name':', 'id':', ...}, ...] - ''' - self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict)) - try: - self._reload_connection() - if self.osc_api_version == 'v3.3': - project_class_list=self.keystone.projects.findall(**filter_dict) - else: - project_class_list=self.keystone.tenants.findall(**filter_dict) - project_list=[] - for project in project_class_list: - project_list.append(project.to_dict()) - return project_list - except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e: - self._format_exception(e) - - def new_tenant(self, tenant_name, tenant_description): - '''Adds a new tenant to openstack VIM. Returns the tenant identifier''' - self.logger.debug("Adding a new tenant name: %s", tenant_name) - try: - self._reload_connection() - if self.osc_api_version == 'v3.3': - project=self.keystone.projects.create(tenant_name, tenant_description) - else: - project=self.keystone.tenants.create(tenant_name, tenant_description) - return project.id - except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e: - self._format_exception(e) - - def delete_tenant(self, tenant_id): - '''Delete a tenant from openstack VIM. Returns the old tenant identifier''' - self.logger.debug("Deleting tenant %s from VIM", tenant_id) - try: - self._reload_connection() - if self.osc_api_version == 'v3.3': - self.keystone.projects.delete(tenant_id) - else: - self.keystone.tenants.delete(tenant_id) - return tenant_id - except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e: - self._format_exception(e) - - def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None): - '''Adds a tenant network to VIM. Returns the network identifier''' - self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type) - #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile)) - try: - new_net = None - self._reload_connection() - network_dict = {'name': net_name, 'admin_state_up': True} - if net_type=="data" or net_type=="ptp": - if self.config.get('dataplane_physical_net') == None: - raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network") - network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical - network_dict["provider:network_type"] = "vlan" - if vlan!=None: - network_dict["provider:network_type"] = vlan - network_dict["shared"]=shared - new_net=self.neutron.create_network({'network':network_dict}) - #print new_net - #create subnetwork, even if there is no profile - if not ip_profile: - ip_profile = {} - if 'subnet_address' not in ip_profile: - #Fake subnet is required - subnet_rand = random.randint(0, 255) - ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand) - if 'ip_version' not in ip_profile: - ip_profile['ip_version'] = "IPv4" - subnet={"name":net_name+"-subnet", - "network_id": new_net["network"]["id"], - "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6, - "cidr": ip_profile['subnet_address'] - } - if 'gateway_address' in ip_profile: - subnet['gateway_ip'] = ip_profile['gateway_address'] - if ip_profile.get('dns_address'): - #TODO: manage dns_address as a list of addresses separated by commas - subnet['dns_nameservers'] = [] - subnet['dns_nameservers'].append(ip_profile['dns_address']) - if 'dhcp_enabled' in ip_profile: - subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True - if 'dhcp_start_address' in ip_profile: - subnet['allocation_pools']=[] - subnet['allocation_pools'].append(dict()) - subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address'] - if 'dhcp_count' in ip_profile: - #parts = ip_profile['dhcp_start_address'].split('.') - #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3]) - ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address'])) - ip_int += ip_profile['dhcp_count'] - 1 - ip_str = str(netaddr.IPAddress(ip_int)) - subnet['allocation_pools'][0]['end'] = ip_str - #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet)) - self.neutron.create_subnet({"subnet": subnet} ) - return new_net["network"]["id"] - except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e: - if new_net: - self.neutron.delete_network(new_net['network']['id']) - self._format_exception(e) - - 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' - Returns the network list of dictionaries - ''' - self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict)) - try: - self._reload_connection() - if self.osc_api_version == 'v3.3' and "tenant_id" in filter_dict: - filter_dict['project_id'] = filter_dict.pop('tenant_id') - net_dict=self.neutron.list_networks(**filter_dict) - net_list=net_dict["networks"] - self.__net_os2mano(net_list) - return net_list - except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e: - self._format_exception(e) - - def get_network(self, net_id): - '''Obtain details of network from VIM - Returns the network information from a network id''' - self.logger.debug(" Getting tenant network %s from VIM", net_id) - filter_dict={"id": net_id} - net_list = self.get_network_list(filter_dict) - if len(net_list)==0: - raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id)) - elif len(net_list)>1: - raise vimconn.vimconnConflictException("Found more than one network with this criteria") - net = net_list[0] - subnets=[] - for subnet_id in net.get("subnets", () ): - try: - subnet = self.neutron.show_subnet(subnet_id) - except Exception as e: - self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e))) - subnet = {"id": subnet_id, "fault": str(e)} - subnets.append(subnet) - net["subnets"] = subnets - return net - - def delete_network(self, net_id): - '''Deletes a tenant network from VIM. Returns the old network identifier''' - self.logger.debug("Deleting network '%s' from VIM", net_id) - try: - self._reload_connection() - #delete VM ports attached to this networks before the network - ports = self.neutron.list_ports(network_id=net_id) - for p in ports['ports']: - try: - self.neutron.delete_port(p["id"]) - except Exception as e: - self.logger.error("Error deleting port %s: %s", p["id"], str(e)) - self.neutron.delete_network(net_id) - return net_id - except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException, - ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e: - self._format_exception(e) - - 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) - - ''' - net_dict={} - for net_id in net_list: - net = {} - try: - net_vim = self.get_network(net_id) - if net_vim['status'] in netStatus2manoFormat: - net["status"] = netStatus2manoFormat[ net_vim['status'] ] - else: - net["status"] = "OTHER" - net["error_msg"] = "VIM status reported " + net_vim['status'] - - if net['status'] == "ACTIVE" and not net_vim['admin_state_up']: - net['status'] = 'DOWN' - try: - net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256) - except yaml.representer.RepresenterError: - net['vim_info'] = str(net_vim) - if net_vim.get('fault'): #TODO - net['error_msg'] = str(net_vim['fault']) - except vimconn.vimconnNotFoundException as e: - self.logger.error("Exception getting net status: %s", str(e)) - net['status'] = "DELETED" - net['error_msg'] = str(e) - except vimconn.vimconnException as e: - self.logger.error("Exception getting net status: %s", str(e)) - net['status'] = "VIM_ERROR" - net['error_msg'] = str(e) - net_dict[net_id] = net - return net_dict - - def get_flavor(self, flavor_id): - '''Obtain flavor details from the VIM. Returns the flavor dict details''' - self.logger.debug("Getting flavor '%s'", flavor_id) - try: - self._reload_connection() - flavor = self.nova.flavors.find(id=flavor_id) - #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema) - return flavor.to_dict() - except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e: - self._format_exception(e) - - def get_flavor_id_from_data(self, flavor_dict): - """Obtain flavor id that match the flavor description - Returns the flavor_id or raises a vimconnNotFoundException - """ - try: - self._reload_connection() - numa=None - numas = flavor_dict.get("extended",{}).get("numas") - if numas: - #TODO - raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted") - # if len(numas) > 1: - # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa") - # numa=numas[0] - # numas = extended.get("numas") - for flavor in self.nova.flavors.list(): - epa = flavor.get_keys() - if epa: - continue - #TODO - if flavor.ram != flavor_dict["ram"]: - continue - if flavor.vcpus != flavor_dict["vcpus"]: - continue - if flavor.disk != flavor_dict["disk"]: - continue - return flavor.id - raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict))) - except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e: - self._format_exception(e) - - - def new_flavor(self, flavor_data, change_name_if_used=True): - '''Adds a tenant flavor to openstack VIM - if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition - Returns the flavor identifier - ''' - self.logger.debug("Adding flavor '%s'", str(flavor_data)) - retry=0 - max_retries=3 - name_suffix = 0 - name=flavor_data['name'] - while retry 1: - return -1, "Can not add flavor with more than one numa" - numa_properties = {"hw:numa_nodes":str(numa_nodes)} - numa_properties["hw:mem_page_size"] = "large" - numa_properties["hw:cpu_policy"] = "dedicated" - numa_properties["hw:numa_mempolicy"] = "strict" - for numa in numas: - #overwrite ram and vcpus - ram = numa['memory']*1024 - if 'paired-threads' in numa: - vcpus = numa['paired-threads']*2 - numa_properties["hw:cpu_threads_policy"] = "prefer" - elif 'cores' in numa: - vcpus = numa['cores'] - #numa_properties["hw:cpu_threads_policy"] = "prefer" - elif 'threads' in numa: - vcpus = numa['threads'] - numa_properties["hw:cpu_policy"] = "isolated" - for interface in numa.get("interfaces",() ): - if interface["dedicated"]=="yes": - raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable) - #TODO, add the key 'pci_passthrough:alias"="