| David Kranz | 852c5c2 | 2013-10-04 15:10:15 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python | 
 | 2 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | 
 | 3 |  | 
 | 4 | # Copyright 2013 Red Hat, Inc. | 
 | 5 | # All Rights Reserved. | 
 | 6 | # | 
 | 7 | #    Licensed under the Apache License, Version 2.0 (the "License"); you may | 
 | 8 | #    not use this file except in compliance with the License. You may obtain | 
 | 9 | #    a copy of the License at | 
 | 10 | # | 
 | 11 | #         http://www.apache.org/licenses/LICENSE-2.0 | 
 | 12 | # | 
 | 13 | #    Unless required by applicable law or agreed to in writing, software | 
 | 14 | #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | 
 | 15 | #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | 
 | 16 | #    License for the specific language governing permissions and limitations | 
 | 17 | #    under the License. | 
 | 18 |  | 
| David Kranz | e8e2631 | 2013-10-09 21:31:32 -0400 | [diff] [blame] | 19 | import argparse | 
 | 20 | import gzip | 
 | 21 | import os | 
 | 22 | import re | 
 | 23 | import StringIO | 
| David Kranz | 852c5c2 | 2013-10-04 15:10:15 -0400 | [diff] [blame] | 24 | import sys | 
| David Kranz | e8e2631 | 2013-10-09 21:31:32 -0400 | [diff] [blame] | 25 | import urllib2 | 
 | 26 | import yaml | 
 | 27 |  | 
 | 28 |  | 
| David Kranz | e07cdb8 | 2013-11-27 10:53:54 -0500 | [diff] [blame] | 29 | is_neutron = os.environ.get('DEVSTACK_GATE_NEUTRON', "0") == "1" | 
 | 30 | dump_all_errors = is_neutron | 
 | 31 |  | 
 | 32 |  | 
| David Kranz | e8e2631 | 2013-10-09 21:31:32 -0400 | [diff] [blame] | 33 | def process_files(file_specs, url_specs, whitelists): | 
 | 34 |     regexp = re.compile(r"^.*(ERROR|CRITICAL).*\[.*\-.*\]") | 
 | 35 |     had_errors = False | 
 | 36 |     for (name, filename) in file_specs: | 
 | 37 |         whitelist = whitelists.get(name, []) | 
 | 38 |         with open(filename) as content: | 
 | 39 |             if scan_content(name, content, regexp, whitelist): | 
 | 40 |                 had_errors = True | 
 | 41 |     for (name, url) in url_specs: | 
 | 42 |         whitelist = whitelists.get(name, []) | 
 | 43 |         req = urllib2.Request(url) | 
 | 44 |         req.add_header('Accept-Encoding', 'gzip') | 
 | 45 |         page = urllib2.urlopen(req) | 
 | 46 |         buf = StringIO.StringIO(page.read()) | 
 | 47 |         f = gzip.GzipFile(fileobj=buf) | 
 | 48 |         if scan_content(name, f.read().splitlines(), regexp, whitelist): | 
 | 49 |             had_errors = True | 
 | 50 |     return had_errors | 
 | 51 |  | 
 | 52 |  | 
 | 53 | def scan_content(name, content, regexp, whitelist): | 
 | 54 |     had_errors = False | 
| David Kranz | e07cdb8 | 2013-11-27 10:53:54 -0500 | [diff] [blame] | 55 |     print_log_name = True | 
| David Kranz | e8e2631 | 2013-10-09 21:31:32 -0400 | [diff] [blame] | 56 |     for line in content: | 
 | 57 |         if not line.startswith("Stderr:") and regexp.match(line): | 
 | 58 |             whitelisted = False | 
 | 59 |             for w in whitelist: | 
 | 60 |                 pat = ".*%s.*%s.*" % (w['module'].replace('.', '\\.'), | 
 | 61 |                                       w['message']) | 
 | 62 |                 if re.match(pat, line): | 
 | 63 |                     whitelisted = True | 
 | 64 |                     break | 
| David Kranz | e07cdb8 | 2013-11-27 10:53:54 -0500 | [diff] [blame] | 65 |             if not whitelisted or dump_all_errors: | 
| David Kranz | 78dc5ab | 2013-11-29 12:33:02 -0500 | [diff] [blame] | 66 |                 if print_log_name: | 
| David Kranz | e8e2631 | 2013-10-09 21:31:32 -0400 | [diff] [blame] | 67 |                     print("Log File: %s" % name) | 
| David Kranz | e07cdb8 | 2013-11-27 10:53:54 -0500 | [diff] [blame] | 68 |                     print_log_name = False | 
 | 69 |                 if not whitelisted: | 
 | 70 |                     had_errors = True | 
| David Kranz | e8e2631 | 2013-10-09 21:31:32 -0400 | [diff] [blame] | 71 |                 print(line) | 
 | 72 |     return had_errors | 
 | 73 |  | 
 | 74 |  | 
 | 75 | def collect_url_logs(url): | 
 | 76 |     page = urllib2.urlopen(url) | 
 | 77 |     content = page.read() | 
 | 78 |     logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content) | 
 | 79 |     return logs | 
 | 80 |  | 
 | 81 |  | 
 | 82 | def main(opts): | 
 | 83 |     if opts.directory and opts.url or not (opts.directory or opts.url): | 
 | 84 |         print("Must provide exactly one of -d or -u") | 
 | 85 |         exit(1) | 
 | 86 |     print("Checking logs...") | 
 | 87 |     WHITELIST_FILE = os.path.join( | 
 | 88 |         os.path.abspath(os.path.dirname(os.path.dirname(__file__))), | 
 | 89 |         "etc", "whitelist.yaml") | 
 | 90 |  | 
 | 91 |     file_matcher = re.compile(r".*screen-([\w-]+)\.log") | 
 | 92 |     files = [] | 
 | 93 |     if opts.directory: | 
 | 94 |         d = opts.directory | 
 | 95 |         for f in os.listdir(d): | 
 | 96 |             files.append(os.path.join(d, f)) | 
 | 97 |     files_to_process = [] | 
 | 98 |     for f in files: | 
 | 99 |         m = file_matcher.match(f) | 
 | 100 |         if m: | 
 | 101 |             files_to_process.append((m.group(1), f)) | 
 | 102 |  | 
 | 103 |     url_matcher = re.compile(r".*screen-([\w-]+)\.txt\.gz") | 
 | 104 |     urls = [] | 
 | 105 |     if opts.url: | 
 | 106 |         for logfile in collect_url_logs(opts.url): | 
 | 107 |             urls.append("%s/%s" % (opts.url, logfile)) | 
 | 108 |     urls_to_process = [] | 
 | 109 |     for u in urls: | 
 | 110 |         m = url_matcher.match(u) | 
 | 111 |         if m: | 
 | 112 |             urls_to_process.append((m.group(1), u)) | 
 | 113 |  | 
 | 114 |     whitelists = {} | 
 | 115 |     with open(WHITELIST_FILE) as stream: | 
 | 116 |         loaded = yaml.safe_load(stream) | 
 | 117 |         if loaded: | 
 | 118 |             for (name, l) in loaded.iteritems(): | 
 | 119 |                 for w in l: | 
 | 120 |                     assert 'module' in w, 'no module in %s' % name | 
 | 121 |                     assert 'message' in w, 'no message in %s' % name | 
 | 122 |             whitelists = loaded | 
 | 123 |     if process_files(files_to_process, urls_to_process, whitelists): | 
 | 124 |         print("Logs have errors") | 
| David Kranz | e07cdb8 | 2013-11-27 10:53:54 -0500 | [diff] [blame] | 125 |         if is_neutron: | 
 | 126 |             print("Currently not failing neutron builds with errors") | 
 | 127 |             return 0 | 
| David Kranz | b705d46 | 2013-11-27 14:51:26 -0500 | [diff] [blame] | 128 |         print("FAILED") | 
 | 129 |         return 1 | 
| David Kranz | e8e2631 | 2013-10-09 21:31:32 -0400 | [diff] [blame] | 130 |     else: | 
 | 131 |         print("ok") | 
 | 132 |         return 0 | 
 | 133 |  | 
 | 134 | usage = """ | 
 | 135 | Find non-white-listed log errors in log files from a devstack-gate run. | 
 | 136 | Log files will be searched for ERROR or CRITICAL messages. If any | 
 | 137 | error messages do not match any of the whitelist entries contained in | 
 | 138 | etc/whitelist.yaml, those messages will be printed to the console and | 
 | 139 | failure will be returned. A file directory containing logs or a url to the | 
 | 140 | log files of an OpenStack gate job can be provided. | 
 | 141 |  | 
 | 142 | The whitelist yaml looks like: | 
 | 143 |  | 
 | 144 | log-name: | 
 | 145 |     - module: "a.b.c" | 
 | 146 |       message: "regexp" | 
 | 147 |     - module: "a.b.c" | 
 | 148 |       message: "regexp" | 
 | 149 |  | 
 | 150 | repeated for each log file with a whitelist. | 
 | 151 | """ | 
 | 152 |  | 
 | 153 | parser = argparse.ArgumentParser(description=usage) | 
 | 154 | parser.add_argument('-d', '--directory', | 
 | 155 |                     help="Directory containing log files") | 
 | 156 | parser.add_argument('-u', '--url', | 
 | 157 |                     help="url containing logs from an OpenStack gate job") | 
| David Kranz | 852c5c2 | 2013-10-04 15:10:15 -0400 | [diff] [blame] | 158 |  | 
 | 159 | if __name__ == "__main__": | 
| David Kranz | e8e2631 | 2013-10-09 21:31:32 -0400 | [diff] [blame] | 160 |     try: | 
 | 161 |         sys.exit(main(parser.parse_args())) | 
 | 162 |     except Exception as e: | 
 | 163 |         print("Failure in script: %s" % e) | 
 | 164 |         # Don't fail if there is a problem with the script. | 
 | 165 |         sys.exit(0) |