Package ganeti :: Package client :: Module gnt_job
[hide private]
[frames] | no frames]

Source Code for Module ganeti.client.gnt_job

  1  # 
  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  """Job related commands""" 
 22   
 23  # pylint: disable=W0401,W0613,W0614,C0103 
 24  # W0401: Wildcard import ganeti.cli 
 25  # W0613: Unused argument, since all functions follow the same API 
 26  # W0614: Unused import %s from wildcard import (since we need cli) 
 27  # C0103: Invalid name gnt-job 
 28   
 29  from ganeti.cli import * 
 30  from ganeti import constants 
 31  from ganeti import errors 
 32  from ganeti import utils 
 33  from ganeti import cli 
 34  from ganeti import qlang 
 35   
 36   
 37  #: default list of fields for L{ListJobs} 
 38  _LIST_DEF_FIELDS = ["id", "status", "summary"] 
 39   
 40  #: map converting the job status contants to user-visible 
 41  #: names 
 42  _USER_JOB_STATUS = { 
 43    constants.JOB_STATUS_QUEUED: "queued", 
 44    constants.JOB_STATUS_WAITING: "waiting", 
 45    constants.JOB_STATUS_CANCELING: "canceling", 
 46    constants.JOB_STATUS_RUNNING: "running", 
 47    constants.JOB_STATUS_CANCELED: "canceled", 
 48    constants.JOB_STATUS_SUCCESS: "success", 
 49    constants.JOB_STATUS_ERROR: "error", 
 50    } 
 51   
 52   
53 -def _FormatStatus(value):
54 """Formats a job status. 55 56 """ 57 try: 58 return _USER_JOB_STATUS[value] 59 except KeyError: 60 raise errors.ProgrammerError("Unknown job status code '%s'" % value)
61 62
63 -def ListJobs(opts, args):
64 """List the jobs 65 66 @param opts: the command line options selected by the user 67 @type args: list 68 @param args: should be an empty list 69 @rtype: int 70 @return: the desired exit code 71 72 """ 73 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS) 74 75 fmtoverride = { 76 "status": (_FormatStatus, False), 77 "summary": (lambda value: ",".join(str(item) for item in value), False), 78 } 79 fmtoverride.update(dict.fromkeys(["opstart", "opexec", "opend"], 80 (lambda value: map(FormatTimestamp, value), None))) 81 82 qfilter = qlang.MakeSimpleFilter("status", opts.status_filter) 83 84 return GenericList(constants.QR_JOB, selected_fields, args, None, 85 opts.separator, not opts.no_headers, 86 format_override=fmtoverride, verbose=opts.verbose, 87 force_filter=opts.force_filter, namefield="id", 88 qfilter=qfilter)
89 90
91 -def ListJobFields(opts, args):
92 """List job fields. 93 94 @param opts: the command line options selected by the user 95 @type args: list 96 @param args: fields to list, or empty for all 97 @rtype: int 98 @return: the desired exit code 99 100 """ 101 return GenericListFields(constants.QR_JOB, args, opts.separator, 102 not opts.no_headers)
103 104
105 -def ArchiveJobs(opts, args):
106 """Archive jobs. 107 108 @param opts: the command line options selected by the user 109 @type args: list 110 @param args: should contain the job IDs to be archived 111 @rtype: int 112 @return: the desired exit code 113 114 """ 115 client = GetClient() 116 117 rcode = 0 118 for job_id in args: 119 if not client.ArchiveJob(job_id): 120 ToStderr("Failed to archive job with ID '%s'", job_id) 121 rcode = 1 122 123 return rcode
124 125
126 -def AutoArchiveJobs(opts, args):
127 """Archive jobs based on age. 128 129 This will archive jobs based on their age, or all jobs if a 'all' is 130 passed. 131 132 @param opts: the command line options selected by the user 133 @type args: list 134 @param args: should contain only one element, the age as a time spec 135 that can be parsed by L{ganeti.cli.ParseTimespec} or the 136 keyword I{all}, which will cause all jobs to be archived 137 @rtype: int 138 @return: the desired exit code 139 140 """ 141 client = GetClient() 142 143 age = args[0] 144 145 if age == "all": 146 age = -1 147 else: 148 age = ParseTimespec(age) 149 150 (archived_count, jobs_left) = client.AutoArchiveJobs(age) 151 ToStdout("Archived %s jobs, %s unchecked left", archived_count, jobs_left) 152 153 return 0
154 155
156 -def CancelJobs(opts, args):
157 """Cancel not-yet-started jobs. 158 159 @param opts: the command line options selected by the user 160 @type args: list 161 @param args: should contain the job IDs to be cancelled 162 @rtype: int 163 @return: the desired exit code 164 165 """ 166 client = GetClient() 167 result = constants.EXIT_SUCCESS 168 169 for job_id in args: 170 (success, msg) = client.CancelJob(job_id) 171 172 if not success: 173 result = constants.EXIT_FAILURE 174 175 ToStdout(msg) 176 177 return result
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_msg(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]" % utils.CommaJoin(value) 198 else: 199 return str(value) 200 201 selected_fields = [ 202 "id", "status", "ops", "opresult", "opstatus", "oplog", 203 "opstart", "opexec", "opend", "received_ts", "start_ts", "end_ts", 204 ] 205 206 result = GetClient().Query(constants.QR_JOB, selected_fields, 207 qlang.MakeSimpleFilter("id", args)).data 208 209 first = True 210 211 for entry in result: 212 if not first: 213 format_msg(0, "") 214 else: 215 first = False 216 217 ((_, job_id), (rs_status, status), (_, ops), (_, opresult), (_, opstatus), 218 (_, oplog), (_, opstart), (_, opexec), (_, opend), (_, recv_ts), 219 (_, start_ts), (_, end_ts)) = entry 220 221 # Detect non-normal results 222 if rs_status != constants.RS_NORMAL: 223 format_msg(0, "Job ID %s not found" % job_id) 224 continue 225 226 format_msg(0, "Job ID: %s" % job_id) 227 if status in _USER_JOB_STATUS: 228 status = _USER_JOB_STATUS[status] 229 else: 230 raise errors.ProgrammerError("Unknown job status code '%s'" % status) 231 232 format_msg(1, "Status: %s" % status) 233 234 if recv_ts is not None: 235 format_msg(1, "Received: %s" % FormatTimestamp(recv_ts)) 236 else: 237 format_msg(1, "Missing received timestamp (%s)" % str(recv_ts)) 238 239 if start_ts is not None: 240 if recv_ts is not None: 241 d1 = start_ts[0] - recv_ts[0] + (start_ts[1] - recv_ts[1]) / 1000000.0 242 delta = " (delta %.6fs)" % d1 243 else: 244 delta = "" 245 format_msg(1, "Processing start: %s%s" % 246 (FormatTimestamp(start_ts), delta)) 247 else: 248 format_msg(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_msg(1, "Processing end: %s%s" % 257 (FormatTimestamp(end_ts), delta)) 258 else: 259 format_msg(1, "Processing end: unknown (%s)" % str(end_ts)) 260 261 if end_ts is not None and recv_ts is not None: 262 d3 = end_ts[0] - recv_ts[0] + (end_ts[1] - recv_ts[1]) / 1000000.0 263 format_msg(1, "Total processing time: %.6f seconds" % d3) 264 else: 265 format_msg(1, "Total processing time: N/A") 266 format_msg(1, "Opcodes:") 267 for (opcode, result, status, log, s_ts, x_ts, e_ts) in \ 268 zip(ops, opresult, opstatus, oplog, opstart, opexec, opend): 269 format_msg(2, "%s" % opcode["OP_ID"]) 270 format_msg(3, "Status: %s" % status) 271 if isinstance(s_ts, (tuple, list)): 272 format_msg(3, "Processing start: %s" % FormatTimestamp(s_ts)) 273 else: 274 format_msg(3, "No processing start time") 275 if isinstance(x_ts, (tuple, list)): 276 format_msg(3, "Execution start: %s" % FormatTimestamp(x_ts)) 277 else: 278 format_msg(3, "No execution start time") 279 if isinstance(e_ts, (tuple, list)): 280 format_msg(3, "Processing end: %s" % FormatTimestamp(e_ts)) 281 else: 282 format_msg(3, "No processing end time") 283 format_msg(3, "Input fields:") 284 for key in utils.NiceSort(opcode.keys()): 285 if key == "OP_ID": 286 continue 287 val = opcode[key] 288 if isinstance(val, (tuple, list)): 289 val = ",".join([str(item) for item in val]) 290 format_msg(4, "%s: %s" % (key, val)) 291 if result is None: 292 format_msg(3, "No output data") 293 elif isinstance(result, (tuple, list)): 294 if not result: 295 format_msg(3, "Result: empty sequence") 296 else: 297 format_msg(3, "Result:") 298 for elem in result: 299 format_msg(4, result_helper(elem)) 300 elif isinstance(result, dict): 301 if not result: 302 format_msg(3, "Result: empty dictionary") 303 else: 304 format_msg(3, "Result:") 305 for key, val in result.iteritems(): 306 format_msg(4, "%s: %s" % (key, result_helper(val))) 307 else: 308 format_msg(3, "Result: %s" % result) 309 format_msg(3, "Execution log:") 310 for serial, log_ts, log_type, log_msg in log: 311 time_txt = FormatTimestamp(log_ts) 312 encoded = FormatLogMessage(log_type, log_msg) 313 format_msg(4, "%s:%s:%s %s" % (serial, time_txt, log_type, encoded)) 314 return 0 315 316
317 -def WatchJob(opts, args):
318 """Follow a job and print its output as it arrives. 319 320 @param opts: the command line options selected by the user 321 @type args: list 322 @param args: Contains the job ID 323 @rtype: int 324 @return: the desired exit code 325 326 """ 327 job_id = args[0] 328 329 msg = ("Output from job %s follows" % job_id) 330 ToStdout(msg) 331 ToStdout("-" * len(msg)) 332 333 retcode = 0 334 try: 335 cli.PollJob(job_id) 336 except errors.GenericError, err: 337 (retcode, job_result) = cli.FormatError(err) 338 ToStderr("Job %s failed: %s", job_id, job_result) 339 340 return retcode
341 342 343 _PENDING_OPT = \ 344 cli_option("--pending", default=None, 345 action="store_const", dest="status_filter", 346 const=frozenset([ 347 constants.JOB_STATUS_QUEUED, 348 constants.JOB_STATUS_WAITING, 349 ]), 350 help="Show only jobs pending execution") 351 352 _RUNNING_OPT = \ 353 cli_option("--running", default=None, 354 action="store_const", dest="status_filter", 355 const=frozenset([ 356 constants.JOB_STATUS_RUNNING, 357 ]), 358 help="Show jobs currently running only") 359 360 _ERROR_OPT = \ 361 cli_option("--error", default=None, 362 action="store_const", dest="status_filter", 363 const=frozenset([ 364 constants.JOB_STATUS_ERROR, 365 ]), 366 help="Show failed jobs only") 367 368 _FINISHED_OPT = \ 369 cli_option("--finished", default=None, 370 action="store_const", dest="status_filter", 371 const=constants.JOBS_FINALIZED, 372 help="Show finished jobs only") 373 374 375 commands = { 376 "list": ( 377 ListJobs, [ArgJobId()], 378 [NOHDR_OPT, SEP_OPT, FIELDS_OPT, VERBOSE_OPT, FORCE_FILTER_OPT, 379 _PENDING_OPT, _RUNNING_OPT, _ERROR_OPT, _FINISHED_OPT], 380 "[job_id ...]", 381 "Lists the jobs and their status. The available fields can be shown" 382 " using the \"list-fields\" command (see the man page for details)." 383 " The default field list is (in order): %s." % 384 utils.CommaJoin(_LIST_DEF_FIELDS)), 385 "list-fields": ( 386 ListJobFields, [ArgUnknown()], 387 [NOHDR_OPT, SEP_OPT], 388 "[fields...]", 389 "Lists all available fields for jobs"), 390 "archive": ( 391 ArchiveJobs, [ArgJobId(min=1)], [], 392 "<job-id> [<job-id> ...]", "Archive specified jobs"), 393 "autoarchive": ( 394 AutoArchiveJobs, 395 [ArgSuggest(min=1, max=1, choices=["1d", "1w", "4w", "all"])], 396 [], 397 "<age>", "Auto archive jobs older than the given age"), 398 "cancel": ( 399 CancelJobs, [ArgJobId(min=1)], [], 400 "<job-id> [<job-id> ...]", "Cancel specified jobs"), 401 "info": ( 402 ShowJobs, [ArgJobId(min=1)], [], 403 "<job-id> [<job-id> ...]", 404 "Show detailed information about the specified jobs"), 405 "watch": ( 406 WatchJob, [ArgJobId(min=1, max=1)], [], 407 "<job-id>", "Follows a job and prints its output as it arrives"), 408 } 409 410 411 #: dictionary with aliases for commands 412 aliases = { 413 "show": "info", 414 } 415 416
417 -def Main():
418 return GenericMain(commands, aliases=aliases)
419