Skip to content

Commit 6142b2f

Browse files
committed
Allow scheduling products for product increments
* Determine scheduling parameters (`DISTRI`, `VERSION`, `FLAVOR`, `ARCH`, `BUILD`) dynamically from the files in the directory listing of the download repository of the OBS/IBS project targeted by the product increment request * No longer rely on hard-coded parameters except for the OBS/IBS project * Change the meaning of the CLI parameters to act as filters * Add additional flag to create a scheduled product if no jobs have been created yet Related ticket: https://progress.opensuse.org/issues/190251
1 parent af74802 commit 6142b2f

File tree

4 files changed

+199
-40
lines changed

4 files changed

+199
-40
lines changed

openqabot/args.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -242,46 +242,56 @@ def get_parser():
242242

243243
cmdincrementapprove = commands.add_parser(
244244
"increment-approve",
245-
help="Approve the specified product increment if tests passed",
245+
help="Approve the most recent product increment for an OBS project if tests passed",
246246
)
247247
cmdincrementapprove.add_argument(
248248
"--obs-project",
249249
required=False,
250250
type=str,
251251
default="SUSE:SLFO:Products:SLES:16.0:TEST",
252-
help="The project on OBS",
252+
help="The project on OBS to monitor, schedule jobs for (if --schedule is specified) and approve (if all tests passd)",
253253
)
254254
cmdincrementapprove.add_argument(
255255
"--distri",
256256
required=False,
257257
type=str,
258-
default="sle",
259-
help="The DISTRI parameter of relevant scheduled products on openQA",
258+
default="any",
259+
help="Monitor and schedule only products with the specified DISTRI parameter",
260260
)
261261
cmdincrementapprove.add_argument(
262262
"--version",
263263
required=False,
264264
type=str,
265-
default="15.99",
266-
help="The VERSION parameter of relevant scheduled products on openQA",
265+
default="any",
266+
help="Monitor and schedule only products with the specified VERSION parameter",
267267
)
268268
cmdincrementapprove.add_argument(
269269
"--flavor",
270270
required=False,
271271
type=str,
272-
default="Online-Increments",
273-
help="The FLAVOR parameter of relevant scheduled products on openQA",
272+
default="any",
273+
help="Monitor and schedule only products with the specified FLAVOR parameter",
274+
)
275+
cmdincrementapprove.add_argument(
276+
"--schedule",
277+
action="store_true",
278+
help="Schedule a new product (if none exists or if the most recent product has no jobs)",
279+
)
280+
cmdincrementapprove.add_argument(
281+
"--reschedule",
282+
action="store_true",
283+
help="Always schedule a new product (even if one already exists)",
274284
)
275285
cmdincrementapprove.add_argument(
276286
"--accepted",
277287
action="store_true",
278-
help="Consider accepted requests/reviews as well",
288+
help="Consider accepted product increment requests as well",
279289
)
280290
cmdincrementapprove.add_argument(
281291
"--request-id",
282292
required=False,
283293
type=int,
284-
help="Approve the specified request specifically",
294+
help="Check/approve the specified request (instead of the most recent one)",
285295
)
286296
cmdincrementapprove.set_defaults(func=do_increment_approve, no_config=True)
287297

openqabot/incrementapprover.py

Lines changed: 106 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Copyright SUSE LLC
22
# SPDX-License-Identifier: MIT
33
from argparse import Namespace
4-
from typing import Any, Dict, List, Tuple, Optional
4+
from typing import Any, Dict, List, Tuple, Optional, Set, NamedTuple
5+
import re
56
from logging import getLogger
67
from pprint import pformat
78

@@ -10,13 +11,27 @@
1011

1112
from openqabot.openqa import openQAInterface
1213

13-
from . import OBS_GROUP, OBS_URL
14+
from .errors import PostOpenQAError
15+
from .utils import retry10 as requests
16+
from . import OBS_GROUP, OBS_URL, OBS_DOWNLOAD_URL
1417

1518
log = getLogger("bot.increment_approver")
1619
ok_results = set(("passed", "softfailed"))
1720
final_states = set(("done", "cancelled"))
1821

1922

23+
class BuildInfo(NamedTuple):
24+
distri: str
25+
product: str
26+
version: str
27+
flavor: str
28+
arch: str
29+
build: str
30+
31+
def __str__(self):
32+
return f"{self.product}v{self.version} build {self.build}@{self.arch} of flavor {self.flavor}"
33+
34+
2035
class IncrementApprover:
2136
def __init__(self, args: Namespace) -> None:
2237
self.args = args
@@ -35,11 +50,11 @@ def _find_request_on_obs(self) -> Optional[osc.core.Request]:
3550
OBS_GROUP,
3651
args.obs_project,
3752
)
38-
requests = osc.core.get_request_list(
53+
obs_requests = osc.core.get_request_list(
3954
OBS_URL, project=args.obs_project, req_state=relevant_states
4055
)
4156
relevant_request = None
42-
for request in sorted(requests, reverse=True):
57+
for request in sorted(obs_requests, reverse=True):
4358
for review in request.reviews:
4459
if review.by_group == OBS_GROUP and review.state in relevant_states:
4560
relevant_request = request
@@ -54,31 +69,40 @@ def _find_request_on_obs(self) -> Optional[osc.core.Request]:
5469
)
5570
else:
5671
log.debug("Found request %s", relevant_request.id)
72+
if hasattr(relevant_request.state, "to_xml"):
73+
log.debug(relevant_request.to_str())
5774
return relevant_request
5875

59-
def _request_openqa_job_results(self) -> Dict[str, Dict[str, Dict[str, Any]]]:
60-
log.debug("Checking openQA job results")
61-
args = self.args
62-
params = {"distri": args.distri, "version": args.version, "flavor": args.flavor}
76+
def _request_openqa_job_results(
77+
self, build_info: BuildInfo
78+
) -> Dict[str, Dict[str, Dict[str, Any]]]:
79+
log.debug("Checking openQA job results for %s", build_info)
80+
params = {
81+
"distri": build_info.distri,
82+
"version": build_info.version,
83+
"flavor": build_info.flavor,
84+
"arch": build_info.arch,
85+
"build": build_info.build,
86+
}
6387
res = self.client.get_scheduled_product_stats(params)
6488
log.debug("Job statistics:\n%s", pformat(res))
6589
return res
6690

67-
def _are_openqa_jobs_ready(self, res: Dict[str, Dict[str, Dict[str, Any]]]) -> bool:
68-
args = self.args
91+
def _check_openqa_jobs(
92+
self, res: Dict[str, Dict[str, Dict[str, Any]]], build_info: BuildInfo
93+
) -> Optional[bool]:
6994
actual_states = set(res.keys())
7095
pending_states = actual_states - final_states
7196
if len(actual_states) == 0:
7297
log.info(
73-
"Skipping approval, there are no relevant jobs on openQA for %s-%s-%s",
74-
args.distri,
75-
args.version,
76-
args.flavor,
98+
"Skipping approval, there are no relevant jobs on openQA for %s",
99+
build_info,
77100
)
78-
return False
101+
return None
79102
if len(pending_states):
80103
log.info(
81-
"Skipping approval, some jobs on openQA are in pending states (%s)",
104+
"Skipping approval, some jobs on openQA for %s are in pending states (%s)",
105+
build_info,
82106
", ".join(sorted(pending_states)),
83107
)
84108
return False
@@ -126,11 +150,72 @@ def _handle_approval(
126150
log.info(message)
127151
return 0
128152

153+
def _determine_build_info(self) -> Set[BuildInfo]:
154+
# deduce DISTRI, VERSION, FLAVOR, ARCH and BUILD from the spdx files in the repo listing similar to the sync plugin,
155+
# e.g. https://download.suse.de/download/ibs/SUSE:/SLFO:/Products:/SLES:/16.0:/TEST/product/?jsontable=1
156+
path = self.args.obs_project.replace(":", ":/")
157+
url = f"{OBS_DOWNLOAD_URL}/{path}/product/?jsontable=1"
158+
rows = requests.get(url).json().get("data", [])
159+
res = set()
160+
args = self.args
161+
for row in rows:
162+
m = re.search(
163+
"(?P<product>.*)-(?P<version>[^\\-]*?)-(?P<flavor>\\D+[^\\-]*?)-(?P<arch>[^\\-]*?)-Build(?P<build>.*?)\\.spdx.json",
164+
row.get("name", ""),
165+
)
166+
if m:
167+
product = m.group("product")
168+
version = m.group("version")
169+
flavor = m.group("flavor") + "-Increments"
170+
arch = m.group("arch")
171+
build = m.group("build")
172+
if product.startswith("SLE"):
173+
distri = "sle"
174+
else:
175+
continue # skip unknown products
176+
if (
177+
args.distri in ("any", distri)
178+
and args.flavor in ("any", flavor)
179+
and args.version in ("any", version)
180+
):
181+
res.add(BuildInfo(distri, product, version, flavor, arch, build))
182+
return res
183+
184+
def _schedule_openqa_jobs(self, build_info: BuildInfo) -> int:
185+
log.info("Scheduling jobs for %s", build_info)
186+
if self.args.dry:
187+
return 0
188+
try:
189+
self.client.post_job( # create a scheduled product with build info from spdx file
190+
{
191+
"DISTRI": build_info.distri,
192+
"VERSION": build_info.version,
193+
"FLAVOR": build_info.flavor,
194+
"ARCH": build_info.arch,
195+
"BUILD": build_info.build,
196+
}
197+
)
198+
return 0
199+
except PostOpenQAError:
200+
return 1
201+
129202
def __call__(self) -> int:
203+
error_count = 0
130204
request = self._find_request_on_obs()
131205
if request is None:
132-
return 0
133-
res = self._request_openqa_job_results()
134-
if not self._are_openqa_jobs_ready(res):
135-
return 0
136-
return self._handle_approval(request, *(self._evaluate_openqa_job_results(res)))
206+
return error_count
207+
for build_info in self._determine_build_info():
208+
res = self._request_openqa_job_results(build_info)
209+
if self.args.reschedule:
210+
error_count += self._schedule_openqa_jobs(build_info)
211+
continue
212+
openqa_jobs_ready = self._check_openqa_jobs(res, build_info)
213+
if openqa_jobs_ready is None and self.args.schedule:
214+
error_count += self._schedule_openqa_jobs(build_info)
215+
continue
216+
if not openqa_jobs_ready:
217+
continue
218+
error_count += self._handle_approval(
219+
request, *(self._evaluate_openqa_job_results(res))
220+
)
221+
return error_count

responses/test-product-repo.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"data": [
3+
{"name": "SLES-16.0-Online-x86_64-Build139.1.spdx.json"},
4+
{"name": "SLES-16.0-Online-aarch64-Build139.1.spdx.json"},
5+
{"name": "SLES-16.0-Online-s390x-Build139.1.spdx.json"},
6+
{"name": "SLES-16.0-Online-ppc64le-Build139.1.spdx.json"},
7+
{"name": "SLES-SAP-16.0-Online-x86_64-Build139.1.spdx.json"},
8+
{"name": "SLES-SAP-16.0-Online-aarch64-Build139.1.spdx.json"},
9+
{"name": "SLES-SAP-16.0-Online-s390x-Build139.1.spdx.json"},
10+
{"name": "SLES-SAP-16.0-Online-ppc64le-Build139.1.spdx.json"}
11+
]
12+
}

0 commit comments

Comments
 (0)