Support reserve_resource in floatingip_plugin

Partially Implements: blueprint floatingip-reservation
Change-Id: Ieb3e9d23f5d872552dbd7c23035587b0621ba367
This commit is contained in:
Masahito Muroi 2019-01-22 18:21:18 +09:00
parent ee8908cf77
commit 412787c4d0
7 changed files with 752 additions and 18 deletions

View File

@ -418,6 +418,77 @@ def host_get_all_by_queries_including_extracapabilities(queries):
return IMPL.host_get_all_by_queries_including_extracapabilities(queries) return IMPL.host_get_all_by_queries_including_extracapabilities(queries)
# FloatingIP reservation
def fip_reservation_create(fip_reservation_values):
"""Create a floating IP reservation from the values."""
return IMPL.fip_reservation_create(fip_reservation_values)
@to_dict
def fip_reservation_get(fip_reservation_id):
"""Return specific floating IP reservation."""
return IMPL.fip_reservation_get(fip_reservation_id)
def fip_reservation_update(fip_reservation_id, fip_reservation_values):
"""Update floating IP reservation."""
return IMPL.fip_reservation_update(fip_reservation_id,
fip_reservation_values)
def fip_reservation_destroy(fip_reservation_id):
"""Delete specific floating ip reservation."""
return IMPL.fip_reservation_destroy(fip_reservation_id)
# Required FloatingIP
def required_fip_create(required_fip_values):
"""Create a required FIP address from the values."""
return IMPL.required_fip_create(required_fip_values)
@to_dict
def required_fip_get(required_fip_id):
"""Return specific required FIP."""
return IMPL.required_fip_get(required_fip_id)
def required_fip_update(required_fip_id, required_fip_values):
"""Update required FIP."""
return IMPL.required_fip_update(required_fip_id,
required_fip_values)
def required_fip_destroy(required_fip_id):
"""Delete specific required FIP."""
return IMPL.required_fip_destroy(required_fip_id)
# FloatingIP Allocation
def fip_allocation_create(allocation_values):
"""Create a floating ip allocation from the values."""
return IMPL.fip_allocation_create(allocation_values)
@to_dict
def fip_allocation_get_all_by_values(**kwargs):
"""Returns all entries filtered by col=value."""
return IMPL.fip_allocation_get_all_by_values(**kwargs)
def fip_allocation_destroy(allocation_id):
"""Delete specific floating ip allocation."""
IMPL.fip_allocation_destroy(allocation_id)
def fip_allocation_update(allocation_id, allocation_values):
"""Update floating ip allocation."""
IMPL.fip_allocation_update(allocation_id, allocation_values)
# Floating ip # Floating ip
def floatingip_create(values): def floatingip_create(values):
@ -437,6 +508,12 @@ def floatingip_list():
return IMPL.floatingip_list() return IMPL.floatingip_list()
@to_dict
def reservable_fip_get_all_by_queries(queries):
"""Returns reservable fips filtered by an array of queries."""
return IMPL.reservable_fip_get_all_by_queries(queries)
def floatingip_destroy(floatingip_id): def floatingip_destroy(floatingip_id):
"""Delete specific floating ip.""" """Delete specific floating ip."""
IMPL.floatingip_destroy(floatingip_id) IMPL.floatingip_destroy(floatingip_id)

View File

@ -830,6 +830,172 @@ def host_extra_capability_get_all_per_name(host_id, capability_name):
return query.filter_by(capability_name=capability_name).all() return query.filter_by(capability_name=capability_name).all()
# FloatingIP reservation
def fip_reservation_create(fip_reservation_values):
values = fip_reservation_values.copy()
fip_reservation = models.FloatingIPReservation()
fip_reservation.update(values)
session = get_session()
with session.begin():
try:
fip_reservation.save(session=session)
except common_db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise db_exc.BlazarDBDuplicateEntry(
model=fip_reservation.__class__.__name__, columns=e.columns)
return fip_reservation_get(fip_reservation.id)
def _fip_reservation_get(session, fip_reservation_id):
query = model_query(models.FloatingIPReservation, session)
return query.filter_by(id=fip_reservation_id).first()
def fip_reservation_get(fip_reservation_id):
return _fip_reservation_get(get_session(), fip_reservation_id)
def fip_reservation_update(fip_reservation_id, fip_reservation_values):
session = get_session()
with session.begin():
fip_reservation = _fip_reservation_get(session, fip_reservation_id)
fip_reservation.update(fip_reservation_values)
fip_reservation.save(session=session)
return fip_reservation_get(fip_reservation_id)
def fip_reservation_destroy(fip_reservation_id):
session = get_session()
with session.begin():
fip_reservation = _fip_reservation_get(session, fip_reservation_id)
if not fip_reservation:
# raise not found error
raise db_exc.BlazarDBNotFound(
id=fip_reservation_id, model='FloatingIPReservation')
session.delete(fip_reservation)
# Required FIP
def required_fip_create(required_fip_values):
values = required_fip_values.copy()
required_fip = models.RequiredFloatingIP()
required_fip.update(values)
session = get_session()
with session.begin():
try:
required_fip.save(session=session)
except common_db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise db_exc.BlazarDBDuplicateEntry(
model=required_fip.__class__.__name__, columns=e.columns)
return required_fip_get(required_fip.id)
def _required_fip_get(session, required_fip_id):
query = model_query(models.RequiredFloatingIP, session)
return query.filter_by(id=required_fip_id).first()
def required_fip_get(required_fip_id):
return _required_fip_get(get_session(), required_fip_id)
def required_fip_update(required_fip_id, required_fip_values):
session = get_session()
with session.begin():
required_fip = _required_fip_get(session, required_fip_id)
required_fip.update(required_fip_values)
required_fip.save(session=session)
return required_fip_get(required_fip_id)
def required_fip_destroy(required_fip_id):
session = get_session()
with session.begin():
required_fip = _required_fip_get(session, required_fip_id)
if not required_fip:
# raise not found error
raise db_exc.BlazarDBNotFound(
id=required_fip_id, model='RequiredFloatingIP')
session.delete(required_fip)
# FloatingIP Allocation
def _fip_allocation_get(session, fip_allocation_id):
query = model_query(models.FloatingIPAllocation, session)
return query.filter_by(id=fip_allocation_id).first()
def fip_allocation_get(fip_allocation_id):
return _fip_allocation_get(get_session(), fip_allocation_id)
def fip_allocation_create(allocation_values):
values = allocation_values.copy()
fip_allocation = models.FloatingIPAllocation()
fip_allocation.update(values)
session = get_session()
with session.begin():
try:
fip_allocation.save(session=session)
except common_db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise db_exc.BlazarDBDuplicateEntry(
model=fip_allocation.__class__.__name__, columns=e.columns)
return fip_allocation_get(fip_allocation.id)
def fip_allocation_get_all_by_values(**kwargs):
"""Returns all entries filtered by col=value."""
allocation_query = model_query(models.FloatingIPAllocation, get_session())
for name, value in kwargs.items():
column = getattr(models.FloatingIPAllocation, name, None)
if column:
allocation_query = allocation_query.filter(column == value)
return allocation_query.all()
def fip_allocation_destroy(allocation_id):
session = get_session()
with session.begin():
fip_allocation = _fip_allocation_get(session, allocation_id)
if not fip_allocation:
# raise not found error
raise db_exc.BlazarDBNotFound(
id=allocation_id, model='FloatingIPAllocation')
session.delete(fip_allocation)
def fip_allocation_update(allocation_id, allocation_values):
session = get_session()
with session.begin():
fip_allocation = _fip_allocation_get(session, allocation_id)
fip_allocation.update(allocation_values)
fip_allocation.save(session=session)
return fip_allocation_get(allocation_id)
# Floating IP # Floating IP
def _floatingip_get(session, floatingip_id): def _floatingip_get(session, floatingip_id):
query = model_query(models.FloatingIP, session) query = model_query(models.FloatingIP, session)
@ -841,6 +1007,69 @@ def _floatingip_get_all(session):
return query return query
def fip_get_all_by_queries(queries):
"""Returns Floating IPs filtered by an array of queries.
:param queries: array of queries "key op value" where op can be
http://docs.sqlalchemy.org/en/rel_0_7/core/expression_api.html
#sqlalchemy.sql.operators.ColumnOperators
"""
fips_query = model_query(models.FloatingIP, get_session())
oper = {
'<': ['lt', lambda a, b: a >= b],
'>': ['gt', lambda a, b: a <= b],
'<=': ['le', lambda a, b: a > b],
'>=': ['ge', lambda a, b: a < b],
'==': ['eq', lambda a, b: a != b],
'!=': ['ne', lambda a, b: a == b],
}
for query in queries:
try:
key, op, value = query.split(' ', 2)
except ValueError:
raise db_exc.BlazarDBInvalidFilter(query_filter=query)
column = getattr(models.FloatingIP, key, None)
if column is not None:
if op == 'in':
filt = column.in_(value.split(','))
else:
if op in oper:
op = oper[op][0]
try:
attr = [e for e in ['%s', '%s_', '__%s__']
if hasattr(column, e % op)][0] % op
except IndexError:
raise db_exc.BlazarDBInvalidFilterOperator(
filter_operator=op)
if value == 'null':
value = None
filt = getattr(column, attr)(value)
fips_query = fips_query.filter(filt)
else:
raise db_exc.BlazarDBInvalidFilter(query_filter=query)
return fips_query.all()
def reservable_fip_get_all_by_queries(queries):
"""Returns reservable fips filtered by an array of queries.
:param queries: array of queries "key op value" where op can be
http://docs.sqlalchemy.org/en/rel_0_7/core/expression_api.html
#sqlalchemy.sql.operators.ColumnOperators
"""
queries.append('reservable == 1')
return fip_get_all_by_queries(queries)
def floatingip_get(floatingip_id): def floatingip_get(floatingip_id):
return _floatingip_get(get_session(), floatingip_id) return _floatingip_get(get_session(), floatingip_id)

View File

@ -61,6 +61,20 @@ def _get_leases_from_host_id(host_id, start_date, end_date):
yield lease yield lease
def _get_leases_from_fip_id(fip_id, start_date, end_date):
session = get_session()
border0 = sa.and_(models.Lease.start_date < start_date,
models.Lease.end_date < start_date)
border1 = sa.and_(models.Lease.start_date > end_date,
models.Lease.end_date > end_date)
query = (session.query(models.Lease).join(models.Reservation)
.join(models.FloatingIPAllocation)
.filter(models.FloatingIPAllocation.floatingip_id == fip_id)
.filter(~sa.or_(border0, border1)))
for lease in query:
yield lease
def get_reservations_by_host_id(host_id, start_date, end_date): def get_reservations_by_host_id(host_id, start_date, end_date):
session = get_session() session = get_session()
border0 = sa.and_(models.Lease.start_date < start_date, border0 = sa.and_(models.Lease.start_date < start_date,
@ -115,12 +129,14 @@ def get_plugin_reservation(resource_type, resource_id):
raise mgr_exceptions.UnsupportedResourceType(resource_type) raise mgr_exceptions.UnsupportedResourceType(resource_type)
def get_free_periods(resource_id, start_date, end_date, duration): def get_free_periods(resource_id, start_date, end_date, duration,
resource_type='host'):
"""Returns a list of free periods.""" """Returns a list of free periods."""
reserved_periods = get_reserved_periods(resource_id, reserved_periods = get_reserved_periods(resource_id,
start_date, start_date,
end_date, end_date,
duration) duration,
resource_type=resource_type)
free_periods = [] free_periods = []
previous = (start_date, start_date) previous = (start_date, start_date)
if len(reserved_periods) >= 1: if len(reserved_periods) >= 1:
@ -137,10 +153,17 @@ def get_free_periods(resource_id, start_date, end_date, duration):
return free_periods return free_periods
def _get_events(host_id, start_date, end_date): def _get_events(resource_id, start_date, end_date, resource_type):
"""Create a list of events.""" """Create a list of events."""
events = {} events = {}
for lease in _get_leases_from_host_id(host_id, start_date, end_date): if resource_type == 'host':
leases = _get_leases_from_host_id(resource_id, start_date, end_date)
elif resource_type == 'floatingip':
leases = _get_leases_from_fip_id(resource_id, start_date, end_date)
else:
mgr_exceptions.UnsupportedResourceType(resource_type)
for lease in leases:
if lease.start_date < start_date: if lease.start_date < start_date:
min_date = start_date min_date = start_date
else: else:
@ -202,26 +225,30 @@ def _merge_periods(reserved_periods, start_date, end_date, duration):
return merged_reserved_periods return merged_reserved_periods
def get_reserved_periods(host_id, start_date, end_date, duration): def get_reserved_periods(resource_id, start_date, end_date, duration,
"""Returns a list of reserved periods for a host. resource_type='host'):
"""Returns a list of reserved periods for a resource.
The get_reserved_periods function returns a list of periods during which The get_reserved_periods function returns a list of periods during which
the host passed as parameter is reserved. The duration parameter allows to the resource passed as parameter is reserved. The duration parameter
choose the minimum length of time for a period to be considered free. allows to choose the minimum length of time for a period to be
considered free.
:param host_id: the host to consider :param resource_id: the resource to consider
:param start_date: start datetime of the entire period to consider :param start_date: start datetime of the entire period to consider
:param end_date: end datetime of the entire period to consider :param end_date: end datetime of the entire period to consider
:param duration: minimum length of time for a period to be considered free :param duration: minimum length of time for a period to be considered free
:returns: the list of reserved periods for the host, expressed as a list of :param resource_type: A type of resource to consider
two-element tuples, where the first element is the start datetime :returns: the list of reserved periods for the resource, expressed as a
of the reserved period and the second is the end datetime list of two-element tuples, where the first element is the start
datetime of the reserved period and the second is
the end datetime
""" """
capacity = 1 # The resource status is binary (free or reserved) capacity = 1 # The resource status is binary (free or reserved)
quantity = 1 # One reservation per host at the same time quantity = 1 # One reservation per host at the same time
if end_date - start_date < duration: if end_date - start_date < duration:
return [(start_date, end_date)] return [(start_date, end_date)]
events = _get_events(host_id, start_date, end_date) events = _get_events(resource_id, start_date, end_date, resource_type)
reserved_periods = _find_reserved_periods(events, quantity, capacity) reserved_periods = _find_reserved_periods(events, quantity, capacity)
return _merge_periods(reserved_periods, start_date, end_date, duration) return _merge_periods(reserved_periods, start_date, end_date, duration)

View File

@ -124,15 +124,18 @@ def get_plugin_reservation(resource_type, resource_id):
return IMPL.get_plugin_reservation(resource_type, resource_id) return IMPL.get_plugin_reservation(resource_type, resource_id)
def get_free_periods(resource_id, start_date, end_date, duration): def get_free_periods(resource_id, start_date, end_date, duration,
resource_type='host'):
"""Returns a list of free periods.""" """Returns a list of free periods."""
return IMPL.get_free_periods(resource_id, start_date, end_date, duration) return IMPL.get_free_periods(resource_id, start_date, end_date, duration,
resource_type=resource_type)
def get_reserved_periods(resource_id, start_date, end_date, duration): def get_reserved_periods(resource_id, start_date, end_date, duration,
resource_type='host'):
"""Returns a list of reserved periods.""" """Returns a list of reserved periods."""
return IMPL.get_reserved_periods(resource_id, start_date, end_date, return IMPL.get_reserved_periods(resource_id, start_date, end_date,
duration) duration, resource_type=resource_type)
def reservation_ratio(resource_id, start_date, end_date): def reservation_ratio(resource_id, start_date, end_date):

View File

@ -202,3 +202,17 @@ class FloatingIPNotFound(exceptions.NotFound):
class CantDeleteFloatingIP(exceptions.BlazarException): class CantDeleteFloatingIP(exceptions.BlazarException):
code = 409 code = 409
msg_fmt = _("Can't delete floating IP %(floatingip)s. %(msg)s") msg_fmt = _("Can't delete floating IP %(floatingip)s. %(msg)s")
class InvalidIPFormat(exceptions.InvalidInput):
msg_fmt = _("IP address %(ip)s is invalid form.")
class TooLongFloatingIPs(exceptions.InvalidInput):
msg_fmt = _("Invalid values for required_floatingips and amount. "
"The amount must be equal to or longer than length of "
"required_floatingips.")
class NotEnoughFloatingIPAvailable(exceptions.InvalidInput):
msg_fmt = _("Not enough floating IPs available")

View File

@ -12,17 +12,25 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import datetime
from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import netutils
from oslo_utils import strutils
from blazar.db import api as db_api from blazar.db import api as db_api
from blazar.db import exceptions as db_ex from blazar.db import exceptions as db_ex
from blazar.db import utils as db_utils
from blazar import exceptions 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.utils.openstack import neutron from blazar.utils.openstack import neutron
from blazar.utils import plugins as plugins_utils
CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -33,8 +41,58 @@ class FloatingIpPlugin(base.BasePlugin):
title = 'Floating IP Plugin' title = 'Floating IP Plugin'
description = 'This plugin creates and assigns floating IPs.' description = 'This plugin creates and assigns floating IPs.'
def check_params(self, values):
if 'network_id' not in values:
raise manager_ex.MissingParameter(param='network_id')
if 'amount' not in values:
raise manager_ex.MissingParameter(param='amount')
if not strutils.is_int_like(values['amount']):
raise manager_ex.MalformedParameter(param='amount')
# required_floatingips param is an optional parameter
fips = values.get('required_floatingips', [])
if not isinstance(fips, list):
manager_ex.MalformedParameter(param='required_floatingips')
for ip in fips:
if not (netutils.is_valid_ipv4(ip) or netutils.is_valid_ipv6(ip)):
raise manager_ex.InvalidIPFormat(ip=ip)
def reserve_resource(self, reservation_id, values): def reserve_resource(self, reservation_id, values):
raise NotImplementedError """Create floating IP reservation."""
self.check_params(values)
required_fips = values.get('required_floatingips', [])
amount = int(values['amount'])
if len(required_fips) > amount:
raise manager_ex.TooLongFloatingIPs()
floatingip_ids = self._matching_fips(values['network_id'],
required_fips,
amount,
values['start_date'],
values['end_date'])
floatingip_rsrv_values = {
'reservation_id': reservation_id,
'network_id': values['network_id'],
'amount': amount
}
fip_reservation = db_api.fip_reservation_create(floatingip_rsrv_values)
for fip_address in required_fips:
fip_address_values = {
'address': fip_address,
'floatingip_reservation_id': fip_reservation['id']
}
db_api.required_fip_create(fip_address_values)
for fip_id in floatingip_ids:
db_api.fip_allocation_create({'floatingip_id': fip_id,
'reservation_id': reservation_id})
return fip_reservation['id']
def on_start(self, resource_id): def on_start(self, resource_id):
raise NotImplementedError raise NotImplementedError
@ -42,6 +100,54 @@ class FloatingIpPlugin(base.BasePlugin):
def on_end(self, resource_id): def on_end(self, resource_id):
raise NotImplementedError raise NotImplementedError
def _matching_fips(self, network_id, fip_addresses, amount,
start_date, end_date):
filter_array = []
start_date_with_margin = start_date - datetime.timedelta(
minutes=CONF.cleaning_time)
end_date_with_margin = end_date + datetime.timedelta(
minutes=CONF.cleaning_time)
fip_query = ["==", "$floating_network_id", network_id]
filter_array = plugins_utils.convert_requirements(fip_query)
fip_ids = []
not_allocated_fip_ids = []
allocated_fip_ids = []
for fip in db_api.reservable_fip_get_all_by_queries(filter_array):
if not db_api.fip_allocation_get_all_by_values(
floatingip_id=fip['id']):
if fip['floating_ip_address'] in fip_addresses:
fip_ids.append(fip['id'])
else:
not_allocated_fip_ids.append(fip['id'])
elif db_utils.get_free_periods(
fip['id'],
start_date_with_margin,
end_date_with_margin,
end_date_with_margin - start_date_with_margin,
resource_type='floatingip'
) == [
(start_date_with_margin, end_date_with_margin),
]:
if fip['floating_ip_address'] in fip_addresses:
fip_ids.append(fip['id'])
else:
allocated_fip_ids.append(fip['id'])
if len(fip_ids) != len(fip_addresses):
raise manager_ex.NotEnoughFloatingIPAvailable()
fip_ids += not_allocated_fip_ids
if len(fip_ids) >= amount:
return fip_ids[:amount]
fip_ids += allocated_fip_ids
if len(fip_ids) >= amount:
return fip_ids[:amount]
raise manager_ex.NotEnoughFloatingIPAvailable()
def validate_floatingip_params(self, values): def validate_floatingip_params(self, values):
marshall_attributes = set(['floating_network_id', marshall_attributes = set(['floating_network_id',
'floating_ip_address']) 'floating_ip_address'])

View File

@ -12,20 +12,36 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import datetime
import mock import mock
from oslo_config import cfg
from blazar.db import api as db_api from blazar.db import api as db_api
from blazar.db import utils as db_utils
from blazar.manager import exceptions as mgr_exceptions from blazar.manager import exceptions as mgr_exceptions
from blazar.plugins import floatingips as plugin
from blazar.plugins.floatingips import floatingip_plugin from blazar.plugins.floatingips import floatingip_plugin
from blazar import tests from blazar import tests
from blazar.utils.openstack import exceptions as opst_exceptions from blazar.utils.openstack import exceptions as opst_exceptions
from blazar.utils.openstack import neutron from blazar.utils.openstack import neutron
CONF = cfg.CONF
class FloatingIpPluginTest(tests.TestCase): class FloatingIpPluginTest(tests.TestCase):
def setUp(self): def setUp(self):
super(FloatingIpPluginTest, self).setUp() super(FloatingIpPluginTest, self).setUp()
self.cfg = cfg
# Make sure we clean up any override which could impact other tests
self.addCleanup(self.cfg.CONF.reset)
self.db_api = db_api
self.db_utils = db_utils
self.fip_pool = self.patch(neutron, 'FloatingIPPool') self.fip_pool = self.patch(neutron, 'FloatingIPPool')
def test_create_floatingip(self): def test_create_floatingip(self):
@ -143,3 +159,265 @@ class FloatingIpPluginTest(tests.TestCase):
self.assertRaises(mgr_exceptions.FloatingIPNotFound, self.assertRaises(mgr_exceptions.FloatingIPNotFound,
fip_plugin.delete_floatingip, fip_plugin.delete_floatingip,
'non-exists-id') 'non-exists-id')
def test_create_reservation_fips_available(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
values = {
'lease_id': u'018c1b43-e69e-4aef-a543-09681539cf4c',
'network_id': u'f548089e-fb3e-4013-a043-c5ed809c7a67',
'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
}
matching_fips = self.patch(fip_plugin, '_matching_fips')
matching_fips.return_value = ['fip1', 'fip2']
fip_reservation_create = self.patch(self.db_api,
'fip_reservation_create')
fip_allocation_create = self.patch(
self.db_api, 'fip_allocation_create')
fip_plugin.reserve_resource(
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
fip_values = {
'reservation_id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
'network_id': u'f548089e-fb3e-4013-a043-c5ed809c7a67',
'amount': 2
}
fip_reservation_create.assert_called_once_with(fip_values)
calls = [
mock.call(
{'floatingip_id': 'fip1',
'reservation_id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
}),
mock.call(
{'floatingip_id': 'fip2',
'reservation_id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
}),
]
fip_allocation_create.assert_has_calls(calls)
def test_create_reservation_fips_with_required(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
values = {
'lease_id': u'018c1b43-e69e-4aef-a543-09681539cf4c',
'network_id': u'f548089e-fb3e-4013-a043-c5ed809c7a67',
'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,
'required_floatingips': ['172.24.4.100']
}
matching_fips = self.patch(fip_plugin, '_matching_fips')
matching_fips.return_value = ['fip1', 'fip2']
fip_reservation_create = self.patch(self.db_api,
'fip_reservation_create')
fip_reservation_create.return_value = {'id': 'fip_resv_id1'}
fip_allocation_create = self.patch(
self.db_api, 'fip_allocation_create')
required_addr_create = self.patch(self.db_api, 'required_fip_create')
fip_plugin.reserve_resource(
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
fip_values = {
'reservation_id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
'network_id': u'f548089e-fb3e-4013-a043-c5ed809c7a67',
'amount': 2
}
fip_reservation_create.assert_called_once_with(fip_values)
required_addr_create.assert_called_once_with(
{
'address': '172.24.4.100',
'floatingip_reservation_id': 'fip_resv_id1'
})
calls = [
mock.call(
{'floatingip_id': 'fip1',
'reservation_id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
}),
mock.call(
{'floatingip_id': 'fip2',
'reservation_id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
}),
]
fip_allocation_create.assert_has_calls(calls)
def test_create_reservation_with_missing_param_network(self):
values = {
'lease_id': u'018c1b43-e69e-4aef-a543-09681539cf4c',
'amount': 2,
'start_date': datetime.datetime(2017, 3, 1, 20, 0),
'end_date': datetime.datetime(2017, 3, 2, 20, 0),
'resource_type': plugin.RESOURCE_TYPE,
}
fip_plugin = floatingip_plugin.FloatingIpPlugin()
self.assertRaises(
mgr_exceptions.MissingParameter,
fip_plugin.reserve_resource,
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
def test_create_reservation_with_invalid_fip(self):
values = {
'lease_id': u'018c1b43-e69e-4aef-a543-09681539cf4c',
'network_id': u'a37a14f3-e3eb-4fe2-9e36-082b67f12ea0',
'amount': 2,
'required_floatingips': ['aaa.aaa.aaa.aaa'],
'start_date': datetime.datetime(2017, 3, 1, 20, 0),
'end_date': datetime.datetime(2017, 3, 2, 20, 0),
'resource_type': plugin.RESOURCE_TYPE,
}
fip_plugin = floatingip_plugin.FloatingIpPlugin()
self.assertRaises(
mgr_exceptions.InvalidIPFormat,
fip_plugin.reserve_resource,
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
def test_create_reservation_required_bigger_than_amount(self):
values = {
'lease_id': u'018c1b43-e69e-4aef-a543-09681539cf4c',
'network_id': u'a37a14f3-e3eb-4fe2-9e36-082b67f12ea0',
'amount': 1,
'required_floatingips': ['172.24.4.100', '172.24.4.101'],
'start_date': datetime.datetime(2017, 3, 1, 20, 0),
'end_date': datetime.datetime(2017, 3, 2, 20, 0),
'resource_type': plugin.RESOURCE_TYPE,
}
fip_plugin = floatingip_plugin.FloatingIpPlugin()
self.assertRaises(
mgr_exceptions.TooLongFloatingIPs,
fip_plugin.reserve_resource,
u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
values)
def test_matching_fips_not_allocated_fips(self):
def fip_allocation_get_all_by_values(**kwargs):
if kwargs['floatingip_id'] == 'fip1':
return [{'id': 'allocation-id1'}]
fip_plugin = floatingip_plugin.FloatingIpPlugin()
fip_get = self.patch(self.db_api, 'reservable_fip_get_all_by_queries')
fip_get.return_value = [
{'id': 'fip1', 'floating_ip_address': '172.24.4.101'},
{'id': 'fip2', 'floating_ip_address': '172.24.4.102'},
{'id': 'fip3', 'floating_ip_address': '172.24.4.103'},
]
fip_get = self.patch(self.db_api, 'fip_allocation_get_all_by_values')
fip_get.side_effect = fip_allocation_get_all_by_values
fip_get = self.patch(self.db_utils, 'get_free_periods')
fip_get.return_value = [
(datetime.datetime(2013, 12, 19, 20, 0),
datetime.datetime(2013, 12, 19, 21, 0)),
]
result = fip_plugin._matching_fips(
'network-id', [], 2,
datetime.datetime(2013, 12, 19, 20, 0),
datetime.datetime(2013, 12, 19, 21, 0))
self.assertEqual(['fip2', 'fip3'], result)
def test_matching_fips_allocated_fips(self):
def fip_allocation_get_all_by_values(**kwargs):
return [{'id': kwargs['floatingip_id']}]
fip_plugin = floatingip_plugin.FloatingIpPlugin()
fip_get = self.patch(self.db_api, 'reservable_fip_get_all_by_queries')
fip_get.return_value = [
{'id': 'fip1', 'floating_ip_address': '172.24.4.101'},
{'id': 'fip2', 'floating_ip_address': '172.24.4.102'},
{'id': 'fip3', 'floating_ip_address': '172.24.4.103'},
]
fip_get = self.patch(self.db_api, 'fip_allocation_get_all_by_values')
fip_get.side_effect = fip_allocation_get_all_by_values
fip_get = self.patch(self.db_utils, 'get_free_periods')
fip_get.return_value = [
(datetime.datetime(2013, 12, 19, 20, 0),
datetime.datetime(2013, 12, 19, 21, 0)),
]
result = fip_plugin._matching_fips(
'network-id', [], 3,
datetime.datetime(2013, 12, 19, 20, 0),
datetime.datetime(2013, 12, 19, 21, 0))
self.assertEqual(['fip1', 'fip2', 'fip3'], result)
def test_matching_fips_allocated_fips_with_required(self):
def fip_allocation_get_all_by_values(**kwargs):
if kwargs['floatingip_id'] == 'fip1':
return [{'id': 'allocation-id1'}]
fip_plugin = floatingip_plugin.FloatingIpPlugin()
fip_get = self.patch(self.db_api, 'reservable_fip_get_all_by_queries')
fip_get.return_value = [
{'id': 'fip1', 'floating_ip_address': '172.24.4.101'},
{'id': 'fip2', 'floating_ip_address': '172.24.4.102'},
{'id': 'fip3', 'floating_ip_address': '172.24.4.103'},
{'id': 'fip4', 'floating_ip_address': '172.24.4.104'},
]
fip_get = self.patch(self.db_api, 'fip_allocation_get_all_by_values')
fip_get.side_effect = fip_allocation_get_all_by_values
fip_get = self.patch(self.db_utils, 'get_free_periods')
fip_get.return_value = [
(datetime.datetime(2013, 12, 19, 20, 0),
datetime.datetime(2013, 12, 19, 21, 0)),
]
result = fip_plugin._matching_fips(
'network-id', ['172.24.4.102'], 4,
datetime.datetime(2013, 12, 19, 20, 0),
datetime.datetime(2013, 12, 19, 21, 0))
# The order must be 1. required fips, 2. non-allocated fips,
# then 3. allocated fips
self.assertEqual(['fip2', 'fip3', 'fip4', 'fip1'], result)
def test_matching_fips_allocated_fips_with_cleaning_time(self):
def fip_allocation_get_all_by_values(**kwargs):
return [{'id': kwargs['floatingip_id']}]
self.cfg.CONF.set_override('cleaning_time', '5')
fip_get = self.patch(
self.db_api,
'reservable_fip_get_all_by_queries')
fip_get.return_value = [
{'id': 'fip1', 'floating_ip_address': '172.24.4.101'},
{'id': 'fip2', 'floating_ip_address': '172.24.4.102'},
{'id': 'fip3', 'floating_ip_address': '172.24.4.103'},
]
fip_get = self.patch(
self.db_api,
'fip_allocation_get_all_by_values')
fip_get.side_effect = fip_allocation_get_all_by_values
fip_get = self.patch(
self.db_utils,
'get_free_periods')
fip_get.return_value = [
(datetime.datetime(2013, 12, 19, 20, 0)
- datetime.timedelta(minutes=5),
datetime.datetime(2013, 12, 19, 21, 0)
+ datetime.timedelta(minutes=5))
]
fip_plugin = floatingip_plugin.FloatingIpPlugin()
result = fip_plugin._matching_fips(
'network-id', [], 3,
datetime.datetime(2013, 12, 19, 20, 0),
datetime.datetime(2013, 12, 19, 21, 0))
self.assertEqual(['fip1', 'fip2', 'fip3'], result)
start_mergin = (datetime.datetime(2013, 12, 19, 20, 0)
- datetime.timedelta(minutes=5))
end_mergin = (datetime.datetime(2013, 12, 19, 21, 0)
+ datetime.timedelta(minutes=5))
calls = [mock.call(fip, start_mergin, end_mergin,
end_mergin - start_mergin,
resource_type='floatingip')
for fip in ['fip1', 'fip2', 'fip3']]
fip_get.assert_has_calls(calls)
def test_matching_fips_not_matching(self):
fip_plugin = floatingip_plugin.FloatingIpPlugin()
fip_get = self.patch(
self.db_api,
'reservable_fip_get_all_by_queries')
fip_get.return_value = []
self.assertRaises(mgr_exceptions.NotEnoughFloatingIPAvailable,
fip_plugin._matching_fips,
'network-id', [], 2,
datetime.datetime(2013, 12, 19, 20, 0),
datetime.datetime(2013, 12, 19, 21, 0))