lavacli-0.7/ 0000755 0001750 0001750 00000000000 13241511244 014526 5 ustar stylesen stylesen 0000000 0000000 lavacli-0.7/lavacli/ 0000755 0001750 0001750 00000000000 13241511244 016141 5 ustar stylesen stylesen 0000000 0000000 lavacli-0.7/lavacli/utils.py 0000644 0001750 0001750 00000001610 13226116343 017655 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
def print_u(string):
try:
print(string)
except UnicodeEncodeError:
print(string.encode("ascii", errors="replace").decode("ascii"))
lavacli-0.7/lavacli/__main__.py 0000644 0001750 0001750 00000001554 13226116343 020244 0 ustar stylesen stylesen 0000000 0000000 #!/usr/bin/python3
# -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import sys
from . import main
if __name__ == '__main__':
sys.argv[0] = "lavacli"
sys.exit(main())
lavacli-0.7/lavacli/__about__.py 0000644 0001750 0001750 00000002004 13236771255 020433 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
__all__ = ["__author__", "__description__", "__license__", "__url__",
"__version__"]
__author__ = "Rémi Duraffort"
__description__ = 'LAVA XML-RPC command line interface'
__license__ = 'AGPLv3+'
__url__ = 'https://git.linaro.org/lava/lavacli.git'
__version__ = "0.7"
lavacli-0.7/lavacli/commands/ 0000755 0001750 0001750 00000000000 13241511244 017742 5 ustar stylesen stylesen 0000000 0000000 lavacli-0.7/lavacli/commands/identities.py 0000644 0001750 0001750 00000006747 13241511171 022472 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2018 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import os
import yaml
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "add"
config_add = sub.add_parser("add", help="add an identity")
config_add.add_argument("id", type=str, help="identity")
config_add.add_argument("--uri", type=str, required=True,
help="URI of the lava-server RPC endpoint")
config_add.add_argument("--proxy", type=str, default="",
help="http proxy")
# "delete"
config_del = sub.add_parser("delete", help="delete an alias")
config_del.add_argument("id", help="identity")
# "list"
config_list = sub.add_parser("list", help="list available identities")
# "show"
config_show = sub.add_parser("show", help="show identity details")
config_show.add_argument("id", type=str, help="identity")
def help_string():
return "manage lavacli configuration"
def _load_configuration():
config_dir = os.environ.get("XDG_CONFIG_HOME", "~/.config")
config_filename = os.path.expanduser(os.path.join(config_dir,
"lavacli.yaml"))
try:
with open(config_filename, "r", encoding="utf-8") as f_conf:
return yaml.load(f_conf.read())
except (FileNotFoundError, KeyError, TypeError):
return None
def _save_configuration(config):
config_dir = os.environ.get("XDG_CONFIG_HOME", "~/.config")
config_filename = os.path.expanduser(os.path.join(config_dir,
"lavacli.yaml"))
with open(config_filename, "w", encoding="utf-8") as f_conf:
f_conf.write(yaml.dump(config, default_flow_style=False).rstrip("\n"))
def handle_add(proxy, options):
config = _load_configuration()
config[options.id] = {"uri": options.uri}
if options.proxy:
config[options.id]["proxy"] = options.proxy
_save_configuration(config)
def handle_delete(proxy, options):
config = _load_configuration()
del config[options.id]
_save_configuration(config)
def handle_list(proxy, option):
config = _load_configuration()
print("Identities:")
for identity in sorted(config.keys()):
print("* %s" % identity)
def handle_show(proxy, options):
config = _load_configuration()
try:
conf_str = yaml.dump(config[options.id], default_flow_style=False)
print(conf_str.rstrip('\n'))
except KeyError:
print("Unknown identity '%s'" % options.id)
return 1
def handle(proxy, options, _):
handlers = {
"add": handle_add,
"delete": handle_delete,
"list": handle_list,
"show": handle_show
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/results.py 0000644 0001750 0001750 00000005627 13226116343 022033 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import sys
import yaml
def configure_parser(parser):
parser.add_argument("job_id", help="job id")
parser.add_argument("test_suite", nargs="?", default=None, help="test suite")
parser.add_argument("test_case", nargs="?", default=None, help="test case")
parser.add_argument("--yaml", dest="output_format", default=None,
action="store_const", const="yaml",
help="print as yaml")
def help_string():
return "manage results"
def handle(proxy, options, _):
if options.test_case is not None:
data = proxy.results.get_testcase_results_yaml(options.job_id,
options.test_suite,
options.test_case)
elif options.test_suite is not None:
data = proxy.results.get_testsuite_results_yaml(options.job_id,
options.test_suite)
else:
data = proxy.results.get_testjob_results_yaml(options.job_id)
results = yaml.load(data)
if options.output_format == "yaml":
print(yaml.dump(results).rstrip("\n"))
else:
# Only one to print
if options.test_case is not None:
res = results[0]
if not sys.stdout.isatty():
print("%s" % res["result"])
elif res["result"] == "pass":
print("\033[1;32mpass\033[0m")
elif res["result"] == "fail":
print("\033[1;31mfail\033[0m")
else:
print("%s" % res["result"])
# A list to print
else:
print("Results:")
for res in results:
if not sys.stdout.isatty():
print("* %s.%s [%s]" % (res["suite"], res["name"], res["result"]))
elif res["result"] == "pass":
print("* %s.%s [\033[1;32mpass\033[0m]" % (res["suite"], res["name"]))
elif res["result"] == "fail":
print("* %s.%s [\033[1;31mfail\033[0m]" % (res["suite"], res["name"]))
else:
print("* %s.%s [%s]" % (res["suite"], res["name"], res["result"]))
lavacli-0.7/lavacli/commands/workers.py 0000644 0001750 0001750 00000015447 13226116343 022027 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import argparse
import time
import yaml
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "add"
workers_add = sub.add_parser("add", help="add a worker")
workers_add.add_argument("hostname", type=str,
help="worker hostname")
workers_add.add_argument("--description", type=str, default=None,
help="worker description")
workers_add.add_argument("--disabled", action="store_true",
default=False, help="create a disabled worker")
# "config"
workers_config = sub.add_parser("config", help="worker configuration")
config_sub = workers_config.add_subparsers(dest="sub_sub_sub_command",
help="Sub commands")
config_sub.required = True
config_get = config_sub.add_parser("get",
help="get the worker configuration")
config_get.add_argument("hostname", type=str,
help="worker hostname")
config_set = config_sub.add_parser("set",
help="set the worker configuration")
config_set.add_argument("hostname", type=str,
help="worker hostname")
config_set.add_argument("config", type=argparse.FileType('r'),
help="configuration file")
# "list"
workers_list = sub.add_parser("list", help="list workers")
workers_list.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
default=None, help="print as yaml")
# "maintenance"
workers_maintenance = sub.add_parser("maintenance",
help="maintenance the worker")
workers_maintenance.add_argument("hostname", type=str,
help="worker hostname")
workers_maintenance.add_argument("--force", default=False,
action="store_true",
help="force worker maintenance by canceling running jobs")
workers_maintenance.add_argument("--no-wait", dest="wait",
default=True, action="store_false",
help="do not wait for the devices to be idle")
# "update"
update_parser = sub.add_parser("update", help="update worker properties")
update_parser.add_argument("hostname", type=str,
help="worker hostname")
update_parser.add_argument("--description", type=str, default=None,
help="worker description")
update_parser.add_argument("--health", type=str, default=None,
choices=["ACTIVE", "MAINTENANCE", "RETIRED"],
help="worker health")
# "show"
workers_show = sub.add_parser("show", help="show worker details")
workers_show.add_argument("hostname", help="worker hostname")
workers_show.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
default=None, help="print as yaml")
def help_string():
return "manage workers"
def handle_add(proxy, options):
proxy.scheduler.workers.add(options.hostname,
options.description,
options.disabled)
def handle_config(proxy, options):
if options.sub_sub_sub_command == "get":
config = proxy.scheduler.workers.get_config(options.hostname)
print(str(config).rstrip("\n"))
else:
config = options.config.read()
ret = proxy.scheduler.workers.set_config(options.hostname,
config)
if not ret:
print("Unable to store worker configuration")
return 1
def handle_list(proxy, options):
workers = proxy.scheduler.workers.list()
if options.output_format == "yaml":
print(yaml.dump(workers).rstrip("\n"))
else:
print("Workers:")
for worker in workers:
print("* %s" % worker)
def handle_maintenance(proxy, options):
proxy.scheduler.workers.update(options.hostname, None, "MAINTENANCE")
if options.force or options.wait:
worker_devices = proxy.scheduler.workers.show(options.hostname)["devices"]
for device in proxy.scheduler.devices.list():
if device["hostname"] not in worker_devices:
continue
current_job = device["current_job"]
if current_job is not None:
print("-> waiting for job %s" % current_job)
# if --force is passed, cancel the job
if options.force:
print("--> canceling")
proxy.scheduler.jobs.cancel(current_job)
while options.wait and proxy.scheduler.jobs.show(current_job)["state"] != "Finished":
print("--> waiting")
time.sleep(5)
def handle_show(proxy, options):
worker = proxy.scheduler.workers.show(options.hostname)
if options.output_format == "yaml":
print(yaml.dump(worker).rstrip("\n"))
else:
print("hostname : %s" % worker["hostname"])
print("description : %s" % worker["description"])
print("state : %s" % worker["state"])
print("health : %s" % worker["health"])
print("devices : %s" % ", ".join(worker["devices"]))
print("last ping : %s" % worker["last_ping"])
def handle_update(proxy, options):
proxy.scheduler.workers.update(options.hostname,
options.description,
options.health)
def handle(proxy, options, _):
handlers = {
"add": handle_add,
"config": handle_config,
"list": handle_list,
"maintenance": handle_maintenance,
"show": handle_show,
"update": handle_update
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/utils.py 0000644 0001750 0001750 00000004111 13226116343 021455 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import argparse
import yaml
from . import jobs
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "logs"
logs = sub.add_parser("logs", help="log helpers")
logs_sub = logs.add_subparsers(dest="sub_sub_sub_command",
help="Sub commands")
logs_sub.required = True
# "logs.print"
logs_print = logs_sub.add_parser("print",
help="print log file")
logs_print.add_argument("filename",
type=argparse.FileType('r'),
help="log file")
logs_print.add_argument("--filters", default=None, type=str,
help="comma seperated list of levels to show")
logs_print.add_argument("--raw", default=False, action="store_true",
help="print raw logs")
def help_string():
return "utility functions"
def handle_logs(proxy, options):
try:
logs = yaml.load(options.filename.read(), Loader=yaml.CLoader)
except yaml.YAMLError:
print("Invalid yaml file")
else:
jobs.print_logs(logs, options.raw, options.filters)
def handle(proxy, options, _):
handlers = {
"logs": handle_logs,
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/jobs.py 0000644 0001750 0001750 00000030157 13236771255 021275 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import argparse
import datetime
import sys
import time
import yaml
from lavacli.utils import print_u
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "cancel"
jobs_cancel = sub.add_parser("cancel", help="cancel a job")
jobs_cancel.add_argument("job_id", help="job id")
jobs_definition = sub.add_parser("definition", help="job definition")
jobs_definition.add_argument("job_id", help="job id")
# "list"
jobs_list = sub.add_parser("list", help="list jobs")
jobs_list.add_argument("--start", type=int, default=0,
help="skip the N first jobs")
jobs_list.add_argument("--limit", type=int, default=10,
help="limit to N jobs")
jobs_list.add_argument("--yaml", dest="output_format", default=None,
action="store_const", const="yaml",
help="print as yaml")
# "logs"
jobs_logs = sub.add_parser("logs", help="get logs")
jobs_logs.add_argument("job_id", help="job id")
jobs_logs.add_argument("--no-follow", default=False, action="store_true",
help="do not keep polling until the end of the job")
jobs_logs.add_argument("--filters", default=None, type=str,
help="comma seperated list of levels to show")
jobs_logs.add_argument("--polling", default=5, type=int,
help="polling interval in seconds, 5s by default")
jobs_logs.add_argument("--raw", default=False, action="store_true",
help="print raw logs")
# "run"
jobs_run = sub.add_parser("run", help="run the job")
jobs_run.add_argument("definition", type=argparse.FileType('r'),
help="job definition")
jobs_run.add_argument("--filters", default=None, type=str,
help="comma seperated list of levels to show")
jobs_run.add_argument("--no-follow", default=False, action="store_true",
help="do not keep polling until the end of the job")
jobs_run.add_argument("--polling", default=5, type=int,
help="polling interval in seconds, 5s by default")
jobs_run.add_argument("--raw", default=False, action="store_true",
help="print raw logs")
# "show"
jobs_show = sub.add_parser("show", help="job details")
jobs_show.add_argument("job_id", help="job id")
jobs_show.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
default=None, help="print as yaml")
# "resubmit"
jobs_resubmit = sub.add_parser("resubmit", help="resubmit a job")
jobs_resubmit.add_argument("job_id", help="job id")
jobs_resubmit.add_argument("--filters", default=None, type=str,
help="comma seperated list of levels to show")
jobs_resubmit.add_argument("--follow", default=True, dest="no_follow",
action="store_false",
help="resubmit and poll for the logs")
jobs_resubmit.add_argument("--polling", default=5, type=int,
help="polling interval in seconds, 5s by default")
jobs_resubmit.add_argument("--raw", default=False, action="store_true",
help="print raw logs")
# "submit"
jobs_submit = sub.add_parser("submit", help="submit a new job")
jobs_submit.add_argument("definition", type=argparse.FileType('r'),
help="job definition")
# "wait"
jobs_wait = sub.add_parser("wait", help="wait for the job to finish")
jobs_wait.add_argument("job_id", help="job id")
jobs_wait.add_argument("--polling", default=5, type=int,
help="polling interval in seconds, 5s by default")
def help_string():
return "manage jobs"
def handle_cancel(proxy, options):
proxy.scheduler.jobs.cancel(options.job_id)
def handle_definition(proxy, options):
print(proxy.scheduler.jobs.definition(options.job_id))
def handle_list(proxy, options):
jobs = proxy.scheduler.jobs.list(options.start, options.limit)
if options.output_format == "yaml":
print(yaml.dump(jobs).rstrip("\n"))
else:
print("Jobs:")
for job in jobs:
print("* %s: %s,%s [%s] (%s)" % (job["id"], job["state"],
job["health"], job["submitter"],
job["description"]))
if sys.stdout.isatty():
COLORS = {"exception": "\033[1;31m",
"error": "\033[1;31m",
"warning": "\033[1;33m",
"info": "\033[1;37m",
"debug": "",
"target": "\033[32m",
"input": "\033[0;35m",
"feedback": "\033[0;33m",
"results": "\033[1;34m",
"dt": "\033[1;30m",
"end": "\033[0m"}
else:
COLORS = {"exception": "",
"error": "",
"warning": "",
"info": "",
"debug": "",
"target": "",
"input": "",
"feedback": "",
"results": "",
"dt": "",
"end": ""}
def print_logs(logs, raw, filters):
filters = [] if filters is None else filters.split(',')
if raw:
for line in logs:
if filters and not line["lvl"] in filters:
continue
print_u("- " + yaml.dump(line, default_flow_style=True,
default_style='"',
width=10 ** 6,
Dumper=yaml.CDumper)[:-1])
else:
for line in logs:
timestamp = line["dt"].split(".")[0]
level = line["lvl"]
if filters and level not in filters:
continue
if isinstance(line["msg"], dict) and \
"sending" in line["msg"].keys():
level = "input"
msg = str(line["msg"]["sending"])
elif isinstance(line["msg"], bytes):
msg = line["msg"].decode("utf-8", errors="replace")
else:
msg = str(line["msg"])
msg = msg.rstrip("\n")
print_u(COLORS["dt"] + timestamp + COLORS["end"] + " " +
COLORS[level] + msg + COLORS["end"])
def handle_logs(proxy, options):
# Loop
lines = 0
while True:
(finished, data) = proxy.scheduler.jobs.logs(options.job_id, lines)
logs = yaml.load(str(data), Loader=yaml.CLoader)
if logs:
print_logs(logs, options.raw, options.filters)
lines += len(logs)
# Loop only if the job is not finished
if finished or options.no_follow:
break
# Wait some time
time.sleep(options.polling)
if finished:
details = proxy.scheduler.jobs.show(options.job_id)
if details.get("failure_comment"):
print_logs([{"dt": datetime.datetime.utcnow().isoformat(),
"lvl": "info",
"msg": "[lavacli] Failure comment: %s" % details["failure_comment"]}],
options.raw, options.filters)
def handle_resubmit(proxy, options):
job_id = proxy.scheduler.jobs.resubmit(options.job_id)
if options.no_follow:
if isinstance(job_id, list):
for job in job_id:
print(job)
else:
print(job_id)
else:
print_logs([{"dt": datetime.datetime.utcnow().isoformat(),
"lvl": "info",
"msg": "[lavacli] Job %s submitted" % job_id}],
options.raw, options.filters)
# Add the job_id to options for handle_logs
# For multinode, print something and loop on all jobs
if isinstance(job_id, list):
for job in job_id:
print_logs([{"dt": datetime.datetime.utcnow().isoformat(),
"lvl": "info",
"msg": "[lavacli] Seeing %s logs" % job}],
options.raw, options.filters)
options.job_id = job
handle_logs(proxy, options)
else:
options.job_id = job_id
handle_logs(proxy, options)
def handle_run(proxy, options):
job_id = proxy.scheduler.jobs.submit(options.definition.read())
print_logs([{"dt": datetime.datetime.utcnow().isoformat(),
"lvl": "info",
"msg": "[lavacli] Job %s submitted" % job_id}],
options.raw, options.filters)
# Add the job_id to options for handle_logs
# For multinode, print something and loop on all jobs
if isinstance(job_id, list):
for job in job_id:
print_logs([{"dt": datetime.datetime.utcnow().isoformat(),
"lvl": "info",
"msg": "[lavacli] Seeing %s logs" % job}],
options.raw, options.filters)
options.job_id = job
handle_logs(proxy, options)
else:
options.job_id = job_id
handle_logs(proxy, options)
def handle_show(proxy, options):
job = proxy.scheduler.jobs.show(options.job_id)
if options.output_format == "yaml":
job["submit_time"] = job["submit_time"].value
job["start_time"] = job["start_time"].value
job["end_time"] = job["end_time"].value
print(yaml.dump(job).rstrip("\n"))
else:
print("id : %s" % job["id"])
print("description : %s" % job["description"])
print("submitter : %s" % job["submitter"])
print("device-type : %s" % job["device_type"])
print("device : %s" % job["device"])
print("health-check: %s" % job["health_check"])
print("state : %s" % job["state"])
print("Health : %s" % job["health"])
if job.get("failure_comment"):
print("failure : %s" % job["failure_comment"])
print("pipeline : %s" % job["pipeline"])
print("tags : %s" % str(job["tags"]))
print("visibility : %s" % job["visibility"])
print("submit time : %s" % job["submit_time"])
print("start time : %s" % job["start_time"])
print("end time : %s" % job["end_time"])
def handle_submit(proxy, options):
job_id = proxy.scheduler.jobs.submit(options.definition.read())
if isinstance(job_id, list):
for job in job_id:
print(job)
else:
print(job_id)
def handle_wait(proxy, options):
job = proxy.scheduler.jobs.show(options.job_id)
old_state = ""
while job["state"] != "Finished":
if old_state != job["state"]:
if old_state:
sys.stdout.write("\n")
sys.stdout.write(job["state"])
else:
sys.stdout.write(".")
sys.stdout.flush()
old_state = job["state"]
time.sleep(options.polling)
job = proxy.scheduler.jobs.show(options.job_id)
def handle(proxy, options, _):
handlers = {
"cancel": handle_cancel,
"definition": handle_definition,
"list": handle_list,
"logs": handle_logs,
"resubmit": handle_resubmit,
"run": handle_run,
"show": handle_show,
"submit": handle_submit,
"wait": handle_wait,
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/system.py 0000644 0001750 0001750 00000021134 13241511171 021640 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
from contextlib import suppress
import os
import shutil
import time
import xmlrpc.client
import yaml
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "active"
sub.add_parser("active", help="activate the system")
# "api"
sub.add_parser("api", help="print server API version")
# "export"
sys_export = sub.add_parser("export", help="export server configuration")
sys_export.add_argument("name", help="name of the export")
sys_export.add_argument("--full", default=False, action="store_true",
help="do a full export, including retired devices")
# "import"
# sub.add_parser("import", help="import server configuration")
# "maintenance"
sys_maintenance = sub.add_parser("maintenance", help="maintenance the system")
sys_maintenance.add_argument("--force", default=False, action="store_true",
help="force maintenance by canceling jobs")
sys_maintenance.add_argument("--exclude", default=None,
help="comma seperated list of workers to keep untouched")
# "methods"
sys_methods = sub.add_parser("methods", help="list methods")
sys_sub = sys_methods.add_subparsers(dest="sub_sub_sub_command",
help="Sub commands")
sys_sub.required = True
sys_sub.add_parser("list", help="list available methods")
sys_help = sys_sub.add_parser("help", help="method help")
sys_help.add_argument("method", help="method name")
sys_signature = sys_sub.add_parser("signature", help="method signature")
sys_signature.add_argument("method", help="method name")
# "version"
sub.add_parser("version", help="print the server version")
# "whoami"
sub.add_parser("whoami", help="print the current username")
def help_string():
return "system information"
def handle_active(proxy, options):
print("Activate workers:")
workers = proxy.scheduler.workers.list()
for worker in workers:
print("* %s" % worker)
proxy.scheduler.workers.update(worker, None, "ACTIVE")
def handle_api(proxy, options):
print(proxy.system.api_version())
def handle_export(proxy, options):
print("Export to %s" % options.name)
with suppress(FileNotFoundError):
shutil.rmtree(options.name)
os.mkdir(options.name)
os.chdir(options.name)
print("Listing aliases")
aliases = []
for alias in proxy.scheduler.aliases.list():
print("* %s" % alias)
aliases.append(proxy.scheduler.aliases.show(alias))
print("Listing tags")
tags = []
for tag in proxy.scheduler.tags.list():
print("* %s" % tag["name"])
tags.append(tag)
print("Listing workers")
workers = []
for worker in proxy.scheduler.workers.list():
print("* %s" % worker)
w = proxy.scheduler.workers.show(worker)
workers.append({"hostname": w["hostname"],
"description": w["description"],
"state": w["state"],
"health": w["health"]})
print("Listing device-types")
os.mkdir("device-types")
device_types = []
for device_type in proxy.scheduler.device_types.list():
print("* %s" % device_type["name"])
dt = proxy.scheduler.device_types.show(device_type["name"])
device_types.append({"name": dt["name"],
"description": dt["description"],
"display": dt["display"],
"health_disabled": dt["health_disabled"],
"owners_only": dt["owners_only"],
"aliases": dt["aliases"]})
try:
dt_template = proxy.scheduler.device_types.get_template(dt["name"])
except xmlrpc.client.Fault as exc:
if exc.faultCode == 404:
print(" => No template found")
continue
raise
with open(os.path.join("device-types", dt["name"] + ".jinja2"),
"w", encoding="utf-8") as f_out:
f_out.write(str(dt_template))
print("Listing devices")
os.mkdir("devices")
devices = []
for device in proxy.scheduler.devices.list(options.full):
print("* %s" % device["hostname"])
d = proxy.scheduler.devices.show(device["hostname"])
devices.append({"hostname": d["hostname"],
"description": d["description"],
"device_type": d["device_type"],
"pipeline": d["pipeline"],
"worker": d["worker"],
"state": d["state"],
"health": d["health"],
"public": d["public"],
"user": d["user"],
"group": d["group"],
"tags": d["tags"]})
try:
device_dict = proxy.scheduler.devices.get_dictionary(device["hostname"])
except xmlrpc.client.Fault as exc:
if exc.faultCode == 404:
print(" => No device dict found")
continue
raise
with open(os.path.join("devices", device["hostname"] + ".jinja2"),
"w", encoding="utf-8") as f_out:
f_out.write(str(device_dict))
export = {"aliases": aliases,
"devices": devices,
"device-types": device_types,
"tags": tags,
"workers": workers}
# Dump the configuration
with open("instance.yaml", "w", encoding="utf-8") as f_out:
f_out.write(yaml.dump(export).rstrip("\n"))
def handle_maintenance(proxy, options):
print("Maintenance workers:")
workers = proxy.scheduler.workers.list()
excluded_workers = []
if options.exclude:
excluded_workers = options.exclude.split(',')
for worker in workers:
if worker in excluded_workers:
print("* %s [SKIP]" % worker)
continue
print("* %s" % worker)
proxy.scheduler.workers.update(worker, None, "MAINTENANCE")
excluded_devices = []
if excluded_workers:
for worker in excluded_workers:
excluded_devices.extend(proxy.scheduler.workers.show(worker)["devices"])
print("Wait for devices:")
devices = proxy.scheduler.devices.list()
for device in devices:
if device["hostname"] in excluded_devices:
continue
print("* %s" % device["hostname"])
current_job = device["current_job"]
if current_job is not None:
print("--> waiting for job %s" % current_job)
# if --force is passed, cancel the job
if options.force:
print("---> canceling")
proxy.scheduler.jobs.cancel(current_job)
while proxy.scheduler.jobs.show(current_job)["state"] != "Finished":
print("---> waiting")
time.sleep(5)
def handle_methods(proxy, options):
if options.sub_sub_sub_command == "help":
print(proxy.system.methodHelp(options.method))
elif options.sub_sub_sub_command == "signature":
print(proxy.system.methodSignature(options.method))
else:
# Fallback to "list"
methods = proxy.system.listMethods()
for method in methods:
print(method)
def handle_version(proxy, _):
print(proxy.system.version())
def handle_whoami(proxy, _):
username = proxy.system.whoami()
if username is None:
print("")
else:
print(username)
def handle(proxy, options, _):
handlers = {
"active": handle_active,
"api": handle_api,
"export": handle_export,
"maintenance": handle_maintenance,
"methods": handle_methods,
"version": handle_version,
"whoami": handle_whoami
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/devices.py 0000644 0001750 0001750 00000027607 13241511171 021751 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import argparse
import time
import yaml
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "add"
devices_add = sub.add_parser("add", help="add a device")
devices_add.add_argument("hostname", help="hostname of the device")
devices_add.add_argument("--type", required=True, help="device-type")
devices_add.add_argument("--worker", required=True, help="worker hostname")
devices_add.add_argument("--description", default=None,
help="device description")
owner = devices_add.add_mutually_exclusive_group()
owner.add_argument("--user", default=None, help="device owner")
owner.add_argument("--group", default=None, help="device group owner")
devices_add.add_argument("--health", default=None,
choices=["GOOD", "UNKNOWN", "LOOPING", "BAD", "MAINTENANCE", "RETIRED"],
help="device health")
devices_add.add_argument("--private", action="store_true", default=False,
help="private device, public by default")
# "dict"
devices_dict = sub.add_parser("dict", help="device dictionary")
dict_sub = devices_dict.add_subparsers(dest="sub_sub_sub_command",
help="Sub commands")
dict_sub.required = True
dict_get = dict_sub.add_parser("get",
help="get the device dictionary")
dict_get.add_argument("hostname", help="hostname of the device")
dict_get.add_argument("field", nargs="?", default=None,
help="only show the given sub-fields")
dict_get.add_argument("--context", default=None,
help="job context for template rendering")
dict_get.add_argument("--render", action="store_true", default=False,
help="render the dictionary into a configuration")
dict_set = dict_sub.add_parser("set",
help="set the device dictionary")
dict_set.add_argument("hostname", help="hostname of the device")
dict_set.add_argument("config", type=argparse.FileType('r'),
help="device dictionary file")
# "list"
devices_list = sub.add_parser("list", help="list available devices")
devices_list.add_argument("--all", "-a", action="store_true",
default=False,
help="list every devices, inluding retired")
devices_list.add_argument("--yaml", dest="output_format", default=None,
action="store_const", const="yaml",
help="print as yaml")
# "maintenance"
devices_maintenance = sub.add_parser("maintenance",
help="maintenance the device")
devices_maintenance.add_argument("hostname", help="device hostname")
devices_maintenance.add_argument("--force", default=False,
action="store_true",
help="force device maintenance by canceling running job")
devices_maintenance.add_argument("--no-wait", dest="wait",
default=True, action="store_false",
help="do not wait for the device to be idle")
# "show"
devices_show = sub.add_parser("show", help="show device details")
devices_show.add_argument("hostname", help="device hostname")
devices_show.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
default=None, help="print as yaml")
# "tags"
devices_tags = sub.add_parser("tags", help="manage tags for the given device")
tags_sub = devices_tags.add_subparsers(dest="sub_sub_sub_command",
help="Sub commands")
tags_sub.required = True
tags_add = tags_sub.add_parser("add", help="add a tag")
tags_add.add_argument("hostname", help="hostname of the device")
tags_add.add_argument("tag", help="name of the tag")
tags_list = tags_sub.add_parser("list", help="list tags for the device")
tags_list.add_argument("hostname", help="hostname of the device")
tags_list.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
help="print as yaml")
tags_del = tags_sub.add_parser("delete", help="remove a tag")
tags_del.add_argument("hostname", help="hostname of the device")
tags_del.add_argument("tag", help="name of the tag")
# "update"
devices_update = sub.add_parser("update", help="update device properties")
devices_update.add_argument("hostname",
help="hostname of the device")
devices_update.add_argument("--worker", default=None,
help="worker hostname")
devices_update.add_argument("--description", default=None,
help="device description")
owner = devices_update.add_mutually_exclusive_group()
owner.add_argument("--user", default=None, help="device owner")
owner.add_argument("--group", default=None, help="device group owner")
devices_update.add_argument("--health", default=None,
choices=["GOOD", "UNKNOWN", "LOOPING", "BAD", "MAINTENANCE", "RETIRED"],
help="device health")
display = devices_update.add_mutually_exclusive_group()
display.add_argument("--public", default=None, action="store_true",
help="make the device public")
display.add_argument("--private", dest="public", action="store_false",
help="make the device private")
def help_string():
return "manage devices"
def handle_add(proxy, options):
proxy.scheduler.devices.add(options.hostname, options.type,
options.worker, options.user,
options.group, not options.private,
options.health, options.description)
def _lookups(value, fields):
try:
for key in fields:
if isinstance(value, list):
value = value[int(key)]
else:
value = value[key]
except IndexError:
print("list index out of range (%d vs %d)" % (int(key), len(value)))
return 1
except KeyError as exc:
print("Unknow key '%s' for '%s'" % (key, value))
return 1
print(value)
return 0
def handle_dict(proxy, options):
if options.sub_sub_sub_command == "get":
config = proxy.scheduler.devices.get_dictionary(options.hostname,
options.render,
options.context)
if not options.field:
print(str(config).rstrip("\n"))
return 0
if options.render:
value = yaml.load(str(config))
return _lookups(value, options.field.split("."))
else:
# Extract some variables
import jinja2
env = jinja2.Environment()
ast = env.parse(config)
field_name = options.field.split(".")[0]
# Loop on all assignments
for assign in ast.find_all(jinja2.nodes.Assign):
if assign.target.name == field_name:
value = assign.node.as_const()
if options.field == field_name:
print(value)
return 0
else:
return _lookups(value, options.field.split(".")[1:])
print("Unknow field '%s'" % field_name)
return 1
else:
config = options.config.read()
ret = proxy.scheduler.devices.set_dictionary(options.hostname,
config)
if not ret:
print("Unable to set the configuration")
return 0 if ret else 1
def handle_list(proxy, options):
devices = proxy.scheduler.devices.list(options.all)
if options.output_format == "yaml":
print(yaml.dump(devices).rstrip("\n"))
else:
print("Devices:")
for device in devices:
print("* %s (%s): %s,%s" % (device["hostname"], device["type"], device["state"], device["health"]))
def handle_maintenance(proxy, options):
proxy.scheduler.devices.update(options.hostname, None, None, None, None,
"MAINTENANCE", None)
device = proxy.scheduler.devices.show(options.hostname)
current_job = device["current_job"]
if current_job is not None:
print("-> waiting for job %s" % current_job)
# if --force is passed, cancel the job
if options.force:
print("--> canceling")
proxy.scheduler.jobs.cancel(current_job)
while options.wait and proxy.scheduler.jobs.show(current_job)["state"] != "Finished":
print("--> waiting")
time.sleep(5)
def handle_show(proxy, options):
device = proxy.scheduler.devices.show(options.hostname)
if options.output_format == "yaml":
print(yaml.dump(device).rstrip("\n"))
else:
print("name : %s" % device["hostname"])
print("device-type : %s" % device["device_type"])
print("state : %s" % device["state"])
print("health : %s" % device["health"])
print("user : %s" % device["user"])
print("group : %s" % device["group"])
print("health : %s" % device["health"])
print("health job : %s" % device["health_job"])
print("description : %s" % device["description"])
print("public : %s" % device["public"])
print("pipeline : %s" % device["pipeline"])
print("device-dict : %s" % device["has_device_dict"])
print("worker : %s" % device["worker"])
print("current job : %s" % device["current_job"])
print("tags : %s" % device["tags"])
def handle_tags(proxy, options):
if options.sub_sub_sub_command == "add":
proxy.scheduler.devices.tags.add(options.hostname, options.tag)
elif options.sub_sub_sub_command == "delete":
proxy.scheduler.devices.tags.delete(options.hostname, options.tag)
else:
tags = proxy.scheduler.devices.tags.list(options.hostname)
if options.output_format == "yaml":
print(yaml.dump(tags).rstrip("\n"))
else:
print("Tags:")
for tag in tags:
print("* %s" % tag)
def handle_update(proxy, options):
proxy.scheduler.devices.update(options.hostname, options.worker,
options.user, options.group,
options.public, options.health,
options.description)
def handle(proxy, options, _):
handlers = {
"add": handle_add,
"dict": handle_dict,
"list": handle_list,
"maintenance": handle_maintenance,
"show": handle_show,
"tags": handle_tags,
"update": handle_update
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/aliases.py 0000644 0001750 0001750 00000005434 13226116343 021747 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import yaml
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "add"
aliases_add = sub.add_parser("add", help="add an alias")
aliases_add.add_argument("alias", help="alias name")
# "delete"
aliases_del = sub.add_parser("delete", help="delete an alias")
aliases_del.add_argument("alias", help="alias name")
# "list"
aliases_list = sub.add_parser("list", help="list available aliases")
aliases_list.add_argument("--yaml", dest="output_format", default=None,
action="store_const", const="yaml",
help="print as yaml")
# "show"
aliases_show = sub.add_parser("show", help="show alias details")
aliases_show.add_argument("alias", help="alias")
aliases_show.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
default=None, help="print as yaml")
def help_string():
return "manage device-type aliases"
def handle_add(proxy, options):
proxy.scheduler.aliases.add(options.alias)
def handle_delete(proxy, options):
proxy.scheduler.aliases.delete(options.alias)
def handle_list(proxy, options):
aliases = proxy.scheduler.aliases.list()
if options.output_format == "yaml":
print(yaml.dump(aliases).rstrip("\n"))
else:
print("Aliases:")
for alias in aliases:
print("* %s" % alias)
def handle_show(proxy, options):
alias = proxy.scheduler.aliases.show(options.alias)
if options.output_format == "yaml":
print(yaml.dump(alias).rstrip("\n"))
else:
print("name : %s" % alias["name"])
print("device-types:")
for dt in alias["device_types"]:
print("* %s" % dt)
def handle(proxy, options, _):
handlers = {
"add": handle_add,
"delete": handle_delete,
"list": handle_list,
"show": handle_show
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/device_types.py 0000644 0001750 0001750 00000021733 13226116343 023011 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import argparse
import yaml
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "add"
dt_add = sub.add_parser("add", help="add a device type")
dt_add.add_argument("name", help="name of the device-type")
dt_add.add_argument("--description", default=None,
help="device-type description")
dt_add.add_argument("--hide", dest="display", action="store_false",
default=True, help="device is hidden in the UI")
dt_add.add_argument("--owners-only", action="store_true", default=False,
help="devices are only visible to owners")
dt_health = dt_add.add_argument_group("health check")
dt_health.add_argument("--health-frequency", default=24,
help="how often to run health checks.")
dt_health.add_argument("--health-denominator", default="hours",
choices=["hours", "jobs"],
help="initiate health checks by hours or by jobs.")
# "aliases"
dt_aliases = sub.add_parser("aliases", help="manage aliases for the given device-type")
aliases_sub = dt_aliases.add_subparsers(dest="sub_sub_sub_command",
help="Sub commands")
aliases_sub.required = True
aliases_add = aliases_sub.add_parser("add", help="add aliases")
aliases_add.add_argument("name", help="name of the device-type")
aliases_add.add_argument("alias", help="name of alias")
aliases_delete = aliases_sub.add_parser("delete", help="delete aliases")
aliases_delete.add_argument("name", help="name of the device-type")
aliases_delete.add_argument("alias", help="name of alias")
aliases_list = aliases_sub.add_parser("list", help="list aliases for the device-type")
aliases_list.add_argument("name", help="device-type")
aliases_list.add_argument("--yaml", dest="output_format", default=None,
action="store_const", const="yaml",
help="print as yaml")
# "list"
dt_list = sub.add_parser("list", help="list available device-types")
dt_list.add_argument("--all", "-a", dest="show_all",
default=False, action="store_true",
help="show all device types in the database, "
"including non-installed ones")
dt_list.add_argument("--yaml", dest="output_format", default=None,
action="store_const", const="yaml",
help="print as yaml")
# "show"
dt_show = sub.add_parser("show", help="show device-type details")
dt_show.add_argument("name", help="name of the device-type")
dt_show.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
default=None, help="print as yaml")
# "template"
devices_template = sub.add_parser("template", help="device-type template")
dt_sub = devices_template.add_subparsers(dest="sub_sub_sub_command",
help="Sub commands")
dt_sub.required = True
dt_get = dt_sub.add_parser("get",
help="get the device-type template")
dt_get.add_argument("name", help="name of the device-type")
dt_set = dt_sub.add_parser("set",
help="set the device-type template")
dt_set.add_argument("name", help="name of the device-type")
dt_set.add_argument("template", type=argparse.FileType('r'),
help="template file")
# "update"
dt_update = sub.add_parser("update", help="update device-type")
dt_update.add_argument("name", help="name of the device-type")
dt_update.add_argument("--description", default=None,
help="device-type description")
visibility = dt_update.add_mutually_exclusive_group()
visibility.add_argument("--hide", dest="display", action="store_false",
default=None, help="device-type is hidden in the UI")
visibility.add_argument("--show", dest="display", action="store_true",
help="device-type is visible in the UI")
owner = dt_update.add_mutually_exclusive_group()
owner.add_argument("--owners-only", action="store_true", dest="owners_only",
default=None,
help="devices are only visible to owners")
owner.add_argument("--public", action="store_false", dest="owners_only",
help="devices are visible to all users")
dt_health = dt_update.add_argument_group("health check")
dt_health.add_argument("--health-frequency", default=None,
help="how often to run health checks.")
dt_health.add_argument("--health-denominator", default=None,
choices=["hours", "jobs"],
help="initiate health checks by hours or by jobs.")
health = dt_health.add_mutually_exclusive_group()
health.add_argument("--health-disabled", default=None,
action="store_true",
help="disable health checks")
health.add_argument("--health-active", dest="health_disabled",
action="store_false",
help="activate health checks")
def help_string():
return "manage device-types"
def handle_add(proxy, options):
proxy.scheduler.device_types.add(options.name, options.description,
options.display, options.owners_only,
options.health_frequency,
options.health_denominator)
def handle_aliases(proxy, options):
if options.sub_sub_sub_command == "add":
proxy.scheduler.device_types.aliases.add(options.name, options.alias)
elif options.sub_sub_sub_command == "list":
aliases = proxy.scheduler.device_types.aliases.list(options.name)
if options.output_format == "yaml":
print(yaml.dump(aliases).rstrip("\n"))
else:
print("Aliases:")
for alias in aliases:
print("* %s" % alias)
elif options.sub_sub_sub_command == "delete":
proxy.scheduler.device_types.aliases.delete(options.name,
options.alias)
def handle_list(proxy, options):
device_types = proxy.scheduler.device_types.list(options.show_all)
if options.output_format == "yaml":
print(yaml.dump(device_types).rstrip("\n"))
else:
print("Device-Types:")
for dt in device_types:
print("* %s (%s)" % (dt["name"], dt["devices"]))
def handle_show(proxy, options):
dt = proxy.scheduler.device_types.show(options.name)
if options.output_format == "yaml":
print(yaml.dump(dt).rstrip("\n"))
else:
print("name : %s" % dt["name"])
print("description : %s" % dt["description"])
print("display : %s" % dt["display"])
print("owners only : %s" % dt["owners_only"])
print("health disabled : %s" % dt["health_disabled"])
print("aliases : %s" % dt["aliases"])
print("devices : %s" % dt["devices"])
def handle_template(proxy, options):
if options.sub_sub_sub_command == "get":
template = proxy.scheduler.device_types.get_template(options.name)
print(str(template).rstrip("\n"))
else:
template = options.template.read()
proxy.scheduler.device_types.set_template(options.name, template)
def handle_update(proxy, options):
proxy.scheduler.device_types.update(options.name, options.description,
options.display, options.owners_only,
options.health_frequency,
options.health_denominator,
options.health_disabled)
def handle(proxy, options, _):
handlers = {
"add": handle_add,
"aliases": handle_aliases,
"list": handle_list,
"show": handle_show,
"template": handle_template,
"update": handle_update
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/tags.py 0000644 0001750 0001750 00000005721 13226116343 021263 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import yaml
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "add"
tags_add = sub.add_parser("add", help="add a tag")
tags_add.add_argument("tag", help="tag name")
tags_add.add_argument("--description", default=None,
help="tag description")
# "delete"
tags_delete = sub.add_parser("delete", help="delete a tag")
tags_delete.add_argument("tag", help="tag name")
# "list"
tags_list = sub.add_parser("list", help="list tags")
tags_list.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
help="print as yaml")
# "show"
tags_show = sub.add_parser("show", help="show tag details")
tags_show.add_argument("tag", help="tag name")
tags_show.add_argument("--yaml", dest="output_format",
action="store_const", const="yaml",
help="print as yaml")
def help_string():
return "manage device tags"
def handle_add(proxy, options):
proxy.scheduler.tags.add(options.tag, options.description)
def handle_delete(proxy, options):
proxy.scheduler.tags.delete(options.tag)
def handle_list(proxy, options):
tags = proxy.scheduler.tags.list()
if options.output_format == "yaml":
print(yaml.dump(tags).rstrip("\n"))
else:
print("Tags:")
for tag in tags:
if tag["description"]:
print("* %s (%s)" % (tag["name"], tag["description"]))
else:
print("* %s" % tag["name"])
def handle_show(proxy, options):
tag = proxy.scheduler.tags.show(options.tag)
if options.output_format == "yaml":
print(yaml.dump(tag).rstrip("\n"))
else:
print("name : %s" % tag["name"])
print("description: %s" % tag["description"])
print("devices :")
for device in tag["devices"]:
print("* %s" % device)
def handle(proxy, options, _):
handlers = {
"add": handle_add,
"delete": handle_delete,
"list": handle_list,
"show": handle_show
}
return handlers[options.sub_sub_command](proxy, options)
lavacli-0.7/lavacli/commands/events.py 0000644 0001750 0001750 00000016547 13226116343 021641 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import json
from urllib.parse import urlparse
import zmq
from zmq.utils.strtypes import b, u
def configure_parser(parser):
sub = parser.add_subparsers(dest="sub_sub_command", help="Sub commands")
sub.required = True
# "listen"
sub.add_parser("listen", help="listen to events")
# "wait"
wait_parser = sub.add_parser("wait", help="wait for a specific event")
obj_parser = wait_parser.add_subparsers(dest="object", help="object to wait")
obj_parser.required = True
# "wait device"
device_parser = obj_parser.add_parser("device")
device_parser.add_argument("hostname", type=str,
help="hostname of the device")
device_parser.add_argument("--state", default=None,
choices=["IDLE", "RESERVED", "RUNNING"],
help="device state")
device_parser.add_argument("--health", default=None,
choices=["GOOD", "UNKNOWN", "LOOPING", "BAD", "MAINTENANCE", "RETIRED"],
help="device health")
# "wait job"
testjob_parser = obj_parser.add_parser("job")
testjob_parser.add_argument("job_id", help="job id")
testjob_parser.add_argument("--state", default=None,
choices=["SUBMITTED", "SCHEDULING", "SCHEDULED", "RUNNING", "CANCELING", "FINISHED"],
help="job state")
testjob_parser.add_argument("--health", default=None,
choices=["UNKNOWN", "COMPLETE", "INCOMPLETE", "CANCELED"],
help="job health")
# "wait worker"
worker_parser = obj_parser.add_parser("worker")
worker_parser.add_argument("hostname", type=str,
help="worker hostname")
worker_parser.add_argument("--state", default=None,
choices=["ONLINE", "OFFLINE"],
help="worker state")
worker_parser.add_argument("--health", default=None,
choices=["ACTIVE", "MAINTENANCE", "RETIRED"],
help="worker health")
def help_string():
return "listen to events"
def _get_zmq_url(proxy, options, config):
if config is None or config.get("events", {}).get("uri") is None:
url = proxy.scheduler.get_publisher_event_socket()
if '*' in url:
domain = urlparse(options.uri).netloc
if '@' in domain:
domain = domain.split('@')[1]
domain = domain.split(':')[0]
url = url.replace('*', domain)
else:
url = config["events"]["uri"]
return url
def handle_listen(proxy, options, config):
# Try to find the socket url
url = _get_zmq_url(proxy, options, config)
if url is None:
print("Unable to find the socket url")
return 1
context = zmq.Context()
sock = context.socket(zmq.SUB)
sock.setsockopt(zmq.SUBSCRIBE, b"")
# Set the sock proxy (if needed)
socks = config.get("events", {}).get("socks_proxy")
if socks is not None:
print("Listening to %s (socks %s)" % (url, socks))
sock.setsockopt(zmq.SOCKS_PROXY, b(socks))
else:
print("Listening to %s" % url)
try:
sock.connect(url)
except zmq.error.ZMQError as exc:
print("Unable to connect: %s" % exc)
return 1
while True:
msg = sock.recv_multipart()
try:
(topic, _, dt, username, data) = (u(m) for m in msg)
except ValueError:
print("Invalid message: %s" % msg)
continue
# If unknown, print the full data
msg = data
data = json.loads(data)
# Print according to the topic
topic_end = topic.split(".")[-1]
if topic_end == "device":
msg = "[%s] <%s> state=%s health=%s" % (data["device"], data["device_type"], data["state"], data["health"])
if "job" in data:
msg += " for %s" % data["job"]
elif topic_end == "testjob":
msg = "[%s] <%s> state=%s health=%s (%s)" % (data["job"], data.get("device", "??"),
data["state"], data["health"],
data["description"])
elif topic_end == "worker":
msg = "[%s] state=%s health=%s" % (data["hostname"], data["state"], data["health"])
print("\033[1;30m%s\033[0m \033[1;37m%s\033[0m \033[32m%s\033[0m - %s" % (dt, topic, username, msg))
def handle_wait(proxy, options, config):
# Try to find the socket url
url = _get_zmq_url(proxy, options, config)
if url is None:
print("Unable to find the socket url")
return 1
context = zmq.Context()
sock = context.socket(zmq.SUB)
# Filter by topic (if needed)
sock.setsockopt(zmq.SUBSCRIBE, b(config.get("events", {}).get("topic", "")))
# Set the sock proxy (if needed)
socks = config.get("events", {}).get("socks_proxy")
if socks is not None:
print("Listening to %s (socks %s)" % (url, socks))
sock.setsockopt(zmq.SOCKS_PROXY, b(socks))
else:
print("Listening to %s" % url)
try:
sock.connect(url)
except zmq.error.ZMQError as exc:
print("Unable to connect: %s" % exc)
return 1
# "job" is called "testjob" in the events
object_topic = options.object
if object_topic == "job":
object_topic = "testjob"
# Wait for events
while True:
msg = sock.recv_multipart()
try:
(topic, _, dt, username, data) = (u(m) for m in msg)
except ValueError:
print("Invalid message: %s" % msg)
continue
data = json.loads(data)
# Filter by object
obj = topic.split(".")[-1]
if obj != object_topic:
continue
if object_topic == "device":
if data.get("device") != options.hostname:
continue
elif object_topic == "testjob":
if data.get("job") != options.job_id:
continue
else:
if data.get("hostname") != options.hostname:
continue
# Filter by state
if options.state is not None:
if data.get("state") != options.state.capitalize():
continue
# Filter by health
if options.health is not None:
if data.get("health") != options.health.capitalize():
continue
return 0
def handle(proxy, options, config):
handlers = {
"listen": handle_listen,
"wait": handle_wait
}
return handlers[options.sub_sub_command](proxy, options, config)
lavacli-0.7/lavacli/commands/__init__.py 0000644 0001750 0001750 00000000000 13226116343 022045 0 ustar stylesen stylesen 0000000 0000000 lavacli-0.7/lavacli/__init__.py 0000644 0001750 0001750 00000016030 13241511171 020251 0 ustar stylesen stylesen 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
import argparse
import os
import requests
import socket
import sys
from urllib.parse import urlparse
import xmlrpc.client
import yaml
from .__about__ import *
from .commands import (
aliases,
devices,
device_types,
events,
identities,
jobs,
results,
system,
tags,
utils,
workers
)
class RequestsTransport(xmlrpc.client.Transport):
def __init__(self, scheme, proxy=None, timeout=20.0, verify_ssl_cert=True):
super().__init__()
self.scheme = scheme
# Set the user agent
self.user_agent = "lavacli v%s" % __version__
if proxy is None:
self.proxies = {}
else:
self.proxies = {scheme: proxy}
self.timeout = timeout
self.verify_ssl_cert = verify_ssl_cert
if not verify_ssl_cert:
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
def request(self, host, handler, data, verbose=False):
headers = {"User-Agent": self.user_agent,
"Content-Type": "text/xml",
"Accept-Encoding": "gzip"}
url = "%s://%s%s" % (self.scheme, host, handler)
try:
response = None
response = requests.post(url, data=data, headers=headers,
timeout=self.timeout,
verify=self.verify_ssl_cert,
proxies=self.proxies)
response.raise_for_status()
return self.parse_response(response)
except requests.RequestException as e:
if response is None:
raise xmlrpc.client.ProtocolError(url, 500, str(e), "")
else:
raise xmlrpc.client.ProtocolError(url, response.status_code,
str(e), response.headers)
def parse_response(self, resp):
"""
Parse the xmlrpc response.
"""
p, u = self.getparser()
p.feed(resp.text)
p.close()
return u.close()
def load_config(identity):
# Build the path to the configuration file
config_dir = os.environ.get("XDG_CONFIG_HOME", "~/.config")
config_filename = os.path.expanduser(os.path.join(config_dir,
"lavacli.yaml"))
try:
with open(config_filename, "r", encoding="utf-8") as f_conf:
config = yaml.load(f_conf.read())
return config[identity]
except (FileNotFoundError, KeyError, TypeError):
return None
def parser(commands):
parser_obj = argparse.ArgumentParser()
# "--version"
parser_obj.add_argument("--version", action="store_true", default=False,
help="print the version number and exit")
# identity or url
url = parser_obj.add_mutually_exclusive_group()
url.add_argument("--uri", type=str, default=None,
help="URI of the lava-server RPC endpoint")
url.add_argument("--identity", "-i", metavar="ID", type=str, default="default",
help="identity stored in the configuration")
# The sub commands
root = parser_obj.add_subparsers(dest="sub_command", help="Sub commands")
keys = list(commands.keys())
keys.sort()
for name in keys:
cls = commands[name]
cls.configure_parser(root.add_parser(name, help=cls.help_string()))
return parser_obj
def main():
commands = {"aliases": aliases,
"devices": devices,
"device-types": device_types,
"events": events,
"identities": identities,
"jobs": jobs,
"results": results,
"system": system,
"tags": tags,
"utils": utils,
"workers": workers}
# Parse the command line
parser_obj = parser(commands)
options = parser_obj.parse_args()
# Do we have to print the version numer?
if options.version:
print("lavacli %s" % __version__)
return
# Print help if lavacli is called without any arguments
if len(sys.argv) == 1:
parser_obj.print_help()
return 1
if options.uri is None:
config = load_config(options.identity)
if config is None:
print("Unknown identity '%s'" % options.identity)
return 1
username = config.get("username")
token = config.get("token")
if username is not None and token is not None:
p = urlparse(config["uri"])
options.uri = "%s://%s:%s@%s%s" % (p.scheme, username, token, p.netloc, p.path)
else:
options.uri = config["uri"]
else:
config = {}
# Check that a sub_command was given
if options.sub_command is None:
parser_obj.print_help()
return 1
# Create the Transport object
parsed_uri = urlparse(options.uri)
if options.identity:
transport = RequestsTransport(parsed_uri.scheme,
config.get("proxy"),
config.get("timeout", 20.0),
config.get("verify_ssl_cert", True))
else:
transport = RequestsTransport(parsed_uri.scheme)
# Connect to the RPC endpoint
try:
# allow_none is True because the server does support it
proxy = xmlrpc.client.ServerProxy(options.uri, allow_none=True,
transport=transport)
return commands[options.sub_command].handle(proxy, options, config)
except (ConnectionError, socket.gaierror) as exc:
print("Unable to connect to '%s': %s" % (options.uri, str(exc)))
except KeyboardInterrupt:
pass
except xmlrpc.client.Error as exc:
if "sub_sub_command" in options:
print("Unable to call '%s.%s': %s" % (options.sub_command,
options.sub_sub_command,
str(exc)))
else:
print("Unable to call '%s': %s" % (options.sub_command,
str(exc)))
except BaseException as exc:
print("Unknown error when connecting to '%s': %s" % (options.uri, str(exc)))
return 1
lavacli-0.7/lavacli.egg-info/ 0000755 0001750 0001750 00000000000 13241511244 017633 5 ustar stylesen stylesen 0000000 0000000 lavacli-0.7/lavacli.egg-info/requires.txt 0000644 0001750 0001750 00000000026 13241511244 022231 0 ustar stylesen stylesen 0000000 0000000 PyYAML
pyzmq
requests
lavacli-0.7/lavacli.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000001 13241511244 023701 0 ustar stylesen stylesen 0000000 0000000
lavacli-0.7/lavacli.egg-info/zip-safe 0000644 0001750 0001750 00000000001 13236773162 021300 0 ustar stylesen stylesen 0000000 0000000
lavacli-0.7/lavacli.egg-info/entry_points.txt 0000644 0001750 0001750 00000000052 13241511244 023126 0 ustar stylesen stylesen 0000000 0000000 [console_scripts]
lavacli = lavacli:main
lavacli-0.7/lavacli.egg-info/SOURCES.txt 0000644 0001750 0001750 00000001173 13241511244 021521 0 ustar stylesen stylesen 0000000 0000000 setup.py
lavacli/__about__.py
lavacli/__init__.py
lavacli/__main__.py
lavacli/utils.py
lavacli.egg-info/PKG-INFO
lavacli.egg-info/SOURCES.txt
lavacli.egg-info/dependency_links.txt
lavacli.egg-info/entry_points.txt
lavacli.egg-info/requires.txt
lavacli.egg-info/top_level.txt
lavacli.egg-info/zip-safe
lavacli/commands/__init__.py
lavacli/commands/aliases.py
lavacli/commands/device_types.py
lavacli/commands/devices.py
lavacli/commands/events.py
lavacli/commands/identities.py
lavacli/commands/jobs.py
lavacli/commands/results.py
lavacli/commands/system.py
lavacli/commands/tags.py
lavacli/commands/utils.py
lavacli/commands/workers.py lavacli-0.7/lavacli.egg-info/PKG-INFO 0000644 0001750 0001750 00000001551 13241511244 020732 0 ustar stylesen stylesen 0000000 0000000 Metadata-Version: 1.1
Name: lavacli
Version: 0.7
Summary: LAVA XML-RPC command line interface
Home-page: https://git.linaro.org/lava/lavacli.git
Author: Rémi Duraffort
Author-email: ivoire@videolan.org
License: AGPLv3+
Description: UNKNOWN
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Communications
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Networking
lavacli-0.7/lavacli.egg-info/top_level.txt 0000644 0001750 0001750 00000000010 13241511244 022354 0 ustar stylesen stylesen 0000000 0000000 lavacli
lavacli-0.7/PKG-INFO 0000644 0001750 0001750 00000001551 13241511244 015625 0 ustar stylesen stylesen 0000000 0000000 Metadata-Version: 1.1
Name: lavacli
Version: 0.7
Summary: LAVA XML-RPC command line interface
Home-page: https://git.linaro.org/lava/lavacli.git
Author: Rémi Duraffort
Author-email: ivoire@videolan.org
License: AGPLv3+
Description: UNKNOWN
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Communications
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Networking
lavacli-0.7/setup.cfg 0000644 0001750 0001750 00000000046 13241511244 016347 0 ustar stylesen stylesen 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
lavacli-0.7/setup.py 0000644 0001750 0001750 00000004054 13226116343 016247 0 ustar stylesen stylesen 0000000 0000000 #!/usr/bin/python3
# -*- coding: utf-8 -*-
# vim: set ts=4
# Copyright 2017 Rémi Duraffort
# This file is part of lavacli.
#
# lavacli is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# lavacli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with lavacli. If not, see
from setuptools import setup
# grab metadata without importing the module
metadata = {}
with open("lavacli/__about__.py", encoding="utf-8") as fp:
exec(fp.read(), metadata)
# Setup the package
setup(
name='lavacli',
version=metadata['__version__'],
description=metadata['__description__'],
author=metadata['__author__'],
author_email='ivoire@videolan.org',
license=metadata['__license__'],
url=metadata['__url__'],
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Communications",
"Topic :: Software Development :: Testing",
"Topic :: System :: Networking",
],
packages=['lavacli', 'lavacli.commands'],
entry_points={
'console_scripts': [
'lavacli = lavacli:main'
]
},
install_requires=[
"PyYAML",
"pyzmq",
"requests",
],
zip_safe=True
)