From 658dc9d30cfb337159df40fdd62c50de182d83aa Mon Sep 17 00:00:00 2001 From: Henry Gessau Date: Sat, 20 Dec 2014 05:08:43 +0000 Subject: [PATCH] Revert "Add metadata proxy L3 agent driver" This reverts commit 6b38f29fdbd077434f1f7139466479e81bf4882d. Because it broke the functional job. Change-Id: Ibcc577ade490663820f0a4f599afc6127a6e52e6 --- neutron/agent/l3/agent.py | 72 +++++++- neutron/agent/l3/ha.py | 12 +- neutron/agent/metadata/driver.py | 99 ---------- neutron/tests/unit/agent/metadata/__init__.py | 0 .../tests/unit/agent/metadata/test_driver.py | 77 -------- neutron/tests/unit/test_l3_agent.py | 172 ++++++++++++++++-- 6 files changed, 226 insertions(+), 206 deletions(-) delete mode 100644 neutron/agent/metadata/driver.py delete mode 100644 neutron/tests/unit/agent/metadata/__init__.py delete mode 100644 neutron/tests/unit/agent/metadata/test_driver.py diff --git a/neutron/agent/l3/agent.py b/neutron/agent/l3/agent.py index fbd068014..279c332e2 100644 --- a/neutron/agent/l3/agent.py +++ b/neutron/agent/l3/agent.py @@ -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 ! ' diff --git a/neutron/agent/l3/ha.py b/neutron/agent/l3/ha.py index 7db91f9ee..c1f6f4928 100644 --- a/neutron/agent/l3/ha.py +++ b/neutron/agent/l3/ha.py @@ -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 index 2a92b5ba3..000000000 --- a/neutron/agent/metadata/driver.py +++ /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 index e69de29bb..000000000 diff --git a/neutron/tests/unit/agent/metadata/test_driver.py b/neutron/tests/unit/agent/metadata/test_driver.py deleted file mode 100644 index 72f18bdda..000000000 --- a/neutron/tests/unit/agent/metadata/test_driver.py +++ /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) - ]) diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index 14a6808d6..aac84b28c 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -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) + ]) -- 2.45.2