diff --git a/neutron_fwaas/services/logapi/agents/drivers/iptables/driver.py b/neutron_fwaas/services/logapi/agents/drivers/iptables/driver.py index 61a28c9a4..b694228e5 100644 --- a/neutron_fwaas/services/logapi/agents/drivers/iptables/driver.py +++ b/neutron_fwaas/services/logapi/agents/drivers/iptables/driver.py @@ -22,6 +22,7 @@ from oslo_utils import importutils from neutron_fwaas.common import fwaas_constants from neutron_fwaas.services.logapi.common import fwg_callback +from neutron_fwaas.services.logapi.common import port_callback from neutron_fwaas.services.logapi import constants as fw_const from neutron_fwaas.services.logapi.rpc import log_server as rpc_server @@ -64,4 +65,7 @@ def register(): # Register resource callback handler manager.register( fwaas_constants.FIREWALL_GROUP, fwg_callback.FirewallGroupCallBack) + # Register resource callback handler for Neutron ports + manager.register(resources.PORT, port_callback.NeutronPortCallBack) + LOG.debug('FWaaS L3 Logging driver based iptables registered') diff --git a/neutron_fwaas/services/logapi/common/log_db_api.py b/neutron_fwaas/services/logapi/common/log_db_api.py index 5df87a274..d188413c3 100644 --- a/neutron_fwaas/services/logapi/common/log_db_api.py +++ b/neutron_fwaas/services/logapi/common/log_db_api.py @@ -55,10 +55,12 @@ def _get_ports_being_logged(context, log_obj): # TODO(longkb): L2 ports will be supported in the future # Check whether a port is router port or not. if device_owner in nl_const.ROUTER_INTERFACE_OWNERS: - # Check whether a port is attached to firewall group or not - fwg = fw_plugin_db.get_fwg_attached_to_port(context, port_id) - if fwg: - filtered_port_ids.append(port_id) + # Checking port status + if port.get('status') == nl_const.PORT_STATUS_ACTIVE: + # Check whether a port is attached to firewall group or not + fwg = fw_plugin_db.get_fwg_attached_to_port(context, port_id) + if fwg: + filtered_port_ids.append(port_id) return filtered_port_ids @@ -75,6 +77,15 @@ def _make_log_info_dict(log_obj, port_ids): def get_logs_for_port(context, port_id): """Return a list of log_resources bound to a given port_id""" + global fw_plugin_db + if not fw_plugin_db: + fw_plugin = directory.get_plugin(fwaas_constants.FIREWALL_V2) + + # NOTE(longkb): check whether fw plugin was loaded or not. + if not fw_plugin: + return [] + fw_plugin_db = fw_plugin.driver.firewall_db + logs_bounded = [] port = port_objects.Port.get_object(context, id=port_id) diff --git a/neutron_fwaas/services/logapi/common/port_callback.py b/neutron_fwaas/services/logapi/common/port_callback.py new file mode 100644 index 000000000..e508ad07f --- /dev/null +++ b/neutron_fwaas/services/logapi/common/port_callback.py @@ -0,0 +1,40 @@ +# Copyright (c) 2018 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.services.logapi.common import constants as log_const +from neutron.services.logapi.drivers import manager +from neutron_lib.callbacks import events +from neutron_lib import constants as nl_const + +from neutron_fwaas.services.logapi.common import log_db_api + + +class NeutronPortCallBack(manager.ResourceCallBackBase): + + def handle_event(self, resource, event, trigger, **kwargs): + if event == events.AFTER_UPDATE: + context = kwargs.get('context') + original_port = kwargs.get('original_port') + port = kwargs.get('port') + + if port['device_owner'] in nl_const.ROUTER_INTERFACE_OWNERS: + if original_port['status'] != port['status']: + self.trigger_logging(context, port) + + def trigger_logging(self, context, port): + log_resources = log_db_api.get_logs_for_port(context, port['id']) + if log_resources: + self.resource_push_api( + log_const.RESOURCE_UPDATE, context, log_resources) diff --git a/neutron_fwaas/tests/unit/services/logapi/common/test_log_db_api.py b/neutron_fwaas/tests/unit/services/logapi/common/test_log_db_api.py index 3f42fcdda..33b193f9b 100644 --- a/neutron_fwaas/tests/unit/services/logapi/common/test_log_db_api.py +++ b/neutron_fwaas/tests/unit/services/logapi/common/test_log_db_api.py @@ -54,13 +54,15 @@ def _fake_log_info(id, project_id, ports_id, event='ALL'): return expected -def _fake_port_object(port_id, device_owner, +def _fake_port_object(port_id, device_owner, status, project_id=uuidutils.generate_uuid()): port_data = { 'id': port_id, 'device_owner': device_owner, 'project_id': project_id } + if status: + port_data['status'] = status return port_data @@ -77,13 +79,16 @@ class LoggingRpcCallbackTestCase(base.BaseTestCase): self.router_port = uuidutils.generate_uuid() self.fake_vm_port = \ _fake_port_object(self.vm_port, - nl_const.DEVICE_OWNER_COMPUTE_PREFIX) + nl_const.DEVICE_OWNER_COMPUTE_PREFIX, + nl_const.PORT_STATUS_ACTIVE) self.fake_router_port = \ _fake_port_object(self.router_port, - nl_const.DEVICE_OWNER_ROUTER_INTF) + nl_const.DEVICE_OWNER_ROUTER_INTF, + nl_const.PORT_STATUS_ACTIVE) self.fake_router_ports = \ - [_fake_port_object(self.router_port, device) + [_fake_port_object(self.router_port, device, + nl_const.PORT_STATUS_ACTIVE) for device in nl_const.ROUTER_INTERFACE_OWNERS] def test_get_fwg_log_info_for_log_resources(self): @@ -159,6 +164,19 @@ class LoggingRpcCallbackTestCase(base.BaseTestCase): log_db_api._get_ports_being_logged(self.context, log_obj) self.assertEqual([self.router_port], logged_port_ids) + # Test with inactive router port + self.fake_router_port['status'] = nl_const.PORT_STATUS_DOWN + log_obj = _create_log_object(tenant_id, resource_id=fwg_id, + target_id=self.router_port) + + log_db_api.fw_plugin_db. \ + get_fwg_attached_to_port = mock.Mock(return_value='fwg_id') + with mock.patch.object(port_objects.Port, 'get_object', + return_value=self.fake_router_port): + logged_port_ids = \ + log_db_api._get_ports_being_logged(self.context, log_obj) + self.assertEqual([], logged_port_ids) + def test_get_ports_being_logged_with_resource_id(self): tenant_id = uuidutils.generate_uuid() fwg_id = uuidutils.generate_uuid() @@ -199,6 +217,18 @@ class LoggingRpcCallbackTestCase(base.BaseTestCase): log_db_api._get_ports_being_logged(self.context, log_obj) self.assertEqual([self.router_port], logged_port_ids) + # Test with inactive router port + log_db_api.fw_plugin_db.get_ports_in_firewall_group = \ + mock.Mock(return_value=[self.router_port]) + log_db_api.fw_plugin_db. \ + get_fwg_attached_to_port = mock.Mock(return_value='fwg_id') + + with mock.patch.object(port_objects.Port, 'get_object', + return_value=self.fake_router_port): + logged_port_ids = \ + log_db_api._get_ports_being_logged(self.context, log_obj) + self.assertEqual([self.router_port], logged_port_ids) + def test_get_ports_being_logged_with_ports_in_tenant(self): tenant_id = uuidutils.generate_uuid() log_obj = _create_log_object(tenant_id) diff --git a/neutron_fwaas/tests/unit/services/logapi/common/test_port_callback.py b/neutron_fwaas/tests/unit/services/logapi/common/test_port_callback.py new file mode 100644 index 000000000..a7eba97f6 --- /dev/null +++ b/neutron_fwaas/tests/unit/services/logapi/common/test_port_callback.py @@ -0,0 +1,203 @@ +# Copyright (c) 2018 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.objects import ports as port_objects +from neutron.services.logapi.drivers import base as log_driver_base +from neutron.services.logapi.drivers import manager as driver_mgr +from neutron.tests import base +from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry +from neutron_lib.callbacks import resources +from neutron_lib import constants as nl_const +from oslo_utils import uuidutils + +from neutron_fwaas.services.logapi.common import log_db_api +from neutron_fwaas.services.logapi.common import port_callback + +FAKE_DRIVER = None + + +class FakeDriver(log_driver_base.DriverBase): + + @staticmethod + def create(): + return FakeDriver( + name='fake_driver', + vif_types=[], + vnic_types=[], + supported_logging_types=['firewall_group'], + requires_rpc=True + ) + + +def fake_register(): + global FAKE_DRIVER + if not FAKE_DRIVER: + FAKE_DRIVER = FakeDriver.create() + driver_mgr.register(resources.PORT, port_callback.NeutronPortCallBack) + + +class TestFirewallGroupRuleCallback(base.BaseTestCase): + + def setUp(self): + super(TestFirewallGroupRuleCallback, self).setUp() + self.driver_manager = driver_mgr.LoggingServiceDriverManager() + self.port_callback = port_callback.NeutronPortCallBack(mock.Mock(), + mock.Mock()) + self.m_context = mock.Mock() + + def _create_port_object(self, name=None, device_owner=None, + status=nl_const.PORT_STATUS_ACTIVE): + port_data = { + 'id': uuidutils.generate_uuid(), + 'project_id': 'fake_tenant_id', + 'status': status + } + if name: + port_data['name'] = name + if device_owner: + port_data['device_owner'] = device_owner + return port_objects.Port(**port_data) + + @mock.patch.object(port_callback.NeutronPortCallBack, 'handle_event') + def test_handle_event(self, m_port_cb_handler): + fake_register() + self.driver_manager.register_driver(FAKE_DRIVER) + + registry.notify(resources.PORT, events.AFTER_CREATE, mock.ANY) + m_port_cb_handler.assert_called_once_with( + resources.PORT, events.AFTER_CREATE, mock.ANY) + + m_port_cb_handler.reset_mock() + registry.notify( + resources.PORT, events.AFTER_UPDATE, mock.ANY) + m_port_cb_handler.assert_called_once_with( + resources.PORT, events.AFTER_UPDATE, mock.ANY) + + m_port_cb_handler.reset_mock() + registry.notify( + 'non_registered_resource', events.AFTER_CREATE, mock.ANY) + m_port_cb_handler.assert_not_called() + + m_port_cb_handler.reset_mock() + registry.notify( + 'non_registered_resource', events.AFTER_UPDATE, mock.ANY) + m_port_cb_handler.assert_not_called() + + def test_trigger_logging(self): + fake_log_obj = mock.Mock() + self.port_callback.resource_push_api = mock.Mock() + port = self._create_port_object(device_owner='fake_device_owner') + + # Test with log resource could be found from DB + with mock.patch.object(log_db_api, 'get_logs_for_port', + return_value=[fake_log_obj]): + self.port_callback.trigger_logging(self.m_context, port) + self.port_callback.resource_push_api.assert_called() + + # Test with log resource could not be found from DB + self.port_callback.resource_push_api.reset_mock() + with mock.patch.object(log_db_api, 'get_logs_for_port', + return_value=[]): + self.port_callback.trigger_logging(self.m_context, port) + self.port_callback.resource_push_api.assert_not_called() + + def test_handle_event_with_router_port(self): + with mock.patch.object(self.port_callback, 'trigger_logging'): + # Test for router port enabling + f_port_config = self._fake_port_config( + nl_const.DEVICE_OWNER_ROUTER_INTF, action='enable') + self.port_callback.handle_event(mock.ANY, + events.AFTER_UPDATE, + mock.ANY, + **f_port_config) + self.port_callback.trigger_logging.assert_called() + + # Test for router port disabling + self.port_callback.trigger_logging.reset_mock() + f_port_config = self._fake_port_config( + nl_const.DEVICE_OWNER_ROUTER_INTF, action='disable') + self.port_callback.handle_event(mock.ANY, + events.AFTER_UPDATE, + mock.ANY, + **f_port_config) + self.port_callback.trigger_logging.assert_called() + + # Test for router port status does not change + self.port_callback.trigger_logging.reset_mock() + f_port_config = \ + self._fake_port_config(nl_const.DEVICE_OWNER_ROUTER_INTF) + self.port_callback.handle_event(mock.ANY, + events.AFTER_UPDATE, + mock.ANY, + **f_port_config) + self.port_callback.trigger_logging.assert_not_called() + + def test_handle_event_with_non_router_port(self): + with mock.patch.object(self.port_callback, 'trigger_logging'): + # Test for port enabling + f_port_config = self._fake_port_config('fake_port_type', + action='enable') + self.port_callback.handle_event(mock.ANY, + events.AFTER_UPDATE, + mock.ANY, + **f_port_config) + self.port_callback.trigger_logging.assert_not_called() + + # Test for port disabling + self.port_callback.trigger_logging.reset_mock() + f_port_config = self._fake_port_config('fake_port_type', + action='disable') + self.port_callback.handle_event(mock.ANY, + events.AFTER_UPDATE, + mock.ANY, + **f_port_config) + self.port_callback.trigger_logging.assert_not_called() + + def _fake_port_config(self, device_owner, action=None): + f_kwargs = {} + f_kwargs['context'] = self.m_context + if action == 'enable': + # Create original port with DOWN status + original_port = self._create_port_object( + device_owner=device_owner, status=nl_const.PORT_STATUS_DOWN) + + # Create port with ACTIVE status + port = self._create_port_object( + device_owner=device_owner, status=nl_const.PORT_STATUS_ACTIVE) + f_kwargs['original_port'] = original_port + f_kwargs['port'] = port + elif action == 'disable': + # Create original port with ACTIVE status + original_port = self._create_port_object( + device_owner=device_owner, status=nl_const.PORT_STATUS_ACTIVE) + + # Create port with DOWN status + port = self._create_port_object( + device_owner=device_owner, status=nl_const.PORT_STATUS_DOWN) + f_kwargs['original_port'] = original_port + f_kwargs['port'] = port + else: + # Create original port with ACTIVE status + original_port = self._create_port_object( + device_owner=device_owner, status=nl_const.PORT_STATUS_ACTIVE) + + # Create port with ACTIVE status + port = self._create_port_object( + device_owner=device_owner, status=nl_const.PORT_STATUS_ACTIVE) + f_kwargs['original_port'] = original_port + f_kwargs['port'] = port + return f_kwargs