From 623fd95e618306e9c840b8b202a97803facb2680 Mon Sep 17 00:00:00 2001 From: spilkey-cisco <110940806+spilkey-cisco@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:46:22 -0700 Subject: [PATCH] Default implementation of under/over speed checks (#382) * Default implementation of under/over speed checks * Update comment, remove requirement for float conversion * Fan test coverage --- sonic_platform_base/fan_base.py | 46 ++++++++++ tests/fan_base_test.py | 158 ++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 tests/fan_base_test.py diff --git a/sonic_platform_base/fan_base.py b/sonic_platform_base/fan_base.py index 2b1259144..1eaa28859 100644 --- a/sonic_platform_base/fan_base.py +++ b/sonic_platform_base/fan_base.py @@ -66,6 +66,52 @@ def get_speed_tolerance(self): """ raise NotImplementedError + def is_under_speed(self): + """ + Calculates if the fan speed is under the tolerated low speed threshold + + Default calculation requires get_speed_tolerance to be implemented, and checks + if the current fan speed (expressed as a percentage) is lower than + percent below the target fan speed (expressed as a percentage) + + Returns: + A boolean, True if fan speed is under the low threshold, False if not + """ + speed = self.get_speed() + target_speed = self.get_target_speed() + tolerance = self.get_speed_tolerance() + + for param, value in (('speed', speed), ('target speed', target_speed), ('speed tolerance', tolerance)): + if not isinstance(value, int): + raise TypeError(f'Fan {param} is not an integer value: {param}={value}') + if value < 0 or value > 100: + raise ValueError(f'Fan {param} is not a valid percentage value: {param}={value}') + + return speed * 100 < target_speed * (100 - tolerance) + + def is_over_speed(self): + """ + Calculates if the fan speed is over the tolerated high speed threshold + + Default calculation requires get_speed_tolerance to be implemented, and checks + if the current fan speed (expressed as a percentage) is higher than + percent above the target fan speed (expressed as a percentage) + + Returns: + A boolean, True if fan speed is over the high threshold, False if not + """ + speed = self.get_speed() + target_speed = self.get_target_speed() + tolerance = self.get_speed_tolerance() + + for param, value in (('speed', speed), ('target speed', target_speed), ('speed tolerance', tolerance)): + if not isinstance(value, int): + raise TypeError(f'Fan {param} is not an integer value: {param}={value}') + if value < 0 or value > 100: + raise ValueError(f'Fan {param} is not a valid percentage value: {param}={value}') + + return speed * 100 > target_speed * (100 + tolerance) + def set_speed(self, speed): """ Sets the fan speed diff --git a/tests/fan_base_test.py b/tests/fan_base_test.py new file mode 100644 index 000000000..af3055e28 --- /dev/null +++ b/tests/fan_base_test.py @@ -0,0 +1,158 @@ +''' +Test FanBase module +''' + +from unittest import mock +from sonic_platform_base.fan_base import FanBase + +class TestFanBase: + ''' + Collection of FanBase test methods + ''' + + @staticmethod + def test_is_under_speed(): + ''' + Test fan.is_under_speed default implementation + ''' + fan = FanBase() + fan.get_speed = mock.MagicMock(return_value=100) + fan.get_target_speed = mock.MagicMock(return_value=50) + fan.get_speed_tolerance = mock.MagicMock(return_value=10) + + for func in (fan.get_speed, fan.get_target_speed, fan.get_speed_tolerance): + return_val = func() + + # Check type and bounds errors + for value, exc_type in ((None, TypeError), (-1, ValueError), (101, ValueError)): + func.return_value = value + expected_exception = False + try: + fan.is_under_speed() + except Exception as exc: + expected_exception = isinstance(exc, exc_type) + assert expected_exception + + # Reset function return value + func.return_value = return_val + + # speed=100, minimum tolerated speed=45, not under speed + assert not fan.is_under_speed() + + # speed=46, minimum tolerated speed=45, not under speed + fan.get_speed.return_value = 46 + assert not fan.is_under_speed() + + # speed=45, minimum tolerated speed=45, not under speed + fan.get_speed.return_value = 45 + assert not fan.is_under_speed() + + # speed=44, minimum tolerated speed=45, under speed + fan.get_speed.return_value = 44 + assert fan.is_under_speed() + + # speed=44, minimum tolerated speed=40, not under speed + fan.get_speed_tolerance.return_value = 20 + assert not fan.is_under_speed() + + # speed=41, minimum tolerated speed=40, not under speed + fan.get_speed.return_value = 41 + assert not fan.is_under_speed() + + # speed=40, minimum tolerated speed=40, not under speed + fan.get_speed.return_value = 40 + assert not fan.is_under_speed() + + # speed=39, minimum tolerated speed=40, under speed + fan.get_speed.return_value = 39 + assert fan.is_under_speed() + + # speed=1, minimum tolerated speed=40, under speed + fan.get_speed.return_value = 1 + assert fan.is_under_speed() + + @staticmethod + def test_is_over_speed(): + ''' + test fan.is_over_speed default implementation + ''' + fan = FanBase() + fan.get_speed = mock.MagicMock(return_value=1) + fan.get_target_speed = mock.MagicMock(return_value=50) + fan.get_speed_tolerance = mock.MagicMock(return_value=10) + + for func in (fan.get_speed, fan.get_target_speed, fan.get_speed_tolerance): + return_val = func() + + # Check type and bounds errors + for value, exc_type in ((None, TypeError), (-1, ValueError), (101, ValueError)): + func.return_value = value + expected_exception = False + try: + fan.is_under_speed() + except Exception as exc: + expected_exception = isinstance(exc, exc_type) + assert expected_exception + + # Reset function return value + func.return_value = return_val + + # speed=1, maximum tolerated speed=55, not over speed + assert not fan.is_over_speed() + + # speed=54, maximum tolerated speed=55, not over speed + fan.get_speed.return_value = 54 + assert not fan.is_over_speed() + + # speed=55, maximum tolerated speed=55, not over speed + fan.get_speed.return_value = 55 + assert not fan.is_over_speed() + + # speed=56, maximum tolerated speed=55, over speed + fan.get_speed.return_value = 56 + assert fan.is_over_speed() + + # speed=56, maximum tolerated speed=60, not over speed + fan.get_speed_tolerance.return_value = 20 + assert not fan.is_over_speed() + + # speed=59, maximum tolerated speed=60, not over speed + fan.get_speed.return_value = 59 + assert not fan.is_over_speed() + + # speed=60, maximum tolerated speed=60, not over speed + fan.get_speed.return_value = 60 + assert not fan.is_over_speed() + + # speed=61, maximum tolerated speed=60, over speed + fan.get_speed.return_value = 61 + assert fan.is_over_speed() + + # speed=100, maximum tolerated speed=60, over speed + fan.get_speed.return_value = 100 + assert fan.is_over_speed() + + @staticmethod + def test_fan_base(): + ''' + Verify unimplemented methods + ''' + fan = FanBase() + not_implemented_methods = [ + (fan.get_direction,), + (fan.get_speed,), + (fan.get_target_speed,), + (fan.get_speed_tolerance,), + (fan.set_speed, 50), + (fan.set_status_led, 'green'), + (fan.get_status_led,)] + + for method in not_implemented_methods: + expected_exception = False + try: + func = method[0] + args = method[1:] + func(*args) + except Exception as exc: + expected_exception = isinstance(exc, NotImplementedError) + assert expected_exception