# 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 constants from oslo_config import cfg from oslo_utils import uuidutils from neutron.objects.logapi import logging_resource as log_object from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \ as ovs_consts from neutron.services.logapi.common import exceptions as log_exc from neutron.services.logapi.drivers.openvswitch \ import ovs_firewall_log as ovsfw_log from neutron.services.logapi.rpc import agent as agent_rpc from neutron.tests import base from neutron.tests import tools COOKIE_ID = uuidutils.generate_uuid() PORT_ID = uuidutils.generate_uuid() PROJECT_ID = uuidutils.generate_uuid() ACTION = tools.get_random_security_event() LOG_ID = uuidutils.generate_uuid() SG_ID = uuidutils.generate_uuid() REMOTE_SG_ID = uuidutils.generate_uuid() FakeSGLogInfo = [ { 'id': LOG_ID, 'ports_log': [{'port_id': PORT_ID, 'security_group_rules': [ {'ethertype': constants.IPv4, 'protocol': constants.PROTO_NAME_TCP, 'direction': constants.INGRESS_DIRECTION, 'port_range_min': 123, 'port_range_max': 123, 'security_group_id': SG_ID}, {'ethertype': constants.IPv4, 'protocol': constants.PROTO_NAME_UDP, 'direction': constants.EGRESS_DIRECTION, 'security_group_id': SG_ID}, {'ethertype': constants.IPv6, 'protocol': constants.PROTO_NAME_TCP, 'remote_group_id': REMOTE_SG_ID, 'direction': constants.EGRESS_DIRECTION, 'security_group_id': SG_ID} ]}], 'event': 'ALL', 'project_id': PROJECT_ID, } ] def set_log_driver_config(ctrl_rate_limit, ctrl_burst_limit): cfg.CONF.set_override('rate_limit', ctrl_rate_limit, group='network_log') cfg.CONF.set_override('burst_limit', ctrl_burst_limit, group='network_log') class TestCookie(base.BaseTestCase): def setUp(self): super(TestCookie, self).setUp() self.cookie = ovsfw_log.Cookie(COOKIE_ID, PORT_ID, ACTION, PROJECT_ID) self.cookie.log_object_refs = set([LOG_ID]) def test_add_log_object_refs(self): new_log_id = uuidutils.generate_uuid() expected = set([LOG_ID, new_log_id]) self.cookie.add_log_obj_ref(new_log_id) self.assertEqual(expected, self.cookie.log_object_refs) def test_removed_log_object_ref(self): expected = set() self.cookie.remove_log_obj_ref(LOG_ID) self.assertEqual(expected, self.cookie.log_object_refs) def test_is_empty(self): self.cookie.remove_log_obj_ref(LOG_ID) result = self.cookie.is_empty self.assertTrue(result) class FakeOVSPort(object): def __init__(self, name, port, mac): self.port_name = name self.ofport = port self.vif_mac = mac class TestOVSFirewallLoggingDriver(base.BaseTestCase): def setUp(self): super(TestOVSFirewallLoggingDriver, self).setUp() mock_int_br = mock.Mock() mock_int_br.br.dump_flows.return_value = [] self._mock_initialize_bridge = mock.patch.object( ovsfw_log.OVSFirewallLoggingDriver, 'initialize_bridge', return_value=mock_int_br) self.mock_initialize_bridge = self._mock_initialize_bridge.start() self.log_driver = ovsfw_log.OVSFirewallLoggingDriver(mock.Mock()) resource_rpc_mock = mock.patch.object( agent_rpc, 'LoggingApiStub', autospec=True).start() self.log_driver.start_logapp = mock.Mock() self.log_driver.initialize(resource_rpc_mock) self.log_driver.SUPPORTED_LOGGING_TYPES = ['security_group'] self.mock_bridge = self.log_driver.int_br self.mock_bridge.reset_mock() self.fake_ovs_port = FakeOVSPort('port', 1, '00:00:00:00:00:00') self.mock_bridge.br.get_vif_port_by_id.return_value = \ self.fake_ovs_port log_data = { 'context': None, 'name': 'test1', 'id': LOG_ID, 'project_id': PROJECT_ID, 'event': 'ALL', 'resource_type': 'security_group' } self.log_resource = log_object.Log(**log_data) @property def port_ofport(self): return self.mock_bridge.br.get_vif_port_by_id.return_value.ofport @property def port_mac(self): return self.mock_bridge.br.get_vif_port_by_id.return_value.vif_mac def test_initialize_bridge(self): self._mock_initialize_bridge.stop() br = self.log_driver.initialize_bridge(self.mock_bridge) self.assertEqual(self.mock_bridge.deferred.return_value, br) def test_set_controller_rate_limit(self): self._mock_initialize_bridge.stop() set_log_driver_config(100, 25) self.log_driver.initialize_bridge(self.mock_bridge) expected_calls = [mock.call.set_controller_rate_limit(100), mock.call.set_controller_burst_limit(25)] self.mock_bridge.assert_has_calls(expected_calls) def test_generate_cookie(self): cookie_id = self.log_driver.generate_cookie( PORT_ID, ACTION, LOG_ID, PROJECT_ID) cookie = self.log_driver._get_cookie_by_id(cookie_id) self.assertIn(cookie, self.log_driver.cookies_table) def test__get_cookie_by_id_not_found(self): cookie_id = uuidutils.generate_uuid() cookie = ovsfw_log.Cookie(cookie_id=uuidutils.generate_uuid(), port=PORT_ID, action=ACTION, project=PROJECT_ID) self.log_driver.cookies_table = set([cookie]) self.assertRaises(log_exc.CookieNotFound, self.log_driver._get_cookie_by_id, cookie_id) def test_start_log_with_update_or_create_log_event(self): context = mock.Mock() log_data = {'log_resources': [self.log_resource]} self.log_driver.resource_rpc.get_sg_log_info_for_log_resources.\ return_value = FakeSGLogInfo self.log_driver.start_logging(context, **log_data) accept_cookie = self.log_driver._get_cookie(PORT_ID, 'ACCEPT') drop_cookie = self.log_driver._get_cookie(PORT_ID, 'DROP') conj_id = self.log_driver.conj_id_map.get_conj_id( SG_ID, REMOTE_SG_ID, constants.EGRESS_DIRECTION, constants.IPv6) add_rules = [ # log ingress tcp port=123 mock.call( actions='controller', cookie=accept_cookie.id, reg5=self.port_ofport, dl_type="0x{:04x}".format(constants.ETHERTYPE_IP), nw_proto=constants.PROTO_NUM_TCP, priority=77, table=ovs_consts.ACCEPTED_INGRESS_TRAFFIC_TABLE, tcp_dst='0x007b'), # log egress tcp6 mock.call( actions='resubmit(,%d),controller' % ( ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE), cookie=accept_cookie.id, reg5=self.port_ofport, dl_type="0x{:04x}".format(constants.ETHERTYPE_IPV6), priority=70, reg7=conj_id + 1, table=ovs_consts.ACCEPTED_EGRESS_TRAFFIC_TABLE), # log egress udp mock.call( actions='resubmit(,%d),controller' % ( ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE), cookie=accept_cookie.id, reg5=self.port_ofport, dl_type="0x{:04x}".format(constants.ETHERTYPE_IP), nw_proto=constants.PROTO_NUM_UDP, priority=77, table=ovs_consts.ACCEPTED_EGRESS_TRAFFIC_TABLE, ), # log drop mock.call( actions='controller', cookie=drop_cookie.id, priority=53, reg5=self.port_ofport, table=ovs_consts.DROPPED_TRAFFIC_TABLE, ) ] self.mock_bridge.br.add_flow.assert_has_calls( add_rules, any_order=True) def test_stop_log_with_delete_log_event(self): context = mock.Mock() log_data = {'log_resources': [self.log_resource]} self.log_driver.resource_rpc.get_sg_log_info_for_log_resources.\ return_value = FakeSGLogInfo self.log_driver.start_logging(context, **log_data) accept_cookie = self.log_driver._get_cookie(PORT_ID, 'ACCEPT') drop_cookie = self.log_driver._get_cookie(PORT_ID, 'DROP') self.mock_bridge.reset_mock() self.log_driver.stop_logging(context, **log_data) delete_rules = [ # delete drop flow mock.call( table=ovs_consts.DROPPED_TRAFFIC_TABLE, cookie=drop_cookie.id ), # delete accept flows mock.call( table=ovs_consts.ACCEPTED_EGRESS_TRAFFIC_TABLE, cookie=accept_cookie.id ), mock.call( table=ovs_consts.ACCEPTED_INGRESS_TRAFFIC_TABLE, cookie=accept_cookie.id ) ] self.mock_bridge.br.delete_flows.assert_has_calls( delete_rules, any_order=True) def test_start_log_with_add_port_event(self): context = mock.Mock() log_data = {'port_id': PORT_ID} self.log_driver.resource_rpc.get_sg_log_info_for_port.return_value = \ [ { 'id': uuidutils.generate_uuid(), 'ports_log': [{'port_id': PORT_ID, 'security_group_rules': [ {'ethertype': constants.IPv4, 'protocol': constants.PROTO_NAME_TCP, 'direction': constants.INGRESS_DIRECTION, 'port_range_min': 123, 'port_range_max': 123, 'security_group_id': 456}]}], 'event': 'ACCEPT', 'project_id': PROJECT_ID, } ] self.log_driver.start_logging(context, **log_data) accept_cookie = self.log_driver._get_cookie(PORT_ID, 'ACCEPT') add_rules = [ # log ingress tcp port=123 mock.call( actions='controller', cookie=accept_cookie.id, reg5=self.port_ofport, dl_type="0x{:04x}".format(constants.ETHERTYPE_IP), nw_proto=constants.PROTO_NUM_TCP, priority=77, table=ovs_consts.ACCEPTED_INGRESS_TRAFFIC_TABLE, tcp_dst='0x007b') ] self.mock_bridge.br.add_flow.assert_has_calls( add_rules, any_order=True) def test_stop_log_with_delete_port_event(self): context = mock.Mock() log_data = {'port_id': PORT_ID} # add port self.log_driver.resource_rpc.get_sg_log_info_for_port.return_value = \ FakeSGLogInfo self.log_driver.start_logging(context, **log_data) accept_cookie = self.log_driver._get_cookie(PORT_ID, 'ACCEPT') drop_cookie = self.log_driver._get_cookie(PORT_ID, 'DROP') self.mock_bridge.reset_mock() # delete port self.log_driver.stop_logging( context, port_id=PORT_ID) delete_rules = [ # delete accept flows mock.call( table=ovs_consts.ACCEPTED_INGRESS_TRAFFIC_TABLE, cookie=accept_cookie.id ), mock.call( table=ovs_consts.ACCEPTED_EGRESS_TRAFFIC_TABLE, cookie=accept_cookie.id ), # delete drop flow mock.call( table=ovs_consts.DROPPED_TRAFFIC_TABLE, cookie=drop_cookie.id ), ] self.mock_bridge.br.delete_flows.assert_has_calls( delete_rules, any_order=True)