Remove DHCP lease logic

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
This commit is contained in:
Aaron Rosen 2013-07-16 16:06:32 -07:00
parent 2e7f35c88b
commit d9832282cf
15 changed files with 190 additions and 585 deletions

View File

@ -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()

View 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

View 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

View 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)

View 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:

View 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,

View 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

View 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."""

View File

@ -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))

View 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):

View 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)

View 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:

View 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)

View 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'

View File

@ -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