Merge "Retry dhcp_release on failures"
This commit is contained in:
commit
e82c84b0e0
@ -58,6 +58,8 @@ METADATA_PORT = 80
|
|||||||
WIN2k3_STATIC_DNS = 249
|
WIN2k3_STATIC_DNS = 249
|
||||||
NS_PREFIX = 'qdhcp-'
|
NS_PREFIX = 'qdhcp-'
|
||||||
DNSMASQ_SERVICE_NAME = 'dnsmasq'
|
DNSMASQ_SERVICE_NAME = 'dnsmasq'
|
||||||
|
DHCP_RELEASE_TRIES = 3
|
||||||
|
DHCP_RELEASE_TRIES_SLEEP = 0.3
|
||||||
|
|
||||||
|
|
||||||
class DictModel(dict):
|
class DictModel(dict):
|
||||||
@ -470,10 +472,10 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
"will not call it again.")
|
"will not call it again.")
|
||||||
return self._IS_DHCP_RELEASE6_SUPPORTED
|
return self._IS_DHCP_RELEASE6_SUPPORTED
|
||||||
|
|
||||||
def _release_lease(self, mac_address, ip, client_id=None,
|
def _release_lease(self, mac_address, ip, ip_version, client_id=None,
|
||||||
server_id=None, iaid=None):
|
server_id=None, iaid=None):
|
||||||
"""Release a DHCP lease."""
|
"""Release a DHCP lease."""
|
||||||
if netaddr.IPAddress(ip).version == constants.IP_VERSION_6:
|
if ip_version == constants.IP_VERSION_6:
|
||||||
if not self._is_dhcp_release6_supported():
|
if not self._is_dhcp_release6_supported():
|
||||||
return
|
return
|
||||||
cmd = ['dhcp_release6', '--iface', self.interface_name,
|
cmd = ['dhcp_release6', '--iface', self.interface_name,
|
||||||
@ -754,15 +756,12 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
LOG.debug('Error while reading hosts file %s', filename)
|
LOG.debug('Error while reading hosts file %s', filename)
|
||||||
return leases
|
return leases
|
||||||
|
|
||||||
def _read_v6_leases_file_leases(self, filename):
|
def _read_leases_file_leases(self, filename, ip_version=None):
|
||||||
"""
|
"""
|
||||||
reading information from leases file which is needed to pass to
|
Read information from leases file, which is needed to pass to
|
||||||
dhcp_release6 command line utility if some of these leases are not
|
dhcp_release6 command line utility if some of these leases are not
|
||||||
needed anymore
|
needed anymore
|
||||||
|
|
||||||
in this method ipv4 entries in leases file are ignored, as info in
|
|
||||||
hosts file is enough
|
|
||||||
|
|
||||||
each line in dnsmasq leases file is one of the following
|
each line in dnsmasq leases file is one of the following
|
||||||
* duid entry: duid server_duid
|
* duid entry: duid server_duid
|
||||||
There MUST be single duid entry per file
|
There MUST be single duid entry per file
|
||||||
@ -791,7 +790,8 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
dnsmasq-discuss/2016q2/010595.html
|
dnsmasq-discuss/2016q2/010595.html
|
||||||
|
|
||||||
:param filename: leases file
|
:param filename: leases file
|
||||||
:return: dict, keys are IPv6 addresses, values are dicts containing
|
:param ip_version: IP version of entries to return, or None for all
|
||||||
|
:return: dict, keys are IP(v6) addresses, values are dicts containing
|
||||||
iaid, client_id and server_id
|
iaid, client_id and server_id
|
||||||
"""
|
"""
|
||||||
leases = {}
|
leases = {}
|
||||||
@ -812,7 +812,8 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
parts = l.strip().split()
|
parts = l.strip().split()
|
||||||
(iaid, ip, client_id) = parts[1], parts[2], parts[4]
|
(iaid, ip, client_id) = parts[1], parts[2], parts[4]
|
||||||
ip = ip.strip('[]')
|
ip = ip.strip('[]')
|
||||||
if netaddr.IPAddress(ip).version == constants.IP_VERSION_4:
|
if (ip_version and
|
||||||
|
netaddr.IPAddress(ip).version != ip_version):
|
||||||
continue
|
continue
|
||||||
leases[ip] = {'iaid': iaid,
|
leases[ip] = {'iaid': iaid,
|
||||||
'client_id': client_id,
|
'client_id': client_id,
|
||||||
@ -824,26 +825,68 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
filename = self.get_conf_file_name('host')
|
filename = self.get_conf_file_name('host')
|
||||||
old_leases = self._read_hosts_file_leases(filename)
|
old_leases = self._read_hosts_file_leases(filename)
|
||||||
leases_filename = self.get_conf_file_name('leases')
|
leases_filename = self.get_conf_file_name('leases')
|
||||||
# here is dhcpv6 stuff needed to craft dhcpv6 packet
|
cur_leases = self._read_leases_file_leases(leases_filename)
|
||||||
v6_leases = self._read_v6_leases_file_leases(leases_filename)
|
if not cur_leases:
|
||||||
|
return
|
||||||
|
|
||||||
|
v4_leases = set()
|
||||||
|
for (k, v) in cur_leases.items():
|
||||||
|
# IPv4 leases have a MAC, IPv6 ones do not, so we must ignore
|
||||||
|
if netaddr.IPAddress(k).version == constants.IP_VERSION_4:
|
||||||
|
# treat '*' as None, see note in _read_leases_file_leases()
|
||||||
|
client_id = v['client_id']
|
||||||
|
if client_id is '*':
|
||||||
|
client_id = None
|
||||||
|
v4_leases.add((k, v['iaid'], client_id))
|
||||||
|
|
||||||
new_leases = set()
|
new_leases = set()
|
||||||
for port in self.network.ports:
|
for port in self.network.ports:
|
||||||
client_id = self._get_client_id(port)
|
client_id = self._get_client_id(port)
|
||||||
for alloc in port.fixed_ips:
|
for alloc in port.fixed_ips:
|
||||||
new_leases.add((alloc.ip_address, port.mac_address, client_id))
|
new_leases.add((alloc.ip_address, port.mac_address, client_id))
|
||||||
|
|
||||||
for ip, mac, client_id in old_leases - new_leases:
|
# If an entry is in the leases or host file(s), but doesn't have
|
||||||
entry = v6_leases.get(ip, None)
|
# a fixed IP on a corresponding neutron port, consider it stale.
|
||||||
version = netaddr.IPAddress(ip).version
|
entries_to_release = (v4_leases | old_leases) - new_leases
|
||||||
if entry:
|
if not entries_to_release:
|
||||||
# must release IPv6 lease
|
return
|
||||||
self._release_lease(mac, ip, entry['client_id'],
|
|
||||||
entry['server_id'], entry['iaid'])
|
# Try DHCP_RELEASE_TRIES times to release a lease, re-reading the
|
||||||
# must release only if v4 lease. If we have ipv6 address missing
|
# file each time to see if it's still there. We loop +1 times to
|
||||||
# in old_leases, that means it's released already and nothing to do
|
# check the lease file one last time before logging any remaining
|
||||||
# here
|
# entries.
|
||||||
elif version == constants.IP_VERSION_4:
|
for i in range(DHCP_RELEASE_TRIES + 1):
|
||||||
self._release_lease(mac, ip, client_id)
|
entries_not_present = set()
|
||||||
|
for ip, mac, client_id in entries_to_release:
|
||||||
|
try:
|
||||||
|
entry = cur_leases[ip]
|
||||||
|
except KeyError:
|
||||||
|
entries_not_present.add((ip, mac, client_id))
|
||||||
|
continue
|
||||||
|
# if not the final loop, try and release
|
||||||
|
if i < DHCP_RELEASE_TRIES:
|
||||||
|
ip_version = netaddr.IPAddress(ip).version
|
||||||
|
if ip_version == constants.IP_VERSION_6:
|
||||||
|
client_id = entry['client_id']
|
||||||
|
self._release_lease(mac, ip, ip_version, client_id,
|
||||||
|
entry['server_id'], entry['iaid'])
|
||||||
|
|
||||||
|
# Remove elements that were not in the current leases file,
|
||||||
|
# no need to look for them again, and see if we're done.
|
||||||
|
entries_to_release -= entries_not_present
|
||||||
|
if not entries_to_release:
|
||||||
|
break
|
||||||
|
|
||||||
|
if i < DHCP_RELEASE_TRIES:
|
||||||
|
time.sleep(DHCP_RELEASE_TRIES_SLEEP)
|
||||||
|
cur_leases = self._read_leases_file_leases(leases_filename)
|
||||||
|
if not cur_leases:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
LOG.warning("Could not release DHCP leases for these IP "
|
||||||
|
"addresses after %d tries: %s",
|
||||||
|
DHCP_RELEASE_TRIES,
|
||||||
|
', '.join(ip for ip, m, c in entries_to_release))
|
||||||
|
|
||||||
def _output_addn_hosts_file(self):
|
def _output_addn_hosts_file(self):
|
||||||
"""Writes a dnsmasq compatible additional hosts file.
|
"""Writes a dnsmasq compatible additional hosts file.
|
||||||
|
@ -1915,18 +1915,26 @@ class TestDnsmasq(TestBase):
|
|||||||
mac1 = '00:00:80:aa:bb:cc'
|
mac1 = '00:00:80:aa:bb:cc'
|
||||||
ip2 = '192.168.1.3'
|
ip2 = '192.168.1.3'
|
||||||
mac2 = '00:00:80:cc:bb:aa'
|
mac2 = '00:00:80:cc:bb:aa'
|
||||||
ip3 = '0001:0002:0003:004:0005:0006:0007:0008'
|
ip3 = '0001:0002:0003:0004:0005:0006:0007:0008'
|
||||||
mac3 = '00:00:80:bb:aa:cc'
|
mac3 = '00:00:80:bb:aa:cc'
|
||||||
|
|
||||||
old_leases = {(ip1, mac1, None), (ip2, mac2, None), (ip3, mac3, None)}
|
old_leases = {(ip1, mac1, None), (ip2, mac2, None), (ip3, mac3, None)}
|
||||||
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
dnsmasq._read_v6_leases_file_leases = mock.Mock(
|
# Because the lease release code could fire multiple times, the
|
||||||
return_value={
|
# second read of the lease file must not have the entries that
|
||||||
'0001:0002:0003:004:0005:0006:0007:0008':
|
# would have been released.
|
||||||
{'iaid': 0xff,
|
dnsmasq._read_leases_file_leases = mock.Mock(
|
||||||
'client_id': 'client_id',
|
side_effect=[{ip1: {'iaid': mac1,
|
||||||
'server_id': 'server_id'}}
|
'client_id': 'client_id',
|
||||||
)
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip3: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
},
|
||||||
|
{}])
|
||||||
|
|
||||||
dnsmasq._output_hosts_file = mock.Mock()
|
dnsmasq._output_hosts_file = mock.Mock()
|
||||||
dnsmasq._release_lease = mock.Mock()
|
dnsmasq._release_lease = mock.Mock()
|
||||||
@ -1935,12 +1943,16 @@ class TestDnsmasq(TestBase):
|
|||||||
|
|
||||||
dnsmasq._release_unused_leases()
|
dnsmasq._release_unused_leases()
|
||||||
|
|
||||||
dnsmasq._release_lease.assert_has_calls([mock.call(mac1, ip1, None),
|
dnsmasq._release_lease.assert_has_calls([mock.call(mac1, ip1,
|
||||||
mock.call(mac2, ip2, None),
|
constants.IP_VERSION_4,
|
||||||
|
None, 'server_id', mac1),
|
||||||
|
mock.call(mac2, ip2,
|
||||||
|
constants.IP_VERSION_4,
|
||||||
|
None, 'server_id', mac2),
|
||||||
mock.call(mac3, ip3,
|
mock.call(mac3, ip3,
|
||||||
'client_id',
|
constants.IP_VERSION_6,
|
||||||
'server_id',
|
'client_id', 'server_id',
|
||||||
0xff),
|
0xff),
|
||||||
],
|
],
|
||||||
any_order=True)
|
any_order=True)
|
||||||
|
|
||||||
@ -1952,17 +1964,24 @@ class TestDnsmasq(TestBase):
|
|||||||
ip2 = '192.168.1.3'
|
ip2 = '192.168.1.3'
|
||||||
mac2 = '00:00:80:cc:bb:aa'
|
mac2 = '00:00:80:cc:bb:aa'
|
||||||
|
|
||||||
old_leases = set([(ip1, mac1, None), (ip2, mac2, None)])
|
old_leases = set([(ip1, mac1, 'client_id'), (ip2, mac2, None)])
|
||||||
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
dnsmasq._read_v6_leases_file_leases = mock.Mock(
|
# Because the lease release code could fire multiple times, the
|
||||||
return_value={'fdca:3ba5:a17a::1': {'iaid': 0xff,
|
# second read of the lease file must not have the entries that
|
||||||
'client_id': 'client_id',
|
# would have been released.
|
||||||
'server_id': 'server_id'}
|
dnsmasq._read_leases_file_leases = mock.Mock(
|
||||||
})
|
side_effect=[{ip1: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': None,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
},
|
||||||
|
{}])
|
||||||
ipw = mock.patch(
|
ipw = mock.patch(
|
||||||
'neutron.agent.linux.ip_lib.IpNetnsCommand.execute').start()
|
'neutron.agent.linux.ip_lib.IpNetnsCommand.execute').start()
|
||||||
dnsmasq._release_unused_leases()
|
dnsmasq._release_unused_leases()
|
||||||
# Verify that dhcp_release is called both for ipv4 and ipv6 addresses.
|
# Verify that dhcp_release is called both for ipv4 and ipv6 addresses.
|
||||||
self.assertEqual(2, ipw.call_count)
|
self.assertEqual(2, ipw.call_count)
|
||||||
ipw.assert_has_calls([mock.call(['dhcp_release6',
|
ipw.assert_has_calls([mock.call(['dhcp_release6',
|
||||||
'--iface', None, '--ip', ip1,
|
'--iface', None, '--ip', ip1,
|
||||||
@ -1981,7 +2000,7 @@ class TestDnsmasq(TestBase):
|
|||||||
|
|
||||||
old_leases = set([(ip1, mac1, None)])
|
old_leases = set([(ip1, mac1, None)])
|
||||||
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
dnsmasq._read_v6_leases_file_leases = mock.Mock(
|
dnsmasq._read_leases_file_leases = mock.Mock(
|
||||||
return_value={'fdca:3ba5:a17a::1': {'iaid': 0xff,
|
return_value={'fdca:3ba5:a17a::1': {'iaid': 0xff,
|
||||||
'client_id': 'client_id',
|
'client_id': 'client_id',
|
||||||
'server_id': 'server_id'}
|
'server_id': 'server_id'}
|
||||||
@ -2003,7 +2022,7 @@ class TestDnsmasq(TestBase):
|
|||||||
|
|
||||||
old_leases = set([(ip1, mac1, None), (ip2, mac2, None)])
|
old_leases = set([(ip1, mac1, None), (ip2, mac2, None)])
|
||||||
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
dnsmasq._read_v6_leases_file_leases = mock.Mock(
|
dnsmasq._read_leases_file_leases = mock.Mock(
|
||||||
return_value={ip6: {'iaid': 0xff,
|
return_value={ip6: {'iaid': 0xff,
|
||||||
'client_id': 'client_id',
|
'client_id': 'client_id',
|
||||||
'server_id': 'server_id'}
|
'server_id': 'server_id'}
|
||||||
@ -2031,11 +2050,24 @@ class TestDnsmasq(TestBase):
|
|||||||
|
|
||||||
old_leases = set([(ip1, mac1, client_id1), (ip2, mac2, client_id2)])
|
old_leases = set([(ip1, mac1, client_id1), (ip2, mac2, client_id2)])
|
||||||
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
dnsmasq._read_v6_leases_file_leases = mock.Mock(
|
# Because the lease release code could fire multiple times, the
|
||||||
return_value={ip6: {'iaid': 0xff,
|
# second read of the lease file must not have the entries that
|
||||||
|
# would have been released.
|
||||||
|
dnsmasq._read_leases_file_leases = mock.Mock(
|
||||||
|
side_effect=[{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip1: {'iaid': mac1,
|
||||||
|
'client_id': client_id1,
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': client_id2,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
},
|
||||||
|
{ip6: {'iaid': 0xff,
|
||||||
'client_id': 'client_id',
|
'client_id': 'client_id',
|
||||||
'server_id': 'server_id'}
|
'server_id': 'server_id'}
|
||||||
})
|
}])
|
||||||
dnsmasq._output_hosts_file = mock.Mock()
|
dnsmasq._output_hosts_file = mock.Mock()
|
||||||
dnsmasq._release_lease = mock.Mock()
|
dnsmasq._release_lease = mock.Mock()
|
||||||
dnsmasq.network.ports = []
|
dnsmasq.network.ports = []
|
||||||
@ -2043,8 +2075,10 @@ class TestDnsmasq(TestBase):
|
|||||||
dnsmasq._release_unused_leases()
|
dnsmasq._release_unused_leases()
|
||||||
|
|
||||||
dnsmasq._release_lease.assert_has_calls(
|
dnsmasq._release_lease.assert_has_calls(
|
||||||
[mock.call(mac1, ip1, client_id1),
|
[mock.call(mac1, ip1, constants.IP_VERSION_4, client_id1,
|
||||||
mock.call(mac2, ip2, client_id2)],
|
'server_id', mac1),
|
||||||
|
mock.call(mac2, ip2, constants.IP_VERSION_4, client_id2,
|
||||||
|
'server_id', mac2)],
|
||||||
any_order=True)
|
any_order=True)
|
||||||
|
|
||||||
def test_release_unused_leases_one_lease(self):
|
def test_release_unused_leases_one_lease(self):
|
||||||
@ -2058,11 +2092,21 @@ class TestDnsmasq(TestBase):
|
|||||||
|
|
||||||
old_leases = set([(ip1, mac1, None), (ip2, mac2, None)])
|
old_leases = set([(ip1, mac1, None), (ip2, mac2, None)])
|
||||||
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
dnsmasq._read_v6_leases_file_leases = mock.Mock(
|
# Because the lease release code could fire multiple times, the
|
||||||
return_value={ip6: {'iaid': 0xff,
|
# second read of the lease file must not have the entries that
|
||||||
|
# would have been released.
|
||||||
|
dnsmasq._read_leases_file_leases = mock.Mock(
|
||||||
|
side_effect=[{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': None,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
},
|
||||||
|
{ip6: {'iaid': 0xff,
|
||||||
'client_id': 'client_id',
|
'client_id': 'client_id',
|
||||||
'server_id': 'server_id'}
|
'server_id': 'server_id'}
|
||||||
})
|
}])
|
||||||
dnsmasq._output_hosts_file = mock.Mock()
|
dnsmasq._output_hosts_file = mock.Mock()
|
||||||
dnsmasq._release_lease = mock.Mock()
|
dnsmasq._release_lease = mock.Mock()
|
||||||
dnsmasq.network.ports = [FakePort1()]
|
dnsmasq.network.ports = [FakePort1()]
|
||||||
@ -2070,7 +2114,7 @@ class TestDnsmasq(TestBase):
|
|||||||
dnsmasq._release_unused_leases()
|
dnsmasq._release_unused_leases()
|
||||||
|
|
||||||
dnsmasq._release_lease.assert_called_once_with(
|
dnsmasq._release_lease.assert_called_once_with(
|
||||||
mac2, ip2, None)
|
mac2, ip2, constants.IP_VERSION_4, None, 'server_id', mac2)
|
||||||
|
|
||||||
def test_release_unused_leases_one_lease_with_client_id(self):
|
def test_release_unused_leases_one_lease_with_client_id(self):
|
||||||
dnsmasq = self._get_dnsmasq(FakeDualNetwork())
|
dnsmasq = self._get_dnsmasq(FakeDualNetwork())
|
||||||
@ -2086,18 +2130,138 @@ class TestDnsmasq(TestBase):
|
|||||||
old_leases = set([(ip1, mac1, client_id1), (ip2, mac2, client_id2)])
|
old_leases = set([(ip1, mac1, client_id1), (ip2, mac2, client_id2)])
|
||||||
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
dnsmasq._output_hosts_file = mock.Mock()
|
dnsmasq._output_hosts_file = mock.Mock()
|
||||||
dnsmasq._read_v6_leases_file_leases = mock.Mock(
|
# Because the lease release code could fire multiple times, the
|
||||||
return_value={ip6: {'iaid': 0xff,
|
# second read of the lease file must not have the entries that
|
||||||
|
# would have been released.
|
||||||
|
dnsmasq._read_leases_file_leases = mock.Mock(
|
||||||
|
side_effect=[{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip1: {'iaid': mac1,
|
||||||
|
'client_id': client_id1,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
},
|
||||||
|
{ip6: {'iaid': 0xff,
|
||||||
'client_id': 'client_id',
|
'client_id': 'client_id',
|
||||||
'server_id': 'server_id'}
|
'server_id': 'server_id'}
|
||||||
})
|
}])
|
||||||
dnsmasq._release_lease = mock.Mock()
|
dnsmasq._release_lease = mock.Mock()
|
||||||
dnsmasq.network.ports = [FakePort5()]
|
dnsmasq.network.ports = [FakePort5()]
|
||||||
|
|
||||||
dnsmasq._release_unused_leases()
|
dnsmasq._release_unused_leases()
|
||||||
|
|
||||||
dnsmasq._release_lease.assert_called_once_with(
|
dnsmasq._release_lease.assert_called_once_with(
|
||||||
mac1, ip1, client_id1)
|
mac1, ip1, constants.IP_VERSION_4, client_id1, 'server_id', mac1)
|
||||||
|
|
||||||
|
def test_release_unused_leases_one_lease_from_leases_file(self):
|
||||||
|
# leases file has a stale entry that is not in the host file
|
||||||
|
dnsmasq = self._get_dnsmasq(FakeDualNetwork())
|
||||||
|
|
||||||
|
ip1 = '192.168.0.2'
|
||||||
|
mac1 = '00:00:80:aa:bb:cc'
|
||||||
|
ip2 = '192.168.0.3'
|
||||||
|
mac2 = '00:00:80:cc:bb:aa'
|
||||||
|
ip6 = '2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d'
|
||||||
|
|
||||||
|
old_leases = set([(ip1, mac1, None)])
|
||||||
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
|
# Because the lease release code could fire multiple times, the
|
||||||
|
# second read of the lease file must not have the entries that
|
||||||
|
# would have been released.
|
||||||
|
dnsmasq._read_leases_file_leases = mock.Mock(
|
||||||
|
side_effect=[{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': None,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
},
|
||||||
|
{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
}])
|
||||||
|
dnsmasq._output_hosts_file = mock.Mock()
|
||||||
|
dnsmasq._release_lease = mock.Mock()
|
||||||
|
dnsmasq.network.ports = [FakePort1()]
|
||||||
|
|
||||||
|
dnsmasq._release_unused_leases()
|
||||||
|
|
||||||
|
dnsmasq._release_lease.assert_called_once_with(
|
||||||
|
mac2, ip2, constants.IP_VERSION_4, None, 'server_id', mac2)
|
||||||
|
|
||||||
|
@mock.patch.object(dhcp.LOG, 'warn')
|
||||||
|
def _test_release_unused_leases_one_lease_mult_times(self, mock_log_warn,
|
||||||
|
removed):
|
||||||
|
# Simulate a dhcp_release failure where the lease remains in the
|
||||||
|
# lease file despite multiple dhcp_release calls
|
||||||
|
dnsmasq = self._get_dnsmasq(FakeDualNetwork())
|
||||||
|
|
||||||
|
ip1 = '192.168.0.2'
|
||||||
|
mac1 = '00:00:80:aa:bb:cc'
|
||||||
|
ip2 = '192.168.0.3'
|
||||||
|
mac2 = '00:00:80:cc:bb:aa'
|
||||||
|
ip6 = '2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d'
|
||||||
|
|
||||||
|
old_leases = set([(ip1, mac1, None), (ip2, mac2, None)])
|
||||||
|
dnsmasq._read_hosts_file_leases = mock.Mock(return_value=old_leases)
|
||||||
|
# Because the lease release code could fire multiple times, the
|
||||||
|
# second and subsequent reads of the lease file must have the
|
||||||
|
# entries that were not released.
|
||||||
|
side_effect = [{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': None,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
},
|
||||||
|
{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': None,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
},
|
||||||
|
{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': None,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
}]
|
||||||
|
# entry did/didn't go away after final dhcp_release try
|
||||||
|
if not removed:
|
||||||
|
side_effect.append(
|
||||||
|
{ip6: {'iaid': 0xff,
|
||||||
|
'client_id': 'client_id',
|
||||||
|
'server_id': 'server_id'},
|
||||||
|
ip2: {'iaid': mac2,
|
||||||
|
'client_id': None,
|
||||||
|
'server_id': 'server_id'}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
side_effect.append({})
|
||||||
|
|
||||||
|
dnsmasq._read_leases_file_leases = mock.Mock(side_effect=side_effect)
|
||||||
|
dnsmasq._output_hosts_file = mock.Mock()
|
||||||
|
dnsmasq._release_lease = mock.Mock()
|
||||||
|
dnsmasq.network.ports = [FakePort1()]
|
||||||
|
|
||||||
|
dnsmasq._release_unused_leases()
|
||||||
|
|
||||||
|
self.assertEqual(dhcp.DHCP_RELEASE_TRIES,
|
||||||
|
dnsmasq._release_lease.call_count)
|
||||||
|
|
||||||
|
self.assertEqual(dhcp.DHCP_RELEASE_TRIES + 1,
|
||||||
|
dnsmasq._read_leases_file_leases.call_count)
|
||||||
|
|
||||||
|
if not removed:
|
||||||
|
self.assertTrue(mock_log_warn.called)
|
||||||
|
|
||||||
|
def test_release_unused_leases_one_lease_mult_times_not_removed(self):
|
||||||
|
self._test_release_unused_leases_one_lease_mult_times(False)
|
||||||
|
|
||||||
|
def test_release_unused_leases_one_lease_mult_times_removed(self):
|
||||||
|
self._test_release_unused_leases_one_lease_mult_times(True)
|
||||||
|
|
||||||
def test_read_hosts_file_leases(self):
|
def test_read_hosts_file_leases(self):
|
||||||
filename = '/path/to/file'
|
filename = '/path/to/file'
|
||||||
@ -2145,12 +2309,12 @@ class TestDnsmasq(TestBase):
|
|||||||
("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc",
|
("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc",
|
||||||
'client2')]), leases)
|
'client2')]), leases)
|
||||||
|
|
||||||
def test_read_v6_leases_file_leases(self):
|
def _test_read_leases_file_leases(self, ip_version):
|
||||||
filename = '/path/to/file'
|
filename = '/path/to/file'
|
||||||
lines = [
|
lines = [
|
||||||
"1472673289 aa:bb:cc:00:00:01 192.168.1.2 host-192-168-1-2 *",
|
"1472673289 aa:bb:cc:00:00:02 192.168.1.2 host-192-168-1-2 *",
|
||||||
"1472673289 aa:bb:cc:00:00:01 192.168.1.3 host-192-168-1-3 *",
|
"1472673289 aa:bb:cc:00:00:03 192.168.1.3 host-192-168-1-3 *",
|
||||||
"1472673289 aa:bb:cc:00:00:01 192.168.1.4 host-192-168-1-4 *",
|
"1472673289 aa:bb:cc:00:00:04 192.168.1.4 host-192-168-1-4 *",
|
||||||
"duid 00:01:00:01:02:03:04:05:06:07:08:09:0a:0b",
|
"duid 00:01:00:01:02:03:04:05:06:07:08:09:0a:0b",
|
||||||
"1472597740 1044800001 [2001:DB8::a] host-2001-db8--a "
|
"1472597740 1044800001 [2001:DB8::a] host-2001-db8--a "
|
||||||
"00:04:4a:d0:d2:34:19:2b:49:08:84:e8:34:bd:0c:dc:b9:3b",
|
"00:04:4a:d0:d2:34:19:2b:49:08:84:e8:34:bd:0c:dc:b9:3b",
|
||||||
@ -2164,14 +2328,13 @@ class TestDnsmasq(TestBase):
|
|||||||
|
|
||||||
dnsmasq = self._get_dnsmasq(FakeDualNetwork())
|
dnsmasq = self._get_dnsmasq(FakeDualNetwork())
|
||||||
with mock.patch('os.path.exists', return_value=True):
|
with mock.patch('os.path.exists', return_value=True):
|
||||||
leases = dnsmasq._read_v6_leases_file_leases(filename)
|
leases = dnsmasq._read_leases_file_leases(filename, ip_version)
|
||||||
server_id = '00:01:00:01:02:03:04:05:06:07:08:09:0a:0b'
|
server_id = '00:01:00:01:02:03:04:05:06:07:08:09:0a:0b'
|
||||||
entry1 = {'iaid': '1044800001',
|
entry1 = {'iaid': '1044800001',
|
||||||
'client_id': '00:04:4a:d0:d2:34:19:2b:49:08:84:'
|
'client_id': '00:04:4a:d0:d2:34:19:2b:49:08:84:'
|
||||||
'e8:34:bd:0c:dc:b9:3b',
|
'e8:34:bd:0c:dc:b9:3b',
|
||||||
'server_id': server_id
|
'server_id': server_id
|
||||||
}
|
}
|
||||||
|
|
||||||
entry2 = {'iaid': '1044800002',
|
entry2 = {'iaid': '1044800002',
|
||||||
'client_id': '00:04:ce:96:53:3d:f2:c2:4c:4c:81:'
|
'client_id': '00:04:ce:96:53:3d:f2:c2:4c:4c:81:'
|
||||||
'7d:db:c9:8d:d2:74:22:3b:0a',
|
'7d:db:c9:8d:d2:74:22:3b:0a',
|
||||||
@ -2182,13 +2345,46 @@ class TestDnsmasq(TestBase):
|
|||||||
'7f:5c:33:31:37:5d:80:77:b4',
|
'7f:5c:33:31:37:5d:80:77:b4',
|
||||||
'server_id': server_id
|
'server_id': server_id
|
||||||
}
|
}
|
||||||
expected = {'2001:DB8::a': entry1,
|
v6_expected = {'2001:DB8::a': entry1,
|
||||||
'2001:DB8::b': entry2,
|
'2001:DB8::b': entry2,
|
||||||
'2001:DB8::c': entry3
|
'2001:DB8::c': entry3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry4 = {'iaid': 'aa:bb:cc:00:00:02',
|
||||||
|
'client_id': '*',
|
||||||
|
'server_id': None
|
||||||
|
}
|
||||||
|
entry5 = {'iaid': 'aa:bb:cc:00:00:03',
|
||||||
|
'client_id': '*',
|
||||||
|
'server_id': None
|
||||||
|
}
|
||||||
|
entry6 = {'iaid': 'aa:bb:cc:00:00:04',
|
||||||
|
'client_id': '*',
|
||||||
|
'server_id': None
|
||||||
|
}
|
||||||
|
v4_expected = {'192.168.1.2': entry4,
|
||||||
|
'192.168.1.3': entry5,
|
||||||
|
'192.168.1.4': entry6
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = {}
|
||||||
|
if not ip_version or ip_version == constants.IP_VERSION_6:
|
||||||
|
expected.update(v6_expected)
|
||||||
|
|
||||||
|
if not ip_version or ip_version == constants.IP_VERSION_4:
|
||||||
|
expected.update(v4_expected)
|
||||||
|
|
||||||
self.assertEqual(expected, leases)
|
|
||||||
mock_open.assert_called_once_with(filename)
|
mock_open.assert_called_once_with(filename)
|
||||||
|
self.assertEqual(expected, leases)
|
||||||
|
|
||||||
|
def test_read_v6_leases_file_leases(self):
|
||||||
|
self._test_read_leases_file_leases(constants.IP_VERSION_6)
|
||||||
|
|
||||||
|
def test_read_v4_leases_file_leases(self):
|
||||||
|
self._test_read_leases_file_leases(constants.IP_VERSION_4)
|
||||||
|
|
||||||
|
def test_read_all_leases_file_leases(self):
|
||||||
|
self._test_read_leases_file_leases(None)
|
||||||
|
|
||||||
def test_make_subnet_interface_ip_map(self):
|
def test_make_subnet_interface_ip_map(self):
|
||||||
with mock.patch('neutron.agent.linux.ip_lib.IPDevice') as ip_dev:
|
with mock.patch('neutron.agent.linux.ip_lib.IPDevice') as ip_dev:
|
||||||
|
Loading…
Reference in New Issue
Block a user