]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Provide way to reserve dhcp port during failovers
authorEd Bak <ed.bak2@hp.com>
Fri, 7 Mar 2014 17:16:15 +0000 (17:16 +0000)
committerEd Bak <ed.bak2@hp.com>
Tue, 20 May 2014 17:24:22 +0000 (17:24 +0000)
This change provides a way to save the dhcp port when failing
over a network from one dhcp agent to another.  When a
dhcp-agent-network-remove is issued, the dhcp port device_id is
marked as reserved which causes it to not be deleted. When a
subsequent dhcp-agent-network-add is issued, the reserved port
is used and the device_id is corrected.  This is desirable
in order to maintain the dhcp port ip address so that dns doesn't
get impacted. Unit test added.

Change-Id: I531d7ffab074b01adfe186d2c3df43ca978359cd
Closes-Bug: #1288923

neutron/agent/linux/dhcp.py
neutron/common/constants.py
neutron/common/utils.py
neutron/db/agentschedulers_db.py
neutron/tests/unit/openvswitch/test_agent_scheduler.py
neutron/tests/unit/test_db_plugin.py
neutron/tests/unit/test_dhcp_agent.py

index 59a2d7ad185d53ebeb3033b5c1e56669392695f2..432f0f6023f70da2a3578f17bd745af30371a35f 100644 (file)
@@ -22,7 +22,6 @@ import re
 import shutil
 import socket
 import sys
-import uuid
 
 import netaddr
 from oslo.config import cfg
@@ -32,6 +31,7 @@ from neutron.agent.linux import ip_lib
 from neutron.agent.linux import utils
 from neutron.common import constants
 from neutron.common import exceptions
+from neutron.common import utils as commonutils
 from neutron.openstack.common import importutils
 from neutron.openstack.common import jsonutils
 from neutron.openstack.common import log as logging
@@ -696,9 +696,7 @@ class DeviceManager(object):
         """Return a unique DHCP device ID for this host on the network."""
         # There could be more than one dhcp server per network, so create
         # a device id that combines host and network ids
-
-        host_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname())
-        return 'dhcp%s-%s' % (host_uuid, network.id)
+        return commonutils.get_dhcp_agent_device_id(network.id, self.conf.host)
 
     def _set_default_route(self, network, device_name):
         """Sets the default gateway for this dhcp namespace.
@@ -775,6 +773,19 @@ class DeviceManager(object):
                 # 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': {'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'
index 14178970f190073c47911aafe74381a7f65f1125..cf3fb6025bce45fd1d11b206c2b8d75ef57764dd 100644 (file)
@@ -34,6 +34,8 @@ DEVICE_OWNER_ROUTER_GW = "network:router_gateway"
 DEVICE_OWNER_FLOATINGIP = "network:floatingip"
 DEVICE_OWNER_DHCP = "network:dhcp"
 
+DEVICE_ID_RESERVED_DHCP_PORT = "reserved_dhcp_port"
+
 FLOATINGIP_KEY = '_floatingips'
 INTERFACE_KEY = '_interfaces'
 METERING_LABEL_KEY = '_metering_labels'
index 5b0d38a1d24d3d9461dab8c0dc1e4baac46a34d0..317f35432d9c25da0218cb147f03b41ef4408d32 100644 (file)
@@ -25,6 +25,7 @@ import os
 import random
 import signal
 import socket
+import uuid
 
 from eventlet.green import subprocess
 from oslo.config import cfg
@@ -216,3 +217,12 @@ def get_random_string(length):
         rndstr += hashlib.sha224(str(random.random())).hexdigest()
 
     return rndstr[0:length]
+
+
+def get_dhcp_agent_device_id(network_id, host):
+    # Split host so as to always use only the hostname and
+    # not the domain name. This will guarantee consistentcy
+    # whether a local hostname or an fqdn is passed in.
+    local_hostname = host.split('.')[0]
+    host_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, str(local_hostname))
+    return 'dhcp%s-%s' % (host_uuid, network_id)
index 1e46a94faa825030adeb626a1857db4daed308d1..9ba1b2d1f2bb71baab7f46af5dd623e1ca7a7e1c 100644 (file)
@@ -20,6 +20,7 @@ from sqlalchemy.orm import exc
 from sqlalchemy.orm import joinedload
 
 from neutron.common import constants
+from neutron.common import utils
 from neutron.db import agents_db
 from neutron.db import model_base
 from neutron.extensions import dhcpagentscheduler
@@ -155,6 +156,16 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
             except exc.NoResultFound:
                 raise dhcpagentscheduler.NetworkNotHostedByDhcpAgent(
                     network_id=network_id, agent_id=id)
+
+            # reserve the port, so the ip is reused on a subsequent add
+            device_id = utils.get_dhcp_agent_device_id(network_id,
+                                                       agent['host'])
+            filters = dict(device_id=[device_id])
+            ports = self.get_ports(context, filters=filters)
+            for port in ports:
+                port['device_id'] = constants.DEVICE_ID_RESERVED_DHCP_PORT
+                self.update_port(context, port['id'], dict(port=port))
+
             context.session.delete(binding)
         dhcp_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_DHCP)
         if dhcp_notifier:
index 72d57568a29197f559c26c0cc5daf3636830c05e..ddc1cee8b3cb7fa6b266587edd848858342b43bd 100644 (file)
@@ -576,6 +576,30 @@ class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
         self.assertEqual(1, num_before_remove)
         self.assertEqual(0, num_after_remove)
 
+    def test_reserved_port_after_network_remove_from_dhcp_agent(self):
+        dhcp_hosta = {
+            'binary': 'neutron-dhcp-agent',
+            'host': DHCP_HOSTA,
+            'topic': 'DHCP_AGENT',
+            'configurations': {'dhcp_driver': 'dhcp_driver',
+                               'use_namespaces': True,
+                               },
+            'agent_type': constants.AGENT_TYPE_DHCP}
+        self._register_one_agent_state(dhcp_hosta)
+        hosta_id = self._get_agent_id(constants.AGENT_TYPE_DHCP,
+                                      DHCP_HOSTA)
+        with self.port(device_owner=constants.DEVICE_OWNER_DHCP,
+                       host=DHCP_HOSTA) as port1:
+            self._remove_network_from_dhcp_agent(hosta_id,
+                                                 port1['port']['network_id'])
+            port_res = self._list_ports(
+                'json',
+                200,
+                network_id=port1['port']['network_id'])
+            port_list = self.deserialize('json', port_res)
+            self.assertEqual(port_list['ports'][0]['device_id'],
+                             constants.DEVICE_ID_RESERVED_DHCP_PORT)
+
     def test_router_auto_schedule_with_invalid_router(self):
         with self.router() as router:
             l3_rpc = l3_rpc_base.L3RpcCallbackMixin()
index 7f764e332c62d267211ca2005f1351b6a8d69782..1b7059bc9c561a78a8ee53d281e6ae4cc4d678bd 100644 (file)
@@ -31,6 +31,7 @@ from neutron.api.v2 import router
 from neutron.common import constants
 from neutron.common import exceptions as n_exc
 from neutron.common import test_lib
+from neutron.common import utils
 from neutron import context
 from neutron.db import api as db
 from neutron.db import db_base_plugin_v2
@@ -347,6 +348,13 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
             # Arg must be present
             if arg in kwargs:
                 data['port'][arg] = kwargs[arg]
+        # create a dhcp port device id if one hasn't been supplied
+        if ('device_owner' in kwargs and
+            kwargs['device_owner'] == constants.DEVICE_OWNER_DHCP and
+            'host' in kwargs and
+            not 'device_id' in kwargs):
+            device_id = utils.get_dhcp_agent_device_id(net_id, kwargs['host'])
+            data['port']['device_id'] = device_id
         port_req = self.new_create_request('ports', data, fmt)
         if (kwargs.get('set_context') and 'tenant_id' in kwargs):
             # create a specific auth context for this request
index 5978874cf32c153ad26961808c4aba3d99e89386..cd7c4198563714124d850f9a526ed91979b01a3d 100644 (file)
@@ -1268,14 +1268,12 @@ class TestDeviceManager(base.BaseTestCase):
         expected = ('dhcp1ae5f96c-c527-5079-82ea-371a01645457-12345678-1234-'
                     '5678-1234567890ab')
 
-        with mock.patch('socket.gethostname') as get_host:
-            with mock.patch('uuid.uuid5') as uuid5:
-                uuid5.return_value = '1ae5f96c-c527-5079-82ea-371a01645457'
-                get_host.return_value = 'localhost'
-
-                dh = dhcp.DeviceManager(cfg.CONF, cfg.CONF.root_helper, None)
-                self.assertEqual(dh.get_device_id(fake_net), expected)
-                uuid5.assert_called_once_with(uuid.NAMESPACE_DNS, 'localhost')
+        with mock.patch('uuid.uuid5') as uuid5:
+            uuid5.return_value = '1ae5f96c-c527-5079-82ea-371a01645457'
+
+            dh = dhcp.DeviceManager(cfg.CONF, cfg.CONF.root_helper, None)
+            uuid5.called_once_with(uuid.NAMESPACE_DNS, cfg.CONF.host)
+            self.assertEqual(dh.get_device_id(fake_net), expected)
 
     def test_update(self):
         # Try with namespaces and no metadata network