Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ f36d7d81

History | View | Annotate | Download (41.9 kB)

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

    
22
"""Module dealing with command line parsing"""
23

    
24

    
25
import sys
26
import textwrap
27
import os.path
28
import copy
29
import time
30
import logging
31
from cStringIO import StringIO
32

    
33
from ganeti import utils
34
from ganeti import errors
35
from ganeti import constants
36
from ganeti import opcodes
37
from ganeti import luxi
38
from ganeti import ssconf
39
from ganeti import rpc
40

    
41
from optparse import (OptionParser, TitledHelpFormatter,
42
                      Option, OptionValueError)
43

    
44

    
45
__all__ = [
46
  # Command line options
47
  "BACKEND_OPT",
48
  "CONFIRM_OPT",
49
  "DEBUG_OPT",
50
  "DEBUG_SIMERR_OPT",
51
  "DISKIDX_OPT",
52
  "DISK_OPT",
53
  "DISK_TEMPLATE_OPT",
54
  "FIELDS_OPT",
55
  "FILESTORE_DIR_OPT",
56
  "FILESTORE_DRIVER_OPT",
57
  "HVLIST_OPT",
58
  "HVOPTS_OPT",
59
  "HYPERVISOR_OPT",
60
  "IALLOCATOR_OPT",
61
  "IGNORE_CONSIST_OPT",
62
  "FORCE_OPT",
63
  "NET_OPT",
64
  "NODE_LIST_OPT",
65
  "NODE_PLACEMENT_OPT",
66
  "NOHDR_OPT",
67
  "NOIPCHECK_OPT",
68
  "NONICS_OPT",
69
  "NONLIVE_OPT",
70
  "NWSYNC_OPT",
71
  "OS_OPT",
72
  "OS_SIZE_OPT",
73
  "SEP_OPT",
74
  "SINGLE_NODE_OPT",
75
  "SUBMIT_OPT",
76
  "SYNC_OPT",
77
  "TAG_SRC_OPT",
78
  "USEUNITS_OPT",
79
  "VERBOSE_OPT",
80
  # Generic functions for CLI programs
81
  "GenericMain",
82
  "GetClient",
83
  "GetOnlineNodes",
84
  "JobExecutor",
85
  "JobSubmittedException",
86
  "ParseTimespec",
87
  "SubmitOpCode",
88
  "SubmitOrSend",
89
  "UsesRPC",
90
  # Formatting functions
91
  "ToStderr", "ToStdout",
92
  "FormatError",
93
  "GenerateTable",
94
  "AskUser",
95
  "FormatTimestamp",
96
  # Tags functions
97
  "ListTags",
98
  "AddTags",
99
  "RemoveTags",
100
  # command line options support infrastructure
101
  "ARGS_MANY_INSTANCES",
102
  "ARGS_MANY_NODES",
103
  "ARGS_NONE",
104
  "ARGS_ONE_INSTANCE",
105
  "ARGS_ONE_NODE",
106
  "ArgChoice",
107
  "ArgCommand",
108
  "ArgFile",
109
  "ArgHost",
110
  "ArgInstance",
111
  "ArgJobId",
112
  "ArgNode",
113
  "ArgSuggest",
114
  "ArgUnknown",
115
  "OPT_COMPL_INST_ADD_NODES",
116
  "OPT_COMPL_MANY_NODES",
117
  "OPT_COMPL_ONE_IALLOCATOR",
118
  "OPT_COMPL_ONE_INSTANCE",
119
  "OPT_COMPL_ONE_NODE",
120
  "OPT_COMPL_ONE_OS",
121
  "cli_option",
122
  "SplitNodeOption",
123
  ]
124

    
125
NO_PREFIX = "no_"
126
UN_PREFIX = "-"
127

    
128

    
129
class _Argument:
130
  def __init__(self, min=0, max=None):
131
    self.min = min
132
    self.max = max
133

    
134
  def __repr__(self):
135
    return ("<%s min=%s max=%s>" %
136
            (self.__class__.__name__, self.min, self.max))
137

    
138

    
139
class ArgSuggest(_Argument):
140
  """Suggesting argument.
141

142
  Value can be any of the ones passed to the constructor.
143

144
  """
145
  def __init__(self, min=0, max=None, choices=None):
146
    _Argument.__init__(self, min=min, max=max)
147
    self.choices = choices
148

    
149
  def __repr__(self):
150
    return ("<%s min=%s max=%s choices=%r>" %
151
            (self.__class__.__name__, self.min, self.max, self.choices))
152

    
153

    
154
class ArgChoice(ArgSuggest):
155
  """Choice argument.
156

157
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
158
  but value must be one of the choices.
159

160
  """
161

    
162

    
163
class ArgUnknown(_Argument):
164
  """Unknown argument to program (e.g. determined at runtime).
165

166
  """
167

    
168

    
169
class ArgInstance(_Argument):
170
  """Instances argument.
171

172
  """
173

    
174

    
175
class ArgNode(_Argument):
176
  """Node argument.
177

178
  """
179

    
180
class ArgJobId(_Argument):
181
  """Job ID argument.
182

183
  """
184

    
185

    
186
class ArgFile(_Argument):
187
  """File path argument.
188

189
  """
190

    
191

    
192
class ArgCommand(_Argument):
193
  """Command argument.
194

195
  """
196

    
197

    
198
class ArgHost(_Argument):
199
  """Host argument.
200

201
  """
202

    
203

    
204
ARGS_NONE = []
205
ARGS_MANY_INSTANCES = [ArgInstance()]
206
ARGS_MANY_NODES = [ArgNode()]
207
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
208
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
209

    
210

    
211

    
212
def _ExtractTagsObject(opts, args):
213
  """Extract the tag type object.
214

215
  Note that this function will modify its args parameter.
216

217
  """
218
  if not hasattr(opts, "tag_type"):
219
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
220
  kind = opts.tag_type
221
  if kind == constants.TAG_CLUSTER:
222
    retval = kind, kind
223
  elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
224
    if not args:
225
      raise errors.OpPrereqError("no arguments passed to the command")
226
    name = args.pop(0)
227
    retval = kind, name
228
  else:
229
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
230
  return retval
231

    
232

    
233
def _ExtendTags(opts, args):
234
  """Extend the args if a source file has been given.
235

236
  This function will extend the tags with the contents of the file
237
  passed in the 'tags_source' attribute of the opts parameter. A file
238
  named '-' will be replaced by stdin.
239

240
  """
241
  fname = opts.tags_source
242
  if fname is None:
243
    return
244
  if fname == "-":
245
    new_fh = sys.stdin
246
  else:
247
    new_fh = open(fname, "r")
248
  new_data = []
249
  try:
250
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
251
    # because of python bug 1633941
252
    while True:
253
      line = new_fh.readline()
254
      if not line:
255
        break
256
      new_data.append(line.strip())
257
  finally:
258
    new_fh.close()
259
  args.extend(new_data)
260

    
261

    
262
def ListTags(opts, args):
263
  """List the tags on a given object.
264

265
  This is a generic implementation that knows how to deal with all
266
  three cases of tag objects (cluster, node, instance). The opts
267
  argument is expected to contain a tag_type field denoting what
268
  object type we work on.
269

270
  """
271
  kind, name = _ExtractTagsObject(opts, args)
272
  op = opcodes.OpGetTags(kind=kind, name=name)
273
  result = SubmitOpCode(op)
274
  result = list(result)
275
  result.sort()
276
  for tag in result:
277
    ToStdout(tag)
278

    
279

    
280
def AddTags(opts, args):
281
  """Add tags on a given object.
282

283
  This is a generic implementation that knows how to deal with all
284
  three cases of tag objects (cluster, node, instance). The opts
285
  argument is expected to contain a tag_type field denoting what
286
  object type we work on.
287

288
  """
289
  kind, name = _ExtractTagsObject(opts, args)
290
  _ExtendTags(opts, args)
291
  if not args:
292
    raise errors.OpPrereqError("No tags to be added")
293
  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
294
  SubmitOpCode(op)
295

    
296

    
297
def RemoveTags(opts, args):
298
  """Remove tags from a given object.
299

300
  This is a generic implementation that knows how to deal with all
301
  three cases of tag objects (cluster, node, instance). The opts
302
  argument is expected to contain a tag_type field denoting what
303
  object type we work on.
304

305
  """
306
  kind, name = _ExtractTagsObject(opts, args)
307
  _ExtendTags(opts, args)
308
  if not args:
309
    raise errors.OpPrereqError("No tags to be removed")
310
  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
311
  SubmitOpCode(op)
312

    
313

    
314
def check_unit(option, opt, value):
315
  """OptParsers custom converter for units.
316

317
  """
318
  try:
319
    return utils.ParseUnit(value)
320
  except errors.UnitParseError, err:
321
    raise OptionValueError("option %s: %s" % (opt, err))
322

    
323

    
324
def _SplitKeyVal(opt, data):
325
  """Convert a KeyVal string into a dict.
326

327
  This function will convert a key=val[,...] string into a dict. Empty
328
  values will be converted specially: keys which have the prefix 'no_'
329
  will have the value=False and the prefix stripped, the others will
330
  have value=True.
331

332
  @type opt: string
333
  @param opt: a string holding the option name for which we process the
334
      data, used in building error messages
335
  @type data: string
336
  @param data: a string of the format key=val,key=val,...
337
  @rtype: dict
338
  @return: {key=val, key=val}
339
  @raises errors.ParameterError: if there are duplicate keys
340

341
  """
342
  kv_dict = {}
343
  if data:
344
    for elem in data.split(","):
345
      if "=" in elem:
346
        key, val = elem.split("=", 1)
347
      else:
348
        if elem.startswith(NO_PREFIX):
349
          key, val = elem[len(NO_PREFIX):], False
350
        elif elem.startswith(UN_PREFIX):
351
          key, val = elem[len(UN_PREFIX):], None
352
        else:
353
          key, val = elem, True
354
      if key in kv_dict:
355
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
356
                                    (key, opt))
357
      kv_dict[key] = val
358
  return kv_dict
359

    
360

    
361
def check_ident_key_val(option, opt, value):
362
  """Custom parser for ident:key=val,key=val options.
363

364
  This will store the parsed values as a tuple (ident, {key: val}). As such,
365
  multiple uses of this option via action=append is possible.
366

367
  """
368
  if ":" not in value:
369
    ident, rest = value, ''
370
  else:
371
    ident, rest = value.split(":", 1)
372

    
373
  if ident.startswith(NO_PREFIX):
374
    if rest:
375
      msg = "Cannot pass options when removing parameter groups: %s" % value
376
      raise errors.ParameterError(msg)
377
    retval = (ident[len(NO_PREFIX):], False)
378
  elif ident.startswith(UN_PREFIX):
379
    if rest:
380
      msg = "Cannot pass options when removing parameter groups: %s" % value
381
      raise errors.ParameterError(msg)
382
    retval = (ident[len(UN_PREFIX):], None)
383
  else:
384
    kv_dict = _SplitKeyVal(opt, rest)
385
    retval = (ident, kv_dict)
386
  return retval
387

    
388

    
389
def check_key_val(option, opt, value):
390
  """Custom parser class for key=val,key=val options.
391

392
  This will store the parsed values as a dict {key: val}.
393

394
  """
395
  return _SplitKeyVal(opt, value)
396

    
397

    
398
# completion_suggestion is normally a list. Using numeric values not evaluating
399
# to False for dynamic completion.
400
(OPT_COMPL_MANY_NODES,
401
 OPT_COMPL_ONE_NODE,
402
 OPT_COMPL_ONE_INSTANCE,
403
 OPT_COMPL_ONE_OS,
404
 OPT_COMPL_ONE_IALLOCATOR,
405
 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
406

    
407
OPT_COMPL_ALL = frozenset([
408
  OPT_COMPL_MANY_NODES,
409
  OPT_COMPL_ONE_NODE,
410
  OPT_COMPL_ONE_INSTANCE,
411
  OPT_COMPL_ONE_OS,
412
  OPT_COMPL_ONE_IALLOCATOR,
413
  OPT_COMPL_INST_ADD_NODES,
414
  ])
415

    
416

    
417
class CliOption(Option):
418
  """Custom option class for optparse.
419

420
  """
421
  ATTRS = Option.ATTRS + [
422
    "completion_suggest",
423
    ]
424
  TYPES = Option.TYPES + (
425
    "identkeyval",
426
    "keyval",
427
    "unit",
428
    )
429
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
430
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
431
  TYPE_CHECKER["keyval"] = check_key_val
432
  TYPE_CHECKER["unit"] = check_unit
433

    
434

    
435
# optparse.py sets make_option, so we do it for our own option class, too
436
cli_option = CliOption
437

    
438

    
439
DEBUG_OPT = cli_option("-d", "--debug", default=False,
440
                       action="store_true",
441
                       help="Turn debugging on")
442

    
443
NOHDR_OPT = cli_option("--no-headers", default=False,
444
                       action="store_true", dest="no_headers",
445
                       help="Don't display column headers")
446

    
447
SEP_OPT = cli_option("--separator", default=None,
448
                     action="store", dest="separator",
449
                     help=("Separator between output fields"
450
                           " (defaults to one space)"))
451

    
452
USEUNITS_OPT = cli_option("--units", default=None,
453
                          dest="units", choices=('h', 'm', 'g', 't'),
454
                          help="Specify units for output (one of hmgt)")
455

    
456
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
457
                        type="string", metavar="FIELDS",
458
                        help="Comma separated list of output fields")
459

    
460
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
461
                       default=False, help="Force the operation")
462

    
463
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
464
                         default=False, help="Do not require confirmation")
465

    
466
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
467
                         default=None, help="File with tag names")
468

    
469
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
470
                        default=False, action="store_true",
471
                        help=("Submit the job and return the job ID, but"
472
                              " don't wait for the job to finish"))
473

    
474
SYNC_OPT = cli_option("--sync", dest="do_locking",
475
                      default=False, action="store_true",
476
                      help=("Grab locks while doing the queries"
477
                            " in order to ensure more consistent results"))
478

    
479
_DRY_RUN_OPT = cli_option("--dry-run", default=False,
480
                          action="store_true",
481
                          help=("Do not execute the operation, just run the"
482
                                " check steps and verify it it could be"
483
                                " executed"))
484

    
485
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
486
                         action="store_true",
487
                         help="Increase the verbosity of the operation")
488

    
489
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
490
                              action="store_true", dest="simulate_errors",
491
                              help="Debugging option that makes the operation"
492
                              " treat most runtime checks as failed")
493

    
494
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
495
                        default=True, action="store_false",
496
                        help="Don't wait for sync (DANGEROUS!)")
497

    
498
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
499
                               help="Custom disk setup (diskless, file,"
500
                               " plain or drbd)",
501
                               default=None, metavar="TEMPL",
502
                               choices=list(constants.DISK_TEMPLATES))
503

    
504
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
505
                        help="Do not create any network cards for"
506
                        " the instance")
507

    
508
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
509
                               help="Relative path under default cluster-wide"
510
                               " file storage dir to store file-based disks",
511
                               default=None, metavar="<DIR>")
512

    
513
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
514
                                  help="Driver to use for image files",
515
                                  default="loop", metavar="<DRIVER>",
516
                                  choices=list(constants.FILE_DRIVER))
517

    
518
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
519
                            help="Select nodes for the instance automatically"
520
                            " using the <NAME> iallocator plugin",
521
                            default=None, type="string",
522
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
523

    
524
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
525
                    metavar="<os>",
526
                    completion_suggest=OPT_COMPL_ONE_OS)
527

    
528
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
529
                         type="keyval", default={},
530
                         help="Backend parameters")
531

    
532
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
533
                         default={}, dest="hvparams",
534
                         help="Hypervisor parameters")
535

    
536
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
537
                            help="Hypervisor and hypervisor options, in the"
538
                            " format hypervisor:option=value,option=value,...",
539
                            default=None, type="identkeyval")
540

    
541
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
542
                        help="Hypervisor and hypervisor options, in the"
543
                        " format hypervisor:option=value,option=value,...",
544
                        default=[], action="append", type="identkeyval")
545

    
546
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
547
                           action="store_false",
548
                           help="Don't check that the instance's IP"
549
                           " is alive")
550

    
551
NET_OPT = cli_option("--net",
552
                     help="NIC parameters", default=[],
553
                     dest="nics", action="append", type="identkeyval")
554

    
555
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
556
                      dest="disks", action="append", type="identkeyval")
557

    
558
DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
559
                         help="Comma-separated list of disks"
560
                         " indices to act on (e.g. 0,2) (optional,"
561
                         " defaults to all disks)")
562

    
563
OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
564
                         help="Enforces a single-disk configuration using the"
565
                         " given disk size, in MiB unless a suffix is used",
566
                         default=None, type="unit", metavar="<size>")
567

    
568
IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
569
                                dest="ignore_consistency",
570
                                action="store_true", default=False,
571
                                help="Ignore the consistency of the disks on"
572
                                " the secondary")
573

    
574
NONLIVE_OPT = cli_option("--non-live", dest="live",
575
                         default=True, action="store_false",
576
                         help="Do a non-live migration (this usually means"
577
                         " freeze the instance, save the state, transfer and"
578
                         " only then resume running on the secondary node)")
579

    
580
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
581
                                help="Target node and optional secondary node",
582
                                metavar="<pnode>[:<snode>]",
583
                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
584

    
585
NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
586
                           action="append", metavar="<node>",
587
                           help="Use only this node (can be used multiple"
588
                           " times, if not given defaults to all nodes)",
589
                           completion_suggest=OPT_COMPL_ONE_NODE)
590

    
591
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
592
                             metavar="<node>",
593
                             completion_suggest=OPT_COMPL_ONE_NODE)
594

    
595

    
596
def _ParseArgs(argv, commands, aliases):
597
  """Parser for the command line arguments.
598

599
  This function parses the arguments and returns the function which
600
  must be executed together with its (modified) arguments.
601

602
  @param argv: the command line
603
  @param commands: dictionary with special contents, see the design
604
      doc for cmdline handling
605
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
606

607
  """
608
  if len(argv) == 0:
609
    binary = "<command>"
610
  else:
611
    binary = argv[0].split("/")[-1]
612

    
613
  if len(argv) > 1 and argv[1] == "--version":
614
    ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
615
    # Quit right away. That way we don't have to care about this special
616
    # argument. optparse.py does it the same.
617
    sys.exit(0)
618

    
619
  if len(argv) < 2 or not (argv[1] in commands or
620
                           argv[1] in aliases):
621
    # let's do a nice thing
622
    sortedcmds = commands.keys()
623
    sortedcmds.sort()
624

    
625
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
626
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
627
    ToStdout("")
628

    
629
    # compute the max line length for cmd + usage
630
    mlen = max([len(" %s" % cmd) for cmd in commands])
631
    mlen = min(60, mlen) # should not get here...
632

    
633
    # and format a nice command list
634
    ToStdout("Commands:")
635
    for cmd in sortedcmds:
636
      cmdstr = " %s" % (cmd,)
637
      help_text = commands[cmd][4]
638
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
639
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
640
      for line in help_lines:
641
        ToStdout("%-*s   %s", mlen, "", line)
642

    
643
    ToStdout("")
644

    
645
    return None, None, None
646

    
647
  # get command, unalias it, and look it up in commands
648
  cmd = argv.pop(1)
649
  if cmd in aliases:
650
    if cmd in commands:
651
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
652
                                   " command" % cmd)
653

    
654
    if aliases[cmd] not in commands:
655
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
656
                                   " command '%s'" % (cmd, aliases[cmd]))
657

    
658
    cmd = aliases[cmd]
659

    
660
  func, args_def, parser_opts, usage, description = commands[cmd]
661
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT],
662
                        description=description,
663
                        formatter=TitledHelpFormatter(),
664
                        usage="%%prog %s %s" % (cmd, usage))
665
  parser.disable_interspersed_args()
666
  options, args = parser.parse_args()
667

    
668
  if not _CheckArguments(cmd, args_def, args):
669
    return None, None, None
670

    
671
  return func, options, args
672

    
673

    
674
def _CheckArguments(cmd, args_def, args):
675
  """Verifies the arguments using the argument definition.
676

677
  Algorithm:
678

679
    1. Abort with error if values specified by user but none expected.
680

681
    1. For each argument in definition
682

683
      1. Keep running count of minimum number of values (min_count)
684
      1. Keep running count of maximum number of values (max_count)
685
      1. If it has an unlimited number of values
686

687
        1. Abort with error if it's not the last argument in the definition
688

689
    1. If last argument has limited number of values
690

691
      1. Abort with error if number of values doesn't match or is too large
692

693
    1. Abort with error if user didn't pass enough values (min_count)
694

695
  """
696
  if args and not args_def:
697
    ToStderr("Error: Command %s expects no arguments", cmd)
698
    return False
699

    
700
  min_count = None
701
  max_count = None
702
  check_max = None
703

    
704
  last_idx = len(args_def) - 1
705

    
706
  for idx, arg in enumerate(args_def):
707
    if min_count is None:
708
      min_count = arg.min
709
    elif arg.min is not None:
710
      min_count += arg.min
711

    
712
    if max_count is None:
713
      max_count = arg.max
714
    elif arg.max is not None:
715
      max_count += arg.max
716

    
717
    if idx == last_idx:
718
      check_max = (arg.max is not None)
719

    
720
    elif arg.max is None:
721
      raise errors.ProgrammerError("Only the last argument can have max=None")
722

    
723
  if check_max:
724
    # Command with exact number of arguments
725
    if (min_count is not None and max_count is not None and
726
        min_count == max_count and len(args) != min_count):
727
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
728
      return False
729

    
730
    # Command with limited number of arguments
731
    if max_count is not None and len(args) > max_count:
732
      ToStderr("Error: Command %s expects only %d argument(s)",
733
               cmd, max_count)
734
      return False
735

    
736
  # Command with some required arguments
737
  if min_count is not None and len(args) < min_count:
738
    ToStderr("Error: Command %s expects at least %d argument(s)",
739
             cmd, min_count)
740
    return False
741

    
742
  return True
743

    
744

    
745
def SplitNodeOption(value):
746
  """Splits the value of a --node option.
747

748
  """
749
  if value and ':' in value:
750
    return value.split(':', 1)
751
  else:
752
    return (value, None)
753

    
754

    
755
def UsesRPC(fn):
756
  def wrapper(*args, **kwargs):
757
    rpc.Init()
758
    try:
759
      return fn(*args, **kwargs)
760
    finally:
761
      rpc.Shutdown()
762
  return wrapper
763

    
764

    
765
def AskUser(text, choices=None):
766
  """Ask the user a question.
767

768
  @param text: the question to ask
769

770
  @param choices: list with elements tuples (input_char, return_value,
771
      description); if not given, it will default to: [('y', True,
772
      'Perform the operation'), ('n', False, 'Do no do the operation')];
773
      note that the '?' char is reserved for help
774

775
  @return: one of the return values from the choices list; if input is
776
      not possible (i.e. not running with a tty, we return the last
777
      entry from the list
778

779
  """
780
  if choices is None:
781
    choices = [('y', True, 'Perform the operation'),
782
               ('n', False, 'Do not perform the operation')]
783
  if not choices or not isinstance(choices, list):
784
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
785
  for entry in choices:
786
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
787
      raise errors.ProgrammerError("Invalid choices element to AskUser")
788

    
789
  answer = choices[-1][1]
790
  new_text = []
791
  for line in text.splitlines():
792
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
793
  text = "\n".join(new_text)
794
  try:
795
    f = file("/dev/tty", "a+")
796
  except IOError:
797
    return answer
798
  try:
799
    chars = [entry[0] for entry in choices]
800
    chars[-1] = "[%s]" % chars[-1]
801
    chars.append('?')
802
    maps = dict([(entry[0], entry[1]) for entry in choices])
803
    while True:
804
      f.write(text)
805
      f.write('\n')
806
      f.write("/".join(chars))
807
      f.write(": ")
808
      line = f.readline(2).strip().lower()
809
      if line in maps:
810
        answer = maps[line]
811
        break
812
      elif line == '?':
813
        for entry in choices:
814
          f.write(" %s - %s\n" % (entry[0], entry[2]))
815
        f.write("\n")
816
        continue
817
  finally:
818
    f.close()
819
  return answer
820

    
821

    
822
class JobSubmittedException(Exception):
823
  """Job was submitted, client should exit.
824

825
  This exception has one argument, the ID of the job that was
826
  submitted. The handler should print this ID.
827

828
  This is not an error, just a structured way to exit from clients.
829

830
  """
831

    
832

    
833
def SendJob(ops, cl=None):
834
  """Function to submit an opcode without waiting for the results.
835

836
  @type ops: list
837
  @param ops: list of opcodes
838
  @type cl: luxi.Client
839
  @param cl: the luxi client to use for communicating with the master;
840
             if None, a new client will be created
841

842
  """
843
  if cl is None:
844
    cl = GetClient()
845

    
846
  job_id = cl.SubmitJob(ops)
847

    
848
  return job_id
849

    
850

    
851
def PollJob(job_id, cl=None, feedback_fn=None):
852
  """Function to poll for the result of a job.
853

854
  @type job_id: job identified
855
  @param job_id: the job to poll for results
856
  @type cl: luxi.Client
857
  @param cl: the luxi client to use for communicating with the master;
858
             if None, a new client will be created
859

860
  """
861
  if cl is None:
862
    cl = GetClient()
863

    
864
  prev_job_info = None
865
  prev_logmsg_serial = None
866

    
867
  while True:
868
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
869
                                 prev_logmsg_serial)
870
    if not result:
871
      # job not found, go away!
872
      raise errors.JobLost("Job with id %s lost" % job_id)
873

    
874
    # Split result, a tuple of (field values, log entries)
875
    (job_info, log_entries) = result
876
    (status, ) = job_info
877

    
878
    if log_entries:
879
      for log_entry in log_entries:
880
        (serial, timestamp, _, message) = log_entry
881
        if callable(feedback_fn):
882
          feedback_fn(log_entry[1:])
883
        else:
884
          encoded = utils.SafeEncode(message)
885
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
886
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
887

    
888
    # TODO: Handle canceled and archived jobs
889
    elif status in (constants.JOB_STATUS_SUCCESS,
890
                    constants.JOB_STATUS_ERROR,
891
                    constants.JOB_STATUS_CANCELING,
892
                    constants.JOB_STATUS_CANCELED):
893
      break
894

    
895
    prev_job_info = job_info
896

    
897
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
898
  if not jobs:
899
    raise errors.JobLost("Job with id %s lost" % job_id)
900

    
901
  status, opstatus, result = jobs[0]
902
  if status == constants.JOB_STATUS_SUCCESS:
903
    return result
904
  elif status in (constants.JOB_STATUS_CANCELING,
905
                  constants.JOB_STATUS_CANCELED):
906
    raise errors.OpExecError("Job was canceled")
907
  else:
908
    has_ok = False
909
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
910
      if status == constants.OP_STATUS_SUCCESS:
911
        has_ok = True
912
      elif status == constants.OP_STATUS_ERROR:
913
        errors.MaybeRaise(msg)
914
        if has_ok:
915
          raise errors.OpExecError("partial failure (opcode %d): %s" %
916
                                   (idx, msg))
917
        else:
918
          raise errors.OpExecError(str(msg))
919
    # default failure mode
920
    raise errors.OpExecError(result)
921

    
922

    
923
def SubmitOpCode(op, cl=None, feedback_fn=None):
924
  """Legacy function to submit an opcode.
925

926
  This is just a simple wrapper over the construction of the processor
927
  instance. It should be extended to better handle feedback and
928
  interaction functions.
929

930
  """
931
  if cl is None:
932
    cl = GetClient()
933

    
934
  job_id = SendJob([op], cl)
935

    
936
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
937

    
938
  return op_results[0]
939

    
940

    
941
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
942
  """Wrapper around SubmitOpCode or SendJob.
943

944
  This function will decide, based on the 'opts' parameter, whether to
945
  submit and wait for the result of the opcode (and return it), or
946
  whether to just send the job and print its identifier. It is used in
947
  order to simplify the implementation of the '--submit' option.
948

949
  It will also add the dry-run parameter from the options passed, if true.
950

951
  """
952
  if opts and opts.dry_run:
953
    op.dry_run = opts.dry_run
954
  if opts and opts.submit_only:
955
    job_id = SendJob([op], cl=cl)
956
    raise JobSubmittedException(job_id)
957
  else:
958
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
959

    
960

    
961
def GetClient():
962
  # TODO: Cache object?
963
  try:
964
    client = luxi.Client()
965
  except luxi.NoMasterError:
966
    master, myself = ssconf.GetMasterAndMyself()
967
    if master != myself:
968
      raise errors.OpPrereqError("This is not the master node, please connect"
969
                                 " to node '%s' and rerun the command" %
970
                                 master)
971
    else:
972
      raise
973
  return client
974

    
975

    
976
def FormatError(err):
977
  """Return a formatted error message for a given error.
978

979
  This function takes an exception instance and returns a tuple
980
  consisting of two values: first, the recommended exit code, and
981
  second, a string describing the error message (not
982
  newline-terminated).
983

984
  """
985
  retcode = 1
986
  obuf = StringIO()
987
  msg = str(err)
988
  if isinstance(err, errors.ConfigurationError):
989
    txt = "Corrupt configuration file: %s" % msg
990
    logging.error(txt)
991
    obuf.write(txt + "\n")
992
    obuf.write("Aborting.")
993
    retcode = 2
994
  elif isinstance(err, errors.HooksAbort):
995
    obuf.write("Failure: hooks execution failed:\n")
996
    for node, script, out in err.args[0]:
997
      if out:
998
        obuf.write("  node: %s, script: %s, output: %s\n" %
999
                   (node, script, out))
1000
      else:
1001
        obuf.write("  node: %s, script: %s (no output)\n" %
1002
                   (node, script))
1003
  elif isinstance(err, errors.HooksFailure):
1004
    obuf.write("Failure: hooks general failure: %s" % msg)
1005
  elif isinstance(err, errors.ResolverError):
1006
    this_host = utils.HostInfo.SysName()
1007
    if err.args[0] == this_host:
1008
      msg = "Failure: can't resolve my own hostname ('%s')"
1009
    else:
1010
      msg = "Failure: can't resolve hostname '%s'"
1011
    obuf.write(msg % err.args[0])
1012
  elif isinstance(err, errors.OpPrereqError):
1013
    obuf.write("Failure: prerequisites not met for this"
1014
               " operation:\n%s" % msg)
1015
  elif isinstance(err, errors.OpExecError):
1016
    obuf.write("Failure: command execution error:\n%s" % msg)
1017
  elif isinstance(err, errors.TagError):
1018
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1019
  elif isinstance(err, errors.JobQueueDrainError):
1020
    obuf.write("Failure: the job queue is marked for drain and doesn't"
1021
               " accept new requests\n")
1022
  elif isinstance(err, errors.JobQueueFull):
1023
    obuf.write("Failure: the job queue is full and doesn't accept new"
1024
               " job submissions until old jobs are archived\n")
1025
  elif isinstance(err, errors.TypeEnforcementError):
1026
    obuf.write("Parameter Error: %s" % msg)
1027
  elif isinstance(err, errors.ParameterError):
1028
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1029
  elif isinstance(err, errors.GenericError):
1030
    obuf.write("Unhandled Ganeti error: %s" % msg)
1031
  elif isinstance(err, luxi.NoMasterError):
1032
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
1033
               " and listening for connections?")
1034
  elif isinstance(err, luxi.TimeoutError):
1035
    obuf.write("Timeout while talking to the master daemon. Error:\n"
1036
               "%s" % msg)
1037
  elif isinstance(err, luxi.ProtocolError):
1038
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1039
               "%s" % msg)
1040
  elif isinstance(err, JobSubmittedException):
1041
    obuf.write("JobID: %s\n" % err.args[0])
1042
    retcode = 0
1043
  else:
1044
    obuf.write("Unhandled exception: %s" % msg)
1045
  return retcode, obuf.getvalue().rstrip('\n')
1046

    
1047

    
1048
def GenericMain(commands, override=None, aliases=None):
1049
  """Generic main function for all the gnt-* commands.
1050

1051
  Arguments:
1052
    - commands: a dictionary with a special structure, see the design doc
1053
                for command line handling.
1054
    - override: if not None, we expect a dictionary with keys that will
1055
                override command line options; this can be used to pass
1056
                options from the scripts to generic functions
1057
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1058

1059
  """
1060
  # save the program name and the entire command line for later logging
1061
  if sys.argv:
1062
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1063
    if len(sys.argv) >= 2:
1064
      binary += " " + sys.argv[1]
1065
      old_cmdline = " ".join(sys.argv[2:])
1066
    else:
1067
      old_cmdline = ""
1068
  else:
1069
    binary = "<unknown program>"
1070
    old_cmdline = ""
1071

    
1072
  if aliases is None:
1073
    aliases = {}
1074

    
1075
  try:
1076
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1077
  except errors.ParameterError, err:
1078
    result, err_msg = FormatError(err)
1079
    ToStderr(err_msg)
1080
    return 1
1081

    
1082
  if func is None: # parse error
1083
    return 1
1084

    
1085
  if override is not None:
1086
    for key, val in override.iteritems():
1087
      setattr(options, key, val)
1088

    
1089
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1090
                     stderr_logging=True, program=binary)
1091

    
1092
  if old_cmdline:
1093
    logging.info("run with arguments '%s'", old_cmdline)
1094
  else:
1095
    logging.info("run with no arguments")
1096

    
1097
  try:
1098
    result = func(options, args)
1099
  except (errors.GenericError, luxi.ProtocolError,
1100
          JobSubmittedException), err:
1101
    result, err_msg = FormatError(err)
1102
    logging.exception("Error during command processing")
1103
    ToStderr(err_msg)
1104

    
1105
  return result
1106

    
1107

    
1108
def GenerateTable(headers, fields, separator, data,
1109
                  numfields=None, unitfields=None,
1110
                  units=None):
1111
  """Prints a table with headers and different fields.
1112

1113
  @type headers: dict
1114
  @param headers: dictionary mapping field names to headers for
1115
      the table
1116
  @type fields: list
1117
  @param fields: the field names corresponding to each row in
1118
      the data field
1119
  @param separator: the separator to be used; if this is None,
1120
      the default 'smart' algorithm is used which computes optimal
1121
      field width, otherwise just the separator is used between
1122
      each field
1123
  @type data: list
1124
  @param data: a list of lists, each sublist being one row to be output
1125
  @type numfields: list
1126
  @param numfields: a list with the fields that hold numeric
1127
      values and thus should be right-aligned
1128
  @type unitfields: list
1129
  @param unitfields: a list with the fields that hold numeric
1130
      values that should be formatted with the units field
1131
  @type units: string or None
1132
  @param units: the units we should use for formatting, or None for
1133
      automatic choice (human-readable for non-separator usage, otherwise
1134
      megabytes); this is a one-letter string
1135

1136
  """
1137
  if units is None:
1138
    if separator:
1139
      units = "m"
1140
    else:
1141
      units = "h"
1142

    
1143
  if numfields is None:
1144
    numfields = []
1145
  if unitfields is None:
1146
    unitfields = []
1147

    
1148
  numfields = utils.FieldSet(*numfields)
1149
  unitfields = utils.FieldSet(*unitfields)
1150

    
1151
  format_fields = []
1152
  for field in fields:
1153
    if headers and field not in headers:
1154
      # TODO: handle better unknown fields (either revert to old
1155
      # style of raising exception, or deal more intelligently with
1156
      # variable fields)
1157
      headers[field] = field
1158
    if separator is not None:
1159
      format_fields.append("%s")
1160
    elif numfields.Matches(field):
1161
      format_fields.append("%*s")
1162
    else:
1163
      format_fields.append("%-*s")
1164

    
1165
  if separator is None:
1166
    mlens = [0 for name in fields]
1167
    format = ' '.join(format_fields)
1168
  else:
1169
    format = separator.replace("%", "%%").join(format_fields)
1170

    
1171
  for row in data:
1172
    if row is None:
1173
      continue
1174
    for idx, val in enumerate(row):
1175
      if unitfields.Matches(fields[idx]):
1176
        try:
1177
          val = int(val)
1178
        except ValueError:
1179
          pass
1180
        else:
1181
          val = row[idx] = utils.FormatUnit(val, units)
1182
      val = row[idx] = str(val)
1183
      if separator is None:
1184
        mlens[idx] = max(mlens[idx], len(val))
1185

    
1186
  result = []
1187
  if headers:
1188
    args = []
1189
    for idx, name in enumerate(fields):
1190
      hdr = headers[name]
1191
      if separator is None:
1192
        mlens[idx] = max(mlens[idx], len(hdr))
1193
        args.append(mlens[idx])
1194
      args.append(hdr)
1195
    result.append(format % tuple(args))
1196

    
1197
  for line in data:
1198
    args = []
1199
    if line is None:
1200
      line = ['-' for _ in fields]
1201
    for idx in xrange(len(fields)):
1202
      if separator is None:
1203
        args.append(mlens[idx])
1204
      args.append(line[idx])
1205
    result.append(format % tuple(args))
1206

    
1207
  return result
1208

    
1209

    
1210
def FormatTimestamp(ts):
1211
  """Formats a given timestamp.
1212

1213
  @type ts: timestamp
1214
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1215

1216
  @rtype: string
1217
  @return: a string with the formatted timestamp
1218

1219
  """
1220
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1221
    return '?'
1222
  sec, usec = ts
1223
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1224

    
1225

    
1226
def ParseTimespec(value):
1227
  """Parse a time specification.
1228

1229
  The following suffixed will be recognized:
1230

1231
    - s: seconds
1232
    - m: minutes
1233
    - h: hours
1234
    - d: day
1235
    - w: weeks
1236

1237
  Without any suffix, the value will be taken to be in seconds.
1238

1239
  """
1240
  value = str(value)
1241
  if not value:
1242
    raise errors.OpPrereqError("Empty time specification passed")
1243
  suffix_map = {
1244
    's': 1,
1245
    'm': 60,
1246
    'h': 3600,
1247
    'd': 86400,
1248
    'w': 604800,
1249
    }
1250
  if value[-1] not in suffix_map:
1251
    try:
1252
      value = int(value)
1253
    except ValueError:
1254
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1255
  else:
1256
    multiplier = suffix_map[value[-1]]
1257
    value = value[:-1]
1258
    if not value: # no data left after stripping the suffix
1259
      raise errors.OpPrereqError("Invalid time specification (only"
1260
                                 " suffix passed)")
1261
    try:
1262
      value = int(value) * multiplier
1263
    except ValueError:
1264
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1265
  return value
1266

    
1267

    
1268
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1269
  """Returns the names of online nodes.
1270

1271
  This function will also log a warning on stderr with the names of
1272
  the online nodes.
1273

1274
  @param nodes: if not empty, use only this subset of nodes (minus the
1275
      offline ones)
1276
  @param cl: if not None, luxi client to use
1277
  @type nowarn: boolean
1278
  @param nowarn: by default, this function will output a note with the
1279
      offline nodes that are skipped; if this parameter is True the
1280
      note is not displayed
1281

1282
  """
1283
  if cl is None:
1284
    cl = GetClient()
1285

    
1286
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1287
                         use_locking=False)
1288
  offline = [row[0] for row in result if row[1]]
1289
  if offline and not nowarn:
1290
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1291
  return [row[0] for row in result if not row[1]]
1292

    
1293

    
1294
def _ToStream(stream, txt, *args):
1295
  """Write a message to a stream, bypassing the logging system
1296

1297
  @type stream: file object
1298
  @param stream: the file to which we should write
1299
  @type txt: str
1300
  @param txt: the message
1301

1302
  """
1303
  if args:
1304
    args = tuple(args)
1305
    stream.write(txt % args)
1306
  else:
1307
    stream.write(txt)
1308
  stream.write('\n')
1309
  stream.flush()
1310

    
1311

    
1312
def ToStdout(txt, *args):
1313
  """Write a message to stdout only, bypassing the logging system
1314

1315
  This is just a wrapper over _ToStream.
1316

1317
  @type txt: str
1318
  @param txt: the message
1319

1320
  """
1321
  _ToStream(sys.stdout, txt, *args)
1322

    
1323

    
1324
def ToStderr(txt, *args):
1325
  """Write a message to stderr only, bypassing the logging system
1326

1327
  This is just a wrapper over _ToStream.
1328

1329
  @type txt: str
1330
  @param txt: the message
1331

1332
  """
1333
  _ToStream(sys.stderr, txt, *args)
1334

    
1335

    
1336
class JobExecutor(object):
1337
  """Class which manages the submission and execution of multiple jobs.
1338

1339
  Note that instances of this class should not be reused between
1340
  GetResults() calls.
1341

1342
  """
1343
  def __init__(self, cl=None, verbose=True):
1344
    self.queue = []
1345
    if cl is None:
1346
      cl = GetClient()
1347
    self.cl = cl
1348
    self.verbose = verbose
1349
    self.jobs = []
1350

    
1351
  def QueueJob(self, name, *ops):
1352
    """Record a job for later submit.
1353

1354
    @type name: string
1355
    @param name: a description of the job, will be used in WaitJobSet
1356
    """
1357
    self.queue.append((name, ops))
1358

    
1359
  def SubmitPending(self):
1360
    """Submit all pending jobs.
1361

1362
    """
1363
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1364
    for ((status, data), (name, _)) in zip(results, self.queue):
1365
      self.jobs.append((status, data, name))
1366

    
1367
  def GetResults(self):
1368
    """Wait for and return the results of all jobs.
1369

1370
    @rtype: list
1371
    @return: list of tuples (success, job results), in the same order
1372
        as the submitted jobs; if a job has failed, instead of the result
1373
        there will be the error message
1374

1375
    """
1376
    if not self.jobs:
1377
      self.SubmitPending()
1378
    results = []
1379
    if self.verbose:
1380
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1381
      if ok_jobs:
1382
        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1383
    for submit_status, jid, name in self.jobs:
1384
      if not submit_status:
1385
        ToStderr("Failed to submit job for %s: %s", name, jid)
1386
        results.append((False, jid))
1387
        continue
1388
      if self.verbose:
1389
        ToStdout("Waiting for job %s for %s...", jid, name)
1390
      try:
1391
        job_result = PollJob(jid, cl=self.cl)
1392
        success = True
1393
      except (errors.GenericError, luxi.ProtocolError), err:
1394
        _, job_result = FormatError(err)
1395
        success = False
1396
        # the error message will always be shown, verbose or not
1397
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1398

    
1399
      results.append((success, job_result))
1400
    return results
1401

    
1402
  def WaitOrShow(self, wait):
1403
    """Wait for job results or only print the job IDs.
1404

1405
    @type wait: boolean
1406
    @param wait: whether to wait or not
1407

1408
    """
1409
    if wait:
1410
      return self.GetResults()
1411
    else:
1412
      if not self.jobs:
1413
        self.SubmitPending()
1414
      for status, result, name in self.jobs:
1415
        if status:
1416
          ToStdout("%s: %s", result, name)
1417
        else:
1418
          ToStderr("Failure for %s: %s", name, result)