~ubuntu-branches/ubuntu/vivid/neutron/vivid

« back to all changes in this revision

Viewing changes to neutron/agent/linux/dhcp.py

  • Committer: Package Import Robot
  • Author(s): Corey Bryant, Corey Bryant, James Page
  • Date: 2015-02-16 09:59:01 UTC
  • mfrom: (1.1.20)
  • Revision ID: package-import@ubuntu.com-20150216095901-irf6gdwh5tzmfi73
Tags: 1:2015.1~b2-0ubuntu1
[ Corey Bryant ]
* New upstream release.
  - d/control: Align with upstream dependencies.
  - d/p/disable-udev-tests.patch: Rebased.
  - d/p/pep-0476.patch: Dropped. Fixed upstream.

[ James Page ]
* d/neutron-common.install: Drop fwaas_driver.ini and vpnaas.filters, will
  provided by driver specific packages.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
import sys
23
23
 
24
24
import netaddr
25
 
from oslo.config import cfg
26
25
from oslo.serialization import jsonutils
27
26
from oslo.utils import importutils
28
27
import six
31
30
from neutron.agent.linux import utils
32
31
from neutron.common import constants
33
32
from neutron.common import exceptions
 
33
from neutron.common import ipv6_utils
34
34
from neutron.common import utils as commonutils
35
 
from neutron.i18n import _LE
 
35
from neutron.i18n import _LE, _LI
36
36
from neutron.openstack.common import log as logging
37
37
from neutron.openstack.common import uuidutils
38
38
 
39
39
LOG = logging.getLogger(__name__)
40
40
 
41
 
OPTS = [
42
 
    cfg.StrOpt('dhcp_confs',
43
 
               default='$state_path/dhcp',
44
 
               help=_('Location to store DHCP server config files')),
45
 
    cfg.StrOpt('dhcp_domain',
46
 
               default='openstacklocal',
47
 
               help=_('Domain to use for building the hostnames')),
48
 
    cfg.StrOpt('dnsmasq_config_file',
49
 
               default='',
50
 
               help=_('Override the default dnsmasq settings with this file')),
51
 
    cfg.ListOpt('dnsmasq_dns_servers',
52
 
                help=_('Comma-separated list of the DNS servers which will be '
53
 
                       'used as forwarders.'),
54
 
                deprecated_name='dnsmasq_dns_server'),
55
 
    cfg.BoolOpt('dhcp_delete_namespaces', default=False,
56
 
                help=_("Delete namespace after removing a dhcp server.")),
57
 
    cfg.IntOpt(
58
 
        'dnsmasq_lease_max',
59
 
        default=(2 ** 24),
60
 
        help=_('Limit number of leases to prevent a denial-of-service.')),
61
 
    cfg.BoolOpt('dhcp_broadcast_reply', default=False,
62
 
                help=_("Use broadcast in DHCP replies")),
63
 
]
64
 
 
65
41
IPV4 = 4
66
42
IPV6 = 6
67
43
UDP = 'udp'
76
52
METADATA_PORT = 80
77
53
WIN2k3_STATIC_DNS = 249
78
54
NS_PREFIX = 'qdhcp-'
 
55
DNSMASQ_SERVICE_NAME = 'dnsmasq'
79
56
 
80
57
 
81
58
class DictModel(dict):
136
113
@six.add_metaclass(abc.ABCMeta)
137
114
class DhcpBase(object):
138
115
 
139
 
    def __init__(self, conf, network, root_helper='sudo',
 
116
    def __init__(self, conf, network, process_monitor, root_helper='sudo',
140
117
                 version=None, plugin=None):
141
118
        self.conf = conf
142
119
        self.network = network
143
120
        self.root_helper = root_helper
 
121
        self.process_monitor = process_monitor
144
122
        self.device_manager = DeviceManager(self.conf,
145
123
                                            self.root_helper, plugin)
146
124
        self.version = version
192
170
class DhcpLocalProcess(DhcpBase):
193
171
    PORTS = []
194
172
 
 
173
    def __init__(self, conf, network, process_monitor, root_helper='sudo',
 
174
                 version=None, plugin=None):
 
175
        super(DhcpLocalProcess, self).__init__(conf, network, process_monitor,
 
176
                                               root_helper, version, plugin)
 
177
        self.confs_dir = self.get_confs_dir(conf)
 
178
        self.network_conf_dir = os.path.join(self.confs_dir, network.id)
 
179
        self._ensure_network_conf_dir()
 
180
 
 
181
    @staticmethod
 
182
    def get_confs_dir(conf):
 
183
        return os.path.abspath(os.path.normpath(conf.dhcp_confs))
 
184
 
 
185
    def get_conf_file_name(self, kind):
 
186
        """Returns the file name for a given kind of config file."""
 
187
        return os.path.join(self.network_conf_dir, kind)
 
188
 
 
189
    def _ensure_network_conf_dir(self):
 
190
        """Ensures the directory for configuration files is created."""
 
191
        if not os.path.isdir(self.network_conf_dir):
 
192
            os.makedirs(self.network_conf_dir, 0o755)
 
193
 
 
194
    def _remove_config_files(self):
 
195
        shutil.rmtree(self.network_conf_dir, ignore_errors=True)
 
196
 
195
197
    def _enable_dhcp(self):
196
198
        """check if there is a subnet within the network with dhcp enabled."""
197
199
        for subnet in self.network.subnets:
204
206
        if self.active:
205
207
            self.restart()
206
208
        elif self._enable_dhcp():
 
209
            self._ensure_network_conf_dir()
207
210
            interface_name = self.device_manager.setup(self.network)
208
211
            self.interface_name = interface_name
209
212
            self.spawn_process()
210
213
 
211
214
    def disable(self, retain_port=False):
212
215
        """Disable DHCP for this network by killing the local process."""
213
 
        pid = self.pid
214
 
 
215
 
        if pid:
216
 
            if self.active:
217
 
                cmd = ['kill', '-9', pid]
218
 
                utils.execute(cmd, self.root_helper)
219
 
            else:
220
 
                LOG.debug('DHCP for %(net_id)s is stale, pid %(pid)d '
221
 
                          'does not exist, performing cleanup',
222
 
                          {'net_id': self.network.id, 'pid': pid})
223
 
            if not retain_port:
224
 
                self.device_manager.destroy(self.network,
225
 
                                            self.interface_name)
226
 
        else:
227
 
            LOG.debug('No DHCP started for %s', self.network.id)
 
216
        pid_filename = self.get_conf_file_name('pid')
 
217
 
 
218
        pid = self.process_monitor.get_pid(uuid=self.network.id,
 
219
                                           service=DNSMASQ_SERVICE_NAME,
 
220
                                           pid_file=pid_filename)
 
221
 
 
222
        self.process_monitor.disable(uuid=self.network.id,
 
223
                                     namespace=self.network.namespace,
 
224
                                     service=DNSMASQ_SERVICE_NAME,
 
225
                                     pid_file=pid_filename)
 
226
        if pid and not retain_port:
 
227
            self.device_manager.destroy(self.network, self.interface_name)
228
228
 
229
229
        self._remove_config_files()
230
230
 
238
238
                    LOG.exception(_LE('Failed trying to delete namespace: %s'),
239
239
                                  self.network.namespace)
240
240
 
241
 
    def _remove_config_files(self):
242
 
        confs_dir = os.path.abspath(os.path.normpath(self.conf.dhcp_confs))
243
 
        conf_dir = os.path.join(confs_dir, self.network.id)
244
 
        shutil.rmtree(conf_dir, ignore_errors=True)
245
 
 
246
 
    def get_conf_file_name(self, kind, ensure_conf_dir=False):
247
 
        """Returns the file name for a given kind of config file."""
248
 
        confs_dir = os.path.abspath(os.path.normpath(self.conf.dhcp_confs))
249
 
        conf_dir = os.path.join(confs_dir, self.network.id)
250
 
        if ensure_conf_dir:
251
 
            if not os.path.isdir(conf_dir):
252
 
                os.makedirs(conf_dir, 0o755)
253
 
 
254
 
        return os.path.join(conf_dir, kind)
255
 
 
256
241
    def _get_value_from_conf_file(self, kind, converter=None):
257
242
        """A helper function to read a value from one of the state files."""
258
243
        file_name = self.get_conf_file_name(kind)
267
252
        except IOError:
268
253
            msg = _('Unable to access %s')
269
254
 
270
 
        LOG.debug(msg % file_name)
 
255
        LOG.debug(msg, file_name)
271
256
        return None
272
257
 
273
258
    @property
274
 
    def pid(self):
275
 
        """Last known pid for the DHCP process spawned for this network."""
276
 
        return self._get_value_from_conf_file('pid', int)
277
 
 
278
 
    @property
279
 
    def active(self):
280
 
        pid = self.pid
281
 
        if pid is None:
282
 
            return False
283
 
 
284
 
        cmdline = '/proc/%s/cmdline' % pid
285
 
        try:
286
 
            with open(cmdline, "r") as f:
287
 
                return self.network.id in f.readline()
288
 
        except IOError:
289
 
            return False
290
 
 
291
 
    @property
292
259
    def interface_name(self):
293
260
        return self._get_value_from_conf_file('interface')
294
261
 
295
262
    @interface_name.setter
296
263
    def interface_name(self, value):
297
 
        interface_file_path = self.get_conf_file_name('interface',
298
 
                                                      ensure_conf_dir=True)
 
264
        interface_file_path = self.get_conf_file_name('interface')
299
265
        utils.replace_file(interface_file_path, value)
300
266
 
 
267
    @property
 
268
    def active(self):
 
269
        pid_filename = self.get_conf_file_name('pid')
 
270
        return self.process_monitor.is_active(self.network.id,
 
271
                                              DNSMASQ_SERVICE_NAME,
 
272
                                              pid_file=pid_filename)
 
273
 
301
274
    @abc.abstractmethod
302
275
    def spawn_process(self):
303
276
        pass
315
288
 
316
289
    NEUTRON_NETWORK_ID_KEY = 'NEUTRON_NETWORK_ID'
317
290
    NEUTRON_RELAY_SOCKET_PATH_KEY = 'NEUTRON_RELAY_SOCKET_PATH'
318
 
    MINIMUM_VERSION = 2.63
319
291
 
320
292
    @classmethod
321
293
    def check_version(cls):
322
 
        ver = 0
323
 
        try:
324
 
            cmd = ['dnsmasq', '--version']
325
 
            out = utils.execute(cmd)
326
 
            ver = re.findall("\d+.\d+", out)[0]
327
 
            is_valid_version = float(ver) >= cls.MINIMUM_VERSION
328
 
            if not is_valid_version:
329
 
                LOG.error(_LE('FAILED VERSION REQUIREMENT FOR DNSMASQ. '
330
 
                              'DHCP AGENT MAY NOT RUN CORRECTLY! '
331
 
                              'Please ensure that its version is %s '
332
 
                              'or above!'), cls.MINIMUM_VERSION)
333
 
                raise SystemExit(1)
334
 
        except (OSError, RuntimeError, IndexError, ValueError):
335
 
            LOG.error(_LE('Unable to determine dnsmasq version. '
336
 
                          'Please ensure that its version is %s '
337
 
                          'or above!'), cls.MINIMUM_VERSION)
338
 
            raise SystemExit(1)
339
 
        return float(ver)
 
294
        pass
340
295
 
341
296
    @classmethod
342
297
    def existing_dhcp_networks(cls, conf, root_helper):
343
298
        """Return a list of existing networks ids that we have configs for."""
344
 
 
345
 
        confs_dir = os.path.abspath(os.path.normpath(conf.dhcp_confs))
346
 
 
347
 
        return [
348
 
            c for c in os.listdir(confs_dir)
349
 
            if uuidutils.is_uuid_like(c)
350
 
        ]
351
 
 
352
 
    def spawn_process(self):
353
 
        """Spawns a Dnsmasq process for the network."""
354
 
        env = {
355
 
            self.NEUTRON_NETWORK_ID_KEY: self.network.id,
356
 
        }
357
 
 
 
299
        confs_dir = cls.get_confs_dir(conf)
 
300
        try:
 
301
            return [
 
302
                c for c in os.listdir(confs_dir)
 
303
                if uuidutils.is_uuid_like(c)
 
304
            ]
 
305
        except OSError:
 
306
            return []
 
307
 
 
308
    def _build_cmdline_callback(self, pid_file):
358
309
        cmd = [
359
310
            'dnsmasq',
360
311
            '--no-hosts',
363
314
            '--bind-interfaces',
364
315
            '--interface=%s' % self.interface_name,
365
316
            '--except-interface=lo',
366
 
            '--pid-file=%s' % self.get_conf_file_name(
367
 
                'pid', ensure_conf_dir=True),
368
 
            '--dhcp-hostsfile=%s' % self._output_hosts_file(),
369
 
            '--addn-hosts=%s' % self._output_addn_hosts_file(),
370
 
            '--dhcp-optsfile=%s' % self._output_opts_file(),
 
317
            '--pid-file=%s' % pid_file,
 
318
            '--dhcp-hostsfile=%s' % self.get_conf_file_name('host'),
 
319
            '--addn-hosts=%s' % self.get_conf_file_name('addn_hosts'),
 
320
            '--dhcp-optsfile=%s' % self.get_conf_file_name('opts'),
371
321
            '--leasefile-ro',
 
322
            '--dhcp-authoritative',
372
323
        ]
373
324
 
374
325
        possible_leases = 0
398
349
 
399
350
            # mode is optional and is not set - skip it
400
351
            if mode:
401
 
                cmd.append('--dhcp-range=%s%s,%s,%s,%s' %
402
 
                           ('set:', self._TAG_PREFIX % i,
403
 
                            cidr.network, mode, lease))
 
352
                if subnet.ip_version == 4:
 
353
                    cmd.append('--dhcp-range=%s%s,%s,%s,%s' %
 
354
                               ('set:', self._TAG_PREFIX % i,
 
355
                                cidr.network, mode, lease))
 
356
                else:
 
357
                    cmd.append('--dhcp-range=%s%s,%s,%s,%d,%s' %
 
358
                               ('set:', self._TAG_PREFIX % i,
 
359
                                cidr.network, mode,
 
360
                                cidr.prefixlen, lease))
404
361
                possible_leases += cidr.size
405
362
 
406
363
        # Cap the limit because creating lots of subnets can inflate
420
377
        if self.conf.dhcp_broadcast_reply:
421
378
            cmd.append('--dhcp-broadcast')
422
379
 
423
 
        ip_wrapper = ip_lib.IPWrapper(self.root_helper,
424
 
                                      self.network.namespace)
425
 
        ip_wrapper.netns.execute(cmd, addl_env=env)
 
380
        return cmd
 
381
 
 
382
    def spawn_process(self):
 
383
        """Spawn the process, if it's not spawned already."""
 
384
        self._spawn_or_reload_process(reload_with_HUP=False)
 
385
 
 
386
    def _spawn_or_reload_process(self, reload_with_HUP):
 
387
        """Spawns or reloads a Dnsmasq process for the network.
 
388
 
 
389
        When reload_with_HUP is True, dnsmasq receives a HUP signal,
 
390
        or it's reloaded if the process is not running.
 
391
        """
 
392
 
 
393
        self._output_config_files()
 
394
 
 
395
        pid_filename = self.get_conf_file_name('pid')
 
396
 
 
397
        self.process_monitor.enable(
 
398
            uuid=self.network.id,
 
399
            cmd_callback=self._build_cmdline_callback,
 
400
            namespace=self.network.namespace,
 
401
            service=DNSMASQ_SERVICE_NAME,
 
402
            cmd_addl_env={self.NEUTRON_NETWORK_ID_KEY: self.network.id},
 
403
            reload_cfg=reload_with_HUP,
 
404
            pid_file=pid_filename)
426
405
 
427
406
    def _release_lease(self, mac_address, ip):
428
407
        """Release a DHCP lease."""
431
410
                                      self.network.namespace)
432
411
        ip_wrapper.netns.execute(cmd)
433
412
 
 
413
    def _output_config_files(self):
 
414
        self._output_hosts_file()
 
415
        self._output_addn_hosts_file()
 
416
        self._output_opts_file()
 
417
 
434
418
    def reload_allocations(self):
435
419
        """Rebuild the dnsmasq config and signal the dnsmasq to reload."""
436
420
 
442
426
            return
443
427
 
444
428
        self._release_unused_leases()
445
 
        self._output_hosts_file()
446
 
        self._output_addn_hosts_file()
447
 
        self._output_opts_file()
448
 
        if self.active:
449
 
            cmd = ['kill', '-HUP', self.pid]
450
 
            utils.execute(cmd, self.root_helper)
451
 
        else:
452
 
            LOG.debug('Pid %d is stale, relaunching dnsmasq', self.pid)
 
429
        self._spawn_or_reload_process(reload_with_HUP=True)
453
430
        LOG.debug('Reloading allocations for network: %s', self.network.id)
454
431
        self.device_manager.update(self.network, self.interface_name)
455
432
 
460
437
        (
461
438
            port,  # a DictModel instance representing the port.
462
439
            alloc,  # a DictModel instance of the allocated ip and subnet.
 
440
                    # if alloc is None, it means there is no need to allocate
 
441
                    # an IPv6 address because of stateless DHCPv6 network.
463
442
            host_name,  # Host name.
464
443
            name,  # Canonical hostname in the format 'hostname[.domain]'.
465
444
        )
473
452
                # dhcp agent
474
453
                if alloc.subnet_id in v6_nets:
475
454
                    addr_mode = v6_nets[alloc.subnet_id].ipv6_address_mode
476
 
                    if addr_mode != constants.DHCPV6_STATEFUL:
477
 
                        continue
 
455
                    if addr_mode == constants.IPV6_SLAAC:
 
456
                        continue
 
457
                    elif addr_mode == constants.DHCPV6_STATELESS:
 
458
                        alloc = hostname = fqdn = None
 
459
                        yield (port, alloc, hostname, fqdn)
 
460
                        continue
 
461
 
478
462
                hostname = 'host-%s' % alloc.ip_address.replace(
479
463
                    '.', '-').replace(':', '-')
480
464
                fqdn = hostname
502
486
        filename = self.get_conf_file_name('host')
503
487
 
504
488
        LOG.debug('Building host file: %s', filename)
 
489
        dhcp_enabled_subnet_ids = [s.id for s in self.network.subnets
 
490
                                   if s.enable_dhcp]
505
491
        for (port, alloc, hostname, name) in self._iter_hosts():
 
492
            if not alloc:
 
493
                if getattr(port, 'extra_dhcp_opts', False):
 
494
                    buf.write('%s,%s%s\n' %
 
495
                              (port.mac_address, 'set:', port.id))
 
496
                    LOG.debug('Adding %(mac)s : set:%(tag)s',
 
497
                              {"mac": port.mac_address, "tag": port.id})
 
498
                continue
 
499
 
 
500
            # don't write ip address which belongs to a dhcp disabled subnet.
 
501
            if alloc.subnet_id not in dhcp_enabled_subnet_ids:
 
502
                continue
 
503
 
506
504
            # (dzyu) Check if it is legal ipv6 address, if so, need wrap
507
505
            # it with '[]' to let dnsmasq to distinguish MAC address from
508
506
            # IPv6 address.
510
508
            if netaddr.valid_ipv6(ip_address):
511
509
                ip_address = '[%s]' % ip_address
512
510
 
513
 
            LOG.debug('Adding %(mac)s : %(name)s : %(ip)s',
514
 
                      {"mac": port.mac_address, "name": name,
515
 
                       "ip": ip_address})
516
 
 
517
511
            if getattr(port, 'extra_dhcp_opts', False):
518
512
                buf.write('%s,%s,%s,%s%s\n' %
519
513
                          (port.mac_address, name, ip_address,
520
514
                           'set:', port.id))
 
515
                LOG.debug('Adding %(mac)s : %(name)s : %(ip)s : '
 
516
                          'set:%(tag)s',
 
517
                          {"mac": port.mac_address, "name": name,
 
518
                           "ip": ip_address, "tag": port.id})
521
519
            else:
522
520
                buf.write('%s,%s,%s\n' %
523
521
                          (port.mac_address, name, ip_address))
 
522
                LOG.debug('Adding %(mac)s : %(name)s : %(ip)s',
 
523
                          {"mac": port.mac_address, "name": name,
 
524
                           "ip": ip_address})
524
525
 
525
526
        utils.replace_file(filename, buf.getvalue())
526
527
        LOG.debug('Done building host file %s', filename)
561
562
        for (port, alloc, hostname, fqdn) in self._iter_hosts():
562
563
            # It is compulsory to write the `fqdn` before the `hostname` in
563
564
            # order to obtain it in PTR responses.
564
 
            buf.write('%s\t%s %s\n' % (alloc.ip_address, fqdn, hostname))
 
565
            if alloc:
 
566
                buf.write('%s\t%s %s\n' % (alloc.ip_address, fqdn, hostname))
565
567
        addn_hosts = self.get_conf_file_name('addn_hosts')
566
568
        utils.replace_file(addn_hosts, buf.getvalue())
567
569
        return addn_hosts
568
570
 
569
571
    def _output_opts_file(self):
570
572
        """Write a dnsmasq compatible options file."""
571
 
 
 
573
        options, subnet_index_map = self._generate_opts_per_subnet()
 
574
        options += self._generate_opts_per_port(subnet_index_map)
 
575
 
 
576
        name = self.get_conf_file_name('opts')
 
577
        utils.replace_file(name, '\n'.join(options))
 
578
        return name
 
579
 
 
580
    def _generate_opts_per_subnet(self):
 
581
        options = []
 
582
        subnet_index_map = {}
572
583
        if self.conf.enable_isolated_metadata:
573
584
            subnet_to_interface_ip = self._make_subnet_interface_ip_map()
574
 
 
575
 
        options = []
576
 
 
577
585
        isolated_subnets = self.get_isolated_subnets(self.network)
578
 
        dhcp_ips = collections.defaultdict(list)
579
 
        subnet_idx_map = {}
580
586
        for i, subnet in enumerate(self.network.subnets):
581
587
            if (not subnet.enable_dhcp or
582
588
                (subnet.ip_version == 6 and
593
599
            else:
594
600
                # use the dnsmasq ip as nameservers only if there is no
595
601
                # dns-server submitted by the server
596
 
                subnet_idx_map[subnet.id] = i
 
602
                subnet_index_map[subnet.id] = i
597
603
 
598
604
            if self.conf.dhcp_domain and subnet.ip_version == 6:
599
605
                options.append('tag:tag%s,option6:domain-search,%s' %
638
644
                else:
639
645
                    options.append(self._format_option(subnet.ip_version,
640
646
                                                       i, 'router'))
 
647
        return options, subnet_index_map
641
648
 
 
649
    def _generate_opts_per_port(self, subnet_index_map):
 
650
        options = []
 
651
        dhcp_ips = collections.defaultdict(list)
642
652
        for port in self.network.ports:
643
653
            if getattr(port, 'extra_dhcp_opts', False):
644
 
                for ip_version in (4, 6):
645
 
                    if any(
646
 
                        netaddr.IPAddress(ip.ip_address).version == ip_version
647
 
                            for ip in port.fixed_ips):
648
 
                        options.extend(
649
 
                            # TODO(xuhanp):Instead of applying extra_dhcp_opts
650
 
                            # to both DHCPv4 and DHCPv6, we need to find a new
651
 
                            # way to specify options for v4 and v6
652
 
                            # respectively. We also need to validate the option
653
 
                            # before applying it.
654
 
                            self._format_option(ip_version, port.id,
655
 
                                                opt.opt_name, opt.opt_value)
656
 
                            for opt in port.extra_dhcp_opts)
 
654
                port_ip_versions = set(
 
655
                    [netaddr.IPAddress(ip.ip_address).version
 
656
                     for ip in port.fixed_ips])
 
657
                for opt in port.extra_dhcp_opts:
 
658
                    opt_ip_version = opt.ip_version
 
659
                    if opt_ip_version in port_ip_versions:
 
660
                        options.append(
 
661
                            self._format_option(opt_ip_version, port.id,
 
662
                                                opt.opt_name, opt.opt_value))
 
663
                    else:
 
664
                        LOG.info(_LI("Cannot apply dhcp option %(opt)s "
 
665
                                     "because it's ip_version %(version)d "
 
666
                                     "is not in port's address IP versions"),
 
667
                                 {'opt': opt.opt_name,
 
668
                                  'version': opt_ip_version})
657
669
 
658
670
            # provides all dnsmasq ip as dns-server if there is more than
659
671
            # one dnsmasq for a subnet and there is no dns-server submitted
660
672
            # by the server
661
673
            if port.device_owner == constants.DEVICE_OWNER_DHCP:
662
674
                for ip in port.fixed_ips:
663
 
                    i = subnet_idx_map.get(ip.subnet_id)
 
675
                    i = subnet_index_map.get(ip.subnet_id)
664
676
                    if i is None:
665
677
                        continue
666
678
                    dhcp_ips[i].append(ip.ip_address)
676
688
                            ','.join(
677
689
                                Dnsmasq._convert_to_literal_addrs(ip_version,
678
690
                                                                  vx_ips))))
679
 
 
680
 
        name = self.get_conf_file_name('opts')
681
 
        utils.replace_file(name, '\n'.join(options))
682
 
        return name
 
691
        return options
683
692
 
684
693
    def _make_subnet_interface_ip_map(self):
685
694
        ip_dev = ip_lib.IPDevice(
706
715
    def _format_option(self, ip_version, tag, option, *args):
707
716
        """Format DHCP option by option name or code."""
708
717
        option = str(option)
 
718
        pattern = "(tag:(.*),)?(.*)$"
 
719
        matches = re.match(pattern, option)
 
720
        extra_tag = matches.groups()[0]
 
721
        option = matches.groups()[2]
709
722
 
710
723
        if isinstance(tag, int):
711
724
            tag = self._TAG_PREFIX % tag
715
728
                option = 'option:%s' % option
716
729
            else:
717
730
                option = 'option6:%s' % option
718
 
 
719
 
        return ','.join(('tag:' + tag, '%s' % option) + args)
 
731
        if extra_tag:
 
732
            tags = ('tag:' + tag, extra_tag[:-1], '%s' % option)
 
733
        else:
 
734
            tags = ('tag:' + tag, '%s' % option)
 
735
        return ','.join(tags + args)
720
736
 
721
737
    @staticmethod
722
738
    def _convert_to_literal_addrs(ip_version, ips):
736
752
        subnets = dict((subnet.id, subnet) for subnet in network.subnets)
737
753
 
738
754
        for port in network.ports:
739
 
            if port.device_owner not in (constants.DEVICE_OWNER_ROUTER_INTF,
740
 
                                         constants.DEVICE_OWNER_DVR_INTERFACE):
 
755
            if port.device_owner not in constants.ROUTER_INTERFACE_OWNERS:
741
756
                continue
742
757
            for alloc in port.fixed_ips:
743
758
                if subnets[alloc.subnet_id].gateway_ip == alloc.ip_address:
965
980
        ip_cidrs = []
966
981
        for fixed_ip in port.fixed_ips:
967
982
            subnet = fixed_ip.subnet
968
 
            net = netaddr.IPNetwork(subnet.cidr)
969
 
            ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
970
 
            ip_cidrs.append(ip_cidr)
 
983
            if not ipv6_utils.is_auto_address_subnet(subnet):
 
984
                net = netaddr.IPNetwork(subnet.cidr)
 
985
                ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
 
986
                ip_cidrs.append(ip_cidr)
971
987
 
972
988
        if (self.conf.enable_isolated_metadata and
973
989
            self.conf.use_namespaces):