]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add functional test for l3-agent metadata proxy
authorCedric Brandily <zzelle@gmail.com>
Fri, 21 Nov 2014 22:11:25 +0000 (23:11 +0100)
committerCedric Brandily <zzelle@gmail.com>
Mon, 15 Dec 2014 15:53:59 +0000 (15:53 +0000)
This change add a functional test for l3-agent metadata proxy to
verify:

 * iptables redirection from 169.254.169.254:80
 * headers added by the neutron-ns-metadata-proxy
 * proxy to the metadata_socket by the neutron-ns-metadata-proxy

Change-Id: I16f1269644a56d932426daa3a89dd5a8f747b314

neutron/tests/functional/agent/linux/base.py
neutron/tests/functional/agent/test_l3_agent.py
neutron/tests/functional/contrib/filters.template

index 4b06bdf7b37a9745db85f0496d3141d6409dd0d9..b900d56fd65a8d41bc12e0242c0e1a308e208601 100644 (file)
@@ -14,6 +14,8 @@
 
 import random
 
+import netaddr
+
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import ovs_lib
 from neutron.agent.linux import utils
@@ -24,6 +26,7 @@ from neutron.tests.functional import base as functional_base
 
 
 BR_PREFIX = 'test-br'
+PORT_PREFIX = 'test-port'
 ICMP_BLOCK_RULE = '-p icmp -j DROP'
 VETH_PREFIX = 'tst-vth'
 
@@ -49,6 +52,14 @@ class BaseLinuxTestCase(functional_base.BaseSudoTestCase):
                 self.skipTest(skip_msg)
             raise
 
+    def _create_namespace(self):
+        ip_cmd = ip_lib.IPWrapper(self.root_helper)
+        name = "func-%s" % uuidutils.generate_uuid()
+        namespace = ip_cmd.ensure_namespace(name)
+        self.addCleanup(namespace.netns.delete, namespace.namespace)
+
+        return namespace
+
     def create_resource(self, name_prefix, creation_func, *args, **kwargs):
         """Create a new resource that does not already exist.
 
@@ -74,11 +85,27 @@ class BaseLinuxTestCase(functional_base.BaseSudoTestCase):
         veth1, veth2 = ip_wrapper.add_veth(name1, name2)
         return veth1, veth2
 
+    def set_namespace_gateway(self, port_dev, gateway_ip):
+        """Set gateway for the namespace associated to the port."""
+        if not port_dev.namespace:
+            self.fail('tests should not change test machine gateway')
+        port_dev.route.add_gateway(gateway_ip)
+
+    def shift_ip_cidr(self, ip_cidr, offset=1):
+        """Shift ip_cidr offset times.
+
+        example: shift_ip_cidr("1.2.3.4/24", 2) ==> "1.2.3.6/24"
+        """
+        net = netaddr.IPNetwork(ip_cidr)
+        net.value += offset
+        return str(net)
+
 
 class BaseOVSLinuxTestCase(BaseLinuxTestCase):
     def setUp(self):
         super(BaseOVSLinuxTestCase, self).setUp()
         self.ovs = ovs_lib.BaseOVS(self.root_helper)
+        self.ip = ip_lib.IPWrapper(self.root_helper)
 
     def create_ovs_bridge(self, br_prefix=BR_PREFIX):
         br = self.create_resource(br_prefix, self.ovs.add_bridge)
@@ -88,6 +115,29 @@ class BaseOVSLinuxTestCase(BaseLinuxTestCase):
     def get_ovs_bridge(self, br_name):
         return ovs_lib.OVSBridge(br_name, self.root_helper)
 
+    def create_ovs_port_in_ns(self, br, ns):
+        def create_port(name):
+            br.add_port(name)
+            self.addCleanup(br.delete_port, name)
+            br.set_db_attribute('Interface', name, 'type', 'internal')
+            return name
+        port_name = self.create_resource(PORT_PREFIX, create_port)
+        port_dev = self.ip.device(port_name)
+        ns.add_device_to_namespace(port_dev)
+        port_dev.link.set_up()
+        return port_dev
+
+    def bind_namespace_to_cidr(self, namespace, br, ip_cidr):
+        """Bind namespace to cidr (on layer2 and 3).
+
+        Bind the namespace to a subnet by creating an ovs port in the namespace
+        and configuring port ip.
+        """
+        net = netaddr.IPNetwork(ip_cidr)
+        port_dev = self.create_ovs_port_in_ns(br, namespace)
+        port_dev.addr.add(net.version, str(net), net.broadcast)
+        return port_dev
+
 
 class BaseIPVethTestCase(BaseLinuxTestCase):
     SRC_ADDRESS = '192.168.0.1'
@@ -104,14 +154,6 @@ class BaseIPVethTestCase(BaseLinuxTestCase):
         device.addr.add(ip_version=ip_version, cidr=cidr, broadcast=broadcast)
         device.link.set_up()
 
-    def _create_namespace(self):
-        ip_cmd = ip_lib.IPWrapper(self.root_helper)
-        name = "func-%s" % uuidutils.generate_uuid()
-        namespace = ip_cmd.ensure_namespace(name)
-        self.addCleanup(namespace.netns.delete, namespace.namespace)
-
-        return namespace
-
     def prepare_veth_pairs(self, src_addr=None,
                            dst_addr=None,
                            broadcast_addr=None,
index 73ee94aea9b0e02ba29ea6fdf9817f5d54686c69..0eaab4bbdb2453f47ff16eb35586e745a7efe010 100644 (file)
@@ -19,11 +19,16 @@ import functools
 import fixtures
 import mock
 from oslo.config import cfg
+import webob
+import webob.dec
+import webob.exc
 
 from neutron.agent.common import config as agent_config
 from neutron.agent.l3 import agent as l3_agent
+from neutron.agent.linux import dhcp
 from neutron.agent.linux import external_process
 from neutron.agent.linux import ip_lib
+from neutron.agent.metadata import agent as metadata_agent
 from neutron.common import config as common_config
 from neutron.common import constants as l3_constants
 from neutron.openstack.common import log as logging
@@ -36,6 +41,8 @@ from neutron.tests.unit import test_l3_agent
 LOG = logging.getLogger(__name__)
 _uuid = uuidutils.generate_uuid
 
+METADATA_REQUEST_TIMEOUT = 60
+
 
 class L3AgentTestFramework(base.BaseOVSLinuxTestCase):
     def setUp(self):
@@ -363,3 +370,64 @@ class L3HATestFramework(L3AgentTestFramework):
 
         helpers.wait_until_true(lambda: router2.ha_state == 'master')
         helpers.wait_until_true(lambda: router1.ha_state == 'fault')
+
+
+class MetadataFakeProxyHandler(object):
+
+    def __init__(self, status):
+        self.status = status
+
+    @webob.dec.wsgify()
+    def __call__(self, req):
+        return webob.Response(status=self.status)
+
+
+class MetadataL3AgentTestCase(L3AgentTestFramework):
+
+    def _create_metadata_fake_server(self, status):
+        server = metadata_agent.UnixDomainWSGIServer('metadata-fake-server')
+        self.addCleanup(server.stop)
+        server.start(MetadataFakeProxyHandler(status),
+                     self.agent.conf.metadata_proxy_socket,
+                     workers=0, backlog=4096)
+
+    def test_access_to_metadata_proxy(self):
+        """Test access to the l3-agent metadata proxy.
+
+        The test creates:
+         * A l3-agent metadata service:
+           * A router (which creates a metadata proxy in the router namespace),
+           * A fake metadata server
+         * A "client" namespace (simulating a vm) with a port on router
+           internal subnet.
+
+        The test queries from the "client" namespace the metadata proxy on
+        http://169.254.169.254 and asserts that the metadata proxy added
+        the X-Forwarded-For and X-Neutron-Router-Id headers to the request
+        and forwarded the http request to the fake metadata server and the
+        response to the "client" namespace.
+        """
+        router_info = self.generate_router_info(enable_ha=False)
+        router = self.manage_router(self.agent, router_info)
+        self._create_metadata_fake_server(webob.exc.HTTPOk.code)
+
+        # Create and configure client namespace
+        client_ns = self._create_namespace()
+        router_ip_cidr = router.internal_ports[0]['ip_cidr']
+        ip_cidr = self.shift_ip_cidr(router_ip_cidr)
+        br_int = self.get_ovs_bridge(self.agent.conf.ovs_integration_bridge)
+        port = self.bind_namespace_to_cidr(client_ns, br_int, ip_cidr)
+        self.set_namespace_gateway(port, router_ip_cidr.partition('/')[0])
+
+        # Query metadata proxy
+        url = 'http://%(host)s:%(port)s' % {'host': dhcp.METADATA_DEFAULT_IP,
+                                            'port': dhcp.METADATA_PORT}
+        cmd = 'curl', '--max-time', METADATA_REQUEST_TIMEOUT, '-D-', url
+        try:
+            raw_headers = client_ns.netns.execute(cmd)
+        except RuntimeError:
+            self.fail('metadata proxy unreachable on %s before timeout' % url)
+
+        # Check status code
+        firstline = raw_headers.splitlines()[0]
+        self.assertIn(str(webob.exc.HTTPOk.code), firstline.split())
index a57e71cf0b36757fe86ea86fb4d385f7db03025b..cfd37a24a935bee9fb55c282b3439df5dceb5d93 100644 (file)
@@ -13,3 +13,6 @@ kill_tox_python: KillFilter, root, $BASE_PATH/bin/python, -9
 
 # enable ping from namespace
 ping_filter: CommandFilter, ping, root
+
+# enable curl from namespace
+curl_filter: CommandFilter, curl, root