]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add a fullstack fake VM, basic connectivity test
authorAssaf Muller <amuller@redhat.com>
Fri, 12 Jun 2015 19:07:17 +0000 (15:07 -0400)
committerJohn Schwarz <jschwarz@redhat.com>
Wed, 19 Aug 2015 17:09:55 +0000 (20:09 +0300)
* 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

doc/source/devref/fullstack_testing.rst
neutron/tests/common/machine_fixtures.py
neutron/tests/common/net_helpers.py
neutron/tests/fullstack/resources/client.py
neutron/tests/fullstack/resources/environment.py
neutron/tests/fullstack/resources/machine.py [new file with mode: 0644]
neutron/tests/fullstack/test_connectivity.py [new file with mode: 0644]
neutron/tests/fullstack/test_l3_agent.py
neutron/tests/functional/agent/linux/test_ovsdb_monitor.py
neutron/tests/functional/agent/test_ovs_flows.py

index 68cb6a518b5056c2dc14dfb637488a1918f0cb65..f1ff581dc35e12099f78c9d7b60b625e8490b320 100644 (file)
@@ -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
index 65a1a433cd11646fbda41076616767ef2245823e..ebad9e120d19d346c019e1fdb2748cc0588e9f8a 100644 (file)
@@ -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.
index d4bfe3736b4f15bf3e0f29698f04c995c829cf24..6cb83772abe5f547ec21ab0f6e55f638198833d7 100644 (file)
@@ -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):
index 42350793c5944d059df90e744be1ff0444b88131..4ae0ff4c4a823167c2f053d85447ab7841441984 100644 (file)
@@ -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)
index 77f868e7f3ea85ffd9dab335259edfd19dadb67d..ef68e44ae4b87870e2d46e01b4b4997964633927 100644 (file)
@@ -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 (file)
index 0000000..3553322
--- /dev/null
@@ -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 (file)
index 0000000..34c6c3f
--- /dev/null
@@ -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)
index 9f8036c3bfbcc6f0d692878c2504599cec29d3f9..046a4060608c6a986a5c4d6ae13c6ecdcebe1706 100644 (file)
@@ -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)
index fc49b1ae4d199c8322a9367f45d30caf342a700d..e88329df43c66ae7ee8d6e4b78071d2e57181a23 100644 (file)
@@ -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'))
index 5d73ea1a5f309f20a8237dfc78147468a1a248e2..d9856a8f4bf4a30cbff8853004d9e19e70e34443 100644 (file)
@@ -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):