Skip to content

Commit da44d71

Browse files
authored
YANG Validation for ConfigDB Updates: PORT_STORM_CONTROL, PORT_QOS_MAP, BUFFER_PROFILE, BUFFER_PG, BUFFER_QUEUE, BUFFER_POOL, FEATURE, DEFAULT_LOSSLESS_BUFFER_PARAMETER tables (sonic-net#2498)
1 parent ba9b628 commit da44d71

File tree

6 files changed

+215
-26
lines changed

6 files changed

+215
-26
lines changed

config/feature.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import click
44
from swsscommon import swsscommon
55
from utilities_common.cli import AbbreviationGroup, pass_db
6+
from .validated_config_db_connector import ValidatedConfigDBConnector
67

78
SELECT_TIMEOUT = 1000 # ms
89

@@ -24,7 +25,12 @@ def set_feature_state(cfgdb_clients, name, state, block):
2425
raise Exception("Feature '{}' state is always enabled and can not be modified".format(name))
2526

2627
for ns, cfgdb in cfgdb_clients.items():
27-
cfgdb.mod_entry('FEATURE', name, {'state': state})
28+
try:
29+
config_db = ValidatedConfigDBConnector(cfgdb)
30+
config_db.mod_entry('FEATURE', name, {'state': state})
31+
except ValueError as e:
32+
ctx = click.get_current_context()
33+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
2834

2935
if block:
3036
db = swsscommon.DBConnector('STATE_DB', 0)
@@ -66,7 +72,12 @@ def _update_field(db, name, fld, val):
6672
if name not in tbl:
6773
click.echo("Unable to retrieve {} from FEATURE table".format(name))
6874
sys.exit(1)
69-
db.cfgdb.mod_entry('FEATURE', name, { fld: val })
75+
try:
76+
config_db = ValidatedConfigDBConnector(db.cfgdb)
77+
config_db.mod_entry('FEATURE', name, { fld: val })
78+
except ValueError as e:
79+
ctx = click.get_current_context()
80+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
7081

7182

7283
#
@@ -137,5 +148,10 @@ def feature_autorestart(db, name, autorestart):
137148
return
138149

139150
for ns, cfgdb in db.cfgdb_clients.items():
140-
cfgdb.mod_entry('FEATURE', name, {'auto_restart': autorestart})
151+
try:
152+
config_db = ValidatedConfigDBConnector(cfgdb)
153+
config_db.mod_entry('FEATURE', name, {'auto_restart': autorestart})
154+
except ValueError as e:
155+
ctx = click.get_current_context()
156+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
141157

config/main.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -697,17 +697,25 @@ def storm_control_set_entry(port_name, kbps, storm_type, namespace):
697697
return False
698698

699699
#Validate kbps value
700-
config_db = ConfigDBConnector()
700+
config_db = ValidatedConfigDBConnector(ConfigDBConnector())
701701
config_db.connect()
702702
key = port_name + '|' + storm_type
703703
entry = config_db.get_entry('PORT_STORM_CONTROL', key)
704704

705705
if len(entry) == 0:
706-
config_db.set_entry('PORT_STORM_CONTROL', key, {'kbps':kbps})
706+
try:
707+
config_db.set_entry('PORT_STORM_CONTROL', key, {'kbps':kbps})
708+
except ValueError as e:
709+
ctx = click.get_current_context()
710+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
707711
else:
708712
kbps_value = int(entry.get('kbps',0))
709713
if kbps_value != kbps:
710-
config_db.mod_entry('PORT_STORM_CONTROL', key, {'kbps':kbps})
714+
try:
715+
config_db.mod_entry('PORT_STORM_CONTROL', key, {'kbps':kbps})
716+
except ValueError as e:
717+
ctx = click.get_current_context()
718+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
711719

712720
return True
713721

@@ -717,7 +725,7 @@ def storm_control_delete_entry(port_name, storm_type):
717725
if storm_control_interface_validate(port_name) is False:
718726
return False
719727

720-
config_db = ConfigDBConnector()
728+
config_db = ValidatedConfigDBConnector(ConfigDBConnector())
721729
config_db.connect()
722730
key = port_name + '|' + storm_type
723731
entry = config_db.get_entry('PORT_STORM_CONTROL', key)
@@ -726,7 +734,11 @@ def storm_control_delete_entry(port_name, storm_type):
726734
click.echo("%s storm-control not enabled on interface %s" %(storm_type, port_name))
727735
return False
728736
else:
729-
config_db.set_entry('PORT_STORM_CONTROL', key, None)
737+
try:
738+
config_db.set_entry('PORT_STORM_CONTROL', key, None)
739+
except JsonPatchConflict as e:
740+
ctx = click.get_current_context()
741+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
730742

731743
return True
732744

@@ -4470,7 +4482,7 @@ def _parse_object_id(idsmap):
44704482

44714483

44724484
def update_buffer_object(db, interface_name, object_map, override_profile, is_pg, add=True):
4473-
config_db = db.cfgdb
4485+
config_db = ValidatedConfigDBConnector(db.cfgdb)
44744486
ctx = click.get_current_context()
44754487

44764488
# Check whether port is legal
@@ -4500,16 +4512,22 @@ def update_buffer_object(db, interface_name, object_map, override_profile, is_pg
45004512
if is_pg:
45014513
if not 'xoff' in profile_dict.keys() and 'size' in profile_dict.keys():
45024514
ctx.fail("Profile {} doesn't exist or isn't a lossless profile".format(override_profile))
4503-
config_db.set_entry(buffer_table, (interface_name, object_map), {"profile": override_profile})
4515+
try:
4516+
config_db.set_entry(buffer_table, (interface_name, object_map), {"profile": override_profile})
4517+
except ValueError as e:
4518+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
45044519
else:
4505-
config_db.set_entry(buffer_table, (interface_name, object_map), {"profile": "NULL"})
4520+
try:
4521+
config_db.set_entry(buffer_table, (interface_name, object_map), {"profile": "NULL"})
4522+
except ValueError as e:
4523+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
45064524

45074525
if is_pg:
45084526
adjust_pfc_enable(ctx, db, interface_name, object_map, True)
45094527

45104528

45114529
def remove_buffer_object_on_port(db, interface_name, buffer_object_map, is_pg=True):
4512-
config_db = db.cfgdb
4530+
config_db = ValidatedConfigDBConnector(db.cfgdb)
45134531
ctx = click.get_current_context()
45144532

45154533
# Check whether port is legal
@@ -4530,7 +4548,10 @@ def remove_buffer_object_on_port(db, interface_name, buffer_object_map, is_pg=Tr
45304548
ctx.fail("Lossy PG {} can't be removed".format(buffer_object_map))
45314549
else:
45324550
continue
4533-
config_db.set_entry(buffer_table, (interface_name, existing_buffer_object), None)
4551+
try:
4552+
config_db.set_entry(buffer_table, (interface_name, existing_buffer_object), None)
4553+
except JsonPatchConflict as e:
4554+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
45344555
if is_pg:
45354556
adjust_pfc_enable(ctx, db, interface_name, buffer_object_map, False)
45364557
removed = True
@@ -4543,7 +4564,7 @@ def remove_buffer_object_on_port(db, interface_name, buffer_object_map, is_pg=Tr
45434564

45444565

45454566
def adjust_pfc_enable(ctx, db, interface_name, pg_map, add):
4546-
config_db = db.cfgdb
4567+
config_db = ValidatedConfigDBConnector(db.cfgdb)
45474568

45484569
# Fetch the original pfc_enable
45494570
qosmap = config_db.get_entry("PORT_QOS_MAP", interface_name)
@@ -4576,7 +4597,10 @@ def adjust_pfc_enable(ctx, db, interface_name, pg_map, add):
45764597
ctx.fail("Try to add empty priorities")
45774598

45784599
qosmap["pfc_enable"] = pfc_enable[:-1]
4579-
config_db.set_entry("PORT_QOS_MAP", interface_name, qosmap)
4600+
try:
4601+
config_db.set_entry("PORT_QOS_MAP", interface_name, qosmap)
4602+
except ValueError as e:
4603+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
45804604

45814605

45824606
#
@@ -5811,6 +5835,7 @@ def _is_shared_headroom_pool_enabled(ctx, config_db):
58115835

58125836

58135837
def update_profile(ctx, config_db, profile_name, xon, xoff, size, dynamic_th, pool, profile_entry = None):
5838+
config_db = ValidatedConfigDBConnector(config_db)
58145839
params = {}
58155840
if profile_entry:
58165841
params = profile_entry
@@ -5882,14 +5907,17 @@ def update_profile(ctx, config_db, profile_name, xon, xoff, size, dynamic_th, po
58825907
else:
58835908
ctx.fail("No dynamic_th defined in DEFAULT_LOSSLESS_BUFFER_PARAMETER")
58845909

5885-
config_db.set_entry("BUFFER_PROFILE", (profile_name), params)
5910+
try:
5911+
config_db.set_entry("BUFFER_PROFILE", (profile_name), params)
5912+
except ValueError as e:
5913+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
58865914

58875915
@profile.command('remove')
58885916
@click.argument('profile', metavar='<profile>', required=True)
58895917
@clicommon.pass_db
58905918
def remove_profile(db, profile):
58915919
"""Delete a buffer profile"""
5892-
config_db = db.cfgdb
5920+
config_db = ValidatedConfigDBConnector(db.cfgdb)
58935921
ctx = click.get_current_context()
58945922

58955923
existing_pgs = config_db.get_table("BUFFER_PG")
@@ -5901,7 +5929,10 @@ def remove_profile(db, profile):
59015929

59025930
entry = config_db.get_entry("BUFFER_PROFILE", profile)
59035931
if entry:
5904-
config_db.set_entry("BUFFER_PROFILE", profile, None)
5932+
try:
5933+
config_db.set_entry("BUFFER_PROFILE", profile, None)
5934+
except JsonPatchConflict as e:
5935+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
59055936
else:
59065937
ctx.fail("Profile {} doesn't exist".format(profile))
59075938

@@ -5917,7 +5948,7 @@ def shared_headroom_pool(ctx):
59175948
@clicommon.pass_db
59185949
def over_subscribe_ratio(db, ratio):
59195950
"""Configure over subscribe ratio"""
5920-
config_db = db.cfgdb
5951+
config_db = ValidatedConfigDBConnector(db.cfgdb)
59215952
ctx = click.get_current_context()
59225953

59235954
port_number = len(config_db.get_table('PORT'))
@@ -5937,15 +5968,18 @@ def over_subscribe_ratio(db, ratio):
59375968
else:
59385969
v["over_subscribe_ratio"] = ratio
59395970

5940-
config_db.set_entry("DEFAULT_LOSSLESS_BUFFER_PARAMETER", k, v)
5971+
try:
5972+
config_db.set_entry("DEFAULT_LOSSLESS_BUFFER_PARAMETER", k, v)
5973+
except ValueError as e:
5974+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
59415975

59425976

59435977
@shared_headroom_pool.command()
59445978
@click.argument('size', metavar='<size>', type=int, required=True)
59455979
@clicommon.pass_db
59465980
def size(db, size):
59475981
"""Configure shared headroom pool size"""
5948-
config_db = db.cfgdb
5982+
config_db = ValidatedConfigDBConnector(db.cfgdb)
59495983
state_db = db.db
59505984
ctx = click.get_current_context()
59515985

@@ -5964,7 +5998,10 @@ def size(db, size):
59645998
else:
59655999
ingress_lossless_pool["xoff"] = size
59666000

5967-
config_db.set_entry("BUFFER_POOL", "ingress_lossless_pool", ingress_lossless_pool)
6001+
try:
6002+
config_db.set_entry("BUFFER_POOL", "ingress_lossless_pool", ingress_lossless_pool)
6003+
except ValueError as e:
6004+
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
59686005

59696006

59706007
#

config/validated_config_db_connector.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ def create_gcu_patch(self, op, table, key=None, value=None, mod_entry=False):
5353

5454
"""Add patch element to create ConfigDB path if necessary, as GCU is unable to add to a nonexistent path"""
5555
if op == "add" and not self.get_entry(table, key):
56-
path = JsonPointer.from_parts([table, key]).path
56+
if type(key) == tuple:
57+
path = JsonPointer.from_parts([table, '|'.join(key)]).path
58+
else:
59+
path = JsonPointer.from_parts([table, key]).path
5760
gcu_json = {"op": "{}".format(op),
5861
"path": "{}".format(path),
5962
"value": {}}
@@ -106,7 +109,9 @@ def validated_delete_table(self, table):
106109
logger.log_notice("Unable to remove entry, as doing so will result in invalid config. Error: {}".format(e))
107110

108111
def validated_mod_entry(self, table, key, value):
109-
if value is not None:
112+
if isinstance(value, dict) and len(value) == 1 and list(value.values())[0] == "":
113+
op = "remove"
114+
elif value is not None:
110115
op = "add"
111116
else:
112117
op = "remove"
@@ -115,7 +120,9 @@ def validated_mod_entry(self, table, key, value):
115120
self.apply_patch(gcu_patch, table)
116121

117122
def validated_set_entry(self, table, key, value):
118-
if value is not None:
123+
if isinstance(value, dict) and len(value) == 1 and list(value.values())[0] == "":
124+
op = "remove"
125+
elif value is not None:
119126
op = "add"
120127
else:
121128
op = "remove"

tests/buffer_test.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
import sys
33
import pytest
44
import mock
5+
import jsonpatch
56
from importlib import reload
67
from click.testing import CliRunner
78
from unittest import TestCase
89
from swsscommon.swsscommon import ConfigDBConnector
10+
from mock import patch
11+
from jsonpatch import JsonPatchConflict
912

1013
from .mock_tables import dbconnector
1114

@@ -14,6 +17,7 @@
1417
from utilities_common.db import Db
1518

1619
from .buffer_input.buffer_test_vectors import *
20+
from config.validated_config_db_connector import ValidatedConfigDBConnector
1721

1822
test_path = os.path.dirname(os.path.abspath(__file__))
1923
modules_path = os.path.dirname(test_path)
@@ -28,6 +32,15 @@ def setup_class(cls):
2832
os.environ['UTILITIES_UNIT_TESTING'] = "2"
2933
print("SETUP")
3034

35+
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
36+
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=ValueError))
37+
def test_config_buffer_profile_headroom_yang(self):
38+
db = Db()
39+
runner = CliRunner()
40+
result = runner.invoke(config.config.commands["buffer"].commands["profile"].commands["add"],
41+
["testprofile", "--dynamic_th", "3", "--xon", "18432", "--xoff", "32768"], obj=db)
42+
assert "Invalid ConfigDB. Error" in result.output
43+
3144
def test_config_buffer_profile_headroom(self):
3245
runner = CliRunner()
3346
db = Db()
@@ -118,6 +131,16 @@ def test_config_buffer_profile_multiple_or_none_default_buffer_param_in_database
118131
assert result.exit_code != 0
119132
assert "Multiple entries are found in DEFAULT_LOSSLESS_BUFFER_PARAMETER while no dynamic_th specified" in result.output
120133

134+
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
135+
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=ValueError))
136+
def test_config_shp_size_negative_yang(self):
137+
runner = CliRunner()
138+
result = runner.invoke(config.config.commands["buffer"].commands["shared-headroom-pool"].commands["size"],
139+
["200000"])
140+
print(result.exit_code)
141+
print(result.output)
142+
assert "Invalid ConfigDB. Error" in result.output
143+
121144
def test_config_shp_size_negative(self):
122145
runner = CliRunner()
123146
result = runner.invoke(config.config.commands["buffer"].commands["shared-headroom-pool"].commands["size"],
@@ -286,7 +309,34 @@ def setup_class(cls):
286309
os.environ["UTILITIES_UNIT_TESTING"] = "0"
287310
from .mock_tables import dbconnector
288311
dbconnector.dedicated_dbs = {}
312+
313+
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
314+
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=ValueError))
315+
def test_config_int_buffer_pg_lossless_add_yang(self, get_cmd_module):
316+
(config, show) = get_cmd_module
317+
db = Db()
318+
runner = CliRunner()
319+
with mock.patch('utilities_common.cli.run_command') as mock_run_command:
320+
result = runner.invoke(config.config.commands["interface"].commands["buffer"].commands["priority-group"].
321+
commands["lossless"].commands["add"],
322+
["Ethernet0", "5"], obj=db)
323+
assert "Invalid ConfigDB. Error" in result.output
324+
325+
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
326+
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=JsonPatchConflict))
327+
def test_config_int_buffer_pg_lossless_remove(self, get_cmd_module):
328+
(config, show) = get_cmd_module
329+
runner = CliRunner()
330+
db = Db()
289331

332+
# Remove non-exist entry
333+
with mock.patch('utilities_common.cli.run_command') as mock_run_command:
334+
result = runner.invoke(config.config.commands["interface"].commands["buffer"].commands["priority-group"].
335+
commands["lossless"].commands["remove"],
336+
["Ethernet0", "5"], obj=db)
337+
print(result.exit_code, result.output)
338+
assert "Invalidi ConfigDB. Error" in result.output
339+
290340
def test_config_int_buffer_pg_lossless_add(self, get_cmd_module):
291341
(config, show) = get_cmd_module
292342
runner = CliRunner()

0 commit comments

Comments
 (0)