blockerbugs: fix bugzilla query code
There was an unannounced change to rhbz where the default number of results returned for a query was changed to 20. This should be a temporary change that will need to be reverted when a new version of blockerbugs is released with this capability built in.
This commit is contained in:
parent
f3c9f3bb9d
commit
0628aed2b1
2 changed files with 283 additions and 0 deletions
276
roles/blockerbugs/files/20210914-patched-bz_interface.py
Normal file
276
roles/blockerbugs/files/20210914-patched-bz_interface.py
Normal file
|
@ -0,0 +1,276 @@
|
|||
# Copyright 2012, Red Hat, Inc
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Authors:
|
||||
# Tim Flink <tflink@redhat.com>
|
||||
|
||||
"""Common methods for obtaining blocker/FE information from Bugzilla"""
|
||||
|
||||
import logging
|
||||
import datetime
|
||||
from typing import Optional, Any
|
||||
|
||||
import bugzilla
|
||||
from bugzilla.bug import Bug as bzBug
|
||||
from xmlrpc.client import Fault
|
||||
|
||||
from blockerbugs import app
|
||||
|
||||
# rhbz has been updated to have a max of 20 results returned
|
||||
BUGZILLA_QUERY_LIMIT = 20
|
||||
|
||||
base_query = {'o1': 'anywords',
|
||||
'f1': 'blocked',
|
||||
'query_format': 'advanced',
|
||||
'extra_fields': ['flags'],
|
||||
'limit': BUGZILLA_QUERY_LIMIT}
|
||||
|
||||
class BZInterfaceError(Exception):
|
||||
"""A custom wrapper for XMLRPC errors from Bugzilla"""
|
||||
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.msg)
|
||||
|
||||
|
||||
class BlockerBugs():
|
||||
"""The main class for querying Bugzilla"""
|
||||
|
||||
def __init__(self, user: Optional[str] = None, password: Optional[str] = None,
|
||||
url: Optional[str] = 'https://bugzilla.redhat.com/xmlrpc.cgi',
|
||||
bz: Optional[bugzilla.Bugzilla] = None,
|
||||
logger: Optional[logging.Logger] = None) -> None:
|
||||
""":param user: Username to log in as. Use `None` for anonymous access.
|
||||
:param password: User password. Use `None` for anonymous access.
|
||||
:param url: Bugzilla API url.
|
||||
:param bz: `Bugzilla` instance. Created automatically if not provided. If provided,
|
||||
the `user`, `password` and `url` values are ignored.
|
||||
:param logger: A custom `Logger` instance. Otherwise a default Logger is created.
|
||||
"""
|
||||
self.logger = logger or logging.getLogger('bz_interface')
|
||||
self.bz: bugzilla.Bugzilla = bz
|
||||
if not bz:
|
||||
if not (user and password):
|
||||
self.bz = bugzilla.Bugzilla(url=url,
|
||||
cookiefile=None,
|
||||
tokenfile=None)
|
||||
else:
|
||||
self.bz = bugzilla.Bugzilla(url=url,
|
||||
user=user,
|
||||
password=password,
|
||||
cookiefile=None,
|
||||
tokenfile=None)
|
||||
self.logger.info('Using bugzilla URL: %s' % url)
|
||||
|
||||
# https://bugzilla.stage.redhat.com/buglist.cgi?bug_status=NEW&bug_status=ASSIGNED
|
||||
# &bug_status=POST&bug_status=MODIFIED&classification=Fedora&component=anaconda&f1=component
|
||||
# &o1=changedafter&product=Fedora&query_format=advanced&v1=2013-03-21%2012%3A25&version=19
|
||||
def get_bz_query(self, tracker: int, last_update: datetime.datetime = None, offset: int = 0
|
||||
) -> dict[str, Any]:
|
||||
"""Build a Bugzilla query to retrieve all necessary info about all bugs which block the
|
||||
`tracker` bug.
|
||||
|
||||
:param last_update: If provided, the query is modified to ask only about bugs which have
|
||||
recent modifications; otherwise asks about all bugs.
|
||||
:offset: offset to the query instead of just getting the first N results
|
||||
:returns: a dict which can be fed into `bugzilla.Bugzilla.query()`.
|
||||
"""
|
||||
query = {}
|
||||
query.update(base_query)
|
||||
query['v1'] = str(tracker)
|
||||
|
||||
if last_update:
|
||||
last_update_string = last_update.strftime("%Y-%m-%d %H:%M GMT")
|
||||
|
||||
# Since this is kind of confusing, the idea here is to only grab only
|
||||
# the bugs whose whiteboard, blocks or dependson has changed since
|
||||
# the last sync or any bugs that have been created in the last day.
|
||||
|
||||
# keep in mind that OP -> open parenthesis, CP -> closed parenthesis
|
||||
# and the OR is to make sure that a bug is returned if ANY of the
|
||||
# changed after conditions are met
|
||||
|
||||
# FIXME: This seems to be tremendously slowing down Bugzilla5
|
||||
# Related: https://pagure.io/fedora-qa/blockerbugs/issue/184
|
||||
query.update({
|
||||
'f2': 'OP',
|
||||
'j2': 'OR',
|
||||
'f3': 'blocked',
|
||||
'o3': 'changedafter',
|
||||
'v3': last_update_string,
|
||||
'f4': 'status_whiteboard',
|
||||
'o4': 'changedafter',
|
||||
'v4': last_update_string,
|
||||
'f5': 'bug_status',
|
||||
'o5': 'changedafter',
|
||||
'v5': last_update_string,
|
||||
'f6': 'dependson',
|
||||
'o6': 'changedafter',
|
||||
'v6': last_update_string,
|
||||
'f7': 'creation_ts',
|
||||
'o7': 'greaterthaneq',
|
||||
'v7': last_update_string,
|
||||
'f8': 'short_desc',
|
||||
'o8': 'changedafter',
|
||||
'v8': last_update_string,
|
||||
'f9': 'component',
|
||||
'o9': 'changedafter',
|
||||
'v9': last_update_string,
|
||||
'f10': 'CP'
|
||||
})
|
||||
|
||||
# update query with the offset to use, no change in behavior if the default 0 is used
|
||||
query.update({'offset': offset})
|
||||
|
||||
return query
|
||||
|
||||
def query_tracker(self, tracker: int, last_update: Optional[datetime.datetime] = None
|
||||
) -> list[bzBug]:
|
||||
"""Perform a Bugzilla query and retrieve all necessary info about all bugs which block the
|
||||
`tracker` bug (i.e. Blocker or FE bugs).
|
||||
|
||||
:param last_update: If provided, the query is modified to ask only about bugs which have
|
||||
recent modifications; otherwise asks about all bugs.
|
||||
"""
|
||||
|
||||
buglist = []
|
||||
last_query_len = BUGZILLA_QUERY_LIMIT
|
||||
|
||||
|
||||
# this is a hotfix hack to work around the sudden config change in rhbz where the max
|
||||
# number of bugs returned for a query is 20
|
||||
# it seems to be working for now but may need more work going forward
|
||||
while last_query_len == BUGZILLA_QUERY_LIMIT:
|
||||
|
||||
new_query = self.get_bz_query(tracker, last_update, offset=len(buglist))
|
||||
new_buglist = self.bz.query(new_query)
|
||||
buglist.extend(new_buglist)
|
||||
last_query_len = len(new_buglist)
|
||||
|
||||
return buglist
|
||||
|
||||
def query_prioritized(self) -> list[bzBug]:
|
||||
"""Perform a Bugzilla query and retrieve all necessary info about all Prioritized bugs."""
|
||||
# https://bugzilla.redhat.com/buglist.cgi?bug_status=__open__&
|
||||
# f1=flagtypes.name&o1=substring&query_format=advanced&v1=fedora_prioritized_bug%2B
|
||||
query = self.bz.url_to_query(
|
||||
"{}buglist.cgi?bug_status=__open__&f1=flagtypes.name&o1=substring"
|
||||
"&query_format=advanced&v1=fedora_prioritized_bug%2B".format(
|
||||
app.config['BUGZILLA_URL']))
|
||||
buglist = self.bz.query(query)
|
||||
return buglist
|
||||
|
||||
def get_deps(self, bugid: int) -> list[int]:
|
||||
"""Retrieve bug dependencies.
|
||||
|
||||
:returns: A list of ticket numbers which `bugid` depends on.
|
||||
"""
|
||||
return self.bz.getbug(bugid).dependson
|
||||
|
||||
|
||||
class BlockerProposal():
|
||||
def __init__(self, bz, bugid, trackers, is_blocker=False, is_fe=False, bz_user=''):
|
||||
self.bz = bz
|
||||
self.bugid = bugid
|
||||
self.trackers = trackers
|
||||
self.is_blocker = is_blocker
|
||||
self.is_fe = is_fe
|
||||
self.bz_user = bz_user
|
||||
self.bugid_ok = False
|
||||
self.proposal_ok = False
|
||||
self.bugdata = None
|
||||
self.blocks = None
|
||||
|
||||
self.log = logging.getLogger('bz_interface.bugproposal')
|
||||
|
||||
def get_bugdata(self):
|
||||
# get bug data, will raise XMLRPC fault if the bug does not exist
|
||||
try:
|
||||
self.bugdata = self.bz.getbug(self.bugid)
|
||||
except Fault as e:
|
||||
if e.faultCode == 101:
|
||||
raise BZInterfaceError(e.faultString)
|
||||
else:
|
||||
raise
|
||||
|
||||
def get_tracker_type(self):
|
||||
if self.is_blocker and self.is_fe:
|
||||
return 'Blocker and Freeze Exception'
|
||||
if self.is_blocker:
|
||||
return 'Blocker'
|
||||
if self.is_fe:
|
||||
return 'Freeze Exception'
|
||||
if self.is_prioritized:
|
||||
return 'PrioritizedBug'
|
||||
|
||||
def propose_bugs(self, bz_user, milestone_name, justification):
|
||||
comment = ['Proposed as a', self.get_tracker_type(), 'for', milestone_name, 'by',
|
||||
bz_user, 'using the blocker tracking app because:\n\n',
|
||||
justification]
|
||||
tracker_bugs = []
|
||||
if self.is_blocker:
|
||||
tracker_bugs.append(self.trackers['blocker'])
|
||||
if self.is_fe:
|
||||
tracker_bugs.append(self.trackers['fe'])
|
||||
self.log.info('comment: %s' % comment)
|
||||
try:
|
||||
self._do_proposal(tracker_bugs, self.bugid, ' '.join(comment), self.bz_user)
|
||||
except Fault as e:
|
||||
if e.faultCode == 51:
|
||||
# bugzilla account does not exist, this should happen very rarely, if ever
|
||||
# so just redo the call with nothing to add to cc
|
||||
# TODO - it might be useful to ask the user to re-associate accounts again here
|
||||
self._do_proposal(tracker_bugs, self.bugid, ' '.join(comment), '')
|
||||
else:
|
||||
raise BZInterfaceError(e.faultString)
|
||||
|
||||
def _do_proposal(self, tracker, proposed_bugid, comment, bz_user):
|
||||
bug_update = self.bz.build_update(blocks_add=tracker,
|
||||
cc_add=[bz_user],
|
||||
comment=comment)
|
||||
self.bz.update_bugs(proposed_bugid, bug_update)
|
||||
|
||||
def check_proposed_bug(self):
|
||||
if not self.bugdata:
|
||||
self.get_bugdata()
|
||||
|
||||
# check to make sure that bug is not already CLOSED
|
||||
bug_status = self.bugdata.bug_status
|
||||
if 'CLOSED' in bug_status.split():
|
||||
raise BZInterfaceError('Bug %i is CLOSED: %s' % (self.bugid, bug_status))
|
||||
|
||||
def get_blocks(self):
|
||||
if not self.bugdata:
|
||||
self.get_bugdata()
|
||||
self.blocks = self.bugdata.blocked
|
||||
|
||||
def check_blocker_proposal(self):
|
||||
if not self.blocks:
|
||||
self.get_blocks()
|
||||
|
||||
if self.trackers['blocker'] in self.blocks:
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_fe_proposal(self):
|
||||
if not self.blocks:
|
||||
self.get_blocks()
|
||||
|
||||
if self.trackers['fe'] in self.blocks:
|
||||
return False
|
||||
return True
|
|
@ -77,3 +77,10 @@
|
|||
tags:
|
||||
- config
|
||||
- blockerbugs
|
||||
|
||||
# this is a "short-term" patch that won't be needed for future versions of blockerbugs
|
||||
- name: patch bz_interface to work with rhbz changes
|
||||
copy: src=20210914-patched-bz_interface.py dest=/usr/lib/python3.9/site-packages/blockerbugs/utils/bz_interface.py
|
||||
when: master_blockerbugs_node
|
||||
tags:
|
||||
- blockerbugs
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue