Add debug message to report calculated metric for workload_balance

The workload_balance strategy calculates host metrics based on the
instance metrics and those are the ones used to compare with the
threshold.

Currently, the strategy does not reports the calculated values what
makes difficult to troubleshoot sometimes. This patch is adding a debug
message to log those values.

This patch is also adding a new unit test for filter_destination_hosts
based on ram instead of cpu and adding assertions for the new debug
messages. To implement properly the new test, I had to sligthly modify
the ram usage fixtures used for the workload_balance tests.

Change-Id: Ief5e167afcf346ff53471f26adc70795c4b69f68
This commit is contained in:
Alfredo Moralejo
2025-06-03 11:59:14 +02:00
parent 59757249bb
commit 1529e3fadd
3 changed files with 71 additions and 13 deletions

View File

@@ -192,15 +192,30 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
if (free_res['vcpu'] >= required_cores and
free_res['memory'] >= required_mem and
free_res['disk'] >= required_disk):
if (self._meter == 'instance_cpu_usage' and
((src_instance_workload + workload) <
self.threshold / 100 * host.vcpus)):
destination_hosts.append(instance_data)
if (self._meter == 'instance_ram_usage' and
((src_instance_workload + workload) <
self.threshold / 100 * host.memory)):
destination_hosts.append(instance_data)
if self._meter == 'instance_cpu_usage':
usage = src_instance_workload + workload
usage_percent = usage / host.vcpus * 100
limit = self.threshold / 100 * host.vcpus
if usage < limit:
destination_hosts.append(instance_data)
LOG.debug(f"Host {host.hostname} evaluated as destination "
f"for {instance_to_migrate.uuid}. Host usage "
f"for cpu would be {usage_percent}."
f"The threshold is: {self.threshold}. "
f"selected: {usage < limit}"
)
if self._meter == 'instance_ram_usage':
usage = src_instance_workload + workload
usage_percent = usage / host.memory * 100
limit = self.threshold / 100 * host.memory
if usage < limit:
destination_hosts.append(instance_data)
LOG.debug(f"Host {host.hostname} evaluated as destination "
f"for {instance_to_migrate.uuid}. Host usage "
f"for ram would be {usage_percent}."
f"The threshold is: {self.threshold}. "
f"selected: {usage < limit}"
)
return destination_hosts
def group_hosts_by_cpu_or_ram_util(self):
@@ -251,8 +266,10 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
cluster_workload += node_workload
if self._meter == 'instance_cpu_usage':
node_util = node_workload / node.vcpus * 100
host_metric = 'host_cpu_usage_percent'
else:
node_util = node_workload / node.memory * 100
host_metric = 'host_ram_usage_percent'
instance_data = {
'compute_node': node, self._meter: node_util,
@@ -262,6 +279,9 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
overload_hosts.append(instance_data)
else:
nonoverload_hosts.append(instance_data)
LOG.debug(f"Host usage for {node_id}: {host_metric} is "
f"{node_util}. Higher than threshold {self.threshold}: "
f"{node_util >= self.threshold}")
avg_workload = 0
if cluster_size != 0:

View File

@@ -304,8 +304,8 @@ class FakeGnocchiMetrics(object):
mock = {}
# node 0
mock['INSTANCE_1'] = 30
mock['73b09e16-35b7-4922-804e-e8f5d9b740fc'] = 12
mock['INSTANCE_1'] = 40
mock['73b09e16-35b7-4922-804e-e8f5d9b740fc'] = 9
# node 1
mock['INSTANCE_3'] = 12
mock['INSTANCE_4'] = 12

View File

@@ -22,6 +22,7 @@ from unittest import mock
from watcher.applier.loading import default
from watcher.common import utils
from watcher.decision_engine.strategy import strategies
from watcher.decision_engine.strategy.strategies import workload_balance
from watcher.tests.decision_engine.model import gnocchi_metrics
from watcher.tests.decision_engine.strategy.strategies.test_base \
import TestBaseStrategy
@@ -77,7 +78,7 @@ class TestWorkloadBalance(TestBaseStrategy):
n1, n2, avg, w_map = self.strategy.group_hosts_by_cpu_or_ram_util()
self.assertEqual(n1[0]['compute_node'].uuid, 'Node_0')
self.assertEqual(n2[0]['compute_node'].uuid, 'Node_1')
self.assertEqual(avg, 33.0)
self.assertEqual(avg, 36.5)
def test_choose_instance_to_migrate(self):
model = self.fake_c_cluster.generate_scenario_6_with_2_nodes()
@@ -99,7 +100,8 @@ class TestWorkloadBalance(TestBaseStrategy):
n1, avg, w_map)
self.assertIsNone(instance_to_mig)
def test_filter_destination_hosts(self):
@mock.patch.object(workload_balance.LOG, 'debug', autospec=True)
def test_filter_destination_hosts_cpu(self, mock_debug):
model = self.fake_c_cluster.generate_scenario_6_with_2_nodes()
self.m_c_model.return_value = model
self.strategy.datasource = mock.MagicMock(
@@ -111,6 +113,42 @@ class TestWorkloadBalance(TestBaseStrategy):
n2, instance_to_mig[1], avg, w_map)
self.assertEqual(len(dest_hosts), 1)
self.assertEqual(dest_hosts[0]['compute_node'].uuid, 'Node_1')
expected_calls = [
mock.call('Host usage for Node_0: host_cpu_usage_percent is 32.5. '
'Higher than threshold 25.0: True'),
mock.call('Host usage for Node_1: host_cpu_usage_percent is 7.5. '
'Higher than threshold 25.0: False'),
mock.call('Host hostname_1 evaluated as destination for '
'73b09e16-35b7-4922-804e-e8f5d9b740fc. Host usage for '
'cpu would be 20.0.The threshold is: 25.0. selected: '
'True')]
mock_debug.assert_has_calls(expected_calls, any_order=True)
@mock.patch.object(workload_balance.LOG, 'debug', autospec=True)
def test_filter_destination_hosts_ram(self, mock_debug):
model = self.fake_c_cluster.generate_scenario_6_with_2_nodes()
self.m_c_model.return_value = model
self.strategy._meter = 'instance_ram_usage'
self.strategy.threshold = 30.0
self.strategy.datasource = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics_wb)
n1, n2, avg, w_map = self.strategy.group_hosts_by_cpu_or_ram_util()
instance_to_mig = self.strategy.choose_instance_to_migrate(
n1, avg, w_map)
dest_hosts = self.strategy.filter_destination_hosts(
n2, instance_to_mig[1], avg, w_map)
self.assertEqual(len(dest_hosts), 1)
self.assertEqual(dest_hosts[0]['compute_node'].uuid, 'Node_1')
expected_calls = [
mock.call('Host usage for Node_0: host_ram_usage_percent is '
'37.121212121212125. Higher than threshold 30.0: True'),
mock.call('Host usage for Node_1: host_ram_usage_percent is '
'18.181818181818183. Higher than threshold 30.0: False'),
mock.call('Host hostname_1 evaluated as destination for '
'73b09e16-35b7-4922-804e-e8f5d9b740fc. Host usage for '
'ram would be 25.0.The threshold is: 30.0. selected: '
'True')]
mock_debug.assert_has_calls(expected_calls, any_order=True)
def test_execute_no_workload(self):
model = self.fake_c_cluster.\