X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/35049ff2adcd6b5b1b64efb2db58b3b0322975b7..f1de3563520ceb6944e835503f829d654f45cdc4:/scripts/gnt-job diff --git a/scripts/gnt-job b/scripts/gnt-job old mode 100644 new mode 100755 index e98f637..ce81709 --- a/scripts/gnt-job +++ b/scripts/gnt-job @@ -19,88 +19,319 @@ # 02110-1301, USA. +# pylint: disable-msg=W0401,W0614 +# W0401: Wildcard import ganeti.cli +# W0614: Unused import %s from wildcard import (since we need cli) + import sys -import os -import itertools -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 + + +#: 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", + constants.JOB_STATUS_ERROR: "error", + } 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 = ["id", "status"] + selected_fields = _LIST_DEF_FIELDS + elif opts.output.startswith("+"): + selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",") else: selected_fields = opts.output.split(",") - query = { - "object": "jobs", - "fields": selected_fields, - "names": [], - } - - output = SubmitQuery(query) + output = GetClient().QueryJobs(args, selected_fields) if not opts.no_headers: + # TODO: Implement more fields headers = { "id": "ID", "status": "Status", - "op_list": "OpCodes", - "op_status": "OpStatus", - "op_result": "OpResult", + "ops": "OpCodes", + "opresult": "OpCode_result", + "opstatus": "OpCode_status", + "oplog": "OpCode_log", + "summary": "Summary", + "opstart": "OpCode_start", + "opend": "OpCode_end", + "start_ts": "Start", + "end_ts": "End", + "received_ts": "Received", } else: headers = None - # we don't have yet unitfields here - unitfields = None - numfields = ["id"] - # change raw values to nicer strings for row in output: for idx, field in enumerate(selected_fields): val = row[idx] if field == "status": - if val == opcodes.Job.STATUS_PENDING: - val = "pending" - elif val == opcodes.Job.STATUS_RUNNING: - val = "running" - elif val == opcodes.Job.STATUS_SUCCESS: - val = "finished" - elif val == opcodes.Job.STATUS_FAIL: - val = "failed" - elif val == opcodes.Job.STATUS_ABORT: - val = "aborted" + if val in _USER_JOB_STATUS: + val = _USER_JOB_STATUS[val] else: raise errors.ProgrammerError("Unknown job status code '%s'" % val) + elif field == "summary": + val = ",".join(val) + elif field in ("start_ts", "end_ts", "received_ts"): + val = FormatTimestamp(val) + elif field in ("opstart", "opend"): + val = [FormatTimestamp(entry) for entry in 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() + + for job_id in args: + client.ArchiveJob(job_id) + + return 0 + + +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: + (success, msg) = client.CancelJob(job_id) + ToStdout(msg) + + # TODO: Different exit value if not all jobs were canceled? + return 0 + + +def ShowJobs(opts, args): + """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): + """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)) + else: + return str(value) + + selected_fields = [ + "id", "status", "ops", "opresult", "opstatus", "oplog", + "opstart", "opend", "received_ts", "start_ts", "end_ts", + ] + + result = GetClient().QueryJobs(args, selected_fields) + + first = True + + for idx, entry in enumerate(result): + if not first: + format(0, "") + else: + first = False + + if entry is None: + if idx <= len(args): + format(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)) + continue + + (job_id, status, ops, opresult, opstatus, oplog, + opstart, opend, recv_ts, start_ts, end_ts) = entry + format(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) + + if recv_ts is not None: + format(1, "Received: %s" % FormatTimestamp(recv_ts)) + else: + format(1, "Missing received timestamp (%s)" % str(recv_ts)) + + if start_ts is not None: + if recv_ts is not None: + d1 = start_ts[0] - recv_ts[0] + (start_ts[1] - recv_ts[1]) / 1000000.0 + delta = " (delta %.6fs)" % d1 + else: + delta = "" + format(1, "Processing start: %s%s" % (FormatTimestamp(start_ts), delta)) + else: + format(1, "Processing start: unknown (%s)" % str(start_ts)) + + if end_ts is not None: + if start_ts is not None: + d2 = end_ts[0] - start_ts[0] + (end_ts[1] - start_ts[1]) / 1000000.0 + delta = " (delta %.6fs)" % d2 + else: + delta = "" + format(1, "Processing end: %s%s" % (FormatTimestamp(end_ts), delta)) + else: + format(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) + 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) + if isinstance(s_ts, (tuple, list)): + format(3, "Processing start: %s" % FormatTimestamp(s_ts)) + else: + format(3, "No processing start time") + if isinstance(e_ts, (tuple, list)): + format(3, "Processing end: %s" % FormatTimestamp(e_ts)) + else: + format(3, "No processing end time") + format(3, "Input fields:") + for key, val in opcode.iteritems(): + if key == "OP_ID": + continue + if isinstance(val, (tuple, list)): + val = ",".join([str(item) for item in val]) + format(4, "%s: %s" % (key, val)) + if result is None: + format(3, "No output data") + elif isinstance(result, (tuple, list)): + if not result: + format(3, "Result: empty sequence") + else: + format(3, "Result:") + for elem in result: + format(4, result_helper(elem)) + elif isinstance(result, dict): + if not result: + format(3, "Result: empty dictionary") + else: + for key, val in result.iteritems(): + format(4, "%s: %s" % (key, result_helper(val))) + else: + format(3, "Result: %s" % result) + format(3, "Execution log:") + for serial, log_ts, log_type, log_msg in log: + time_txt = FormatTimestamp(log_ts) + encoded = utils.SafeEncode(log_msg) + format(4, "%s:%s:%s %s" % (serial, time_txt, log_type, encoded)) return 0 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" + 'list': (ListJobs, ARGS_ANY, + [DEBUG_OPT, 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): id, status."), + " 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"), }