diff --git a/etc/neutron/rootwrap.d/l3.filters b/etc/neutron/rootwrap.d/l3.filters index f1abc26a93b..0fdf60cd1ec 100644 --- a/etc/neutron/rootwrap.d/l3.filters +++ b/etc/neutron/rootwrap.d/l3.filters @@ -50,8 +50,3 @@ conntrack: CommandFilter, conntrack, root # keepalived state change monitor keepalived_state_change: CommandFilter, neutron-keepalived-state-change, root - -# For creating namespace local /etc -rt_tables_mkdir: RegExpFilter, mkdir, root, mkdir, -p, /etc/netns/qrouter-[^/].* -rt_tables_chown: RegExpFilter, chown, root, chown, [1-9][0-9].*, /etc/netns/qrouter-[^/].* -rt_tables_rmdir: RegExpFilter, rm, root, rm, -r, -f, /etc/netns/qrouter-[^/].* diff --git a/neutron/agent/l3/rt_tables.py b/neutron/agent/l3/rt_tables.py deleted file mode 100644 index d88b5a0030a..00000000000 --- a/neutron/agent/l3/rt_tables.py +++ /dev/null @@ -1,255 +0,0 @@ -# Copyright (c) 2015 Hewlett-Packard Enterprise Development Company, L.P. -# -# 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 os - -import netaddr -from oslo_log import log as logging -import six - -from neutron.agent.common import utils as common_utils -from neutron.agent.linux import ip_lib -from neutron.common import constants -from neutron.common import exceptions -from neutron.common import utils - -LOG = logging.getLogger(__name__) - - -class NamespaceEtcDir(object): - """Creates a directory where namespace local /etc/iproute2 files can live - - Directories are created under /etc/netns//iproute2 so that - when you exec a command inside a namespace, the directory is available as - /etc/iproute2 locally to the namespace. - - The directory ownership is changed to the owner of the L3 agent process - so that root is no longer required to manage the file. This limits the - scope of where root is needed. Changing ownership is justified because - the directory lives under a namespace specific sub-directory of /etc, it - should be considered owned by the L3 agent process, which also manages the - namespace itself. - - The directory and its contents should not be considered config. Nothing - needs to be done for upgrade. The only reason for it to live under /etc - within the namespace is that is the only place from where the ip command - will read it. - """ - - BASE_DIR = "/etc/netns" - - def __init__(self, namespace): - self._directory = os.path.join(self.BASE_DIR, namespace) - - def create(self): - common_utils.execute(['mkdir', '-p', self._directory], - run_as_root=True) - - user_id = os.geteuid() - common_utils.execute(['chown', user_id, self._directory], - run_as_root=True) - - def destroy(self): - common_utils.execute(['rm', '-r', '-f', self._directory], - run_as_root=True) - - def get_full_path(self): - return self._directory - - -class RoutingTable(object): - def __init__(self, namespace, table_id, name): - self.name = name - self.table_id = table_id - self.ip_route = ip_lib.IPRoute(namespace=namespace, table=name) - self._keep = set() - - def __eq__(self, other): - return self.table_id == other.table_id - - def __hash__(self): - return self.table_id - - def add(self, device, cidr): - table = device.route.table(self.name) - cidr = netaddr.IPNetwork(cidr) - # Get the network cidr (e.g. 192.168.5.135/23 -> 192.168.4.0/23) - net = utils.ip_to_cidr(cidr.network, cidr.prefixlen) - self._keep.add((net, device.name)) - table.add_onlink_route(net) - - def add_gateway(self, device, gateway_ip): - table = device.route.table(self.name) - ip_version = ip_lib.get_ip_version(gateway_ip) - self._keep.add((constants.IP_ANY[ip_version], device.name)) - table.add_gateway(gateway_ip) - - def __enter__(self): - self._keep = set() - return self - - def __exit__(self, exc_type, value, traceback): - if exc_type: - return False - - keep = self._keep - self._keep = None - - ipv4_routes = self.ip_route.route.list_routes(constants.IP_VERSION_4) - ipv6_routes = self.ip_route.route.list_routes(constants.IP_VERSION_6) - all_routes = {(r['cidr'], r['dev']) - for r in ipv4_routes + ipv6_routes} - - for cidr, dev in all_routes - keep: - try: - self.ip_route.route.delete_route(cidr, dev=dev) - except exceptions.DeviceNotFoundError: - pass - - return True - - -class RoutingTablesManager(object): - """Manages mapping from routing table name to routing tables - - The iproute2 package can read a mapping from /etc/iproute2/rt_tables. When - namespaces are used, it is possible to maintain an rt_tables file that is - unique to the namespace. - - It is necessary to maintain this mapping on disk somewhere because it must - survive agent restarts. Otherwise, we'd be remapping each time. It is not - necessary to maintain it in the Neutron database because it is an - agent-local implementation detail. - - While it could be kept in any local file, it is convenient to keep it in - the rt_tables file so that we can simply pass the table name to the - ip route commands. It will also be helpful for debugging to be able to use - the table name on the command line manually. - """ - - FILENAME = 'iproute2/rt_tables' - ALL_IDS = set(range(1024, 2048)) - DEFAULT_TABLES = {"local": 255, - "main": 254, - "default": 253, - "unspec": 0} - - def __init__(self, namespace): - self._namespace = namespace - self.etc = NamespaceEtcDir(namespace) - self._rt_tables_filename = os.path.join( - self.etc.get_full_path(), self.FILENAME) - self._tables = {} - self.initialize_map() - - def initialize_map(self): - # Create a default table if one is not already found - self.etc.create() - utils.ensure_dir(os.path.dirname(self._rt_tables_filename)) - if not os.path.exists(self._rt_tables_filename): - self._write_map(self.DEFAULT_TABLES) - self._keep = set() - - def _get_or_create(self, table_id, table_name): - table = self._tables.get(table_id) - if not table: - self._tables[table_id] = table = RoutingTable( - self._namespace, table_id, table_name) - return table - - def get(self, table_name): - """Returns the table ID for the given table name""" - table_id = self._read_map().get(table_name) - if table_id is not None: - return self._get_or_create(table_id, table_name) - - def get_all(self): - return set(self._get_or_create(t_id, name) - for name, t_id in self._read_map().items()) - - def add(self, table_name): - """Ensures there is a single table id available for the table name""" - name_to_id = self._read_map() - - def get_and_keep(table_id, table_name): - table = self._get_or_create(table_id, table_name) - self._keep.add(table) - return table - - # If it is already there, just return it. - if table_name in name_to_id: - return get_and_keep(name_to_id[table_name], table_name) - - # Otherwise, find an available id and write the new file - table_ids = set(name_to_id.values()) - available_ids = self.ALL_IDS - table_ids - name_to_id[table_name] = table_id = available_ids.pop() - self._write_map(name_to_id) - return get_and_keep(table_id, table_name) - - def delete(self, table_name): - """Removes the table from the file""" - name_to_id = self._read_map() - - # If it is already there, remove it - table_id = name_to_id.pop(table_name, None) - self._tables.pop(table_id, None) - - # Write the new file - self._write_map(name_to_id) - - def _write_map(self, name_to_id): - buf = six.StringIO() - for name, table_id in name_to_id.items(): - buf.write("%s\t%s\n" % (table_id, name)) - utils.replace_file(self._rt_tables_filename, buf.getvalue()) - - def _read_map(self): - result = {} - with open(self._rt_tables_filename, "r") as rt_file: - for line in rt_file: - fields = line.split() - if len(fields) != 2: - continue - table_id_str, name = fields - try: - table_id = int(table_id_str) - except ValueError: - continue - result[name] = table_id - return result - - def destroy(self): - self.etc.destroy() - - def __enter__(self): - for rt in self.get_all(): - if rt.table_id not in self.DEFAULT_TABLES.values(): - rt.__enter__() - self._keep = set() - return self - - def __exit__(self, exc_type, value, traceback): - if exc_type: - return False - - all_tables = set(rt for rt in self.get_all() - if rt.table_id not in self.DEFAULT_TABLES.values()) - for rt in all_tables: - rt.__exit__(None, None, None) - - for rt in all_tables - self._keep: - self.delete(rt.name) - - return True diff --git a/neutron/tests/unit/agent/l3/test_rt_tables.py b/neutron/tests/unit/agent/l3/test_rt_tables.py deleted file mode 100644 index 70471c82b32..00000000000 --- a/neutron/tests/unit/agent/l3/test_rt_tables.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (c) 2015 Hewlett-Packard Enterprise Development Company, L.P. -# -# 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 fixtures -import mock - -from neutron.agent.common import utils as common_utils -from neutron.agent.l3 import rt_tables -from neutron.tests import base - - -def mock_netnamespace_directory(function): - """Decorator to test RoutingTablesManager with temp dir - - Allows direct testing of RoutingTablesManager by changing the directory - where it finds the rt_tables to one in /tmp where root privileges are not - required and it won't mess with any real routing tables. - """ - orig_execute = common_utils.execute - - def execute_no_root(*args, **kwargs): - kwargs['run_as_root'] = False - orig_execute(*args, **kwargs) - - def inner(*args, **kwargs): - with fixtures.TempDir() as tmpdir: - cls = rt_tables.NamespaceEtcDir - with mock.patch.object(common_utils, 'execute') as execute,\ - mock.patch.object(cls, 'BASE_DIR', tmpdir.path): - execute.side_effect = execute_no_root - function(*args, **kwargs) - return inner - - -class TestRoutingTablesManager(base.BaseTestCase): - def setUp(self): - super(TestRoutingTablesManager, self).setUp() - self.ns_name = "fakens" - - @mock_netnamespace_directory - def test_default_tables(self): - rtm = rt_tables.RoutingTablesManager(self.ns_name) - self.assertEqual(253, rtm.get("default").table_id) - self.assertEqual(254, rtm.get("main").table_id) - self.assertEqual(255, rtm.get("local").table_id) - self.assertEqual(0, rtm.get("unspec").table_id) - - @mock_netnamespace_directory - def test_get_all(self): - rtm = rt_tables.RoutingTablesManager(self.ns_name) - table_names = set(rt.name for rt in rtm.get_all()) - self.assertEqual({"main", "default", "local", "unspec"}, table_names) - - new_table = rtm.add("faketable") - self.assertIn(new_table, rtm.get_all()) - - @mock_netnamespace_directory - def test_add(self): - rtm = rt_tables.RoutingTablesManager(self.ns_name) - added_table = rtm.add("faketable") - self.assertGreaterEqual(added_table.table_id, 1024) - - table = rtm.get("faketable") - self.assertEqual(added_table, table) - - # Be sure that adding it twice gets the same result - added_again = rtm.add("faketable") - self.assertEqual(added_table, added_again) - - @mock_netnamespace_directory - def test_delete(self): - rtm = rt_tables.RoutingTablesManager(self.ns_name) - rtm.add("faketable") - rtm.delete("faketable") - - table = rtm.get("faketable") - self.assertIsNone(table)