Merge pull request #785 from coolo/advanced_ttm
Have TTM work through openqa comments
This commit is contained in:
commit
75bd6dffaf
@ -19,6 +19,8 @@ import signal
|
||||
import time
|
||||
|
||||
from xml.etree import cElementTree as ET
|
||||
from pprint import pprint
|
||||
from openqa_client.client import OpenQA_Client
|
||||
|
||||
import osc
|
||||
|
||||
@ -31,6 +33,7 @@ from osclib.conf import Config
|
||||
from osclib.stagingapi import StagingAPI
|
||||
from osc.core import makeurl
|
||||
|
||||
ISSUE_FILE = 'issues_to_ignore'
|
||||
|
||||
# QA Results
|
||||
QA_INPROGRESS = 1
|
||||
@ -39,13 +42,20 @@ QA_PASSED = 3
|
||||
|
||||
|
||||
class ToTestBase(object):
|
||||
|
||||
"""Base class to store the basic interface"""
|
||||
|
||||
def __init__(self, project, dryrun=False):
|
||||
self.project = project
|
||||
self.dryrun = dryrun
|
||||
self.api = StagingAPI(osc.conf.config['apiurl'], project='openSUSE:%s' % project)
|
||||
self.known_failures = self.known_failures_from_dashboard(project)
|
||||
self.api = StagingAPI(
|
||||
osc.conf.config['apiurl'], project='openSUSE:%s' % project)
|
||||
self.openqa = OpenQA_Client(server='https://openqa.opensuse.org')
|
||||
self.issues_to_ignore = []
|
||||
if os.path.isfile(ISSUE_FILE):
|
||||
with open(ISSUE_FILE, 'r') as f:
|
||||
for line in f.readlines():
|
||||
self.issues_to_ignore.append(line.strip())
|
||||
|
||||
def openqa_group(self):
|
||||
return self.project
|
||||
@ -85,7 +95,6 @@ class ToTestBase(object):
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def ftp_build_version(self, project, tree):
|
||||
for binary in self.binaries_of_product('openSUSE:%s' % project, tree):
|
||||
result = re.match(r'openSUSE.*Build(.*)-Media1.report', binary)
|
||||
@ -119,7 +128,8 @@ class ToTestBase(object):
|
||||
|
||||
"""
|
||||
|
||||
url = makeurl('https://openqa.opensuse.org', ['api', 'v1', 'jobs'], { 'group': self.openqa_group(), 'build': snapshot } )
|
||||
url = makeurl('https://openqa.opensuse.org',
|
||||
['api', 'v1', 'jobs'], {'group': self.openqa_group(), 'build': snapshot})
|
||||
f = self.api.retried_GET(url)
|
||||
jobs = []
|
||||
for job in json.load(f)['jobs']:
|
||||
@ -146,7 +156,8 @@ class ToTestBase(object):
|
||||
if 'fatal' in flags or 'important' in flags:
|
||||
return module['name']
|
||||
break
|
||||
logger.info('%s %s %s'%(module['name'], module['result'], module['flags']))
|
||||
logger.info('%s %s %s' %
|
||||
(module['name'], module['result'], module['flags']))
|
||||
|
||||
def overall_result(self, snapshot):
|
||||
"""Analyze the openQA jobs of a given snapshot Returns a QAResult"""
|
||||
@ -162,23 +173,49 @@ class ToTestBase(object):
|
||||
|
||||
number_of_fails = 0
|
||||
in_progress = False
|
||||
machines = []
|
||||
for job in jobs:
|
||||
machines.append(job['settings']['MACHINE'])
|
||||
# print json.dumps(job, sort_keys=True, indent=4)
|
||||
if job['result'] in ('failed', 'incomplete', 'skipped', 'user_cancelled', 'obsoleted'):
|
||||
jobname = job['name']
|
||||
# Record machines we have tests for
|
||||
if jobname in self.known_failures:
|
||||
logger.debug("known failure %s ignored", jobname)
|
||||
self.known_failures.remove(jobname)
|
||||
continue
|
||||
number_of_fails += 1
|
||||
# print json.dumps(job, sort_keys=True, indent=4), jobname
|
||||
failedmodule = self.find_failed_module(job['modules'])
|
||||
url = 'https://openqa.opensuse.org/tests/%s' % job['id']
|
||||
logger.info("job %s failed %s, see %s", jobname, 'early' if failedmodule is None else failedmodule, url)
|
||||
# if number_of_fails < 3: continue
|
||||
logger.info("job %s failed, see %s", jobname, url)
|
||||
url = makeurl('https://openqa.opensuse.org',
|
||||
['api', 'v1', 'jobs', str(job['id']), 'comments'])
|
||||
f = self.api.retried_GET(url)
|
||||
comments = json.load(f)
|
||||
refs = set()
|
||||
#pprint(comments)
|
||||
labeled = 0
|
||||
to_ignore = False
|
||||
for comment in comments:
|
||||
for ref in comment['bugrefs']:
|
||||
refs.add(str(ref))
|
||||
if comment['userName'] == 'ttm' and comment['text'] == 'label:unknown_failure':
|
||||
labeled = comment['id']
|
||||
if comment['text'].find('@ttm ignore') >= 0:
|
||||
to_ignore = True
|
||||
ignored = True
|
||||
for ref in refs:
|
||||
if not ref in self.issues_to_ignore:
|
||||
if to_ignore:
|
||||
self.issues_to_ignore.append(ref)
|
||||
with open(ISSUE_FILE, 'a') as f:
|
||||
f.write("%s\n" % ref)
|
||||
else:
|
||||
ignored = False
|
||||
if not ignored:
|
||||
number_of_fails += 1
|
||||
if not labeled:
|
||||
data = {'text': 'label:unknown_failure'}
|
||||
self.openqa.openqa_request(
|
||||
'POST', 'jobs/%s/comments' % job['id'], data=data)
|
||||
elif labeled:
|
||||
# remove flag - unfortunately can't delete comment unless admin
|
||||
data = {'text': 'Ignored issue'}
|
||||
self.openqa.openqa_request(
|
||||
'PUT', 'jobs/%s/comments/%d' % (job['id'], labeled), data=data)
|
||||
|
||||
elif job['result'] == 'passed' or job['result'] == 'softfailed':
|
||||
continue
|
||||
elif job['result'] == 'none':
|
||||
@ -193,11 +230,6 @@ class ToTestBase(object):
|
||||
if in_progress:
|
||||
return QA_INPROGRESS
|
||||
|
||||
machines = list(set(machines))
|
||||
for item in machines:
|
||||
for item2 in self.known_failures:
|
||||
if item2.split('@')[1] == item:
|
||||
logger.info('now passing %s'%item2)
|
||||
return QA_PASSED
|
||||
|
||||
def all_repos_done(self, project, codes=None):
|
||||
@ -210,7 +242,8 @@ class ToTestBase(object):
|
||||
# sufficient here, so don't try to add it :-)
|
||||
codes = ['published', 'unpublished'] if not codes else codes
|
||||
|
||||
url = self.api.makeurl(['build', project, '_result'], {'code': 'failed'})
|
||||
url = self.api.makeurl(
|
||||
['build', project, '_result'], {'code': 'failed'})
|
||||
f = self.api.retried_GET(url)
|
||||
root = ET.parse(f).getroot()
|
||||
ready = True
|
||||
@ -223,10 +256,12 @@ class ToTestBase(object):
|
||||
if repo.get('arch') in ('armv6l', 'armv7l'):
|
||||
continue
|
||||
if repo.get('dirty', '') == 'true':
|
||||
logger.info('%s %s %s -> %s'%(repo.get('project'), repo.get('repository'), repo.get('arch'), 'dirty'))
|
||||
logger.info('%s %s %s -> %s' % (repo.get('project'),
|
||||
repo.get('repository'), repo.get('arch'), 'dirty'))
|
||||
ready = False
|
||||
if repo.get('code') not in codes:
|
||||
logger.info('%s %s %s -> %s'%(repo.get('project'), repo.get('repository'), repo.get('arch'), repo.get('code')))
|
||||
logger.info('%s %s %s -> %s' % (repo.get('project'),
|
||||
repo.get('repository'), repo.get('arch'), repo.get('code')))
|
||||
ready = False
|
||||
return ready
|
||||
|
||||
@ -267,7 +302,8 @@ class ToTestBase(object):
|
||||
for repo in root.findall('result'):
|
||||
status = repo.find('status')
|
||||
if status.get('code') != 'succeeded':
|
||||
logger.info('%s %s %s %s -> %s'%(project, package, repository, arch, status.get('code')))
|
||||
logger.info(
|
||||
'%s %s %s %s -> %s' % (project, package, repository, arch, status.get('code')))
|
||||
return False
|
||||
|
||||
maxsize = self.maxsize_for_package(package)
|
||||
@ -282,7 +318,8 @@ class ToTestBase(object):
|
||||
continue
|
||||
isosize = int(binary.get('size', 0))
|
||||
if isosize > maxsize:
|
||||
logger.error('%s %s %s %s: %s'%(project, package, repository, arch, 'too large by %s bytes' % (isosize-maxsize)))
|
||||
logger.error('%s %s %s %s: %s' % (
|
||||
project, package, repository, arch, 'too large by %s bytes' % (isosize - maxsize)))
|
||||
return False
|
||||
|
||||
return True
|
||||
@ -334,26 +371,31 @@ class ToTestBase(object):
|
||||
release = 'Snapshot%s' % snapshot if snapshot else None
|
||||
logger.info('Updating snapshot %s' % snapshot)
|
||||
if not self.dryrun:
|
||||
self.api.switch_flag_in_prj('openSUSE:%s:ToTest' % self.project, flag='publish', state='disable')
|
||||
self.api.switch_flag_in_prj(
|
||||
'openSUSE:%s:ToTest' % self.project, flag='publish', state='disable')
|
||||
|
||||
for product in self.ftp_products:
|
||||
self.release_package('openSUSE:%s' % self.project, product)
|
||||
|
||||
for cd in self.livecd_products:
|
||||
self.release_package('openSUSE:%s:Live' % self.project, cd, set_release=release)
|
||||
self.release_package('openSUSE:%s:Live' %
|
||||
self.project, cd, set_release=release)
|
||||
|
||||
for cd in self.main_products:
|
||||
self.release_package('openSUSE:%s' % self.project, cd, set_release=release)
|
||||
self.release_package(
|
||||
'openSUSE:%s' % self.project, cd, set_release=release)
|
||||
|
||||
def publish_factory_totest(self):
|
||||
logger.info('Publish ToTest')
|
||||
if not self.dryrun:
|
||||
self.api.switch_flag_in_prj('openSUSE:%s:ToTest' % self.project, flag='publish', state='enable')
|
||||
self.api.switch_flag_in_prj(
|
||||
'openSUSE:%s:ToTest' % self.project, flag='publish', state='enable')
|
||||
|
||||
def totest_is_publishing(self):
|
||||
"""Find out if the publishing flag is set in totest's _meta"""
|
||||
|
||||
url = self.api.makeurl(['source', 'openSUSE:%s:ToTest' % self.project, '_meta'])
|
||||
url = self.api.makeurl(
|
||||
['source', 'openSUSE:%s:ToTest' % self.project, '_meta'])
|
||||
f = self.api.retried_GET(url)
|
||||
root = ET.parse(f).getroot()
|
||||
if not root.find('publish'): # default true
|
||||
@ -371,9 +413,11 @@ class ToTestBase(object):
|
||||
new_snapshot = self.current_version()
|
||||
|
||||
current_result = self.overall_result(current_snapshot)
|
||||
current_qa_version = self.api.load_file_content("%s:Staging" % self.api.project, "dashboard", "version_totest")
|
||||
current_qa_version = self.api.load_file_content(
|
||||
"%s:Staging" % self.api.project, "dashboard", "version_totest")
|
||||
|
||||
logger.info('current_snapshot %s: %s'%(current_snapshot, self._result2str(current_result)))
|
||||
logger.info('current_snapshot %s: %s' %
|
||||
(current_snapshot, self._result2str(current_result)))
|
||||
logger.debug('new_snapshot %s', new_snapshot)
|
||||
logger.debug('current_qa_version %s', current_qa_version)
|
||||
|
||||
@ -387,7 +431,8 @@ class ToTestBase(object):
|
||||
can_release = False
|
||||
elif not self.all_repos_done('openSUSE:%s:ToTest' % self.project):
|
||||
logger.debug("not all repos done, can't release")
|
||||
# the repos have to be done, otherwise we better not touch them with a new release
|
||||
# the repos have to be done, otherwise we better not touch them
|
||||
# with a new release
|
||||
can_release = False
|
||||
|
||||
can_publish = (current_result == QA_PASSED)
|
||||
@ -404,7 +449,8 @@ class ToTestBase(object):
|
||||
can_release = False # we have to wait
|
||||
else:
|
||||
# We reached a very bad status: openQA testing is 'done', but not of the same version
|
||||
# currently in :ToTest. This can happen when 'releasing' the product failed
|
||||
# currently in :ToTest. This can happen when 'releasing' the
|
||||
# product failed
|
||||
raise Exception("Publishing stopped: tested version (%s) does not match :ToTest version (%s)"
|
||||
% (current_qa_version, current_snapshot))
|
||||
|
||||
@ -416,25 +462,13 @@ class ToTestBase(object):
|
||||
new_snapshot = self.current_version()
|
||||
self.update_totest(new_snapshot)
|
||||
|
||||
def known_failures_from_dashboard(self, project):
|
||||
known_failures = []
|
||||
if self.project == "Factory:PowerPC" or self.project == "Factory:zSystems":
|
||||
project = "Factory"
|
||||
else:
|
||||
project = self.project
|
||||
|
||||
url = self.api.makeurl(['source', 'openSUSE:%s:Staging' % project, 'dashboard', 'known_failures'])
|
||||
f = self.api.retried_GET(url)
|
||||
for line in f:
|
||||
if not line[0] == '#':
|
||||
known_failures.append(line.strip())
|
||||
return known_failures
|
||||
|
||||
def write_version_to_dashboard(self, target, version):
|
||||
if not self.dryrun:
|
||||
url = self.api.makeurl(['source', 'openSUSE:%s:Staging' % self.project, 'dashboard', 'version_%s' % target])
|
||||
url = self.api.makeurl(
|
||||
['source', 'openSUSE:%s:Staging' % self.project, 'dashboard', 'version_%s' % target])
|
||||
osc.core.http_PUT(url + '?comment=Update+version', data=version)
|
||||
|
||||
|
||||
class ToTestFactory(ToTestBase):
|
||||
main_products = ['_product:openSUSE-dvd5-dvd-i586',
|
||||
'_product:openSUSE-dvd5-dvd-x86_64',
|
||||
@ -486,6 +520,7 @@ class ToTestFactoryPowerPC(ToTestBase):
|
||||
def jobs_num(self):
|
||||
return 4
|
||||
|
||||
|
||||
class ToTestFactoryzSystems(ToTestBase):
|
||||
main_products = ['_product:openSUSE-dvd5-dvd-s390x',
|
||||
'_product:openSUSE-cd-mini-s390x']
|
||||
@ -509,6 +544,7 @@ class ToTestFactoryzSystems(ToTestBase):
|
||||
def jobs_num(self):
|
||||
return 1
|
||||
|
||||
|
||||
class ToTestFactoryARM(ToTestFactory):
|
||||
main_products = ['_product:openSUSE-cd-mini-aarch64',
|
||||
'_product:openSUSE-dvd5-dvd-aarch64']
|
||||
@ -529,6 +565,7 @@ class ToTestFactoryARM(ToTestFactory):
|
||||
def jobs_num(self):
|
||||
return 2
|
||||
|
||||
|
||||
class ToTest423(ToTestBase):
|
||||
main_products = [
|
||||
'_product:openSUSE-cd-mini-x86_64',
|
||||
@ -570,7 +607,9 @@ class ToTest423(ToTestBase):
|
||||
# omit snapshot, we don't want to rename on release
|
||||
super(ToTest423, self).update_totest()
|
||||
|
||||
|
||||
class CommandlineInterface(cmdln.Cmdln):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cmdln.Cmdln.__init__(self, args, kwargs)
|
||||
|
||||
@ -587,7 +626,8 @@ class CommandlineInterface(cmdln.Cmdln):
|
||||
parser.add_option("--dry", action="store_true", help="dry run")
|
||||
parser.add_option("--debug", action="store_true", help="debug output")
|
||||
parser.add_option("--verbose", action="store_true", help="verbose")
|
||||
parser.add_option("--osc-debug", action="store_true", help="osc debug output")
|
||||
parser.add_option(
|
||||
"--osc-debug", action="store_true", help="osc debug output")
|
||||
return parser
|
||||
|
||||
def postoptparse(self):
|
||||
@ -611,7 +651,8 @@ class CommandlineInterface(cmdln.Cmdln):
|
||||
Config('openSUSE:%s' % project)
|
||||
|
||||
if project not in self.totest_class:
|
||||
msg = 'Project %s not recognized. Possible values [%s]' % (project, ', '.join(self.totest_class))
|
||||
msg = 'Project %s not recognized. Possible values [%s]' % (
|
||||
project, ', '.join(self.totest_class))
|
||||
raise cmdln.CmdlnUserError(msg)
|
||||
|
||||
return self.totest_class[project](project, self.options.dry)
|
||||
@ -625,6 +666,7 @@ class CommandlineInterface(cmdln.Cmdln):
|
||||
"""
|
||||
|
||||
class ExTimeout(Exception):
|
||||
|
||||
"""raised on timeout"""
|
||||
|
||||
if opts.interval:
|
||||
@ -641,14 +683,16 @@ class CommandlineInterface(cmdln.Cmdln):
|
||||
|
||||
if opts.interval:
|
||||
if os.isatty(0):
|
||||
logger.info("sleeping %d minutes. Press enter to check now ..."%opts.interval)
|
||||
logger.info(
|
||||
"sleeping %d minutes. Press enter to check now ..." % opts.interval)
|
||||
signal.alarm(opts.interval * 60)
|
||||
try:
|
||||
raw_input()
|
||||
except ExTimeout:
|
||||
pass
|
||||
signal.alarm(0)
|
||||
logger.info("recheck at %s"%datetime.datetime.now().isoformat())
|
||||
logger.info("recheck at %s" %
|
||||
datetime.datetime.now().isoformat())
|
||||
else:
|
||||
logger.info("sleeping %d minutes." % opts.interval)
|
||||
time.sleep(opts.interval * 60)
|
||||
|
Loading…
x
Reference in New Issue
Block a user