diff --git a/climate/manager/service.py b/climate/manager/service.py index 3546a5ab..f8966d2e 100644 --- a/climate/manager/service.py +++ b/climate/manager/service.py @@ -163,17 +163,26 @@ class ManagerService(rpc_service.Service): start_date = lease_values['start_date'] end_date = lease_values['end_date'] + now = datetime.datetime.utcnow() + now = datetime.datetime(now.year, + now.month, + now.day, + now.hour, + now.minute) if start_date == 'now': - start_date = datetime.datetime.utcnow() + start_date = now else: start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d %H:%M") end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d %H:%M") + if start_date < now: + raise exceptions.NotAuthorized( + 'Start date must later than current date') + ctx = context.current() lease_values['user_id'] = ctx.user_id lease_values['tenant_id'] = ctx.tenant_id - lease_values['start_date'] = start_date lease_values['end_date'] = end_date @@ -211,8 +220,95 @@ class ManagerService(rpc_service.Service): @service_utils.export_context def update_lease(self, lease_id, values): - if values: + if not values: + return db_api.lease_get(lease_id) + + if len(values) == 1 and 'name' in values: db_api.lease_update(lease_id, values) + return db_api.lease_get(lease_id) + + lease = db_api.lease_get(lease_id) + start_date = values.get( + 'start_date', + datetime.datetime.strftime(lease['start_date'], "%Y-%m-%d %H:%M")) + end_date = values.get( + 'end_date', + datetime.datetime.strftime(lease['end_date'], "%Y-%m-%d %H:%M")) + + now = datetime.datetime.utcnow() + now = datetime.datetime(now.year, + now.month, + now.day, + now.hour, + now.minute) + if start_date == 'now': + start_date = now + else: + start_date = datetime.datetime.strptime(start_date, + "%Y-%m-%d %H:%M") + end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d %H:%M") + + values['start_date'] = start_date + values['end_date'] = end_date + + if (lease['start_date'] < now and + values['start_date'] != lease['start_date']): + raise exceptions.NotAuthorized( + 'Cannot modify the start date of already started leases') + + if (lease['start_date'] > now and + values['start_date'] < now): + raise exceptions.NotAuthorized( + 'Start date must later than current date') + + if lease['end_date'] < now: + raise exceptions.NotAuthorized( + 'Terminated leases can only be renamed') + + if (values['end_date'] < now or + values['end_date'] < values['start_date']): + raise exceptions.NotAuthorized( + 'End date must be later than current and start date') + + #TODO(frossigneux) rollback if an exception is raised + 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'] + self.plugins[resource_type].update_reservation( + reservation['id'], + reservation) + + events = db_api.event_get_all_sorted_by_filters( + 'lease_id', + 'asc', + { + 'lease_id': lease_id, + 'event_type': 'start_lease' + } + ) + if len(events) != 1: + raise exceptions.ClimateException( + 'Start lease event not found') + event = events[0] + db_api.event_update(event['id'], {'time': values['start_date']}) + + events = db_api.event_get_all_sorted_by_filters( + 'lease_id', + 'asc', + { + 'lease_id': lease_id, + 'event_type': 'end_lease' + } + ) + if len(events) != 1: + raise exceptions.ClimateException( + 'End lease event not found') + event = events[0] + db_api.event_update(event['id'], {'time': values['end_date']}) + + db_api.lease_update(lease_id, values) return db_api.lease_get(lease_id) @service_utils.export_context diff --git a/climate/plugins/base.py b/climate/plugins/base.py index eb53a7c1..0f47113e 100644 --- a/climate/plugins/base.py +++ b/climate/plugins/base.py @@ -68,6 +68,13 @@ class BasePlugin(object): } db_api.reservation_create(reservation_values) + def update_reservation(self, reservation_id, values): + """Update reservation.""" + reservation_values = { + 'resource_id': values['resource_id'] + } + db_api.reservation_update(reservation_id, reservation_values) + @abc.abstractmethod def on_end(self, resource_id): """Delete resource.""" diff --git a/climate/plugins/oshosts/host_plugin.py b/climate/plugins/oshosts/host_plugin.py index b28004d1..e99734d2 100644 --- a/climate/plugins/oshosts/host_plugin.py +++ b/climate/plugins/oshosts/host_plugin.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import json import six @@ -87,6 +88,70 @@ class PhysicalHostPlugin(base.BasePlugin): db_api.host_allocation_create({'compute_host_id': host_id, 'reservation_id': reservation['id']}) + def update_reservation(self, reservation_id, values): + """Update reservation.""" + reservation = db_api.reservation_get(reservation_id) + lease = db_api.lease_get(reservation['lease_id']) + hosts_in_pool = self.pool.get_computehosts( + reservation['resource_id']) + if (values['start_date'] < lease['start_date'] or + values['end_date'] > lease['end_date']): + allocations = [] + 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 (hosts_in_pool and + self.nova.hypervisors.get( + self._get_hypervisor_from_name( + allocation['compute_host_id']) + ).__dict__['running_vms'] > 0): + raise RuntimeError('Not enough hosts available') + if allocations: + host_reservation = \ + db_api.host_reservation_get_by_reservation_id( + reservation_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 RuntimeError('Not enough hosts available') + if hosts_in_pool: + old_hosts = [allocation['compute_host_id'] + for allocation in allocations] + self.pool.remove_computehost(reservation['resource_id'], + old_hosts) + 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) + host_name = host['hypervisor_hostname'] + self.pool.add_computehost(reservation['resource_id'], + host_name) + def on_start(self, resource_id): """Add the hosts in the pool.""" reservations = db_api.reservation_get_all_by_values( diff --git a/climate/tests/manager/test_service.py b/climate/tests/manager/test_service.py index f715ef2b..b4aefda7 100644 --- a/climate/tests/manager/test_service.py +++ b/climate/tests/manager/test_service.py @@ -193,22 +193,196 @@ class ServiceTestCase(tests.TestCase): self.assertRaises( ValueError, self.manager.create_lease, lease_values) - def test_update_lease_is_values(self): - lease_values = {'end_date': '2025-12-12 13:13'} - - lease = self.manager.update_lease(self.lease_id, lease_values) - + def test_update_lease_completed_lease_rename(self): + lease_values = {'name': 'renamed'} + target = datetime.datetime(2015, 1, 1) + with mock.patch.object(datetime, + 'datetime', + mock.Mock(wraps=datetime.datetime)) as patched: + patched.utcnow.return_value = target + lease = self.manager.update_lease(self.lease_id, lease_values) self.lease_update.assert_called_once_with(self.lease_id, lease_values) self.assertEqual(lease, self.lease) + def test_update_lease_not_started_modify_dates(self): + def fake_event_get_all(sort_key, sort_dir, filters): + if filters['event_type'] == 'start_lease': + return [{'id': u'2eeb784a-2d84-4a89-a201-9d42d61eecb1'}] + else: + return [{'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}] + + lease_values = { + 'name': 'renamed', + 'start_date': '2015-12-01 20:00', + 'end_date': '2015-12-01 22:00' + } + 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', + 'start_date': datetime.datetime(2013, 12, 20, 20, 00), + 'end_date': datetime.datetime(2013, 12, 20, 21, 00) + } + ] + event_get_all = self.patch(db_api, 'event_get_all_sorted_by_filters') + event_get_all.side_effect = fake_event_get_all + 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', + { + '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) + } + ) + calls = [mock.call('2eeb784a-2d84-4a89-a201-9d42d61eecb1', + {'time': datetime.datetime(2015, 12, 1, 20, 00)}), + mock.call('7085381b-45e0-4e5d-b24a-f965f5e6e5d7', + {'time': datetime.datetime(2015, 12, 1, 22, 00)}) + ] + self.event_update.assert_has_calls(calls) + self.lease_update.assert_called_once_with(self.lease_id, lease_values) + + def test_update_lease_started_modify_end_date(self): + def fake_event_get_all(sort_key, sort_dir, filters): + if filters['event_type'] == 'start_lease': + return [{'id': u'2eeb784a-2d84-4a89-a201-9d42d61eecb1'}] + else: + return [{'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}] + + lease_values = { + 'name': 'renamed', + 'end_date': '2013-12-20 16:00' + } + 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', + 'start_date': datetime.datetime(2013, 12, 20, 13, 00), + 'end_date': datetime.datetime(2013, 12, 20, 15, 00) + } + ] + event_get_all = self.patch(db_api, 'event_get_all_sorted_by_filters') + event_get_all.side_effect = fake_event_get_all + target = datetime.datetime(2013, 12, 20, 14, 00) + 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', + { + '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) + } + ) + 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, 16, 00)}) + ] + self.event_update.assert_has_calls(calls) + self.lease_update.assert_called_once_with(self.lease_id, lease_values) + def test_update_lease_is_not_values(self): lease_values = None - lease = self.manager.update_lease(self.lease_id, lease_values) - - self.lease_update.assert_not_called() + self.lease_get.assert_called_once_with(self.lease_id) self.assertEqual(lease, self.lease) + def test_update_lease_started_modify_start_date(self): + def fake_event_get_all(sort_key, sort_dir, filters): + if filters['event_type'] == 'start_lease': + return [{'id': u'2eeb784a-2d84-4a89-a201-9d42d61eecb1'}] + else: + return [{'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}] + + lease_values = { + 'name': 'renamed', + 'start_date': '2013-12-20 16:00' + } + target = datetime.datetime(2013, 12, 20, 14, 00) + with mock.patch.object(datetime, + 'datetime', + mock.Mock(wraps=datetime.datetime)) as patched: + patched.utcnow.return_value = target + self.assertRaises( + exceptions.NotAuthorized, self.manager.update_lease, + self.lease_id, lease_values) + + def test_update_lease_not_started_start_date_before_current_time(self): + def fake_event_get_all(sort_key, sort_dir, filters): + if filters['event_type'] == 'start_lease': + return [{'id': u'2eeb784a-2d84-4a89-a201-9d42d61eecb1'}] + else: + return [{'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}] + + lease_values = { + 'name': 'renamed', + 'start_date': '2013-12-14 13:00' + } + 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( + exceptions.NotAuthorized, self.manager.update_lease, + self.lease_id, lease_values) + + def test_update_lease_end_date_before_current_time(self): + def fake_event_get_all(sort_key, sort_dir, filters): + if filters['event_type'] == 'start_lease': + return [{'id': u'2eeb784a-2d84-4a89-a201-9d42d61eecb1'}] + else: + return [{'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}] + + lease_values = { + 'name': 'renamed', + 'end_date': '2013-12-14 13:00' + } + 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( + exceptions.NotAuthorized, self.manager.update_lease, + self.lease_id, lease_values) + + def test_update_lease_completed_modify_dates(self): + def fake_event_get_all(sort_key, sort_dir, filters): + if filters['event_type'] == 'start_lease': + return [{'id': u'2eeb784a-2d84-4a89-a201-9d42d61eecb1'}] + else: + return [{'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}] + + lease_values = { + 'name': 'renamed', + 'end_date': '2013-12-15 20:00' + } + target = datetime.datetime(2015, 12, 15) + with mock.patch.object(datetime, + 'datetime', + mock.Mock(wraps=datetime.datetime)) as patched: + patched.utcnow.return_value = target + self.assertRaises( + exceptions.NotAuthorized, self.manager.update_lease, + self.lease_id, lease_values) + def test_delete_lease_before_starting_date(self): self.patch(self.manager, 'get_lease').\ return_value = self.lease diff --git a/climate/tests/plugins/test_physical_host_plugin.py b/climate/tests/plugins/test_physical_host_plugin.py index b3fbc5f1..c049f6b5 100644 --- a/climate/tests/plugins/test_physical_host_plugin.py +++ b/climate/tests/plugins/test_physical_host_plugin.py @@ -157,8 +157,10 @@ class PhysicalHostPluginTestCase(tests.TestCase): self.nova_inventory = nova_inventory self.rp_create = self.patch(self.rp.ReservationPool, 'create') self.patch(self.rp.ReservationPool, 'get_aggregate_from_name_or_id') - self.patch(self.rp.ReservationPool, 'add_computehost') - self.patch(self.rp.ReservationPool, 'remove_computehost') + self.add_compute_host = self.patch(self.rp.ReservationPool, + 'add_computehost') + self.remove_compute_host = self.patch(self.rp.ReservationPool, + 'remove_computehost') self.get_host_details = self.patch(self.nova_inventory.NovaInventory, 'get_host_details') self.get_host_details.return_value = self.fake_host @@ -396,6 +398,226 @@ class PhysicalHostPluginTestCase(tests.TestCase): ] host_allocation_create.assert_has_calls(calls) + def test_update_reservation_shorten(self): + values = { + 'start_date': datetime.datetime(2013, 12, 19, 20, 30), + 'end_date': datetime.datetime(2013, 12, 19, 21, 00) + } + 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 = { + 'start_date': datetime.datetime(2013, 12, 19, 20, 00), + 'end_date': datetime.datetime(2013, 12, 19, 21, 00) + } + host_allocation_get_all = self.patch( + self.db_api, + 'host_allocation_get_all_by_values') + get_computehosts = self.patch(self.rp.ReservationPool, + 'get_computehosts') + get_computehosts.return_value = ['host1'] + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_allocation_get_all.assert_not_called() + + def test_update_reservation_extend(self): + values = { + 'start_date': datetime.datetime(2013, 12, 19, 20, 00), + 'end_date': datetime.datetime(2013, 12, 19, 21, 30) + } + 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 = { + '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( + self.db_api, + 'host_reservation_get_by_reservation_id') + host_allocation_get_all = self.patch( + self.db_api, + 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': u'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_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)) + ] + get_computehosts = self.patch(self.rp.ReservationPool, + 'get_computehosts') + get_computehosts.return_value = ['host1'] + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get_by_reservation_id.assert_not_called() + + def test_update_reservation_move_failure(self): + values = { + 'start_date': datetime.datetime(2013, 12, 20, 20, 00), + 'end_date': datetime.datetime(2013, 12, 20, 21, 30) + } + 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 = { + '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( + self.db_api, + 'host_reservation_get_by_reservation_id') + host_allocation_get_all = self.patch( + self.db_api, + 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': u'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_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), + datetime.datetime(2013, 12, 20, 21, 00)) + ] + get_computehosts = self.patch(self.rp.ReservationPool, + 'get_computehosts') + get_computehosts.return_value = ['host1'] + self.patch(self.fake_phys_plugin, '_get_hypervisor_from_name') + get_hypervisors = self.patch(self.nova.hypervisors, 'get') + get_hypervisors.return_value = mock.MagicMock(running_vms=1) + self.assertRaises( + RuntimeError, self.fake_phys_plugin.update_reservation, + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get_by_reservation_id.assert_not_called() + + def test_update_reservation_move_overlap(self): + values = { + 'start_date': datetime.datetime(2013, 12, 19, 20, 30), + 'end_date': datetime.datetime(2013, 12, 19, 21, 30) + } + 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 = { + '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( + self.db_api, + 'host_reservation_get_by_reservation_id') + host_allocation_get_all = self.patch( + self.db_api, + 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': u'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_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.rp.ReservationPool, + 'get_computehosts') + get_computehosts.return_value = [] + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get_by_reservation_id.assert_not_called() + + def test_update_reservation_move_realloc(self): + values = { + 'start_date': datetime.datetime(2013, 12, 20, 20, 00), + 'end_date': datetime.datetime(2013, 12, 20, 21, 30) + } + 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 = { + '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.return_value = {'hypervisor_hostname': 'host2'} + host_reservation_get_by_reservation_id = self.patch( + self.db_api, + 'host_reservation_get_by_reservation_id') + host_allocation_get_all = self.patch( + self.db_api, + 'host_allocation_get_all_by_values') + host_allocation_get_all.return_value = [ + { + 'id': u'dd305477-4df8-4547-87f6-69069ee546a6', + 'compute_host_id': 'host1' + } + ] + host_allocation_create = self.patch( + self.db_api, + 'host_allocation_create') + host_allocation_destroy = self.patch( + self.db_api, + 'host_allocation_destroy') + get_full_periods = self.patch(self.db_utils, 'get_full_periods') + get_full_periods.return_value = [ + (datetime.datetime(2013, 12, 20, 20, 30), + datetime.datetime(2013, 12, 20, 21, 00)) + ] + get_computehosts = self.patch(self.rp.ReservationPool, + 'get_computehosts') + get_computehosts.return_value = ['host1'] + self.patch(self.fake_phys_plugin, '_get_hypervisor_from_name') + get_hypervisors = self.patch(self.nova.hypervisors, 'get') + get_hypervisors.return_value = mock.MagicMock(running_vms=0) + matching_hosts = self.patch(self.fake_phys_plugin, '_matching_hosts') + matching_hosts.return_value = ['host2'] + self.fake_phys_plugin.update_reservation( + '706eb3bc-07ed-4383-be93-b32845ece672', + values) + host_reservation_get_by_reservation_id.assert_called_with( + '706eb3bc-07ed-4383-be93-b32845ece672') + host_allocation_destroy.assert_called_with( + 'dd305477-4df8-4547-87f6-69069ee546a6') + host_allocation_create.assert_called_with( + { + 'compute_host_id': 'host2', + 'reservation_id': '706eb3bc-07ed-4383-be93-b32845ece672' + } + ) + self.remove_compute_host.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + ['host1'] + ) + self.add_compute_host.assert_called_with( + '91253650-cc34-4c4f-bbe8-c943aa7d0c9b', + 'host2' + ) + def test_on_start(self): reservation_get_all_by_values = self.patch( self.db_api, 'reservation_get_all_by_values')