xref: /openbmc/phosphor-logging/extensions/openpower-pels/registry/tools/validate_registry.py (revision 1ed1067cea13c64037bfe0fe318c9233b811f73d)
1*1ed1067cSMatt Spinler#!/usr/bin/env python3
2*1ed1067cSMatt Spinler
3*1ed1067cSMatt Spinlerimport argparse
4*1ed1067cSMatt Spinlerimport json
5*1ed1067cSMatt Spinlerimport sys
6*1ed1067cSMatt Spinler
7*1ed1067cSMatt Spinlerr"""
8*1ed1067cSMatt SpinlerValidates the PEL message registry JSON, which includes checking it against
9*1ed1067cSMatt Spinlera JSON schema using the jsonschema module as well as doing some extra checks
10*1ed1067cSMatt Spinlerthat can't be encoded in the schema.
11*1ed1067cSMatt Spinler"""
12*1ed1067cSMatt Spinler
13*1ed1067cSMatt Spinler
14*1ed1067cSMatt Spinlerdef check_duplicate_names(registry_json):
15*1ed1067cSMatt Spinler    r"""
16*1ed1067cSMatt Spinler    Check that there aren't any message registry entries with the same
17*1ed1067cSMatt Spinler    'Name' field.  There may be a use case for this in the future, but there
18*1ed1067cSMatt Spinler    isn't right now.
19*1ed1067cSMatt Spinler
20*1ed1067cSMatt Spinler    registry_json: The message registry JSON
21*1ed1067cSMatt Spinler    """
22*1ed1067cSMatt Spinler
23*1ed1067cSMatt Spinler    names = []
24*1ed1067cSMatt Spinler    for entry in registry_json["PELs"]:
25*1ed1067cSMatt Spinler        if entry["Name"] in names:
26*1ed1067cSMatt Spinler            sys.exit("Found multiple uses of error {}".format(entry["Name"]))
27*1ed1067cSMatt Spinler        else:
28*1ed1067cSMatt Spinler            names.append(entry["Name"])
29*1ed1067cSMatt Spinler
30*1ed1067cSMatt Spinler
31*1ed1067cSMatt Spinlerdef check_duplicate_reason_codes(registry_json):
32*1ed1067cSMatt Spinler    r"""
33*1ed1067cSMatt Spinler    Check that there aren't any message registry entries with the same
34*1ed1067cSMatt Spinler    'ReasonCode' field.
35*1ed1067cSMatt Spinler
36*1ed1067cSMatt Spinler    registry_json: The message registry JSON
37*1ed1067cSMatt Spinler    """
38*1ed1067cSMatt Spinler
39*1ed1067cSMatt Spinler    reasonCodes = []
40*1ed1067cSMatt Spinler    for entry in registry_json["PELs"]:
41*1ed1067cSMatt Spinler        if entry["SRC"]["ReasonCode"] in reasonCodes:
42*1ed1067cSMatt Spinler            sys.exit(
43*1ed1067cSMatt Spinler                "Found duplicate SRC reason code {}".format(
44*1ed1067cSMatt Spinler                    entry["SRC"]["ReasonCode"]
45*1ed1067cSMatt Spinler                )
46*1ed1067cSMatt Spinler            )
47*1ed1067cSMatt Spinler        else:
48*1ed1067cSMatt Spinler            reasonCodes.append(entry["SRC"]["ReasonCode"])
49*1ed1067cSMatt Spinler
50*1ed1067cSMatt Spinler
51*1ed1067cSMatt Spinlerdef check_component_id(registry_json):
52*1ed1067cSMatt Spinler    r"""
53*1ed1067cSMatt Spinler    Check that the upper byte of the ComponentID field matches the upper byte
54*1ed1067cSMatt Spinler    of the ReasonCode field, but not on "11" type SRCs where they aren't
55*1ed1067cSMatt Spinler    supposed to match.
56*1ed1067cSMatt Spinler
57*1ed1067cSMatt Spinler    registry_json: The message registry JSON
58*1ed1067cSMatt Spinler    """
59*1ed1067cSMatt Spinler
60*1ed1067cSMatt Spinler    for entry in registry_json["PELs"]:
61*1ed1067cSMatt Spinler        # Don't check on "11" SRCs as those reason codes aren't supposed to
62*1ed1067cSMatt Spinler        # match the component ID.
63*1ed1067cSMatt Spinler        if entry["SRC"].get("Type", "") == "11":
64*1ed1067cSMatt Spinler            continue
65*1ed1067cSMatt Spinler
66*1ed1067cSMatt Spinler        if "ComponentID" in entry:
67*1ed1067cSMatt Spinler            id = int(entry["ComponentID"], 16)
68*1ed1067cSMatt Spinler            reason_code = int(entry["SRC"]["ReasonCode"], 16)
69*1ed1067cSMatt Spinler
70*1ed1067cSMatt Spinler            if (id & 0xFF00) != (reason_code & 0xFF00):
71*1ed1067cSMatt Spinler                sys.exit(
72*1ed1067cSMatt Spinler                    "Found mismatching component ID {} vs reason "
73*1ed1067cSMatt Spinler                    "code {} for error {}".format(
74*1ed1067cSMatt Spinler                        entry["ComponentID"],
75*1ed1067cSMatt Spinler                        entry["SRC"]["ReasonCode"],
76*1ed1067cSMatt Spinler                        entry["Name"],
77*1ed1067cSMatt Spinler                    )
78*1ed1067cSMatt Spinler                )
79*1ed1067cSMatt Spinler
80*1ed1067cSMatt Spinler
81*1ed1067cSMatt Spinlerdef check_message_args(registry_json):
82*1ed1067cSMatt Spinler    r"""
83*1ed1067cSMatt Spinler    Check that if the Message field uses the '%' style placeholders that there
84*1ed1067cSMatt Spinler    are that many entries in the MessageArgSources field.  Also checks that
85*1ed1067cSMatt Spinler    the MessageArgSources field is present but only if there are placeholders.
86*1ed1067cSMatt Spinler
87*1ed1067cSMatt Spinler    registry_json: The message registry JSON
88*1ed1067cSMatt Spinler    """
89*1ed1067cSMatt Spinler
90*1ed1067cSMatt Spinler    for entry in registry_json["PELs"]:
91*1ed1067cSMatt Spinler        num_placeholders = entry["Documentation"]["Message"].count("%")
92*1ed1067cSMatt Spinler        if num_placeholders == 0:
93*1ed1067cSMatt Spinler            continue
94*1ed1067cSMatt Spinler
95*1ed1067cSMatt Spinler        if "MessageArgSources" not in entry["Documentation"]:
96*1ed1067cSMatt Spinler            sys.exit(
97*1ed1067cSMatt Spinler                "Missing MessageArgSources property for error {}".format(
98*1ed1067cSMatt Spinler                    entry["Name"]
99*1ed1067cSMatt Spinler                )
100*1ed1067cSMatt Spinler            )
101*1ed1067cSMatt Spinler
102*1ed1067cSMatt Spinler        if num_placeholders != len(
103*1ed1067cSMatt Spinler            entry["Documentation"]["MessageArgSources"]
104*1ed1067cSMatt Spinler        ):
105*1ed1067cSMatt Spinler            sys.exit(
106*1ed1067cSMatt Spinler                "Different number of placeholders found in "
107*1ed1067cSMatt Spinler                "Message vs MessageArgSources for error {}".format(
108*1ed1067cSMatt Spinler                    entry["Name"]
109*1ed1067cSMatt Spinler                )
110*1ed1067cSMatt Spinler            )
111*1ed1067cSMatt Spinler
112*1ed1067cSMatt Spinler
113*1ed1067cSMatt Spinlerdef validate_schema(registry, schema):
114*1ed1067cSMatt Spinler    r"""
115*1ed1067cSMatt Spinler    Validates the passed in JSON against the passed in schema JSON
116*1ed1067cSMatt Spinler
117*1ed1067cSMatt Spinler    registry: Path of the file containing the registry JSON
118*1ed1067cSMatt Spinler    schema:   Path of the file containing the schema JSON
119*1ed1067cSMatt Spinler              Use None to skip the pure schema validation
120*1ed1067cSMatt Spinler    """
121*1ed1067cSMatt Spinler
122*1ed1067cSMatt Spinler    with open(registry) as registry_handle:
123*1ed1067cSMatt Spinler        registry_json = json.load(registry_handle)
124*1ed1067cSMatt Spinler
125*1ed1067cSMatt Spinler        if schema:
126*1ed1067cSMatt Spinler            import jsonschema
127*1ed1067cSMatt Spinler
128*1ed1067cSMatt Spinler            with open(schema) as schema_handle:
129*1ed1067cSMatt Spinler                schema_json = json.load(schema_handle)
130*1ed1067cSMatt Spinler
131*1ed1067cSMatt Spinler                try:
132*1ed1067cSMatt Spinler                    jsonschema.validate(registry_json, schema_json)
133*1ed1067cSMatt Spinler                except jsonschema.ValidationError as e:
134*1ed1067cSMatt Spinler                    print(e)
135*1ed1067cSMatt Spinler                    sys.exit("Schema validation failed")
136*1ed1067cSMatt Spinler
137*1ed1067cSMatt Spinler        check_duplicate_names(registry_json)
138*1ed1067cSMatt Spinler
139*1ed1067cSMatt Spinler        check_duplicate_reason_codes(registry_json)
140*1ed1067cSMatt Spinler
141*1ed1067cSMatt Spinler        check_component_id(registry_json)
142*1ed1067cSMatt Spinler
143*1ed1067cSMatt Spinler        check_message_args(registry_json)
144*1ed1067cSMatt Spinler
145*1ed1067cSMatt Spinler
146*1ed1067cSMatt Spinlerif __name__ == "__main__":
147*1ed1067cSMatt Spinler    parser = argparse.ArgumentParser(
148*1ed1067cSMatt Spinler        description="PEL message registry validator"
149*1ed1067cSMatt Spinler    )
150*1ed1067cSMatt Spinler
151*1ed1067cSMatt Spinler    parser.add_argument(
152*1ed1067cSMatt Spinler        "-s",
153*1ed1067cSMatt Spinler        "--schema-file",
154*1ed1067cSMatt Spinler        dest="schema_file",
155*1ed1067cSMatt Spinler        help="The message registry JSON schema file",
156*1ed1067cSMatt Spinler        required=True,
157*1ed1067cSMatt Spinler    )
158*1ed1067cSMatt Spinler
159*1ed1067cSMatt Spinler    parser.add_argument(
160*1ed1067cSMatt Spinler        "-r",
161*1ed1067cSMatt Spinler        "--registry-file",
162*1ed1067cSMatt Spinler        dest="registry_file",
163*1ed1067cSMatt Spinler        help="The message registry JSON file",
164*1ed1067cSMatt Spinler        required=True,
165*1ed1067cSMatt Spinler    )
166*1ed1067cSMatt Spinler
167*1ed1067cSMatt Spinler    parser.add_argument(
168*1ed1067cSMatt Spinler        "-k",
169*1ed1067cSMatt Spinler        "--skip-schema-validation",
170*1ed1067cSMatt Spinler        action="store_true",
171*1ed1067cSMatt Spinler        dest="skip_schema",
172*1ed1067cSMatt Spinler        help="Skip running schema validation. Only do the extra checks.",
173*1ed1067cSMatt Spinler    )
174*1ed1067cSMatt Spinler
175*1ed1067cSMatt Spinler    args = parser.parse_args()
176*1ed1067cSMatt Spinler
177*1ed1067cSMatt Spinler    schema = args.schema_file
178*1ed1067cSMatt Spinler    if args.skip_schema:
179*1ed1067cSMatt Spinler        schema = None
180*1ed1067cSMatt Spinler
181*1ed1067cSMatt Spinler    validate_schema(args.registry_file, schema)
182