Merge "fullstack: Migration from iptables_hybrid to openvswitch"
This commit is contained in:
commit
d49a2beeee
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue