From 9d5c540b47b5c5611914f1765a196ad44f868751 Mon Sep 17 00:00:00 2001 From: Aaron Rosen Date: Sun, 12 May 2013 14:53:18 -0700 Subject: [PATCH] Improve DHCP agent performance 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 --- etc/dhcp_agent.ini | 4 + neutron/agent/dhcp_agent.py | 123 ++++++++++++--- neutron/agent/rpc.py | 2 +- neutron/db/dhcp_rpc_base.py | 65 +++++++- neutron/plugins/bigswitch/plugin.py | 2 +- neutron/plugins/brocade/NeutronPlugin.py | 4 +- neutron/plugins/hyperv/rpc_callbacks.py | 2 +- .../plugins/linuxbridge/lb_neutron_plugin.py | 5 +- neutron/plugins/ml2/rpc.py | 5 +- neutron/plugins/nec/nec_plugin.py | 2 +- neutron/plugins/nicira/NeutronPlugin.py | 4 +- neutron/tests/unit/test_dhcp_agent.py | 141 ++++++++++++------ 12 files changed, 286 insertions(+), 73 deletions(-) diff --git a/etc/dhcp_agent.ini b/etc/dhcp_agent.ini index bab650b79..a950b6ab4 100644 --- a/etc/dhcp_agent.ini +++ b/etc/dhcp_agent.ini @@ -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 diff --git a/neutron/agent/dhcp_agent.py b/neutron/agent/dhcp_agent.py index 810322ff0..38582e166 100644 --- a/neutron/agent/dhcp_agent.py +++ b/neutron/agent/dhcp_agent.py @@ -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_dhcp_port(self, network): + """Create/update DHCP port for the host if needed and return port.""" + + device_id = self.get_device_id(network) + 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.""" - device_id = self.get_device_id(network) - port = self.plugin.get_dhcp_port(network.id, device_id) - + port = self.setup_dhcp_port(network) interface_name = self.get_interface_name(network, port) if self.conf.use_namespaces: diff --git a/neutron/agent/rpc.py b/neutron/agent/rpc.py index 89e1a084d..653776f30 100644 --- a/neutron/agent/rpc.py +++ b/neutron/agent/rpc.py @@ -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__( diff --git a/neutron/db/dhcp_rpc_base.py b/neutron/db/dhcp_rpc_base.py index 60858328f..fb47be301 100644 --- a/neutron/db/dhcp_rpc_base.py +++ b/neutron/db/dhcp_rpc_base.py @@ -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) diff --git a/neutron/plugins/bigswitch/plugin.py b/neutron/plugins/bigswitch/plugin.py index 29a5fb7f9..b517fe6f7 100644 --- a/neutron/plugins/bigswitch/plugin.py +++ b/neutron/plugins/bigswitch/plugin.py @@ -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]) diff --git a/neutron/plugins/brocade/NeutronPlugin.py b/neutron/plugins/brocade/NeutronPlugin.py index 137fb979e..b341e12d1 100644 --- a/neutron/plugins/brocade/NeutronPlugin.py +++ b/neutron/plugins/brocade/NeutronPlugin.py @@ -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__( diff --git a/neutron/plugins/hyperv/rpc_callbacks.py b/neutron/plugins/hyperv/rpc_callbacks.py index 7b26525de..9f596b2aa 100644 --- a/neutron/plugins/hyperv/rpc_callbacks.py +++ b/neutron/plugins/hyperv/rpc_callbacks.py @@ -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 diff --git a/neutron/plugins/linuxbridge/lb_neutron_plugin.py b/neutron/plugins/linuxbridge/lb_neutron_plugin.py index db9e8c88e..1f6383fc3 100644 --- a/neutron/plugins/linuxbridge/lb_neutron_plugin.py +++ b/neutron/plugins/linuxbridge/lb_neutron_plugin.py @@ -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__( diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index 0413da647..3762a2c15 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -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__( diff --git a/neutron/plugins/nec/nec_plugin.py b/neutron/plugins/nec/nec_plugin.py index 144f6da67..b60b9a6ec 100644 --- a/neutron/plugins/nec/nec_plugin.py +++ b/neutron/plugins/nec/nec_plugin.py @@ -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): diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index eeebb24bc..c9d38b129 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -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) diff --git a/neutron/tests/unit/test_dhcp_agent.py b/neutron/tests/unit/test_dhcp_agent.py index 0bdced2c1..9a15bf0ea 100644 --- a/neutron/tests/unit/test_dhcp_agent.py +++ b/neutron/tests/unit/test_dhcp_agent.py @@ -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')