fullstack: Migration from iptables_hybrid to openvswitch

Add test validating migration from iptables_hybrid firewall driver to
openvswitch. The test creates simple environment with a single node then
spawns two vms, each has its own security group. Then firewall is
switched and OVS agent is restarted. Connectivity is then validated
again, security groups are removed, tested no traffic is allowed and
then security groups are added back to make sure new firewall driver
works with updates.

Change-Id: Idef80c76c1b82be9f1007f17ea661c9ccdc2b1ae
This commit is contained in:
Jakub Libosvar 2018-04-20 15:29:02 +00:00
parent 98e5f01714
commit 75d28cbc73
4 changed files with 165 additions and 6 deletions

View File

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

View File

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

View File

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

View File

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