xref: /openbmc/phosphor-power/phosphor-regulators/tools/validate-regulators-config.py (revision 23dd60ba01476530121159eec352d08da80d95d2)
1#!/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
13def handle_validation_error():
14    sys.exit("Validation failed.")
15
16def 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
65def 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
101def 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
107    json_ids = get_values(config_json, 'id')
108    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
117def 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
192    check_duplicate_object_id(config_json)
193
194def 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)
213                handle_validation_error()
214
215    return config_json
216
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
237    config_json = validate_schema(args.configuration_file, args.schema_file)
238
239    check_for_duplicates(config_json)
240
241    check_infinite_loops(config_json)
242
243    check_run_rule_value_exists(config_json)
244