return False
@staticmethod
- def _recycle_ip(context, network_id, subnet_id, port_id, ip_address):
+ def _hold_ip(context, network_id, subnet_id, port_id, ip_address):
+ alloc_qry = context.session.query(models_v2.IPAllocation)
+ 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
+ QuantumDbPluginV2._recycle_ip(
+ context, network_id, subnet_id, ip_address)
+ else:
+ LOG.debug("Hold allocated IP %s (%s/%s/%s)", ip_address,
+ network_id, subnet_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)
+ 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.all():
+ QuantumDbPluginV2._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
subnet.
"""
context.session.add(ip_range)
LOG.debug("Recycle: created new %s-%s", ip_address, ip_address)
QuantumDbPluginV2._delete_ip_allocation(context, network_id, subnet_id,
- port_id, ip_address)
+ ip_address)
@staticmethod
def _default_allocation_expiration():
"ip address %s.", network_id, ip_address)
@staticmethod
- def _delete_ip_allocation(context, network_id, subnet_id, port_id,
- ip_address):
+ def _delete_ip_allocation(context, network_id, subnet_id, ip_address):
# Delete the IP address from the IPAllocate table
- LOG.debug("Delete allocated IP %s (%s/%s/%s)", ip_address,
- network_id, subnet_id, port_id)
+ LOG.debug("Delete allocated IP %s (%s/%s)", ip_address,
+ network_id, subnet_id)
alloc_qry = context.session.query(models_v2.IPAllocation)
allocated = alloc_qry.filter_by(network_id=network_id,
- port_id=port_id,
ip_address=ip_address,
subnet_id=subnet_id).delete()
new_ips):
"""Add or remove IPs from the port."""
ips = []
+
# Remove all of the intersecting elements
for original_ip in original_ips[:]:
for new_ip in new_ips[:]:
# Check if the IP's to add are OK
to_add = self._test_fixed_ips_for_port(context, network_id, new_ips)
for ip in original_ips:
- LOG.debug("Port update. Deleting %s", ip)
- QuantumDbPluginV2._recycle_ip(context,
- network_id=network_id,
- subnet_id=ip['subnet_id'],
- ip_address=ip['ip_address'],
- port_id=port_id)
+ LOG.debug("Port update. Hold %s", ip)
+ QuantumDbPluginV2._hold_ip(context,
+ network_id,
+ ip['subnet_id'],
+ port_id,
+ ip['ip_address'])
if to_add:
LOG.debug("Port update. Adding %s", to_add)
allocated_qry = allocated_qry.options(orm.joinedload('ports'))
allocated = allocated_qry.filter_by(subnet_id=id).all()
- only_svc = all(a.ports.device_owner.startswith(AGENT_OWNER_PREFIX)
+ only_svc = all(not a.port_id or
+ a.ports.device_owner.startswith(AGENT_OWNER_PREFIX)
for a in allocated)
if not only_svc:
raise q_exc.NetworkInUse(subnet_id=id)
tenant_id = self._get_tenant_id_for_create(context, p)
with context.session.begin(subtransactions=True):
+ self._recycle_expired_ip_allocations(context, p['network_id'])
network = self._get_network(context, p["network_id"])
# Ensure that a MAC address is defined and it is unique on the
port = self._get_port(context, id)
# Check if the IPs need to be updated
if 'fixed_ips' in p:
+ self._recycle_expired_ip_allocations(context,
+ port['network_id'])
original = self._make_port_dict(port)
ips = self._update_ips_for_port(context,
port["network_id"],
# Gateway address will not be recycled, but we do
# need to delete the allocation from the DB
QuantumDbPluginV2._delete_ip_allocation(
- context,
- a['network_id'], a['subnet_id'],
- id, a['ip_address'])
+ context, a['network_id'],
+ a['subnet_id'], a['ip_address'])
LOG.debug("Gateway address (%s/%s) is not recycled",
a['ip_address'], a['subnet_id'])
continue
- QuantumDbPluginV2._recycle_ip(context,
- network_id=a['network_id'],
- subnet_id=a['subnet_id'],
- ip_address=a['ip_address'],
- port_id=id)
+ QuantumDbPluginV2._hold_ip(context,
+ a['network_id'],
+ a['subnet_id'],
+ id,
+ a['ip_address'])
context.session.delete(port)
def get_port(self, context, id, fields=None):
data['port']['admin_state_up'])
ips = res['port']['fixed_ips']
self.assertEquals(len(ips), 1)
- self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
+ self.assertEquals(ips[0]['ip_address'], '10.0.0.3')
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
def test_update_port_add_additional_ip(self):
data['port']['admin_state_up'])
ips = res['port']['fixed_ips']
self.assertEquals(len(ips), 2)
- self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
+ self.assertEquals(ips[0]['ip_address'], '10.0.0.3')
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
- self.assertEquals(ips[1]['ip_address'], '10.0.0.3')
+ self.assertEquals(ips[1]['ip_address'], '10.0.0.4')
self.assertEquals(ips[1]['subnet_id'], subnet['subnet']['id'])
def test_requested_duplicate_mac(self):
self._delete('ports', p['port']['id'])
def test_recycling(self):
+ # set expirations to past so that recycling is checked
+ reference = datetime.datetime(2012, 8, 13, 23, 11, 0)
+ cfg.CONF.set_override('dhcp_lease_duration', 0)
fmt = 'json'
+
with self.subnet(cidr='10.0.1.0/24') as subnet:
with self.port(subnet=subnet) as port:
- ips = port['port']['fixed_ips']
- self.assertEquals(len(ips), 1)
- self.assertEquals(ips[0]['ip_address'], '10.0.1.2')
- self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
- net_id = port['port']['network_id']
- ports = []
- for i in range(16 - 3):
+ with mock.patch.object(timeutils, 'utcnow') as mock_utcnow:
+ mock_utcnow.return_value = reference
+ ips = port['port']['fixed_ips']
+ self.assertEquals(len(ips), 1)
+ self.assertEquals(ips[0]['ip_address'], '10.0.1.2')
+ self.assertEquals(ips[0]['subnet_id'],
+ subnet['subnet']['id'])
+ net_id = port['port']['network_id']
+ ports = []
+ for i in range(16 - 3):
+ res = self._create_port(fmt, net_id=net_id)
+ p = self.deserialize(fmt, res)
+ ports.append(p)
+ for i in range(16 - 3):
+ x = random.randrange(0, len(ports), 1)
+ p = ports.pop(x)
+ self._delete('ports', p['port']['id'])
res = self._create_port(fmt, net_id=net_id)
- p = self.deserialize(fmt, res)
- ports.append(p)
- for i in range(16 - 3):
- x = random.randrange(0, len(ports), 1)
- p = ports.pop(x)
- self._delete('ports', p['port']['id'])
- res = self._create_port(fmt, net_id=net_id)
- port = self.deserialize(fmt, res)
- ips = port['port']['fixed_ips']
- self.assertEquals(len(ips), 1)
- self.assertEquals(ips[0]['ip_address'], '10.0.1.3')
- self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
- self._delete('ports', port['port']['id'])
+ port = self.deserialize(fmt, res)
+ ips = port['port']['fixed_ips']
+ self.assertEquals(len(ips), 1)
+ self.assertEquals(ips[0]['ip_address'], '10.0.1.3')
+ self.assertEquals(ips[0]['subnet_id'],
+ subnet['subnet']['id'])
+ self._delete('ports', port['port']['id'])
def test_invalid_admin_state(self):
with self.network() as network:
self.assertEquals(res.status_int, 422)
def test_default_allocation_expiration(self):
+ cfg.CONF.set_override('dhcp_lease_duration', 120)
reference = datetime.datetime(2012, 8, 13, 23, 11, 0)
- timeutils.utcnow.override_time = reference
- cfg.CONF.set_override('dhcp_lease_duration', 120)
- expires = QuantumManager.get_plugin()._default_allocation_expiration()
- timeutils.utcnow
- cfg.CONF.reset()
- timeutils.utcnow.override_time = None
- self.assertEqual(expires, reference + datetime.timedelta(seconds=120))
+ with mock.patch.object(timeutils, 'utcnow') as mock_utcnow:
+ mock_utcnow.return_value = reference
+
+ plugin = QuantumManager.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)
ip_allocation.expiration - timeutils.utcnow(),
datetime.timedelta(seconds=10))
- cfg.CONF.reset()
+ def test_port_delete_holds_ip(self):
+ plugin = QuantumManager.get_plugin()
+ base_class = db_base_plugin_v2.QuantumDbPluginV2
+ 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.assertEquals(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)
'255.255.255.0',
120)
self.assertTrue(log.mock_calls)
- cfg.CONF.reset()
+
+ def test_hold_ip_address(self):
+ plugin = QuantumManager.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.assertEquals(len(q.all()), 1)
+
+ def test_recycle_held_ip_address(self):
+ plugin = QuantumManager.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.assertEquals(len(rc.mock_calls), 1)
+ self.assertEquals(update_context._recycled_networks,
+ set([subnet['subnet']['network_id']]))
+
+ def test_recycle_expired_previously_run_within_context(self):
+ plugin = QuantumManager.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.assertEquals(update_context._recycled_networks,
+ set([subnet['subnet']['network_id']]))
class TestNetworksV2(QuantumDbPluginV2TestCase):