Skip to content

Commit 8765fce

Browse files
[GCU Bug Fix] Cherry-pick RDMA Platform Validator PR to 202205 (#3051)
Cherry-pick PR #2692 (set up test infrastructure used in later PR 2857), #2857 (platform validator PR), #2913 (edge case fix), #2951, #3018 (add support for new Mellanox HwSKU in conf file) This change stops GCU from modifying a protected RDMA field. Signed-off-by: Stephen Sun <[email protected]> Co-authored-by: Stephen Sun <[email protected]>
1 parent 5d3c563 commit 8765fce

File tree

8 files changed

+666
-65
lines changed

8 files changed

+666
-65
lines changed
Lines changed: 127 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,132 @@
1-
from sonic_py_common import device_info
1+
import os
22
import re
3+
import json
4+
import jsonpointer
5+
import subprocess
6+
from sonic_py_common import device_info
7+
from .gu_common import GenericConfigUpdaterError
8+
9+
10+
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
11+
GCU_TABLE_MOD_CONF_FILE = f"{SCRIPT_DIR}/gcu_field_operation_validators.conf.json"
12+
GET_HWSKU_CMD = "sonic-cfggen -d -v DEVICE_METADATA.localhost.hwsku"
13+
14+
def get_asic_name():
15+
asic = "unknown"
16+
17+
if os.path.exists(GCU_TABLE_MOD_CONF_FILE):
18+
with open(GCU_TABLE_MOD_CONF_FILE, "r") as s:
19+
gcu_field_operation_conf = json.load(s)
20+
else:
21+
raise GenericConfigUpdaterError("GCU table modification validators config file not found")
22+
23+
asic_mapping = gcu_field_operation_conf["helper_data"]["rdma_config_update_validator"]
24+
asic_type = device_info.get_sonic_version_info()['asic_type']
25+
26+
if asic_type == 'cisco-8000':
27+
asic = "cisco-8000"
28+
elif asic_type == 'mellanox' or asic_type == 'vs' or asic_type == 'broadcom':
29+
proc = subprocess.Popen(GET_HWSKU_CMD, shell=True, universal_newlines=True, stdout=subprocess.PIPE)
30+
output, err = proc.communicate()
31+
hwsku = output.rstrip('\n')
32+
if asic_type == 'mellanox' or asic_type == 'vs':
33+
spc1_hwskus = asic_mapping["mellanox_asics"]["spc1"]
34+
spc2_hwskus = asic_mapping["mellanox_asics"]["spc2"]
35+
spc3_hwskus = asic_mapping["mellanox_asics"]["spc3"]
36+
spc4_hwskus = asic_mapping["mellanox_asics"]["spc4"]
37+
if hwsku.lower() in [spc1_hwsku.lower() for spc1_hwsku in spc1_hwskus]:
38+
asic = "spc1"
39+
return asic
40+
if hwsku.lower() in [spc2_hwsku.lower() for spc2_hwsku in spc2_hwskus]:
41+
asic = "spc2"
42+
return asic
43+
if hwsku.lower() in [spc3_hwsku.lower() for spc3_hwsku in spc3_hwskus]:
44+
asic = "spc3"
45+
return asic
46+
if hwsku.lower() in [spc4_hwsku.lower() for spc4_hwsku in spc4_hwskus]:
47+
asic = "spc4"
48+
return asic
49+
if asic_type == 'broadcom' or asic_type == 'vs':
50+
broadcom_asics = asic_mapping["broadcom_asics"]
51+
for asic_shorthand, hwskus in broadcom_asics.items():
52+
if asic != "unknown":
53+
break
54+
for hwsku_cur in hwskus:
55+
if hwsku_cur.lower() in hwsku.lower():
56+
asic = asic_shorthand
57+
break
58+
59+
return asic
360

4-
def rdma_config_update_validator():
5-
version_info = device_info.get_sonic_version_info()
6-
asic_type = version_info.get('asic_type')
761

8-
if (asic_type != 'mellanox' and asic_type != 'broadcom' and asic_type != 'cisco-8000'):
62+
def rdma_config_update_validator(patch_element):
63+
asic = get_asic_name()
64+
if asic == "unknown":
965
return False
66+
version_info = device_info.get_sonic_version_info()
67+
build_version = version_info.get('build_version')
68+
version_substrings = build_version.split('.')
69+
branch_version = None
70+
71+
for substring in version_substrings:
72+
if substring.isdigit() and re.match(r'^\d{8}$', substring):
73+
branch_version = substring
74+
75+
path = patch_element["path"]
76+
table = jsonpointer.JsonPointer(path).parts[0]
77+
78+
# Helper function to return relevant cleaned paths, consdiers case where the jsonpatch value is a dict
79+
# 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)
80+
def _get_fields_in_patch():
81+
cleaned_fields = []
82+
83+
field_elements = jsonpointer.JsonPointer(path).parts[1:]
84+
cleaned_field_elements = [elem for elem in field_elements if not any(char.isdigit() for char in elem)]
85+
cleaned_field = '/'.join(cleaned_field_elements).lower()
86+
87+
88+
if 'value' in patch_element.keys() and isinstance(patch_element['value'], dict):
89+
for key in patch_element['value']:
90+
if len(cleaned_field) > 0:
91+
cleaned_fields.append(cleaned_field + '/' + key)
92+
else:
93+
cleaned_fields.append(key)
94+
else:
95+
cleaned_fields.append(cleaned_field)
96+
97+
return cleaned_fields
98+
99+
if os.path.exists(GCU_TABLE_MOD_CONF_FILE):
100+
with open(GCU_TABLE_MOD_CONF_FILE, "r") as s:
101+
gcu_field_operation_conf = json.load(s)
102+
else:
103+
raise GenericConfigUpdaterError("GCU table modification validators config file not found")
104+
105+
tables = gcu_field_operation_conf["tables"]
106+
scenarios = tables[table]["validator_data"]["rdma_config_update_validator"]
107+
108+
cleaned_fields = _get_fields_in_patch()
109+
for cleaned_field in cleaned_fields:
110+
scenario = None
111+
for key in scenarios.keys():
112+
if cleaned_field in scenarios[key]["fields"]:
113+
scenario = scenarios[key]
114+
break
115+
116+
if scenario is None:
117+
return False
118+
119+
if scenario["platforms"][asic] == "":
120+
return False
121+
122+
if patch_element['op'] not in scenario["operations"]:
123+
return False
124+
125+
if branch_version is not None:
126+
if asic in scenario["platforms"]:
127+
if branch_version < scenario["platforms"][asic]:
128+
return False
129+
else:
130+
return False
131+
10132
return True

generic_config_updater/gcu_field_operation_validators.conf.json

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,147 @@
1010
"e.g. 'show.acl.test_acl'",
1111
"",
1212
"field_operation_validators for a given table defines a list of validators that all must pass for modification to the specified field and table to be allowed",
13+
"",
14+
"validator_data provides data relevant to each validator",
1315
""
1416
],
17+
"helper_data": {
18+
"rdma_config_update_validator": {
19+
"mellanox_asics": {
20+
"spc1": [ "ACS-MSN2700", "ACS-MSN2740", "ACS-MSN2100", "ACS-MSN2410", "ACS-MSN2010", "Mellanox-SN2700", "Mellanox-SN2700-D48C8" ],
21+
"spc2": [ "ACS-MSN3800", "Mellanox-SN3800-D112C8" ],
22+
"spc3": [ "ACS-MSN4700", "ACS-MSN4600", "ACS-MSN4600C", "ACS-MSN4410", "Mellanox-SN4600C-D112C8", "Mellanox-SN4600C-C64", "Mellanox-SN4700-O8C48" ],
23+
"spc4": [ "ACS-SN5600"]
24+
},
25+
"broadcom_asics": {
26+
"th": [ "Force10-S6100", "Arista-7060CX-32S-C32", "Arista-7060CX-32S-C32-T1", "Arista-7060CX-32S-D48C8", "Celestica-DX010-C32", "Seastone-DX010" ],
27+
"th2": [ "Arista-7260CX3-D108C8", "Arista-7260CX3-C64", "Arista-7260CX3-Q64" ],
28+
"td2": [ "Force10-S6000", "Force10-S6000-Q24S32", "Arista-7050-QX32", "Arista-7050-QX-32S", "Nexus-3164", "Arista-7050QX32S-Q32" ],
29+
"td3": [ "Arista-7050CX3-32S-C32", "Arista-7050CX3-32S-D48C8" ]
30+
}
31+
}
32+
},
1533
"tables": {
1634
"PFC_WD": {
17-
"field_operation_validators": [ "generic_config_updater.field_operation_validators.rdma_config_update_validator" ]
35+
"field_operation_validators": [ "generic_config_updater.field_operation_validators.rdma_config_update_validator" ],
36+
"validator_data": {
37+
"rdma_config_update_validator": {
38+
"PFCWD enable/disable": {
39+
"fields": [
40+
"restoration_time",
41+
"detection_time",
42+
"action",
43+
"global/poll_interval",
44+
""
45+
],
46+
"operations": ["remove", "add", "replace"],
47+
"platforms": {
48+
"spc1": "20181100",
49+
"spc2": "20191100",
50+
"spc3": "20220500",
51+
"spc4": "20221100",
52+
"td2": "20181100",
53+
"th": "20181100",
54+
"th2": "20181100",
55+
"td3": "20201200",
56+
"cisco-8000": "20201200"
57+
}
58+
}
59+
}
60+
}
61+
},
62+
"BUFFER_POOL": {
63+
"field_operation_validators": [ "generic_config_updater.field_operation_validators.rdma_config_update_validator" ],
64+
"validator_data": {
65+
"rdma_config_update_validator": {
66+
"Shared/headroom pool size changes": {
67+
"fields": [
68+
"ingress_lossless_pool/xoff",
69+
"ingress_lossless_pool/size",
70+
"egress_lossy_pool/size"
71+
],
72+
"operations": ["replace"],
73+
"platforms": {
74+
"spc1": "20191100",
75+
"spc2": "20191100",
76+
"spc3": "20220500",
77+
"spc4": "20221100",
78+
"td2": "",
79+
"th": "20221100",
80+
"th2": "20221100",
81+
"td3": "20221100",
82+
"cisco-8000": ""
83+
}
84+
}
85+
}
86+
}
87+
},
88+
"BUFFER_PROFILE": {
89+
"field_operation_validators": [ "generic_config_updater.field_operation_validators.rdma_config_update_validator" ],
90+
"validator_data": {
91+
"rdma_config_update_validator": {
92+
"Dynamic threshold tuning": {
93+
"fields": [
94+
"dynamic_th"
95+
],
96+
"operations": ["replace"],
97+
"platforms": {
98+
"spc1": "20181100",
99+
"spc2": "20191100",
100+
"spc3": "20220500",
101+
"spc4": "20221100",
102+
"td2": "20181100",
103+
"th": "20181100",
104+
"th2": "20181100",
105+
"td3": "20201200",
106+
"cisco-8000": ""
107+
}
108+
},
109+
"PG headroom modification": {
110+
"fields": [
111+
"xoff"
112+
],
113+
"operations": ["replace"],
114+
"platforms": {
115+
"spc1": "20191100",
116+
"spc2": "20191100",
117+
"spc3": "20220500",
118+
"spc4": "20221100",
119+
"td2": "",
120+
"th": "20221100",
121+
"th2": "20221100",
122+
"td3": "20221100",
123+
"cisco-8000": ""
124+
}
125+
}
126+
}
127+
}
128+
},
129+
"WRED_PROFILE": {
130+
"field_operation_validators": [ "generic_config_updater.field_operation_validators.rdma_config_update_validator" ],
131+
"validator_data": {
132+
"rdma_config_update_validator": {
133+
"ECN tuning": {
134+
"fields": [
135+
"azure_lossless/green_min_threshold",
136+
"azure_lossless/green_max_threshold",
137+
"azure_lossless/green_drop_probability"
138+
],
139+
"operations": ["replace"],
140+
"platforms": {
141+
"spc1": "20181100",
142+
"spc2": "20191100",
143+
"spc3": "20220500",
144+
"spc4": "20221100",
145+
"td2": "20181100",
146+
"th": "20181100",
147+
"th2": "20181100",
148+
"td3": "20201200",
149+
"cisco-8000": ""
150+
}
151+
}
152+
}
153+
}
18154
}
19155
}
20156
}

generic_config_updater/gu_common.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,15 @@ def validate_field_operation(self, old_config, target_config):
166166
if any(op['op'] == operation and field == op['path'] for op in patch):
167167
raise IllegalPatchOperationError("Given patch operation is invalid. Operation: {} is illegal on field: {}".format(operation, field))
168168

169-
def _invoke_validating_function(cmd):
169+
def _invoke_validating_function(cmd, jsonpatch_element):
170170
# cmd is in the format as <package/module name>.<method name>
171171
method_name = cmd.split(".")[-1]
172172
module_name = ".".join(cmd.split(".")[0:-1])
173173
if module_name != "generic_config_updater.field_operation_validators" or "validator" not in method_name:
174174
raise GenericConfigUpdaterError("Attempting to call invalid method {} in module {}. Module must be generic_config_updater.field_operation_validators, and method must be a defined validator".format(method_name, module_name))
175175
module = importlib.import_module(module_name, package=None)
176176
method_to_call = getattr(module, method_name)
177-
return method_to_call()
177+
return method_to_call(jsonpatch_element)
178178

179179
if os.path.exists(GCU_FIELD_OP_CONF_FILE):
180180
with open(GCU_FIELD_OP_CONF_FILE, "r") as s:
@@ -194,7 +194,7 @@ def _invoke_validating_function(cmd):
194194
validating_functions.update(tables.get(table, {}).get("field_operation_validators", []))
195195

196196
for function in validating_functions:
197-
if not _invoke_validating_function(function):
197+
if not _invoke_validating_function(function, element):
198198
raise IllegalPatchOperationError("Modification of {} table is illegal- validating function {} returned False".format(table, function))
199199

200200
def validate_lanes(self, config_db):

0 commit comments

Comments
 (0)