L2 agent extension support for Tap-as-a-Service

This patch adds L2 agent extension for TaaS.

Change-Id: Ife1807c04d8f0f44f067e1905890262ae2f7e426
This commit is contained in:
Kazuhiro Suzuki 2017-07-04 15:16:26 +09:00
parent 7fa6d23311
commit 33b116ee7e
12 changed files with 271 additions and 165 deletions

View File

@ -7,5 +7,4 @@ A `local.conf` recipe to enable tap-as-a-service::
[[local|localrc]]
enable_plugin tap-as-a-service https://github.com/openstack/tap-as-a-service
enable_service taas
enable_service taas_openvswitch_agent
TAAS_SERVICE_DRIVER=TAAS:TAAS:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default

View File

@ -31,20 +31,6 @@ function configure_taas_plugin {
_neutron_service_plugin_class_add taas
}
function configure_taas_openvswitch_agent {
local conf=$TAAS_OVS_AGENT_CONF_FILE
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
iniset $conf taas vlan_range_end 3500
}
function start_taas_openvswitch_agent {
run_process taas_openvswitch_agent "$TAAS_OVS_AGENT_BINARY --config-file $NEUTRON_CONF --config-file $TAAS_OVS_AGENT_CONF_FILE"
}
if is_service_enabled taas; then
if [[ "$1" == "stack" ]]; then
if [[ "$2" == "pre-install" ]]; then
@ -67,20 +53,22 @@ if is_service_enabled taas; then
fi
fi
if is_service_enabled taas_openvswitch_agent; then
if is_service_enabled q-agt neutron-agent; then
if [[ "$1" == "stack" ]]; then
if [[ "$2" == "pre-install" ]]; then
:
elif [[ "$2" == "install" ]]; then
install_taas
elif [[ "$2" == "post-config" ]]; then
configure_taas_openvswitch_agent
if is_service_enabled q-agt neutron-agent; then
source $NEUTRON_DIR/devstack/lib/l2_agent
plugin_agent_add_l2_agent_extension taas
configure_l2_agent
fi
elif [[ "$2" == "extra" ]]; then
# NOTE(yamamoto): This agent should be run after ovs-agent
# sets up its bridges. (bug 1515104)
start_taas_openvswitch_agent
:
fi
elif [[ "$1" == "unstack" ]]; then
stop_process taas_openvswitch_agent
:
fi
fi

View File

@ -0,0 +1,91 @@
# Copyright 2017 FUJITSU LABORATORIES LTD.
# Copyright 2016 NEC Technologies India Pvt. Ltd.
#
# 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
import six
from neutron.agent.l2 import l2_agent_extension
from neutron_taas.services.taas.agents.ovs import taas_ovs_agent
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
OPTS = [
cfg.IntOpt(
'taas_agent_periodic_interval',
default=5,
help=_('Seconds between periodic task runs')
)
]
cfg.CONF.register_opts(OPTS)
@six.add_metaclass(abc.ABCMeta)
class TaasAgentDriver(object):
"""Defines stable abstract interface for TaaS Agent Driver."""
@abc.abstractmethod
def initialize(self):
"""Perform Taas agent driver initialization."""
def consume_api(self, agent_api):
"""Consume the AgentAPI instance from the TaasAgentExtension class
:param agent_api: An instance of an agent specific API
"""
@abc.abstractmethod
def create_tap_service(self, tap_service):
"""Create a Tap Service request in driver."""
@abc.abstractmethod
def create_tap_flow(self, tap_flow):
"""Create a tap flow request in driver."""
@abc.abstractmethod
def delete_tap_service(self, tap_service):
"""delete a Tap Service request in driver."""
@abc.abstractmethod
def delete_tap_flow(self, tap_flow):
"""Delete a tap flow request in driver."""
class TaasAgentExtension(l2_agent_extension.L2AgentExtension):
def initialize(self, connection, driver_type):
"""Initialize agent extension."""
self.taas_agent = taas_ovs_agent.TaasOvsAgentRpcCallback(
cfg.CONF, driver_type)
self.taas_agent.consume_api(self.agent_api)
self.taas_agent.initialize()
def consume_api(self, agent_api):
"""Receive neutron agent API object
Allows an extension to gain access to resources internal to the
neutron agent and otherwise unavailable to the extension.
"""
self.agent_api = agent_api
def handle_port(self, context, port):
pass
def delete_port(self, context, port):
pass

View File

@ -1,75 +0,0 @@
# 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.
import sys
import eventlet
eventlet.monkey_patch()
from oslo_config import cfg
from oslo_service import service
from neutron.agent.common import config
from neutron.common import config as common_config
from neutron.common import rpc as n_rpc
from neutron_taas._i18n import _
from neutron_taas.common import topics
from neutron_taas.services.taas.agents.ovs import taas_ovs_agent
OPTS = [
cfg.IntOpt(
'taas_agent_periodic_interval',
default=5,
help=_('Seconds between periodic task runs')
)
]
class TaaSOVSAgentService(n_rpc.Service):
def start(self):
super(TaaSOVSAgentService, self).start()
self.tg.add_timer(
cfg.CONF.taas_agent_periodic_interval,
self.manager.periodic_tasks,
None,
None
)
def main():
# Load the configuration parameters.
cfg.CONF.register_opts(OPTS)
config.register_root_helper(cfg.CONF)
common_config.init(sys.argv[1:])
config.setup_logging()
# Set up RPC
mgr = taas_ovs_agent.TaasOvsAgentRpcCallback(cfg.CONF)
endpoints = [mgr]
conn = n_rpc.create_connection()
conn.create_consumer(topics.TAAS_AGENT, endpoints, fanout=False)
conn.consume_in_threads()
svc = TaaSOVSAgentService(
host=cfg.CONF.host,
topic=topics.TAAS_PLUGIN,
manager=mgr
)
service.launch(cfg.CONF, svc).wait()
if __name__ == '__main__':
main()

View File

@ -14,15 +14,15 @@
# under the License.
from neutron.agent.common import config
from neutron.common import rpc as n_rpc
from neutron import manager
from neutron_taas._i18n import _
from neutron_taas.common import topics
from neutron_taas.services.taas.agents import taas_agent_api as api
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
from oslo_service import service
LOG = logging.getLogger(__name__)
@ -37,30 +37,25 @@ class TaasOvsPluginApi(api.TaasPluginApiMixin):
class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
def __init__(self, conf):
def __init__(self, conf, driver_type):
LOG.debug("TaaS OVS Agent initialize called")
self.conf = conf
taas_driver_class_path = cfg.CONF.taas.driver
self.taas_enabled = cfg.CONF.taas.enabled
self.driver_type = driver_type
self.root_helper = config.get_root_helper(conf)
try:
self.taas_driver = importutils.import_object(
taas_driver_class_path, self.root_helper)
LOG.debug("TaaS Driver Loaded: '%s'", taas_driver_class_path)
except ImportError:
msg = _('Error importing TaaS device driver: %s')
raise ImportError(msg % taas_driver_class_path)
# setup RPC to msg taas plugin
self.taas_plugin_rpc = TaasOvsPluginApi(topics.TAAS_PLUGIN,
conf.host)
super(TaasOvsAgentRpcCallback, self).__init__()
return
def initialize(self):
self.taas_driver = manager.NeutronManager.load_class_for_provider(
'neutron_taas.taas.agent_drivers', self.driver_type)()
self.taas_driver.consume_api(self.agent_api)
self.taas_driver.initialize()
self._taas_rpc_setup()
TaasOvsAgentService(self).start()
def consume_api(self, agent_api):
self.agent_api = agent_api
def _invoke_driver_for_plugin_api(self, context, args, func_name):
LOG.debug("Invoking Driver for %(func_name)s from agent",
@ -117,10 +112,33 @@ class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
tap_flow_msg,
'delete_tap_flow')
def periodic_tasks(self, argv):
def _taas_rpc_setup(self):
# setup RPC to msg taas plugin
self.taas_plugin_rpc = TaasOvsPluginApi(
topics.TAAS_PLUGIN, self.conf.host)
endpoints = [self]
conn = n_rpc.create_connection()
conn.create_consumer(topics.TAAS_AGENT, endpoints, fanout=False)
conn.consume_in_threads()
def periodic_tasks(self):
#
# Regenerate the flow in br-tun's TAAS_SEND_FLOOD table
# to ensure all existing tunnel ports are included.
#
self.taas_driver.update_tunnel_flood_flow()
pass
class TaasOvsAgentService(service.Service):
def __init__(self, driver):
super(TaasOvsAgentService, self).__init__()
self.driver = driver
def start(self):
super(TaasOvsAgentService, self).start()
self.tg.add_timer(
int(cfg.CONF.taas_agent_periodic_interval),
self.driver.periodic_tasks,
None
)

View File

@ -50,6 +50,14 @@ class TaasAgentRpcCallbackMixin(object):
def __init__(self):
super(TaasAgentRpcCallbackMixin, self).__init__()
def consume_api(self, agent_api):
"""Receive neutron agent API object
Allows an extension to gain access to resources internal to the
neutron agent and otherwise unavailable to the extension.
"""
self.agent_api = agent_api
def create_tap_service(self, context, tap_service, host):
"""Handle RPC cast from plugin to create a tap service."""
pass

View File

@ -16,10 +16,12 @@
from neutron.agent.common import ovs_lib
from neutron.agent.linux import utils
from neutron.conf.agent import common
# from neutron.plugins.openvswitch.common import constants as ovs_consts
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
as ovs_consts
from neutron_taas.services.taas.drivers import taas_base
from neutron_taas.services.taas.agents.extensions import taas as taas_base
from oslo_config import cfg
from oslo_log import log as logging
import ovs_constants as taas_ovs_consts
import ovs_utils as taas_ovs_utils
@ -34,14 +36,16 @@ class OVSBridge_tap_extension(ovs_lib.OVSBridge):
super(OVSBridge_tap_extension, self).__init__(br_name)
class OvsTaasDriver(taas_base.TaasDriverBase):
def __init__(self, root_helper):
class OvsTaasDriver(taas_base.TaasAgentDriver):
def __init__(self):
super(OvsTaasDriver, self).__init__()
LOG.debug("Initializing Taas OVS Driver")
self.agent_api = None
self.root_helper = common.get_root_helper(cfg.CONF)
self.root_helper = root_helper
self.int_br = OVSBridge_tap_extension('br-int', self.root_helper)
self.tun_br = OVSBridge_tap_extension('br-tun', self.root_helper)
def initialize(self):
self.int_br = self.agent_api.request_int_br()
self.tun_br = self.agent_api.request_tun_br()
self.tap_br = OVSBridge_tap_extension('br-tap', self.root_helper)
# Prepare OVS bridges for TaaS
@ -185,6 +189,9 @@ class OvsTaasDriver(taas_base.TaasDriverBase):
return
def consume_api(self, agent_api):
self.agent_api = agent_api
def create_tap_service(self, tap_service):
taas_id = tap_service['taas_id']
port = tap_service['port']

View File

@ -1,37 +0,0 @@
# 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.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class TaasDriverBase(object):
@abc.abstractmethod
def create_tap_service(self, tap_service):
pass
@abc.abstractmethod
def delete_tap_service(self, tap_service):
pass
@abc.abstractmethod
def create_tap_flow(self, tap_flow):
pass
@abc.abstractmethod
def delete_tap_flow(self, tap_flow):
pass

View File

@ -0,0 +1,105 @@
# Copyright 2017 FUJITSU LABORATORIES LTD.
# 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 copy
import mock
from webob import exc
from oslo_utils import uuidutils
from neutron.tests.unit.api.v2 import test_base as test_api_v2
from neutron.tests.unit.extensions import base as test_api_v2_extension
from neutron_taas.extensions import taas as taas_ext
_uuid = uuidutils.generate_uuid
_get_path = test_api_v2._get_path
TAP_SERVICE_PATH = 'taas/tap_services'
TAP_FLOW_PATH = 'taas/tap_flows'
class TaasExtensionTestCase(test_api_v2_extension.ExtensionTestCase):
fmt = 'json'
def setUp(self):
super(TaasExtensionTestCase, self).setUp()
self._setUpExtension(
'neutron_taas.extensions.taas.TaasPluginBase',
'TAAS',
taas_ext.RESOURCE_ATTRIBUTE_MAP,
taas_ext.Taas,
'taas',
plural_mappings={}
)
def test_create_tap_service(self):
tenant_id = _uuid()
tap_service_data = {
'tenant_id': tenant_id,
'name': 'MyTap',
'description': 'This is my tap service',
'port_id': _uuid(),
'project_id': tenant_id,
}
data = {'tap_service': tap_service_data}
expected_ret_val = copy.copy(data['tap_service'])
expected_ret_val.update({'id': _uuid()})
instance = self.plugin.return_value
instance.create_tap_service.return_value = expected_ret_val
res = self.api.post(_get_path(TAP_SERVICE_PATH, fmt=self.fmt),
self.serialize(data),
content_type='application/%s' % self.fmt)
instance.create_tap_service.assert_called_with(
mock.ANY,
tap_service=data)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
res = self.deserialize(res)
self.assertIn('tap_service', res)
self.assertEqual(expected_ret_val, res['tap_service'])
def test_delete_tap_service(self):
self._test_entity_delete('tap_service')
def test_create_tap_flow(self):
tenant_id = _uuid()
tap_flow_data = {
'tenant_id': tenant_id,
'name': 'MyTapFlow',
'description': 'This is my tap flow',
'direction': 'BOTH',
'tap_service_id': _uuid(),
'source_port': _uuid(),
'project_id': tenant_id,
}
data = {'tap_flow': tap_flow_data}
expected_ret_val = copy.copy(data['tap_flow'])
expected_ret_val.update({'id': _uuid()})
instance = self.plugin.return_value
instance.create_tap_flow.return_value = expected_ret_val
res = self.api.post(_get_path(TAP_FLOW_PATH, fmt=self.fmt),
self.serialize(data),
content_type='application/%s' % self.fmt)
instance.create_tap_flow.assert_called_with(
mock.ANY,
tap_flow=data)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
res = self.deserialize(res)
self.assertIn('tap_flow', res)
self.assertEqual(expected_ret_val, res['tap_flow'])
def test_delete_tap_flow(self):
self._test_entity_delete('tap_flow')

View File

@ -45,8 +45,10 @@ mapping_file = babel.cfg
output_file = neutron_taas/locale/neutron_taas.pot
[entry_points]
console_scripts =
neutron-taas-openvswitch-agent = neutron_taas.services.taas.agents.ovs.agent:main
neutron.agent.l2.extensions =
taas = neutron_taas.services.taas.agents.extensions.taas:TaasAgentExtension
neutron_taas.taas.agent_drivers =
ovs = neutron_taas.services.taas.drivers.linux.ovs_taas:OvsTaasDriver
neutron.service_plugins =
taas = neutron_taas.services.taas.taas_plugin:TaasPlugin
neutron.db.alembic_migrations =