# 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
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):
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.'))
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
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__(
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."""
"""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))
"'%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):
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:
'''
- BASE_RPC_API_VERSION = '1.0'
+ BASE_RPC_API_VERSION = '1.1'
def __init__(self, topic):
super(PluginApi, self).__init__(
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):
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')
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')
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)
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])
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__(
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
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__(
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__(
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):
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.
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)
# License for the specific language governing permissions and limitations
# under the License.
+import copy
import os
import socket
import sys
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',
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',
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)
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:
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')
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)
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'
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
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')
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
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')