gnt_job.py 11.2 KB
Newer Older
1
#
Iustin Pop's avatar
Iustin Pop committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#

# Copyright (C) 2006, 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

Iustin Pop's avatar
Iustin Pop committed
21
"""Job related commands"""
Iustin Pop's avatar
Iustin Pop committed
22

23
# pylint: disable-msg=W0401,W0613,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0613: Unused argument, since all functions follow the same API
26
# W0614: Unused import %s from wildcard import (since we need cli)
Iustin Pop's avatar
Iustin Pop committed
27
# C0103: Invalid name gnt-job
28

Iustin Pop's avatar
Iustin Pop committed
29 30 31
from ganeti.cli import *
from ganeti import constants
from ganeti import errors
32
from ganeti import utils
33
from ganeti import cli
Iustin Pop's avatar
Iustin Pop committed
34 35


Iustin Pop's avatar
Iustin Pop committed
36
#: default list of fields for L{ListJobs}
37
_LIST_DEF_FIELDS = ["id", "status", "summary"]
38

Iustin Pop's avatar
Iustin Pop committed
39 40
#: map converting the job status contants to user-visible
#: names
41 42
_USER_JOB_STATUS = {
  constants.JOB_STATUS_QUEUED: "queued",
Iustin Pop's avatar
Iustin Pop committed
43
  constants.JOB_STATUS_WAITLOCK: "waiting",
44
  constants.JOB_STATUS_CANCELING: "canceling",
45 46 47 48 49 50
  constants.JOB_STATUS_RUNNING: "running",
  constants.JOB_STATUS_CANCELED: "canceled",
  constants.JOB_STATUS_SUCCESS: "success",
  constants.JOB_STATUS_ERROR: "error",
  }

51

Iustin Pop's avatar
Iustin Pop committed
52 53 54
def ListJobs(opts, args):
  """List the jobs

Iustin Pop's avatar
Iustin Pop committed
55 56 57 58 59 60
  @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

Iustin Pop's avatar
Iustin Pop committed
61
  """
62
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
Iustin Pop's avatar
Iustin Pop committed
63

Iustin Pop's avatar
Iustin Pop committed
64
  output = GetClient().QueryJobs(args, selected_fields)
Iustin Pop's avatar
Iustin Pop committed
65
  if not opts.no_headers:
66
    # TODO: Implement more fields
Iustin Pop's avatar
Iustin Pop committed
67 68 69
    headers = {
      "id": "ID",
      "status": "Status",
70
      "priority": "Prio",
71 72 73
      "ops": "OpCodes",
      "opresult": "OpCode_result",
      "opstatus": "OpCode_status",
74
      "oplog": "OpCode_log",
75
      "summary": "Summary",
76
      "opstart": "OpCode_start",
Iustin Pop's avatar
Iustin Pop committed
77
      "opexec": "OpCode_exec",
78
      "opend": "OpCode_end",
79
      "oppriority": "OpCode_prio",
80 81 82
      "start_ts": "Start",
      "end_ts": "End",
      "received_ts": "Received",
Iustin Pop's avatar
Iustin Pop committed
83 84 85 86
      }
  else:
    headers = None

87 88
  numfields = ["priority"]

Iustin Pop's avatar
Iustin Pop committed
89
  # change raw values to nicer strings
90 91 92 93 94
  for row_id, row in enumerate(output):
    if row is None:
      ToStderr("No such job: %s" % args[row_id])
      continue

Iustin Pop's avatar
Iustin Pop committed
95 96 97
    for idx, field in enumerate(selected_fields):
      val = row[idx]
      if field == "status":
98 99
        if val in _USER_JOB_STATUS:
          val = _USER_JOB_STATUS[val]
Iustin Pop's avatar
Iustin Pop committed
100 101
        else:
          raise errors.ProgrammerError("Unknown job status code '%s'" % val)
102 103
      elif field == "summary":
        val = ",".join(val)
104 105
      elif field in ("start_ts", "end_ts", "received_ts"):
        val = FormatTimestamp(val)
Iustin Pop's avatar
Iustin Pop committed
106
      elif field in ("opstart", "opexec", "opend"):
107
        val = [FormatTimestamp(entry) for entry in val]
Iustin Pop's avatar
Iustin Pop committed
108 109 110 111

      row[idx] = str(val)

  data = GenerateTable(separator=opts.separator, headers=headers,
112 113
                       fields=selected_fields, data=output,
                       numfields=numfields)
Iustin Pop's avatar
Iustin Pop committed
114
  for line in data:
115
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
116 117 118 119

  return 0


120
def ArchiveJobs(opts, args):
Iustin Pop's avatar
Iustin Pop committed
121 122 123 124 125 126 127 128 129
  """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

  """
130 131
  client = GetClient()

Iustin Pop's avatar
Iustin Pop committed
132
  rcode = 0
133
  for job_id in args:
Iustin Pop's avatar
Iustin Pop committed
134 135 136
    if not client.ArchiveJob(job_id):
      ToStderr("Failed to archive job with ID '%s'", job_id)
      rcode = 1
137

Iustin Pop's avatar
Iustin Pop committed
138
  return rcode
139 140


Iustin Pop's avatar
Iustin Pop committed
141
def AutoArchiveJobs(opts, args):
Iustin Pop's avatar
Iustin Pop committed
142 143 144 145 146 147 148 149
  """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
Iustin Pop's avatar
Iustin Pop committed
150 151
      that can be parsed by L{ganeti.cli.ParseTimespec} or the
      keyword I{all}, which will cause all jobs to be archived
Iustin Pop's avatar
Iustin Pop committed
152 153 154 155
  @rtype: int
  @return: the desired exit code

  """
Iustin Pop's avatar
Iustin Pop committed
156 157 158 159 160 161 162 163 164
  client = GetClient()

  age = args[0]

  if age == 'all':
    age = -1
  else:
    age = ParseTimespec(age)

165 166 167
  (archived_count, jobs_left) = client.AutoArchiveJobs(age)
  ToStdout("Archived %s jobs, %s unchecked left", archived_count, jobs_left)

Iustin Pop's avatar
Iustin Pop committed
168 169 170
  return 0


171
def CancelJobs(opts, args):
Iustin Pop's avatar
Iustin Pop committed
172 173 174 175 176 177 178 179 180
  """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

  """
181
  client = GetClient()
182
  result = constants.EXIT_SUCCESS
183 184

  for job_id in args:
185 186 187 188 189
    (success, msg) = client.CancelJob(job_id)

    if not success:
      result = constants.EXIT_FAILURE

190
    ToStdout(msg)
191

192
  return result
193 194


Iustin Pop's avatar
Iustin Pop committed
195
def ShowJobs(opts, args):
Iustin Pop's avatar
Iustin Pop committed
196 197 198 199 200 201 202
  """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
Iustin Pop's avatar
Iustin Pop committed
203 204

  """
Iustin Pop's avatar
Iustin Pop committed
205
  def format_msg(level, text):
Iustin Pop's avatar
Iustin Pop committed
206
    """Display the text indented."""
207
    ToStdout("%s%s", "  " * level, text)
Iustin Pop's avatar
Iustin Pop committed
208 209 210 211

  def result_helper(value):
    """Format a result field in a nice way."""
    if isinstance(value, (tuple, list)):
212
      return "[%s]" % utils.CommaJoin(value)
Iustin Pop's avatar
Iustin Pop committed
213 214 215
    else:
      return str(value)

216 217
  selected_fields = [
    "id", "status", "ops", "opresult", "opstatus", "oplog",
Iustin Pop's avatar
Iustin Pop committed
218
    "opstart", "opexec", "opend", "received_ts", "start_ts", "end_ts",
219
    ]
Iustin Pop's avatar
Iustin Pop committed
220 221 222 223 224

  result = GetClient().QueryJobs(args, selected_fields)

  first = True

225
  for idx, entry in enumerate(result):
Iustin Pop's avatar
Iustin Pop committed
226
    if not first:
Iustin Pop's avatar
Iustin Pop committed
227
      format_msg(0, "")
Iustin Pop's avatar
Iustin Pop committed
228 229
    else:
      first = False
230 231

    if entry is None:
232
      if idx <= len(args):
Iustin Pop's avatar
Iustin Pop committed
233
        format_msg(0, "Job ID %s not found" % args[idx])
234 235 236
      else:
        # this should not happen, when we don't pass args it will be a
        # valid job returned
Iustin Pop's avatar
Iustin Pop committed
237
        format_msg(0, "Job ID requested as argument %s not found" % (idx + 1))
238 239 240
      continue

    (job_id, status, ops, opresult, opstatus, oplog,
Iustin Pop's avatar
Iustin Pop committed
241
     opstart, opexec, opend, recv_ts, start_ts, end_ts) = entry
Iustin Pop's avatar
Iustin Pop committed
242
    format_msg(0, "Job ID: %s" % job_id)
Iustin Pop's avatar
Iustin Pop committed
243 244 245
    if status in _USER_JOB_STATUS:
      status = _USER_JOB_STATUS[status]
    else:
246
      raise errors.ProgrammerError("Unknown job status code '%s'" % status)
Iustin Pop's avatar
Iustin Pop committed
247

Iustin Pop's avatar
Iustin Pop committed
248
    format_msg(1, "Status: %s" % status)
249 250

    if recv_ts is not None:
Iustin Pop's avatar
Iustin Pop committed
251
      format_msg(1, "Received:         %s" % FormatTimestamp(recv_ts))
252
    else:
Iustin Pop's avatar
Iustin Pop committed
253
      format_msg(1, "Missing received timestamp (%s)" % str(recv_ts))
254 255 256 257 258 259 260

    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 = ""
Iustin Pop's avatar
Iustin Pop committed
261 262
      format_msg(1, "Processing start: %s%s" %
                 (FormatTimestamp(start_ts), delta))
263
    else:
Iustin Pop's avatar
Iustin Pop committed
264
      format_msg(1, "Processing start: unknown (%s)" % str(start_ts))
265 266 267 268 269 270 271

    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 = ""
Iustin Pop's avatar
Iustin Pop committed
272 273
      format_msg(1, "Processing end:   %s%s" %
                 (FormatTimestamp(end_ts), delta))
274
    else:
Iustin Pop's avatar
Iustin Pop committed
275
      format_msg(1, "Processing end:   unknown (%s)" % str(end_ts))
276 277 278

    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
Iustin Pop's avatar
Iustin Pop committed
279
      format_msg(1, "Total processing time: %.6f seconds" % d3)
280
    else:
Iustin Pop's avatar
Iustin Pop committed
281 282
      format_msg(1, "Total processing time: N/A")
    format_msg(1, "Opcodes:")
Iustin Pop's avatar
Iustin Pop committed
283 284
    for (opcode, result, status, log, s_ts, x_ts, e_ts) in \
            zip(ops, opresult, opstatus, oplog, opstart, opexec, opend):
Iustin Pop's avatar
Iustin Pop committed
285 286
      format_msg(2, "%s" % opcode["OP_ID"])
      format_msg(3, "Status: %s" % status)
287
      if isinstance(s_ts, (tuple, list)):
Iustin Pop's avatar
Iustin Pop committed
288
        format_msg(3, "Processing start: %s" % FormatTimestamp(s_ts))
289
      else:
Iustin Pop's avatar
Iustin Pop committed
290
        format_msg(3, "No processing start time")
Iustin Pop's avatar
Iustin Pop committed
291
      if isinstance(x_ts, (tuple, list)):
Iustin Pop's avatar
Iustin Pop committed
292
        format_msg(3, "Execution start:  %s" % FormatTimestamp(x_ts))
Iustin Pop's avatar
Iustin Pop committed
293
      else:
Iustin Pop's avatar
Iustin Pop committed
294
        format_msg(3, "No execution start time")
295
      if isinstance(e_ts, (tuple, list)):
Iustin Pop's avatar
Iustin Pop committed
296
        format_msg(3, "Processing end:   %s" % FormatTimestamp(e_ts))
297
      else:
Iustin Pop's avatar
Iustin Pop committed
298 299
        format_msg(3, "No processing end time")
      format_msg(3, "Input fields:")
300
      for key in utils.NiceSort(opcode.keys()):
Iustin Pop's avatar
Iustin Pop committed
301 302
        if key == "OP_ID":
          continue
303
        val = opcode[key]
Iustin Pop's avatar
Iustin Pop committed
304
        if isinstance(val, (tuple, list)):
305
          val = ",".join([str(item) for item in val])
Iustin Pop's avatar
Iustin Pop committed
306
        format_msg(4, "%s: %s" % (key, val))
Iustin Pop's avatar
Iustin Pop committed
307
      if result is None:
Iustin Pop's avatar
Iustin Pop committed
308
        format_msg(3, "No output data")
Iustin Pop's avatar
Iustin Pop committed
309 310
      elif isinstance(result, (tuple, list)):
        if not result:
Iustin Pop's avatar
Iustin Pop committed
311
          format_msg(3, "Result: empty sequence")
Iustin Pop's avatar
Iustin Pop committed
312
        else:
Iustin Pop's avatar
Iustin Pop committed
313
          format_msg(3, "Result:")
Iustin Pop's avatar
Iustin Pop committed
314
          for elem in result:
Iustin Pop's avatar
Iustin Pop committed
315
            format_msg(4, result_helper(elem))
Iustin Pop's avatar
Iustin Pop committed
316 317
      elif isinstance(result, dict):
        if not result:
Iustin Pop's avatar
Iustin Pop committed
318
          format_msg(3, "Result: empty dictionary")
Iustin Pop's avatar
Iustin Pop committed
319 320
        else:
          for key, val in result.iteritems():
Iustin Pop's avatar
Iustin Pop committed
321
            format_msg(4, "%s: %s" % (key, result_helper(val)))
Iustin Pop's avatar
Iustin Pop committed
322
      else:
Iustin Pop's avatar
Iustin Pop committed
323 324
        format_msg(3, "Result: %s" % result)
      format_msg(3, "Execution log:")
325 326
      for serial, log_ts, log_type, log_msg in log:
        time_txt = FormatTimestamp(log_ts)
327
        encoded = FormatLogMessage(log_type, log_msg)
Iustin Pop's avatar
Iustin Pop committed
328
        format_msg(4, "%s:%s:%s %s" % (serial, time_txt, log_type, encoded))
Iustin Pop's avatar
Iustin Pop committed
329 330 331
  return 0


332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
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


Iustin Pop's avatar
Iustin Pop committed
358
commands = {
359 360
  'list': (
    ListJobs, [ArgJobId()],
361
    [NOHDR_OPT, SEP_OPT, FIELDS_OPT],
362 363 364 365 366
    "[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"
367
    " list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS)),
368
  'archive': (
369
    ArchiveJobs, [ArgJobId(min=1)], [],
370 371 372
    "<job-id> [<job-id> ...]", "Archive specified jobs"),
  'autoarchive': (
    AutoArchiveJobs,
373
    [ArgSuggest(min=1, max=1, choices=["1d", "1w", "4w", "all"])],
374
    [],
375 376
    "<age>", "Auto archive jobs older than the given age"),
  'cancel': (
377
    CancelJobs, [ArgJobId(min=1)], [],
378 379
    "<job-id> [<job-id> ...]", "Cancel specified jobs"),
  'info': (
380
    ShowJobs, [ArgJobId(min=1)], [],
381 382 383
    "<job-id> [<job-id> ...]",
    "Show detailed information about the specified jobs"),
  'watch': (
384
    WatchJob, [ArgJobId(min=1, max=1)], [],
385
    "<job-id>", "Follows a job and prints its output as it arrives"),
Iustin Pop's avatar
Iustin Pop committed
386 387 388
  }


389 390
def Main():
  return GenericMain(commands)