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 preceed 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("Can not 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        if (not (os.path.isfile(meta_yaml))):
136            print("Can not find meta yaml file " + meta_yaml)
137            exit(1)
138
139        # Verify the input mako file
140        template_path = "/".join((i_template_dir, i_elog_mako))
141        if (not (os.path.isfile(template_path))):
142            print("Can not find input template file " + template_path)
143            exit(1)
144
145        get_elog_data(error_yaml,
146                      meta_yaml,
147                      error_yamls[error_yaml],
148                      # Last arg is a tuple
149                      (errors,
150                       error_msg,
151                       error_lvl,
152                       meta,
153                       meta_data,
154                       parents,
155                       metadata_process))
156
157    if(not check_error_inheritance(errors, parents)):
158        print("Error - failed to validate error inheritance")
159        exit(1)
160
161    errors = order_inherited_errors(errors, parents)
162
163    # Load the mako template and call it with the required data
164    yaml_dir = i_yaml_dir.strip("./")
165    yaml_dir = yaml_dir.strip("../")
166    template = Template(filename=template_path)
167    f = open(i_output_hpp, 'w')
168    f.write(template.render(
169            errors=errors,
170            error_msg=error_msg,
171            error_lvl=error_lvl,
172            meta=meta,
173            meta_data=meta_data,
174            parents=parents,
175            metadata_process=metadata_process))
176    f.close()
177
178
179def get_elog_data(i_elog_yaml,
180                  i_elog_meta_yaml,
181                  i_namespace,
182                  o_elog_data):
183    r"""
184    Parse the error and metadata yaml files in order to pull out
185    error metadata.
186
187    Description of arguments:
188    i_elog_yaml                 error yaml file
189    i_elog_meta_yaml            metadata yaml file
190    i_namespace                 namespace data
191    o_elog_data                 error metadata
192    """
193    (errors, error_msg, error_lvl, meta,
194     meta_data, parents, metadata_process) = o_elog_data
195    ifile = yaml.safe_load(open(i_elog_yaml))
196    mfile = yaml.safe_load(open(i_elog_meta_yaml))
197    for i in mfile:
198        match = None
199        # Find the corresponding meta data for this entry
200        for j in ifile:
201            if j['name'] == i['name']:
202                match = j
203                break
204        if (match is None):
205            print("Error - Did not find meta data for " + i['name'])
206            exit(1)
207        # Grab the main error and it's info
208        fullname = i_namespace.replace('/', '.') + ('.') + i['name']
209        errors.append(fullname)
210        parent = None
211        if('inherits' in i):
212            # Get 0th inherited error (current support - single inheritance)
213            parent = i['inherits'][0]
214        parents[fullname] = parent
215        error_msg[fullname] = match['description'].strip()
216        try:
217            error_lvl[fullname] = i['level']
218        except:
219            print ("No level found for: " + i['name'] + ", using INFO")
220            error_lvl[fullname] = "INFO"
221        tmp_meta = []
222        # grab all the meta data fields and info
223        if('meta' in i):
224            for j in i['meta']:
225                str_short = j['str'].split('=')[0]
226                tmp_meta.append(str_short)
227                meta_data[str_short] = {}
228                meta_data[str_short]['str'] = j['str']
229                meta_data[str_short]['str_short'] = str_short
230                meta_data[str_short]['type'] = get_cpp_type(j['type'])
231                if(('process' in j) and (True == j['process'])):
232                    metadata_process[str_short] = fullname + "." + str_short
233            meta[fullname] = tmp_meta
234
235    # Debug
236    # for i in errors:
237    #   print "ERROR: " + errors[i]
238    #   print " MSG:  " + error_msg[errors[i]]
239    #   print " LVL:  " + error_lvl[errors[i]]
240    #   print " META: "
241    #   print meta[i]
242
243
244def main(i_args):
245    parser = OptionParser()
246
247    parser.add_option("-m", "--mako", dest="elog_mako",
248                      default="elog-gen-template.mako.hpp",
249                      help="input mako template file to use")
250
251    parser.add_option("-o", "--output", dest="output_hpp",
252                      default="elog-errors.hpp",
253                      help="output hpp to generate, elog-errors.hpp default")
254
255    parser.add_option("-y", "--yamldir", dest="yamldir",
256                      default="None",
257                      help="Base directory of yaml files to process")
258
259    parser.add_option("-u", "--testdir", dest="testdir",
260                      default="./tools/example/",
261                      help="Unit test directory of yaml files to process")
262
263    parser.add_option("-t", "--templatedir", dest="templatedir",
264                      default="phosphor-logging/templates/",
265                      help="Base directory of files to process")
266
267    (options, args) = parser.parse_args(i_args)
268
269    gen_elog_hpp(options.yamldir,
270                 options.testdir,
271                 options.output_hpp,
272                 options.templatedir,
273                 options.elog_mako)
274
275# Only run if it's a script
276if __name__ == '__main__':
277    main(sys.argv[1:])
278