Add script to remove queries for fixed bugs

This automates the process to remove old queries
for fixed bugs. It's a bit conservative to start
so it doesn't check for open reviews nor does it
filter out affected projects with non-Fix* status
on the bug. It can be made more robust once we're
confident in how it works and play with it on the
open queries.

Change-Id: Iaaf17892804453b99a846be27457c88e5a8f8a55
This commit is contained in:
Matt Riedemann 2019-08-19 17:30:01 -04:00
parent 81b697f270
commit dbeeceeb8e
3 changed files with 182 additions and 1 deletions

View File

@ -160,7 +160,40 @@ Queries that have "suppress-graph: true" in them generally should not be
removed since we basically want to keep those around, they are persistent infra removed since we basically want to keep those around, they are persistent infra
issues and are not going away. issues and are not going away.
Steps: Automated Cleanup
~~~~~~~~~~~~~~~~~
#. Run the ``elastic-recheck-cleanup`` command:
.. code-block:: console
$ tox -e venv -- elastic-recheck-cleanup -h
...
usage: elastic-recheck-cleanup [-h] [--bug <bug>] [--dry-run] [-v]
Remove old queries where the affected projects list the bug status as one
of: Fix Committed, Fix Released
optional arguments:
-h, --help show this help message and exit
--bug <bug> Specific bug number/id to clean. Returns an exit code of
1 if no query is found for the bug.
--dry-run Print out old queries that would be removed but do not
actually remove them.
-v Print verbose information during execution.
.. note:: You may want to run with the ``--dry-run`` option first and
sanity check the removed queries before committing them.
#. Commit the changes and push them up for review:
.. code-block:: console
$ git commit -a -m "Remove old queries: `date +%F`"
$ git review -t rm-old-queries
Manual Cleanup
~~~~~~~~~~~~~~
#. Go to the `All Pipelines <http://status.openstack.org/elastic-recheck/index.html>`_ page. #. Go to the `All Pipelines <http://status.openstack.org/elastic-recheck/index.html>`_ page.
#. Look for anything that is grayed out at the bottom which means it has not #. Look for anything that is grayed out at the bottom which means it has not

View File

@ -0,0 +1,147 @@
#!/usr/bin/env python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import os
from elastic_recheck.cmd import graph
from elastic_recheck import elasticRecheck as er
# TODO(mriedem): We may want to include Invalid, Incomplete and Won't Fix in
# this list since a bug could be reported against multiple projects but only
# fixed in one of them and marked Invalid in others.
FIXED_STATUSES = ('Fix Committed', 'Fix Released')
class InvalidProjectstatus(Exception):
"""Indicates an error parsing a bug data's affected projects"""
def get_project_status(affected_bug_data):
"""Parses a (<project> - <status>) value
:param affected_bug_data: String of the expected form
"(<project> - <status>)".
:raises: InvalidProjectStatus if the affected_bug_data string cannot be
parsed
:returns: Two-item tuple of:
- project
- status
"""
# Note that the string can be "Unknown (Private Bug)" or "Unknown" so
# handle parsing errors.
project_status = affected_bug_data.split(' - ')
if len(project_status) != 2:
raise InvalidProjectstatus(affected_bug_data)
# Trim leading ( and trailing ).
return project_status[0][1:], project_status[1][:-1]
def main():
parser = argparse.ArgumentParser(
description='Remove old queries where the affected projects list the '
'bug status as one of: %s' % ', '.join(FIXED_STATUSES))
parser.add_argument('--bug', metavar='<bug>',
help='Specific bug number/id to clean. Returns an '
'exit code of 1 if no query is found for the '
'bug.')
parser.add_argument('--dry-run', action='store_true', default=False,
help='Print out old queries that would be removed but '
'do not actually remove them.')
parser.add_argument('-v', dest='verbose',
action='store_true', default=False,
help='Print verbose information during execution.')
args = parser.parse_args()
verbose = args.verbose
dry_run = args.dry_run
def info(message):
if verbose:
print(message)
info('Loading queries')
classifier = er.Classifier('queries')
processed = [] # keep track of the bugs we've looked at
cleaned = [] # keep track of the queries we've removed
for query in classifier.queries:
bug = query['bug']
processed.append(bug)
# If we're looking for a specific bug check to see if we found it.
if args.bug and bug != args.bug:
continue
# Skip anything with suppress-graph: true since those are meant to be
# kept around even if they don't have hits.
if query.get('suppress-graph', False):
info('Skipping query for bug %s since it has '
'"suppress-graph: true"' % bug)
continue
info('Getting data for bug: %s' % bug)
bug_data = graph.get_launchpad_bug(bug)
affects = bug_data.get('affects')
# affects is a comma-separated list of (<project> - <status>), e.g.
# "(neutron - Confirmed), (nova - Fix Released)".
if affects:
affects = affects.split(',')
fixed_in_all_affected_projects = True
for affected in affects:
affected = affected.strip()
try:
project, status = get_project_status(affected)
if status not in FIXED_STATUSES:
# TODO(mriedem): It could be useful to report queries
# that do not have hits but the bug is not marked as
# fixed.
info('Bug %s is not fixed for project %s' %
(bug, project))
fixed_in_all_affected_projects = False
break
except InvalidProjectstatus:
print('Unable to parse project status "%s" for bug %s' %
(affected, bug))
fixed_in_all_affected_projects = False
break
if fixed_in_all_affected_projects:
# TODO(mriedem): It might be good to sanity check that a query
# does not have hits if we are going to remove it even if the
# bug is marked as fixed, e.g. bug 1745168. The bug may have
# re-appeared, or still be a problem on stable branches, or the
# query may be too broad.
if dry_run:
info('[DRY-RUN] Remove query for bug: %s' % bug)
else:
info('Removing query for bug: %s' % bug)
os.remove('queries/%s.yaml' % bug)
cleaned.append(bug)
else:
print('Unable to determine affected projects for bug %s' % bug)
# If a specific bug was provided did we find it?
if args.bug and args.bug not in processed:
print('Unable to find query for bug: %s' % args.bug)
return 1
# Print a summary of what we cleaned.
prefix = '[DRY-RUN] ' if dry_run else ''
# If we didn't remove anything, just print None.
if not cleaned:
cleaned.append('None')
info('%sRemoved queries:\n%s' % (prefix, '\n'.join(sorted(cleaned))))
if __name__ == "__main__":
main()

View File

@ -32,6 +32,7 @@ console_scripts =
elastic-recheck-success = elastic_recheck.cmd.check_success:main elastic-recheck-success = elastic_recheck.cmd.check_success:main
elastic-recheck-uncategorized = elastic_recheck.cmd.uncategorized_fails:main elastic-recheck-uncategorized = elastic_recheck.cmd.uncategorized_fails:main
elastic-recheck-query = elastic_recheck.cmd.query:main elastic-recheck-query = elastic_recheck.cmd.query:main
elastic-recheck-cleanup = elastic_recheck.cmd.cleanup:main
[upload_sphinx] [upload_sphinx]
upload-dir = doc/build/html upload-dir = doc/build/html