gnt-job 9.95 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
31
32
import sys

from ganeti.cli import *
from ganeti import constants
from ganeti import errors


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

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

48

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

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

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

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

  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
      if field == "status":
95
96
        if val in _USER_JOB_STATUS:
          val = _USER_JOB_STATUS[val]
Iustin Pop's avatar
Iustin Pop committed
97
98
        else:
          raise errors.ProgrammerError("Unknown job status code '%s'" % val)
99
100
      elif field == "summary":
        val = ",".join(val)
101
102
103
104
      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
105
106
107
108
109

      row[idx] = str(val)

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

  return 0


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

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

  for job_id in args:
    client.ArchiveJob(job_id)

  return 0


Iustin Pop's avatar
Iustin Pop committed
135
def AutoArchiveJobs(opts, args):
Iustin Pop's avatar
Iustin Pop committed
136
137
138
139
140
141
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
      that can be parsed by L{cli.ParseTimespec} or the keyword I{all},
      which will cause all jobs to be archived
  @rtype: int
  @return: the desired exit code

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

  age = args[0]

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

  client.AutoArchiveJobs(age)
  return 0


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

  """
173
174
175
  client = GetClient()

  for job_id in args:
176
177
    (success, msg) = client.CancelJob(job_id)
    ToStdout(msg)
178

179
  # TODO: Different exit value if not all jobs were canceled?
180
181
182
  return 0


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

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

  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)

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

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

  first = True

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

    if entry is None:
220
221
222
223
224
225
      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))
226
227
228
229
      continue

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

    format(1, "Status: %s" % status)
237
238
239
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

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


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


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