From bcb2a42ce4d1d950f09912c22cdafcfc6c0b5d45 Mon Sep 17 00:00:00 2001 From: Sara Nierodzik Date: Tue, 12 Feb 2019 13:13:20 +0000 Subject: [PATCH] Addition of advertiser and extension into NC This patch adds the advertiser as well as extension files to the neutron classifier. The unit tests are already included. This patch is related to the PoC patches which focus on the integration of Neutron Classifier into Neutron's core repo.. Link: https://review.opendev.org/#/c/670050/ Note: Only the L2 Agent is currently being imported. Recent changes include: - Include the migrated classification groups back into the neutron classifier in order to write the functional tests. - Change all objects imported from neutron_classifier/objects/ classification to be imported as class_obj. To do: - Functional tests for the advertiser and extension. Change-Id: Ibf5424b643b027da1fd03780f7ef81b970400c28 --- neutron_classifier/common/constants.py | 22 ++- neutron_classifier/common/validators.py | 14 +- neutron_classifier/db/classification.py | 27 ++-- .../4e97d48da530_initial_ccf_database_.py | 2 +- neutron_classifier/db/models.py | 4 +- neutron_classifier/objects/__init__.py | 2 +- .../{classifications.py => classification.py} | 1 + .../services/classification/advertiser.py | 126 +++++++++++++++ .../services/classification/extension.py | 125 +++++++++++++++ .../services/classification/plugin.py | 47 +++--- .../tests/functional/requirements.txt | 4 +- .../tests/functional/test_api.py | 42 +++-- neutron_classifier/tests/objects_base.py | 16 +- .../unit/api/test_classification_group.py | 27 ++-- .../unit/objects/test_object_versions.py | 1 - .../tests/unit/objects/test_objects.py | 48 +++--- .../classifications/test_advertiser.py | 146 ++++++++++++++++++ .../classifications/test_extension.py | 96 ++++++++++++ .../services/classifications/test_plugin.py | 52 ++++--- setup.cfg | 2 + 20 files changed, 664 insertions(+), 140 deletions(-) rename neutron_classifier/objects/{classifications.py => classification.py} (99%) create mode 100644 neutron_classifier/services/classification/advertiser.py create mode 100644 neutron_classifier/services/classification/extension.py create mode 100644 neutron_classifier/tests/unit/services/classifications/test_advertiser.py create mode 100644 neutron_classifier/tests/unit/services/classifications/test_extension.py diff --git a/neutron_classifier/common/constants.py b/neutron_classifier/common/constants.py index 71c80a7..15fc567 100644 --- a/neutron_classifier/common/constants.py +++ b/neutron_classifier/common/constants.py @@ -13,19 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_classifier.objects import classification as class_obj -from neutron_classifier.objects import classifications as cs - -COMMON_FIELDS = cs.ClassificationBase.fields.keys() -FIELDS_IPV4 = list(set(cs.IPV4Classification.fields.keys()) - +COMMON_FIELDS = class_obj.ClassificationBase.fields.keys() +FIELDS_IPV4 = list(set(class_obj.IPV4Classification.fields.keys()) - set(COMMON_FIELDS)) -FIELDS_IPV6 = list(set(cs.IPV6Classification.fields.keys()) - +FIELDS_IPV6 = list(set(class_obj.IPV6Classification.fields.keys()) - set(COMMON_FIELDS)) -FIELDS_TCP = list(set(cs.TCPClassification.fields.keys()) - +FIELDS_TCP = list(set(class_obj.TCPClassification.fields.keys()) - set(COMMON_FIELDS)) -FIELDS_UDP = list(set(cs.UDPClassification.fields.keys()) - +FIELDS_UDP = list(set(class_obj.UDPClassification.fields.keys()) - set(COMMON_FIELDS)) -FIELDS_ETHERNET = list(set(cs.EthernetClassification.fields.keys()) - +FIELDS_ETHERNET = list(set(class_obj.EthernetClassification.fields.keys()) - set(COMMON_FIELDS)) @@ -34,3 +33,10 @@ SUPPORTED_FIELDS = {'ipv4': FIELDS_IPV4, 'tcp': FIELDS_TCP, 'udp': FIELDS_UDP, 'ethernet': FIELDS_ETHERNET} + +# Method names for receiving classifications +PRECOMMIT_POSTFIX = '_precommit' +CREATE_CLASS = 'create_classification' +CREATE_CLASS_PRECOMMIT = CREATE_CLASS + PRECOMMIT_POSTFIX +DELETE_CLASS = 'delete_classification' +DELETE_CLASS_PRECOMMIT = DELETE_CLASS + PRECOMMIT_POSTFIX diff --git a/neutron_classifier/common/validators.py b/neutron_classifier/common/validators.py index 3b4389c..8480cfd 100644 --- a/neutron_classifier/common/validators.py +++ b/neutron_classifier/common/validators.py @@ -19,7 +19,7 @@ from neutron_classifier.common import ipv6_validators from neutron_classifier.common import tcp_validators from neutron_classifier.common import udp_validators from neutron_classifier.db import models -from neutron_classifier.objects import classifications +from neutron_classifier.objects import classification as class_obj from neutron_lib.db import api as db_api @@ -33,9 +33,9 @@ type_validators['udp'] = udp_validators.validators_dict def check_valid_classifications(context, cs): for c_id in cs: - c_model = classifications.ClassificationBase + c_model = class_obj.ClassificationBase c = c_model.get_object(context, id=c_id) - c_type_clas = classifications.CLASS_MAP[c.c_type] + c_type_clas = class_obj.CLASS_MAP[c.c_type] classification = c_type_clas.get_object(context, id=c_id) if not classification or (classification.id != c_id): raise exceptions.InvalidClassificationId() @@ -55,12 +55,12 @@ def check_can_delete_classification_group(context, cg_id): classification group, meaning is already mapped to a parent classification group. In that case we cannot delete it and will raise an exception. """ - cgs = classifications.ClassificationGroup.get_objects(context) + cgs = class_obj.ClassificationGroup.get_objects(context) for cg in cgs: with db_api.CONTEXT_WRITER.using(context): - cg_obj = classifications.ClassificationGroup.get_object(context, - id=cg.id) - mapped_cgs = classifications._get_mapped_classification_groups( + cg_obj = class_obj.ClassificationGroup.get_object(context, + id=cg.id) + mapped_cgs = class_obj._get_mapped_classification_groups( context, cg_obj) if cg_id in [mcg.id for mcg in mapped_cgs]: raise exceptions.ConsumedClassificationGroup() diff --git a/neutron_classifier/db/classification.py b/neutron_classifier/db/classification.py index 12001d3..0072ae8 100644 --- a/neutron_classifier/db/classification.py +++ b/neutron_classifier/db/classification.py @@ -17,16 +17,17 @@ from oslo_utils import uuidutils from neutron_lib.db import api as db_api +from neutron.db import agents_db from neutron.objects import base as base_obj from neutron_classifier.common import exceptions from neutron_classifier.common import validators -from neutron_classifier.objects import classifications +from neutron_classifier.objects import classification as class_obj LOG = logging.getLogger(__name__) -class TrafficClassificationGroupPlugin(object): +class TrafficClassificationGroupPlugin(agents_db.AgentDbMixin): def __init__(self): super(TrafficClassificationGroupPlugin, self).__init__() @@ -51,7 +52,7 @@ class TrafficClassificationGroupPlugin(object): db_dict = details if 'tenant_id' in details: del details['tenant_id'] - cg = classifications.ClassificationGroup(context, **details) + cg = class_obj.ClassificationGroup(context, **details) with db_api.CONTEXT_WRITER.using(context): cg.create() @@ -60,7 +61,7 @@ class TrafficClassificationGroupPlugin(object): with db_api.CONTEXT_WRITER.using(context): if c_flag: for cl in mappings['c_ids']: - cg_c_mapping = classifications.CGToClassificationMapping( + cg_c_mapping = class_obj.CGToClassificationMapping( context, container_cg_id=cg.id, stored_classification_id=cl) @@ -68,7 +69,7 @@ class TrafficClassificationGroupPlugin(object): if cg_flag: for cg_id in mappings['cg_ids']: cg_cg_mapping =\ - classifications.CGToClassificationGroupMapping( + class_obj.CGToClassificationGroupMapping( context, container_cg_id=cg.id, stored_cg_id=cg_id @@ -84,7 +85,7 @@ class TrafficClassificationGroupPlugin(object): def delete_classification_group(self, context, classification_group_id): if validators.check_can_delete_classification_group( context, classification_group_id): - cg = classifications.ClassificationGroup.get_object( + cg = class_obj.ClassificationGroup.get_object( context, id=classification_group_id) with db_api.CONTEXT_WRITER.using(context): cg.delete() @@ -98,7 +99,7 @@ class TrafficClassificationGroupPlugin(object): if key not in valid_keys: raise exceptions.InvalidUpdateRequest() with db_api.CONTEXT_WRITER.using(context): - cg = classifications.ClassificationGroup.update_object( + cg = class_obj.ClassificationGroup.update_object( context, fields_to_update, id=classification_group_id) db_dict = self._make_db_dict(cg) return db_dict @@ -140,12 +141,12 @@ class TrafficClassificationGroupPlugin(object): def get_classification_group(self, context, classification_group_id, fields=None): with db_api.CONTEXT_WRITER.using(context): - cg = classifications.ClassificationGroup.get_object( + cg = class_obj.ClassificationGroup.get_object( context, id=classification_group_id) db_dict = self._make_db_dict(cg) - mapped_cs = classifications._get_mapped_classifications(context, - cg) - mapped_cgs = classifications._get_mapped_classification_groups( + mapped_cs = class_obj._get_mapped_classifications(context, + cg) + mapped_cgs = class_obj._get_mapped_classification_groups( context, cg) c_dict = self._make_c_dicts(mapped_cs) cg_dict = self._make_db_dicts(mapped_cgs) @@ -157,7 +158,7 @@ class TrafficClassificationGroupPlugin(object): marker=None, page_reverse=False, filters=None, fields=None): pager = base_obj.Pager(sorts, limit, page_reverse, marker) - cgs = classifications.ClassificationGroup.get_objects(context, - _pager=pager) + cgs = class_obj.ClassificationGroup.get_objects(context, + _pager=pager) db_dict = self._make_db_dicts(cgs) return db_dict diff --git a/neutron_classifier/db/migration/alembic_migrations/versions/queens/expand/4e97d48da530_initial_ccf_database_.py b/neutron_classifier/db/migration/alembic_migrations/versions/queens/expand/4e97d48da530_initial_ccf_database_.py index 838eb2b..22d2b8f 100644 --- a/neutron_classifier/db/migration/alembic_migrations/versions/queens/expand/4e97d48da530_initial_ccf_database_.py +++ b/neutron_classifier/db/migration/alembic_migrations/versions/queens/expand/4e97d48da530_initial_ccf_database_.py @@ -1,4 +1,4 @@ -# Copyright 2017 Intel Corporation. +# Copyright 2017 Intel Corporation # # 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 diff --git a/neutron_classifier/db/models.py b/neutron_classifier/db/models.py index 3007b5e..0b5e26e 100644 --- a/neutron_classifier/db/models.py +++ b/neutron_classifier/db/models.py @@ -13,6 +13,7 @@ # under the License. from neutron_lib.db import model_base + from neutron_lib.db import model_query as mq import sqlalchemy as sa @@ -187,7 +188,8 @@ def _generate_dict_from_cg_db(model, fields=None): def _read_all_classification_groups(plugin, context): """Returns all classification groups.""" - class_group = plugin._get_collection(context, ClassificationGroup, + class_group = plugin._get_collection(context, + ClassificationGroup, _generate_dict_from_cg_db) return class_group diff --git a/neutron_classifier/objects/__init__.py b/neutron_classifier/objects/__init__.py index 97c77f8..5b860ab 100644 --- a/neutron_classifier/objects/__init__.py +++ b/neutron_classifier/objects/__init__.py @@ -13,5 +13,5 @@ def register_objects(): # local import to avoid circular import failure - __import__('neutron_classifier.objects.classifications') + __import__('neutron_classifier.objects.classification') __import__('neutron_classifier.objects.classification_type') diff --git a/neutron_classifier/objects/classifications.py b/neutron_classifier/objects/classification.py similarity index 99% rename from neutron_classifier/objects/classifications.py rename to neutron_classifier/objects/classification.py index 0f14cb8..c96469b 100644 --- a/neutron_classifier/objects/classifications.py +++ b/neutron_classifier/objects/classification.py @@ -21,6 +21,7 @@ from oslo_versionedobjects import fields as obj_fields from neutron.objects import base from neutron.objects import common_types from neutron.objects import rbac_db + from neutron_lib.db import api as db_api from neutron_classifier.db import models diff --git a/neutron_classifier/services/classification/advertiser.py b/neutron_classifier/services/classification/advertiser.py new file mode 100644 index 0000000..ca92027 --- /dev/null +++ b/neutron_classifier/services/classification/advertiser.py @@ -0,0 +1,126 @@ +# Copyright 2019 Intel Corporation. +# +# 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_log import log as logging + +from neutron.api.rpc.callbacks import events as rpc_events +from neutron.api.rpc.callbacks.producer import registry as rpc_registry +from neutron.api.rpc.callbacks import resources +from neutron.api.rpc.handlers import resources_rpc +from neutron_classifier.common import constants as nc_consts +from neutron_classifier.db import models +from neutron_classifier.objects import classification as class_obj + +from neutron_lib.db import api as db_api +from neutron_lib import rpc as n_rpc + +import oslo_messaging + + +LOG = logging.getLogger(__name__) + + +class NeutronClassifierAdvertiserCallback(object): + """Neutron Classifier RPC server""" + + def __init__(self, adv): + self.target = oslo_messaging.Target(version='1.0') + self.adv = adv + + def get_classification_group_mapping(self, context, **kwargs): + cg_id = kwargs['cg_id'] + return self.adv._get_classification_group_mapping(context, cg_id) + + def get_classification(self, context, **kwargs): + c_id = kwargs['c_id'] + return self.adv._get_classification("classification", c_id, + context=context) + + +class ClassificationAdvertiser(object): + + def __init__(self): + self.rpc_notifications_required = True + + self._init_classification_topics() + + rpc_registry.provide(self._get_classification, + class_obj.ClassificationBase.obj_name()) + rpc_registry.provide(self._get_classification_group, + class_obj.ClassificationGroup.obj_name()) + + if self.rpc_notifications_required: + self.push_api = resources_rpc.ResourcesPushRpcApi() + + def _init_classification_topics(self): + resources.register_resource_class(class_obj.ClassificationGroup) + resources.register_resource_class(class_obj.ClassificationBase) + for cls in class_obj.CLASS_MAP.values(): + resources.register_resource_class(cls) + + self.conn = n_rpc.Connection() + endpoints = [NeutronClassifierAdvertiserCallback(self)] + self.conn.create_consumer("q-classifier", endpoints, + fanout=False) + self.conn.consume_in_threads() + + @staticmethod + def _get_classification(resource, classification_id, **kwargs): + context = kwargs.get('context') + if context is None: + LOG.warning( + 'Received %(resource)s %(classification_id)s without context', + {'resource': resource, 'classification_id': classification_id}) + return + + c = class_obj.ClassificationBase(context, id=classification_id) + c_obj = class_obj.CLASS_MAP[c.c_type] + classification = c_obj.get_object(context, id=classification_id) + return classification + + @staticmethod + def _get_classification_group(resource, cg_id, **kwargs): + context = kwargs.get('context') + if context is None: + LOG.warning( + 'Received %(resource)s %(classification_id)s without context', + {'resource': resource, 'classification_id': cg_id}) + return + + cg = class_obj.ClassificationGroup.\ + get_object(context, + id=cg_id) + return cg + + @db_api.CONTEXT_READER + def _get_classification_group_mapping(self, context, cg_id): + with db_api.CONTEXT_READER.using(context): + mapped_db_cs = models._read_classifications(context, cg_id) + mapped_db_cgs = models._read_classification_groups(context, cg_id) + mapped_cs = [cs.id for cs in mapped_db_cs] + mapped_cgs = [cgs.id for cgs in mapped_db_cgs] + group_mappings = {'classifications': mapped_cs, + 'classification_groups': mapped_cgs} + return group_mappings + + def call(self, method_name, *args, **kwargs): + """Helper method for calling a method across all extensions.""" + if self.rpc_notifications_required: + context = kwargs.get('context') or args[0] + cls_obj = kwargs.get('classification') or args[1] + + if method_name == nc_consts.CREATE_CLASS: + self.push_api.push(context, [cls_obj], rpc_events.CREATED) + elif method_name == nc_consts.DELETE_CLASS: + self.push_api.push(context, [cls_obj], rpc_events.DELETED) diff --git a/neutron_classifier/services/classification/extension.py b/neutron_classifier/services/classification/extension.py new file mode 100644 index 0000000..a5142e6 --- /dev/null +++ b/neutron_classifier/services/classification/extension.py @@ -0,0 +1,125 @@ +# Copyright 2019 Intel Corporation. +# +# 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.api.rpc.callbacks.consumer import registry +from neutron.api.rpc.callbacks import events +from neutron.api.rpc.callbacks import resources +from neutron.api.rpc.handlers import resources_rpc +from neutron_classifier.objects import classification as class_obj +from neutron_lib.agent import l2_extension +from neutron_lib import context +from neutron_lib import rpc as lib_rpc + +from oslo_log import log as logging + +import oslo_messaging + +LOG = logging.getLogger(__name__) + + +class NeutronClassifierApi(object): + + def __init__(self): + self.res_rpc = resources_rpc.ResourcesPullRpcApi() + self.context = context.get_admin_context_without_session() + target = oslo_messaging.Target(topic="q-classifier", + version='1.0') + self.client = lib_rpc.get_client(target) + + def get_classification(self, class_id): + cctx = self.client.prepare() + return cctx.call(self.context, "get_classification", c_id=class_id) + + def get_classification_group(self, class_grp_id): + return self.res_rpc.pull(self.context, + class_obj.ClassificationGroup.obj_name(), + class_grp_id) + + def _get_classification_group_mapping(self, class_grp_id): + cctx = self.client.prepare() + return cctx.call(self.context, 'get_classification_group_mapping', + cg_id=class_grp_id) + + +class NeutronClassifierExtension(l2_extension.L2AgentExtension): + + SUPPORTED_RESOURCE_TYPES = [ + class_obj.ClassificationGroup.obj_name(), + class_obj.ClassificationBase.obj_name(), + class_obj.EthernetClassification.obj_name(), + class_obj.IPV4Classification.obj_name(), + class_obj.IPV6Classification.obj_name(), + class_obj.UDPClassification.obj_name(), + class_obj.TCPClassification.obj_name()] + + def __init__(self): + super(NeutronClassifierExtension, self).__init__() + resources.register_resource_class(class_obj.ClassificationGroup) + resources.register_resource_class(class_obj.ClassificationBase) + self.class_type_list = [] + for cls in class_obj.CLASS_MAP.values(): + resources.register_resource_class(cls) + self.class_type_list.append(cls.obj_name()) + + def consume_api(self, agent_api): + self.agent_api = agent_api + agent_api.register_classification_api(NeutronClassifierApi()) + + def initialize(self, connection, driver_type): + super(NeutronClassifierExtension, self).initialize(connection, + driver_type) + self.resource_rpc = resources_rpc.ResourcesPullRpcApi() + self._register_rpc_consumers(connection) + + def handle_port(self, context, port): + pass + + def delete_port(self, context, port): + pass + + def _register_rpc_consumers(self, connection): + + '''Allows an extension to receive notifications. + + The notification shows the updates made to + items of interest. + ''' + + endpoints = [resources_rpc.ResourcesPushRpcCallback()] + for resource_type in self.SUPPORTED_RESOURCE_TYPES: + registry.register(self.handle_notification, resource_type) + topic = resources_rpc.resource_type_versioned_topic(resource_type) + connection.create_consumer(topic, endpoints, fanout=True) + + def handle_notification(self, context, resource_type, + class_objs, event_type): + '''Alerts the l2 extension agent. + + Notifies if a classification or a classification + group has been made. + ''' + + if (event_type == events.CREATED + and resource_type == + class_obj.ClassificationGroup.obj_name()): + for c_obj in class_objs: + self.agent_api.register_classification_group( + c_obj.id, c_obj) + + if (event_type == events.CREATED and resource_type + in self.class_type_list): + for c_obj in class_objs: + self.agent_api.register_classification(c_obj.id, + c_obj) diff --git a/neutron_classifier/services/classification/plugin.py b/neutron_classifier/services/classification/plugin.py index df43eb9..02c8ba4 100644 --- a/neutron_classifier/services/classification/plugin.py +++ b/neutron_classifier/services/classification/plugin.py @@ -14,26 +14,30 @@ from oslo_log import log as logging -from neutron_lib.db import api as db_api - +from neutron.db import agents_db from neutron.objects import base as base_obj + +from neutron_classifier.common import constants as nc_consts from neutron_classifier.common import exceptions from neutron_classifier.common import validators from neutron_classifier.db import classification as c_db from neutron_classifier.extensions import classification +from neutron_classifier.objects import classification as class_obj from neutron_classifier.objects import classification_type as type_obj -from neutron_classifier.objects import classifications as class_group +from neutron_classifier.services.classification import advertiser +from neutron_lib.db import api as db_api LOG = logging.getLogger(__name__) class ClassificationPlugin(classification.NeutronClassificationPluginBase, - c_db.TrafficClassificationGroupPlugin): + c_db.TrafficClassificationGroupPlugin, + agents_db.AgentDbMixin): supported_extension_aliases = ['neutron_classifier'] def __init__(self): super(ClassificationPlugin, self).__init__() - self.driver_manager = None + self.driver_manager = advertiser.ClassificationAdvertiser() def create_classification(self, context, classification): details = self.break_out_headers(classification) @@ -44,24 +48,29 @@ class ClassificationPlugin(classification.NeutronClassificationPluginBase, if key not in validators.type_validators[c_type].keys(): raise exceptions.InvalidClassificationDefintion() - cl = class_group.CLASS_MAP[c_type](context, **details) + cl = class_obj.CLASS_MAP[c_type](context, **details) with db_api.CONTEXT_WRITER.using(context): cl.create() db_dict = self.merge_header(cl) db_dict['id'] = cl['id'] + self.driver_manager.call(nc_consts.CREATE_CLASS, context, cl) + return db_dict def delete_classification(self, context, classification_id): - cl = class_group.ClassificationBase.get_object(context, - id=classification_id) - cl_class = class_group.CLASS_MAP[cl.c_type] + cl = class_obj.ClassificationBase.\ + get_object(context, + id=classification_id) + cl_class = class_obj.CLASS_MAP[cl.c_type] classification = cl_class.get_object(context, id=classification_id) validators.check_valid_classifications(context, [classification_id]) with db_api.CONTEXT_WRITER.using(context): classification.delete() + self.driver_manager.call(nc_consts.DELETE_CLASS, context, + classification) def update_classification(self, context, classification_id, fields_to_update): @@ -71,9 +80,10 @@ class ClassificationPlugin(classification.NeutronClassificationPluginBase, for key in field_keys: if key not in valid_keys: raise exceptions.InvalidUpdateRequest() - cl = class_group.ClassificationBase.get_object(context, - id=classification_id) - cl_class = class_group.CLASS_MAP[cl.c_type] + cl = class_obj.ClassificationBase.\ + get_object(context, + id=classification_id) + cl_class = class_obj.CLASS_MAP[cl.c_type] with db_api.CONTEXT_WRITER.using(context): classification = cl_class.update_object( context, fields_to_update, id=classification_id) @@ -82,9 +92,10 @@ class ClassificationPlugin(classification.NeutronClassificationPluginBase, return db_dict def get_classification(self, context, classification_id, fields=None): - cl = class_group.ClassificationBase.get_object(context, - id=classification_id) - cl_class = class_group.CLASS_MAP[cl.c_type] + cl = class_obj.ClassificationBase.\ + get_object(context, + id=classification_id) + cl_class = class_obj.CLASS_MAP[cl.c_type] classification = cl_class.get_object(context, id=classification_id) clas = self.merge_header(classification) @@ -95,8 +106,8 @@ class ClassificationPlugin(classification.NeutronClassificationPluginBase, page_reverse=False): c_type = filters['c_type'][0] pager = base_obj.Pager(sorts, limit, page_reverse, marker) - cl = class_group.CLASS_MAP[c_type].get_objects(context, - _pager=pager) + cl = class_obj.CLASS_MAP[c_type].get_objects(context, + _pager=pager) db_dict = self.merge_headers(cl) return db_dict @@ -106,7 +117,7 @@ class ClassificationPlugin(classification.NeutronClassificationPluginBase, ret_list = [] if not filters: filters = {} - for key in class_group.CLASS_MAP.keys(): + for key in class_obj.CLASS_MAP.keys(): types = {} obj = type_obj.ClassificationType.get_object(key) types['type'] = obj.type diff --git a/neutron_classifier/tests/functional/requirements.txt b/neutron_classifier/tests/functional/requirements.txt index 87ce45c..1e06ee8 100644 --- a/neutron_classifier/tests/functional/requirements.txt +++ b/neutron_classifier/tests/functional/requirements.txt @@ -2,6 +2,6 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -psutil>=3.2.2 # BSD +psutil>=5.6.3 # BSD psycopg2 -PyMySQL>=0.7.6 # MIT License +PyMySQL>=0.9.3 # MIT License diff --git a/neutron_classifier/tests/functional/test_api.py b/neutron_classifier/tests/functional/test_api.py index 6c623eb..41b1e70 100644 --- a/neutron_classifier/tests/functional/test_api.py +++ b/neutron_classifier/tests/functional/test_api.py @@ -22,7 +22,7 @@ from neutron_classifier.common import exceptions from neutron_classifier.common import validators from neutron_classifier.db.classification import\ TrafficClassificationGroupPlugin as cg_plugin -from neutron_classifier.objects import classifications +from neutron_classifier.objects import classification as class_obj from neutron_classifier.services.classification.plugin import\ ClassificationPlugin as c_plugin from neutron_classifier.tests import objects_base as obj_base @@ -57,8 +57,8 @@ class ClassificationGroupApiTest(testlib_api.MySQLTestCaseMixin, def test_create_classification_group(self): with db_api.CONTEXT_WRITER.using(self.ctx): - tcp_class = classifications.TCPClassification - ipv4_class = classifications.IPV4Classification + tcp_class = class_obj.TCPClassification + ipv4_class = class_obj.IPV4Classification cg2 = self._create_test_cg('Test Group 1') tcp = self._create_test_classification('tcp', tcp_class) ipv4 = self._create_test_classification('ipv4', ipv4_class) @@ -72,11 +72,11 @@ class ClassificationGroupApiTest(testlib_api.MySQLTestCaseMixin, }} cg1 = self.test_plugin.create_classification_group(self.ctx, cg_dict) - fetch_cg1 = classifications.ClassificationGroup.get_object( + fetch_cg1 = class_obj.ClassificationGroup.get_object( self.ctx, id=cg1['id']) - mapped_cgs = classifications._get_mapped_classification_groups( + mapped_cgs = class_obj._get_mapped_classification_groups( self.ctx, fetch_cg1) - mapped_cs = classifications._get_mapped_classifications( + mapped_cs = class_obj._get_mapped_classifications( self.ctx, fetch_cg1) mapped_classification_groups = [cg.id for cg in mapped_cgs] mapped_classifications = [c.id for c in mapped_cs] @@ -96,7 +96,7 @@ class ClassificationGroupApiTest(testlib_api.MySQLTestCaseMixin, self.test_plugin.update_classification_group( self.ctx, cg1.id, {'classification_group': {'name': 'Test Group updated'}}) - fetch_cg1 = classifications.ClassificationGroup.get_object( + fetch_cg1 = class_obj.ClassificationGroup.get_object( self.ctx, id=cg1['id']) self.assertRaises( exceptions.InvalidUpdateRequest, @@ -110,7 +110,7 @@ class ClassificationGroupApiTest(testlib_api.MySQLTestCaseMixin, with db_api.CONTEXT_WRITER.using(self.ctx): cg1 = self._create_test_cg('Test Group 0') self.test_plugin.delete_classification_group(self.ctx, cg1.id) - fetch_cg1 = classifications.ClassificationGroup.get_object( + fetch_cg1 = class_obj.ClassificationGroup.get_object( self.ctx, id=cg1['id']) self.assertIsNone(fetch_cg1) @@ -122,8 +122,13 @@ class ClassificationApiTest(testlib_api.MySQLTestCaseMixin, super(ClassificationApiTest, self).setUp() self.test_clas_plugin = c_plugin() + def mock_call(*args, **kwargs): + pass + + self.test_clas_plugin.driver_manager.call = mock_call + def test_create_classification(self): - attrs = self.get_random_attrs(classifications.EthernetClassification) + attrs = self.get_random_attrs(class_obj.EthernetClassification) c_type = 'ethernet' attrs['c_type'] = c_type attrs['definition'] = {} @@ -133,7 +138,7 @@ class ClassificationApiTest(testlib_api.MySQLTestCaseMixin, with db_api.CONTEXT_WRITER.using(self.ctx): c1 = self.test_clas_plugin.create_classification(self.ctx, c_attrs) - fetch_c1 = classifications.EthernetClassification.get_object( + fetch_c1 = class_obj.EthernetClassification.get_object( self.ctx, id=c1['id'] ) c_attrs['classification']['definition']['src_port'] = 'xyz' @@ -147,16 +152,16 @@ class ClassificationApiTest(testlib_api.MySQLTestCaseMixin, self.assertEqual(y, fetch_c1[x]) def test_delete_classification(self): - tcp_class = classifications.TCPClassification + tcp_class = class_obj.TCPClassification with db_api.CONTEXT_WRITER.using(self.ctx): tcp = self._create_test_classification('tcp', tcp_class) self.test_clas_plugin.delete_classification(self.ctx, tcp.id) - fetch_tcp = classifications.TCPClassification.get_object( + fetch_tcp = class_obj.TCPClassification.get_object( self.ctx, id=tcp.id) self.assertIsNone(fetch_tcp) def test_get_classification(self): - ipv4_class = classifications.IPV4Classification + ipv4_class = class_obj.IPV4Classification with db_api.CONTEXT_WRITER.using(self.ctx): ipv4 = self._create_test_classification('ipv4', ipv4_class) fetch_ipv4 = self.test_clas_plugin.get_classification(self.ctx, @@ -166,9 +171,9 @@ class ClassificationApiTest(testlib_api.MySQLTestCaseMixin, def test_get_classifications(self): with db_api.CONTEXT_WRITER.using(self.ctx): c1 = self._create_test_classification( - 'ipv6', classifications.IPV6Classification) + 'ipv6', class_obj.IPV6Classification) c2 = self._create_test_classification( - 'udp', classifications.UDPClassification) + 'udp', class_obj.UDPClassification) fetch_cs_udp = self.test_clas_plugin.get_classifications( self.ctx, filters={'c_type': ['udp']}) fetch_cs_ipv6 = self.test_clas_plugin.get_classifications( @@ -180,11 +185,14 @@ class ClassificationApiTest(testlib_api.MySQLTestCaseMixin, def test_update_classification(self): c1 = self._create_test_classification( - 'ethernet', classifications.EthernetClassification) + 'ethernet', class_obj.EthernetClassification) updated_name = 'Test Updated Classification' with db_api.CONTEXT_WRITER.using(self.ctx): self.test_clas_plugin.update_classification( self.ctx, c1.id, {'classification': {'name': updated_name}}) - fetch_c1 = classifications.EthernetClassification.get_object( + fetch_c1 = class_obj.EthernetClassification.get_object( self.ctx, id=c1.id) self.assertEqual(fetch_c1.name, updated_name) + + def tearDown(self): + super(ClassificationApiTest, self).tearDown() diff --git a/neutron_classifier/tests/objects_base.py b/neutron_classifier/tests/objects_base.py index ca64bb9..a34de5b 100644 --- a/neutron_classifier/tests/objects_base.py +++ b/neutron_classifier/tests/objects_base.py @@ -19,7 +19,7 @@ from neutron_lib import context from neutron.tests.unit.objects import test_base -from neutron_classifier.objects import classifications +from neutron_classifier.objects import classification as class_obj from neutron_classifier.tests import tools @@ -27,8 +27,8 @@ class _CCFObjectsTestCommon(object): # TODO(ndahiwade): this represents classifications containing Enum fields, # will need to be reworked if more classifications are added here later. - _Enum_classifications = [classifications.IPV4Classification, - classifications.IPV6Classification] + _Enum_classifications = [class_obj.IPV4Classification, + class_obj.IPV6Classification] _Enumfield = oslo_versionedobjects.fields.EnumField ctx = context.get_admin_context() @@ -49,7 +49,7 @@ class _CCFObjectsTestCommon(object): 'project_id': uuidutils.generate_uuid(), 'shared': False, 'operator': 'AND'} - cg = classifications.ClassificationGroup(self.ctx, **attrs) + cg = class_obj.ClassificationGroup(self.ctx, **attrs) cg.create() return cg @@ -65,15 +65,15 @@ class _CCFObjectsTestCommon(object): def _create_test_cg_cg_mapping(self, cg1, cg2): attrs = {'container_cg_id': cg1, 'stored_cg_id': cg2} - cg_m_cg = classifications.CGToClassificationGroupMapping(self.ctx, - **attrs) + cg_m_cg = class_obj.CGToClassificationGroupMapping(self.ctx, + **attrs) cg_m_cg.create() return cg_m_cg def _create_test_cg_c_mapping(self, cg, c): attrs = {'container_cg_id': cg, 'stored_classification_id': c} - cg_m_c = classifications.CGToClassificationMapping(self.ctx, - **attrs) + cg_m_c = class_obj.CGToClassificationMapping(self.ctx, + **attrs) cg_m_c.create() return cg_m_c diff --git a/neutron_classifier/tests/unit/api/test_classification_group.py b/neutron_classifier/tests/unit/api/test_classification_group.py index a187a94..dd4559b 100644 --- a/neutron_classifier/tests/unit/api/test_classification_group.py +++ b/neutron_classifier/tests/unit/api/test_classification_group.py @@ -15,8 +15,7 @@ import mock from neutron.objects import base as base_obj from neutron_classifier.db import classification as cg_api -from neutron_classifier.objects import classifications - +from neutron_classifier.objects import classification as class_obj from neutron_classifier.tests import base from neutron_lib import context from oslo_utils import uuidutils @@ -72,10 +71,10 @@ class TestClassificationGroupPlugin(base.BaseClassificationTestCase): } return self.test_cg - @mock.patch.object(classifications.CGToClassificationGroupMapping, + @mock.patch.object(class_obj.CGToClassificationGroupMapping, 'create') - @mock.patch.object(classifications.CGToClassificationMapping, 'create') - @mock.patch.object(classifications.ClassificationGroup, 'create') + @mock.patch.object(class_obj.CGToClassificationMapping, 'create') + @mock.patch.object(class_obj.ClassificationGroup, 'create') def test_create_classification_group(self, mock_cg_create, mock_cg_c_mapping_create, mock_cg_cg_mapping_create): @@ -105,7 +104,7 @@ class TestClassificationGroupPlugin(base.BaseClassificationTestCase): mock_manager.create_cg_cg.assert_called_once() self.assertEqual(mock_manager.create_cg_c.call_count, c_len) - @mock.patch.object(classifications.ClassificationGroup, 'get_object') + @mock.patch.object(class_obj.ClassificationGroup, 'get_object') @mock.patch('neutron_classifier.common.validators.' 'check_can_delete_classification_group') def test_delete_classification_group(self, mock_valid_delete, @@ -134,11 +133,11 @@ class TestClassificationGroupPlugin(base.BaseClassificationTestCase): mock_manager.mock_calls.index(mock_cg_get_call) < mock_manager.mock_calls.index(mock_cg_delete_call)) - @mock.patch('neutron_classifier.objects.classifications.' + @mock.patch('neutron_classifier.objects.classification.' '_get_mapped_classification_groups') - @mock.patch('neutron_classifier.objects.classifications.' + @mock.patch('neutron_classifier.objects.classification.' '_get_mapped_classifications') - @mock.patch.object(classifications.ClassificationGroup, 'get_object') + @mock.patch.object(class_obj.ClassificationGroup, 'get_object') @mock.patch('neutron_classifier.db.classification.' 'TrafficClassificationGroupPlugin._make_db_dicts') def test_get_classification_group(self, mock_db_dicts, mock_cg_get, @@ -177,7 +176,7 @@ class TestClassificationGroupPlugin(base.BaseClassificationTestCase): mock_mapped_cgs.assert_called_once() @mock.patch.object(base_obj, 'Pager') - @mock.patch.object(classifications.ClassificationGroup, 'get_objects') + @mock.patch.object(class_obj.ClassificationGroup, 'get_objects') @mock.patch.object(cg_api.TrafficClassificationGroupPlugin, '_make_db_dicts') def test_get_classification_groups(self, mock_db_dicts, mock_cgs_get, @@ -193,8 +192,8 @@ class TestClassificationGroupPlugin(base.BaseClassificationTestCase): test_cg1 = test_cg1['classification_group'] test_cg2 = test_cg2['classification_group'] - cg1 = classifications.ClassificationGroup(self.ctxt, **test_cg1) - cg2 = classifications.ClassificationGroup(self.ctxt, **test_cg2) + cg1 = class_obj.ClassificationGroup(self.ctxt, **test_cg1) + cg2 = class_obj.ClassificationGroup(self.ctxt, **test_cg2) cg_list = [self.cg_plugin._make_db_dict(cg) for cg in [cg1, cg2]] mock_manager.get_cgs.return_value = cg_list @@ -206,7 +205,7 @@ class TestClassificationGroupPlugin(base.BaseClassificationTestCase): mock_manager.db_dicts.assert_called_once() self.assertEqual(len(mock_manager.mock_calls), 3) - @mock.patch.object(classifications.ClassificationGroup, 'update_object') + @mock.patch.object(class_obj.ClassificationGroup, 'update_object') def test_update_classification_group(self, mock_cg_update): mock_manager = mock.Mock() mock_manager.attach_mock(mock_cg_update, 'cg_update') @@ -215,7 +214,7 @@ class TestClassificationGroupPlugin(base.BaseClassificationTestCase): test_cg = self._generate_test_classification_group('Test Group') test_cg = test_cg['classification_group'] - cg = classifications.ClassificationGroup(self.ctxt, **test_cg) + cg = class_obj.ClassificationGroup(self.ctxt, **test_cg) updated_fields = {'classification_group': {'name': 'Test Group Updated', diff --git a/neutron_classifier/tests/unit/objects/test_object_versions.py b/neutron_classifier/tests/unit/objects/test_object_versions.py index c83beda..2ac691c 100644 --- a/neutron_classifier/tests/unit/objects/test_object_versions.py +++ b/neutron_classifier/tests/unit/objects/test_object_versions.py @@ -22,7 +22,6 @@ from oslo_versionedobjects import base as obj_base from oslo_versionedobjects import fixture from neutron import objects as n_obj - from neutron_classifier import objects from neutron_classifier.tests import base as test_base diff --git a/neutron_classifier/tests/unit/objects/test_objects.py b/neutron_classifier/tests/unit/objects/test_objects.py index 3e770ae..c8c3262 100644 --- a/neutron_classifier/tests/unit/objects/test_objects.py +++ b/neutron_classifier/tests/unit/objects/test_objects.py @@ -14,7 +14,7 @@ import oslo_versionedobjects -from neutron_classifier.objects import classifications +from neutron_classifier.objects import classification as class_obj from neutron_classifier.tests import objects_base as obj_base from neutron_classifier.tests import tools @@ -33,18 +33,18 @@ class ClassificationGroupTest(test_base.BaseDbObjectTestCase, # we are adding it here for our use rather than adding in neutron. test_base.FIELD_TYPE_VALUE_GENERATOR_MAP[ oslo_versionedobjects.fields.EnumField] = tools.get_random_operator - _test_class = classifications.ClassificationGroup + _test_class = class_obj.ClassificationGroup def test_get_object(self): cg = self._create_test_cg('Test Group 0') - fetch_cg = classifications.ClassificationGroup.get_object( + fetch_cg = class_obj.ClassificationGroup.get_object( self.ctx, id=cg.id) self.assertEqual(cg, fetch_cg) def test_get_objects(self): cg1 = self._create_test_cg('Test Group 1') cg2 = self._create_test_cg('Test Group 2') - cgs = classifications.ClassificationGroup.get_objects(self.ctx) + cgs = class_obj.ClassificationGroup.get_objects(self.ctx) self.assertIn(cg1, cgs) self.assertIn(cg2, cgs) @@ -55,7 +55,7 @@ class ClassificationGroupTest(test_base.BaseDbObjectTestCase, class UDPClassificationTest(testlib_api.SqlTestCase, obj_base._CCFObjectsTestCommon): - test_class = classifications.UDPClassification + test_class = class_obj.UDPClassification def test_get_object(self): udp = self._create_test_classification('udp', self.test_class) @@ -73,7 +73,7 @@ class UDPClassificationTest(testlib_api.SqlTestCase, class IPV4ClassificationTest(testlib_api.SqlTestCase, obj_base._CCFObjectsTestCommon): - test_class = classifications.IPV4Classification + test_class = class_obj.IPV4Classification def test_get_object(self): ipv4 = self._create_test_classification('ipv4', self.test_class) @@ -91,7 +91,7 @@ class IPV4ClassificationTest(testlib_api.SqlTestCase, class IPV6ClassificationTest(testlib_api.SqlTestCase, obj_base._CCFObjectsTestCommon): - test_class = classifications.IPV6Classification + test_class = class_obj.IPV6Classification def test_get_object(self): ipv6 = self._create_test_classification('ipv6', self.test_class) @@ -109,7 +109,7 @@ class IPV6ClassificationTest(testlib_api.SqlTestCase, class TCPClassificationTest(testlib_api.SqlTestCase, obj_base._CCFObjectsTestCommon): - test_class = classifications.TCPClassification + test_class = class_obj.TCPClassification def test_get_object(self): tcp = self._create_test_classification('tcp', self.test_class) @@ -127,7 +127,7 @@ class TCPClassificationTest(testlib_api.SqlTestCase, class EthernetClassificationTest(testlib_api.SqlTestCase, obj_base._CCFObjectsTestCommon): - test_class = classifications.EthernetClassification + test_class = class_obj.EthernetClassification def test_get_object(self): ethernet = self._create_test_classification('ethernet', @@ -153,11 +153,11 @@ class CGToClassificationGroupMappingTest(testlib_api.SqlTestCase, cg1 = self._create_test_cg('Test Group 0') cg2 = self._create_test_cg('Test Group 1') cg_m_cg = self._create_test_cg_cg_mapping(cg1.id, cg2.id) - fetch_cg = classifications.ClassificationGroup.get_object( + fetch_cg = class_obj.ClassificationGroup.get_object( self.ctx, id=cg1.id) - mapped_cg = classifications._get_mapped_classification_groups( + mapped_cg = class_obj._get_mapped_classification_groups( self.ctx, fetch_cg) - fetch_cg_m_cg = classifications.CGToClassificationGroupMapping.\ + fetch_cg_m_cg = class_obj.CGToClassificationGroupMapping.\ get_object(self.ctx, id=cg_m_cg.container_cg_id) self.assertEqual(mapped_cg[0], cg2) self.assertEqual(cg_m_cg, fetch_cg_m_cg) @@ -171,9 +171,9 @@ class CGToClassificationGroupMappingTest(testlib_api.SqlTestCase, cgs = [cg2, cg3, cg4] for cg in cgs: self._create_test_cg_cg_mapping(cg1.id, cg.id) - fetch_cg1 = classifications.ClassificationGroup.get_object( + fetch_cg1 = class_obj.ClassificationGroup.get_object( self.ctx, id=cg1.id) - mapped_cgs = classifications._get_mapped_classification_groups( + mapped_cgs = class_obj._get_mapped_classification_groups( self.ctx, fetch_cg1) for cg in cgs: self.assertIn(cg, mapped_cgs) @@ -188,15 +188,15 @@ class CGToClassificationMappingTest(testlib_api.SqlTestCase, with db_api.CONTEXT_WRITER.using(self.ctx): cg = self._create_test_cg('Test Group') cl_ = self._create_test_classification( - 'udp', classifications.UDPClassification) + 'udp', class_obj.UDPClassification) cg_m_c = self._create_test_cg_c_mapping(cg.id, cl_.id) - fetch_c = classifications.UDPClassification.get_object( + fetch_c = class_obj.UDPClassification.get_object( self.ctx, id=cl_.id) - fetch_cg = classifications.ClassificationGroup.get_object( + fetch_cg = class_obj.ClassificationGroup.get_object( self.ctx, id=cg.id) - mapped_cs = classifications._get_mapped_classifications( + mapped_cs = class_obj._get_mapped_classifications( self.ctx, fetch_cg) - fetch_cg_m_c = classifications.CGToClassificationMapping. \ + fetch_cg_m_c = class_obj.CGToClassificationMapping. \ get_object(self.ctx, id=cg_m_c.container_cg_id) self.assertIn(fetch_c, mapped_cs) self.assertEqual(cg_m_c, fetch_cg_m_c) @@ -205,17 +205,17 @@ class CGToClassificationMappingTest(testlib_api.SqlTestCase, with db_api.CONTEXT_WRITER.using(self.ctx): cg = self._create_test_cg('Test Group') c1 = self._create_test_classification( - 'tcp', classifications.TCPClassification) + 'tcp', class_obj.TCPClassification) c2 = self._create_test_classification( - 'udp', classifications.UDPClassification) + 'udp', class_obj.UDPClassification) c3 = self._create_test_classification( - 'ethernet', classifications.EthernetClassification) + 'ethernet', class_obj.EthernetClassification) cs = [c1, c2, c3] for c in cs: self._create_test_cg_c_mapping(cg.id, c.id) - fetch_cg = classifications.ClassificationGroup.get_object( + fetch_cg = class_obj.ClassificationGroup.get_object( self.ctx, id=cg.id) - mapped_cs = classifications._get_mapped_classifications( + mapped_cs = class_obj._get_mapped_classifications( self.ctx, fetch_cg) for c in cs: self.assertIn(c, mapped_cs) diff --git a/neutron_classifier/tests/unit/services/classifications/test_advertiser.py b/neutron_classifier/tests/unit/services/classifications/test_advertiser.py new file mode 100644 index 0000000..d83a00f --- /dev/null +++ b/neutron_classifier/tests/unit/services/classifications/test_advertiser.py @@ -0,0 +1,146 @@ +# Copyright 2019 Intel Corporation. +# +# 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 mock +from neutron.api.rpc import handlers +from neutron.tests.unit import testlib_api +from neutron_classifier.services.classification import advertiser +from oslo_utils import uuidutils + + +class TestAdvertiser(testlib_api.SqlTestCase): + + def setUp(self): + super(TestAdvertiser, self).setUp() + self.mock_context = mock.Mock() + self.mock_id = (uuidutils.generate_uuid()) + mock.patch.object(advertiser, 'resources_rpc').start() + mock.patch.object(advertiser, 'n_rpc').start() + + mock.patch.object(advertiser, 'class_obj').start() + mock.patch.object(advertiser, 'db_api').start() + self.advertiser = advertiser.ClassificationAdvertiser() + + mock.patch('neutron.objects.db.api.get_object').start() + mock.patch('neutron_lib.db.api.CONTEXT_READER.using').start() + + @mock.patch.object(advertiser, 'class_obj') + @mock.patch.object(advertiser, 'class_obj') + def test_get_classification(self, mock_class_obj, + mock_cs_group): + + mock_obj = mock.Mock() + mock_cls = mock.Mock() + mock_cls.get_object.return_value = mock_obj + mock_base = mock.Mock() + mock_cs_group.CLASS_MAP = mock.Mock() + + mock_class_obj.ClassificationBase = mock.Mock( + return_value=mock_base) + mock_cs_group.CLASS_MAP.__getitem__ = mock.\ + Mock(return_value=mock_cls) + + return_obj = self.advertiser.\ + _get_classification('', self.mock_id, + context=self.mock_context) + + self.assertEqual(return_obj, mock_obj) + + @mock.patch.object(advertiser, 'class_obj') + def test_get_classification_group(self, mock_class_obj): + + test_cg = mock.Mock() + mock_class_obj.ClassificationGroup.get_object.\ + return_value = test_cg + return_obj = self.advertiser.\ + _get_classification_group('', self.mock_id, + context=self.mock_context) + + self.assertEqual(return_obj, test_cg) + + @mock.patch.object(advertiser.models, '_read_classifications') + @mock.patch.object(advertiser.models, '_read_classification_groups') + @mock.patch.object(advertiser, 'db_api') + def test_get_classification_group_mapping(self, mock_db_api, + mock_read_cls_grp, + mock_read_cls): + mock_db_api.start() + mock_read_cls_grp.start() + mock_read_cls.start() + + ret_obj = None + mock_cls = mock.Mock() + mock_cls_grp = mock.Mock() + mock_cls.id = uuidutils.generate_uuid() + mock_cls_grp.id = uuidutils.generate_uuid() + + mock_read_cls_grp.return_value = [mock_cls_grp] + mock_read_cls.return_value = [mock_cls] + mock_mapped_cs = [mock_cs.id for mock_cs in [mock_cls]] + mock_mapped_cgs = [mock_cgs.id for mock_cgs in [mock_cls_grp]] + with mock_db_api.using(self.mock_context): + ret_obj = self.advertiser._get_classification_group_mapping( + self.mock_context, self.mock_id) + + self.assertEqual( + {'classifications': mock_mapped_cs, 'classification_groups': + mock_mapped_cgs}, ret_obj) + mock_read_cls_grp.assert_called_once_with(self.mock_context, + self.mock_id) + mock_read_cls.assert_called_once_with(self.mock_context, + self.mock_id) + + @mock.patch.object(handlers, 'resources_rpc') + @mock.patch.object(advertiser, 'nc_consts') + @mock.patch.object(advertiser, 'rpc_events') + def test_call_creates_classes(self, mock_rpc_events, mock_nc_consts, + mock_resources_rpc): + + mock_resources_rpc.start() + mock_nc_consts.start() + mock_rpc_events.start() + self.mock_classification = mock.Mock() + + self.advertiser.push_api = mock.Mock() + self.advertiser.push_api.push = mock.Mock() + self.advertiser.call(mock_nc_consts.CREATE_CLASS, + context=self.mock_context, + classification=self.mock_classification) + self.advertiser.push_api.push.\ + assert_called_with(self.mock_context, [self.mock_classification], + mock_rpc_events.CREATED) + + @mock.patch.object(handlers, 'resources_rpc') + @mock.patch.object(advertiser, 'nc_consts') + @mock.patch.object(advertiser, 'rpc_events') + def test_call_deletes_classes(self, mock_rpc_events, mock_nc_consts, + mock_resources_rpc): + mock_resources_rpc.start() + mock_nc_consts.start() + mock_rpc_events.start() + self.mock_classification = mock.Mock() + + self.advertiser.push_api = mock.Mock() + self.advertiser.push_api.push = mock.Mock() + self.advertiser.call(mock_nc_consts.DELETE_CLASS, + context=self.mock_context, + classification=self.mock_classification) + self.advertiser.push_api.push.\ + assert_called_with(self.mock_context, + [self.mock_classification], + mock_rpc_events.DELETED) + + def tearDown(self): + super(TestAdvertiser, self).tearDown() diff --git a/neutron_classifier/tests/unit/services/classifications/test_extension.py b/neutron_classifier/tests/unit/services/classifications/test_extension.py new file mode 100644 index 0000000..3c5d653 --- /dev/null +++ b/neutron_classifier/tests/unit/services/classifications/test_extension.py @@ -0,0 +1,96 @@ +# Copyright 2019 Intel Corporation. +# +# 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 unittest + +import mock + +from neutron.api.rpc.callbacks import events +from neutron_classifier.objects import classification as class_obj +from neutron_classifier.services.classification import extension + +from oslo_utils import uuidutils + + +class TestClassifierExtension(unittest.TestCase): + + def setUp(self): + super(TestClassifierExtension, self).setUp() + self.mock_context = mock.Mock() + mock.patch.object(extension, 'resources').start() + self.mock_id = uuidutils.generate_uuid() + self.extension = extension.NeutronClassifierExtension() + self.extension.agent_api = mock.Mock() + mock_rtvt = mock.patch('neutron.api.rpc.handlers.resources_rpc' + '.resource_type_versioned_topic') + mock_r = mock.patch('neutron.api.rpc.callbacks' + '.consumer.registry.register') + + mock_rtvt.start() + mock_r.start() + + def test_register_rpc_consumers(self): + mock_connection = mock.Mock() + mock_consumer = mock.MagicMock() + mock_connection.create_consumer = mock_consumer + + test_supported_resource_types = [ + class_obj.ClassificationGroup.obj_name(), + class_obj.ClassificationBase.obj_name(), + class_obj.EthernetClassification.obj_name(), + class_obj.IPV4Classification.obj_name(), + class_obj.IPV6Classification.obj_name(), + class_obj.UDPClassification.obj_name(), + class_obj.TCPClassification.obj_name() + ] + + self.extension._register_rpc_consumers(mock_connection) + self.assertEqual(mock_consumer.call_count, + len(test_supported_resource_types)) + + def test_handle_notification_ignores_events(self): + + self.extension.agent_api.register_classification = mock.Mock() + for event_type in set(events.VALID) - {events.CREATED}: + self.extension.handle_notification(mock.Mock(), '', + object(), event_type) + self.assertFalse(self.extension.agent_api. + register_classification.called) + + def test_handle_notification_passes_events_classification(self): + + self.extension.agent_api.register_classification = mock.Mock() + class_obj = mock.Mock() + self.extension.handle_notification(mock.Mock(), 'IPV4Classification', + [class_obj], events.CREATED) + + self.extension.agent_api.register_classification. \ + assert_called_once() + self.assertFalse(self.extension.agent_api. + register_classification_group.called) + + def test_handle_notification_passes_events_classification_group(self): + self.extension.agent_api.register_classification_group = mock.Mock() + class_obj = mock.Mock() + self.extension.handle_notification(mock.Mock(), 'ClassificationGroup', + [class_obj], events.CREATED) + self.extension.agent_api.register_classification_group. \ + assert_called_once() + self.assertFalse(self.extension.agent_api. + register_classification.called) + + def tearDown(self): + super(TestClassifierExtension, self).tearDown() + pass diff --git a/neutron_classifier/tests/unit/services/classifications/test_plugin.py b/neutron_classifier/tests/unit/services/classifications/test_plugin.py index f09a882..3b45fcc 100644 --- a/neutron_classifier/tests/unit/services/classifications/test_plugin.py +++ b/neutron_classifier/tests/unit/services/classifications/test_plugin.py @@ -14,7 +14,7 @@ import mock from neutron.objects import base as base_obj -from neutron_classifier.objects import classifications as class_group +from neutron_classifier.objects import classification as class_obj from neutron_classifier.services.classification import plugin from neutron_classifier.tests import base from neutron_lib import context @@ -31,6 +31,8 @@ class TestPlugin(base.BaseClassificationTestCase): mock.patch('neutron.objects.db.api.update_object').start() mock.patch('neutron.objects.db.api.delete_object').start() mock.patch('neutron.objects.db.api.get_object').start() + mock.patch('neutron_classifier.services.classification.advertiser' + '.ClassificationAdvertiser').start() self.cl_plugin = plugin.ClassificationPlugin() @@ -38,7 +40,7 @@ class TestPlugin(base.BaseClassificationTestCase): mock.patch.object(self.ctxt.session, 'refresh').start() mock.patch.object(self.ctxt.session, 'expunge').start() - mock.patch('neutron_classifier.objects.classifications').start() + mock.patch('neutron_classifier.objects.classification').start() self._generate_test_classifications() @@ -106,8 +108,8 @@ class TestPlugin(base.BaseClassificationTestCase): self.assertEqual(self.test_classification['classification'], cl) - @mock.patch.object(class_group.EthernetClassification, 'create') - @mock.patch.object(class_group.EthernetClassification, 'id', + @mock.patch.object(class_obj.EthernetClassification, 'create') + @mock.patch.object(class_obj.EthernetClassification, 'id', return_value=uuidutils.generate_uuid()) def test_create_classification(self, mock_ethernet_id, mock_ethernet_create): @@ -123,15 +125,15 @@ class TestPlugin(base.BaseClassificationTestCase): self.ctxt, self.test_classification) expected_val = self.test_classification['classification'] - expected_val['id'] = class_group.EthernetClassification.id + expected_val['id'] = class_obj.EthernetClassification.id self.assertEqual(expected_val, val) mock_manager.create.assert_called_once() @mock.patch.object(plugin.ClassificationPlugin, 'merge_header') - @mock.patch.object(class_group.ClassificationBase, 'get_object') - @mock.patch.object(class_group.EthernetClassification, 'update_object') - @mock.patch.object(class_group.EthernetClassification, 'id', + @mock.patch.object(class_obj.ClassificationBase, 'get_object') + @mock.patch.object(class_obj.EthernetClassification, 'update_object') + @mock.patch.object(class_obj.EthernetClassification, 'id', return_value=uuidutils.generate_uuid()) def test_update_classification(self, mock_id, mock_ethernet_update, mock_class_get, mock_merge): @@ -143,7 +145,7 @@ class TestPlugin(base.BaseClassificationTestCase): mock_manager.reset_mock() mock_manager.start() - class_obj = class_group.EthernetClassification( + c_obj = class_obj.EthernetClassification( self.ctxt, **self.test_classification_broken_headers) ethernet_classification_update = {'classification': { @@ -152,29 +154,29 @@ class TestPlugin(base.BaseClassificationTestCase): mock_manager.get_classification().c_type = 'ethernet' self.cl_plugin.update_classification( - self.ctxt, class_obj.id, + self.ctxt, c_obj.id, ethernet_classification_update) classification_update_mock_call = mock.call.update( self.ctxt, {'description': 'Test Ethernet Classification Version 2', 'name': 'test_ethernet_classification Version 2'}, - id=class_obj.id) + id=c_obj.id) self.assertIn(classification_update_mock_call, mock_manager.mock_calls) self.assertEqual(mock_manager.get_classification.call_count, 2) - @mock.patch.object(class_group.ClassificationBase, 'get_object') - @mock.patch.object(class_group.EthernetClassification, 'get_object') + @mock.patch.object(class_obj.ClassificationBase, 'get_object') + @mock.patch.object(class_obj.EthernetClassification, 'get_object') def test_delete_classification(self, mock_ethernet_get, mock_base_get): mock_manager = mock.Mock() mock_manager.attach_mock(mock_base_get, 'get_object') mock_manager.attach_mock(mock_ethernet_get, 'get_object') - eth_class_obj = class_group.EthernetClassification( + eth_class_obj = class_obj.EthernetClassification( self.ctxt, **self.test_classification_broken_headers) eth_class_obj.delete = mock.Mock() - base_class_obj = class_group.ClassificationBase( + base_class_obj = class_obj.ClassificationBase( self.ctxt, **self.test_classification_broken_headers) mock_base_get.return_value = base_class_obj @@ -193,8 +195,8 @@ class TestPlugin(base.BaseClassificationTestCase): mock_manager.mock_calls) self.assertTrue(eth_class_obj.delete.assert_called_once) - @mock.patch.object(class_group.ClassificationBase, 'get_object') - @mock.patch.object(class_group.EthernetClassification, 'get_object') + @mock.patch.object(class_obj.ClassificationBase, 'get_object') + @mock.patch.object(class_obj.EthernetClassification, 'get_object') def test_get_classification(self, mock_ethernet_get, mock_base_get): mock_manager = mock.Mock() @@ -206,9 +208,9 @@ class TestPlugin(base.BaseClassificationTestCase): definition = eth_classification.pop('definition') - base_class_obj = class_group.ClassificationBase( + base_class_obj = class_obj.ClassificationBase( self.ctxt, **eth_classification) - eth_class_obj = class_group.EthernetClassification( + eth_class_obj = class_obj.EthernetClassification( self.ctxt, **self.test_classification_broken_headers) mock_base_get.return_value = base_class_obj @@ -227,8 +229,8 @@ class TestPlugin(base.BaseClassificationTestCase): mock_manager.mock_calls) self.assertTrue(eth_classification, value) - @mock.patch.object(class_group.ClassificationBase, 'get_objects') - @mock.patch.object(class_group.EthernetClassification, 'get_objects') + @mock.patch.object(class_obj.ClassificationBase, 'get_objects') + @mock.patch.object(class_obj.EthernetClassification, 'get_objects') @mock.patch.object(base_obj, 'Pager') def test_get_classifications(self, mock_pager, mock_ethernet_get, mock_base_get): @@ -242,13 +244,13 @@ class TestPlugin(base.BaseClassificationTestCase): definition = eth_cl_1.pop('definition') definition_2 = eth_cl_2.pop('definition') - base_class_obj_1 = class_group.ClassificationBase( + base_class_obj_1 = class_obj.ClassificationBase( self.ctxt, **eth_cl_1) - base_class_obj_2 = class_group.ClassificationBase( + base_class_obj_2 = class_obj.ClassificationBase( self.ctxt, **eth_cl_2) - eth_class_obj_1 = class_group.EthernetClassification( + eth_class_obj_1 = class_obj.EthernetClassification( self.ctxt, **self.test_classification_broken_headers) - eth_class_obj_2 = class_group.EthernetClassification( + eth_class_obj_2 = class_obj.EthernetClassification( self.ctxt, **self.test_classification_2_broken_headers) base_list = [base_class_obj_1, base_class_obj_2] diff --git a/setup.cfg b/setup.cfg index 5d2a2c7..d294699 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,6 +59,8 @@ openstack.neutronclient.v2 = network classification group update = neutron_classifier.cli.openstack_cli.classification_group:UpdateClassificationGroup neutron.db.alembic_migrations = neutron-classifier = neutron_classifier.db.migration:alembic_migrations +neutron.agent.l2.extensions = + neutron_classifier = neutron_classifier.services.classification.extension:NeutronClassifierExtension [build_sphinx] source-dir = doc/source