From 137a6d61053fb1cfb9a0a583b5a5c0f6253c75e6 Mon Sep 17 00:00:00 2001 From: Assaf Muller Date: Thu, 31 May 2018 14:24:00 -0400 Subject: [PATCH] Pass network's dns_domain to dnsmasq conf The Neutron API exposes the 'dns_domain' attribute on the Network model. Presently, deployments using the DHCP agent ignore this attribute when resolving DNS queries between instances. This patch changes that so that the DHCP agent will pass on the dns_domain to the network's dnsmasq process, in turn passing it to instances. UpgradeImpact Closes-Bug: 1774710 Change-Id: I6120d504959631f084d63458f6e9dada0dc5cbdf --- neutron/agent/linux/dhcp.py | 13 +-- neutron/tests/unit/agent/linux/test_dhcp.py | 98 +++++++++++-------- .../notes/dns_domain-6f0e628aeb3c650c.yaml | 13 +++ 3 files changed, 79 insertions(+), 45 deletions(-) create mode 100644 releasenotes/notes/dns_domain-6f0e628aeb3c650c.yaml diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index 00491586e05..aae3ef453bb 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -130,6 +130,7 @@ class DhcpBase(object): version=None, plugin=None): self.conf = conf self.network = network + self.dns_domain = self.network.get('dns_domain', self.conf.dns_domain) self.process_monitor = process_monitor self.device_manager = DeviceManager(self.conf, plugin) self.version = version @@ -419,8 +420,8 @@ class Dnsmasq(DhcpLocalProcess): for server in self.conf.dnsmasq_dns_servers: cmd.append('--server=%s' % server) - if self.conf.dns_domain: - cmd.append('--domain=%s' % self.conf.dns_domain) + if self.dns_domain: + cmd.append('--domain=%s' % self.dns_domain) if self.conf.dhcp_broadcast_reply: cmd.append('--dhcp-broadcast') @@ -610,8 +611,8 @@ class Dnsmasq(DhcpLocalProcess): hostname = 'host-%s' % alloc.ip_address.replace( '.', '-').replace(':', '-') fqdn = hostname - if self.conf.dns_domain: - fqdn = '%s.%s' % (fqdn, self.conf.dns_domain) + if self.dns_domain: + fqdn = '%s.%s' % (fqdn, self.dns_domain) yield (port, alloc, hostname, fqdn, no_dhcp, no_opts) def _get_port_extra_dhcp_opts(self, port): @@ -958,9 +959,9 @@ class Dnsmasq(DhcpLocalProcess): # dns-server submitted by the server subnet_index_map[subnet.id] = i - if self.conf.dns_domain and subnet.ip_version == 6: + if self.dns_domain and subnet.ip_version == 6: options.append('tag:tag%s,option6:domain-search,%s' % - (i, ''.join(self.conf.dns_domain))) + (i, ''.join(self.dns_domain))) gateway = subnet.gateway_ip host_routes = [] diff --git a/neutron/tests/unit/agent/linux/test_dhcp.py b/neutron/tests/unit/agent/linux/test_dhcp.py index 2a348bb7b43..07a10217712 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -461,7 +461,14 @@ class FakeV4SubnetAgentWithNoDnsProvided(FakeV4Subnet): self.host_routes = [] -class FakeV4MultipleAgentsWithoutDnsProvided(object): +class FakeNetworkBase(object): + dns_domain = 'openstacklocal' + + def get(self, attr, default=None): + return getattr(self, attr) or default + + +class FakeV4MultipleAgentsWithoutDnsProvided(FakeNetworkBase): def __init__(self): self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' self.subnets = [FakeV4SubnetMultipleAgentsWithoutDnsProvided()] @@ -470,7 +477,7 @@ class FakeV4MultipleAgentsWithoutDnsProvided(object): self.namespace = 'qdhcp-ns' -class FakeV4AgentWithoutDnsProvided(object): +class FakeV4AgentWithoutDnsProvided(FakeNetworkBase): def __init__(self): self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' self.subnets = [FakeV4SubnetMultipleAgentsWithoutDnsProvided()] @@ -479,7 +486,7 @@ class FakeV4AgentWithoutDnsProvided(object): self.namespace = 'qdhcp-ns' -class FakeV4AgentWithManyDnsProvided(object): +class FakeV4AgentWithManyDnsProvided(FakeNetworkBase): def __init__(self): self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' self.subnets = [FakeV4SubnetAgentWithManyDnsProvided()] @@ -488,7 +495,7 @@ class FakeV4AgentWithManyDnsProvided(object): self.namespace = 'qdhcp-ns' -class FakeV4AgentWithNoDnsProvided(object): +class FakeV4AgentWithNoDnsProvided(FakeNetworkBase): def __init__(self): self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' self.subnets = [FakeV4SubnetAgentWithNoDnsProvided()] @@ -503,7 +510,7 @@ class FakeV4SubnetMultipleAgentsWithDnsProvided(FakeV4Subnet): self.host_routes = [] -class FakeV4MultipleAgentsWithDnsProvided(object): +class FakeV4MultipleAgentsWithDnsProvided(FakeNetworkBase): def __init__(self): self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' self.subnets = [FakeV4SubnetMultipleAgentsWithDnsProvided()] @@ -621,7 +628,7 @@ class FakeV4SubnetNoRouter(FakeV4Subnet): self.dns_nameservers = [] -class FakeV4Network(object): +class FakeV4Network(FakeNetworkBase): def __init__(self): self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' self.subnets = [FakeV4Subnet()] @@ -629,7 +636,7 @@ class FakeV4Network(object): self.namespace = 'qdhcp-ns' -class FakeV4NetworkClientId(object): +class FakeV4NetworkClientId(FakeNetworkBase): def __init__(self): self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' self.subnets = [FakeV4Subnet()] @@ -637,7 +644,7 @@ class FakeV4NetworkClientId(object): self.namespace = 'qdhcp-ns' -class FakeV4NetworkClientIdNum(object): +class FakeV4NetworkClientIdNum(FakeNetworkBase): def __init__(self): self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' self.subnets = [FakeV4Subnet()] @@ -645,7 +652,7 @@ class FakeV4NetworkClientIdNum(object): self.namespace = 'qdhcp-ns' -class FakeV4NetworkClientIdNumStr(object): +class FakeV4NetworkClientIdNumStr(FakeNetworkBase): def __init__(self): self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' self.subnets = [FakeV4Subnet()] @@ -653,7 +660,7 @@ class FakeV4NetworkClientIdNumStr(object): self.namespace = 'qdhcp-ns' -class FakeV6Network(object): +class FakeV6Network(FakeNetworkBase): def __init__(self): self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' self.subnets = [FakeV6Subnet()] @@ -661,7 +668,7 @@ class FakeV6Network(object): self.namespace = 'qdhcp-ns' -class FakeDualNetwork(object): +class FakeDualNetwork(FakeNetworkBase): def __init__(self, domain='openstacklocal'): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV6SubnetDHCPStateful()] @@ -669,9 +676,10 @@ class FakeDualNetwork(object): self.ports = [FakePort1(domain=domain), FakeV6Port(domain=domain), FakeDualPort(domain=domain), FakeRouterPort(domain=domain)] + self.dns_domain = domain -class FakeDeviceManagerNetwork(object): +class FakeDeviceManagerNetwork(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV6SubnetDHCPStateful()] @@ -682,7 +690,7 @@ class FakeDeviceManagerNetwork(object): self.namespace = 'qdhcp-ns' -class FakeDualNetworkReserved(object): +class FakeDualNetworkReserved(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV6SubnetDHCPStateful()] @@ -691,7 +699,7 @@ class FakeDualNetworkReserved(object): self.namespace = 'qdhcp-ns' -class FakeDualNetworkReserved2(object): +class FakeDualNetworkReserved2(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV6SubnetDHCPStateful()] @@ -701,7 +709,7 @@ class FakeDualNetworkReserved2(object): self.namespace = 'qdhcp-ns' -class FakeNetworkDhcpPort(object): +class FakeNetworkDhcpPort(FakeNetworkBase): def __init__(self): self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' self.subnets = [FakeV4Subnet()] @@ -709,7 +717,7 @@ class FakeNetworkDhcpPort(object): self.namespace = 'qdhcp-ns' -class FakeDualNetworkGatewayRoute(object): +class FakeDualNetworkGatewayRoute(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4SubnetGatewayRoute(), FakeV6SubnetDHCPStateful()] @@ -717,7 +725,7 @@ class FakeDualNetworkGatewayRoute(object): self.namespace = 'qdhcp-ns' -class FakeDualNetworkSingleDHCP(object): +class FakeDualNetworkSingleDHCP(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV4SubnetNoDHCP()] @@ -725,7 +733,7 @@ class FakeDualNetworkSingleDHCP(object): self.namespace = 'qdhcp-ns' -class FakeDualNetworkSingleDHCPBothAttaced(object): +class FakeDualNetworkSingleDHCPBothAttaced(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' # dhcp-agent actually can't get the subnet with dhcp disabled @@ -734,7 +742,7 @@ class FakeDualNetworkSingleDHCPBothAttaced(object): self.namespace = 'qdhcp-ns' -class FakeDualNetworkDualDHCP(object): +class FakeDualNetworkDualDHCP(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV4Subnet2()] @@ -742,7 +750,7 @@ class FakeDualNetworkDualDHCP(object): self.namespace = 'qdhcp-ns' -class FakeDualNetworkDualDHCPOnLinkSubnetRoutesDisabled(object): +class FakeDualNetworkDualDHCPOnLinkSubnetRoutesDisabled(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV4SubnetSegmentID()] @@ -750,7 +758,7 @@ class FakeDualNetworkDualDHCPOnLinkSubnetRoutesDisabled(object): self.namespace = 'qdhcp-ns' -class FakeNonLocalSubnets(object): +class FakeNonLocalSubnets(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4SubnetSegmentID2()] @@ -759,7 +767,7 @@ class FakeNonLocalSubnets(object): self.namespace = 'qdhcp-ns' -class FakeDualNetworkTriDHCPOneOnLinkSubnetRoute(object): +class FakeDualNetworkTriDHCPOneOnLinkSubnetRoute(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV4Subnet2(), @@ -769,28 +777,28 @@ class FakeDualNetworkTriDHCPOneOnLinkSubnetRoute(object): self.namespace = 'qdhcp-ns' -class FakeV4NoGatewayNetwork(object): +class FakeV4NoGatewayNetwork(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4SubnetNoGateway()] self.ports = [FakePort1()] -class FakeV4NetworkNoRouter(object): +class FakeV4NetworkNoRouter(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4SubnetNoRouter()] self.ports = [FakePort1()] -class FakeV4MetadataNetwork(object): +class FakeV4MetadataNetwork(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4MetadataSubnet()] self.ports = [FakeRouterPort(ip_address='169.254.169.253')] -class FakeV4NetworkDistRouter(object): +class FakeV4NetworkDistRouter(FakeNetworkBase): def __init__(self): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet()] @@ -799,7 +807,7 @@ class FakeV4NetworkDistRouter(object): dev_owner=constants.DEVICE_OWNER_DVR_INTERFACE)] -class FakeDualV4Pxe3Ports(object): +class FakeDualV4Pxe3Ports(FakeNetworkBase): def __init__(self, port_detail="portsSame"): self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.subnets = [FakeV4Subnet(), FakeV4SubnetNoDHCP()] @@ -833,7 +841,7 @@ class FakeDualV4Pxe3Ports(object): DhcpOpt(opt_name='bootfile-name', opt_value='pxelinux3.0')] -class FakeV4NetworkPxe2Ports(object): +class FakeV4NetworkPxe2Ports(FakeNetworkBase): def __init__(self, port_detail="portsSame"): self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' self.subnets = [FakeV4Subnet()] @@ -859,7 +867,7 @@ class FakeV4NetworkPxe2Ports(object): DhcpOpt(opt_name='bootfile-name', opt_value='pxelinux.0')] -class FakeV4NetworkPxe3Ports(object): +class FakeV4NetworkPxe3Ports(FakeNetworkBase): def __init__(self, port_detail="portsSame"): self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' self.subnets = [FakeV4Subnet()] @@ -893,7 +901,7 @@ class FakeV4NetworkPxe3Ports(object): DhcpOpt(opt_name='bootfile-name', opt_value='pxelinux3.0')] -class FakeV6NetworkPxePort(object): +class FakeV6NetworkPxePort(FakeNetworkBase): def __init__(self): self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' self.subnets = [FakeV6SubnetDHCPStateful()] @@ -906,7 +914,7 @@ class FakeV6NetworkPxePort(object): ip_version=6)] -class FakeV6NetworkPxePortWrongOptVersion(object): +class FakeV6NetworkPxePortWrongOptVersion(FakeNetworkBase): def __init__(self): self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' self.subnets = [FakeV6SubnetDHCPStateful()] @@ -919,14 +927,14 @@ class FakeV6NetworkPxePortWrongOptVersion(object): ip_version=6)] -class FakeDualStackNetworkSingleDHCP(object): +class FakeDualStackNetworkSingleDHCP(FakeNetworkBase): def __init__(self): self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' self.subnets = [FakeV4Subnet(), FakeV6SubnetSlaac()] self.ports = [FakePort1(), FakePort4(), FakeRouterPort()] -class FakeDualStackNetworkingSingleDHCPTags(object): +class FakeDualStackNetworkingSingleDHCPTags(FakeNetworkBase): def __init__(self): self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' self.subnets = [FakeV4Subnet(), FakeV6SubnetSlaac()] @@ -937,7 +945,7 @@ class FakeDualStackNetworkingSingleDHCPTags(object): opt_value='pxelinux.0')] -class FakeV4NetworkMultipleTags(object): +class FakeV4NetworkMultipleTags(FakeNetworkBase): def __init__(self): self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' self.subnets = [FakeV4Subnet()] @@ -947,7 +955,7 @@ class FakeV4NetworkMultipleTags(object): DhcpOpt(opt_name='tag:ipxe,bootfile-name', opt_value='pxelinux.0')] -class FakeV6NetworkStatelessDHCP(object): +class FakeV6NetworkStatelessDHCP(FakeNetworkBase): def __init__(self): self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' self.subnets = [FakeV6SubnetStateless()] @@ -955,7 +963,7 @@ class FakeV6NetworkStatelessDHCP(object): self.namespace = 'qdhcp-ns' -class FakeV6NetworkStatelessDHCPNoDnsProvided(object): +class FakeV6NetworkStatelessDHCPNoDnsProvided(FakeNetworkBase): def __init__(self): self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' self.subnets = [FakeV6SubnetStatelessNoDnsProvided()] @@ -963,7 +971,7 @@ class FakeV6NetworkStatelessDHCPNoDnsProvided(object): self.namespace = 'qdhcp-ns' -class FakeV6NetworkStatelessDHCPBadPrefixLength(object): +class FakeV6NetworkStatelessDHCPBadPrefixLength(FakeNetworkBase): def __init__(self): self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' self.subnets = [FakeV6SubnetStatelessBadPrefixLength()] @@ -971,7 +979,7 @@ class FakeV6NetworkStatelessDHCPBadPrefixLength(object): self.namespace = 'qdhcp-ns' -class FakeNetworkWithV6SatelessAndV4DHCPSubnets(object): +class FakeNetworkWithV6SatelessAndV4DHCPSubnets(FakeNetworkBase): def __init__(self): self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' self.subnets = [FakeV6SubnetStateless(), FakeV4Subnet()] @@ -1321,11 +1329,23 @@ class TestDnsmasq(TestBase): (exp_host_name, exp_host_data, exp_addn_name, exp_addn_data) = self._test_no_dns_domain_alloc_data self.conf.set_override('dns_domain', '') - network = FakeDualNetwork(domain=self.conf.dns_domain) + network = FakeDualNetwork(domain='') self._test_spawn(['--conf-file='], network=network) self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), mock.call(exp_addn_name, exp_addn_data)]) + def test_spawn_with_dns_domain_conf(self): + self.conf.set_override('dns_domain', 'starwars.local') + network = FakeDualNetwork(domain=None) + self._test_spawn( + ['--conf-file=', '--domain=starwars.local'], network=network) + + def test_spawn_with_dns_domain_api(self): + self.conf.set_override('dns_domain', 'wrong.answer') + network = FakeDualNetwork(domain='right.answer') + self._test_spawn( + ['--conf-file=', '--domain=right.answer'], network=network) + def test_spawn_no_dhcp_range(self): network = FakeV6Network() subnet = FakeV6SubnetSlaac() diff --git a/releasenotes/notes/dns_domain-6f0e628aeb3c650c.yaml b/releasenotes/notes/dns_domain-6f0e628aeb3c650c.yaml new file mode 100644 index 00000000000..501f175145a --- /dev/null +++ b/releasenotes/notes/dns_domain-6f0e628aeb3c650c.yaml @@ -0,0 +1,13 @@ +--- +fixes: + - | + Previously a network's dns_domain attribute was ignored by the DHCP agent. + With this release, OpenStack deployments using Neutron's DHCP agent will + be able to specify a per network dns_domain and have instances configure + that domain in their dns resolver configuration files (Linux's + /etc/resolv.conf) to allow for local partial DNS lookups. The per-network + dns_domain value will override the DHCP agent's default dns_domain + configuration value. Note that it's also possible to update a network's + dns_domain, and that new value will be propogated to new instances + or when instances renew their DHCP lease. However, existing leases will + live on with the old dns_domain value.