diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index 1a508c15c7..28d0a66f4d 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -16,6 +16,7 @@ # under the License. import abc +import collections import os import re import shutil @@ -459,6 +460,9 @@ class Dnsmasq(DhcpLocalProcess): subnet_to_interface_ip = self._make_subnet_interface_ip_map() options = [] + + dhcp_ips = collections.defaultdict(list) + subnet_idx_map = {} for i, subnet in enumerate(self.network.subnets): if not subnet.enable_dhcp: continue @@ -466,6 +470,10 @@ class Dnsmasq(DhcpLocalProcess): options.append( self._format_option(i, 'dns-server', ','.join(subnet.dns_nameservers))) + else: + # use the dnsmasq ip as nameservers only if there is no + # dns-server submitted by the server + subnet_idx_map[subnet.id] = i gateway = subnet.gateway_ip host_routes = [] @@ -503,6 +511,22 @@ class Dnsmasq(DhcpLocalProcess): self._format_option(port.id, opt.opt_name, opt.opt_value) for opt in port.extra_dhcp_opts) + # provides all dnsmasq ip as dns-server if there is more than + # one dnsmasq for a subnet and there is no dns-server submitted + # by the server + if port.device_owner == 'network:dhcp': + for ip in port.fixed_ips: + i = subnet_idx_map.get(ip.subnet_id) + if i is None: + continue + dhcp_ips[i].append(ip.ip_address) + + for i, ips in dhcp_ips.items(): + if len(ips) > 1: + options.append(self._format_option(i, + 'dns-server', + ','.join(ips))) + name = self.get_conf_file_name('opts') utils.replace_file(name, '\n'.join(options)) return name diff --git a/neutron/tests/unit/test_linux_dhcp.py b/neutron/tests/unit/test_linux_dhcp.py index 617ce21d2f..6d3cf8bd27 100644 --- a/neutron/tests/unit/test_linux_dhcp.py +++ b/neutron/tests/unit/test_linux_dhcp.py @@ -89,6 +89,30 @@ class FakeRouterPort: self.extra_dhcp_opts = [] +class FakePortMultipleAgents1: + id = 'rrrrrrrr-rrrr-rrrr-rrrr-rrrrrrrrrrrr' + admin_state_up = True + device_owner = 'network:dhcp' + fixed_ips = [FakeIPAllocation('192.168.0.5', + 'dddddddd-dddd-dddd-dddd-dddddddddddd')] + mac_address = '00:00:0f:dd:dd:dd' + + def __init__(self): + self.extra_dhcp_opts = [] + + +class FakePortMultipleAgents2: + id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + admin_state_up = True + device_owner = 'network:dhcp' + fixed_ips = [FakeIPAllocation('192.168.0.6', + 'dddddddd-dddd-dddd-dddd-dddddddddddd')] + mac_address = '00:00:0f:ee:ee:ee' + + def __init__(self): + self.extra_dhcp_opts = [] + + class FakeV4HostRoute: destination = '20.0.0.1/24' nexthop = '20.0.0.1' @@ -124,6 +148,42 @@ class FakeV4SubnetGatewayRoute: dns_nameservers = ['8.8.8.8'] +class FakeV4SubnetMultipleAgentsWithoutDnsProvided: + id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' + ip_version = 4 + cidr = '192.168.0.0/24' + gateway_ip = '192.168.0.1' + enable_dhcp = True + dns_nameservers = [] + host_routes = [] + + +class FakeV4MultipleAgentsWithoutDnsProvided: + id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' + subnets = [FakeV4SubnetMultipleAgentsWithoutDnsProvided()] + ports = [FakePort1(), FakePort2(), FakePort3(), FakeRouterPort(), + FakePortMultipleAgents1(), FakePortMultipleAgents2()] + namespace = 'qdhcp-ns' + + +class FakeV4SubnetMultipleAgentsWithDnsProvided: + id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' + ip_version = 4 + cidr = '192.168.0.0/24' + gateway_ip = '192.168.0.1' + enable_dhcp = True + dns_nameservers = ['8.8.8.8'] + host_routes = [] + + +class FakeV4MultipleAgentsWithDnsProvided: + id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' + subnets = [FakeV4SubnetMultipleAgentsWithDnsProvided()] + ports = [FakePort1(), FakePort2(), FakePort3(), FakeRouterPort(), + FakePortMultipleAgents1(), FakePortMultipleAgents2()] + namespace = 'qdhcp-ns' + + class FakeV6Subnet: id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' ip_version = 6 @@ -710,6 +770,30 @@ tag:tag1,249,%s,%s""".lstrip() % (fake_v6, self.safe.assert_called_once_with('/foo/opts', expected) + def test_output_opts_file_multiple_agents_without_dns_provided(self): + expected = """ +tag:tag0,option:router,192.168.0.1 +tag:tag0,option:dns-server,192.168.0.5,192.168.0.6""".lstrip() + with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn: + conf_fn.return_value = '/foo/opts' + dm = dhcp.Dnsmasq(self.conf, + FakeV4MultipleAgentsWithoutDnsProvided(), + version=float(2.59)) + dm._output_opts_file() + self.safe.assert_called_once_with('/foo/opts', expected) + + def test_output_opts_file_multiple_agents_with_dns_provided(self): + expected = """ +tag:tag0,option:dns-server,8.8.8.8 +tag:tag0,option:router,192.168.0.1""".lstrip() + with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn: + conf_fn.return_value = '/foo/opts' + dm = dhcp.Dnsmasq(self.conf, + FakeV4MultipleAgentsWithDnsProvided(), + version=float(2.59)) + dm._output_opts_file() + self.safe.assert_called_once_with('/foo/opts', expected) + def test_output_opts_file_single_dhcp(self): expected = """ tag:tag0,option:dns-server,8.8.8.8