Skip to content

Commit b250701

Browse files
authored
YANG Validation for ConfigDB Updates: Fix Decorator Bug (#2405)
1 parent f62d1e5 commit b250701

File tree

4 files changed

+84
-61
lines changed

4 files changed

+84
-61
lines changed

config/validated_config_db_connector.py

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,66 @@
55
from generic_config_updater.generic_updater import GenericUpdater, ConfigFormat
66
from generic_config_updater.gu_common import EmptyTableError, genericUpdaterLogging
77

8-
def ValidatedConfigDBConnector(config_db_connector):
9-
yang_enabled = device_info.is_yang_config_validation_enabled(config_db_connector)
10-
if yang_enabled:
11-
config_db_connector.set_entry = validated_set_entry
12-
config_db_connector.delete_table = validated_delete_table
13-
return config_db_connector
14-
15-
def make_path_value_jsonpatch_compatible(table, key, value):
16-
if type(key) == tuple:
17-
path = JsonPointer.from_parts([table, '|'.join(key)]).path
18-
else:
19-
path = JsonPointer.from_parts([table, key]).path
20-
if value == {"NULL" : "NULL"}:
21-
value = {}
22-
return path, value
23-
24-
def create_gcu_patch(op, table, key=None, value=None):
25-
if key:
26-
path, value = make_path_value_jsonpatch_compatible(table, key, value)
27-
else:
28-
path = "/{}".format(table)
29-
30-
gcu_json_input = []
31-
gcu_json = {"op": "{}".format(op),
32-
"path": "{}".format(path)}
33-
if op == "add":
34-
gcu_json["value"] = value
35-
36-
gcu_json_input.append(gcu_json)
37-
gcu_patch = jsonpatch.JsonPatch(gcu_json_input)
38-
return gcu_patch
39-
40-
def validated_delete_table(table):
41-
gcu_patch = create_gcu_patch("remove", table)
42-
format = ConfigFormat.CONFIGDB.name
43-
config_format = ConfigFormat[format.upper()]
44-
try:
45-
GenericUpdater().apply_patch(patch=gcu_patch, config_format=config_format, verbose=False, dry_run=False, ignore_non_yang_tables=False, ignore_paths=None)
46-
except ValueError as e:
47-
logger = genericUpdaterLogging.get_logger(title="Patch Applier", print_all_to_console=True)
48-
logger.log_notice("Unable to remove entry, as doing so will result in invalid config. Error: {}".format(e))
49-
50-
def validated_set_entry(table, key, value):
51-
if value is not None:
52-
op = "add"
53-
else:
54-
op = "remove"
8+
class ValidatedConfigDBConnector(object):
559

56-
gcu_patch = create_gcu_patch(op, table, key, value)
57-
format = ConfigFormat.CONFIGDB.name
58-
config_format = ConfigFormat[format.upper()]
59-
60-
try:
61-
GenericUpdater().apply_patch(patch=gcu_patch, config_format=config_format, verbose=False, dry_run=False, ignore_non_yang_tables=False, ignore_paths=None)
62-
except EmptyTableError:
63-
validated_delete_table(table)
10+
def __init__(self, config_db_connector):
11+
self.connector = config_db_connector
12+
self.yang_enabled = device_info.is_yang_config_validation_enabled(self.connector)
13+
14+
def __getattr__(self, name):
15+
if self.yang_enabled:
16+
if name == "set_entry":
17+
return self.validated_set_entry
18+
if name == "delete_table":
19+
return self.validated_delete_table
20+
return self.connector.__getattribute__(name)
21+
22+
def make_path_value_jsonpatch_compatible(self, table, key, value):
23+
if type(key) == tuple:
24+
path = JsonPointer.from_parts([table, '|'.join(key)]).path
25+
else:
26+
path = JsonPointer.from_parts([table, key]).path
27+
if value == {"NULL" : "NULL"}:
28+
value = {}
29+
return path, value
30+
31+
def create_gcu_patch(self, op, table, key=None, value=None):
32+
if key:
33+
path, value = self.make_path_value_jsonpatch_compatible(table, key, value)
34+
else:
35+
path = "/{}".format(table)
36+
37+
gcu_json_input = []
38+
gcu_json = {"op": "{}".format(op),
39+
"path": "{}".format(path)}
40+
if op == "add":
41+
gcu_json["value"] = value
42+
43+
gcu_json_input.append(gcu_json)
44+
gcu_patch = jsonpatch.JsonPatch(gcu_json_input)
45+
return gcu_patch
46+
47+
def validated_delete_table(self, table):
48+
gcu_patch = self.create_gcu_patch("remove", table)
49+
format = ConfigFormat.CONFIGDB.name
50+
config_format = ConfigFormat[format.upper()]
51+
try:
52+
GenericUpdater().apply_patch(patch=gcu_patch, config_format=config_format, verbose=False, dry_run=False, ignore_non_yang_tables=False, ignore_paths=None)
53+
except ValueError as e:
54+
logger = genericUpdaterLogging.get_logger(title="Patch Applier", print_all_to_console=True)
55+
logger.log_notice("Unable to remove entry, as doing so will result in invalid config. Error: {}".format(e))
56+
57+
def validated_set_entry(self, table, key, value):
58+
if value is not None:
59+
op = "add"
60+
else:
61+
op = "remove"
62+
63+
gcu_patch = self.create_gcu_patch(op, table, key, value)
64+
format = ConfigFormat.CONFIGDB.name
65+
config_format = ConfigFormat[format.upper()]
66+
67+
try:
68+
GenericUpdater().apply_patch(patch=gcu_patch, config_format=config_format, verbose=False, dry_run=False, ignore_non_yang_tables=False, ignore_paths=None)
69+
except EmptyTableError:
70+
self.validated_delete_table(table)

tests/config_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,7 +1638,7 @@ def setup_class(cls):
16381638
importlib.reload(config.main)
16391639

16401640
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
1641-
@patch("config.validated_config_db_connector.validated_set_entry", mock.Mock(side_effect=ValueError))
1641+
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=ValueError))
16421642
def test_add_loopback_with_invalid_name_yang_validation(self):
16431643
config.ADHOC_VALIDATION = False
16441644
runner = CliRunner()
@@ -1687,7 +1687,7 @@ def test_del_nonexistent_loopback_adhoc_validation(self):
16871687
assert result.exit_code != 0
16881688
assert "Loopbax1 is invalid, name should have prefix 'Loopback' and suffix '<0-999>'" in result.output
16891689

1690-
@patch("config.validated_config_db_connector.validated_set_entry", mock.Mock(return_value=True))
1690+
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(return_value=True))
16911691
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
16921692
def test_add_loopback_yang_validation(self):
16931693
config.ADHOC_VALIDATION = False

tests/portchannel_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def setup_class(cls):
1919
print("SETUP")
2020

2121
@patch("config.main.is_portchannel_present_in_db", mock.Mock(return_value=False))
22-
@patch("config.validated_config_db_connector.validated_set_entry", mock.Mock(side_effect=ValueError))
22+
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=ValueError))
2323
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
2424
def test_add_portchannel_with_invalid_name_yang_validation(self):
2525
config.ADHOC_VALIDATION = False
@@ -46,7 +46,7 @@ def test_add_portchannel_with_invalid_name_adhoc_validation(self):
4646
assert result.exit_code != 0
4747
assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output
4848

49-
@patch("config.validated_config_db_connector.validated_set_entry", mock.Mock(side_effect=JsonPatchConflict))
49+
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=JsonPatchConflict))
5050
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
5151
def test_delete_nonexistent_portchannel_yang_validation(self):
5252
config.ADHOC_VALIDATION = False

tests/validated_config_db_connector_test.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import imp
22
import os
33
import mock
4+
import jsonpatch
45

56
imp.load_source('validated_config_db_connector', \
67
os.path.join(os.path.dirname(__file__), '..', 'config', 'validated_config_db_connector.py'))
@@ -9,6 +10,9 @@
910
from unittest import TestCase
1011
from mock import patch
1112
from generic_config_updater.gu_common import EmptyTableError
13+
from validated_config_db_connector import ValidatedConfigDBConnector
14+
from swsscommon.swsscommon import ConfigDBConnector
15+
1216
from utilities_common.db import Db
1317

1418
SAMPLE_TABLE = 'VLAN'
@@ -22,9 +26,21 @@ class TestValidatedConfigDBConnector(TestCase):
2226
Test Class for validated_config_db_connector.py
2327
2428
'''
25-
def test_validated_config_db_connector_empty_table(self):
29+
def test_validated_set_entry_empty_table(self):
2630
mock_generic_updater = mock.Mock()
2731
mock_generic_updater.apply_patch = mock.Mock(side_effect=EmptyTableError)
2832
with mock.patch('validated_config_db_connector.GenericUpdater', return_value=mock_generic_updater):
29-
remove_entry_success = validated_config_db_connector.validated_set_entry(SAMPLE_TABLE, SAMPLE_KEY, SAMPLE_VALUE_EMPTY)
33+
remove_entry_success = validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry(mock.Mock(), SAMPLE_TABLE, SAMPLE_KEY, SAMPLE_VALUE_EMPTY)
3034
assert not remove_entry_success
35+
36+
def test_validated_delete_table_invalid_delete(self):
37+
mock_generic_updater = mock.Mock()
38+
mock_generic_updater.apply_patch = mock.Mock(side_effect=ValueError)
39+
with mock.patch('validated_config_db_connector.GenericUpdater', return_value=mock_generic_updater):
40+
delete_table_success = validated_config_db_connector.ValidatedConfigDBConnector.validated_delete_table(mock.Mock(), SAMPLE_TABLE)
41+
assert not delete_table_success
42+
43+
def test_create_gcu_patch(self):
44+
expected_gcu_patch = jsonpatch.JsonPatch([{"op": "add", "path": "/PORTCHANNEL/PortChannel01", "value": "test"}])
45+
created_gcu_patch = validated_config_db_connector.ValidatedConfigDBConnector.create_gcu_patch(ValidatedConfigDBConnector(ConfigDBConnector()), "add", "PORTCHANNEL", "PortChannel01", "test")
46+
assert expected_gcu_patch == created_gcu_patch

0 commit comments

Comments
 (0)