blob: 081ff8b9e59c1105ad1fa76deefc2b43ae73ff87 [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
50def check_run_rule_value_exists(config_json):
51 r"""
52 Check if any run_rule actions specify a rule ID that does not exist.
53 config_json: Configuration file JSON
54 """
55
56 rule_ids = get_values(config_json, 'run_rule')
57 valid_rule_ids = get_rule_ids(config_json)
58 for rule_id in rule_ids:
59 if rule_id not in valid_rule_ids:
60 sys.stderr.write("Error: Rule ID does not exist.\n"+\
61 "Found run_rule action that specifies invalid rule ID "+\
62 rule_id+'\n')
63 handle_validation_error()
64
Bob Kingd114cd92020-02-10 13:56:02 +080065def check_infinite_loops_in_rule(config_json, rule_json, call_stack=[]):
66 r"""
67 Check if a 'run_rule' action in the specified rule causes an
68 infinite loop.
69 config_json: Configuration file JSON.
70 rule_json: A rule in the JSON config file.
71 call_stack: Current call stack of rules.
72 """
73
74 call_stack.append(rule_json['id'])
75 for action in rule_json.get('actions', {}):
76 if 'run_rule' in action:
77 run_rule_id = action['run_rule']
78 if run_rule_id in call_stack:
79 call_stack.append(run_rule_id)
80 sys.stderr.write(\
81 "Infinite loop caused by run_rule actions.\n"+\
82 str(call_stack)+'\n')
83 handle_validation_error()
84 else:
85 for rule in config_json.get('rules', {}):
86 if rule['id'] == run_rule_id:
87 check_infinite_loops_in_rule(\
88 config_json, rule, call_stack)
89 call_stack.pop()
90
91def check_infinite_loops(config_json):
92 r"""
93 Check if rule in config file is called recursively, causing an
94 infinite loop.
95 config_json: Configuration file JSON
96 """
97
98 for rule in config_json.get('rules', {}):
99 check_infinite_loops_in_rule(config_json, rule)
100
Bob King5b27a952020-01-20 18:04:15 +0800101def check_duplicate_object_id(config_json):
102 r"""
103 Check that there aren't any JSON objects with the same 'id' property value.
104 config_json: Configuration file JSON
105 """
106
Bob King23dd60b2020-02-26 10:23:21 +0800107 json_ids = get_values(config_json, 'id')
Bob King5b27a952020-01-20 18:04:15 +0800108 unique_ids = set()
109 for id in json_ids:
110 if id in unique_ids:
111 sys.stderr.write("Error: Duplicate ID.\n"+\
112 "Found multiple objects with the ID "+id+'\n')
113 handle_validation_error()
114 else:
115 unique_ids.add(id)
116
Bob King95b796a2020-01-15 14:45:06 +0800117def check_duplicate_rule_id(config_json):
118 r"""
119 Check that there aren't any "rule" elements with the same 'id' field.
120 config_json: Configuration file JSON
121 """
122 rule_ids = []
123 for rule in config_json.get('rules', {}):
124 rule_id = rule['id']
125 if rule_id in rule_ids:
126 sys.stderr.write("Error: Duplicate rule ID.\n"+\
127 "Found multiple rules with the ID "+rule_id+'\n')
128 handle_validation_error()
129 else:
130 rule_ids.append(rule_id)
131
132def check_duplicate_chassis_number(config_json):
133 r"""
134 Check that there aren't any "chassis" elements with the same 'number' field.
135 config_json: Configuration file JSON
136 """
137 numbers = []
138 for chassis in config_json.get('chassis', {}):
139 number = chassis['number']
140 if number in numbers:
141 sys.stderr.write("Error: Duplicate chassis number.\n"+\
142 "Found multiple chassis with the number "+str(number)+'\n')
143 handle_validation_error()
144 else:
145 numbers.append(number)
146
147def check_duplicate_device_id(config_json):
148 r"""
149 Check that there aren't any "devices" with the same 'id' field.
150 config_json: Configuration file JSON
151 """
152 device_ids = []
153 for chassis in config_json.get('chassis', {}):
154 for device in chassis.get('devices', {}):
155 device_id = device['id']
156 if device_id in device_ids:
157 sys.stderr.write("Error: Duplicate device ID.\n"+\
158 "Found multiple devices with the ID "+device_id+'\n')
159 handle_validation_error()
160 else:
161 device_ids.append(device_id)
162
163def check_duplicate_rail_id(config_json):
164 r"""
165 Check that there aren't any "rails" with the same 'id' field.
166 config_json: Configuration file JSON
167 """
168 rail_ids = []
169 for chassis in config_json.get('chassis', {}):
170 for device in chassis.get('devices', {}):
171 for rail in device.get('rails', {}):
172 rail_id = rail['id']
173 if rail_id in rail_ids:
174 sys.stderr.write("Error: Duplicate rail ID.\n"+\
175 "Found multiple rails with the ID "+rail_id+'\n')
176 handle_validation_error()
177 else:
178 rail_ids.append(rail_id)
179
180def check_for_duplicates(config_json):
181 r"""
182 Check for duplicate ID.
183 """
184 check_duplicate_rule_id(config_json)
185
186 check_duplicate_chassis_number(config_json)
187
188 check_duplicate_device_id(config_json)
189
190 check_duplicate_rail_id(config_json)
191
Bob King5b27a952020-01-20 18:04:15 +0800192 check_duplicate_object_id(config_json)
193
Bob King5cc01282019-12-17 18:11:57 +0800194def validate_schema(config, schema):
195 r"""
196 Validates the specified config file using the specified
197 schema file.
198
199 config: Path of the file containing the config JSON
200 schema: Path of the file containing the schema JSON
201 """
202
203 with open(config) as config_handle:
204 config_json = json.load(config_handle)
205
206 with open(schema) as schema_handle:
207 schema_json = json.load(schema_handle)
208
209 try:
210 jsonschema.validate(config_json, schema_json)
211 except jsonschema.ValidationError as e:
212 print(e)
Bob King95b796a2020-01-15 14:45:06 +0800213 handle_validation_error()
214
215 return config_json
Bob King5cc01282019-12-17 18:11:57 +0800216
217if __name__ == '__main__':
218
219 parser = argparse.ArgumentParser(
220 description='phosphor-regulators configuration file validator')
221
222 parser.add_argument('-s', '--schema-file', dest='schema_file',
223 help='The phosphor-regulators schema file')
224
225 parser.add_argument('-c', '--configuration-file', dest='configuration_file',
226 help='The phosphor-regulators configuration file')
227
228 args = parser.parse_args()
229
230 if not args.schema_file:
231 parser.print_help()
232 sys.exit("Error: Schema file is required.")
233 if not args.configuration_file:
234 parser.print_help()
235 sys.exit("Error: Configuration file is required.")
236
Bob King95b796a2020-01-15 14:45:06 +0800237 config_json = validate_schema(args.configuration_file, args.schema_file)
Bob Kingd114cd92020-02-10 13:56:02 +0800238
Bob King95b796a2020-01-15 14:45:06 +0800239 check_for_duplicates(config_json)
Bob Kingd114cd92020-02-10 13:56:02 +0800240
241 check_infinite_loops(config_json)
242
Bob King23dd60b2020-02-26 10:23:21 +0800243 check_run_rule_value_exists(config_json)