components/openstack/neutron/files/agent/solaris/dhcp.py
branchs11u2-sru
changeset 4156 4b1def16fe9b
parent 3619 639868f63ef4
--- a/components/openstack/neutron/files/agent/solaris/dhcp.py	Thu Apr 16 01:36:32 2015 -0700
+++ b/components/openstack/neutron/files/agent/solaris/dhcp.py	Mon Apr 20 12:35:51 2015 -0700
@@ -20,16 +20,17 @@
 # @author: Girish Moodalbail, Oracle, Inc.
 
 import abc
+import collections
 import os
 import re
 import shutil
 import socket
-import StringIO
 import sys
 import uuid
 
 import netaddr
 from oslo.config import cfg
+import six
 
 from neutron.agent.linux import utils
 from neutron.agent.solaris import net_lib
@@ -52,15 +53,16 @@
     cfg.StrOpt('dnsmasq_config_file',
                default='',
                help=_('Override the default dnsmasq settings with this file')),
-    cfg.StrOpt('dnsmasq_dns_server',
-               help=_('Use another DNS server before any in '
-                      '/etc/resolv.conf.')),
+    cfg.ListOpt('dnsmasq_dns_servers',
+                help=_('Comma-separated list of the DNS servers which will be '
+                       'used as forwarders.'),
+                deprecated_name='dnsmasq_dns_server'),
+    cfg.BoolOpt('dhcp_delete_namespaces', default=False,
+                help=_("Delete namespace after removing a dhcp server.")),
     cfg.IntOpt(
         'dnsmasq_lease_max',
         default=(2 ** 24),
         help=_('Limit number of leases to prevent a denial-of-service.')),
-    cfg.StrOpt('interface_driver',
-               help=_("The driver used to manage the virtual interface.")),
 ]
 
 IPV4 = 4
@@ -78,17 +80,46 @@
 WIN2k3_STATIC_DNS = 249
 
 
-class DictModel(object):
+class DictModel(dict):
     """Convert dict into an object that provides attribute access to values."""
-    def __init__(self, d):
-        for key, value in d.iteritems():
-            if isinstance(value, list):
-                value = [DictModel(item) if isinstance(item, dict) else item
-                         for item in value]
-            elif isinstance(value, dict):
-                value = DictModel(value)
+
+    def __init__(self, *args, **kwargs):
+        """Convert dict values to DictModel values."""
+        super(DictModel, self).__init__(*args, **kwargs)
+
+        def needs_upgrade(item):
+            """Check if `item` is a dict and needs to be changed to DictModel.
+            """
+            return isinstance(item, dict) and not isinstance(item, DictModel)
+
+        def upgrade(item):
+            """Upgrade item if it needs to be upgraded."""
+            if needs_upgrade(item):
+                return DictModel(item)
+            else:
+                return item
 
-            setattr(self, key, value)
+        for key, value in self.iteritems():
+            if isinstance(value, (list, tuple)):
+                # Keep the same type but convert dicts to DictModels
+                self[key] = type(value)(
+                    (upgrade(item) for item in value)
+                )
+            elif needs_upgrade(value):
+                # Change dict instance values to DictModel instance values
+                self[key] = DictModel(value)
+
+    def __getattr__(self, name):
+        try:
+            return self[name]
+        except KeyError as e:
+            raise AttributeError(e)
+
+    def __setattr__(self, name, value):
+        self[name] = value
+
+    def __delattr__(self, name):
+        del self[name]
 
 
 class NetModel(DictModel):
@@ -103,8 +134,8 @@
         return self._ns_name
 
 
[email protected]_metaclass(abc.ABCMeta)
 class DhcpBase(object):
-    __metaclass__ = abc.ABCMeta
 
     def __init__(self, conf, network, root_helper='sudo',
                  version=None, plugin=None):
@@ -127,17 +158,12 @@
         """Restart the dhcp service for the network."""
         self.disable(retain_port=True)
         self.enable()
-        self.device_manager.update(self.network)
 
     @abc.abstractproperty
     def active(self):
         """Boolean representing the running state of the DHCP server."""
 
     @abc.abstractmethod
-    def release_lease(self, mac_address, removed_ips):
-        """Release a DHCP lease."""
-
-    @abc.abstractmethod
     def reload_allocations(self):
         """Force the DHCP server to reload the assignment database."""
 
@@ -145,13 +171,23 @@
     def existing_dhcp_networks(cls, conf, root_helper):
         """Return a list of existing networks ids that we have configs for."""
 
-        raise NotImplementedError
+        raise NotImplementedError()
 
     @classmethod
     def check_version(cls):
         """Execute version checks on DHCP server."""
 
-        raise NotImplementedError
+        raise NotImplementedError()
+
+    @classmethod
+    def get_isolated_subnets(cls, network):
+        """Returns a dict indicating whether or not a subnet is isolated"""
+        raise NotImplementedError()
+
+    @classmethod
+    def should_enable_metadata(cls, conf, network):
+        """True if the metadata-proxy should be enabled for the network."""
+        raise NotImplementedError()
 
 
 class DhcpLocalProcess(DhcpBase):
@@ -166,11 +202,10 @@
 
     def enable(self):
         """Enables DHCP for this network by spawning a local process."""
-        interface_name = self.device_manager.setup(self.network,
-                                                   reuse_existing=True)
         if self.active:
             self.restart()
         elif self._enable_dhcp():
+            interface_name = self.device_manager.setup(self.network)
             self.interface_name = interface_name
             self.spawn_process()
 
@@ -178,15 +213,17 @@
         """Disable DHCP for this network by killing the local process."""
         pid = self.pid
 
-        if self.active:
-            cmd = ['kill', '-9', pid]
-            utils.execute(cmd, self.root_helper)
+        if pid:
+            if self.active:
+                cmd = ['kill', '-9', pid]
+                utils.execute(cmd, self.root_helper)
+            else:
+                LOG.debug(_('DHCP for %(net_id)s is stale, pid %(pid)d '
+                            'does not exist, performing cleanup'),
+                          {'net_id': self.network.id, 'pid': pid})
             if not retain_port:
-                self.device_manager.destroy(self.network, self.interface_name)
-
-        elif pid:
-            LOG.debug(_('DHCP for %(net_id)s pid %(pid)d is stale, ignoring '
-                        'command'), {'net_id': self.network.id, 'pid': pid})
+                self.device_manager.destroy(self.network,
+                                            self.interface_name)
         else:
             LOG.debug(_('No DHCP started for %s'), self.network.id)
 
@@ -268,7 +305,8 @@
 
     NEUTRON_NETWORK_ID_KEY = 'NEUTRON_NETWORK_ID'
     NEUTRON_RELAY_SOCKET_PATH_KEY = 'NEUTRON_RELAY_SOCKET_PATH'
-    MINIMUM_VERSION = 2.59
+    MINIMUM_VERSION = 2.63
+    MINIMUM_IPV6_VERSION = 2.67
 
     @classmethod
     def check_version(cls):
@@ -285,10 +323,19 @@
                               'DHCP AGENT MAY NOT RUN CORRECTLY! '
                               'Please ensure that its version is %s '
                               'or above!'), cls.MINIMUM_VERSION)
+                raise SystemExit(1)
+            is_valid_version = float(ver) >= cls.MINIMUM_IPV6_VERSION
+            if not is_valid_version:
+                LOG.warning(_('FAILED VERSION REQUIREMENT FOR DNSMASQ. '
+                              'DHCP AGENT MAY NOT RUN CORRECTLY WHEN '
+                              'SERVING IPV6 STATEFUL SUBNETS! '
+                              'Please ensure that its version is %s '
+                              'or above!'), cls.MINIMUM_IPV6_VERSION)
         except (OSError, RuntimeError, IndexError, ValueError):
             LOG.warning(_('Unable to determine dnsmasq version. '
                           'Please ensure that its version is %s '
                           'or above!'), cls.MINIMUM_VERSION)
+            raise SystemExit(1)
         return float(ver)
 
     @classmethod
@@ -319,43 +366,60 @@
             '--pid-file=%s' % self.get_conf_file_name(
                 'pid', ensure_conf_dir=True),
             '--dhcp-hostsfile=%s' % self._output_hosts_file(),
+            '--addn-hosts=%s' % self._output_addn_hosts_file(),
             '--dhcp-optsfile=%s' % self._output_opts_file(),
             '--leasefile-ro',
         ]
 
         possible_leases = 0
         for i, subnet in enumerate(self.network.subnets):
+            mode = None
             # if a subnet is specified to have dhcp disabled
             if not subnet.enable_dhcp:
                 continue
             if subnet.ip_version == 4:
                 mode = 'static'
             else:
-                # TODO(gmoodalb): how do we indicate other options
-                # ra-only, slaac, ra-nameservers, and ra-stateless.
                 # We need to also set the DUID for the DHCPv6 server to use
                 macaddr_cmd = ['/usr/sbin/dladm', 'show-linkprop',
                                '-co', 'value', '-p', 'mac-address',
                                self.interface_name]
                 stdout = utils.execute(macaddr_cmd)
                 uid = stdout.splitlines()[0].strip()
+                # format the MAC address
+                uid = ':'.join(['%.2x' % w for w in netaddr.EUI(uid).words])
                 # IANA assigned ID for Oracle
                 enterprise_id = '111'
                 cmd.append('--dhcp-duid=%s,%s' % (enterprise_id, uid))
-                mode = 'static'
-            if self.version >= self.MINIMUM_VERSION:
-                set_tag = 'set:'
-            else:
-                set_tag = ''
+
+                # Note(scollins) If the IPv6 attributes are not set, set it as
+                # static to preserve previous behavior
+                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'
 
             cidr = netaddr.IPNetwork(subnet.cidr)
 
-            cmd.append('--dhcp-range=%s%s,%s,%s,%ss' %
-                       (set_tag, self._TAG_PREFIX % i,
-                        cidr.network,
-                        mode,
-                        self.conf.dhcp_lease_duration))
-            possible_leases += cidr.size
+            if self.conf.dhcp_lease_duration == -1:
+                lease = 'infinite'
+            else:
+                lease = '%ss' % self.conf.dhcp_lease_duration
+
+            # mode is optional and is not set - skip it
+            if mode:
+                if subnet.ip_version == 4:
+                    cmd.append('--dhcp-range=%s%s,%s,%s,%s' %
+                               ('set:', self._TAG_PREFIX % i,
+                                cidr.network, mode, lease))
+                else:
+                    cmd.append('--dhcp-range=%s%s,%s,%s,%d,%s' %
+                               ('set:', self._TAG_PREFIX % i,
+                                cidr.network, mode,
+                                cidr.prefixlen, lease))
+                possible_leases += cidr.size
 
         # Cap the limit because creating lots of subnets can inflate
         # this possible lease cap.
@@ -363,8 +427,10 @@
                    min(possible_leases, self.conf.dnsmasq_lease_max))
 
         cmd.append('--conf-file=%s' % self.conf.dnsmasq_config_file)
-        if self.conf.dnsmasq_dns_server:
-            cmd.append('--server=%s' % self.conf.dnsmasq_dns_server)
+        if self.conf.dnsmasq_dns_servers:
+            cmd.extend(
+                '--server=%s' % server
+                for server in self.conf.dnsmasq_dns_servers)
 
         if self.conf.dhcp_domain:
             cmd.append('--domain=%s' % self.conf.dhcp_domain)
@@ -372,12 +438,11 @@
         # TODO(gmoodalb): prepend the env vars before command
         utils.execute(cmd, self.root_helper)
 
-    def release_lease(self, mac_address, removed_ips):
+    def _release_lease(self, mac_address, ip):
         """Release a DHCP lease."""
-        for ip in removed_ips or []:
-            cmd = ['/usr/lib/inet/dhcp_release', self.interface_name,
-                   ip, mac_address]
-            utils.execute(cmd, self.root_helper)
+        cmd = ['/usr/lib/inet/dhcp_release', self.interface_name,
+               ip, mac_address]
+        utils.execute(cmd, self.root_helper)
 
     def reload_allocations(self):
         """Rebuild the dnsmasq config and signal the dnsmasq to reload."""
@@ -389,7 +454,9 @@
                         'turned off DHCP: %s'), self.network.id)
             return
 
+        self._release_unused_leases()
         self._output_hosts_file()
+        self._output_addn_hosts_file()
         self._output_opts_file()
         if self.active:
             cmd = ['kill', '-HUP', self.pid]
@@ -397,32 +464,120 @@
         else:
             LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), self.pid)
         LOG.debug(_('Reloading allocations for network: %s'), self.network.id)
-        self.device_manager.update(self.network)
+        self.device_manager.update(self.network, self.interface_name)
+
+    def _iter_hosts(self):
+        """Iterate over hosts.
 
-    def _output_hosts_file(self):
-        """Writes a dnsmasq compatible hosts file."""
-        r = re.compile('[:.]')
-        buf = StringIO.StringIO()
-
+        For each host on the network we yield a tuple containing:
+        (
+            port,  # a DictModel instance representing the port.
+            alloc,  # a DictModel instance of the allocated ip and subnet.
+            host_name,  # Host name.
+            name,  # Canonical hostname in the format 'hostname[.domain]'.
+        )
+        """
+        v6_nets = dict((subnet.id, subnet) for subnet in
+                       self.network.subnets if subnet.ip_version == 6)
         for port in self.network.ports:
             for alloc in port.fixed_ips:
-                name = 'host-%s.%s' % (r.sub('-', alloc.ip_address),
-                                       self.conf.dhcp_domain)
-                set_tag = ''
-                if getattr(port, 'extra_dhcp_opts', False):
-                    if self.version >= self.MINIMUM_VERSION:
-                        set_tag = 'set:'
+                # Note(scollins) Only create entries that are
+                # associated with the subnet being managed by this
+                # dhcp agent
+                if alloc.subnet_id in v6_nets:
+                    addr_mode = v6_nets[alloc.subnet_id].ipv6_address_mode
+                    if addr_mode != constants.DHCPV6_STATEFUL:
+                        continue
+                hostname = 'host-%s' % alloc.ip_address.replace(
+                    '.', '-').replace(':', '-')
+                fqdn = hostname
+                if self.conf.dhcp_domain:
+                    fqdn = '%s.%s' % (fqdn, self.conf.dhcp_domain)
+                yield (port, alloc, hostname, fqdn)
+
+    def _output_hosts_file(self):
+        """Writes a dnsmasq compatible dhcp hosts file.
+
+        The generated file is sent to the --dhcp-hostsfile option of dnsmasq,
+        and lists the hosts on the network which should receive a dhcp lease.
+        Each line in this file is in the form::
+
+            'mac_address,FQDN,ip_address'
+
+        IMPORTANT NOTE: a dnsmasq instance does not resolve hosts defined in
+        this file if it did not give a lease to a host listed in it (e.g.:
+        multiple dnsmasq instances on the same network if this network is on
+        multiple network nodes). This file is only defining hosts which
+        should receive a dhcp lease, the hosts resolution in itself is
+        defined by the `_output_addn_hosts_file` method.
+        """
+        buf = six.StringIO()
+        filename = self.get_conf_file_name('host')
+
+        LOG.debug(_('Building host file: %s'), filename)
+        for (port, alloc, hostname, name) in self._iter_hosts():
+            # (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_address = alloc.ip_address
+            if netaddr.valid_ipv6(ip_address):
+                ip_address = '[%s]' % ip_address
+
+            LOG.debug(_('Adding %(mac)s : %(name)s : %(ip)s'),
+                      {"mac": port.mac_address, "name": name,
+                       "ip": ip_address})
 
-                    buf.write('%s,%s,%s,%s%s\n' %
-                              (port.mac_address, name, alloc.ip_address,
-                               set_tag, port.id))
-                else:
-                    buf.write('%s,%s,%s\n' %
-                              (port.mac_address, name, alloc.ip_address))
+            if getattr(port, 'extra_dhcp_opts', False):
+                buf.write('%s,%s,%s,%s%s\n' %
+                          (port.mac_address, name, ip_address,
+                           'set:', port.id))
+            else:
+                buf.write('%s,%s,%s\n' %
+                          (port.mac_address, name, ip_address))
+
+        utils.replace_file(filename, buf.getvalue())
+        LOG.debug(_('Done building host file %s'), filename)
+        return filename
+
+    def _read_hosts_file_leases(self, filename):
+        leases = set()
+        if os.path.exists(filename):
+            with open(filename) as f:
+                for l in f.readlines():
+                    host = l.strip().split(',')
+                    leases.add((host[2], host[0]))
+        return leases
+
+    def _release_unused_leases(self):
+        filename = self.get_conf_file_name('host')
+        old_leases = self._read_hosts_file_leases(filename)
 
-        name = self.get_conf_file_name('host')
-        utils.replace_file(name, buf.getvalue())
-        return name
+        new_leases = set()
+        for port in self.network.ports:
+            for alloc in port.fixed_ips:
+                new_leases.add((alloc.ip_address, port.mac_address))
+
+        for ip, mac in old_leases - new_leases:
+            self._release_lease(mac, ip)
+
+    def _output_addn_hosts_file(self):
+        """Writes a dnsmasq compatible additional hosts file.
+
+        The generated file is sent to the --addn-hosts option of dnsmasq,
+        and lists the hosts on the network which should be resolved even if
+        the dnsmaq instance did not give a lease to the host (see the
+        `_output_hosts_file` method).
+        Each line in this file is in the same form as a standard /etc/hosts
+        file.
+        """
+        buf = six.StringIO()
+        for (port, alloc, hostname, fqdn) in self._iter_hosts():
+            # It is compulsory to write the `fqdn` before the `hostname` in
+            # order to obtain it in PTR responses.
+            buf.write('%s\t%s %s\n' % (alloc.ip_address, fqdn, hostname))
+        addn_hosts = self.get_conf_file_name('addn_hosts')
+        utils.replace_file(addn_hosts, buf.getvalue())
+        return addn_hosts
 
     def _output_opts_file(self):
         """Write a dnsmasq compatible options file."""
@@ -431,53 +586,109 @@
             subnet_to_interface_ip = self._make_subnet_interface_ip_map()
 
         options = []
+
+        isolated_subnets = self.get_isolated_subnets(self.network)
+        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:
                 if hr.destination == "0.0.0.0/0":
-                    gateway = hr.nexthop
+                    if not gateway:
+                        gateway = hr.nexthop
                 else:
                     host_routes.append("%s,%s" % (hr.destination, hr.nexthop))
 
             # Add host routes for isolated network segments
-            enable_metadata = (
-                self.conf.enable_isolated_metadata
-                and not subnet.gateway_ip
-                and subnet.ip_version == 4)
 
-            if enable_metadata:
+            if (isolated_subnets[subnet.id] and
+                    self.conf.enable_isolated_metadata and
+                    subnet.ip_version == 4):
                 subnet_dhcp_ip = subnet_to_interface_ip[subnet.id]
                 host_routes.append(
                     '%s/32,%s' % (METADATA_DEFAULT_IP, subnet_dhcp_ip)
                 )
 
-            if host_routes:
-                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 subnet.ip_version == 4:
                 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
+            # by the server
+            if port.device_owner == constants.DEVICE_OWNER_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():
+            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))
@@ -487,22 +698,52 @@
         # TODO(gmoodalb): need to complete this when we support metadata
         pass
 
-    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)
+
+    @staticmethod
+    def _convert_to_literal_addrs(ip_version, ips):
+        if ip_version == 4:
+            return ips
+        return ['[' + ip + ']' for ip in ips]
+
+    @classmethod
+    def get_isolated_subnets(cls, network):
+        """Returns a dict indicating whether or not a subnet is isolated
 
-        return ','.join((set_tag + tag, '%s' % option) + args)
+        A subnet is considered non-isolated if there is a port connected to
+        the subnet, and the port's ip address matches that of the subnet's
+        gateway. The port must be owned by a nuetron router.
+        """
+        isolated_subnets = collections.defaultdict(lambda: True)
+        subnets = dict((subnet.id, subnet) for subnet in network.subnets)
+
+        for port in network.ports:
+            if port.device_owner != constants.DEVICE_OWNER_ROUTER_INTF:
+                continue
+            for alloc in port.fixed_ips:
+                if subnets[alloc.subnet_id].gateway_ip == alloc.ip_address:
+                    isolated_subnets[alloc.subnet_id] = False
+
+        return isolated_subnets
+
+    @classmethod
+    def should_enable_metadata(cls, conf, network):
+        """True if there exists a subnet for which a metadata proxy is needed
+        """
+        return False
 
     @classmethod
     def lease_update(cls):
@@ -538,7 +779,9 @@
         self.root_helper = root_helper
         self.plugin = plugin
         if not conf.interface_driver:
-            raise SystemExit(_('You must specify an interface driver'))
+            msg = _('An interface driver must be specified')
+            LOG.error(msg)
+            raise SystemExit(1)
         try:
             self.driver = importutils.import_object(
                 conf.interface_driver, conf)
@@ -546,7 +789,8 @@
             msg = (_("Error importing interface driver '%(driver)s': "
                    "%(inner)s") % {'driver': conf.interface_driver,
                                    'inner': e})
-            raise SystemExit(msg)
+            LOG.error(msg)
+            raise SystemExit(1)
 
     def get_interface_name(self, network, port):
         """Return interface(device) name for use by the DHCP process."""
@@ -560,6 +804,16 @@
         host_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname())
         return 'dhcp%s-%s' % (host_uuid, network.id)
 
+    def _set_default_route(self, network, device_name):
+        """Sets the default gateway for this dhcp namespace.
+
+        This method is idempotent and will only adjust the route if adjusting
+        it would change it from what it already is.  This makes it safe to call
+        and avoids unnecessary perturbation of the system.
+        """
+        # we do not support namespaces
+        pass
+
     def setup_dhcp_port(self, network):
         """Create/update DHCP port for the host if needed and return port."""
 
@@ -595,12 +849,29 @@
                     port_fixed_ips.extend(
                         [dict(subnet_id=s) for s in dhcp_enabled_subnet_ids])
                     dhcp_port = self.plugin.update_dhcp_port(
-                        port.id, {'port': {'fixed_ips': port_fixed_ips}})
+                        port.id, {'port': {'network_id': network.id,
+                                           'fixed_ips': port_fixed_ips}})
+                    if not dhcp_port:
+                        raise exceptions.Conflict()
                 else:
                     dhcp_port = port
                 # break since we found port that matches device_id
                 break
 
+        # check for a reserved DHCP port
+        if dhcp_port is None:
+            LOG.debug(_('DHCP port %(device_id)s on network %(network_id)s'
+                        ' does not yet exist. Checking for a reserved port.'),
+                      {'device_id': device_id, 'network_id': network.id})
+            for port in network.ports:
+                port_device_id = getattr(port, 'device_id', None)
+                if port_device_id == constants.DEVICE_ID_RESERVED_DHCP_PORT:
+                    dhcp_port = self.plugin.update_dhcp_port(
+                        port.id, {'port': {'network_id': network.id,
+                                           'device_id': device_id}})
+                    if dhcp_port:
+                        break
+
         # DHCP port has not yet been created.
         if dhcp_port is None:
             LOG.debug(_('DHCP port %(device_id)s on network %(network_id)s'
@@ -615,6 +886,9 @@
                 fixed_ips=[dict(subnet_id=s) for s in dhcp_enabled_subnet_ids])
             dhcp_port = self.plugin.create_dhcp_port({'port': port_dict})
 
+        if not dhcp_port:
+            raise exceptions.Conflict()
+
         # Convert subnet_id to subnet dict
         fixed_ips = [dict(subnet_id=fixed_ip.subnet_id,
                           ip_address=fixed_ip.ip_address,
@@ -627,21 +901,16 @@
 
         return dhcp_port
 
-    def setup(self, network, reuse_existing=False):
+    def setup(self, network):
         """Create and initialize a device for network's DHCP on this host."""
         port = self.setup_dhcp_port(network)
         interface_name = self.get_interface_name(network, port)
 
         if net_lib.Datalink.datalink_exists(interface_name):
-            if not reuse_existing:
-                raise exceptions.PreexistingDeviceFailure(
-                    dev_name=interface_name)
-
-                LOG.debug(_('Reusing existing device: %s.'), interface_name)
+            LOG.debug(_('Reusing existing device: %s.'), interface_name)
         else:
             self.driver.plug(network.tenant_id, network.id,
-                             port.id,
-                             interface_name)
+                             port.id, interface_name)
         ip_cidrs = []
         for fixed_ip in port.fixed_ips:
             subnet = fixed_ip.subnet
@@ -649,14 +918,18 @@
             ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
             ip_cidrs.append(ip_cidr)
 
-        if (self.conf.enable_isolated_metadata and self.conf.use_namespaces):
+        if (self.conf.enable_isolated_metadata and
+                self.conf.use_namespaces):
             ip_cidrs.append(METADATA_DEFAULT_CIDR)
 
         self.driver.init_l3(interface_name, ip_cidrs)
 
+        if self.conf.use_namespaces:
+            self._set_default_route(network, interface_name)
+
         return interface_name
 
-    def update(self, network):
+    def update(self, network, device_name):
         """Update device settings for the network's DHCP on this host."""
         pass