X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=vimconn_openstack.py;h=b501d9da28ca3605d4b7a3600e538936aa28d7f4;hb=06e6c396413630640cafae3488442a0869f1642d;hp=76e7e4c509521aaa5a018cc53e26f3d1f9336226;hpb=ae4a8d1771650d4016cb4e910b61670bb2478390;p=osm%2FRO.git diff --git a/vimconn_openstack.py b/vimconn_openstack.py index 76e7e4c5..b501d9da 100644 --- a/vimconn_openstack.py +++ b/vimconn_openstack.py @@ -24,22 +24,31 @@ ''' osconnector implements all the methods to interact with openstack using the python-client. ''' -__author__="Alfonso Tierno, Gerardo Garcia" +__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, exceptions as nvExceptions -import keystoneclient.v2_0.client as ksClient +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 +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 @@ -54,16 +63,28 @@ vmStatus2manoFormat={'ACTIVE':'ACTIVE', 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="DEBUG", config={}): - '''using common constructor parameters. In this case + 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 @@ -80,8 +101,17 @@ class vimconnector(vimconn.vimconnector): 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('mano.vim.openstack') + 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 @@ -90,21 +120,37 @@ class vimconnector(vimconn.vimconnector): if index=='tenant_id': self.reload_client=True self.tenant_id = value - if value: - self.k_creds['tenant_id'] = value - self.n_creds['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: - del self.k_creds['tenant_name'] - del self.n_creds['project_id'] + 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 value: - self.k_creds['tenant_name'] = value - self.n_creds['project_id'] = 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: - del self.k_creds['tenant_name'] - del self.n_creds['project_id'] + 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 @@ -143,14 +189,23 @@ class vimconnector(vimconn.vimconnector): #test valid params if len(self.n_creds) <4: raise ksExceptions.ClientException("Not enough parameters to connect to openstack") - self.nova = nClient.Client(2, **self.n_creds) - self.keystone = ksClient.Client(**self.k_creds) + 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.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL') - self.neutron = neClient.Client('2.0', endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_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''' @@ -171,8 +226,8 @@ class vimconnector(vimconn.vimconnector): 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, - neClient.exceptions.ConnectionFailed)): + ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed + )): raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception)) elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException, neExceptions.NeutronException, nvExceptions.BadRequest)): @@ -192,15 +247,18 @@ class vimconnector(vimconn.vimconnector): Returns the tenant list of dictionaries: [{'name':', 'id':', ...}, ...] ''' - self.logger.debug("Getting tenant from VIM filter: '%s'", str(filter_dict)) + self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict)) try: self._reload_connection() - tenant_class_list=self.keystone.tenants.findall(**filter_dict) - tenant_list=[] - for tenant in tenant_class_list: - tenant_list.append(tenant.to_dict()) - return tenant_list - except (ksExceptions.ConnectionError, ksExceptions.ClientException) as e: + 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): @@ -208,9 +266,12 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("Adding a new tenant name: %s", tenant_name) try: self._reload_connection() - tenant=self.keystone.tenants.create(tenant_name, tenant_description) - return tenant.id - except (ksExceptions.ConnectionError, ksExceptions.ClientException) as e: + 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): @@ -218,15 +279,20 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("Deleting tenant %s from VIM", tenant_id) try: self._reload_connection() - self.keystone.tenants.delete(tenant_id) + 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) as e: + except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e: self._format_exception(e) - - def new_network(self,net_name,net_type, shared=False, cidr=None, vlan=None): + + 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": @@ -239,17 +305,45 @@ class vimconnector(vimconn.vimconnector): network_dict["shared"]=shared new_net=self.neutron.create_network({'network':network_dict}) #print new_net - #create fake subnetwork - if not cidr: - cidr="192.168.111.0/24" + #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, - "cidr": cidr + "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) as e: + 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={}): @@ -266,11 +360,13 @@ class vimconnector(vimconn.vimconnector): 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, neClient.exceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException) as e: + except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e: self._format_exception(e) def get_network(self, net_id): @@ -310,7 +406,7 @@ class vimconnector(vimconn.vimconnector): self.neutron.delete_network(net_id) return net_id except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException, - neClient.exceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException) as e: + ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e: self._format_exception(e) def refresh_nets_status(self, net_list): @@ -341,9 +437,12 @@ class vimconnector(vimconn.vimconnector): net["status"] = "OTHER" net["error_msg"] = "VIM status reported " + net_vim['status'] - if net['status'] == "ACIVE" and not net_vim['admin_state_up']: + if net['status'] == "ACTIVE" and not net_vim['admin_state_up']: net['status'] = 'DOWN' - net['vim_info'] = yaml.safe_dump(net_vim) + 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: @@ -365,9 +464,41 @@ class vimconnector(vimconn.vimconnector): 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) as e: + 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 @@ -421,7 +552,7 @@ class vimconnector(vimconn.vimconnector): numa_properties["hw:cpu_policy"] = "isolated" for interface in numa.get("interfaces",() ): if interface["dedicated"]=="yes": - raise vimconn.HTTP_Service_Unavailable("Passthrough interfaces are not supported for the openstack connector") + 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"="