From 784864d1785bcd32297865cae5a7a243b05ed30d Mon Sep 17 00:00:00 2001 From: Hong Hui Xiao Date: Mon, 14 Mar 2016 13:19:52 +0000 Subject: [PATCH] Update metadata proxy when subnet add/delete With current code, if first subnet of the network is an ipv6 subnet, the metadata proxy will not be spawned. If user then adds ipv4 subnet with dhcp enabled, the metadata proxy will still not be spawned. As a result, the metadata service will not be available for the network. This patch will kill/spawn metadata proxy, when subnet add/delete. So, even if the first subnet of the network is not an ipv4 subnet with dhcp enabled, the metadata proxy can still be spawned if network has subnets need metadata proxy. Closes-bug: #1556991 Change-Id: I0b45af8f2b756732f45c13d7e2dbcd30653cc026 --- neutron/agent/dhcp/agent.py | 35 ++++++++-------- neutron/agent/linux/dhcp.py | 9 ++++- .../tests/functional/agent/test_dhcp_agent.py | 40 +++++++++++++++++++ neutron/tests/unit/agent/dhcp/test_agent.py | 4 +- neutron/tests/unit/agent/linux/test_dhcp.py | 4 +- 5 files changed, 68 insertions(+), 24 deletions(-) diff --git a/neutron/agent/dhcp/agent.py b/neutron/agent/dhcp/agent.py index fec5babcca7..7c1bfc2105f 100644 --- a/neutron/agent/dhcp/agent.py +++ b/neutron/agent/dhcp/agent.py @@ -266,14 +266,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 @@ -281,18 +277,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) @@ -332,6 +316,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.""" @@ -418,6 +405,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 7f914af4826..30acb96cea4 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -1051,7 +1051,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 @@ -1064,7 +1066,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 b3b2a038287..b73473d8abc 100644 --- a/neutron/tests/unit/agent/dhcp/test_agent.py +++ b/neutron/tests/unit/agent/dhcp/test_agent.py @@ -665,12 +665,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 97bdba87c75..20cec9a0cc2 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -2078,8 +2078,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):