Documentation updates for gnt-job
[ganeti-local] / scripts / gnt-job
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 # pylint: disable-msg=W0401,W0614
23 # W0401: Wildcard import ganeti.cli
24 # W0614: Unused import %s from wildcard import (since we need cli)
25
26 import sys
27
28 from ganeti.cli import *
29 from ganeti import constants
30 from ganeti import errors
31
32
33 #: default list of fields for L{ListJobs}
34 _LIST_DEF_FIELDS = ["id", "status", "summary"]
35
36 #: map converting the job status contants to user-visible
37 #: names
38 _USER_JOB_STATUS = {
39   constants.JOB_STATUS_QUEUED: "queued",
40   constants.JOB_STATUS_WAITLOCK: "waiting",
41   constants.JOB_STATUS_RUNNING: "running",
42   constants.JOB_STATUS_CANCELED: "canceled",
43   constants.JOB_STATUS_SUCCESS: "success",
44   constants.JOB_STATUS_ERROR: "error",
45   }
46
47
48 def ListJobs(opts, args):
49   """List the jobs
50
51   @param opts: the command line options selected by the user
52   @type args: list
53   @param args: should be an empty list
54   @rtype: int
55   @return: the desired exit code
56
57   """
58   if opts.output is None:
59     selected_fields = _LIST_DEF_FIELDS
60   elif opts.output.startswith("+"):
61     selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
62   else:
63     selected_fields = opts.output.split(",")
64
65   output = GetClient().QueryJobs(None, selected_fields)
66   if not opts.no_headers:
67     # TODO: Implement more fields
68     headers = {
69       "id": "ID",
70       "status": "Status",
71       "ops": "OpCodes",
72       "opresult": "OpCode_result",
73       "opstatus": "OpCode_status",
74       "oplog": "OpCode_log",
75       "summary": "Summary",
76       "opstart": "OpCode_start",
77       "opend": "OpCode_end",
78       "start_ts": "Start",
79       "end_ts": "End",
80       "received_ts": "Received",
81       }
82   else:
83     headers = None
84
85   # we don't have yet unitfields here
86   unitfields = None
87   numfields = None
88
89   # change raw values to nicer strings
90   for row in output:
91     for idx, field in enumerate(selected_fields):
92       val = row[idx]
93       if field == "status":
94         if val in _USER_JOB_STATUS:
95           val = _USER_JOB_STATUS[val]
96         else:
97           raise errors.ProgrammerError("Unknown job status code '%s'" % val)
98       elif field == "summary":
99         val = ",".join(val)
100       elif field in ("start_ts", "end_ts", "received_ts"):
101         val = FormatTimestamp(val)
102       elif field in ("opstart", "opend"):
103         val = [FormatTimestamp(entry) for entry in val]
104
105       row[idx] = str(val)
106
107   data = GenerateTable(separator=opts.separator, headers=headers,
108                        fields=selected_fields, unitfields=unitfields,
109                        numfields=numfields, data=output)
110   for line in data:
111     ToStdout(line)
112
113   return 0
114
115
116 def ArchiveJobs(opts, args):
117   """Archive jobs.
118
119   @param opts: the command line options selected by the user
120   @type args: list
121   @param args: should contain the job IDs to be archived
122   @rtype: int
123   @return: the desired exit code
124
125   """
126   client = GetClient()
127
128   for job_id in args:
129     client.ArchiveJob(job_id)
130
131   return 0
132
133
134 def AutoArchiveJobs(opts, args):
135   """Archive jobs based on age.
136
137   This will archive jobs based on their age, or all jobs if a 'all' is
138   passed.
139
140   @param opts: the command line options selected by the user
141   @type args: list
142   @param args: should contain only one element, the age as a time spec
143       that can be parsed by L{cli.ParseTimespec} or the keyword I{all},
144       which will cause all jobs to be archived
145   @rtype: int
146   @return: the desired exit code
147
148   """
149   client = GetClient()
150
151   age = args[0]
152
153   if age == 'all':
154     age = -1
155   else:
156     age = ParseTimespec(age)
157
158   client.AutoArchiveJobs(age)
159   return 0
160
161
162 def CancelJobs(opts, args):
163   """Cancel not-yet-started jobs.
164
165   @param opts: the command line options selected by the user
166   @type args: list
167   @param args: should contain the job IDs to be cancelled
168   @rtype: int
169   @return: the desired exit code
170
171   """
172   client = GetClient()
173
174   for job_id in args:
175     client.CancelJob(job_id)
176
177   return 0
178
179
180 def ShowJobs(opts, args):
181   """Show detailed information about jobs.
182
183   @param opts: the command line options selected by the user
184   @type args: list
185   @param args: should contain the job IDs to be queried
186   @rtype: int
187   @return: the desired exit code
188
189   """
190   def format(level, text):
191     """Display the text indented."""
192     ToStdout("%s%s", "  " * level, text)
193
194   def result_helper(value):
195     """Format a result field in a nice way."""
196     if isinstance(value, (tuple, list)):
197       return "[%s]" % (", ".join(str(elem) for elem in value))
198     else:
199       return str(value)
200
201   selected_fields = [
202     "id", "status", "ops", "opresult", "opstatus", "oplog",
203     "opstart", "opend", "received_ts", "start_ts", "end_ts",
204     ]
205
206   result = GetClient().QueryJobs(args, selected_fields)
207
208   first = True
209
210   for idx, entry in enumerate(result):
211     if not first:
212       format(0, "")
213     else:
214       first = False
215
216     if entry is None:
217       if idx <= len(args):
218         format(0, "Job ID %s not found" % args[idx])
219       else:
220         # this should not happen, when we don't pass args it will be a
221         # valid job returned
222         format(0, "Job ID requested as argument %s not found" % (idx + 1))
223       continue
224
225     (job_id, status, ops, opresult, opstatus, oplog,
226      opstart, opend, recv_ts, start_ts, end_ts) = entry
227     format(0, "Job ID: %s" % job_id)
228     if status in _USER_JOB_STATUS:
229       status = _USER_JOB_STATUS[status]
230     else:
231       raise errors.ProgrammerError("Unknown job status code '%s'" % status)
232
233     format(1, "Status: %s" % status)
234
235     if recv_ts is not None:
236       format(1, "Received:         %s" % FormatTimestamp(recv_ts))
237     else:
238       format(1, "Missing received timestamp (%s)" % str(recv_ts))
239
240     if start_ts is not None:
241       if recv_ts is not None:
242         d1 = start_ts[0] - recv_ts[0] + (start_ts[1] - recv_ts[1]) / 1000000.0
243         delta = " (delta %.6fs)" % d1
244       else:
245         delta = ""
246       format(1, "Processing start: %s%s" % (FormatTimestamp(start_ts), delta))
247     else:
248       format(1, "Processing start: unknown (%s)" % str(start_ts))
249
250     if end_ts is not None:
251       if start_ts is not None:
252         d2 = end_ts[0] - start_ts[0] + (end_ts[1] - start_ts[1]) / 1000000.0
253         delta = " (delta %.6fs)" % d2
254       else:
255         delta = ""
256       format(1, "Processing end:   %s%s" % (FormatTimestamp(end_ts), delta))
257     else:
258       format(1, "Processing end:   unknown (%s)" % str(end_ts))
259
260     if end_ts is not None and recv_ts is not None:
261       d3 = end_ts[0] - recv_ts[0] + (end_ts[1] - recv_ts[1]) / 1000000.0
262       format(1, "Total processing time: %.6f seconds" % d3)
263     else:
264       format(1, "Total processing time: N/A")
265     format(1, "Opcodes:")
266     for (opcode, result, status, log, s_ts, e_ts) in \
267             zip(ops, opresult, opstatus, oplog, opstart, opend):
268       format(2, "%s" % opcode["OP_ID"])
269       format(3, "Status: %s" % status)
270       if isinstance(s_ts, (tuple, list)):
271         format(3, "Processing start: %s" % FormatTimestamp(s_ts))
272       else:
273         format(3, "No processing start time")
274       if isinstance(e_ts, (tuple, list)):
275         format(3, "Processing end:   %s" % FormatTimestamp(e_ts))
276       else:
277         format(3, "No processing end time")
278       format(3, "Input fields:")
279       for key, val in opcode.iteritems():
280         if key == "OP_ID":
281           continue
282         if isinstance(val, (tuple, list)):
283           val = ",".join(val)
284         format(4, "%s: %s" % (key, val))
285       if result is None:
286         format(3, "No output data")
287       elif isinstance(result, (tuple, list)):
288         if not result:
289           format(3, "Result: empty sequence")
290         else:
291           format(3, "Result:")
292           for elem in result:
293             format(4, result_helper(elem))
294       elif isinstance(result, dict):
295         if not result:
296           format(3, "Result: empty dictionary")
297         else:
298           for key, val in result.iteritems():
299             format(4, "%s: %s" % (key, result_helper(val)))
300       else:
301         format(3, "Result: %s" % result)
302       format(3, "Execution log:")
303       for serial, log_ts, log_type, log_msg in log:
304         time_txt = FormatTimestamp(log_ts)
305         encoded = str(log_msg).encode('string_escape')
306         format(4, "%s:%s:%s %s" % (serial, time_txt, log_type, encoded))
307   return 0
308
309
310 commands = {
311   'list': (ListJobs, ARGS_NONE,
312             [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
313             "", "List the jobs and their status. The available fields are"
314            " (see the man page for details): id, status, op_list,"
315            " op_status, op_result."
316            " The default field"
317            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS)),
318   'archive': (ArchiveJobs, ARGS_ANY,
319               [DEBUG_OPT],
320               "<job-id> [<job-id> ...]",
321               "Archive specified jobs"),
322   'autoarchive': (AutoArchiveJobs, ARGS_ONE,
323               [DEBUG_OPT],
324               "<age>",
325               "Auto archive jobs older than the given age"),
326   'cancel': (CancelJobs, ARGS_ANY,
327              [DEBUG_OPT],
328              "<job-id> [<job-id> ...]",
329              "Cancel specified jobs"),
330   'info': (ShowJobs, ARGS_ANY, [DEBUG_OPT],
331            "<job-id> [<job-id> ...]",
332            "Show detailed information about the specified jobs"),
333   }
334
335
336 if __name__ == '__main__':
337   sys.exit(GenericMain(commands))