Physical host reservation (DB related things)

Implements bp:host-manager

Co-Authored-By: Swann Croiset <swann.croiset@bull.net>

Change-Id: Icbaa44412e041fb689d9a8bc3afdabfece96e7d4
This commit is contained in:
François Rossigneux 2014-01-02 19:00:25 +01:00 committed by Swann Croiset
parent df1b79d636
commit ce5fa9ba23
8 changed files with 956 additions and 37 deletions

View File

@ -266,6 +266,32 @@ def host_reservation_update(host_reservation_id,
host_reservation_values)
#Allocation
def host_allocation_create(allocation_values):
"""Create an allocation from the values."""
return IMPL.host_allocation_create(allocation_values)
@to_dict
def host_allocation_get_all_by_values(**kwargs):
"""Returns all entries filtered by col=value."""
return IMPL.host_allocation_get_all_by_values(**kwargs)
# TODO(frossigneux) get methods
def host_allocation_destroy(allocation_id):
"""Delete specific allocation."""
IMPL.host_allocation_destroy(allocation_id)
def host_allocation_update(allocation_id, allocation_values):
"""Update allocation."""
IMPL.host_allocation_update(allocation_id, allocation_values)
# Compute Hosts
def host_create(values):
@ -339,4 +365,12 @@ def host_extra_capability_update(host_extra_capability_id, values):
def host_extra_capability_get_all_per_name(host_id,
extra_capability_name):
return IMPL.host_extra_capability_get_all_per_name(host_id,
extra_capability_name)
# Host matching
def host_get_all_by_queries_including_extracapabilities(queries):
"""Returns hosts filtered by an array of queries."""
return IMPL.host_get_all_by_queries_including_extracapabilities(queries)

View File

@ -148,7 +148,6 @@ def reservation_get_all():
def reservation_get_all_by_lease_id(lease_id):
reservations = model_query(models.Reservation, get_session()).\
filter_by(lease_id=lease_id)
return reservations.all()
@ -436,6 +435,73 @@ def host_reservation_destroy(host_reservation_id):
session.delete(host_reservation)
#ComputeHostAllocation
def _host_allocation_get(session, host_allocation_id):
query = model_query(models.ComputeHostAllocation, session)
return query.filter_by(id=host_allocation_id).first()
def host_allocation_get(host_allocation_id):
return _host_allocation_get(get_session(),
host_allocation_id)
def host_allocation_get_all():
query = model_query(models.ComputeHostAllocation, get_session())
return query.all()
def host_allocation_get_all_by_values(**kwargs):
"""Returns all entries filtered by col=value."""
allocation_query = model_query(models.ComputeHostAllocation, get_session())
for name, value in kwargs.items():
column = getattr(models.ComputeHostAllocation, name, None)
if column:
allocation_query = allocation_query.filter(column == value)
return allocation_query.all()
def host_allocation_create(values):
values = values.copy()
host_allocation = models.ComputeHostAllocation()
host_allocation.update(values)
session = get_session()
with session.begin():
try:
host_allocation.save(session=session)
except db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise RuntimeError("DBDuplicateEntry: %s" % e.columns)
return host_allocation_get(host_allocation.id)
def host_allocation_update(host_allocation_id, values):
session = get_session()
with session.begin():
host_allocation = _host_allocation_get(session,
host_allocation_id)
host_allocation.update(values)
host_allocation.save(session=session)
return host_allocation_get(host_allocation_id)
def host_allocation_destroy(host_allocation_id):
session = get_session()
with session.begin():
host_allocation = _host_allocation_get(session,
host_allocation_id)
if not host_allocation:
# raise not found error
raise RuntimeError("Host Allocation not found!")
session.delete(host_allocation)
#ComputeHost
def _host_get(session, host_id):
query = model_query(models.ComputeHost, session)
@ -473,36 +539,64 @@ def host_get_all_by_queries(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
"""
hosts_query = model_query(models.ComputeHost, get_session())
#key_not_found = []
oper = dict({'<': 'lt', '>': 'gt', '<=': 'le', '>=': 'ge', '==': 'eq',
'!=': 'ne'})
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],
}
hosts = []
for query in queries:
try:
key, op, value = query.split(' ', 3)
except ValueError:
raise RuntimeError('Invalid filter: %s' % query)
column = getattr(models.ComputeHost, key, None)
if not column:
raise RuntimeError('Invalid filter column: %s' % key)
if op == 'in':
filt = column.in_(value.split(','))
else:
if op in oper:
op = oper[op]
try:
attr = filter(lambda e: hasattr(column, e % op),
['%s', '%s_', '__%s__'])[0] % op
except IndexError:
raise RuntimeError('Invalid filter operator: %s' % op)
if value == 'null':
value = None
filt = getattr(column, attr)(value)
hosts_query = hosts_query.filter(filt)
return hosts_query.all()
column = getattr(models.ComputeHost, key, None)
if column:
if op == 'in':
filt = column.in_(value.split(','))
else:
if op in oper:
op = oper[op][0]
try:
attr = filter(lambda e: hasattr(column, e % op),
['%s', '%s_', '__%s__'])[0] % op
except IndexError:
raise RuntimeError('Invalid filter operator: %s' % op)
if value == 'null':
value = None
filt = getattr(column, attr)(value)
hosts_query = hosts_query.filter(filt)
else:
#looking for extra capabilities matches
extra_filter = model_query(
models.ComputeHostExtraCapability, get_session()
).filter(models.ComputeHostExtraCapability.capability_name == key
).all()
if not extra_filter:
raise RuntimeError(
'No Host with ExtraCapability "%s" found' % key)
for host in extra_filter:
if op in oper and oper[op][1](host.capability_value, value):
hosts.append(host.computehost_id)
elif op not in oper:
msg = 'Operator %s for extra capabilities not implemented'
raise NotImplementedError(msg % op)
return hosts_query.filter(~models.ComputeHost.id.in_(hosts)).all()
def host_create(values):

View File

@ -84,6 +84,11 @@ class Reservation(mb.ClimateBase):
cascade="all,delete",
backref='reservation',
lazy='joined')
computehost_allocations = relationship('ComputeHostAllocation',
uselist=False,
cascade="all,delete",
backref='reservation',
lazy='joined')
def to_dict(self):
return super(Reservation, self).to_dict()
@ -122,6 +127,22 @@ class ComputeHostReservation(mb.ClimateBase):
return super(ComputeHostReservation, self).to_dict()
class ComputeHostAllocation(mb.ClimateBase):
"""Mapping between ComputeHost, ComputeHostReservation and Reservation.
"""
__tablename__ = 'computehost_allocations'
id = _id_column()
compute_host_id = sa.Column(sa.String(36),
sa.ForeignKey('computehosts.id'))
reservation_id = sa.Column(sa.String(36),
sa.ForeignKey('reservations.id'))
def to_dict(self):
return super(ComputeHostAllocation, self).to_dict()
class ComputeHost(mb.ClimateBase):
"""Specifies resources asked by reservation from
Compute Host Reservation API.

View File

@ -0,0 +1,223 @@
# -*- coding: utf-8 -*-
#
# Author: François Rossigneux <francois.rossigneux@inria.fr>
#
# 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.
import datetime
import sys
import sqlalchemy as sa
from climate.db.sqlalchemy import models
from climate.openstack.common.db.sqlalchemy import session as db_session
get_session = db_session.get_session
def get_backend():
"""The backend is this module itself."""
return sys.modules[__name__]
def _get_leases_from_resource_id(resource_id, start_date, end_date):
session = get_session()
for lease in session.query(models.Lease).\
join(models.Reservation,
models.Lease.id == models.Reservation.lease_id).\
filter(models.Reservation.resource_id == resource_id).\
filter(~sa.or_(sa.and_(models.Lease.start_date < start_date,
models.Lease.end_date < start_date),
sa.and_(models.Lease.start_date > end_date,
models.Lease.end_date > end_date))):
yield lease
def _get_leases_from_host_id(host_id, start_date, end_date):
session = get_session()
for lease in session.query(models.Lease).\
join(models.Reservation,
models.Lease.id == models.Reservation.lease_id).\
join(models.ComputeHostAllocation,
models.Reservation.id ==
models.ComputeHostAllocation.reservation_id).\
filter(models.ComputeHostAllocation.compute_host_id == host_id).\
filter(~sa.or_(sa.and_(models.Lease.start_date < start_date,
models.Lease.end_date < start_date),
sa.and_(models.Lease.start_date > end_date,
models.Lease.end_date > end_date))):
yield lease
def get_free_periods(resource_id, start_date, end_date, duration):
"""Returns a list of free periods."""
full_periods = get_full_periods(resource_id,
start_date,
end_date,
duration)
free_periods = []
previous = (start_date, start_date)
if len(full_periods) >= 1:
for period in full_periods:
free_periods.append((previous[1], period[0]))
previous = period
free_periods.append((previous[1], end_date))
if free_periods[0][0] == free_periods[0][1]:
del free_periods[0]
if free_periods[-1][0] == free_periods[-1][1]:
del free_periods[-1]
elif start_date != end_date and start_date + duration <= end_date:
free_periods.append((start_date, end_date))
return free_periods
def _get_events(host_id, start_date, end_date):
"""Create a list of events."""
events = {}
for lease in _get_leases_from_host_id(host_id, start_date, end_date):
if lease.start_date < start_date:
min_date = start_date
else:
min_date = lease.start_date
if lease.end_date > end_date:
max_date = end_date
else:
max_date = lease.end_date
if min_date in events.keys():
events[min_date]['quantity'] += 1
else:
events[min_date] = {'quantity': 1}
if max_date in events.keys():
events[max_date]['quantity'] -= 1
else:
events[max_date] = {'quantity': -1}
return events
def _find_full_periods(events, quantity, capacity):
"""Find the full periods."""
full_periods = []
used = 0
full_start = None
for event_date in sorted(events):
used += events[event_date]['quantity']
if not full_start and used + quantity > capacity:
full_start = event_date
elif full_start and used + quantity <= capacity:
full_periods.append((full_start, event_date))
full_start = None
return full_periods
def _merge_periods(full_periods, start_date, end_date, duration):
"""Merge periods if the interval is too narrow."""
full_start = None
full_end = None
previous = None
merged_full_periods = []
for period in full_periods:
if not full_start:
full_start = period[0]
# Enough time between the two full periods
if previous and period[0] - previous[1] >= duration:
full_end = previous[1]
merged_full_periods.append((full_start, full_end))
full_start = period[0]
full_end = period[1]
previous = period
if previous and end_date - previous[1] < duration:
merged_full_periods.append((full_start, end_date))
elif previous:
merged_full_periods.append((full_start, previous[1]))
if (len(merged_full_periods) >= 1 and
merged_full_periods[0][0] - start_date < duration):
merged_full_periods[0] = (start_date, merged_full_periods[0][1])
return merged_full_periods
def get_full_periods(host_id, start_date, end_date, duration):
"""Returns a list of full periods."""
capacity = 1 # The resource status is binary (empty or full)
quantity = 1 # One reservation per host at the same time
if end_date - start_date < duration:
return [(start_date, end_date)]
events = _get_events(host_id, start_date, end_date)
full_periods = _find_full_periods(events, quantity, capacity)
return _merge_periods(full_periods, start_date, end_date, duration)
def reservation_ratio(host_id, start_date, end_date):
res_time = reservation_time(host_id, start_date, end_date).seconds
return float(res_time) / (end_date - start_date).seconds
def availability_time(host_id, start_date, end_date):
res_time = reservation_time(host_id, start_date, end_date)
return end_date - start_date - res_time
def reservation_time(host_id, start_date, end_date):
res_time = datetime.timedelta(0)
for lease in _get_leases_from_host_id(host_id, start_date, end_date):
res_time += lease.end_date - lease.start_date
if lease.start_date < start_date:
res_time -= start_date - lease.start_date
if lease.end_date > end_date:
res_time -= lease.end_date - end_date
return res_time
def number_of_reservations(host_id, start_date, end_date):
return sum(1 for x in
_get_leases_from_host_id(host_id, start_date, end_date))
def longest_lease(host_id, start_date, end_date):
max_duration = datetime.timedelta(0)
longest_lease = None
session = get_session()
for lease in session.query(models.Lease).\
join(models.Reservation,
models.Lease.id == models.Reservation.lease_id).\
join(models.ComputeHostAllocation,
models.Reservation.id ==
models.ComputeHostAllocation.reservation_id).\
filter(models.ComputeHostAllocation.compute_host_id == host_id).\
filter(models.Lease.start_date >= start_date).\
filter(models.Lease.end_date <= end_date):
duration = lease.end_date - lease.start_date
if max_duration < duration:
max_duration = duration
longest_lease = lease.id
return longest_lease
def shortest_lease(host_id, start_date, end_date):
# TODO(frossigneux) Fix max timedelta
min_duration = datetime.timedelta(365 * 1000)
longest_lease = None
session = get_session()
for lease in session.query(models.Lease).\
join(models.Reservation,
models.Lease.id == models.Reservation.lease_id).\
join(models.ComputeHostAllocation,
models.Reservation.id ==
models.ComputeHostAllocation.reservation_id).\
filter(models.ComputeHostAllocation.compute_host_id == host_id).\
filter(models.Lease.start_date >= start_date).\
filter(models.Lease.end_date <= end_date):
duration = lease.end_date - lease.start_date
if min_duration > duration:
min_duration = duration
longest_lease = lease.id
return longest_lease

139
climate/db/utils.py Normal file
View File

@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
#
# Author: François Rossigneux <francois.rossigneux@inria.fr>
#
# 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.
"""Defines interface for DB access.
Functions in this module are imported into the climate.db namespace. Call these
functions from climate.db namespace, not the climate.db.api namespace.
All functions in this module return objects that implement a dictionary-like
interface.
**Related Flags**
:db_backend: string to lookup in the list of LazyPluggable backends.
`sqlalchemy` is the only supported backend right now.
:sql_connection: string specifying the sqlalchemy connection to use, like:
`sqlite:///var/lib/climate/climate.sqlite`.
"""
from oslo.config import cfg
from climate.openstack.common.db import api as db_api
from climate.openstack.common import log as logging
CONF = cfg.CONF
_BACKEND_MAPPING = {
'sqlalchemy': 'climate.db.sqlalchemy.utils',
}
IMPL = db_api.DBAPI(backend_mapping=_BACKEND_MAPPING)
LOG = logging.getLogger(__name__)
def setup_db():
"""Set up database, create tables, etc.
Return True on success, False otherwise
"""
return IMPL.setup_db()
def drop_db():
"""Drop database.
Return True on success, False otherwise
"""
return IMPL.drop_db()
## Helpers for building constraints / equality checks
def constraint(**conditions):
"""Return a constraint object suitable for use with some updates."""
return IMPL.constraint(**conditions)
def equal_any(*values):
"""Return an equality condition object suitable for use in a constraint.
Equal_any conditions require that a model object's attribute equal any
one of the given values.
"""
return IMPL.equal_any(*values)
def not_equal(*values):
"""Return an inequality condition object suitable for use in a constraint.
Not_equal conditions require that a model object's attribute differs from
all of the given values.
"""
return IMPL.not_equal(*values)
def to_dict(func):
def decorator(*args, **kwargs):
res = func(*args, **kwargs)
if isinstance(res, list):
return [item.to_dict() for item in res]
if res:
return res.to_dict()
else:
return None
return decorator
def get_free_periods(resource_id, start_date, end_date, duration):
"""Returns a list of free periods."""
return IMPL.get_free_periods(resource_id, start_date, end_date, duration)
def get_full_periods(resource_id, start_date, end_date, duration):
"""Returns a list of full periods."""
return IMPL.get_full_periods(resource_id, start_date, end_date, duration)
def reservation_ratio(resource_id, start_date, end_date):
return IMPL.reservation_ratio(resource_id, start_date, end_date)
def availability_time(resource_id, start_date, end_date):
return IMPL.availability_time(resource_id, start_date, end_date)
def reservation_time(resource_id, start_date, end_date):
return IMPL.reservation_time(resource_id, start_date, end_date)
def number_of_reservations(resource_id, start_date, end_date):
return IMPL.number_of_reservations(resource_id, start_date, end_date)
def longest_lease(resource_id, start_date, end_date):
return IMPL.longest_lease(resource_id, start_date, end_date)
def shortest_lease(resource_id, start_date, end_date):
return IMPL.shortest_lease(resource_id, start_date, end_date)

View File

@ -31,15 +31,17 @@ def _get_fake_lease_uuid():
return 'aaaaaaaa-1111-bbbb-2222-cccccccccccc'
def _get_fake_phys_reservation_values(lease_id=_get_fake_lease_uuid()):
def _get_fake_phys_reservation_values(lease_id=_get_fake_lease_uuid(),
resource_id=None):
return {'lease_id': lease_id,
'resource_id': '1234',
'resource_id': '1234' if not resource_id else resource_id,
'resource_type': 'physical:host'}
def _get_fake_virt_reservation_values(lease_id=_get_fake_lease_uuid()):
def _get_fake_virt_reservation_values(lease_id=_get_fake_lease_uuid(),
resource_id=None):
return {'lease_id': lease_id,
'resource_id': '5678',
'resource_id': '5678' if not resource_id else resource_id,
'resource_type': 'virtual:instance'}
@ -50,34 +52,44 @@ def _get_fake_event_values(lease_id=_get_fake_lease_uuid(),
'time': _get_datetime('2030-03-01 00:00')}
def _get_datetime(value):
return datetime.datetime.strptime(value, "%Y-%m-%d %H:%M")
def _get_datetime(value='2030-01-01 00:00'):
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M')
def _get_fake_virt_lease_values(id=_get_fake_lease_uuid(),
name='fake_virt_lease'):
name='fake_virt_lease',
start_date=_get_datetime('2030-01-01 00:00'),
end_date=_get_datetime('2030-01-02 00:00'),
resource_id=None):
return {'id': id,
'name': name,
'user_id': 'fake',
'tenant_id': 'fake',
'start_date': _get_datetime('2030-01-01 00:00'),
'end_date': _get_datetime('2030-01-02 00:00'),
'start_date': start_date,
'end_date': end_date,
'trust': 'trust',
'reservations': [_get_fake_virt_reservation_values(lease_id=id)],
'reservations': [_get_fake_virt_reservation_values(
lease_id=id,
resource_id=resource_id)],
'events': []
}
def _get_fake_phys_lease_values(id=_get_fake_lease_uuid(),
name='fake_phys_lease'):
name='fake_phys_lease',
start_date=_get_datetime('2030-01-01 00:00'),
end_date=_get_datetime('2030-01-02 00:00'),
resource_id=None):
return {'id': id,
'name': name,
'user_id': 'fake',
'tenant_id': 'fake',
'start_date': _get_datetime('2030-01-01 00:00'),
'end_date': _get_datetime('2030-01-02 00:00'),
'start_date': start_date,
'end_date': end_date,
'trust': 'trust',
'reservations': [_get_fake_phys_reservation_values(lease_id=id)],
'reservations': [_get_fake_phys_reservation_values(
lease_id=id,
resource_id=resource_id)],
'events': []
}
@ -94,7 +106,15 @@ def _create_physical_lease(values=_get_fake_phys_lease_values(),
if random is True:
values = _get_fake_phys_lease_values(id=_get_fake_random_uuid(),
name=_get_fake_random_uuid())
return db_api.lease_create(values)
lease = db_api.lease_create(values)
for reservation in db_api.reservation_get_all_by_lease_id(lease['id']):
allocation_values = {
'id': _get_fake_random_uuid(),
'compute_host_id': values['reservations'][0]['resource_id'],
'reservation_id': reservation['id']
}
db_api.host_allocation_create(allocation_values)
return lease
def _get_fake_host_reservation_values(id=_get_fake_random_uuid(),
@ -355,6 +375,24 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
self.assertEqual(1, len(
db_api.host_get_all_by_queries(['cpu_info like %Westmere%'])))
def test_search_for_hosts_by_extra_capability(self):
"""Create one host and test extra capability queries."""
db_api.host_create(_get_fake_host_values(id=1))
db_api.host_extra_capability_create(
_get_fake_host_extra_capabilities(computehost_id=1))
self.assertEqual(1, len(
db_api.host_get_all_by_queries(['vgpu == 2'])))
self.assertEqual(0, len(
db_api.host_get_all_by_queries(['vgpu != 2'])))
self.assertEqual(1, len(
db_api.host_get_all_by_queries(['cpu_info like %Westmere%',
'vgpu == 2'])))
self.assertEqual(0, len(
db_api.host_get_all_by_queries(['cpu_info like %wrongcpu%',
'vgpu == 2'])))
self.assertRaises(RuntimeError,
db_api.host_get_all_by_queries, ['apples < 2048'])
def test_search_for_hosts_by_composed_queries(self):
"""Create one host and test composed queries."""

View File

@ -0,0 +1,348 @@
# -*- coding: utf-8 -*-
#
# Author: François Rossigneux <francois.rossigneux@inria.fr>
#
# 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.
import datetime
from climate.db.sqlalchemy import api as db_api
from climate.db.sqlalchemy import utils as db_utils
from climate.openstack.common import context
from climate.openstack.common import uuidutils
from climate import tests
def _get_fake_random_uuid():
return unicode(uuidutils.generate_uuid())
def _get_fake_lease_uuid():
"""Returns a fake uuid."""
return 'aaaaaaaa-1111-bbbb-2222-cccccccccccc'
def _get_fake_phys_reservation_values(lease_id=_get_fake_lease_uuid(),
resource_id=None):
return {'lease_id': lease_id,
'resource_id': '1234' if not resource_id else resource_id,
'resource_type': 'physical:host'}
def _get_datetime(value='2030-01-01 00:00'):
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M')
def _get_fake_phys_lease_values(id=_get_fake_lease_uuid(),
name='fake_phys_lease',
start_date=_get_datetime('2030-01-01 00:00'),
end_date=_get_datetime('2030-01-02 00:00'),
resource_id=None):
return {'id': id,
'name': name,
'start_date': start_date,
'end_date': end_date,
'trust': 'trust',
'reservations': [_get_fake_phys_reservation_values(
lease_id=id,
resource_id=resource_id)],
'events': []
}
def _create_physical_lease(values=_get_fake_phys_lease_values(),
random=False):
"""Creating fake lease having a single physical resource."""
if random is True:
values = _get_fake_phys_lease_values(id=_get_fake_random_uuid(),
name=_get_fake_random_uuid())
lease = db_api.lease_create(values)
for reservation in db_api.reservation_get_all_by_lease_id(lease['id']):
allocation_values = {
'id': _get_fake_random_uuid(),
'compute_host_id': values['reservations'][0]['resource_id'],
'reservation_id': reservation['id']
}
db_api.host_allocation_create(allocation_values)
return lease
def _get_fake_phys_lease_values(id=_get_fake_lease_uuid(),
name='fake_phys_lease',
start_date=_get_datetime('2030-01-01 00:00'),
end_date=_get_datetime('2030-01-02 00:00'),
resource_id=None):
return {'id': id,
'name': name,
'start_date': start_date,
'end_date': end_date,
'trust': 'trust',
'reservations': [_get_fake_phys_reservation_values(
lease_id=id,
resource_id=resource_id)],
'events': []
}
class SQLAlchemyDBUtilsTestCase(tests.DBTestCase):
"""Test case for SQLAlchemy DB utils."""
def setUp(self):
super(SQLAlchemyDBUtilsTestCase, self).setUp()
self.set_context(context.get_admin_context())
def _setup_leases(self):
"""Setup some leases."""
r1 = _get_fake_phys_lease_values(
id='lease1',
name='fake_phys_lease_r1',
start_date=_get_datetime('2030-01-01 09:00'),
end_date=_get_datetime('2030-01-01 10:30'),
resource_id='r1')
r2 = _get_fake_phys_lease_values(
id='lease2',
name='fake_phys_lease_r2',
start_date=_get_datetime('2030-01-01 11:00'),
end_date=_get_datetime('2030-01-01 12:45'),
resource_id='r2')
r3 = _get_fake_phys_lease_values(
id='lease3',
name='fake_phys_lease_r3',
start_date=_get_datetime('2030-01-01 13:00'),
end_date=_get_datetime('2030-01-01 14:00'),
resource_id='r1')
_create_physical_lease(values=r1)
_create_physical_lease(values=r2)
_create_physical_lease(values=r3)
def test_get_free_periods(self):
"""Find the free periods."""
self._setup_leases()
start_date = datetime.datetime.strptime('2028-01-01 08:00',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2099-01-01 00:00',
'%Y-%m-%d %H:%M')
duration = datetime.timedelta(hours=1)
free_periods = db_utils.get_free_periods('r1',
start_date,
end_date,
duration)
self.assertEqual(len(free_periods), 3)
self.assertEqual(free_periods[0][0].strftime('%Y-%m-%d %H:%M'),
'2028-01-01 08:00')
self.assertEqual(free_periods[0][1].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 09:00')
self.assertEqual(free_periods[1][0].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 10:30')
self.assertEqual(free_periods[1][1].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 13:00')
self.assertEqual(free_periods[2][0].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 14:00')
self.assertEqual(free_periods[2][1].strftime('%Y-%m-%d %H:%M'),
'2099-01-01 00:00')
duration = datetime.timedelta(hours=3)
free_periods = db_utils.get_free_periods('r1',
start_date,
end_date,
duration)
self.assertEqual(len(free_periods), 2)
self.assertEqual(free_periods[0][0].strftime('%Y-%m-%d %H:%M'),
'2028-01-01 08:00')
self.assertEqual(free_periods[0][1].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 09:00')
self.assertEqual(free_periods[1][0].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 14:00')
self.assertEqual(free_periods[1][1].strftime('%Y-%m-%d %H:%M'),
'2099-01-01 00:00')
def test_get_full_periods(self):
"""Find the full periods."""
self._setup_leases()
start_date = datetime.datetime.strptime('2028-01-01 08:00',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2099-01-01 00:00',
'%Y-%m-%d %H:%M')
duration = datetime.timedelta(hours=1)
full_periods = db_utils.get_full_periods('r1',
start_date,
end_date,
duration)
self.assertEqual(len(full_periods), 2)
self.assertEqual(full_periods[0][0].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 09:00')
self.assertEqual(full_periods[0][1].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 10:30')
self.assertEqual(full_periods[1][0].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 13:00')
self.assertEqual(full_periods[1][1].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 14:00')
duration = datetime.timedelta(hours=3)
full_periods = db_utils.get_full_periods('r1',
start_date,
end_date,
duration)
self.assertEqual(len(full_periods), 1)
self.assertEqual(full_periods[0][0].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 09:00')
self.assertEqual(full_periods[0][1].strftime('%Y-%m-%d %H:%M'),
'2030-01-01 14:00')
def test_availability_time(self):
"""Find the total availability time."""
self._setup_leases()
start_date = datetime.datetime.strptime('2030-01-01 09:15',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2030-01-01 10:15',
'%Y-%m-%d %H:%M')
availability_time = db_utils.availability_time('r1',
start_date,
end_date)
self.assertEqual(availability_time.seconds, 0 * 60)
start_date = datetime.datetime.strptime('2030-01-01 09:15',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2030-01-01 13:45',
'%Y-%m-%d %H:%M')
availability_time = db_utils.availability_time('r1',
start_date,
end_date)
self.assertEqual(availability_time.seconds, 150 * 60)
start_date = datetime.datetime.strptime('2030-01-01 08:00',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2030-01-01 15:00',
'%Y-%m-%d %H:%M')
availability_time = db_utils.availability_time('r1',
start_date,
end_date)
self.assertEqual(availability_time.seconds, 270 * 60)
def test_reservation_time(self):
"""Find the total reserved time."""
self._setup_leases()
start_date = datetime.datetime.strptime('2030-01-01 09:15',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2030-01-01 10:15',
'%Y-%m-%d %H:%M')
reservation_time = db_utils.reservation_time('r1',
start_date,
end_date)
self.assertEqual(reservation_time.seconds, 60 * 60)
start_date = datetime.datetime.strptime('2030-01-01 09:15',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2030-01-01 13:45',
'%Y-%m-%d %H:%M')
reservation_time = db_utils.reservation_time('r1',
start_date,
end_date)
self.assertEqual(reservation_time.seconds, 120 * 60)
start_date = datetime.datetime.strptime('2030-01-01 08:00',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2030-01-01 15:00',
'%Y-%m-%d %H:%M')
reservation_time = db_utils.reservation_time('r1',
start_date,
end_date)
self.assertEqual(reservation_time.seconds, 150 * 60)
def test_reservation_ratio(self):
"""Find the reservation ratio."""
self._setup_leases()
start_date = datetime.datetime.strptime('2030-01-01 09:15',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2030-01-01 14:30',
'%Y-%m-%d %H:%M')
reservation_time = db_utils.reservation_time('r1',
start_date,
end_date)
availability_time = db_utils.availability_time('r1',
start_date,
end_date)
reservation_ratio = db_utils.reservation_ratio('r1',
start_date,
end_date)
self.assertEqual(reservation_ratio,
float(reservation_time.seconds) /
(end_date - start_date).seconds)
self.assertEqual(
reservation_ratio,
float((end_date - start_date - availability_time).seconds) /
(end_date - start_date).seconds)
def test_number_of_reservations(self):
"""Find the number of reservations."""
self._setup_leases()
start_date = datetime.datetime.strptime('2030-01-01 09:15',
'%Y-%m-%d %H:%M')
end_date = datetime.datetime.strptime('2030-01-01 14:30',
'%Y-%m-%d %H:%M')
self.assertEqual(
db_utils.number_of_reservations('r1', start_date, end_date),
2)
def test_longest_lease(self):
"""Find the longest lease."""
self._setup_leases()
self.assertEqual(
db_utils.longest_lease(
'r1',
start_date=_get_datetime('2030-01-01 08:00'),
end_date=_get_datetime('2099-01-01 10:00')),
'lease1')
self.assertEqual(
db_utils.longest_lease(
'r1',
start_date=_get_datetime('2030-01-01 10:00'),
end_date=_get_datetime('2099-01-01 00:00')),
'lease3')
self.assertEqual(
db_utils.longest_lease(
'r1',
start_date=_get_datetime('2030-01-01 08:00'),
end_date=_get_datetime('2030-01-01 11:00')),
'lease1')
self.assertIsNone(
db_utils.longest_lease(
'r1',
start_date=_get_datetime('2030-01-01 10:15'),
end_date=_get_datetime('2030-01-01 13:00')),
'lease1')
def test_shortest_lease(self):
"""Find the shortest lease."""
self._setup_leases()
self.assertEqual(
db_utils.shortest_lease(
'r1',
start_date=_get_datetime('2030-01-01 08:00'),
end_date=_get_datetime('2099-01-01 10:00')),
'lease3')
self.assertEqual(
db_utils.shortest_lease(
'r1',
start_date=_get_datetime('2030-01-01 10:00'),
end_date=_get_datetime('2099-01-01 00:00')),
'lease3')
self.assertEqual(
db_utils.shortest_lease(
'r1',
start_date=_get_datetime('2030-01-01 08:00'),
end_date=_get_datetime('2030-01-01 11:00')),
'lease1')
self.assertIsNone(
db_utils.shortest_lease(
'r1',
start_date=_get_datetime('2030-01-01 10:15'),
end_date=_get_datetime('2030-01-01 13:00')),
'lease1')
# TODO(frossigneux) longest_availability
# TODO(frossigneux) shortest_availability

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
#
# Author: François Rossigneux <francois.rossigneux@inria.fr>
#
# 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 climate import tests
class DBUtilsTestCase(tests.TestCase):
"""Test case for DB Utils."""
pass