blob: 77313e77c25466f8165b2c3c3879e786b91a823e [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
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
Bob King23dd60b2020-02-26 10:23:21 +080076def 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
Bob Kingd114cd92020-02-10 13:56:02 +080091def 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
Bob King5b27a952020-01-20 18:04:15 +0800127def 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
Bob King23dd60b2020-02-26 10:23:21 +0800133 json_ids = get_values(config_json, 'id')
Bob King5b27a952020-01-20 18:04:15 +0800134 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
Bob King95b796a2020-01-15 14:45:06 +0800143def 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
Bob King5b27a952020-01-20 18:04:15 +0800218 check_duplicate_object_id(config_json)
219
Bob King5cc01282019-12-17 18:11:57 +0800220def 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)
Bob King95b796a2020-01-15 14:45:06 +0800239 handle_validation_error()
240
241 return config_json
Bob King5cc01282019-12-17 18:11:57 +0800242
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
Bob King95b796a2020-01-15 14:45:06 +0800263 config_json = validate_schema(args.configuration_file, args.schema_file)
Bob Kingd114cd92020-02-10 13:56:02 +0800264
Bob King95b796a2020-01-15 14:45:06 +0800265 check_for_duplicates(config_json)
Bob Kingd114cd92020-02-10 13:56:02 +0800266
267 check_infinite_loops(config_json)
268
Bob King23dd60b2020-02-26 10:23:21 +0800269 check_run_rule_value_exists(config_json)
Bob Kinge9597542020-02-26 10:47:40 +0800270
271 check_set_device_value_exists(config_json)