From 1715eb7c8e1f2433df3c081e357f8c40dfe2a28a Mon Sep 17 00:00:00 2001 From: "Sean M. Collins" Date: Tue, 10 Jun 2014 15:20:49 -0400 Subject: [PATCH] Ensure entries in dnsmasq belong to a subnet using DHCP In certain configurations, Neutron calculates SLAAC addresses for IPv6 subnets and adds them to the fixed_ips field of a port. Since those subnets are not being managed by DHCP, do not add those fixed_ip entries to the host file. Closes-bug: #1316190 Related-bug: #1257446 Change-Id: I77dd55063296990c9df385f331f5de5d42402786 --- neutron/agent/linux/dhcp.py | 21 ++++++++-- neutron/tests/unit/test_linux_dhcp.py | 59 +++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index 84de415be40..c837fa56643 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -366,9 +366,14 @@ class Dnsmasq(DhcpLocalProcess): if subnet.ip_version == 4: mode = 'static' else: - # TODO(mark): how do we indicate other options - # ra-only, slaac, ra-nameservers, and ra-stateless. - mode = 'static' + # Note(scollins) If the IPv6 attributes are not set, set it as + # static to preserve previous behavior + if (not getattr(subnet, 'ipv6_ra_mode', None) and + not getattr(subnet, 'ipv6_address_mode', None)): + mode = 'static' + elif getattr(subnet, 'ipv6_ra_mode', None) is None: + # RA mode is not set - do not launch dnsmasq + continue if self.version >= self.MINIMUM_VERSION: set_tag = 'set:' else: @@ -445,8 +450,18 @@ class Dnsmasq(DhcpLocalProcess): name, # Host name and domain name in the format 'hostname.domain'. ) """ + v6_nets = dict((subnet.id, subnet) for subnet in + self.network.subnets if subnet.ip_version == 6) for port in self.network.ports: for alloc in port.fixed_ips: + # Note(scollins) Only create entries that are + # associated with the subnet being managed by this + # dhcp agent + if alloc.subnet_id in v6_nets: + ra_mode = v6_nets[alloc.subnet_id].ipv6_ra_mode + addr_mode = v6_nets[alloc.subnet_id].ipv6_address_mode + if (ra_mode is None and addr_mode == constants.IPV6_SLAAC): + continue hostname = 'host-%s' % alloc.ip_address.replace( '.', '-').replace(':', '-') fqdn = '%s.%s' % (hostname, self.conf.dhcp_domain) diff --git a/neutron/tests/unit/test_linux_dhcp.py b/neutron/tests/unit/test_linux_dhcp.py index bedf1755f44..691b27f4029 100644 --- a/neutron/tests/unit/test_linux_dhcp.py +++ b/neutron/tests/unit/test_linux_dhcp.py @@ -49,7 +49,8 @@ class FakePort1: id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' admin_state_up = True device_owner = 'foo1' - fixed_ips = [FakeIPAllocation('192.168.0.2')] + fixed_ips = [FakeIPAllocation('192.168.0.2', + 'dddddddd-dddd-dddd-dddd-dddddddddddd')] mac_address = '00:00:80:aa:bb:cc' def __init__(self): @@ -60,7 +61,8 @@ class FakePort2: id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' admin_state_up = False device_owner = 'foo2' - fixed_ips = [FakeIPAllocation('fdca:3ba5:a17a:4ba3::2')] + fixed_ips = [FakeIPAllocation('fdca:3ba5:a17a:4ba3::2', + 'ffffffff-ffff-ffff-ffff-ffffffffffff')] mac_address = '00:00:f3:aa:bb:cc' def __init__(self): @@ -71,14 +73,29 @@ class FakePort3: id = '44444444-4444-4444-4444-444444444444' admin_state_up = True device_owner = 'foo3' - fixed_ips = [FakeIPAllocation('192.168.0.3'), - FakeIPAllocation('fdca:3ba5:a17a:4ba3::3')] + fixed_ips = [FakeIPAllocation('192.168.0.3', + 'dddddddd-dddd-dddd-dddd-dddddddddddd'), + FakeIPAllocation('fdca:3ba5:a17a:4ba3::3', + 'ffffffff-ffff-ffff-ffff-ffffffffffff')] mac_address = '00:00:0f:aa:bb:cc' def __init__(self): self.extra_dhcp_opts = [] +class FakePort4: + + id = 'gggggggg-gggg-gggg-gggg-gggggggggggg' + admin_state_up = False + device_owner = 'foo3' + fixed_ips = [FakeIPAllocation('192.168.0.4', + 'ffda:3ba5:a17a:4ba3:0216:3eff:fec2:771d')] + mac_address = '00:16:3E:C2:77:1D' + + def __init__(self): + self.extra_dhcp_opts = [] + + class FakeRouterPort: id = 'rrrrrrrr-rrrr-rrrr-rrrr-rrrrrrrrrrrr' admin_state_up = True @@ -194,6 +211,8 @@ class FakeV6Subnet: enable_dhcp = True host_routes = [FakeV6HostRoute] dns_nameservers = ['2001:0200:feed:7ac0::1'] + ipv6_ra_mode = None + ipv6_address_mode = None class FakeV4SubnetNoDHCP: @@ -206,6 +225,17 @@ class FakeV4SubnetNoDHCP: dns_nameservers = [] +class FakeV6SubnetSlaac: + id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' + ip_version = 6 + cidr = 'ffda:3ba5:a17a:4ba3::/64' + gateway_ip = 'ffda:3ba5:a17a:4ba3::1' + enable_dhcp = True + host_routes = [FakeV6HostRoute] + ipv6_address_mode = constants.IPV6_SLAAC + ipv6_ra_mode = None + + class FakeV4SubnetNoGateway: id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' ip_version = 4 @@ -370,6 +400,13 @@ class FakeV4NetworkPxe3Ports: DhcpOpt(opt_name='bootfile-name', opt_value='pxelinux3.0')] +class FakeDualStackNetworkSingleDHCP: + id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' + + subnets = [FakeV4Subnet(), FakeV6SubnetSlaac()] + ports = [FakePort1(), FakePort4(), FakeRouterPort()] + + class LocalChild(dhcp.DhcpLocalProcess): PORTS = {4: [4], 6: [6]} @@ -1250,3 +1287,17 @@ tag:tag0,option:router""".lstrip() def test_check_version_failed_cmd_execution(self): self._check_version('Error while executing command', 0) + + def test_only_populates_dhcp_enabled_subnets(self): + exp_host_name = '/dhcp/eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee/host' + exp_host_data = ('00:00:80:aa:bb:cc,host-192-168-0-2.openstacklocal,' + '192.168.0.2\n' + '00:16:3E:C2:77:1D,host-192-168-0-4.openstacklocal,' + '192.168.0.4\n' + '00:00:0f:rr:rr:rr,host-192-168-0-1.openstacklocal,' + '192.168.0.1\n').lstrip() + dm = dhcp.Dnsmasq(self.conf, FakeDualStackNetworkSingleDHCP(), + version=float(2.59)) + dm._output_hosts_file() + self.safe.assert_has_calls([mock.call(exp_host_name, + exp_host_data)])