PEL: Script to validate message registry JSON

This script will check the JSON against the schema, as well as do a few
other checks that can't be encoded into a schema.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I97c0fc0f69baac3c076b557b67e47ce8abfccf36
diff --git a/extensions/openpower-pels/registry/tools/process_registry.py b/extensions/openpower-pels/registry/tools/process_registry.py
new file mode 100755
index 0000000..3357c0d
--- /dev/null
+++ b/extensions/openpower-pels/registry/tools/process_registry.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+
+import argparse
+import json
+import sys
+
+r"""
+Validates the PEL message registry JSON, which includes checking it against
+a JSON schema using the jsonschema module as well as doing some extra checks
+that can't be encoded in the schema.
+"""
+
+
+def check_duplicate_names(registry_json):
+    r"""
+    Check that there aren't any message registry entries with the same
+    'Name' field.  There may be a use case for this in the future, but there
+    isn't right now.
+
+    registry_json: The message registry JSON
+    """
+
+    names = {}
+    for entry in registry_json['PELs']:
+        if entry['Name'] in names.keys():
+            sys.exit("Found multiple uses of error {}".format(entry['Name']))
+        else:
+            names[entry['Name']] = {}
+
+
+def check_component_id(registry_json):
+    r"""
+    Check that the upper byte of the ComponentID field matches the upper byte
+    of the ReasonCode field, but not on "11" type SRCs where they aren't
+    supposed to match.
+
+    registry_json: The message registry JSON
+    """
+
+    for entry in registry_json['PELs']:
+
+        # Don't check on "11" SRCs as those reason codes aren't supposed to
+        # match the component ID.
+        if entry.get('Type', '') == "11":
+            continue
+
+        if 'ComponentID' in entry:
+            id = int(entry['ComponentID'], 16)
+            reason_code = int(entry['SRC']['ReasonCode'], 16)
+
+            if (id & 0xFF00) != (reason_code & 0xFF00):
+                sys.exit("Found mismatching component ID {} vs reason "
+                         "code {} for error {}".format(
+                             entry['ComponentID'],
+                             entry['SRC']['ReasonCode'],
+                             entry['Name']))
+
+
+def check_message_args(registry_json):
+    r"""
+    Check that if the Message field uses the '%' style placeholders that there
+    are that many entries in the MessageArgSources field.  Also checks that
+    the MessageArgSources field is present but only if there are placeholders.
+
+    registry_json: The message registry JSON
+    """
+
+    for entry in registry_json['PELs']:
+        num_placeholders = entry['Documentation']['Message'].count('%')
+        if num_placeholders == 0:
+            continue
+
+        if 'MessageArgSources' not in entry['Documentation']:
+            sys.exit("Missing MessageArgSources property for error {}".
+                     format(entry['Name']))
+
+        if num_placeholders != \
+                len(entry['Documentation']['MessageArgSources']):
+                    sys.exit("Different number of placeholders found in "
+                             "Message vs MessageArgSources for error {}".
+                             format(entry['Name']))
+
+
+def validate_schema(registry, schema):
+    r"""
+    Validates the passed in JSON against the passed in schema JSON
+
+    registry: Path of the file containing the registry JSON
+    schema:   Path of the file containing the schema JSON
+              Use None to skip the pure schema validation
+    """
+
+    with open(registry) as registry_handle:
+        registry_json = json.load(registry_handle)
+
+        if schema:
+
+            import jsonschema
+
+            with open(schema) as schema_handle:
+                schema_json = json.load(schema_handle)
+
+                try:
+                    jsonschema.validate(registry_json, schema_json)
+                except jsonschema.ValidationError as e:
+                    print(e)
+                    sys.exit("Schema validation failed")
+
+        check_duplicate_names(registry_json)
+
+        check_component_id(registry_json)
+
+        check_message_args(registry_json)
+
+
+if __name__ == '__main__':
+
+    parser = argparse.ArgumentParser(
+        description='PEL message registry processor')
+
+    parser.add_argument('-v', '--validate', action='store_true',
+                        dest='validate',
+                        help='Validate the JSON using the schema')
+
+    parser.add_argument('-s', '--schema-file', dest='schema_file',
+                        help='The message registry JSON schema file')
+
+    parser.add_argument('-r', '--registry-file', dest='registry_file',
+                        help='The message registry JSON file')
+    parser.add_argument('-k', '--skip-schema-validation', action='store_true',
+                        dest='skip_schema',
+                        help='Skip running schema validation. '
+                             'Only do the extra checks.')
+
+    args = parser.parse_args()
+
+    if args.validate:
+        if not args.schema_file:
+            sys.exit("Schema file required")
+
+        if not args.registry_file:
+            sys.exit("Registry file required")
+
+        schema = args.schema_file
+        if args.skip_schema:
+            schema = None
+
+        validate_schema(args.registry_file, schema)