blob: f70d9b48a9595eaeded188e32ec69facb65e394d [file] [log] [blame]
Lei YU43082e72020-03-09 10:32:48 +08001#!/usr/bin/env python3
Bob King5cc01282019-12-17 18:11:57 +08002
3import argparse
4import json
Bob King5cc01282019-12-17 18:11:57 +08005import jsonschema
Bob Kingb7552f02020-10-15 14:34:17 +08006import os
7import sys
Bob King5cc01282019-12-17 18:11:57 +08008
9r"""
10Validates the phosphor-regulators configuration file. Checks it against a JSON
11schema as well as doing some extra checks that can't be encoded in the schema.
12"""
13
Bob King95b796a2020-01-15 14:45:06 +080014def handle_validation_error():
15 sys.exit("Validation failed.")
16
Bob King23dd60b2020-02-26 10:23:21 +080017def get_values(json_element, key, result = None):
18 r"""
19 Finds all occurrences of a key within the specified JSON element and its
20 children. Returns the associated values.
21 To search the entire configuration file, pass the root JSON element
22 json_element: JSON element within the config file.
23 key: key name.
24 result: list of values found with the specified key.
25 """
26
27 if result is None:
28 result = []
29 if type(json_element) is dict:
30 for json_key in json_element:
31 if json_key == key:
32 result.append(json_element[json_key])
33 elif type(json_element[json_key]) in (list, dict):
34 get_values(json_element[json_key], key, result)
35 elif type(json_element) is list:
36 for item in json_element:
37 if type(item) in (list, dict):
38 get_values(item, key, result)
39 return result
40
41def get_rule_ids(config_json):
42 r"""
43 Get all rule IDs in the configuration file.
44 config_json: Configuration file JSON
45 """
46 rule_ids = []
47 for rule in config_json.get('rules', {}):
48 rule_ids.append(rule['id'])
49 return rule_ids
50
Bob Kinge9597542020-02-26 10:47:40 +080051def get_device_ids(config_json):
52 r"""
53 Get all device IDs in the configuration file.
54 config_json: Configuration file JSON
55 """
56 device_ids = []
57 for chassis in config_json.get('chassis', {}):
58 for device in chassis.get('devices', {}):
59 device_ids.append(device['id'])
60 return device_ids
61
Bob Kinga533d702020-02-26 10:51:24 +080062def check_number_of_elements_in_masks(config_json):
63 r"""
64 Check if the number of bit masks in the 'masks' property matches the number
65 of byte values in the 'values' property.
66 config_json: Configuration file JSON
67 """
68
69 i2c_write_bytes = get_values(config_json, 'i2c_write_bytes')
70 i2c_compare_bytes = get_values(config_json, 'i2c_compare_bytes')
71
72 for object in i2c_write_bytes:
73 if 'masks' in object:
74 if len(object.get('masks', [])) != len(object.get('values', [])):
75 sys.stderr.write("Error: Invalid i2c_write_bytes action.\n"+\
76 "The masks array must have the same size as the values array. "+\
77 "masks: "+str(object.get('masks', []))+\
78 ", values: "+str(object.get('values', []))+'.\n')
79 handle_validation_error()
80
81 for object in i2c_compare_bytes:
82 if 'masks' in object:
83 if len(object.get('masks', [])) != len(object.get('values', [])):
84 sys.stderr.write("Error: Invalid i2c_compare_bytes action.\n"+\
85 "The masks array must have the same size as the values array. "+\
86 "masks: "+str(object.get('masks', []))+\
87 ", values: "+str(object.get('values', []))+'.\n')
88 handle_validation_error()
89
Bob King9146df22020-02-26 10:49:33 +080090def check_rule_id_exists(config_json):
91 r"""
92 Check if a rule_id property specifies a rule ID that does not exist.
93 config_json: Configuration file JSON
94 """
95
96 rule_ids = get_values(config_json, 'rule_id')
97 valid_rule_ids = get_rule_ids(config_json)
98 for rule_id in rule_ids:
99 if rule_id not in valid_rule_ids:
100 sys.stderr.write("Error: Rule ID does not exist.\n"+\
101 "Found rule_id value that specifies invalid rule ID "+\
102 rule_id+'\n')
103 handle_validation_error()
104
Shawn McCarney5d4a9c72021-08-19 18:45:59 -0500105def check_device_id_exists(config_json):
106 r"""
107 Check if a device_id property specifies a device ID that does not exist.
108 config_json: Configuration file JSON
109 """
110
111 device_ids = get_values(config_json, 'device_id')
112 valid_device_ids = get_device_ids(config_json)
113 for device_id in device_ids:
114 if device_id not in valid_device_ids:
115 sys.stderr.write("Error: Device ID does not exist.\n"+\
116 "Found device_id value that specifies invalid device ID "+\
117 device_id+'\n')
118 handle_validation_error()
119
Bob Kinge9597542020-02-26 10:47:40 +0800120def check_set_device_value_exists(config_json):
121 r"""
122 Check if a set_device action specifies a device ID that does not exist.
123 config_json: Configuration file JSON
124 """
125
126 device_ids = get_values(config_json, 'set_device')
127 valid_device_ids = get_device_ids(config_json)
128 for device_id in device_ids:
129 if device_id not in valid_device_ids:
130 sys.stderr.write("Error: Device ID does not exist.\n"+\
131 "Found set_device action that specifies invalid device ID "+\
132 device_id+'\n')
133 handle_validation_error()
134
Bob King23dd60b2020-02-26 10:23:21 +0800135def check_run_rule_value_exists(config_json):
136 r"""
137 Check if any run_rule actions specify a rule ID that does not exist.
138 config_json: Configuration file JSON
139 """
140
141 rule_ids = get_values(config_json, 'run_rule')
142 valid_rule_ids = get_rule_ids(config_json)
143 for rule_id in rule_ids:
144 if rule_id not in valid_rule_ids:
145 sys.stderr.write("Error: Rule ID does not exist.\n"+\
146 "Found run_rule action that specifies invalid rule ID "+\
147 rule_id+'\n')
148 handle_validation_error()
149
Bob Kingd114cd92020-02-10 13:56:02 +0800150def check_infinite_loops_in_rule(config_json, rule_json, call_stack=[]):
151 r"""
152 Check if a 'run_rule' action in the specified rule causes an
153 infinite loop.
154 config_json: Configuration file JSON.
155 rule_json: A rule in the JSON config file.
156 call_stack: Current call stack of rules.
157 """
158
159 call_stack.append(rule_json['id'])
160 for action in rule_json.get('actions', {}):
161 if 'run_rule' in action:
162 run_rule_id = action['run_rule']
163 if run_rule_id in call_stack:
164 call_stack.append(run_rule_id)
165 sys.stderr.write(\
166 "Infinite loop caused by run_rule actions.\n"+\
167 str(call_stack)+'\n')
168 handle_validation_error()
169 else:
170 for rule in config_json.get('rules', {}):
171 if rule['id'] == run_rule_id:
172 check_infinite_loops_in_rule(\
173 config_json, rule, call_stack)
174 call_stack.pop()
175
176def check_infinite_loops(config_json):
177 r"""
178 Check if rule in config file is called recursively, causing an
179 infinite loop.
180 config_json: Configuration file JSON
181 """
182
183 for rule in config_json.get('rules', {}):
184 check_infinite_loops_in_rule(config_json, rule)
185
Bob King5b27a952020-01-20 18:04:15 +0800186def check_duplicate_object_id(config_json):
187 r"""
188 Check that there aren't any JSON objects with the same 'id' property value.
189 config_json: Configuration file JSON
190 """
191
Bob King23dd60b2020-02-26 10:23:21 +0800192 json_ids = get_values(config_json, 'id')
Bob King5b27a952020-01-20 18:04:15 +0800193 unique_ids = set()
194 for id in json_ids:
195 if id in unique_ids:
196 sys.stderr.write("Error: Duplicate ID.\n"+\
197 "Found multiple objects with the ID "+id+'\n')
198 handle_validation_error()
199 else:
200 unique_ids.add(id)
201
Bob King95b796a2020-01-15 14:45:06 +0800202def check_duplicate_rule_id(config_json):
203 r"""
204 Check that there aren't any "rule" elements with the same 'id' field.
205 config_json: Configuration file JSON
206 """
207 rule_ids = []
208 for rule in config_json.get('rules', {}):
209 rule_id = rule['id']
210 if rule_id in rule_ids:
211 sys.stderr.write("Error: Duplicate rule ID.\n"+\
212 "Found multiple rules with the ID "+rule_id+'\n')
213 handle_validation_error()
214 else:
215 rule_ids.append(rule_id)
216
217def check_duplicate_chassis_number(config_json):
218 r"""
219 Check that there aren't any "chassis" elements with the same 'number' field.
220 config_json: Configuration file JSON
221 """
222 numbers = []
223 for chassis in config_json.get('chassis', {}):
224 number = chassis['number']
225 if number in numbers:
226 sys.stderr.write("Error: Duplicate chassis number.\n"+\
227 "Found multiple chassis with the number "+str(number)+'\n')
228 handle_validation_error()
229 else:
230 numbers.append(number)
231
232def check_duplicate_device_id(config_json):
233 r"""
234 Check that there aren't any "devices" with the same 'id' field.
235 config_json: Configuration file JSON
236 """
237 device_ids = []
238 for chassis in config_json.get('chassis', {}):
239 for device in chassis.get('devices', {}):
240 device_id = device['id']
241 if device_id in device_ids:
242 sys.stderr.write("Error: Duplicate device ID.\n"+\
243 "Found multiple devices with the ID "+device_id+'\n')
244 handle_validation_error()
245 else:
246 device_ids.append(device_id)
247
248def check_duplicate_rail_id(config_json):
249 r"""
250 Check that there aren't any "rails" with the same 'id' field.
251 config_json: Configuration file JSON
252 """
253 rail_ids = []
254 for chassis in config_json.get('chassis', {}):
255 for device in chassis.get('devices', {}):
256 for rail in device.get('rails', {}):
257 rail_id = rail['id']
258 if rail_id in rail_ids:
259 sys.stderr.write("Error: Duplicate rail ID.\n"+\
260 "Found multiple rails with the ID "+rail_id+'\n')
261 handle_validation_error()
262 else:
263 rail_ids.append(rail_id)
264
265def check_for_duplicates(config_json):
266 r"""
267 Check for duplicate ID.
268 """
269 check_duplicate_rule_id(config_json)
270
271 check_duplicate_chassis_number(config_json)
272
273 check_duplicate_device_id(config_json)
274
275 check_duplicate_rail_id(config_json)
276
Bob King5b27a952020-01-20 18:04:15 +0800277 check_duplicate_object_id(config_json)
278
Bob King5cc01282019-12-17 18:11:57 +0800279def validate_schema(config, schema):
280 r"""
281 Validates the specified config file using the specified
282 schema file.
283
284 config: Path of the file containing the config JSON
285 schema: Path of the file containing the schema JSON
286 """
287
288 with open(config) as config_handle:
289 config_json = json.load(config_handle)
290
291 with open(schema) as schema_handle:
292 schema_json = json.load(schema_handle)
293
294 try:
295 jsonschema.validate(config_json, schema_json)
296 except jsonschema.ValidationError as e:
297 print(e)
Bob King95b796a2020-01-15 14:45:06 +0800298 handle_validation_error()
299
300 return config_json
Bob King5cc01282019-12-17 18:11:57 +0800301
Bob Kingb7552f02020-10-15 14:34:17 +0800302def validate_JSON_format(file):
303 with open(file) as json_data:
304 try:
305 return json.load(json_data)
306 except ValueError as err:
307 return False
308 return True
309
Bob King5cc01282019-12-17 18:11:57 +0800310if __name__ == '__main__':
311
312 parser = argparse.ArgumentParser(
313 description='phosphor-regulators configuration file validator')
314
315 parser.add_argument('-s', '--schema-file', dest='schema_file',
316 help='The phosphor-regulators schema file')
317
318 parser.add_argument('-c', '--configuration-file', dest='configuration_file',
319 help='The phosphor-regulators configuration file')
320
321 args = parser.parse_args()
322
323 if not args.schema_file:
324 parser.print_help()
325 sys.exit("Error: Schema file is required.")
Bob Kingb7552f02020-10-15 14:34:17 +0800326 if not os.path.exists(args.schema_file):
327 parser.print_help()
328 sys.exit("Error: Schema file does not exist.")
329 if not os.access(args.schema_file, os.R_OK):
330 parser.print_help()
331 sys.exit("Error: Schema file is not readable.")
332 if not validate_JSON_format(args.schema_file):
333 parser.print_help()
334 sys.exit("Error: Schema file is not in the JSON format.")
Bob King5cc01282019-12-17 18:11:57 +0800335 if not args.configuration_file:
336 parser.print_help()
337 sys.exit("Error: Configuration file is required.")
Bob Kingb7552f02020-10-15 14:34:17 +0800338 if not os.path.exists(args.configuration_file):
339 parser.print_help()
340 sys.exit("Error: Configuration file does not exist.")
341 if not os.access(args.configuration_file, os.R_OK):
342 parser.print_help()
343 sys.exit("Error: Configuration file is not readable.")
344 if not validate_JSON_format(args.configuration_file):
345 parser.print_help()
346 sys.exit("Error: Configuration file is not in the JSON format.")
Bob King5cc01282019-12-17 18:11:57 +0800347
Bob King95b796a2020-01-15 14:45:06 +0800348 config_json = validate_schema(args.configuration_file, args.schema_file)
Bob Kingd114cd92020-02-10 13:56:02 +0800349
Bob King95b796a2020-01-15 14:45:06 +0800350 check_for_duplicates(config_json)
Bob Kingd114cd92020-02-10 13:56:02 +0800351
352 check_infinite_loops(config_json)
353
Bob King23dd60b2020-02-26 10:23:21 +0800354 check_run_rule_value_exists(config_json)
Bob Kinge9597542020-02-26 10:47:40 +0800355
356 check_set_device_value_exists(config_json)
Bob King9146df22020-02-26 10:49:33 +0800357
358 check_rule_id_exists(config_json)
Bob Kinga533d702020-02-26 10:51:24 +0800359
Shawn McCarney5d4a9c72021-08-19 18:45:59 -0500360 check_device_id_exists(config_json)
361
Bob Kinga533d702020-02-26 10:51:24 +0800362 check_number_of_elements_in_masks(config_json)