2020-01-30 15:00:06 +01:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import logging
|
|
|
|
import osc
|
2020-01-30 20:37:36 +01:00
|
|
|
import yaml
|
2022-02-18 17:01:38 +01:00
|
|
|
from osc.core import http_GET, makeurl, show_project_meta
|
2020-01-30 15:00:06 +01:00
|
|
|
from M2Crypto.SSL import SSLError as SSLError
|
2020-01-30 20:37:36 +01:00
|
|
|
from osclib.core import attribute_value_load
|
2020-01-30 15:00:06 +01:00
|
|
|
from lxml import etree as ET
|
|
|
|
from openqa_client.client import OpenQA_Client
|
2022-02-18 17:01:38 +01:00
|
|
|
from urllib.error import HTTPError
|
2020-01-30 21:59:46 +01:00
|
|
|
from datetime import datetime, timezone
|
2020-01-30 15:00:06 +01:00
|
|
|
|
|
|
|
from flask import Flask, render_template
|
|
|
|
|
|
|
|
class Fetcher(object):
|
|
|
|
def __init__(self, apiurl, opts):
|
|
|
|
self.projects = []
|
|
|
|
self.opts = opts
|
|
|
|
self.apiurl = apiurl
|
|
|
|
if apiurl.endswith('suse.de'):
|
|
|
|
openqa_url = 'https://openqa.suse.de'
|
|
|
|
else:
|
|
|
|
openqa_url = 'https://openqa.opensuse.org'
|
2020-01-31 08:34:54 +01:00
|
|
|
self.openqa = OpenQA_Client(openqa_url)
|
|
|
|
|
|
|
|
def openqa_results(self, openqa_group, snapshot):
|
|
|
|
jobs = {}
|
|
|
|
if not openqa_group or not snapshot:
|
|
|
|
return jobs
|
|
|
|
result = self.openqa.openqa_request('GET', 'jobs', {'groupid': openqa_group, 'build': snapshot, 'latest': 1})
|
|
|
|
for job in result['jobs']:
|
|
|
|
if job['clone_id'] or job['result'] == 'obsoleted':
|
|
|
|
continue
|
|
|
|
key = job['result']
|
|
|
|
if job['state'] != 'done':
|
|
|
|
key = job['state']
|
|
|
|
if key == 'uploading' or key == 'assigned':
|
|
|
|
key = 'running'
|
|
|
|
jobs.setdefault(key, []).append(job['name'])
|
|
|
|
return jobs
|
2020-01-30 15:00:06 +01:00
|
|
|
|
2020-01-31 07:41:58 +01:00
|
|
|
def add(self, name, **kwargs):
|
2020-01-30 15:00:06 +01:00
|
|
|
# cyclic dependency!
|
2020-01-31 07:41:58 +01:00
|
|
|
self.projects.append(Project(self, name, kwargs))
|
2020-01-30 15:00:06 +01:00
|
|
|
|
|
|
|
def build_summary(self, project, repository):
|
|
|
|
url = makeurl(self.apiurl, ['build', project, '_result'], { 'repository': repository, 'view': 'summary' })
|
2020-01-31 14:03:23 +01:00
|
|
|
try:
|
|
|
|
f = http_GET(url)
|
2022-02-18 17:01:38 +01:00
|
|
|
except HTTPError:
|
2020-01-31 14:03:23 +01:00
|
|
|
return { 'building': -1 }
|
2020-01-30 15:00:06 +01:00
|
|
|
root = ET.parse(f).getroot()
|
|
|
|
failed = 0
|
|
|
|
unresolvable = 0
|
|
|
|
building = 0
|
|
|
|
succeeded = 0
|
2020-09-03 11:08:22 +02:00
|
|
|
broken = 0
|
2020-01-30 15:00:06 +01:00
|
|
|
for result in root.findall('.//statuscount'):
|
|
|
|
code = result.get('code')
|
|
|
|
count = int(result.get('count'))
|
2020-09-01 18:09:07 +02:00
|
|
|
if code == 'excluded' or code == 'disabled' or code == 'locked':
|
2022-02-18 16:39:16 +01:00
|
|
|
continue # ignore
|
2020-01-30 15:00:06 +01:00
|
|
|
if code == 'succeeded':
|
|
|
|
succeeded += count
|
|
|
|
continue
|
2020-09-03 11:08:22 +02:00
|
|
|
if code == 'broken':
|
|
|
|
broken += count
|
|
|
|
continue
|
2020-01-30 15:00:06 +01:00
|
|
|
if code == "failed":
|
|
|
|
failed += count
|
|
|
|
continue
|
|
|
|
if code == "unresolvable":
|
|
|
|
unresolvable += count
|
|
|
|
continue
|
|
|
|
building += count
|
|
|
|
# let's count them as building
|
|
|
|
if building > 0:
|
|
|
|
building += unresolvable
|
|
|
|
unresolvable = 0
|
2020-01-31 14:03:23 +01:00
|
|
|
if building + failed + succeeded == 0:
|
|
|
|
return {'building': -1}
|
2020-09-21 13:29:29 +02:00
|
|
|
return { 'building': 10000 - int(building * 10000 / (building + failed + succeeded + broken)),
|
2020-01-30 15:00:06 +01:00
|
|
|
'failed': failed,
|
2020-09-03 11:08:22 +02:00
|
|
|
'broken': broken,
|
2020-01-30 15:00:06 +01:00
|
|
|
'unresolvable': unresolvable }
|
|
|
|
|
|
|
|
def generate_all_archs(self, project):
|
|
|
|
meta = ET.fromstringlist(show_project_meta(self.apiurl, project))
|
|
|
|
archs = set()
|
|
|
|
for arch in meta.findall('.//arch'):
|
|
|
|
archs.add(arch.text)
|
|
|
|
result = []
|
|
|
|
for arch in archs:
|
|
|
|
result.append(f"arch_{arch}=1")
|
|
|
|
return '&'.join(result)
|
|
|
|
|
2020-01-30 20:37:36 +01:00
|
|
|
def fetch_ttm_status(self, project):
|
|
|
|
text = attribute_value_load(self.apiurl, project, 'ToTestManagerStatus')
|
|
|
|
if text:
|
|
|
|
return yaml.safe_load(text)
|
|
|
|
return dict()
|
|
|
|
|
|
|
|
def fetch_product_version(self, project):
|
|
|
|
return attribute_value_load(self.apiurl, project, 'ProductVersion')
|
|
|
|
|
2020-01-30 15:00:06 +01:00
|
|
|
class Project(object):
|
2020-01-31 07:41:58 +01:00
|
|
|
def __init__(self, fetcher, name, kwargs):
|
2020-01-30 15:00:06 +01:00
|
|
|
self.fetcher = fetcher
|
|
|
|
self.name = name
|
2020-01-31 07:41:58 +01:00
|
|
|
self.nick = kwargs.get('nick')
|
2020-01-31 07:54:12 +01:00
|
|
|
self.openqa_version = kwargs.get('openqa_version')
|
|
|
|
self.openqa_group = kwargs.get('openqa_group')
|
2020-01-31 08:34:54 +01:00
|
|
|
self.openqa_id = kwargs.get('openqa_groupid')
|
2020-01-31 07:41:58 +01:00
|
|
|
self.download_url = kwargs.get('download_url')
|
2020-01-30 15:00:06 +01:00
|
|
|
self.all_archs = fetcher.generate_all_archs(name)
|
2020-01-30 20:37:36 +01:00
|
|
|
self.ttm_status = fetcher.fetch_ttm_status(name)
|
|
|
|
self.ttm_version = fetcher.fetch_product_version(name)
|
2020-01-30 15:00:06 +01:00
|
|
|
|
2020-01-31 14:03:23 +01:00
|
|
|
def build_summary(self, repo):
|
|
|
|
return fetcher.build_summary(self.name, repo)
|
2020-01-30 15:00:06 +01:00
|
|
|
|
|
|
|
def all_archs(self):
|
|
|
|
self.all_archs
|
|
|
|
|
2020-01-31 08:34:54 +01:00
|
|
|
def openqa_summary(self):
|
|
|
|
return self.fetcher.openqa_results(self.openqa_id, self.ttm_status.get('testing'))
|
|
|
|
|
2020-01-30 15:00:06 +01:00
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description='Bot to sync openQA status to OBS')
|
|
|
|
parser.add_argument("--apiurl", '-A', type=str, help='API URL of OBS')
|
|
|
|
parser.add_argument('-p', '--project', type=str, default='Factory',
|
|
|
|
help='openSUSE version to make the check (Factory, 15.2)')
|
|
|
|
parser.add_argument('-d', '--debug', action='store_true', default=False,
|
|
|
|
help='enable debug information')
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
osc.conf.get_config(override_apiurl = args.apiurl)
|
|
|
|
osc.conf.config['debug'] = args.debug
|
|
|
|
apiurl = osc.conf.config['apiurl']
|
|
|
|
|
|
|
|
fetcher = Fetcher(apiurl, args)
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
2021-12-21 14:34:57 +01:00
|
|
|
if ("Factory" in args.project):
|
|
|
|
fetcher.add('openSUSE:Factory', nick='Factory', download_url='https://download.opensuse.org/tumbleweed/iso/', openqa_group='openSUSE Tumbleweed', openqa_version='Tumbleweed', openqa_groupid=1)
|
|
|
|
fetcher.add('openSUSE:Factory:Live', nick='Live')
|
|
|
|
fetcher.add('openSUSE:Factory:Rings:0-Bootstrap', nick='Ring 0')
|
|
|
|
fetcher.add('openSUSE:Factory:Rings:1-MinimalX', nick='Ring 1')
|
|
|
|
fetcher.add('openSUSE:Factory:ARM', nick='ARM', download_url='http://download.opensuse.org/ports/aarch64/tumbleweed/iso/', openqa_group='openSUSE Tumbleweed AArch64', openqa_version='Tumbleweed', openqa_groupid=3)
|
|
|
|
fetcher.add('openSUSE:Factory:ARM:Live', nick='ARM Live')
|
|
|
|
fetcher.add('openSUSE:Factory:ARM:Rings:0-Bootstrap', nick='ARM Ring 0')
|
|
|
|
fetcher.add('openSUSE:Factory:ARM:Rings:1-MinimalX', nick='ARM Ring 1')
|
|
|
|
fetcher.add('openSUSE:Factory:PowerPC', nick='Power', download_url='http://download.opensuse.org/ports/ppc/tumbleweed/iso/', openqa_group='openSUSE Tumbleweed PowerPC', openqa_version='Tumbleweed', openqa_groupid=4)
|
|
|
|
fetcher.add('openSUSE:Factory:zSystems', nick='System Z', download_url='http://download.opensuse.org/ports/zsystems/tumbleweed/iso/', openqa_group='openSUSE Tumbleweed s390x', openqa_version='Tumbleweed', openqa_groupid=34)
|
|
|
|
fetcher.add('openSUSE:Factory:RISCV', nick='Risc V', download_url='http://download.opensuse.org/ports/riscv/tumbleweed/iso/')
|
|
|
|
else:
|
|
|
|
fetcher.add('openSUSE:Leap:15.4', nick='Leap:15.4', download_url='https://download.opensuse.org/distribution/leap/15.4/iso', openqa_group='openSUSE Leap 15', openqa_version='15.4', openqa_groupid=50)
|
|
|
|
fetcher.add('openSUSE:Backports:SLE-15-SP4', nick='Backports:SLE-15-SP4')
|
|
|
|
fetcher.add('openSUSE:Leap:15.4:Images', nick='Leap:15.4:Images', openqa_group='openSUSE Leap 15.4 Images', openqa_version='15.4', openqa_groupid=89)
|
|
|
|
fetcher.add('openSUSE:Leap:15.4:ARM', nick='Leap:15.4:ARM', download_url='https://download.opensuse.org/ports/armv7hl/distribution/leap/15.4/iso', openqa_group='openSUSE Leap 15.4 ARMv7', openqa_version='15.4', openqa_groupid=92)
|
|
|
|
fetcher.add('openSUSE:Leap:15.4:ARM:Images', nick='Leap:15.4:ARM:Images', openqa_group='openSUSE Leap 15.4 ARMv7 Images', openqa_version='15.4', openqa_groupid=91)
|
|
|
|
fetcher.add('openSUSE:Leap:15.3:Images', nick='Leap:15.3:Images', openqa_group='openSUSE Leap 15.3 Images', openqa_version='15.3', openqa_groupid=77)
|
|
|
|
fetcher.add('openSUSE:Leap:15.3:ARM', nick='Leap:15.3:ARM', download_url='https://download.opensuse.org/ports/armv7hl/distribution/leap/15.3/iso', openqa_group='openSUSE Leap 15 ARM', openqa_version='15.3', openqa_groupid=79)
|
|
|
|
fetcher.add('openSUSE:Leap:15.3:ARM:Images', nick='Leap:15.3:ARM:Images', openqa_group='openSUSE Leap 15.3 ARMv7 Images', openqa_version='15.3', openqa_groupid=83)
|
2020-01-30 15:00:06 +01:00
|
|
|
|
|
|
|
with app.app_context():
|
|
|
|
rendered = render_template('dashboard.html',
|
|
|
|
projectname = args.project,
|
2020-01-30 21:59:46 +01:00
|
|
|
lastupdate = datetime.now(timezone.utc),
|
2020-01-30 15:00:06 +01:00
|
|
|
projects = fetcher.projects)
|
|
|
|
print(rendered)
|