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) 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 # FloatingIP Allocation
def fip_allocation_create(allocation_values): def fip_allocation_create(allocation_values):

View File

@ -934,6 +934,16 @@ def required_fip_destroy(required_fip_id):
session.delete(required_fip) 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 # FloatingIP Allocation
def _fip_allocation_get(session, fip_allocation_id): def _fip_allocation_get(session, fip_allocation_id):

View File

@ -216,3 +216,9 @@ class TooLongFloatingIPs(exceptions.InvalidInput):
class NotEnoughFloatingIPAvailable(exceptions.InvalidInput): class NotEnoughFloatingIPAvailable(exceptions.InvalidInput):
msg_fmt = _("Not enough floating IPs available") 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 from oslo_log import log as logging
import six import six
from blazar.db import api as db_api
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
@ -66,12 +64,10 @@ class BasePlugin(object):
"""Reserve resource.""" """Reserve resource."""
pass pass
@abc.abstractmethod
def update_reservation(self, reservation_id, values): def update_reservation(self, reservation_id, values):
"""Update reservation.""" """Update reservation."""
reservation_values = { pass
'resource_id': values['resource_id']
}
db_api.reservation_update(reservation_id, reservation_values)
@abc.abstractmethod @abc.abstractmethod
def on_end(self, resource_id): 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.manager import exceptions as manager_ex
from blazar.plugins import base from blazar.plugins import base
from blazar.plugins import floatingips as plugin from blazar.plugins import floatingips as plugin
from blazar import status
from blazar.utils.openstack import neutron from blazar.utils.openstack import neutron
from blazar.utils import plugins as plugins_utils 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)): if not (netutils.is_valid_ipv4(ip) or netutils.is_valid_ipv6(ip)):
raise manager_ex.InvalidIPFormat(ip=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): def reserve_resource(self, reservation_id, values):
"""Create floating IP reservation.""" """Create floating IP reservation."""
self.check_params(values) self.check_params(values)
@ -95,6 +174,50 @@ class FloatingIpPlugin(base.BasePlugin):
'reservation_id': reservation_id}) 'reservation_id': reservation_id})
return fip_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): def on_start(self, resource_id):
fip_reservation = db_api.fip_reservation_get(resource_id) fip_reservation = db_api.fip_reservation_get(resource_id)
allocations = db_api.fip_allocation_get_all_by_values( 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): def reserve_resource(self, reservation_id, values):
return None return None
def update_reservation(self, reservation_id, values):
return None
def on_start(self, resource_id): def on_start(self, resource_id):
return 'Resource %s should be started this moment.' % 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', u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values) 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): def test_on_start(self):
fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get') fip_reservation_get = self.patch(self.db_api, 'fip_reservation_get')
fip_reservation_get.return_value = { 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.