diff --git a/neutron/agent/dhcp/agent.py b/neutron/agent/dhcp/agent.py index c83ec4cb28c..45d3421b2f8 100644 --- a/neutron/agent/dhcp/agent.py +++ b/neutron/agent/dhcp/agent.py @@ -271,14 +271,10 @@ class DhcpAgent(manager.Manager): if not network.admin_state_up: return - enable_metadata = self.dhcp_driver_cls.should_enable_metadata( - self.conf, network) - dhcp_network_enabled = False - for subnet in network.subnets: if subnet.enable_dhcp: if self.call_driver('enable', network): - dhcp_network_enabled = True + self.update_isolated_metadata_proxy(network) self.cache.put(network) # After enabling dhcp for network, mark all existing # ports as ready. So that the status of ports which are @@ -286,18 +282,6 @@ class DhcpAgent(manager.Manager): self.dhcp_ready_ports |= {p.id for p in network.ports} break - if enable_metadata and dhcp_network_enabled: - for subnet in network.subnets: - if subnet.ip_version == 4 and subnet.enable_dhcp: - self.enable_isolated_metadata_proxy(network) - break - elif (not self.conf.force_metadata and - not self.conf.enable_isolated_metadata): - # In the case that the dhcp agent ran with metadata enabled, - # and dhcp agent now starts with metadata disabled, check and - # delete any metadata_proxy. - self.disable_isolated_metadata_proxy(network) - def disable_dhcp_helper(self, network_id): """Disable DHCP for a network known to the agent.""" network = self.cache.get_network_by_id(network_id) @@ -337,6 +321,9 @@ class DhcpAgent(manager.Manager): elif self.call_driver('restart', network): self.cache.put(network) + # Update the metadata proxy after the dhcp driver has been updated + self.update_isolated_metadata_proxy(network) + @utils.synchronized('dhcp-agent') def network_create_end(self, context, payload): """Handle the network.create.end notification event.""" @@ -436,6 +423,20 @@ class DhcpAgent(manager.Manager): else: self.call_driver('reload_allocations', network) + def update_isolated_metadata_proxy(self, network): + """Spawn or kill metadata proxy. + + According to return from driver class, spawn or kill the metadata + proxy process. Spawn an existing metadata proxy or kill a nonexistent + metadata proxy will just silently return. + """ + should_enable_metadata = self.dhcp_driver_cls.should_enable_metadata( + self.conf, network) + if should_enable_metadata: + self.enable_isolated_metadata_proxy(network) + else: + self.disable_isolated_metadata_proxy(network) + def enable_isolated_metadata_proxy(self, network): # The proxy might work for either a single network diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index 78dfed41d0d..bcb412ef6c3 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -1070,7 +1070,9 @@ class Dnsmasq(DhcpLocalProcess): with 3rd party backends. """ if conf.force_metadata: - return True + # Only ipv4 subnet, with dhcp enabled, will use metadata proxy. + return any(s for s in network.subnets + if s.ip_version == 4 and s.enable_dhcp) if conf.enable_metadata_network and conf.enable_isolated_metadata: # check if the network has a metadata subnet @@ -1083,7 +1085,10 @@ class Dnsmasq(DhcpLocalProcess): return False isolated_subnets = cls.get_isolated_subnets(network) - return any(isolated_subnets[subnet.id] for subnet in network.subnets) + # Only ipv4 isolated subnet, which has dhcp enabled, will use + # metadata proxy. + return any(isolated_subnets[s.id] for s in network.subnets + if s.ip_version == 4 and s.enable_dhcp) class DeviceManager(object): diff --git a/neutron/tests/functional/agent/test_dhcp_agent.py b/neutron/tests/functional/agent/test_dhcp_agent.py index d14a3e7384a..9235b6690f6 100644 --- a/neutron/tests/functional/agent/test_dhcp_agent.py +++ b/neutron/tests/functional/agent/test_dhcp_agent.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import os.path import eventlet @@ -314,6 +315,45 @@ class DHCPAgentOVSTestCase(DHCPAgentOVSTestFramework): sleep=0.1, exception=RuntimeError("Stale metadata proxy didn't get killed")) + def _test_metadata_proxy_spawn_kill_with_subnet_create_delete(self): + network = self.network_dict_for_dhcp(ip_version=6) + self.configure_dhcp_for_network(network=network) + pm = self._get_metadata_proxy_process(network) + + # A newly created network with ipv6 subnet will not have metadata proxy + self.assertFalse(pm.active) + + new_network = copy.deepcopy(network) + dhcp_enabled_ipv4_subnet = self.create_subnet_dict(network.id) + new_network.subnets.append(dhcp_enabled_ipv4_subnet) + self.mock_plugin_api.get_network_info.return_value = new_network + self.agent.refresh_dhcp_helper(network.id) + # Metadata proxy should be spawned for the newly added subnet + common_utils.wait_until_true( + lambda: pm.active, + timeout=5, + sleep=0.1, + exception=RuntimeError("Metadata proxy didn't spawn")) + + self.mock_plugin_api.get_network_info.return_value = network + self.agent.refresh_dhcp_helper(network.id) + # Metadata proxy should be killed because network doesn't need it. + common_utils.wait_until_true( + lambda: not pm.active, + timeout=5, + sleep=0.1, + exception=RuntimeError("Metadata proxy didn't get killed")) + + def test_enable_isolated_metadata_for_subnet_create_delete(self): + self.conf.set_override('force_metadata', False) + self.conf.set_override('enable_isolated_metadata', True) + self._test_metadata_proxy_spawn_kill_with_subnet_create_delete() + + def test_force_metadata_for_subnet_create_delete(self): + self.conf.set_override('force_metadata', True) + self.conf.set_override('enable_isolated_metadata', False) + self._test_metadata_proxy_spawn_kill_with_subnet_create_delete() + def test_notify_port_ready_after_enable_dhcp(self): network = self.network_dict_for_dhcp() dhcp_port = self.create_port_dict( diff --git a/neutron/tests/unit/agent/dhcp/test_agent.py b/neutron/tests/unit/agent/dhcp/test_agent.py index 8b366cfd053..fa53d08323a 100644 --- a/neutron/tests/unit/agent/dhcp/test_agent.py +++ b/neutron/tests/unit/agent/dhcp/test_agent.py @@ -667,12 +667,10 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): self.external_process.assert_has_calls([ self._process_manager_constructor_call(), mock.call().enable()]) - elif not enable_isolated_metadata: + else: self.external_process.assert_has_calls([ self._process_manager_constructor_call(ns=None), mock.call().disable()]) - else: - self.assertFalse(self.external_process.call_count) def test_enable_dhcp_helper_enable_metadata_isolated_network(self): self._enable_dhcp_helper(isolated_network, diff --git a/neutron/tests/unit/agent/linux/test_dhcp.py b/neutron/tests/unit/agent/linux/test_dhcp.py index 48c7d4c6e75..d43d7b44621 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -2148,8 +2148,8 @@ class TestDnsmasq(TestBase): def test_should_force_metadata_returns_true(self): self.conf.set_override("force_metadata", True) - self.assertTrue(dhcp.Dnsmasq.should_enable_metadata(self.conf, - mock.ANY)) + self.assertTrue(dhcp.Dnsmasq.should_enable_metadata( + self.conf, FakeDualNetworkDualDHCP())) def _test__generate_opts_per_subnet_helper(self, config_opts, expected_mdt_ip):