X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/aad81f98ca9fca86aeb5292ae87dca300fc6b83a..f99010b2058cb2f2fc08e50fd475c473308b591f:/scripts/gnt-job diff --git a/scripts/gnt-job b/scripts/gnt-job index 3c831be..2e71bae 100755 --- a/scripts/gnt-job +++ b/scripts/gnt-job @@ -18,26 +18,32 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. +"""Job related commands""" + +# pylint: disable-msg=W0401,W0613,W0614,C0103 +# W0401: Wildcard import ganeti.cli +# W0613: Unused argument, since all functions follow the same API +# W0614: Unused import %s from wildcard import (since we need cli) +# C0103: Invalid name gnt-job import sys -import os -import itertools -import time -from optparse import make_option -from cStringIO import StringIO from ganeti.cli import * -from ganeti import opcodes -from ganeti import logger from ganeti import constants -from ganeti import utils from ganeti import errors +from ganeti import utils +from ganeti import cli +#: default list of fields for L{ListJobs} _LIST_DEF_FIELDS = ["id", "status", "summary"] +#: map converting the job status contants to user-visible +#: names _USER_JOB_STATUS = { constants.JOB_STATUS_QUEUED: "queued", + constants.JOB_STATUS_WAITLOCK: "waiting", + constants.JOB_STATUS_CANCELING: "canceling", constants.JOB_STATUS_RUNNING: "running", constants.JOB_STATUS_CANCELED: "canceled", constants.JOB_STATUS_SUCCESS: "success", @@ -48,6 +54,12 @@ _USER_JOB_STATUS = { def ListJobs(opts, args): """List the jobs + @param opts: the command line options selected by the user + @type args: list + @param args: should be an empty list + @rtype: int + @return: the desired exit code + """ if opts.output is None: selected_fields = _LIST_DEF_FIELDS @@ -56,7 +68,7 @@ def ListJobs(opts, args): else: selected_fields = opts.output.split(",") - output = GetClient().QueryJobs(None, selected_fields) + output = GetClient().QueryJobs(args, selected_fields) if not opts.no_headers: # TODO: Implement more fields headers = { @@ -68,20 +80,22 @@ def ListJobs(opts, args): "oplog": "OpCode_log", "summary": "Summary", "opstart": "OpCode_start", + "opexec": "OpCode_exec", "opend": "OpCode_end", "start_ts": "Start", "end_ts": "End", "received_ts": "Received", + "lock_status": "LockStatus", } else: headers = None - # we don't have yet unitfields here - unitfields = None - numfields = None - # change raw values to nicer strings - for row in output: + for row_id, row in enumerate(output): + if row is None: + ToStderr("No such job: %s" % args[row_id]) + continue + for idx, field in enumerate(selected_fields): val = row[idx] if field == "status": @@ -93,86 +107,151 @@ def ListJobs(opts, args): val = ",".join(val) elif field in ("start_ts", "end_ts", "received_ts"): val = FormatTimestamp(val) - elif field in ("opstart", "opend"): + elif field in ("opstart", "opexec", "opend"): val = [FormatTimestamp(entry) for entry in val] + elif field == "lock_status" and not val: + val = "-" row[idx] = str(val) data = GenerateTable(separator=opts.separator, headers=headers, - fields=selected_fields, unitfields=unitfields, - numfields=numfields, data=output) + fields=selected_fields, data=output) for line in data: - print line + ToStdout(line) return 0 def ArchiveJobs(opts, args): + """Archive jobs. + + @param opts: the command line options selected by the user + @type args: list + @param args: should contain the job IDs to be archived + @rtype: int + @return: the desired exit code + + """ client = GetClient() + rcode = 0 for job_id in args: - client.ArchiveJob(job_id) + if not client.ArchiveJob(job_id): + ToStderr("Failed to archive job with ID '%s'", job_id) + rcode = 1 + + return rcode + + +def AutoArchiveJobs(opts, args): + """Archive jobs based on age. + + This will archive jobs based on their age, or all jobs if a 'all' is + passed. + + @param opts: the command line options selected by the user + @type args: list + @param args: should contain only one element, the age as a time spec + that can be parsed by L{ganeti.cli.ParseTimespec} or the + keyword I{all}, which will cause all jobs to be archived + @rtype: int + @return: the desired exit code + + """ + client = GetClient() + + age = args[0] + + if age == 'all': + age = -1 + else: + age = ParseTimespec(age) + + (archived_count, jobs_left) = client.AutoArchiveJobs(age) + ToStdout("Archived %s jobs, %s unchecked left", archived_count, jobs_left) return 0 def CancelJobs(opts, args): + """Cancel not-yet-started jobs. + + @param opts: the command line options selected by the user + @type args: list + @param args: should contain the job IDs to be cancelled + @rtype: int + @return: the desired exit code + + """ client = GetClient() for job_id in args: - client.CancelJob(job_id) + (_, msg) = client.CancelJob(job_id) + ToStdout(msg) + # TODO: Different exit value if not all jobs were canceled? return 0 def ShowJobs(opts, args): - """List the jobs + """Show detailed information about jobs. + + @param opts: the command line options selected by the user + @type args: list + @param args: should contain the job IDs to be queried + @rtype: int + @return: the desired exit code """ - def format(level, text): + def format_msg(level, text): """Display the text indented.""" - print "%s%s" % (" " * level, text) + ToStdout("%s%s", " " * level, text) def result_helper(value): """Format a result field in a nice way.""" if isinstance(value, (tuple, list)): - return "[%s]" % (", ".join(str(elem) for elem in value)) + return "[%s]" % utils.CommaJoin(value) else: return str(value) selected_fields = [ "id", "status", "ops", "opresult", "opstatus", "oplog", - "opstart", "opend", "received_ts", "start_ts", "end_ts", + "opstart", "opexec", "opend", "received_ts", "start_ts", "end_ts", ] result = GetClient().QueryJobs(args, selected_fields) first = True - for entry, arg_job in zip(result, args): + for idx, entry in enumerate(result): if not first: - format(0, "") + format_msg(0, "") else: first = False if entry is None: - format(0, "Job ID %s not found" % arg_job) + if idx <= len(args): + format_msg(0, "Job ID %s not found" % args[idx]) + else: + # this should not happen, when we don't pass args it will be a + # valid job returned + format_msg(0, "Job ID requested as argument %s not found" % (idx + 1)) continue (job_id, status, ops, opresult, opstatus, oplog, - opstart, opend, recv_ts, start_ts, end_ts) = entry - format(0, "Job ID: %s" % job_id) + opstart, opexec, opend, recv_ts, start_ts, end_ts) = entry + format_msg(0, "Job ID: %s" % job_id) if status in _USER_JOB_STATUS: status = _USER_JOB_STATUS[status] else: - raise errors.ProgrammerError("Unknown job status code '%s'" % val) + raise errors.ProgrammerError("Unknown job status code '%s'" % status) - format(1, "Status: %s" % status) + format_msg(1, "Status: %s" % status) if recv_ts is not None: - format(1, "Received: %s" % FormatTimestamp(recv_ts)) + format_msg(1, "Received: %s" % FormatTimestamp(recv_ts)) else: - format(1, "Missing received timestamp (%s)" % str(recv_ts)) + format_msg(1, "Missing received timestamp (%s)" % str(recv_ts)) if start_ts is not None: if recv_ts is not None: @@ -180,9 +259,10 @@ def ShowJobs(opts, args): delta = " (delta %.6fs)" % d1 else: delta = "" - format(1, "Processing start: %s%s" % (FormatTimestamp(start_ts), delta)) + format_msg(1, "Processing start: %s%s" % + (FormatTimestamp(start_ts), delta)) else: - format(1, "Processing start: unknown (%s)" % str(start_ts)) + format_msg(1, "Processing start: unknown (%s)" % str(start_ts)) if end_ts is not None: if start_ts is not None: @@ -190,79 +270,119 @@ def ShowJobs(opts, args): delta = " (delta %.6fs)" % d2 else: delta = "" - format(1, "Processing end: %s%s" % (FormatTimestamp(end_ts), delta)) + format_msg(1, "Processing end: %s%s" % + (FormatTimestamp(end_ts), delta)) else: - format(1, "Processing end: unknown (%s)" % str(end_ts)) + format_msg(1, "Processing end: unknown (%s)" % str(end_ts)) if end_ts is not None and recv_ts is not None: d3 = end_ts[0] - recv_ts[0] + (end_ts[1] - recv_ts[1]) / 1000000.0 - format(1, "Total processing time: %.6f seconds" % d3) + format_msg(1, "Total processing time: %.6f seconds" % d3) else: - format(1, "Total processing time: N/A") - format(1, "Opcodes:") - for (opcode, result, status, log, s_ts, e_ts) in \ - zip(ops, opresult, opstatus, oplog, opstart, opend): - format(2, "%s" % opcode["OP_ID"]) - format(3, "Status: %s" % status) + format_msg(1, "Total processing time: N/A") + format_msg(1, "Opcodes:") + for (opcode, result, status, log, s_ts, x_ts, e_ts) in \ + zip(ops, opresult, opstatus, oplog, opstart, opexec, opend): + format_msg(2, "%s" % opcode["OP_ID"]) + format_msg(3, "Status: %s" % status) if isinstance(s_ts, (tuple, list)): - format(3, "Processing start: %s" % FormatTimestamp(s_ts)) + format_msg(3, "Processing start: %s" % FormatTimestamp(s_ts)) else: - format(3, "No processing start time") + format_msg(3, "No processing start time") + if isinstance(x_ts, (tuple, list)): + format_msg(3, "Execution start: %s" % FormatTimestamp(x_ts)) + else: + format_msg(3, "No execution start time") if isinstance(e_ts, (tuple, list)): - format(3, "Processing end: %s" % FormatTimestamp(e_ts)) + format_msg(3, "Processing end: %s" % FormatTimestamp(e_ts)) else: - format(3, "No processing end time") - format(3, "Input fields:") + format_msg(3, "No processing end time") + format_msg(3, "Input fields:") for key, val in opcode.iteritems(): if key == "OP_ID": continue if isinstance(val, (tuple, list)): - val = ",".join(val) - format(4, "%s: %s" % (key, val)) + val = ",".join([str(item) for item in val]) + format_msg(4, "%s: %s" % (key, val)) if result is None: - format(3, "No output data") + format_msg(3, "No output data") elif isinstance(result, (tuple, list)): if not result: - format(3, "Result: empty sequence") + format_msg(3, "Result: empty sequence") else: - format(3, "Result:") + format_msg(3, "Result:") for elem in result: - format(4, result_helper(elem)) + format_msg(4, result_helper(elem)) elif isinstance(result, dict): if not result: - format(3, "Result: empty dictionary") + format_msg(3, "Result: empty dictionary") else: for key, val in result.iteritems(): - format(4, "%s: %s" % (key, result_helper(val))) + format_msg(4, "%s: %s" % (key, result_helper(val))) else: - format(3, "Result: %s" % result) - format(3, "Execution log:") + format_msg(3, "Result: %s" % result) + format_msg(3, "Execution log:") for serial, log_ts, log_type, log_msg in log: time_txt = FormatTimestamp(log_ts) - encoded = str(log_msg).encode('string_escape') - format(4, "%s:%s:%s %s" % (serial, time_txt, log_type, encoded)) + encoded = FormatLogMessage(log_type, log_msg) + format_msg(4, "%s:%s:%s %s" % (serial, time_txt, log_type, encoded)) return 0 +def WatchJob(opts, args): + """Follow a job and print its output as it arrives. + + @param opts: the command line options selected by the user + @type args: list + @param args: Contains the job ID + @rtype: int + @return: the desired exit code + + """ + job_id = args[0] + + msg = ("Output from job %s follows" % job_id) + ToStdout(msg) + ToStdout("-" * len(msg)) + + retcode = 0 + try: + cli.PollJob(job_id) + except errors.GenericError, err: + (retcode, job_result) = cli.FormatError(err) + ToStderr("Job %s failed: %s", job_id, job_result) + + return retcode + + commands = { - 'list': (ListJobs, ARGS_NONE, - [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], - "", "List the jobs and their status. The available fields are" - " (see the man page for details): id, status, op_list," - " op_status, op_result." - " The default field" - " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS)), - 'archive': (ArchiveJobs, ARGS_ANY, - [DEBUG_OPT], - " [ ...]", - "Archive specified jobs"), - 'cancel': (CancelJobs, ARGS_ANY, - [DEBUG_OPT], - " [ ...]", - "Cancel specified jobs"), - 'info': (ShowJobs, ARGS_ANY, [DEBUG_OPT], - " [ ...]", - "Show detailed information about the specified jobs"), + 'list': ( + ListJobs, [ArgJobId()], + [NOHDR_OPT, SEP_OPT, FIELDS_OPT], + "[job_id ...]", + "List the jobs and their status. The available fields are" + " (see the man page for details): id, status, op_list," + " op_status, op_result." + " The default field" + " list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS)), + 'archive': ( + ArchiveJobs, [ArgJobId(min=1)], [], + " [ ...]", "Archive specified jobs"), + 'autoarchive': ( + AutoArchiveJobs, + [ArgSuggest(min=1, max=1, choices=["1d", "1w", "4w", "all"])], + [], + "", "Auto archive jobs older than the given age"), + 'cancel': ( + CancelJobs, [ArgJobId(min=1)], [], + " [ ...]", "Cancel specified jobs"), + 'info': ( + ShowJobs, [ArgJobId(min=1)], [], + " [ ...]", + "Show detailed information about the specified jobs"), + 'watch': ( + WatchJob, [ArgJobId(min=1, max=1)], [], + "", "Follows a job and prints its output as it arrives"), }