From 6de94cca2d34c03cb5f0ceeb7cd455a9a3d9f7fd Mon Sep 17 00:00:00 2001 From: Santhosh Fernandes Date: Wed, 29 Mar 2017 12:37:18 +0530 Subject: [PATCH] Add gnocchi support in outlet_temp_control strategy This patch adds gnocchi support in outlet_temp_control strategy and adds unit tests corresponding to that change. Partiallly-Implements: bp gnocchi-watcher Change-Id: I2c2e9a86c470f3402adc3dbb7eb9995c643d5b37 --- .../strategies/outlet_temp_control.py | 60 ++++++++++++++++--- .../strategies/test_outlet_temp_control.py | 58 ++++++++++++++---- 2 files changed, 100 insertions(+), 18 deletions(-) diff --git a/watcher/decision_engine/strategy/strategies/outlet_temp_control.py b/watcher/decision_engine/strategy/strategies/outlet_temp_control.py index b7691c0f6..bbafd02b0 100644 --- a/watcher/decision_engine/strategy/strategies/outlet_temp_control.py +++ b/watcher/decision_engine/strategy/strategies/outlet_temp_control.py @@ -28,11 +28,14 @@ Outlet (Exhaust Air) Temperature is one of the important thermal telemetries to measure thermal/workload status of server. """ +import datetime + from oslo_log import log from watcher._i18n import _ from watcher.common import exception as wexc from watcher.datasource import ceilometer as ceil +from watcher.datasource import gnocchi as gnoc from watcher.decision_engine.model import element from watcher.decision_engine.strategy.strategies import base @@ -71,9 +74,15 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy): """ # noqa # The meter to report outlet temperature in ceilometer - METER_NAME = "hardware.ipmi.node.outlet_temperature" MIGRATION = "migrate" + METRIC_NAMES = dict( + ceilometer=dict( + host_outlet_temp='hardware.ipmi.node.outlet_temperature'), + gnocchi=dict( + host_outlet_temp='hardware.ipmi.node.outlet_temperature'), + ) + def __init__(self, config, osc=None): """Outlet temperature control using live migration @@ -83,8 +92,8 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy): :type osc: :py:class:`~.OpenStackClients` instance, optional """ super(OutletTempControl, self).__init__(config, osc) - self._meter = self.METER_NAME self._ceilometer = None + self._gnocchi = None @classmethod def get_name(cls): @@ -118,6 +127,12 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy): "type": "number", "default": 30 }, + "granularity": { + "description": "The time between two measures in an " + "aggregated timeseries of a metric.", + "type": "number", + "default": 300 + }, }, } @@ -131,6 +146,20 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy): def ceilometer(self, c): self._ceilometer = c + @property + def gnocchi(self): + if self._gnocchi is None: + self._gnocchi = gnoc.GnocchiHelper(osc=self.osc) + return self._gnocchi + + @gnocchi.setter + def gnocchi(self, g): + self._gnocchi = g + + @property + def granularity(self): + return self.input_parameters.get('granularity', 300) + def calc_used_resource(self, node): """Calculate the used vcpus, memory and disk based on VM flavors""" instances = self.compute_model.get_node_instances(node) @@ -153,14 +182,31 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy): hosts_need_release = [] hosts_target = [] + metric_name = self.METRIC_NAMES[ + self.config.datasource]['host_outlet_temp'] for node in nodes.values(): resource_id = node.uuid + outlet_temp = None - outlet_temp = self.ceilometer.statistic_aggregation( - resource_id=resource_id, - meter_name=self._meter, - period=self.period, - aggregate='avg') + if self.config.datasource == "ceilometer": + outlet_temp = self.ceilometer.statistic_aggregation( + resource_id=resource_id, + meter_name=metric_name, + period=self.period, + aggregate='avg' + ) + elif self.config.datasource == "gnocchi": + stop_time = datetime.datetime.utcnow() + start_time = stop_time - datetime.timedelta( + seconds=int(self.period)) + outlet_temp = self.gnocchi.statistic_aggregation( + resource_id=resource_id, + metric=metric_name, + granularity=self.granularity, + start_time=start_time, + stop_time=stop_time, + aggregation='mean' + ) # some hosts may not have outlet temp meters, remove from target if outlet_temp is None: LOG.warning("%s: no outlet temp data", resource_id) diff --git a/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py b/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py index 52cecfe6d..596bbbcc9 100644 --- a/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py +++ b/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py @@ -17,6 +17,7 @@ # limitations under the License. # import collections +import datetime import mock from watcher.applier.loading import default @@ -27,14 +28,25 @@ from watcher.decision_engine.strategy import strategies from watcher.tests import base from watcher.tests.decision_engine.model import ceilometer_metrics from watcher.tests.decision_engine.model import faker_cluster_state +from watcher.tests.decision_engine.model import gnocchi_metrics class TestOutletTempControl(base.TestCase): + scenarios = [ + ("Ceilometer", + {"datasource": "ceilometer", + "fake_datasource_cls": ceilometer_metrics.FakeCeilometerMetrics}), + ("Gnocchi", + {"datasource": "gnocchi", + "fake_datasource_cls": gnocchi_metrics.FakeGnocchiMetrics}), + ] + def setUp(self): super(TestOutletTempControl, self).setUp() # fake metrics - self.fake_metrics = ceilometer_metrics.FakeCeilometerMetrics() + self.fake_metrics = self.fake_datasource_cls() + # fake cluster self.fake_cluster = faker_cluster_state.FakerModelCollector() @@ -44,11 +56,11 @@ class TestOutletTempControl(base.TestCase): self.m_model = p_model.start() self.addCleanup(p_model.stop) - p_ceilometer = mock.patch.object( - strategies.OutletTempControl, "ceilometer", + p_datasource = mock.patch.object( + strategies.OutletTempControl, self.datasource, new_callable=mock.PropertyMock) - self.m_ceilometer = p_ceilometer.start() - self.addCleanup(p_ceilometer.stop) + self.m_datasource = p_datasource.start() + self.addCleanup(p_datasource.stop) p_audit_scope = mock.patch.object( strategies.OutletTempControl, "audit_scope", @@ -60,9 +72,10 @@ class TestOutletTempControl(base.TestCase): self.m_audit_scope.return_value = mock.Mock() self.m_model.return_value = model_root.ModelRoot() - self.m_ceilometer.return_value = mock.Mock( + self.m_datasource.return_value = mock.Mock( statistic_aggregation=self.fake_metrics.mock_get_statistics) - self.strategy = strategies.OutletTempControl(config=mock.Mock()) + self.strategy = strategies.OutletTempControl( + config=mock.Mock(datasource=self.datasource)) self.strategy.input_parameters = utils.Struct() self.strategy.input_parameters.update({'threshold': 34.3}) @@ -159,13 +172,36 @@ class TestOutletTempControl(base.TestCase): strategies.OutletTempControl, "ceilometer") m_ceilometer = p_ceilometer.start() self.addCleanup(p_ceilometer.stop) + p_gnocchi = mock.patch.object(strategies.OutletTempControl, "gnocchi") + m_gnocchi = p_gnocchi.start() + self.addCleanup(p_gnocchi.stop) + datetime_patcher = mock.patch.object( + datetime, 'datetime', + mock.Mock(wraps=datetime.datetime) + ) + mocked_datetime = datetime_patcher.start() + mocked_datetime.utcnow.return_value = datetime.datetime( + 2017, 3, 19, 18, 53, 11, 657417) + self.addCleanup(datetime_patcher.stop) m_ceilometer.statistic_aggregation = mock.Mock( side_effect=self.fake_metrics.mock_get_statistics) + m_gnocchi.statistic_aggregation = mock.Mock( + side_effect=self.fake_metrics.mock_get_statistics) node = model.get_node_by_uuid('Node_0') self.strategy.input_parameters.update({'threshold': 35.0}) self.strategy.threshold = 35.0 self.strategy.group_hosts_by_outlet_temp() - m_ceilometer.statistic_aggregation.assert_any_call( - aggregate='avg', - meter_name='hardware.ipmi.node.outlet_temperature', - period=30, resource_id=node.uuid) + if self.strategy.config.datasource == "ceilometer": + m_ceilometer.statistic_aggregation.assert_any_call( + aggregate='avg', + meter_name='hardware.ipmi.node.outlet_temperature', + period=30, resource_id=node.uuid) + elif self.strategy.config.datasource == "gnocchi": + stop_time = datetime.datetime.utcnow() + start_time = stop_time - datetime.timedelta( + seconds=int('30')) + m_gnocchi.statistic_aggregation.assert_called_with( + resource_id=mock.ANY, + metric='hardware.ipmi.node.outlet_temperature', + granularity=300, start_time=start_time, stop_time=stop_time, + aggregation='mean')