143082e70SLei YU#!/usr/bin/env python3 25cc01280SBob King 35cc01280SBob Kingimport argparse 45cc01280SBob Kingimport json 5b7552f0cSBob Kingimport os 6b7552f0cSBob Kingimport sys 75cc01280SBob King 8*a93243d1SPatrick Williamsimport jsonschema 9*a93243d1SPatrick Williams 105cc01280SBob Kingr""" 115cc01280SBob KingValidates the phosphor-regulators configuration file. Checks it against a JSON 125cc01280SBob Kingschema as well as doing some extra checks that can't be encoded in the schema. 135cc01280SBob King""" 145cc01280SBob King 15*a93243d1SPatrick Williams 1695b796a4SBob Kingdef handle_validation_error(): 1795b796a4SBob King sys.exit("Validation failed.") 1895b796a4SBob King 19*a93243d1SPatrick Williams 2023dd60baSBob Kingdef get_values(json_element, key, result=None): 2123dd60baSBob King r""" 2223dd60baSBob King Finds all occurrences of a key within the specified JSON element and its 2323dd60baSBob King children. Returns the associated values. 2423dd60baSBob King To search the entire configuration file, pass the root JSON element 2523dd60baSBob King json_element: JSON element within the config file. 2623dd60baSBob King key: key name. 2723dd60baSBob King result: list of values found with the specified key. 2823dd60baSBob King """ 2923dd60baSBob King 3023dd60baSBob King if result is None: 3123dd60baSBob King result = [] 3223dd60baSBob King if type(json_element) is dict: 3323dd60baSBob King for json_key in json_element: 3423dd60baSBob King if json_key == key: 3523dd60baSBob King result.append(json_element[json_key]) 3623dd60baSBob King elif type(json_element[json_key]) in (list, dict): 3723dd60baSBob King get_values(json_element[json_key], key, result) 3823dd60baSBob King elif type(json_element) is list: 3923dd60baSBob King for item in json_element: 4023dd60baSBob King if type(item) in (list, dict): 4123dd60baSBob King get_values(item, key, result) 4223dd60baSBob King return result 4323dd60baSBob King 44*a93243d1SPatrick Williams 4523dd60baSBob Kingdef get_rule_ids(config_json): 4623dd60baSBob King r""" 4723dd60baSBob King Get all rule IDs in the configuration file. 4823dd60baSBob King config_json: Configuration file JSON 4923dd60baSBob King """ 5023dd60baSBob King rule_ids = [] 51*a93243d1SPatrick Williams for rule in config_json.get("rules", {}): 52*a93243d1SPatrick Williams rule_ids.append(rule["id"]) 5323dd60baSBob King return rule_ids 5423dd60baSBob King 55*a93243d1SPatrick Williams 56e959754bSBob Kingdef get_device_ids(config_json): 57e959754bSBob King r""" 58e959754bSBob King Get all device IDs in the configuration file. 59e959754bSBob King config_json: Configuration file JSON 60e959754bSBob King """ 61e959754bSBob King device_ids = [] 62*a93243d1SPatrick Williams for chassis in config_json.get("chassis", {}): 63*a93243d1SPatrick Williams for device in chassis.get("devices", {}): 64*a93243d1SPatrick Williams device_ids.append(device["id"]) 65e959754bSBob King return device_ids 66e959754bSBob King 67*a93243d1SPatrick Williams 68a533d700SBob Kingdef check_number_of_elements_in_masks(config_json): 69a533d700SBob King r""" 70a533d700SBob King Check if the number of bit masks in the 'masks' property matches the number 71a533d700SBob King of byte values in the 'values' property. 72a533d700SBob King config_json: Configuration file JSON 73a533d700SBob King """ 74a533d700SBob King 75*a93243d1SPatrick Williams i2c_write_bytes = get_values(config_json, "i2c_write_bytes") 76*a93243d1SPatrick Williams i2c_compare_bytes = get_values(config_json, "i2c_compare_bytes") 77a533d700SBob King 78a533d700SBob King for object in i2c_write_bytes: 79*a93243d1SPatrick Williams if "masks" in object: 80*a93243d1SPatrick Williams if len(object.get("masks", [])) != len(object.get("values", [])): 81*a93243d1SPatrick Williams sys.stderr.write( 82*a93243d1SPatrick Williams "Error: Invalid i2c_write_bytes action.\n" 83*a93243d1SPatrick Williams + "The masks array must have the same size as the values" 84*a93243d1SPatrick Williams + " array. masks: " 85*a93243d1SPatrick Williams + str(object.get("masks", [])) 86*a93243d1SPatrick Williams + ", values: " 87*a93243d1SPatrick Williams + str(object.get("values", [])) 88*a93243d1SPatrick Williams + ".\n" 89*a93243d1SPatrick Williams ) 90a533d700SBob King handle_validation_error() 91a533d700SBob King 92a533d700SBob King for object in i2c_compare_bytes: 93*a93243d1SPatrick Williams if "masks" in object: 94*a93243d1SPatrick Williams if len(object.get("masks", [])) != len(object.get("values", [])): 95*a93243d1SPatrick Williams sys.stderr.write( 96*a93243d1SPatrick Williams "Error: Invalid i2c_compare_bytes action.\n" 97*a93243d1SPatrick Williams + "The masks array must have the same size as the values " 98*a93243d1SPatrick Williams + "array. masks: " 99*a93243d1SPatrick Williams + str(object.get("masks", [])) 100*a93243d1SPatrick Williams + ", values: " 101*a93243d1SPatrick Williams + str(object.get("values", [])) 102*a93243d1SPatrick Williams + ".\n" 103*a93243d1SPatrick Williams ) 104a533d700SBob King handle_validation_error() 105a533d700SBob King 106*a93243d1SPatrick Williams 1079146df2bSBob Kingdef check_rule_id_exists(config_json): 1089146df2bSBob King r""" 1099146df2bSBob King Check if a rule_id property specifies a rule ID that does not exist. 1109146df2bSBob King config_json: Configuration file JSON 1119146df2bSBob King """ 1129146df2bSBob King 113*a93243d1SPatrick Williams rule_ids = get_values(config_json, "rule_id") 1149146df2bSBob King valid_rule_ids = get_rule_ids(config_json) 1159146df2bSBob King for rule_id in rule_ids: 1169146df2bSBob King if rule_id not in valid_rule_ids: 117*a93243d1SPatrick Williams sys.stderr.write( 118*a93243d1SPatrick Williams "Error: Rule ID does not exist.\n" 119*a93243d1SPatrick Williams + "Found rule_id value that specifies invalid rule ID " 120*a93243d1SPatrick Williams + rule_id 121*a93243d1SPatrick Williams + "\n" 122*a93243d1SPatrick Williams ) 1239146df2bSBob King handle_validation_error() 1249146df2bSBob King 125*a93243d1SPatrick Williams 1265d4a9c78SShawn McCarneydef check_device_id_exists(config_json): 1275d4a9c78SShawn McCarney r""" 1285d4a9c78SShawn McCarney Check if a device_id property specifies a device ID that does not exist. 1295d4a9c78SShawn McCarney config_json: Configuration file JSON 1305d4a9c78SShawn McCarney """ 1315d4a9c78SShawn McCarney 132*a93243d1SPatrick Williams device_ids = get_values(config_json, "device_id") 1335d4a9c78SShawn McCarney valid_device_ids = get_device_ids(config_json) 1345d4a9c78SShawn McCarney for device_id in device_ids: 1355d4a9c78SShawn McCarney if device_id not in valid_device_ids: 136*a93243d1SPatrick Williams sys.stderr.write( 137*a93243d1SPatrick Williams "Error: Device ID does not exist.\n" 138*a93243d1SPatrick Williams + "Found device_id value that specifies invalid device ID " 139*a93243d1SPatrick Williams + device_id 140*a93243d1SPatrick Williams + "\n" 141*a93243d1SPatrick Williams ) 1425d4a9c78SShawn McCarney handle_validation_error() 1435d4a9c78SShawn McCarney 144*a93243d1SPatrick Williams 145e959754bSBob Kingdef check_set_device_value_exists(config_json): 146e959754bSBob King r""" 147e959754bSBob King Check if a set_device action specifies a device ID that does not exist. 148e959754bSBob King config_json: Configuration file JSON 149e959754bSBob King """ 150e959754bSBob King 151*a93243d1SPatrick Williams device_ids = get_values(config_json, "set_device") 152e959754bSBob King valid_device_ids = get_device_ids(config_json) 153e959754bSBob King for device_id in device_ids: 154e959754bSBob King if device_id not in valid_device_ids: 155*a93243d1SPatrick Williams sys.stderr.write( 156*a93243d1SPatrick Williams "Error: Device ID does not exist.\n" 157*a93243d1SPatrick Williams + "Found set_device action that specifies invalid device ID " 158*a93243d1SPatrick Williams + device_id 159*a93243d1SPatrick Williams + "\n" 160*a93243d1SPatrick Williams ) 161e959754bSBob King handle_validation_error() 162e959754bSBob King 163*a93243d1SPatrick Williams 16423dd60baSBob Kingdef check_run_rule_value_exists(config_json): 16523dd60baSBob King r""" 16623dd60baSBob King Check if any run_rule actions specify a rule ID that does not exist. 16723dd60baSBob King config_json: Configuration file JSON 16823dd60baSBob King """ 16923dd60baSBob King 170*a93243d1SPatrick Williams rule_ids = get_values(config_json, "run_rule") 17123dd60baSBob King valid_rule_ids = get_rule_ids(config_json) 17223dd60baSBob King for rule_id in rule_ids: 17323dd60baSBob King if rule_id not in valid_rule_ids: 174*a93243d1SPatrick Williams sys.stderr.write( 175*a93243d1SPatrick Williams "Error: Rule ID does not exist.\n" 176*a93243d1SPatrick Williams + "Found run_rule action that specifies invalid rule ID " 177*a93243d1SPatrick Williams + rule_id 178*a93243d1SPatrick Williams + "\n" 179*a93243d1SPatrick Williams ) 18023dd60baSBob King handle_validation_error() 18123dd60baSBob King 182*a93243d1SPatrick Williams 183d114cd94SBob Kingdef check_infinite_loops_in_rule(config_json, rule_json, call_stack=[]): 184d114cd94SBob King r""" 185d114cd94SBob King Check if a 'run_rule' action in the specified rule causes an 186d114cd94SBob King infinite loop. 187d114cd94SBob King config_json: Configuration file JSON. 188d114cd94SBob King rule_json: A rule in the JSON config file. 189d114cd94SBob King call_stack: Current call stack of rules. 190d114cd94SBob King """ 191d114cd94SBob King 192*a93243d1SPatrick Williams call_stack.append(rule_json["id"]) 193*a93243d1SPatrick Williams for action in rule_json.get("actions", {}): 194*a93243d1SPatrick Williams if "run_rule" in action: 195*a93243d1SPatrick Williams run_rule_id = action["run_rule"] 196d114cd94SBob King if run_rule_id in call_stack: 197d114cd94SBob King call_stack.append(run_rule_id) 198*a93243d1SPatrick Williams sys.stderr.write( 199*a93243d1SPatrick Williams "Infinite loop caused by run_rule actions.\n" 200*a93243d1SPatrick Williams + str(call_stack) 201*a93243d1SPatrick Williams + "\n" 202*a93243d1SPatrick Williams ) 203d114cd94SBob King handle_validation_error() 204d114cd94SBob King else: 205*a93243d1SPatrick Williams for rule in config_json.get("rules", {}): 206*a93243d1SPatrick Williams if rule["id"] == run_rule_id: 207*a93243d1SPatrick Williams check_infinite_loops_in_rule( 208*a93243d1SPatrick Williams config_json, rule, call_stack 209*a93243d1SPatrick Williams ) 210d114cd94SBob King call_stack.pop() 211d114cd94SBob King 212*a93243d1SPatrick Williams 213d114cd94SBob Kingdef check_infinite_loops(config_json): 214d114cd94SBob King r""" 215d114cd94SBob King Check if rule in config file is called recursively, causing an 216d114cd94SBob King infinite loop. 217d114cd94SBob King config_json: Configuration file JSON 218d114cd94SBob King """ 219d114cd94SBob King 220*a93243d1SPatrick Williams for rule in config_json.get("rules", {}): 221d114cd94SBob King check_infinite_loops_in_rule(config_json, rule) 222d114cd94SBob King 223*a93243d1SPatrick Williams 2245b27a95bSBob Kingdef check_duplicate_object_id(config_json): 2255b27a95bSBob King r""" 2265b27a95bSBob King Check that there aren't any JSON objects with the same 'id' property value. 2275b27a95bSBob King config_json: Configuration file JSON 2285b27a95bSBob King """ 2295b27a95bSBob King 230*a93243d1SPatrick Williams json_ids = get_values(config_json, "id") 2315b27a95bSBob King unique_ids = set() 2325b27a95bSBob King for id in json_ids: 2335b27a95bSBob King if id in unique_ids: 234*a93243d1SPatrick Williams sys.stderr.write( 235*a93243d1SPatrick Williams "Error: Duplicate ID.\n" 236*a93243d1SPatrick Williams + "Found multiple objects with the ID " 237*a93243d1SPatrick Williams + id 238*a93243d1SPatrick Williams + "\n" 239*a93243d1SPatrick Williams ) 2405b27a95bSBob King handle_validation_error() 2415b27a95bSBob King else: 2425b27a95bSBob King unique_ids.add(id) 2435b27a95bSBob King 244*a93243d1SPatrick Williams 24595b796a4SBob Kingdef check_duplicate_rule_id(config_json): 24695b796a4SBob King r""" 24795b796a4SBob King Check that there aren't any "rule" elements with the same 'id' field. 24895b796a4SBob King config_json: Configuration file JSON 24995b796a4SBob King """ 25095b796a4SBob King rule_ids = [] 251*a93243d1SPatrick Williams for rule in config_json.get("rules", {}): 252*a93243d1SPatrick Williams rule_id = rule["id"] 25395b796a4SBob King if rule_id in rule_ids: 254*a93243d1SPatrick Williams sys.stderr.write( 255*a93243d1SPatrick Williams "Error: Duplicate rule ID.\n" 256*a93243d1SPatrick Williams + "Found multiple rules with the ID " 257*a93243d1SPatrick Williams + rule_id 258*a93243d1SPatrick Williams + "\n" 259*a93243d1SPatrick Williams ) 26095b796a4SBob King handle_validation_error() 26195b796a4SBob King else: 26295b796a4SBob King rule_ids.append(rule_id) 26395b796a4SBob King 264*a93243d1SPatrick Williams 26595b796a4SBob Kingdef check_duplicate_chassis_number(config_json): 26695b796a4SBob King r""" 267*a93243d1SPatrick Williams Check that there aren't any "chassis" elements with the same 'number' 268*a93243d1SPatrick Williams field. 26995b796a4SBob King config_json: Configuration file JSON 27095b796a4SBob King """ 27195b796a4SBob King numbers = [] 272*a93243d1SPatrick Williams for chassis in config_json.get("chassis", {}): 273*a93243d1SPatrick Williams number = chassis["number"] 27495b796a4SBob King if number in numbers: 275*a93243d1SPatrick Williams sys.stderr.write( 276*a93243d1SPatrick Williams "Error: Duplicate chassis number.\n" 277*a93243d1SPatrick Williams + "Found multiple chassis with the number " 278*a93243d1SPatrick Williams + str(number) 279*a93243d1SPatrick Williams + "\n" 280*a93243d1SPatrick Williams ) 28195b796a4SBob King handle_validation_error() 28295b796a4SBob King else: 28395b796a4SBob King numbers.append(number) 28495b796a4SBob King 285*a93243d1SPatrick Williams 28695b796a4SBob Kingdef check_duplicate_device_id(config_json): 28795b796a4SBob King r""" 28895b796a4SBob King Check that there aren't any "devices" with the same 'id' field. 28995b796a4SBob King config_json: Configuration file JSON 29095b796a4SBob King """ 29195b796a4SBob King device_ids = [] 292*a93243d1SPatrick Williams for chassis in config_json.get("chassis", {}): 293*a93243d1SPatrick Williams for device in chassis.get("devices", {}): 294*a93243d1SPatrick Williams device_id = device["id"] 29595b796a4SBob King if device_id in device_ids: 296*a93243d1SPatrick Williams sys.stderr.write( 297*a93243d1SPatrick Williams "Error: Duplicate device ID.\n" 298*a93243d1SPatrick Williams + "Found multiple devices with the ID " 299*a93243d1SPatrick Williams + device_id 300*a93243d1SPatrick Williams + "\n" 301*a93243d1SPatrick Williams ) 30295b796a4SBob King handle_validation_error() 30395b796a4SBob King else: 30495b796a4SBob King device_ids.append(device_id) 30595b796a4SBob King 306*a93243d1SPatrick Williams 30795b796a4SBob Kingdef check_duplicate_rail_id(config_json): 30895b796a4SBob King r""" 30995b796a4SBob King Check that there aren't any "rails" with the same 'id' field. 31095b796a4SBob King config_json: Configuration file JSON 31195b796a4SBob King """ 31295b796a4SBob King rail_ids = [] 313*a93243d1SPatrick Williams for chassis in config_json.get("chassis", {}): 314*a93243d1SPatrick Williams for device in chassis.get("devices", {}): 315*a93243d1SPatrick Williams for rail in device.get("rails", {}): 316*a93243d1SPatrick Williams rail_id = rail["id"] 31795b796a4SBob King if rail_id in rail_ids: 318*a93243d1SPatrick Williams sys.stderr.write( 319*a93243d1SPatrick Williams "Error: Duplicate rail ID.\n" 320*a93243d1SPatrick Williams + "Found multiple rails with the ID " 321*a93243d1SPatrick Williams + rail_id 322*a93243d1SPatrick Williams + "\n" 323*a93243d1SPatrick Williams ) 32495b796a4SBob King handle_validation_error() 32595b796a4SBob King else: 32695b796a4SBob King rail_ids.append(rail_id) 32795b796a4SBob King 328*a93243d1SPatrick Williams 32995b796a4SBob Kingdef check_for_duplicates(config_json): 33095b796a4SBob King r""" 33195b796a4SBob King Check for duplicate ID. 33295b796a4SBob King """ 33395b796a4SBob King check_duplicate_rule_id(config_json) 33495b796a4SBob King 33595b796a4SBob King check_duplicate_chassis_number(config_json) 33695b796a4SBob King 33795b796a4SBob King check_duplicate_device_id(config_json) 33895b796a4SBob King 33995b796a4SBob King check_duplicate_rail_id(config_json) 34095b796a4SBob King 3415b27a95bSBob King check_duplicate_object_id(config_json) 3425b27a95bSBob King 343*a93243d1SPatrick Williams 3445cc01280SBob Kingdef validate_schema(config, schema): 3455cc01280SBob King r""" 3465cc01280SBob King Validates the specified config file using the specified 3475cc01280SBob King schema file. 3485cc01280SBob King 3495cc01280SBob King config: Path of the file containing the config JSON 3505cc01280SBob King schema: Path of the file containing the schema JSON 3515cc01280SBob King """ 3525cc01280SBob King 3535cc01280SBob King with open(config) as config_handle: 3545cc01280SBob King config_json = json.load(config_handle) 3555cc01280SBob King 3565cc01280SBob King with open(schema) as schema_handle: 3575cc01280SBob King schema_json = json.load(schema_handle) 3585cc01280SBob King 3595cc01280SBob King try: 3605cc01280SBob King jsonschema.validate(config_json, schema_json) 3615cc01280SBob King except jsonschema.ValidationError as e: 3625cc01280SBob King print(e) 36395b796a4SBob King handle_validation_error() 36495b796a4SBob King 36595b796a4SBob King return config_json 3665cc01280SBob King 367*a93243d1SPatrick Williams 368b7552f0cSBob Kingdef validate_JSON_format(file): 369b7552f0cSBob King with open(file) as json_data: 370b7552f0cSBob King try: 371b7552f0cSBob King return json.load(json_data) 372*a93243d1SPatrick Williams except ValueError: 373b7552f0cSBob King return False 374b7552f0cSBob King return True 375b7552f0cSBob King 3765cc01280SBob King 377*a93243d1SPatrick Williamsif __name__ == "__main__": 3785cc01280SBob King parser = argparse.ArgumentParser( 379*a93243d1SPatrick Williams description="phosphor-regulators configuration file validator" 380*a93243d1SPatrick Williams ) 3815cc01280SBob King 382*a93243d1SPatrick Williams parser.add_argument( 383*a93243d1SPatrick Williams "-s", 384*a93243d1SPatrick Williams "--schema-file", 385*a93243d1SPatrick Williams dest="schema_file", 386*a93243d1SPatrick Williams help="The phosphor-regulators schema file", 387*a93243d1SPatrick Williams ) 3885cc01280SBob King 389*a93243d1SPatrick Williams parser.add_argument( 390*a93243d1SPatrick Williams "-c", 391*a93243d1SPatrick Williams "--configuration-file", 392*a93243d1SPatrick Williams dest="configuration_file", 393*a93243d1SPatrick Williams help="The phosphor-regulators configuration file", 394*a93243d1SPatrick Williams ) 3955cc01280SBob King 3965cc01280SBob King args = parser.parse_args() 3975cc01280SBob King 3985cc01280SBob King if not args.schema_file: 3995cc01280SBob King parser.print_help() 4005cc01280SBob King sys.exit("Error: Schema file is required.") 401b7552f0cSBob King if not os.path.exists(args.schema_file): 402b7552f0cSBob King parser.print_help() 403b7552f0cSBob King sys.exit("Error: Schema file does not exist.") 404b7552f0cSBob King if not os.access(args.schema_file, os.R_OK): 405b7552f0cSBob King parser.print_help() 406b7552f0cSBob King sys.exit("Error: Schema file is not readable.") 407b7552f0cSBob King if not validate_JSON_format(args.schema_file): 408b7552f0cSBob King parser.print_help() 409b7552f0cSBob King sys.exit("Error: Schema file is not in the JSON format.") 4105cc01280SBob King if not args.configuration_file: 4115cc01280SBob King parser.print_help() 4125cc01280SBob King sys.exit("Error: Configuration file is required.") 413b7552f0cSBob King if not os.path.exists(args.configuration_file): 414b7552f0cSBob King parser.print_help() 415b7552f0cSBob King sys.exit("Error: Configuration file does not exist.") 416b7552f0cSBob King if not os.access(args.configuration_file, os.R_OK): 417b7552f0cSBob King parser.print_help() 418b7552f0cSBob King sys.exit("Error: Configuration file is not readable.") 419b7552f0cSBob King if not validate_JSON_format(args.configuration_file): 420b7552f0cSBob King parser.print_help() 421b7552f0cSBob King sys.exit("Error: Configuration file is not in the JSON format.") 4225cc01280SBob King 42395b796a4SBob King config_json = validate_schema(args.configuration_file, args.schema_file) 424d114cd94SBob King 42595b796a4SBob King check_for_duplicates(config_json) 426d114cd94SBob King 427d114cd94SBob King check_infinite_loops(config_json) 428d114cd94SBob King 42923dd60baSBob King check_run_rule_value_exists(config_json) 430e959754bSBob King 431e959754bSBob King check_set_device_value_exists(config_json) 4329146df2bSBob King 4339146df2bSBob King check_rule_id_exists(config_json) 434a533d700SBob King 4355d4a9c78SShawn McCarney check_device_id_exists(config_json) 4365d4a9c78SShawn McCarney 437a533d700SBob King check_number_of_elements_in_masks(config_json) 438