1#!/usr/bin/env python
2
3r"""
4This script will parse error log yaml file(s) and generate
5a header file which will then be used by the error logging client and
6server to collect and validate the error information generated by the
7openbmc software components.
8
9This code uses a mako template to provide the basic template of the header
10file we're going to generate.  We then call it with information from the
11yaml to generate the header file.
12"""
13
14from mako.template import Template
15from optparse import OptionParser
16import yaml
17import sys
18import os
19
20
21def order_inherited_errors(i_errors, i_parents):
22    # the ordered list of errors
23    errors = list()
24    has_inheritance = False
25    for error in i_errors:
26        if(i_parents[error] is not None):
27            has_inheritance = True
28            break
29
30    if(has_inheritance):
31        # Order the error codes list such that an error is never placed
32        # before it's parent. This way generated code can ensure parent
33        # definitions precede child error definitions.
34        while(len(errors) < len(i_errors)):
35            for error in i_errors:
36                if(error in errors):
37                    # already ordererd
38                    continue
39                if((not i_parents[error]) or (i_parents[error] in errors)):
40                    # parent present, or has no parent, either way this error
41                    # can be added
42                    errors.append(error)
43    else:
44        # no inherited errors
45        errors = i_errors
46
47    return errors
48
49
50def check_error_inheritance(i_errors, i_parents):
51    for error in i_errors:
52        if(i_parents[error] and (i_parents[error] not in i_errors)):
53            print(error + " inherits " + i_parents[error] +
54                  " but the latter is not defined")
55            return False
56    return True
57
58
59# Return the yaml files with their directory structure plus the file name
60# without the yaml extension, which will be used to set the namespaces.
61# Ex: file xyz/openbmc_project/Error/Callout/Device.errors.yaml
62# will have namespce xyz/openbmc_project/Error/Callout/Device
63def get_error_yaml_files(i_yaml_dir, i_test_dir):
64    yaml_files = dict()
65    if i_yaml_dir != "None":
66        for root, dirs, files in os.walk(i_yaml_dir):
67            for files in filter(lambda file:
68                                file.endswith('.errors.yaml'), files):
69                splitdir = root.split(i_yaml_dir)[1] + "/" + files[:-12]
70                if splitdir.startswith("/"):
71                    splitdir = splitdir[1:]
72                yaml_files[(os.path.join(root, files))] = splitdir
73    for root, dirs, files in os.walk(i_test_dir):
74        for files in filter(lambda file: file.endswith('.errors.yaml'), files):
75            splitdir = root.split(i_test_dir)[1] + "/" + files[:-12]
76            yaml_files[(os.path.join(root, files))] = splitdir
77    return yaml_files
78
79
80def get_meta_yaml_file(i_error_yaml_file):
81    # the meta data will be defined in file name where we replace
82    # <Interface>.errors.yaml with <Interface>.metadata.yaml
83    meta_yaml = i_error_yaml_file.replace("errors", "metadata")
84    return meta_yaml
85
86
87def get_cpp_type(i_type):
88    typeMap = {
89        'int16': 'int16_t',
90        'int32': 'int32_t',
91        'int64': 'int64_t',
92        'uint16': 'uint16_t',
93        'uint32': 'uint32_t',
94        'uint64': 'uint64_t',
95        'double': 'double',
96        # const char* aids usage of constexpr
97        'string': 'const char*'}
98
99    return typeMap[i_type]
100
101
102def gen_elog_hpp(i_yaml_dir, i_test_dir, i_output_hpp,
103                 i_template_dir, i_elog_mako):
104    r"""
105    Read  yaml file(s) under input yaml dir, grab the relevant data and call
106    the mako template to generate the output header file.
107
108    Description of arguments:
109    i_yaml_dir                  directory containing base error yaml files
110    i_test_dir                  directory containing test error yaml files
111    i_output_hpp                name of the to be generated output hpp
112    i_template_dir              directory containing error mako templates
113    i_elog_mako                 error mako template to render
114    """
115
116    # Input parameters to mako template
117    errors = list()  # Main error codes
118    error_msg = dict()  # Error msg that corresponds to error code
119    error_lvl = dict()  # Error code log level (debug, info, error, ...)
120    meta = dict()  # The meta data names associated (ERRNO, FILE_NAME, ...)
121    meta_data = dict()  # The meta data info (type, format)
122    parents = dict()
123    metadata_process = dict()  # metadata that have the 'process' keyword set
124
125    error_yamls = get_error_yaml_files(i_yaml_dir, i_test_dir)
126
127    for error_yaml in error_yamls:
128        # Verify the error yaml file
129        if (not (os.path.isfile(error_yaml))):
130            print("Cannot find input yaml file " + error_yaml)
131            exit(1)
132
133        # Verify the metadata yaml file
134        meta_yaml = get_meta_yaml_file(error_yaml)
135
136        # Verify the input mako file
137        template_path = "/".join((i_template_dir, i_elog_mako))
138        if (not (os.path.isfile(template_path))):
139            print("Cannot find input template file " + template_path)
140            exit(1)
141
142        get_elog_data(error_yaml,
143                      meta_yaml,
144                      error_yamls[error_yaml],
145                      # Last arg is a tuple
146                      (errors,
147                       error_msg,
148                       error_lvl,
149                       meta,
150                       meta_data,
151                       parents,
152                       metadata_process))
153
154    if(not check_error_inheritance(errors, parents)):
155        print("Error - failed to validate error inheritance")
156        exit(1)
157
158    errors = order_inherited_errors(errors, parents)
159
160    # Load the mako template and call it with the required data
161    yaml_dir = i_yaml_dir.strip("./")
162    yaml_dir = yaml_dir.strip("../")
163    template = Template(filename=template_path)
164    f = open(i_output_hpp, 'w')
165    f.write(template.render(
166            errors=errors,
167            error_msg=error_msg,
168            error_lvl=error_lvl,
169            meta=meta,
170            meta_data=meta_data,
171            parents=parents,
172            metadata_process=metadata_process))
173    f.close()
174
175def get_elog_data(i_elog_yaml,
176                  i_elog_meta_yaml,
177                  i_namespace,
178                  o_elog_data):
179    r"""
180    Parse the error and metadata yaml files in order to pull out
181    error metadata.
182
183    Use default values if metadata yaml file is not found.
184
185    Description of arguments:
186    i_elog_yaml                 error yaml file
187    i_elog_meta_yaml            metadata yaml file
188    i_namespace                 namespace data
189    o_elog_data                 error metadata
190    """
191    (errors, error_msg, error_lvl, meta,
192     meta_data, parents, metadata_process) = o_elog_data
193    ifile = yaml.safe_load(open(i_elog_yaml))
194
195    #for all the errors in error yaml file
196    for error in ifile:
197        if 'name' not in error:
198            print("Error - Did not find name in entry %s in file %s " % (
199                str(error), i_elog_yaml))
200            exit(1)
201        fullname = i_namespace.replace('/', '.') + ('.') + error['name']
202        errors.append(fullname)
203
204        if 'description' in error:
205            error_msg[fullname] = error['description'].strip()
206
207        #set default values
208        error_lvl[fullname] = "ERR"
209        parents[fullname] = None
210
211        #check if meta data yaml file is found
212        if not os.path.isfile(i_elog_meta_yaml):
213            continue
214        mfile = yaml.safe_load(open(i_elog_meta_yaml))
215
216        # Find the meta data entry
217        match = None
218        for meta_entry in mfile:
219            if meta_entry['name'] == error['name']:
220                match = meta_entry
221                break
222
223        if match is None:
224            print("Error - Did not find error named %s in %s" % (
225                error['name'], i_elog_meta_yaml))
226            continue
227
228        error_lvl[fullname] = match.get('level', 'ERR')
229
230        # Get 0th inherited error (current support - single inheritance)
231        if 'inherits' in match:
232            parents[fullname]  = match['inherits'][0]
233
234        # Put all errors in meta[] even the meta is empty
235        # so that child errors could inherits such error without meta
236        tmp_meta = []
237        if 'meta' in match:
238            # grab all the meta data fields and info
239            for i in match['meta']:
240                str_short = i['str'].split('=')[0]
241                tmp_meta.append(str_short)
242                meta_data[str_short] = {}
243                meta_data[str_short]['str'] = i['str']
244                meta_data[str_short]['str_short'] = str_short
245                meta_data[str_short]['type'] = get_cpp_type(i['type'])
246                if ('process' in i) and (True == i['process']):
247                    metadata_process[str_short] = fullname + "." + str_short
248        meta[fullname] = tmp_meta
249
250    # Debug
251    # for i in errors:
252    #   print "ERROR: " + errors[i]
253    #   print " MSG:  " + error_msg[errors[i]]
254    #   print " LVL:  " + error_lvl[errors[i]]
255    #   print " META: "
256    #   print meta[i]
257
258
259def main(i_args):
260    parser = OptionParser()
261
262    parser.add_option("-m", "--mako", dest="elog_mako",
263                      default="elog-gen-template.mako.hpp",
264                      help="input mako template file to use")
265
266    parser.add_option("-o", "--output", dest="output_hpp",
267                      default="elog-errors.hpp",
268                      help="output hpp to generate, elog-errors.hpp default")
269
270    parser.add_option("-y", "--yamldir", dest="yamldir",
271                      default="None",
272                      help="Base directory of yaml files to process")
273
274    parser.add_option("-u", "--testdir", dest="testdir",
275                      default="./tools/example/",
276                      help="Unit test directory of yaml files to process")
277
278    parser.add_option("-t", "--templatedir", dest="templatedir",
279                      default="phosphor-logging/templates/",
280                      help="Base directory of files to process")
281
282    (options, args) = parser.parse_args(i_args)
283
284    gen_elog_hpp(options.yamldir,
285                 options.testdir,
286                 options.output_hpp,
287                 options.templatedir,
288                 options.elog_mako)
289
290# Only run if it's a script
291if __name__ == '__main__':
292    main(sys.argv[1:])
293