nova/nova/objects/numa.py

272 lines
9.3 KiB
Python

# Copyright 2014 Red Hat Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 import versionutils
from nova import exception
from nova.objects import base
from nova.objects import fields
from nova.virt import hardware
def all_things_equal(obj_a, obj_b):
if obj_b is None:
return False
for name in obj_a.fields:
set_a = name in obj_a
set_b = name in obj_b
if set_a != set_b:
return False
elif not set_a:
continue
if getattr(obj_a, name) != getattr(obj_b, name):
return False
return True
@base.NovaObjectRegistry.register
class NUMACell(base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Added pinned_cpus and siblings fields
# Version 1.2: Added mempages field
# Version 1.3: Add network_metadata field
VERSION = '1.3'
fields = {
'id': fields.IntegerField(read_only=True),
'cpuset': fields.SetOfIntegersField(),
'memory': fields.IntegerField(),
'cpu_usage': fields.IntegerField(default=0),
'memory_usage': fields.IntegerField(default=0),
'pinned_cpus': fields.SetOfIntegersField(),
'siblings': fields.ListOfSetsOfIntegersField(),
'mempages': fields.ListOfObjectsField('NUMAPagesTopology'),
'network_metadata': fields.ObjectField('NetworkMetadata'),
}
def obj_make_compatible(self, primitive, target_version):
super(NUMACell, self).obj_make_compatible(primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 3):
primitive.pop('network_metadata', None)
def __eq__(self, other):
return all_things_equal(self, other)
def __ne__(self, other):
return not (self == other)
@property
def free_cpus(self):
return self.cpuset - self.pinned_cpus or set()
@property
def free_siblings(self):
return [sibling_set & self.free_cpus
for sibling_set in self.siblings]
@property
def avail_cpus(self):
return len(self.free_cpus)
@property
def avail_memory(self):
return self.memory - self.memory_usage
@property
def has_threads(self):
"""Check if SMT threads, a.k.a. HyperThreads, are present."""
return any(len(sibling_set) > 1 for sibling_set in self.siblings)
def pin_cpus(self, cpus):
if cpus - self.cpuset:
raise exception.CPUPinningUnknown(requested=list(cpus),
cpuset=list(self.cpuset))
if self.pinned_cpus & cpus:
raise exception.CPUPinningInvalid(requested=list(cpus),
free=list(self.cpuset -
self.pinned_cpus))
self.pinned_cpus |= cpus
def unpin_cpus(self, cpus):
if cpus - self.cpuset:
raise exception.CPUUnpinningUnknown(requested=list(cpus),
cpuset=list(self.cpuset))
if (self.pinned_cpus & cpus) != cpus:
raise exception.CPUUnpinningInvalid(requested=list(cpus),
pinned=list(self.pinned_cpus))
self.pinned_cpus -= cpus
def pin_cpus_with_siblings(self, cpus):
pin_siblings = set()
for sib in self.siblings:
if cpus & sib:
pin_siblings.update(sib)
self.pin_cpus(pin_siblings)
def unpin_cpus_with_siblings(self, cpus):
pin_siblings = set()
for sib in self.siblings:
if cpus & sib:
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_hugepages(self, pagesize, memory):
"""Returns whether memory can fit into hugepages size
:param pagesize: a page size in KibB
:param memory: a memory size asked to fit in KiB
:returns: whether memory can fit in hugepages
:raises: MemoryPageSizeNotSupported if page size not supported
"""
for pages in self.mempages:
if pages.size_kb == pagesize:
return (memory <= pages.free_kb and
(memory % pages.size_kb) == 0)
raise exception.MemoryPageSizeNotSupported(pagesize=pagesize)
@base.NovaObjectRegistry.register
class NUMAPagesTopology(base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Adds reserved field
VERSION = '1.1'
fields = {
'size_kb': fields.IntegerField(),
'total': fields.IntegerField(),
'used': fields.IntegerField(default=0),
'reserved': fields.IntegerField(default=0),
}
def obj_make_compatible(self, primitive, target_version):
super(NUMAPagesTopology, self).obj_make_compatible(primitive,
target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 1):
primitive.pop('reserved', None)
def __eq__(self, other):
return all_things_equal(self, other)
def __ne__(self, other):
return not (self == other)
@property
def free(self):
"""Returns the number of avail pages."""
if not self.obj_attr_is_set('reserved'):
# In case where an old compute node is sharing resource to
# an updated node we must ensure that this property is defined.
self.reserved = 0
return self.total - self.used - self.reserved
@property
def free_kb(self):
"""Returns the avail memory size in KiB."""
return self.free * self.size_kb
@base.NovaObjectRegistry.register
class NUMATopology(base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Update NUMACell to 1.1
# Version 1.2: Update NUMACell to 1.2
VERSION = '1.2'
fields = {
'cells': fields.ListOfObjectsField('NUMACell'),
}
def __eq__(self, other):
return all_things_equal(self, other)
def __ne__(self, other):
return not (self == other)
@property
def has_threads(self):
"""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())
@classmethod
def obj_from_db_obj(cls, db_obj):
"""Convert serialized representation to object.
Deserialize instances of this object that have been stored as JSON
blobs in the database.
"""
return cls.obj_from_primitive(jsonutils.loads(db_obj))
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):
# Version 1.0: Initial version
# Version 1.1: Add network_metadata field
VERSION = '1.1'
fields = {
'cpu_allocation_ratio': fields.FloatField(),
'ram_allocation_ratio': fields.FloatField(),
'network_metadata': fields.ObjectField('NetworkMetadata'),
}
def obj_make_compatible(self, primitive, target_version):
super(NUMATopologyLimits, self).obj_make_compatible(primitive,
target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 1):
primitive.pop('network_metadata', None)