diff --git a/neutron/agent/linux/openvswitch_firewall/iptables.py b/neutron/agent/linux/openvswitch_firewall/iptables.py index 923705d57a4..c2ac8f5f7f8 100644 --- a/neutron/agent/linux/openvswitch_firewall/iptables.py +++ b/neutron/agent/linux/openvswitch_firewall/iptables.py @@ -33,6 +33,12 @@ def get_iptables_driver_instance(): return HybridIptablesHelper() +def is_bridge_cleaned(bridge): + other_config = bridge.db_get_val( + 'Bridge', bridge.br_name, 'other_config') + return other_config.get(Helper.CLEANED_METADATA, '').lower() == 'true' + + class Helper(object): """Helper to avoid loading firewall driver. @@ -83,9 +89,7 @@ class Helper(object): @property def has_not_been_cleaned(self): - other_config = self.int_br.db_get_val( - 'Bridge', self.int_br.br_name, 'other_config') - return other_config.get(self.CLEANED_METADATA, '').lower() != 'true' + return not is_bridge_cleaned(self.int_br) def mark_as_cleaned(self): # TODO(jlibosva): Make it a single transaction diff --git a/neutron/tests/common/config_fixtures.py b/neutron/tests/common/config_fixtures.py index 3928e20e791..bfa09e4fba3 100644 --- a/neutron/tests/common/config_fixtures.py +++ b/neutron/tests/common/config_fixtures.py @@ -52,6 +52,9 @@ class ConfigFileFixture(fixtures.Fixture): self.temp_dir = temp_dir def _setUp(self): + self.write_config_to_configfile() + + def write_config_to_configfile(self): config_parser = self.dict_to_config_parser(self.config) # Need to randomly generate a unique folder to put the file in self.filename = os.path.join(self.temp_dir, self.base_filename) diff --git a/neutron/tests/fullstack/resources/machine.py b/neutron/tests/fullstack/resources/machine.py index 688050152b0..4a4f254657c 100644 --- a/neutron/tests/fullstack/resources/machine.py +++ b/neutron/tests/fullstack/resources/machine.py @@ -38,9 +38,9 @@ class FakeFullstackMachinesList(list): def ping_all(self): # Generate an iterable of all unique pairs. For example: - # itertools.combinations(range(3), 2) results in: - # ((0, 1), (0, 2), (1, 2)) - for vm_1, vm_2 in itertools.combinations(self, 2): + # itertools.permutations(range(3), 2) results in: + # ((0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)) + for vm_1, vm_2 in itertools.permutations(self, 2): vm_1.block_until_ping(vm_2.ip) diff --git a/neutron/tests/fullstack/test_firewall.py b/neutron/tests/fullstack/test_firewall.py new file mode 100644 index 00000000000..05d5479edfd --- /dev/null +++ b/neutron/tests/fullstack/test_firewall.py @@ -0,0 +1,152 @@ +# Copyright 2018 Red Hat, Inc. +# +# 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 functools + +from neutron_lib import constants +from oslo_log import log as logging +from oslo_utils import uuidutils + +from neutron.agent.common import ovs_lib +from neutron.agent.linux import iptables_firewall +from neutron.agent.linux import iptables_manager +from neutron.agent.linux.openvswitch_firewall import iptables as ovs_iptables +from neutron.common import utils +from neutron.tests.common import machine_fixtures +from neutron.tests.fullstack import base +from neutron.tests.fullstack.resources import environment +from neutron.tests.fullstack.resources import machine + +LOG = logging.getLogger(__name__) + + +class IptablesNotConfiguredException(Exception): + pass + + +class VmsUnreachableException(Exception): + pass + + +class FirewallMigrationTestCase(base.BaseFullStackTestCase): + def setUp(self): + host_descriptions = [ + environment.HostDescription( + l3_agent=False, + of_interface='native', + l2_agent_type=constants.AGENT_TYPE_OVS, + firewall_driver='iptables_hybrid', + dhcp_agent=False, + )] + env = environment.Environment( + environment.EnvironmentDescription(), + host_descriptions) + super(FirewallMigrationTestCase, self).setUp(env) + # fullstack doesn't separate nodes running ovs agent so iptables rules + # are implemented in root namespace + self.iptables_manager = iptables_manager.IptablesManager() + + def _prepare_resources(self): + self.tenant_uuid = uuidutils.generate_uuid() + network = self.safe_client.create_network(self.tenant_uuid) + self.safe_client.create_subnet( + self.tenant_uuid, network['id'], '20.0.0.0/24', enable_dhcp=False) + vms = machine.FakeFullstackMachinesList( + self.useFixture( + machine.FakeFullstackMachine( + self.environment.hosts[0], + network['id'], + self.tenant_uuid, + self.safe_client, + use_dhcp=False)) + for i in range(2)) + vms.block_until_all_boot() + + for vm in vms: + self._add_icmp_security_group_rule(vm) + + return vms + + def _add_icmp_security_group_rule(self, vm): + sg_id = self.safe_client.create_security_group(self.tenant_uuid)['id'] + self.safe_client.create_security_group_rule( + self.tenant_uuid, sg_id, + direction=constants.INGRESS_DIRECTION, + ethertype=constants.IPv4, + protocol=constants.PROTO_NAME_ICMP) + self.safe_client.client.update_port( + vm.neutron_port['id'], + body={'port': {'security_groups': [sg_id]}}) + self.addCleanup( + self.safe_client.client.update_port, + vm.neutron_port['id'], + body={'port': {'security_groups': []}}) + + def _validate_iptables_rules(self, vms): + """Check if rules from iptables firewall are configured. + + Raises IptablesNotConfiguredException exception if no rules are found. + """ + for vm in vms: + vm_tap_device = iptables_firewall.get_hybrid_port_name( + vm.neutron_port['id']) + filter_rules = self.iptables_manager.get_rules_for_table('filter') + if not any(vm_tap_device in line for line in filter_rules): + raise IptablesNotConfiguredException( + "There are no iptables rules configured for interface %s" % + vm_tap_device) + + def _switch_firewall(self, firewall_driver): + """Switch firewall_driver to given driver and restart the agent.""" + l2_agent = self.environment.hosts[0].l2_agent + l2_agent_config = l2_agent.agent_cfg_fixture.config + l2_agent_config['securitygroup']['firewall_driver'] = firewall_driver + l2_agent.agent_cfg_fixture.write_config_to_configfile() + l2_agent.restart() + + int_bridge = ovs_lib.OVSBridge( + l2_agent_config['ovs']['integration_bridge']) + predicate = functools.partial( + ovs_iptables.is_bridge_cleaned, int_bridge) + utils.wait_until_true( + predicate, + exception=RuntimeError( + "Bridge %s hasn't been marked as clean." % int_bridge.br_name)) + + def test_migration(self): + vms = self._prepare_resources() + # Make sure ICMP packets can get through with iptables firewall + vms.ping_all() + self._validate_iptables_rules(vms) + self._switch_firewall('openvswitch') + # Make sure security groups still work after migration + vms.ping_all() + + self.assertRaises( + IptablesNotConfiguredException, self._validate_iptables_rules, vms) + + # Remove security groups so traffic cannot get through + for vm in vms: + self.safe_client.client.update_port( + vm.neutron_port['id'], + body={'port': {'security_groups': []}}) + + # TODO(jlibosva): Test all permutations and don't fail on the first one + self.assertRaises(machine_fixtures.FakeMachineException, vms.ping_all) + + # Add back some security groups allowing ICMP and test traffic can now + # get through + for vm in vms: + self._add_icmp_security_group_rule(vm) + vms.ping_all()