Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions kcidev/libs/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import logging
import os
import sys

Expand Down Expand Up @@ -65,6 +66,10 @@ def config_path(settings):
return global_path


def kci_info(content):
logging.info(content)


def kci_msg(content):
click.echo(content)

Expand Down
91 changes: 63 additions & 28 deletions kcidev/libs/maestro_common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import errno
import json
import time

Expand All @@ -11,9 +12,9 @@


def maestro_print_api_call(host, data=None):
click.secho("maestro api endpoint: " + host, fg="green")
kci_info("maestro api endpoint: " + host)
if data:
kci_log(json.dumps(data, indent=4))
kci_info(json.dumps(data, indent=4))


def maestro_api_error(response):
Expand Down Expand Up @@ -53,10 +54,10 @@ def maestro_get_node(url, nodeid):
response.raise_for_status()
except requests.exceptions.HTTPError as ex:
kci_err(ex.response.json().get("detail"))
click.Abort()
sys.exit(errno.ENOENT)
Copy link
Member

@aliceinwire aliceinwire Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why use errno.ENOENT? I thought was referred only to local directory not for HTTPError

ENOENT means "No such file or directory", and is for path operations.

https://github.com/torvalds/linux/blob/5c8c229261f14159b54b9a32f12e5fa89d88b905/include/uapi/asm-generic/errno-base.h#L6

except Exception as ex:
kci_err(ex)
click.Abort()
sys.exit(errno.ENOENT)

return response.json()

Expand All @@ -78,10 +79,10 @@ def maestro_get_nodes(url, limit, offset, filter):
response.raise_for_status()
except requests.exceptions.HTTPError as ex:
kci_err(ex.response.json().get("detail"))
click.Abort()
sys.exit(errno.ENOENT)
except Exception as ex:
kci_err(ex)
click.Abort()
sys.exit(errno.ENOENT)

return response.json()

Expand All @@ -106,7 +107,7 @@ def maestro_check_node(node):
else:
if state == "running":
return "RUNNING"
elif state == "done" and result == "pass":
elif state == "done" and (result == "pass" or result == "fail"):
return "DONE"
else:
return "FAIL"
Expand Down Expand Up @@ -134,17 +135,57 @@ def maestro_retrieve_treeid_nodes(baseurl, token, treeid):
return response.json()


def maestro_node_result(node):
result = node["result"]
if node["kind"] == "checkout":
if (
result == "available"
or result == "closing"
or result == "done"
or result == "pass"
):
kci_msg_green_nonl("PASS")
elif result == None:
kci_msg_green_nonl("PASS")
else:
kci_msg_red_nonl("FAIL")
else:
if node["result"] == "pass":
kci_msg_green_nonl("PASS")
elif node["result"] == "fail":
kci_msg_red_nonl("FAIL")
else:
kci_msg_yellow_nonl(node["result"])

if node["kind"] == "checkout":
kci_msg_nonl(" branch checkout")
else:
kci_msg_nonl(f" {node["kind"]}: ")

if node["kind"] != "checkout":
kci_msg_nonl(f"{node["name"]}")

kci_msg(f" - node_id:{node["id"]} ({node["updated"]})")


def maestro_watch_jobs(baseurl, token, treeid, job_filter, test):
# we need to add to job_filter "checkout" node
job_filter = list(job_filter)
job_filter.append("checkout")
kci_log(f"job_filter: {", ".join(job_filter)}")
previous_nodes = None
running = False

job_info = {}
for job in job_filter:
job_info[job] = {"done": False, "running": False}

while True:
inprogress = 0
joblist = job_filter.copy()
nodes = maestro_retrieve_treeid_nodes(baseurl, token, treeid)
if not nodes:
click.secho("No nodes found. Retrying...", fg="yellow")
kci_warning("No nodes found. Retrying...")
time.sleep(5)
continue
if previous_nodes == nodes:
Expand All @@ -153,10 +194,7 @@ def maestro_watch_jobs(baseurl, token, treeid, job_filter, test):
continue

time_local = time.localtime()
click.echo(f"\nCurrent time: {time.strftime('%Y-%m-%d %H:%M:%S', time_local)}")
click.secho(
f"Total tree nodes {len(nodes)} found. job_filter: {job_filter}", fg="green"
)
kci_info(f"\nCurrent time: {time.strftime('%Y-%m-%d %H:%M:%S', time_local)}")

# Tricky part in watch is that we might have one item in job_filter (job, test),
# but it might spawn multiple nodes with same name
Expand All @@ -166,30 +204,29 @@ def maestro_watch_jobs(baseurl, token, treeid, job_filter, test):
if node["name"] == test:
test_result = node["result"]
if node["name"] in job_filter:
result = maestro_check_node(node)
if result == "DONE":
status = maestro_check_node(node)
if status == "DONE":
if job_info[node["name"]]["running"]:
kci_msg("")
job_info[node["name"]]["running"] = False
if not job_info[node["name"]]["done"]:
maestro_node_result(node)
job_info[node["name"]]["done"] = True
if isinstance(joblist, list) and node["name"] in joblist:
joblist.remove(node["name"])
color = "green"
elif result == "RUNNING":
elif status == "RUNNING":
job_info[node["name"]]["running"] = True
inprogress += 1
color = "yellow"
else:
if isinstance(joblist, list) and node["name"] in joblist:
joblist.remove(node["name"])
color = "red"
# if test is same as job, dont indicate infra-failure if test job fail
if test and test != node["name"]:
# if we have a test, and prior job failed, we should indicate that
kci_err(f"Job {node['name']} failed, test can't be executed")
sys.exit(2)
nodeid = node.get("id")
click.secho(
f"Node: {nodeid} job: {node['name']} State: {node['state']} Result: {node['result']}",
fg=color,
)
if isinstance(joblist, list) and len(joblist) == 0 and inprogress == 0:
click.secho("All jobs completed", fg="green")
kci_info("All jobs completed")
if not test:
return
else:
Expand All @@ -201,13 +238,11 @@ def maestro_watch_jobs(baseurl, token, treeid, job_filter, test):
continue

if test_result and test_result == "pass":
click.secho(f"Test {test} passed", fg="green")
sys.exit(0)
elif test_result:
# ignore null, that means result not ready yet
kci_err(f"Test {test} failed: {test_result}")
sys.exit(1)

kci_msg_nonl(f"\rRefresh every 30s...")
running = True
kci_msg_nonl(f"\rRunning job...")
previous_nodes = nodes
time.sleep(30)
10 changes: 9 additions & 1 deletion kcidev/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import logging

import click

from kcidev.libs.common import *
Expand Down Expand Up @@ -28,8 +30,14 @@
required=False,
)
@click.option("--instance", help="API instance to use", required=False)
@click.option("--debug", is_flag=True, help="Enable debug info")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice to have a debug option

@click.pass_context
def cli(ctx, settings, instance):
def cli(ctx, settings, instance, debug):
if debug:
# DEBUG level is too verbose about included packages
# us INFO instead
logging.basicConfig(level=logging.INFO)

subcommand = ctx.invoked_subcommand
ctx.obj = {"CFG": load_toml(settings, subcommand)}
ctx.obj["SETTINGS"] = settings
Expand Down
2 changes: 1 addition & 1 deletion kcidev/subcommands/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def retrieve_tot_commit(repourl, branch):
return sha


@click.command(help="Create custom tree checkout on KernelCI and trigger a tests")
@click.command(help="Trigger a test for a specific tree/branch/commit")
@click.option(
"--giturl",
help="Git URL to checkout",
Expand Down
5 changes: 4 additions & 1 deletion kcidev/subcommands/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
)
@click.option(
"--test",
help="Return code based on the test result",
help="Return 0 if the test name supplied passed, 1 otherwise",
)
@click.pass_context
def watch(ctx, nodeid, job_filter, test):
Expand All @@ -31,6 +31,9 @@ def watch(ctx, nodeid, job_filter, test):
token = cfg[instance]["token"]

node = maestro_get_node(apiurl, nodeid)
if not node:
kci_err(f"node id {nodeid} not found.")
sys.exit(errno.ENOENT)
maestro_watch_jobs(apiurl, token, node["treeid"], job_filter, test)


Expand Down
Loading