# 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 functools from oslo.config import cfg from neutron.common import constants as q_const from neutron.common import exceptions as n_exc from neutron.common import rpc as q_rpc from neutron.common import topics from neutron.db import agents_db from neutron.db import db_base_plugin_v2 from neutron.db import external_net_db from neutron.db import l3_gwmode_db from neutron.db import portbindings_db from neutron.extensions import portbindings from neutron.openstack.common import excutils from neutron.openstack.common import log as logging from neutron.openstack.common import rpc from neutron.openstack.common.rpc import proxy from neutron.plugins.ibm.common import config # noqa from neutron.plugins.ibm.common import constants from neutron.plugins.ibm.common import exceptions as sdnve_exc from neutron.plugins.ibm import sdnve_api as sdnve from neutron.plugins.ibm import sdnve_api_fake as sdnve_fake LOG = logging.getLogger(__name__) class SdnveRpcCallbacks(): def __init__(self, notifier): self.notifier = notifier # used to notify the agent def create_rpc_dispatcher(self): '''Get the rpc dispatcher for this manager. If a manager would like to set an rpc API version, or support more than one class as the target of rpc messages, override this method. ''' return q_rpc.PluginRpcDispatcher([self, agents_db.AgentExtRpcCallback()]) def sdnve_info(self, rpc_context, **kwargs): '''Update new information.''' info = kwargs.get('info') # Notify all other listening agents self.notifier.info_update(rpc_context, info) return info class AgentNotifierApi(proxy.RpcProxy): '''Agent side of the SDN-VE rpc API.''' BASE_RPC_API_VERSION = '1.0' def __init__(self, topic): super(AgentNotifierApi, self).__init__( topic=topic, default_version=self.BASE_RPC_API_VERSION) self.topic_info_update = topics.get_topic_name(topic, constants.INFO, topics.UPDATE) def info_update(self, context, info): self.fanout_cast(context, self.make_msg('info_update', info=info), topic=self.topic_info_update) def _ha(func): '''Supports the high availability feature of the controller.''' @functools.wraps(func) def hawrapper(self, *args, **kwargs): '''This wrapper sets the new controller if necessary When a controller is detected to be not responding, and a new controller is chosen to be used in its place, this decorator makes sure the existing integration bridges are set to point to the new controleer by calling the set_controller method. ''' ret_func = func(self, *args, **kwargs) self.set_controller(args[0]) return ret_func return hawrapper class SdnvePluginV2(db_base_plugin_v2.NeutronDbPluginV2, external_net_db.External_net_db_mixin, portbindings_db.PortBindingMixin, l3_gwmode_db.L3_NAT_db_mixin, ): ''' Implement the Neutron abstractions using SDN-VE SDN Controller. ''' __native_bulk_support = False __native_pagination_support = False __native_sorting_support = False supported_extension_aliases = ["binding", "router", "external-net"] def __init__(self, configfile=None): self.base_binding_dict = { portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS, portbindings.VIF_DETAILS: {portbindings.CAP_PORT_FILTER: False}} super(SdnvePluginV2, self).__init__() self.setup_rpc() self.sdnve_controller_select() if self.fake_controller: self.sdnve_client = sdnve_fake.FakeClient() else: self.sdnve_client = sdnve.Client() def sdnve_controller_select(self): self.fake_controller = cfg.CONF.SDNVE.use_fake_controller def setup_rpc(self): # RPC support self.topic = topics.PLUGIN self.conn = rpc.create_connection(new=True) self.notifier = AgentNotifierApi(topics.AGENT) self.callbacks = SdnveRpcCallbacks(self.notifier) self.dispatcher = self.callbacks.create_rpc_dispatcher() self.conn.create_consumer(self.topic, self.dispatcher, fanout=False) # Consume from all consumers in a thread self.conn.consume_in_thread() def _update_base_binding_dict(self, tenant_type): if tenant_type == constants.TENANT_TYPE_OVERLAY: self.base_binding_dict[ portbindings.VIF_TYPE] = portbindings.VIF_TYPE_BRIDGE if tenant_type == constants.TENANT_TYPE_OF: self.base_binding_dict[ portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS def set_controller(self, context): LOG.info(_("Set a new controller if needed.")) new_controller = self.sdnve_client.sdnve_get_controller() if new_controller: self.notifier.info_update( context, {'new_controller': new_controller}) LOG.info(_("Set the controller to a new controller: %s"), new_controller) def _process_request(self, request, current): new_request = dict( (k, v) for k, v in request.items() if v != current.get(k)) msg = _("Original SDN-VE HTTP request: %(orig)s; New request: %(new)s") LOG.debug(msg, {'orig': request, 'new': new_request}) return new_request # # Network # @_ha def create_network(self, context, network): LOG.debug(_("Create network in progress: %r"), network) session = context.session tenant_id = self._get_tenant_id_for_create(context, network['network']) # Create a new SDN-VE tenant if need be sdnve_tenant = self.sdnve_client.sdnve_check_and_create_tenant( tenant_id) if sdnve_tenant is None: raise sdnve_exc.SdnveException( msg=_('Create net failed: no SDN-VE tenant.')) with session.begin(subtransactions=True): net = super(SdnvePluginV2, self).create_network(context, network) self._process_l3_create(context, net, network['network']) # Create SDN-VE network (res, data) = self.sdnve_client.sdnve_create('network', net) if res not in constants.HTTP_ACCEPTABLE: super(SdnvePluginV2, self).delete_network(context, net['id']) raise sdnve_exc.SdnveException( msg=(_('Create net failed in SDN-VE: %s') % res)) LOG.debug(_("Created network: %s"), net['id']) return net @_ha def update_network(self, context, id, network): LOG.debug(_("Update network in progress: %r"), network) session = context.session processed_request = {} with session.begin(subtransactions=True): original_network = super(SdnvePluginV2, self).get_network( context, id) processed_request['network'] = self._process_request( network['network'], original_network) net = super(SdnvePluginV2, self).update_network( context, id, network) self._process_l3_update(context, net, network['network']) if processed_request['network']: (res, data) = self.sdnve_client.sdnve_update( 'network', id, processed_request['network']) if res not in constants.HTTP_ACCEPTABLE: net = super(SdnvePluginV2, self).update_network( context, id, {'network': original_network}) raise sdnve_exc.SdnveException( msg=(_('Update net failed in SDN-VE: %s') % res)) return net @_ha def delete_network(self, context, id): LOG.debug(_("Delete network in progress: %s"), id) super(SdnvePluginV2, self).delete_network(context, id) (res, data) = self.sdnve_client.sdnve_delete('network', id) if res not in constants.HTTP_ACCEPTABLE: LOG.error( _("Delete net failed after deleting the network in DB: %s"), res) @_ha def get_network(self, context, id, fields=None): LOG.debug(_("Get network in progress: %s"), id) return super(SdnvePluginV2, self).get_network(context, id, fields) @_ha def get_networks(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): LOG.debug(_("Get networks in progress")) return super(SdnvePluginV2, self).get_networks( context, filters, fields, sorts, limit, marker, page_reverse) # # Port # @_ha def create_port(self, context, port): LOG.debug(_("Create port in progress: %r"), port) session = context.session # Set port status as 'ACTIVE' to avoid needing the agent port['port']['status'] = q_const.PORT_STATUS_ACTIVE port_data = port['port'] with session.begin(subtransactions=True): port = super(SdnvePluginV2, self).create_port(context, port) if 'id' not in port: return port # If the tenant_id is set to '' by create_port, add the id to # the request being sent to the controller as the controller # requires a tenant id tenant_id = port.get('tenant_id') if not tenant_id: LOG.debug(_("Create port does not have tenant id info")) original_network = super(SdnvePluginV2, self).get_network( context, port['network_id']) original_tenant_id = original_network['tenant_id'] port['tenant_id'] = original_tenant_id LOG.debug( _("Create port does not have tenant id info; " "obtained is: %s"), port['tenant_id']) os_tenant_id = tenant_id id_na, tenant_type = self.sdnve_client.sdnve_get_tenant_byid( os_tenant_id) self._update_base_binding_dict(tenant_type) self._process_portbindings_create_and_update(context, port_data, port) # NOTE(mb): Remove this block when controller is updated # Remove the information that the controller does not accept sdnve_port = port.copy() sdnve_port.pop('device_id', None) sdnve_port.pop('device_owner', None) (res, data) = self.sdnve_client.sdnve_create('port', sdnve_port) if res not in constants.HTTP_ACCEPTABLE: super(SdnvePluginV2, self).delete_port(context, port['id']) raise sdnve_exc.SdnveException( msg=(_('Create port failed in SDN-VE: %s') % res)) LOG.debug(_("Created port: %s"), port.get('id', 'id not found')) return port @_ha def update_port(self, context, id, port): LOG.debug(_("Update port in progress: %r"), port) session = context.session processed_request = {} with session.begin(subtransactions=True): original_port = super(SdnvePluginV2, self).get_port( context, id) processed_request['port'] = self._process_request( port['port'], original_port) updated_port = super(SdnvePluginV2, self).update_port( context, id, port) os_tenant_id = updated_port['tenant_id'] id_na, tenant_type = self.sdnve_client.sdnve_get_tenant_byid( os_tenant_id) self._update_base_binding_dict(tenant_type) self._process_portbindings_create_and_update(context, port['port'], updated_port) if processed_request['port']: (res, data) = self.sdnve_client.sdnve_update( 'port', id, processed_request['port']) if res not in constants.HTTP_ACCEPTABLE: updated_port = super(SdnvePluginV2, self).update_port( context, id, {'port': original_port}) raise sdnve_exc.SdnveException( msg=(_('Update port failed in SDN-VE: %s') % res)) return updated_port @_ha def delete_port(self, context, id, l3_port_check=True): LOG.debug(_("Delete port in progress: %s"), id) # if needed, check to see if this is a port owned by # an l3-router. If so, we should prevent deletion. if l3_port_check: self.prevent_l3_port_deletion(context, id) self.disassociate_floatingips(context, id) super(SdnvePluginV2, self).delete_port(context, id) (res, data) = self.sdnve_client.sdnve_delete('port', id) if res not in constants.HTTP_ACCEPTABLE: LOG.error( _("Delete port operation failed in SDN-VE " "after deleting the port from DB: %s"), res) # # Subnet # @_ha def create_subnet(self, context, subnet): LOG.debug(_("Create subnet in progress: %r"), subnet) new_subnet = super(SdnvePluginV2, self).create_subnet(context, subnet) # Note(mb): Use of null string currently required by controller sdnve_subnet = new_subnet.copy() if subnet.get('gateway_ip') is None: sdnve_subnet['gateway_ip'] = 'null' (res, data) = self.sdnve_client.sdnve_create('subnet', sdnve_subnet) if res not in constants.HTTP_ACCEPTABLE: super(SdnvePluginV2, self).delete_subnet(context, new_subnet['id']) raise sdnve_exc.SdnveException( msg=(_('Create subnet failed in SDN-VE: %s') % res)) LOG.debug(_("Subnet created: %s"), new_subnet['id']) return new_subnet @_ha def update_subnet(self, context, id, subnet): LOG.debug(_("Update subnet in progress: %r"), subnet) session = context.session processed_request = {} with session.begin(subtransactions=True): original_subnet = super(SdnvePluginV2, self).get_subnet( context, id) processed_request['subnet'] = self._process_request( subnet['subnet'], original_subnet) updated_subnet = super(SdnvePluginV2, self).update_subnet( context, id, subnet) if processed_request['subnet']: # Note(mb): Use of string containing null required by controller if 'gateway_ip' in processed_request['subnet']: if processed_request['subnet'].get('gateway_ip') is None: processed_request['subnet']['gateway_ip'] = 'null' (res, data) = self.sdnve_client.sdnve_update( 'subnet', id, processed_request['subnet']) if res not in constants.HTTP_ACCEPTABLE: for key in subnet['subnet'].keys(): subnet['subnet'][key] = original_subnet[key] super(SdnvePluginV2, self).update_subnet( context, id, subnet) raise sdnve_exc.SdnveException( msg=(_('Update subnet failed in SDN-VE: %s') % res)) return updated_subnet @_ha def delete_subnet(self, context, id): LOG.debug(_("Delete subnet in progress: %s"), id) super(SdnvePluginV2, self).delete_subnet(context, id) (res, data) = self.sdnve_client.sdnve_delete('subnet', id) if res not in constants.HTTP_ACCEPTABLE: LOG.error(_("Delete subnet operation failed in SDN-VE after " "deleting the subnet from DB: %s"), res) # # Router # @_ha def create_router(self, context, router): LOG.debug(_("Create router in progress: %r"), router) if router['router']['admin_state_up'] is False: LOG.warning(_('Ignoring admin_state_up=False for router=%r. ' 'Overriding with True'), router) router['router']['admin_state_up'] = True tenant_id = self._get_tenant_id_for_create(context, router['router']) # Create a new Pinnaacles tenant if need be sdnve_tenant = self.sdnve_client.sdnve_check_and_create_tenant( tenant_id) if sdnve_tenant is None: raise sdnve_exc.SdnveException( msg=_('Create router failed: no SDN-VE tenant.')) new_router = super(SdnvePluginV2, self).create_router(context, router) # Create Sdnve router (res, data) = self.sdnve_client.sdnve_create('router', new_router) if res not in constants.HTTP_ACCEPTABLE: super(SdnvePluginV2, self).delete_router(context, new_router['id']) raise sdnve_exc.SdnveException( msg=(_('Create router failed in SDN-VE: %s') % res)) LOG.debug(_("Router created: %r"), new_router) return new_router @_ha def update_router(self, context, id, router): LOG.debug(_("Update router in progress: id=%(id)s " "router=%(router)r"), {'id': id, 'router': router}) session = context.session processed_request = {} if not router['router'].get('admin_state_up', True): raise n_exc.NotImplementedError(_('admin_state_up=False ' 'routers are not ' 'supported.')) with session.begin(subtransactions=True): original_router = super(SdnvePluginV2, self).get_router( context, id) processed_request['router'] = self._process_request( router['router'], original_router) updated_router = super(SdnvePluginV2, self).update_router( context, id, router) if processed_request['router']: (res, data) = self.sdnve_client.sdnve_update( 'router', id, processed_request['router']) if res not in constants.HTTP_ACCEPTABLE: super(SdnvePluginV2, self).update_router( context, id, {'router': original_router}) raise sdnve_exc.SdnveException( msg=(_('Update router failed in SDN-VE: %s') % res)) return updated_router @_ha def delete_router(self, context, id): LOG.debug(_("Delete router in progress: %s"), id) super(SdnvePluginV2, self).delete_router(context, id) (res, data) = self.sdnve_client.sdnve_delete('router', id) if res not in constants.HTTP_ACCEPTABLE: LOG.error( _("Delete router operation failed in SDN-VE after " "deleting the router in DB: %s"), res) @_ha def add_router_interface(self, context, router_id, interface_info): LOG.debug(_("Add router interface in progress: " "router_id=%(router_id)s " "interface_info=%(interface_info)r"), {'router_id': router_id, 'interface_info': interface_info}) new_interface = super(SdnvePluginV2, self).add_router_interface( context, router_id, interface_info) LOG.debug( _("SdnvePluginV2.add_router_interface called. Port info: %s"), new_interface) request_info = interface_info.copy() request_info['port_id'] = new_interface['port_id'] # Add the subnet_id to the request sent to the controller if 'subnet_id' not in interface_info: request_info['subnet_id'] = new_interface['subnet_id'] (res, data) = self.sdnve_client.sdnve_update( 'router', router_id + '/add_router_interface', request_info) if res not in constants.HTTP_ACCEPTABLE: super(SdnvePluginV2, self).remove_router_interface( context, router_id, interface_info) raise sdnve_exc.SdnveException( msg=(_('Update router-add-interface failed in SDN-VE: %s') % res)) LOG.debug(_("Added router interface: %r"), new_interface) return new_interface def _add_router_interface_only(self, context, router_id, interface_info): LOG.debug(_("Add router interface only called: " "router_id=%(router_id)s " "interface_info=%(interface_info)r"), {'router_id': router_id, 'interface_info': interface_info}) port_id = interface_info.get('port_id') if port_id: (res, data) = self.sdnve_client.sdnve_update( 'router', router_id + '/add_router_interface', interface_info) if res not in constants.HTTP_ACCEPTABLE: LOG.error(_("SdnvePluginV2._add_router_interface_only: " "failed to add the interface in the roll back." " of a remove_router_interface operation")) @_ha def remove_router_interface(self, context, router_id, interface_info): LOG.debug(_("Remove router interface in progress: " "router_id=%(router_id)s " "interface_info=%(interface_info)r"), {'router_id': router_id, 'interface_info': interface_info}) subnet_id = interface_info.get('subnet_id') if not subnet_id: portid = interface_info.get('port_id') if not portid: raise sdnve_exc.BadInputException(msg=_('No port ID')) myport = super(SdnvePluginV2, self).get_port(context, portid) LOG.debug(_("SdnvePluginV2.remove_router_interface port: %s"), myport) myfixed_ips = myport.get('fixed_ips') if not myfixed_ips: raise sdnve_exc.BadInputException(msg=_('No fixed IP')) subnet_id = myfixed_ips[0].get('subnet_id') if subnet_id: interface_info['subnet_id'] = subnet_id LOG.debug( _("SdnvePluginV2.remove_router_interface subnet_id: %s"), subnet_id) (res, data) = self.sdnve_client.sdnve_update( 'router', router_id + '/remove_router_interface', interface_info) if res not in constants.HTTP_ACCEPTABLE: raise sdnve_exc.SdnveException( msg=(_('Update router-remove-interface failed SDN-VE: %s') % res)) session = context.session with session.begin(subtransactions=True): try: info = super(SdnvePluginV2, self).remove_router_interface( context, router_id, interface_info) except Exception: with excutils.save_and_reraise_exception(): self._add_router_interface_only(context, router_id, interface_info) return info # # Floating Ip # @_ha def create_floatingip(self, context, floatingip): LOG.debug(_("Create floatingip in progress: %r"), floatingip) new_floatingip = super(SdnvePluginV2, self).create_floatingip( context, floatingip) (res, data) = self.sdnve_client.sdnve_create( 'floatingip', {'floatingip': new_floatingip}) if res not in constants.HTTP_ACCEPTABLE: super(SdnvePluginV2, self).delete_floatingip( context, new_floatingip['id']) raise sdnve_exc.SdnveException( msg=(_('Creating floating ip operation failed ' 'in SDN-VE controller: %s') % res)) LOG.debug(_("Created floatingip : %r"), new_floatingip) return new_floatingip @_ha def update_floatingip(self, context, id, floatingip): LOG.debug(_("Update floatingip in progress: %r"), floatingip) session = context.session processed_request = {} with session.begin(subtransactions=True): original_floatingip = super( SdnvePluginV2, self).get_floatingip(context, id) processed_request['floatingip'] = self._process_request( floatingip['floatingip'], original_floatingip) updated_floatingip = super( SdnvePluginV2, self).update_floatingip(context, id, floatingip) if processed_request['floatingip']: (res, data) = self.sdnve_client.sdnve_update( 'floatingip', id, {'floatingip': processed_request['floatingip']}) if res not in constants.HTTP_ACCEPTABLE: super(SdnvePluginV2, self).update_floatingip( context, id, {'floatingip': original_floatingip}) raise sdnve_exc.SdnveException( msg=(_('Update floating ip failed in SDN-VE: %s') % res)) return updated_floatingip @_ha def delete_floatingip(self, context, id): LOG.debug(_("Delete floatingip in progress: %s"), id) super(SdnvePluginV2, self).delete_floatingip(context, id) (res, data) = self.sdnve_client.sdnve_delete('floatingip', id) if res not in constants.HTTP_ACCEPTABLE: LOG.error(_("Delete floatingip failed in SDN-VE: %s"), res)