blob: bca989d77c4ace35f6999ecab99fd85b767f17f2 [file] [log] [blame]
Bob King5cc01282019-12-17 18:11:57 +08001#!/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
Bob King95b796a2020-01-15 14:45:06 +080013def handle_validation_error():
14 sys.exit("Validation failed.")
15
Bob King23dd60b2020-02-26 10:23:21 +080016def 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
Bob Kinge9597542020-02-26 10:47:40 +080050def 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
Bob Kinga533d702020-02-26 10:51:24 +080061def check_number_of_elements_in_masks(config_json):
62 r"""
63 Check if the number of bit masks in the 'masks' property matches the number
64 of byte values in the 'values' property.
65 config_json: Configuration file JSON
66 """
67
68 i2c_write_bytes = get_values(config_json, 'i2c_write_bytes')
69 i2c_compare_bytes = get_values(config_json, 'i2c_compare_bytes')
70
71 for object in i2c_write_bytes:
72 if 'masks' in object:
73 if len(object.get('masks', [])) != len(object.get('values', [])):
74 sys.stderr.write("Error: Invalid i2c_write_bytes action.\n"+\
75 "The masks array must have the same size as the values array. "+\
76 "masks: "+str(object.get('masks', []))+\
77 ", values: "+str(object.get('values', []))+'.\n')
78 handle_validation_error()
79
80 for object in i2c_compare_bytes:
81 if 'masks' in object:
82 if len(object.get('masks', [])) != len(object.get('values', [])):
83 sys.stderr.write("Error: Invalid i2c_compare_bytes action.\n"+\
84 "The masks array must have the same size as the values array. "+\
85 "masks: "+str(object.get('masks', []))+\
86 ", values: "+str(object.get('values', []))+'.\n')
87 handle_validation_error()
88
Bob King9146df22020-02-26 10:49:33 +080089def check_rule_id_exists(config_json):
90 r"""
91 Check if a rule_id property specifies a rule ID that does not exist.
92 config_json: Configuration file JSON
93 """
94
95 rule_ids = get_values(config_json, 'rule_id')
96 valid_rule_ids = get_rule_ids(config_json)
97 for rule_id in rule_ids:
98 if rule_id not in valid_rule_ids:
99 sys.stderr.write("Error: Rule ID does not exist.\n"+\
100 "Found rule_id value that specifies invalid rule ID "+\
101 rule_id+'\n')
102 handle_validation_error()
103
Bob Kinge9597542020-02-26 10:47:40 +0800104def check_set_device_value_exists(config_json):
105 r"""
106 Check if a set_device action specifies a device ID that does not exist.
107 config_json: Configuration file JSON
108 """
109
110 device_ids = get_values(config_json, 'set_device')
111 valid_device_ids = get_device_ids(config_json)
112 for device_id in device_ids:
113 if device_id not in valid_device_ids:
114 sys.stderr.write("Error: Device ID does not exist.\n"+\
115 "Found set_device action that specifies invalid device ID "+\
116 device_id+'\n')
117 handle_validation_error()
118
Bob King23dd60b2020-02-26 10:23:21 +0800119def check_run_rule_value_exists(config_json):
120 r"""
121 Check if any run_rule actions specify a rule ID that does not exist.
122 config_json: Configuration file JSON
123 """
124
125 rule_ids = get_values(config_json, 'run_rule')
126 valid_rule_ids = get_rule_ids(config_json)
127 for rule_id in rule_ids:
128 if rule_id not in valid_rule_ids:
129 sys.stderr.write("Error: Rule ID does not exist.\n"+\
130 "Found run_rule action that specifies invalid rule ID "+\
131 rule_id+'\n')
132 handle_validation_error()
133
Bob Kingd114cd92020-02-10 13:56:02 +0800134def check_infinite_loops_in_rule(config_json, rule_json, call_stack=[]):
135 r"""
136 Check if a 'run_rule' action in the specified rule causes an
137 infinite loop.
138 config_json: Configuration file JSON.
139 rule_json: A rule in the JSON config file.
140 call_stack: Current call stack of rules.
141 """
142
143 call_stack.append(rule_json['id'])
144 for action in rule_json.get('actions', {}):
145 if 'run_rule' in action:
146 run_rule_id = action['run_rule']
147 if run_rule_id in call_stack:
148 call_stack.append(run_rule_id)
149 sys.stderr.write(\
150 "Infinite loop caused by run_rule actions.\n"+\
151 str(call_stack)+'\n')
152 handle_validation_error()
153 else:
154 for rule in config_json.get('rules', {}):
155 if rule['id'] == run_rule_id:
156 check_infinite_loops_in_rule(\
157 config_json, rule, call_stack)
158 call_stack.pop()
159
160def check_infinite_loops(config_json):
161 r"""
162 Check if rule in config file is called recursively, causing an
163 infinite loop.
164 config_json: Configuration file JSON
165 """
166
167 for rule in config_json.get('rules', {}):
168 check_infinite_loops_in_rule(config_json, rule)
169
Bob King5b27a952020-01-20 18:04:15 +0800170def check_duplicate_object_id(config_json):
171 r"""
172 Check that there aren't any JSON objects with the same 'id' property value.
173 config_json: Configuration file JSON
174 """
175
Bob King23dd60b2020-02-26 10:23:21 +0800176 json_ids = get_values(config_json, 'id')
Bob King5b27a952020-01-20 18:04:15 +0800177 unique_ids = set()
178 for id in json_ids:
179 if id in unique_ids:
180 sys.stderr.write("Error: Duplicate ID.\n"+\
181 "Found multiple objects with the ID "+id+'\n')
182 handle_validation_error()
183 else:
184 unique_ids.add(id)
185
Bob King95b796a2020-01-15 14:45:06 +0800186def check_duplicate_rule_id(config_json):
187 r"""
188 Check that there aren't any "rule" elements with the same 'id' field.
189 config_json: Configuration file JSON
190 """
191 rule_ids = []
192 for rule in config_json.get('rules', {}):
193 rule_id = rule['id']
194 if rule_id in rule_ids:
195 sys.stderr.write("Error: Duplicate rule ID.\n"+\
196 "Found multiple rules with the ID "+rule_id+'\n')
197 handle_validation_error()
198 else:
199 rule_ids.append(rule_id)
200
201def check_duplicate_chassis_number(config_json):
202 r"""
203 Check that there aren't any "chassis" elements with the same 'number' field.
204 config_json: Configuration file JSON
205 """
206 numbers = []
207 for chassis in config_json.get('chassis', {}):
208 number = chassis['number']
209 if number in numbers:
210 sys.stderr.write("Error: Duplicate chassis number.\n"+\
211 "Found multiple chassis with the number "+str(number)+'\n')
212 handle_validation_error()
213 else:
214 numbers.append(number)
215
216def check_duplicate_device_id(config_json):
217 r"""
218 Check that there aren't any "devices" with the same 'id' field.
219 config_json: Configuration file JSON
220 """
221 device_ids = []
222 for chassis in config_json.get('chassis', {}):
223 for device in chassis.get('devices', {}):
224 device_id = device['id']
225 if device_id in device_ids:
226 sys.stderr.write("Error: Duplicate device ID.\n"+\
227 "Found multiple devices with the ID "+device_id+'\n')
228 handle_validation_error()
229 else:
230 device_ids.append(device_id)
231
232def check_duplicate_rail_id(config_json):
233 r"""
234 Check that there aren't any "rails" with the same 'id' field.
235 config_json: Configuration file JSON
236 """
237 rail_ids = []
238 for chassis in config_json.get('chassis', {}):
239 for device in chassis.get('devices', {}):
240 for rail in device.get('rails', {}):
241 rail_id = rail['id']
242 if rail_id in rail_ids:
243 sys.stderr.write("Error: Duplicate rail ID.\n"+\
244 "Found multiple rails with the ID "+rail_id+'\n')
245 handle_validation_error()
246 else:
247 rail_ids.append(rail_id)
248
249def check_for_duplicates(config_json):
250 r"""
251 Check for duplicate ID.
252 """
253 check_duplicate_rule_id(config_json)
254
255 check_duplicate_chassis_number(config_json)
256
257 check_duplicate_device_id(config_json)
258
259 check_duplicate_rail_id(config_json)
260
Bob King5b27a952020-01-20 18:04:15 +0800261 check_duplicate_object_id(config_json)
262
Bob King5cc01282019-12-17 18:11:57 +0800263def validate_schema(config, schema):
264 r"""
265 Validates the specified config file using the specified
266 schema file.
267
268 config: Path of the file containing the config JSON
269 schema: Path of the file containing the schema JSON
270 """
271
272 with open(config) as config_handle:
273 config_json = json.load(config_handle)
274
275 with open(schema) as schema_handle:
276 schema_json = json.load(schema_handle)
277
278 try:
279 jsonschema.validate(config_json, schema_json)
280 except jsonschema.ValidationError as e:
281 print(e)
Bob King95b796a2020-01-15 14:45:06 +0800282 handle_validation_error()
283
284 return config_json
Bob King5cc01282019-12-17 18:11:57 +0800285
286if __name__ == '__main__':
287
288 parser = argparse.ArgumentParser(
289 description='phosphor-regulators configuration file validator')
290
291 parser.add_argument('-s', '--schema-file', dest='schema_file',
292 help='The phosphor-regulators schema file')
293
294 parser.add_argument('-c', '--configuration-file', dest='configuration_file',
295 help='The phosphor-regulators configuration file')
296
297 args = parser.parse_args()
298
299 if not args.schema_file:
300 parser.print_help()
301 sys.exit("Error: Schema file is required.")
302 if not args.configuration_file:
303 parser.print_help()
304 sys.exit("Error: Configuration file is required.")
305
Bob King95b796a2020-01-15 14:45:06 +0800306 config_json = validate_schema(args.configuration_file, args.schema_file)
Bob Kingd114cd92020-02-10 13:56:02 +0800307
Bob King95b796a2020-01-15 14:45:06 +0800308 check_for_duplicates(config_json)
Bob Kingd114cd92020-02-10 13:56:02 +0800309
310 check_infinite_loops(config_json)
311
Bob King23dd60b2020-02-26 10:23:21 +0800312 check_run_rule_value_exists(config_json)
Bob Kinge9597542020-02-26 10:47:40 +0800313
314 check_set_device_value_exists(config_json)
Bob King9146df22020-02-26 10:49:33 +0800315
316 check_rule_id_exists(config_json)
Bob Kinga533d702020-02-26 10:51:24 +0800317
318 check_number_of_elements_in_masks(config_json)