Merge "Fix overcommit for NUMA-based instances"

This commit is contained in:
Zuul 2019-01-18 09:00:10 +00:00 committed by Gerrit Code Review
commit 5ac6b2ef40
3 changed files with 60 additions and 19 deletions

View File

@ -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):

View File

@ -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()

View File

@ -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',