1#!/usr/bin/env python 2 3import argparse 4import json 5import sys 6import jsonschema 7 8r""" 9Validates the phosphor-regulators configuration file. Checks it against a JSON 10schema as well as doing some extra checks that can't be encoded in the schema. 11""" 12 13def handle_validation_error(): 14 sys.exit("Validation failed.") 15 16def get_values(json_element, key, result = None): 17 r""" 18 Finds all occurrences of a key within the specified JSON element and its 19 children. Returns the associated values. 20 To search the entire configuration file, pass the root JSON element 21 json_element: JSON element within the config file. 22 key: key name. 23 result: list of values found with the specified key. 24 """ 25 26 if result is None: 27 result = [] 28 if type(json_element) is dict: 29 for json_key in json_element: 30 if json_key == key: 31 result.append(json_element[json_key]) 32 elif type(json_element[json_key]) in (list, dict): 33 get_values(json_element[json_key], key, result) 34 elif type(json_element) is list: 35 for item in json_element: 36 if type(item) in (list, dict): 37 get_values(item, key, result) 38 return result 39 40def get_rule_ids(config_json): 41 r""" 42 Get all rule IDs in the configuration file. 43 config_json: Configuration file JSON 44 """ 45 rule_ids = [] 46 for rule in config_json.get('rules', {}): 47 rule_ids.append(rule['id']) 48 return rule_ids 49 50def get_device_ids(config_json): 51 r""" 52 Get all device IDs in the configuration file. 53 config_json: Configuration file JSON 54 """ 55 device_ids = [] 56 for chassis in config_json.get('chassis', {}): 57 for device in chassis.get('devices', {}): 58 device_ids.append(device['id']) 59 return device_ids 60 61def check_set_device_value_exists(config_json): 62 r""" 63 Check if a set_device action specifies a device ID that does not exist. 64 config_json: Configuration file JSON 65 """ 66 67 device_ids = get_values(config_json, 'set_device') 68 valid_device_ids = get_device_ids(config_json) 69 for device_id in device_ids: 70 if device_id not in valid_device_ids: 71 sys.stderr.write("Error: Device ID does not exist.\n"+\ 72 "Found set_device action that specifies invalid device ID "+\ 73 device_id+'\n') 74 handle_validation_error() 75 76def check_run_rule_value_exists(config_json): 77 r""" 78 Check if any run_rule actions specify a rule ID that does not exist. 79 config_json: Configuration file JSON 80 """ 81 82 rule_ids = get_values(config_json, 'run_rule') 83 valid_rule_ids = get_rule_ids(config_json) 84 for rule_id in rule_ids: 85 if rule_id not in valid_rule_ids: 86 sys.stderr.write("Error: Rule ID does not exist.\n"+\ 87 "Found run_rule action that specifies invalid rule ID "+\ 88 rule_id+'\n') 89 handle_validation_error() 90 91def check_infinite_loops_in_rule(config_json, rule_json, call_stack=[]): 92 r""" 93 Check if a 'run_rule' action in the specified rule causes an 94 infinite loop. 95 config_json: Configuration file JSON. 96 rule_json: A rule in the JSON config file. 97 call_stack: Current call stack of rules. 98 """ 99 100 call_stack.append(rule_json['id']) 101 for action in rule_json.get('actions', {}): 102 if 'run_rule' in action: 103 run_rule_id = action['run_rule'] 104 if run_rule_id in call_stack: 105 call_stack.append(run_rule_id) 106 sys.stderr.write(\ 107 "Infinite loop caused by run_rule actions.\n"+\ 108 str(call_stack)+'\n') 109 handle_validation_error() 110 else: 111 for rule in config_json.get('rules', {}): 112 if rule['id'] == run_rule_id: 113 check_infinite_loops_in_rule(\ 114 config_json, rule, call_stack) 115 call_stack.pop() 116 117def check_infinite_loops(config_json): 118 r""" 119 Check if rule in config file is called recursively, causing an 120 infinite loop. 121 config_json: Configuration file JSON 122 """ 123 124 for rule in config_json.get('rules', {}): 125 check_infinite_loops_in_rule(config_json, rule) 126 127def check_duplicate_object_id(config_json): 128 r""" 129 Check that there aren't any JSON objects with the same 'id' property value. 130 config_json: Configuration file JSON 131 """ 132 133 json_ids = get_values(config_json, 'id') 134 unique_ids = set() 135 for id in json_ids: 136 if id in unique_ids: 137 sys.stderr.write("Error: Duplicate ID.\n"+\ 138 "Found multiple objects with the ID "+id+'\n') 139 handle_validation_error() 140 else: 141 unique_ids.add(id) 142 143def check_duplicate_rule_id(config_json): 144 r""" 145 Check that there aren't any "rule" elements with the same 'id' field. 146 config_json: Configuration file JSON 147 """ 148 rule_ids = [] 149 for rule in config_json.get('rules', {}): 150 rule_id = rule['id'] 151 if rule_id in rule_ids: 152 sys.stderr.write("Error: Duplicate rule ID.\n"+\ 153 "Found multiple rules with the ID "+rule_id+'\n') 154 handle_validation_error() 155 else: 156 rule_ids.append(rule_id) 157 158def check_duplicate_chassis_number(config_json): 159 r""" 160 Check that there aren't any "chassis" elements with the same 'number' field. 161 config_json: Configuration file JSON 162 """ 163 numbers = [] 164 for chassis in config_json.get('chassis', {}): 165 number = chassis['number'] 166 if number in numbers: 167 sys.stderr.write("Error: Duplicate chassis number.\n"+\ 168 "Found multiple chassis with the number "+str(number)+'\n') 169 handle_validation_error() 170 else: 171 numbers.append(number) 172 173def check_duplicate_device_id(config_json): 174 r""" 175 Check that there aren't any "devices" with the same 'id' field. 176 config_json: Configuration file JSON 177 """ 178 device_ids = [] 179 for chassis in config_json.get('chassis', {}): 180 for device in chassis.get('devices', {}): 181 device_id = device['id'] 182 if device_id in device_ids: 183 sys.stderr.write("Error: Duplicate device ID.\n"+\ 184 "Found multiple devices with the ID "+device_id+'\n') 185 handle_validation_error() 186 else: 187 device_ids.append(device_id) 188 189def check_duplicate_rail_id(config_json): 190 r""" 191 Check that there aren't any "rails" with the same 'id' field. 192 config_json: Configuration file JSON 193 """ 194 rail_ids = [] 195 for chassis in config_json.get('chassis', {}): 196 for device in chassis.get('devices', {}): 197 for rail in device.get('rails', {}): 198 rail_id = rail['id'] 199 if rail_id in rail_ids: 200 sys.stderr.write("Error: Duplicate rail ID.\n"+\ 201 "Found multiple rails with the ID "+rail_id+'\n') 202 handle_validation_error() 203 else: 204 rail_ids.append(rail_id) 205 206def check_for_duplicates(config_json): 207 r""" 208 Check for duplicate ID. 209 """ 210 check_duplicate_rule_id(config_json) 211 212 check_duplicate_chassis_number(config_json) 213 214 check_duplicate_device_id(config_json) 215 216 check_duplicate_rail_id(config_json) 217 218 check_duplicate_object_id(config_json) 219 220def validate_schema(config, schema): 221 r""" 222 Validates the specified config file using the specified 223 schema file. 224 225 config: Path of the file containing the config JSON 226 schema: Path of the file containing the schema JSON 227 """ 228 229 with open(config) as config_handle: 230 config_json = json.load(config_handle) 231 232 with open(schema) as schema_handle: 233 schema_json = json.load(schema_handle) 234 235 try: 236 jsonschema.validate(config_json, schema_json) 237 except jsonschema.ValidationError as e: 238 print(e) 239 handle_validation_error() 240 241 return config_json 242 243if __name__ == '__main__': 244 245 parser = argparse.ArgumentParser( 246 description='phosphor-regulators configuration file validator') 247 248 parser.add_argument('-s', '--schema-file', dest='schema_file', 249 help='The phosphor-regulators schema file') 250 251 parser.add_argument('-c', '--configuration-file', dest='configuration_file', 252 help='The phosphor-regulators configuration file') 253 254 args = parser.parse_args() 255 256 if not args.schema_file: 257 parser.print_help() 258 sys.exit("Error: Schema file is required.") 259 if not args.configuration_file: 260 parser.print_help() 261 sys.exit("Error: Configuration file is required.") 262 263 config_json = validate_schema(args.configuration_file, args.schema_file) 264 265 check_for_duplicates(config_json) 266 267 check_infinite_loops(config_json) 268 269 check_run_rule_value_exists(config_json) 270 271 check_set_device_value_exists(config_json) 272