Skip to content

Commit c5798a3

Browse files
committed
Calculate memory_mb_max_unit based on instance's memory_mb
Previously the memory_mb_max_unit was relying on the overallMemoryUsage reported by the ESXi, which was wrong, because the host could still have a big VM with reserved memory, that was not consuming any RAM and thus not reflected in the memory usage. We are now looking for the instances spawned on that ESXi and subtract their configured memory_mb from the total RAM capacity of the host, resulting in a realistic memory_mb_max_unit. Change-Id: I72821c97d80f9f675dca943d65f94e36824d81b6
1 parent d1a45a5 commit c5798a3

File tree

4 files changed

+161
-8
lines changed

4 files changed

+161
-8
lines changed

nova/tests/unit/virt/vmwareapi/test_driver_api.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,8 +2377,8 @@ def test_get_available_resource(self):
23772377
stats['supported_instances'])
23782378
memory_mbmax_unit = stats['stats']['memory_mb_max_unit']
23792379
vcpus_max_unit = stats['stats']['vcpus_max_unit']
2380-
# memory_mb_used = fake.HostSystem.overallMemoryUsage = 500
2381-
self.assertEqual(memory_mbmax_unit, 1024 - 500)
2380+
# memory_mb_used = fake.HostSystem.hardware.memorySize = 1024
2381+
self.assertEqual(memory_mbmax_unit, 1024)
23822382
# vcpus_max_unit = fake.HostSystem.summary.hardware.numCpuThreads
23832383
self.assertEqual(vcpus_max_unit, 16)
23842384

@@ -2910,3 +2910,24 @@ def test_host_state_service_disabled(self, mock_service):
29102910
self.assertEqual(2, mock_save.call_count)
29112911
self.assertFalse(service.disabled)
29122912
self.assertFalse(self.conn._vc_state._auto_service_disabled)
2913+
2914+
@mock.patch.object(vmops.VMwareVMOps,
2915+
'get_available_memory_per_host')
2916+
def test_get_cluster_stats(self, mock_get_mem_per_host):
2917+
mem_per_host = {
2918+
'host-1': 1024,
2919+
'host-2': 4096,
2920+
'host-3': 0,
2921+
}
2922+
mock_get_mem_per_host.return_value = mem_per_host
2923+
2924+
host_stats = {
2925+
'node-001': {'vcpus': 8},
2926+
'node-002': {'vcpus': 16},
2927+
self.conn._nodename: {'vcpus': 24}
2928+
}
2929+
2930+
result = self.conn._get_cluster_stats(host_stats)
2931+
2932+
self.assertEqual(16, result['vcpus_max_unit'])
2933+
self.assertEqual(4096, result['memory_mb_max_unit'])

nova/tests/unit/virt/vmwareapi/test_vmops.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4203,3 +4203,83 @@ def test_stack_vm_to_host_if_needed_equal_hosts(
42034203
# were returned into is not changed
42044204
self._test_stack_vm_to_host_if_needed(
42054205
requested_mb, hosts, expected)
4206+
4207+
@ddt.unpack
4208+
@ddt.data(
4209+
# test 1
4210+
([
4211+
{'host': dict(memory_size=1024),
4212+
'instances_mem': [512]},
4213+
{'host': dict(memory_size=4096,
4214+
in_maintenance_mode=True),
4215+
'instances_mem': []},
4216+
{'host': dict(memory_size=8192,
4217+
connection_state='N/A'),
4218+
'instances_mem': []}
4219+
],
4220+
# available memory per host
4221+
# expect only hosts in valid state to be returned
4222+
[512]),
4223+
4224+
# test 2
4225+
([
4226+
{'host': dict(memory_size=1024),
4227+
'instances_mem': [256, 256, 256]},
4228+
{'host': dict(memory_size=1024),
4229+
'instances_mem': [512, 512, 512]}
4230+
],
4231+
# available memory per host
4232+
# second host is over-provisioned, but we should still
4233+
# return 0 available memory MB.
4234+
[256, 0])
4235+
4236+
)
4237+
def test_get_available_memory_per_host(self,
4238+
hosts_with_instances,
4239+
expected_results):
4240+
def _fake_host(in_maintenance_mode=False,
4241+
connection_state="connected",
4242+
memory_size=0):
4243+
host = vmwareapi_fake.HostSystem()
4244+
host.set("summary.runtime", mock.Mock(
4245+
inMaintenanceMode=in_maintenance_mode,
4246+
connectionState=connection_state))
4247+
host.set("summary.hardware", mock.Mock(
4248+
memorySize=memory_size * units.Mi))
4249+
return host
4250+
4251+
hosts = []
4252+
instances_ret = []
4253+
4254+
for hi in hosts_with_instances:
4255+
host = _fake_host(**hi['host'])
4256+
hosts.append(host)
4257+
for mem in hi['instances_mem']:
4258+
instances_ret.append(
4259+
(uuidutils.generate_uuid(),
4260+
{'runtime.host': host.mo_id,
4261+
'config.hardware.memoryMB': mem}))
4262+
4263+
hosts_ret = (
4264+
[h.mo_id for h in hosts],
4265+
None
4266+
)
4267+
4268+
def _mock_with_ret(vim, ret_res):
4269+
return mock.Mock(__enter__=mock.Mock(return_value=ret_res),
4270+
__exit__=mock.Mock(return_value=None))
4271+
4272+
with test.nested(
4273+
mock.patch.object(self._session, "_call_method",
4274+
return_value=hosts),
4275+
mock.patch.object(vutil, 'WithRetrieval', _mock_with_ret),
4276+
mock.patch.object(self._vmops, '_list_instances_in_cluster',
4277+
return_value=instances_ret),
4278+
mock.patch.object(vm_util,
4279+
'get_hosts_and_reservations_for_cluster',
4280+
return_value=hosts_ret)
4281+
) as (mock_call_method, mock_with_retrieval, mock_list_instances,
4282+
mock_get_hosts_and_res):
4283+
4284+
result = self._vmops.get_available_memory_per_host()
4285+
self.assertEqual(list(result.values()), expected_results)

nova/virt/vmwareapi/driver.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,11 @@ def _get_cluster_stats(self, host_stats):
405405
# memory_mb_max_unit represents the free memory of the freest host
406406
# available in the cluster. Can be used by the scheduler to determine
407407
# if the cluster can accommodate a big VM.
408-
memory_mb_max_unit = 0
408+
# It's calculated by looking at the configured memoryMB of instances
409+
# in that host, not taking into account the actual memory usage.
410+
memory_mb_max_unit = max(
411+
mem for mem in
412+
self._vmops.get_available_memory_per_host().values())
409413
# vcpus_max_unit represents the vCPUs of a physical node from the
410414
# cluster. It can be used to determine if a VM can be powered-on in
411415
# this cluster, by comparing its flavor.vcpus to this value.
@@ -414,11 +418,6 @@ def _get_cluster_stats(self, host_stats):
414418
# Skip the cluster node
415419
if nodename == self._nodename:
416420
continue
417-
memory_mb_free = (resources['memory_mb'] -
418-
resources['memory_mb_used'])
419-
if memory_mb_max_unit < memory_mb_free:
420-
memory_mb_max_unit = memory_mb_free
421-
422421
vcpus = resources['vcpus']
423422
if vcpus_max_unit < vcpus:
424423
vcpus_max_unit = vcpus

nova/virt/vmwareapi/vmops.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4808,3 +4808,56 @@ def check_can_live_migrate_source(self, instance):
48084808
raise exception.MigrationPreCheckError(
48094809
reason=("Found non-configdrive CD-ROM. "
48104810
"Can only migrate configdrive"))
4811+
4812+
def get_available_memory_per_host(self):
4813+
"""Retrieves per-host available RAM.
4814+
4815+
The value is calculated by subtracting the total configured
4816+
memory_mb of the instances residing on that host from the
4817+
total memory size of the host.
4818+
4819+
Returns a dict containing available RAM information per host.
4820+
{
4821+
"host-ref-value": 1024
4822+
}
4823+
"""
4824+
(host_mors, _) = vm_util.get_hosts_and_reservations_for_cluster(
4825+
self._session, self._cluster)
4826+
4827+
ram_per_host = {}
4828+
# initialize ram_per_host with the hosts in the expected state
4829+
# and their total memorySize.
4830+
result = self._session._call_method(vim_util,
4831+
"get_properties_for_a_collection_of_objects",
4832+
"HostSystem", host_mors,
4833+
["summary.runtime", "summary.hardware"])
4834+
with vutil.WithRetrieval(self._session.vim, result) as objects:
4835+
for obj in objects:
4836+
host_props = vutil.propset_dict(obj.propSet)
4837+
runtime_summary = host_props['summary.runtime']
4838+
if (runtime_summary.inMaintenanceMode or
4839+
runtime_summary.connectionState != "connected"):
4840+
continue
4841+
host_ref_value = vutil.get_moref_value(obj.obj)
4842+
hardware_summary = host_props.get("summary.hardware")
4843+
mem_size = getattr(hardware_summary, "memorySize", 0)
4844+
ram_per_host[host_ref_value] = mem_size // units.Mi
4845+
4846+
props = ['config.hardware.memoryMB', 'runtime.host']
4847+
vms = self._list_instances_in_cluster(additional_properties=props)
4848+
4849+
for (vm_uuid, vm_props) in vms:
4850+
host_obj = vm_props.get('runtime.host')
4851+
if not host_obj:
4852+
continue
4853+
host_ref_value = vutil.get_moref_value(host_obj)
4854+
if host_ref_value not in ram_per_host:
4855+
continue
4856+
4857+
vm_mb = vm_props.get('config.hardware.memoryMB', 0)
4858+
4859+
# make sure the minimum available memory >= 0
4860+
ram_per_host[host_ref_value] = \
4861+
max(ram_per_host[host_ref_value] - vm_mb, 0)
4862+
4863+
return ram_per_host

0 commit comments

Comments
 (0)