Merge "GRE/ERSPAN mirroring for taas"
This commit is contained in:
@@ -25,6 +25,9 @@ function install_taas {
|
||||
function configure_taas_plugin {
|
||||
echo "Configuring taas"
|
||||
neutron_service_plugin_class_add taas
|
||||
if is_service_enabled tap_mirror; then
|
||||
neutron_service_plugin_class_add tapmirror
|
||||
fi
|
||||
iniadd /$Q_PLUGIN_CONF_FILE service_providers service_provider "TAAS:TAAS:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default"
|
||||
}
|
||||
|
||||
|
||||
@@ -30,3 +30,24 @@
|
||||
# DELETE /taas/tap_services/{id}
|
||||
#"delete_tap_service": "rule:admin_or_owner"
|
||||
|
||||
# Create a Tap Mirror
|
||||
# POST /taas/tap_mirrors
|
||||
# Intended scope(s): project
|
||||
#"create_tap_mirror": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
|
||||
|
||||
# Update a Tap Mirror
|
||||
# PUT /taas/tap_mirrors/{id}
|
||||
# Intended scope(s): project
|
||||
#"update_tap_mirror": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
|
||||
|
||||
# Show a Tap Mirror
|
||||
# GET /taas/tap_mirrors
|
||||
# GET /taas/tap_mirrors/{id}
|
||||
# Intended scope(s): project
|
||||
#"get_tap_mirror": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
|
||||
|
||||
# Delete a Tap Mirror
|
||||
# DELETE /taas/tap_mirrors/{id}
|
||||
# Intended scope(s): project
|
||||
#"delete_tap_mirror": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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),
|
||||
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)
|
||||
)
|
||||
@@ -1 +1 @@
|
||||
ccbcc559d175
|
||||
f8f1f10ebaf9
|
||||
|
||||
139
neutron_taas/db/tap_mirror_db.py
Normal file
139
neutron_taas/db/tap_mirror_db.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# 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.api.definitions import tap_mirror as mirror_extension
|
||||
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.exceptions import taas as taas_exc
|
||||
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 tap_m_extension
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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 _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):
|
||||
core_plugin = directory.get_plugin()
|
||||
return core_plugin.get_port(context, port_id)
|
||||
|
||||
@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 taas_exc.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 taas_exc.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)
|
||||
78
neutron_taas/extensions/tap_mirror.py
Normal file
78
neutron_taas/extensions/tap_mirror.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# 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.definitions import tap_mirror as tap_mirror_api_def
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
78
neutron_taas/policies/tap_mirror.py
Normal file
78
neutron_taas/policies/tap_mirror.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# 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.conf.policies import base
|
||||
|
||||
|
||||
COLLECTION_PATH = '/taas/tap_mirrors'
|
||||
RESOURCE_PATH = '/taas/tap_mirrors/{id}'
|
||||
|
||||
rules = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name='create_tap_mirror',
|
||||
check_str=base.ADMIN_OR_PROJECT_MEMBER,
|
||||
scope_types=['project'],
|
||||
description='Create a Tap Mirror',
|
||||
operations=[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': COLLECTION_PATH
|
||||
}
|
||||
],
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='update_tap_mirror',
|
||||
check_str=base.ADMIN_OR_PROJECT_MEMBER,
|
||||
scope_types=['project'],
|
||||
description='Update a Tap Mirror',
|
||||
operations=[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': RESOURCE_PATH
|
||||
}
|
||||
],
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='get_tap_mirror',
|
||||
check_str=base.ADMIN_OR_PROJECT_READER,
|
||||
scope_types=['project'],
|
||||
description='Show a Tap Mirror',
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': COLLECTION_PATH
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': RESOURCE_PATH
|
||||
},
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='delete_tap_mirror',
|
||||
check_str=base.ADMIN_OR_PROJECT_MEMBER,
|
||||
scope_types=['project'],
|
||||
description='Delete a Tap Mirror',
|
||||
operations=[
|
||||
{
|
||||
'method': 'DELETE',
|
||||
'path': RESOURCE_PATH,
|
||||
}
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return rules
|
||||
128
neutron_taas/services/taas/tap_mirror_plugin.py
Normal file
128
neutron_taas/services/taas/tap_mirror_plugin.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# 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.api.definitions import portbindings
|
||||
from neutron_lib.api.definitions import tap_mirror as t_m_api_def
|
||||
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 oslo_log import helpers as log_helpers
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron_taas.common import constants as taas_consts
|
||||
from neutron_taas.db import tap_mirror_db
|
||||
|
||||
|
||||
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):
|
||||
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[portbindings.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 disabled!")
|
||||
|
||||
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 taas_exc.TapMirrorTunnelConflict(
|
||||
tunnel_id=tunnel_id)
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def delete_tap_mirror(self, context, id):
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
tm = self.get_tap_mirror(context, id)
|
||||
if tm:
|
||||
# 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 taas_exc.TapMirrorNotFound:
|
||||
LOG.debug("Tap Mirror not found: %s", t_m['id'])
|
||||
119
neutron_taas/tests/unit/db/test_tap_mirror_db.py
Normal file
119
neutron_taas/tests/unit/db/test_tap_mirror_db.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# 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 neutron_lib.exceptions import taas as taas_exc
|
||||
|
||||
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().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-project-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():
|
||||
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():
|
||||
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():
|
||||
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():
|
||||
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():
|
||||
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(taas_exc.TapMirrorNotFound,
|
||||
self._get_tap_mirror, result['id'])
|
||||
79
neutron_taas/tests/unit/extensions/test_tap_mirror.py
Normal file
79
neutron_taas/tests/unit/extensions/test_tap_mirror.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# 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
|
||||
|
||||
from neutron_lib.api.definitions import tap_mirror as tap_mirror_api
|
||||
|
||||
from neutron_taas import extensions as taas_extensions
|
||||
|
||||
|
||||
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().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')
|
||||
@@ -0,0 +1,98 @@
|
||||
# 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.tests.unit import testlib_api
|
||||
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_taas.services.taas import tap_mirror_plugin
|
||||
|
||||
|
||||
class TestTapMirrorPlugin(testlib_api.SqlTestCase):
|
||||
def setUp(self):
|
||||
super().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):
|
||||
with testtools.ExpectedException(taas_exc.TapMirrorNotFound):
|
||||
self._plugin.delete_tap_mirror(self._context, 'non-existent')
|
||||
@@ -38,6 +38,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.config.opts =
|
||||
|
||||
Reference in New Issue
Block a user