Enable instance_plugin to handle on_start and on_end events

The Blazar manager service calls on_start/on_end methods of the
instance_plugin class at the beginning or end of reservations. This
patch implements the two methods in instance_plugin.

The on_start method adds a flavor access right to the reserving user and
adds allocated hosts to the reserved aggregate.

The on_end method removes the access right to avoid creating new
instances with the reserved flavor first, then it deletes all instances
created by the flavor and removes hosts from the reserved aggregate.

Partially implements: blueprint new-instance-reservation

Change-Id: I885139224d8116599b2b02cab6ac1831352f8cb1
This commit is contained in:
Masahito Muroi 2017-07-07 16:51:29 +09:00
parent c2b65be7c4
commit c79cd10f06
3 changed files with 112 additions and 4 deletions

View File

@ -257,7 +257,43 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper):
"support updates of reservation.")
def on_start(self, resource_id):
pass
ctx = context.current()
instance_reservation = db_api.instance_reservation_get(resource_id)
reservation_id = instance_reservation['reservation_id']
try:
self.nova.flavor_access.add_tenant_access(reservation_id,
ctx.project_id)
except nova_exceptions.ClientException:
LOG.info('Failed to associate flavor %s to project %s' %
(reservation_id, ctx.project_id))
raise mgr_exceptions.EventError()
pool = nova.ReservationPool()
for allocation in db_api.host_allocation_get_all_by_values(
reservation_id=reservation_id):
host = db_api.host_get(allocation['compute_host_id'])
pool.add_computehost(instance_reservation['aggregate_id'],
host['service_name'], stay_in=True)
def on_end(self, resource_id):
pass
instance_reservation = db_api.instance_reservation_get(resource_id)
ctx = context.current()
try:
self.nova.flavor_access.remove_tenant_access(
instance_reservation['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'])
for allocation in allocations:
db_api.host_allocation_destroy(allocation['id'])
for server in self.nova.servers.list(search_opts={
'flavor': instance_reservation['reservation_id'],
'all_tenants': 1}, detailed=False):
server.delete()
self.cleanup_resources(instance_reservation)

View File

@ -444,3 +444,75 @@ class TestVirtualInstancePlugin(tests.TestCase):
metadata={'reservation': 'reservation-id1',
'filter_tenant_id': 'fake-project',
'affinity_id': 'server_group_id1'})
def test_on_start(self):
def fake_host_get(host_id):
return {'service_name': 'host' + host_id[-1]}
self.set_context(context.BlazarContext(project_id='fake-project'))
plugin = instance_plugin.VirtualInstancePlugin()
mock_inst_get = self.patch(db_api, 'instance_reservation_get')
mock_inst_get.return_value = {'reservation_id': 'reservation-id1',
'aggregate_id': 'aggregate-id1'}
mock_nova = mock.MagicMock()
type(plugin).nova = mock_nova
fake_pool = mock.MagicMock()
mock_pool = self.patch(nova, 'ReservationPool')
mock_pool.return_value = fake_pool
mock_alloc_get = self.patch(db_api,
'host_allocation_get_all_by_values')
mock_alloc_get.return_value = [
{'compute_host_id': 'host-id1'}, {'compute_host_id': 'host-id2'},
{'compute_host_id': 'host-id3'}]
mock_host_get = self.patch(db_api, 'host_get')
mock_host_get.side_effect = fake_host_get
plugin.on_start('resource-id1')
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)
def test_on_end(self):
self.set_context(context.BlazarContext(project_id='fake-project-id'))
plugin = instance_plugin.VirtualInstancePlugin()
fake_instance_reservation = {'reservation_id': 'reservation-id1'}
mock_inst_get = self.patch(db_api, 'instance_reservation_get')
mock_inst_get.return_value = fake_instance_reservation
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'}]
self.patch(db_api, 'host_allocation_destroy')
fake_servers = [mock.MagicMock(method='delete') for i in range(5)]
mock_nova = mock.MagicMock()
type(plugin).nova = mock_nova
mock_nova.servers.list.return_value = fake_servers
mock_cleanup_resources = self.patch(plugin, 'cleanup_resources')
plugin.on_end('resource-id1')
mock_nova.flavor_access.remove_tenant_access.assert_called_once_with(
'reservation-id1', 'fake-project-id')
mock_nova.servers.list.assert_called_once_with(
search_opts={'flavor': 'reservation-id1', 'all_tenants': 1},
detailed=False)
for fake in fake_servers:
fake.delete.assert_called_once()
mock_cleanup_resources.assert_called_once_with(
fake_instance_reservation)

View File

@ -323,7 +323,7 @@ class ReservationPool(NovaClientWrapper):
except manager_exceptions.AggregateNotFound:
return []
def add_computehost(self, pool, host):
def add_computehost(self, pool, host, stay_in=False):
"""Add a compute host to an aggregate.
The `host` must exist otherwise raise an error
@ -344,7 +344,7 @@ class ReservationPool(NovaClientWrapper):
except manager_exceptions.AggregateNotFound:
raise manager_exceptions.NoFreePool()
if freepool_agg.id != agg.id:
if freepool_agg.id != agg.id and not stay_in:
if host not in freepool_agg.hosts:
raise manager_exceptions.HostNotInFreePool(
host=host, freepool_name=freepool_agg.name)