diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index e11b4acd..4963bb16 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -27,7 +27,9 @@ from blazar.plugins import base from blazar.plugins import instances as plugin from blazar.plugins import oshosts from blazar import status +from blazar.utils.openstack import exceptions as openstack_ex from blazar.utils.openstack import nova +from blazar.utils.openstack import placement from blazar.utils import plugins as plugins_utils CONF = cfg.CONF @@ -54,6 +56,7 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): self.freepool_name = CONF.nova.aggregate_freepool_name self.monitor = oshosts.host_plugin.PhysicalHostMonitorPlugin() self.monitor.register_healing_handler(self.heal_reservations) + self.placement_client = placement.BlazarPlacementClient() def filter_hosts_by_reservation(self, hosts, start_date, end_date, excludes): @@ -216,11 +219,17 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): 'is_public': False } reserved_flavor = self.nova.nova.flavors.create(**flavor_details) + + # Set extra specs to the flavor + rsv_id_rc_format = reservation_id.upper().replace("-", "_") + reservation_rc = "resources:CUSTOM_RESERVATION_" + rsv_id_rc_format extra_specs = { FLAVOR_EXTRA_SPEC: reservation_id, - "affinity_id": group_id + "affinity_id": group_id, + reservation_rc: "1" } reserved_flavor.set_keys(extra_specs) + return reserved_flavor def _create_resources(self, inst_reservation): @@ -248,6 +257,8 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): } agg = pool.create(name=reservation_id, metadata=pool_metadata) + self.placement_client.create_reservation_class(reservation_id) + return reserved_flavor, reserved_group, agg def cleanup_resources(self, instance_reservation): @@ -289,6 +300,8 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): err_msg = ('Fail to add host %s to aggregate %s.' % (host, reservation['aggregate_id'])) raise mgr_exceptions.NovaClientError(err_msg) + self.placement_client.update_reservation_inventory( + host['service_name'], reservation['id'], 1) else: try: self.nova.nova.flavors.delete(reservation['id']) @@ -458,28 +471,38 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): host = db_api.host_get(allocation['compute_host_id']) pool.add_computehost(instance_reservation['aggregate_id'], host['service_name'], stay_in=True) + self.placement_client.update_reservation_inventory( + host['service_name'], reservation_id, 1) def on_end(self, resource_id): instance_reservation = db_api.instance_reservation_get(resource_id) + reservation_id = instance_reservation['reservation_id'] ctx = context.current() try: self.nova.flavor_access.remove_tenant_access( - instance_reservation['reservation_id'], ctx.project_id) + reservation_id, ctx.project_id) except nova_exceptions.NotFound: pass allocations = db_api.host_allocation_get_all_by_values( - reservation_id=instance_reservation['reservation_id']) + reservation_id=reservation_id) for allocation in allocations: + host = db_api.host_get(allocation['compute_host_id']) db_api.host_allocation_destroy(allocation['id']) + try: + self.placement_client.delete_reservation_inventory( + host['service_name'], reservation_id) + except openstack_ex.ResourceProviderNotFound: + pass for server in self.nova.servers.list(search_opts={ - 'flavor': instance_reservation['reservation_id'], + 'flavor': reservation_id, 'all_tenants': 1}, detailed=False): server.delete() self.cleanup_resources(instance_reservation) + self.placement_client.delete_reservation_class(reservation_id) def heal_reservations(self, failed_resources, interval_begin, interval_end): @@ -534,6 +557,11 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): host = db_api.host_get(allocation['compute_host_id']) pool.remove_computehost(reservation['aggregate_id'], host['service_name']) + try: + self.placement_client.delete_reservation_inventory( + host['service_name'], reservation['id']) + except openstack_ex.ResourceProviderNotFound: + pass # Allocate an alternative host. values = {} @@ -561,6 +589,8 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): pool.add_computehost(reservation['aggregate_id'], new_host['service_name'], stay_in=True) + self.placement_client.update_reservation_inventory( + new_host['service_name'], reservation['id'], 1) LOG.warn('Resource changed for reservation %s (lease: %s).', reservation['id'], lease['name']) diff --git a/blazar/tests/plugins/instances/test_instance_plugin.py b/blazar/tests/plugins/instances/test_instance_plugin.py index 9050dbe4..e001edb4 100644 --- a/blazar/tests/plugins/instances/test_instance_plugin.py +++ b/blazar/tests/plugins/instances/test_instance_plugin.py @@ -540,6 +540,9 @@ class TestVirtualInstancePlugin(tests.TestCase): type(plugin).nova = mock_nova mock_nova.nova.flavors.create.return_value = fake_flavor + mock_create_reservation_class = self.patch( + plugin.placement_client, 'create_reservation_class') + fake_pool = mock.MagicMock(id='pool-id1') fake_agg = mock.MagicMock() fake_pool.create.return_value = fake_agg @@ -560,12 +563,15 @@ class TestVirtualInstancePlugin(tests.TestCase): vcpus=2, ram=1024, disk=20, is_public=False) fake_flavor.set_keys.assert_called_once_with( {'aggregate_instance_extra_specs:reservation': 'reservation-id1', - 'affinity_id': 'server_group_id1'}) + 'affinity_id': 'server_group_id1', + 'resources:CUSTOM_RESERVATION_RESERVATION_ID1': '1'}) fake_pool.create.assert_called_once_with( name='reservation-id1', metadata={'reservation': 'reservation-id1', 'filter_tenant_id': 'fake-project', 'affinity_id': 'server_group_id1'}) + mock_create_reservation_class.assert_called_once_with( + 'reservation-id1') def test_pickup_hosts_for_update(self): reservation = {'id': 'reservation-id1', 'status': 'pending'} @@ -675,7 +681,8 @@ class TestVirtualInstancePlugin(tests.TestCase): vcpus=2, ram=1024, disk=10, is_public=False) fake_flavor.set_keys.assert_called_once_with( {'aggregate_instance_extra_specs:reservation': 'reservation-id1', - 'affinity_id': 'group-1'}) + 'affinity_id': 'group-1', + 'resources:CUSTOM_RESERVATION_RESERVATION_ID1': '1'}) def test_update_resources_in_active(self): def fake_host_get(host_id): @@ -692,6 +699,9 @@ class TestVirtualInstancePlugin(tests.TestCase): self.set_context(context.BlazarContext(project_id='fake-project')) plugin = instance_plugin.VirtualInstancePlugin() + mock_update_reservation_inventory = self.patch( + plugin.placement_client, 'update_reservation_inventory') + fake_pool = mock.MagicMock() mock_pool = self.patch(nova, 'ReservationPool') mock_pool.return_value = fake_pool @@ -709,9 +719,10 @@ class TestVirtualInstancePlugin(tests.TestCase): mock_reservation_get.assert_called_once_with('reservation-id1') for i in range(3): - fake_pool.add_computehost.assert_any_call('aggregate-1', - 'host' + str(i + 1), - stay_in=True) + fake_pool.add_computehost.assert_any_call( + 'aggregate-1', 'host' + str(i + 1), stay_in=True) + mock_update_reservation_inventory.assert_any_call( + 'host' + str(i + 1), 'reservation-id1', 1) def test_update_reservation(self): plugin = instance_plugin.VirtualInstancePlugin() @@ -856,6 +867,9 @@ class TestVirtualInstancePlugin(tests.TestCase): mock_pool = self.patch(nova, 'ReservationPool') mock_pool.return_value = fake_pool + mock_update_reservation_inventory = self.patch( + plugin.placement_client, 'update_reservation_inventory') + mock_alloc_get = self.patch(db_api, 'host_allocation_get_all_by_values') mock_alloc_get.return_value = [ @@ -870,9 +884,10 @@ class TestVirtualInstancePlugin(tests.TestCase): mock_nova.flavor_access.add_tenant_access.assert_called_once_with( 'reservation-id1', 'fake-project') for i in range(3): - fake_pool.add_computehost.assert_any_call('aggregate-id1', - 'host' + str(i + 1), - stay_in=True) + fake_pool.add_computehost.assert_any_call( + 'aggregate-id1', 'host' + str(i + 1), stay_in=True) + mock_update_reservation_inventory.assert_any_call( + 'host' + str(i + 1), 'reservation-id1', 1) def test_on_end(self): self.set_context(context.BlazarContext(project_id='fake-project-id')) @@ -885,8 +900,20 @@ class TestVirtualInstancePlugin(tests.TestCase): mock_alloc_get = self.patch(db_api, 'host_allocation_get_all_by_values') - mock_alloc_get.return_value = [{'id': 'host-alloc-id1'}, - {'id': 'host-alloc-id2'}] + mock_alloc_get.return_value = [{'id': 'host-alloc-id1', + 'compute_host_id': 'host-id1'}, + {'id': 'host-alloc-id2', + 'compute_host_id': 'host-id2'}] + + mock_host_get = self.patch(db_api, 'host_get') + mock_host_get.side_effect = [ + {'service_name': 'host1'}, {'service_name': 'host2'} + ] + + mock_delete_reservation_inventory = self.patch( + plugin.placement_client, 'delete_reservation_inventory') + mock_delete_reservation_class = self.patch( + plugin.placement_client, 'delete_reservation_class') self.patch(db_api, 'host_allocation_destroy') @@ -907,8 +934,13 @@ class TestVirtualInstancePlugin(tests.TestCase): detailed=False) for fake in fake_servers: fake.delete.assert_called_once() + for i in range(2): + mock_delete_reservation_inventory.assert_any_call( + 'host' + str(i + 1), 'reservation-id1') mock_cleanup_resources.assert_called_once_with( fake_instance_reservation) + mock_delete_reservation_class.assert_called_once_with( + 'reservation-id1') def test_heal_reservations_before_start_and_resources_changed(self): plugin = instance_plugin.VirtualInstancePlugin() @@ -1132,13 +1164,17 @@ class TestVirtualInstancePlugin(tests.TestCase): lease_get = self.patch(db_api, 'lease_get') lease_get.return_value = dummy_lease host_get = self.patch(db_api, 'host_get') - host_get.return_value = failed_host + host_get.side_effect = [failed_host, new_host] fake_pool = mock.MagicMock() mock_pool = self.patch(nova, 'ReservationPool') mock_pool.return_value = fake_pool pickup_hosts = self.patch(plugin, 'pickup_hosts') pickup_hosts.return_value = {'added': [new_host['id']], 'removed': []} alloc_update = self.patch(db_api, 'host_allocation_update') + mock_delete_reservation_inventory = self.patch( + plugin.placement_client, 'delete_reservation_inventory') + mock_update_reservation_inventory = self.patch( + plugin.placement_client, 'update_reservation_inventory') with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched: @@ -1155,8 +1191,12 @@ class TestVirtualInstancePlugin(tests.TestCase): {'compute_host_id': new_host['id']}) fake_pool.add_computehost.assert_called_once_with( dummy_reservation['aggregate_id'], - failed_host['service_name'], + new_host['service_name'], stay_in=True) + mock_delete_reservation_inventory.assert_called_once_with( + 'compute-1', 'rsrv-1') + mock_update_reservation_inventory.assert_called_once_with( + 'compute-2', 'rsrv-1', 1) self.assertEqual(True, result) def test_reallocate_missing_resources(self):