Merge "Remove DHCP lease logic"

This commit is contained in:
Jenkins 2013-08-13 15:45:59 +00:00 committed by Gerrit Code Review
commit c7bd098271
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 # mac_generation_retries = 16
# DHCP Lease duration (in seconds) # DHCP Lease duration (in seconds)
# dhcp_lease_duration = 120 # dhcp_lease_duration = 86400
# Allow sending resource operation notification to DHCP agent # Allow sending resource operation notification to DHCP agent
# dhcp_agent_notification = True # dhcp_agent_notification = True

View File

@ -9,7 +9,7 @@
[Filters] [Filters]
# dhcp-agent # 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 # dhcp-agent uses kill as well, that's handled by the generic KillFilter
# it looks like these are the only signals needed, per # it looks like these are the only signals needed, per
# neutron/agent/linux/dhcp.py # 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 cat: RegExpFilter, cat, root, cat, /proc/\d+/cmdline
ovs-vsctl: CommandFilter, ovs-vsctl, root ovs-vsctl: CommandFilter, ovs-vsctl, root
ivs-ctl: CommandFilter, ivs-ctl, root ivs-ctl: CommandFilter, ivs-ctl, root
dhcp_release: CommandFilter, dhcp_release, root
# metadata proxy # metadata proxy
metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root 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 context
from neutron import manager from neutron import manager
from neutron.openstack.common import importutils 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 log as logging
from neutron.openstack.common import loopingcall from neutron.openstack.common import loopingcall
from neutron.openstack.common.rpc import proxy from neutron.openstack.common.rpc import proxy
from neutron.openstack.common import service from neutron.openstack.common import service
from neutron.openstack.common import uuidutils
from neutron import service as neutron_service from neutron import service as neutron_service
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -81,8 +79,10 @@ class DhcpAgent(manager.Manager):
ctx = context.get_admin_context_without_session() ctx = context.get_admin_context_without_session()
self.plugin_rpc = DhcpPluginApi(topics.PLUGIN, ctx) self.plugin_rpc = DhcpPluginApi(topics.PLUGIN, ctx)
self.device_manager = DeviceManager(self.conf, self.plugin_rpc) 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.dhcp_version = self.dhcp_driver_cls.check_version()
self._populate_networks_cache() self._populate_networks_cache()
@ -114,13 +114,12 @@ class DhcpAgent(manager.Manager):
"""Activate the DHCP agent.""" """Activate the DHCP agent."""
self.sync_state() self.sync_state()
self.periodic_resync() self.periodic_resync()
self.lease_relay.start()
def _ns_name(self, network): def _ns_name(self, network):
if self.conf.use_namespaces: if self.conf.use_namespaces:
return NS_PREFIX + network.id 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.""" """Invoke an action on a DHCP driver instance."""
try: try:
# the Driver expects something that is duck typed similar to # the Driver expects something that is duck typed similar to
@ -131,21 +130,13 @@ class DhcpAgent(manager.Manager):
self.device_manager, self.device_manager,
self._ns_name(network), self._ns_name(network),
self.dhcp_version) self.dhcp_version)
getattr(driver, action)() getattr(driver, action)(**action_kwargs)
return True return True
except Exception: except Exception:
self.needs_resync = True self.needs_resync = True
LOG.exception(_('Unable to %s dhcp.'), action) 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): def sync_state(self):
"""Sync the local DHCP state with Neutron.""" """Sync the local DHCP state with Neutron."""
LOG.info(_('Synchronizing state')) LOG.info(_('Synchronizing state'))
@ -246,6 +237,22 @@ class DhcpAgent(manager.Manager):
if new_cidrs: if new_cidrs:
self.device_manager.update(network) 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') @utils.synchronized('dhcp-agent')
def network_create_end(self, context, payload): def network_create_end(self, context, payload):
"""Handle the network.create.end notification event.""" """Handle the network.create.end notification event."""
@ -289,6 +296,7 @@ class DhcpAgent(manager.Manager):
port = DictModel(payload['port']) port = DictModel(payload['port'])
network = self.cache.get_network_by_id(port.network_id) network = self.cache.get_network_by_id(port.network_id)
if network: if network:
self.release_lease_for_removed_ips(port, network)
self.cache.put_port(port) self.cache.put_port(port)
self.call_driver('reload_allocations', network) self.call_driver('reload_allocations', network)
@ -302,6 +310,12 @@ class DhcpAgent(manager.Manager):
if port: if port:
network = self.cache.get_network_by_id(port.network_id) network = self.cache.get_network_by_id(port.network_id)
self.cache.remove_port(port) 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) self.call_driver('reload_allocations', network)
def enable_isolated_metadata_proxy(self, network): def enable_isolated_metadata_proxy(self, network):
@ -435,16 +449,6 @@ class DhcpPluginApi(proxy.RpcProxy):
host=self.host), host=self.host),
topic=self.topic) 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): class NetworkCache(object):
"""Agent cache of the current network state.""" """Agent cache of the current network state."""
@ -747,67 +751,6 @@ class DictModel(object):
setattr(self, key, value) 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): class DhcpAgentWithStateReport(DhcpAgent):
def __init__(self, host=None): def __init__(self, host=None):
super(DhcpAgentWithStateReport, self).__init__(host=host) super(DhcpAgentWithStateReport, self).__init__(host=host)
@ -863,7 +806,6 @@ def register_options():
config.register_agent_state_opts_helper(cfg.CONF) config.register_agent_state_opts_helper(cfg.CONF)
config.register_root_helper(cfg.CONF) config.register_root_helper(cfg.CONF)
cfg.CONF.register_opts(DeviceManager.OPTS) cfg.CONF.register_opts(DeviceManager.OPTS)
cfg.CONF.register_opts(DhcpLeaseRelay.OPTS)
cfg.CONF.register_opts(dhcp.OPTS) cfg.CONF.register_opts(dhcp.OPTS)
cfg.CONF.register_opts(interface.OPTS) cfg.CONF.register_opts(interface.OPTS)

View File

@ -89,6 +89,10 @@ class DhcpBase(object):
def active(self): def active(self):
"""Boolean representing the running state of the DHCP server.""" """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 @abc.abstractmethod
def reload_allocations(self): def reload_allocations(self):
"""Force the DHCP server to reload the assignment database.""" """Force the DHCP server to reload the assignment database."""
@ -261,8 +265,6 @@ class Dnsmasq(DhcpLocalProcess):
"""Spawns a Dnsmasq process for the network.""" """Spawns a Dnsmasq process for the network."""
env = { env = {
self.NEUTRON_NETWORK_ID_KEY: self.network.id, self.NEUTRON_NETWORK_ID_KEY: self.network.id,
self.NEUTRON_RELAY_SOCKET_PATH_KEY:
self.conf.dhcp_lease_relay_socket
} }
cmd = [ cmd = [
@ -279,7 +281,6 @@ class Dnsmasq(DhcpLocalProcess):
#'--dhcp-lease-max=%s' % ?, #'--dhcp-lease-max=%s' % ?,
'--dhcp-hostsfile=%s' % self._output_hosts_file(), '--dhcp-hostsfile=%s' % self._output_hosts_file(),
'--dhcp-optsfile=%s' % self._output_opts_file(), '--dhcp-optsfile=%s' % self._output_opts_file(),
'--dhcp-script=%s' % self._lease_relay_script_path(),
'--leasefile-ro', '--leasefile-ro',
] ]
@ -318,6 +319,16 @@ class Dnsmasq(DhcpLocalProcess):
cmd = ['%s=%s' % pair for pair in env.items()] + cmd cmd = ['%s=%s' % pair for pair in env.items()] + cmd
utils.execute(cmd, self.root_helper) 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): def reload_allocations(self):
"""Rebuild the dnsmasq config and signal the dnsmasq to reload.""" """Rebuild the dnsmasq config and signal the dnsmasq to reload."""
@ -428,10 +439,6 @@ class Dnsmasq(DhcpLocalProcess):
return retval 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): def _format_option(self, index, option, *args):
"""Format DHCP option by option name or code.""" """Format DHCP option by option name or code."""
if self.version >= self.MINIMUM_VERSION: if self.version >= self.MINIMUM_VERSION:

View File

@ -71,7 +71,7 @@ core_opts = [
help=_("Maximum number of host routes per subnet")), help=_("Maximum number of host routes per subnet")),
cfg.IntOpt('max_fixed_ips_per_port', default=5, cfg.IntOpt('max_fixed_ips_per_port', default=5,
help=_("Maximum number of fixed ips per port")), 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', deprecated_name='dhcp_lease_time',
help=_("DHCP lease duration")), help=_("DHCP lease duration")),
cfg.BoolOpt('dhcp_agent_notification', default=True, cfg.BoolOpt('dhcp_agent_notification', default=True,

View File

@ -293,54 +293,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
return True return True
return False 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 @staticmethod
def _recycle_ip(context, network_id, subnet_id, ip_address): def _recycle_ip(context, network_id, subnet_id, ip_address):
"""Return an IP address to the pool of free IP's on the network """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, NeutronDbPluginV2._delete_ip_allocation(context, network_id, subnet_id,
ip_address) 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, def update_fixed_ip_lease_expiration(self, context, network_id,
ip_address, lease_remaining): 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) to_add = self._test_fixed_ips_for_port(context, network_id, new_ips)
for ip in original_ips: for ip in original_ips:
LOG.debug(_("Port update. Hold %s"), ip) LOG.debug(_("Port update. Hold %s"), ip)
NeutronDbPluginV2._hold_ip(context, NeutronDbPluginV2._recycle_ip(context,
network_id, network_id,
ip['subnet_id'], ip['subnet_id'],
port_id, ip['ip_address'])
ip['ip_address'])
if to_add: if to_add:
LOG.debug(_("Port update. Adding %s"), 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) tenant_id = self._get_tenant_id_for_create(context, p)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
self._recycle_expired_ip_allocations(context, network_id)
network = self._get_network(context, network_id) network = self._get_network(context, network_id)
# Ensure that a MAC address is defined and it is unique on the # 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, port_id=port_id,
ip_address=ip_address, ip_address=ip_address,
subnet_id=subnet_id, subnet_id=subnet_id,
expiration=self._default_allocation_expiration()
) )
context.session.add(allocated) context.session.add(allocated)
@ -1387,8 +1331,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
# Check if the IPs need to be updated # Check if the IPs need to be updated
if 'fixed_ips' in p: if 'fixed_ips' in p:
changed_ips = True changed_ips = True
self._recycle_expired_ip_allocations(context,
port['network_id'])
original = self._make_port_dict(port, process_extensions=False) original = self._make_port_dict(port, process_extensions=False)
added_ips, prev_ips = self._update_ips_for_port( added_ips, prev_ips = self._update_ips_for_port(
context, port["network_id"], id, original["fixed_ips"], context, port["network_id"], id, original["fixed_ips"],
@ -1398,8 +1340,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
for ip in added_ips: for ip in added_ips:
allocated = models_v2.IPAllocation( allocated = models_v2.IPAllocation(
network_id=port['network_id'], port_id=port.id, network_id=port['network_id'], port_id=port.id,
ip_address=ip['ip_address'], subnet_id=ip['subnet_id'], ip_address=ip['ip_address'], subnet_id=ip['subnet_id'])
expiration=self._default_allocation_expiration())
context.session.add(allocated) context.session.add(allocated)
# Remove all attributes in p which are not in the port DB model # Remove all attributes in p which are not in the port DB model
# and then update the port # and then update the port
@ -1428,11 +1369,10 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
if NeutronDbPluginV2._check_ip_in_allocation_pool( if NeutronDbPluginV2._check_ip_in_allocation_pool(
context, a['subnet_id'], subnet['gateway_ip'], context, a['subnet_id'], subnet['gateway_ip'],
a['ip_address']): a['ip_address']):
NeutronDbPluginV2._hold_ip(context, NeutronDbPluginV2._recycle_ip(context,
a['network_id'], a['network_id'],
a['subnet_id'], a['subnet_id'],
id, a['ip_address'])
a['ip_address'])
else: else:
# IPs out of allocation pool will not be recycled, but # IPs out of allocation pool will not be recycled, but
# we do need to delete the allocation from the DB # 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): def get_active_networks(self, context, **kwargs):
"""Retrieve and return a list of the active network ids.""" """Retrieve and return a list of the active network ids."""
# NOTE(arosen): This method is no longer used by the DHCP agent but is # 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 # left so that neutron-dhcp-agents will still continue to work if
# quantum-server is upgraded and not the agent. # neutron-server is upgraded and not the agent.
host = kwargs.get('host') host = kwargs.get('host')
LOG.debug(_('get_active_networks requested from %s'), host) LOG.debug(_('get_active_networks requested from %s'), host)
nets = self._get_active_networks(context, **kwargs) 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 # 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 # left so that neutron-dhcp-agents will still continue to work if
# quantum-server is upgraded and not the agent. # neutron-server is upgraded and not the agent.
host = kwargs.get('host') host = kwargs.get('host')
network_id = kwargs.get('network_id') network_id = kwargs.get('network_id')
@ -209,20 +209,13 @@ class DhcpRpcCallbackMixin(object):
def update_lease_expiration(self, context, **kwargs): def update_lease_expiration(self, context, **kwargs):
"""Release the fixed_ip associated the subnet on a port.""" """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') 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 ' LOG.warning(_('Updating lease expiration is now deprecated. Issued '
'%(network_id)s from %(host)s.'), 'from host %(host)s.') % host)
{'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)
def create_dhcp_port(self, context, **kwargs): def create_dhcp_port(self, context, **kwargs):
"""Create the dhcp port.""" """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", network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id",
ondelete="CASCADE"), ondelete="CASCADE"),
nullable=False, primary_key=True) nullable=False, primary_key=True)
expiration = sa.Column(sa.DateTime, nullable=True)
class Route(object): class Route(object):

View File

@ -41,6 +41,6 @@ class ConfigurationTest(base.BaseTestCase):
'..', '..', '..') '..', '..', '..')
absolute_dir = os.path.abspath(relative_dir) absolute_dir = os.path.abspath(relative_dir)
self.assertEqual(absolute_dir, cfg.CONF.state_path) 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.assertFalse(cfg.CONF.allow_overlapping_ips)
self.assertEqual('neutron', cfg.CONF.control_exchange) 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']) self.assertEqual(ips[1]['subnet_id'], subnet['subnet']['id'])
def test_update_port_update_ips(self): 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 Check a port update with the specified subnet_id's. A IP address
will be allocated for each subnet_id. 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: with self.port(subnet=subnet) as port:
data = {'port': {'admin_state_up': False, data = {'port': {'admin_state_up': False,
'fixed_ips': [{'subnet_id': 'fixed_ips': [{'subnet_id':
subnet['subnet']['id']}]}} subnet['subnet']['id'],
'ip_address': '10.0.0.3'}]}}
req = self.new_update_request('ports', data, req = self.new_update_request('ports', data,
port['port']['id']) port['port']['id'])
res = self.deserialize(self.fmt, req.get_response(self.api)) 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']) data['port']['admin_state_up'])
ips = res['port']['fixed_ips'] ips = res['port']['fixed_ips']
self.assertEqual(len(ips), 2) 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[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']) self.assertEqual(ips[1]['subnet_id'], subnet['subnet']['id'])
def test_requested_duplicate_mac(self): 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) res = port_req.get_response(self.api)
self.assertEqual(res.status_int, 400) 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): def test_update_fixed_ip_lease_expiration_invalid_address(self):
cfg.CONF.set_override('dhcp_lease_duration', 10) cfg.CONF.set_override('dhcp_lease_duration', 10)
plugin = NeutronManager.get_plugin() 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) 120)
self.assertTrue(log.mock_calls) 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): def test_recycle_ip_address_without_allocation_pool(self):
plugin = NeutronManager.get_plugin() plugin = NeutronManager.get_plugin()
allocation_pools = [{"start": '10.0.0.10', 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) q = q.filter_by(subnet_id=subnet_id)
self.assertEqual(q.count(), 0) 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): def test_max_fixed_ips_exceeded(self):
with self.subnet(gateway_ip='10.0.0.3', with self.subnet(gateway_ip='10.0.0.3',
cidr='10.0.0.0/24') as subnet: cidr='10.0.0.0/24') as subnet:

View File

@ -17,7 +17,6 @@
import copy import copy
import os import os
import socket
import sys import sys
import uuid import uuid
@ -33,7 +32,6 @@ from neutron.agent.linux import dhcp
from neutron.agent.linux import interface from neutron.agent.linux import interface
from neutron.common import constants from neutron.common import constants
from neutron.common import exceptions from neutron.common import exceptions
from neutron.openstack.common import jsonutils
from neutron.tests import base from neutron.tests import base
@ -99,7 +97,8 @@ fake_port1 = FakeModel('12345678-1234-aaaa-1234567890ab',
fake_port2 = FakeModel('12345678-1234-aaaa-123456789000', fake_port2 = FakeModel('12345678-1234-aaaa-123456789000',
mac_address='aa:bb:cc:dd:ee:99', 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', fake_meta_port = FakeModel('12345678-1234-aaaa-1234567890ab',
mac_address='aa:bb:cc:dd:ee:ff', mac_address='aa:bb:cc:dd:ee:ff',
@ -147,7 +146,6 @@ class TestDhcpAgent(base.BaseTestCase):
def test_dhcp_agent_manager(self): def test_dhcp_agent_manager(self):
state_rpc_str = 'neutron.agent.rpc.PluginReportStateAPI' state_rpc_str = 'neutron.agent.rpc.PluginReportStateAPI'
lease_relay_str = 'neutron.agent.dhcp_agent.DhcpLeaseRelay'
with mock.patch.object(DhcpAgentWithStateReport, with mock.patch.object(DhcpAgentWithStateReport,
'sync_state', 'sync_state',
autospec=True) as mock_sync_state: autospec=True) as mock_sync_state:
@ -155,34 +153,27 @@ class TestDhcpAgent(base.BaseTestCase):
'periodic_resync', 'periodic_resync',
autospec=True) as mock_periodic_resync: autospec=True) as mock_periodic_resync:
with mock.patch(state_rpc_str) as state_rpc: 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:
with mock.patch.object(sys, 'argv') as sys_argv: sys_argv.return_value = [
sys_argv.return_value = [ 'dhcp', '--config-file',
'dhcp', '--config-file', etcdir('neutron.conf.test')]
etcdir('neutron.conf.test')] cfg.CONF.register_opts(dhcp_agent.DhcpAgent.OPTS)
cfg.CONF.register_opts(dhcp_agent.DhcpAgent.OPTS) config.register_agent_state_opts_helper(cfg.CONF)
config.register_agent_state_opts_helper(cfg.CONF) config.register_root_helper(cfg.CONF)
config.register_root_helper(cfg.CONF) cfg.CONF.register_opts(
cfg.CONF.register_opts( dhcp_agent.DeviceManager.OPTS)
dhcp_agent.DeviceManager.OPTS) cfg.CONF.register_opts(dhcp.OPTS)
cfg.CONF.register_opts( cfg.CONF.register_opts(interface.OPTS)
dhcp_agent.DhcpLeaseRelay.OPTS) cfg.CONF(project='neutron')
cfg.CONF.register_opts(dhcp.OPTS) agent_mgr = DhcpAgentWithStateReport('testhost')
cfg.CONF.register_opts(interface.OPTS) eventlet.greenthread.sleep(1)
cfg.CONF(project='neutron') agent_mgr.after_start()
agent_mgr = DhcpAgentWithStateReport('testhost') mock_sync_state.assert_called_once_with(agent_mgr)
eventlet.greenthread.sleep(1) mock_periodic_resync.assert_called_once_with(agent_mgr)
agent_mgr.after_start() state_rpc.assert_has_calls(
mock_sync_state.assert_called_once_with(agent_mgr) [mock.call(mock.ANY),
mock_periodic_resync.assert_called_once_with( mock.call().report_state(mock.ANY, mock.ANY,
agent_mgr) mock.ANY)])
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()])
def test_dhcp_agent_main_agent_manager(self): def test_dhcp_agent_main_agent_manager(self):
logging_str = 'neutron.agent.common.config.setup_logging' logging_str = 'neutron.agent.common.config.setup_logging'
@ -202,13 +193,11 @@ class TestDhcpAgent(base.BaseTestCase):
dhcp = dhcp_agent.DhcpAgent(HOSTNAME) dhcp = dhcp_agent.DhcpAgent(HOSTNAME)
attrs_to_mock = dict( attrs_to_mock = dict(
[(a, mock.DEFAULT) for a in [(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: with mock.patch.multiple(dhcp, **attrs_to_mock) as mocks:
dhcp.run() dhcp.run()
mocks['sync_state'].assert_called_once_with() mocks['sync_state'].assert_called_once_with()
mocks['periodic_resync'].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): def test_ns_name(self):
with mock.patch('neutron.agent.dhcp_agent.DeviceManager'): with mock.patch('neutron.agent.dhcp_agent.DeviceManager'):
@ -255,28 +244,6 @@ class TestDhcpAgent(base.BaseTestCase):
self.assertEqual(log.call_count, 1) self.assertEqual(log.call_count, 1)
self.assertTrue(dhcp.needs_resync) 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): def _test_sync_state_helper(self, known_networks, active_networks):
with mock.patch('neutron.agent.dhcp_agent.DhcpPluginApi') as plug: with mock.patch('neutron.agent.dhcp_agent.DhcpPluginApi') as plug:
mock_plugin = mock.Mock() mock_plugin = mock.Mock()
@ -425,7 +392,6 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
def setUp(self): def setUp(self):
super(TestDhcpAgentEventHandler, self).setUp() super(TestDhcpAgentEventHandler, self).setUp()
cfg.CONF.register_opts(dhcp_agent.DeviceManager.OPTS) 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(dhcp.OPTS)
cfg.CONF.set_override('interface_driver', cfg.CONF.set_override('interface_driver',
'neutron.agent.linux.interface.NullDriver') 'neutron.agent.linux.interface.NullDriver')
@ -754,26 +720,52 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
def test_port_update_end(self): def test_port_update_end(self):
payload = dict(port=vars(fake_port2)) payload = dict(port=vars(fake_port2))
self.cache.get_network_by_id.return_value = fake_network 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.dhcp.port_update_end(None, payload)
self.cache.assert_has_calls( self.cache.assert_has_calls(
[mock.call.get_network_by_id(fake_port2.network_id), [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)]) mock.call.put_port(mock.ANY)])
self.call_driver.assert_called_once_with('reload_allocations', self.call_driver.assert_called_once_with('reload_allocations',
fake_network) 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): def test_port_delete_end(self):
payload = dict(port_id=fake_port2.id) payload = dict(port_id=fake_port2.id)
self.cache.get_network_by_id.return_value = fake_network self.cache.get_network_by_id.return_value = fake_network
self.cache.get_port_by_id.return_value = fake_port2 self.cache.get_port_by_id.return_value = fake_port2
self.dhcp.port_delete_end(None, payload) 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( self.cache.assert_has_calls(
[mock.call.get_port_by_id(fake_port2.id), [mock.call.get_port_by_id(fake_port2.id),
mock.call.get_network_by_id(fake_network.id), mock.call.get_network_by_id(fake_network.id),
mock.call.remove_port(fake_port2)]) mock.call.remove_port(fake_port2)])
self.call_driver.assert_called_once_with('reload_allocations', self.call_driver.assert_has_calls(
fake_network) [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): def test_port_delete_end_unknown_port(self):
payload = dict(port_id='unknown') payload = dict(port_id='unknown')
@ -865,16 +857,6 @@ class TestDhcpPluginApiProxy(base.BaseTestCase):
device_id='devid', device_id='devid',
host='foo') 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): class TestNetworkCache(base.BaseTestCase):
def test_put_network(self): 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') 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): class TestDictModel(base.BaseTestCase):
def test_basic_dict(self): def test_basic_dict(self):
d = dict(a=1, b=2) d = dict(a=1, b=2)

View File

@ -16,7 +16,6 @@
# under the License. # under the License.
import os import os
import socket
import mock import mock
from oslo.config import cfg from oslo.config import cfg
@ -24,7 +23,6 @@ from oslo.config import cfg
from neutron.agent.common import config from neutron.agent.common import config
from neutron.agent.linux import dhcp from neutron.agent.linux import dhcp
from neutron.common import config as base_config from neutron.common import config as base_config
from neutron.openstack.common import jsonutils
from neutron.tests import base from neutron.tests import base
@ -184,6 +182,9 @@ class TestDhcpBase(base.BaseTestCase):
def reload_allocations(self): def reload_allocations(self):
pass pass
def release_lease(self):
pass
@property @property
def active(self): def active(self):
return True return True
@ -209,6 +210,9 @@ class LocalChild(dhcp.DhcpLocalProcess):
def spawn_process(self): def spawn_process(self):
self.called.append('spawn') self.called.append('spawn')
def release_lease(self):
self.called.append('release_lease')
class TestBase(base.BaseTestCase): class TestBase(base.BaseTestCase):
def setUp(self): def setUp(self):
@ -219,9 +223,6 @@ class TestBase(base.BaseTestCase):
self.conf = config.setup_conf() self.conf = config.setup_conf()
self.conf.register_opts(base_config.core_opts) self.conf.register_opts(base_config.core_opts)
self.conf.register_opts(dhcp.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', self.conf.register_opt(cfg.BoolOpt('enable_isolated_metadata',
default=True)) default=True))
self.conf(args=args) self.conf(args=args)
@ -230,9 +231,9 @@ class TestBase(base.BaseTestCase):
self.replace_p = mock.patch('neutron.agent.linux.utils.replace_file') self.replace_p = mock.patch('neutron.agent.linux.utils.replace_file')
self.execute_p = mock.patch('neutron.agent.linux.utils.execute') self.execute_p = mock.patch('neutron.agent.linux.utils.execute')
self.addCleanup(self.replace_p.stop)
self.addCleanup(self.execute_p.stop) self.addCleanup(self.execute_p.stop)
self.safe = self.replace_p.start() self.safe = self.replace_p.start()
self.addCleanup(self.replace_p.stop)
self.execute = self.execute_p.start() self.execute = self.execute_p.start()
@ -433,7 +434,6 @@ class TestDnsmasq(TestBase):
'exec', 'exec',
'qdhcp-ns', 'qdhcp-ns',
'env', 'env',
'NEUTRON_RELAY_SOCKET_PATH=/dhcp/lease_relay',
'NEUTRON_NETWORK_ID=cccccccc-cccc-cccc-cccc-cccccccccccc', 'NEUTRON_NETWORK_ID=cccccccc-cccc-cccc-cccc-cccccccccccc',
'dnsmasq', 'dnsmasq',
'--no-hosts', '--no-hosts',
@ -445,11 +445,9 @@ class TestDnsmasq(TestBase):
'--pid-file=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/pid', '--pid-file=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/pid',
'--dhcp-hostsfile=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host', '--dhcp-hostsfile=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host',
'--dhcp-optsfile=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/opts', '--dhcp-optsfile=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/opts',
('--dhcp-script=/usr/local/bin/neutron-dhcp-agent-'
'dnsmasq-lease-update'),
'--leasefile-ro', '--leasefile-ro',
'--dhcp-range=set:tag0,192.168.0.0,static,120s', '--dhcp-range=set:tag0,192.168.0.0,static,86400s',
'--dhcp-range=set:tag1,fdca:3ba5:a17a:4ba3::,static,120s'] '--dhcp-range=set:tag1,fdca:3ba5:a17a:4ba3::,static,86400s']
expected.extend(extra_options) expected.extend(extra_options)
self.execute.return_value = ('', '') self.execute.return_value = ('', '')
@ -585,6 +583,17 @@ tag:tag0,option:router""".lstrip()
self.safe.assert_called_once_with('/foo/opts', expected) 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): def test_reload_allocations(self):
exp_host_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host' 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,' 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'} {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): def test_remove_config_files(self):
net = FakeV4Network() net = FakeV4Network()
path = '/opt/data/neutron/dhcp' path = '/opt/data/neutron/dhcp'

View File

@ -73,7 +73,6 @@ console_scripts =
neutron-db-manage = neutron.db.migration.cli:main neutron-db-manage = neutron.db.migration.cli:main
neutron-debug = neutron.debug.shell:main neutron-debug = neutron.debug.shell:main
neutron-dhcp-agent = neutron.agent.dhcp_agent: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-hyperv-agent = neutron.plugins.hyperv.agent.hyperv_neutron_agent:main
neutron-l3-agent = neutron.agent.l3_agent:main neutron-l3-agent = neutron.agent.l3_agent:main
neutron-lbaas-agent = neutron.services.loadbalancer.drivers.haproxy.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-db-manage = neutron.db.migration.cli:main
quantum-debug = neutron.debug.shell:main quantum-debug = neutron.debug.shell:main
quantum-dhcp-agent = neutron.agent.dhcp_agent: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-hyperv-agent = neutron.plugins.hyperv.agent.hyperv_neutron_agent:main
quantum-l3-agent = neutron.agent.l3_agent:main quantum-l3-agent = neutron.agent.l3_agent:main
quantum-lbaas-agent = neutron.services.loadbalancer.drivers.haproxy.agent:main quantum-lbaas-agent = neutron.services.loadbalancer.drivers.haproxy.agent:main