Subscribe Neutron port update events for FWG Logging handling
Currently, FWaaS L3 logging only sync with firewall group related events, it does not care about Neutron port status update as reported in [1]. This patch aims to subscribe Neutron callback events that will trigger FWG Logging driver for further handling. [1] https://bugs.launchpad.net/neutron/+bug/1788759 Change-Id: If2754040dad0bae6c224ceaec8b7e66436a2195d Co-Authored-By: Nguyen Phuong An <AnNP@vn.fujitsu.com> Closes-Bug: #1788759
This commit is contained in:
parent
314e1de7fc
commit
48b82f7c92
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue