]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Remove DHCP lease logic
authorAaron Rosen <arosen@nicira.com>
Tue, 16 Jul 2013 23:06:32 +0000 (16:06 -0700)
committerAaron Rosen <arosen@nicira.com>
Mon, 12 Aug 2013 23:55:48 +0000 (16:55 -0700)
Previously neutron was keeping track of dhcp lease time in order
to ensure it didn't hand out an ip address that was already leased.
This patch removes that logic and instead leverages the dhcp_release
utility. This allows us to reuse ip addresses immediately after a port
is deleted. This patch also bumps the lease time to 24 hours instead
of 2 minutes with reduces the amount of dhcp traffic.

DocImpact

There is a DocImpact for this bug related to the upgrade path. One should
first upgrade their dhcp-agents. Then wait till the dhcp_lease time has
expired. Lastly, update neutron-server in order to avoid the case where
an instance is deleted and the dnsmasq process has not released the lease
and neturon allocates that ip to a new port.

Fixes bug: 1202392
Implements blueprint: remove-dhcp-lease

Change-Id: Ifcb4f093c92904ceb896438987d53e692eb7fb26

15 files changed:
bin/neutron-dhcp-agent-dnsmasq-lease-update [deleted file]
etc/neutron.conf
etc/neutron/rootwrap.d/dhcp.filters
neutron/agent/dhcp_agent.py
neutron/agent/linux/dhcp.py
neutron/common/config.py
neutron/db/db_base_plugin_v2.py
neutron/db/dhcp_rpc_base.py
neutron/db/migration/alembic_migrations/versions/f9263d6df56_remove_dhcp_lease.py [new file with mode: 0644]
neutron/db/models_v2.py
neutron/tests/unit/test_config.py
neutron/tests/unit/test_db_plugin.py
neutron/tests/unit/test_dhcp_agent.py
neutron/tests/unit/test_linux_dhcp.py
setup.cfg

diff --git a/bin/neutron-dhcp-agent-dnsmasq-lease-update b/bin/neutron-dhcp-agent-dnsmasq-lease-update
deleted file mode 100755 (executable)
index 41bbbe3..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright (c) 2012 OpenStack Foundation.
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from neutron.agent.linux import dhcp
-dhcp.Dnsmasq.lease_update()
index 8cdaa1d657e0780b3866e8aec112c655440ab60d..9f66b8cbde452922315e68de4376218dc0403938 100644 (file)
@@ -69,7 +69,7 @@ lock_path = $state_path/lock
 # mac_generation_retries = 16
 
 # DHCP Lease duration (in seconds)
-# dhcp_lease_duration = 120
+# dhcp_lease_duration = 86400
 
 # Allow sending resource operation notification to DHCP agent
 # dhcp_agent_notification = True
index e615ddb9a39498e24249948d814554f2e2fd785c..26e2e56643beaccf4e364b747e1dd7123cbe394d 100644 (file)
@@ -9,7 +9,7 @@
 [Filters]
 
 # dhcp-agent
-dnsmasq: EnvFilter, dnsmasq, root, NEUTRON_RELAY_SOCKET_PATH=, NEUTRON_NETWORK_ID=
+dnsmasq: EnvFilter, dnsmasq, root, NEUTRON_NETWORK_ID=
 # dhcp-agent uses kill as well, that's handled by the generic KillFilter
 # it looks like these are the only signals needed, per
 # neutron/agent/linux/dhcp.py
@@ -20,6 +20,7 @@ kill_dnsmasq_usr: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP
 cat: RegExpFilter, cat, root, cat, /proc/\d+/cmdline
 ovs-vsctl: CommandFilter, ovs-vsctl, root
 ivs-ctl: CommandFilter, ivs-ctl, root
+dhcp_release: CommandFilter, dhcp_release, root
 
 # metadata proxy
 metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root
index 627bcca62e664836aa52dec37ba8c24f7412a3d4..a826d2f312e5afd4c27352eda2c10f46b06d061d 100644 (file)
@@ -37,12 +37,10 @@ from neutron.common import utils
 from neutron import context
 from neutron import manager
 from neutron.openstack.common import importutils
-from neutron.openstack.common import jsonutils
 from neutron.openstack.common import log as logging
 from neutron.openstack.common import loopingcall
 from neutron.openstack.common.rpc import proxy
 from neutron.openstack.common import service
-from neutron.openstack.common import uuidutils
 from neutron import service as neutron_service
 
 LOG = logging.getLogger(__name__)
@@ -81,8 +79,10 @@ class DhcpAgent(manager.Manager):
         ctx = context.get_admin_context_without_session()
         self.plugin_rpc = DhcpPluginApi(topics.PLUGIN, ctx)
         self.device_manager = DeviceManager(self.conf, self.plugin_rpc)
-        self.lease_relay = DhcpLeaseRelay(self.update_lease)
-
+        # create dhcp dir to store dhcp info
+        dhcp_dir = os.path.dirname("/%s/dhcp/" % self.conf.state_path)
+        if not os.path.isdir(dhcp_dir):
+            os.makedirs(dhcp_dir, 0o755)
         self.dhcp_version = self.dhcp_driver_cls.check_version()
         self._populate_networks_cache()
 
@@ -114,13 +114,12 @@ class DhcpAgent(manager.Manager):
         """Activate the DHCP agent."""
         self.sync_state()
         self.periodic_resync()
-        self.lease_relay.start()
 
     def _ns_name(self, network):
         if self.conf.use_namespaces:
             return NS_PREFIX + network.id
 
-    def call_driver(self, action, network):
+    def call_driver(self, action, network, **action_kwargs):
         """Invoke an action on a DHCP driver instance."""
         try:
             # the Driver expects something that is duck typed similar to
@@ -131,21 +130,13 @@ class DhcpAgent(manager.Manager):
                                           self.device_manager,
                                           self._ns_name(network),
                                           self.dhcp_version)
-            getattr(driver, action)()
+            getattr(driver, action)(**action_kwargs)
             return True
 
         except Exception:
             self.needs_resync = True
             LOG.exception(_('Unable to %s dhcp.'), action)
 
-    def update_lease(self, network_id, ip_address, time_remaining):
-        try:
-            self.plugin_rpc.update_lease_expiration(network_id, ip_address,
-                                                    time_remaining)
-        except Exception:
-            self.needs_resync = True
-            LOG.exception(_('Unable to update lease'))
-
     def sync_state(self):
         """Sync the local DHCP state with Neutron."""
         LOG.info(_('Synchronizing state'))
@@ -246,6 +237,22 @@ class DhcpAgent(manager.Manager):
         if new_cidrs:
             self.device_manager.update(network)
 
+    def release_lease_for_removed_ips(self, port, network):
+        """Releases the dhcp lease for ips removed from a port."""
+        prev_port = self.cache.get_port_by_id(port.id)
+        if prev_port:
+            previous_ips = set(fixed_ip.ip_address
+                               for fixed_ip in prev_port.fixed_ips)
+            current_ips = set(fixed_ip.ip_address
+                              for fixed_ip in port.fixed_ips)
+            # pass in port with removed ips on it
+            removed_ips = previous_ips - current_ips
+            if removed_ips:
+                self.call_driver('release_lease',
+                                 network,
+                                 mac_address=port.mac_address,
+                                 removed_ips=removed_ips)
+
     @utils.synchronized('dhcp-agent')
     def network_create_end(self, context, payload):
         """Handle the network.create.end notification event."""
@@ -289,6 +296,7 @@ class DhcpAgent(manager.Manager):
         port = DictModel(payload['port'])
         network = self.cache.get_network_by_id(port.network_id)
         if network:
+            self.release_lease_for_removed_ips(port, network)
             self.cache.put_port(port)
             self.call_driver('reload_allocations', network)
 
@@ -302,6 +310,12 @@ class DhcpAgent(manager.Manager):
         if port:
             network = self.cache.get_network_by_id(port.network_id)
             self.cache.remove_port(port)
+            removed_ips = [fixed_ip.ip_address
+                           for fixed_ip in port.fixed_ips]
+            self.call_driver('release_lease',
+                             network,
+                             mac_address=port.mac_address,
+                             removed_ips=removed_ips)
             self.call_driver('reload_allocations', network)
 
     def enable_isolated_metadata_proxy(self, network):
@@ -435,16 +449,6 @@ class DhcpPluginApi(proxy.RpcProxy):
                                        host=self.host),
                          topic=self.topic)
 
-    def update_lease_expiration(self, network_id, ip_address, lease_remaining):
-        """Make a remote process call to update the ip lease expiration."""
-        self.cast(self.context,
-                  self.make_msg('update_lease_expiration',
-                                network_id=network_id,
-                                ip_address=ip_address,
-                                lease_remaining=lease_remaining,
-                                host=self.host),
-                  topic=self.topic)
-
 
 class NetworkCache(object):
     """Agent cache of the current network state."""
@@ -747,67 +751,6 @@ class DictModel(object):
             setattr(self, key, value)
 
 
-class DhcpLeaseRelay(object):
-    """UNIX domain socket server for processing lease updates.
-
-    Network namespace isolation prevents the DHCP process from notifying
-    Neutron directly.  This class works around the limitation by using the
-    domain socket to pass the information.  This class handles message.
-    receiving and then calls the callback method.
-    """
-
-    OPTS = [
-        cfg.StrOpt('dhcp_lease_relay_socket',
-                   default='$state_path/dhcp/lease_relay',
-                   help=_('Location to DHCP lease relay UNIX domain socket'))
-    ]
-
-    def __init__(self, lease_update_callback):
-        self.callback = lease_update_callback
-
-        dirname = os.path.dirname(cfg.CONF.dhcp_lease_relay_socket)
-        if os.path.isdir(dirname):
-            try:
-                os.unlink(cfg.CONF.dhcp_lease_relay_socket)
-            except OSError:
-                if os.path.exists(cfg.CONF.dhcp_lease_relay_socket):
-                    raise
-        else:
-            os.makedirs(dirname, 0o755)
-
-    def _handler(self, client_sock, client_addr):
-        """Handle incoming lease relay stream connection.
-
-        This method will only read the first 1024 bytes and then close the
-        connection.  The limit exists to limit the impact of misbehaving
-        clients.
-        """
-        try:
-            msg = client_sock.recv(1024)
-            data = jsonutils.loads(msg)
-            client_sock.close()
-
-            network_id = data['network_id']
-            if not uuidutils.is_uuid_like(network_id):
-                raise ValueError(_("Network ID %s is not a valid UUID") %
-                                 network_id)
-            ip_address = str(netaddr.IPAddress(data['ip_address']))
-            lease_remaining = int(data['lease_remaining'])
-            self.callback(network_id, ip_address, lease_remaining)
-        except ValueError as e:
-            LOG.warn(_('Unable to parse lease relay msg to dict.'))
-            LOG.warn(_('Exception value: %s'), e)
-            LOG.warn(_('Message representation: %s'), repr(msg))
-        except Exception as e:
-            LOG.exception(_('Unable update lease. Exception'))
-
-    def start(self):
-        """Spawn a green thread to run the lease relay unix socket server."""
-        listener = eventlet.listen(cfg.CONF.dhcp_lease_relay_socket,
-                                   family=socket.AF_UNIX)
-        eventlet.spawn(eventlet.serve, listener, self._handler)
-
-
 class DhcpAgentWithStateReport(DhcpAgent):
     def __init__(self, host=None):
         super(DhcpAgentWithStateReport, self).__init__(host=host)
@@ -863,7 +806,6 @@ def register_options():
     config.register_agent_state_opts_helper(cfg.CONF)
     config.register_root_helper(cfg.CONF)
     cfg.CONF.register_opts(DeviceManager.OPTS)
-    cfg.CONF.register_opts(DhcpLeaseRelay.OPTS)
     cfg.CONF.register_opts(dhcp.OPTS)
     cfg.CONF.register_opts(interface.OPTS)
 
index ef10b35f8ef892907de354510b8ed07d7a5079d8..9ea49bb7b7b1dd17a72f3a4c7746e4aca8edc8ef 100644 (file)
@@ -89,6 +89,10 @@ class DhcpBase(object):
     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."""
@@ -261,8 +265,6 @@ class Dnsmasq(DhcpLocalProcess):
         """Spawns a Dnsmasq process for the network."""
         env = {
             self.NEUTRON_NETWORK_ID_KEY: self.network.id,
-            self.NEUTRON_RELAY_SOCKET_PATH_KEY:
-            self.conf.dhcp_lease_relay_socket
         }
 
         cmd = [
@@ -279,7 +281,6 @@ class Dnsmasq(DhcpLocalProcess):
             #'--dhcp-lease-max=%s' % ?,
             '--dhcp-hostsfile=%s' % self._output_hosts_file(),
             '--dhcp-optsfile=%s' % self._output_opts_file(),
-            '--dhcp-script=%s' % self._lease_relay_script_path(),
             '--leasefile-ro',
         ]
 
@@ -318,6 +319,16 @@ class Dnsmasq(DhcpLocalProcess):
             cmd = ['%s=%s' % pair for pair in env.items()] + cmd
             utils.execute(cmd, self.root_helper)
 
+    def release_lease(self, mac_address, removed_ips):
+        """Release a DHCP lease."""
+        for ip in removed_ips or []:
+            cmd = ['dhcp_release', self.interface_name, ip, mac_address]
+            if self.namespace:
+                ip_wrapper = ip_lib.IPWrapper(self.root_helper, self.namespace)
+                ip_wrapper.netns.execute(cmd)
+            else:
+                utils.execute(cmd, self.root_helper)
+
     def reload_allocations(self):
         """Rebuild the dnsmasq config and signal the dnsmasq to reload."""
 
@@ -428,10 +439,6 @@ class Dnsmasq(DhcpLocalProcess):
 
         return retval
 
-    def _lease_relay_script_path(self):
-        return os.path.join(os.path.dirname(sys.argv[0]),
-                            'neutron-dhcp-agent-dnsmasq-lease-update')
-
     def _format_option(self, index, option, *args):
         """Format DHCP option by option name or code."""
         if self.version >= self.MINIMUM_VERSION:
index 6bbe080b73964ea0d52929ec7dc69996bb554a3e..4f0eb47bf0b8d1b53cadb9ee219a555313c90d60 100644 (file)
@@ -71,7 +71,7 @@ core_opts = [
                help=_("Maximum number of host routes per subnet")),
     cfg.IntOpt('max_fixed_ips_per_port', default=5,
                help=_("Maximum number of fixed ips per port")),
-    cfg.IntOpt('dhcp_lease_duration', default=120,
+    cfg.IntOpt('dhcp_lease_duration', default=86400,
                deprecated_name='dhcp_lease_time',
                help=_("DHCP lease duration")),
     cfg.BoolOpt('dhcp_agent_notification', default=True,
index cbdfbfa5bf1c6b71a14d2f550bc2bfbba329132d..1c76ae3e604eb45bc68df97b8c37ae1b570452ec 100644 (file)
@@ -293,54 +293,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
             return True
         return False
 
-    @staticmethod
-    def _hold_ip(context, network_id, subnet_id, port_id, ip_address):
-        alloc_qry = context.session.query(
-            models_v2.IPAllocation).with_lockmode('update')
-        allocated = alloc_qry.filter_by(network_id=network_id,
-                                        port_id=port_id,
-                                        ip_address=ip_address,
-                                        subnet_id=subnet_id).one()
-
-        if not allocated:
-            return
-        if allocated.expiration < timeutils.utcnow():
-            # immediately delete expired allocations
-            NeutronDbPluginV2._recycle_ip(
-                context, network_id, subnet_id, ip_address)
-        else:
-            LOG.debug(_("Hold allocated IP %(ip_address)s "
-                        "(%(network_id)s/%(subnet_id)s/%(port_id)s)"),
-                      {'ip_address': ip_address,
-                       'network_id': network_id,
-                       'subnet_id': subnet_id,
-                       'port_id': port_id})
-            allocated.port_id = None
-
-    @staticmethod
-    def _recycle_expired_ip_allocations(context, network_id):
-        """Return held ip allocations with expired leases back to the pool."""
-        if network_id in getattr(context, '_recycled_networks', set()):
-            return
-
-        expired_qry = context.session.query(
-            models_v2.IPAllocation).with_lockmode('update')
-        expired_qry = expired_qry.filter_by(network_id=network_id,
-                                            port_id=None)
-        expired_qry = expired_qry.filter(
-            models_v2.IPAllocation.expiration <= timeutils.utcnow())
-
-        for expired in expired_qry:
-            NeutronDbPluginV2._recycle_ip(context,
-                                          network_id,
-                                          expired['subnet_id'],
-                                          expired['ip_address'])
-
-        if hasattr(context, '_recycled_networks'):
-            context._recycled_networks.add(network_id)
-        else:
-            context._recycled_networks = set([network_id])
-
     @staticmethod
     def _recycle_ip(context, network_id, subnet_id, ip_address):
         """Return an IP address to the pool of free IP's on the network
@@ -424,11 +376,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
         NeutronDbPluginV2._delete_ip_allocation(context, network_id, subnet_id,
                                                 ip_address)
 
-    @staticmethod
-    def _default_allocation_expiration():
-        return (timeutils.utcnow() +
-                datetime.timedelta(seconds=cfg.CONF.dhcp_lease_duration))
-
     def update_fixed_ip_lease_expiration(self, context, network_id,
                                          ip_address, lease_remaining):
 
@@ -690,11 +637,10 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
         to_add = self._test_fixed_ips_for_port(context, network_id, new_ips)
         for ip in original_ips:
             LOG.debug(_("Port update. Hold %s"), ip)
-            NeutronDbPluginV2._hold_ip(context,
-                                       network_id,
-                                       ip['subnet_id'],
-                                       port_id,
-                                       ip['ip_address'])
+            NeutronDbPluginV2._recycle_ip(context,
+                                          network_id,
+                                          ip['subnet_id'],
+                                          ip['ip_address'])
 
         if to_add:
             LOG.debug(_("Port update. Adding %s"), to_add)
@@ -1321,7 +1267,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
         tenant_id = self._get_tenant_id_for_create(context, p)
 
         with context.session.begin(subtransactions=True):
-            self._recycle_expired_ip_allocations(context, network_id)
             network = self._get_network(context, network_id)
 
             # Ensure that a MAC address is defined and it is unique on the
@@ -1372,7 +1317,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
                         port_id=port_id,
                         ip_address=ip_address,
                         subnet_id=subnet_id,
-                        expiration=self._default_allocation_expiration()
                     )
                     context.session.add(allocated)
 
@@ -1387,8 +1331,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
             # Check if the IPs need to be updated
             if 'fixed_ips' in p:
                 changed_ips = True
-                self._recycle_expired_ip_allocations(context,
-                                                     port['network_id'])
                 original = self._make_port_dict(port, process_extensions=False)
                 added_ips, prev_ips = self._update_ips_for_port(
                     context, port["network_id"], id, original["fixed_ips"],
@@ -1398,8 +1340,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
                 for ip in added_ips:
                     allocated = models_v2.IPAllocation(
                         network_id=port['network_id'], port_id=port.id,
-                        ip_address=ip['ip_address'], subnet_id=ip['subnet_id'],
-                        expiration=self._default_allocation_expiration())
+                        ip_address=ip['ip_address'], subnet_id=ip['subnet_id'])
                     context.session.add(allocated)
             # Remove all attributes in p which are not in the port DB model
             # and then update the port
@@ -1428,11 +1369,10 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
             if NeutronDbPluginV2._check_ip_in_allocation_pool(
                 context, a['subnet_id'], subnet['gateway_ip'],
                 a['ip_address']):
-                NeutronDbPluginV2._hold_ip(context,
-                                           a['network_id'],
-                                           a['subnet_id'],
-                                           id,
-                                           a['ip_address'])
+                NeutronDbPluginV2._recycle_ip(context,
+                                              a['network_id'],
+                                              a['subnet_id'],
+                                              a['ip_address'])
             else:
                 # IPs out of allocation pool will not be recycled, but
                 # we do need to delete the allocation from the DB
index fb47be301a37c0baf375ce530ef83d958e073622..25a2876cdc53f736a758459a26de239d0de388a8 100644 (file)
@@ -47,8 +47,8 @@ class DhcpRpcCallbackMixin(object):
     def get_active_networks(self, context, **kwargs):
         """Retrieve and return a list of the active network ids."""
         # NOTE(arosen): This method is no longer used by the DHCP agent but is
-        # left so that quantum-dhcp-agents will still continue to work if
-        # quantum-server is upgraded and not the agent.
+        # left so that neutron-dhcp-agents will still continue to work if
+        # neutron-server is upgraded and not the agent.
         host = kwargs.get('host')
         LOG.debug(_('get_active_networks requested from %s'), host)
         nets = self._get_active_networks(context, **kwargs)
@@ -97,8 +97,8 @@ class DhcpRpcCallbackMixin(object):
 
         """
         # NOTE(arosen): This method is no longer used by the DHCP agent but is
-        # left so that quantum-dhcp-agents will still continue to work if
-        # quantum-server is upgraded and not the agent.
+        # left so that neutron-dhcp-agents will still continue to work if
+        # neutron-server is upgraded and not the agent.
 
         host = kwargs.get('host')
         network_id = kwargs.get('network_id')
@@ -209,20 +209,13 @@ class DhcpRpcCallbackMixin(object):
 
     def update_lease_expiration(self, context, **kwargs):
         """Release the fixed_ip associated the subnet on a port."""
+        # NOTE(arosen): This method is no longer used by the DHCP agent but is
+        # left so that neutron-dhcp-agents will still continue to work if
+        # neutron-server is upgraded and not the agent.
         host = kwargs.get('host')
-        network_id = kwargs.get('network_id')
-        ip_address = kwargs.get('ip_address')
-        lease_remaining = kwargs.get('lease_remaining')
-
-        LOG.debug(_('Updating lease expiration for %(ip_address)s on network '
-                    '%(network_id)s from %(host)s.'),
-                  {'ip_address': ip_address,
-                   'network_id': network_id,
-                   'host': host})
-        plugin = manager.NeutronManager.get_plugin()
 
-        plugin.update_fixed_ip_lease_expiration(context, network_id,
-                                                ip_address, lease_remaining)
+        LOG.warning(_('Updating lease expiration is now deprecated. Issued  '
+                      'from host %(host)s.') % host)
 
     def create_dhcp_port(self, context, **kwargs):
         """Create the dhcp port."""
diff --git a/neutron/db/migration/alembic_migrations/versions/f9263d6df56_remove_dhcp_lease.py b/neutron/db/migration/alembic_migrations/versions/f9263d6df56_remove_dhcp_lease.py
new file mode 100644 (file)
index 0000000..e6f38a9
--- /dev/null
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 OpenStack Foundation
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+
+"""remove_dhcp_lease
+
+Revision ID: f9263d6df56
+Revises: c88b6b5fea3
+Create Date: 2013-07-17 12:31:33.731197
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'f9263d6df56'
+down_revision = 'c88b6b5fea3'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+    '*'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade(active_plugins=None, options=None):
+    op.drop_column('ipallocations', u'expiration')
+
+
+def downgrade(active_plugins=None, options=None):
+    op.add_column('ipallocations', sa.Column(u'expiration', sa.DATETIME(),
+                  nullable=True))
index 8c00ffce546d5acc35c71e70fdcaa631eaeb7216..992bd1374ab269e46a098d54d160eb1a59ce10a5 100644 (file)
@@ -99,7 +99,6 @@ class IPAllocation(model_base.BASEV2):
     network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id",
                                                         ondelete="CASCADE"),
                            nullable=False, primary_key=True)
-    expiration = sa.Column(sa.DateTime, nullable=True)
 
 
 class Route(object):
index b43a5ffdf3ec7f22c07ce6a93aa493ea50199ef1..cb773ed64dcef0c660f5d1ae69d9e3fc9c73bde7 100644 (file)
@@ -41,6 +41,6 @@ class ConfigurationTest(base.BaseTestCase):
                                     '..', '..', '..')
         absolute_dir = os.path.abspath(relative_dir)
         self.assertEqual(absolute_dir, cfg.CONF.state_path)
-        self.assertEqual(120, cfg.CONF.dhcp_lease_duration)
+        self.assertEqual(86400, cfg.CONF.dhcp_lease_duration)
         self.assertFalse(cfg.CONF.allow_overlapping_ips)
         self.assertEqual('neutron', cfg.CONF.control_exchange)
index 4a45d34f2be9991dd5b1c41c805cc24f4cc95370..8eadcf187559f6947e0d502d3e5fc4db06ebc945 100644 (file)
@@ -1191,7 +1191,7 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
                 self.assertEqual(ips[1]['subnet_id'], subnet['subnet']['id'])
 
     def test_update_port_update_ips(self):
-        """Update IP and generate new IP on port.
+        """Update IP and associate new IP on port.
 
         Check a port update with the specified subnet_id's. A IP address
         will be allocated for each subnet_id.
@@ -1200,7 +1200,8 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
             with self.port(subnet=subnet) as port:
                 data = {'port': {'admin_state_up': False,
                                  'fixed_ips': [{'subnet_id':
-                                                subnet['subnet']['id']}]}}
+                                                subnet['subnet']['id'],
+                                                'ip_address': '10.0.0.3'}]}}
                 req = self.new_update_request('ports', data,
                                               port['port']['id'])
                 res = self.deserialize(self.fmt, req.get_response(self.api))
@@ -1227,9 +1228,9 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
                                  data['port']['admin_state_up'])
                 ips = res['port']['fixed_ips']
                 self.assertEqual(len(ips), 2)
-                self.assertEqual(ips[0]['ip_address'], '10.0.0.3')
+                self.assertEqual(ips[0]['ip_address'], '10.0.0.2')
                 self.assertEqual(ips[0]['subnet_id'], subnet['subnet']['id'])
-                self.assertEqual(ips[1]['ip_address'], '10.0.0.4')
+                self.assertEqual(ips[1]['ip_address'], '10.0.0.3')
                 self.assertEqual(ips[1]['subnet_id'], subnet['subnet']['id'])
 
     def test_requested_duplicate_mac(self):
@@ -1634,57 +1635,6 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
             res = port_req.get_response(self.api)
             self.assertEqual(res.status_int, 400)
 
-    def test_default_allocation_expiration(self):
-        cfg.CONF.set_override('dhcp_lease_duration', 120)
-        reference = datetime.datetime(2012, 8, 13, 23, 11, 0)
-
-        with mock.patch.object(timeutils, 'utcnow') as mock_utcnow:
-            mock_utcnow.return_value = reference
-
-            plugin = NeutronManager.get_plugin()
-            expires = plugin._default_allocation_expiration()
-            self.assertEqual(expires,
-                             reference + datetime.timedelta(seconds=120))
-
-    def test_update_fixed_ip_lease_expiration(self):
-        cfg.CONF.set_override('dhcp_lease_duration', 10)
-        plugin = NeutronManager.get_plugin()
-        with self.subnet() as subnet:
-            with self.port(subnet=subnet) as port:
-                update_context = context.Context('', port['port']['tenant_id'])
-                plugin.update_fixed_ip_lease_expiration(
-                    update_context,
-                    subnet['subnet']['network_id'],
-                    port['port']['fixed_ips'][0]['ip_address'],
-                    500)
-
-                q = update_context.session.query(models_v2.IPAllocation)
-                q = q.filter_by(
-                    port_id=port['port']['id'],
-                    ip_address=port['port']['fixed_ips'][0]['ip_address'])
-
-                ip_allocation = q.one()
-
-                self.assertThat(
-                    ip_allocation.expiration - timeutils.utcnow(),
-                    matchers.GreaterThan(datetime.timedelta(seconds=10)))
-
-    def test_port_delete_holds_ip(self):
-        base_class = db_base_plugin_v2.NeutronDbPluginV2
-        with mock.patch.object(base_class, '_hold_ip') as hold_ip:
-            with self.subnet() as subnet:
-                with self.port(subnet=subnet, no_delete=True) as port:
-                    req = self.new_delete_request('ports', port['port']['id'])
-                    res = req.get_response(self.api)
-                    self.assertEqual(res.status_int, 204)
-
-                    hold_ip.assert_called_once_with(
-                        mock.ANY,
-                        port['port']['network_id'],
-                        port['port']['fixed_ips'][0]['subnet_id'],
-                        port['port']['id'],
-                        port['port']['fixed_ips'][0]['ip_address'])
-
     def test_update_fixed_ip_lease_expiration_invalid_address(self):
         cfg.CONF.set_override('dhcp_lease_duration', 10)
         plugin = NeutronManager.get_plugin()
@@ -1699,27 +1649,6 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
                         120)
                     self.assertTrue(log.mock_calls)
 
-    def test_hold_ip_address(self):
-        plugin = NeutronManager.get_plugin()
-        with self.subnet() as subnet:
-            with self.port(subnet=subnet) as port:
-                update_context = context.Context('', port['port']['tenant_id'])
-                port_id = port['port']['id']
-                with mock.patch.object(db_base_plugin_v2, 'LOG') as log:
-                    ip_address = port['port']['fixed_ips'][0]['ip_address']
-                    plugin._hold_ip(
-                        update_context,
-                        subnet['subnet']['network_id'],
-                        subnet['subnet']['id'],
-                        port_id,
-                        ip_address)
-                    self.assertTrue(log.mock_calls)
-
-                    q = update_context.session.query(models_v2.IPAllocation)
-                    q = q.filter_by(port_id=None, ip_address=ip_address)
-
-                self.assertEqual(q.count(), 1)
-
     def test_recycle_ip_address_without_allocation_pool(self):
         plugin = NeutronManager.get_plugin()
         allocation_pools = [{"start": '10.0.0.10',
@@ -1742,47 +1671,6 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
                 q = q.filter_by(subnet_id=subnet_id)
                 self.assertEqual(q.count(), 0)
 
-    def test_recycle_held_ip_address(self):
-        plugin = NeutronManager.get_plugin()
-        with self.subnet() as subnet:
-            with self.port(subnet=subnet) as port:
-                update_context = context.Context('', port['port']['tenant_id'])
-                port_id = port['port']['id']
-                port_obj = plugin._get_port(update_context, port_id)
-
-                for fixed_ip in port_obj.fixed_ips:
-                    fixed_ip.active = False
-                    fixed_ip.expiration = datetime.datetime.utcnow()
-
-                with mock.patch.object(plugin, '_recycle_ip') as rc:
-                    plugin._recycle_expired_ip_allocations(
-                        update_context, subnet['subnet']['network_id'])
-                    rc.assertEqual(len(rc.mock_calls), 1)
-                    self.assertEqual(update_context._recycled_networks,
-                                     set([subnet['subnet']['network_id']]))
-
-    def test_recycle_expired_previously_run_within_context(self):
-        plugin = NeutronManager.get_plugin()
-        with self.subnet() as subnet:
-            with self.port(subnet=subnet) as port:
-                update_context = context.Context('', port['port']['tenant_id'])
-                port_id = port['port']['id']
-                port_obj = plugin._get_port(update_context, port_id)
-
-                update_context._recycled_networks = set(
-                    [subnet['subnet']['network_id']])
-
-                for fixed_ip in port_obj.fixed_ips:
-                    fixed_ip.active = False
-                    fixed_ip.expiration = datetime.datetime.utcnow()
-
-                with mock.patch.object(plugin, '_recycle_ip') as rc:
-                    plugin._recycle_expired_ip_allocations(
-                        update_context, subnet['subnet']['network_id'])
-                    rc.assertFalse(rc.called)
-                    self.assertEqual(update_context._recycled_networks,
-                                     set([subnet['subnet']['network_id']]))
-
     def test_max_fixed_ips_exceeded(self):
         with self.subnet(gateway_ip='10.0.0.3',
                          cidr='10.0.0.0/24') as subnet:
index 675bda03f054d5fce24e67bc627787041668a63f..728f7fba554546993502d36c1db38bc113625f7e 100644 (file)
@@ -17,7 +17,6 @@
 
 import copy
 import os
-import socket
 import sys
 import uuid
 
@@ -33,7 +32,6 @@ from neutron.agent.linux import dhcp
 from neutron.agent.linux import interface
 from neutron.common import constants
 from neutron.common import exceptions
-from neutron.openstack.common import jsonutils
 from neutron.tests import base
 
 
@@ -99,7 +97,8 @@ fake_port1 = FakeModel('12345678-1234-aaaa-1234567890ab',
 
 fake_port2 = FakeModel('12345678-1234-aaaa-123456789000',
                        mac_address='aa:bb:cc:dd:ee:99',
-                       network_id='12345678-1234-5678-1234567890ab')
+                       network_id='12345678-1234-5678-1234567890ab',
+                       fixed_ips=[])
 
 fake_meta_port = FakeModel('12345678-1234-aaaa-1234567890ab',
                            mac_address='aa:bb:cc:dd:ee:ff',
@@ -147,7 +146,6 @@ class TestDhcpAgent(base.BaseTestCase):
 
     def test_dhcp_agent_manager(self):
         state_rpc_str = 'neutron.agent.rpc.PluginReportStateAPI'
-        lease_relay_str = 'neutron.agent.dhcp_agent.DhcpLeaseRelay'
         with mock.patch.object(DhcpAgentWithStateReport,
                                'sync_state',
                                autospec=True) as mock_sync_state:
@@ -155,34 +153,27 @@ class TestDhcpAgent(base.BaseTestCase):
                                    'periodic_resync',
                                    autospec=True) as mock_periodic_resync:
                 with mock.patch(state_rpc_str) as state_rpc:
-                    with mock.patch(lease_relay_str) as mock_lease_relay:
-                        with mock.patch.object(sys, 'argv') as sys_argv:
-                            sys_argv.return_value = [
-                                'dhcp', '--config-file',
-                                etcdir('neutron.conf.test')]
-                            cfg.CONF.register_opts(dhcp_agent.DhcpAgent.OPTS)
-                            config.register_agent_state_opts_helper(cfg.CONF)
-                            config.register_root_helper(cfg.CONF)
-                            cfg.CONF.register_opts(
-                                dhcp_agent.DeviceManager.OPTS)
-                            cfg.CONF.register_opts(
-                                dhcp_agent.DhcpLeaseRelay.OPTS)
-                            cfg.CONF.register_opts(dhcp.OPTS)
-                            cfg.CONF.register_opts(interface.OPTS)
-                            cfg.CONF(project='neutron')
-                            agent_mgr = DhcpAgentWithStateReport('testhost')
-                            eventlet.greenthread.sleep(1)
-                            agent_mgr.after_start()
-                            mock_sync_state.assert_called_once_with(agent_mgr)
-                            mock_periodic_resync.assert_called_once_with(
-                                agent_mgr)
-                            state_rpc.assert_has_calls(
-                                [mock.call(mock.ANY),
-                                 mock.call().report_state(mock.ANY, mock.ANY,
-                                                          mock.ANY)])
-                            mock_lease_relay.assert_has_calls(
-                                [mock.call(mock.ANY),
-                                 mock.call().start()])
+                    with mock.patch.object(sys, 'argv') as sys_argv:
+                        sys_argv.return_value = [
+                            'dhcp', '--config-file',
+                            etcdir('neutron.conf.test')]
+                        cfg.CONF.register_opts(dhcp_agent.DhcpAgent.OPTS)
+                        config.register_agent_state_opts_helper(cfg.CONF)
+                        config.register_root_helper(cfg.CONF)
+                        cfg.CONF.register_opts(
+                            dhcp_agent.DeviceManager.OPTS)
+                        cfg.CONF.register_opts(dhcp.OPTS)
+                        cfg.CONF.register_opts(interface.OPTS)
+                        cfg.CONF(project='neutron')
+                        agent_mgr = DhcpAgentWithStateReport('testhost')
+                        eventlet.greenthread.sleep(1)
+                        agent_mgr.after_start()
+                        mock_sync_state.assert_called_once_with(agent_mgr)
+                        mock_periodic_resync.assert_called_once_with(agent_mgr)
+                        state_rpc.assert_has_calls(
+                            [mock.call(mock.ANY),
+                             mock.call().report_state(mock.ANY, mock.ANY,
+                                                      mock.ANY)])
 
     def test_dhcp_agent_main_agent_manager(self):
         logging_str = 'neutron.agent.common.config.setup_logging'
@@ -202,13 +193,11 @@ class TestDhcpAgent(base.BaseTestCase):
             dhcp = dhcp_agent.DhcpAgent(HOSTNAME)
             attrs_to_mock = dict(
                 [(a, mock.DEFAULT) for a in
-                 ['sync_state', 'lease_relay', 'periodic_resync']])
+                 ['sync_state', 'periodic_resync']])
             with mock.patch.multiple(dhcp, **attrs_to_mock) as mocks:
                 dhcp.run()
                 mocks['sync_state'].assert_called_once_with()
                 mocks['periodic_resync'].assert_called_once_with()
-                mocks['lease_relay'].assert_has_mock_calls(
-                    [mock.call.start()])
 
     def test_ns_name(self):
         with mock.patch('neutron.agent.dhcp_agent.DeviceManager'):
@@ -255,28 +244,6 @@ class TestDhcpAgent(base.BaseTestCase):
                 self.assertEqual(log.call_count, 1)
                 self.assertTrue(dhcp.needs_resync)
 
-    def test_update_lease(self):
-        with mock.patch('neutron.agent.dhcp_agent.DhcpPluginApi') as plug:
-            dhcp = dhcp_agent.DhcpAgent(HOSTNAME)
-            dhcp.update_lease('net_id', '192.168.1.1', 120)
-            plug.assert_has_calls(
-                [mock.call().update_lease_expiration(
-                    'net_id', '192.168.1.1', 120)])
-
-    def test_update_lease_failure(self):
-        with mock.patch('neutron.agent.dhcp_agent.DhcpPluginApi') as plug:
-            plug.return_value.update_lease_expiration.side_effect = Exception
-
-            with mock.patch.object(dhcp_agent.LOG, 'exception') as log:
-                dhcp = dhcp_agent.DhcpAgent(HOSTNAME)
-                dhcp.update_lease('net_id', '192.168.1.1', 120)
-                plug.assert_has_calls(
-                    [mock.call().update_lease_expiration(
-                        'net_id', '192.168.1.1', 120)])
-
-                self.assertTrue(log.called)
-                self.assertTrue(dhcp.needs_resync)
-
     def _test_sync_state_helper(self, known_networks, active_networks):
         with mock.patch('neutron.agent.dhcp_agent.DhcpPluginApi') as plug:
             mock_plugin = mock.Mock()
@@ -425,7 +392,6 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
     def setUp(self):
         super(TestDhcpAgentEventHandler, self).setUp()
         cfg.CONF.register_opts(dhcp_agent.DeviceManager.OPTS)
-        cfg.CONF.register_opts(dhcp_agent.DhcpLeaseRelay.OPTS)
         cfg.CONF.register_opts(dhcp.OPTS)
         cfg.CONF.set_override('interface_driver',
                               'neutron.agent.linux.interface.NullDriver')
@@ -754,26 +720,52 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
     def test_port_update_end(self):
         payload = dict(port=vars(fake_port2))
         self.cache.get_network_by_id.return_value = fake_network
+        self.cache.get_port_by_id.return_value = fake_port2
         self.dhcp.port_update_end(None, payload)
         self.cache.assert_has_calls(
             [mock.call.get_network_by_id(fake_port2.network_id),
+             mock.call.get_port_by_id(fake_port2.id),
              mock.call.put_port(mock.ANY)])
         self.call_driver.assert_called_once_with('reload_allocations',
                                                  fake_network)
 
+    def test_port_update_change_ip_on_port(self):
+        payload = dict(port=vars(fake_port1))
+        self.cache.get_network_by_id.return_value = fake_network
+        updated_fake_port1 = copy.deepcopy(fake_port1)
+        updated_fake_port1.fixed_ips[0].ip_address = '172.9.9.99'
+        self.cache.get_port_by_id.return_value = updated_fake_port1
+        self.dhcp.port_update_end(None, payload)
+        self.cache.assert_has_calls(
+            [mock.call.get_network_by_id(fake_port1.network_id),
+             mock.call.get_port_by_id(fake_port1.id),
+             mock.call.put_port(mock.ANY)])
+        self.call_driver.assert_has_calls(
+            [mock.call.call_driver(
+                'release_lease',
+                fake_network,
+                mac_address=fake_port1.mac_address,
+                removed_ips=set([updated_fake_port1.fixed_ips[0].ip_address])),
+             mock.call.call_driver('reload_allocations', fake_network)])
+
     def test_port_delete_end(self):
         payload = dict(port_id=fake_port2.id)
         self.cache.get_network_by_id.return_value = fake_network
         self.cache.get_port_by_id.return_value = fake_port2
 
         self.dhcp.port_delete_end(None, payload)
-
+        removed_ips = [fixed_ip.ip_address
+                       for fixed_ip in fake_port2.fixed_ips]
         self.cache.assert_has_calls(
             [mock.call.get_port_by_id(fake_port2.id),
              mock.call.get_network_by_id(fake_network.id),
              mock.call.remove_port(fake_port2)])
-        self.call_driver.assert_called_once_with('reload_allocations',
-                                                 fake_network)
+        self.call_driver.assert_has_calls(
+            [mock.call.call_driver('release_lease',
+                                   fake_network,
+                                   mac_address=fake_port2.mac_address,
+                                   removed_ips=removed_ips),
+             mock.call.call_driver('reload_allocations', fake_network)])
 
     def test_port_delete_end_unknown_port(self):
         payload = dict(port_id='unknown')
@@ -865,16 +857,6 @@ class TestDhcpPluginApiProxy(base.BaseTestCase):
                                               device_id='devid',
                                               host='foo')
 
-    def test_update_lease_expiration(self):
-        with mock.patch.object(self.proxy, 'cast') as mock_cast:
-            self.proxy.update_lease_expiration('netid', 'ipaddr', 1)
-            self.assertTrue(mock_cast.called)
-        self.make_msg.assert_called_once_with('update_lease_expiration',
-                                              network_id='netid',
-                                              ip_address='ipaddr',
-                                              lease_remaining=1,
-                                              host='foo')
-
 
 class TestNetworkCache(base.BaseTestCase):
     def test_put_network(self):
@@ -1363,123 +1345,6 @@ class TestDeviceManager(base.BaseTestCase):
         device.route.add_gateway.assert_called_once_with('192.168.1.1')
 
 
-class TestDhcpLeaseRelay(base.BaseTestCase):
-    def setUp(self):
-        super(TestDhcpLeaseRelay, self).setUp()
-        cfg.CONF.register_opts(dhcp_agent.DhcpLeaseRelay.OPTS)
-        self.unlink_p = mock.patch('os.unlink')
-        self.unlink = self.unlink_p.start()
-
-    def tearDown(self):
-        self.unlink_p.stop()
-        super(TestDhcpLeaseRelay, self).tearDown()
-
-    def test_init_relay_socket_path_no_prev_socket(self):
-        with mock.patch('os.path.exists') as exists:
-            exists.return_value = False
-            self.unlink.side_effect = OSError
-
-            dhcp_agent.DhcpLeaseRelay(None)
-
-            self.unlink.assert_called_once_with(
-                cfg.CONF.dhcp_lease_relay_socket)
-            exists.assert_called_once_with(cfg.CONF.dhcp_lease_relay_socket)
-
-    def test_init_relay_socket_path_prev_socket_exists(self):
-        with mock.patch('os.path.exists') as exists:
-            exists.return_value = False
-
-            dhcp_agent.DhcpLeaseRelay(None)
-
-            self.unlink.assert_called_once_with(
-                cfg.CONF.dhcp_lease_relay_socket)
-            self.assertFalse(exists.called)
-
-    def test_init_relay_socket_path_prev_socket_unlink_failure(self):
-        self.unlink.side_effect = OSError
-        with mock.patch('os.path.exists') as exists:
-            exists.return_value = True
-            with testtools.ExpectedException(OSError):
-                dhcp_agent.DhcpLeaseRelay(None)
-
-                self.unlink.assert_called_once_with(
-                    cfg.CONF.dhcp_lease_relay_socket)
-                exists.assert_called_once_with(
-                    cfg.CONF.dhcp_lease_relay_socket)
-
-    def test_handler_valid_data(self):
-        network_id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
-        ip_address = '192.168.1.9'
-        lease_remaining = 120
-
-        json_rep = jsonutils.dumps(dict(network_id=network_id,
-                                        lease_remaining=lease_remaining,
-                                        ip_address=ip_address))
-        handler = mock.Mock()
-        mock_sock = mock.Mock()
-        mock_sock.recv.return_value = json_rep
-
-        relay = dhcp_agent.DhcpLeaseRelay(handler)
-
-        relay._handler(mock_sock, mock.Mock())
-        mock_sock.assert_has_calls([mock.call.recv(1024), mock.call.close()])
-        handler.called_once_with(network_id, ip_address, lease_remaining)
-
-    def test_handler_invalid_data(self):
-        network_id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
-        ip_address = '192.168.x.x'
-        lease_remaining = 120
-
-        json_rep = jsonutils.dumps(
-            dict(network_id=network_id,
-                 lease_remaining=lease_remaining,
-                 ip_address=ip_address))
-
-        handler = mock.Mock()
-        mock_sock = mock.Mock()
-        mock_sock.recv.return_value = json_rep
-
-        relay = dhcp_agent.DhcpLeaseRelay(handler)
-
-        with mock.patch('neutron.openstack.common.'
-                        'uuidutils.is_uuid_like') as validate:
-            validate.return_value = False
-
-            with mock.patch.object(dhcp_agent.LOG, 'warn') as log:
-
-                relay._handler(mock_sock, mock.Mock())
-                mock_sock.assert_has_calls(
-                    [mock.call.recv(1024), mock.call.close()])
-                self.assertFalse(handler.called)
-                self.assertTrue(log.called)
-
-    def test_handler_other_exception(self):
-        handler = mock.Mock()
-        mock_sock = mock.Mock()
-        mock_sock.recv.side_effect = Exception
-
-        relay = dhcp_agent.DhcpLeaseRelay(handler)
-
-        with mock.patch.object(dhcp_agent.LOG, 'exception') as log:
-            relay._handler(mock_sock, mock.Mock())
-            mock_sock.assert_has_calls([mock.call.recv(1024)])
-            self.assertFalse(handler.called)
-            self.assertTrue(log.called)
-
-    def test_start(self):
-        with mock.patch.object(dhcp_agent, 'eventlet') as mock_eventlet:
-            handler = mock.Mock()
-            relay = dhcp_agent.DhcpLeaseRelay(handler)
-            relay.start()
-
-            mock_eventlet.assert_has_calls(
-                [mock.call.listen(cfg.CONF.dhcp_lease_relay_socket,
-                                  family=socket.AF_UNIX),
-                 mock.call.spawn(mock_eventlet.serve,
-                                 mock.call.listen.return_value,
-                                 relay._handler)])
-
-
 class TestDictModel(base.BaseTestCase):
     def test_basic_dict(self):
         d = dict(a=1, b=2)
index 22850b42e0ed106c4e7a5a9efe62835f443b8516..a4471b4098d5d71ec8689799335ea2f82dacaa23 100644 (file)
@@ -16,7 +16,6 @@
 #    under the License.
 
 import os
-import socket
 
 import mock
 from oslo.config import cfg
@@ -24,7 +23,6 @@ from oslo.config import cfg
 from neutron.agent.common import config
 from neutron.agent.linux import dhcp
 from neutron.common import config as base_config
-from neutron.openstack.common import jsonutils
 from neutron.tests import base
 
 
@@ -184,6 +182,9 @@ class TestDhcpBase(base.BaseTestCase):
             def reload_allocations(self):
                 pass
 
+            def release_lease(self):
+                pass
+
             @property
             def active(self):
                 return True
@@ -209,6 +210,9 @@ class LocalChild(dhcp.DhcpLocalProcess):
     def spawn_process(self):
         self.called.append('spawn')
 
+    def release_lease(self):
+        self.called.append('release_lease')
+
 
 class TestBase(base.BaseTestCase):
     def setUp(self):
@@ -219,9 +223,6 @@ class TestBase(base.BaseTestCase):
         self.conf = config.setup_conf()
         self.conf.register_opts(base_config.core_opts)
         self.conf.register_opts(dhcp.OPTS)
-        self.conf.register_opt(
-            cfg.StrOpt('dhcp_lease_relay_socket',
-                       default='$state_path/dhcp/lease_relay'))
         self.conf.register_opt(cfg.BoolOpt('enable_isolated_metadata',
                                            default=True))
         self.conf(args=args)
@@ -230,9 +231,9 @@ class TestBase(base.BaseTestCase):
 
         self.replace_p = mock.patch('neutron.agent.linux.utils.replace_file')
         self.execute_p = mock.patch('neutron.agent.linux.utils.execute')
+        self.addCleanup(self.replace_p.stop)
         self.addCleanup(self.execute_p.stop)
         self.safe = self.replace_p.start()
-        self.addCleanup(self.replace_p.stop)
         self.execute = self.execute_p.start()
 
 
@@ -433,7 +434,6 @@ class TestDnsmasq(TestBase):
             'exec',
             'qdhcp-ns',
             'env',
-            'NEUTRON_RELAY_SOCKET_PATH=/dhcp/lease_relay',
             'NEUTRON_NETWORK_ID=cccccccc-cccc-cccc-cccc-cccccccccccc',
             'dnsmasq',
             '--no-hosts',
@@ -445,11 +445,9 @@ class TestDnsmasq(TestBase):
             '--pid-file=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/pid',
             '--dhcp-hostsfile=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host',
             '--dhcp-optsfile=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/opts',
-            ('--dhcp-script=/usr/local/bin/neutron-dhcp-agent-'
-             'dnsmasq-lease-update'),
             '--leasefile-ro',
-            '--dhcp-range=set:tag0,192.168.0.0,static,120s',
-            '--dhcp-range=set:tag1,fdca:3ba5:a17a:4ba3::,static,120s']
+            '--dhcp-range=set:tag0,192.168.0.0,static,86400s',
+            '--dhcp-range=set:tag1,fdca:3ba5:a17a:4ba3::,static,86400s']
         expected.extend(extra_options)
 
         self.execute.return_value = ('', '')
@@ -585,6 +583,17 @@ tag:tag0,option:router""".lstrip()
 
         self.safe.assert_called_once_with('/foo/opts', expected)
 
+    def test_release_lease(self):
+        dm = dhcp.Dnsmasq(self.conf, FakeDualNetwork(), namespace='qdhcp-ns',
+                          version=float(2.59))
+        dm.release_lease(mac_address=FakePort2.mac_address,
+                         removed_ips=[FakePort2.fixed_ips[0].ip_address])
+        exp_args = ['ip', 'netns', 'exec', 'qdhcp-ns', 'dhcp_release',
+                    dm.interface_name, FakePort2.fixed_ips[0].ip_address,
+                    FakePort2.mac_address]
+        self.execute.assert_called_once_with(exp_args, root_helper='sudo',
+                                             check_exit_code=True)
+
     def test_reload_allocations(self):
         exp_host_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host'
         exp_host_data = ('00:00:80:aa:bb:cc,host-192-168-0-2.openstacklocal,'
@@ -693,69 +702,6 @@ tag:tag1,249,%s,%s""".lstrip() % (fake_v6,
                 {FakeV4Subnet.id: '192.168.0.1'}
             )
 
-    def _test_lease_relay_script_helper(self, action, lease_remaining,
-                                        path_exists=True):
-        relay_path = '/dhcp/relay_socket'
-        network_id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
-        mac_address = 'aa:bb:cc:dd:ee:ff'
-        ip_address = '192.168.1.9'
-
-        json_rep = jsonutils.dumps(dict(network_id=network_id,
-                                        lease_remaining=lease_remaining,
-                                        mac_address=mac_address,
-                                        ip_address=ip_address))
-
-        environ = {
-            'NEUTRON_NETWORK_ID': network_id,
-            'NEUTRON_RELAY_SOCKET_PATH': relay_path,
-            'DNSMASQ_TIME_REMAINING': '120',
-        }
-
-        def fake_environ(name, default=None):
-            return environ.get(name, default)
-
-        with mock.patch('os.environ') as mock_environ:
-            mock_environ.get.side_effect = fake_environ
-
-            with mock.patch.object(dhcp, 'sys') as mock_sys:
-                mock_sys.argv = [
-                    'lease-update',
-                    action,
-                    mac_address,
-                    ip_address,
-                ]
-
-                with mock.patch('socket.socket') as mock_socket:
-                    mock_conn = mock.Mock()
-                    mock_socket.return_value = mock_conn
-
-                    with mock.patch('os.path.exists') as mock_exists:
-                        mock_exists.return_value = path_exists
-
-                        dhcp.Dnsmasq.lease_update()
-
-                        mock_exists.assert_called_once_with(relay_path)
-                        if path_exists:
-                            mock_socket.assert_called_once_with(
-                                socket.AF_UNIX, socket.SOCK_STREAM)
-
-                            mock_conn.assert_has_calls(
-                                [mock.call.connect(relay_path),
-                                 mock.call.send(json_rep),
-                                 mock.call.close()])
-
-    def test_lease_relay_script_add(self):
-        self._test_lease_relay_script_helper('add', 120)
-
-    def test_lease_relay_script_old(self):
-        self._test_lease_relay_script_helper('old', 120)
-
-    def test_lease_relay_script_del(self):
-        self._test_lease_relay_script_helper('del', 0)
-
-    def test_lease_relay_script_add_socket_missing(self):
-        self._test_lease_relay_script_helper('add', 120, False)
-
     def test_remove_config_files(self):
         net = FakeV4Network()
         path = '/opt/data/neutron/dhcp'
index d30b962c5e0068e02653f7ff7c8698a7ba43606b..4e31862865dc467968ac3dc7319af304a922add9 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -73,7 +73,6 @@ console_scripts =
     neutron-db-manage = neutron.db.migration.cli:main
     neutron-debug = neutron.debug.shell:main
     neutron-dhcp-agent = neutron.agent.dhcp_agent:main
-    neutron-dhcp-agent-dnsmasq-lease-update = neutron.agent.linux.dhcp:Dnsmasq.lease_update
     neutron-hyperv-agent = neutron.plugins.hyperv.agent.hyperv_neutron_agent:main
     neutron-l3-agent = neutron.agent.l3_agent:main
     neutron-lbaas-agent = neutron.services.loadbalancer.drivers.haproxy.agent:main
@@ -91,7 +90,6 @@ console_scripts =
     quantum-db-manage = neutron.db.migration.cli:main
     quantum-debug = neutron.debug.shell:main
     quantum-dhcp-agent = neutron.agent.dhcp_agent:main
-    quantum-dhcp-agent-dnsmasq-lease-update = neutron.agent.linux.dhcp:Dnsmasq.lease_update
     quantum-hyperv-agent = neutron.plugins.hyperv.agent.hyperv_neutron_agent:main
     quantum-l3-agent = neutron.agent.l3_agent:main
     quantum-lbaas-agent = neutron.services.loadbalancer.drivers.haproxy.agent:main