Merge "Update InstanceNUMACell version in more cases" into stable/2024.1
This commit is contained in:
@@ -198,26 +198,78 @@ class InstanceNUMATopology(base.NovaObject,
|
|||||||
# come from instance_extra or request_spec too.
|
# come from instance_extra or request_spec too.
|
||||||
update_db = False
|
update_db = False
|
||||||
for cell in obj.cells:
|
for cell in obj.cells:
|
||||||
if len(cell.cpuset) == 0:
|
version = versionutils.convert_version_to_tuple(cell.VERSION)
|
||||||
|
|
||||||
|
if version < (1, 4):
|
||||||
|
LOG.warning(
|
||||||
|
"InstanceNUMACell %s with version %s for instance %s has "
|
||||||
|
"too old version in the DB, don't know how to update, "
|
||||||
|
"ignoring.", cell, cell.VERSION, obj.instance_uuid)
|
||||||
continue
|
continue
|
||||||
# NOTE(gibi): This data migration populates the pcpuset field that
|
|
||||||
# is new in version 1.5. However below we bump the object version
|
if (version >= (1, 5) and
|
||||||
# to 1.6 directly. This is intentional. The version 1.6 introduced
|
cell.cpu_policy == obj_fields.CPUAllocationPolicy.DEDICATED and
|
||||||
# a new possible value 'mixed' for the cpu_policy field. As that
|
(cell.cpuset or not cell.pcpuset)
|
||||||
# is a forward compatible change we don't have a specific data
|
):
|
||||||
# migration for it. But we also don't have an automated way to bump
|
LOG.warning(
|
||||||
# old object versions from 1.5 to 1.6. So we do it here just to
|
"InstanceNUMACell %s with version %s is inconsistent as "
|
||||||
# avoid inconsistency between data and version in the DB.
|
"the version is 1.5 or greater, cpu_policy is dedicated, "
|
||||||
if cell.cpu_policy == obj_fields.CPUAllocationPolicy.DEDICATED:
|
"but cpuset is not empty or pcpuset is empty.",
|
||||||
cell.pcpuset = cell.cpuset
|
cell, cell.VERSION)
|
||||||
cell.cpuset = set()
|
continue
|
||||||
cell.VERSION = '1.6'
|
|
||||||
update_db = True
|
# NOTE(gibi): The data migration between 1.4. and 1.5 populates the
|
||||||
else:
|
# pcpuset field that is new in version 1.5. However below we update
|
||||||
if 'pcpuset' not in cell:
|
# the object version to 1.6 directly. This is intentional. The
|
||||||
|
# version 1.6 introduced a new possible value 'mixed' for the
|
||||||
|
# cpu_policy field. As that is a forward compatible change we don't
|
||||||
|
# have a specific data migration for it. But we also don't have an
|
||||||
|
# automated way to update old object versions from 1.5 to 1.6. So
|
||||||
|
# we do it here just to avoid inconsistency between data and
|
||||||
|
# version in the DB.
|
||||||
|
if version < (1, 6):
|
||||||
|
if cell.cpu_policy == obj_fields.CPUAllocationPolicy.DEDICATED:
|
||||||
|
if "pcpuset" not in cell or not cell.pcpuset:
|
||||||
|
# this cell was never migrated to 1.6, migrate it.
|
||||||
|
cell.pcpuset = cell.cpuset
|
||||||
|
cell.cpuset = set()
|
||||||
|
cell.VERSION = '1.6'
|
||||||
|
update_db = True
|
||||||
|
else:
|
||||||
|
# This data was already migrated to 1.6 format but the
|
||||||
|
# version string wasn't updated to 1.6. This happened
|
||||||
|
# before the fix
|
||||||
|
# https://bugs.launchpad.net/nova/+bug/2097360
|
||||||
|
# Only update the version string.
|
||||||
|
cell.VERSION = '1.6'
|
||||||
|
update_db = True
|
||||||
|
elif cell.cpu_policy in (
|
||||||
|
None, obj_fields.CPUAllocationPolicy.SHARED):
|
||||||
|
# no data migration needed just add the new field and
|
||||||
|
# stamp the new version in the DB
|
||||||
cell.pcpuset = set()
|
cell.pcpuset = set()
|
||||||
cell.VERSION = '1.6'
|
cell.VERSION = '1.6'
|
||||||
update_db = True
|
update_db = True
|
||||||
|
else: # obj_fields.CPUAllocationPolicy.MIXED
|
||||||
|
# This means the cell data already got updated to the 1.6
|
||||||
|
# content as MIXED only supported with 1.6 but the version
|
||||||
|
# was not updated to 1.6.
|
||||||
|
# We should not do the data migration as that would trample
|
||||||
|
# the pcpuset field. Just stamp the 1.6 version in the DB
|
||||||
|
# and hope for the best.
|
||||||
|
LOG.warning(
|
||||||
|
"InstanceNUMACell %s with version %s for instance %s "
|
||||||
|
"has older than 1.6 version in the DB but using the "
|
||||||
|
"1.6 feature CPUAllocationPolicy.MIXED. So nova "
|
||||||
|
"assumes that the data is in 1.6 format and only the "
|
||||||
|
"version string is old. Correcting the version string "
|
||||||
|
"in the DB.", cell, cell.VERSION, obj.instance_uuid)
|
||||||
|
cell.VERSION = '1.6'
|
||||||
|
update_db = True
|
||||||
|
|
||||||
|
# When the next ovo version 1.7 is added it needs to be handed
|
||||||
|
# here to do any migration if needed and to ensure the version in
|
||||||
|
# the DB is stamped to 1.7
|
||||||
|
|
||||||
return update_db
|
return update_db
|
||||||
|
|
||||||
|
@@ -363,6 +363,7 @@ class _TestInstanceNUMATopology(object):
|
|||||||
fake_topo_obj = copy.deepcopy(fake_topo_obj_w_cell_v1_4)
|
fake_topo_obj = copy.deepcopy(fake_topo_obj_w_cell_v1_4)
|
||||||
for cell in fake_topo_obj.cells:
|
for cell in fake_topo_obj.cells:
|
||||||
cell.cpu_policy = objects.fields.CPUAllocationPolicy.DEDICATED
|
cell.cpu_policy = objects.fields.CPUAllocationPolicy.DEDICATED
|
||||||
|
cell.VERSION = '1.4'
|
||||||
|
|
||||||
numa_topology = objects.InstanceNUMATopology.obj_from_db_obj(
|
numa_topology = objects.InstanceNUMATopology.obj_from_db_obj(
|
||||||
self.context, fake_instance_uuid, fake_topo_obj._to_json())
|
self.context, fake_instance_uuid, fake_topo_obj._to_json())
|
||||||
@@ -402,6 +403,7 @@ class _TestInstanceNUMATopology(object):
|
|||||||
fake_topo_obj = copy.deepcopy(fake_topo_obj_w_cell_v1_4)
|
fake_topo_obj = copy.deepcopy(fake_topo_obj_w_cell_v1_4)
|
||||||
for cell in fake_topo_obj.cells:
|
for cell in fake_topo_obj.cells:
|
||||||
cell.cpu_policy = objects.fields.CPUAllocationPolicy.SHARED
|
cell.cpu_policy = objects.fields.CPUAllocationPolicy.SHARED
|
||||||
|
cell.VERSION = '1.4'
|
||||||
|
|
||||||
numa_topology = objects.InstanceNUMATopology.obj_from_db_obj(
|
numa_topology = objects.InstanceNUMATopology.obj_from_db_obj(
|
||||||
self.context, fake_instance_uuid, fake_topo_obj._to_json())
|
self.context, fake_instance_uuid, fake_topo_obj._to_json())
|
||||||
@@ -412,7 +414,7 @@ class _TestInstanceNUMATopology(object):
|
|||||||
self.assertEqual(topo_cell.cpuset, obj_cell.cpuset)
|
self.assertEqual(topo_cell.cpuset, obj_cell.cpuset)
|
||||||
self.assertEqual(set(), obj_cell.pcpuset)
|
self.assertEqual(set(), obj_cell.pcpuset)
|
||||||
|
|
||||||
def test__migrate_legacy_dedicated_instance_cpuset(self):
|
def test__migrate_legacy_dedicated_instance_cpuset_dedicate_1_4(self):
|
||||||
# Create a topology with a cell on latest version. Would be nice
|
# Create a topology with a cell on latest version. Would be nice
|
||||||
# to create one the old 1.4 cell version directly but that is only
|
# to create one the old 1.4 cell version directly but that is only
|
||||||
# possible indirectly as done below.
|
# possible indirectly as done below.
|
||||||
@@ -446,7 +448,118 @@ class _TestInstanceNUMATopology(object):
|
|||||||
# pcpuset
|
# pcpuset
|
||||||
self.assertEqual(set(), topo_loaded.cells[0].cpuset)
|
self.assertEqual(set(), topo_loaded.cells[0].cpuset)
|
||||||
self.assertEqual({0, 1}, topo_loaded.cells[0].pcpuset)
|
self.assertEqual({0, 1}, topo_loaded.cells[0].pcpuset)
|
||||||
# and the version is bumped to 1.6
|
# and the version is updated to 1.6
|
||||||
|
self.assertEqual('1.6', topo_loaded.cells[0].VERSION)
|
||||||
|
|
||||||
|
def test__migrate_legacy_dedicated_instance_cpuset_shared_1_4(self):
|
||||||
|
# Create a topology with a cell on latest version. Would be nice
|
||||||
|
# to create one the old 1.4 cell version directly but that is only
|
||||||
|
# possible indirectly as done below.
|
||||||
|
topo = objects.InstanceNUMATopology(
|
||||||
|
instance_uuid=fake_instance_uuid,
|
||||||
|
cells=[
|
||||||
|
objects.InstanceNUMACell(id=0, cpuset={0, 1}, pcpuset=set()),
|
||||||
|
])
|
||||||
|
topo.cells[0].cpu_policy = objects.fields.CPUAllocationPolicy.SHARED
|
||||||
|
|
||||||
|
# Use the builtin backlevelling logic to pull it back to old cell
|
||||||
|
# version
|
||||||
|
topo_with_cell_1_4 = topo.obj_to_primitive(
|
||||||
|
target_version='1.3', version_manifest={'InstanceNUMACell': '1.4'})
|
||||||
|
|
||||||
|
# Just check that the backlevelling works, and we have a cell with
|
||||||
|
# version and data on 1.4 level
|
||||||
|
cell_1_4_primitive = topo_with_cell_1_4['nova_object.data']['cells'][0]
|
||||||
|
self.assertEqual('1.4', cell_1_4_primitive['nova_object.version'])
|
||||||
|
self.assertEqual(
|
||||||
|
(0, 1), cell_1_4_primitive['nova_object.data']['cpuset'])
|
||||||
|
self.assertNotIn('pcpuset', cell_1_4_primitive['nova_object.data'])
|
||||||
|
|
||||||
|
# Now simulate that such old data is loaded from the DB and migrated
|
||||||
|
# from 1.4 to 1.6 by the data migration
|
||||||
|
topo_loaded = objects.InstanceNUMATopology.obj_from_db_obj(
|
||||||
|
self.context, fake_instance_uuid,
|
||||||
|
jsonutils.dumps(topo_with_cell_1_4))
|
||||||
|
|
||||||
|
# In place data migration did not move the data as the cpu_policy is
|
||||||
|
# shared
|
||||||
|
self.assertEqual({0, 1}, topo_loaded.cells[0].cpuset)
|
||||||
|
self.assertEqual(set(), topo_loaded.cells[0].pcpuset)
|
||||||
|
# but the version is updated to 1.6
|
||||||
|
self.assertEqual('1.6', topo_loaded.cells[0].VERSION)
|
||||||
|
|
||||||
|
def test__migrate_legacy_dedicated_instance_cpuset_dedicated_half_migrated(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
# Before the fix for https://bugs.launchpad.net/nova/+bug/2097360
|
||||||
|
# landed Nova only half migrated the InstanceNUMACell from 1.4 to 1.6
|
||||||
|
# by doing the data move but not updating the version string in the DB.
|
||||||
|
# This test case ensures that if such migration happened before the fix
|
||||||
|
# was deployed then Nova fixed the DB content on the next load as well.
|
||||||
|
|
||||||
|
# Create a topology with a cell on latest version. Would be nice
|
||||||
|
# to create one the old 1.4 cell version directly but that is only
|
||||||
|
# possible indirectly as done below.
|
||||||
|
topo = objects.InstanceNUMATopology(
|
||||||
|
instance_uuid=fake_instance_uuid,
|
||||||
|
cells=[
|
||||||
|
objects.InstanceNUMACell(id=0, cpuset=set(), pcpuset={0, 1}),
|
||||||
|
])
|
||||||
|
topo.cells[0].cpu_policy = objects.fields.CPUAllocationPolicy.DEDICATED
|
||||||
|
|
||||||
|
# simulate the half done migration by pulling back the version string
|
||||||
|
# in the primitive form to 1.4 while keeping the data on 1.6 format.
|
||||||
|
topo_primitive = topo.obj_to_primitive()
|
||||||
|
cell_primitive = topo_primitive['nova_object.data']['cells'][0]
|
||||||
|
self.assertEqual('1.6', cell_primitive['nova_object.version'])
|
||||||
|
cell_primitive['nova_object.version'] = '1.4'
|
||||||
|
|
||||||
|
topo_loaded = objects.InstanceNUMATopology.obj_from_db_obj(
|
||||||
|
self.context, fake_instance_uuid, jsonutils.dumps(topo_primitive))
|
||||||
|
|
||||||
|
# the data did not change
|
||||||
|
self.assertEqual(set(), topo_loaded.cells[0].cpuset)
|
||||||
|
self.assertEqual({0, 1}, topo_loaded.cells[0].pcpuset)
|
||||||
|
# but the version is updated to 1.6
|
||||||
|
self.assertEqual('1.6', topo_loaded.cells[0].VERSION)
|
||||||
|
|
||||||
|
def test__migrate_legacy_dedicated_instance_cpuset_mixed_1_4(self):
|
||||||
|
# Before the fix for https://bugs.launchpad.net/nova/+bug/2097360
|
||||||
|
# landed Nova only half migrated the InstanceNUMACell from 1.4 to 1.6
|
||||||
|
# by doing the data move but not updating the version string in the DB.
|
||||||
|
# After this the instance is resized to flavor with mixed cpu_policy
|
||||||
|
# then there is a good chance that the DB has version string 1.4 but
|
||||||
|
# the data is on 1.6 format with the cpu_policy set to mixed.
|
||||||
|
# This test case ensures that if such situation happened before the fix
|
||||||
|
# was deployed then Nova fixed the DB content on the next load as well.
|
||||||
|
|
||||||
|
# Create a topology with a cell on latest version. Would be nice
|
||||||
|
# to create one the old 1.4 cell version directly but that is only
|
||||||
|
# possible indirectly as done below.
|
||||||
|
topo = objects.InstanceNUMATopology(
|
||||||
|
instance_uuid=fake_instance_uuid,
|
||||||
|
cells=[
|
||||||
|
objects.InstanceNUMACell(id=0, cpuset={0, 1}, pcpuset={2, 3}),
|
||||||
|
])
|
||||||
|
topo.cells[0].cpu_policy = objects.fields.CPUAllocationPolicy.MIXED
|
||||||
|
|
||||||
|
# simulate the half done migration by pulling back the version string
|
||||||
|
# in the primitive form to 1.4 while keeping the data on 1.6 format.
|
||||||
|
topo_primitive = topo.obj_to_primitive()
|
||||||
|
cell_primitive = topo_primitive['nova_object.data']['cells'][0]
|
||||||
|
self.assertEqual('1.6', cell_primitive['nova_object.version'])
|
||||||
|
cell_primitive['nova_object.version'] = '1.4'
|
||||||
|
|
||||||
|
topo_loaded = objects.InstanceNUMATopology.obj_from_db_obj(
|
||||||
|
self.context, fake_instance_uuid, jsonutils.dumps(topo_primitive))
|
||||||
|
|
||||||
|
# the data did not change
|
||||||
|
self.assertEqual({0, 1}, topo_loaded.cells[0].cpuset)
|
||||||
|
self.assertEqual({2, 3}, topo_loaded.cells[0].pcpuset)
|
||||||
|
self.assertEqual(
|
||||||
|
objects.fields.CPUAllocationPolicy.MIXED,
|
||||||
|
topo_loaded.cells[0].cpu_policy)
|
||||||
|
# but the version is updated to 1.6
|
||||||
self.assertEqual('1.6', topo_loaded.cells[0].VERSION)
|
self.assertEqual('1.6', topo_loaded.cells[0].VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -722,6 +722,9 @@ class _TestRequestSpecObject(object):
|
|||||||
id=1, cpuset={3, 4}, memory=512, cpu_policy="dedicated"),
|
id=1, cpuset={3, 4}, memory=512, cpu_policy="dedicated"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
for cell in numa_topology.cells:
|
||||||
|
cell.VERSION = '1.4'
|
||||||
|
|
||||||
spec_obj = fake_request_spec.fake_spec_obj()
|
spec_obj = fake_request_spec.fake_spec_obj()
|
||||||
spec_obj.numa_topology = numa_topology
|
spec_obj.numa_topology = numa_topology
|
||||||
fake_spec = fake_request_spec.fake_db_spec(spec_obj)
|
fake_spec = fake_request_spec.fake_db_spec(spec_obj)
|
||||||
|
Reference in New Issue
Block a user