Claims: Make sure move claims create a migration context records

This way we will be able to keep track of the resources that were
actually claimed for those resources (such as NUMA topology) which are
not just a simple sum but actually have state.

This puts in the infra for tracking the claimed resources, however 2
additional steps are needed to fully use this and make resource tracking
work for all instances when migrating:

1) We don't use the resources claimed and stashed in the Migration
context to do resource tracking. This will be a follow-on fix in the
resource tracker.

2) We still don't properly assign the newly claimed topology to the
instance once the migration is confirmed - this will be done in a follow
up patch that builds on this work.

Change-Id: Ib0532f0cbebc5568d9b94b74b7a1555fcf871df5
Partial-bug: #1417667
Related-blueprint: migration-fix-resource-tracking
This commit is contained in:
Nikola Dipanov 2015-08-28 16:14:05 +01:00
parent 9f61d1eb64
commit c458921729
5 changed files with 109 additions and 20 deletions

View File

@ -293,3 +293,17 @@ class MoveClaim(Claim):
self.context, self.context,
self.instance, instance_type=self.instance_type, self.instance, instance_type=self.instance_type,
image_meta=self.image_meta) image_meta=self.image_meta)
def create_migration_context(self):
if not self.migration:
# FIXME(ndipanov): Move this to a LOG.warn once Mitaka opens up
LOG.debug("Can't create a migration_context record without a "
"migration object specified.")
return
mig_context = objects.MigrationContext(
context=self.context, instance_uuid=self.instance.uuid,
migration_id=self.migration.id,
old_numa_topology=self.instance.numa_topology,
new_numa_topology=self.claimed_numa_topology)
return mig_context

View File

@ -238,6 +238,8 @@ class ResourceTracker(object):
image_meta, self, self.compute_node, image_meta, self, self.compute_node,
overhead=overhead, limits=limits) overhead=overhead, limits=limits)
claim.migration = migration claim.migration = migration
instance.migration_context = claim.create_migration_context()
instance.save()
# Mark the resources in-use for the resize landing on this # Mark the resources in-use for the resize landing on this
# compute host: # compute host:
@ -339,6 +341,8 @@ class ResourceTracker(object):
ctxt = context.elevated() ctxt = context.elevated()
self._update(ctxt) self._update(ctxt)
instance.drop_migration_context()
@utils.synchronized(COMPUTE_RESOURCE_SEMAPHORE) @utils.synchronized(COMPUTE_RESOURCE_SEMAPHORE)
def update_usage(self, context, instance): def update_usage(self, context, instance):
"""Update the resource usage and stats after a change in an """Update the resource usage and stats after a change in an

View File

@ -415,3 +415,25 @@ class MoveClaimTestCase(ClaimTestCase):
def test_abort(self, mock_get): def test_abort(self, mock_get):
claim = self._abort() claim = self._abort()
self.assertTrue(claim.tracker.rcalled) self.assertTrue(claim.tracker.rcalled)
@mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid',
return_value=objects.InstancePCIRequests(requests=[]))
def test_create_migration_context(self, mock_get):
numa_topology = objects.InstanceNUMATopology(
cells=[objects.InstanceNUMACell(
id=1, cpuset=set([1, 2]), memory=512)])
self.instance.numa_topology = None
claim = self._claim(numa_topology=numa_topology)
migration = objects.Migration(context=self.context, id=42)
claim.migration = migration
fake_mig_context = mock.Mock(spec=objects.MigrationContext)
with mock.patch('nova.objects.MigrationContext',
return_value=fake_mig_context) as ctxt_mock:
claim.create_migration_context()
ctxt_mock.assert_called_once_with(
context=self.context, instance_uuid=self.instance.uuid,
migration_id=42, old_numa_topology=None,
new_numa_topology=mock.ANY)
self.assertIsInstance(ctxt_mock.call_args[1]['new_numa_topology'],
objects.InstanceNUMATopology)
self.assertEqual(migration, claim.migration)

View File

@ -1090,9 +1090,10 @@ class _MoveClaimTestCase(BaseTrackerTestCase):
self.instance_type = self._fake_flavor_create() self.instance_type = self._fake_flavor_create()
self.claim_method = self.tracker._move_claim self.claim_method = self.tracker._move_claim
@mock.patch('nova.objects.Instance.save')
@mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid', @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid',
return_value=objects.InstancePCIRequests(requests=[])) return_value=objects.InstancePCIRequests(requests=[]))
def test_abort(self, mock_get): def test_abort(self, mock_get, mock_save):
try: try:
with self.claim_method(self.context, self.instance, with self.claim_method(self.context, self.instance,
self.instance_type, limits=self.limits): self.instance_type, limits=self.limits):
@ -1104,10 +1105,12 @@ class _MoveClaimTestCase(BaseTrackerTestCase):
self._assert(0, 'local_gb_used') self._assert(0, 'local_gb_used')
self._assert(0, 'vcpus_used') self._assert(0, 'vcpus_used')
self.assertEqual(0, len(self.tracker.tracked_migrations)) self.assertEqual(0, len(self.tracker.tracked_migrations))
mock_save.assert_called_once_with()
@mock.patch('nova.objects.Instance.save')
@mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid', @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid',
return_value=objects.InstancePCIRequests(requests=[])) return_value=objects.InstancePCIRequests(requests=[]))
def test_additive_claims(self, mock_get): def test_additive_claims(self, mock_get, mock_save):
limits = self._limits( limits = self._limits(
2 * FAKE_VIRT_MEMORY_WITH_OVERHEAD, 2 * FAKE_VIRT_MEMORY_WITH_OVERHEAD,
@ -1115,20 +1118,25 @@ class _MoveClaimTestCase(BaseTrackerTestCase):
2 * FAKE_VIRT_VCPUS) 2 * FAKE_VIRT_VCPUS)
self.claim_method( self.claim_method(
self.context, self.instance, self.instance_type, limits=limits) self.context, self.instance, self.instance_type, limits=limits)
mock_save.assert_called_once_with()
mock_save.reset_mock()
instance2 = self._fake_instance_obj() instance2 = self._fake_instance_obj()
self.claim_method( self.claim_method(
self.context, instance2, self.instance_type, limits=limits) self.context, instance2, self.instance_type, limits=limits)
mock_save.assert_called_once_with()
self._assert(2 * FAKE_VIRT_MEMORY_WITH_OVERHEAD, 'memory_mb_used') self._assert(2 * FAKE_VIRT_MEMORY_WITH_OVERHEAD, 'memory_mb_used')
self._assert(2 * FAKE_VIRT_LOCAL_GB, 'local_gb_used') self._assert(2 * FAKE_VIRT_LOCAL_GB, 'local_gb_used')
self._assert(2 * FAKE_VIRT_VCPUS, 'vcpus_used') self._assert(2 * FAKE_VIRT_VCPUS, 'vcpus_used')
@mock.patch('nova.objects.Instance.save')
@mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid', @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid',
return_value=objects.InstancePCIRequests(requests=[])) return_value=objects.InstancePCIRequests(requests=[]))
def test_revert(self, mock_get): def test_revert(self, mock_get, mock_save):
self.claim_method( self.claim_method(
self.context, self.instance, self.instance_type, self.context, self.instance, self.instance_type,
image_meta={}, limits=self.limits) image_meta={}, limits=self.limits)
mock_save.assert_called_once_with()
self.tracker.drop_move_claim(self.context, self.instance) self.tracker.drop_move_claim(self.context, self.instance)
self.assertEqual(0, len(self.tracker.tracked_instances)) self.assertEqual(0, len(self.tracker.tracked_instances))
@ -1137,20 +1145,23 @@ class _MoveClaimTestCase(BaseTrackerTestCase):
self._assert(0, 'local_gb_used') self._assert(0, 'local_gb_used')
self._assert(0, 'vcpus_used') self._assert(0, 'vcpus_used')
@mock.patch('nova.objects.Instance.save')
@mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid', @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid',
return_value=objects.InstancePCIRequests(requests=[])) return_value=objects.InstancePCIRequests(requests=[]))
def test_move_type_not_tracked(self, mock_get): def test_move_type_not_tracked(self, mock_get, mock_save):
self.claim_method(self.context, self.instance, self.claim_method(self.context, self.instance,
self.instance_type, limits=self.limits, move_type="evacuation") self.instance_type, limits=self.limits, move_type="evacuation")
mock_save.assert_called_once_with()
self._assert(0, 'memory_mb_used') self._assert(0, 'memory_mb_used')
self._assert(0, 'local_gb_used') self._assert(0, 'local_gb_used')
self._assert(0, 'vcpus_used') self._assert(0, 'vcpus_used')
self.assertEqual(0, len(self.tracker.tracked_migrations)) self.assertEqual(0, len(self.tracker.tracked_migrations))
@mock.patch('nova.objects.Instance.save')
@mock.patch.object(objects.Migration, 'save') @mock.patch.object(objects.Migration, 'save')
def test_existing_migration(self, save_mock): def test_existing_migration(self, save_mock, save_inst_mock):
migration = objects.Migration(self.context, migration = objects.Migration(self.context, id=42,
instance_uuid=self.instance.uuid, instance_uuid=self.instance.uuid,
status='accepted', status='accepted',
migration_type='evacuation') migration_type='evacuation')
@ -1161,6 +1172,7 @@ class _MoveClaimTestCase(BaseTrackerTestCase):
self.assertEqual("pre-migrating", migration.status) self.assertEqual("pre-migrating", migration.status)
self.assertEqual(0, len(self.tracker.tracked_migrations)) self.assertEqual(0, len(self.tracker.tracked_migrations))
save_mock.assert_called_once_with() save_mock.assert_called_once_with()
save_inst_mock.assert_called_once_with()
class ResizeClaimTestCase(_MoveClaimTestCase): class ResizeClaimTestCase(_MoveClaimTestCase):

View File

@ -304,6 +304,19 @@ _MIGRATION_INSTANCE_FIXTURES = {
), ),
} }
_MIGRATION_CONTEXT_FIXTURES = {
'f4f0bfea-fe7e-4264-b598-01cb13ef1997': objects.MigrationContext(
instance_uuid='f4f0bfea-fe7e-4264-b598-01cb13ef1997',
migration_id=3,
new_numa_topology=None,
old_numa_topology=None),
'c17741a5-6f3d-44a8-ade8-773dc8c29124': objects.MigrationContext(
instance_uuid='c17741a5-6f3d-44a8-ade8-773dc8c29124',
migration_id=3,
new_numa_topology=None,
old_numa_topology=None),
}
def overhead_zero(instance): def overhead_zero(instance):
# Emulate that the driver does not adjust the memory # Emulate that the driver does not adjust the memory
@ -1163,6 +1176,7 @@ class TestInstanceClaim(BaseTestCase):
self.assertEqualNUMAHostTopology(expected_numa, new_numa) self.assertEqualNUMAHostTopology(expected_numa, new_numa)
@mock.patch('nova.objects.Instance.save')
@mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node') @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
@mock.patch('nova.objects.Instance.get_by_uuid') @mock.patch('nova.objects.Instance.get_by_uuid')
@mock.patch('nova.objects.InstanceList.get_by_host_and_node') @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
@ -1195,11 +1209,12 @@ class TestMoveClaim(BaseTestCase):
self.rt.update_available_resource(self.ctx) self.rt.update_available_resource(self.ctx)
def register_mocks(self, pci_mock, inst_list_mock, inst_by_uuid, def register_mocks(self, pci_mock, inst_list_mock, inst_by_uuid,
migr_mock): migr_mock, inst_save_mock):
pci_mock.return_value = objects.InstancePCIRequests(requests=[]) pci_mock.return_value = objects.InstancePCIRequests(requests=[])
self.inst_list_mock = inst_list_mock self.inst_list_mock = inst_list_mock
self.inst_by_uuid = inst_by_uuid self.inst_by_uuid = inst_by_uuid
self.migr_mock = migr_mock self.migr_mock = migr_mock
self.inst_save_mock = inst_save_mock
def audit(self, rt, instances, migrations, migr_inst): def audit(self, rt, instances, migrations, migr_inst):
self.inst_list_mock.return_value = \ self.inst_list_mock.return_value = \
@ -1232,62 +1247,83 @@ class TestMoveClaim(BaseTestCase):
@mock.patch('nova.objects.Flavor.get_by_id') @mock.patch('nova.objects.Flavor.get_by_id')
def test_claim(self, flavor_mock, pci_mock, inst_list_mock, inst_by_uuid, def test_claim(self, flavor_mock, pci_mock, inst_list_mock, inst_by_uuid,
migr_mock): migr_mock, inst_save_mock):
"""Resize self.instance and check that the expected quantities of each """Resize self.instance and check that the expected quantities of each
resource have been consumed. resource have been consumed.
""" """
self.register_mocks(pci_mock, inst_list_mock, inst_by_uuid, migr_mock) self.register_mocks(pci_mock, inst_list_mock, inst_by_uuid, migr_mock,
inst_save_mock)
self.driver_mock.get_host_ip_addr.return_value = "fake-ip" self.driver_mock.get_host_ip_addr.return_value = "fake-ip"
flavor_mock.return_value = objects.Flavor(**self.flavor) flavor_mock.return_value = objects.Flavor(**self.flavor)
mig_context_obj = _MIGRATION_CONTEXT_FIXTURES[self.instance.uuid]
expected = copy.deepcopy(self.rt.compute_node) expected = copy.deepcopy(self.rt.compute_node)
self.adjust_expected(expected, self.flavor) self.adjust_expected(expected, self.flavor)
with mock.patch.object(self.rt, '_create_migration') as migr_mock: create_mig_mock = mock.patch.object(self.rt, '_create_migration')
mig_ctxt_mock = mock.patch('nova.objects.MigrationContext',
return_value=mig_context_obj)
with create_mig_mock as migr_mock, mig_ctxt_mock as ctxt_mock:
migr_mock.return_value = _MIGRATION_FIXTURES['source-only'] migr_mock.return_value = _MIGRATION_FIXTURES['source-only']
claim = self.rt.resize_claim( claim = self.rt.resize_claim(
self.ctx, self.instance, self.flavor, None) self.ctx, self.instance, self.flavor, None)
self.assertEqual(1, ctxt_mock.call_count)
self.assertIsInstance(claim, claims.MoveClaim) self.assertIsInstance(claim, claims.MoveClaim)
inst_save_mock.assert_called_once_with()
self.assertTrue(obj_base.obj_equal_prims(expected, self.assertTrue(obj_base.obj_equal_prims(expected,
self.rt.compute_node)) self.rt.compute_node))
def test_same_host(self, pci_mock, inst_list_mock, inst_by_uuid, def test_same_host(self, pci_mock, inst_list_mock, inst_by_uuid,
migr_mock): migr_mock, inst_save_mock):
"""Resize self.instance to the same host but with a different flavor. """Resize self.instance to the same host but with a different flavor.
Then abort the claim. Check that the same amount of resources are Then abort the claim. Check that the same amount of resources are
available afterwards as we started with. available afterwards as we started with.
""" """
self.register_mocks(pci_mock, inst_list_mock, inst_by_uuid, migr_mock) self.register_mocks(pci_mock, inst_list_mock, inst_by_uuid, migr_mock,
inst_save_mock)
migr_obj = _MIGRATION_FIXTURES['source-and-dest'] migr_obj = _MIGRATION_FIXTURES['source-and-dest']
self.instance = _MIGRATION_INSTANCE_FIXTURES[migr_obj['instance_uuid']] self.instance = _MIGRATION_INSTANCE_FIXTURES[migr_obj['instance_uuid']]
self.instance._context = self.ctx
mig_context_obj = _MIGRATION_CONTEXT_FIXTURES[self.instance.uuid]
with mock.patch.object(self.instance, 'save'): with mock.patch.object(self.instance, 'save'):
self.rt.instance_claim(self.ctx, self.instance, None) self.rt.instance_claim(self.ctx, self.instance, None)
expected = copy.deepcopy(self.rt.compute_node) expected = copy.deepcopy(self.rt.compute_node)
with mock.patch.object(self.rt, '_create_migration') as migr_mock: create_mig_mock = mock.patch.object(self.rt, '_create_migration')
mig_ctxt_mock = mock.patch('nova.objects.MigrationContext',
return_value=mig_context_obj)
with create_mig_mock as migr_mock, mig_ctxt_mock as ctxt_mock:
migr_mock.return_value = migr_obj migr_mock.return_value = migr_obj
claim = self.rt.resize_claim(self.ctx, self.instance, claim = self.rt.resize_claim(self.ctx, self.instance,
_INSTANCE_TYPE_OBJ_FIXTURES[1], None) _INSTANCE_TYPE_OBJ_FIXTURES[1], None)
self.assertEqual(1, ctxt_mock.call_count)
self.audit(self.rt, [self.instance], [migr_obj], self.instance) self.audit(self.rt, [self.instance], [migr_obj], self.instance)
inst_save_mock.assert_called_once_with()
self.assertNotEqual(expected, self.rt.compute_node) self.assertNotEqual(expected, self.rt.compute_node)
claim.abort() claim.instance.migration_context = mig_context_obj
self.assertTrue(obj_base.obj_equal_prims(expected, with mock.patch('nova.objects.MigrationContext._destroy') as destroy_m:
self.rt.compute_node)) claim.abort()
self.assertTrue(obj_base.obj_equal_prims(expected,
self.rt.compute_node))
destroy_m.assert_called_once_with(self.ctx, claim.instance.uuid)
def test_revert_reserve_source( def test_revert_reserve_source(
self, pci_mock, inst_list_mock, inst_by_uuid, migr_mock): self, pci_mock, inst_list_mock, inst_by_uuid, migr_mock,
inst_save_mock):
"""Check that the source node of an instance migration reserves """Check that the source node of an instance migration reserves
resources until the migration has completed, even if the migration is resources until the migration has completed, even if the migration is
reverted. reverted.
""" """
self.register_mocks(pci_mock, inst_list_mock, inst_by_uuid, migr_mock) self.register_mocks(pci_mock, inst_list_mock, inst_by_uuid, migr_mock,
inst_save_mock)
# Get our migrations, instances and itypes in a row # Get our migrations, instances and itypes in a row
src_migr = _MIGRATION_FIXTURES['source-only'] src_migr = _MIGRATION_FIXTURES['source-only']
@ -1342,8 +1378,9 @@ class TestMoveClaim(BaseTestCase):
src_rt.compute_node)) src_rt.compute_node))
def test_dupe_filter(self, pci_mock, inst_list_mock, inst_by_uuid, def test_dupe_filter(self, pci_mock, inst_list_mock, inst_by_uuid,
migr_mock): migr_mock, inst_save_mock):
self.register_mocks(pci_mock, inst_list_mock, inst_by_uuid, migr_mock) self.register_mocks(pci_mock, inst_list_mock, inst_by_uuid, migr_mock,
inst_save_mock)
migr_obj = _MIGRATION_FIXTURES['source-and-dest'] migr_obj = _MIGRATION_FIXTURES['source-and-dest']
# This is good enough to prevent a lazy-load; value is unimportant # This is good enough to prevent a lazy-load; value is unimportant