ver = re.findall("\d+.\d+", out)[0]
is_valid_version = float(ver) >= cls.MINIMUM_VERSION
if not is_valid_version:
- LOG.warning(_('FAILED VERSION REQUIREMENT FOR DNSMASQ. '
- 'DHCP AGENT MAY NOT RUN CORRECTLY! '
- 'Please ensure that its version is %s '
- 'or above!'), cls.MINIMUM_VERSION)
+ LOG.error(_('FAILED VERSION REQUIREMENT FOR DNSMASQ. '
+ 'DHCP AGENT MAY NOT RUN CORRECTLY! '
+ 'Please ensure that its version is %s '
+ 'or above!'), cls.MINIMUM_VERSION)
+ raise SystemExit(1)
except (OSError, RuntimeError, IndexError, ValueError):
LOG.error(_('Unable to determine dnsmasq version. '
'Please ensure that its version is %s '
else:
# 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)):
+ addr_mode = getattr(subnet, 'ipv6_address_mode', None)
+ ra_mode = getattr(subnet, 'ipv6_ra_mode', None)
+ if (addr_mode in [constants.DHCPV6_STATEFUL,
+ constants.DHCPV6_STATELESS] or
+ not addr_mode and not ra_mode):
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:
- set_tag = ''
cidr = netaddr.IPNetwork(subnet.cidr)
# mode is optional and is not set - skip it
if mode:
cmd.append('--dhcp-range=%s%s,%s,%s,%s' %
- (set_tag, self._TAG_PREFIX % i,
+ ('set:', self._TAG_PREFIX % i,
cidr.network, mode, lease))
- else:
- cmd.append('--dhcp-range=%s%s,%s,%s' %
- (set_tag, self._TAG_PREFIX % i,
- cidr.network, lease))
-
- possible_leases += cidr.size
+ possible_leases += cidr.size
# Cap the limit because creating lots of subnets can inflate
# this possible lease cap.
# 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):
+ if addr_mode != constants.DHCPV6_STATEFUL:
continue
hostname = 'host-%s' % alloc.ip_address.replace(
'.', '-').replace(':', '-')
LOG.debug(_('Building host file: %s'), filename)
for (port, alloc, hostname, name) in self._iter_hosts():
- set_tag = ''
# (dzyu) Check if it is legal ipv6 address, if so, need wrap
# it with '[]' to let dnsmasq to distinguish MAC address from
# IPv6 address.
"ip": ip_address})
if getattr(port, 'extra_dhcp_opts', False):
- if self.version >= self.MINIMUM_VERSION:
- set_tag = 'set:'
-
buf.write('%s,%s,%s,%s%s\n' %
(port.mac_address, name, ip_address,
- set_tag, port.id))
+ 'set:', port.id))
else:
buf.write('%s,%s,%s\n' %
(port.mac_address, name, ip_address))
dhcp_ips = collections.defaultdict(list)
subnet_idx_map = {}
for i, subnet in enumerate(self.network.subnets):
- if not subnet.enable_dhcp:
+ if (not subnet.enable_dhcp or
+ (subnet.ip_version == 6 and
+ getattr(subnet, 'ipv6_address_mode', None)
+ in [None, constants.IPV6_SLAAC])):
continue
if subnet.dns_nameservers:
options.append(
- self._format_option(i, 'dns-server',
- ','.join(subnet.dns_nameservers)))
+ self._format_option(
+ subnet.ip_version, i, 'dns-server',
+ ','.join(
+ Dnsmasq._convert_to_literal_addrs(
+ subnet.ip_version, 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
+ if self.conf.dhcp_domain and subnet.ip_version == 6:
+ options.append('tag:tag%s,option6:domain-search,%s' %
+ (i, ''.join(self.conf.dhcp_domain)))
+
gateway = subnet.gateway_ip
host_routes = []
for hr in subnet.host_routes:
'%s/32,%s' % (METADATA_DEFAULT_IP, subnet_dhcp_ip)
)
- if host_routes:
- if gateway and subnet.ip_version == 4:
- host_routes.append("%s,%s" % ("0.0.0.0/0", gateway))
- options.append(
- self._format_option(i, 'classless-static-route',
- ','.join(host_routes)))
- options.append(
- self._format_option(i, WIN2k3_STATIC_DNS,
- ','.join(host_routes)))
-
if subnet.ip_version == 4:
+ if host_routes:
+ if gateway:
+ host_routes.append("%s,%s" % ("0.0.0.0/0", gateway))
+ options.append(
+ self._format_option(subnet.ip_version, i,
+ 'classless-static-route',
+ ','.join(host_routes)))
+ options.append(
+ self._format_option(subnet.ip_version, i,
+ WIN2k3_STATIC_DNS,
+ ','.join(host_routes)))
+
if gateway:
- options.append(self._format_option(i, 'router', gateway))
+ options.append(self._format_option(subnet.ip_version,
+ i, 'router',
+ gateway))
else:
- options.append(self._format_option(i, 'router'))
+ options.append(self._format_option(subnet.ip_version,
+ i, 'router'))
for port in self.network.ports:
if getattr(port, 'extra_dhcp_opts', False):
- options.extend(
- self._format_option(port.id, opt.opt_name, opt.opt_value)
- for opt in port.extra_dhcp_opts)
+ for ip_version in (4, 6):
+ if any(
+ netaddr.IPAddress(ip.ip_address).version == ip_version
+ for ip in port.fixed_ips):
+ options.extend(
+ # TODO(xuhanp):Instead of applying extra_dhcp_opts
+ # to both DHCPv4 and DHCPv6, we need to find a new
+ # way to specify options for v4 and v6
+ # respectively. We also need to validate the option
+ # before applying it.
+ self._format_option(ip_version, 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
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)))
+ for ip_version in (4, 6):
+ vx_ips = [ip for ip in ips
+ if netaddr.IPAddress(ip).version == ip_version]
+ if vx_ips:
+ options.append(
+ self._format_option(
+ ip_version, i, 'dns-server',
+ ','.join(
+ Dnsmasq._convert_to_literal_addrs(ip_version,
+ vx_ips))))
name = self.get_conf_file_name('opts')
utils.replace_file(name, '\n'.join(options))
return retval
- def _format_option(self, tag, option, *args):
+ def _format_option(self, ip_version, tag, option, *args):
"""Format DHCP option by option name or code."""
- if self.version >= self.MINIMUM_VERSION:
- set_tag = 'tag:'
- else:
- set_tag = ''
-
option = str(option)
if isinstance(tag, int):
tag = self._TAG_PREFIX % tag
if not option.isdigit():
- option = 'option:%s' % option
+ if ip_version == 4:
+ option = 'option:%s' % option
+ else:
+ option = 'option6:%s' % option
+
+ return ','.join(('tag:' + tag, '%s' % option) + args)
- return ','.join((set_tag + tag, '%s' % option) + args)
+ @staticmethod
+ def _convert_to_literal_addrs(ip_version, ips):
+ if ip_version == 4:
+ return ips
+ return ['[' + ip + ']' for ip in ips]
def _enable_metadata(self, subnet):
'''Determine if the metadata route will be pushed to hosts on subnet.
import os
import mock
+import netaddr
from oslo.config import cfg
import testtools
id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
admin_state_up = False
device_owner = 'foo2'
- fixed_ips = [FakeIPAllocation('fdca:3ba5:a17a:4ba3::2',
- 'ffffffff-ffff-ffff-ffff-ffffffffffff')]
+ fixed_ips = [FakeIPAllocation('192.168.0.3',
+ 'dddddddd-dddd-dddd-dddd-dddddddddddd')]
mac_address = '00:00:f3:aa:bb:cc'
def __init__(self):
id = '44444444-4444-4444-4444-444444444444'
admin_state_up = True
device_owner = 'foo3'
- fixed_ips = [FakeIPAllocation('192.168.0.3',
+ fixed_ips = [FakeIPAllocation('192.168.0.4',
'dddddddd-dddd-dddd-dddd-dddddddddddd'),
- FakeIPAllocation('fdca:3ba5:a17a:4ba3::3',
- 'ffffffff-ffff-ffff-ffff-ffffffffffff')]
+ FakeIPAllocation('192.168.1.2',
+ 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee')]
mac_address = '00:00:0f:aa:bb:cc'
def __init__(self):
self.extra_dhcp_opts = []
+class FakeV6Port:
+ id = 'hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh'
+ admin_state_up = True
+ device_owner = 'foo3'
+ fixed_ips = [FakeIPAllocation('fdca:3ba5:a17a:4ba3::2',
+ 'ffffffff-ffff-ffff-ffff-ffffffffffff')]
+ mac_address = '00:00:f3:aa:bb:cc'
+
+ def __init__(self):
+ self.extra_dhcp_opts = []
+
+
+class FakeDualPort:
+ id = 'hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh'
+ admin_state_up = True
+ device_owner = 'foo3'
+ 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 FakeRouterPort:
id = 'rrrrrrrr-rrrr-rrrr-rrrr-rrrrrrrrrrrr'
admin_state_up = True
dns_nameservers = []
+class FakeV6SubnetDHCPStateful:
+ id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
+ ip_version = 6
+ cidr = 'fdca:3ba5:a17a:4ba3::/64'
+ gateway_ip = 'fdca:3ba5:a17a:4ba3::1'
+ enable_dhcp = True
+ host_routes = [FakeV6HostRoute]
+ dns_nameservers = ['2001:0200:feed:7ac0::1']
+ ipv6_ra_mode = None
+ ipv6_address_mode = constants.DHCPV6_STATEFUL
+
+
class FakeV6SubnetSlaac:
id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
ip_version = 6
class FakeDualNetwork:
id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
- subnets = [FakeV4Subnet(), FakeV6Subnet()]
- ports = [FakePort1(), FakePort2(), FakePort3(), FakeRouterPort()]
+ subnets = [FakeV4Subnet(), FakeV6SubnetDHCPStateful()]
+ ports = [FakePort1(), FakeV6Port(), FakeDualPort(), FakeRouterPort()]
namespace = 'qdhcp-ns'
class FakeDualNetworkGatewayRoute:
id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
- subnets = [FakeV4SubnetGatewayRoute(), FakeV6Subnet()]
+ subnets = [FakeV4SubnetGatewayRoute(), FakeV6SubnetDHCPStateful()]
ports = [FakePort1(), FakePort2(), FakePort3(), FakeRouterPort()]
namespace = 'qdhcp-ns'
prefix = '--dhcp-range=set:tag%d,%s,static,%s%s'
else:
prefix = '--dhcp-range=set:tag%d,%s,%s%s'
- expected.extend(prefix %
- (i, s.cidr.split('/')[0], lease_duration, seconds)
- for i, s in enumerate(network.subnets))
-
- expected.append('--dhcp-lease-max=%d' % max_leases)
+ possible_leases = 0
+ for i, s in enumerate(network.subnets):
+ if (s.ip_version != 6
+ or s.ipv6_address_mode == constants.DHCPV6_STATEFUL):
+ expected.extend([prefix % (
+ i, s.cidr.split('/')[0], lease_duration, seconds)])
+ possible_leases += netaddr.IPNetwork(s.cidr).size
+
+ expected.append('--dhcp-lease-max=%d' % min(
+ possible_leases, max_leases))
expected.extend(extra_options)
self.execute.return_value = ('', '')
self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data),
mock.call(exp_addn_name, exp_addn_data)])
- def test_spawn_no_dnsmasq_ipv6_mode(self):
+ def test_spawn_no_dhcp_range(self):
network = FakeV6Network()
- subnet = FakeV6Subnet()
- subnet.ipv6_ra_mode = True
+ subnet = FakeV6SubnetSlaac()
network.subnets = [subnet]
self._test_spawn(['--conf-file=', '--domain=openstacklocal'],
network, has_static=False)
def test_output_opts_file(self):
fake_v6 = '2001:0200:feed:7ac0::1'
- fake_v6_cidr = '2001:0200:feed:7ac0::/64'
expected = (
'tag:tag0,option:dns-server,8.8.8.8\n'
'tag:tag0,option:classless-static-route,20.0.0.1/24,20.0.0.1,'
'0.0.0.0/0,192.168.0.1\n'
'tag:tag0,249,20.0.0.1/24,20.0.0.1,0.0.0.0/0,192.168.0.1\n'
'tag:tag0,option:router,192.168.0.1\n'
- 'tag:tag1,option:dns-server,%s\n'
- 'tag:tag1,option:classless-static-route,%s,%s\n'
- 'tag:tag1,249,%s,%s').lstrip() % (fake_v6,
- fake_v6_cidr, fake_v6,
- fake_v6_cidr, fake_v6)
+ 'tag:tag1,option6:dns-server,%s\n'
+ 'tag:tag1,option6:domain-search,openstacklocal').lstrip() % (
+ '[' + fake_v6 + ']')
with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn:
conf_fn.return_value = '/foo/opts'
def test_output_opts_file_gateway_route(self):
fake_v6 = '2001:0200:feed:7ac0::1'
- fake_v6_cidr = '2001:0200:feed:7ac0::/64'
expected = """
tag:tag0,option:dns-server,8.8.8.8
tag:tag0,option:router,192.168.0.1
-tag:tag1,option:dns-server,%s
-tag:tag1,option:classless-static-route,%s,%s
-tag:tag1,249,%s,%s""".lstrip() % (fake_v6,
- fake_v6_cidr, fake_v6,
- fake_v6_cidr, fake_v6)
+tag:tag1,option6:dns-server,%s
+tag:tag1,option6:domain-search,openstacklocal""".lstrip() % (
+ '[' + fake_v6 + ']')
with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn:
conf_fn.return_value = '/foo/opts'
self.safe.assert_called_once_with('/foo/opts', expected)
- def test_output_opts_file_single_dhcp_ver2_48(self):
- expected = (
- 'tag0,option:dns-server,8.8.8.8\n'
- 'tag0,option:classless-static-route,20.0.0.1/24,20.0.0.1,'
- '0.0.0.0/0,192.168.0.1\n'
- 'tag0,249,20.0.0.1/24,20.0.0.1,0.0.0.0/0,192.168.0.1\n'
- '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, FakeDualNetworkSingleDHCP(),
- version=float(2.48))
- dm._output_opts_file()
-
- self.safe.assert_called_once_with('/foo/opts', expected)
-
def test_output_opts_file_no_gateway(self):
expected = """
tag:tag0,option:classless-static-route,169.254.169.254/32,192.168.1.1
self.safe.assert_called_once_with('/foo/opts', expected)
- def test_output_opts_file_pxe_3port_1net_diff_details(self):
- expected = (
- 'tag:tag0,option:dns-server,8.8.8.8\n'
- 'tag:tag0,option:classless-static-route,20.0.0.1/24,20.0.0.1,'
- '0.0.0.0/0,192.168.0.1\n'
- 'tag:tag0,249,20.0.0.1/24,20.0.0.1,0.0.0.0/0,192.168.0.1\n'
- 'tag:tag0,option:router,192.168.0.1\n'
- 'tag:eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee,'
- 'option:tftp-server,192.168.0.3\n'
- 'tag:eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee,'
- 'option:server-ip-address,192.168.0.2\n'
- 'tag:eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee,'
- 'option:bootfile-name,pxelinux.0\n'
- 'tag:ffffffff-ffff-ffff-ffff-ffffffffffff,'
- 'option:tftp-server,192.168.0.5\n'
- 'tag:ffffffff-ffff-ffff-ffff-ffffffffffff,'
- 'option:server-ip-address,192.168.0.5\n'
- 'tag:ffffffff-ffff-ffff-ffff-ffffffffffff,'
- 'option:bootfile-name,pxelinux2.0\n'
- 'tag:44444444-4444-4444-4444-444444444444,'
- 'option:tftp-server,192.168.0.7\n'
- 'tag:44444444-4444-4444-4444-444444444444,'
- 'option:server-ip-address,192.168.0.7\n'
- 'tag:44444444-4444-4444-4444-444444444444,'
- 'option:bootfile-name,pxelinux3.0')
- expected = expected.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,
- FakeV4NetworkPxe3Ports("portsDifferent"),
- version=dhcp.Dnsmasq.MINIMUM_VERSION)
- dm._output_opts_file()
-
- self.safe.assert_called_once_with('/foo/opts', expected)
-
def test_output_opts_file_pxe_3port_2net(self):
expected = (
'tag:tag0,option:dns-server,8.8.8.8\n'
).lstrip()
exp_opt_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/opts'
fake_v6 = '2001:0200:feed:7ac0::1'
- fake_v6_cidr = '2001:0200:feed:7ac0::/64'
exp_opt_data = (
'tag:tag0,option:dns-server,8.8.8.8\n'
'tag:tag0,option:classless-static-route,20.0.0.1/24,20.0.0.1,'
'0.0.0.0/0,192.168.0.1\n'
'tag:tag0,249,20.0.0.1/24,20.0.0.1,0.0.0.0/0,192.168.0.1\n'
'tag:tag0,option:router,192.168.0.1\n'
- 'tag:tag1,option:dns-server,%s\n'
- 'tag:tag1,option:classless-static-route,%s,%s\n'
- 'tag:tag1,249,%s,%s').lstrip() % (fake_v6,
- fake_v6_cidr, fake_v6,
- fake_v6_cidr, fake_v6)
+ 'tag:tag1,option6:dns-server,%s\n'
+ 'tag:tag1,option6:domain-search,openstacklocal').lstrip() % (
+ '[' + fake_v6 + ']')
return (exp_host_name, exp_host_data,
exp_addn_name, exp_addn_data,
exp_opt_name, exp_opt_data,)
float(2.65))
def test_check_fail_version(self):
- self._check_version('Dnsmasq version 2.48 Copyright (c)...',
- float(2.48))
+ with testtools.ExpectedException(SystemExit):
+ self._check_version('Dnsmasq version 2.62 Copyright (c)...', 0)
def test_check_version_failed_cmd_execution(self):
with testtools.ExpectedException(SystemExit):