From: Assaf Muller Date: Sun, 23 Nov 2014 14:09:41 +0000 (+0200) Subject: Add metadata proxy L3 agent driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=6b38f29fdbd077434f1f7139466479e81bf4882d;p=openstack-build%2Fneutron-build.git Add metadata proxy L3 agent driver To-Do: * vArmourL3NATAgent (Before this patch) overrides _spawn/destroy_metadata_proxy with empty stubs. This behavior should be maintained so that the metadata proxy is not created when using vArmourL3NATAgent. It also overrides _router_added and _router_removed, causing L3 agent driver notifications to not send out. Partially-implements: blueprint restructure-l3-agent Change-Id: I36900b02bff34269f789956aa324379ff51eb81b --- diff --git a/neutron/agent/l3/agent.py b/neutron/agent/l3/agent.py index 279c332e2..fbd068014 100644 --- a/neutron/agent/l3/agent.py +++ b/neutron/agent/l3/agent.py @@ -37,6 +37,7 @@ 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 @@ -277,6 +278,10 @@ 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) @@ -396,8 +401,6 @@ 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): @@ -468,22 +471,11 @@ 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: @@ -501,50 +493,12 @@ 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']: @@ -1171,22 +1125,6 @@ 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 c1f6f4928..7db91f9ee 100644 --- a/neutron/agent/l3/ha.py +++ b/neutron/agent/l3/ha.py @@ -20,6 +20,7 @@ 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 @@ -143,6 +144,7 @@ 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) @@ -178,8 +180,14 @@ class AgentMixin(object): instance.remove_vips_vroutes_by_interface(interface) def _add_keepalived_notifiers(self, ri): - callback = self._get_metadata_proxy_callback(ri.router_id) - pm = self._get_metadata_proxy_process_manager(ri.router_id, ri.ns_name) + 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)) 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 new file mode 100644 index 000000000..2a92b5ba3 --- /dev/null +++ b/neutron/agent/metadata/driver.py @@ -0,0 +1,99 @@ +# 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 new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/tests/unit/agent/metadata/test_driver.py b/neutron/tests/unit/agent/metadata/test_driver.py new file mode 100644 index 000000000..72f18bdda --- /dev/null +++ b/neutron/tests/unit/agent/metadata/test_driver.py @@ -0,0 +1,77 @@ +# 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 aac84b28c..14a6808d6 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -29,10 +29,12 @@ 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 @@ -163,6 +165,8 @@ 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) @@ -889,60 +893,6 @@ 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): @@ -1635,22 +1585,27 @@ vrrp_instance VR_1 { self.conf.set_override('enable_metadata_proxy', False) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) router_id = _uuid() - router = {'id': _uuid(), + router = {'id': router_id, 'external_gateway_info': {}, 'routes': [], 'distributed': False} + driver = metadata_driver.MetadataDriver with mock.patch.object( - agent, '_destroy_metadata_proxy') as destroy_proxy: + driver, '_destroy_metadata_proxy') as destroy_proxy: with mock.patch.object( - agent, '_spawn_metadata_proxy') as spawn_proxy: - agent._router_added(router_id, router) + driver, '_spawn_metadata_proxy') as spawn_proxy: + agent._process_added_router(router) if enableflag: - spawn_proxy.assert_called_with(router_id, mock.ANY) + spawn_proxy.assert_called_with(router_id, + mock.ANY, + mock.ANY) else: self.assertFalse(spawn_proxy.call_count) agent._router_removed(router_id) if enableflag: - destroy_proxy.assert_called_with(mock.ANY, mock.ANY) + destroy_proxy.assert_called_with(router_id, + mock.ANY, + mock.ANY) else: self.assertFalse(destroy_proxy.call_count) @@ -1660,18 +1615,6 @@ vrrp_instance VR_1 { 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', '') @@ -1787,18 +1730,6 @@ vrrp_instance VR_1 { 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, @@ -2194,76 +2125,3 @@ vrrp_instance VR_1 { 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) - ])