From 24c77966b6449257c2bfa87419aafc3bb5f3c287 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 13 Feb 2013 16:46:43 -0800 Subject: [PATCH] Metadata support for NVP plugin Bug #1121119 Allows the NVP plugin to leverage the metadata proxy, by creating an ad-hoc topology for allowing access to a metadata proxy from a NVP router leveraging existing agents. This patch also removes previous code for metadata support in the NVP plugin, which was based on DHCP Option 121. This is now provided by the dhcp agent as well. Change-Id: If37ef388e063f40bb06908ee2f72c431f29dac31 --- etc/dhcp_agent.ini | 17 +- etc/quantum/plugins/nicira/nvp.ini | 10 +- quantum/agent/dhcp_agent.py | 55 ++++++- .../nicira/nicira_nvp_plugin/QuantumPlugin.py | 63 +++----- .../nicira/nicira_nvp_plugin/common/config.py | 3 + .../common/metadata_access.py | 150 ++++++++++++++++++ .../tests/unit/nicira/test_nicira_plugin.py | 120 +++++++++++++- quantum/tests/unit/test_dhcp_agent.py | 83 ++++++++-- quantum/tests/unit/test_l3_plugin.py | 21 +-- 9 files changed, 438 insertions(+), 84 deletions(-) create mode 100644 quantum/plugins/nicira/nicira_nvp_plugin/common/metadata_access.py diff --git a/etc/dhcp_agent.ini b/etc/dhcp_agent.ini index 0422baf84..a3c798f38 100644 --- a/etc/dhcp_agent.ini +++ b/etc/dhcp_agent.ini @@ -30,9 +30,16 @@ dhcp_driver = quantum.agent.linux.dhcp.Dnsmasq # iproute2 package that supports namespaces). # use_namespaces = True -# The DHCP server can assist with providing metadata support on isolated -# networks. Setting this value to True will cause the DHCP server to append -# specific host routes to the DHCP request. The metadata service will only -# be activated when the subnet gateway_ip is None. The guest instance must -# be configured to request host routes via DHCP (Option 121). +# The DHCP server can assist with providing metadata support on isolated +# networks. Setting this value to True will cause the DHCP server to append +# specific host routes to the DHCP request. The metadata service will only +# be activated when the subnet gateway_ip is None. The guest instance must +# be configured to request host routes via DHCP (Option 121). # enable_isolated_metadata = False +# Allows for serving metadata requests coming from a dedicated metadata +# access network whose cidr is 169.254.169.254/16 (or larger prefix), and +# is connected to a Quantum router from which the VMs send metadata +# request. In this case DHCP Option 121 will not be injected in VMs, as +# they will be able to reach 169.254.169.254 through a router. +# This option requires enable_isolated_metadata = True +# enable_metadata_network = False diff --git a/etc/quantum/plugins/nicira/nvp.ini b/etc/quantum/plugins/nicira/nvp.ini index 7a6b6a065..54775489b 100644 --- a/etc/quantum/plugins/nicira/nvp.ini +++ b/etc/quantum/plugins/nicira/nvp.ini @@ -1,10 +1,3 @@ -[DEFAULT] - -# The following flag will cause a host route to the metadata server -# to be injected into instances. The metadata server will be reached -# via the dhcp server. -metadata_dhcp_host_route = False - [DATABASE] # This line MUST be changed to actually run the plugin. # Example: @@ -39,6 +32,9 @@ reconnect_interval = 2 # is not specified. If it is empty or reference a non-existent cluster # the first cluster specified in this configuration file will be used # default_cluster_name = +# The following flag enables the creation of a dedicated connection +# to the metadata proxy for metadata server access via Quantum router +# enable_metadata_access_network = True #[CLUSTER:example] # This is uuid of the default NVP Transport zone that will be used for diff --git a/quantum/agent/dhcp_agent.py b/quantum/agent/dhcp_agent.py index 97c384f0b..3ea81a87d 100644 --- a/quantum/agent/dhcp_agent.py +++ b/quantum/agent/dhcp_agent.py @@ -29,6 +29,7 @@ from quantum.agent.linux import external_process from quantum.agent.linux import interface from quantum.agent.linux import ip_lib from quantum.agent import rpc as agent_rpc +from quantum.common import constants from quantum.common import exceptions from quantum.common import topics from quantum import context @@ -40,7 +41,8 @@ from quantum.openstack.common import uuidutils LOG = logging.getLogger(__name__) NS_PREFIX = 'qdhcp-' -METADATA_DEFAULT_IP = '169.254.169.254/16' +METADATA_DEFAULT_PREFIX = 16 +METADATA_DEFAULT_IP = '169.254.169.254/%d' % METADATA_DEFAULT_PREFIX METADATA_PORT = 80 @@ -54,7 +56,11 @@ class DhcpAgent(object): cfg.BoolOpt('use_namespaces', default=True, help=_("Allow overlapping IP.")), cfg.BoolOpt('enable_isolated_metadata', default=False, - help=_("Support Metadata requests on isolated networks.")) + help=_("Support Metadata requests on isolated networks.")), + cfg.BoolOpt('enable_metadata_network', default=False, + help=_("Allows for serving metadata requests from a " + "dedicate network. Requires " + "enable isolated_metadata = True ")) ] def __init__(self, conf): @@ -245,13 +251,37 @@ class DhcpAgent(object): self.call_driver('reload_allocations', network) def enable_isolated_metadata_proxy(self, network): + + # The proxy might work for either a single network + # or all the networks connected via a router + # to the one passed as a parameter + quantum_lookup_param = '--network_id=%s' % network.id + meta_cidr = netaddr.IPNetwork(METADATA_DEFAULT_IP) + has_metadata_subnet = any(netaddr.IPNetwork(s.cidr) in meta_cidr + for s in network.subnets) + if (self.conf.enable_metadata_network and has_metadata_subnet): + router_ports = [port for port in network.ports + if (port.device_owner == + constants.DEVICE_OWNER_ROUTER_INTF)] + if router_ports: + # Multiple router ports should not be allowed + if len(router_ports) > 1: + LOG.warning(_("%(port_num)d router ports found on the " + "metadata access network. Only the port " + "%(port_id)s, for router %(router_id)s " + "will be considered"), + {'port_num': len(router_ports), + 'port_id': router_ports[0].id, + 'router_id': router_ports[0].device_id}) + quantum_lookup_param = ('--router_id=%s' % + router_ports[0].device_id) + def callback(pid_file): return ['quantum-ns-metadata-proxy', '--pid_file=%s' % pid_file, - '--network_id=%s' % network.id, + quantum_lookup_param, '--state_path=%s' % self.conf.state_path, '--metadata_port=%d' % METADATA_PORT] - pm = external_process.ProcessManager( self.conf, network.id, @@ -480,7 +510,9 @@ class DeviceManager(object): ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen) ip_cidrs.append(ip_cidr) - if self.conf.enable_isolated_metadata and self.conf.use_namespaces: + if (self.conf.enable_isolated_metadata and + self.conf.use_namespaces and + not self.conf.enable_metadata_network): ip_cidrs.append(METADATA_DEFAULT_IP) self.driver.init_l3(interface_name, ip_cidrs, @@ -492,6 +524,19 @@ class DeviceManager(object): self.root_helper) device.route.pullup_route(interface_name) + if self.conf.enable_metadata_network: + meta_cidr = netaddr.IPNetwork(METADATA_DEFAULT_IP) + metadata_subnets = [s for s in network.subnets if + netaddr.IPNetwork(s.cidr) in meta_cidr] + if metadata_subnets: + # Add a gateway so that packets can be routed back to VMs + device = ip_lib.IPDevice(interface_name, + self.root_helper, + namespace) + # Only 1 subnet on metadata access network + gateway_ip = metadata_subnets[0].gateway_ip + device.route.add_gateway(gateway_ip) + return interface_name def destroy(self, network, device_name): diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py b/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py index 2a9c742d0..1a4d1b19a 100644 --- a/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py +++ b/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py @@ -47,6 +47,8 @@ from quantum.extensions import portsecurity as psec from quantum.extensions import providernet as pnet from quantum.extensions import securitygroup as ext_sg from quantum.openstack.common import rpc +from quantum.plugins.nicira.nicira_nvp_plugin.common import (metadata_access + as nvp_meta) from quantum.plugins.nicira.nicira_nvp_plugin.common import (securitygroups as nvp_sec) from quantum import policy @@ -84,7 +86,11 @@ def parse_config(): NVPCluster objects, 'plugin_config' is a dictionary with plugin parameters (currently only 'max_lp_per_bridged_ls'). """ - nvp_options = cfg.CONF.NVP + # Warn if metadata_dhcp_host_route option is specified + if cfg.CONF.metadata_dhcp_host_route: + LOG.warning(_("The metadata_dhcp_host_route is now obsolete, and " + "will have no effect. Instead, please set the " + "enable_isolated_metadata option in dhcp_agent.ini")) nvp_conf = config.ClusterConfigOptions(cfg.CONF) cluster_names = config.register_cluster_groups(nvp_conf) nvp_conf.log_opt_values(LOG, logging.DEBUG) @@ -104,7 +110,7 @@ def parse_config(): 'default_l3_gw_service_uuid': nvp_conf[cluster_name].default_l3_gw_service_uuid}) LOG.debug(_("Cluster options:%s"), clusters_options) - return nvp_options, clusters_options + return cfg.CONF.NVP, clusters_options class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin): @@ -125,7 +131,9 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, l3_db.L3_NAT_db_mixin, portsecurity_db.PortSecurityDbMixin, securitygroups_db.SecurityGroupDbMixin, - nvp_sec.NVPSecurityGroups, qos_db.NVPQoSDbMixin): + nvp_sec.NVPSecurityGroups, + qos_db.NVPQoSDbMixin, + nvp_meta.NvpMetadataAccess): """ NvpPluginV2 is a Quantum plugin that provides L2 Virtual Network functionality using NVP. @@ -671,26 +679,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, "logical network %s"), network.id) raise nvp_exc.NvpNoMorePortsException(network=network.id) - def _ensure_metadata_host_route(self, context, fixed_ip_data, - is_delete=False): - subnet = self._get_subnet(context, fixed_ip_data['subnet_id']) - metadata_routes = [r for r in subnet.routes - if r['destination'] == '169.254.169.254/32'] - if metadata_routes: - # We should have only a single metadata route at any time - # because the route logic forbids two routes with the same - # destination. Update next hop with the provided IP address - if not is_delete: - metadata_routes[0].nexthop = fixed_ip_data['ip_address'] - else: - context.session.delete(metadata_routes[0]) - else: - # add the metadata route - route = models_v2.Route(subnet_id=subnet.id, - destination='169.254.169.254/32', - nexthop=fixed_ip_data['ip_address']) - context.session.add(route) - def setup_rpc(self): # RPC support for dhcp self.topic = topics.PLUGIN @@ -1100,16 +1088,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, with context.session.begin(subtransactions=True): # First we allocate port in quantum database quantum_db = super(NvpPluginV2, self).create_port(context, port) - # If we have just created a dhcp port, and metadata request are - # forwarded there, we need to verify the appropriate host route is - # in place - if (cfg.CONF.metadata_dhcp_host_route and - (quantum_db.get('device_owner') == - constants.DEVICE_OWNER_DHCP)): - if (quantum_db.get('fixed_ips') and - len(quantum_db.get('fixed_ips'))): - self._ensure_metadata_host_route( - context, quantum_db.get('fixed_ips')[0]) # Update fields obtained from quantum db (eg: MAC address) port["port"].update(quantum_db) # port security extension checks @@ -1172,16 +1150,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, # copy values over ret_port.update(port['port']) - # TODO(salvatore-orlando): We might need transaction management - # but the change for metadata support should not be too disruptive - fixed_ip_data = port['port'].get('fixed_ips') - if (cfg.CONF.metadata_dhcp_host_route and - ret_port.get('device_owner') == constants.DEVICE_OWNER_DHCP - and fixed_ip_data): - self._ensure_metadata_host_route(context, - fixed_ip_data[0], - is_delete=True) - # populate port_security setting if psec.PORTSECURITY not in port['port']: ret_port[psec.PORTSECURITY] = self._get_port_security_binding( @@ -1526,6 +1494,10 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, order=NVP_EXTGW_NAT_RULES_ORDER, match_criteria={'source_ip_addresses': subnet['cidr']}) + # Ensure the NVP logical router has a connection to a 'metadata access' + # network (with a proxy listening on its DHCP port), by creating it + # if needed. + self._handle_metadata_access_network(context, router_id) LOG.debug(_("Add_router_interface completed for subnet:%(subnet_id)s " "and router:%(router_id)s"), {'subnet_id': subnet_id, 'router_id': router_id}) @@ -1585,6 +1557,11 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, {'q_port_id': port_id, 'nvp_port_id': lport['uuid']}) return + + # Ensure the connection to the 'metadata access network' + # is removed (with the network) if this the last subnet + # on the router + self._handle_metadata_access_network(context, router_id) try: if not subnet: subnet = self._get_subnet(context, subnet_id) diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/common/config.py b/quantum/plugins/nicira/nicira_nvp_plugin/common/config.py index fc98c7e8d..b26ae26ad 100644 --- a/quantum/plugins/nicira/nicira_nvp_plugin/common/config.py +++ b/quantum/plugins/nicira/nicira_nvp_plugin/common/config.py @@ -34,6 +34,9 @@ nvp_opts = [ "(default -1 meaning do not time out)")), cfg.StrOpt('default_cluster_name', help=_("Default cluster name")), + cfg.BoolOpt('enable_metadata_access_network', default=True, + help=_("Enables dedicated connection to the metadata proxy " + "for metadata server access via Quantum router")), ] cluster_opts = [ diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/common/metadata_access.py b/quantum/plugins/nicira/nicira_nvp_plugin/common/metadata_access.py new file mode 100644 index 000000000..8ec2c1ead --- /dev/null +++ b/quantum/plugins/nicira/nicira_nvp_plugin/common/metadata_access.py @@ -0,0 +1,150 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Nicira, Inc. +# 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 equired 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. +# +# @author: Salvatore Orlando, VMware + +import netaddr + +from quantum.api.v2 import attributes +from quantum.common import constants +from quantum.common import exceptions as q_exc +from quantum.db import l3_db +from quantum.openstack.common import cfg +from quantum.openstack.common import log as logging +from quantum.openstack.common.notifier import api as notifier_api +from quantum.plugins.nicira.nicira_nvp_plugin.common import (exceptions + as nvp_exc) +from quantum.plugins.nicira.nicira_nvp_plugin import NvpApiClient + + +LOG = logging.getLogger(__name__) + +METADATA_DEFAULT_PREFIX = 30 +METADATA_SUBNET_CIDR = '169.254.169.252/%d' % METADATA_DEFAULT_PREFIX +METADATA_GATEWAY_IP = '169.254.169.253' + + +class NvpMetadataAccess(object): + + def _find_metadata_port(self, context, ports): + for port in ports: + for fixed_ip in port['fixed_ips']: + cidr = netaddr.IPNetwork( + self.get_subnet(context, fixed_ip['subnet_id'])['cidr']) + if cidr in netaddr.IPNetwork(METADATA_SUBNET_CIDR): + return port + + def _create_metadata_access_network(self, context, router_id): + # This will still ensure atomicity on Quantum DB + # context.elevated() creates a deep-copy context + ctx_elevated = context.elevated() + with ctx_elevated.session.begin(subtransactions=True): + # Add network + # Network name is likely to be truncated on NVP + + net_data = {'name': ('meta-%s' % router_id)[:40], + 'tenant_id': '', # intentionally not set + 'admin_state_up': True, + 'port_security_enabled': False, + 'shared': False, + 'status': constants.NET_STATUS_ACTIVE} + meta_net = self.create_network(ctx_elevated, + {'network': net_data}) + # Add subnet + subnet_data = {'network_id': meta_net['id'], + 'tenant_id': '', # intentionally not set + 'name': 'meta-%s' % router_id, + 'ip_version': 4, + 'shared': False, + 'cidr': METADATA_SUBNET_CIDR, + 'enable_dhcp': True, + # Ensure default allocation pool is generated + 'allocation_pools': attributes.ATTR_NOT_SPECIFIED, + 'gateway_ip': METADATA_GATEWAY_IP, + 'dns_nameservers': [], + 'host_routes': []} + meta_sub = self.create_subnet(ctx_elevated, + {'subnet': subnet_data}) + self.add_router_interface(ctx_elevated, router_id, + {'subnet_id': meta_sub['id']}) + # We need to send a notification to the dhcp agent in order + # to start the metadata agent proxy + # Note: the publisher id is the same used in the api module + notifier_api.notify(context, + notifier_api.publisher_id('network'), + 'network.create.end', + notifier_api.CONF.default_notification_level, + {'network': meta_net}) + + def _destroy_metadata_access_network(self, context, router_id, ports): + + # context.elevated() creates a deep-copy context + ctx_elevated = context.elevated() + # This will still ensure atomicity on Quantum DB + with ctx_elevated.session.begin(subtransactions=True): + if ports: + meta_port = self._find_metadata_port(ctx_elevated, ports) + if not meta_port: + return + meta_net_id = meta_port['network_id'] + self.remove_router_interface( + ctx_elevated, router_id, {'port_id': meta_port['id']}) + # Remove network (this will remove the subnet too) + self.delete_network(ctx_elevated, meta_net_id) + # We need to send a notification to the dhcp agent in order + # to stop the metadata agent proxy + # Note: the publisher id is the same used in the api module + notifier_api.notify( + context, + notifier_api.publisher_id('network'), + 'network.delete.end', + notifier_api.CONF.default_notification_level, + {'network_id': meta_net_id}) + + def _handle_metadata_access_network(self, context, router_id): + if not cfg.CONF.NVP.enable_metadata_access_network: + LOG.debug(_("Metadata access network is disabled")) + return + # As we'll use a different device_owner for metadata interface + # this query will return only 'real' router interfaces + ctx_elevated = context.elevated() + device_filter = {'device_id': [router_id], + 'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]} + with ctx_elevated.session.begin(subtransactions=True): + ports = self.get_ports(ctx_elevated, filters=device_filter) + try: + if ports: + if not self._find_metadata_port(ctx_elevated, ports): + self._create_metadata_access_network(context, + router_id) + elif len(ports) == 1: + # The only port left if the metadata port + self._destroy_metadata_access_network(context, + router_id, + ports) + else: + LOG.debug(_("No router interface found for router '%s'. " + "No metadata access network should be " + "created or destroyed"), router_id) + # TODO(salvatore-orlando): A better exception handling in the + # NVP plugin would allow us to improve error handling here + except (q_exc.QuantumException, nvp_exc.NvpPluginException, + NvpApiClient.NvpApiException): + # Any exception here should be regarded as non-fatal + LOG.exception(_("An error occurred while operating on the " + "metadata access network for router:'%s'"), + router_id) diff --git a/quantum/tests/unit/nicira/test_nicira_plugin.py b/quantum/tests/unit/nicira/test_nicira_plugin.py index 261120fc4..d5c527836 100644 --- a/quantum/tests/unit/nicira/test_nicira_plugin.py +++ b/quantum/tests/unit/nicira/test_nicira_plugin.py @@ -19,6 +19,7 @@ import os import mock from oslo.config import cfg +import netaddr import webob.exc import quantum.common.test_lib as test_lib @@ -82,6 +83,7 @@ class NiciraPluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase): instance.return_value.get_nvp_version.return_value = "2.999" instance.return_value.request.side_effect = _fake_request super(NiciraPluginV2TestCase, self).setUp(self._plugin_name) + cfg.CONF.set_override('enable_metadata_access_network', False, 'NVP') def tearDown(self): self.fc.reset_all() @@ -259,10 +261,120 @@ class TestNiciraSecurityGroup(ext_sg.TestSecurityGroups, class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, NiciraPluginV2TestCase): - def test_floatingip_with_assoc_fails(self): - self._test_floatingip_with_assoc_fails( - 'quantum.plugins.nicira.nicira_nvp_plugin.' - 'QuantumPlugin.NvpPluginV2') + def test_floatingip_with_assoc_fails(self): + self._test_floatingip_with_assoc_fails( + 'quantum.plugins.nicira.nicira_nvp_plugin.' + 'QuantumPlugin.NvpPluginV2') + + def _nvp_metadata_setup(self): + cfg.CONF.set_override('enable_metadata_access_network', True, 'NVP') + + def _nvp_metadata_teardown(self): + cfg.CONF.set_override('enable_metadata_access_network', False, 'NVP') + + def test_router_add_interface_subnet_with_metadata_access(self): + self._nvp_metadata_setup() + notifications = ['router.create.start', + 'router.create.end', + 'network.create.start', + 'network.create.end', + 'subnet.create.start', + 'subnet.create.end', + 'router.interface.create', + 'network.create.end', + 'router.interface.create', + 'router.interface.delete', + 'router.interface.delete', + 'network.delete.end'] + self.test_router_add_interface_subnet(exp_notifications=notifications) + self._nvp_metadata_teardown() + + def test_router_add_interface_port_with_metadata_access(self): + self._nvp_metadata_setup() + self.test_router_add_interface_port() + self._nvp_metadata_teardown() + + def test_router_add_interface_dupsubnet_returns_400_with_metadata(self): + self._nvp_metadata_setup() + self.test_router_add_interface_dup_subnet1_returns_400() + self._nvp_metadata_teardown() + + def test_router_add_interface_overlapped_cidr_returns_400_with(self): + self._nvp_metadata_setup() + self.test_router_add_interface_overlapped_cidr_returns_400() + self._nvp_metadata_teardown() + + def test_router_remove_interface_inuse_returns_409_with_metadata(self): + self._nvp_metadata_setup() + self.test_router_remove_interface_inuse_returns_409() + self._nvp_metadata_teardown() + + def test_router_remove_iface_wrong_sub_returns_409_with_metadata(self): + self._nvp_metadata_setup() + self.test_router_remove_interface_wrong_subnet_returns_409() + self._nvp_metadata_teardown() + + def test_router_delete_with_metadata_access(self): + self._nvp_metadata_setup() + self.test_router_delete() + self._nvp_metadata_teardown() + + def test_router_delete_with_port_existed_returns_409_with_metadata(self): + self._nvp_metadata_setup() + self.test_router_delete_with_port_existed_returns_409() + self._nvp_metadata_teardown() + + def test_metadatata_network_created_with_router_interface_add(self): + self._nvp_metadata_setup() + with self.router() as r: + with self.subnet() as s: + body = self._router_interface_action('add', + r['router']['id'], + s['subnet']['id'], + None) + r_ports = self._list('ports')['ports'] + self.assertEqual(len(r_ports), 2) + ips = [] + for port in r_ports: + ips.extend([netaddr.IPAddress(fixed_ip['ip_address']) + for fixed_ip in port['fixed_ips']]) + meta_cidr = netaddr.IPNetwork('169.254.0.0/16') + self.assertTrue(any([ip in meta_cidr for ip in ips])) + # Needed to avoid 409 + body = self._router_interface_action('remove', + r['router']['id'], + s['subnet']['id'], + None) + self._nvp_metadata_teardown() + + def test_metadatata_network_removed_with_router_interface_remove(self): + self._nvp_metadata_setup() + with self.router() as r: + with self.subnet() as s: + self._router_interface_action('add', r['router']['id'], + s['subnet']['id'], None) + subnets = self._list('subnets')['subnets'] + self.assertEqual(len(subnets), 2) + meta_cidr = netaddr.IPNetwork('169.254.0.0/16') + for subnet in subnets: + cidr = netaddr.IPNetwork(subnet['cidr']) + if meta_cidr == cidr or meta_cidr in cidr.supernet(16): + meta_sub_id = subnet['id'] + meta_net_id = subnet['network_id'] + ports = self._list( + 'ports', + query_params='network_id=%s' % meta_net_id)['ports'] + self.assertEqual(len(ports), 1) + meta_port_id = ports[0]['id'] + self._router_interface_action('remove', r['router']['id'], + s['subnet']['id'], None) + self._show('networks', meta_net_id, + webob.exc.HTTPNotFound.code) + self._show('ports', meta_port_id, + webob.exc.HTTPNotFound.code) + self._show('subnets', meta_sub_id, + webob.exc.HTTPNotFound.code) + self._nvp_metadata_teardown() class NvpQoSTestExtensionManager(object): diff --git a/quantum/tests/unit/test_dhcp_agent.py b/quantum/tests/unit/test_dhcp_agent.py index e04d35003..0b6e52537 100644 --- a/quantum/tests/unit/test_dhcp_agent.py +++ b/quantum/tests/unit/test_dhcp_agent.py @@ -14,7 +14,6 @@ # 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 socket import sys @@ -27,6 +26,7 @@ import unittest2 as unittest from quantum.agent.common import config from quantum.agent import dhcp_agent from quantum.agent.linux import interface +from quantum.common import constants from quantum.common import exceptions from quantum.openstack.common import jsonutils @@ -54,13 +54,20 @@ fake_subnet1 = FakeModel('bbbbbbbb-bbbb-bbbb-bbbbbbbbbbbb', fake_subnet2 = FakeModel('dddddddd-dddd-dddd-dddddddddddd', network_id='12345678-1234-5678-1234567890ab', - enable_dhcp=False) + cidr='172.9.9.0/24', enable_dhcp=False) fake_subnet3 = FakeModel('bbbbbbbb-1111-2222-bbbbbbbbbbbb', network_id='12345678-1234-5678-1234567890ab', cidr='192.168.1.1/24', enable_dhcp=True) +fake_meta_subnet = FakeModel('bbbbbbbb-1111-2222-bbbbbbbbbbbb', + network_id='12345678-1234-5678-1234567890ab', + cidr='169.254.169.252/30', + gateway_ip='169.254.169.253', enable_dhcp=True) + fake_fixed_ip = FakeModel('', subnet=fake_subnet1, ip_address='172.9.9.9') +fake_meta_fixed_ip = FakeModel('', subnet=fake_meta_subnet, + ip_address='169.254.169.254') fake_port1 = FakeModel('12345678-1234-aaaa-1234567890ab', mac_address='aa:bb:cc:dd:ee:ff', @@ -71,12 +78,25 @@ fake_port2 = FakeModel('12345678-1234-aaaa-123456789000', mac_address='aa:bb:cc:dd:ee:99', network_id='12345678-1234-5678-1234567890ab') +fake_meta_port = FakeModel('12345678-1234-aaaa-1234567890ab', + mac_address='aa:bb:cc:dd:ee:ff', + network_id='12345678-1234-5678-1234567890ab', + device_owner=constants.DEVICE_OWNER_ROUTER_INTF, + device_id='forzanapoli', + fixed_ips=[fake_meta_fixed_ip]) + fake_network = FakeModel('12345678-1234-5678-1234567890ab', tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa', admin_state_up=True, subnets=[fake_subnet1, fake_subnet2], ports=[fake_port1]) +fake_meta_network = FakeModel('12345678-1234-5678-1234567890ab', + tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa', + admin_state_up=True, + subnets=[fake_meta_subnet], + ports=[fake_meta_port]) + fake_down_network = FakeModel('12345678-dddd-dddd-1234567890ab', tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa', admin_state_up=False, @@ -417,6 +437,27 @@ class TestDhcpAgentEventHandler(unittest.TestCase): mock.call().disable() ]) + def test_enable_isolated_metadata_proxy_with_metadata_network(self): + cfg.CONF.set_override('enable_metadata_network', True) + class_path = 'quantum.agent.linux.ip_lib.IPWrapper' + self.external_process_p.stop() + # Ensure the mock is restored if this test fail + try: + with mock.patch(class_path) as ip_wrapper: + self.dhcp.enable_isolated_metadata_proxy(fake_meta_network) + ip_wrapper.assert_has_calls([mock.call( + 'sudo', + 'qdhcp-12345678-1234-5678-1234567890ab'), + mock.call().netns.execute(['quantum-ns-metadata-proxy', + mock.ANY, + '--router_id=forzanapoli', + mock.ANY, + mock.ANY]) + ]) + finally: + self.external_process_p.start() + cfg.CONF.set_override('enable_metadata_network', False) + def test_network_create_end(self): payload = dict(network=dict(id=fake_network.id)) @@ -751,44 +792,61 @@ class TestDeviceManager(unittest.TestCase): self.device_exists = self.device_exists_p.start() self.dvr_cls_p = mock.patch('quantum.agent.linux.interface.NullDriver') + self.iproute_cls_p = mock.patch('quantum.agent.linux.' + 'ip_lib.IpRouteCommand') driver_cls = self.dvr_cls_p.start() + iproute_cls = self.iproute_cls_p.start() self.mock_driver = mock.MagicMock() self.mock_driver.DEV_NAME_LEN = ( interface.LinuxInterfaceDriver.DEV_NAME_LEN) + self.mock_iproute = mock.MagicMock() driver_cls.return_value = self.mock_driver + iproute_cls.return_value = self.mock_iproute def tearDown(self): self.dvr_cls_p.stop() self.device_exists_p.stop() + self.iproute_cls_p.stop() cfg.CONF.reset() - def _test_setup_helper(self, device_exists, reuse_existing=False): + def _test_setup_helper(self, device_exists, reuse_existing=False, + metadata_access_network=False, + net=None, port=None): + net = net or fake_network + port = port or fake_port1 plugin = mock.Mock() - plugin.get_dhcp_port.return_value = fake_port1 + plugin.get_dhcp_port.return_value = port or fake_port1 self.device_exists.return_value = device_exists self.mock_driver.get_device_name.return_value = 'tap12345678-12' dh = dhcp_agent.DeviceManager(cfg.CONF, plugin) - interface_name = dh.setup(fake_network, reuse_existing) + interface_name = dh.setup(net, reuse_existing) self.assertEqual(interface_name, 'tap12345678-12') plugin.assert_has_calls([ - mock.call.get_dhcp_port(fake_network.id, mock.ANY)]) + mock.call.get_dhcp_port(net.id, mock.ANY)]) - namespace = dhcp_agent.NS_PREFIX + fake_network.id + namespace = dhcp_agent.NS_PREFIX + net.id + if metadata_access_network: + expected_ips = ['169.254.169.254/30'] + else: + expected_ips = ['172.9.9.9/24', '169.254.169.254/16'] expected = [mock.call.init_l3('tap12345678-12', - ['172.9.9.9/24', '169.254.169.254/16'], + expected_ips, namespace=namespace)] if not reuse_existing: expected.insert(0, - mock.call.plug(fake_network.id, - fake_port1.id, + mock.call.plug(net.id, + port.id, 'tap12345678-12', 'aa:bb:cc:dd:ee:ff', namespace=namespace)) + if metadata_access_network: + self.mock_iproute.assert_has_calls( + [mock.call.add_gateway('169.254.169.253')]) self.mock_driver.assert_has_calls(expected) @@ -802,6 +860,11 @@ class TestDeviceManager(unittest.TestCase): def test_setup_device_exists_reuse(self): self._test_setup_helper(True, True) + def test_setup_with_metadata_access_network(self): + cfg.CONF.set_override('enable_metadata_network', True) + self._test_setup_helper(False, metadata_access_network=True, + net=fake_meta_network, port=fake_meta_port) + def test_destroy(self): fake_network = FakeModel('12345678-1234-5678-1234567890ab', tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa') diff --git a/quantum/tests/unit/test_l3_plugin.py b/quantum/tests/unit/test_l3_plugin.py index 0fb090cdb..7e6d2f729 100644 --- a/quantum/tests/unit/test_l3_plugin.py +++ b/quantum/tests/unit/test_l3_plugin.py @@ -588,7 +588,16 @@ class L3NatDBTestCase(L3NatTestCaseBase): fip['floatingip']['router_id'], None, expected_code=exc.HTTPConflict.code) - def test_router_add_interface_subnet(self): + def test_router_add_interface_subnet(self, exp_notifications=None): + if not exp_notifications: + exp_notifications = ['router.create.start', + 'router.create.end', + 'network.create.start', + 'network.create.end', + 'subnet.create.start', + 'subnet.create.end', + 'router.interface.create', + 'router.interface.delete'] with self.router() as r: with self.subnet() as s: body = self._router_interface_action('add', @@ -609,17 +618,9 @@ class L3NatDBTestCase(L3NatTestCaseBase): body = self._show('ports', r_port_id, expected_code=exc.HTTPNotFound.code) - self.assertEqual(len(test_notifier.NOTIFICATIONS), 8) self.assertEqual( set(n['event_type'] for n in test_notifier.NOTIFICATIONS), - set(['router.create.start', - 'router.create.end', - 'network.create.start', - 'network.create.end', - 'subnet.create.start', - 'subnet.create.end', - 'router.interface.create', - 'router.interface.delete'])) + set(exp_notifications)) def test_router_add_interface_subnet_with_bad_tenant_returns_404(self): with mock.patch('quantum.context.Context.to_dict') as tdict: -- 2.45.2