diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index c79f6577dbe..d47abfca1ec 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -512,7 +512,9 @@ class Dnsmasq(DhcpLocalProcess): # an IPv6 address because of stateless DHCPv6 network. host_name, # Host name. name, # Canonical hostname in the format 'hostname[.domain]'. - no_dhcp, # A flag indicating that the address doesn't need DHCP. + no_dhcp, # A flag indicating that the address doesn't need a DHCP + # IP address. + no_opts, # A flag indication that options shouldn't be written ) """ v6_nets = dict((subnet.id, subnet) for subnet in @@ -528,10 +530,14 @@ class Dnsmasq(DhcpLocalProcess): dns_ip_map = {d.ip_address: d for d in dns_assignment} for alloc in fixed_ips: no_dhcp = False + no_opts = False if alloc.subnet_id in v6_nets: addr_mode = v6_nets[alloc.subnet_id].ipv6_address_mode no_dhcp = addr_mode in (constants.IPV6_SLAAC, constants.DHCPV6_STATELESS) + # we don't setup anything for SLAAC. It doesn't make sense + # to provide options for a client that won't use DHCP + no_opts = addr_mode == constants.IPV6_SLAAC # If dns_name attribute is supported by ports API, return the # dns_assignment generated by the Neutron server. Otherwise, @@ -545,7 +551,7 @@ class Dnsmasq(DhcpLocalProcess): fqdn = hostname if self.conf.dhcp_domain: fqdn = '%s.%s' % (fqdn, self.conf.dhcp_domain) - yield (port, alloc, hostname, fqdn, no_dhcp) + yield (port, alloc, hostname, fqdn, no_dhcp, no_opts) def _get_port_extra_dhcp_opts(self, port): return getattr(port, edo_ext.EXTRADHCPOPTS, False) @@ -577,7 +583,8 @@ class Dnsmasq(DhcpLocalProcess): timestamp = int(time.time()) + self.conf.dhcp_lease_duration dhcp_enabled_subnet_ids = [s.id for s in self.network.subnets if s.enable_dhcp] - for (port, alloc, hostname, name, no_dhcp) in self._iter_hosts(): + for host_tuple in self._iter_hosts(): + port, alloc, hostname, name, no_dhcp, no_opts = host_tuple # don't write ip address which belongs to a dhcp disabled subnet # or an IPv6 SLAAC/stateless subnet if no_dhcp or alloc.subnet_id not in dhcp_enabled_subnet_ids: @@ -627,9 +634,10 @@ class Dnsmasq(DhcpLocalProcess): if s.enable_dhcp] # NOTE(ihrachyshka): the loop should not log anything inside it, to # avoid potential performance drop when lots of hosts are dumped - for (port, alloc, hostname, name, no_dhcp) in self._iter_hosts(): + for host_tuple in self._iter_hosts(): + port, alloc, hostname, name, no_dhcp, no_opts = host_tuple if no_dhcp: - if self._get_port_extra_dhcp_opts(port): + if not no_opts and self._get_port_extra_dhcp_opts(port): buf.write('%s,%s%s\n' % (port.mac_address, 'set:', port.id)) continue @@ -722,7 +730,8 @@ class Dnsmasq(DhcpLocalProcess): file. """ buf = six.StringIO() - for (port, alloc, hostname, fqdn, no_dhcp) in self._iter_hosts(): + for host_tuple in self._iter_hosts(): + port, alloc, hostname, fqdn, no_dhcp, no_opts = host_tuple # It is compulsory to write the `fqdn` before the `hostname` in # order to obtain it in PTR responses. if alloc: diff --git a/neutron/tests/unit/agent/linux/test_dhcp.py b/neutron/tests/unit/agent/linux/test_dhcp.py index 929cc2d35d6..c28c51c9233 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -729,6 +729,19 @@ class FakeDualStackNetworkSingleDHCP(object): ports = [FakePort1(), FakePort4(), FakeRouterPort()] +class FakeDualStackNetworkingSingleDHCPTags(object): + id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' + + subnets = [FakeV4Subnet(), FakeV6SubnetSlaac()] + ports = [FakePort1(), FakePort4(), FakeRouterPort()] + + def __init__(self): + for port in self.ports: + port.extra_dhcp_opts = [ + DhcpOpt(opt_name='tag:ipxe,bootfile-name', + opt_value='pxelinux.0')] + + class FakeV4NetworkMultipleTags(object): id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' subnets = [FakeV4Subnet()] @@ -1852,6 +1865,19 @@ class TestDnsmasq(TestBase): self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), mock.call(exp_opt_name, exp_opt_data)]) + def test_host_file_on_net_with_v6_slaac_and_v4(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,' + 'set:eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee\n' + '00:16:3E:C2:77:1D,host-192-168-0-4.openstacklocal.,192.168.0.4,' + 'set:gggggggg-gggg-gggg-gggg-gggggggggggg\n00:00:0f:rr:rr:rr,' + 'host-192-168-0-1.openstacklocal.,192.168.0.1,' + 'set:rrrrrrrr-rrrr-rrrr-rrrr-rrrrrrrrrrrr\n').lstrip() + dm = self._get_dnsmasq(FakeDualStackNetworkingSingleDHCPTags()) + dm._output_hosts_file() + self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data)]) + def test_host_and_opts_file_on_net_with_V6_stateless_and_V4_subnets( self): exp_host_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/host'