Merge "objects: Add migrate-on-load behavior for legacy NUMA objects"
This commit is contained in:
commit
7dfe999180
|
@ -209,6 +209,12 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
|
|||
init_x_ratio = getattr(CONF, 'initial_%s' % key)
|
||||
value = r if r else init_x_ratio
|
||||
online_updates[key] = value
|
||||
elif key == 'numa_topology' and value and (
|
||||
'nova_object.name' not in value):
|
||||
# TODO(stephenfin): Remove this online migration in X or later,
|
||||
# once this has bedded in
|
||||
value = objects.NUMATopology.from_legacy_object(value)
|
||||
online_updates[key] = value
|
||||
elif key == 'mapped':
|
||||
value = 0 if value is None else value
|
||||
|
||||
|
|
|
@ -901,9 +901,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
|
|||
if db_topology is None:
|
||||
self.numa_topology = None
|
||||
elif db_topology is not _NO_DATA_SENTINEL:
|
||||
self.numa_topology = \
|
||||
objects.InstanceNUMATopology.obj_from_db_obj(self.uuid,
|
||||
db_topology)
|
||||
self.numa_topology = objects.InstanceNUMATopology.obj_from_db_obj(
|
||||
self._context, self.uuid, db_topology)
|
||||
else:
|
||||
try:
|
||||
self.numa_topology = \
|
||||
|
|
|
@ -70,17 +70,6 @@ class InstanceNUMACell(base.NovaEphemeralObject,
|
|||
def __len__(self):
|
||||
return len(self.cpuset)
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, data_dict):
|
||||
# NOTE(sahid): Used as legacy, could be renamed in
|
||||
# _legacy_from_dict_ to the future to avoid confusing.
|
||||
cpuset = hardware.parse_cpu_spec(data_dict.get('cpus', ''))
|
||||
memory = data_dict.get('mem', {}).get('total', 0)
|
||||
cell_id = data_dict.get('id')
|
||||
pagesize = data_dict.get('pagesize')
|
||||
return cls(id=cell_id, cpuset=cpuset,
|
||||
memory=memory, pagesize=pagesize)
|
||||
|
||||
@property
|
||||
def siblings(self):
|
||||
cpu_list = sorted(list(self.cpuset))
|
||||
|
@ -146,29 +135,47 @@ class InstanceNUMATopology(base.NovaObject,
|
|||
}
|
||||
|
||||
@classmethod
|
||||
def obj_from_primitive(cls, primitive, context=None):
|
||||
if 'nova_object.name' in primitive:
|
||||
obj_topology = super(InstanceNUMATopology, cls).obj_from_primitive(
|
||||
primitive, context=None)
|
||||
else:
|
||||
# NOTE(sahid): This compatibility code needs to stay until we can
|
||||
# guarantee that there are no cases of the old format stored in
|
||||
# the database (or forever, if we can never guarantee that).
|
||||
obj_topology = InstanceNUMATopology._from_dict(primitive)
|
||||
obj_topology.id = 0
|
||||
return obj_topology
|
||||
|
||||
@classmethod
|
||||
def obj_from_db_obj(cls, instance_uuid, db_obj):
|
||||
def obj_from_db_obj(cls, context, instance_uuid, db_obj):
|
||||
primitive = jsonutils.loads(db_obj)
|
||||
obj_topology = cls.obj_from_primitive(primitive)
|
||||
|
||||
if 'nova_object.name' not in db_obj:
|
||||
obj_topology.instance_uuid = instance_uuid
|
||||
# No benefit to store a list of changed fields
|
||||
obj_topology.obj_reset_changes()
|
||||
if 'nova_object.name' in primitive:
|
||||
obj = cls.obj_from_primitive(primitive)
|
||||
else:
|
||||
obj = cls._migrate_legacy_object(context, instance_uuid, primitive)
|
||||
|
||||
return obj_topology
|
||||
return obj
|
||||
|
||||
# TODO(stephenfin): Remove in X or later, once this has bedded in
|
||||
@classmethod
|
||||
def _migrate_legacy_object(cls, context, instance_uuid, primitive):
|
||||
"""Convert a pre-Liberty object to a real o.vo.
|
||||
|
||||
Handle an unversioned object created prior to Liberty, by transforming
|
||||
to a versioned object and saving back the serialized version of this.
|
||||
|
||||
:param context: RequestContext
|
||||
:param instance_uuid: The UUID of the instance this topology is
|
||||
associated with.
|
||||
:param primitive: A serialized representation of the legacy object.
|
||||
:returns: A serialized representation of the updated object.
|
||||
"""
|
||||
obj = cls(
|
||||
instance_uuid=instance_uuid,
|
||||
cells=[
|
||||
InstanceNUMACell(
|
||||
id=cell.get('id'),
|
||||
cpuset=hardware.parse_cpu_spec(cell.get('cpus', '')),
|
||||
memory=cell.get('mem', {}).get('total', 0),
|
||||
pagesize=cell.get('pagesize'),
|
||||
) for cell in primitive.get('cells', [])
|
||||
],
|
||||
)
|
||||
db_obj = jsonutils.dumps(obj.obj_to_primitive())
|
||||
values = {
|
||||
'numa_topology': db_obj,
|
||||
}
|
||||
db.instance_extra_update_by_uuid(context, instance_uuid, values)
|
||||
return obj
|
||||
|
||||
# TODO(ndipanov) Remove this method on the major version bump to 2.0
|
||||
@base.remotable
|
||||
|
@ -188,7 +195,8 @@ class InstanceNUMATopology(base.NovaObject,
|
|||
if db_extra['numa_topology'] is None:
|
||||
return None
|
||||
|
||||
return cls.obj_from_db_obj(instance_uuid, db_extra['numa_topology'])
|
||||
return cls.obj_from_db_obj(
|
||||
context, instance_uuid, db_extra['numa_topology'])
|
||||
|
||||
def _to_json(self):
|
||||
return jsonutils.dumps(self.obj_to_primitive())
|
||||
|
@ -197,14 +205,6 @@ class InstanceNUMATopology(base.NovaObject,
|
|||
"""Defined so that boolean testing works the same as for lists."""
|
||||
return len(self.cells)
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, data_dict):
|
||||
# NOTE(sahid): Used as legacy, could be renamed in _legacy_from_dict_
|
||||
# in the future to avoid confusing.
|
||||
return cls(cells=[
|
||||
InstanceNUMACell._from_dict(cell_dict)
|
||||
for cell_dict in data_dict.get('cells', [])])
|
||||
|
||||
@property
|
||||
def cpu_pinning(self):
|
||||
"""Return a set of all host CPUs this NUMATopology is pinned to."""
|
||||
|
|
|
@ -119,18 +119,6 @@ class NUMACell(base.NovaObject):
|
|||
pin_siblings.update(sib)
|
||||
self.unpin_cpus(pin_siblings)
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, data_dict):
|
||||
cpuset = hardware.parse_cpu_spec(
|
||||
data_dict.get('cpus', ''))
|
||||
cpu_usage = data_dict.get('cpu_usage', 0)
|
||||
memory = data_dict.get('mem', {}).get('total', 0)
|
||||
memory_usage = data_dict.get('mem', {}).get('used', 0)
|
||||
cell_id = data_dict.get('id')
|
||||
return cls(id=cell_id, cpuset=cpuset, memory=memory,
|
||||
cpu_usage=cpu_usage, memory_usage=memory_usage,
|
||||
mempages=[], pinned_cpus=set([]), siblings=[])
|
||||
|
||||
def can_fit_pagesize(self, pagesize, memory, use_free=True):
|
||||
"""Returns whether memory can fit into a given pagesize.
|
||||
|
||||
|
@ -219,18 +207,6 @@ class NUMATopology(base.NovaObject):
|
|||
"""Check if any cell use SMT threads (a.k.a. Hyperthreads)"""
|
||||
return any(cell.has_threads for cell in self.cells)
|
||||
|
||||
@classmethod
|
||||
def obj_from_primitive(cls, primitive, context=None):
|
||||
if 'nova_object.name' in primitive:
|
||||
obj_topology = super(NUMATopology, cls).obj_from_primitive(
|
||||
primitive, context=context)
|
||||
else:
|
||||
# NOTE(sahid): This compatibility code needs to stay until we can
|
||||
# guarantee that there are no cases of the old format stored in
|
||||
# the database (or forever, if we can never guarantee that).
|
||||
obj_topology = NUMATopology._from_dict(primitive)
|
||||
return obj_topology
|
||||
|
||||
def _to_json(self):
|
||||
return jsonutils.dumps(self.obj_to_primitive())
|
||||
|
||||
|
@ -243,16 +219,33 @@ class NUMATopology(base.NovaObject):
|
|||
"""
|
||||
return cls.obj_from_primitive(jsonutils.loads(db_obj))
|
||||
|
||||
@classmethod
|
||||
def from_legacy_object(cls, primitive: str):
|
||||
"""Convert a pre-Liberty object to a (serialized) real o.vo.
|
||||
|
||||
:param primitive: A serialized representation of the legacy object.
|
||||
:returns: A serialized representation of the updated object.
|
||||
"""
|
||||
topology = cls(
|
||||
cells=[
|
||||
NUMACell(
|
||||
id=cell.get('id'),
|
||||
cpuset=hardware.parse_cpu_spec(cell.get('cpus', '')),
|
||||
cpu_usage=cell.get('cpu_usage', 0),
|
||||
memory=cell.get('mem', {}).get('total', 0),
|
||||
memory_usage=cell.get('mem', {}).get('used', 0),
|
||||
mempages=[],
|
||||
pinned_cpus=set(),
|
||||
siblings=[],
|
||||
) for cell in jsonutils.loads(primitive).get('cells', [])
|
||||
],
|
||||
)
|
||||
return topology._to_json()
|
||||
|
||||
def __len__(self):
|
||||
"""Defined so that boolean testing works the same as for lists."""
|
||||
return len(self.cells)
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, data_dict):
|
||||
return cls(cells=[
|
||||
NUMACell._from_dict(cell_dict)
|
||||
for cell_dict in data_dict.get('cells', [])])
|
||||
|
||||
|
||||
@base.NovaObjectRegistry.register
|
||||
class NUMATopologyLimits(base.NovaObject):
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils.fixture import uuidsentinel
|
||||
|
||||
import nova.conf
|
||||
|
@ -168,6 +170,42 @@ class ComputeNodeTestCase(test.TestCase):
|
|||
self.assertEqual(1, len(cns))
|
||||
self.assertEqual(cn1.uuid, cns[0].uuid)
|
||||
|
||||
def test_numa_topology_online_migration_when_load(self):
|
||||
"""Ensure legacy NUMA topology objects are reserialized to o.vo's."""
|
||||
cn = fake_compute_obj.obj_clone()
|
||||
cn._context = self.context
|
||||
cn.create()
|
||||
|
||||
legacy_topology = jsonutils.dumps({
|
||||
"cells": [
|
||||
{
|
||||
"id": 0,
|
||||
"cpus": "0-3",
|
||||
"mem": {"total": 512, "used": 256},
|
||||
"cpu_usage": 2,
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"cpus": "4,5,6,7",
|
||||
"mem": {"total": 512, "used": 0},
|
||||
"cpu_usage": 0,
|
||||
}
|
||||
]
|
||||
})
|
||||
db.compute_node_update(
|
||||
self.context, cn.id, {'numa_topology': legacy_topology})
|
||||
|
||||
cn_db = db.compute_node_get(self.context, cn.id)
|
||||
self.assertEqual(legacy_topology, cn_db['numa_topology'])
|
||||
self.assertNotIn('nova_object.name', cn_db['numa_topology'])
|
||||
|
||||
# trigger online migration
|
||||
objects.ComputeNodeList.get_all(self.context)
|
||||
|
||||
cn_db = db.compute_node_get(self.context, cn.id)
|
||||
self.assertNotEqual(legacy_topology, cn_db['numa_topology'])
|
||||
self.assertIn('nova_object.name', cn_db['numa_topology'])
|
||||
|
||||
def test_ratio_online_migration_when_load(self):
|
||||
# set cpu and disk, and leave ram unset(None)
|
||||
self.flags(cpu_allocation_ratio=1.0)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from nova.compute import vm_states
|
||||
|
@ -154,3 +155,41 @@ class InstanceObjectTestCase(test.TestCase):
|
|||
self.assertEqual(0, counts['instances'])
|
||||
self.assertEqual(0, counts['cores'])
|
||||
self.assertEqual(0, counts['ram'])
|
||||
|
||||
def test_numa_topology_online_migration(self):
|
||||
"""Ensure legacy NUMA topology objects are reserialized to o.vo's."""
|
||||
instance = self._create_instance(host='fake-host', node='fake-node')
|
||||
|
||||
legacy_topology = jsonutils.dumps({
|
||||
"cells": [
|
||||
{
|
||||
"id": 0,
|
||||
"cpus": "0-3",
|
||||
"mem": {"total": 512},
|
||||
"pagesize": 4
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"cpus": "4,5,6,7",
|
||||
"mem": {"total": 512},
|
||||
"pagesize": 4
|
||||
}
|
||||
]
|
||||
})
|
||||
db.instance_extra_update_by_uuid(
|
||||
self.context, instance.uuid, {'numa_topology': legacy_topology})
|
||||
|
||||
instance_db = db.instance_extra_get_by_instance_uuid(
|
||||
self.context, instance.uuid, ['numa_topology'])
|
||||
self.assertEqual(legacy_topology, instance_db['numa_topology'])
|
||||
self.assertNotIn('nova_object.name', instance_db['numa_topology'])
|
||||
|
||||
# trigger online migration
|
||||
objects.InstanceList.get_by_host_and_node(
|
||||
self.context, 'fake-host', 'fake-node',
|
||||
expected_attrs=['numa_topology'])
|
||||
|
||||
instance_db = db.instance_extra_get_by_instance_uuid(
|
||||
self.context, instance.uuid, ['numa_topology'])
|
||||
self.assertNotEqual(legacy_topology, instance_db['numa_topology'])
|
||||
self.assertIn('nova_object.name', instance_db['numa_topology'])
|
||||
|
|
Loading…
Reference in New Issue