Add update_reservation to floating IP plugin

The update reservation functionality was not implemented in the original
plugin. Consequently, any mixed resource lease containing a floating IP
resource would fail when the lease was updated.

Co-Authored-By: Jacob Colleran <jakecoll@uchicago.edu>
Change-Id: I2f55f33ba5af9d9f2b84b716caaa8ed1cfd0db66
This commit is contained in:
Pierre Riteau 2019-09-12 15:56:36 +02:00
parent 520005b13d
commit 3137ffc84d
8 changed files with 415 additions and 6 deletions

View File

@ -466,6 +466,11 @@ def required_fip_destroy(required_fip_id):
return IMPL.required_fip_destroy(required_fip_id)
def required_fip_destroy_by_fip_reservation_id(fip_reservation_id):
"""Delete all required FIPs for a floating IP reservation."""
return IMPL.required_fip_destroy_by_fip_reservation_id(fip_reservation_id)
# FloatingIP Allocation
def fip_allocation_create(allocation_values):

View File

@ -934,6 +934,16 @@ def required_fip_destroy(required_fip_id):
session.delete(required_fip)
def required_fip_destroy_by_fip_reservation_id(fip_reservation_id):
session = get_session()
with session.begin():
required_fips = model_query(
models.RequiredFloatingIP, session).filter_by(
floatingip_reservation_id=fip_reservation_id)
for required_fip in required_fips:
required_fip_destroy(required_fip['id'])
# FloatingIP Allocation
def _fip_allocation_get(session, fip_allocation_id):

View File

@ -216,3 +216,9 @@ class TooLongFloatingIPs(exceptions.InvalidInput):
class NotEnoughFloatingIPAvailable(exceptions.InvalidInput):
msg_fmt = _("Not enough floating IPs available")
class CantUpdateFloatingIPReservation(exceptions.BlazarException):
code = 400
msg_fmt = _("Floating IP reservation cannot be updated with requested "
"parameters. %(msg)s")

View File

@ -19,8 +19,6 @@ from oslo_config import cfg
from oslo_log import log as logging
import six
from blazar.db import api as db_api
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -66,12 +64,10 @@ class BasePlugin(object):
"""Reserve resource."""
pass
@abc.abstractmethod
def update_reservation(self, reservation_id, values):
"""Update reservation."""
reservation_values = {
'resource_id': values['resource_id']
}
db_api.reservation_update(reservation_id, reservation_values)
pass
@abc.abstractmethod
def on_end(self, resource_id):

View File

@ -27,6 +27,7 @@ from blazar import exceptions
from blazar.manager import exceptions as manager_ex
from blazar.plugins import base
from blazar.plugins import floatingips as plugin
from blazar import status
from blazar.utils.openstack import neutron
from blazar.utils import plugins as plugins_utils
@ -61,6 +62,84 @@ class FloatingIpPlugin(base.BasePlugin):
if not (netutils.is_valid_ipv4(ip) or netutils.is_valid_ipv6(ip)):
raise manager_ex.InvalidIPFormat(ip=ip)
def _update_allocations(self, dates_before, dates_after, reservation_id,
reservation_status, fip_reservation, values):
amount = int(values.get('amount', fip_reservation['amount']))
fip_allocations = db_api.fip_allocation_get_all_by_values(
reservation_id=reservation_id)
allocs_to_remove = self._allocations_to_remove(
dates_before, dates_after, fip_allocations, amount)
if (allocs_to_remove and
reservation_status == status.reservation.ACTIVE):
raise manager_ex.CantUpdateFloatingIPReservation(
msg="Cannot remove allocations from an active reservation")
kept_fips = len(fip_allocations) - len(allocs_to_remove)
fip_ids_to_add = []
if kept_fips < amount:
needed_fips = amount - kept_fips
required_fips = values.get(
'required_floatingips',
fip_reservation['required_floatingips'])
fip_ids_to_add = self._matching_fips(
fip_reservation['network_id'], required_fips, needed_fips,
dates_after['start_date'], dates_after['end_date'])
if len(fip_ids_to_add) < needed_fips:
raise manager_ex.NotEnoughFloatingIPAvailable()
for fip_id in fip_ids_to_add:
LOG.debug('Adding floating IP {} to reservation {}'.format(
fip_id, reservation_id))
db_api.fip_allocation_create({
'floatingip_id': fip_id,
'reservation_id': reservation_id})
for allocation in allocs_to_remove:
LOG.debug('Removing floating IP {} from reservation {}'.format(
allocation['floatingip_id'], reservation_id))
db_api.fip_allocation_destroy(allocation['id'])
def _allocations_to_remove(self, dates_before, dates_after, allocs,
amount):
"""Find candidate floating IP allocations to remove."""
allocs_to_remove = []
for alloc in allocs:
is_extension = (
dates_before['start_date'] > dates_after['start_date'] or
dates_before['end_date'] < dates_after['end_date'])
if is_extension:
reserved_periods = db_utils.get_reserved_periods(
alloc['floatingip_id'],
dates_after['start_date'],
dates_after['end_date'],
datetime.timedelta(seconds=1),
resource_type='floatingip')
max_start = max(dates_before['start_date'],
dates_after['start_date'])
min_end = min(dates_before['end_date'],
dates_after['end_date'])
if not (len(reserved_periods) == 0 or
(len(reserved_periods) == 1 and
reserved_periods[0][0] == max_start and
reserved_periods[0][1] == min_end)):
allocs_to_remove.append(alloc)
continue
allocs_to_keep = [a for a in allocs if a not in allocs_to_remove]
if len(allocs_to_keep) > amount:
allocs_to_remove.extend(
allocs_to_keep[:(len(allocs_to_keep) - amount)])
return allocs_to_remove
def reserve_resource(self, reservation_id, values):
"""Create floating IP reservation."""
self.check_params(values)
@ -95,6 +174,50 @@ class FloatingIpPlugin(base.BasePlugin):
'reservation_id': reservation_id})
return fip_reservation['id']
def update_reservation(self, reservation_id, values):
"""Update reservation."""
reservation = db_api.reservation_get(reservation_id)
lease = db_api.lease_get(reservation['lease_id'])
dates_before = {'start_date': lease['start_date'],
'end_date': lease['end_date']}
dates_after = {'start_date': values['start_date'],
'end_date': values['end_date']}
fip_reservation = db_api.fip_reservation_get(
reservation['resource_id'])
if ('network_id' in values and
values.get('network_id') != fip_reservation['network_id']):
raise manager_ex.CantUpdateFloatingIPReservation(
msg="Updating network_id is not supported")
required_fips = fip_reservation['required_floatingips']
if ('required_floatingips' in values and
values['required_floatingips'] != required_fips and
values['required_floatingips'] != []):
raise manager_ex.CantUpdateFloatingIPReservation(
msg="Updating required_floatingips is not supported except "
"with an empty list")
self._update_allocations(dates_before, dates_after, reservation_id,
reservation['status'], fip_reservation,
values)
updates = {}
if 'amount' in values:
updates['amount'] = values.get('amount')
if updates:
db_api.fip_reservation_update(fip_reservation['id'], updates)
if ('required_floatingips' in values and
values['required_floatingips'] != required_fips):
db_api.required_fip_destroy_by_fip_reservation_id(
fip_reservation['id'])
for fip_address in values.get('required_floatingips'):
fip_address_values = {
'address': fip_address,
'floatingip_reservation_id': fip_reservation['id']
}
db_api.required_fip_create(fip_address_values)
def on_start(self, resource_id):
fip_reservation = db_api.fip_reservation_get(resource_id)
allocations = db_api.fip_allocation_get_all_by_values(

View File

@ -54,6 +54,9 @@ class FakePlugin(base.BasePlugin):
def reserve_resource(self, reservation_id, values):
return None
def update_reservation(self, reservation_id, values):
return None
def on_start(self, resource_id):
return 'Resource %s should be started this moment.' % resource_id

View File

@ -320,6 +320,265 @@ class FloatingIpPluginTest(tests.TestCase):
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
def test_update_reservation_increase_amount_fips_available(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
values = {
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
'resource_type': plugin.RESOURCE_TYPE,
'amount': 2,
}
lease_get = self.patch(self.db_api, 'lease_get')
lease_get.return_value = {
'id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
}
reservation_get = self.patch(self.db_api, 'reservation_get')
reservation_get.return_value = {
'id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'lease_id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'resource_id': 'fip-reservation-id-1',
'resource_type': 'virtual:floatingip',
'status': 'pending',
}
fip_allocation_get_all_by_values = self.patch(
self.db_api, 'fip_allocation_get_all_by_values'
)
fip_allocation_get_all_by_values.return_value = [{
'floatingip_id': 'fip1'
}]
fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get')
fip_reservation_get.return_value = {
'id': 'fip_resv_id1',
'amount': 1,
'reservation_id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'network_id': 'f548089e-fb3e-4013-a043-c5ed809c7a67',
'required_floatingips': [],
}
matching_fips = self.patch(fip_plugin, '_matching_fips')
matching_fips.return_value = ['fip2']
fip_reservation_update = self.patch(self.db_api,
'fip_reservation_update')
fip_allocation_create = self.patch(
self.db_api, 'fip_allocation_create')
fip_plugin.update_reservation(
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
fip_reservation_update.assert_called_once_with(
'fip_resv_id1', {'amount': 2})
calls = [
mock.call(
{'floatingip_id': 'fip2',
'reservation_id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
})
]
fip_allocation_create.assert_has_calls(calls)
def test_update_reservation_increase_amount_fips_unavailable(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
values = {
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
'resource_type': plugin.RESOURCE_TYPE,
'amount': 2,
}
lease_get = self.patch(self.db_api, 'lease_get')
lease_get.return_value = {
'id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
}
reservation_get = self.patch(self.db_api, 'reservation_get')
reservation_get.return_value = {
'id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'lease_id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'resource_id': 'fip-reservation-id-1',
'resource_type': 'virtual:floatingip',
'status': 'pending',
}
fip_allocation_get_all_by_values = self.patch(
self.db_api, 'fip_allocation_get_all_by_values'
)
fip_allocation_get_all_by_values.return_value = [{
'floatingip_id': 'fip1'
}]
fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get')
fip_reservation_get.return_value = {
'id': 'fip_resv_id1',
'amount': 1,
'reservation_id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'network_id': 'f548089e-fb3e-4013-a043-c5ed809c7a67',
'required_floatingips': [],
}
matching_fips = self.patch(fip_plugin, '_matching_fips')
matching_fips.return_value = []
self.assertRaises(mgr_exceptions.NotEnoughFloatingIPAvailable,
fip_plugin.update_reservation,
'441c1476-9f8f-4700-9f30-cd9b6fef3509', values)
def test_update_reservation_decrease_amount(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
values = {
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
'resource_type': plugin.RESOURCE_TYPE,
'amount': 1,
}
lease_get = self.patch(self.db_api, 'lease_get')
lease_get.return_value = {
'id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
}
reservation_get = self.patch(self.db_api, 'reservation_get')
reservation_get.return_value = {
'id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'lease_id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'resource_id': 'fip-reservation-id-1',
'resource_type': 'virtual:floatingip',
'status': 'pending',
}
fip_allocation_get_all_by_values = self.patch(
self.db_api, 'fip_allocation_get_all_by_values'
)
fip_allocation_get_all_by_values.return_value = [
{'id': 'fip_alloc_1', 'floatingip_id': 'fip1'},
{'id': 'fip_alloc_2', 'floatingip_id': 'fip2'},
]
fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get')
fip_reservation_get.return_value = {
'id': 'fip_resv_id1',
'amount': 2,
'reservation_id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'network_id': 'f548089e-fb3e-4013-a043-c5ed809c7a67',
'required_floatingips': [],
}
fip_allocation_destroy = self.patch(self.db_api,
'fip_allocation_destroy')
fip_reservation_update = self.patch(self.db_api,
'fip_reservation_update')
fip_plugin.update_reservation(
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
fip_reservation_update.assert_called_once_with(
'fip_resv_id1', {'amount': 1})
calls = [
mock.call('fip_alloc_1')
]
fip_allocation_destroy.assert_has_calls(calls)
def test_update_reservation_remove_required_fips(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
values = {
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
'resource_type': plugin.RESOURCE_TYPE,
'required_floatingips': [],
}
lease_get = self.patch(self.db_api, 'lease_get')
lease_get.return_value = {
'id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
}
reservation_get = self.patch(self.db_api, 'reservation_get')
reservation_get.return_value = {
'id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'lease_id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'resource_id': 'fip-reservation-id-1',
'resource_type': 'virtual:floatingip',
'status': 'pending',
}
fip_allocation_get_all_by_values = self.patch(
self.db_api, 'fip_allocation_get_all_by_values'
)
fip_allocation_get_all_by_values.return_value = [{
'floatingip_id': 'fip1'
}]
fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get')
fip_reservation_get.return_value = {
'id': 'fip_resv_id1',
'amount': 1,
'reservation_id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'network_id': 'f548089e-fb3e-4013-a043-c5ed809c7a67',
'required_floatingips': ['172.24.4.100']
}
required_fip_destroy_by_fip_reservation_id = self.patch(
self.db_api, 'required_fip_destroy_by_fip_reservation_id')
fip_plugin.update_reservation(
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
calls = [mock.call('fip_resv_id1')]
required_fip_destroy_by_fip_reservation_id.assert_has_calls(calls)
def test_update_reservation_change_required_fips(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
values = {
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
'resource_type': plugin.RESOURCE_TYPE,
'required_floatingips': ['172.24.4.101'],
}
lease_get = self.patch(self.db_api, 'lease_get')
lease_get.return_value = {
'id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
}
reservation_get = self.patch(self.db_api, 'reservation_get')
reservation_get.return_value = {
'id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'lease_id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'resource_id': 'fip-reservation-id-1',
'resource_type': 'virtual:floatingip',
'status': 'pending',
}
fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get')
fip_reservation_get.return_value = {
'id': 'fip_resv_id1',
'amount': 1,
'reservation_id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'network_id': 'f548089e-fb3e-4013-a043-c5ed809c7a67',
'required_floatingips': ['172.24.4.100']
}
self.assertRaises(mgr_exceptions.CantUpdateFloatingIPReservation,
fip_plugin.update_reservation,
'441c1476-9f8f-4700-9f30-cd9b6fef3509', values)
def test_update_reservation_change_network_id(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
values = {
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
'resource_type': plugin.RESOURCE_TYPE,
'network_id': 'new-network-id',
}
lease_get = self.patch(self.db_api, 'lease_get')
lease_get.return_value = {
'id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'start_date': datetime.datetime(2013, 12, 19, 20, 0),
'end_date': datetime.datetime(2013, 12, 19, 21, 0),
}
reservation_get = self.patch(self.db_api, 'reservation_get')
reservation_get.return_value = {
'id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'lease_id': '018c1b43-e69e-4aef-a543-09681539cf4c',
'resource_id': 'fip-reservation-id-1',
'resource_type': 'virtual:floatingip',
'status': 'pending',
}
fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get')
fip_reservation_get.return_value = {
'id': 'fip_resv_id1',
'amount': 1,
'reservation_id': '441c1476-9f8f-4700-9f30-cd9b6fef3509',
'network_id': 'f548089e-fb3e-4013-a043-c5ed809c7a67',
}
self.assertRaises(mgr_exceptions.CantUpdateFloatingIPReservation,
fip_plugin.update_reservation,
'441c1476-9f8f-4700-9f30-cd9b6fef3509', values)
def test_on_start(self):
fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get')
fip_reservation_get.return_value = {

View File

@ -0,0 +1,7 @@
---
features:
- |
Adds support for updating floating IP reservations, with some limitations.
The ``amount`` of reserved floating IPs can be updated; however, updating
``network_id`` is denied and ``required_floatingips`` can only be changed
to an empty list.