diff --git a/sonic-pcied/scripts/pcied b/sonic-pcied/scripts/pcied index f88f159d1..fb59fff3f 100644 --- a/sonic-pcied/scripts/pcied +++ b/sonic-pcied/scripts/pcied @@ -131,7 +131,8 @@ class DaemonPcied(daemon_base.DaemonBase): self.aer_stats = {} if Id is not None: - self.device_table.set(self.device_name, [('id', Id)]) + fvp = swsscommon.FieldValuePairs([('id', Id)]) + self.device_table.set(self.device_name, fvp) self.aer_stats = platform_pcieutil.get_pcie_aer_stats(bus=Bus, dev=Dev, func=Fn) self.update_aer_to_statedb() diff --git a/sonic-pcied/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-pcied/tests/mocked_libs/swsscommon/swsscommon.py index e5aa2a2ba..ddb3cd686 100644 --- a/sonic-pcied/tests/mocked_libs/swsscommon/swsscommon.py +++ b/sonic-pcied/tests/mocked_libs/swsscommon/swsscommon.py @@ -26,6 +26,9 @@ def get(self, key): def get_size(self): return (len(self.mock_dict)) + def getKeys(self): + return list(self.mock_dict.keys()) + class FieldValuePairs: fv_dict = {} diff --git a/sonic-pcied/tests/test_DaemonPcied.py b/sonic-pcied/tests/test_DaemonPcied.py index 2c3c953e7..f3e343654 100644 --- a/sonic-pcied/tests/test_DaemonPcied.py +++ b/sonic-pcied/tests/test_DaemonPcied.py @@ -158,22 +158,18 @@ def test_check_pcie_devices(self): @mock.patch('pcied.load_platform_pcieutil', mock.MagicMock()) def test_update_pcie_devices_status_db(self): daemon_pcied = pcied.DaemonPcied(SYSLOG_IDENTIFIER) - daemon_pcied.status_table = mock.MagicMock() daemon_pcied.log_info = mock.MagicMock() daemon_pcied.log_error = mock.MagicMock() # test for pass resultInfo daemon_pcied.update_pcie_devices_status_db(0) - assert daemon_pcied.status_table.set.call_count == 1 assert daemon_pcied.log_info.call_count == 1 assert daemon_pcied.log_error.call_count == 0 - daemon_pcied.status_table.set.reset_mock() daemon_pcied.log_info.reset_mock() # test for resultInfo with 1 device failed to detect daemon_pcied.update_pcie_devices_status_db(1) - assert daemon_pcied.status_table.set.call_count == 1 assert daemon_pcied.log_info.call_count == 0 assert daemon_pcied.log_error.call_count == 1 @@ -182,20 +178,17 @@ def test_update_pcie_devices_status_db(self): @mock.patch('pcied.read_id_file') def test_check_n_update_pcie_aer_stats(self, mock_read): daemon_pcied = pcied.DaemonPcied(SYSLOG_IDENTIFIER) - daemon_pcied.device_table = mock.MagicMock() daemon_pcied.update_aer_to_statedb = mock.MagicMock() pcied.platform_pcieutil.get_pcie_aer_stats = mock.MagicMock() mock_read.return_value = None daemon_pcied.check_n_update_pcie_aer_stats(0,1,0) assert daemon_pcied.update_aer_to_statedb.call_count == 0 - assert daemon_pcied.device_table.set.call_count == 0 assert pcied.platform_pcieutil.get_pcie_aer_stats.call_count == 0 mock_read.return_value = '1714' daemon_pcied.check_n_update_pcie_aer_stats(0,1,0) assert daemon_pcied.update_aer_to_statedb.call_count == 1 - assert daemon_pcied.device_table.set.call_count == 1 assert pcied.platform_pcieutil.get_pcie_aer_stats.call_count == 1 @@ -203,7 +196,6 @@ def test_check_n_update_pcie_aer_stats(self, mock_read): def test_update_aer_to_statedb(self): daemon_pcied = pcied.DaemonPcied(SYSLOG_IDENTIFIER) daemon_pcied.log_debug = mock.MagicMock() - daemon_pcied.device_table = mock.MagicMock() daemon_pcied.device_name = mock.MagicMock() daemon_pcied.aer_stats = pcie_aer_stats_no_err @@ -220,6 +212,3 @@ def test_update_aer_to_statedb(self): daemon_pcied.update_aer_to_statedb() assert daemon_pcied.log_debug.call_count == 0 - assert daemon_pcied.device_table.set.call_count == 1 - - daemon_pcied.device_table.set.reset_mock() diff --git a/sonic-psud/scripts/psud b/sonic-psud/scripts/psud index b52b98948..ce588589c 100644 --- a/sonic-psud/scripts/psud +++ b/sonic-psud/scripts/psud @@ -380,6 +380,7 @@ class DaemonPsud(daemon_base.DaemonBase): self.psu_tbl = None self.psu_chassis_info = None self.first_run = True + self.psu_threshold_exceeded_logged = False global platform_psuutil global platform_chassis @@ -458,6 +459,7 @@ class DaemonPsud(daemon_base.DaemonBase): if not platform_chassis: return + self.psu_threshold_exceeded_logged = False for index, psu in enumerate(platform_chassis.get_all_psus()): try: self._update_single_psu_data(index + 1, psu) @@ -535,25 +537,37 @@ class DaemonPsud(daemon_base.DaemonBase): power_warning_suppress_threshold = try_get(psu.get_psu_power_warning_suppress_threshold, NOT_AVAILABLE) power_critical_threshold = try_get(psu.get_psu_power_critical_threshold, NOT_AVAILABLE) if psu_status.check_psu_power_threshold: + # Calculate total power + system_power = float(power) + for _, other_psu in enumerate(platform_chassis.get_all_psus()): + if other_psu is psu: + # Skip the current PSU + continue + power_str = try_get(other_psu.get_power, NOT_AVAILABLE) + if power_str != NOT_AVAILABLE: + system_power += float(power_str) + if power_warning_suppress_threshold == NOT_AVAILABLE or power_critical_threshold == NOT_AVAILABLE: self.log_error("PSU power thresholds become invalid: threshold {} critical threshold {}".format(power_warning_suppress_threshold, power_critical_threshold)) psu_status.check_psu_power_threshold = False psu_status.power_exceeded_threshold = False elif psu_status.power_exceeded_threshold: # The failing threshold is the warning threshold - if power < power_warning_suppress_threshold: + if system_power < power_warning_suppress_threshold: # Clear alarm power_exceeded_threshold = False else: # The rising threshold is the critical threshold - if power >= power_critical_threshold: + if system_power >= power_critical_threshold: # Raise alarm power_exceeded_threshold = True - if psu_status.set_power_exceed_threshold(power_exceeded_threshold): + if psu_status.set_power_exceed_threshold(power_exceeded_threshold) and not self.psu_threshold_exceeded_logged: + # Since this is a system level PSU power exceeding check, we do not need to log it for each PSU log_on_status_changed(self, not psu_status.power_exceeded_threshold, - 'PSU power warning cleared: {} power {} is back to normal.'.format(name, power), - 'PSU power warning: {} power {} exceeds critical threshold {}.'.format(name, power, power_critical_threshold)) + 'PSU power warning cleared: system power {} is back to normal, below the warning suppress threshold {}.'.format(system_power, power_warning_suppress_threshold), + 'PSU power warning: system power {} exceeds the critical threshold {}.'.format(system_power, power_critical_threshold)) + self.psu_threshold_exceeded_logged = True if presence and psu_status.set_voltage(voltage, voltage_high_threshold, voltage_low_threshold): set_led = True diff --git a/sonic-psud/tests/test_DaemonPsud.py b/sonic-psud/tests/test_DaemonPsud.py index 482eb1cdd..805c76cc0 100644 --- a/sonic-psud/tests/test_DaemonPsud.py +++ b/sonic-psud/tests/test_DaemonPsud.py @@ -188,19 +188,22 @@ def test_power_threshold(self): psu = MockPsu('PSU 1', 0, True, 'Fake Model', '12345678', '1234') psud.platform_chassis = MockChassis() psud.platform_chassis._psu_list.append(psu) + another_psu = MockPsu('PSU 2', 0, True, 'Fake Model', '12345678', '1234') + another_psu.set_power(10.0) + psud.platform_chassis._psu_list.append(another_psu) daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) daemon_psud.psu_tbl = mock.MagicMock() - psu.get_psu_power_critical_threshold = mock.MagicMock(return_value=120.0) - psu.get_psu_power_warning_suppress_threshold = mock.MagicMock(return_value=110.0) + psu.get_psu_power_critical_threshold = mock.MagicMock(return_value=130.0) + psu.get_psu_power_warning_suppress_threshold = mock.MagicMock(return_value=120.0) # Normal start. All good and all thresholds are supported # Power is in normal range (below warning threshold) daemon_psud._update_single_psu_data(1, psu) assert daemon_psud.psu_status_dict[1].check_psu_power_threshold assert not daemon_psud.psu_status_dict[1].power_exceeded_threshold - expected_fvp = self._construct_expected_fvp(100.0, 110.0, 120.0, False) + expected_fvp = self._construct_expected_fvp(100.0, 120.0, 130.0, False) daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) daemon_psud._update_led_color() assert psu.STATUS_LED_COLOR_GREEN == psu.get_status_led() @@ -213,7 +216,7 @@ def test_power_threshold(self): daemon_psud._update_single_psu_data(1, psu) assert daemon_psud.psu_status_dict[1].check_psu_power_threshold assert not daemon_psud.psu_status_dict[1].power_exceeded_threshold - expected_fvp = self._construct_expected_fvp(115.0, 110.0, 120.0, False) + expected_fvp = self._construct_expected_fvp(115.0, 120.0, 130.0, False) daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) daemon_psud._update_led_color() assert psu.STATUS_LED_COLOR_GREEN == psu.get_status_led() @@ -224,7 +227,7 @@ def test_power_threshold(self): daemon_psud._update_single_psu_data(1, psu) assert daemon_psud.psu_status_dict[1].check_psu_power_threshold assert daemon_psud.psu_status_dict[1].power_exceeded_threshold - expected_fvp = self._construct_expected_fvp(125.0, 110.0, 120.0, True) + expected_fvp = self._construct_expected_fvp(125.0, 120.0, 130.0, True) daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) daemon_psud._update_led_color() assert psu.STATUS_LED_COLOR_GREEN == psu.get_status_led() @@ -235,7 +238,7 @@ def test_power_threshold(self): daemon_psud._update_single_psu_data(1, psu) assert daemon_psud.psu_status_dict[1].check_psu_power_threshold assert daemon_psud.psu_status_dict[1].power_exceeded_threshold - expected_fvp = self._construct_expected_fvp(115.0, 110.0, 120.0, True) + expected_fvp = self._construct_expected_fvp(115.0, 120.0, 130.0, True) daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) daemon_psud._update_led_color() assert psu.STATUS_LED_COLOR_GREEN == psu.get_status_led() @@ -246,7 +249,7 @@ def test_power_threshold(self): daemon_psud._update_single_psu_data(1, psu) assert daemon_psud.psu_status_dict[1].check_psu_power_threshold assert not daemon_psud.psu_status_dict[1].power_exceeded_threshold - expected_fvp = self._construct_expected_fvp(105.0, 110.0, 120.0, False) + expected_fvp = self._construct_expected_fvp(105.0, 120.0, 130.0, False) daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) assert psu.STATUS_LED_COLOR_GREEN == psu.get_status_led() daemon_psud._update_led_color() @@ -257,7 +260,7 @@ def test_power_threshold(self): daemon_psud._update_single_psu_data(1, psu) assert daemon_psud.psu_status_dict[1].check_psu_power_threshold assert daemon_psud.psu_status_dict[1].power_exceeded_threshold - expected_fvp = self._construct_expected_fvp(125.0, 110.0, 120.0, True) + expected_fvp = self._construct_expected_fvp(125.0, 120.0, 130.0, True) daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) daemon_psud._update_led_color() assert psu.STATUS_LED_COLOR_GREEN == psu.get_status_led() @@ -268,7 +271,7 @@ def test_power_threshold(self): daemon_psud._update_single_psu_data(1, psu) assert daemon_psud.psu_status_dict[1].check_psu_power_threshold assert not daemon_psud.psu_status_dict[1].power_exceeded_threshold - expected_fvp = self._construct_expected_fvp(105.0, 110.0, 120.0, False) + expected_fvp = self._construct_expected_fvp(105.0, 120.0, 130.0, False) daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) daemon_psud._update_led_color() assert psu.STATUS_LED_COLOR_GREEN == psu.get_status_led() diff --git a/sonic-thermalctld/scripts/thermalctld b/sonic-thermalctld/scripts/thermalctld index 806bf5413..0abc0f4b0 100644 --- a/sonic-thermalctld/scripts/thermalctld +++ b/sonic-thermalctld/scripts/thermalctld @@ -119,57 +119,35 @@ class FanStatus(logger.Logger): self.status = status return True - def _check_speed_value_available(self, speed, target_speed, tolerance, current_status): - if speed == NOT_AVAILABLE or target_speed == NOT_AVAILABLE or tolerance == NOT_AVAILABLE: - if isinstance(tolerance, int) and (tolerance > 100 or tolerance < 0): - self.log_warning('Invalid tolerance value: {}'.format(tolerance)) - return False - - if current_status is True: - self.log_warning('Fan speed or target_speed or tolerance became unavailable, ' - 'speed={}, target_speed={}, tolerance={}'.format(speed, target_speed, tolerance)) - return False - return True - - def set_under_speed(self, speed, target_speed, tolerance): + def set_under_speed(self, is_under_speed): """ Set and cache Fan under speed status - :param speed: Fan speed - :param target_speed: Fan target speed - :param tolerance: Threshold between Fan speed and target speed + :param is_under_speed: Fan under speed threshold status :return: True if status changed else False """ - if not self._check_speed_value_available(speed, target_speed, tolerance, self.under_speed): - old_status = self.under_speed - self.under_speed = False - return old_status != self.under_speed + if is_under_speed == NOT_AVAILABLE: + if self.under_speed: + self.log_warning('Fan under speed threshold check became unavailable') + is_under_speed = False - status = speed < target_speed * (1 - float(tolerance) / 100) - if status == self.under_speed: - return False + old_status = self.under_speed + self.under_speed = is_under_speed + return old_status != self.under_speed - self.under_speed = status - return True - - def set_over_speed(self, speed, target_speed, tolerance): + def set_over_speed(self, is_over_speed): """ Set and cache Fan over speed status - :param speed: Fan speed - :param target_speed: Fan target speed - :param tolerance: Threshold between Fan speed and target speed + :param is_over_speed: Fan over speed threshold status :return: True if status changed else False """ - if not self._check_speed_value_available(speed, target_speed, tolerance, self.over_speed): - old_status = self.over_speed - self.over_speed = False - return old_status != self.over_speed + if is_over_speed == NOT_AVAILABLE: + if self.over_speed: + self.log_warning('Fan over speed threshold check became unavailable') + is_over_speed = False - status = speed > target_speed * (1 + float(tolerance) / 100) - if status == self.over_speed: - return False - - self.over_speed = status - return True + old_status = self.over_speed + self.over_speed = is_over_speed + return old_status != self.over_speed def is_ok(self): """ @@ -315,16 +293,18 @@ class FanUpdater(logger.Logger): fan_status = self.fan_status_dict[fan_name] speed = NOT_AVAILABLE - speed_tolerance = NOT_AVAILABLE speed_target = NOT_AVAILABLE + is_under_speed = NOT_AVAILABLE + is_over_speed = NOT_AVAILABLE fan_fault_status = NOT_AVAILABLE fan_direction = NOT_AVAILABLE is_replaceable = try_get(fan.is_replaceable, False) presence = try_get(fan.get_presence, False) if presence: speed = try_get(fan.get_speed) - speed_tolerance = try_get(fan.get_speed_tolerance) speed_target = try_get(fan.get_target_speed) + is_under_speed = try_get(fan.is_under_speed) + is_over_speed = try_get(fan.is_over_speed) fan_fault_status = try_get(fan.get_status, False) fan_direction = try_get(fan.get_direction) @@ -344,20 +324,20 @@ class FanUpdater(logger.Logger): 'Fan fault warning: {} is broken'.format(fan_name) ) - if presence and fan_status.set_under_speed(speed, speed_target, speed_tolerance): + if presence and fan_status.set_under_speed(is_under_speed): set_led = True self._log_on_status_changed(not fan_status.under_speed, 'Fan low speed warning cleared: {} speed is back to normal'.format(fan_name), - 'Fan low speed warning: {} current speed={}, target speed={}, tolerance={}'. - format(fan_name, speed, speed_target, speed_tolerance) + 'Fan low speed warning: {} current speed={}, target speed={}'. + format(fan_name, speed, speed_target) ) - if presence and fan_status.set_over_speed(speed, speed_target, speed_tolerance): + if presence and fan_status.set_over_speed(is_over_speed): set_led = True self._log_on_status_changed(not fan_status.over_speed, 'Fan high speed warning cleared: {} speed is back to normal'.format(fan_name), - 'Fan high speed warning: {} target speed={}, current speed={}, tolerance={}'. - format(fan_name, speed_target, speed, speed_tolerance) + 'Fan high speed warning: {} current speed={}, target speed={}'. + format(fan_name, speed, speed_target) ) # We don't set PSU led here, PSU led will be handled in psud @@ -376,8 +356,9 @@ class FanUpdater(logger.Logger): ('status', str(fan_fault_status)), ('direction', str(fan_direction)), ('speed', str(speed)), - ('speed_tolerance', str(speed_tolerance)), ('speed_target', str(speed_target)), + ('is_under_speed', str(is_under_speed)), + ('is_over_speed', str(is_over_speed)), ('is_replaceable', str(is_replaceable)), ('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S')) ]) diff --git a/sonic-thermalctld/tests/test_thermalctld.py b/sonic-thermalctld/tests/test_thermalctld.py index 3c8d14c89..6fe9ccbd1 100644 --- a/sonic-thermalctld/tests/test_thermalctld.py +++ b/sonic-thermalctld/tests/test_thermalctld.py @@ -68,30 +68,6 @@ class TestFanStatus(object): """ Test cases to cover functionality in FanStatus class """ - def test_check_speed_value_available(self): - fan_status = thermalctld.FanStatus() - - ret = fan_status._check_speed_value_available(30, 32, 5, True) - assert ret == True - assert fan_status.log_warning.call_count == 0 - - ret = fan_status._check_speed_value_available(thermalctld.NOT_AVAILABLE, 32, 105, True) - assert ret == False - assert fan_status.log_warning.call_count == 1 - fan_status.log_warning.assert_called_with('Invalid tolerance value: 105') - - # Reset - fan_status.log_warning.reset_mock() - - ret = fan_status._check_speed_value_available(thermalctld.NOT_AVAILABLE, 32, 5, False) - assert ret == False - assert fan_status.log_warning.call_count == 0 - - ret = fan_status._check_speed_value_available(thermalctld.NOT_AVAILABLE, 32, 5, True) - assert ret == False - assert fan_status.log_warning.call_count == 1 - fan_status.log_warning.assert_called_with('Fan speed or target_speed or tolerance became unavailable, speed=N/A, target_speed=32, tolerance=5') - def test_set_presence(self): fan_status = thermalctld.FanStatus() ret = fan_status.set_presence(True) @@ -104,52 +80,48 @@ def test_set_presence(self): def test_set_under_speed(self): fan_status = thermalctld.FanStatus() - ret = fan_status.set_under_speed(thermalctld.NOT_AVAILABLE, thermalctld.NOT_AVAILABLE, thermalctld.NOT_AVAILABLE) - assert not ret - - ret = fan_status.set_under_speed(thermalctld.NOT_AVAILABLE, thermalctld.NOT_AVAILABLE, 0) - assert not ret - ret = fan_status.set_under_speed(thermalctld.NOT_AVAILABLE, 0, 0) + ret = fan_status.set_under_speed(False) assert not ret - ret = fan_status.set_under_speed(0, 0, 0) - assert not ret - - ret = fan_status.set_under_speed(80, 100, 19) + ret = fan_status.set_under_speed(True) assert ret assert fan_status.under_speed assert not fan_status.is_ok() - ret = fan_status.set_under_speed(81, 100, 19) + ret = fan_status.set_under_speed(True) + assert not ret + + ret = fan_status.set_under_speed(False) assert ret assert not fan_status.under_speed assert fan_status.is_ok() - def test_set_over_speed(self): - fan_status = thermalctld.FanStatus() - ret = fan_status.set_over_speed(thermalctld.NOT_AVAILABLE, thermalctld.NOT_AVAILABLE, thermalctld.NOT_AVAILABLE) - assert not ret - - ret = fan_status.set_over_speed(thermalctld.NOT_AVAILABLE, thermalctld.NOT_AVAILABLE, 0) + ret = fan_status.set_under_speed(False) assert not ret - ret = fan_status.set_over_speed(thermalctld.NOT_AVAILABLE, 0, 0) - assert not ret + def test_set_over_speed(self): + fan_status = thermalctld.FanStatus() - ret = fan_status.set_over_speed(0, 0, 0) + ret = fan_status.set_over_speed(False) assert not ret - ret = fan_status.set_over_speed(120, 100, 19) + ret = fan_status.set_over_speed(True) assert ret assert fan_status.over_speed assert not fan_status.is_ok() - ret = fan_status.set_over_speed(120, 100, 21) + ret = fan_status.set_over_speed(True) + assert not ret + + ret = fan_status.set_over_speed(False) assert ret assert not fan_status.over_speed assert fan_status.is_ok() + ret = fan_status.set_over_speed(False) + assert not ret + class TestFanUpdater(object): """ @@ -251,7 +223,7 @@ def test_fan_under_speed(self): fan_list = chassis.get_all_fans() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_RED assert fan_updater.log_warning.call_count == 1 - fan_updater.log_warning.assert_called_with('Fan low speed warning: FanDrawer 0 fan 1 current speed=1, target speed=2, tolerance=0') + fan_updater.log_warning.assert_called_with('Fan low speed warning: FanDrawer 0 fan 1 current speed=1, target speed=2') fan_list[0].make_normal_speed() fan_updater.update() @@ -267,7 +239,7 @@ def test_fan_over_speed(self): fan_list = chassis.get_all_fans() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_RED assert fan_updater.log_warning.call_count == 1 - fan_updater.log_warning.assert_called_with('Fan high speed warning: FanDrawer 0 fan 1 target speed=1, current speed=2, tolerance=0') + fan_updater.log_warning.assert_called_with('Fan high speed warning: FanDrawer 0 fan 1 current speed=2, target speed=1') fan_list[0].make_normal_speed() fan_updater.update() diff --git a/sonic-xcvrd/tests/optics_si_settings.json b/sonic-xcvrd/tests/optics_si_settings.json new file mode 100644 index 000000000..2b9defc81 --- /dev/null +++ b/sonic-xcvrd/tests/optics_si_settings.json @@ -0,0 +1,222 @@ +{ + "GLOBAL_MEDIA_SETTINGS":{ + "0-31":{ + "100G_SPEED":{ + "CREDO-CAC82X321M2MC0HW":{ + "OutputEqPreCursorTargetRx":{ + "OutputEqPreCursorTargetRx1":3, + "OutputEqPreCursorTargetRx2":3, + "OutputEqPreCursorTargetRx3":3, + "OutputEqPreCursorTargetRx4":3, + "OutputEqPreCursorTargetRx5":3, + "OutputEqPreCursorTargetRx6":3, + "OutputEqPreCursorTargetRx7":3, + "OutputEqPreCursorTargetRx8":3 + }, + "OutputEqPostCursorTargetRx":{ + "OutputEqPostCursorTargetRx1":0, + "OutputEqPostCursorTargetRx2":0, + "OutputEqPostCursorTargetRx3":0, + "OutputEqPostCursorTargetRx4":0, + "OutputEqPostCursorTargetRx5":0, + "OutputEqPostCursorTargetRx6":0, + "OutputEqPostCursorTargetRx7":0, + "OutputEqPostCursorTargetRx8":0 + }, + "OutputAmplitudeTargetRx":{ + "OutputAmplitudeTargetRx1":0, + "OutputAmplitudeTargetRx2":0, + "OutputAmplitudeTargetRx3":0, + "OutputAmplitudeTargetRx4":0, + "OutputAmplitudeTargetRx5":0, + "OutputAmplitudeTargetRx6":0, + "OutputAmplitudeTargetRx7":0, + "OutputAmplitudeTargetRx8":0 + } + } + } + } + }, + "PORT_MEDIA_SETTINGS":{ + "0":{ + "100G_SPEED":{ + "CREDO-CAC82X321M2MC0HW":{ + "OutputEqPreCursorTargetRx":{ + "OutputEqPreCursorTargetRx1":3, + "OutputEqPreCursorTargetRx2":3, + "OutputEqPreCursorTargetRx3":3, + "OutputEqPreCursorTargetRx4":3, + "OutputEqPreCursorTargetRx5":3, + "OutputEqPreCursorTargetRx6":3, + "OutputEqPreCursorTargetRx7":3, + "OutputEqPreCursorTargetRx8":3 + }, + "OutputEqPostCursorTargetRx":{ + "OutputEqPostCursorTargetRx1":0, + "OutputEqPostCursorTargetRx2":0, + "OutputEqPostCursorTargetRx3":0, + "OutputEqPostCursorTargetRx4":0, + "OutputEqPostCursorTargetRx5":0, + "OutputEqPostCursorTargetRx6":0, + "OutputEqPostCursorTargetRx7":0, + "OutputEqPostCursorTargetRx8":0 + }, + "OutputAmplitudeTargetRx":{ + "OutputAmplitudeTargetRx1":0, + "OutputAmplitudeTargetRx2":0, + "OutputAmplitudeTargetRx3":0, + "OutputAmplitudeTargetRx4":0, + "OutputAmplitudeTargetRx5":0, + "OutputAmplitudeTargetRx6":0, + "OutputAmplitudeTargetRx7":0, + "OutputAmplitudeTargetRx8":0 + } + } + } + }, + "1":{ + "100G_SPEED":{ + "Default":{ + "OutputEqPreCursorTargetRx":{ + "OutputEqPreCursorTargetRx1":3, + "OutputEqPreCursorTargetRx2":3, + "OutputEqPreCursorTargetRx3":3, + "OutputEqPreCursorTargetRx4":3, + "OutputEqPreCursorTargetRx5":3, + "OutputEqPreCursorTargetRx6":3, + "OutputEqPreCursorTargetRx7":3, + "OutputEqPreCursorTargetRx8":3 + }, + "OutputEqPostCursorTargetRx":{ + "OutputEqPostCursorTargetRx1":0, + "OutputEqPostCursorTargetRx2":0, + "OutputEqPostCursorTargetRx3":0, + "OutputEqPostCursorTargetRx4":0, + "OutputEqPostCursorTargetRx5":0, + "OutputEqPostCursorTargetRx6":0, + "OutputEqPostCursorTargetRx7":0, + "OutputEqPostCursorTargetRx8":0 + }, + "OutputAmplitudeTargetRx":{ + "OutputAmplitudeTargetRx1":1, + "OutputAmplitudeTargetRx2":1, + "OutputAmplitudeTargetRx3":1, + "OutputAmplitudeTargetRx4":1, + "OutputAmplitudeTargetRx5":1, + "OutputAmplitudeTargetRx6":1, + "OutputAmplitudeTargetRx7":1, + "OutputAmplitudeTargetRx8":1 + } + } + } + }, + "10":{ + "100G_SPEED":{ + "Default":{ + "OutputEqPreCursorTargetRx":{ + "OutputEqPreCursorTargetRx1":3, + "OutputEqPreCursorTargetRx2":3, + "OutputEqPreCursorTargetRx3":3, + "OutputEqPreCursorTargetRx4":3, + "OutputEqPreCursorTargetRx5":3, + "OutputEqPreCursorTargetRx6":3, + "OutputEqPreCursorTargetRx7":3, + "OutputEqPreCursorTargetRx8":3 + }, + "OutputEqPostCursorTargetRx":{ + "OutputEqPostCursorTargetRx1":0, + "OutputEqPostCursorTargetRx2":0, + "OutputEqPostCursorTargetRx3":0, + "OutputEqPostCursorTargetRx4":0, + "OutputEqPostCursorTargetRx5":0, + "OutputEqPostCursorTargetRx6":0, + "OutputEqPostCursorTargetRx7":0, + "OutputEqPostCursorTargetRx8":0 + }, + "OutputAmplitudeTargetRx":{ + "OutputAmplitudeTargetRx1":1, + "OutputAmplitudeTargetRx2":1, + "OutputAmplitudeTargetRx3":1, + "OutputAmplitudeTargetRx4":1, + "OutputAmplitudeTargetRx5":1, + "OutputAmplitudeTargetRx6":1, + "OutputAmplitudeTargetRx7":1, + "OutputAmplitudeTargetRx8":1 + } + } + } + }, + "11":{ + "100G_SPEED":{ + "Default":{ + "OutputEqPreCursorTargetRx":{ + "OutputEqPreCursorTargetRx1":3, + "OutputEqPreCursorTargetRx2":3, + "OutputEqPreCursorTargetRx3":3, + "OutputEqPreCursorTargetRx4":3, + "OutputEqPreCursorTargetRx5":3, + "OutputEqPreCursorTargetRx6":3, + "OutputEqPreCursorTargetRx7":3, + "OutputEqPreCursorTargetRx8":3 + }, + "OutputEqPostCursorTargetRx":{ + "OutputEqPostCursorTargetRx1":0, + "OutputEqPostCursorTargetRx2":0, + "OutputEqPostCursorTargetRx3":0, + "OutputEqPostCursorTargetRx4":0, + "OutputEqPostCursorTargetRx5":0, + "OutputEqPostCursorTargetRx6":0, + "OutputEqPostCursorTargetRx7":0, + "OutputEqPostCursorTargetRx8":0 + }, + "OutputAmplitudeTargetRx":{ + "OutputAmplitudeTargetRx1":1, + "OutputAmplitudeTargetRx2":1, + "OutputAmplitudeTargetRx3":1, + "OutputAmplitudeTargetRx4":1, + "OutputAmplitudeTargetRx5":1, + "OutputAmplitudeTargetRx6":1, + "OutputAmplitudeTargetRx7":1, + "OutputAmplitudeTargetRx8":1 + } + } + } + }, + "12":{ + "100G_SPEED":{ + "Default":{ + "OutputEqPreCursorTargetRx":{ + "OutputEqPreCursorTargetRx1":3, + "OutputEqPreCursorTargetRx2":3, + "OutputEqPreCursorTargetRx3":3, + "OutputEqPreCursorTargetRx4":3, + "OutputEqPreCursorTargetRx5":3, + "OutputEqPreCursorTargetRx6":3, + "OutputEqPreCursorTargetRx7":3, + "OutputEqPreCursorTargetRx8":3 + }, + "OutputEqPostCursorTargetRx":{ + "OutputEqPostCursorTargetRx1":0, + "OutputEqPostCursorTargetRx2":0, + "OutputEqPostCursorTargetRx3":0, + "OutputEqPostCursorTargetRx4":0, + "OutputEqPostCursorTargetRx5":0, + "OutputEqPostCursorTargetRx6":0, + "OutputEqPostCursorTargetRx7":0, + "OutputEqPostCursorTargetRx8":0 + }, + "OutputAmplitudeTargetRx":{ + "OutputAmplitudeTargetRx1":1, + "OutputAmplitudeTargetRx2":1, + "OutputAmplitudeTargetRx3":1, + "OutputAmplitudeTargetRx4":1, + "OutputAmplitudeTargetRx5":1, + "OutputAmplitudeTargetRx6":1, + "OutputAmplitudeTargetRx7":1, + "OutputAmplitudeTargetRx8":1 + } + } + } + } + } +} diff --git a/sonic-xcvrd/tests/test_xcvrd.py b/sonic-xcvrd/tests/test_xcvrd.py index 2581c7a0d..a3adf5823 100644 --- a/sonic-xcvrd/tests/test_xcvrd.py +++ b/sonic-xcvrd/tests/test_xcvrd.py @@ -1,6 +1,7 @@ #from unittest.mock import DEFAULT from xcvrd.xcvrd_utilities.port_mapping import * from xcvrd.xcvrd_utilities.sfp_status_helper import * +from xcvrd.xcvrd_utilities.optics_si_parser import * from xcvrd.xcvrd import * import pytest import copy @@ -41,6 +42,14 @@ global_media_settings = media_settings_with_comma_dict['GLOBAL_MEDIA_SETTINGS'].pop('1-32') media_settings_with_comma_dict['GLOBAL_MEDIA_SETTINGS']['1-5,6,7-20,21-32'] = global_media_settings +with open(os.path.join(test_path, 'optics_si_settings.json'), 'r') as fn: + optics_si_settings_dict = json.load(fn) +port_optics_si_settings = {} +optics_si_settings_with_comma_dict = copy.deepcopy(optics_si_settings_dict) +global_optics_si_settings = optics_si_settings_with_comma_dict['GLOBAL_MEDIA_SETTINGS'].pop('0-31') +port_optics_si_settings['PORT_MEDIA_SETTINGS'] = optics_si_settings_with_comma_dict.pop('PORT_MEDIA_SETTINGS') +optics_si_settings_with_comma_dict['GLOBAL_MEDIA_SETTINGS']['0-5,6,7-20,21-31'] = global_optics_si_settings + class TestXcvrdThreadException(object): @patch('xcvrd.xcvrd.platform_chassis', MagicMock()) @@ -470,6 +479,39 @@ def _check_notify_media_setting(self, index): port_mapping.handle_port_change_event(port_change_event) notify_media_setting(logical_port_name, xcvr_info_dict, app_port_tbl, port_mapping) + @patch('xcvrd.xcvrd_utilities.optics_si_parser.g_optics_si_dict', optics_si_settings_dict) + @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) + def test_fetch_optics_si_setting(self): + self._check_fetch_optics_si_setting(1) + + @patch('xcvrd.xcvrd_utilities.optics_si_parser.g_optics_si_dict', optics_si_settings_with_comma_dict) + @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) + def test_fetch_optics_si_setting_with_comma(self): + self._check_fetch_optics_si_setting(1) + self._check_fetch_optics_si_setting(6) + + @patch('xcvrd.xcvrd_utilities.optics_si_parser.g_optics_si_dict', port_optics_si_settings) + @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) + def test_fetch_optics_si_setting_with_port(self): + self._check_fetch_optics_si_setting(1) + + @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) + @patch('xcvrd.xcvrd_utilities.optics_si_parser.get_module_vendor_key', MagicMock(return_value=('CREDO-CAC82X321M','CREDO'))) + def _check_fetch_optics_si_setting(self, index): + port = 1 + lane_speed = 100 + mock_sfp = MagicMock() + optics_si_parser.fetch_optics_si_setting(port, lane_speed, mock_sfp) + + def test_get_module_vendor_key(self): + mock_sfp = MagicMock() + mock_xcvr_api = MagicMock() + mock_sfp.get_xcvr_api = MagicMock(return_value=mock_xcvr_api) + mock_xcvr_api.get_manufacturer = MagicMock(return_value='Credo ') + mock_xcvr_api.get_model = MagicMock(return_value='CAC82X321HW') + result = get_module_vendor_key(1, mock_sfp) + assert result == ('CREDO-CAC82X321HW','CREDO') + def test_detect_port_in_error_status(self): class MockTable: def get(self, key): @@ -764,6 +806,89 @@ def get_host_lane_assignment_option_side_effect(app): appl = task.get_cmis_application_desired(mock_xcvr_api, host_lane_count, speed) assert task.get_cmis_host_lanes_mask(mock_xcvr_api, appl, host_lane_count, subport) == expected + def test_CmisManagerTask_post_port_active_apsel_to_db(self): + mock_xcvr_api = MagicMock() + mock_xcvr_api.get_active_apsel_hostlane = MagicMock(side_effect=[ + { + 'ActiveAppSelLane1': 1, + 'ActiveAppSelLane2': 1, + 'ActiveAppSelLane3': 1, + 'ActiveAppSelLane4': 1, + 'ActiveAppSelLane5': 1, + 'ActiveAppSelLane6': 1, + 'ActiveAppSelLane7': 1, + 'ActiveAppSelLane8': 1 + }, + { + 'ActiveAppSelLane1': 2, + 'ActiveAppSelLane2': 2, + 'ActiveAppSelLane3': 2, + 'ActiveAppSelLane4': 2, + 'ActiveAppSelLane5': 2, + 'ActiveAppSelLane6': 2, + 'ActiveAppSelLane7': 2, + 'ActiveAppSelLane8': 2 + }, + NotImplementedError + ]) + mock_xcvr_api.get_application_advertisement = MagicMock(side_effect=[ + { + 1: { + 'media_lane_count': 4, + 'host_lane_count': 8 + } + }, + { + 2: { + 'media_lane_count': 1, + 'host_lane_count': 2 + } + } + ]) + + int_tbl = Table("STATE_DB", TRANSCEIVER_INFO_TABLE) + + port_mapping = PortMapping() + stop_event = threading.Event() + task = CmisManagerTask(DEFAULT_NAMESPACE, port_mapping, stop_event) + task.xcvr_table_helper.get_intf_tbl = MagicMock(return_value=int_tbl) + + # case: partial lanes update + lport = "Ethernet0" + host_lanes_mask = 0xc + ret = task.post_port_active_apsel_to_db(mock_xcvr_api, lport, host_lanes_mask) + assert int_tbl.getKeys() == ["Ethernet0"] + assert dict(int_tbl.mock_dict["Ethernet0"]) == {'active_apsel_hostlane3': '1', + 'active_apsel_hostlane4': '1', + 'host_lane_count': '8', + 'media_lane_count': '4'} + # case: full lanes update + lport = "Ethernet8" + host_lanes_mask = 0xff + task.post_port_active_apsel_to_db(mock_xcvr_api, lport, host_lanes_mask) + assert int_tbl.getKeys() == ["Ethernet0", "Ethernet8"] + assert dict(int_tbl.mock_dict["Ethernet0"]) == {'active_apsel_hostlane3': '1', + 'active_apsel_hostlane4': '1', + 'host_lane_count': '8', + 'media_lane_count': '4'} + assert dict(int_tbl.mock_dict["Ethernet8"]) == {'active_apsel_hostlane1': '2', + 'active_apsel_hostlane2': '2', + 'active_apsel_hostlane3': '2', + 'active_apsel_hostlane4': '2', + 'active_apsel_hostlane5': '2', + 'active_apsel_hostlane6': '2', + 'active_apsel_hostlane7': '2', + 'active_apsel_hostlane8': '2', + 'host_lane_count': '2', + 'media_lane_count': '1'} + + # case: NotImplementedError + int_tbl = Table("STATE_DB", TRANSCEIVER_INFO_TABLE) # a new empty table + lport = "Ethernet0" + host_lanes_mask = 0xf + ret = task.post_port_active_apsel_to_db(mock_xcvr_api, lport, host_lanes_mask) + assert int_tbl.getKeys() == [] + @patch('xcvrd.xcvrd.platform_chassis') @patch('xcvrd.xcvrd_utilities.port_mapping.subscribe_port_update_event', MagicMock(return_value=(None, None))) @patch('xcvrd.xcvrd_utilities.port_mapping.handle_port_update_event', MagicMock()) diff --git a/sonic-xcvrd/xcvrd/xcvrd.py b/sonic-xcvrd/xcvrd/xcvrd.py index f59cf6433..ab06b9ab1 100644 --- a/sonic-xcvrd/xcvrd/xcvrd.py +++ b/sonic-xcvrd/xcvrd/xcvrd.py @@ -27,6 +27,7 @@ from .xcvrd_utilities import sfp_status_helper from .xcvrd_utilities import port_mapping + from .xcvrd_utilities import optics_si_parser except ImportError as e: raise ImportError(str(e) + " - required module not found") @@ -1335,6 +1336,36 @@ def configure_laser_frequency(self, api, lport, freq, grid=75): self.log_error("{} Tuning in progress, subport selection may fail!".format(lport)) return api.set_laser_freq(freq, grid) + def post_port_active_apsel_to_db(self, api, lport, host_lanes_mask): + try: + act_apsel = api.get_active_apsel_hostlane() + appl_advt = api.get_application_advertisement() + except NotImplementedError: + helper_logger.log_error("Required feature is not implemented") + return + + tuple_list = [] + for lane in range(self.CMIS_MAX_HOST_LANES): + if ((1 << lane) & host_lanes_mask) == 0: + continue + act_apsel_lane = act_apsel.get('ActiveAppSelLane{}'.format(lane + 1), 'N/A') + tuple_list.append(('active_apsel_hostlane{}'.format(lane + 1), + str(act_apsel_lane))) + + # also update host_lane_count and media_lane_count + if len(tuple_list) > 0: + appl_advt_act = appl_advt.get(act_apsel_lane) + host_lane_count = appl_advt_act.get('host_lane_count', 'N/A') if appl_advt_act else 'N/A' + tuple_list.append(('host_lane_count', str(host_lane_count))) + media_lane_count = appl_advt_act.get('media_lane_count', 'N/A') if appl_advt_act else 'N/A' + tuple_list.append(('media_lane_count', str(media_lane_count))) + + asic_index = self.port_mapping.get_asic_id_for_logical_port(lport) + intf_tbl = self.xcvr_table_helper.get_intf_tbl(asic_index) + fvs = swsscommon.FieldValuePairs(tuple_list) + intf_tbl.set(lport, fvs) + self.log_notice("{}: updated TRANSCEIVER_INFO_TABLE {}".format(lport, tuple_list)) + def wait_for_port_config_done(self, namespace): # Connect to APPL_DB and subscribe to PORT table notifications appl_db = daemon_base.db_connect("APPL_DB", namespace=namespace) @@ -1500,8 +1531,8 @@ def task_worker(self): appl, lport, subport) if self.port_dict[lport]['media_lanes_mask'] <= 0: self.log_error("{}: Invalid media lane mask received - media_lane_count {} " - "media_lane_assignment_options {} lport{} subport {}" - " appl {}!".format(media_lane_count,media_lane_assignment_options,lport,subport,appl)) + "media_lane_assignment_options {} subport {}" + " appl {}!".format(lport, media_lane_count, media_lane_assignment_options, subport, appl)) self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_FAILED continue media_lanes_mask = self.port_dict[lport]['media_lanes_mask'] @@ -1561,6 +1592,11 @@ def task_worker(self): self.port_dict[lport]['cmis_expired'] = now + datetime.timedelta(seconds = max(modulePwrUpDuration, dpDeinitDuration)) elif state == self.CMIS_STATE_AP_CONF: + # Explicit control bit to apply custom Host SI settings. + # It will be set to 1 and applied via set_application if + # custom SI settings is applicable + ec = 0 + # TODO: Use fine grained time when the CMIS memory map is available if not self.check_module_state(api, ['ModuleReady']): if (expired is not None) and (expired <= now): @@ -1583,9 +1619,28 @@ def task_worker(self): else: self.log_notice("{} configured laser frequency {} GHz".format(lport, freq)) + # Stage custom SI settings + if optics_si_parser.optics_si_present(): + optics_si_dict = {} + # Apply module SI settings if applicable + lane_speed = int(speed/1000)//host_lane_count + optics_si_dict = optics_si_parser.fetch_optics_si_setting(pport, lane_speed, sfp) + + if optics_si_dict: + self.log_notice("{}: Apply Optics SI found for Vendor: {} PN: {} lane speed: {}G". + format(lport, api.get_manufacturer(), api.get_model(), lane_speed)) + if not api.stage_custom_si_settings(host_lanes_mask, optics_si_dict): + self.log_notice("{}: unable to stage custom SI settings ".format(lport)) + self.force_cmis_reinit(lport, retries + 1) + continue + + # Set Explicit control bit to apply Custom Host SI settings + ec = 1 + # D.1.3 Software Configuration and Initialization - if not api.set_application(host_lanes_mask, appl): - self.log_notice("{}: unable to set application".format(lport)) + api.set_application(host_lanes_mask, appl, ec) + if not api.scs_apply_datapath_init(host_lanes_mask): + self.log_notice("{}: unable to set application and stage DP init".format(lport)) self.force_cmis_reinit(lport, retries + 1) continue @@ -1641,6 +1696,7 @@ def task_worker(self): self.log_notice("{}: READY".format(lport)) self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_READY + self.post_port_active_apsel_to_db(api, lport, host_lanes_mask) except (NotImplementedError, AttributeError) as e: self.log_error("{}: internal errors due to {}".format(lport, e)) @@ -2419,9 +2475,10 @@ def init(self): self.xcvr_table_helper = XcvrTableHelper(self.namespaces) if is_fast_reboot_enabled(): - self.log_info("Skip loading media_settings.json in case of fast-reboot") + self.log_info("Skip loading media_settings.json and optics_si_settings.json in case of fast-reboot") else: self.load_media_settings() + optics_si_parser.load_optics_si_settings() # Make sure this daemon started after all port configured self.log_notice("XCVRD INIT: Wait for port config is done") diff --git a/sonic-xcvrd/xcvrd/xcvrd_utilities/optics_si_parser.py b/sonic-xcvrd/xcvrd/xcvrd_utilities/optics_si_parser.py new file mode 100644 index 000000000..fff418563 --- /dev/null +++ b/sonic-xcvrd/xcvrd/xcvrd_utilities/optics_si_parser.py @@ -0,0 +1,137 @@ +import json +import os + +from sonic_py_common import device_info, logger +from xcvrd import xcvrd + +g_optics_si_dict = {} + +SYSLOG_IDENTIFIER = "xcvrd" +helper_logger = logger.Logger(SYSLOG_IDENTIFIER) + +def get_optics_si_settings_value(physical_port, lane_speed, key, vendor_name_str): + GLOBAL_MEDIA_SETTINGS_KEY = 'GLOBAL_MEDIA_SETTINGS' + PORT_MEDIA_SETTINGS_KEY = 'PORT_MEDIA_SETTINGS' + DEFAULT_KEY = 'Default' + SPEED_KEY = str(lane_speed) + 'G_SPEED' + RANGE_SEPARATOR = '-' + COMMA_SEPARATOR = ',' + default_dict = {} + optics_si_dict = {} + + # Keys under global media settings can be a list or range or list of ranges + # of physical port numbers. Below are some examples + # 1-32 + # 1,2,3,4,5 + # 1-4,9-12 + + if GLOBAL_MEDIA_SETTINGS_KEY in g_optics_si_dict: + for keys in g_optics_si_dict[GLOBAL_MEDIA_SETTINGS_KEY]: + if COMMA_SEPARATOR in keys: + port_list = keys.split(COMMA_SEPARATOR) + for port in port_list: + if RANGE_SEPARATOR in port: + if xcvrd.check_port_in_range(port, physical_port): + optics_si_dict = g_optics_si_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] + break + elif str(physical_port) == port: + optics_si_dict = g_optics_si_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] + break + + elif RANGE_SEPARATOR in keys: + if xcvrd.check_port_in_range(keys, physical_port): + optics_si_dict = g_optics_si_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] + + key_dict = {} + if SPEED_KEY in optics_si_dict: + if key in optics_si_dict[SPEED_KEY]: + key_dict = optics_si_dict[SPEED_KEY] + return key_dict[key] + elif vendor_name_str in optics_si_dict[SPEED_KEY]: + key_dict = optics_si_dict[SPEED_KEY] + return key_dict[vendor_name_str] + elif DEFAULT_KEY in optics_si_dict[SPEED_KEY]: + key_dict = optics_si_dict[SPEED_KEY] + default_dict = key_dict[DEFAULT_KEY] + + optics_si_dict = {} + + if PORT_MEDIA_SETTINGS_KEY in g_optics_si_dict: + for keys in g_optics_si_dict[PORT_MEDIA_SETTINGS_KEY]: + if int(keys) == physical_port: + optics_si_dict = g_optics_si_dict[PORT_MEDIA_SETTINGS_KEY][keys] + break + if len(optics_si_dict) == 0: + if len(default_dict) != 0: + return default_dict + else: + helper_logger.log_error("Error: No values for physical port '{}'".format(physical_port)) + return {} + + key_dict = {} + if SPEED_KEY in optics_si_dict: + if key in optics_si_dict[SPEED_KEY]: + key_dict = optics_si_dict[SPEED_KEY] + return key_dict[key] + elif vendor_name_str in optics_si_dict[SPEED_KEY]: + key_dict = optics_si_dict[SPEED_KEY] + return key_dict[vendor_name_str] + elif DEFAULT_KEY in optics_si_dict[SPEED_KEY]: + key_dict = optics_si_dict[SPEED_KEY] + default_dict = key_dict[DEFAULT_KEY] + elif len(default_dict) != 0: + return default_dict + + return default_dict + +def get_module_vendor_key(physical_port, sfp): + api = sfp.get_xcvr_api() + if api is None: + helper_logger.log_info("Module {} xcvrd api not found".format(physical_port)) + return None + + vendor_name = api.get_manufacturer() + if vendor_name is None: + helper_logger.log_info("Module {} vendor name not found".format(physical_port)) + return None + + vendor_pn = api.get_model() + if vendor_pn is None: + helper_logger.log_info("Module {} vendor part number not found".format(physical_port)) + return None + + return vendor_name.upper().strip() + '-' + vendor_pn.upper().strip(), vendor_name.upper().strip() + +def fetch_optics_si_setting(physical_port, lane_speed, sfp): + if not g_optics_si_dict: + return + + optics_si = {} + + if not xcvrd._wrapper_get_presence(physical_port): + helper_logger.log_info("Module {} presence not detected during notify".format(physical_port)) + return optics_si + vendor_key, vendor_name = get_module_vendor_key(physical_port, sfp) + if vendor_key is None or vendor_name is None: + helper_logger.log_error("Error: No Vendor Key found for port '{}'".format(logical_port_name)) + return optics_si + optics_si = get_optics_si_settings_value(physical_port, lane_speed, vendor_key, vendor_name) + return optics_si + +def load_optics_si_settings(): + global g_optics_si_dict + (platform_path, _) = device_info.get_paths_to_platform_and_hwsku_dirs() + + optics_si_settings_file_path = os.path.join(platform_path, "optics_si_settings.json") + if not os.path.isfile(optics_si_settings_file_path): + helper_logger.log_info("No optics SI file exists") + return {} + + with open(optics_si_settings_file_path, "r") as optics_si_file: + g_optics_si_dict = json.load(optics_si_file) + +def optics_si_present(): + if g_optics_si_dict: + return True + return False + diff --git a/sonic-ycabled/tests/test_y_cable_helper.py b/sonic-ycabled/tests/test_y_cable_helper.py index 04ac6f7b5..84630eb48 100644 --- a/sonic-ycabled/tests/test_y_cable_helper.py +++ b/sonic-ycabled/tests/test_y_cable_helper.py @@ -1730,6 +1730,7 @@ def test_change_ports_status_for_y_cable_change_event(self, mock_swsscommon_tabl def mock_get_asic_id(mock_logical_port_name): return 0 + state_db = {} y_cable_presence = [True] logical_port_dict = {'Ethernet0': '1'} @@ -1745,7 +1746,7 @@ def mock_get_asic_id(mock_logical_port_name): patched_util.get_asic_id_for_logical_port.return_value = 0 rc = change_ports_status_for_y_cable_change_event( - logical_port_dict, y_cable_presence, port_tbl, port_table_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl, stop_event=threading.Event()) + logical_port_dict, y_cable_presence, port_tbl, port_table_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl, state_db, stop_event=threading.Event()) assert(rc == None) @@ -1764,6 +1765,7 @@ def mock_get_asic_id(mock_logical_port_name): y_cable_presence = [True] logical_port_dict = {'Ethernet0': '1'} + state_db = {} mock_table = MagicMock() mock_table.getKeys = MagicMock(return_value=['Ethernet0', 'Ethernet4']) @@ -1777,7 +1779,7 @@ def mock_get_asic_id(mock_logical_port_name): patched_util.get_asic_id_for_logical_port.return_value = 0 rc = change_ports_status_for_y_cable_change_event( - logical_port_dict, y_cable_presence, port_tbl, port_table_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl,stop_event=threading.Event()) + logical_port_dict, y_cable_presence, port_tbl, port_table_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl, state_db, stop_event=threading.Event()) assert(rc == None) @@ -1794,6 +1796,7 @@ def mock_get_asic_id(mock_logical_port_name): y_cable_presence = [True] logical_port_dict = {'Ethernet0': '2'} + state_db = {} mock_table = MagicMock() mock_table.getKeys = MagicMock(return_value=['Ethernet0', 'Ethernet4']) @@ -1806,7 +1809,7 @@ def mock_get_asic_id(mock_logical_port_name): patched_util.get_asic_id_for_logical_port.return_value = 0 rc = change_ports_status_for_y_cable_change_event( - logical_port_dict, y_cable_presence,port_tbl, port_table_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl, stop_event=threading.Event()) + logical_port_dict, y_cable_presence,port_tbl, port_table_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl, state_db, stop_event=threading.Event()) assert(rc == None) @@ -7141,3 +7144,317 @@ def test_ycable_graceful_client(self, channel, stub): read_side = 1 Y_cable_restart_client = GracefulRestartClient("Ethernet48", None, read_side) + +class TestYcableScriptExecution(object): + + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + def test_ycable_helper_cli_worker(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + stop_event = threading.Event() + + asic_index = 0 + Y_cable_cli_task = YCableCliUpdateTask() + Y_cable_cli_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + + #Y_cable_cli_task.task_stopping_event.is_set = MagicMock(side_effect=False) + + expected_exception_start = None + expected_exception_join = None + #Y_cable_cli_task.start() + Y_cable_cli_task.task_cli_worker() + Y_cable_cli_task.task_stopping_event.clear() + + assert swsscommon.Select.select.call_count == 1 + #y_cable_helper.handle_show_hwmode_state_cmd_arg_tbl_notification.assert_called() + Y_cable_cli_task_n = YCableCliUpdateTask() + Y_cable_cli_task_n.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + + mock_selectable.pop = MagicMock( + side_effect=[('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + + Y_cable_cli_task_n.task_cli_worker() + assert swsscommon.Select.select.call_count == 2 + + + + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + def test_ycable_helper_cli_worker_execution(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[(False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False) ,('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), (None, None, None), (None, None, None)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + stop_event = threading.Event() + + asic_index = 0 + Y_cable_cli_task = YCableCliUpdateTask() + Y_cable_cli_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + Y_cable_cli_task.cli_table_helper.xcvrd_show_hwmode_dir_cmd_tbl[asic_index].return_value = mock_selectable + + #Y_cable_cli_task.task_stopping_event.is_set = MagicMock(side_effect=False) + + expected_exception_start = None + expected_exception_join = None + trace = None + """ + try: + #Y_cable_cli_task.start() + Y_cable_cli_task.task_cli_worker() + time.sleep(5) + Y_cable_cli_task.task_stopping_event.clear() + except Exception as e1: + expected_exception_start = e1 + trace = traceback.format_exc() + """ + + + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + #@patch('swsscommon.swsscommon.Table') + def test_ycable_helper_table_worker(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[('Ethernet0', swsscommon.SET_COMMAND, (('state', 'active'), )), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + + Y_cable_task = YCableTableUpdateTask() + Y_cable_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + mock_table = MagicMock() + """mock_table.getKeys = MagicMock(return_value=['Ethernet0', 'Ethernet4']) + mock_table.get = MagicMock( + side_effect=[(True, (('index', 1), )), (True, (('index', 2), ))]) + mock_swsscommon_table.return_value = mock_table + """ + Y_cable_task.hw_mux_cable_tbl_keys = MagicMock(side_effect={0:["Ethernet0", "Ethernet4"]}) + Y_cable_task.task_worker() + assert swsscommon.Select.select.call_count == 1 + + + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + @patch('ycable.ycable_utilities.y_cable_helper.check_mux_cable_port_type', MagicMock(return_value=(True,"active-active"))) + def test_ycable_helper_table_worker_active_active(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), ('Ethernet0', swsscommon.SET_COMMAND, (('index', '1'), )), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + + Y_cable_task = YCableTableUpdateTask() + Y_cable_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + Y_cable_task.task_worker() + assert swsscommon.Select.select.call_count == 1 + + + + @patch('ycable.ycable_utilities.y_cable_helper.check_mux_cable_port_type', MagicMock(return_value=(True,"active-active"))) + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + @patch('ycable.ycable_utilities.y_cable_helper.grpc_port_stubs', MagicMock(return_value={})) + @patch('ycable.ycable_utilities.y_cable_helper.grpc_port_channels', MagicMock(return_value={})) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.FieldValuePairs', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + #@patch('swsscommon.swsscommon.Table') + def test_ycable_helper_table_worker_probe_active_active(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[(False, False, False), (False, False, False), (False, False, False), ('Ethernet0', swsscommon.SET_COMMAND, (('state', 'active'), )), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + + Y_cable_task = YCableTableUpdateTask() + Y_cable_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + """ + mock_table = MagicMock() + mock_table.getKeys = MagicMock(return_value=['Ethernet0', 'Ethernet4']) + mock_table.get = MagicMock( + side_effect=[(True, (('index', 1), )), (True, (('index', 2), ))]) + mock_swsscommon_table.return_value = mock_table + """ + + Y_cable_task.hw_mux_cable_tbl_keys = MagicMock(side_effect={0:["Ethernet0", "Ethernet4"]}) + Y_cable_task.task_worker() + assert swsscommon.Select.select.call_count == 1 + + + + + + + + @patch('ycable.ycable_utilities.y_cable_helper.check_mux_cable_port_type', MagicMock(return_value=(True,"active-active"))) + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + @patch('ycable.ycable_utilities.y_cable_helper.grpc_port_stubs', MagicMock(return_value={})) + @patch('ycable.ycable_utilities.y_cable_helper.grpc_port_channels', MagicMock(return_value={})) + @patch('swsscommon.swsscommon.FieldValuePairs', MagicMock()) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + #@patch('swsscommon.swsscommon.Table') + def test_ycable_helper_table_worker_probe_active(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[(False, False, False), (False, False, False), ('Ethernet0', swsscommon.SET_COMMAND, (('state', 'active'), ('command', 'probe'),)), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + + Y_cable_task = YCableTableUpdateTask() + Y_cable_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + """ + mock_table = MagicMock() + mock_table.getKeys = MagicMock(return_value=['Ethernet0', 'Ethernet4']) + mock_table.get = MagicMock( + side_effect=[(True, (('index', 1), )), (True, (('index', 2), ))]) + mock_swsscommon_table.return_value = mock_table + """ + + Y_cable_task.hw_mux_cable_tbl_keys = MagicMock(side_effect={0:["Ethernet0", "Ethernet4"]}) + Y_cable_task.task_worker() + assert swsscommon.Select.select.call_count == 1 + + + + + + + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + #@patch('swsscommon.swsscommon.Table') + def test_ycable_helper_table_worker_probe(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[(False, False, False), ('Ethernet0', swsscommon.SET_COMMAND, (('state', 'active'), )), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + + Y_cable_task = YCableTableUpdateTask() + Y_cable_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + #mock_table = MagicMock() + #mock_table.getKeys = MagicMock(return_value=['Ethernet0', 'Ethernet4']) + swsscommon.Table.return_value.get.return_value = ( + True, {"read_side": "1", "state":"active"}) + #mock_table.get = MagicMock( + # side_effect=[(True, (('index', 1), )), (True, (('index', 2), ))]) + swsscommon.Table.return_value.getKeys.return_value = ( + ['Ethernet0', 'Ethernet4']) + #mock_swsscommon_table.return_value = mock_table + + Y_cable_task.task_worker() + assert swsscommon.Select.select.call_count == 1 + + + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + #@patch('swsscommon.swsscommon.Table') + def test_ycable_helper_table_worker_toggle(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[('Ethernet0', swsscommon.SET_COMMAND, (('state', 'active'), )), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + + Y_cable_task = YCableTableUpdateTask() + Y_cable_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + #mock_table = MagicMock() + #mock_table.getKeys = MagicMock(return_value=['Ethernet0', 'Ethernet4']) + swsscommon.Table.return_value.get.return_value = ( + True, {"read_side": "1", "state":"active"}) + #mock_table.get = MagicMock( + # side_effect=[(True, (('index', 1), )), (True, (('index', 2), ))]) + swsscommon.Table.return_value.getKeys.return_value = ( + ['Ethernet0', 'Ethernet4']) + #mock_swsscommon_table.return_value = mock_table + + Y_cable_task.task_worker() + assert swsscommon.Select.select.call_count == 1 + + + @patch('ycable.ycable_utilities.y_cable_helper.check_mux_cable_port_type', MagicMock(return_value=(True,"active-active"))) + @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) + @patch('swsscommon.swsscommon.Select.TIMEOUT', MagicMock(return_value=None)) + @patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj', MagicMock()) + @patch('ycable.ycable_utilities.y_cable_helper.grpc_port_stubs', MagicMock(return_value={})) + @patch('ycable.ycable_utilities.y_cable_helper.grpc_port_channels', MagicMock(return_value={})) + @patch('swsscommon.swsscommon.FieldValuePairs', MagicMock()) + #@patch('swsscommon.swsscommon.CastSelectableToRedisSelectObj.getDbConnector', MagicMock()) + @patch('swsscommon.swsscommon.SubscriberStateTable') + @patch('swsscommon.swsscommon.Select.select') + #@patch('swsscommon.swsscommon.Table') + def test_ycable_helper_table_worker_toggle_active_active(self, mock_select, mock_sub_table): + + mock_selectable = MagicMock() + mock_selectable.pop = MagicMock( + side_effect=[('Ethernet0', swsscommon.SET_COMMAND, (('state', 'active'), ("command", "probe"),)), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False), (False, False, False)]) + mock_select.return_value = (swsscommon.Select.OBJECT, mock_selectable) + mock_sub_table.return_value = mock_selectable + + + Y_cable_task = YCableTableUpdateTask() + Y_cable_task.task_stopping_event.is_set = MagicMock(side_effect=[False, True]) + #mock_table = MagicMock() + #mock_table.getKeys = MagicMock(return_value=['Ethernet0', 'Ethernet4']) + swsscommon.Table.return_value.get.return_value = ( + True, {"read_side": "1", "state":"active"}) + #mock_table.get = MagicMock( + # side_effect=[(True, (('index', 1), )), (True, (('index', 2), ))]) + swsscommon.Table.return_value.getKeys.return_value = ( + ['Ethernet0', 'Ethernet4']) + #mock_swsscommon_table.return_value = mock_table + + Y_cable_task.task_worker() + assert swsscommon.Select.select.call_count == 1 + + diff --git a/sonic-ycabled/tests/test_ycable.py b/sonic-ycabled/tests/test_ycable.py index b45fea22e..1847ffd8d 100644 --- a/sonic-ycabled/tests/test_ycable.py +++ b/sonic-ycabled/tests/test_ycable.py @@ -83,6 +83,7 @@ def test_ycable_helper_class_run_loop(self): Y_cable_cli_task.start() Y_cable_cli_task.join() + @patch("swsscommon.swsscommon.Select", MagicMock()) @patch("swsscommon.swsscommon.Select.addSelectable", MagicMock()) def test_ycable_helper_class_run(self): @@ -307,13 +308,15 @@ def test_handle_state_update_task(self): port = "Ethernet0" fvp_dict = {} + state_db = {} y_cable_presence = False stopping_event = None port_tbl, port_tbl_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl = {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} - rc = handle_state_update_task(port, fvp_dict, y_cable_presence, port_tbl, port_tbl_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl, stopping_event) + rc = handle_state_update_task(port, fvp_dict, y_cable_presence, port_tbl, port_tbl_keys, loopback_tbl, loopback_keys, hw_mux_cable_tbl, hw_mux_cable_tbl_peer, y_cable_tbl, static_tbl, mux_tbl, grpc_client, fwd_state_response_tbl, state_db, stopping_event) assert(rc == None) + def wait_until(total_wait_time, interval, call_back, *args, **kwargs): wait_time = 0 while wait_time <= total_wait_time: @@ -353,20 +356,13 @@ def test_ycable_helper_class_run_loop_with_exception(self): except Exception as e2: expected_exception_join = e2 - """ - #Handy debug Helpers or else use import logging - #f = open("newfile", "w") - #f.write(format(e2)) - #f.write(format(m1)) - #f.write(trace) - """ - assert(type(expected_exception_start) == type(expected_exception_join)) assert(expected_exception_start.args == expected_exception_join.args) assert("NotImplementedError" in str(trace) and "effect" in str(trace)) assert("sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py" in str(trace)) assert("swsscommon.Select" in str(trace)) + class TestYcableAsyncScript(object): @patch("swsscommon.swsscommon.Select", MagicMock(side_effect=NotImplementedError)) @@ -400,3 +396,49 @@ def test_ycable_helper_async_client_run_loop_with_exception(self, sfputil): assert("sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py" in str(trace)) assert("setup_grpc_channel_for_port" in str(trace)) + +class TestYcableActiveActiveHelper(object): + + @patch("ycable.ycable.platform_sfputil") + def test_check_presence_for_active_active_cable_type(self, sfputil): + + + y_cable_tbl = {} + test_db = "TEST_DB" + status = True + asic_index = 0 + fvs = [('state', "auto"), ('read_side', 1), ('soc_ipv4', '192.168.0.1/32')] + y_cable_tbl[asic_index] = swsscommon.Table( + test_db[asic_index], "Y_CABLE_TABLE") + y_cable_tbl[asic_index] = swsscommon.Table( + test_db[asic_index], "Y_CABLE_TABLE") + y_cable_tbl[asic_index].get.return_value = (status, fvs) + + sfputil.logical = ["Ethernet0", "Ethernet4"] + sfputil.get_asic_id_for_logical_port = MagicMock(return_value=0) + rc = check_presence_for_active_active_cable_type(y_cable_tbl) + + assert(rc == False) + + @patch('ycable.ycable_utilities.y_cable_helper.check_mux_cable_port_type', MagicMock(return_value=(True,"active-active"))) + @patch("ycable.ycable.platform_sfputil") + def test_check_presence_for_active_active_cable_type(self, sfputil): + + + y_cable_tbl = {} + test_db = "TEST_DB" + status = True + asic_index = 0 + fvs = [('state', "auto"), ('read_side', 1), ('soc_ipv4', '192.168.0.1/32')] + y_cable_tbl[asic_index] = swsscommon.Table( + test_db[asic_index], "Y_CABLE_TABLE") + y_cable_tbl[asic_index] = swsscommon.Table( + test_db[asic_index], "Y_CABLE_TABLE") + y_cable_tbl[asic_index].get.return_value = (status, fvs) + + sfputil.logical = ["Ethernet0", "Ethernet4"] + sfputil.get_asic_id_for_logical_port = MagicMock(return_value=0) + rc = check_presence_for_active_active_cable_type(y_cable_tbl) + + assert(rc == True) + diff --git a/sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py b/sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py index 7946d8e3d..f7a916bab 100644 --- a/sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py +++ b/sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py @@ -387,10 +387,10 @@ def apply_grpc_secrets_configuration(SECRETS_PATH, grpc_config): if grpc_client_config is not None: config = grpc_client_config.get("config", None) if config is not None: - type = config.get("type",None) + type_chan = config.get("type",None) auth_level = config.get("auth_level",None) log_level = config.get("log_level", None) - fvs_updated = swsscommon.FieldValuePairs([('type', type), + fvs_updated = swsscommon.FieldValuePairs([('type', type_chan), ('auth_level',auth_level ), ('log_level',log_level)]) grpc_config[asic_index].set('config', fvs_updated) @@ -407,7 +407,7 @@ def apply_grpc_secrets_configuration(SECRETS_PATH, grpc_config): grpc_config[asic_index].set('certs', fvs_updated) -def get_grpc_credentials(type, kvp): +def get_grpc_credentials(type_chan, kvp): root_file = kvp.get("ca_crt", None) if root_file is not None and os.path.isfile(root_file): @@ -416,7 +416,7 @@ def get_grpc_credentials(type, kvp): helper_logger.log_error("grpc credential channel setup no root file in config_db") return None - if type == "mutual": + if type_chan == "mutual": cert_file = kvp.get("client_crt", None) if cert_file is not None and os.path.isfile(cert_file): cert_chain = open(cert_file, 'rb').read() @@ -435,7 +435,7 @@ def get_grpc_credentials(type, kvp): root_certificates=root_cert, private_key=key, certificate_chain=cert_chain) - elif type == "server": + elif type_chan == "server": credential = grpc.ssl_channel_credentials( root_certificates=root_cert) else: @@ -458,7 +458,7 @@ def connect_channel(channel, stub, port): else: break -def create_channel(type, level, kvp, soc_ip, port, asic_index, fwd_state_response_tbl, is_async): +def create_channel(type_chan, level, kvp, soc_ip, port, asic_index, fwd_state_response_tbl, is_async): # Helper callback to get an channel connectivity state def wait_for_state_change(channel_connectivity): @@ -487,28 +487,33 @@ def wait_for_state_change(channel_connectivity): grpc_port_connectivity[port] = "SHUTDOWN" - if type == "secure": + if type_chan == "secure": credential = get_grpc_credentials(level, kvp) target_name = kvp.get("grpc_ssl_credential", None) if credential is None or target_name is None: return (None, None) - GRPC_CLIENT_OPTIONS.append(('grpc.ssl_target_name_override', '{}'.format(target_name))) if is_async: - channel = grpc.aio.secure_channel("{}:{}".format(soc_ip, GRPC_PORT), credential, options=GRPC_CLIENT_OPTIONS) + ASYNC_GRPC_CLIENT_OPTIONS = [] + ASYNC_GRPC_CLIENT_OPTIONS.append(('grpc.ssl_target_name_override', '{}'.format(target_name))) + channel = grpc.aio.secure_channel("{}:{}".format(soc_ip, GRPC_PORT), credential, options=ASYNC_GRPC_CLIENT_OPTIONS) + stub = linkmgr_grpc_driver_pb2_grpc.DualToRActiveStub(channel) else: + GRPC_CLIENT_OPTIONS.append(('grpc.ssl_target_name_override', '{}'.format(target_name))) channel = grpc.secure_channel("{}:{}".format(soc_ip, GRPC_PORT), credential, options=GRPC_CLIENT_OPTIONS) + stub = linkmgr_grpc_driver_pb2_grpc.DualToRActiveStub(channel) else: if is_async: - channel = grpc.aio.insecure_channel("{}:{}".format(soc_ip, GRPC_PORT), options=GRPC_CLIENT_OPTIONS) + channel = grpc.aio.insecure_channel("{}:{}".format(soc_ip, GRPC_PORT)) + stub = linkmgr_grpc_driver_pb2_grpc.DualToRActiveStub(channel) else: channel = grpc.insecure_channel("{}:{}".format(soc_ip, GRPC_PORT), options=GRPC_CLIENT_OPTIONS) + stub = linkmgr_grpc_driver_pb2_grpc.DualToRActiveStub(channel) - stub = linkmgr_grpc_driver_pb2_grpc.DualToRActiveStub(channel) if not is_async and channel is not None: @@ -541,7 +546,7 @@ def setup_grpc_channel_for_port(port, soc_ip, asic_index, grpc_config, fwd_state #if no config from config DB, treat channel to be as insecure - type = "insecure" + type_chan = "insecure" level = "server" (status, fvs) = grpc_config[asic_index].get("config") @@ -550,12 +555,12 @@ def setup_grpc_channel_for_port(port, soc_ip, asic_index, grpc_config, fwd_state "Could not retreive fieldvalue pairs for {}, inside config_db table kvp config for {} for setting up channel type".format(port, grpc_config[asic_index].getTableName())) else: grpc_config_dict = dict(fvs) - type = grpc_config_dict.get("type", None) + type_chan = grpc_config_dict.get("type", None) level = grpc_config_dict.get("auth_level", None) kvp = {} - if type == "secure": + if type_chan == "secure": (status, fvs) = grpc_config[asic_index].get("certs") if status is False: helper_logger.log_warning( @@ -565,7 +570,7 @@ def setup_grpc_channel_for_port(port, soc_ip, asic_index, grpc_config, fwd_state kvp = dict(fvs) - channel, stub = create_channel(type, level, kvp, soc_ip, port, asic_index, fwd_state_response_tbl, is_async) + channel, stub = create_channel(type_chan, level, kvp, soc_ip, port, asic_index, fwd_state_response_tbl, is_async) if stub is None: helper_logger.log_warning("stub was not setup for gRPC soc ip {} port {}, no gRPC soc server running ?".format(soc_ip, port)) @@ -3713,6 +3718,7 @@ def task_worker(self): handle_hw_mux_cable_table_grpc_notification( fvp, self.table_helper.get_hw_mux_cable_tbl(), asic_index, self.table_helper.get_mux_metrics_tbl(), False, port, self.table_helper.get_port_tbl(), self.table_helper.get_grpc_config_tbl(), self.table_helper.get_fwd_state_response_tbl()) + while True: (port_m, op_m, fvp_m) = self.table_helper.get_mux_cable_command_tbl()[asic_index].pop() @@ -3843,6 +3849,7 @@ def task_cli_worker(self): namespace = redisSelectObj.getDbConnector().getNamespace() asic_index = multi_asic.get_asic_index_from_namespace(namespace) + while True: (key, op_m, fvp_m) = self.cli_table_helper.xcvrd_log_tbl[asic_index].pop()