|
|
|
#!/usr/bin/python3
|
|
|
|
# Generate a list of autopkgtest request.cgi URLs to
|
|
|
|
# re-run all autopkgtests which regressed
|
|
|
|
# Copyright (C) 2015-2016 Canonical Ltd.
|
|
|
|
# Author: Martin Pitt <martin.pitt@ubuntu.com>
|
|
|
|
|
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
# This library 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
|
|
|
|
# Lesser General Public License for more details.
|
|
|
|
|
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
|
|
|
# USA
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
import dateutil.parser
|
|
|
|
from dateutil.tz import tzutc
|
|
|
|
import urllib.request
|
|
|
|
import urllib.parse
|
|
|
|
import argparse
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import yaml
|
|
|
|
import json
|
|
|
|
|
|
|
|
request_url = 'https://autopkgtest.ubuntu.com/request.cgi'
|
|
|
|
default_series = 'groovy'
|
|
|
|
args = None
|
|
|
|
|
|
|
|
|
|
|
|
def get_cache_dir():
|
|
|
|
cache_dir = os.environ.get('XDG_CACHE_HOME',
|
|
|
|
os.path.expanduser(os.path.join('~', '.cache')))
|
|
|
|
uat_cache = os.path.join(cache_dir, 'ubuntu-archive-tools')
|
|
|
|
os.makedirs(uat_cache, exist_ok=True)
|
|
|
|
return uat_cache
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args():
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
'Generate %s URLs to re-run regressions' % request_url,
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
|
description='''Typical workflow:
|
|
|
|
- export autopkgtest.ubuntu.com session cookie into ~/.cache/autopkgtest.cookie
|
|
|
|
Use a browser plugin or get the value from the settings and create it with
|
|
|
|
printf "autopkgtest.ubuntu.com\\tTRUE\\t/\\tTRUE\\t0\\tsession\\tVALUE\\n" > ~/.cache/autopkgtest.cookie
|
|
|
|
(The cookie is valid for one month)
|
|
|
|
|
|
|
|
- retry-autopkgtest-regressions [opts...] | vipe | xargs -rn1 -P10 wget --load-cookies ~/.cache/autopkgtest.cookie -O-
|
|
|
|
edit URL list to pick/remove requests as desired, then close editor to let it run
|
|
|
|
''')
|
|
|
|
parser.add_argument('-s', '--series', default=default_series,
|
|
|
|
help='Ubuntu series (default: %(default)s)')
|
|
|
|
parser.add_argument('--bileto', metavar='TICKETNUMBER',
|
|
|
|
help='Run for bileto ticket')
|
|
|
|
parser.add_argument('--all-proposed', action='store_true',
|
|
|
|
help='run tests against all of proposed, i. e. with disabling apt pinning')
|
|
|
|
parser.add_argument('--state', default='REGRESSION',
|
|
|
|
help='generate commands for given test state (default: %(default)s)')
|
|
|
|
parser.add_argument('--max-age', type=float, metavar='DAYS',
|
|
|
|
help='only consider candidates which are at most '
|
|
|
|
'this number of days old (float allowed)')
|
|
|
|
parser.add_argument('--min-age', type=float, metavar='DAYS',
|
|
|
|
help='only consider candidates which are at least '
|
|
|
|
'this number of days old (float allowed)')
|
|
|
|
parser.add_argument('--blocks',
|
|
|
|
help='rerun only those tests that were triggered '
|
|
|
|
'by the named package')
|
|
|
|
parser.add_argument('--no-proposed', action='store_true',
|
|
|
|
help='run tests against release+updates instead of '
|
|
|
|
'against proposed, to re-establish a baseline for the '
|
|
|
|
'test. This currently only works for packages that '
|
|
|
|
'do not themselves have a newer version in proposed.')
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
|
|
|
|
|
|
|
def get_regressions(excuses_url, release, retry_state, min_age, max_age,
|
|
|
|
blocks, no_proposed):
|
|
|
|
'''Return dictionary with regressions
|
|
|
|
|
|
|
|
Return dict: release → pkg → arch → [trigger, ...]
|
|
|
|
'''
|
|
|
|
cache_file = None
|
|
|
|
|
|
|
|
# load YAML excuses
|
|
|
|
|
|
|
|
# ignore bileto urls wrt caching, they're usually too small to matter
|
|
|
|
# and we don't do proper cache expiry
|
|
|
|
m = re.search('people.canonical.com/~ubuntu-archive/proposed-migration/'
|
|
|
|
'([^/]*)/([^/]*)',
|
|
|
|
excuses_url)
|
|
|
|
if m:
|
|
|
|
cache_dir = get_cache_dir()
|
|
|
|
cache_file = os.path.join(cache_dir, '%s_%s' % (m.group(1), m.group(2)))
|
|
|
|
try:
|
|
|
|
prev_mtime = os.stat(cache_file).st_mtime
|
|
|
|
except FileNotFoundError:
|
|
|
|
prev_mtime = 0
|
|
|
|
prev_timestamp = datetime.fromtimestamp(prev_mtime, tz=tzutc())
|
|
|
|
new_timestamp = datetime.now(tz=tzutc()).timestamp()
|
|
|
|
|
|
|
|
f = urllib.request.urlopen(excuses_url)
|
|
|
|
if cache_file:
|
|
|
|
remote_ts = dateutil.parser.parse(f.headers['last-modified'])
|
|
|
|
if remote_ts > prev_timestamp:
|
|
|
|
with open('%s.new' % cache_file, 'wb') as new_cache:
|
|
|
|
for line in f:
|
|
|
|
new_cache.write(line)
|
|
|
|
os.rename('%s.new' % cache_file, cache_file)
|
|
|
|
os.utime(cache_file, times=(new_timestamp, new_timestamp))
|
|
|
|
f.close()
|
|
|
|
f = open(cache_file, 'rb')
|
|
|
|
|
|
|
|
excuses = yaml.load(f, Loader=yaml.CSafeLoader)
|
|
|
|
f.close()
|
|
|
|
regressions = {}
|
|
|
|
for excuse in excuses['sources']:
|
|
|
|
if blocks and blocks != excuse['source']:
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
age = excuse['policy_info']['age']['current-age']
|
|
|
|
except KeyError:
|
|
|
|
age = None
|
|
|
|
|
|
|
|
# excuses are sorted by ascending age
|
|
|
|
if min_age is not None and age is not None and age < min_age:
|
|
|
|
continue
|
|
|
|
if max_age is not None and age is not None and age > max_age:
|
|
|
|
break
|
|
|
|
for pkg, archinfo in excuse.get('policy_info', {}).get('autopkgtest', {}).items():
|
|
|
|
try:
|
|
|
|
pkg, pkg_ver = re.split('[ /]+', pkg, 1) # split off version (either / or space separated)
|
|
|
|
# error and the package version is unknown
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
if no_proposed:
|
|
|
|
trigger = pkg + '/' + pkg_ver
|
|
|
|
else:
|
|
|
|
trigger = excuse['source'] + '/' + excuse['new-version']
|
|
|
|
for arch, state in archinfo.items():
|
|
|
|
if state[0] == retry_state:
|
|
|
|
regressions.setdefault(release, {}).setdefault(
|
|
|
|
pkg, {}).setdefault(arch, []).append(trigger)
|
|
|
|
|
|
|
|
return regressions
|
|
|
|
|
|
|
|
|
|
|
|
args = parse_args()
|
|
|
|
|
|
|
|
extra_params = []
|
|
|
|
if args.all_proposed:
|
|
|
|
extra_params.append(('all-proposed', '1'))
|
|
|
|
|
|
|
|
if args.bileto:
|
|
|
|
url_root = 'https://bileto.ubuntu.com'
|
|
|
|
ticket_url = url_root + '/v2/ticket/%s' % args.bileto
|
|
|
|
excuses_url = None
|
|
|
|
with urllib.request.urlopen(ticket_url) as f:
|
|
|
|
ticket = json.loads(f.read().decode('utf-8'))['tickets'][0]
|
|
|
|
ppa_name = ticket.get('ppa', '')
|
|
|
|
for line in ticket.get('autopkgtest', '').splitlines():
|
|
|
|
if args.series in line:
|
|
|
|
excuses_url = line
|
|
|
|
break
|
|
|
|
if excuses_url.startswith('/'):
|
|
|
|
excuses_url = url_root + excuses_url
|
|
|
|
excuses_url = excuses_url.replace('.html', '.yaml')
|
|
|
|
extra_params += [('ppa', 'ci-train-ppa-service/stable-phone-overlay'),
|
|
|
|
('ppa', 'ci-train-ppa-service/%s' % ppa_name)]
|
|
|
|
else:
|
|
|
|
excuses_url = 'http://people.canonical.com/~ubuntu-archive/proposed-migration/%s/update_excuses.yaml' % args.series
|
|
|
|
regressions = get_regressions(excuses_url, args.series, args.state,
|
|
|
|
args.min_age, args.max_age, args.blocks,
|
|
|
|
args.no_proposed)
|
|
|
|
|
|
|
|
for release, pkgmap in regressions.items():
|
|
|
|
for pkg, archmap in pkgmap.items():
|
|
|
|
for arch, triggers in archmap.items():
|
|
|
|
params = [('release', release), ('arch', arch), ('package', pkg)]
|
|
|
|
params += [('trigger', t) for t in triggers]
|
|
|
|
params += extra_params
|
|
|
|
url = request_url + '?' + urllib.parse.urlencode(params)
|
|
|
|
print(url)
|