From: Assaf Muller Date: Fri, 12 Jun 2015 19:07:17 +0000 (-0400) Subject: Add a fullstack fake VM, basic connectivity test X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=53fe9ddd6cc6943774fae609586a0adf9b2989f2;p=openstack-build%2Fneutron-build.git Add a fullstack fake VM, basic connectivity test * Full stack tests' fake VMs are represented via a namespace, MAC, IP address and default gateway. They're plugged to an OVS bridge via an OVS internal port. As opposed to the current fake machine class used in functional testing, this new fake machine also creates a Neutron port via the API and sets the IP and MAC according to it. It also sets additional attributes on the OVS port to allow the OVS agent to do its magic. * The functional fake machine and the full stack fake machine should continue to share commonalities. * The fullstack fake machine currently takes the IP address from the port and statically assigns it to the namespace device. Later when I'll add support for the DHCP agent in full stack testing this assignment will look for the dhcp attribute of the subnet and either assign the IP address via 'ip' or call a dhcp client. * Added a basic L2 connectivity test between two such machines on the same Neutron network. * OVSPortFixture now uses OVSInterfaceDriver to plug the port instead of replicate a lot of the code. I had to make a small change to _setup_arp_spoof_for_port since all OVS ports are now created with their external-ids set. Change-Id: Ib985b7e742f58f1a6eb6fc598df3cbac31046951 --- diff --git a/doc/source/devref/fullstack_testing.rst b/doc/source/devref/fullstack_testing.rst index 68cb6a518..f1ff581dc 100644 --- a/doc/source/devref/fullstack_testing.rst +++ b/doc/source/devref/fullstack_testing.rst @@ -55,10 +55,10 @@ through the API and then assert that a namespace was created for it. Full stack tests run in the Neutron tree with Neutron resources alone. You may use the Neutron API (The Neutron server is set to NOAUTH so that Keystone -is out of the picture). instances may be simulated with a helper class that -contains a container-like object in its own namespace and IP address. It has -helper methods to send different kinds of traffic. The "instance" may be -connected to br-int or br-ex, to simulate internal or external traffic. +is out of the picture). VMs may be simulated with a container-like class: +neutron.tests.fullstack.resources.machine.FakeFullstackMachine. +An example of its usage may be found at: +neutron/tests/fullstack/test_connectivity.py. Full stack testing can simulate multi node testing by starting an agent multiple times. Specifically, each node would have its own copy of the diff --git a/neutron/tests/common/machine_fixtures.py b/neutron/tests/common/machine_fixtures.py index 65a1a433c..ebad9e120 100644 --- a/neutron/tests/common/machine_fixtures.py +++ b/neutron/tests/common/machine_fixtures.py @@ -19,7 +19,39 @@ from neutron.agent.linux import ip_lib from neutron.tests.common import net_helpers -class FakeMachine(fixtures.Fixture): +class FakeMachineBase(fixtures.Fixture): + def __init__(self): + self.port = None + + def _setUp(self): + ns_fixture = self.useFixture( + net_helpers.NamespaceFixture()) + self.namespace = ns_fixture.name + + def execute(self, *args, **kwargs): + ns_ip_wrapper = ip_lib.IPWrapper(self.namespace) + return ns_ip_wrapper.netns.execute(*args, **kwargs) + + def assert_ping(self, dst_ip): + net_helpers.assert_ping(self.namespace, dst_ip) + + def assert_no_ping(self, dst_ip): + net_helpers.assert_no_ping(self.namespace, dst_ip) + + @property + def ip(self): + raise NotImplementedError() + + @property + def ip_cidr(self): + raise NotImplementedError() + + @property + def mac_address(self): + return self.port.link.address + + +class FakeMachine(FakeMachineBase): """Create a fake machine. :ivar bridge: bridge on which the fake machine is bound @@ -43,9 +75,7 @@ class FakeMachine(fixtures.Fixture): self.gateway_ip = gateway_ip def _setUp(self): - ns_fixture = self.useFixture( - net_helpers.NamespaceFixture()) - self.namespace = ns_fixture.name + super(FakeMachine, self)._setUp() self.port = self.useFixture( net_helpers.PortFixture.get(self.bridge, self.namespace)).port @@ -68,26 +98,12 @@ class FakeMachine(fixtures.Fixture): self.port.addr.delete(self._ip_cidr) self._ip_cidr = ip_cidr - @property - def mac_address(self): - return self.port.link.address - - @mac_address.setter + @FakeMachineBase.mac_address.setter def mac_address(self, mac_address): self.port.link.set_down() self.port.link.set_address(mac_address) self.port.link.set_up() - def execute(self, *args, **kwargs): - ns_ip_wrapper = ip_lib.IPWrapper(self.namespace) - return ns_ip_wrapper.netns.execute(*args, **kwargs) - - def assert_ping(self, dst_ip): - net_helpers.assert_ping(self.namespace, dst_ip) - - def assert_no_ping(self, dst_ip): - net_helpers.assert_no_ping(self.namespace, dst_ip) - class PeerMachines(fixtures.Fixture): """Create 'amount' peered machines on an ip_cidr. diff --git a/neutron/tests/common/net_helpers.py b/neutron/tests/common/net_helpers.py index d4bfe3736..6cb83772a 100644 --- a/neutron/tests/common/net_helpers.py +++ b/neutron/tests/common/net_helpers.py @@ -25,15 +25,18 @@ import subprocess import fixtures import netaddr +from oslo_config import cfg from oslo_utils import uuidutils import six from neutron.agent.common import config from neutron.agent.common import ovs_lib from neutron.agent.linux import bridge_lib +from neutron.agent.linux import interface from neutron.agent.linux import ip_lib from neutron.agent.linux import utils from neutron.common import constants as n_const +from neutron.db import db_base_plugin_common from neutron.tests import base as tests_base from neutron.tests.common import base as common_base from neutron.tests import tools @@ -420,10 +423,13 @@ class PortFixture(fixtures.Fixture): :ivar bridge: port bridge """ - def __init__(self, bridge=None, namespace=None): + def __init__(self, bridge=None, namespace=None, mac=None, port_id=None): super(PortFixture, self).__init__() self.bridge = bridge self.namespace = namespace + self.mac = ( + mac or db_base_plugin_common.DbBasePluginCommon._generate_mac()) + self.port_id = port_id or uuidutils.generate_uuid() @abc.abstractmethod def _create_bridge_fixture(self): @@ -436,10 +442,10 @@ class PortFixture(fixtures.Fixture): self.bridge = self.useFixture(self._create_bridge_fixture()).bridge @classmethod - def get(cls, bridge, namespace=None): + def get(cls, bridge, namespace=None, mac=None, port_id=None): """Deduce PortFixture class from bridge type and instantiate it.""" if isinstance(bridge, ovs_lib.OVSBridge): - return OVSPortFixture(bridge, namespace) + return OVSPortFixture(bridge, namespace, mac, port_id) if isinstance(bridge, bridge_lib.BridgeDevice): return LinuxBridgePortFixture(bridge, namespace) if isinstance(bridge, VethBridge): @@ -468,30 +474,26 @@ class OVSBridgeFixture(fixtures.Fixture): class OVSPortFixture(PortFixture): - def __init__(self, bridge=None, namespace=None, attrs=None): - super(OVSPortFixture, self).__init__(bridge, namespace) - if attrs is None: - attrs = [] - self.attrs = attrs - def _create_bridge_fixture(self): return OVSBridgeFixture() def _setUp(self): super(OVSPortFixture, self)._setUp() - port_name = common_base.create_resource(PORT_PREFIX, self.create_port) + interface_config = cfg.ConfigOpts() + interface_config.register_opts(interface.OPTS) + ovs_interface = interface.OVSInterfaceDriver(interface_config) + + port_name = tests_base.get_rand_device_name(PORT_PREFIX) + ovs_interface.plug_new( + None, + self.port_id, + port_name, + self.mac, + bridge=self.bridge.br_name, + namespace=self.namespace) self.addCleanup(self.bridge.delete_port, port_name) - self.port = ip_lib.IPDevice(port_name) - - ns_ip_wrapper = ip_lib.IPWrapper(self.namespace) - ns_ip_wrapper.add_device_to_namespace(self.port) - self.port.link.set_up() - - def create_port(self, name): - self.attrs.insert(0, ('type', 'internal')) - self.bridge.add_port(name, *self.attrs) - return name + self.port = ip_lib.IPDevice(port_name, self.namespace) class LinuxBridgeFixture(fixtures.Fixture): diff --git a/neutron/tests/fullstack/resources/client.py b/neutron/tests/fullstack/resources/client.py index 42350793c..4ae0ff4c4 100644 --- a/neutron/tests/fullstack/resources/client.py +++ b/neutron/tests/fullstack/resources/client.py @@ -65,6 +65,13 @@ class ClientFixture(fixtures.Fixture): return self._create_resource(resource_type, spec) + def create_port(self, tenant_id, network_id, hostname): + return self._create_resource( + 'port', + {'network_id': network_id, + 'tenant_id': tenant_id, + 'binding:host_id': hostname}) + def add_router_interface(self, router_id, subnet_id): body = {'subnet_id': subnet_id} self.client.add_interface_router(router=router_id, body=body) diff --git a/neutron/tests/fullstack/resources/environment.py b/neutron/tests/fullstack/resources/environment.py index 77f868e7f..ef68e44ae 100644 --- a/neutron/tests/fullstack/resources/environment.py +++ b/neutron/tests/fullstack/resources/environment.py @@ -98,6 +98,10 @@ class Host(fixtures.Fixture): net_helpers.create_patch_ports( self.central_external_bridge, host_external_bridge) + @property + def hostname(self): + return self.neutron_config.config.DEFAULT.host + @property def l3_agent(self): return self.agents['l3'] diff --git a/neutron/tests/fullstack/resources/machine.py b/neutron/tests/fullstack/resources/machine.py new file mode 100644 index 000000000..355332220 --- /dev/null +++ b/neutron/tests/fullstack/resources/machine.py @@ -0,0 +1,71 @@ +# Copyright 2015 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 netaddr + +from neutron.agent.linux import utils +from neutron.tests.common import machine_fixtures +from neutron.tests.common import net_helpers + + +class FakeFullstackMachine(machine_fixtures.FakeMachineBase): + def __init__(self, host, network_id, tenant_id, safe_client): + super(FakeFullstackMachine, self).__init__() + self.bridge = host.ovs_agent.br_int + self.host_binding = host.hostname + self.tenant_id = tenant_id + self.network_id = network_id + self.safe_client = safe_client + + def _setUp(self): + super(FakeFullstackMachine, self)._setUp() + + self.neutron_port = self.safe_client.create_port( + network_id=self.network_id, + tenant_id=self.tenant_id, + hostname=self.host_binding) + self.neutron_port_id = self.neutron_port['id'] + mac_address = self.neutron_port['mac_address'] + + self.port = self.useFixture( + net_helpers.PortFixture.get( + self.bridge, self.namespace, mac_address, + self.neutron_port_id)).port + + self._ip = self.neutron_port['fixed_ips'][0]['ip_address'] + subnet_id = self.neutron_port['fixed_ips'][0]['subnet_id'] + subnet = self.safe_client.client.show_subnet(subnet_id) + prefixlen = netaddr.IPNetwork(subnet['subnet']['cidr']).prefixlen + self._ip_cidr = '%s/%s' % (self._ip, prefixlen) + + # TODO(amuller): Support DHCP + self.port.addr.add(self.ip_cidr) + + self.gateway_ip = subnet['subnet']['gateway_ip'] + if self.gateway_ip: + net_helpers.set_namespace_gateway(self.port, self.gateway_ip) + + @property + def ip(self): + return self._ip + + @property + def ip_cidr(self): + return self._ip_cidr + + def block_until_boot(self): + utils.wait_until_true( + lambda: (self.safe_client.client.show_port(self.neutron_port_id) + ['port']['status'] == 'ACTIVE'), + sleep=3) diff --git a/neutron/tests/fullstack/test_connectivity.py b/neutron/tests/fullstack/test_connectivity.py new file mode 100644 index 000000000..34c6c3f2a --- /dev/null +++ b/neutron/tests/fullstack/test_connectivity.py @@ -0,0 +1,49 @@ +# Copyright 2015 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. + +from oslo_utils import uuidutils + +from neutron.tests.fullstack import base +from neutron.tests.fullstack.resources import environment +from neutron.tests.fullstack.resources import machine + + +class TestConnectivitySameNetwork(base.BaseFullStackTestCase): + + def __init__(self, *args, **kwargs): + host_descriptions = [ + environment.HostDescription(l3_agent=False) for _ in range(2)] + env = environment.Environment(host_descriptions) + super(TestConnectivitySameNetwork, self).__init__(env, *args, **kwargs) + + def test_connectivity(self): + tenant_uuid = uuidutils.generate_uuid() + + network = self.safe_client.create_network(tenant_uuid) + self.safe_client.create_subnet( + tenant_uuid, network['id'], '20.0.0.0/24') + + vms = [ + self.useFixture( + machine.FakeFullstackMachine( + self.environment.hosts[i], + network['id'], + tenant_uuid, + self.safe_client)) + for i in range(2)] + + for vm in vms: + vm.block_until_boot() + + vms[0].assert_ping(vms[1].ip) diff --git a/neutron/tests/fullstack/test_l3_agent.py b/neutron/tests/fullstack/test_l3_agent.py index 9f8036c3b..046a40606 100644 --- a/neutron/tests/fullstack/test_l3_agent.py +++ b/neutron/tests/fullstack/test_l3_agent.py @@ -26,10 +26,9 @@ from neutron.tests.fullstack.resources import environment class TestLegacyL3Agent(base.BaseFullStackTestCase): def __init__(self, *args, **kwargs): - super(TestLegacyL3Agent, self).__init__( - environment.Environment( - [environment.HostDescription(l3_agent=True)]), - *args, **kwargs) + host_descriptions = [environment.HostDescription(l3_agent=True)] + env = environment.Environment(host_descriptions) + super(TestLegacyL3Agent, self).__init__(env, *args, **kwargs) def _get_namespace(self, router_id): return namespaces.build_ns_name(l3_agent.NS_PREFIX, router_id) diff --git a/neutron/tests/functional/agent/linux/test_ovsdb_monitor.py b/neutron/tests/functional/agent/linux/test_ovsdb_monitor.py index fc49b1ae4..e88329df4 100644 --- a/neutron/tests/functional/agent/linux/test_ovsdb_monitor.py +++ b/neutron/tests/functional/agent/linux/test_ovsdb_monitor.py @@ -135,12 +135,9 @@ class TestSimpleInterfaceMonitor(BaseMonitorTest): devices = self.monitor.get_events() self.assertTrue(devices.get('added'), 'Initial call should always be true') - p_attrs = [('external_ids', {'iface-status': 'active'})] br = self.useFixture(net_helpers.OVSBridgeFixture()) - p1 = self.useFixture(net_helpers.OVSPortFixture( - br.bridge, None, p_attrs)) - p2 = self.useFixture(net_helpers.OVSPortFixture( - br.bridge, None, p_attrs)) + p1 = self.useFixture(net_helpers.OVSPortFixture(br.bridge)) + p2 = self.useFixture(net_helpers.OVSPortFixture(br.bridge)) added_devices = [p1.port.name, p2.port.name] utils.wait_until_true( lambda: self._expected_devices_events(added_devices, 'added')) diff --git a/neutron/tests/functional/agent/test_ovs_flows.py b/neutron/tests/functional/agent/test_ovs_flows.py index 5d73ea1a5..d9856a8f4 100644 --- a/neutron/tests/functional/agent/test_ovs_flows.py +++ b/neutron/tests/functional/agent/test_ovs_flows.py @@ -179,19 +179,15 @@ class _ARPSpoofTestCase(object): net_helpers.assert_ping(self.src_namespace, self.dst_addr, count=2) def _setup_arp_spoof_for_port(self, port, addrs, psec=True): - of_port_map = self.br.get_vif_port_to_ofport_map() - - class VifPort(object): - ofport = of_port_map[port] - port_name = port - + vif = next( + vif for vif in self.br.get_vif_ports() if vif.port_name == port) ip_addr = addrs.pop() details = {'port_security_enabled': psec, 'fixed_ips': [{'ip_address': ip_addr}], 'allowed_address_pairs': [ dict(ip_address=ip) for ip in addrs]} ovsagt.OVSNeutronAgent.setup_arp_spoofing_protection( - self.br_int, VifPort(), details) + self.br_int, vif, details) class ARPSpoofOFCtlTestCase(_ARPSpoofTestCase, _OVSAgentOFCtlTestBase):