diff --git a/devstack/README.rst b/devstack/README.rst index a362595d..d4f2d5d3 100644 --- a/devstack/README.rst +++ b/devstack/README.rst @@ -8,3 +8,6 @@ A `local.conf` recipe to enable tap-as-a-service:: enable_plugin tap-as-a-service https://github.com/openstack/tap-as-a-service enable_service taas enable_service taas_openvswitch_agent + Q_PLUGIN_EXTRA_CONF_PATH=/etc/neutron + Q_PLUGIN_EXTRA_CONF_FILES=(taas_plugin.ini) + TAAS_SERVICE_DRIVER=TAAS:TAAS:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default diff --git a/devstack/devstackgaterc b/devstack/devstackgaterc index 02f137b4..e75e4305 100644 --- a/devstack/devstackgaterc +++ b/devstack/devstackgaterc @@ -27,6 +27,10 @@ OVERRIDE_ENABLED_SERVICES+=,taas,taas_openvswitch_agent OVERRIDE_ENABLED_SERVICES+=,tempest,dstat export OVERRIDE_ENABLED_SERVICES +# Set config file +export DEVSTACK_LOCAL_CONFIG+=$'\n'"Q_PLUGIN_EXTRA_CONF_PATH=/etc/neutron" +export DEVSTACK_LOCAL_CONFIG+=$'\n'"Q_PLUGIN_EXTRA_CONF_FILES=(taas_plugin.ini)" + # Begin list of exclusions. r="^(?!.*" diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 33dc2a89..deb2c07f 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -17,24 +17,23 @@ # This script is meant to be sourced from devstack. It is a wrapper of # devmido scripts that allows proper exporting of environment variables. -ABSOLUTE_PATH=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd) -PLUGIN_PATH=$ABSOLUTE_PATH/.. - -TAAS_OVS_AGENT_BINARY="$NEUTRON_BIN_DIR/neutron-taas-openvswitch-agent" -TAAS_OVS_AGENT_CONF_FILE="/etc/neutron/taas.ini" function install_taas { - pip_install --no-deps --editable $PLUGIN_PATH + pip_install --no-deps --editable $TAAS_PLUGIN_PATH } function configure_taas_plugin { + if [ ! -d $NEUTRON_CONF_DIR ]; then + _create_neutron_conf_dir + fi + cp $TAAS_PLUGIN_PATH/etc/taas_plugin.ini $TAAS_PLUGIN_CONF_FILE _neutron_service_plugin_class_add taas } function configure_taas_openvswitch_agent { local conf=$TAAS_OVS_AGENT_CONF_FILE - cp $PLUGIN_PATH/etc/taas.ini $conf + cp $TAAS_PLUGIN_PATH/etc/taas.ini $conf iniset $conf taas driver neutron_taas.services.taas.drivers.linux.ovs_taas.OvsTaasDriver iniset $conf taas enabled True iniset $conf taas vlan_range_start 3000 @@ -54,6 +53,11 @@ if is_service_enabled taas; then configure_taas_plugin elif [[ "$2" == "post-config" ]]; then neutron-db-manage --subproject tap-as-a-service upgrade head + echo "Configuring taas" + if [ "$TAAS_SERVICE_DRIVER" ]; then + inicomment $TAAS_PLUGIN_CONF_FILE service_providers service_provider + iniadd $TAAS_PLUGIN_CONF_FILE service_providers service_provider $TAAS_SERVICE_DRIVER + fi elif [[ "$2" == "extra" ]]; then : fi diff --git a/devstack/settings b/devstack/settings new file mode 100644 index 00000000..12f469b4 --- /dev/null +++ b/devstack/settings @@ -0,0 +1,6 @@ +# Devstack settings +ABSOLUTE_PATH=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd) +TAAS_PLUGIN_PATH=$ABSOLUTE_PATH/.. +TAAS_PLUGIN_CONF_FILE="/etc/neutron/taas_plugin.ini" +TAAS_OVS_AGENT_BINARY="$NEUTRON_BIN_DIR/neutron-taas-openvswitch-agent" +TAAS_OVS_AGENT_CONF_FILE="/etc/neutron/taas.ini" diff --git a/etc/taas_plugin.ini b/etc/taas_plugin.ini new file mode 100644 index 00000000..53800f62 --- /dev/null +++ b/etc/taas_plugin.ini @@ -0,0 +1,7 @@ +[DEFAULT] + + +[service_providers] +# Defines providers for advanced services using the format: +# ::[:default] (multi valued) +service_provider = TAAS:TAAS:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default diff --git a/neutron_taas/db/migration/alembic_migration/versions/CONTRACT_HEAD b/neutron_taas/db/migration/alembic_migration/versions/CONTRACT_HEAD index 192304df..f9792753 100644 --- a/neutron_taas/db/migration/alembic_migration/versions/CONTRACT_HEAD +++ b/neutron_taas/db/migration/alembic_migration/versions/CONTRACT_HEAD @@ -1 +1 @@ -1817af933379 +2ecce0368a62 diff --git a/neutron_taas/db/migration/alembic_migration/versions/newton/contract/2ecce0368a62_add_foreign_key_constraint_on_tap_id_association.py b/neutron_taas/db/migration/alembic_migration/versions/newton/contract/2ecce0368a62_add_foreign_key_constraint_on_tap_id_association.py new file mode 100644 index 00000000..e04224fc --- /dev/null +++ b/neutron_taas/db/migration/alembic_migration/versions/newton/contract/2ecce0368a62_add_foreign_key_constraint_on_tap_id_association.py @@ -0,0 +1,37 @@ +# Copyright 2016 Midokura SARL +# +# 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. + +"""add foreign key constraint on tap id association + +Revision ID: 2ecce0368a62 +Revises: 1817af933379 +Create Date: 2016-05-19 11:39:52.892610 + +""" + +# revision identifiers, used by Alembic. +revision = '2ecce0368a62' +down_revision = '1817af933379' + +from alembic import op + + +def upgrade(): + op.create_foreign_key( + constraint_name=None, + source_table='tap_id_associations', + referent_table='tap_services', + local_cols=['tap_service_id'], + remote_cols=['id'], + ondelete='CASCADE') diff --git a/neutron_taas/db/taas_db.py b/neutron_taas/db/taas_db.py index c54dd28b..ad69b3d6 100755 --- a/neutron_taas/db/taas_db.py +++ b/neutron_taas/db/taas_db.py @@ -22,6 +22,7 @@ from neutron_taas.extensions import taas from oslo_log import log as logging from oslo_utils import uuidutils import sqlalchemy as sa +from sqlalchemy import orm from sqlalchemy.orm import exc @@ -57,8 +58,15 @@ class TapIdAssociation(model_base.BASEV2): # Internal mapping between a TAP Service and # id to be used by the Agents __tablename__ = 'tap_id_associations' - tap_service_id = sa.Column(sa.String(36)) + tap_service_id = sa.Column(sa.String(36), + sa.ForeignKey("tap_services.id", + ondelete='CASCADE')) taas_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + tap_service = orm.relationship( + TapService, + backref=orm.backref("tap_service_id", + lazy="joined", cascade="delete"), + primaryjoin='TapService.id==TapIdAssociation.tap_service_id') class Tass_db_Mixin(taas.TaasPluginBase, base_db.CommonDbMixin): @@ -126,14 +134,18 @@ class Tass_db_Mixin(taas.TaasPluginBase, base_db.CommonDbMixin): ) context.session.add(tap_service_db) + return self._make_tap_service_dict(tap_service_db) + + def create_tap_id_association(self, context, tap_service_id): + LOG.debug("create_tap_id_association() called") # create the TapIdAssociation object with context.session.begin(subtransactions=True): tap_id_association_db = TapIdAssociation( - tap_service_id=tap_service_db['id'] + tap_service_id=tap_service_id ) context.session.add(tap_id_association_db) - return self._make_tap_service_dict(tap_service_db) + return self._make_tap_id_association_dict(tap_id_association_db) def create_tap_flow(self, context, tap_flow): LOG.debug("create_tap_flow() called") @@ -163,9 +175,6 @@ class Tass_db_Mixin(taas.TaasPluginBase, base_db.CommonDbMixin): if not count: raise taas.TapServiceNotFound(tap_id=id) - context.session.query(TapIdAssociation).filter_by( - tap_service_id=id).delete() - def delete_tap_flow(self, context, id): LOG.debug("delete_tap_flow() called") diff --git a/neutron_taas/services/taas/service_drivers/__init__.py b/neutron_taas/services/taas/service_drivers/__init__.py new file mode 100644 index 00000000..d73e538d --- /dev/null +++ b/neutron_taas/services/taas/service_drivers/__init__.py @@ -0,0 +1,63 @@ +# Copyright (C) 2016 Midokura SARL. +# +# 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. + +import abc + +from oslo_log import log as logging +import six + +LOG = logging.getLogger(__name__) + + +@six.add_metaclass(abc.ABCMeta) +class TaasBaseDriver(object): + + def __init__(self, service_plugin): + self.service_plugin = service_plugin + + @property + def service_type(self): + pass + + @abc.abstractmethod + def create_tap_service_precommit(self, context): + pass + + @abc.abstractmethod + def delete_tap_service_precommit(self, context): + pass + + @abc.abstractmethod + def create_tap_flow_precommit(self, context): + pass + + @abc.abstractmethod + def delete_tap_flow_precommit(self, context): + pass + + @abc.abstractmethod + def create_tap_service_postcommit(self, context): + pass + + @abc.abstractmethod + def delete_tap_service_postcommit(self, context): + pass + + @abc.abstractmethod + def create_tap_flow_postcommit(self, context): + pass + + @abc.abstractmethod + def delete_tap_flow_postcommit(self, context): + pass diff --git a/neutron_taas/services/taas/service_drivers/service_driver_context.py b/neutron_taas/services/taas/service_drivers/service_driver_context.py new file mode 100644 index 00000000..3df97aac --- /dev/null +++ b/neutron_taas/services/taas/service_drivers/service_driver_context.py @@ -0,0 +1,67 @@ +# Copyright (C) 2016 Midokura SARL. +# +# 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. + +from neutron_taas.extensions import taas + +from oslo_log import log + +LOG = log.getLogger(__name__) + + +class ServiceDriverContext(object): + """ServiceDriverContext context base class""" + def __init__(self, service_plugin, plugin_context): + self._plugin = service_plugin + self._plugin_context = plugin_context + + +class TapServiceContext(ServiceDriverContext): + + def __init__(self, service_plugin, plugin_context, tap_service): + super(TapServiceContext, self).__init__(service_plugin, plugin_context) + self._tap_service = tap_service + self._tap_id_association = None + self._setup_tap_id_association(tap_service['id']) + + def _setup_tap_id_association(self, tap_service_id): + try: + self._tap_id_association = self._plugin.get_tap_id_association( + self._plugin_context, tap_service_id) + except taas.TapServiceNotFound: + LOG.debug("Not found tap_ip_association for tap_service: %s", + tap_service_id) + + @property + def tap_service(self): + return self._tap_service + + @property + def tap_id_association(self): + return self._tap_id_association + + @tap_id_association.setter + def tap_id_association(self, tap_id_association): + """Set tap_id_association in context""" + self._tap_id_association = tap_id_association + + +class TapFlowContext(ServiceDriverContext): + + def __init__(self, service_plugin, plugin_context, tap_flow): + super(TapFlowContext, self).__init__(service_plugin, plugin_context) + self._tap_flow = tap_flow + + @property + def tap_flow(self): + return self._tap_flow diff --git a/neutron_taas/services/taas/service_drivers/taas_agent_api.py b/neutron_taas/services/taas/service_drivers/taas_agent_api.py new file mode 100644 index 00000000..4e8ac8bc --- /dev/null +++ b/neutron_taas/services/taas/service_drivers/taas_agent_api.py @@ -0,0 +1,81 @@ +# Copyright (C) 2016 Midokura SARL. +# Copyright (C) 2015 Ericsson AB +# Copyright (c) 2015 Gigamon +# +# 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. + +from neutron.common import rpc as n_rpc + +from oslo_log import log as logging +import oslo_messaging as messaging + +LOG = logging.getLogger(__name__) + + +class TaasCallbacks(object): + """Currently there are no callbacks to the Taas Plugin.""" + + def __init__(self, plugin): + super(TaasCallbacks, self).__init__() + self.plugin = plugin + return + + +class TaasAgentApi(object): + """RPC calls to agent APIs""" + + def __init__(self, topic, host): + self.host = host + target = messaging.Target(topic=topic, version='1.0') + self.client = n_rpc.get_client(target) + return + + def create_tap_service(self, context, tap_service, host): + LOG.debug("In RPC Call for Create Tap Service: Host=%s, MSG=%s" % + (host, tap_service)) + + cctxt = self.client.prepare(fanout=True) + cctxt.cast(context, 'create_tap_service', tap_service=tap_service, + host=host) + + return + + def create_tap_flow(self, context, tap_flow_msg, host): + LOG.debug("In RPC Call for Create Tap Flow: Host=%s, MSG=%s" % + (host, tap_flow_msg)) + + cctxt = self.client.prepare(fanout=True) + cctxt.cast(context, 'create_tap_flow', tap_flow_msg=tap_flow_msg, + host=host) + + return + + def delete_tap_service(self, context, tap_service, host): + LOG.debug("In RPC Call for Delete Tap Service: Host=%s, MSG=%s" % + (host, tap_service)) + + cctxt = self.client.prepare(fanout=True) + cctxt.cast(context, 'delete_tap_service', tap_service=tap_service, + host=host) + + return + + def delete_tap_flow(self, context, tap_flow_msg, host): + LOG.debug("In RPC Call for Delete Tap Flow: Host=%s, MSG=%s" % + (host, tap_flow_msg)) + + cctxt = self.client.prepare(fanout=True) + cctxt.cast(context, 'delete_tap_flow', tap_flow_msg=tap_flow_msg, + host=host) + + return diff --git a/neutron_taas/services/taas/service_drivers/taas_rpc.py b/neutron_taas/services/taas/service_drivers/taas_rpc.py new file mode 100644 index 00000000..db876731 --- /dev/null +++ b/neutron_taas/services/taas/service_drivers/taas_rpc.py @@ -0,0 +1,157 @@ +# Copyright (C) 2016 Midokura SARL. +# Copyright (C) 2015 Ericsson AB +# Copyright (c) 2015 Gigamon +# +# 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. + +from neutron.common import rpc as n_rpc +from neutron_taas.common import topics +from neutron_taas.extensions import taas as taas_ex +from neutron_taas.services.taas import service_drivers +from neutron_taas.services.taas.service_drivers import taas_agent_api + +from oslo_config import cfg +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) + + +class TaasRpcDriver(service_drivers.TaasBaseDriver): + """Taas Rpc Service Driver class""" + + def __init__(self, service_plugin): + LOG.debug("Loading TaasRpcDriver.") + super(TaasRpcDriver, self).__init__(service_plugin) + self.endpoints = [taas_agent_api.TaasCallbacks(service_plugin)] + self.conn = n_rpc.create_connection() + self.conn.create_consumer(topics.TAAS_PLUGIN, + self.endpoints, fanout=False) + + self.conn.consume_in_threads() + + self.agent_rpc = taas_agent_api.TaasAgentApi( + topics.TAAS_AGENT, + cfg.CONF.host + ) + + return + + def _get_taas_id(self, context, tf): + ts = self.service_plugin.get_tap_service(context, + tf['tap_service_id']) + taas_id = (self.service_plugin.get_tap_id_association( + context, + tap_service_id=ts['id'])['taas_id'] + + cfg.CONF.taas.vlan_range_start) + return taas_id + + def create_tap_service_precommit(self, context): + ts = context.tap_service + tap_id_association = context._plugin.create_tap_id_association( + context._plugin_context, ts['id']) + context.tap_id_association = tap_id_association + + def create_tap_service_postcommit(self, context): + """Send tap service creation RPC message to agent. + + This RPC message includes taas_id that is added vlan_range_start to + so that taas-ovs-agent can use taas_id as VLANID. + """ + # Get taas id associated with the Tap Service + ts = context.tap_service + tap_id_association = context.tap_id_association + taas_vlan_id = (tap_id_association['taas_id'] + + cfg.CONF.taas.vlan_range_start) + port = self.service_plugin._get_port_details(context._plugin_context, + ts['port_id']) + host = port['binding:host_id'] + + if taas_vlan_id > cfg.CONF.taas.vlan_range_end: + raise taas_ex.TapServiceLimitReached() + + rpc_msg = {'tap_service': ts, + 'taas_id': taas_vlan_id, + 'port': port} + + self.agent_rpc.create_tap_service(context._plugin_context, + rpc_msg, host) + return + + def delete_tap_service_precommit(self, context): + pass + + def delete_tap_service_postcommit(self, context): + """Send tap service deletion RPC message to agent. + + This RPC message includes taas_id that is added vlan_range_start to + so that taas-ovs-agent can use taas_id as VLANID. + """ + ts = context.tap_service + tap_id_association = context.tap_id_association + taas_vlan_id = (tap_id_association['taas_id'] + + cfg.CONF.taas.vlan_range_start) + port = self.service_plugin._get_port_details(context._plugin_context, + ts['port_id']) + host = port['binding:host_id'] + + rpc_msg = {'tap_service': ts, + 'taas_id': taas_vlan_id, + 'port': port} + + self.agent_rpc.delete_tap_service(context._plugin_context, + rpc_msg, host) + return + + def create_tap_flow_precommit(self, context): + pass + + def create_tap_flow_postcommit(self, context): + """Send tap flow creation RPC message to agent.""" + tf = context.tap_flow + taas_id = self._get_taas_id(context._plugin_context, tf) + # Extract the host where the source port is located + port = self.service_plugin._get_port_details(context._plugin_context, + tf['source_port']) + host = port['binding:host_id'] + port_mac = port['mac_address'] + # Send RPC message to both the source port host and + # tap service(destination) port host + rpc_msg = {'tap_flow': tf, + 'port_mac': port_mac, + 'taas_id': taas_id, + 'port': port} + + self.agent_rpc.create_tap_flow(context._plugin_context, rpc_msg, host) + return + + def delete_tap_flow_precommit(self, context): + pass + + def delete_tap_flow_postcommit(self, context): + """Send tap flow deletion RPC message to agent.""" + tf = context.tap_flow + taas_id = self._get_taas_id(context._plugin_context, tf) + # Extract the host where the source port is located + port = self.service_plugin._get_port_details(context._plugin_context, + tf['source_port']) + host = port['binding:host_id'] + port_mac = port['mac_address'] + # Send RPC message to both the source port host and + # tap service(destination) port host + rpc_msg = {'tap_flow': tf, + 'port_mac': port_mac, + 'taas_id': taas_id, + 'port': port} + + self.agent_rpc.delete_tap_flow(context._plugin_context, rpc_msg, host) + return diff --git a/neutron_taas/services/taas/taas_plugin.py b/neutron_taas/services/taas/taas_plugin.py index 454ff6d1..255ab9a1 100755 --- a/neutron_taas/services/taas/taas_plugin.py +++ b/neutron_taas/services/taas/taas_plugin.py @@ -1,3 +1,4 @@ +# Copyright (C) 2016 Midokura SARL. # Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # @@ -13,76 +14,27 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron.common import exceptions as n_exc +from neutron.db import servicetype_db as st_db +from neutron.services import provider_configuration as pconf +from neutron.services import service_base -from oslo_config import cfg -import oslo_messaging as messaging - -from neutron.common import rpc as n_rpc -from neutron_taas.common import topics +from neutron_taas.common import constants from neutron_taas.db import taas_db from neutron_taas.extensions import taas as taas_ex +from neutron_taas.services.taas.service_drivers import (service_driver_context + as sd_context) + from oslo_log import log as logging +from oslo_utils import excutils LOG = logging.getLogger(__name__) -class TaasCallbacks(object): - """Currently there are no callbacks to the Taas Plugin.""" - - def __init__(self, plugin): - super(TaasCallbacks, self).__init__() - self.plugin = plugin - return - - -class TaasAgentApi(object): - """RPC calls to agent APIs""" - - def __init__(self, topic, host): - self.host = host - target = messaging.Target(topic=topic, version='1.0') - self.client = n_rpc.get_client(target) - return - - def create_tap_service(self, context, tap_service, host): - LOG.debug("In RPC Call for Create Tap Service: Host=%s, MSG=%s" % - (host, tap_service)) - - cctxt = self.client.prepare(fanout=True) - cctxt.cast(context, 'create_tap_service', tap_service=tap_service, - host=host) - - return - - def create_tap_flow(self, context, tap_flow_msg, host): - LOG.debug("In RPC Call for Create Tap Flow: Host=%s, MSG=%s" % - (host, tap_flow_msg)) - - cctxt = self.client.prepare(fanout=True) - cctxt.cast(context, 'create_tap_flow', tap_flow_msg=tap_flow_msg, - host=host) - - return - - def delete_tap_service(self, context, tap_service, host): - LOG.debug("In RPC Call for Delete Tap Service: Host=%s, MSG=%s" % - (host, tap_service)) - - cctxt = self.client.prepare(fanout=True) - cctxt.cast(context, 'delete_tap_service', tap_service=tap_service, - host=host) - - return - - def delete_tap_flow(self, context, tap_flow_msg, host): - LOG.debug("In RPC Call for Delete Tap Flow: Host=%s, MSG=%s" % - (host, tap_flow_msg)) - - cctxt = self.client.prepare(fanout=True) - cctxt.cast(context, 'delete_tap_flow', tap_flow_msg=tap_flow_msg, - host=host) - - return +def add_provider_configuration(type_manager, service_type): + type_manager.add_provider_configuration( + service_type, + pconf.ProviderConfiguration('neutron_taas')) class TaasPlugin(taas_db.Tass_db_Mixin): @@ -93,20 +45,24 @@ class TaasPlugin(taas_db.Tass_db_Mixin): def __init__(self): LOG.debug("TAAS PLUGIN INITIALIZED") - self.endpoints = [TaasCallbacks(self)] - - self.conn = n_rpc.create_connection() - self.conn.create_consumer( - topics.TAAS_PLUGIN, self.endpoints, fanout=False) - self.conn.consume_in_threads() - - self.agent_rpc = TaasAgentApi( - topics.TAAS_AGENT, - cfg.CONF.host - ) + self.service_type_manager = st_db.ServiceTypeManager.get_instance() + add_provider_configuration(self.service_type_manager, constants.TAAS) + self._load_drivers() + self.driver = self._get_driver_for_provider(self.default_provider) return + def _load_drivers(self): + """Loads plugin-drivers specified in configuration.""" + self.drivers, self.default_provider = service_base.load_drivers( + 'TAAS', self) + + def _get_driver_for_provider(self, provider): + if provider in self.drivers: + return self.drivers[provider] + raise n_exc.Invalid("Error retrieving driver for provider %s" % + provider) + def create_tap_service(self, context, tap_service): LOG.debug("create_tap_service() called") @@ -130,34 +86,25 @@ class TaasPlugin(taas_db.Tass_db_Mixin): LOG.debug("Host could not be found, Port Binding disbaled!") # Create tap service in the db model - ts = super(TaasPlugin, self).create_tap_service(context, tap_service) - # Get taas id associated with the Tap Service - tap_id_association = self.get_tap_id_association( - context, - tap_service_id=ts['id']) + with context.session.begin(subtransactions=True): + ts = super(TaasPlugin, self).create_tap_service(context, + tap_service) + driver_context = sd_context.TapServiceContext(self, context, ts) + self.driver.create_tap_service_precommit(driver_context) - taas_vlan_id = (tap_id_association['taas_id'] + - cfg.CONF.taas.vlan_range_start) - - if taas_vlan_id > cfg.CONF.taas.vlan_range_end: - raise taas_ex.TapServiceLimitReached() - - rpc_msg = {'tap_service': ts, 'taas_id': taas_vlan_id, 'port': port} - - self.agent_rpc.create_tap_service(context, rpc_msg, host) + try: + self.driver.create_tap_service_postcommit(driver_context) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error("Failed to create tap service on driver," + "deleting tap_service %s", ts['id']) + super(TaasPlugin, self).delete_tap_service(context, ts['id']) return ts def delete_tap_service(self, context, id): LOG.debug("delete_tap_service() called") - # Get taas id associated with the Tap Service - tap_id_association = self.get_tap_id_association( - context, - tap_service_id=id) - - ts = self.get_tap_service(context, id) - # Get all the tap Flows that are associated with the Tap service # and delete them as well t_f_collection = self.get_tap_flows( @@ -167,25 +114,18 @@ class TaasPlugin(taas_db.Tass_db_Mixin): for t_f in t_f_collection: self.delete_tap_flow(context, t_f['id']) - # Get the port and the host that it is on - port_id = ts['port_id'] + with context.session.begin(subtransactions=True): + ts = self.get_tap_service(context, id) + driver_context = sd_context.TapServiceContext(self, context, ts) + super(TaasPlugin, self).delete_tap_service(context, id) + self.driver.delete_tap_service_precommit(driver_context) - port = self._get_port_details(context, port_id) - - host = port['binding:host_id'] - - super(TaasPlugin, self).delete_tap_service(context, id) - - taas_vlan_id = (tap_id_association['taas_id'] + - cfg.CONF.taas.vlan_range_start) - - rpc_msg = {'tap_service': ts, - 'taas_id': taas_vlan_id, - 'port': port} - - self.agent_rpc.delete_tap_service(context, rpc_msg, host) - - return ts + try: + self.driver.delete_tap_service_postcommit(driver_context) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error("Failed to delete tap service on driver. " + "tap_sevice: %s", id) def create_tap_flow(self, context, tap_flow): LOG.debug("create_tap_flow() called") @@ -199,56 +139,38 @@ class TaasPlugin(taas_db.Tass_db_Mixin): ts = self.get_tap_service(context, t_f['tap_service_id']) ts_tenant_id = ts['tenant_id'] - taas_id = (self.get_tap_id_association( - context, - tap_service_id=ts['id'])['taas_id'] + - cfg.CONF.taas.vlan_range_start) - if tenant_id != ts_tenant_id: raise taas_ex.TapServiceNotBelongToTenant() - # Extract the host where the source port is located - port = self._get_port_details(context, t_f['source_port']) - host = port['binding:host_id'] - port_mac = port['mac_address'] - # create tap flow in the db model - tf = super(TaasPlugin, self).create_tap_flow(context, tap_flow) + with context.session.begin(subtransactions=True): + tf = super(TaasPlugin, self).create_tap_flow(context, tap_flow) + driver_context = sd_context.TapFlowContext(self, context, tf) + self.driver.create_tap_flow_precommit(driver_context) - # Send RPC message to both the source port host and - # tap service(destination) port host - rpc_msg = {'tap_flow': tf, - 'port_mac': port_mac, - 'taas_id': taas_id, - 'port': port} - - self.agent_rpc.create_tap_flow(context, rpc_msg, host) + try: + self.driver.create_tap_flow_postcommit(driver_context) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error("Failed to create tap flow on driver," + "deleting tap_flow %s", tf['id']) + super(TaasPlugin, self).delete_tap_flow(context, tf['id']) return tf def delete_tap_flow(self, context, id): LOG.debug("delete_tap_flow() called") - tf = self.get_tap_flow(context, id) + with context.session.begin(subtransactions=True): + tf = self.get_tap_flow(context, id) + driver_context = sd_context.TapFlowContext(self, context, tf) + super(TaasPlugin, self).delete_tap_flow(context, id) + self.driver.delete_tap_flow_precommit(driver_context) - taas_id = (self.get_tap_id_association( - context, - tf['tap_service_id'])['taas_id'] + - cfg.CONF.taas.vlan_range_start) - - port = self._get_port_details(context, tf['source_port']) - host = port['binding:host_id'] - port_mac = port['mac_address'] - - super(TaasPlugin, self).delete_tap_flow(context, id) - - # Send RPC message to both the source port host and - # tap service(destination) port host - rpc_msg = {'tap_flow': tf, - 'port_mac': port_mac, - 'taas_id': taas_id, - 'port': port} - - self.agent_rpc.delete_tap_flow(context, rpc_msg, host) - - return tf + try: + self.driver.delete_tap_flow_postcommit(driver_context) + except Exception: + with excutils.save_and_reraise_exception(): + with excutils.save_and_reraise_exception(): + LOG.error("Failed to delete tap flow on driver. " + "tap_flow: %s", id) diff --git a/neutron_taas/tests/unit/services/taas/test_taas_plugin.py b/neutron_taas/tests/unit/services/taas/test_taas_plugin.py index a31046a0..f8da0493 100644 --- a/neutron_taas/tests/unit/services/taas/test_taas_plugin.py +++ b/neutron_taas/tests/unit/services/taas/test_taas_plugin.py @@ -18,7 +18,6 @@ import contextlib import mock import testtools -from oslo_config import cfg from oslo_utils import uuidutils import neutron.common.rpc as n_rpc @@ -28,15 +27,28 @@ from neutron.tests.unit import testlib_api import neutron_taas.db.taas_db # noqa import neutron_taas.extensions.taas as taas_ext +from neutron_taas.services.taas.service_drivers import taas_agent_api from neutron_taas.services.taas import taas_plugin +class DummyError(Exception): + pass + + class TestTaasPlugin(testlib_api.SqlTestCase): def setUp(self): super(TestTaasPlugin, self).setUp() mock.patch.object(n_rpc, 'create_connection', auto_spec=True).start() - mock.patch.object(taas_plugin, 'TaasCallbacks', auto_spec=True).start() - mock.patch.object(taas_plugin, 'TaasAgentApi', auto_spec=True).start() + mock.patch.object(taas_agent_api, + 'TaasCallbacks', auto_spec=True).start() + mock.patch.object(taas_agent_api, + 'TaasAgentApi', auto_spec=True).start() + self.driver = mock.MagicMock() + mock.patch('neutron.services.service_base.load_drivers', + return_value=({'dummy_provider': self.driver}, + 'dummy_provider')).start() + mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', + return_value=mock.MagicMock()).start() self._plugin = taas_plugin.TaasPlugin() self._context = context.get_admin_context() @@ -73,15 +85,17 @@ class TestTaasPlugin(testlib_api.SqlTestCase): return_value=self._port_details): yield self._plugin.create_tap_service(self._context, req) self._tap_service['id'] = mock.ANY - expected_msg = { - 'tap_service': self._tap_service, - 'taas_id': mock.ANY, - 'port': self._port_details, - } - self._plugin.agent_rpc.assert_has_calls([ - mock.call.create_tap_service(self._context, expected_msg, - self._host_id), + + self.driver.assert_has_calls([ + mock.call.create_tap_service_precommit(mock.ANY), + mock.call.create_tap_service_postcommit(mock.ANY), ]) + pre_args = self.driver.create_tap_service_precommit.call_args[0][0] + self.assertEqual(self._context, pre_args._plugin_context) + self.assertEqual(self._tap_service, pre_args.tap_service) + post_args = self.driver.create_tap_service_postcommit.call_args[0][0] + self.assertEqual(self._context, post_args._plugin_context) + self.assertEqual(self._tap_service, post_args.tap_service) @contextlib.contextmanager def tap_flow(self, tap_service, tenant_id=None): @@ -96,16 +110,17 @@ class TestTaasPlugin(testlib_api.SqlTestCase): yield self._plugin.create_tap_flow(self._context, req) self._tap_flow['id'] = mock.ANY self._tap_service['id'] = mock.ANY - expected_msg = { - 'tap_flow': self._tap_flow, - 'port_mac': self._port_details['mac_address'], - 'taas_id': mock.ANY, - 'port': self._port_details, - } - self._plugin.agent_rpc.assert_has_calls([ - mock.call.create_tap_flow(self._context, expected_msg, - self._host_id), + + self.driver.assert_has_calls([ + mock.call.create_tap_flow_precommit(mock.ANY), + mock.call.create_tap_flow_postcommit(mock.ANY), ]) + pre_args = self.driver.create_tap_flow_precommit.call_args[0][0] + self.assertEqual(self._context, pre_args._plugin_context) + self.assertEqual(self._tap_flow, pre_args.tap_flow) + post_args = self.driver.create_tap_flow_postcommit.call_args[0][0] + self.assertEqual(self._context, post_args._plugin_context) + self.assertEqual(self._tap_flow, post_args.tap_flow) def test_create_tap_service(self): with self.tap_service(): @@ -116,57 +131,71 @@ class TestTaasPlugin(testlib_api.SqlTestCase): with testtools.ExpectedException(taas_ext.PortDoesNotBelongToTenant), \ self.tap_service(): pass - self.assertEqual([], self._plugin.agent_rpc.mock_calls) + self.assertEqual([], self.driver.mock_calls) def test_create_tap_service_reach_limit(self): - cfg.CONF.set_override('vlan_range_end', 3900, 'taas') # same as start - with testtools.ExpectedException(taas_ext.TapServiceLimitReached), \ - self.tap_service(): - pass - self.assertEqual([], self._plugin.agent_rpc.mock_calls) + # TODO(Yoichiro):Need to move this test to taas_rpc test + pass + + def test_create_tap_service_failed_on_service_driver(self): + attr = {'create_tap_service_postcommit.side_effect': DummyError} + self.driver.configure_mock(**attr) + with testtools.ExpectedException(DummyError): + req = { + 'tap_service': self._tap_service, + } + with mock.patch.object(self._plugin, '_get_port_details', + return_value=self._port_details): + self._plugin.create_tap_service(self._context, req) def test_delete_tap_service(self): with self.tap_service() as ts: self._plugin.delete_tap_service(self._context, ts['id']) - expected_msg = { - 'tap_service': self._tap_service, - 'taas_id': mock.ANY, - 'port': self._port_details, - } - self._plugin.agent_rpc.assert_has_calls([ - mock.call.delete_tap_service(self._context, expected_msg, - self._host_id), + self.driver.assert_has_calls([ + mock.call.delete_tap_service_precommit(mock.ANY), + mock.call.delete_tap_service_postcommit(mock.ANY), ]) + pre_args = self.driver.delete_tap_service_precommit.call_args[0][0] + self.assertEqual(self._context, pre_args._plugin_context) + self.assertEqual(self._tap_service, pre_args.tap_service) + post_args = self.driver.delete_tap_service_postcommit.call_args[0][0] + self.assertEqual(self._context, post_args._plugin_context) + self.assertEqual(self._tap_service, post_args.tap_service) def test_delete_tap_service_with_flow(self): with self.tap_service() as ts, \ - self.tap_flow(tap_service=ts['id']) as tf: + self.tap_flow(tap_service=ts['id']): self._plugin.delete_tap_service(self._context, ts['id']) - expected_msg = { - 'tap_service': self._tap_service, - 'taas_id': mock.ANY, - 'port': self._port_details, - } - self._plugin.agent_rpc.assert_has_calls([ - mock.call.delete_tap_service(self._context, expected_msg, - self._host_id), - ]) - self._tap_flow['id'] = tf['id'] - expected_msg = { - 'tap_flow': self._tap_flow, - 'taas_id': mock.ANY, - 'port_mac': self._port_details['mac_address'], - 'port': self._port_details, - } - self._plugin.agent_rpc.assert_has_calls([ - mock.call.delete_tap_flow(self._context, expected_msg, - self._host_id), + self.driver.assert_has_calls([ + mock.call.delete_tap_flow_precommit(mock.ANY), + mock.call.delete_tap_flow_postcommit(mock.ANY), + mock.call.delete_tap_service_precommit(mock.ANY), + mock.call.delete_tap_service_postcommit(mock.ANY), ]) + pre_args = self.driver.delete_tap_flow_precommit.call_args[0][0] + self.assertEqual(self._context, pre_args._plugin_context) + self.assertEqual(self._tap_flow, pre_args.tap_flow) + post_args = self.driver.delete_tap_flow_postcommit.call_args[0][0] + self.assertEqual(self._context, post_args._plugin_context) + self.assertEqual(self._tap_flow, post_args.tap_flow) + pre_args = self.driver.delete_tap_service_precommit.call_args[0][0] + self.assertEqual(self._context, pre_args._plugin_context) + self.assertEqual(self._tap_service, pre_args.tap_service) + post_args = self.driver.delete_tap_service_postcommit.call_args[0][0] + self.assertEqual(self._context, post_args._plugin_context) + self.assertEqual(self._tap_service, post_args.tap_service) def test_delete_tap_service_non_existent(self): with testtools.ExpectedException(taas_ext.TapServiceNotFound): self._plugin.delete_tap_service(self._context, 'non-existent') + def test_delete_tap_service_failed_on_service_driver(self): + attr = {'delete_tap_service_postcommit.side_effect': DummyError} + self.driver.configure_mock(**attr) + with self.tap_service() as ts: + with testtools.ExpectedException(DummyError): + self._plugin.delete_tap_service(self._context, ts['id']) + def test_create_tap_flow(self): with self.tap_service() as ts, self.tap_flow(tap_service=ts['id']): pass @@ -177,18 +206,39 @@ class TestTaasPlugin(testlib_api.SqlTestCase): self.tap_flow(tap_service=ts['id'], tenant_id='other-tenant'): pass + def test_create_tap_flow_failed_on_service_driver(self): + with self.tap_service() as ts: + attr = {'create_tap_flow_postcommit.side_effect': DummyError} + self.driver.configure_mock(**attr) + with testtools.ExpectedException(DummyError): + self._tap_flow['tap_service_id'] = ts['id'] + req = { + 'tap_flow': self._tap_flow, + } + with mock.patch.object(self._plugin, '_get_port_details', + return_value=self._port_details): + self._plugin.create_tap_flow(self._context, req) + def test_delete_tap_flow(self): with self.tap_service() as ts, \ self.tap_flow(tap_service=ts['id']) as tf: self._plugin.delete_tap_flow(self._context, tf['id']) self._tap_flow['id'] = tf['id'] - expected_msg = { - 'tap_flow': self._tap_flow, - 'taas_id': mock.ANY, - 'port_mac': self._port_details['mac_address'], - 'port': self._port_details, - } - self._plugin.agent_rpc.assert_has_calls([ - mock.call.delete_tap_flow(self._context, expected_msg, - self._host_id), + self.driver.assert_has_calls([ + mock.call.delete_tap_flow_precommit(mock.ANY), + mock.call.delete_tap_flow_postcommit(mock.ANY), ]) + pre_args = self.driver.delete_tap_flow_precommit.call_args[0][0] + self.assertEqual(self._context, pre_args._plugin_context) + self.assertEqual(self._tap_flow, pre_args.tap_flow) + post_args = self.driver.delete_tap_flow_postcommit.call_args[0][0] + self.assertEqual(self._context, post_args._plugin_context) + self.assertEqual(self._tap_flow, post_args.tap_flow) + + def test_delete_tap_flow_failed_on_service_driver(self): + with self.tap_service() as ts, \ + self.tap_flow(tap_service=ts['id']) as tf: + attr = {'delete_tap_flow_postcommit.side_effect': DummyError} + self.driver.configure_mock(**attr) + with testtools.ExpectedException(DummyError): + self._plugin.delete_tap_flow(self._context, tf['id'])