Skip to content

Commit 8f343eb

Browse files
isabelmsftmssonicbld
authored andcommitted
[GCU] Add PORT table StateDB Validator (#2936)
1 parent 61903ee commit 8f343eb

File tree

5 files changed

+161
-3
lines changed

5 files changed

+161
-3
lines changed

generic_config_updater/field_operation_validators.py

+75-2
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"
@@ -75,7 +76,7 @@ def rdma_config_update_validator(patch_element):
7576
path = patch_element["path"]
7677
table = jsonpointer.JsonPointer(path).parts[0]
7778

78-
# Helper function to return relevant cleaned paths, consdiers case where the jsonpatch value is a dict
79+
# Helper function to return relevant cleaned paths, considers case where the jsonpatch value is a dict
7980
# 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)
8081
def _get_fields_in_patch():
8182
cleaned_fields = []
@@ -130,3 +131,75 @@ def _get_fields_in_patch():
130131
return False
131132

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

generic_config_updater/gcu_field_operation_validators.conf.json

+3
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@
152152
}
153153
}
154154
}
155+
},
156+
"PORT": {
157+
"field_operation_validators": [ "generic_config_updater.field_operation_validators.port_config_update_validator" ]
155158
}
156159
}
157160
}

scripts/portconfig

+3-1
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

+79
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

+1
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)