Skip to content

Commit 55b5f33

Browse files
author
Jeny Sadadia
committed
Implement command to validate builds
Get builds from dashboard and validate them with maestro result. Command introduced with the commit: `kci-dev validate build --all-checkouts --giturl <URL> \ --commit <commit-hash> --branch <git-branch>` Provide `--all-checkouts` to build validation stats for all available checkouts. Build validation for a specific checkout can be performed by using all three options: --giturl, --branch, and --commit. The command will provide below information: 1. Build count validation 2. Missing build information 3. Build status validation Signed-off-by: Jeny Sadadia <[email protected]>
1 parent 22fbbb2 commit 55b5f33

File tree

7 files changed

+207
-13
lines changed

7 files changed

+207
-13
lines changed

kcidev/libs/maestro_common.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,29 @@ def maestro_get_node(url, nodeid):
6262
return response.json()
6363

6464

65-
def maestro_get_nodes(url, limit, offset, filter):
65+
def maestro_get_nodes(url, limit, offset, filter, paginate):
6666
headers = {
6767
"Content-Type": "application/json; charset=utf-8",
6868
}
69-
url = url + "latest/nodes/fast?limit=" + str(limit) + "&offset=" + str(offset)
70-
maestro_print_api_call(url)
71-
if filter:
72-
for f in filter:
73-
# TBD: We need to add translate filter to API
74-
# if we need anything more complex than eq(=)
75-
url = url + "&" + f
7669

70+
if paginate:
71+
url = url + "latest/nodes/fast?limit=" + str(limit) + "&offset=" + str(offset)
72+
if filter:
73+
for f in filter:
74+
# TBD: We need to add translate filter to API
75+
# if we need anything more complex than eq(=)
76+
url = url + "&" + f
77+
78+
else:
79+
url = url + "latest/nodes/fast"
80+
if filter:
81+
url = url + "?"
82+
for f in filter:
83+
# TBD: We need to add translate filter to API
84+
# if we need anything more complex than eq(=)
85+
url = url + "&" + f
86+
87+
maestro_print_api_call(url)
7788
response = requests.get(url, headers=headers)
7889
try:
7990
response.raise_for_status()

kcidev/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
patch,
1616
results,
1717
testretry,
18+
validate,
1819
watch,
1920
)
2021

@@ -65,6 +66,7 @@ def run():
6566
cli.add_command(maestro_results.maestro_results)
6667
cli.add_command(testretry.testretry)
6768
cli.add_command(results.results)
69+
cli.add_command(validate.validate)
6870
cli.add_command(watch.watch)
6971
cli()
7072

kcidev/subcommands/maestro_results.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,21 @@
4444
multiple=True,
4545
help="Print only particular field(s) from node data",
4646
)
47+
@click.option(
48+
"--count",
49+
is_flag=True,
50+
required=False,
51+
help="Print only count of nodes",
52+
)
53+
@click.option(
54+
"--paginate",
55+
is_flag=True,
56+
required=False,
57+
default=True,
58+
help="Set True if pagination is required in the output. Default is True",
59+
)
4760
@click.pass_context
48-
def maestro_results(ctx, nodeid, nodes, limit, offset, filter, field):
61+
def maestro_results(ctx, nodeid, nodes, limit, offset, filter, field, count, paginate):
4962
config = ctx.obj.get("CFG")
5063
instance = ctx.obj.get("INSTANCE")
5164
url = config[instance]["api"]
@@ -55,7 +68,11 @@ def maestro_results(ctx, nodeid, nodes, limit, offset, filter, field):
5568
if nodeid:
5669
results = maestro_get_node(url, nodeid)
5770
if nodes:
58-
results = maestro_get_nodes(url, limit, offset, filter)
71+
results = maestro_get_nodes(url, limit, offset, filter, paginate)
72+
if count:
73+
kci_msg_cyan("Maestro:")
74+
kci_msg(f"Build count:{len(results)}")
75+
return results
5976
maestro_print_nodes(results, field)
6077

6178

kcidev/subcommands/results/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def summary(origin, git_folder, giturl, branch, commit, latest, arch, use_json):
5959
@results_display_options
6060
def trees(origin, use_json):
6161
"""List trees from a give origin."""
62-
cmd_list_trees(origin, use_json)
62+
return cmd_list_trees(origin, use_json)
6363

6464

6565
@results.command()
@@ -84,7 +84,7 @@ def builds(
8484
origin, giturl, branch, commit, latest, git_folder
8585
)
8686
data = dashboard_fetch_builds(origin, giturl, branch, commit, arch, use_json)
87-
cmd_builds(data, commit, download_logs, status, count, use_json)
87+
return cmd_builds(data, commit, download_logs, status, count, use_json)
8888

8989

9090
@results.command()

kcidev/subcommands/results/parser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def cmd_list_trees(origin, use_json):
135135
kci_msg(f" giturl: {t['git_repository_url']}")
136136
kci_msg(f" latest: {t['git_commit_hash']} ({t['git_commit_name']})")
137137
kci_msg(f" latest: {t['start_time']}")
138+
return trees
138139

139140

140141
def cmd_builds(data, commit, download_logs, status, count, use_json):
@@ -171,9 +172,11 @@ def cmd_builds(data, commit, download_logs, status, count, use_json):
171172
if count and use_json:
172173
kci_msg(f'{{"count":{filtered_builds}}}')
173174
elif count:
174-
kci_msg(filtered_builds)
175+
kci_msg_cyan("Dashboard:")
176+
kci_msg(f"Build count:{filtered_builds}")
175177
elif use_json:
176178
kci_msg(json.dumps(builds))
179+
return data["builds"]
177180

178181

179182
def print_build(build, log_path):
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import click
2+
3+
from kcidev.libs.common import kci_msg_yellow
4+
from kcidev.libs.git_repo import set_giturl_branch_commit
5+
from kcidev.subcommands.results import trees
6+
7+
from .helper import get_build_stats
8+
9+
10+
@click.group(
11+
help="Get results from the dashboard and validate them with maestro",
12+
)
13+
def validate():
14+
"""Commands related to results validation"""
15+
16+
17+
@validate.command()
18+
@click.option(
19+
"--all-checkouts",
20+
is_flag=True,
21+
help="Get build validation stats for all available checkouts",
22+
)
23+
@click.option(
24+
"--origin",
25+
help="Select KCIDB origin",
26+
default="maestro",
27+
)
28+
@click.option(
29+
"--giturl",
30+
help="Git URL of kernel tree",
31+
)
32+
@click.option(
33+
"--branch",
34+
help="Branch to get results for",
35+
)
36+
@click.option(
37+
"--commit",
38+
help="Commit or tag to get results for",
39+
)
40+
@click.option(
41+
"--git-folder",
42+
help="Path of git repository folder",
43+
)
44+
@click.option(
45+
"--latest",
46+
is_flag=True,
47+
help="Select latest results available",
48+
)
49+
@click.option("--arch", help="Filter by arch")
50+
@click.pass_context
51+
def build(ctx, all_checkouts, origin, giturl, branch, commit, git_folder, latest, arch):
52+
"""
53+
Provide --all-checkouts flag to pull all builds of available
54+
checkouts from dashboard and compare them with results from maestro.
55+
56+
Build validation for a specific checkout can be performed by
57+
using all three options: --giturl, --branch, and --commit
58+
If above options are not provided, if will take latest/provided commit
59+
checkout from the git folder specified.
60+
"""
61+
if all_checkouts:
62+
if giturl or branch or commit:
63+
raise click.UsageError(
64+
"Cannot use --all-checkouts with --giturl, --branch, or --commit"
65+
)
66+
trees_list = ctx.invoke(trees, origin=origin)
67+
for tree in trees_list:
68+
giturl = tree["git_repository_url"]
69+
branch = tree["git_repository_branch"]
70+
commit = tree["git_commit_hash"]
71+
kci_msg_yellow(f"URL: {giturl}\nbranch: {branch}\ncommit: {commit}")
72+
get_build_stats(ctx, giturl, branch, commit, arch)
73+
else:
74+
giturl, branch, commit = set_giturl_branch_commit(
75+
origin, giturl, branch, commit, latest, git_folder
76+
)
77+
get_build_stats(ctx, giturl, branch, commit, arch)
78+
79+
80+
if __name__ == "__main__":
81+
main_kcidev()

kcidev/subcommands/validate/helper.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env python3
2+
3+
import click
4+
5+
from kcidev.libs.common import kci_msg_green, kci_msg_red
6+
from kcidev.subcommands.maestro_results import maestro_results
7+
from kcidev.subcommands.results import builds
8+
9+
10+
def get_builds(ctx, giturl, branch, commit, arch=None):
11+
"""Get builds matching git URL, branch, and commit
12+
Architecture can also be provided for filtering"""
13+
maestro_builds = []
14+
dashboard_builds = []
15+
filters = [
16+
"kind=kbuild",
17+
"data.kernel_revision.url=" + giturl,
18+
"data.kernel_revision.branch=" + branch,
19+
"data.kernel_revision.commit=" + commit,
20+
]
21+
if arch:
22+
filters.append("data.arch=" + arch)
23+
maestro_builds = ctx.invoke(
24+
maestro_results,
25+
count=True,
26+
nodes=True,
27+
filter=filters,
28+
paginate=False,
29+
)
30+
try:
31+
dashboard_builds = ctx.invoke(
32+
builds, giturl=giturl, branch=branch, commit=commit, count=True
33+
)
34+
except click.Abort:
35+
kci_msg_red("Aborted while fetching dashboard builds")
36+
return maestro_builds, []
37+
return maestro_builds, dashboard_builds
38+
39+
40+
def find_missing_builds(maestro_builds, dashboard_builds):
41+
"""
42+
Compare build IDs found in maestro with dashboard builds
43+
Return missing builds in dashboard.
44+
"""
45+
missing_builds = []
46+
dashboard_build_id = [b["id"].split(":")[1] for b in dashboard_builds]
47+
for b in maestro_builds:
48+
if b["id"] not in dashboard_build_id:
49+
missing_builds.append(b)
50+
if missing_builds:
51+
print("Missing builds:", missing_builds)
52+
53+
54+
def validate_build_status(maestro_builds, dashboard_builds):
55+
"""
56+
Validate if the build status of dashboard pulled build
57+
matches with the maestro build status
58+
"""
59+
status_map = {
60+
"pass": "PASS",
61+
"fail": "FAIL",
62+
"incomplete": "ERROR",
63+
}
64+
dashboard_build_status_dict = {b["id"]: b["status"] for b in dashboard_builds}
65+
for b in maestro_builds:
66+
build_id = f"maestro:{b['id']}"
67+
if build_id in dashboard_build_status_dict:
68+
if dashboard_build_status_dict[build_id] != status_map.get(b["result"]):
69+
kci_msg_red(f"Build status mismatched: {build_id}")
70+
71+
72+
def get_build_stats(ctx, giturl, branch, commit, arch=None):
73+
"""Get build stats"""
74+
maestro_builds, dashboard_builds = get_builds(ctx, giturl, branch, commit, arch)
75+
if len(dashboard_builds) == len(maestro_builds):
76+
kci_msg_green("Build count matched")
77+
else:
78+
kci_msg_red("Build count didn't match")
79+
find_missing_builds(maestro_builds, dashboard_builds)
80+
validate_build_status(maestro_builds, dashboard_builds)

0 commit comments

Comments
 (0)