Support tag instances when boot(3/4)
This is the third patch of the series, this patch adds a new rpc version to ComputeTaskManager to accept tags as a parameter in schedule_and_build_instances() Change-Id: Iac54b9627cb4398e45f1f15a2f4e7d6f86242093 Implemetes: blueprint support-tag-instance-when-boot
This commit is contained in:
parent
9261b85dbf
commit
b3cf0ab1ed
|
@ -218,7 +218,7 @@ class ComputeTaskManager(base.Base):
|
||||||
may involve coordinating activities on multiple compute nodes.
|
may involve coordinating activities on multiple compute nodes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
target = messaging.Target(namespace='compute_task', version='1.16')
|
target = messaging.Target(namespace='compute_task', version='1.17')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ComputeTaskManager, self).__init__()
|
super(ComputeTaskManager, self).__init__()
|
||||||
|
@ -835,6 +835,16 @@ class ComputeTaskManager(base.Base):
|
||||||
bdm.update_or_create()
|
bdm.update_or_create()
|
||||||
return instance_block_device_mapping
|
return instance_block_device_mapping
|
||||||
|
|
||||||
|
def _create_tags(self, context, instance_uuid, tags):
|
||||||
|
"""Create the Tags objects in the db."""
|
||||||
|
if tags:
|
||||||
|
tag_list = [tag.tag for tag in tags]
|
||||||
|
instance_tags = objects.TagList.create(
|
||||||
|
context, instance_uuid, tag_list)
|
||||||
|
return instance_tags
|
||||||
|
else:
|
||||||
|
return tags
|
||||||
|
|
||||||
def _bury_in_cell0(self, context, request_spec, exc,
|
def _bury_in_cell0(self, context, request_spec, exc,
|
||||||
build_requests=None, instances=None):
|
build_requests=None, instances=None):
|
||||||
"""Ensure all provided build_requests and instances end up in cell0.
|
"""Ensure all provided build_requests and instances end up in cell0.
|
||||||
|
@ -901,7 +911,8 @@ class ComputeTaskManager(base.Base):
|
||||||
def schedule_and_build_instances(self, context, build_requests,
|
def schedule_and_build_instances(self, context, build_requests,
|
||||||
request_specs, image,
|
request_specs, image,
|
||||||
admin_password, injected_files,
|
admin_password, injected_files,
|
||||||
requested_networks, block_device_mapping):
|
requested_networks, block_device_mapping,
|
||||||
|
tags=None):
|
||||||
# Add all the UUIDs for the instances
|
# Add all the UUIDs for the instances
|
||||||
instance_uuids = [spec.instance_uuid for spec in request_specs]
|
instance_uuids = [spec.instance_uuid for spec in request_specs]
|
||||||
try:
|
try:
|
||||||
|
@ -971,6 +982,7 @@ class ComputeTaskManager(base.Base):
|
||||||
want_result=False)
|
want_result=False)
|
||||||
instance_bdms = self._create_block_device_mapping(
|
instance_bdms = self._create_block_device_mapping(
|
||||||
cell, instance.flavor, instance.uuid, block_device_mapping)
|
cell, instance.flavor, instance.uuid, block_device_mapping)
|
||||||
|
instance_tags = self._create_tags(cctxt, instance.uuid, tags)
|
||||||
|
|
||||||
# Update mapping for instance. Normally this check is guarded by
|
# Update mapping for instance. Normally this check is guarded by
|
||||||
# a try/except but if we're here we know that a newer nova-api
|
# a try/except but if we're here we know that a newer nova-api
|
||||||
|
@ -981,7 +993,8 @@ class ComputeTaskManager(base.Base):
|
||||||
inst_mapping.save()
|
inst_mapping.save()
|
||||||
|
|
||||||
if not self._delete_build_request(
|
if not self._delete_build_request(
|
||||||
build_request, instance, cell, instance_bdms):
|
context, build_request, instance, cell, instance_bdms,
|
||||||
|
instance_tags):
|
||||||
# The build request was deleted before/during scheduling so
|
# The build request was deleted before/during scheduling so
|
||||||
# the instance is gone and we don't have anything to build for
|
# the instance is gone and we don't have anything to build for
|
||||||
# this one.
|
# this one.
|
||||||
|
@ -1006,13 +1019,15 @@ class ComputeTaskManager(base.Base):
|
||||||
host=host['host'], node=host['nodename'],
|
host=host['host'], node=host['nodename'],
|
||||||
limits=host['limits'])
|
limits=host['limits'])
|
||||||
|
|
||||||
def _delete_build_request(self, build_request, instance, cell,
|
def _delete_build_request(self, context, build_request, instance, cell,
|
||||||
instance_bdms):
|
instance_bdms, instance_tags):
|
||||||
"""Delete a build request after creating the instance in the cell.
|
"""Delete a build request after creating the instance in the cell.
|
||||||
|
|
||||||
This method handles cleaning up the instance in case the build request
|
This method handles cleaning up the instance in case the build request
|
||||||
is already deleted by the time we try to delete it.
|
is already deleted by the time we try to delete it.
|
||||||
|
|
||||||
|
:param context: the context of the request being handled
|
||||||
|
:type context: nova.context.RequestContext
|
||||||
:param build_request: the build request to delete
|
:param build_request: the build request to delete
|
||||||
:type build_request: nova.objects.BuildRequest
|
:type build_request: nova.objects.BuildRequest
|
||||||
:param instance: the instance created from the build_request
|
:param instance: the instance created from the build_request
|
||||||
|
@ -1021,6 +1036,8 @@ class ComputeTaskManager(base.Base):
|
||||||
:type cell: nova.objects.CellMapping
|
:type cell: nova.objects.CellMapping
|
||||||
:param instance_bdms: list of block device mappings for the instance
|
:param instance_bdms: list of block device mappings for the instance
|
||||||
:type instance_bdms: nova.objects.BlockDeviceMappingList
|
:type instance_bdms: nova.objects.BlockDeviceMappingList
|
||||||
|
:param instance_tags: list of tags for the instance
|
||||||
|
:type instance_tags: nova.objects.TagList
|
||||||
:returns: True if the build request was successfully deleted, False if
|
:returns: True if the build request was successfully deleted, False if
|
||||||
the build request was already deleted and the instance is now gone.
|
the build request was already deleted and the instance is now gone.
|
||||||
"""
|
"""
|
||||||
|
@ -1029,7 +1046,7 @@ class ComputeTaskManager(base.Base):
|
||||||
except exception.BuildRequestNotFound:
|
except exception.BuildRequestNotFound:
|
||||||
# This indicates an instance deletion request has been
|
# This indicates an instance deletion request has been
|
||||||
# processed, and the build should halt here. Clean up the
|
# processed, and the build should halt here. Clean up the
|
||||||
# bdm and instance record.
|
# bdm, tags and instance record.
|
||||||
with obj_target_cell(instance, cell) as cctxt:
|
with obj_target_cell(instance, cell) as cctxt:
|
||||||
with compute_utils.notify_about_instance_delete(
|
with compute_utils.notify_about_instance_delete(
|
||||||
self.notifier, cctxt, instance):
|
self.notifier, cctxt, instance):
|
||||||
|
@ -1051,5 +1068,11 @@ class ComputeTaskManager(base.Base):
|
||||||
bdm.destroy()
|
bdm.destroy()
|
||||||
except exception.ObjectActionError:
|
except exception.ObjectActionError:
|
||||||
pass
|
pass
|
||||||
|
if instance_tags:
|
||||||
|
with try_target_cell(context, cell) as target_ctxt:
|
||||||
|
try:
|
||||||
|
objects.TagList.destroy(target_ctxt, instance.uuid)
|
||||||
|
except exception.InstanceNotFound:
|
||||||
|
pass
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -273,6 +273,7 @@ class ComputeTaskAPI(object):
|
||||||
1.14 - Added request_spec to unshelve_instance()
|
1.14 - Added request_spec to unshelve_instance()
|
||||||
1.15 - Added live_migrate_instance
|
1.15 - Added live_migrate_instance
|
||||||
1.16 - Added schedule_and_build_instances
|
1.16 - Added schedule_and_build_instances
|
||||||
|
1.17 - Added tags to schedule_and_build_instances()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -353,18 +354,24 @@ class ComputeTaskAPI(object):
|
||||||
cctxt.cast(context, 'build_instances', **kw)
|
cctxt.cast(context, 'build_instances', **kw)
|
||||||
|
|
||||||
def schedule_and_build_instances(self, context, build_requests,
|
def schedule_and_build_instances(self, context, build_requests,
|
||||||
request_specs,
|
request_specs,
|
||||||
image, admin_password, injected_files,
|
image, admin_password, injected_files,
|
||||||
requested_networks,
|
requested_networks,
|
||||||
block_device_mapping):
|
block_device_mapping,
|
||||||
version = '1.16'
|
tags=None):
|
||||||
|
version = '1.17'
|
||||||
kw = {'build_requests': build_requests,
|
kw = {'build_requests': build_requests,
|
||||||
'request_specs': request_specs,
|
'request_specs': request_specs,
|
||||||
'image': jsonutils.to_primitive(image),
|
'image': jsonutils.to_primitive(image),
|
||||||
'admin_password': admin_password,
|
'admin_password': admin_password,
|
||||||
'injected_files': injected_files,
|
'injected_files': injected_files,
|
||||||
'requested_networks': requested_networks,
|
'requested_networks': requested_networks,
|
||||||
'block_device_mapping': block_device_mapping}
|
'block_device_mapping': block_device_mapping,
|
||||||
|
'tags': tags}
|
||||||
|
|
||||||
|
if not self.client.can_send_version(version):
|
||||||
|
version = '1.16'
|
||||||
|
del kw['tags']
|
||||||
|
|
||||||
cctxt = self.client.prepare(version=version)
|
cctxt = self.client.prepare(version=version)
|
||||||
cctxt.cast(context, 'schedule_and_build_instances', **kw)
|
cctxt.cast(context, 'schedule_and_build_instances', **kw)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import copy
|
||||||
import mock
|
import mock
|
||||||
from mox3 import mox
|
from mox3 import mox
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
from oslo_versionedobjects import exception as ovo_exc
|
from oslo_versionedobjects import exception as ovo_exc
|
||||||
import six
|
import six
|
||||||
|
@ -1352,19 +1353,21 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
||||||
tag=''))
|
tag=''))
|
||||||
params['block_device_mapping'] = objects.BlockDeviceMappingList(
|
params['block_device_mapping'] = objects.BlockDeviceMappingList(
|
||||||
objects=[bdm])
|
objects=[bdm])
|
||||||
|
tag = objects.Tag(self.ctxt, tag='tag1')
|
||||||
|
params['tags'] = objects.TagList(objects=[tag])
|
||||||
self.params = params
|
self.params = params
|
||||||
|
|
||||||
@mock.patch('nova.availability_zones.get_host_availability_zone')
|
@mock.patch('nova.availability_zones.get_host_availability_zone')
|
||||||
@mock.patch('nova.compute.rpcapi.ComputeAPI.build_and_run_instance')
|
@mock.patch('nova.compute.rpcapi.ComputeAPI.build_and_run_instance')
|
||||||
@mock.patch('nova.scheduler.rpcapi.SchedulerAPI.select_destinations')
|
@mock.patch('nova.scheduler.rpcapi.SchedulerAPI.select_destinations')
|
||||||
def test_schedule_and_build_instances(self, select_destinations,
|
def _do_schedule_and_build_instances_test(self, params,
|
||||||
build_and_run_instance,
|
select_destinations,
|
||||||
get_az):
|
build_and_run_instance,
|
||||||
|
get_az):
|
||||||
select_destinations.return_value = [{'host': 'fake-host',
|
select_destinations.return_value = [{'host': 'fake-host',
|
||||||
'nodename': 'fake-nodename',
|
'nodename': 'fake-nodename',
|
||||||
'limits': None}]
|
'limits': None}]
|
||||||
get_az.return_value = 'myaz'
|
get_az.return_value = 'myaz'
|
||||||
params = self.params
|
|
||||||
details = {}
|
details = {}
|
||||||
|
|
||||||
def _build_and_run_instance(ctxt, *args, **kwargs):
|
def _build_and_run_instance(ctxt, *args, **kwargs):
|
||||||
|
@ -1382,24 +1385,53 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
||||||
|
|
||||||
instance_uuid = details['instance'].uuid
|
instance_uuid = details['instance'].uuid
|
||||||
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
||||||
self.context, instance_uuid)
|
self.ctxt, instance_uuid)
|
||||||
ephemeral = list(filter(block_device.new_format_is_ephemeral, bdms))
|
ephemeral = list(filter(block_device.new_format_is_ephemeral, bdms))
|
||||||
self.assertEqual(1, len(ephemeral))
|
self.assertEqual(1, len(ephemeral))
|
||||||
swap = list(filter(block_device.new_format_is_swap, bdms))
|
swap = list(filter(block_device.new_format_is_swap, bdms))
|
||||||
self.assertEqual(0, len(swap))
|
self.assertEqual(0, len(swap))
|
||||||
|
|
||||||
self.assertEqual(1, ephemeral[0].volume_size)
|
self.assertEqual(1, ephemeral[0].volume_size)
|
||||||
|
return instance_uuid
|
||||||
|
|
||||||
cells = objects.CellMappingList.get_all(self.context)
|
def test_schedule_and_build_instances(self):
|
||||||
|
instance_uuid = self._do_schedule_and_build_instances_test(
|
||||||
|
self.params)
|
||||||
|
cells = objects.CellMappingList.get_all(self.ctxt)
|
||||||
|
|
||||||
# NOTE(danms): Assert that we created the InstanceAction in the
|
# NOTE(danms): Assert that we created the InstanceAction in the
|
||||||
# correct cell
|
# correct cell
|
||||||
|
# NOTE(Kevin Zheng): Also assert tags in the correct cell
|
||||||
for cell in cells:
|
for cell in cells:
|
||||||
with context.target_cell(self.context, cell) as cctxt:
|
with context.target_cell(self.ctxt, cell) as cctxt:
|
||||||
actions = objects.InstanceActionList.get_by_instance_uuid(
|
actions = objects.InstanceActionList.get_by_instance_uuid(
|
||||||
cctxt, instance_uuid)
|
cctxt, instance_uuid)
|
||||||
if cell.name == 'cell1':
|
if cell.name == 'cell1':
|
||||||
self.assertEqual(1, len(actions))
|
self.assertEqual(1, len(actions))
|
||||||
|
tags = objects.TagList.get_by_resource_id(
|
||||||
|
cctxt, instance_uuid)
|
||||||
|
self.assertEqual(1, len(tags))
|
||||||
|
else:
|
||||||
|
self.assertEqual(0, len(actions))
|
||||||
|
|
||||||
|
def test_schedule_and_build_instances_no_tags_provided(self):
|
||||||
|
params = copy.deepcopy(self.params)
|
||||||
|
del params['tags']
|
||||||
|
instance_uuid = self._do_schedule_and_build_instances_test(params)
|
||||||
|
cells = objects.CellMappingList.get_all(self.ctxt)
|
||||||
|
|
||||||
|
# NOTE(danms): Assert that we created the InstanceAction in the
|
||||||
|
# correct cell
|
||||||
|
# NOTE(Kevin Zheng): Also assert tags in the correct cell
|
||||||
|
for cell in cells:
|
||||||
|
with context.target_cell(self.ctxt, cell) as cctxt:
|
||||||
|
actions = objects.InstanceActionList.get_by_instance_uuid(
|
||||||
|
cctxt, instance_uuid)
|
||||||
|
if cell.name == 'cell1':
|
||||||
|
self.assertEqual(1, len(actions))
|
||||||
|
tags = objects.TagList.get_by_resource_id(
|
||||||
|
cctxt, instance_uuid)
|
||||||
|
self.assertEqual(0, len(tags))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(0, len(actions))
|
self.assertEqual(0, len(actions))
|
||||||
|
|
||||||
|
@ -1483,6 +1515,9 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
||||||
self.assertEqual('error', instance.vm_state)
|
self.assertEqual('error', instance.vm_state)
|
||||||
self.assertIsNone(instance.task_state)
|
self.assertIsNone(instance.task_state)
|
||||||
|
|
||||||
|
@mock.patch('nova.conductor.manager.try_target_cell')
|
||||||
|
@mock.patch('nova.objects.TagList.destroy')
|
||||||
|
@mock.patch('nova.objects.TagList.create')
|
||||||
@mock.patch('nova.compute.utils.notify_about_instance_usage')
|
@mock.patch('nova.compute.utils.notify_about_instance_usage')
|
||||||
@mock.patch('nova.compute.rpcapi.ComputeAPI.build_and_run_instance')
|
@mock.patch('nova.compute.rpcapi.ComputeAPI.build_and_run_instance')
|
||||||
@mock.patch('nova.scheduler.rpcapi.SchedulerAPI.select_destinations')
|
@mock.patch('nova.scheduler.rpcapi.SchedulerAPI.select_destinations')
|
||||||
|
@ -1494,7 +1529,10 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
||||||
br_destroy,
|
br_destroy,
|
||||||
select_destinations,
|
select_destinations,
|
||||||
build_and_run,
|
build_and_run,
|
||||||
notify):
|
notify,
|
||||||
|
taglist_create,
|
||||||
|
taglist_destroy,
|
||||||
|
try_target):
|
||||||
|
|
||||||
br_destroy.side_effect = exc.BuildRequestNotFound(uuid='foo')
|
br_destroy.side_effect = exc.BuildRequestNotFound(uuid='foo')
|
||||||
self.start_service('compute', host='fake-host')
|
self.start_service('compute', host='fake-host')
|
||||||
|
@ -1504,10 +1542,14 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
||||||
targeted_context = mock.MagicMock(
|
targeted_context = mock.MagicMock(
|
||||||
autospec='nova.context.RequestContext')
|
autospec='nova.context.RequestContext')
|
||||||
target_cell.return_value.__enter__.return_value = targeted_context
|
target_cell.return_value.__enter__.return_value = targeted_context
|
||||||
|
try_target.return_value.__enter__.return_value = targeted_context
|
||||||
|
taglist_create.return_value = self.params['tags']
|
||||||
self.conductor.schedule_and_build_instances(**self.params)
|
self.conductor.schedule_and_build_instances(**self.params)
|
||||||
self.assertFalse(build_and_run.called)
|
self.assertFalse(build_and_run.called)
|
||||||
self.assertFalse(bury.called)
|
self.assertFalse(bury.called)
|
||||||
self.assertTrue(br_destroy.called)
|
self.assertTrue(br_destroy.called)
|
||||||
|
taglist_destroy.assert_called_once_with(
|
||||||
|
targeted_context, self.params['build_requests'][0].instance_uuid)
|
||||||
|
|
||||||
test_utils.assert_instance_delete_notification_by_uuid(
|
test_utils.assert_instance_delete_notification_by_uuid(
|
||||||
notify, self.params['build_requests'][0].instance_uuid,
|
notify, self.params['build_requests'][0].instance_uuid,
|
||||||
|
@ -2375,6 +2417,76 @@ class ConductorTaskRPCAPITestCase(_BaseTaskTestCase,
|
||||||
test(None, ctxt, inst))
|
test(None, ctxt, inst))
|
||||||
mock_im.assert_called_once_with(ctxt, inst.uuid)
|
mock_im.assert_called_once_with(ctxt, inst.uuid)
|
||||||
|
|
||||||
|
def test_schedule_and_build_instances_with_tags(self):
|
||||||
|
|
||||||
|
build_request = fake_build_request.fake_req_obj(self.context)
|
||||||
|
request_spec = objects.RequestSpec(
|
||||||
|
instance_uuid=build_request.instance_uuid)
|
||||||
|
image = {'fake_data': 'should_pass_silently'}
|
||||||
|
admin_password = 'fake_password'
|
||||||
|
injected_file = 'fake'
|
||||||
|
requested_network = None
|
||||||
|
block_device_mapping = None
|
||||||
|
tags = ['fake_tag']
|
||||||
|
version = '1.17'
|
||||||
|
cctxt_mock = mock.MagicMock()
|
||||||
|
|
||||||
|
@mock.patch.object(self.conductor.client, 'can_send_version',
|
||||||
|
return_value=True)
|
||||||
|
@mock.patch.object(self.conductor.client, 'prepare',
|
||||||
|
return_value=cctxt_mock)
|
||||||
|
def _test(prepare_mock, can_send_mock):
|
||||||
|
self.conductor.schedule_and_build_instances(
|
||||||
|
self.context, build_request, request_spec, image,
|
||||||
|
admin_password, injected_file, requested_network,
|
||||||
|
block_device_mapping, tags=tags)
|
||||||
|
prepare_mock.assert_called_once_with(version=version)
|
||||||
|
kw = {'build_requests': build_request,
|
||||||
|
'request_specs': request_spec,
|
||||||
|
'image': jsonutils.to_primitive(image),
|
||||||
|
'admin_password': admin_password,
|
||||||
|
'injected_files': injected_file,
|
||||||
|
'requested_networks': requested_network,
|
||||||
|
'block_device_mapping': block_device_mapping,
|
||||||
|
'tags': tags}
|
||||||
|
cctxt_mock.cast.assert_called_once_with(
|
||||||
|
self.context, 'schedule_and_build_instances', **kw)
|
||||||
|
_test()
|
||||||
|
|
||||||
|
def test_schedule_and_build_instances_with_tags_cannot_send(self):
|
||||||
|
|
||||||
|
build_request = fake_build_request.fake_req_obj(self.context)
|
||||||
|
request_spec = objects.RequestSpec(
|
||||||
|
instance_uuid=build_request.instance_uuid)
|
||||||
|
image = {'fake_data': 'should_pass_silently'}
|
||||||
|
admin_password = 'fake_password'
|
||||||
|
injected_file = 'fake'
|
||||||
|
requested_network = None
|
||||||
|
block_device_mapping = None
|
||||||
|
tags = ['fake_tag']
|
||||||
|
cctxt_mock = mock.MagicMock()
|
||||||
|
|
||||||
|
@mock.patch.object(self.conductor.client, 'can_send_version',
|
||||||
|
return_value=False)
|
||||||
|
@mock.patch.object(self.conductor.client, 'prepare',
|
||||||
|
return_value=cctxt_mock)
|
||||||
|
def _test(prepare_mock, can_send_mock):
|
||||||
|
self.conductor.schedule_and_build_instances(
|
||||||
|
self.context, build_request, request_spec, image,
|
||||||
|
admin_password, injected_file, requested_network,
|
||||||
|
block_device_mapping, tags=tags)
|
||||||
|
prepare_mock.assert_called_once_with(version='1.16')
|
||||||
|
kw = {'build_requests': build_request,
|
||||||
|
'request_specs': request_spec,
|
||||||
|
'image': jsonutils.to_primitive(image),
|
||||||
|
'admin_password': admin_password,
|
||||||
|
'injected_files': injected_file,
|
||||||
|
'requested_networks': requested_network,
|
||||||
|
'block_device_mapping': block_device_mapping}
|
||||||
|
cctxt_mock.cast.assert_called_once_with(
|
||||||
|
self.context, 'schedule_and_build_instances', **kw)
|
||||||
|
_test()
|
||||||
|
|
||||||
|
|
||||||
class ConductorTaskAPITestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
class ConductorTaskAPITestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
||||||
"""Compute task API Tests."""
|
"""Compute task API Tests."""
|
||||||
|
|
Loading…
Reference in New Issue