+++ /dev/null
-#!/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()
# 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
[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
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
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__)
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()
"""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
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'))
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."""
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)
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):
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."""
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)
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)
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."""
"""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 = [
#'--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',
]
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."""
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:
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,
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
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):
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)
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
port_id=port_id,
ip_address=ip_address,
subnet_id=subnet_id,
- expiration=self._default_allocation_expiration()
)
context.session.add(allocated)
# 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"],
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
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
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)
"""
# 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')
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."""
--- /dev/null
+# 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))
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):
'..', '..', '..')
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)
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.
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))
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):
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()
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',
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:
import copy
import os
-import socket
import sys
import uuid
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
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',
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:
'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'
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'):
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()
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')
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')
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):
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)
# under the License.
import os
-import socket
import mock
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
def reload_allocations(self):
pass
+ def release_lease(self):
+ pass
+
@property
def active(self):
return True
def spawn_process(self):
self.called.append('spawn')
+ def release_lease(self):
+ self.called.append('release_lease')
+
class TestBase(base.BaseTestCase):
def setUp(self):
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)
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()
'exec',
'qdhcp-ns',
'env',
- 'NEUTRON_RELAY_SOCKET_PATH=/dhcp/lease_relay',
'NEUTRON_NETWORK_ID=cccccccc-cccc-cccc-cccc-cccccccccccc',
'dnsmasq',
'--no-hosts',
'--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 = ('', '')
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,'
{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'
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
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