-
Notifications
You must be signed in to change notification settings - Fork 1
Issue finder #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Issue finder #3
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from fnmatch import fnmatch | ||
from http.client import IncompleteRead | ||
import os | ||
import pathlib | ||
from urllib.error import HTTPError | ||
import urllib.request | ||
import yaml | ||
|
||
from issues.issues import Issues | ||
|
||
|
||
class IssuesFinder: | ||
"""Known OCP CI issues.""" | ||
|
||
def __init__(self): | ||
self.issues_found = [] | ||
self.issues = Issues() | ||
self.issue_yamls = self.read_issue_yaml() | ||
|
||
def read_issue_yaml(self): | ||
"""Return a list of objects read in from issue yaml defs.""" | ||
issue_yamls = [] | ||
script_dir_path = pathlib.Path(__file__).parent.resolve() | ||
for (dirpath, dirnames, filenames) in os.walk( | ||
os.path.join(script_dir_path, "issues") | ||
): | ||
for name in filenames: | ||
if fnmatch(name, "issue_*.yaml"): | ||
yaml_path = os.path.join(dirpath, name) | ||
content = yaml.load(open(yaml_path), Loader=yaml.SafeLoader) | ||
issue_yamls.append(content) | ||
return issue_yamls | ||
|
||
def find_issues(self, logs): | ||
"""Returns a list of known issues found in the test logs.""" | ||
if logs is not None: | ||
log_text = self.get_file_from_url(logs) | ||
if log_text is not None: | ||
for issue_def in self.issue_yamls: | ||
# This could take awhile. | ||
# Let the user know something is happening | ||
print(" .") | ||
if self.issues.search(log_text, issue_def): | ||
self.issues_found.append(issue_def["description"]) | ||
return self.issues_found | ||
|
||
def get_file_from_url(self, url: str): | ||
"Return file content downloaded from url." | ||
# Try three times to help with intermittent connection issues. | ||
for i in range(3): | ||
content = None | ||
try: | ||
content = urllib.request.urlopen(url).read().decode("utf8") | ||
except IncompleteRead: | ||
print(f"Caught IncompleteRead in iteration {i}.") | ||
continue | ||
except HTTPError: | ||
print(f"Skipping download due to HTTPError: {url}") | ||
break | ||
else: | ||
break | ||
return content |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
description: 'https://issues.redhat.com/browse/OCPQE-5204' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the intention here to get known issues no matter which test case id the known issue is logged for as long as the string is matched? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct there is no requirement that an issue be mapped 1:1 to a test case. Many bugs or infrastructure issues can cause failures across multiple test cases. |
||
match_ordered_strings: | ||
- 'Scenario: Etcd basic verification ===' | ||
- 'RuntimeError: See log, waiting for labeled pods futile: k8s-app=etcd' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
description: 'https://issues.redhat.com/browse/OCPQE-5743' | ||
match_ordered_strings: | ||
- '=== After Scenario:' | ||
- 'the server is currently unable to handle the request \(get projects.project.openshift.io\)' | ||
- 'RuntimeError: error getting projects by user' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import re | ||
|
||
|
||
class Issues: | ||
"""Issues definitions.""" | ||
|
||
def match_ordered_strings(self, log: str, targets: list): | ||
"""Returns True if all the regex strings in targets are found in order.""" | ||
found_all = set() | ||
last_match_line = 0 | ||
for target in targets: | ||
line_count = 0 | ||
found = False | ||
for line in log.splitlines(): | ||
if line_count < last_match_line: | ||
continue | ||
line_count += 1 | ||
match = re.search(target, line) | ||
if match: | ||
found = True | ||
break | ||
found_all.add(found) | ||
return all(found_all) | ||
|
||
def get_method_refs(self, names): | ||
"""Return a dict of callable method refs, keyed by name in names.""" | ||
refs = dict() | ||
for name in names: | ||
if name == "description": | ||
continue | ||
refs[name] = getattr(self, name) | ||
return refs | ||
|
||
def search(self, log_text, issue_def): | ||
""" | ||
Return True if log_text matches all the criteria in issue_def. | ||
issue_def: A dict with keys that match method names defined in this | ||
class. | ||
description: issue_def.keys() is expected to be a list of method | ||
names, that match up with methods defined in this class. Each | ||
method is called with log_text and the coresponding issue_def | ||
value as arguments. The issue_def value must contain all the | ||
structured data that is required by the called method. | ||
""" | ||
|
||
found_all = set() | ||
method_refs = self.get_method_refs(issue_def.keys()) | ||
for name, ref in method_refs.items(): | ||
found_all.add(ref(log_text, issue_def[name])) | ||
return all(found_all) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when this class is extended to add other ways to find Issues, I wonder if it makes sense to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is that for any given known issue, there are a set of circumstances that must be true in order to positively identify the issue you're trying to match. Each circumstance may require performing a unique check. For example, profile X in use, operator Y installed and configured and Z error message present in the log. Currently this PR only acts on the cucushift console.html. However, the extended version might also gain access to additional test artifacts such as jenkins logs, must-gather archives, etc. All checks would need to resolve to true to produce a positive match. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to add requirements.txt file (or Pipfile if you're using pipenv) for pyyaml?
not part of this commit but perhaps we should also add some python version > 3.6 in requirements.txt file since f strings not supported in python3 versions prior to 3.6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree