diff --git a/etc/dhcp_agent.ini b/etc/dhcp_agent.ini index 0422baf847..a3c798f385 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 7a6b6a0655..54775489b9 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 97c384f0b4..3ea81a87d3 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 2a9c742d0b..1a4d1b19a6 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 fc98c7e8de..b26ae26ada 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 0000000000..8ec2c1eadb --- /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 261120fc42..d5c527836e 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 e04d350037..0b6e525378 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 0fb090cdba..7e6d2f7290 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: