X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/917b4e566be00703508ab7f736140a65f58fb48d..f99010b2058cb2f2fc08e50fd475c473308b591f:/scripts/gnt-job?ds=sidebyside diff --git a/scripts/gnt-job b/scripts/gnt-job index 29df6a9..2e71bae 100755 --- a/scripts/gnt-job +++ b/scripts/gnt-job @@ -18,16 +18,21 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. +"""Job related commands""" -# pylint: disable-msg=W0401,W0614 +# 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 from ganeti.cli import * from ganeti import constants from ganeti import errors +from ganeti import utils +from ganeti import cli #: default list of fields for L{ListJobs} @@ -38,6 +43,7 @@ _LIST_DEF_FIELDS = ["id", "status", "summary"] _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", @@ -62,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 = { @@ -74,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": @@ -99,14 +107,15 @@ 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: ToStdout(line) @@ -125,10 +134,13 @@ def ArchiveJobs(opts, args): """ 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 0 + return rcode def AutoArchiveJobs(opts, args): @@ -140,8 +152,8 @@ def AutoArchiveJobs(opts, args): @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{cli.ParseTimespec} or the keyword I{all}, - which will cause all jobs to be archived + 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 @@ -155,7 +167,9 @@ def AutoArchiveJobs(opts, args): else: age = ParseTimespec(age) - client.AutoArchiveJobs(age) + (archived_count, jobs_left) = client.AutoArchiveJobs(age) + ToStdout("Archived %s jobs, %s unchecked left", archived_count, jobs_left) + return 0 @@ -172,8 +186,10 @@ def CancelJobs(opts, args): 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 @@ -187,20 +203,20 @@ def ShowJobs(opts, args): @return: the desired exit code """ - def format(level, text): + def format_msg(level, text): """Display the text indented.""" 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) @@ -209,33 +225,33 @@ def ShowJobs(opts, args): for idx, entry in enumerate(result): if not first: - format(0, "") + format_msg(0, "") else: first = False if entry is None: if idx <= len(args): - format(0, "Job ID %s not found" % args[idx]) + 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(0, "Job ID requested as argument %s not found" % (idx + 1)) + 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'" % 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: @@ -243,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: @@ -253,83 +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"), - 'autoarchive': (AutoArchiveJobs, ARGS_ONE, - [DEBUG_OPT], - "", - "Auto archive jobs older than the given age"), - '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"), }