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:
parent
8e63d9e6d7
commit
c939aee8d6
@ -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()
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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."""
|
||||
|
@ -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))
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user