Skip to content

Commit 8cb7320

Browse files
authored
[GCU] Add PORT table StateDB Validator (#2936)
1 parent 6322389 commit 8cb7320

5 files changed

Lines changed: 161 additions & 3 deletions

File tree

generic_config_updater/field_operation_validators.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import subprocess
66
from sonic_py_common import device_info
77
from .gu_common import GenericConfigUpdaterError
8-
8+
from swsscommon import swsscommon
9+
from utilities_common.constants import DEFAULT_SUPPORTED_FECS_LIST
910

1011
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
1112
GCU_TABLE_MOD_CONF_FILE = f"{SCRIPT_DIR}/gcu_field_operation_validators.conf.json"
@@ -71,7 +72,7 @@ def rdma_config_update_validator(patch_element):
7172
path = patch_element["path"]
7273
table = jsonpointer.JsonPointer(path).parts[0]
7374

74-
# Helper function to return relevant cleaned paths, consdiers case where the jsonpatch value is a dict
75+
# Helper function to return relevant cleaned paths, considers case where the jsonpatch value is a dict
7576
# For paths like /PFC_WD/Ethernet112/action, remove Ethernet112 from the path so that we can clearly determine the relevant field (i.e. action, not Ethernet112)
7677
def _get_fields_in_patch():
7778
cleaned_fields = []
@@ -126,3 +127,75 @@ def _get_fields_in_patch():
126127
return False
127128

128129
return True
130+
131+
132+
def read_statedb_entry(table, key, field):
133+
state_db = swsscommon.DBConnector("STATE_DB", 0)
134+
tbl = swsscommon.Table(state_db, table)
135+
return tbl.hget(key, field)[1]
136+
137+
138+
def port_config_update_validator(patch_element):
139+
140+
def _validate_field(field, port, value):
141+
if field == "fec":
142+
supported_fecs_str = read_statedb_entry("PORT_TABLE", port, "supported_fecs")
143+
if supported_fecs_str:
144+
if supported_fecs_str != 'N/A':
145+
supported_fecs_list = [element.strip() for element in supported_fecs_str.split(',')]
146+
else:
147+
supported_fecs_list = []
148+
else:
149+
supported_fecs_list = DEFAULT_SUPPORTED_FECS_LIST
150+
if value.strip() not in supported_fecs_list:
151+
return False
152+
return True
153+
if field == "speed":
154+
supported_speeds_str = read_statedb_entry("PORT_TABLE", port, "supported_speeds") or ''
155+
try:
156+
supported_speeds = [int(s) for s in supported_speeds_str.split(',') if s]
157+
if supported_speeds and int(value) not in supported_speeds:
158+
return False
159+
except ValueError:
160+
return False
161+
return True
162+
return False
163+
164+
def _parse_port_from_path(path):
165+
match = re.search(r"Ethernet\d+", path)
166+
if match:
167+
port = match.group(0)
168+
return port
169+
return None
170+
171+
if patch_element["op"] == "remove":
172+
return True
173+
174+
# for PORT speed and fec configs, need to ensure value is allowed based on StateDB
175+
patch_element_str = json.dumps(patch_element)
176+
path = patch_element["path"]
177+
value = patch_element.get("value")
178+
fields = ['fec', 'speed']
179+
for field in fields:
180+
if field in patch_element_str:
181+
if path.endswith(field):
182+
port = _parse_port_from_path(path)
183+
if not _validate_field(field, port, value):
184+
return False
185+
elif isinstance(value, dict):
186+
if field in value.keys():
187+
port = _parse_port_from_path(path)
188+
value = value[field]
189+
if not _validate_field(field, port, value):
190+
return False
191+
else:
192+
for port_name, port_info in value.items():
193+
if isinstance(port_info, dict):
194+
port = port_name
195+
if field in port_info.keys():
196+
value = port_info[field]
197+
if not _validate_field(field, port, value):
198+
return False
199+
else:
200+
continue
201+
return True

generic_config_updater/gcu_field_operation_validators.conf.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@
145145
}
146146
}
147147
}
148+
},
149+
"PORT": {
150+
"field_operation_validators": [ "generic_config_updater.field_operation_validators.port_config_update_validator" ]
148151
}
149152
}
150153
}

scripts/portconfig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import sys
3030
import decimal
3131
import argparse
3232

33+
from utilities_common.constants import DEFAULT_SUPPORTED_FECS_LIST
34+
3335
# mock the redis for unit test purposes #
3436
try:
3537
if os.environ["UTILITIES_UNIT_TESTING"] == "1" or os.environ["UTILITIES_UNIT_TESTING"] == "2":
@@ -276,7 +278,7 @@ class portconfig(object):
276278
else:
277279
supported_fecs_list = []
278280
else:
279-
supported_fecs_list = ["rs", "fc", "none"]
281+
supported_fecs_list = DEFAULT_SUPPORTED_FECS_LIST
280282

281283
return supported_fecs_list
282284

tests/generic_config_updater/field_operation_validator_test.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,85 @@
1414

1515
class TestValidateFieldOperation(unittest.TestCase):
1616

17+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value=""))
18+
def test_port_config_update_validator_valid_speed_no_state_db(self):
19+
patch_element = {"path": "/PORT/Ethernet3", "op": "add", "value": {"speed": "234"}}
20+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
21+
22+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="40000,30000"))
23+
def test_port_config_update_validator_invalid_speed_existing_state_db(self):
24+
patch_element = {"path": "/PORT/Ethernet3", "op": "add", "value": {"speed": "xyz"}}
25+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == False
26+
27+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="123,234"))
28+
def test_port_config_update_validator_valid_speed_existing_state_db(self):
29+
patch_element = {"path": "/PORT/Ethernet3", "op": "add", "value": {"speed": "234"}}
30+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
31+
32+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="123,234"))
33+
def test_port_config_update_validator_valid_speed_existing_state_db(self):
34+
patch_element = {"path": "/PORT/Ethernet3/speed", "op": "add", "value": "234"}
35+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
36+
37+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="123,234"))
38+
def test_port_config_update_validator_invalid_speed_existing_state_db(self):
39+
patch_element = {"path": "/PORT/Ethernet3/speed", "op": "add", "value": "235"}
40+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == False
41+
42+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="123,234"))
43+
def test_port_config_update_validator_invalid_speed_existing_state_db_nested(self):
44+
patch_element = {"path": "/PORT", "op": "add", "value": {"Ethernet3": {"alias": "Eth0", "speed": "235"}}}
45+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == False
46+
47+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="123,234"))
48+
def test_port_config_update_validator_valid_speed_existing_state_db_nested(self):
49+
patch_element = {"path": "/PORT", "op": "add", "value": {"Ethernet3": {"alias": "Eth0", "speed": "234"}, "Ethernet4": {"alias": "Eth4", "speed": "234"}}}
50+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
51+
52+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="123,234"))
53+
def test_port_config_update_validator_invalid_speed_existing_state_db_nested_2(self):
54+
patch_element = {"path": "/PORT", "op": "add", "value": {"Ethernet3": {"alias": "Eth0", "speed": "234"}, "Ethernet4": {"alias": "Eth4", "speed": "236"}}}
55+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == False
56+
57+
def test_port_config_update_validator_remove(self):
58+
patch_element = {"path": "/PORT/Ethernet3", "op": "remove"}
59+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
60+
61+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="rs, fc"))
62+
def test_port_config_update_validator_invalid_fec_existing_state_db(self):
63+
patch_element = {"path": "/PORT/Ethernet3/fec", "op": "add", "value": "asf"}
64+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == False
65+
66+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="rs, fc"))
67+
def test_port_config_update_validator_invalid_fec_existing_state_db_nested(self):
68+
patch_element = {"path": "/PORT", "op": "add", "value": {"Ethernet3": {"alias": "Eth0", "fec": "none"}, "Ethernet4": {"alias": "Eth4", "fec": "fs"}}}
69+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == False
70+
71+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="rs, fc"))
72+
def test_port_config_update_validator_valid_fec_existing_state_db_nested(self):
73+
patch_element = {"path": "/PORT", "op": "add", "value": {"Ethernet3": {"alias": "Eth0", "fec": "fc"}}}
74+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
75+
76+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="rs, fc"))
77+
def test_port_config_update_validator_valid_fec_existing_state_db_nested_2(self):
78+
patch_element = {"path": "/PORT", "op": "add", "value": {"Ethernet3": {"alias": "Eth0", "fec": "rs"}, "Ethernet4": {"alias": "Eth4", "fec": "fc"}}}
79+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
80+
81+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value="rs, fc"))
82+
def test_port_config_update_validator_valid_fec_existing_state_db(self):
83+
patch_element = {"path": "/PORT/Ethernet3/fec", "op": "add", "value": "rs"}
84+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
85+
86+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value=""))
87+
def test_port_config_update_validator_valid_fec_no_state_db(self):
88+
patch_element = {"path": "/PORT/Ethernet3", "op": "add", "value": {"fec": "rs"}}
89+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == True
90+
91+
@patch("generic_config_updater.field_operation_validators.read_statedb_entry", mock.Mock(return_value=""))
92+
def test_port_config_update_validator_invalid_fec_no_state_db(self):
93+
patch_element = {"path": "/PORT/Ethernet3/fec", "op": "add", "value": "rsf"}
94+
assert generic_config_updater.field_operation_validators.port_config_update_validator(patch_element) == False
95+
1796
@patch("generic_config_updater.field_operation_validators.get_asic_name", mock.Mock(return_value="unknown"))
1897
def test_rdma_config_update_validator_unknown_asic(self):
1998
patch_element = {"path": "/PFC_WD/Ethernet4/restoration_time", "op": "replace", "value": "234234"}

utilities_common/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#All the constant used in sonic-utilities
22

33
DEFAULT_NAMESPACE = ''
4+
DEFAULT_SUPPORTED_FECS_LIST = [ 'rs', 'fc', 'none']
45
DISPLAY_ALL = 'all'
56
DISPLAY_EXTERNAL = 'frontend'
67
BGP_NEIGH_OBJ = 'BGP_NEIGH'

0 commit comments

Comments
 (0)