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 check_duplicate_rule_id(config_json):
17    r"""
18    Check that there aren't any "rule" elements with the same 'id' field.
19    config_json: Configuration file JSON
20    """
21    rule_ids = []
22    for rule in config_json.get('rules', {}):
23        rule_id = rule['id']
24        if rule_id in rule_ids:
25            sys.stderr.write("Error: Duplicate rule ID.\n"+\
26            "Found multiple rules with the ID "+rule_id+'\n')
27            handle_validation_error()
28        else:
29            rule_ids.append(rule_id)
30
31def check_duplicate_chassis_number(config_json):
32    r"""
33    Check that there aren't any "chassis" elements with the same 'number' field.
34    config_json: Configuration file JSON
35    """
36    numbers = []
37    for chassis in config_json.get('chassis', {}):
38        number = chassis['number']
39        if number in numbers:
40            sys.stderr.write("Error: Duplicate chassis number.\n"+\
41            "Found multiple chassis with the number "+str(number)+'\n')
42            handle_validation_error()
43        else:
44            numbers.append(number)
45
46def check_duplicate_device_id(config_json):
47    r"""
48    Check that there aren't any "devices" with the same 'id' field.
49    config_json: Configuration file JSON
50    """
51    device_ids = []
52    for chassis in config_json.get('chassis', {}):
53        for device in chassis.get('devices', {}):
54            device_id = device['id']
55            if device_id in device_ids:
56                sys.stderr.write("Error: Duplicate device ID.\n"+\
57                "Found multiple devices with the ID "+device_id+'\n')
58                handle_validation_error()
59            else:
60                device_ids.append(device_id)
61
62def check_duplicate_rail_id(config_json):
63    r"""
64    Check that there aren't any "rails" with the same 'id' field.
65    config_json: Configuration file JSON
66    """
67    rail_ids = []
68    for chassis in config_json.get('chassis', {}):
69        for device in chassis.get('devices', {}):
70            for rail in device.get('rails', {}):
71                rail_id = rail['id']
72                if rail_id in rail_ids:
73                    sys.stderr.write("Error: Duplicate rail ID.\n"+\
74                    "Found multiple rails with the ID "+rail_id+'\n')
75                    handle_validation_error()
76                else:
77                    rail_ids.append(rail_id)
78
79def check_for_duplicates(config_json):
80    r"""
81    Check for duplicate ID.
82    """
83    check_duplicate_rule_id(config_json)
84
85    check_duplicate_chassis_number(config_json)
86
87    check_duplicate_device_id(config_json)
88
89    check_duplicate_rail_id(config_json)
90
91def validate_schema(config, schema):
92    r"""
93    Validates the specified config file using the specified
94    schema file.
95
96    config:   Path of the file containing the config JSON
97    schema:   Path of the file containing the schema JSON
98    """
99
100    with open(config) as config_handle:
101        config_json = json.load(config_handle)
102
103        with open(schema) as schema_handle:
104            schema_json = json.load(schema_handle)
105
106            try:
107                jsonschema.validate(config_json, schema_json)
108            except jsonschema.ValidationError as e:
109                print(e)
110                handle_validation_error()
111
112    return config_json
113
114if __name__ == '__main__':
115
116    parser = argparse.ArgumentParser(
117        description='phosphor-regulators configuration file validator')
118
119    parser.add_argument('-s', '--schema-file', dest='schema_file',
120                        help='The phosphor-regulators schema file')
121
122    parser.add_argument('-c', '--configuration-file', dest='configuration_file',
123                        help='The phosphor-regulators configuration file')
124
125    args = parser.parse_args()
126
127    if not args.schema_file:
128        parser.print_help()
129        sys.exit("Error: Schema file is required.")
130    if not args.configuration_file:
131        parser.print_help()
132        sys.exit("Error: Configuration file is required.")
133
134    config_json = validate_schema(args.configuration_file, args.schema_file)
135    check_for_duplicates(config_json)
136