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