diff --git a/blazar/api/v1/service.py b/blazar/api/v1/service.py index 48fbd5f3..5d262fdc 100644 --- a/blazar/api/v1/service.py +++ b/blazar/api/v1/service.py @@ -16,7 +16,6 @@ from oslo_log import log as logging from blazar import context -from blazar import exceptions from blazar.manager import rpcapi as manager_rpcapi from blazar import policy from blazar.utils import trusts @@ -67,28 +66,13 @@ class API(object): @policy.authorize('leases', 'update') def update_lease(self, lease_id, data): - """Update lease. Only name changing and prolonging may be proceeded. + """Update lease. :param lease_id: ID of the lease in Blazar DB. :type lease_id: str :param data: New lease characteristics. :type data: dict """ - new_name = data.pop('name', None) - end_date = data.pop('end_date', None) - start_date = data.pop('start_date', None) - - if data: - raise exceptions.BlazarException('Only name changing and ' - 'dates changing may be ' - 'proceeded.') - data = {} - if new_name: - data['name'] = new_name - if end_date: - data['end_date'] = end_date - if start_date: - data['start_date'] = start_date return self.manager_rpcapi.update_lease(lease_id, data) @policy.authorize('leases', 'delete') diff --git a/blazar/api/v1/v1_0.py b/blazar/api/v1/v1_0.py index 7ac7a44c..b42aed66 100644 --- a/blazar/api/v1/v1_0.py +++ b/blazar/api/v1/v1_0.py @@ -50,7 +50,7 @@ def leases_get(lease_id): @rest.put('/leases/') @validation.check_exists(_api.get_lease, lease_id='lease_id') def leases_update(lease_id, data): - """Update lease. Only name changing and prolonging may be proceeded.""" + """Update lease.""" return api_utils.render(lease=_api.update_lease(lease_id, data)) diff --git a/blazar/manager/exceptions.py b/blazar/manager/exceptions.py index 58114e57..97ec0a5e 100644 --- a/blazar/manager/exceptions.py +++ b/blazar/manager/exceptions.py @@ -165,3 +165,8 @@ class InvalidRange(exceptions.BlazarException): code = 400 msg_fmt = _('Invalid values for min/max of hosts. ' 'Max must be equal to or larger than min.') + + +class CantUpdateParameter(exceptions.BlazarException): + code = 409 + msg_fmt = _("%(param)s cannot be updated") diff --git a/blazar/manager/service.py b/blazar/manager/service.py index 4d560baf..b393ba9f 100644 --- a/blazar/manager/service.py +++ b/blazar/manager/service.py @@ -353,14 +353,26 @@ class ManagerService(service_utils.RPCServer): raise e # TODO(frossigneux) rollback if an exception is raised + reservations = values.get('reservations', []) for reservation in ( db_api.reservation_get_all_by_lease_id(lease_id)): - reservation['start_date'] = values['start_date'] - reservation['end_date'] = values['end_date'] - resource_type = reservation['resource_type'] + v = {} + v['start_date'] = values['start_date'] + v['end_date'] = values['end_date'] + try: + v.update(filter(lambda x: x['id'] == reservation['id'], + reservations).pop()) + except KeyError: + raise exceptions.MissingParameter(param='reservation ID') + except IndexError: + pass + resource_type = v.get('resource_type', + reservation['resource_type']) + if resource_type != reservation['resource_type']: + raise exceptions.CantUpdateParameter( + param='resource_type') self.plugins[resource_type].update_reservation( - reservation['id'], - reservation) + reservation['id'], v) event = db_api.event_get_first_sorted_by_filters( 'lease_id', @@ -392,6 +404,10 @@ class ManagerService(service_utils.RPCServer): self._update_before_end_event(lease, values, notifications, before_end_date) + try: + del values['reservations'] + except KeyError: + pass db_api.lease_update(lease_id, values) lease = db_api.lease_get(lease_id) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 3fb410ac..2e301a18 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -110,63 +110,40 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper): """Update reservation.""" reservation = db_api.reservation_get(reservation_id) lease = db_api.lease_get(reservation['lease_id']) - if (values['start_date'] < lease['start_date'] or - values['end_date'] > lease['end_date']): - allocations = [] - hosts_in_pool = [] - for allocation in db_api.host_allocation_get_all_by_values( - reservation_id=reservation_id): - full_periods = db_utils.get_full_periods( - allocation['compute_host_id'], - values['start_date'], - values['end_date'], - datetime.timedelta(seconds=1)) - if lease['start_date'] < values['start_date']: - max_start = values['start_date'] - else: - max_start = lease['start_date'] - if lease['end_date'] < values['end_date']: - min_end = lease['end_date'] - else: - min_end = values['end_date'] - if not (len(full_periods) == 0 or - (len(full_periods) == 1 and - full_periods[0][0] == max_start and - full_periods[0][1] == min_end)): - allocations.append(allocation) - if allocations: - if reservation['status'] == 'active': - raise manager_ex.NotEnoughHostsAvailable() - host_reservation = db_api.host_reservation_get( - reservation['resource_id']) - pool = nova.ReservationPool() - hosts_in_pool.extend(pool.get_computehosts( - host_reservation['aggregate_id'])) - host_ids = self._matching_hosts( - host_reservation['hypervisor_properties'], - host_reservation['resource_properties'], - str(len(allocations)) + '-' + str(len(allocations)), - values['start_date'], - values['end_date']) - if not host_ids: - raise manager_ex.NotEnoughHostsAvailable() - if hosts_in_pool: - old_hosts = [db_api.host_get(allocation['compute_host_id']) - for allocation in allocations] - old_hostnames = [old_host['service_name'] - for old_host in old_hosts] - pool.remove_computehost(host_reservation['aggregate_id'], - old_hostnames) - for allocation in allocations: - db_api.host_allocation_destroy(allocation['id']) - for host_id in host_ids: - db_api.host_allocation_create( - {'compute_host_id': host_id, - 'reservation_id': reservation_id}) - if hosts_in_pool: - host = db_api.host_get(host_id) - pool.add_computehost(host_reservation['aggregate_id'], - host['service_name']) + + if (not filter(lambda x: x in ['min', 'max', 'hypervisor_properties', + 'resource_properties'], values.keys()) + and values['start_date'] >= lease['start_date'] + and values['end_date'] <= lease['end_date']): + # Nothing to update + return + + dates_before = {'start_date': lease['start_date'], + 'end_date': lease['end_date']} + dates_after = {'start_date': values['start_date'], + 'end_date': values['end_date']} + host_reservation = db_api.host_reservation_get( + reservation['resource_id']) + self._update_allocations(dates_before, dates_after, reservation_id, + reservation['status'], host_reservation, + values) + + updates = {} + if 'min' in values or 'max' in values: + count_range = str(values.get( + 'min', host_reservation['count_range'].split('-')[0]) + ) + '-' + str(values.get( + 'max', host_reservation['count_range'].split('-')[1]) + ) + updates['count_range'] = count_range + if 'hypervisor_properties' in values: + updates['hypervisor_properties'] = values.get( + 'hypervisor_properties') + if 'resource_properties' in values: + updates['resource_properties'] = values.get( + 'resource_properties') + if updates: + db_api.host_reservation_update(host_reservation['id'], updates) def on_start(self, resource_id): """Add the hosts in the pool.""" @@ -433,3 +410,101 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper): values['before_end'] = 'default' if values['before_end'] not in before_end_options: raise manager_ex.MalformedParameter(param='before_end') + + def _update_allocations(self, dates_before, dates_after, reservation_id, + reservation_status, host_reservation, values): + min_hosts = self._convert_int_param(values.get( + 'min', host_reservation['count_range'].split('-')[0]), 'min') + max_hosts = self._convert_int_param(values.get( + 'max', host_reservation['count_range'].split('-')[1]), 'max') + if min_hosts < 0 or max_hosts < min_hosts: + raise manager_ex.InvalidRange() + hypervisor_properties = values.get( + 'hypervisor_properties', + host_reservation['hypervisor_properties']) + resource_properties = values.get( + 'resource_properties', + host_reservation['resource_properties']) + allocs = db_api.host_allocation_get_all_by_values( + reservation_id=reservation_id) + + allocs_to_remove = self._allocations_to_remove( + dates_before, dates_after, max_hosts, hypervisor_properties, + resource_properties, allocs) + + if allocs_to_remove and reservation_status == 'active': + raise manager_ex.NotEnoughHostsAvailable() + + kept_hosts = len(allocs) - len(allocs_to_remove) + if kept_hosts < max_hosts: + min_hosts = min_hosts - kept_hosts \ + if (min_hosts - kept_hosts) > 0 else 0 + max_hosts = max_hosts - kept_hosts + host_ids = self._matching_hosts( + hypervisor_properties, resource_properties, + str(min_hosts) + '-' + str(max_hosts), + dates_after['start_date'], dates_after['end_date']) + if len(host_ids) >= min_hosts: + for host_id in host_ids: + db_api.host_allocation_create( + {'compute_host_id': host_id, + 'reservation_id': reservation_id}) + else: + raise manager_ex.NotEnoughHostsAvailable() + + for allocation in allocs_to_remove: + db_api.host_allocation_destroy(allocation['id']) + + def _allocations_to_remove(self, dates_before, dates_after, max_hosts, + hypervisor_properties, resource_properties, + allocs): + allocs_to_remove = [] + requested_host_ids = [host['id'] for host in + self._filter_hosts_by_properties( + hypervisor_properties, resource_properties)] + + for alloc in allocs: + if alloc['compute_host_id'] not in requested_host_ids: + allocs_to_remove.append(alloc) + continue + if (dates_before['start_date'] > dates_after['start_date'] or + dates_before['end_date'] < dates_after['end_date']): + full_periods = db_utils.get_full_periods( + alloc['compute_host_id'], + dates_after['start_date'], + dates_after['end_date'], + datetime.timedelta(seconds=1)) + + max_start = max(dates_before['start_date'], + dates_after['start_date']) + min_end = min(dates_before['end_date'], + dates_after['end_date']) + + if not (len(full_periods) == 0 or + (len(full_periods) == 1 and + full_periods[0][0] == max_start and + full_periods[0][1] == min_end)): + allocs_to_remove.append(alloc) + continue + + kept_hosts = len(allocs) - len(allocs_to_remove) + if kept_hosts > max_hosts: + allocs_to_remove.extend( + [allocation for allocation in allocs + if allocation not in allocs_to_remove + ][:(kept_hosts - max_hosts)] + ) + + return allocs_to_remove + + def _filter_hosts_by_properties(self, hypervisor_properties, + resource_properties): + filter = [] + if hypervisor_properties: + filter += plugins_utils.convert_requirements(hypervisor_properties) + if resource_properties: + filter += plugins_utils.convert_requirements(resource_properties) + if filter: + return db_api.host_get_all_by_queries(filter) + else: + return db_api.host_list() diff --git a/blazar/tests/manager/test_service.py b/blazar/tests/manager/test_service.py index 88a01542..84b002de 100644 --- a/blazar/tests/manager/test_service.py +++ b/blazar/tests/manager/test_service.py @@ -628,8 +628,6 @@ class ServiceTestCase(tests.TestCase): { 'id': u'593e7028-c0d1-4d76-8642-2ffd890b324c', 'resource_type': 'virtual:instance', - 'start_date': datetime.datetime(2013, 12, 20, 20, 00), - 'end_date': datetime.datetime(2013, 12, 20, 21, 00) } ] event_get = self.patch(db_api, 'event_get_first_sorted_by_filters') @@ -643,8 +641,6 @@ class ServiceTestCase(tests.TestCase): self.fake_plugin.update_reservation.assert_called_with( '593e7028-c0d1-4d76-8642-2ffd890b324c', { - 'id': '593e7028-c0d1-4d76-8642-2ffd890b324c', - 'resource_type': 'virtual:instance', 'start_date': datetime.datetime(2015, 12, 1, 20, 00), 'end_date': datetime.datetime(2015, 12, 1, 22, 00) } @@ -659,6 +655,116 @@ class ServiceTestCase(tests.TestCase): self.event_update.assert_has_calls(calls) self.lease_update.assert_called_once_with(self.lease_id, lease_values) + def test_update_modify_reservations(self): + def fake_event_get(sort_key, sort_dir, filters): + if filters['event_type'] == 'start_lease': + return {'id': u'2eeb784a-2d84-4a89-a201-9d42d61eecb1'} + elif filters['event_type'] == 'end_lease': + return {'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'} + elif filters['event_type'] == 'before_end_lease': + delta = datetime.timedelta(hours=1) + return {'id': u'452bf850-e223-4035-9d13-eb0b0197228f', + 'time': self.lease['end_date'] - delta, + 'status': 'UNDONE'} + + lease_values = { + 'reservations': [ + { + 'id': u'593e7028-c0d1-4d76-8642-2ffd890b324c', + 'min': 3, + 'max': 3 + } + ] + } + reservation_get_all = ( + self.patch(self.db_api, 'reservation_get_all_by_lease_id')) + reservation_get_all.return_value = [ + { + 'id': u'593e7028-c0d1-4d76-8642-2ffd890b324c', + 'resource_type': 'virtual:instance', + } + ] + event_get = self.patch(db_api, 'event_get_first_sorted_by_filters') + event_get.side_effect = fake_event_get + target = datetime.datetime(2013, 12, 15) + with mock.patch.object(datetime, + 'datetime', + mock.Mock(wraps=datetime.datetime)) as patched: + patched.utcnow.return_value = target + self.manager.update_lease(self.lease_id, lease_values) + self.fake_plugin.update_reservation.assert_called_with( + '593e7028-c0d1-4d76-8642-2ffd890b324c', + { + 'start_date': datetime.datetime(2013, 12, 20, 13, 00), + 'end_date': datetime.datetime(2013, 12, 20, 15, 00), + 'id': u'593e7028-c0d1-4d76-8642-2ffd890b324c', + 'min': 3, + 'max': 3 + } + ) + calls = [mock.call('2eeb784a-2d84-4a89-a201-9d42d61eecb1', + {'time': datetime.datetime(2013, 12, 20, 13, 00)}), + mock.call('7085381b-45e0-4e5d-b24a-f965f5e6e5d7', + {'time': datetime.datetime(2013, 12, 20, 15, 00)}), + mock.call('452bf850-e223-4035-9d13-eb0b0197228f', + {'time': datetime.datetime(2013, 12, 20, 14, 00)}) + ] + self.event_update.assert_has_calls(calls) + self.lease_update.assert_called_once_with(self.lease_id, lease_values) + + def test_update_modify_reservations_with_invalid_param(self): + lease_values = { + 'reservations': [ + { + 'id': u'593e7028-c0d1-4d76-8642-2ffd890b324c', + 'resource_type': 'invalid', + 'min': 3, + 'max': 3 + } + ] + } + reservation_get_all = ( + self.patch(self.db_api, 'reservation_get_all_by_lease_id')) + reservation_get_all.return_value = [ + { + 'id': u'593e7028-c0d1-4d76-8642-2ffd890b324c', + 'resource_type': 'virtual:instance', + } + ] + target = datetime.datetime(2013, 12, 15) + with mock.patch.object(datetime, + 'datetime', + mock.Mock(wraps=datetime.datetime)) as patched: + patched.utcnow.return_value = target + self.assertRaises( + manager_ex.CantUpdateParameter, self.manager.update_lease, + self.lease_id, lease_values) + + def test_update_modify_reservations_without_reservation_id(self): + lease_values = { + 'reservations': [ + { + 'max': 3 + } + ] + } + reservation_get_all = ( + self.patch(self.db_api, 'reservation_get_all_by_lease_id')) + reservation_get_all.return_value = [ + { + 'id': u'593e7028-c0d1-4d76-8642-2ffd890b324c', + 'resource_type': 'virtual:instance', + } + ] + target = datetime.datetime(2013, 12, 15) + with mock.patch.object(datetime, + 'datetime', + mock.Mock(wraps=datetime.datetime)) as patched: + patched.utcnow.return_value = target + self.assertRaises( + manager_ex.MissingParameter, self.manager.update_lease, + self.lease_id, lease_values) + def test_update_lease_started_modify_end_date_without_before_end(self): def fake_event_get(sort_key, sort_dir, filters): if filters['event_type'] == 'start_lease': @@ -693,8 +799,6 @@ class ServiceTestCase(tests.TestCase): self.fake_plugin.update_reservation.assert_called_with( '593e7028-c0d1-4d76-8642-2ffd890b324c', { - 'id': '593e7028-c0d1-4d76-8642-2ffd890b324c', - 'resource_type': 'virtual:instance', 'start_date': datetime.datetime(2013, 12, 20, 13, 00), 'end_date': datetime.datetime(2013, 12, 20, 16, 00) } @@ -744,8 +848,6 @@ class ServiceTestCase(tests.TestCase): self.fake_plugin.update_reservation.assert_called_with( '593e7028-c0d1-4d76-8642-2ffd890b324c', { - 'id': '593e7028-c0d1-4d76-8642-2ffd890b324c', - 'resource_type': 'virtual:instance', 'start_date': datetime.datetime(2013, 12, 20, 13, 00), 'end_date': datetime.datetime(2013, 12, 20, 16, 00) } @@ -810,8 +912,6 @@ class ServiceTestCase(tests.TestCase): self.fake_plugin.update_reservation.assert_called_with( '593e7028-c0d1-4d76-8642-2ffd890b324c', { - 'id': '593e7028-c0d1-4d76-8642-2ffd890b324c', - 'resource_type': 'virtual:instance', 'start_date': datetime.datetime(2013, 12, 20, 13, 00), 'end_date': datetime.datetime(2013, 12, 20, 16, 00) } diff --git a/blazar/tests/plugins/test_physical_host_plugin.py b/blazar/tests/plugins/test_physical_host_plugin.py index d050bc18..dfab13de 100644 --- a/blazar/tests/plugins/test_physical_host_plugin.py +++ b/blazar/tests/plugins/test_physical_host_plugin.py @@ -482,7 +482,6 @@ class PhysicalHostPluginTestCase(tests.TestCase): reservation_get = self.patch(self.db_api, 'reservation_get') reservation_get.return_value = { 'lease_id': u'10870923-6d56-45c9-b592-f788053f5baa', - 'resource_id': u'91253650-cc34-4c4f-bbe8-c943aa7d0c9b' } lease_get = self.patch(self.db_api, 'lease_get') lease_get.return_value = { @@ -490,19 +489,11 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'end_date': datetime.datetime(2013, 12, 19, 21, 00) } host_reservation_get = self.patch(self.db_api, 'host_reservation_get') - host_reservation_get.return_value = { - 'aggregate_id': 1 - } - get_computehosts = self.patch(self.nova.ReservationPool, - 'get_computehosts') - get_computehosts.return_value = ['host1'] - host_allocation_get_all = self.patch( - self.db_api, - 'host_allocation_get_all_by_values') + self.fake_phys_plugin.update_reservation( '706eb3bc-07ed-4383-be93-b32845ece672', values) - host_allocation_get_all.assert_not_called() + host_reservation_get.assert_not_called() def test_update_reservation_extend(self): values = { @@ -512,7 +503,8 @@ class PhysicalHostPluginTestCase(tests.TestCase): reservation_get = self.patch(self.db_api, 'reservation_get') reservation_get.return_value = { 'lease_id': u'10870923-6d56-45c9-b592-f788053f5baa', - 'resource_id': u'91253650-cc34-4c4f-bbe8-c943aa7d0c9b' + 'resource_id': u'91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' } lease_get = self.patch(self.db_api, 'lease_get') lease_get.return_value = { @@ -520,6 +512,11 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'end_date': datetime.datetime(2013, 12, 19, 21, 00) } host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'count_range': '1-1', + 'hypervisor_properties': '["=", "$memory_mb", "256"]', + 'resource_properties': '' + } host_allocation_get_all = self.patch( self.db_api, 'host_allocation_get_all_by_values') @@ -529,15 +526,26 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'compute_host_id': 'host1' } ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [{'id': 'host1'}] get_full_periods = self.patch(self.db_utils, 'get_full_periods') get_full_periods.return_value = [ (datetime.datetime(2013, 12, 19, 20, 00), datetime.datetime(2013, 12, 19, 21, 00)) ] + host_allocation_create = self.patch( + self.db_api, + 'host_allocation_create') + host_allocation_destroy = self.patch( + self.db_api, + 'host_allocation_destroy') + self.fake_phys_plugin.update_reservation( '706eb3bc-07ed-4383-be93-b32845ece672', values) - host_reservation_get.assert_not_called() + host_allocation_create.assert_not_called() + host_allocation_destroy.assert_not_called() def test_update_reservation_move_failure(self): values = { @@ -559,7 +567,7 @@ class PhysicalHostPluginTestCase(tests.TestCase): self.db_api, 'host_reservation_get') host_reservation_get.return_value = { - 'aggregate_id': 1, + 'count_range': '1-1', 'hypervisor_properties': '["=", "$memory_mb", "256"]', 'resource_properties': '' } @@ -572,6 +580,9 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'compute_host_id': 'host1' } ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [{'id': 'host1'}] get_full_periods = self.patch(self.db_utils, 'get_full_periods') get_full_periods.return_value = [ (datetime.datetime(2013, 12, 20, 20, 30), @@ -581,14 +592,13 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'get_computehosts') get_computehosts.return_value = ['host1'] matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') - matching_hosts.return_value = ['host2'] + matching_hosts.return_value = [] self.assertRaises( manager_exceptions.NotEnoughHostsAvailable, self.fake_phys_plugin.update_reservation, '706eb3bc-07ed-4383-be93-b32845ece672', values) reservation_get.assert_called() - host_reservation_get.assert_not_called() def test_update_reservation_move_overlap(self): values = { @@ -598,16 +608,22 @@ class PhysicalHostPluginTestCase(tests.TestCase): reservation_get = self.patch(self.db_api, 'reservation_get') reservation_get.return_value = { 'lease_id': u'10870923-6d56-45c9-b592-f788053f5baa', - 'resource_id': u'91253650-cc34-4c4f-bbe8-c943aa7d0c9b' + 'resource_id': u'91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' } lease_get = self.patch(self.db_api, 'lease_get') lease_get.return_value = { 'start_date': datetime.datetime(2013, 12, 19, 20, 00), 'end_date': datetime.datetime(2013, 12, 19, 21, 00) } - host_reservation_get_by_reservation_id = self.patch( + host_reservation_get = self.patch( self.db_api, - 'host_reservation_get_by_reservation_id') + 'host_reservation_get') + host_reservation_get.return_value = { + 'count_range': '1-1', + 'hypervisor_properties': '["=", "$memory_mb", "256"]', + 'resource_properties': '' + } host_allocation_get_all = self.patch( self.db_api, 'host_allocation_get_all_by_values') @@ -617,18 +633,26 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'compute_host_id': 'host1' } ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [{'id': 'host1'}] get_full_periods = self.patch(self.db_utils, 'get_full_periods') get_full_periods.return_value = [ (datetime.datetime(2013, 12, 19, 20, 30), datetime.datetime(2013, 12, 19, 21, 00)) ] - get_computehosts = self.patch(self.nova.ReservationPool, - 'get_computehosts') - get_computehosts.return_value = [] + host_allocation_create = self.patch( + self.db_api, + 'host_allocation_create') + host_allocation_destroy = self.patch( + self.db_api, + 'host_allocation_destroy') + self.fake_phys_plugin.update_reservation( '706eb3bc-07ed-4383-be93-b32845ece672', values) - host_reservation_get_by_reservation_id.assert_not_called() + host_allocation_create.assert_not_called() + host_allocation_destroy.assert_not_called() def test_update_reservation_move_realloc(self): values = { @@ -646,14 +670,12 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'start_date': datetime.datetime(2013, 12, 19, 20, 00), 'end_date': datetime.datetime(2013, 12, 19, 21, 00) } - host_get = self.patch(self.db_api, 'host_get') - host_get.side_effect = ({'service_name': 'host1'}, - {'service_name': 'host2'}) host_reservation_get = self.patch( self.db_api, 'host_reservation_get') host_reservation_get.return_value = { 'aggregate_id': 1, + 'count_range': '1-1', 'hypervisor_properties': '["=", "$memory_mb", "256"]', 'resource_properties': '' } @@ -666,6 +688,10 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'compute_host_id': 'host1' } ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [{'id': 'host1'}, + {'id': 'host2'}] host_allocation_create = self.patch( self.db_api, 'host_allocation_create') @@ -677,9 +703,6 @@ class PhysicalHostPluginTestCase(tests.TestCase): (datetime.datetime(2013, 12, 20, 20, 30), datetime.datetime(2013, 12, 20, 21, 00)) ] - get_computehosts = self.patch(self.nova.ReservationPool, - 'get_computehosts') - get_computehosts.return_value = ['host1'] matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') matching_hosts.return_value = ['host2'] self.fake_phys_plugin.update_reservation( @@ -695,14 +718,514 @@ class PhysicalHostPluginTestCase(tests.TestCase): 'reservation_id': '706eb3bc-07ed-4383-be93-b32845ece672' } ) - self.remove_compute_host.assert_called_with( - 1, - ['host1'] + + def test_update_reservation_min_increase_success(self): + values = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00), + 'min': 3 + } + reservation_get = self.patch(self.db_api, 'reservation_get') + reservation_get.return_value = { + 'lease_id': '10870923-6d56-45c9-b592-f788053f5baa', + 'resource_id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' + } + lease_get = self.patch(self.db_api, 'lease_get') + lease_get.return_value = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00) + } + host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'count_range': '2-3', + 'hypervisor_properties': '["=", "$memory_mb", "16384"]', + 'resource_properties': '' + } + host_allocation_get_all = self.patch( + self.db_api, 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + }, + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a7', + 'compute_host_id': 'host2' + } + ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [ + {'id': 'host1'}, + {'id': 'host2'}, + {'id': 'host3'} + ] + host_allocation_destroy = self.patch(self.db_api, + 'host_allocation_destroy') + host_allocation_create = self.patch(self.db_api, + 'host_allocation_create') + matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') + matching_hosts.return_value = ['host3'] + host_reservation_update = self.patch(self.db_api, + 'host_reservation_update') + + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b') + matching_hosts.assert_called_with( + '["=", "$memory_mb", "16384"]', + '', + '1-1', + datetime.datetime(2017, 7, 12, 20, 00), + datetime.datetime(2017, 7, 12, 21, 00) ) - self.add_compute_host.assert_called_with( - 1, - 'host2' + host_allocation_destroy.assert_not_called() + host_allocation_create.assert_called_with( + { + 'compute_host_id': 'host3', + 'reservation_id': '706eb3bc-07ed-4383-be93-b32845ece672' + } ) + host_reservation_update.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + {'count_range': '3-3'} + ) + + def test_update_reservation_min_increase_fail(self): + values = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00), + 'min': 3 + } + reservation_get = self.patch(self.db_api, 'reservation_get') + reservation_get.return_value = { + 'lease_id': '10870923-6d56-45c9-b592-f788053f5baa', + 'resource_id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' + } + lease_get = self.patch(self.db_api, 'lease_get') + lease_get.return_value = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00) + } + host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'count_range': '2-3', + 'hypervisor_properties': '["=", "$memory_mb", "16384"]', + 'resource_properties': '' + } + host_allocation_get_all = self.patch( + self.db_api, 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + }, + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a7', + 'compute_host_id': 'host2' + } + ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [ + {'id': 'host1'}, + {'id': 'host2'} + ] + matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') + matching_hosts.return_value = [] + + self.assertRaises( + manager_exceptions.NotEnoughHostsAvailable, + self.fake_phys_plugin.update_reservation, + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + matching_hosts.assert_called_with( + '["=", "$memory_mb", "16384"]', + '', + '1-1', + datetime.datetime(2017, 7, 12, 20, 00), + datetime.datetime(2017, 7, 12, 21, 00) + ) + + def test_update_reservation_min_decrease(self): + values = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00), + 'min': 1 + } + reservation_get = self.patch(self.db_api, 'reservation_get') + reservation_get.return_value = { + 'lease_id': '10870923-6d56-45c9-b592-f788053f5baa', + 'resource_id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' + } + lease_get = self.patch(self.db_api, 'lease_get') + lease_get.return_value = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00) + } + host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'count_range': '2-2', + 'hypervisor_properties': '["=", "$memory_mb", "16384"]', + 'resource_properties': '' + } + host_allocation_get_all = self.patch( + self.db_api, 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + }, + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a7', + 'compute_host_id': 'host2' + } + ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [ + {'id': 'host1'}, + {'id': 'host2'} + ] + matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') + host_allocation_destroy = self.patch(self.db_api, + 'host_allocation_destroy') + host_allocation_create = self.patch(self.db_api, + 'host_allocation_create') + host_reservation_update = self.patch(self.db_api, + 'host_reservation_update') + + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + matching_hosts.assert_not_called() + host_allocation_destroy.assert_not_called() + host_allocation_create.assert_not_called() + host_reservation_update.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + {'count_range': '1-2'} + ) + + def test_update_reservation_max_increase_alloc(self): + values = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00), + 'max': 3 + } + reservation_get = self.patch(self.db_api, 'reservation_get') + reservation_get.return_value = { + 'lease_id': '10870923-6d56-45c9-b592-f788053f5baa', + 'resource_id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' + } + lease_get = self.patch(self.db_api, 'lease_get') + lease_get.return_value = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00) + } + host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'count_range': '1-2', + 'hypervisor_properties': '["=", "$memory_mb", "16384"]', + 'resource_properties': '' + } + host_allocation_get_all = self.patch( + self.db_api, 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + }, + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a7', + 'compute_host_id': 'host2' + } + ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [ + {'id': 'host1'}, + {'id': 'host2'}, + {'id': 'host3'} + ] + host_allocation_destroy = self.patch(self.db_api, + 'host_allocation_destroy') + host_allocation_create = self.patch(self.db_api, + 'host_allocation_create') + matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') + matching_hosts.return_value = ['host3'] + host_reservation_update = self.patch(self.db_api, + 'host_reservation_update') + + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b') + matching_hosts.assert_called_with( + '["=", "$memory_mb", "16384"]', + '', + '0-1', + datetime.datetime(2017, 7, 12, 20, 00), + datetime.datetime(2017, 7, 12, 21, 00) + ) + host_allocation_destroy.assert_not_called() + host_allocation_create.assert_called_with( + { + 'compute_host_id': 'host3', + 'reservation_id': '706eb3bc-07ed-4383-be93-b32845ece672' + } + ) + host_reservation_update.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + {'count_range': '1-3'} + ) + + def test_update_reservation_max_increase_noalloc(self): + values = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00), + 'max': 3 + } + reservation_get = self.patch(self.db_api, 'reservation_get') + reservation_get.return_value = { + 'lease_id': '10870923-6d56-45c9-b592-f788053f5baa', + 'resource_id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' + } + lease_get = self.patch(self.db_api, 'lease_get') + lease_get.return_value = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00) + } + host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'count_range': '1-2', + 'hypervisor_properties': '["=", "$memory_mb", "16384"]', + 'resource_properties': '' + } + host_allocation_get_all = self.patch( + self.db_api, 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + }, + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a7', + 'compute_host_id': 'host2' + } + ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [ + {'id': 'host1'}, + {'id': 'host2'} + ] + matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') + matching_hosts.return_value = [] + host_reservation_update = self.patch(self.db_api, + 'host_reservation_update') + + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b') + matching_hosts.assert_called_with( + '["=", "$memory_mb", "16384"]', + '', + '0-1', + datetime.datetime(2017, 7, 12, 20, 00), + datetime.datetime(2017, 7, 12, 21, 00) + ) + host_reservation_update.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + {'count_range': '1-3'} + ) + + def test_update_reservation_max_decrease(self): + values = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00), + 'max': 1 + } + reservation_get = self.patch(self.db_api, 'reservation_get') + reservation_get.return_value = { + 'lease_id': '10870923-6d56-45c9-b592-f788053f5baa', + 'resource_id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' + } + lease_get = self.patch(self.db_api, 'lease_get') + lease_get.return_value = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00) + } + host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'count_range': '1-2', + 'hypervisor_properties': '["=", "$memory_mb", "16384"]', + 'resource_properties': '' + } + host_allocation_get_all = self.patch( + self.db_api, 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + }, + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a7', + 'compute_host_id': 'host2' + } + ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [ + {'id': 'host1'}, + {'id': 'host2'} + ] + host_allocation_destroy = self.patch(self.db_api, + 'host_allocation_destroy') + host_reservation_update = self.patch(self.db_api, + 'host_reservation_update') + + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b') + host_allocation_destroy.assert_called_with( + 'dd305477-4df8-4547-87f6-69069ee546a6') + host_reservation_update.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + {'count_range': '1-1'} + ) + + def test_update_reservation_realloc_with_properties_change(self): + values = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00), + 'hypervisor_properties': '["=", "$memory_mb", "32768"]', + } + reservation_get = self.patch(self.db_api, 'reservation_get') + reservation_get.return_value = { + 'lease_id': '10870923-6d56-45c9-b592-f788053f5baa', + 'resource_id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' + } + lease_get = self.patch(self.db_api, 'lease_get') + lease_get.return_value = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00) + } + host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'count_range': '1-1', + 'hypervisor_properties': '["=", "$memory_mb", "16384"]', + 'resource_properties': '' + } + host_allocation_get_all = self.patch( + self.db_api, 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + } + ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [{'id': 'host2'}] + matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') + matching_hosts.return_value = ['host2'] + host_allocation_create = self.patch(self.db_api, + 'host_allocation_create') + host_allocation_destroy = self.patch(self.db_api, + 'host_allocation_destroy') + host_reservation_update = self.patch(self.db_api, + 'host_reservation_update') + + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b') + matching_hosts.assert_called_with( + '["=", "$memory_mb", "32768"]', + '', + '1-1', + datetime.datetime(2017, 7, 12, 20, 00), + datetime.datetime(2017, 7, 12, 21, 00) + ) + host_allocation_create.assert_called_with( + { + 'compute_host_id': 'host2', + 'reservation_id': '706eb3bc-07ed-4383-be93-b32845ece672' + } + ) + host_allocation_destroy.assert_called_with( + 'dd305477-4df8-4547-87f6-69069ee546a6' + ) + host_reservation_update.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + {'hypervisor_properties': '["=", "$memory_mb", "32768"]'} + ) + + def test_update_reservation_no_requested_hosts_available(self): + values = { + 'start_date': datetime.datetime(2017, 7, 12, 20, 00), + 'end_date': datetime.datetime(2017, 7, 12, 21, 00), + 'resource_properties': '[">=", "$vcpus", "32768"]' + } + reservation_get = self.patch(self.db_api, 'reservation_get') + reservation_get.return_value = { + 'lease_id': '10870923-6d56-45c9-b592-f788053f5baa', + 'resource_id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'status': 'pending' + } + lease_get = self.patch(self.db_api, 'lease_get') + lease_get.return_value = { + 'start_date': datetime.datetime(2013, 12, 19, 20, 00), + 'end_date': datetime.datetime(2013, 12, 19, 21, 00) + } + host_reservation_get = self.patch(self.db_api, 'host_reservation_get') + host_reservation_get.return_value = { + 'id': '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'count_range': '1-1', + 'hypervisor_properties': '["=", "$memory_mb", "16384"]', + 'resource_properties': '' + } + host_allocation_get_all = self.patch( + self.db_api, 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': 'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + } + ] + host_get_all_by_queries = self.patch(self.db_api, + 'host_get_all_by_queries') + host_get_all_by_queries.return_value = [] + matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') + matching_hosts.return_value = [] + + self.assertRaises( + manager_exceptions.NotEnoughHostsAvailable, + self.fake_phys_plugin.update_reservation, + '441c1476-9f8f-4700-9f30-cd9b6fef3509', + values) def test_on_start(self): host_reservation_get = self.patch(self.db_api, 'host_reservation_get') diff --git a/doc/source/restapi/rest_api_v1.0.rst b/doc/source/restapi/rest_api_v1.0.rst index eaa7b5be..9ee4ae9e 100644 --- a/doc/source/restapi/rest_api_v1.0.rst +++ b/doc/source/restapi/rest_api_v1.0.rst @@ -358,6 +358,12 @@ are mentioned. { "name": "lease_new_foo", "end_date": "2017-3-12 12:00", + "reservations": [ + { + "id": "087bc740-6d2d-410b-9d47-c7b2b55a9d36", + "max": 3 + } + ] } **response** @@ -381,7 +387,7 @@ are mentioned. "status": "pending", "lease_id": "6ee55c78-ac52-41a6-99af-2d2d73bcc466", "min": 1, - "max": 1, + "max": 3, "resource_id": "5", "created_at": "2017-02-21 14:50:38", "updated_at": null, diff --git a/releasenotes/notes/update-reserved-capacity-9f079395959f4474.yaml b/releasenotes/notes/update-reserved-capacity-9f079395959f4474.yaml new file mode 100644 index 00000000..e35f3b82 --- /dev/null +++ b/releasenotes/notes/update-reserved-capacity-9f079395959f4474.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The lease-update API supports update of reservation properties. e.g. min, + max, hypervisor_properties and resource_properties for host reservation.