diff --git a/src/sonic-config-engine/sonic-cfggen b/src/sonic-config-engine/sonic-cfggen index bdb3c804fc73..da6380c95a8a 100755 --- a/src/sonic-config-engine/sonic-cfggen +++ b/src/sonic-config-engine/sonic-cfggen @@ -78,6 +78,87 @@ def unique_name(l): new_list.append(item) return new_list +def valid_config(data): + """Verifies that proposed config-state (data) is semantically + correct/consistent. + + Args: + data: config-data in dictionary form + + Returns: + 'true' if valid configuration, 'false' otherwise + """ + + # Verifies that there is a valid entry in PORT table for every entry + # present in INTERFACE table. See that there's no need to verify the + # opposite direction as it's perfectly valid to have PORT entries with + # no matching entry in INTERFACE table. + # + # "INTERFACE": { + # "Ethernet112|10.1.2.2/24": {}, + # "Ethernet112|fc00:1:2::2/64": {}, + # } + # + if 'INTERFACE' in data and 'PORT' in data: + interface_table = data['INTERFACE'] + + for key in interface_table: + if key[0] not in data['PORT']: + print("Interface", key[0], "not found in PORT table.") + return False + + # Verifies that VLAN_INTERFACE entries are relying on existing VLAN entities. + # + # "VLAN_INTERFACE": { + # "Vlan100|172.18.1.1/24": { + # "scope": "global", + # } + # } + if 'VLAN_INTERFACE' in data and 'VLAN' in data: + vlan_interface_table = data['VLAN_INTERFACE'] + + for key in vlan_interface_table: + if key[0] not in data['VLAN']: + print("Vlan interface", key[0], "not found in VLAN table.") + return False + + # Verifies that VLAN table holds members that are present in PORT table. + # + # "VLAN": { + # "Vlan100": { + # ... + # "members": [ + # "Ethernet16", + # "Ethernet17" + # } + # } + if 'VLAN' in data and 'PORT' in data: + vlan_table = data['VLAN'] + + for key, value in vlan_table.iteritems(): + for member in value['members']: + if member not in data['PORT']: + print("VLAN member", member, "not found in PORT table.") + return False + + # Verifies that each DEVICE_NEIGHBOR key matches a valid entry in PORT table. + # + # "DEVICE_NEIGHBOR": { + # "Ethernet112": { + # "name": "lnos-x1-a-csw01", + # "port": "Ethernet1" + # } + # } + if 'DEVICE_NEIGHBOR' in data and 'PORT' in data: + devnbr_table = data['DEVICE_NEIGHBOR'] + + for key in devnbr_table: + if key not in data['PORT']: + print("DEVICE_NEIGHBOR port", key, "not found in PORT table.") + return False + + return True + class FormatConverter: """Convert config DB based schema to legacy minigraph based schema for backward capability. @@ -154,6 +235,7 @@ def main(): group.add_argument("-t", "--template", help="render the data with the template file") group.add_argument("-v", "--var", help="print the value of a variable, support jinja2 expression") group.add_argument("--var-json", help="print the value of a variable, in json format") + group.add_argument("--check-json", help="check passed config -- to be used with -j", action='store_true') group.add_argument("--write-to-db", help="write config into configdb", action='store_true') group.add_argument("--print-data", help="print all data", action='store_true') args = parser.parse_args() @@ -241,6 +323,11 @@ def main(): configdb.connect(False) configdb.mod_config(FormatConverter.output_to_db(data)) + if args.check_json: + if valid_config(FormatConverter.output_to_db(data)) == False: + print("\nInvalid configuration detected. No configuration changes processed. Exiting...\n") + sys.exit(1) + if args.print_data: print(json.dumps(FormatConverter.to_serialized(data), indent=4, cls=minigraph_encoder))