ecf12a4dd1
When exception.CPUPinningUnknown or exception.CPUUnPinningUnknown is raised, the value of known vcpuset is NUMACell's pinned_cpus, it should be NUMACell's cpuset. Close-Bug: #1729784 Change-Id: I1902dc40623af62fd83fac222099f664d7d19ed9
286 lines
9.8 KiB
Python
286 lines
9.8 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):
|
|
for name in obj_a.fields:
|
|
set_a = obj_a.obj_attr_is_set(name)
|
|
set_b = obj_b.obj_attr_is_set(name)
|
|
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.2'
|
|
|
|
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'),
|
|
}
|
|
|
|
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
|
|
|
|
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):
|
|
# NOTE(snikitin): Empty siblings list means that HyperThreading is
|
|
# disabled on the NUMA cell and we must pin CPUs like normal CPUs.
|
|
if not self.siblings:
|
|
self.pin_cpus(cpus)
|
|
return
|
|
|
|
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):
|
|
# NOTE(snikitin): Empty siblings list means that HyperThreading is
|
|
# disabled on the NUMA cell and we must unpin CPUs like normal CPUs.
|
|
if not self.siblings:
|
|
self.unpin_cpus(cpus)
|
|
return
|
|
|
|
pin_siblings = set()
|
|
for sib in self.siblings:
|
|
if cpus & sib:
|
|
pin_siblings.update(sib)
|
|
self.unpin_cpus(pin_siblings)
|
|
|
|
def _to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'cpus': hardware.format_cpu_spec(
|
|
self.cpuset, allow_ranges=False),
|
|
'mem': {
|
|
'total': self.memory,
|
|
'used': self.memory_usage},
|
|
'cpu_usage': self.cpu_usage}
|
|
|
|
@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'),
|
|
}
|
|
|
|
@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):
|
|
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)
|
|
|
|
def _to_dict(self):
|
|
# TODO(sahid): needs to be removed.
|
|
return {'cells': [cell._to_dict() for cell in 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.0'
|
|
|
|
fields = {
|
|
'cpu_allocation_ratio': fields.FloatField(),
|
|
'ram_allocation_ratio': fields.FloatField(),
|
|
}
|
|
|
|
def to_dict_legacy(self, host_topology):
|
|
cells = []
|
|
for cell in host_topology.cells:
|
|
cells.append(
|
|
{'cpus': hardware.format_cpu_spec(
|
|
cell.cpuset, allow_ranges=False),
|
|
'mem': {'total': cell.memory,
|
|
'limit': cell.memory * self.ram_allocation_ratio},
|
|
'cpu_limit': len(cell.cpuset) * self.cpu_allocation_ratio,
|
|
'id': cell.id})
|
|
return {'cells': cells}
|
|
|
|
@classmethod
|
|
def obj_from_db_obj(cls, db_obj):
|
|
if 'nova_object.name' in db_obj:
|
|
obj_topology = cls.obj_from_primitive(db_obj)
|
|
else:
|
|
# NOTE(sahid): This compatibility code needs to stay until we can
|
|
# guarantee that all compute nodes are using RPC API => 3.40.
|
|
cell = db_obj['cells'][0]
|
|
ram_ratio = cell['mem']['limit'] / float(cell['mem']['total'])
|
|
cpu_ratio = cell['cpu_limit'] / float(len(hardware.parse_cpu_spec(
|
|
cell['cpus'])))
|
|
obj_topology = NUMATopologyLimits(
|
|
cpu_allocation_ratio=cpu_ratio,
|
|
ram_allocation_ratio=ram_ratio)
|
|
return obj_topology
|