blob: 125bd0c4071d5dfda3a1e71b850d4c3b589f0799 [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
Bob Kinge9597542020-02-26 10:47:40 +0800105def check_set_device_value_exists(config_json):
106 r"""
107 Check if a set_device action specifies a device ID that does not exist.
108 config_json: Configuration file JSON
109 """
110
111 device_ids = get_values(config_json, 'set_device')
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 set_device action that specifies invalid device ID "+\
117 device_id+'\n')
118 handle_validation_error()
119
Bob King23dd60b2020-02-26 10:23:21 +0800120def check_run_rule_value_exists(config_json):
121 r"""
122 Check if any run_rule actions specify a rule ID that does not exist.
123 config_json: Configuration file JSON
124 """
125
126 rule_ids = get_values(config_json, 'run_rule')
127 valid_rule_ids = get_rule_ids(config_json)
128 for rule_id in rule_ids:
129 if rule_id not in valid_rule_ids:
130 sys.stderr.write("Error: Rule ID does not exist.\n"+\
131 "Found run_rule action that specifies invalid rule ID "+\
132 rule_id+'\n')
133 handle_validation_error()
134
Bob Kingd114cd92020-02-10 13:56:02 +0800135def check_infinite_loops_in_rule(config_json, rule_json, call_stack=[]):
136 r"""
137 Check if a 'run_rule' action in the specified rule causes an
138 infinite loop.
139 config_json: Configuration file JSON.
140 rule_json: A rule in the JSON config file.
141 call_stack: Current call stack of rules.
142 """
143
144 call_stack.append(rule_json['id'])
145 for action in rule_json.get('actions', {}):
146 if 'run_rule' in action:
147 run_rule_id = action['run_rule']
148 if run_rule_id in call_stack:
149 call_stack.append(run_rule_id)
150 sys.stderr.write(\
151 "Infinite loop caused by run_rule actions.\n"+\
152 str(call_stack)+'\n')
153 handle_validation_error()
154 else:
155 for rule in config_json.get('rules', {}):
156 if rule['id'] == run_rule_id:
157 check_infinite_loops_in_rule(\
158 config_json, rule, call_stack)
159 call_stack.pop()
160
161def check_infinite_loops(config_json):
162 r"""
163 Check if rule in config file is called recursively, causing an
164 infinite loop.
165 config_json: Configuration file JSON
166 """
167
168 for rule in config_json.get('rules', {}):
169 check_infinite_loops_in_rule(config_json, rule)
170
Bob King5b27a952020-01-20 18:04:15 +0800171def check_duplicate_object_id(config_json):
172 r"""
173 Check that there aren't any JSON objects with the same 'id' property value.
174 config_json: Configuration file JSON
175 """
176
Bob King23dd60b2020-02-26 10:23:21 +0800177 json_ids = get_values(config_json, 'id')
Bob King5b27a952020-01-20 18:04:15 +0800178 unique_ids = set()
179 for id in json_ids:
180 if id in unique_ids:
181 sys.stderr.write("Error: Duplicate ID.\n"+\
182 "Found multiple objects with the ID "+id+'\n')
183 handle_validation_error()
184 else:
185 unique_ids.add(id)
186
Bob King95b796a2020-01-15 14:45:06 +0800187def check_duplicate_rule_id(config_json):
188 r"""
189 Check that there aren't any "rule" elements with the same 'id' field.
190 config_json: Configuration file JSON
191 """
192 rule_ids = []
193 for rule in config_json.get('rules', {}):
194 rule_id = rule['id']
195 if rule_id in rule_ids:
196 sys.stderr.write("Error: Duplicate rule ID.\n"+\
197 "Found multiple rules with the ID "+rule_id+'\n')
198 handle_validation_error()
199 else:
200 rule_ids.append(rule_id)
201
202def check_duplicate_chassis_number(config_json):
203 r"""
204 Check that there aren't any "chassis" elements with the same 'number' field.
205 config_json: Configuration file JSON
206 """
207 numbers = []
208 for chassis in config_json.get('chassis', {}):
209 number = chassis['number']
210 if number in numbers:
211 sys.stderr.write("Error: Duplicate chassis number.\n"+\
212 "Found multiple chassis with the number "+str(number)+'\n')
213 handle_validation_error()
214 else:
215 numbers.append(number)
216
217def check_duplicate_device_id(config_json):
218 r"""
219 Check that there aren't any "devices" with the same 'id' field.
220 config_json: Configuration file JSON
221 """
222 device_ids = []
223 for chassis in config_json.get('chassis', {}):
224 for device in chassis.get('devices', {}):
225 device_id = device['id']
226 if device_id in device_ids:
227 sys.stderr.write("Error: Duplicate device ID.\n"+\
228 "Found multiple devices with the ID "+device_id+'\n')
229 handle_validation_error()
230 else:
231 device_ids.append(device_id)
232
233def check_duplicate_rail_id(config_json):
234 r"""
235 Check that there aren't any "rails" with the same 'id' field.
236 config_json: Configuration file JSON
237 """
238 rail_ids = []
239 for chassis in config_json.get('chassis', {}):
240 for device in chassis.get('devices', {}):
241 for rail in device.get('rails', {}):
242 rail_id = rail['id']
243 if rail_id in rail_ids:
244 sys.stderr.write("Error: Duplicate rail ID.\n"+\
245 "Found multiple rails with the ID "+rail_id+'\n')
246 handle_validation_error()
247 else:
248 rail_ids.append(rail_id)
249
250def check_for_duplicates(config_json):
251 r"""
252 Check for duplicate ID.
253 """
254 check_duplicate_rule_id(config_json)
255
256 check_duplicate_chassis_number(config_json)
257
258 check_duplicate_device_id(config_json)
259
260 check_duplicate_rail_id(config_json)
261
Bob King5b27a952020-01-20 18:04:15 +0800262 check_duplicate_object_id(config_json)
263
Bob King5cc01282019-12-17 18:11:57 +0800264def validate_schema(config, schema):
265 r"""
266 Validates the specified config file using the specified
267 schema file.
268
269 config: Path of the file containing the config JSON
270 schema: Path of the file containing the schema JSON
271 """
272
273 with open(config) as config_handle:
274 config_json = json.load(config_handle)
275
276 with open(schema) as schema_handle:
277 schema_json = json.load(schema_handle)
278
279 try:
280 jsonschema.validate(config_json, schema_json)
281 except jsonschema.ValidationError as e:
282 print(e)
Bob King95b796a2020-01-15 14:45:06 +0800283 handle_validation_error()
284
285 return config_json
Bob King5cc01282019-12-17 18:11:57 +0800286
Bob Kingb7552f02020-10-15 14:34:17 +0800287def validate_JSON_format(file):
288 with open(file) as json_data:
289 try:
290 return json.load(json_data)
291 except ValueError as err:
292 return False
293 return True
294
Bob King5cc01282019-12-17 18:11:57 +0800295if __name__ == '__main__':
296
297 parser = argparse.ArgumentParser(
298 description='phosphor-regulators configuration file validator')
299
300 parser.add_argument('-s', '--schema-file', dest='schema_file',
301 help='The phosphor-regulators schema file')
302
303 parser.add_argument('-c', '--configuration-file', dest='configuration_file',
304 help='The phosphor-regulators configuration file')
305
306 args = parser.parse_args()
307
308 if not args.schema_file:
309 parser.print_help()
310 sys.exit("Error: Schema file is required.")
Bob Kingb7552f02020-10-15 14:34:17 +0800311 if not os.path.exists(args.schema_file):
312 parser.print_help()
313 sys.exit("Error: Schema file does not exist.")
314 if not os.access(args.schema_file, os.R_OK):
315 parser.print_help()
316 sys.exit("Error: Schema file is not readable.")
317 if not validate_JSON_format(args.schema_file):
318 parser.print_help()
319 sys.exit("Error: Schema file is not in the JSON format.")
Bob King5cc01282019-12-17 18:11:57 +0800320 if not args.configuration_file:
321 parser.print_help()
322 sys.exit("Error: Configuration file is required.")
Bob Kingb7552f02020-10-15 14:34:17 +0800323 if not os.path.exists(args.configuration_file):
324 parser.print_help()
325 sys.exit("Error: Configuration file does not exist.")
326 if not os.access(args.configuration_file, os.R_OK):
327 parser.print_help()
328 sys.exit("Error: Configuration file is not readable.")
329 if not validate_JSON_format(args.configuration_file):
330 parser.print_help()
331 sys.exit("Error: Configuration file is not in the JSON format.")
Bob King5cc01282019-12-17 18:11:57 +0800332
Bob King95b796a2020-01-15 14:45:06 +0800333 config_json = validate_schema(args.configuration_file, args.schema_file)
Bob Kingd114cd92020-02-10 13:56:02 +0800334
Bob King95b796a2020-01-15 14:45:06 +0800335 check_for_duplicates(config_json)
Bob Kingd114cd92020-02-10 13:56:02 +0800336
337 check_infinite_loops(config_json)
338
Bob King23dd60b2020-02-26 10:23:21 +0800339 check_run_rule_value_exists(config_json)
Bob Kinge9597542020-02-26 10:47:40 +0800340
341 check_set_device_value_exists(config_json)
Bob King9146df22020-02-26 10:49:33 +0800342
343 check_rule_id_exists(config_json)
Bob Kinga533d702020-02-26 10:51:24 +0800344
345 check_number_of_elements_in_masks(config_json)