diff --git a/blazar/db/sqlalchemy/utils.py b/blazar/db/sqlalchemy/utils.py index 685bae64..bb65b147 100644 --- a/blazar/db/sqlalchemy/utils.py +++ b/blazar/db/sqlalchemy/utils.py @@ -88,6 +88,18 @@ def get_reservations_by_host_ids(host_ids, start_date, end_date): return query.all() +def get_reservation_allocations_by_host_ids(host_ids, start_date, end_date): + session = get_session() + border0 = models.Lease.end_date < start_date + border1 = models.Lease.start_date > end_date + query = (session.query(models.Reservation, models.ComputeHostAllocation) + .join(models.Lease).join(models.ComputeHostAllocation) + .filter(models.ComputeHostAllocation.compute_host_id + .in_(host_ids)) + .filter(~sa.or_(border0, border1))) + return query.all() + + def get_plugin_reservation(resource_type, resource_id): if resource_type == host_plugin.RESOURCE_TYPE: return api.host_reservation_get(resource_id) diff --git a/blazar/db/utils.py b/blazar/db/utils.py index d0cb46c6..1b69e559 100644 --- a/blazar/db/utils.py +++ b/blazar/db/utils.py @@ -111,6 +111,11 @@ def get_reservations_by_host_ids(host_ids, start_date, end_date): return IMPL.get_reservations_by_host_ids(host_ids, start_date, end_date) +def get_reservation_allocations_by_host_ids(host_ids, start_date, end_date): + return IMPL.get_reservation_allocations_by_host_ids(host_ids, start_date, + end_date) + + def get_plugin_reservation(resource_type, resource_id): return IMPL.get_plugin_reservation(resource_type, resource_id) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 877ec8d9..2193a465 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -499,6 +499,55 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper): # they have to rerun raise manager_ex.CantDeleteHost(host=host_id, msg=str(e)) + def list_allocations(self, query): + hosts_id_list = [h['id'] for h in db_api.host_list()] + + hosts_allocations = self.query_host_allocations(hosts_id_list) + return [{"resource_id": host, "reservations": allocs} + for host, allocs in hosts_allocations.items()] + + def get_allocations(self, host_id, query): + host_allocations = self.query_host_allocations([host_id]) + if host_id not in host_allocations: + raise manager_ex.HostNotFound(host=host_id) + allocs = host_allocations[host_id] + return {"resource_id": host_id, "reservations": allocs} + + def query_host_allocations(self, hosts): + """Return dict of host and its allocations. + + The list element forms + { + 'host-id': [ + { + 'lease_id': lease_id, + 'id': reservation_id + }, + ] + }. + """ + start = datetime.datetime.utcnow() + end = datetime.date.max + + # To reduce overhead, this method only executes one query + # to get the allocation information + allocs = db_utils.get_reservation_allocations_by_host_ids( + hosts, start, end) + + hosts_allocs = {} + for reservation, alloc in allocs: + if alloc['compute_host_id'] in hosts_allocs: + hosts_allocs[alloc['compute_host_id']].append({ + 'lease_id': reservation['lease_id'], + 'id': reservation['id'] + }) + else: + hosts_allocs[alloc['compute_host_id']] = [{ + 'lease_id': reservation['lease_id'], + 'id': reservation['id'] + }] + return hosts_allocs + def _matching_hosts(self, hypervisor_properties, resource_properties, count_range, start_date, end_date): """Return the matching hosts (preferably not allocated) diff --git a/blazar/tests/db/sqlalchemy/test_utils.py b/blazar/tests/db/sqlalchemy/test_utils.py index 353b58b3..9bee57ba 100644 --- a/blazar/tests/db/sqlalchemy/test_utils.py +++ b/blazar/tests/db/sqlalchemy/test_utils.py @@ -407,6 +407,36 @@ class SQLAlchemyDBUtilsTestCase(tests.DBTestCase): self.check_reservation([], ['r4'], '2030-01-01 07:00', '2030-01-01 15:00') + def test_get_reservation_allocations_by_host_ids(self): + def create_allocation_tuple(lease_id): + reservation = db_api.reservation_get_all_by_lease_id(lease_id)[0] + allocation = db_api.host_allocation_get_all_by_values( + reservation_id=reservation['id'])[0] + return (reservation['id'], allocation['id']) + + self._setup_leases() + + # query all allocations of lease1, lease2 and lease3 + expected = [ + create_allocation_tuple('lease1'), + create_allocation_tuple('lease2'), + create_allocation_tuple('lease3'), + ] + ret = db_utils.get_reservation_allocations_by_host_ids( + ['r1', 'r2'], '2030-01-01 08:00', '2030-01-01 15:00') + + self.assertListEqual(expected, [(r['id'], a['id']) for r, a in ret]) + + # query allocations of lease2 and lease3 + expected = [ + create_allocation_tuple('lease2'), + create_allocation_tuple('lease3'), + ] + ret = db_utils.get_reservation_allocations_by_host_ids( + ['r1', 'r2'], '2030-01-01 11:30', '2030-01-01 15:00') + + self.assertListEqual(expected, [(r['id'], a['id']) for r, a in ret]) + def test_get_plugin_reservation_with_host(self): patch_host_reservation_get = self.patch(db_api, 'host_reservation_get') patch_host_reservation_get.return_value = { diff --git a/blazar/tests/plugins/oshosts/test_physical_host_plugin.py b/blazar/tests/plugins/oshosts/test_physical_host_plugin.py index ba59a117..13a41e8e 100644 --- a/blazar/tests/plugins/oshosts/test_physical_host_plugin.py +++ b/blazar/tests/plugins/oshosts/test_physical_host_plugin.py @@ -429,6 +429,98 @@ class PhysicalHostPluginTestCase(tests.TestCase): self.fake_phys_plugin.delete_computehost, self.fake_host_id) + def test_list_allocations(self): + def reservation_allocation_tuple(r_id, l_id, h_id): + return ({'id': r_id, 'lease_id': l_id}, {'compute_host_id': h_id}) + + self.db_get_reserv_allocs = self.patch( + self.db_utils, 'get_reservation_allocations_by_host_ids') + + # Expecting a list of (Reservation, Allocation) + self.db_get_reserv_allocs.return_value = [ + reservation_allocation_tuple('reservation-1', 'lease-1', 'host-1'), + reservation_allocation_tuple('reservation-1', 'lease-1', 'host-2'), + reservation_allocation_tuple('reservation-2', 'lease-1', 'host-2'), + reservation_allocation_tuple('reservation-2', 'lease-1', 'host-3'), + reservation_allocation_tuple('reservation-3', 'lease-2', 'host-1'), + ] + + expected = [ + { + 'resource_id': 'host-1', + 'reservations': [ + {'id': 'reservation-1', 'lease_id': 'lease-1'}, + {'id': 'reservation-3', 'lease_id': 'lease-2'}, + ] + }, + { + 'resource_id': 'host-2', + 'reservations': [ + {'id': 'reservation-1', 'lease_id': 'lease-1'}, + {'id': 'reservation-2', 'lease_id': 'lease-1'}, + ] + }, + { + 'resource_id': 'host-3', + 'reservations': [ + {'id': 'reservation-2', 'lease_id': 'lease-1'}, + ] + } + ] + ret = self.fake_phys_plugin.list_allocations({}) + + # Sort returned value to use assertListEqual + for r in ret: + r['reservations'].sort(key=lambda x: x['id']) + ret.sort(key=lambda x: x['resource_id']) + + self.assertListEqual(expected, ret) + + def test_get_allocations(self): + def reservation_allocation_tuple(r_id, l_id, h_id): + return ({'id': r_id, 'lease_id': l_id}, {'compute_host_id': h_id}) + + self.db_get_reserv_allocs = self.patch( + self.db_utils, 'get_reservation_allocations_by_host_ids') + + # Expecting a list of (Reservation, Allocation) + self.db_get_reserv_allocs.return_value = [ + reservation_allocation_tuple('reservation-1', 'lease-1', 'host-1'), + reservation_allocation_tuple('reservation-1', 'lease-1', 'host-2'), + reservation_allocation_tuple('reservation-2', 'lease-1', 'host-2'), + reservation_allocation_tuple('reservation-2', 'lease-1', 'host-3'), + reservation_allocation_tuple('reservation-3', 'lease-2', 'host-1'), + ] + + expected = { + 'resource_id': 'host-1', + 'reservations': [ + {'id': 'reservation-1', 'lease_id': 'lease-1'}, + {'id': 'reservation-3', 'lease_id': 'lease-2'}, + ] + } + ret = self.fake_phys_plugin.get_allocations('host-1', {}) + self.assertDictEqual(expected, ret) + + def test_get_allocations_with_invalid_host(self): + def reservation_allocation_tuple(r_id, l_id, h_id): + return ({'id': r_id, 'lease_id': l_id}, {'compute_host_id': h_id}) + + self.db_get_reserv_allocs = self.patch( + self.db_utils, 'get_reservation_allocations_by_host_ids') + + # Expecting a list of (Reservation, Allocation) + self.db_get_reserv_allocs.return_value = [ + reservation_allocation_tuple('reservation-1', 'lease-1', 'host-1'), + reservation_allocation_tuple('reservation-1', 'lease-1', 'host-2'), + reservation_allocation_tuple('reservation-2', 'lease-1', 'host-2'), + reservation_allocation_tuple('reservation-2', 'lease-1', 'host-3'), + reservation_allocation_tuple('reservation-3', 'lease-2', 'host-1'), + ] + self.assertRaises(manager_exceptions.HostNotFound, + self.fake_phys_plugin.get_allocations, + 'no-reserved-host', {}) + def test_create_reservation_no_hosts_available(self): now = datetime.datetime.utcnow() values = {