Skip to content

Commit 34e0c02

Browse files
author
Balazs Gibizer
committed
Store old_flavor already on source host during resize
During resize, on the source host, in resize_instance(), the instance.host and .node is updated to point to the destination host. This indicates to the source host's resource tracker that the allocation of this instance does not need to be tracked as an instance but as an outbound migration instead. However for the source host's resource tracker to do that it, needs to use the instance.old_flavor. Unfortunately the instance.old_flavor is only set during finish_resize() on the destination host. (resize_instance cast to the finish_resize). So it is possible that a running resize_instance() set the instance.host to point to the destination and then before the finish_resize could set the old_flavor an update_available_resources periodic runs on the source host. This causes that the allocation of this instance is not tracked as an instance as the instance.host point to the destination but it is not tracked as a migration either as the instance.old_flavor is not yet set. So the allocation on the source host is simply dropped by the periodic job. When such migration is confirmed the confirm_resize() tries to drop the same resource allocation again but fails as the pinned CPUs of the instance already freed. When such migration is reverted instead, then revert succeeds but the source host resource allocation will not contain the resource allocation of the instance until the next update_available_resources periodic runs and corrects it. This does not affect resources tracked exclusively in placement (e.g. VCPU, MEMORY_MB, DISK_GB) but it does affect NUMA related resource that are still tracked in the resource tracker (e.g. huge pages, pinned CPUs). This patch moves the instance.old_flavor setting to the source node to the same transaction that sets the instance.host to point to the destination host. Hence solving the race condition. Change-Id: Ic0d6c59147abe5e094e8f13e0d3523b178daeba9 Closes-Bug: #1944759 (cherry picked from commit b841e55) (cherry picked from commit d4edcd6) (cherry picked from commit c8b04d1)
1 parent 0b1fa9b commit 34e0c02

File tree

2 files changed

+25
-27
lines changed

2 files changed

+25
-27
lines changed

nova/compute/manager.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5654,6 +5654,14 @@ def _resize_instance(self, context, instance, image,
56545654

56555655
instance.host = migration.dest_compute
56565656
instance.node = migration.dest_node
5657+
# NOTE(gibi): as the instance now tracked on the destination we
5658+
# have to make sure that the source compute resource track can
5659+
# track this instance as a migration. For that the resource tracker
5660+
# needs to see the old_flavor set on the instance. The old_flavor
5661+
# setting used to be done on the destination host in finish_resize
5662+
# but that is racy with a source host update_available_resource
5663+
# periodic run
5664+
instance.old_flavor = instance.flavor
56575665
instance.task_state = task_states.RESIZE_MIGRATED
56585666
instance.save(expected_task_state=task_states.RESIZE_MIGRATING)
56595667

@@ -5767,6 +5775,10 @@ def _finish_resize(self, context, instance, migration, disk_info,
57675775
# to ACTIVE for backwards compatibility
57685776
old_vm_state = instance.system_metadata.get('old_vm_state',
57695777
vm_states.ACTIVE)
5778+
# NOTE(gibi): this is already set by the resize_instance on the source
5779+
# node before calling finish_resize on destination but during upgrade
5780+
# it can be that the source node is not having the fix for bug 1944759
5781+
# yet. This assignment can be removed in Z release.
57705782
instance.old_flavor = old_flavor
57715783

57725784
if old_instance_type_id != new_instance_type_id:

nova/tests/functional/libvirt/test_numa_servers.py

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -766,10 +766,10 @@ def inject_periodic_to_finish_resize(*args, **kwargs):
766766

767767
dst_host = server['OS-EXT-SRV-ATTR:host']
768768

769-
# This is a resource accounting bug, we should have 2 cpus pinned on
770-
# both computes. The source should have it due to the outbound
771-
# migration and the destination due to the instance running there
772-
self._assert_pinned_cpus(src_host, 0)
769+
# we have 2 cpus pinned on both computes. The source should have it
770+
# due to the outbound migration and the destination due to the
771+
# instance running there
772+
self._assert_pinned_cpus(src_host, 2)
773773
self._assert_pinned_cpus(dst_host, 2)
774774

775775
return server, src_host, dst_host
@@ -781,30 +781,17 @@ def test_resize_confirm_bug_1944759(self):
781781
# Now confirm the resize
782782
post = {'confirmResize': None}
783783

784-
# FIXME(gibi): This is bug 1944759 where during resize, on the source
785-
# node the resize_instance() call at the point of calling finish_resize
786-
# overlaps with a update_available_resources() periodic job. This
787-
# causes that the periodic job will not track the migration nor the
788-
# instance and therefore freeing the resource allocation. Then when
789-
# later the resize is confirmed the confirm_resize on the source
790-
# compute also wants to free up the resources, the pinned CPUs, and it
791-
# fails as they are already freed.
792-
exc = self.assertRaises(
793-
client.OpenStackApiException,
794-
self.api.post_server_action, server['id'], post
795-
)
796-
self.assertEqual(500, exc.response.status_code)
797-
self.assertIn('CPUUnpinningInvalid', str(exc))
784+
self.api.post_server_action(server['id'], post)
785+
self._wait_for_state_change(server, 'ACTIVE')
798786

799-
# confirm failed above but the resource allocation reflects that the
800-
# VM is running on the dest node
787+
# the resource allocation reflects that the VM is running on the dest
788+
# node
801789
self._assert_pinned_cpus(src_host, 0)
802790
self._assert_pinned_cpus(dst_host, 2)
803791

792+
# and running periodics does not break it either
804793
self._run_periodics()
805794

806-
# and such allocation situation is stable so as a recovery the VM
807-
# can be reset-state to ACTIVE without problem.
808795
self._assert_pinned_cpus(src_host, 0)
809796
self._assert_pinned_cpus(dst_host, 2)
810797

@@ -820,15 +807,14 @@ def test_resize_revert_bug_1944759(self):
820807
self.api.post_server_action(server['id'], post)
821808
self._wait_for_state_change(server, 'ACTIVE')
822809

823-
# This is a resource accounting bug. After the revert the source host
824-
# should have 2 cpus pinned due to the instance.
825-
self._assert_pinned_cpus(src_host, 0)
810+
# After the revert the source host should have 2 cpus pinned due to
811+
# the instance.
812+
self._assert_pinned_cpus(src_host, 2)
826813
self._assert_pinned_cpus(dst_host, 0)
827814

828-
# running the periodic job will fix the resource accounting
815+
# running the periodic job will not break it either
829816
self._run_periodics()
830817

831-
# this is now correct
832818
self._assert_pinned_cpus(src_host, 2)
833819
self._assert_pinned_cpus(dst_host, 0)
834820

0 commit comments

Comments
 (0)