gnt-job 10.1 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python
#

# 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.


22
23
24
25
# pylint: disable-msg=W0401,W0614
# W0401: Wildcard import ganeti.cli
# W0614: Unused import %s from wildcard import (since we need cli)

Iustin Pop's avatar
Iustin Pop committed
26
27
28
29
30
import sys

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


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

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

49

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

Iustin Pop's avatar
Iustin Pop committed
53
54
55
56
57
58
  @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
59
60
  """
  if opts.output is None:
61
62
63
    selected_fields = _LIST_DEF_FIELDS
  elif opts.output.startswith("+"):
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
Iustin Pop's avatar
Iustin Pop committed
64
65
66
  else:
    selected_fields = opts.output.split(",")

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

  # we don't have yet unitfields here
  unitfields = None
89
  numfields = None
Iustin Pop's avatar
Iustin Pop committed
90
91
92
93
94
95

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

      row[idx] = str(val)

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

  return 0


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

  """
128
129
130
131
132
133
134
135
  client = GetClient()

  for job_id in args:
    client.ArchiveJob(job_id)

  return 0


Iustin Pop's avatar
Iustin Pop committed
136
def AutoArchiveJobs(opts, args):
Iustin Pop's avatar
Iustin Pop committed
137
138
139
140
141
142
143
144
  """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
145
146
      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
147
148
149
150
  @rtype: int
  @return: the desired exit code

  """
Iustin Pop's avatar
Iustin Pop committed
151
152
153
154
155
156
157
158
159
  client = GetClient()

  age = args[0]

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

160
161
162
  (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
163
164
165
  return 0


166
def CancelJobs(opts, args):
Iustin Pop's avatar
Iustin Pop committed
167
168
169
170
171
172
173
174
175
  """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

  """
176
177
178
  client = GetClient()

  for job_id in args:
179
180
    (success, msg) = client.CancelJob(job_id)
    ToStdout(msg)
181

182
  # TODO: Different exit value if not all jobs were canceled?
183
184
185
  return 0


Iustin Pop's avatar
Iustin Pop committed
186
def ShowJobs(opts, args):
Iustin Pop's avatar
Iustin Pop committed
187
188
189
190
191
192
193
  """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
194
195
196
197

  """
  def format(level, text):
    """Display the text indented."""
198
    ToStdout("%s%s", "  " * level, text)
Iustin Pop's avatar
Iustin Pop committed
199
200
201
202
203
204
205
206

  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)

207
208
209
210
  selected_fields = [
    "id", "status", "ops", "opresult", "opstatus", "oplog",
    "opstart", "opend", "received_ts", "start_ts", "end_ts",
    ]
Iustin Pop's avatar
Iustin Pop committed
211
212
213
214
215

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

  first = True

216
  for idx, entry in enumerate(result):
Iustin Pop's avatar
Iustin Pop committed
217
218
219
220
    if not first:
      format(0, "")
    else:
      first = False
221
222

    if entry is None:
223
224
225
226
227
228
      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))
229
230
231
232
      continue

    (job_id, status, ops, opresult, opstatus, oplog,
     opstart, opend, recv_ts, start_ts, end_ts) = entry
Iustin Pop's avatar
Iustin Pop committed
233
234
235
236
    format(0, "Job ID: %s" % job_id)
    if status in _USER_JOB_STATUS:
      status = _USER_JOB_STATUS[status]
    else:
237
      raise errors.ProgrammerError("Unknown job status code '%s'" % status)
Iustin Pop's avatar
Iustin Pop committed
238
239

    format(1, "Status: %s" % status)
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

    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")
Iustin Pop's avatar
Iustin Pop committed
271
    format(1, "Opcodes:")
272
273
    for (opcode, result, status, log, s_ts, e_ts) in \
            zip(ops, opresult, opstatus, oplog, opstart, opend):
Iustin Pop's avatar
Iustin Pop committed
274
275
      format(2, "%s" % opcode["OP_ID"])
      format(3, "Status: %s" % status)
276
277
278
279
280
281
282
283
      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")
Iustin Pop's avatar
Iustin Pop committed
284
285
286
287
288
      format(3, "Input fields:")
      for key, val in opcode.iteritems():
        if key == "OP_ID":
          continue
        if isinstance(val, (tuple, list)):
289
          val = ",".join([str(item) for item in val])
Iustin Pop's avatar
Iustin Pop committed
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
        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)
308
      format(3, "Execution log:")
309
310
      for serial, log_ts, log_type, log_msg in log:
        time_txt = FormatTimestamp(log_ts)
311
        encoded = utils.SafeEncode(log_msg)
312
        format(4, "%s:%s:%s %s" % (serial, time_txt, log_type, encoded))
Iustin Pop's avatar
Iustin Pop committed
313
314
315
  return 0


Iustin Pop's avatar
Iustin Pop committed
316
317
commands = {
  'list': (ListJobs, ARGS_NONE,
318
319
            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
            "", "List the jobs and their status. The available fields are"
320
321
           " (see the man page for details): id, status, op_list,"
           " op_status, op_result."
Iustin Pop's avatar
Iustin Pop committed
322
           " The default field"
323
324
325
326
327
           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS)),
  'archive': (ArchiveJobs, ARGS_ANY,
              [DEBUG_OPT],
              "<job-id> [<job-id> ...]",
              "Archive specified jobs"),
Iustin Pop's avatar
Iustin Pop committed
328
329
330
331
  'autoarchive': (AutoArchiveJobs, ARGS_ONE,
              [DEBUG_OPT],
              "<age>",
              "Auto archive jobs older than the given age"),
332
333
334
335
  'cancel': (CancelJobs, ARGS_ANY,
             [DEBUG_OPT],
             "<job-id> [<job-id> ...]",
             "Cancel specified jobs"),
Iustin Pop's avatar
Iustin Pop committed
336
337
338
  'info': (ShowJobs, ARGS_ANY, [DEBUG_OPT],
           "<job-id> [<job-id> ...]",
           "Show detailed information about the specified jobs"),
Iustin Pop's avatar
Iustin Pop committed
339
340
341
342
343
  }


if __name__ == '__main__':
  sys.exit(GenericMain(commands))