Use placement inventory for instance reservation

In instance plugin, this patch
  - changess the _create_resources() to create the
    ``CUSTOM_RESERVATION_{reservation UUID}`` class and add it into
    the private flavor extra specs.
  - changes the on_start(), update_resources(), and _reallocate() to
    add the ``CUSTOM_RESERVATION_{reservation UUID}`` inventory to the
    reservation provider. Note that the total parameter is fixed to 1.
    This should be fixed in a following patch where arbitrary affinity
    policy is enabled.
  - changes the on_end() to remove the custom resevation resource
    inventory from the reservation provider and delete the resource
    class itself.

Change-Id: I41b9a9fba22830e391799fa2763e4e845959f37c
Blueprint: placement-api
This commit is contained in:
Tetsuro Nakamura 2018-07-23 14:27:04 +09:00
parent b7b600efc2
commit e90cc65799
2 changed files with 86 additions and 16 deletions

View File

@ -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'])

View File

@ -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):