Implement job 'waiting' status
[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 import sys
23 import os
24 import itertools
25 import time
26 from optparse import make_option
27 from cStringIO import StringIO
28
29 from ganeti.cli import *
30 from ganeti import opcodes
31 from ganeti import logger
32 from ganeti import constants
33 from ganeti import utils
34 from ganeti import errors
35
36
37 _LIST_DEF_FIELDS = ["id", "status", "summary"]
38
39 _USER_JOB_STATUS = {
40   constants.JOB_STATUS_QUEUED: "queued",
41   constants.JOB_STATUS_WAITLOCK: "waiting",
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   """
53   if opts.output is None:
54     selected_fields = _LIST_DEF_FIELDS
55   elif opts.output.startswith("+"):
56     selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
57   else:
58     selected_fields = opts.output.split(",")
59
60   output = GetClient().QueryJobs(None, selected_fields)
61   if not opts.no_headers:
62     # TODO: Implement more fields
63     headers = {
64       "id": "ID",
65       "status": "Status",
66       "ops": "OpCodes",
67       "opresult": "OpCode_result",
68       "opstatus": "OpCode_status",
69       "oplog": "OpCode_log",
70       "summary": "Summary",
71       "opstart": "OpCode_start",
72       "opend": "OpCode_end",
73       "start_ts": "Start",
74       "end_ts": "End",
75       "received_ts": "Received",
76       }
77   else:
78     headers = None
79
80   # we don't have yet unitfields here
81   unitfields = None
82   numfields = None
83
84   # change raw values to nicer strings
85   for row in output:
86     for idx, field in enumerate(selected_fields):
87       val = row[idx]
88       if field == "status":
89         if val in _USER_JOB_STATUS:
90           val = _USER_JOB_STATUS[val]
91         else:
92           raise errors.ProgrammerError("Unknown job status code '%s'" % val)
93       elif field == "summary":
94         val = ",".join(val)
95       elif field in ("start_ts", "end_ts", "received_ts"):
96         val = FormatTimestamp(val)
97       elif field in ("opstart", "opend"):
98         val = [FormatTimestamp(entry) for entry in val]
99
100       row[idx] = str(val)
101
102   data = GenerateTable(separator=opts.separator, headers=headers,
103                        fields=selected_fields, unitfields=unitfields,
104                        numfields=numfields, data=output)
105   for line in data:
106     print line
107
108   return 0
109
110
111 def ArchiveJobs(opts, args):
112   client = GetClient()
113
114   for job_id in args:
115     client.ArchiveJob(job_id)
116
117   return 0
118
119
120 def AutoArchiveJobs(opts, args):
121   client = GetClient()
122
123   age = args[0]
124
125   if age == 'all':
126     age = -1
127   else:
128     age = ParseTimespec(age)
129
130   client.AutoArchiveJobs(age)
131   return 0
132
133
134 def CancelJobs(opts, args):
135   client = GetClient()
136
137   for job_id in args:
138     client.CancelJob(job_id)
139
140   return 0
141
142
143 def ShowJobs(opts, args):
144   """List the jobs
145
146   """
147   def format(level, text):
148     """Display the text indented."""
149     print "%s%s" % ("  " * level, text)
150
151   def result_helper(value):
152     """Format a result field in a nice way."""
153     if isinstance(value, (tuple, list)):
154       return "[%s]" % (", ".join(str(elem) for elem in value))
155     else:
156       return str(value)
157
158   selected_fields = [
159     "id", "status", "ops", "opresult", "opstatus", "oplog",
160     "opstart", "opend", "received_ts", "start_ts", "end_ts",
161     ]
162
163   result = GetClient().QueryJobs(args, selected_fields)
164
165   first = True
166
167   for idx, entry in enumerate(result):
168     if not first:
169       format(0, "")
170     else:
171       first = False
172
173     if entry is None:
174       if idx <= len(args):
175         format(0, "Job ID %s not found" % args[idx])
176       else:
177         # this should not happen, when we don't pass args it will be a
178         # valid job returned
179         format(0, "Job ID requested as argument %s not found" % (idx + 1))
180       continue
181
182     (job_id, status, ops, opresult, opstatus, oplog,
183      opstart, opend, recv_ts, start_ts, end_ts) = entry
184     format(0, "Job ID: %s" % job_id)
185     if status in _USER_JOB_STATUS:
186       status = _USER_JOB_STATUS[status]
187     else:
188       raise errors.ProgrammerError("Unknown job status code '%s'" % val)
189
190     format(1, "Status: %s" % status)
191
192     if recv_ts is not None:
193       format(1, "Received:         %s" % FormatTimestamp(recv_ts))
194     else:
195       format(1, "Missing received timestamp (%s)" % str(recv_ts))
196
197     if start_ts is not None:
198       if recv_ts is not None:
199         d1 = start_ts[0] - recv_ts[0] + (start_ts[1] - recv_ts[1]) / 1000000.0
200         delta = " (delta %.6fs)" % d1
201       else:
202         delta = ""
203       format(1, "Processing start: %s%s" % (FormatTimestamp(start_ts), delta))
204     else:
205       format(1, "Processing start: unknown (%s)" % str(start_ts))
206
207     if end_ts is not None:
208       if start_ts is not None:
209         d2 = end_ts[0] - start_ts[0] + (end_ts[1] - start_ts[1]) / 1000000.0
210         delta = " (delta %.6fs)" % d2
211       else:
212         delta = ""
213       format(1, "Processing end:   %s%s" % (FormatTimestamp(end_ts), delta))
214     else:
215       format(1, "Processing end:   unknown (%s)" % str(end_ts))
216
217     if end_ts is not None and recv_ts is not None:
218       d3 = end_ts[0] - recv_ts[0] + (end_ts[1] - recv_ts[1]) / 1000000.0
219       format(1, "Total processing time: %.6f seconds" % d3)
220     else:
221       format(1, "Total processing time: N/A")
222     format(1, "Opcodes:")
223     for (opcode, result, status, log, s_ts, e_ts) in \
224             zip(ops, opresult, opstatus, oplog, opstart, opend):
225       format(2, "%s" % opcode["OP_ID"])
226       format(3, "Status: %s" % status)
227       if isinstance(s_ts, (tuple, list)):
228         format(3, "Processing start: %s" % FormatTimestamp(s_ts))
229       else:
230         format(3, "No processing start time")
231       if isinstance(e_ts, (tuple, list)):
232         format(3, "Processing end:   %s" % FormatTimestamp(e_ts))
233       else:
234         format(3, "No processing end time")
235       format(3, "Input fields:")
236       for key, val in opcode.iteritems():
237         if key == "OP_ID":
238           continue
239         if isinstance(val, (tuple, list)):
240           val = ",".join(val)
241         format(4, "%s: %s" % (key, val))
242       if result is None:
243         format(3, "No output data")
244       elif isinstance(result, (tuple, list)):
245         if not result:
246           format(3, "Result: empty sequence")
247         else:
248           format(3, "Result:")
249           for elem in result:
250             format(4, result_helper(elem))
251       elif isinstance(result, dict):
252         if not result:
253           format(3, "Result: empty dictionary")
254         else:
255           for key, val in result.iteritems():
256             format(4, "%s: %s" % (key, result_helper(val)))
257       else:
258         format(3, "Result: %s" % result)
259       format(3, "Execution log:")
260       for serial, log_ts, log_type, log_msg in log:
261         time_txt = FormatTimestamp(log_ts)
262         encoded = str(log_msg).encode('string_escape')
263         format(4, "%s:%s:%s %s" % (serial, time_txt, log_type, encoded))
264   return 0
265
266
267 commands = {
268   'list': (ListJobs, ARGS_NONE,
269             [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
270             "", "List the jobs and their status. The available fields are"
271            " (see the man page for details): id, status, op_list,"
272            " op_status, op_result."
273            " The default field"
274            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS)),
275   'archive': (ArchiveJobs, ARGS_ANY,
276               [DEBUG_OPT],
277               "<job-id> [<job-id> ...]",
278               "Archive specified jobs"),
279   'autoarchive': (AutoArchiveJobs, ARGS_ONE,
280               [DEBUG_OPT],
281               "<age>",
282               "Auto archive jobs older than the given age"),
283   'cancel': (CancelJobs, ARGS_ANY,
284              [DEBUG_OPT],
285              "<job-id> [<job-id> ...]",
286              "Cancel specified jobs"),
287   'info': (ShowJobs, ARGS_ANY, [DEBUG_OPT],
288            "<job-id> [<job-id> ...]",
289            "Show detailed information about the specified jobs"),
290   }
291
292
293 if __name__ == '__main__':
294   sys.exit(GenericMain(commands))