]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Improve DHCP agent performance
authorAaron Rosen <arosen@nicira.com>
Sun, 12 May 2013 21:53:18 +0000 (14:53 -0700)
committerAaron Rosen <arosen@nicira.com>
Wed, 10 Jul 2013 04:46:43 +0000 (21:46 -0700)
Previously when starting the dhcp agent the sync_state() process would
be extremely expensive as it would query quantum server for each network.
In order to improve performance a get_active_networks_info() was added
so this information could be retrieved in one query rather than doing a
query for each active network.

The second part of this patch optimizes the logic to avoid calling
get_dhcp_port(). Previously, this method was called once for each network
which makes a call to get_subnets() and get_ports() unnecessarily as
the dhcp agent can determine itself if it needs to update a port or create a
port for dhcp.

This patch also threads the inital sync process and maintains backwards
compatibility with the previous rpc api.

There was also a trivial change to the nvp_plugin where filters are assumed to
be a dict.

implements blueprint improve-dhcp-agent-performance

Change-Id: I3b631057f595250dad76516faa9b421789f60953

12 files changed:
etc/dhcp_agent.ini
neutron/agent/dhcp_agent.py
neutron/agent/rpc.py
neutron/db/dhcp_rpc_base.py
neutron/plugins/bigswitch/plugin.py
neutron/plugins/brocade/NeutronPlugin.py
neutron/plugins/hyperv/rpc_callbacks.py
neutron/plugins/linuxbridge/lb_neutron_plugin.py
neutron/plugins/ml2/rpc.py
neutron/plugins/nec/nec_plugin.py
neutron/plugins/nicira/NeutronPlugin.py
neutron/tests/unit/test_dhcp_agent.py

index bab650b799d854bebf45e57aef66fd42dce5dea7..a950b6ab48d2c0531a0c692386244809b0577373 100644 (file)
@@ -40,3 +40,7 @@ dhcp_driver = neutron.agent.linux.dhcp.Dnsmasq
 # they will be able to reach 169.254.169.254 through a router.
 # This option requires enable_isolated_metadata = True
 # enable_metadata_network = False
+
+# Number of threads to use during sync process. Should not exceed connection
+# pool size configured on server.
+# num_sync_threads = 4
index 810322ff0a074fd2e24ea4c37afbb353bdd6bf96..38582e166acbc95e0de7bb50a54cd6f1e72d9564 100644 (file)
@@ -67,6 +67,8 @@ class DhcpAgent(manager.Manager):
                     help=_("Allows for serving metadata requests from a "
                            "dedicated network. Requires "
                            "enable_isolated_metadata = True")),
+        cfg.IntOpt('num_sync_threads', default=4,
+                   help=_('Number of threads to use during sync process.')),
     ]
 
     def __init__(self, host=None):
@@ -147,15 +149,18 @@ class DhcpAgent(manager.Manager):
     def sync_state(self):
         """Sync the local DHCP state with Neutron."""
         LOG.info(_('Synchronizing state'))
-        known_networks = set(self.cache.get_network_ids())
+        pool = eventlet.GreenPool(cfg.CONF.num_sync_threads)
+        known_network_ids = set(self.cache.get_network_ids())
 
         try:
-            active_networks = set(self.plugin_rpc.get_active_networks())
-            for deleted_id in known_networks - active_networks:
+            active_networks = self.plugin_rpc.get_active_networks_info()
+            active_network_ids = set(network.id for network in active_networks)
+            for deleted_id in known_network_ids - active_network_ids:
                 self.disable_dhcp_helper(deleted_id)
 
-            for network_id in active_networks:
-                self.refresh_dhcp_helper(network_id)
+            for network in active_networks:
+                pool.spawn_n(self.configure_dhcp_for_network, network)
+
         except Exception:
             self.needs_resync = True
             LOG.exception(_('Unable to sync network state.'))
@@ -180,7 +185,9 @@ class DhcpAgent(manager.Manager):
             self.needs_resync = True
             LOG.exception(_('Network %s RPC info call failed.'), network_id)
             return
+        self.configure_dhcp_for_network(network)
 
+    def configure_dhcp_for_network(self, network):
         if not network.admin_state_up:
             return
 
@@ -349,10 +356,12 @@ class DhcpPluginApi(proxy.RpcProxy):
 
     API version history:
         1.0 - Initial version.
+        1.1 - Added get_active_networks_info, create_dhcp_port,
+              and update_dhcp_port methods.
 
     """
 
-    BASE_RPC_API_VERSION = '1.0'
+    BASE_RPC_API_VERSION = '1.1'
 
     def __init__(self, topic, context):
         super(DhcpPluginApi, self).__init__(
@@ -360,11 +369,13 @@ class DhcpPluginApi(proxy.RpcProxy):
         self.context = context
         self.host = cfg.CONF.host
 
-    def get_active_networks(self):
-        """Make a remote process call to retrieve the active networks."""
-        return self.call(self.context,
-                         self.make_msg('get_active_networks', host=self.host),
-                         topic=self.topic)
+    def get_active_networks_info(self):
+        """Make a remote process call to retrieve all network info."""
+        networks = self.call(self.context,
+                             self.make_msg('get_active_networks_info',
+                                           host=self.host),
+                             topic=self.topic)
+        return [DictModel(n) for n in networks]
 
     def get_network_info(self, network_id):
         """Make a remote process call to retrieve network info."""
@@ -378,8 +389,25 @@ class DhcpPluginApi(proxy.RpcProxy):
         """Make a remote process call to create the dhcp port."""
         return DictModel(self.call(self.context,
                                    self.make_msg('get_dhcp_port',
-                                                 network_id=network_id,
-                                                 device_id=device_id,
+                                   network_id=network_id,
+                                   device_id=device_id,
+                                   host=self.host),
+                         topic=self.topic))
+
+    def create_dhcp_port(self, port):
+        """Make a remote process call to create the dhcp port."""
+        return DictModel(self.call(self.context,
+                                   self.make_msg('create_dhcp_port',
+                                                 port=port,
+                                                 host=self.host),
+                                   topic=self.topic))
+
+    def update_dhcp_port(self, port_id, port):
+        """Make a remote process call to update the dhcp port."""
+        return DictModel(self.call(self.context,
+                                   self.make_msg('update_dhcp_port',
+                                                 port_id=port_id,
+                                                 port=port,
                                                  host=self.host),
                                    topic=self.topic))
 
@@ -515,11 +543,8 @@ class DeviceManager(object):
                     "'%s'") % conf.interface_driver
             raise SystemExit(msg)
 
-    def get_interface_name(self, network, port=None):
+    def get_interface_name(self, network, port):
         """Return interface(device) name for use by the DHCP process."""
-        if not port:
-            device_id = self.get_device_id(network)
-            port = self.plugin.get_dhcp_port(network.id, device_id)
         return self.driver.get_device_name(port)
 
     def get_device_id(self, network):
@@ -577,11 +602,69 @@ class DeviceManager(object):
 
             device.route.delete_gateway(gateway)
 
-    def setup(self, network, reuse_existing=False):
-        """Create and initialize a device for network's DHCP on this host."""
+    def setup_dhcp_port(self, network):
+        """Create/update DHCP port for the host if needed and return port."""
+
         device_id = self.get_device_id(network)
-        port = self.plugin.get_dhcp_port(network.id, device_id)
+        subnets = {}
+        dhcp_enabled_subnet_ids = []
+        for subnet in network.subnets:
+            if subnet.enable_dhcp:
+                dhcp_enabled_subnet_ids.append(subnet.id)
+                subnets[subnet.id] = subnet
 
+        dhcp_port = None
+        for port in network.ports:
+            port_device_id = getattr(port, 'device_id', None)
+            if port_device_id == device_id:
+                port_fixed_ips = []
+                for fixed_ip in port.fixed_ips:
+                    port_fixed_ips.append({'subnet_id': fixed_ip.subnet_id,
+                                           'ip_address': fixed_ip.ip_address})
+                    if fixed_ip.subnet_id in dhcp_enabled_subnet_ids:
+                        dhcp_enabled_subnet_ids.remove(fixed_ip.subnet_id)
+
+                # If there are dhcp_enabled_subnet_ids here that means that
+                # we need to add those to the port and call update.
+                if dhcp_enabled_subnet_ids:
+                    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}})
+                else:
+                    dhcp_port = port
+                # break since we found port that matches device_id
+                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'
+                        ' does not yet exist.'), {'device_id': device_id,
+                                                  'network_id': network.id})
+            port_dict = dict(
+                name='',
+                admin_state_up=True,
+                device_id=device_id,
+                network_id=network.id,
+                tenant_id=network.tenant_id,
+                fixed_ips=[dict(subnet_id=s) for s in dhcp_enabled_subnet_ids])
+            dhcp_port = self.plugin.create_dhcp_port({'port': port_dict})
+
+        # Convert subnet_id to subnet dict
+        fixed_ips = [dict(subnet_id=fixed_ip.subnet_id,
+                          ip_address=fixed_ip.ip_address,
+                          subnet=subnets[fixed_ip.subnet_id])
+                     for fixed_ip in dhcp_port.fixed_ips]
+
+        ips = [DictModel(item) if isinstance(item, dict) else item
+               for item in fixed_ips]
+        dhcp_port.fixed_ips = ips
+
+        return dhcp_port
+
+    def setup(self, network, reuse_existing=False):
+        """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 self.conf.use_namespaces:
index 89e1a084dbf3649ed054b86026d11cf6294a0373..653776f30e41d23c5eade5dd7f590e7364c93ab2 100644 (file)
@@ -71,7 +71,7 @@ class PluginApi(proxy.RpcProxy):
 
     '''
 
-    BASE_RPC_API_VERSION = '1.0'
+    BASE_RPC_API_VERSION = '1.1'
 
     def __init__(self, topic):
         super(PluginApi, self).__init__(
index 60858328f561dca23af2ea43004e763b2faffcb9..fb47be301a37c0baf375ce530ef83d958e073622 100644 (file)
@@ -29,10 +29,9 @@ LOG = logging.getLogger(__name__)
 class DhcpRpcCallbackMixin(object):
     """A mix-in that enable DHCP agent support in plugin implementations."""
 
-    def get_active_networks(self, context, **kwargs):
-        """Retrieve and return a list of the active network ids."""
+    def _get_active_networks(self, context, **kwargs):
+        """Retrieve and return a list of the active networks."""
         host = kwargs.get('host')
-        LOG.debug(_('Network list requested from %s'), host)
         plugin = manager.NeutronManager.get_plugin()
         if utils.is_extension_supported(
             plugin, constants.DHCP_AGENT_SCHEDULER_EXT_ALIAS):
@@ -43,8 +42,37 @@ class DhcpRpcCallbackMixin(object):
         else:
             filters = dict(admin_state_up=[True])
             nets = plugin.get_networks(context, filters=filters)
+        return nets
+
+    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.
+        host = kwargs.get('host')
+        LOG.debug(_('get_active_networks requested from %s'), host)
+        nets = self._get_active_networks(context, **kwargs)
         return [net['id'] for net in nets]
 
+    def get_active_networks_info(self, context, **kwargs):
+        """Returns all the networks/subnets/ports in system."""
+        host = kwargs.get('host')
+        LOG.debug(_('get_active_networks_info from %s'), host)
+        networks = self._get_active_networks(context, **kwargs)
+        plugin = manager.NeutronManager.get_plugin()
+        filters = {'network_id': [network['id'] for network in networks]}
+        ports = plugin.get_ports(context, filters=filters)
+        filters['enable_dhcp'] = [True]
+        subnets = plugin.get_subnets(context, filters=filters)
+
+        for network in networks:
+            network['subnets'] = [subnet for subnet in subnets
+                                  if subnet['network_id'] == network['id']]
+            network['ports'] = [port for port in ports
+                                if port['network_id'] == network['id']]
+
+        return networks
+
     def get_network_info(self, context, **kwargs):
         """Retrieve and return a extended information about a network."""
         network_id = kwargs.get('network_id')
@@ -68,6 +96,10 @@ class DhcpRpcCallbackMixin(object):
         network state.
 
         """
+        # 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.
+
         host = kwargs.get('host')
         network_id = kwargs.get('network_id')
         device_id = kwargs.get('device_id')
@@ -191,3 +223,30 @@ class DhcpRpcCallbackMixin(object):
 
         plugin.update_fixed_ip_lease_expiration(context, network_id,
                                                 ip_address, lease_remaining)
+
+    def create_dhcp_port(self, context, **kwargs):
+        """Create the dhcp port."""
+        host = kwargs.get('host')
+        port = kwargs.get('port')
+        LOG.debug(_('Create dhcp port %(port)s '
+                    'from %(host)s.'),
+                  {'port': port,
+                   'host': host})
+
+        port['port']['device_owner'] = constants.DEVICE_OWNER_DHCP
+        if 'mac_address' not in port['port']:
+            port['port']['mac_address'] = attributes.ATTR_NOT_SPECIFIED
+        plugin = manager.NeutronManager.get_plugin()
+        return plugin.create_port(context, port)
+
+    def update_dhcp_port(self, context, **kwargs):
+        """Update the dhcp port."""
+        host = kwargs.get('host')
+        port_id = kwargs.get('port_id')
+        port = kwargs.get('port')
+        LOG.debug(_('Update dhcp port %(port)s '
+                    'from %(host)s.'),
+                  {'port': port,
+                   'host': host})
+        plugin = manager.NeutronManager.get_plugin()
+        return plugin.update_port(context, port_id, port)
index 29a5fb7f95f6ee598041529156039eb3f11b913b..b517fe6f78a799e97708bbb95806276d583b7f4c 100644 (file)
@@ -323,7 +323,7 @@ class ServerPool(object):
 
 class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):
 
-    RPC_API_VERSION = '1.0'
+    RPC_API_VERSION = '1.1'
 
     def create_rpc_dispatcher(self):
         return q_rpc.PluginRpcDispatcher([self])
index 137fb979eef714af656de1c6d088acc3af9da034..b341e12d138406348103cea0f4f597ef08e7a86b 100644 (file)
@@ -163,10 +163,12 @@ class AgentNotifierApi(proxy.RpcProxy,
 
     API version history:
         1.0 - Initial version.
+        1.1 - Added get_active_networks_info, create_dhcp_port,
+              and update_dhcp_port methods.
 
     """
 
-    BASE_RPC_API_VERSION = '1.0'
+    BASE_RPC_API_VERSION = '1.1'
 
     def __init__(self, topic):
         super(AgentNotifierApi, self).__init__(
index 7b26525de41aa541896a55134c2a622a175f69e4..9f596b2aa910e29bcdd541abb30a7afc157e3bd1 100644 (file)
@@ -32,7 +32,7 @@ class HyperVRpcCallbacks(
         l3_rpc_base.L3RpcCallbackMixin):
 
     # Set RPC API version to 1.0 by default.
-    RPC_API_VERSION = '1.0'
+    RPC_API_VERSION = '1.1'
 
     def __init__(self, notifier):
         self.notifier = notifier
index db9e8c88efcbae507b7206008b10d0112f65f5d0..1f6383fc33edf807d685d959b0b6ac7f1fc5108f 100644 (file)
@@ -149,10 +149,13 @@ class AgentNotifierApi(proxy.RpcProxy,
 
     API version history:
         1.0 - Initial version.
+        1.1 - Added get_active_networks_info, create_dhcp_port,
+              and update_dhcp_port methods.
+
 
     '''
 
-    BASE_RPC_API_VERSION = '1.0'
+    BASE_RPC_API_VERSION = '1.1'
 
     def __init__(self, topic):
         super(AgentNotifierApi, self).__init__(
index 0413da6479f5b3fe314bd0971105a055b5a7bdd2..3762a2c154bf296e716f037ddffb3dea9c3a3504 100644 (file)
@@ -166,9 +166,12 @@ class AgentNotifierApi(proxy.RpcProxy,
 
     API version history:
         1.0 - Initial version.
+        1.1 - Added get_active_networks_info, create_dhcp_port,
+              update_dhcp_port, and removed get_dhcp_port methods.
+
     """
 
-    BASE_RPC_API_VERSION = '1.0'
+    BASE_RPC_API_VERSION = '1.1'
 
     def __init__(self, topic):
         super(AgentNotifierApi, self).__init__(
index 144f6da67e4aae851e049f4482b17b0970566a04..b60b9a6ec88aed26541307494d2dbe3afd981111 100644 (file)
@@ -602,7 +602,7 @@ class NECPluginV2AgentNotifierApi(proxy.RpcProxy,
 
 class DhcpRpcCallback(dhcp_rpc_base.DhcpRpcCallbackMixin):
     # DhcpPluginApi BASE_RPC_API_VERSION
-    RPC_API_VERSION = '1.0'
+    RPC_API_VERSION = '1.1'
 
 
 class L3RpcCallback(l3_rpc_base.L3RpcCallbackMixin):
index eeebb24bc335490a5436651ccae8fec3287e011a..c9d38b129164d054ead5e5b6a824bb05c5e38363 100644 (file)
@@ -111,7 +111,7 @@ def create_nvp_cluster(cluster_opts, concurrent_connections,
 class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
 
     # Set RPC API version to 1.0 by default.
-    RPC_API_VERSION = '1.0'
+    RPC_API_VERSION = '1.1'
 
     def create_rpc_dispatcher(self):
         '''Get the rpc dispatcher for this manager.
@@ -1033,6 +1033,8 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
         return net
 
     def get_ports(self, context, filters=None, fields=None):
+        if filters is None:
+            filters = {}
         with context.session.begin(subtransactions=True):
             neutron_lports = super(NvpPluginV2, self).get_ports(
                 context, filters)
index 0bdced2c18678552f8db02e49131dee333c3a7f1..9a15bf0ea908e5c8fcf9b29ba041aa9fb8623d52 100644 (file)
@@ -15,6 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import copy
 import os
 import socket
 import sys
@@ -53,14 +54,24 @@ class FakeModel:
     def __str__(self):
         return str(self.__dict__)
 
-
+fake_tenant_id = 'aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa'
+fake_subnet1_allocation_pools = FakeModel('', start='172.9.9.2',
+                                          end='172.9.9.254')
 fake_subnet1 = FakeModel('bbbbbbbb-bbbb-bbbb-bbbbbbbbbbbb',
                          network_id='12345678-1234-5678-1234567890ab',
-                         cidr='172.9.9.0/24', enable_dhcp=True)
+                         cidr='172.9.9.0/24', enable_dhcp=True, name='',
+                         tenant_id=fake_tenant_id, gateway_ip='172.9.9.1',
+                         host_routes=[], dns_nameservers=[], ip_version=4,
+                         allocation_pools=fake_subnet1_allocation_pools)
 
+fake_subnet2_allocation_pools = FakeModel('', start='172.9.8.2',
+                                          end='172.9.8.254')
 fake_subnet2 = FakeModel('dddddddd-dddd-dddd-dddddddddddd',
                          network_id='12345678-1234-5678-1234567890ab',
-                         cidr='172.9.9.0/24', enable_dhcp=False)
+                         cidr='172.9.8.0/24', enable_dhcp=False, name='',
+                         tenant_id=fake_tenant_id, gateway_ip='172.9.8.1',
+                         host_routes=[], dns_nameservers=[], ip_version=4,
+                         allocation_pools=fake_subnet2_allocation_pools)
 
 fake_subnet3 = FakeModel('bbbbbbbb-1111-2222-bbbbbbbbbbbb',
                          network_id='12345678-1234-5678-1234567890ab',
@@ -71,14 +82,20 @@ fake_meta_subnet = FakeModel('bbbbbbbb-1111-2222-bbbbbbbbbbbb',
                              cidr='169.254.169.252/30',
                              gateway_ip='169.254.169.253', enable_dhcp=True)
 
-fake_fixed_ip = FakeModel('', subnet=fake_subnet1, ip_address='172.9.9.9')
+fake_fixed_ip1 = FakeModel('', subnet_id=fake_subnet1.id,
+                           ip_address='172.9.9.9')
 fake_meta_fixed_ip = FakeModel('', subnet=fake_meta_subnet,
                                ip_address='169.254.169.254')
+fake_allocation_pool_subnet1 = FakeModel('', start='172.9.9.2',
+                                         end='172.9.9.254')
+
 
 fake_port1 = FakeModel('12345678-1234-aaaa-1234567890ab',
+                       device_id='dhcp-12345678-1234-aaaa-1234567890ab',
+                       allocation_pools=fake_subnet1_allocation_pools,
                        mac_address='aa:bb:cc:dd:ee:ff',
                        network_id='12345678-1234-5678-1234567890ab',
-                       fixed_ips=[fake_fixed_ip])
+                       fixed_ips=[fake_fixed_ip1])
 
 fake_port2 = FakeModel('12345678-1234-aaaa-123456789000',
                        mac_address='aa:bb:cc:dd:ee:99',
@@ -263,7 +280,7 @@ class TestDhcpAgent(base.BaseTestCase):
     def _test_sync_state_helper(self, known_networks, active_networks):
         with mock.patch('neutron.agent.dhcp_agent.DhcpPluginApi') as plug:
             mock_plugin = mock.Mock()
-            mock_plugin.get_active_networks.return_value = active_networks
+            mock_plugin.get_active_networks_info.return_value = active_networks
             plug.return_value = mock_plugin
 
             dhcp = dhcp_agent.DhcpAgent(HOSTNAME)
@@ -298,7 +315,7 @@ class TestDhcpAgent(base.BaseTestCase):
     def test_sync_state_plugin_error(self):
         with mock.patch('neutron.agent.dhcp_agent.DhcpPluginApi') as plug:
             mock_plugin = mock.Mock()
-            mock_plugin.get_active_networks.side_effect = Exception
+            mock_plugin.get_active_networks_info.side_effect = Exception
             plug.return_value = mock_plugin
 
             with mock.patch.object(dhcp_agent.LOG, 'exception') as log:
@@ -781,12 +798,6 @@ class TestDhcpPluginApiProxy(base.BaseTestCase):
         self.call_p.stop()
         super(TestDhcpPluginApiProxy, self).tearDown()
 
-    def test_get_active_networks(self):
-        self.proxy.get_active_networks()
-        self.assertTrue(self.call.called)
-        self.make_msg.assert_called_once_with('get_active_networks',
-                                              host='foo')
-
     def test_get_network_info(self):
         self.call.return_value = dict(a=1)
         retval = self.proxy.get_network_info('netid')
@@ -806,6 +817,34 @@ class TestDhcpPluginApiProxy(base.BaseTestCase):
                                               device_id='devid',
                                               host='foo')
 
+    def test_get_active_networks_info(self):
+        self.proxy.get_active_networks_info()
+        self.make_msg.assert_called_once_with('get_active_networks_info',
+                                              host='foo')
+
+    def test_create_dhcp_port(self):
+        port_body = (
+            {'port':
+                {'name': '', 'admin_state_up': True,
+                 'network_id': fake_network.id,
+                 'tenant_id': fake_network.tenant_id,
+                 'fixed_ips': [{'subnet_id': fake_fixed_ip1.subnet_id}],
+                 'device_id': mock.ANY}})
+
+        self.proxy.create_dhcp_port(port_body)
+        self.make_msg.assert_called_once_with('create_dhcp_port',
+                                              port=port_body,
+                                              host='foo')
+
+    def test_update_dhcp_port(self):
+        port_body = {'port': {'fixed_ips':
+                              [{'subnet_id': fake_fixed_ip1.subnet_id}]}}
+        self.proxy.update_dhcp_port(fake_port1.id, port_body)
+        self.make_msg.assert_called_once_with('update_dhcp_port',
+                                              port_id=fake_port1.id,
+                                              port=port_body,
+                                              host='foo')
+
     def test_release_dhcp_port(self):
         self.proxy.release_dhcp_port('netid', 'devid')
         self.assertTrue(self.call.called)
@@ -1019,6 +1058,7 @@ class TestDeviceManager(base.BaseTestCase):
         net = net or fake_network
         port = port or fake_port1
         plugin = mock.Mock()
+        plugin.create_dhcp_port.return_value = port or fake_port1
         plugin.get_dhcp_port.return_value = port or fake_port1
         self.device_exists.return_value = device_exists
         self.mock_driver.get_device_name.return_value = 'tap12345678-12'
@@ -1030,7 +1070,12 @@ class TestDeviceManager(base.BaseTestCase):
         self.assertEqual(interface_name, 'tap12345678-12')
 
         plugin.assert_has_calls([
-            mock.call.get_dhcp_port(net.id, mock.ANY)])
+            mock.call.create_dhcp_port(
+                {'port': {'name': '', 'admin_state_up': True,
+                          'network_id': net.id, 'tenant_id': net.tenant_id,
+                          'fixed_ips':
+                          [{'subnet_id': fake_fixed_ip1.subnet_id}],
+                          'device_id': mock.ANY}})])
 
         namespace = dhcp_agent.NS_PREFIX + net.id
 
@@ -1060,6 +1105,46 @@ class TestDeviceManager(base.BaseTestCase):
     def test_setup_device_exists_reuse(self):
         self._test_setup_helper(True, True)
 
+    def test_create_dhcp_port_create_new(self):
+        plugin = mock.Mock()
+        dh = dhcp_agent.DeviceManager(cfg.CONF, plugin)
+        plugin.create_dhcp_port.return_value = fake_network.ports[0]
+        dh.setup_dhcp_port(fake_network)
+        plugin.assert_has_calls([
+            mock.call.create_dhcp_port(
+                {'port': {'name': '', 'admin_state_up': True,
+                          'network_id':
+                          fake_network.id, 'tenant_id': fake_network.tenant_id,
+                          'fixed_ips':
+                          [{'subnet_id': fake_fixed_ip1.subnet_id}],
+                          'device_id': mock.ANY}})])
+
+    def test_create_dhcp_port_update_add_subnet(self):
+        plugin = mock.Mock()
+        dh = dhcp_agent.DeviceManager(cfg.CONF, plugin)
+        fake_network_copy = copy.deepcopy(fake_network)
+        fake_network_copy.ports[0].device_id = dh.get_device_id(fake_network)
+        fake_network_copy.subnets[1].enable_dhcp = True
+        plugin.update_dhcp_port.return_value = fake_network.ports[0]
+        dh.setup_dhcp_port(fake_network_copy)
+        port_body = {'port': {
+                     'fixed_ips': [{'subnet_id': fake_fixed_ip1.subnet_id,
+                                    'ip_address': fake_fixed_ip1.ip_address},
+                                   {'subnet_id': fake_subnet2.id}]}}
+
+        plugin.assert_has_calls([
+            mock.call.update_dhcp_port(fake_network_copy.ports[0].id,
+                                       port_body)])
+
+    def test_create_dhcp_port_no_update_or_create(self):
+        plugin = mock.Mock()
+        dh = dhcp_agent.DeviceManager(cfg.CONF, plugin)
+        fake_network_copy = copy.deepcopy(fake_network)
+        fake_network_copy.ports[0].device_id = dh.get_device_id(fake_network)
+        dh.setup_dhcp_port(fake_network_copy)
+        self.assertFalse(plugin.setup_dhcp_port.called)
+        self.assertFalse(plugin.update_dhcp_port.called)
+
     def test_destroy(self):
         fake_network = FakeModel('12345678-1234-5678-1234567890ab',
                                  tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa')
@@ -1069,9 +1154,6 @@ class TestDeviceManager(base.BaseTestCase):
 
         with mock.patch('neutron.agent.linux.interface.NullDriver') as dvr_cls:
             mock_driver = mock.MagicMock()
-            #mock_driver.DEV_NAME_LEN = (
-            #    interface.LinuxInterfaceDriver.DEV_NAME_LEN)
-            #mock_driver.port = fake_port
             mock_driver.get_device_name.return_value = 'tap12345678-12'
             dvr_cls.return_value = mock_driver
 
@@ -1112,31 +1194,6 @@ class TestDeviceManager(base.BaseTestCase):
 
             self.assertEqual(len(plugin.mock_calls), 0)
 
-    def test_get_interface_name_no_port_provided(self):
-        fake_network = FakeModel('12345678-1234-5678-1234567890ab',
-                                 tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa')
-
-        fake_port = FakeModel('12345678-1234-aaaa-1234567890ab',
-                              mac_address='aa:bb:cc:dd:ee:ff')
-
-        with mock.patch('neutron.agent.linux.interface.NullDriver') as dvr_cls:
-            mock_driver = mock.MagicMock()
-            mock_driver.get_device_name.return_value = 'tap12345678-12'
-            dvr_cls.return_value = mock_driver
-
-            plugin = mock.Mock()
-            plugin.get_dhcp_port.return_value = fake_port
-
-            dh = dhcp_agent.DeviceManager(cfg.CONF, plugin)
-            dh.get_interface_name(fake_network)
-
-            dvr_cls.assert_called_once_with(cfg.CONF)
-            mock_driver.assert_has_calls(
-                [mock.call.get_device_name(fake_port)])
-
-            plugin.assert_has_calls(
-                [mock.call.get_dhcp_port(fake_network.id, mock.ANY)])
-
     def test_get_device_id(self):
         fake_network = FakeModel('12345678-1234-5678-1234567890ab',
                                  tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa')