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