Fix overcommit for NUMA-based instances
Change I5f5c621f2f0fa1bc18ee9a97d17085107a5dee53 modified how we
evaluated available memory for instances with a NUMA topology.
Previously, we used a non-pagesize aware check unless the user had
explicitly requested a specific pagesize. This means that for instances
without pagesize requests, nova considers hugepages as available memory
when deciding if a host has enough available memory for the instance.
The aforementioned change modified this so that all NUMA-based
instances, whether they had hugepages or not, would use the
pagesize-aware check. Unfortunately the functionality it was reusing to
do this was functionality previously only used for hugepages. Hugepages
cannot be oversubscribed so we did not take oversubscription into
account, comparing against available memory on the host (i.e. memory not
consumed by other instances) rather than total memory. This is OK when
using hugepages but not small pages, where overcommit is OK.
Given that overcommit is already handled elsewhere in the code, we
simply modify the non-hugepage code path to check for available memory
of the lowest pagesize vs. total memory.
Change-Id: I890b2c81cd49c1c601e9baee6a249709d0f6810e
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
Closes-Bug: #1810977
(cherry picked from commit fd19aeafbc
)
This commit is contained in:
parent
444d65221c
commit
780ccfcbde
|
@ -137,19 +137,23 @@ class NUMACell(base.NovaObject):
|
|||
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
|
||||
def can_fit_pagesize(self, pagesize, memory, use_free=True):
|
||||
"""Returns whether memory can fit into a given pagesize.
|
||||
|
||||
:param pagesize: a page size in KibB
|
||||
:param memory: a memory size asked to fit in KiB
|
||||
:param use_free: if true, assess based on free memory rather than total
|
||||
memory. This means overcommit is not allowed, which should be the
|
||||
case for hugepages since these are memlocked by the kernel and
|
||||
can't be swapped out.
|
||||
|
||||
:returns: whether memory can fit in hugepages
|
||||
:raises: MemoryPageSizeNotSupported if page size not supported
|
||||
"""
|
||||
for pages in self.mempages:
|
||||
avail_kb = pages.free_kb if use_free else pages.total_kb
|
||||
if pages.size_kb == pagesize:
|
||||
return (memory <= pages.free_kb and
|
||||
(memory % pages.size_kb) == 0)
|
||||
return memory <= avail_kb and (memory % pages.size_kb) == 0
|
||||
raise exception.MemoryPageSizeNotSupported(pagesize=pagesize)
|
||||
|
||||
|
||||
|
@ -193,6 +197,11 @@ class NUMAPagesTopology(base.NovaObject):
|
|||
"""Returns the avail memory size in KiB."""
|
||||
return self.free * self.size_kb
|
||||
|
||||
@property
|
||||
def total_kb(self):
|
||||
"""Returns the total memory size in KiB."""
|
||||
return self.total * self.size_kb
|
||||
|
||||
|
||||
@base.NovaObjectRegistry.register
|
||||
class NUMATopology(base.NovaObject):
|
||||
|
|
|
@ -135,7 +135,8 @@ class _TestNUMA(object):
|
|||
self.assertEqual(512, pages_topology.free)
|
||||
self.assertEqual(1048576, pages_topology.free_kb)
|
||||
|
||||
def test_can_fit_hugepages(self):
|
||||
def test_can_fit_pagesize(self):
|
||||
# NOTE(stephenfin): '**' is Python's "power of" symbol
|
||||
cell = objects.NUMACell(
|
||||
id=0, cpuset=set([1, 2]), memory=1024,
|
||||
siblings=[set([1]), set([2])], pinned_cpus=set([]),
|
||||
|
@ -148,18 +149,42 @@ class _TestNUMA(object):
|
|||
size_kb=1048576, total=4, used=1, reserved=1)])
|
||||
|
||||
pagesize = 2048
|
||||
self.assertTrue(cell.can_fit_hugepages(pagesize, 2 ** 20))
|
||||
self.assertFalse(cell.can_fit_hugepages(pagesize, 2 ** 21))
|
||||
self.assertFalse(cell.can_fit_hugepages(pagesize, 2 ** 19 + 1))
|
||||
self.assertTrue(cell.can_fit_pagesize(pagesize, 2 ** 20))
|
||||
self.assertFalse(cell.can_fit_pagesize(pagesize, 2 ** 21))
|
||||
self.assertFalse(cell.can_fit_pagesize(pagesize, 2 ** 19 + 1))
|
||||
|
||||
pagesize = 1048576
|
||||
self.assertTrue(cell.can_fit_hugepages(pagesize, 2 ** 20))
|
||||
self.assertTrue(cell.can_fit_hugepages(pagesize, 2 ** 20 * 2))
|
||||
self.assertFalse(cell.can_fit_hugepages(pagesize, 2 ** 20 * 3))
|
||||
self.assertTrue(cell.can_fit_pagesize(pagesize, 2 ** 20))
|
||||
self.assertTrue(cell.can_fit_pagesize(pagesize, 2 ** 20 * 2))
|
||||
self.assertFalse(cell.can_fit_pagesize(pagesize, 2 ** 20 * 3))
|
||||
|
||||
self.assertRaises(
|
||||
exception.MemoryPageSizeNotSupported,
|
||||
cell.can_fit_hugepages, 12345, 2 ** 20)
|
||||
cell.can_fit_pagesize, 12345, 2 ** 20)
|
||||
|
||||
def test_can_fit_pagesize_oversubscription(self):
|
||||
"""Validate behavior when using page oversubscription.
|
||||
|
||||
While hugepages aren't themselves oversubscribable, we also track small
|
||||
pages which are.
|
||||
"""
|
||||
# NOTE(stephenfin): '**' is Python's "power of" symbol
|
||||
cell = objects.NUMACell(
|
||||
id=0, cpuset=set([1, 2]), memory=1024,
|
||||
siblings=[set([1]), set([2])], pinned_cpus=set([]),
|
||||
mempages=[
|
||||
# 1 GiB total, all used
|
||||
objects.NUMAPagesTopology(
|
||||
size_kb=4, total=2 ** 18, used=2 ** 18),
|
||||
])
|
||||
|
||||
pagesize = 4
|
||||
# request 2^20 KiB (so 1 GiB)
|
||||
self.assertTrue(cell.can_fit_pagesize(
|
||||
pagesize, 2 ** 20, use_free=False))
|
||||
# request 2^20 + 1 KiB (so # > 1 GiB)
|
||||
self.assertFalse(cell.can_fit_pagesize(
|
||||
pagesize, 2 ** 20 + 1, use_free=False))
|
||||
|
||||
def test_default_behavior(self):
|
||||
inst_cell = objects.NUMACell()
|
||||
|
|
|
@ -636,7 +636,7 @@ def _numa_cell_supports_pagesize_request(host_cell, inst_cell):
|
|||
def verify_pagesizes(host_cell, inst_cell, avail_pagesize):
|
||||
inst_cell_mem = inst_cell.memory * units.Ki
|
||||
for pagesize in avail_pagesize:
|
||||
if host_cell.can_fit_hugepages(pagesize, inst_cell_mem):
|
||||
if host_cell.can_fit_pagesize(pagesize, inst_cell_mem):
|
||||
return pagesize
|
||||
|
||||
if inst_cell.pagesize == MEMPAGES_SMALL:
|
||||
|
@ -1038,13 +1038,16 @@ def _numa_fit_instance_cell(host_cell, instance_cell, limit_cell=None,
|
|||
# The instance provides a NUMA topology but does not define any
|
||||
# particular page size for its memory.
|
||||
if host_cell.mempages:
|
||||
# The host supports explicit page sizes. Use the smallest
|
||||
# available page size.
|
||||
# The host supports explicit page sizes. Use a pagesize-aware
|
||||
# memory check using the smallest available page size.
|
||||
pagesize = _get_smallest_pagesize(host_cell)
|
||||
LOG.debug('No specific pagesize requested for instance, '
|
||||
'selected pagesize: %d', pagesize)
|
||||
if not host_cell.can_fit_hugepages(
|
||||
pagesize, instance_cell.memory * units.Ki):
|
||||
# we want to allow overcommit in this case as we're not using
|
||||
# hugepages
|
||||
if not host_cell.can_fit_pagesize(pagesize,
|
||||
instance_cell.memory * units.Ki,
|
||||
use_free=False):
|
||||
LOG.debug('Not enough available memory to schedule instance '
|
||||
'with pagesize %(pagesize)d. Required: '
|
||||
'%(required)s, available: %(available)s, total: '
|
||||
|
@ -1055,8 +1058,12 @@ def _numa_fit_instance_cell(host_cell, instance_cell, limit_cell=None,
|
|||
'pagesize': pagesize})
|
||||
return
|
||||
else:
|
||||
# NOTE (ndipanov): do not allow an instance to overcommit against
|
||||
# itself on any NUMA cell
|
||||
# The host does not support explicit page sizes. Ignore pagesizes
|
||||
# completely.
|
||||
# NOTE(stephenfin): Do not allow an instance to overcommit against
|
||||
# itself on any NUMA cell, i.e. with 'ram_allocation_ratio = 2.0'
|
||||
# on a host with 1GB RAM, we should allow two 1GB instances but not
|
||||
# one 2GB instance.
|
||||
if instance_cell.memory > host_cell.memory:
|
||||
LOG.debug('Not enough host cell memory to fit instance cell. '
|
||||
'Required: %(required)d, actual: %(actual)d',
|
||||
|
|
Loading…
Reference in New Issue