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:
Kim Bao Long 2018-09-07 12:30:25 +07:00 committed by Yushiro FURUKAWA
parent 314e1de7fc
commit 48b82f7c92
5 changed files with 296 additions and 8 deletions

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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