From 913c9e78b92cb000cc59e19cb84a5b8a1eae076b Mon Sep 17 00:00:00 2001 From: Nguyen Phuong An Date: Wed, 9 Nov 2016 17:02:48 +0700 Subject: [PATCH] [log]: implement logging plugin This patch introduces the logging api definition and initial implementation of LoggingApiPlugin. The api definition code will be removed after [1] has been merged on neutron lib. [1]https://review.openstack.org/#/c/415817/ Co-Authored-By: Yushiro FURUKAWA Partially-implements: blueprint security-group-logging Related-Bug: #1468366 Change-Id: Iace31506502de25da9dce5fcfdbfe2c726bea27f --- etc/policy.json | 9 +- neutron/api/rpc/callbacks/resources.py | 3 + neutron/extensions/logging.py | 158 +++++++++++++ neutron/objects/logapi/event_types.py | 2 +- neutron/plugins/common/constants.py | 1 + neutron/services/logapi/common/constants.py | 2 +- neutron/services/logapi/common/exceptions.py | 15 +- neutron/services/logapi/logging_plugin.py | 93 ++++++++ neutron/tests/etc/policy.json | 9 +- neutron/tests/tools.py | 2 +- .../tests/unit/services/logapi/__init__.py | 0 neutron/tests/unit/services/logapi/base.py | 38 ++++ .../services/logapi/test_logging_plugin.py | 207 ++++++++++++++++++ setup.cfg | 1 + 14 files changed, 525 insertions(+), 15 deletions(-) create mode 100644 neutron/extensions/logging.py create mode 100644 neutron/services/logapi/logging_plugin.py create mode 100644 neutron/tests/unit/services/logapi/__init__.py create mode 100644 neutron/tests/unit/services/logapi/base.py create mode 100644 neutron/tests/unit/services/logapi/test_logging_plugin.py diff --git a/etc/policy.json b/etc/policy.json index e1e36bb1ec7..ee7134067ad 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -225,5 +225,12 @@ "get_security_group_rules": "rule:admin_or_owner", "get_security_group_rule": "rule:admin_or_owner", "create_security_group_rule": "rule:admin_or_owner", - "delete_security_group_rule": "rule:admin_or_owner" + "delete_security_group_rule": "rule:admin_or_owner", + + "get_loggable_resources": "rule:admin_only", + "create_log": "rule:admin_only", + "update_log": "rule:admin_only", + "delete_log": "rule:admin_only", + "get_logs": "rule:admin_only", + "get_log": "rule:admin_only" } diff --git a/neutron/api/rpc/callbacks/resources.py b/neutron/api/rpc/callbacks/resources.py index 646719b747e..d152adbb71b 100644 --- a/neutron/api/rpc/callbacks/resources.py +++ b/neutron/api/rpc/callbacks/resources.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron.objects.logapi import logging_resource as log_object from neutron.objects import network from neutron.objects import ports from neutron.objects.qos import policy @@ -19,6 +20,7 @@ from neutron.objects import trunk # Supported types +LOGGING_RESOURCE = log_object.Log.obj_name() TRUNK = trunk.Trunk.obj_name() QOS_POLICY = policy.QosPolicy.obj_name() SUBPORT = trunk.SubPort.obj_name() @@ -38,6 +40,7 @@ _VALID_CLS = ( network.Network, securitygroup.SecurityGroup, securitygroup.SecurityGroupRule, + log_object.Log, ) _TYPE_TO_CLS_MAP = {cls.obj_name(): cls for cls in _VALID_CLS} diff --git a/neutron/extensions/logging.py b/neutron/extensions/logging.py new file mode 100644 index 00000000000..7733b664cf6 --- /dev/null +++ b/neutron/extensions/logging.py @@ -0,0 +1,158 @@ +# Copyright (c) 2017 Fujitsu Limited +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import abc +import itertools + +from neutron_lib.api import converters +from neutron_lib.api import extensions as api_extensions +from neutron_lib.db import constants as db_const +from neutron_lib.services import base as service_base +import six + +from neutron.api.v2 import resource_helper +from neutron.plugins.common import constants +from neutron.services.logapi.common import constants as log_const + + +LOG_PREFIX = "/log" +# Attribute Map +RESOURCE_ATTRIBUTE_MAP = { + 'logs': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'project_id': {'allow_post': True, 'allow_put': False, + 'required_by_policy': True, + 'validate': { + 'type:string': + db_const.PROJECT_ID_FIELD_SIZE}, + 'is_visible': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': db_const.NAME_FIELD_SIZE}, + 'default': '', 'is_visible': True}, + 'resource_type': {'allow_post': True, 'allow_put': False, + 'required_by_policy': True, + 'validate': + {'type:string': db_const.RESOURCE_TYPE_FIELD_SIZE}, + 'is_visible': True}, + 'resource_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid_or_none': None}, + 'default': None, 'is_visible': True}, + 'event': {'allow_post': True, 'allow_put': False, + 'validate': {'type:values': log_const.LOG_EVENTS}, + 'default': log_const.ALL_EVENT, 'is_visible': True}, + 'target_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid_or_none': None}, + 'default': None, 'is_visible': True}, + 'enabled': {'allow_post': True, 'allow_put': True, + 'is_visible': True, 'default': True, + 'convert_to': converters.convert_to_boolean}, + }, + + 'loggable_resources': { + 'type': {'allow_post': False, 'allow_put': False, + 'is_visible': True}}, +} + + +class Logging(api_extensions.ExtensionDescriptor): + """Neutron logging api extension.""" + + @classmethod + def get_name(cls): + return "Logging API Extension" + + @classmethod + def get_alias(cls): + return "logging" + + @classmethod + def get_description(cls): + return "Provides a logging API for resources such as security group" + + @classmethod + def get_updated(cls): + return "2017-01-01T10:00:00-00:00" + + @classmethod + def get_plugin_interface(cls): + return LoggingPluginBase + + @classmethod + def get_resources(cls): + """Returns Ext Resources.""" + plural_mappings = resource_helper.build_plural_mappings( + {}, itertools.chain(RESOURCE_ATTRIBUTE_MAP)) + + resources = resource_helper.build_resource_info( + plural_mappings, + RESOURCE_ATTRIBUTE_MAP, + constants.LOG_API, + translate_name=True, + allow_bulk=True) + + return resources + + def update_attributes_map(self, attributes, extension_attrs_map=None): + super(Logging, self).update_attributes_map( + attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP) + + def get_extended_resources(self, version): + if version == "2.0": + return dict(list(RESOURCE_ATTRIBUTE_MAP.items())) + else: + return {} + + +@six.add_metaclass(abc.ABCMeta) +class LoggingPluginBase(service_base.ServicePluginBase): + + path_prefix = LOG_PREFIX + + def get_plugin_description(self): + return "Logging API Service Plugin" + + @classmethod + def get_plugin_type(cls): + return constants.LOG_API + + @abc.abstractmethod + def get_logs(self, context, filters=None, fields=None, sorts=None, + limit=None, marker=None, page_reverse=False): + pass + + @abc.abstractmethod + def get_log(self, context, log_id, fields=None): + pass + + @abc.abstractmethod + def create_log(self, context, log): + pass + + @abc.abstractmethod + def update_log(self, context, log_id, log): + pass + + @abc.abstractmethod + def delete_log(self, context, log_id): + pass + + @abc.abstractmethod + def get_loggable_resources(self, context, filters=None, fields=None, + sorts=None, limit=None, + marker=None, page_reverse=False): + pass diff --git a/neutron/objects/logapi/event_types.py b/neutron/objects/logapi/event_types.py index 7575a2e9334..82c385e3e60 100644 --- a/neutron/objects/logapi/event_types.py +++ b/neutron/objects/logapi/event_types.py @@ -35,4 +35,4 @@ class SecurityEvent(obj_fields.String): class SecurityEventField(obj_fields.AutoTypedField): - AUTO_TYPE = SecurityEvent(valid_values=log_const.VALID_EVENTS) + AUTO_TYPE = SecurityEvent(valid_values=log_const.LOG_EVENTS) diff --git a/neutron/plugins/common/constants.py b/neutron/plugins/common/constants.py index 7599e903781..fa278710653 100644 --- a/neutron/plugins/common/constants.py +++ b/neutron/plugins/common/constants.py @@ -25,6 +25,7 @@ VPN = "VPN" METERING = "METERING" FLAVORS = "FLAVORS" QOS = "QOS" +LOG_API = "LOGGING" # Maps extension alias to service type that # can be implemented by the core plugin. diff --git a/neutron/services/logapi/common/constants.py b/neutron/services/logapi/common/constants.py index acda2a632da..e9184b7d2e4 100644 --- a/neutron/services/logapi/common/constants.py +++ b/neutron/services/logapi/common/constants.py @@ -16,5 +16,5 @@ ACCEPT_EVENT = 'ACCEPT' DROP_EVENT = 'DROP' ALL_EVENT = 'ALL' -VALID_EVENTS = [ACCEPT_EVENT, DROP_EVENT, ALL_EVENT] +LOG_EVENTS = [ACCEPT_EVENT, DROP_EVENT, ALL_EVENT] LOGGING_PLUGIN = 'logging-plugin' diff --git a/neutron/services/logapi/common/exceptions.py b/neutron/services/logapi/common/exceptions.py index 2192370fcf9..7dafba78ed2 100644 --- a/neutron/services/logapi/common/exceptions.py +++ b/neutron/services/logapi/common/exceptions.py @@ -1,5 +1,4 @@ -# Copyright 2016 Fujitsu Limited. -# +# Copyright 2017 Fujitsu Limited. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -18,13 +17,9 @@ from neutron._i18n import _ from neutron_lib import exceptions as n_exc -class ResourceLogNotFound(n_exc.NotFound): - message = _("Resource log %(id)s could not be found.") +class LogResourceNotFound(n_exc.NotFound): + message = _("Log resource %(log_id)s could not be found.") -class ParentResourceNotFound(n_exc.NotFound): - message = _("Parent resource %(parent_resource_id)s could not be found.") - - -class ResourceNotFound(n_exc.NotFound): - message = _("Resource %(resource_id)s could not be found.") +class InvalidLogReosurceType(n_exc.InvalidInput): + message = _("Invalid log resource_type: %(resource_type)s.") diff --git a/neutron/services/logapi/logging_plugin.py b/neutron/services/logapi/logging_plugin.py new file mode 100644 index 00000000000..3a3922be8dd --- /dev/null +++ b/neutron/services/logapi/logging_plugin.py @@ -0,0 +1,93 @@ +# Copyright (c) 2017 Fujitsu Limited +# All Rights Reserved. +# +# 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 api as db_api +from neutron.db import db_base_plugin_common +from neutron.extensions import logging as log_ext +from neutron.objects import base as base_obj +from neutron.objects.logapi import logging_resource as log_object +from neutron.services.logapi.common import exceptions as log_exc + + +class LoggingPlugin(log_ext.LoggingPluginBase): + """Implementation of the Neutron logging api plugin.""" + + supported_extension_aliases = ['logging'] + + __native_pagination_support = True + __native_sorting_support = True + + @property + def supported_logging_types(self): + # Todo(annp): supported_logging_types will dynamic load from + # log_drivers. So return value for this function is a temporary. + return [] + + @db_base_plugin_common.filter_fields + @db_base_plugin_common.convert_result_to_dict + def get_logs(self, context, filters=None, fields=None, sorts=None, + limit=None, marker=None, page_reverse=False): + """Return information for available log objects""" + filters = filters or {} + pager = base_obj.Pager(sorts, limit, page_reverse, marker) + return log_object.Log.get_objects(context, _pager=pager, **filters) + + def _get_log(self, context, log_id): + """Return the log object or raise if not found""" + log_obj = log_object.Log.get_object(context, id=log_id) + if not log_obj: + raise log_exc.LogResourceNotFound(log_id=log_id) + return log_obj + + @db_base_plugin_common.filter_fields + @db_base_plugin_common.convert_result_to_dict + def get_log(self, context, log_id, fields=None): + return self._get_log(context, log_id) + + @db_base_plugin_common.convert_result_to_dict + def create_log(self, context, log): + """Create a log object""" + log_data = log['log'] + with db_api.context_manager.writer.using(context): + # body 'log' contains both tenant_id and project_id + # but only latter needs to be used to create Log object. + # We need to remove redundant keyword. + log_data.pop('tenant_id', None) + log_obj = log_object.Log(context=context, **log_data) + log_obj.create() + return log_obj + + @db_base_plugin_common.convert_result_to_dict + def update_log(self, context, log_id, log): + """Update information for the specified log object""" + log_data = log['log'] + with db_api.context_manager.writer.using(context): + log_obj = log_object.Log(context, id=log_id) + log_obj.update_fields(log_data, reset_changes=True) + log_obj.update() + return log_obj + + def delete_log(self, context, log_id): + """Delete the specified log object""" + with db_api.context_manager.writer.using(context): + log_obj = self._get_log(context, log_id) + log_obj.delete() + + def get_loggable_resources(self, context, filters=None, fields=None, + sorts=None, limit=None, + marker=None, page_reverse=False): + """Get supported logging types""" + return [{'type': type_} + for type_ in self.supported_logging_types] diff --git a/neutron/tests/etc/policy.json b/neutron/tests/etc/policy.json index e1e36bb1ec7..ee7134067ad 100644 --- a/neutron/tests/etc/policy.json +++ b/neutron/tests/etc/policy.json @@ -225,5 +225,12 @@ "get_security_group_rules": "rule:admin_or_owner", "get_security_group_rule": "rule:admin_or_owner", "create_security_group_rule": "rule:admin_or_owner", - "delete_security_group_rule": "rule:admin_or_owner" + "delete_security_group_rule": "rule:admin_or_owner", + + "get_loggable_resources": "rule:admin_only", + "create_log": "rule:admin_only", + "update_log": "rule:admin_only", + "delete_log": "rule:admin_only", + "get_logs": "rule:admin_only", + "get_log": "rule:admin_only" } diff --git a/neutron/tests/tools.py b/neutron/tests/tools.py index fc105f04e83..ba2dc6c956c 100644 --- a/neutron/tests/tools.py +++ b/neutron/tests/tools.py @@ -323,4 +323,4 @@ def get_random_ipv6_mode(): def get_random_security_event(): - return random.choice(log_const.VALID_EVENTS) + return random.choice(log_const.LOG_EVENTS) diff --git a/neutron/tests/unit/services/logapi/__init__.py b/neutron/tests/unit/services/logapi/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/services/logapi/base.py b/neutron/tests/unit/services/logapi/base.py new file mode 100644 index 00000000000..585387b4913 --- /dev/null +++ b/neutron/tests/unit/services/logapi/base.py @@ -0,0 +1,38 @@ +# 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.callbacks.consumer import registry as cons_registry +from neutron.api.rpc.callbacks.producer import registry as prod_registry +from neutron.api.rpc.callbacks import resource_manager +from neutron.tests.unit import testlib_api + + +class BaseLogTestCase(testlib_api.SqlTestCase): + def setUp(self): + super(BaseLogTestCase, self).setUp() + + with mock.patch.object( + resource_manager.ResourceCallbacksManager, '_singleton', + new_callable=mock.PropertyMock(return_value=False)): + + self.cons_mgr = resource_manager.ConsumerResourceCallbacksManager() + self.prod_mgr = resource_manager.ProducerResourceCallbacksManager() + for mgr in (self.cons_mgr, self.prod_mgr): + mgr.clear() + + mock.patch.object( + cons_registry, '_get_manager', return_value=self.cons_mgr).start() + + mock.patch.object( + prod_registry, '_get_manager', return_value=self.prod_mgr).start() diff --git a/neutron/tests/unit/services/logapi/test_logging_plugin.py b/neutron/tests/unit/services/logapi/test_logging_plugin.py new file mode 100644 index 00000000000..31e90f31026 --- /dev/null +++ b/neutron/tests/unit/services/logapi/test_logging_plugin.py @@ -0,0 +1,207 @@ +# Copyright (C) 2017 Fujitsu Limited +# All Rights Reserved. +# +# 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_lib import context +from neutron_lib.plugins import directory +from oslo_config import cfg +from oslo_utils import uuidutils + +from neutron import manager +from neutron.objects.logapi import logging_resource as log_object +from neutron.objects import ports +from neutron.objects import securitygroup as sg_object +from neutron.plugins.common import constants +from neutron.services.logapi.common import exceptions as log_exc +from neutron.tests.unit.services.logapi import base + +DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' + + +class TestLoggingPlugin(base.BaseLogTestCase): + def setUp(self): + super(TestLoggingPlugin, self).setUp() + self.setup_coreplugin(load_plugins=False) + + mock.patch('neutron.objects.db.api.create_object').start() + 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() + # We don't use real models as per mocks above. We also need to mock-out + # methods that work with real data types + mock.patch( + 'neutron.objects.base.NeutronDbObject.modify_fields_from_db' + ).start() + + cfg.CONF.set_override("core_plugin", DB_PLUGIN_KLASS) + cfg.CONF.set_override("service_plugins", + ["neutron.services.logapi.logging_plugin.LoggingPlugin"]) + + manager.init() + self.log_plugin = directory.get_plugin(constants.LOG_API) + self.ctxt = context.Context('fake_user', 'fake_tenant') + mock.patch.object(self.ctxt.session, 'refresh').start() + mock.patch.object(self.ctxt.session, 'expunge').start() + + def test_get_logs(self): + with mock.patch.object(log_object.Log, 'get_objects')\ + as get_objects_mock: + filters = {'filter': 'filter_id'} + self.log_plugin.get_logs(self.ctxt, filters=filters) + get_objects_mock.assert_called_once_with(self.ctxt, + _pager=mock.ANY, filter='filter_id') + + def test_get_log_without_return_value(self): + with mock.patch.object(log_object.Log, 'get_object', + return_value=None): + self.assertRaises( + log_exc.LogResourceNotFound, + self.log_plugin.get_log, + self.ctxt, + mock.ANY, + ) + + def test_get_log_with_return_value(self): + log_id = uuidutils.generate_uuid() + with mock.patch.object(log_object.Log, 'get_object')\ + as get_object_mock: + self.log_plugin.get_log(self.ctxt, log_id) + get_object_mock.assert_called_once_with(self.ctxt, + id=log_id) + + @mock.patch('neutron.db._utils.model_query') + def test_create_log_full_options(self, query_mock): + log = {'log': {'resource_type': 'security_group', + 'enabled': True, + 'resource_id': uuidutils.generate_uuid(), + 'target_id': uuidutils.generate_uuid()}} + sg = mock.Mock() + port = mock.Mock() + new_log = mock.Mock() + with mock.patch.object(sg_object.SecurityGroup, 'get_object', + return_value=sg): + with mock.patch.object(ports.Port, 'get_object', + return_value=port): + with mock.patch('neutron.objects.logapi.' + 'logging_resource.Log', + return_value=new_log) as init_log_mock: + self.log_plugin.create_log(self.ctxt, log) + init_log_mock.assert_called_once_with(context=self.ctxt, + **log['log']) + self.assertTrue(new_log.create.called) + + def test_create_log_without_sg_resource(self): + log = {'log': {'resource_type': 'security_group', + 'enabled': True, + 'target_id': uuidutils.generate_uuid()}} + new_log = mock.Mock() + new_log.enabled = True + port = mock.Mock() + with mock.patch.object(ports.Port, 'get_object', return_value=port): + with mock.patch('neutron.objects.logapi.logging_resource.Log', + return_value=new_log) as init_log_mock: + self.log_plugin.create_log(self.ctxt, log) + init_log_mock.assert_called_once_with( + context=self.ctxt, **log['log']) + self.assertTrue(new_log.create.called) + + def test_create_log_without_parent_resource(self): + log = {'log': {'resource_type': 'security_group', + 'enabled': True, + 'resource_id': uuidutils.generate_uuid()}} + new_log = mock.Mock() + new_log.enabled = True + sg = mock.Mock() + with mock.patch.object(sg_object.SecurityGroup, 'get_object', + return_value=sg): + with mock.patch('neutron.objects.logapi.logging_resource.Log', + return_value=new_log) as init_log_mock: + self.log_plugin.create_log(self.ctxt, log) + init_log_mock.assert_called_once_with(context=self.ctxt, + **log['log']) + self.assertTrue(new_log.create.called) + + def test_create_log_without_target(self): + log = {'log': {'resource_type': 'security_group', + 'enabled': True, }} + new_log = mock.Mock() + new_log.enabled = True + with mock.patch('neutron.objects.logapi.' + 'logging_resource.Log', + return_value=new_log) as init_log_mock: + self.log_plugin.create_log(self.ctxt, log) + init_log_mock.assert_called_once_with(context=self.ctxt, + **log['log']) + self.assertTrue(new_log.create.called) + + def test_create_log_disabled(self): + log_data = {'log': {'resource_type': 'security_group', + 'enabled': False}} + new_log = mock.Mock() + new_log.enabled = False + with mock.patch('neutron.objects.logapi.' + 'logging_resource.Log', + return_value=new_log) as init_log_mock: + self.log_plugin.create_log(self.ctxt, log_data) + init_log_mock.assert_called_once_with( + context=self.ctxt, **log_data['log']) + self.assertTrue(new_log.create.called) + + def test_update_log(self): + log_data = {'log': {'enabled': True}} + new_log = mock.Mock() + new_log.id = uuidutils.generate_uuid() + with mock.patch('neutron.objects.logapi.' + 'logging_resource.Log', + return_value=new_log) as update_log_mock: + self.log_plugin.update_log(self.ctxt, new_log.id, log_data) + update_log_mock.assert_called_once_with(self.ctxt, + id=new_log.id) + new_log.update_fields.assert_called_once_with(log_data['log'], + reset_changes=True) + self.assertTrue(new_log.update.called) + + def test_update_log_none_enabled(self): + log_data = {'log': {}} + new_log = mock.Mock() + new_log.id = uuidutils.generate_uuid() + with mock.patch('neutron.objects.logapi.' + 'logging_resource.Log', + return_value=new_log) as update_log_mock: + self.log_plugin.update_log(self.ctxt, new_log.id, log_data) + update_log_mock.assert_called_once_with(self.ctxt, + id=new_log.id) + new_log.update_fields.assert_called_once_with(log_data['log'], + reset_changes=True) + self.assertTrue(new_log.update.called) + + def test_delete_log(self): + delete_log = mock.Mock() + delete_log.id = uuidutils.generate_uuid() + with mock.patch.object(log_object.Log, 'get_object', + return_value=delete_log) as delete_log_mock: + self.log_plugin.delete_log(self.ctxt, delete_log.id) + delete_log_mock.assert_called_once_with(self.ctxt, + id=delete_log.id) + self.assertTrue(delete_log.delete.called) + + def test_delete_nonexistent_log(self): + with mock.patch.object(log_object.Log, 'get_object', + return_value=None): + self.assertRaises( + log_exc.LogResourceNotFound, + self.log_plugin.delete_log, + self.ctxt, + mock.ANY) diff --git a/setup.cfg b/setup.cfg index f6aaed9c053..5687d29e252 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,6 +82,7 @@ neutron.service_plugins = timestamp = neutron.services.timestamp.timestamp_plugin:TimeStampPlugin trunk = neutron.services.trunk.plugin:TrunkPlugin loki = neutron.services.loki.loki_plugin:LokiPlugin + logapi = neutron.services.logapi.logging_plugin:LoggingPlugin neutron.ml2.type_drivers = flat = neutron.plugins.ml2.drivers.type_flat:FlatTypeDriver local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver