# Copyright 2014 IBM Corp. # # 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. # # @author: Mohammad Banikazemi, IBM Corp. import httplib import urllib import httplib2 from keystoneclient.v2_0 import client as keyclient from oslo.config import cfg from neutron.api.v2 import attributes from neutron.openstack.common import log as logging from neutron.plugins.ibm.common import config # noqa from neutron.plugins.ibm.common import constants from neutron import wsgi LOG = logging.getLogger(__name__) SDNVE_VERSION = '2.0' SDNVE_ACTION_PREFIX = '/sdnve' SDNVE_RETRIES = 0 SDNVE_RETRIY_INTERVAL = 1 SDNVE_TENANT_TYPE_OVERLAY = u'DOVE' SDNVE_URL = 'https://%s:%s%s' class RequestHandler(object): '''Handles processing requests to and responses from controller.''' def __init__(self, controller_ips=None, port=None, ssl=None, base_url=None, userid=None, password=None, timeout=10, formats=None): '''Initializes the RequestHandler for communication with controller Following keyword arguments are used; if not specified, default values are used. :param port: Username for authentication. :param timeout: Time out for http requests. :param userid: User id for accessing controller. :param password: Password for accessing the controller. :param base_url: The base url for the controller. :param controller_ips: List of controller IP addresses. :param formats: Supported formats. ''' self.port = port or cfg.CONF.SDNVE.port self.timeout = timeout self._s_meta = None self.connection = None self.httpclient = httplib2.Http( disable_ssl_certificate_validation=True) self.cookie = None userid = userid or cfg.CONF.SDNVE.userid password = password or cfg.CONF.SDNVE.password if (userid and password): self.httpclient.add_credentials(userid, password) self.base_url = base_url or cfg.CONF.SDNVE.base_url self.controller_ips = controller_ips or cfg.CONF.SDNVE.controller_ips LOG.info(_("The IP addr of available SDN-VE controllers: %s"), self.controller_ips) self.controller_ip = self.controller_ips[0] LOG.info(_("The SDN-VE controller IP address: %s"), self.controller_ip) self.new_controller = False self.format = formats or cfg.CONF.SDNVE.format self.version = SDNVE_VERSION self.action_prefix = SDNVE_ACTION_PREFIX self.retries = SDNVE_RETRIES self.retry_interval = SDNVE_RETRIY_INTERVAL def serialize(self, data): '''Serializes a dictionary with a single key.''' if isinstance(data, dict): return wsgi.Serializer().serialize(data, self.content_type()) elif data: raise TypeError(_("unable to serialize object type: '%s'") % type(data)) def deserialize(self, data, status_code): '''Deserializes an xml or json string into a dictionary.''' # NOTE(mb): Temporary fix for backend controller requirement data = data.replace("router_external", "router:external") if status_code == httplib.NO_CONTENT: return data try: deserialized_data = wsgi.Serializer( metadata=self._s_meta).deserialize(data, self.content_type()) deserialized_data = deserialized_data['body'] except Exception: deserialized_data = data return deserialized_data def content_type(self, format=None): '''Returns the mime-type for either 'xml' or 'json'.''' return 'application/%s' % (format or self.format) def delete(self, url, body=None, headers=None, params=None): return self.do_request("DELETE", url, body=body, headers=headers, params=params) def get(self, url, body=None, headers=None, params=None): return self.do_request("GET", url, body=body, headers=headers, params=params) def post(self, url, body=None, headers=None, params=None): return self.do_request("POST", url, body=body, headers=headers, params=params) def put(self, url, body=None, headers=None, params=None): return self.do_request("PUT", url, body=body, headers=headers, params=params) def do_request(self, method, url, body=None, headers=None, params=None, connection_type=None): status_code = -1 replybody_deserialized = '' if body: body = self.serialize(body) self.headers = headers or {'Content-Type': self.content_type()} if self.cookie: self.headers['cookie'] = self.cookie if self.controller_ip != self.controller_ips[0]: controllers = [self.controller_ip] else: controllers = [] controllers.extend(self.controller_ips) for controller_ip in controllers: serverurl = SDNVE_URL % (controller_ip, self.port, self.base_url) myurl = serverurl + url if params and isinstance(params, dict): myurl += '?' + urllib.urlencode(params, doseq=1) try: LOG.debug(_("Sending request to SDN-VE. url: " "%(myurl)s method: %(method)s body: " "%(body)s header: %(header)s "), {'myurl': myurl, 'method': method, 'body': body, 'header': self.headers}) resp, replybody = self.httpclient.request( myurl, method=method, body=body, headers=self.headers) LOG.debug(("Response recd from SDN-VE. resp: %(resp)s" "body: %(body)s"), {'resp': resp.status, 'body': replybody}) status_code = resp.status except Exception as e: LOG.error(_("Error: Could not reach server: %(url)s " "Exception: %(excp)s."), {'url': myurl, 'excp': e}) self.cookie = None continue if status_code not in constants.HTTP_ACCEPTABLE: LOG.debug(_("Error message: %(reply)s -- Status: %(status)s"), {'reply': replybody, 'status': status_code}) else: LOG.debug(_("Received response status: %s"), status_code) if resp.get('set-cookie'): self.cookie = resp['set-cookie'] replybody_deserialized = self.deserialize( replybody, status_code) LOG.debug(_("Deserialized body: %s"), replybody_deserialized) if controller_ip != self.controller_ip: # bcast the change of controller self.new_controller = True self.controller_ip = controller_ip return (status_code, replybody_deserialized) return (httplib.REQUEST_TIMEOUT, 'Could not reach server(s)') class Client(RequestHandler): '''Client for SDNVE controller.''' def __init__(self): '''Initialize a new SDNVE client.''' super(Client, self).__init__() self.keystoneclient = KeystoneClient() resource_path = { 'network': "ln/networks/", 'subnet': "ln/subnets/", 'port': "ln/ports/", 'tenant': "ln/tenants/", 'router': "ln/routers/", 'floatingip': "ln/floatingips/", } def process_request(self, body): '''Processes requests according to requirements of controller.''' if self.format == 'json': body = dict( (k.replace(':', '_'), v) for k, v in body.items() if attributes.is_attr_set(v)) def sdnve_list(self, resource, **params): '''Fetches a list of resources.''' res = self.resource_path.get(resource, None) if not res: LOG.info(_("Bad resource for forming a list request")) return 0, '' return self.get(res, params=params) def sdnve_show(self, resource, specific, **params): '''Fetches information of a certain resource.''' res = self.resource_path.get(resource, None) if not res: LOG.info(_("Bad resource for forming a show request")) return 0, '' return self.get(res + specific, params=params) def sdnve_create(self, resource, body): '''Creates a new resource.''' res = self.resource_path.get(resource, None) if not res: LOG.info(_("Bad resource for forming a create request")) return 0, '' self.process_request(body) status, data = self.post(res, body=body) return (status, data) def sdnve_update(self, resource, specific, body=None): '''Updates a resource.''' res = self.resource_path.get(resource, None) if not res: LOG.info(_("Bad resource for forming a update request")) return 0, '' self.process_request(body) return self.put(res + specific, body=body) def sdnve_delete(self, resource, specific): '''Deletes the specified resource.''' res = self.resource_path.get(resource, None) if not res: LOG.info(_("Bad resource for forming a delete request")) return 0, '' return self.delete(res + specific) def _tenant_id_conversion(self, osid): return osid def sdnve_get_tenant_byid(self, os_tenant_id): sdnve_tenant_id = self._tenant_id_conversion(os_tenant_id) resp, content = self.sdnve_show('tenant', sdnve_tenant_id) if resp in constants.HTTP_ACCEPTABLE: tenant_id = content.get('id') tenant_type = content.get('network_type') if tenant_type == SDNVE_TENANT_TYPE_OVERLAY: tenant_type = constants.TENANT_TYPE_OVERLAY return tenant_id, tenant_type return None, None def sdnve_check_and_create_tenant(self, os_tenant_id, network_type=None): if not os_tenant_id: return tenant_id, tenant_type = self.sdnve_get_tenant_byid(os_tenant_id) if tenant_id: if not network_type: return tenant_id if tenant_type != network_type: LOG.info(_("Non matching tenant and network types: " "%(ttype)s %(ntype)s"), {'ttype': tenant_type, 'ntype': network_type}) return return tenant_id # Have to create a new tenant sdnve_tenant_id = self._tenant_id_conversion(os_tenant_id) if not network_type: network_type = self.keystoneclient.get_tenant_type(os_tenant_id) if network_type == constants.TENANT_TYPE_OVERLAY: network_type = SDNVE_TENANT_TYPE_OVERLAY pinn_desc = ("Created by SDN-VE Neutron Plugin, OS project name = " + self.keystoneclient.get_tenant_name(os_tenant_id)) res, content = self.sdnve_create('tenant', {'id': sdnve_tenant_id, 'name': os_tenant_id, 'network_type': network_type, 'description': pinn_desc}) if res not in constants.HTTP_ACCEPTABLE: return return sdnve_tenant_id def sdnve_get_controller(self): if self.new_controller: self.new_controller = False return self.controller_ip class KeystoneClient(object): def __init__(self, username=None, tenant_name=None, password=None, auth_url=None): keystone_conf = cfg.CONF.keystone_authtoken keystone_auth_url = ('%s://%s:%s/v2.0/' % (keystone_conf.auth_protocol, keystone_conf.auth_host, keystone_conf.auth_port)) username = username or keystone_conf.admin_user tenant_name = tenant_name or keystone_conf.admin_tenant_name password = password or keystone_conf.admin_password auth_url = auth_url or keystone_auth_url self.overlay_signature = cfg.CONF.SDNVE.overlay_signature self.of_signature = cfg.CONF.SDNVE.of_signature self.default_tenant_type = cfg.CONF.SDNVE.default_tenant_type self.client = keyclient.Client(username=username, password=password, tenant_name=tenant_name, auth_url=auth_url) def get_tenant_byid(self, id): try: return self.client.tenants.get(id) except Exception: LOG.exception(_("Did not find tenant: %r"), id) def get_tenant_type(self, id): tenant = self.get_tenant_byid(id) if tenant: description = tenant.description if description: if (description.find(self.overlay_signature) >= 0): return constants.TENANT_TYPE_OVERLAY if (description.find(self.of_signature) >= 0): return constants.TENANT_TYPE_OF return self.default_tenant_type def get_tenant_name(self, id): tenant = self.get_tenant_byid(id) if tenant: return tenant.name return 'not found'