]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Revert "Add metadata proxy L3 agent driver"
authorHenry Gessau <gessau@cisco.com>
Sat, 20 Dec 2014 05:08:43 +0000 (05:08 +0000)
committerHenry Gessau <gessau@cisco.com>
Sat, 20 Dec 2014 05:08:43 +0000 (05:08 +0000)
This reverts commit 6b38f29fdbd077434f1f7139466479e81bf4882d.

Because it broke the functional job.

Change-Id: Ibcc577ade490663820f0a4f599afc6127a6e52e6

neutron/agent/l3/agent.py
neutron/agent/l3/ha.py
neutron/agent/metadata/driver.py [deleted file]
neutron/tests/unit/agent/metadata/__init__.py [deleted file]
neutron/tests/unit/agent/metadata/test_driver.py [deleted file]
neutron/tests/unit/test_l3_agent.py

index fbd06801447e1068843b107bf3e9370fc74a7344..279c332e2edcd527230c8cef37a24d5d8aa45c3e 100644 (file)
@@ -37,7 +37,6 @@ from neutron.agent.linux import interface
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import iptables_manager
 from neutron.agent.linux import ra
-from neutron.agent.metadata import driver as metadata_driver
 from neutron.agent import rpc as agent_rpc
 from neutron.common import config as common_config
 from neutron.common import constants as l3_constants
@@ -278,10 +277,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         self.target_ex_net_id = None
         self.use_ipv6 = ipv6_utils.is_enabled()
 
-        if self.conf.enable_metadata_proxy:
-            driver = metadata_driver.MetadataDriver.instance(self)
-            self.event_observers.add(driver)
-
     def _fip_ns_subscribe(self, router_id):
         is_first = (len(self.fip_ns_subscribers) == 0)
         self.fip_ns_subscribers.add(router_id)
@@ -401,6 +396,8 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
     def _destroy_router_namespace(self, ns):
         router_id = self.get_router_id(ns)
         ra.disable_ipv6_ra(router_id, ns, self.root_helper)
+        if self.conf.enable_metadata_proxy:
+            self._destroy_metadata_proxy(router_id, ns)
         ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns)
         for d in ns_ip.get_devices(exclude_loopback=True):
             if d.name.startswith(INTERNAL_DEV_PREFIX):
@@ -471,11 +468,22 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         self.router_info[router_id] = ri
         if self.conf.use_namespaces:
             self._create_router_namespace(ri)
+        for c, r in self.metadata_filter_rules():
+            ri.iptables_manager.ipv4['filter'].add_rule(c, r)
+        for c, r in self.metadata_nat_rules():
+            ri.iptables_manager.ipv4['nat'].add_rule(c, r)
+        ri.iptables_manager.apply()
         self.process_router_add(ri)
 
         if ri.is_ha:
             self.process_ha_router_added(ri)
 
+        if self.conf.enable_metadata_proxy:
+            if ri.is_ha:
+                self._add_keepalived_notifiers(ri)
+            else:
+                self._spawn_metadata_proxy(ri.router_id, ri.ns_name)
+
     def _router_removed(self, router_id):
         ri = self.router_info.get(router_id)
         if ri is None:
@@ -493,12 +501,50 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         ri.router[l3_constants.INTERFACE_KEY] = []
         ri.router[l3_constants.FLOATINGIP_KEY] = []
         self.process_router(ri)
+        for c, r in self.metadata_filter_rules():
+            ri.iptables_manager.ipv4['filter'].remove_rule(c, r)
+        for c, r in self.metadata_nat_rules():
+            ri.iptables_manager.ipv4['nat'].remove_rule(c, r)
+        ri.iptables_manager.apply()
         del self.router_info[router_id]
         self._destroy_router_namespace(ri.ns_name)
 
         self.event_observers.notify(
             adv_svc.AdvancedService.after_router_removed, ri)
 
+    def _get_metadata_proxy_callback(self, router_id):
+
+        def callback(pid_file):
+            metadata_proxy_socket = self.conf.metadata_proxy_socket
+            proxy_cmd = ['neutron-ns-metadata-proxy',
+                         '--pid_file=%s' % pid_file,
+                         '--metadata_proxy_socket=%s' % metadata_proxy_socket,
+                         '--router_id=%s' % router_id,
+                         '--state_path=%s' % self.conf.state_path,
+                         '--metadata_port=%s' % self.conf.metadata_port]
+            proxy_cmd.extend(config.get_log_args(
+                self.conf, 'neutron-ns-metadata-proxy-%s.log' %
+                router_id))
+            return proxy_cmd
+
+        return callback
+
+    def _get_metadata_proxy_process_manager(self, router_id, ns_name):
+        return external_process.ProcessManager(
+            self.conf,
+            router_id,
+            self.root_helper,
+            ns_name)
+
+    def _spawn_metadata_proxy(self, router_id, ns_name):
+        callback = self._get_metadata_proxy_callback(router_id)
+        pm = self._get_metadata_proxy_process_manager(router_id, ns_name)
+        pm.enable(callback)
+
+    def _destroy_metadata_proxy(self, router_id, ns_name):
+        pm = self._get_metadata_proxy_process_manager(router_id, ns_name)
+        pm.disable()
+
     def _set_subnet_arp_info(self, ri, port):
         """Set ARP info retrieved from Plugin for existing ports."""
         if 'id' not in port['subnet'] or not ri.router['distributed']:
@@ -1125,6 +1171,22 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         if ri.router['distributed']:
             self._destroy_snat_namespace(ns_name)
 
+    def metadata_filter_rules(self):
+        rules = []
+        if self.conf.enable_metadata_proxy:
+            rules.append(('INPUT', '-s 0.0.0.0/0 -d 127.0.0.1 '
+                          '-p tcp -m tcp --dport %s '
+                          '-j ACCEPT' % self.conf.metadata_port))
+        return rules
+
+    def metadata_nat_rules(self):
+        rules = []
+        if self.conf.enable_metadata_proxy:
+            rules.append(('PREROUTING', '-s 0.0.0.0/0 -d 169.254.169.254/32 '
+                          '-p tcp -m tcp --dport 80 -j REDIRECT '
+                          '--to-port %s' % self.conf.metadata_port))
+        return rules
+
     def external_gateway_nat_rules(self, ex_gw_ip, interface_name):
         rules = [('POSTROUTING', '! -i %(interface_name)s '
                   '! -o %(interface_name)s -m conntrack ! '
index 7db91f9ee0a528c3356f97b667a391bd467fc824..c1f6f492803494620c9dbf38a4d45e562a2cec25 100644 (file)
@@ -20,7 +20,6 @@ import signal
 from oslo.config import cfg
 
 from neutron.agent.linux import keepalived
-from neutron.agent.metadata import driver as metadata_driver
 from neutron.common import constants as l3_constants
 from neutron.i18n import _LE
 from neutron.openstack.common import log as logging
@@ -144,7 +143,6 @@ class AgentMixin(object):
         ri.ha_port = ha_port
 
         self._init_keepalived_manager(ri)
-        self._add_keepalived_notifiers(ri)
 
     def process_ha_router_removed(self, ri):
         self.ha_network_removed(ri)
@@ -180,14 +178,8 @@ class AgentMixin(object):
         instance.remove_vips_vroutes_by_interface(interface)
 
     def _add_keepalived_notifiers(self, ri):
-        callback = (
-            metadata_driver.MetadataDriver._get_metadata_proxy_callback(
-                ri.router_id, self.conf))
-        pm = (
-            metadata_driver.MetadataDriver.
-            _get_metadata_proxy_process_manager(ri.router_id,
-                                                ri.ns_name,
-                                                self.conf))
+        callback = self._get_metadata_proxy_callback(ri.router_id)
+        pm = self._get_metadata_proxy_process_manager(ri.router_id, ri.ns_name)
         pid = pm.get_pid_file_name(ensure_pids_dir=True)
         ri.keepalived_manager.add_notifier(
             callback(pid), 'master', ri.ha_vr_id)
diff --git a/neutron/agent/metadata/driver.py b/neutron/agent/metadata/driver.py
deleted file mode 100644 (file)
index 2a92b5b..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-# Copyright 2014 OpenStack Foundation.
-# All Rights Reserved.
-#
-#    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 neutron.agent.common import config
-from neutron.agent.linux import external_process
-from neutron.openstack.common import log as logging
-from neutron.services import advanced_service
-
-LOG = logging.getLogger(__name__)
-
-
-class MetadataDriver(advanced_service.AdvancedService):
-    def __init__(self, l3_agent):
-        super(MetadataDriver, self).__init__(l3_agent)
-        self.metadata_port = l3_agent.conf.metadata_port
-
-    def after_router_added(self, router):
-        for c, r in self.metadata_filter_rules(self.metadata_port):
-            router.iptables_manager.ipv4['filter'].add_rule(c, r)
-        for c, r in self.metadata_nat_rules(self.metadata_port):
-            router.iptables_manager.ipv4['nat'].add_rule(c, r)
-        router.iptables_manager.apply()
-
-        if not router.is_ha:
-            self._spawn_metadata_proxy(router.router_id,
-                                       router.ns_name,
-                                       self.l3_agent.conf)
-
-    def before_router_removed(self, router):
-        for c, r in self.metadata_filter_rules(self.metadata_port):
-            router.iptables_manager.ipv4['filter'].remove_rule(c, r)
-        for c, r in self.metadata_nat_rules(self.metadata_port):
-            router.iptables_manager.ipv4['nat'].remove_rule(c, r)
-        router.iptables_manager.apply()
-
-        self._destroy_metadata_proxy(router.router['id'],
-                                     router.ns_name,
-                                     self.l3_agent.conf)
-
-    @classmethod
-    def metadata_filter_rules(cls, port):
-        return [('INPUT', '-s 0.0.0.0/0 -d 127.0.0.1 '
-                 '-p tcp -m tcp --dport %s '
-                 '-j ACCEPT' % port)]
-
-    @classmethod
-    def metadata_nat_rules(cls, port):
-        return [('PREROUTING', '-s 0.0.0.0/0 -d 169.254.169.254/32 '
-                 '-p tcp -m tcp --dport 80 -j REDIRECT '
-                 '--to-port %s' % port)]
-
-    @classmethod
-    def _get_metadata_proxy_callback(cls, router_id, conf):
-
-        def callback(pid_file):
-            metadata_proxy_socket = conf.metadata_proxy_socket
-            proxy_cmd = ['neutron-ns-metadata-proxy',
-                         '--pid_file=%s' % pid_file,
-                         '--metadata_proxy_socket=%s' % metadata_proxy_socket,
-                         '--router_id=%s' % router_id,
-                         '--state_path=%s' % conf.state_path,
-                         '--metadata_port=%s' % conf.metadata_port]
-            proxy_cmd.extend(config.get_log_args(
-                conf, 'neutron-ns-metadata-proxy-%s.log' %
-                router_id))
-            return proxy_cmd
-
-        return callback
-
-    @classmethod
-    def _get_metadata_proxy_process_manager(cls, router_id, ns_name, conf):
-        return external_process.ProcessManager(
-            conf,
-            router_id,
-            config.get_root_helper(conf),
-            ns_name)
-
-    @classmethod
-    def _spawn_metadata_proxy(cls, router_id, ns_name, conf):
-        callback = cls._get_metadata_proxy_callback(router_id, conf)
-        pm = cls._get_metadata_proxy_process_manager(router_id, ns_name, conf)
-        pm.enable(callback)
-
-    @classmethod
-    def _destroy_metadata_proxy(cls, router_id, ns_name, conf):
-        pm = cls._get_metadata_proxy_process_manager(router_id, ns_name, conf)
-        pm.disable()
diff --git a/neutron/tests/unit/agent/metadata/__init__.py b/neutron/tests/unit/agent/metadata/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/neutron/tests/unit/agent/metadata/test_driver.py b/neutron/tests/unit/agent/metadata/test_driver.py
deleted file mode 100644 (file)
index 72f18bd..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-# Copyright 2014 OpenStack Foundation.
-# All Rights Reserved.
-#
-#    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 mock
-
-from oslo.config import cfg
-
-from neutron.agent.common import config as agent_config
-from neutron.agent.l3 import agent as l3_agent
-from neutron.agent.metadata import driver as metadata_driver
-from neutron.openstack.common import uuidutils
-from neutron.tests import base
-
-
-_uuid = uuidutils.generate_uuid
-
-
-class TestMetadataDriver(base.BaseTestCase):
-    def setUp(self):
-        super(TestMetadataDriver, self).setUp()
-        cfg.CONF.register_opts(l3_agent.L3NATAgent.OPTS)
-        agent_config.register_root_helper(cfg.CONF)
-
-    def test_metadata_nat_rules(self):
-        rules = ('PREROUTING', '-s 0.0.0.0/0 -d 169.254.169.254/32 '
-                 '-p tcp -m tcp --dport 80 -j REDIRECT --to-port 8775')
-        self.assertEqual(
-            [rules],
-            metadata_driver.MetadataDriver.metadata_nat_rules(8775))
-
-    def test_metadata_filter_rules(self):
-        rules = ('INPUT', '-s 0.0.0.0/0 -d 127.0.0.1 '
-                 '-p tcp -m tcp --dport 8775 -j ACCEPT')
-        self.assertEqual(
-            [rules],
-            metadata_driver.MetadataDriver.metadata_filter_rules(8775))
-
-    def test_spawn_metadata_proxy(self):
-        router_id = _uuid()
-        router_ns = 'qrouter-%s' % router_id
-        metadata_port = 8080
-        ip_class_path = 'neutron.agent.linux.ip_lib.IPWrapper'
-
-        cfg.CONF.set_override('metadata_port', metadata_port)
-        cfg.CONF.set_override('log_file', 'test.log')
-        cfg.CONF.set_override('debug', True)
-
-        driver = metadata_driver.MetadataDriver
-        with mock.patch(ip_class_path) as ip_mock:
-            driver._spawn_metadata_proxy(router_id, router_ns, cfg.CONF)
-            ip_mock.assert_has_calls([
-                mock.call('sudo', router_ns),
-                mock.call().netns.execute([
-                    'neutron-ns-metadata-proxy',
-                    mock.ANY,
-                    mock.ANY,
-                    '--router_id=%s' % router_id,
-                    mock.ANY,
-                    '--metadata_port=%s' % metadata_port,
-                    '--debug',
-                    '--verbose',
-                    '--log-file=neutron-ns-metadata-proxy-%s.log' %
-                    router_id
-                ], addl_env=None)
-            ])
index 14a6808d6d3a97f0d4b405b729bf6347918134bc..aac84b28ca19d9b1a57475866de57fdc83db2e32 100644 (file)
@@ -29,12 +29,10 @@ from neutron.agent.l3 import link_local_allocator as lla
 from neutron.agent.l3 import router_info as l3router
 from neutron.agent.linux import interface
 from neutron.agent.linux import ra
-from neutron.agent.metadata import driver as metadata_driver
 from neutron.common import config as base_config
 from neutron.common import constants as l3_constants
 from neutron.common import exceptions as n_exc
 from neutron.i18n import _LE
-from neutron.openstack.common import log
 from neutron.openstack.common import uuidutils
 from neutron.plugins.common import constants as p_const
 from neutron.tests import base
@@ -165,8 +163,6 @@ class TestBasicRouterOperations(base.BaseTestCase):
         super(TestBasicRouterOperations, self).setUp()
         self.conf = agent_config.setup_conf()
         self.conf.register_opts(base_config.core_opts)
-        self.conf.register_cli_opts(log.common_cli_opts)
-        self.conf.register_cli_opts(log.logging_cli_opts)
         self.conf.register_opts(l3_agent.L3NATAgent.OPTS)
         self.conf.register_opts(ha.OPTS)
         agent_config.register_interface_driver_opts_helper(self.conf)
@@ -893,6 +889,60 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.assertEqual(agent.process_router_floating_ip_nat_rules.called,
                          distributed)
 
+    def test_ha_router_keepalived_config(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = prepare_router_data(enable_ha=True)
+        router['routes'] = [
+            {'destination': '8.8.8.8/32', 'nexthop': '35.4.0.10'},
+            {'destination': '8.8.4.4/32', 'nexthop': '35.4.0.11'}]
+        ri = l3router.RouterInfo(router['id'], self.conf.root_helper,
+                                 router=router)
+        ri.router = router
+        with contextlib.nested(mock.patch.object(agent,
+                                                 '_spawn_metadata_proxy'),
+                               mock.patch('neutron.agent.linux.'
+                                          'utils.replace_file'),
+                               mock.patch('neutron.agent.linux.'
+                                          'utils.execute'),
+                               mock.patch('os.makedirs')):
+            agent.process_ha_router_added(ri)
+            agent.process_router(ri)
+            config = ri.keepalived_manager.config
+            ha_iface = agent.get_ha_device_name(ri.ha_port['id'])
+            ex_iface = agent.get_external_device_name(ri.ex_gw_port['id'])
+            int_iface = agent.get_internal_device_name(
+                ri.internal_ports[0]['id'])
+
+            expected = """vrrp_sync_group VG_1 {
+    group {
+        VR_1
+    }
+}
+vrrp_instance VR_1 {
+    state BACKUP
+    interface %(ha_iface)s
+    virtual_router_id 1
+    priority 50
+    nopreempt
+    advert_int 2
+    track_interface {
+        %(ha_iface)s
+    }
+    virtual_ipaddress {
+        19.4.4.4/24 dev %(ex_iface)s
+    }
+    virtual_ipaddress_excluded {
+        35.4.0.4/24 dev %(int_iface)s
+    }
+    virtual_routes {
+        0.0.0.0/0 via 19.4.4.1 dev %(ex_iface)s
+        8.8.8.8/32 via 35.4.0.10
+        8.8.4.4/32 via 35.4.0.11
+    }
+}""" % {'ha_iface': ha_iface, 'ex_iface': ex_iface, 'int_iface': int_iface}
+
+            self.assertEqual(expected, config.get_config_str())
+
     @mock.patch('neutron.agent.linux.ip_lib.IPDevice')
     def _test_process_router_floating_ip_addresses_add(self, ri,
                                                        agent, IPDevice):
@@ -1585,27 +1635,22 @@ class TestBasicRouterOperations(base.BaseTestCase):
             self.conf.set_override('enable_metadata_proxy', False)
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router_id = _uuid()
-        router = {'id': router_id,
+        router = {'id': _uuid(),
                   'external_gateway_info': {},
                   'routes': [],
                   'distributed': False}
-        driver = metadata_driver.MetadataDriver
         with mock.patch.object(
-            driver, '_destroy_metadata_proxy') as destroy_proxy:
+            agent, '_destroy_metadata_proxy') as destroy_proxy:
             with mock.patch.object(
-                driver, '_spawn_metadata_proxy') as spawn_proxy:
-                agent._process_added_router(router)
+                agent, '_spawn_metadata_proxy') as spawn_proxy:
+                agent._router_added(router_id, router)
                 if enableflag:
-                    spawn_proxy.assert_called_with(router_id,
-                                                   mock.ANY,
-                                                   mock.ANY)
+                    spawn_proxy.assert_called_with(router_id, mock.ANY)
                 else:
                     self.assertFalse(spawn_proxy.call_count)
                 agent._router_removed(router_id)
                 if enableflag:
-                    destroy_proxy.assert_called_with(router_id,
-                                                     mock.ANY,
-                                                     mock.ANY)
+                    destroy_proxy.assert_called_with(mock.ANY, mock.ANY)
                 else:
                     self.assertFalse(destroy_proxy.call_count)
 
@@ -1615,6 +1660,18 @@ class TestBasicRouterOperations(base.BaseTestCase):
     def test_disable_metadata_proxy_spawn(self):
         self._configure_metadata_proxy(enableflag=False)
 
+    def test_metadata_nat_rules(self):
+        self.conf.set_override('enable_metadata_proxy', False)
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        self.assertEqual([], agent.metadata_nat_rules())
+
+        self.conf.set_override('metadata_port', '8775')
+        self.conf.set_override('enable_metadata_proxy', True)
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        rules = ('PREROUTING', '-s 0.0.0.0/0 -d 169.254.169.254/32 '
+                 '-p tcp -m tcp --dport 80 -j REDIRECT --to-port 8775')
+        self.assertEqual([rules], agent.metadata_nat_rules())
+
     def test_router_id_specified_in_conf(self):
         self.conf.set_override('use_namespaces', False)
         self.conf.set_override('router_id', '')
@@ -1730,6 +1787,18 @@ class TestBasicRouterOperations(base.BaseTestCase):
             msg = _LE("Error importing interface driver '%s'")
             log.error.assert_called_once_with(msg, 'wrong_driver')
 
+    def test_metadata_filter_rules(self):
+        self.conf.set_override('enable_metadata_proxy', False)
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        self.assertEqual([], agent.metadata_filter_rules())
+
+        self.conf.set_override('metadata_port', '8775')
+        self.conf.set_override('enable_metadata_proxy', True)
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        rules = ('INPUT', '-s 0.0.0.0/0 -d 127.0.0.1 '
+                 '-p tcp -m tcp --dport 8775 -j ACCEPT')
+        self.assertEqual([rules], agent.metadata_filter_rules())
+
     def _cleanup_namespace_test(self,
                                 stale_namespace_list,
                                 router_list,
@@ -2125,3 +2194,76 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.assertIn(_join('-C', conffile), cmd)
         self.assertIn(_join('-p', pidfile), cmd)
         self.assertIn(_join('-m', 'syslog'), cmd)
+
+
+class TestL3AgentEventHandler(base.BaseTestCase):
+
+    def setUp(self):
+        super(TestL3AgentEventHandler, self).setUp()
+        cfg.CONF.register_opts(l3_agent.L3NATAgent.OPTS)
+        cfg.CONF.register_opts(ha.OPTS)
+        agent_config.register_interface_driver_opts_helper(cfg.CONF)
+        agent_config.register_use_namespaces_opts_helper(cfg.CONF)
+        cfg.CONF.set_override(
+            'interface_driver', 'neutron.agent.linux.interface.NullDriver'
+        )
+        cfg.CONF.set_override('use_namespaces', True)
+        cfg.CONF.set_override('verbose', False)
+        agent_config.register_root_helper(cfg.CONF)
+
+        device_exists_p = mock.patch(
+            'neutron.agent.linux.ip_lib.device_exists')
+        device_exists_p.start()
+
+        utils_exec_p = mock.patch(
+            'neutron.agent.linux.utils.execute')
+        utils_exec_p.start()
+
+        drv_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver')
+        driver_cls = drv_cls_p.start()
+        mock_driver = mock.MagicMock()
+        mock_driver.DEV_NAME_LEN = (
+            interface.LinuxInterfaceDriver.DEV_NAME_LEN)
+        driver_cls.return_value = mock_driver
+
+        l3_plugin_p = mock.patch(
+            'neutron.agent.l3.agent.L3PluginApi')
+        l3_plugin_cls = l3_plugin_p.start()
+        l3_plugin_cls.return_value = mock.MagicMock()
+
+        self.external_process_p = mock.patch(
+            'neutron.agent.linux.external_process.ProcessManager'
+        )
+        self.external_process_p.start()
+        looping_call_p = mock.patch(
+            'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
+        looping_call_p.start()
+        self.agent = l3_agent.L3NATAgent(HOSTNAME)
+
+    def test_spawn_metadata_proxy(self):
+        router_id = _uuid()
+        metadata_port = 8080
+        ip_class_path = 'neutron.agent.linux.ip_lib.IPWrapper'
+
+        cfg.CONF.set_override('metadata_port', metadata_port)
+        cfg.CONF.set_override('log_file', 'test.log')
+        cfg.CONF.set_override('debug', True)
+
+        self.external_process_p.stop()
+        ri = l3router.RouterInfo(router_id, None, None)
+        with mock.patch(ip_class_path) as ip_mock:
+            self.agent._spawn_metadata_proxy(ri.router_id, ri.ns_name)
+            ip_mock.assert_has_calls([
+                mock.call('sudo', ri.ns_name),
+                mock.call().netns.execute([
+                    'neutron-ns-metadata-proxy',
+                    mock.ANY,
+                    mock.ANY,
+                    '--router_id=%s' % router_id,
+                    mock.ANY,
+                    '--metadata_port=%s' % metadata_port,
+                    '--debug',
+                    '--log-file=neutron-ns-metadata-proxy-%s.log' %
+                    router_id
+                ], addl_env=None)
+            ])