GRE/ERSPAN mirroring for taas

Server side of the Tap Mirroring feature.
The RPC and other plugin parts will be in the next patch
for the OVS driver.

Related-Bug: #2015471
Change-Id: I9a38d9aedbfcec13636420de0b739277a1a8f691
This commit is contained in:
elajkat 2023-06-05 13:08:04 +02:00 committed by Lajos Katona
parent 8d82419125
commit 6e28f4bb85
14 changed files with 886 additions and 1 deletions

View File

@ -27,6 +27,9 @@ function configure_taas_plugin {
neutron_server_config_add $TAAS_PLUGIN_CONF_FILE
neutron_service_plugin_class_add taas
neutron_deploy_rootwrap_filters $TAAS_PLUGIN_PATH
if is_service_enabled tap_mirror; then
neutron_service_plugin_class_add tapmirror
fi
}
if is_service_enabled taas; then

View File

@ -30,3 +30,19 @@
# DELETE /taas/tap_services/{id}
#"delete_tap_service": "rule:admin_or_owner"
# Create a Tap Mirror
# POST /taas/tap_mirrors
#"create_tap_mirror": "rule:admin_or_owner"
# Update a Tap Mirror
# PUT /taas/tap_mirrors/{id}
#"update_tap_mirror": "rule:admin_or_owner"
# Show a Tap Mirror
# GET /taas/tap_mirrors/{id}
#"get_tap_mirror": "rule:admin_or_owner"
# Delete a Tap Mirror
# DELETE /taas/tap_mirrors/{id}
#"delete_tap_mirror": "rule:admin_or_owner"

View File

@ -0,0 +1,50 @@
# 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 alembic import op
from neutron_lib.db import constants as db_const
import sqlalchemy as sa
"""ERSPAN or GRE mirroring for taas
Revision ID: f8f1f10ebaf9
Revises: ccbcc559d175
Create Date: 2023-05-05 11:59:10.007052
"""
# revision identifiers, used by Alembic.
revision = 'f8f1f10ebaf9'
down_revision = 'ccbcc559d175'
mirror_type_enum = sa.Enum('erspanv1', 'gre', name='tapmirrors_type')
def upgrade():
op.create_table(
'tap_mirrors',
sa.Column('id', sa.String(length=db_const.UUID_FIELD_SIZE),
primary_key=True, nullable=False),
sa.Column('project_id', sa.String(
length=db_const.PROJECT_ID_FIELD_SIZE), nullable=True),
sa.Column('name', sa.String(length=db_const.NAME_FIELD_SIZE),
nullable=True),
sa.Column('description', sa.String(
length=db_const.DESCRIPTION_FIELD_SIZE), nullable=True),
sa.Column('port_id', sa.String(db_const.UUID_FIELD_SIZE),
nullable=False),
sa.Column('directions', sa.String(255), nullable=False),
sa.Column('remote_ip', sa.String(db_const.IP_ADDR_FIELD_SIZE)),
sa.Column('mirror_type', mirror_type_enum, nullable=False)
)

View File

@ -1 +1 @@
ccbcc559d175
f8f1f10ebaf9

View File

@ -0,0 +1,153 @@
# 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 sqlalchemy as sa
from sqlalchemy.orm import exc
from neutron_lib.db import api as db_api
from neutron_lib.db import constants as db_const
from neutron_lib.db import model_base
from neutron_lib.db import model_query
from neutron_lib.db import utils as db_utils
from neutron_lib import exceptions as n_lib_exception
from neutron_lib.plugins import directory
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from neutron_taas.extensions import _tap_mirror as mirror_extension
from neutron_taas.extensions import tap_mirror as tap_m_extension
LOG = logging.getLogger(__name__)
# TODO(lajoskatona): Remove these exceptions when
# https://review.opendev.org/c/openstack/neutron-lib/+/895603 is
# released.
class TapMirrorNotFound(n_lib_exception.NotFound):
message = _("Tap Mirror %(mirror_id)s does not exist")
class TapMirrorTunnelConflict(n_lib_exception.Conflict):
message = _("Tap Mirror with tunnel_id %(tunnel_id)s already used")
class TapMirror(model_base.BASEV2, model_base.HasId,
model_base.HasProjectNoIndex):
"""Represents a Tap Mirror
A Tap Mirror can be a GRE or ERSPAN tunnel representation.
"""
__tablename__ = 'tap_mirrors'
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
port_id = sa.Column(sa.String(db_const.UUID_FIELD_SIZE),
nullable=False)
directions = sa.Column(sa.String(255), nullable=False)
remote_ip = sa.Column(sa.String(db_const.IP_ADDR_FIELD_SIZE),
nullable=False)
mirror_type = sa.Column(sa.Enum('erspanv1', 'gre',
name='tapmirrors_type'),
nullable=False)
api_collections = [mirror_extension.COLLECTION_NAME]
collection_resource_map = {
mirror_extension.COLLECTION_NAME: mirror_extension.RESOURCE_NAME}
class Taas_mirror_db_Mixin(tap_m_extension.TapMirrorBase):
def _core_plugin(self):
return directory.get_plugin()
def _make_tap_mirror_dict(self, tap_mirror, fields=None):
res = {
'id': tap_mirror.get('id'),
'project_id': tap_mirror.get('project_id'),
'name': tap_mirror.get('name'),
'description': tap_mirror.get('description'),
'port_id': tap_mirror.get('port_id'),
'directions': jsonutils.loads(tap_mirror.get('directions')),
'remote_ip': tap_mirror.get('remote_ip'),
'mirror_type': tap_mirror.get('mirror_type'),
}
return db_utils.resource_fields(res, fields)
@db_api.retry_if_session_inactive()
def get_port_details(self, context, port_id):
with db_api.CONTEXT_READER.using(context):
port = self._core_plugin().get_port(context, port_id)
return port
@db_api.retry_if_session_inactive()
@log_helpers.log_method_call
def create_tap_mirror(self, context, tap_mirror):
fields = tap_mirror['tap_mirror']
project_id = fields.get('project_id')
with db_api.CONTEXT_WRITER.using(context):
tap_mirror_db = TapMirror(
id=uuidutils.generate_uuid(),
project_id=project_id,
name=fields.get('name'),
description=fields.get('description'),
port_id=fields.get('port_id'),
directions=jsonutils.dumps(fields.get('directions')),
remote_ip=fields.get('remote_ip'),
mirror_type=fields.get('mirror_type'),
)
# TODO(lajoskatona): Check tunnel_id...
context.session.add(tap_mirror_db)
return self._make_tap_mirror_dict(tap_mirror_db)
def _get_tap_mirror(self, context, id):
with db_api.CONTEXT_READER.using(context):
try:
return model_query.get_by_id(context, TapMirror, id)
except exc.NoResultFound:
raise TapMirrorNotFound(mirror_id=id)
@log_helpers.log_method_call
def get_tap_mirror(self, context, id, fields=None):
with db_api.CONTEXT_READER.using(context):
t_m = self._get_tap_mirror(context, id)
return self._make_tap_mirror_dict(t_m, fields)
@db_api.retry_if_session_inactive()
@log_helpers.log_method_call
def get_tap_mirrors(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
with db_api.CONTEXT_READER.using(context):
return model_query.get_collection(context, TapMirror,
self._make_tap_mirror_dict,
filters=filters, fields=fields)
@log_helpers.log_method_call
def delete_tap_mirror(self, context, id):
with db_api.CONTEXT_WRITER.using(context):
count = context.session.query(TapMirror).filter_by(id=id).delete()
if not count:
raise TapMirrorNotFound(mirror_id=id)
@db_api.retry_if_session_inactive()
@log_helpers.log_method_call
def update_tap_mirror(self, context, id, tap_mirror):
t_m = tap_mirror['tap_mirror']
with db_api.CONTEXT_WRITER.using(context):
tap_mirror_db = self._get_tap_mirror(context, id)
tap_mirror_db.update(t_m)
return self._make_tap_mirror_dict(tap_mirror_db)

View File

@ -0,0 +1,77 @@
# 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.
# TODO(lajoskatona): Remove this file, when patch
# https://review.opendev.org/c/openstack/neutron-lib/+/885354 is released
from neutron_lib.db import constants as db_const
ALIAS = 'tap-mirror'
IS_SHIM_EXTENSION = False
IS_STANDARD_ATTR_EXTENSION = False
NAME = "Tap as a Service GRE or ERSPAN mirror"
DESCRIPTION = "Neutron Tap as a Service extension for GRE or ERSPAN mirroring."
UPDATED_TIMESTAMP = "2023-05-05T11:45:00-00:00"
RESOURCE_NAME = 'tap_mirror'
COLLECTION_NAME = 'tap_mirrors'
mirror_types_list = ['erspanv1', 'gre']
DIRECTION_SPEC = {
'type:dict': {
'IN': {'type:integer': None, 'default': None, 'required': False},
'OUT': {'type:integer': None, 'default': None, 'required': False}
}
}
RESOURCE_ATTRIBUTE_MAP = {
COLLECTION_NAME: {
'id': {
'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None}, 'is_visible': True,
'primary_key': True},
'project_id': {
'allow_post': True, 'allow_put': False,
'validate': {'type:string': db_const.PROJECT_ID_FIELD_SIZE},
'required_by_policy': True, 'is_filter': True,
'is_sort_key': True, 'is_visible': True},
'name': {
'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True, 'default': ''},
'description': {
'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True, 'default': ''},
'port_id': {
'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'enforce_policy': True, 'is_visible': True},
'directions': {
'allow_post': True, 'allow_put': False,
'validate': DIRECTION_SPEC,
'is_visible': True},
'remote_ip': {
'allow_post': True, 'allow_put': False,
'validate': {'type:ip_address': None},
'is_visible': True},
'mirror_type': {
'allow_post': True, 'allow_put': False,
'validate': {'type:values': mirror_types_list},
'is_visible': True},
}
}
SUB_RESOURCE_ATTRIBUTE_MAP = None
ACTION_MAP = {}
ACTION_STATUS = {}
REQUIRED_EXTENSIONS = []
OPTIONAL_EXTENSIONS = []

View File

@ -0,0 +1,81 @@
# 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 neutron_lib.api import extensions as api_extensions
from neutron_lib.services import base as service_base
from neutron.api.v2 import resource_helper
# TODO(lajoskatona): use the api definition from neutron-lib when
# https://review.opendev.org/c/openstack/neutron-lib/+/885354 released
from neutron_taas.extensions import _tap_mirror as tap_mirror_api_def
class Tap_mirror(api_extensions.APIExtensionDescriptor):
api_definition = tap_mirror_api_def
@classmethod
def get_resources(cls):
plural_mappings = resource_helper.build_plural_mappings(
{}, tap_mirror_api_def.RESOURCE_ATTRIBUTE_MAP)
resources = resource_helper.build_resource_info(
plural_mappings,
tap_mirror_api_def.RESOURCE_ATTRIBUTE_MAP,
tap_mirror_api_def.ALIAS,
translate_name=False,
allow_bulk=False)
return resources
@classmethod
def get_plugin_interface(cls):
return TapMirrorBase
class TapMirrorBase(service_base.ServicePluginBase, metaclass=abc.ABCMeta):
def get_plugin_description(self):
return tap_mirror_api_def.DESCRIPTION
@classmethod
def get_plugin_type(cls):
return tap_mirror_api_def.ALIAS
@abc.abstractmethod
def create_tap_mirror(self, context, tap_mirror):
"""Create a Tap Mirror."""
pass
@abc.abstractmethod
def get_tap_mirror(self, context, id, fields=None):
"""Get a Tap Mirror."""
pass
@abc.abstractmethod
def get_tap_mirrors(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
"""List all Tap Mirrors."""
pass
@abc.abstractmethod
def delete_tap_mirror(self, context, id):
"""Delete a Tap Mirror."""
pass
@abc.abstractmethod
def update_tap_mirror(self, context, id, tap_mirror):
"""Update a Tap Mirror."""
pass

View File

@ -13,6 +13,7 @@
import itertools
from neutron_taas.policies import tap_flow
from neutron_taas.policies import tap_mirror
from neutron_taas.policies import tap_service
@ -20,4 +21,5 @@ def list_rules():
return itertools.chain(
tap_flow.list_rules(),
tap_service.list_rules(),
tap_mirror.list_rules(),
)

View File

@ -0,0 +1,66 @@
# 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 oslo_policy import policy
from neutron_lib.policy import RULE_ADMIN_OR_OWNER
rules = [
policy.DocumentedRuleDefault(
'create_tap_mirror',
RULE_ADMIN_OR_OWNER,
'Create a Tap Mirror',
[
{
'method': 'POST',
'path': '/taas/tap_mirrors',
}
]
),
policy.DocumentedRuleDefault(
'update_tap_mirror',
RULE_ADMIN_OR_OWNER,
'Update a Tap Mirror',
[
{
'method': 'PUT',
'path': '/taas/tap_mirrors/{id}',
}
]
),
policy.DocumentedRuleDefault(
'get_tap_mirror',
RULE_ADMIN_OR_OWNER,
'Show a Tap Mirror',
[
{
'method': 'GET',
'path': '/taas/tap_mirrors/{id}',
}
]
),
policy.DocumentedRuleDefault(
'delete_tap_mirror',
RULE_ADMIN_OR_OWNER,
'Delete a Tap Mirror',
[
{
'method': 'DELETE',
'path': '/taas/tap_mirrors/{id}',
}
]
),
]
def list_rules():
return rules

View File

@ -0,0 +1,133 @@
# 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.db import servicetype_db as st_db
from neutron.services import provider_configuration as pconf
from neutron.services import service_base
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib.db import api as db_api
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import taas as taas_exc
from neutron_taas.common import constants as taas_consts
from neutron_taas.db import tap_mirror_db
from neutron_taas.extensions import _tap_mirror as t_m_api_def
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
@registry.has_registry_receivers
class TapMirrorPlugin(tap_mirror_db.Taas_mirror_db_Mixin):
supported_extension_aliases = [t_m_api_def.ALIAS]
path_prefix = "/taas"
def __init__(self):
LOG.debug("TAP Mirror PLUGIN INITIALIZED")
super().__init__()
self.service_type_manager = st_db.ServiceTypeManager.get_instance()
self.service_type_manager.add_provider_configuration(
taas_consts.TAAS,
pconf.ProviderConfiguration('neutron_taas'))
# Load the service driver from neutron.conf.
self.drivers, self.default_provider = service_base.load_drivers(
taas_consts.TAAS, self)
# Associate driver names to driver objects
for driver_name, driver in self.drivers.items():
driver.name = driver_name
self.driver = self._get_driver_for_provider(self.default_provider)
LOG.info(("Tap Mirror plugin using service drivers: "
"%(service_drivers)s, default: %(default_driver)s"),
{'service_drivers': self.drivers.keys(),
'default_driver': self.default_provider})
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)
@log_helpers.log_method_call
def create_tap_mirror(self, context, tap_mirror):
t_m = tap_mirror['tap_mirror']
port_id = t_m['port_id']
project_id = t_m['project_id']
with db_api.CONTEXT_READER.using(context):
# Get port details
port = self.get_port_details(context, port_id)
if port['tenant_id'] != project_id:
raise taas_exc.PortDoesNotBelongToTenant()
host = port['binding:host_id']
if host is not None:
LOG.debug("Host on which the port is created = %s" % host)
else:
LOG.debug("Host could not be found, Port Binding disbaled!")
with db_api.CONTEXT_WRITER.using(context):
self._validate_tap_tunnel_id(context, t_m['directions'])
tm = super().create_tap_mirror(context, tap_mirror)
return tm
def _validate_tap_tunnel_id(self, context, mirror_directions):
mirrors = self.get_tap_mirrors(context)
for mirror in mirrors:
for direction, tunnel_id in mirror['directions'].items():
if tunnel_id in mirror_directions.values():
raise tap_mirror_db.TapMirrorTunnelConflict(
tunnel_id=tunnel_id)
@log_helpers.log_method_call
def delete_tap_mirror(self, context, id):
with db_api.CONTEXT_READER.using(context):
tm = self.get_tap_mirror(context, id)
if tm:
with db_api.CONTEXT_WRITER.using(context):
# check if tunnel id was really deleted
super().delete_tap_mirror(context, id)
@registry.receives(resources.PORT, [events.PRECOMMIT_DELETE])
@log_helpers.log_method_call
def handle_delete_port(self, resource, event, trigger, payload):
context = payload.context
deleted_port = payload.latest_state
if not deleted_port:
LOG.error("Tap Mirror: Handle Delete Port: "
"Invalid port object received")
return
deleted_port_id = deleted_port['id']
LOG.info("Tap Mirror: Handle Delete Port: %s", deleted_port_id)
tap_mirrors = self.get_tap_mirrors(
context,
filters={'port_id': [deleted_port_id]}, fields=['id'])
for t_m in tap_mirrors:
try:
self.delete_tap_mirror(context, t_m['id'])
except tap_mirror_db.TapMirrorNotFound:
# TODO(lajoskaton): change this to use exception from n-lib,
# when it will be released
LOG.debug("Not found tap_mirror: %s", t_m['id'])

View File

@ -0,0 +1,118 @@
# 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.tests.unit import testlib_api
from neutron_lib import context
from oslo_utils import importutils
from oslo_utils import uuidutils
from neutron_taas.db import tap_mirror_db
DB_PLUGIN_KLAAS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
_uuid = uuidutils.generate_uuid
class TapMirrorDbTestCase(testlib_api.SqlTestCase):
"""Unit test for Tap Mirror DB support."""
def setUp(self):
super(TapMirrorDbTestCase, self).setUp()
self.ctx = context.get_admin_context()
self.db_mixin = tap_mirror_db.Taas_mirror_db_Mixin()
self.plugin = importutils.import_object(DB_PLUGIN_KLAAS)
self.project_id = 'fake-tenant-id'
def _get_tap_mirror_data(self, name='tm-1', port_id=None,
directions='{"IN": "99"}', remote_ip='10.99.8.3',
mirror_type='erspanv1'):
port_id = port_id or _uuid()
return {"tap_mirror": {"name": name,
"project_id": self.project_id,
"description": "test tap mirror",
"port_id": port_id,
'directions': directions,
'remote_ip': remote_ip,
'mirror_type': mirror_type
}
}
def _get_tap_mirror(self, tap_mirror_id):
"""Helper method to retrieve tap Mirror."""
with self.ctx.session.begin(subtransactions=True):
return self.db_mixin.get_tap_mirror(self.ctx, tap_mirror_id)
def _get_tap_mirrors(self):
"""Helper method to retrieve all tap Mirror."""
with self.ctx.session.begin(subtransactions=True):
return self.db_mixin.get_tap_mirrors(self.ctx)
def _create_tap_mirror(self, tap_mirror):
"""Helper method to create tap Mirror."""
with self.ctx.session.begin(subtransactions=True):
return self.db_mixin.create_tap_mirror(self.ctx, tap_mirror)
def _update_tap_mirror(self, tap_mirror_id, tap_mirror):
"""Helper method to update tap Mirror."""
with self.ctx.session.begin(subtransactions=True):
return self.db_mixin.update_tap_mirror(self.ctx,
tap_mirror_id,
tap_mirror)
def _delete_tap_mirror(self, tap_mirror_id):
"""Helper method to delete tap Mirror."""
with self.ctx.session.begin(subtransactions=True):
return self.db_mixin.delete_tap_mirror(self.ctx, tap_mirror_id)
def test_tap_mirror_get(self):
name = 'test-tap-mirror'
data = self._get_tap_mirror_data(name=name)
result = self._create_tap_mirror(data)
get_result = self._get_tap_mirror(result['id'])
self.assertEqual(name, get_result['name'])
def test_tap_mirror_create(self):
name = 'test-tap-mirror'
port_id = _uuid()
data = self._get_tap_mirror_data(name=name, port_id=port_id)
result = self._create_tap_mirror(data)
self.assertEqual(name, result['name'])
self.assertEqual(port_id, result['port_id'])
def test_tap_mirror_list(self):
name_1 = "tm-1"
data_1 = self._get_tap_mirror_data(name=name_1)
name_2 = "tm-2"
data_2 = self._get_tap_mirror_data(name=name_2)
self._create_tap_mirror(data_1)
self._create_tap_mirror(data_2)
tap_mirrors = self._get_tap_mirrors()
self.assertEqual(2, len(tap_mirrors))
def test_tap_mirror_update(self):
original_name = "tm-1"
updated_name = "tm-1-got-updated"
data = self._get_tap_mirror_data(name=original_name)
tm = self._create_tap_mirror(data)
updated_data = self._get_tap_mirror_data(name=updated_name)
tm_updated = self._update_tap_mirror(tm['id'], updated_data)
self.assertEqual(updated_name, tm_updated['name'])
def test_tap_mirror_delete(self):
data = self._get_tap_mirror_data()
result = self._create_tap_mirror(data)
self._delete_tap_mirror(result['id'])
self.assertRaises(tap_mirror_db.TapMirrorNotFound,
self._get_tap_mirror, result['id'])

View File

@ -0,0 +1,82 @@
# 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
from unittest import mock
from webob import exc
from oslo_utils import uuidutils
from neutron.api import extensions
from neutron.conf import common as conf_common
from neutron.tests.unit.api.v2 import test_base as test_api_v2
from neutron.tests.unit.extensions import base as test_extensions_base
# Uncomment this line when
# https://review.opendev.org/c/openstack/neutron-lib/+/885354 is released
# from neutron_lib.api.definitions import tap_mirror as tap_mirror_api
from neutron_taas import extensions as taas_extensions
# Remove this line when the above api-def is released
from neutron_taas.extensions import _tap_mirror as tap_mirror_api
TAP_MIRROR_PATH = 'taas/tap_mirrors'
class TapMirrorExtensionTestCase(test_extensions_base.ExtensionTestCase):
def setUp(self):
conf_common.register_core_common_config_opts()
extensions.append_api_extensions_path(taas_extensions.__path__)
super(TapMirrorExtensionTestCase, self).setUp()
plural_mappings = {'tap_mirror': 'tap_mirrors'}
self.setup_extension(
'%s.%s' % (taas_extensions.tap_mirror.TapMirrorBase.__module__,
taas_extensions.tap_mirror.TapMirrorBase.__name__),
tap_mirror_api.ALIAS,
taas_extensions.tap_mirror.Tap_mirror,
'taas',
plural_mappings=plural_mappings,
translate_resource_name=False)
self.instance = self.plugin.return_value
def test_create_tap_mirror(self):
project_id = uuidutils.generate_uuid()
tap_mirror_data = {
'project_id': project_id,
'tenant_id': project_id,
'name': 'MyMirror',
'description': 'This is my Tap Mirror',
'port_id': uuidutils.generate_uuid(),
'directions': {"IN": 101},
'remote_ip': '10.99.8.3',
'mirror_type': 'gre',
}
data = {'tap_mirror': tap_mirror_data}
expected_ret_val = copy.copy(tap_mirror_data)
expected_ret_val.update({'id': uuidutils.generate_uuid()})
self.instance.create_tap_mirror.return_value = expected_ret_val
res = self.api.post(test_api_v2._get_path(TAP_MIRROR_PATH,
fmt=self.fmt),
self.serialize(data),
content_type='application/%s' % self.fmt)
self.instance.create_tap_mirror.assert_called_with(
mock.ANY,
tap_mirror=data)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
res = self.deserialize(res)
self.assertIn('tap_mirror', res)
self.assertEqual(expected_ret_val, res['tap_mirror'])
def test_delete_tap_mirror(self):
self._test_entity_delete('tap_mirror')

View File

@ -0,0 +1,103 @@
# 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 contextlib
import testtools
from unittest import mock
from neutron_lib import context
from neutron_lib.exceptions import taas as taas_exc
from neutron_lib import rpc as n_rpc
from neutron_lib.utils import net as n_utils
from oslo_utils import uuidutils
from neutron.tests.unit import testlib_api
from neutron_taas.db import tap_mirror_db as tm_db
from neutron_taas.services.taas import tap_mirror_plugin
class TestTapMirrorPlugin(testlib_api.SqlTestCase):
def setUp(self):
super(TestTapMirrorPlugin, self).setUp()
mock.patch.object(n_rpc, 'Connection', spec=object).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 = tap_mirror_plugin.TapMirrorPlugin()
self._context = context.get_admin_context()
self._project_id = self._tenant_id = uuidutils.generate_uuid()
self._network_id = uuidutils.generate_uuid()
self._host_id = 'host-A'
self._port_id = uuidutils.generate_uuid()
self._port_details = {
'tenant_id': self._tenant_id,
'binding:host_id': self._host_id,
'mac_address': n_utils.get_random_mac(
'fa:16:3e:00:00:00'.split(':')),
}
self._tap_mirror = {
'project_id': self._project_id,
',tenant_id': self._tenant_id,
'name': 'MyMirror',
'description': 'This is my Tap Mirror',
'port_id': self._port_id,
'directions': {"IN": 101},
'remote_ip': '10.99.8.3',
'mirror_type': 'gre',
}
@contextlib.contextmanager
def tap_mirror(self):
req = {
'tap_mirror': self._tap_mirror,
}
with mock.patch.object(self._plugin, 'get_port_details',
return_value=self._port_details):
mirror = self._plugin.create_tap_mirror(self._context, req)
self._tap_mirror['id'] = mock.ANY
# TODO(lajoskatona): Add more checks for the pre/post phases
# (check the next patches)
yield self._plugin.get_tap_mirror(self._context,
mirror['id'])
def test_create_tap_mirror(self):
with self.tap_mirror():
pass
def test_create_tap_mirror_wrong_project_id(self):
self._port_details['project_id'] = 'other-tenant'
self._port_details['tenant_id'] = 'other-tenant'
with testtools.ExpectedException(taas_exc.PortDoesNotBelongToTenant), \
self.tap_mirror():
pass
self.assertEqual([], self.driver.mock_calls)
def test_delete_tap_mrror(self):
with self.tap_mirror() as tm:
self._plugin.delete_tap_mirror(self._context, tm['id'])
self._tap_mirror['id'] = tm['id']
def test_delete_tap_mirror_non_existent(self):
# TODO(lajoskatona): change this to an import to neutron-lib when
# https://review.opendev.org/c/openstack/neutron-lib/+/895603 is
# released.
with testtools.ExpectedException(tm_db.TapMirrorNotFound):
self._plugin.delete_tap_mirror(self._context, 'non-existent')

View File

@ -63,6 +63,7 @@ neutron_taas.taas.agent_drivers =
sriov = neutron_taas.services.taas.drivers.linux.sriov_nic_taas:SriovNicTaasDriver
neutron.service_plugins =
taas = neutron_taas.services.taas.taas_plugin:TaasPlugin
tapmirror = neutron_taas.services.taas.tap_mirror_plugin:TapMirrorPlugin
neutron.db.alembic_migrations =
tap-as-a-service = neutron_taas.db.migration:alembic_migration
oslo.policy.policies =