Skip to content

Commit d74bc9b

Browse files
authored
[xcvrd] Re-organize transceiver DOM and STATUS tables + enable link event handling for DOM monitoring + DOM polling based on physical port (#604)
* [xcvrd] Re-organize transceiver DOM and STATUS tables * [xcvrd] Re-organize transceiver DOM and STATUS tables * Fixed print statement for asic_index None check * Fixed print statement for asic_index None check * Added _TABLE suffix in TRANSCEIVER STATUS metadata tables * Added remove_stale_transceiver_info function * Renamed get_transceiver_dom_sensor_info to get_transceiver_dom_sensor_real_value * Added fix for SffLoggerForPortUpdateEvent class does not have log_debug function * Addressed review comments and added _validate_and_get_physical_port function * Using DomInfoUpdateTask specific logger now * Enabled polling for only first subport + added support for TRANSCEIVER_STATUS_SW table * Fixed typo * Added link event handling to update flags related diagnostic tables * Added 1s delay before updating diagnostic db post link change event * Added new line per PR review * Enabled DOM tables update for flat memory modules
1 parent a86dd89 commit d74bc9b

File tree

16 files changed

+1649
-776
lines changed

16 files changed

+1649
-776
lines changed

sonic-xcvrd/tests/test_xcvrd.py

Lines changed: 760 additions & 355 deletions
Large diffs are not rendered by default.

sonic-xcvrd/xcvrd/dom/dom_mgr.py

Lines changed: 182 additions & 133 deletions
Large diffs are not rendered by default.
Lines changed: 198 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,224 @@
1+
from datetime import datetime
12
from swsscommon import swsscommon
3+
from xcvrd.xcvrd_utilities.utils import XCVRDUtils
24

35
class DBUtils:
46
"""
57
This class contains utility functions to interact with the redis database.
68
"""
7-
def __init__(self, logger):
9+
NEVER = "never"
10+
NOT_AVAILABLE = "N/A"
11+
12+
def __init__(self, sfp_obj_dict, port_mapping, task_stopping_event, logger):
13+
self.sfp_obj_dict = sfp_obj_dict
14+
self.port_mapping = port_mapping
15+
self.task_stopping_event = task_stopping_event
16+
self.xcvrd_utils = XCVRDUtils(sfp_obj_dict, logger)
817
self.logger = logger
918

10-
"""
11-
Updates the metadata tables for flag table
12-
As part of the metadata update, the following tables are updated:
13-
- Change Count Table
14-
- Last Set Time Table
15-
- Last Clear Time Table
16-
"""
17-
def update_flag_metadata_tables(self, logical_port_name, field_name, current_value,
19+
def post_diagnostic_values_to_db(self, logical_port_name, table, get_values_func,
20+
db_cache=None, beautify_func=None, enable_flat_memory_check=False):
21+
"""
22+
Posts the diagnostic values to the database.
23+
24+
Args:
25+
logical_port_name (str): Logical port name.
26+
table (object): Database table object.
27+
get_values_func (function): Function to get diagnostic values.
28+
db_cache (dict, optional): Cache for diagnostic values.
29+
beautify_func (function, optional): Function to beautify the diagnostic values. Defaults to self.beautify_info_dict.
30+
enable_flat_memory_check (bool, optional): Flag to check for flat memory support. Defaults to False.
31+
"""
32+
physical_port = self._validate_and_get_physical_port(logical_port_name, enable_flat_memory_check)
33+
if physical_port is None:
34+
return
35+
36+
try:
37+
if db_cache is not None and physical_port in db_cache:
38+
# If cache is enabled and diagnostic values are in cache, just read from cache, no need read from EEPROM
39+
diagnostic_values_dict = db_cache[physical_port]
40+
else:
41+
diagnostic_values_dict = get_values_func(physical_port)
42+
if db_cache is not None:
43+
# If cache is enabled, put diagnostic values to cache
44+
db_cache[physical_port] = diagnostic_values_dict
45+
if diagnostic_values_dict is not None:
46+
if not diagnostic_values_dict:
47+
return
48+
49+
# Use the provided beautify function or default to self.beautify_info_dict
50+
(beautify_func or self.beautify_info_dict)(diagnostic_values_dict)
51+
fvs = swsscommon.FieldValuePairs(
52+
[(k, v) for k, v in diagnostic_values_dict.items()] +
53+
[("last_update_time", self.get_current_time())]
54+
)
55+
table.set(logical_port_name, fvs)
56+
57+
except NotImplementedError:
58+
self.logger.log_error(f"Post port diagnostic values to db failed for {logical_port_name} "
59+
"as functionality is not implemented")
60+
return
61+
62+
def _validate_and_get_physical_port(self, logical_port_name, enable_flat_memory_check=False):
63+
"""
64+
Validates the logical port and retrieves the corresponding physical port.
65+
66+
Validation Steps:
67+
1. Ensures `task_stopping_event` is not set.
68+
2. Checks if the logical port maps to a physical port.
69+
3. Checks if the physical port has an associated SFP object.
70+
4. Checks if the transceiver is present.
71+
5. (Optional) Ensures the transceiver is not flat memory if `enable_flat_memory_check` is True.
72+
73+
If any of these checks fail, an error message is logged and `None` is returned.
74+
If all checks pass, the physical port number is returned.
75+
76+
Args:
77+
logical_port_name (str): Logical port name.
78+
enable_flat_memory_check (bool): Flag to check for flat memory support.
79+
80+
Returns:
81+
int: The physical port number if validation succeeds, or None if validation fails.
82+
"""
83+
if self.task_stopping_event.is_set():
84+
return None
85+
86+
pport_list = self.port_mapping.get_logical_to_physical(logical_port_name)
87+
if not pport_list:
88+
self.logger.log_error(f"Validate and get physical port failed for {logical_port_name} "
89+
"as no physical port found")
90+
return None
91+
92+
physical_port = pport_list[0]
93+
94+
if physical_port not in self.sfp_obj_dict:
95+
self.logger.log_error(f"Validate and get physical port failed for {logical_port_name} "
96+
"as no sfp object found")
97+
return None
98+
99+
if not self.xcvrd_utils.get_transceiver_presence(physical_port):
100+
return None
101+
102+
if enable_flat_memory_check and self.xcvrd_utils.is_transceiver_flat_memory(physical_port):
103+
return None
104+
105+
return physical_port
106+
107+
def _update_flag_metadata_tables(self, logical_port_name, curr_flag_dict,
18108
flag_values_dict_update_time,
19109
flag_value_table,
20110
flag_change_count_table, flag_last_set_time_table, flag_last_clear_time_table,
21111
table_name_for_logging):
112+
"""
113+
Updates the metadata tables for a flag table.
114+
115+
This method compares the current flag values with the values stored in the database.
116+
If there are changes, it updates the metadata tables accordingly, including:
117+
- Change count
118+
- Last set time
119+
- Last clear time
120+
121+
Args:
122+
logical_port_name (str): Logical port name.
123+
curr_flag_dict (dict): Current flag values.
124+
flag_values_dict_update_time (str): Timestamp of the update.
125+
flag_value_table (swsscommon.Table): Table containing flag values.
126+
flag_change_count_table (swsscommon.Table): Table for change counts.
127+
flag_last_set_time_table (swsscommon.Table): Table for last set times.
128+
flag_last_clear_time_table (swsscommon.Table): Table for last clear times.
129+
table_name_for_logging (str): Name of the table for logging purposes.
130+
"""
22131
if flag_value_table is None:
23132
self.logger.log_error(f"flag_value_table {table_name_for_logging} is None for port {logical_port_name}")
24133
return
25134

135+
# Retrieve existing flag values from the database
26136
found, db_flags_value_dict = flag_value_table.get(logical_port_name)
27-
# Table is empty, this is the first update to the metadata tables (this also means that the transceiver was detected for the first time)
28-
# Initialize the change count to 0 and last set and clear times to 'never'
29137
if not found:
30-
flag_change_count_table.set(logical_port_name, swsscommon.FieldValuePairs([(field_name, '0')]))
31-
flag_last_set_time_table.set(logical_port_name, swsscommon.FieldValuePairs([(field_name, 'never')]))
32-
flag_last_clear_time_table.set(logical_port_name, swsscommon.FieldValuePairs([(field_name, 'never')]))
138+
# Initialize metadata tables for the first update
139+
self._initialize_metadata_tables(logical_port_name, curr_flag_dict,
140+
flag_change_count_table, flag_last_set_time_table, flag_last_clear_time_table)
33141
return
34-
else:
35-
db_flags_value_dict = dict(db_flags_value_dict)
36142

37-
# No metadata update required if the value is 'N/A'
38-
if str(current_value).strip() == 'N/A':
39-
return
143+
db_flags_value_dict = dict(db_flags_value_dict)
40144

41-
# Update metadata if the value of flag has changed from the previous value
42-
if field_name in db_flags_value_dict and db_flags_value_dict[field_name] != str(current_value):
43-
found, db_change_count_dict = flag_change_count_table.get(logical_port_name)
44-
if not found:
45-
self.logger.log_error(f"Failed to get the change count for table {table_name_for_logging} port {logical_port_name}")
46-
return
47-
db_change_count_dict = dict(db_change_count_dict)
48-
db_change_count = int(db_change_count_dict[field_name])
49-
db_change_count += 1
50-
flag_change_count_table.set(logical_port_name, swsscommon.FieldValuePairs([(field_name, str(db_change_count))]))
51-
if current_value:
52-
flag_last_set_time_table.set(logical_port_name, swsscommon.FieldValuePairs([(field_name, flag_values_dict_update_time)]))
53-
else:
54-
flag_last_clear_time_table.set(logical_port_name, swsscommon.FieldValuePairs([(field_name, flag_values_dict_update_time)]))
145+
# Update metadata for each flag
146+
for flag_key, curr_flag_value in curr_flag_dict.items():
147+
if str(curr_flag_value).strip() == self.NOT_AVAILABLE:
148+
continue # Skip "N/A" values
149+
150+
if flag_key in db_flags_value_dict and db_flags_value_dict[flag_key] != str(curr_flag_value):
151+
self._update_flag_metadata(logical_port_name, flag_key, curr_flag_value,
152+
flag_values_dict_update_time, flag_change_count_table,
153+
flag_last_set_time_table, flag_last_clear_time_table,
154+
table_name_for_logging)
55155

56156
def beautify_info_dict(self, info_dict):
57157
for k, v in info_dict.items():
58158
if not isinstance(v, str):
59159
info_dict[k] = str(v)
160+
161+
def get_current_time(self, time_format="%a %b %d %H:%M:%S %Y"):
162+
"""
163+
Returns the current time in the specified format (UTC time).
164+
165+
Args:
166+
time_format (str): The format in which to return the time. Defaults to "Day Mon DD HH:MM:SS YYYY".
167+
168+
Returns:
169+
str: The current time in UTC.
170+
"""
171+
return datetime.utcnow().strftime(time_format)
172+
173+
def _initialize_metadata_tables(self, logical_port_name, curr_flag_dict,
174+
flag_change_count_table, flag_last_set_time_table,
175+
flag_last_clear_time_table):
176+
"""
177+
Initializes metadata tables for the first update.
178+
179+
Args:
180+
logical_port_name (str): Logical port name.
181+
curr_flag_dict (dict): Current flag values.
182+
flag_change_count_table (swsscommon.Table): Table for change counts.
183+
flag_last_set_time_table (swsscommon.Table): Table for last set times.
184+
flag_last_clear_time_table (swsscommon.Table): Table for last clear times.
185+
"""
186+
for key in curr_flag_dict.keys():
187+
flag_change_count_table.set(logical_port_name, swsscommon.FieldValuePairs([(key, '0')]))
188+
flag_last_set_time_table.set(logical_port_name, swsscommon.FieldValuePairs([(key, self.NEVER)]))
189+
flag_last_clear_time_table.set(logical_port_name, swsscommon.FieldValuePairs([(key, self.NEVER)]))
190+
191+
def _update_flag_metadata(self, logical_port_name, flag_key, curr_flag_value,
192+
flag_values_dict_update_time, flag_change_count_table,
193+
flag_last_set_time_table, flag_last_clear_time_table,
194+
table_name_for_logging):
195+
"""
196+
Updates metadata for a single flag.
197+
198+
Args:
199+
logical_port_name (str): Logical port name.
200+
flag_key (str): The flag key.
201+
curr_flag_value (str): The current flag value.
202+
flag_values_dict_update_time (str): Timestamp of the update.
203+
flag_change_count_table (swsscommon.Table): Table for change counts.
204+
flag_last_set_time_table (swsscommon.Table): Table for last set times.
205+
flag_last_clear_time_table (swsscommon.Table): Table for last clear times.
206+
table_name_for_logging (str): Name of the table for logging purposes.
207+
"""
208+
# Retrieve the current change count
209+
found, db_change_count_dict = flag_change_count_table.get(logical_port_name)
210+
if not found:
211+
self.logger.log_warning(f"Failed to get the change count for table {table_name_for_logging} port {logical_port_name}")
212+
return
213+
214+
db_change_count_dict = dict(db_change_count_dict)
215+
db_change_count = int(db_change_count_dict.get(flag_key, 0)) + 1
216+
217+
# Update the change count
218+
flag_change_count_table.set(logical_port_name, swsscommon.FieldValuePairs([(flag_key, str(db_change_count))]))
219+
220+
# Update the last set or clear time
221+
if curr_flag_value:
222+
flag_last_set_time_table.set(logical_port_name, swsscommon.FieldValuePairs([(flag_key, flag_values_dict_update_time)]))
223+
else:
224+
flag_last_clear_time_table.set(logical_port_name, swsscommon.FieldValuePairs([(flag_key, flag_values_dict_update_time)]))

sonic-xcvrd/xcvrd/dom/utilities/dom_sensor/__init__.py

Whitespace-only changes.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import re
2+
from xcvrd.dom.utilities.db.utils import DBUtils
3+
from xcvrd.dom.utilities.dom_sensor.utils import DOMUtils
4+
from swsscommon import swsscommon
5+
6+
7+
class DOMDBUtils(DBUtils):
8+
"""
9+
This class provides utility functions for managing DB operations
10+
related to DOM on transceivers.
11+
Handles data related to the following tables:
12+
- TRANSCEIVER_DOM_SENSOR
13+
- TRANSCEIVER_DOM_FLAG and its corresponding metadata tables (change count, set time, clear time)
14+
- TRANSCEIVER_DOM_THRESHOLD
15+
"""
16+
TEMP_UNIT = 'C'
17+
VOLT_UNIT = 'Volts'
18+
POWER_UNIT = 'dBm'
19+
BIAS_UNIT = 'mA'
20+
21+
def __init__(self, sfp_obj_dict, port_mapping, xcvr_table_helper, task_stopping_event, logger):
22+
super().__init__(sfp_obj_dict, port_mapping, task_stopping_event, logger)
23+
self.xcvr_table_helper = xcvr_table_helper
24+
self.dom_utils = DOMUtils(self.sfp_obj_dict, logger)
25+
self.logger = logger
26+
27+
def post_port_dom_sensor_info_to_db(self, logical_port_name, db_cache=None):
28+
asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name)
29+
if asic_index is None:
30+
self.logger.log_error(f"Post port dom sensor info to db failed for {logical_port_name} "
31+
"as no asic index found")
32+
return
33+
34+
return self.post_diagnostic_values_to_db(logical_port_name,
35+
self.xcvr_table_helper.get_dom_tbl(asic_index),
36+
self.dom_utils.get_transceiver_dom_sensor_real_value,
37+
db_cache=db_cache,
38+
beautify_func=self._beautify_dom_info_dict)
39+
40+
def post_port_dom_flags_to_db(self, logical_port_name, db_cache=None):
41+
asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name)
42+
if asic_index is None:
43+
self.logger.log_error(f"Post port dom flags to db failed for {logical_port_name} "
44+
"as no asic index found")
45+
return
46+
47+
physical_port = self._validate_and_get_physical_port(logical_port_name)
48+
if physical_port is None:
49+
return
50+
51+
try:
52+
if db_cache is not None and physical_port in db_cache:
53+
# If cache is enabled and dom flag values are in cache, just read from cache, no need read from EEPROM
54+
dom_flags_dict = db_cache[physical_port]
55+
else:
56+
# Reading from the EEPROM as the cache is empty
57+
dom_flags_dict = self.dom_utils.get_transceiver_dom_flags(physical_port)
58+
if dom_flags_dict is None:
59+
self.logger.log_error(f"Post port dom flags to db failed for {logical_port_name} "
60+
"as no dom flags found")
61+
return
62+
if dom_flags_dict:
63+
dom_flags_dict_update_time = self.get_current_time()
64+
self._update_flag_metadata_tables(logical_port_name, dom_flags_dict,
65+
dom_flags_dict_update_time,
66+
self.xcvr_table_helper.get_dom_flag_tbl(asic_index),
67+
self.xcvr_table_helper.get_dom_flag_change_count_tbl(asic_index),
68+
self.xcvr_table_helper.get_dom_flag_set_time_tbl(asic_index),
69+
self.xcvr_table_helper.get_dom_flag_clear_time_tbl(asic_index),
70+
"DOM flags")
71+
72+
if db_cache is not None:
73+
# If cache is enabled, put dom flag values to cache
74+
db_cache[physical_port] = dom_flags_dict
75+
76+
if dom_flags_dict is not None:
77+
if not dom_flags_dict:
78+
return
79+
80+
self._beautify_dom_info_dict(dom_flags_dict)
81+
fvs = swsscommon.FieldValuePairs(
82+
[(k, v) for k, v in dom_flags_dict.items()] +
83+
[("last_update_time", self.get_current_time())]
84+
)
85+
self.xcvr_table_helper.get_dom_flag_tbl(asic_index).set(logical_port_name, fvs)
86+
else:
87+
return
88+
89+
except NotImplementedError:
90+
self.logger.log_error(f"Post port dom flags to db failed for {logical_port_name} "
91+
"as no dom flags found")
92+
return
93+
94+
def post_port_dom_thresholds_to_db(self, logical_port_name, db_cache=None):
95+
asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name)
96+
if asic_index is None:
97+
self.logger.log_error(f"Post port dom thresholds to db failed for {logical_port_name} "
98+
"as no asic index found")
99+
return
100+
101+
return self.post_diagnostic_values_to_db(logical_port_name,
102+
self.xcvr_table_helper.get_dom_threshold_tbl(asic_index),
103+
self.dom_utils.get_transceiver_dom_thresholds,
104+
db_cache=db_cache,
105+
beautify_func=self._beautify_dom_info_dict)
106+
107+
def _strip_unit(self, value, unit):
108+
# Strip unit from raw data
109+
if isinstance(value, str) and value.endswith(unit):
110+
return value[:-len(unit)]
111+
return str(value)
112+
113+
# Remove unnecessary unit from the raw data
114+
def _beautify_dom_info_dict(self, dom_info_dict):
115+
if dom_info_dict is None:
116+
self.logger.log_warning("DOM info dict is None while beautifying")
117+
return
118+
119+
for k, v in dom_info_dict.items():
120+
if k == 'temperature':
121+
dom_info_dict[k] = self._strip_unit(v, self.TEMP_UNIT)
122+
elif k == 'voltage':
123+
dom_info_dict[k] = self._strip_unit(v, self.VOLT_UNIT)
124+
elif re.match('^(tx|rx)[1-8]power$', k):
125+
dom_info_dict[k] = self._strip_unit(v, self.POWER_UNIT)
126+
elif re.match('^(tx|rx)[1-8]bias$', k):
127+
dom_info_dict[k] = self._strip_unit(v, self.BIAS_UNIT)
128+
elif type(v) is not str:
129+
# For all the other keys:
130+
dom_info_dict[k] = str(v)

0 commit comments

Comments
 (0)