From 9d800021293d8564ced1c071b6415d9a97b3ef49 Mon Sep 17 00:00:00 2001 From: shihanzhang Date: Tue, 30 Jun 2015 10:14:19 +0800 Subject: [PATCH] Unplug the VIF if dhcp port is deleted if user delete the dhcp port, dhcp-agent should unplug the VIF for this dhcp port, then the driver do 'reload_allocations' will raise a exception, dhcp-agent resync for this network and re-create dhcp port. Change-Id: I40b85033d075562c43ce4d0e68296211b3241197 Closes-bug: #1469615 --- neutron/agent/linux/dhcp.py | 9 ++++ neutron/tests/unit/agent/linux/test_dhcp.py | 55 +++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index 0a06259c1a0..f1f43b7bd92 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -657,14 +657,23 @@ class Dnsmasq(DhcpLocalProcess): old_leases = self._read_hosts_file_leases(filename) new_leases = set() + dhcp_port_exists = False + dhcp_port_on_this_host = self.device_manager.get_device_id( + self.network) for port in self.network.ports: client_id = self._get_client_id(port) for alloc in port.fixed_ips: new_leases.add((alloc.ip_address, port.mac_address, client_id)) + if port.device_id == dhcp_port_on_this_host: + dhcp_port_exists = True for ip, mac, client_id in old_leases - new_leases: self._release_lease(mac, ip, client_id) + if not dhcp_port_exists: + self.device_manager.driver.unplug( + self.interface_name, namespace=self.network.namespace) + def _output_addn_hosts_file(self): """Writes a dnsmasq compatible additional hosts file. diff --git a/neutron/tests/unit/agent/linux/test_dhcp.py b/neutron/tests/unit/agent/linux/test_dhcp.py index 41a3173d1f4..c453139bc2c 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -48,6 +48,19 @@ class DhcpOpt(object): return str(self.__dict__) +class FakeDhcpPort(object): + id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa' + admin_state_up = True + device_owner = 'network:dhcp' + fixed_ips = [FakeIPAllocation('192.168.0.1', + 'dddddddd-dddd-dddd-dddd-dddddddddddd')] + mac_address = '00:00:80:aa:bb:ee' + device_id = 'fake_dhcp_port' + + def __init__(self): + self.extra_dhcp_opts = [] + + class FakePort1(object): id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' admin_state_up = True @@ -55,6 +68,7 @@ class FakePort1(object): fixed_ips = [FakeIPAllocation('192.168.0.2', 'dddddddd-dddd-dddd-dddd-dddddddddddd')] mac_address = '00:00:80:aa:bb:cc' + device_id = 'fake_port1' def __init__(self): self.extra_dhcp_opts = [] @@ -67,6 +81,7 @@ class FakePort2(object): fixed_ips = [FakeIPAllocation('192.168.0.3', 'dddddddd-dddd-dddd-dddd-dddddddddddd')] mac_address = '00:00:f3:aa:bb:cc' + device_id = 'fake_port2' def __init__(self): self.extra_dhcp_opts = [] @@ -81,6 +96,7 @@ class FakePort3(object): FakeIPAllocation('192.168.1.2', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee')] mac_address = '00:00:0f:aa:bb:cc' + device_id = 'fake_port3' def __init__(self): self.extra_dhcp_opts = [] @@ -96,6 +112,7 @@ class FakePort4(object): FakeIPAllocation('ffda:3ba5:a17a:4ba3:0216:3eff:fec2:771d', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee')] mac_address = '00:16:3E:C2:77:1D' + device_id = 'fake_port4' def __init__(self): self.extra_dhcp_opts = [] @@ -108,6 +125,7 @@ class FakePort5(object): fixed_ips = [FakeIPAllocation('192.168.0.5', 'dddddddd-dddd-dddd-dddd-dddddddddddd')] mac_address = '00:00:0f:aa:bb:55' + device_id = 'fake_port5' def __init__(self): self.extra_dhcp_opts = [ @@ -122,6 +140,7 @@ class FakePort6(object): fixed_ips = [FakeIPAllocation('192.168.0.6', 'dddddddd-dddd-dddd-dddd-dddddddddddd')] mac_address = '00:00:0f:aa:bb:66' + device_id = 'fake_port6' def __init__(self): self.extra_dhcp_opts = [ @@ -140,6 +159,7 @@ class FakeV6Port(object): fixed_ips = [FakeIPAllocation('fdca:3ba5:a17a:4ba3::2', 'ffffffff-ffff-ffff-ffff-ffffffffffff')] mac_address = '00:00:f3:aa:bb:cc' + device_id = 'fake_port6' def __init__(self): self.extra_dhcp_opts = [] @@ -152,6 +172,7 @@ class FakeV6PortExtraOpt(object): fixed_ips = [FakeIPAllocation('ffea:3ba5:a17a:4ba3:0216:3eff:fec2:771d', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee')] mac_address = '00:16:3e:c2:77:1d' + device_id = 'fake_port6' def __init__(self): self.extra_dhcp_opts = [ @@ -169,6 +190,7 @@ class FakeDualPortWithV6ExtraOpt(object): FakeIPAllocation('ffea:3ba5:a17a:4ba3:0216:3eff:fec2:771d', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee')] mac_address = '00:16:3e:c2:77:1d' + device_id = 'fake_port6' def __init__(self): self.extra_dhcp_opts = [ @@ -186,6 +208,7 @@ class FakeDualPort(object): FakeIPAllocation('fdca:3ba5:a17a:4ba3::3', 'ffffffff-ffff-ffff-ffff-ffffffffffff')] mac_address = '00:00:0f:aa:bb:cc' + device_id = 'fake_dual_port' def __init__(self): self.extra_dhcp_opts = [] @@ -196,6 +219,7 @@ class FakeRouterPort(object): admin_state_up = True device_owner = constants.DEVICE_OWNER_ROUTER_INTF mac_address = '00:00:0f:rr:rr:rr' + device_id = 'fake_router_port' def __init__(self, dev_owner=constants.DEVICE_OWNER_ROUTER_INTF, ip_address='192.168.0.1'): @@ -212,6 +236,7 @@ class FakeRouterPort2(object): fixed_ips = [FakeIPAllocation('192.168.1.1', 'dddddddd-dddd-dddd-dddd-dddddddddddd')] mac_address = '00:00:0f:rr:rr:r2' + device_id = 'fake_router_port2' def __init__(self): self.extra_dhcp_opts = [] @@ -224,6 +249,7 @@ class FakePortMultipleAgents1(object): fixed_ips = [FakeIPAllocation('192.168.0.5', 'dddddddd-dddd-dddd-dddd-dddddddddddd')] mac_address = '00:00:0f:dd:dd:dd' + device_id = 'fake_multiple_agents_port' def __init__(self): self.extra_dhcp_opts = [] @@ -236,6 +262,7 @@ class FakePortMultipleAgents2(object): fixed_ips = [FakeIPAllocation('192.168.0.6', 'dddddddd-dddd-dddd-dddd-dddddddddddd')] mac_address = '00:00:0f:ee:ee:ee' + device_id = 'fake_multiple_agents_port2' def __init__(self): self.extra_dhcp_opts = [] @@ -437,6 +464,13 @@ class FakeDualNetwork(object): namespace = 'qdhcp-ns' +class FakeNetworkDhcpPort(object): + id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + subnets = [FakeV4Subnet()] + ports = [FakePort1(), FakeDhcpPort()] + namespace = 'qdhcp-ns' + + class FakeDualNetworkGatewayRoute(object): id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' subnets = [FakeV4SubnetGatewayRoute(), FakeV6SubnetDHCPStateful()] @@ -1425,12 +1459,33 @@ class TestDnsmasq(TestBase): dnsmasq._output_hosts_file = mock.Mock() dnsmasq._release_lease = mock.Mock() dnsmasq.network.ports = [] + dnsmasq.device_manager.driver.unplug = mock.Mock() dnsmasq._release_unused_leases() dnsmasq._release_lease.assert_has_calls([mock.call(mac1, ip1, None), mock.call(mac2, ip2, None)], any_order=True) + dnsmasq.device_manager.driver.unplug.assert_has_calls( + [mock.call(dnsmasq.interface_name, + namespace=dnsmasq.network.namespace)]) + + def test_release_unused_leases_with_dhcp_port(self): + dnsmasq = self._get_dnsmasq(FakeNetworkDhcpPort()) + ip1 = '192.168.1.2' + mac1 = '00:00:80:aa:bb:cc' + ip2 = '192.168.1.3' + mac2 = '00:00:80:cc:bb:aa' + + old_leases = set([(ip1, mac1, None), (ip2, mac2, None)]) + dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases) + dnsmasq._output_hosts_file = mock.Mock() + dnsmasq._release_lease = mock.Mock() + dnsmasq.device_manager.get_device_id = mock.Mock( + return_value='fake_dhcp_port') + dnsmasq._release_unused_leases() + self.assertFalse( + dnsmasq.device_manager.driver.unplug.called) def test_release_unused_leases_with_client_id(self): dnsmasq = self._get_dnsmasq(FakeDualNetwork())