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:
parent
df1b79d636
commit
ce5fa9ba23
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
223
climate/db/sqlalchemy/utils.py
Normal file
223
climate/db/sqlalchemy/utils.py
Normal 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
139
climate/db/utils.py
Normal 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)
|
@ -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."""
|
||||
|
||||
|
348
climate/tests/db/sqlalchemy/test_utils.py
Normal file
348
climate/tests/db/sqlalchemy/test_utils.py
Normal 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
|
22
climate/tests/db/test_utils.py
Normal file
22
climate/tests/db/test_utils.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user