Move the node flags options to cli.py
[ganeti-local] / lib / cli.py
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   "ALL_OPT",
48   "AUTO_REPLACE_OPT",
49   "BACKEND_OPT",
50   "CLEANUP_OPT",
51   "CONFIRM_OPT",
52   "DEBUG_OPT",
53   "DEBUG_SIMERR_OPT",
54   "DISKIDX_OPT",
55   "DISK_OPT",
56   "DISK_TEMPLATE_OPT",
57   "DRAINED_OPT",
58   "FIELDS_OPT",
59   "FILESTORE_DIR_OPT",
60   "FILESTORE_DRIVER_OPT",
61   "HVLIST_OPT",
62   "HVOPTS_OPT",
63   "HYPERVISOR_OPT",
64   "IALLOCATOR_OPT",
65   "IGNORE_CONSIST_OPT",
66   "IGNORE_FAILURES_OPT",
67   "IGNORE_SIZE_OPT",
68   "FORCE_OPT",
69   "MC_OPT",
70   "NET_OPT",
71   "NEW_SECONDARY_OPT",
72   "NODE_LIST_OPT",
73   "NODE_PLACEMENT_OPT",
74   "NOHDR_OPT",
75   "NOIPCHECK_OPT",
76   "NONICS_OPT",
77   "NONLIVE_OPT",
78   "NOSTART_OPT",
79   "NOSSH_KEYCHECK_OPT",
80   "NWSYNC_OPT",
81   "ON_PRIMARY_OPT",
82   "ON_SECONDARY_OPT",
83   "OFFLINE_OPT",
84   "OS_OPT",
85   "OS_SIZE_OPT",
86   "READD_OPT",
87   "SECONDARY_IP_OPT",
88   "SELECT_OS_OPT",
89   "SEP_OPT",
90   "SHOWCMD_OPT",
91   "SINGLE_NODE_OPT",
92   "SRC_DIR_OPT",
93   "SRC_NODE_OPT",
94   "SUBMIT_OPT",
95   "STATIC_OPT",
96   "SYNC_OPT",
97   "TAG_SRC_OPT",
98   "USEUNITS_OPT",
99   "VERBOSE_OPT",
100   # Generic functions for CLI programs
101   "GenericMain",
102   "GetClient",
103   "GetOnlineNodes",
104   "JobExecutor",
105   "JobSubmittedException",
106   "ParseTimespec",
107   "SubmitOpCode",
108   "SubmitOrSend",
109   "UsesRPC",
110   # Formatting functions
111   "ToStderr", "ToStdout",
112   "FormatError",
113   "GenerateTable",
114   "AskUser",
115   "FormatTimestamp",
116   # Tags functions
117   "ListTags",
118   "AddTags",
119   "RemoveTags",
120   # command line options support infrastructure
121   "ARGS_MANY_INSTANCES",
122   "ARGS_MANY_NODES",
123   "ARGS_NONE",
124   "ARGS_ONE_INSTANCE",
125   "ARGS_ONE_NODE",
126   "ArgChoice",
127   "ArgCommand",
128   "ArgFile",
129   "ArgHost",
130   "ArgInstance",
131   "ArgJobId",
132   "ArgNode",
133   "ArgSuggest",
134   "ArgUnknown",
135   "OPT_COMPL_INST_ADD_NODES",
136   "OPT_COMPL_MANY_NODES",
137   "OPT_COMPL_ONE_IALLOCATOR",
138   "OPT_COMPL_ONE_INSTANCE",
139   "OPT_COMPL_ONE_NODE",
140   "OPT_COMPL_ONE_OS",
141   "cli_option",
142   "SplitNodeOption",
143   ]
144
145 NO_PREFIX = "no_"
146 UN_PREFIX = "-"
147
148
149 class _Argument:
150   def __init__(self, min=0, max=None):
151     self.min = min
152     self.max = max
153
154   def __repr__(self):
155     return ("<%s min=%s max=%s>" %
156             (self.__class__.__name__, self.min, self.max))
157
158
159 class ArgSuggest(_Argument):
160   """Suggesting argument.
161
162   Value can be any of the ones passed to the constructor.
163
164   """
165   def __init__(self, min=0, max=None, choices=None):
166     _Argument.__init__(self, min=min, max=max)
167     self.choices = choices
168
169   def __repr__(self):
170     return ("<%s min=%s max=%s choices=%r>" %
171             (self.__class__.__name__, self.min, self.max, self.choices))
172
173
174 class ArgChoice(ArgSuggest):
175   """Choice argument.
176
177   Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
178   but value must be one of the choices.
179
180   """
181
182
183 class ArgUnknown(_Argument):
184   """Unknown argument to program (e.g. determined at runtime).
185
186   """
187
188
189 class ArgInstance(_Argument):
190   """Instances argument.
191
192   """
193
194
195 class ArgNode(_Argument):
196   """Node argument.
197
198   """
199
200 class ArgJobId(_Argument):
201   """Job ID argument.
202
203   """
204
205
206 class ArgFile(_Argument):
207   """File path argument.
208
209   """
210
211
212 class ArgCommand(_Argument):
213   """Command argument.
214
215   """
216
217
218 class ArgHost(_Argument):
219   """Host argument.
220
221   """
222
223
224 ARGS_NONE = []
225 ARGS_MANY_INSTANCES = [ArgInstance()]
226 ARGS_MANY_NODES = [ArgNode()]
227 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
228 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
229
230
231
232 def _ExtractTagsObject(opts, args):
233   """Extract the tag type object.
234
235   Note that this function will modify its args parameter.
236
237   """
238   if not hasattr(opts, "tag_type"):
239     raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
240   kind = opts.tag_type
241   if kind == constants.TAG_CLUSTER:
242     retval = kind, kind
243   elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
244     if not args:
245       raise errors.OpPrereqError("no arguments passed to the command")
246     name = args.pop(0)
247     retval = kind, name
248   else:
249     raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
250   return retval
251
252
253 def _ExtendTags(opts, args):
254   """Extend the args if a source file has been given.
255
256   This function will extend the tags with the contents of the file
257   passed in the 'tags_source' attribute of the opts parameter. A file
258   named '-' will be replaced by stdin.
259
260   """
261   fname = opts.tags_source
262   if fname is None:
263     return
264   if fname == "-":
265     new_fh = sys.stdin
266   else:
267     new_fh = open(fname, "r")
268   new_data = []
269   try:
270     # we don't use the nice 'new_data = [line.strip() for line in fh]'
271     # because of python bug 1633941
272     while True:
273       line = new_fh.readline()
274       if not line:
275         break
276       new_data.append(line.strip())
277   finally:
278     new_fh.close()
279   args.extend(new_data)
280
281
282 def ListTags(opts, args):
283   """List the tags on a given object.
284
285   This is a generic implementation that knows how to deal with all
286   three cases of tag objects (cluster, node, instance). The opts
287   argument is expected to contain a tag_type field denoting what
288   object type we work on.
289
290   """
291   kind, name = _ExtractTagsObject(opts, args)
292   op = opcodes.OpGetTags(kind=kind, name=name)
293   result = SubmitOpCode(op)
294   result = list(result)
295   result.sort()
296   for tag in result:
297     ToStdout(tag)
298
299
300 def AddTags(opts, args):
301   """Add tags on a given object.
302
303   This is a generic implementation that knows how to deal with all
304   three cases of tag objects (cluster, node, instance). The opts
305   argument is expected to contain a tag_type field denoting what
306   object type we work on.
307
308   """
309   kind, name = _ExtractTagsObject(opts, args)
310   _ExtendTags(opts, args)
311   if not args:
312     raise errors.OpPrereqError("No tags to be added")
313   op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
314   SubmitOpCode(op)
315
316
317 def RemoveTags(opts, args):
318   """Remove tags from a given object.
319
320   This is a generic implementation that knows how to deal with all
321   three cases of tag objects (cluster, node, instance). The opts
322   argument is expected to contain a tag_type field denoting what
323   object type we work on.
324
325   """
326   kind, name = _ExtractTagsObject(opts, args)
327   _ExtendTags(opts, args)
328   if not args:
329     raise errors.OpPrereqError("No tags to be removed")
330   op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
331   SubmitOpCode(op)
332
333
334 def check_unit(option, opt, value):
335   """OptParsers custom converter for units.
336
337   """
338   try:
339     return utils.ParseUnit(value)
340   except errors.UnitParseError, err:
341     raise OptionValueError("option %s: %s" % (opt, err))
342
343
344 def _SplitKeyVal(opt, data):
345   """Convert a KeyVal string into a dict.
346
347   This function will convert a key=val[,...] string into a dict. Empty
348   values will be converted specially: keys which have the prefix 'no_'
349   will have the value=False and the prefix stripped, the others will
350   have value=True.
351
352   @type opt: string
353   @param opt: a string holding the option name for which we process the
354       data, used in building error messages
355   @type data: string
356   @param data: a string of the format key=val,key=val,...
357   @rtype: dict
358   @return: {key=val, key=val}
359   @raises errors.ParameterError: if there are duplicate keys
360
361   """
362   kv_dict = {}
363   if data:
364     for elem in data.split(","):
365       if "=" in elem:
366         key, val = elem.split("=", 1)
367       else:
368         if elem.startswith(NO_PREFIX):
369           key, val = elem[len(NO_PREFIX):], False
370         elif elem.startswith(UN_PREFIX):
371           key, val = elem[len(UN_PREFIX):], None
372         else:
373           key, val = elem, True
374       if key in kv_dict:
375         raise errors.ParameterError("Duplicate key '%s' in option %s" %
376                                     (key, opt))
377       kv_dict[key] = val
378   return kv_dict
379
380
381 def check_ident_key_val(option, opt, value):
382   """Custom parser for ident:key=val,key=val options.
383
384   This will store the parsed values as a tuple (ident, {key: val}). As such,
385   multiple uses of this option via action=append is possible.
386
387   """
388   if ":" not in value:
389     ident, rest = value, ''
390   else:
391     ident, rest = value.split(":", 1)
392
393   if ident.startswith(NO_PREFIX):
394     if rest:
395       msg = "Cannot pass options when removing parameter groups: %s" % value
396       raise errors.ParameterError(msg)
397     retval = (ident[len(NO_PREFIX):], False)
398   elif ident.startswith(UN_PREFIX):
399     if rest:
400       msg = "Cannot pass options when removing parameter groups: %s" % value
401       raise errors.ParameterError(msg)
402     retval = (ident[len(UN_PREFIX):], None)
403   else:
404     kv_dict = _SplitKeyVal(opt, rest)
405     retval = (ident, kv_dict)
406   return retval
407
408
409 def check_key_val(option, opt, value):
410   """Custom parser class for key=val,key=val options.
411
412   This will store the parsed values as a dict {key: val}.
413
414   """
415   return _SplitKeyVal(opt, value)
416
417
418 # completion_suggestion is normally a list. Using numeric values not evaluating
419 # to False for dynamic completion.
420 (OPT_COMPL_MANY_NODES,
421  OPT_COMPL_ONE_NODE,
422  OPT_COMPL_ONE_INSTANCE,
423  OPT_COMPL_ONE_OS,
424  OPT_COMPL_ONE_IALLOCATOR,
425  OPT_COMPL_INST_ADD_NODES) = range(100, 106)
426
427 OPT_COMPL_ALL = frozenset([
428   OPT_COMPL_MANY_NODES,
429   OPT_COMPL_ONE_NODE,
430   OPT_COMPL_ONE_INSTANCE,
431   OPT_COMPL_ONE_OS,
432   OPT_COMPL_ONE_IALLOCATOR,
433   OPT_COMPL_INST_ADD_NODES,
434   ])
435
436
437 class CliOption(Option):
438   """Custom option class for optparse.
439
440   """
441   ATTRS = Option.ATTRS + [
442     "completion_suggest",
443     ]
444   TYPES = Option.TYPES + (
445     "identkeyval",
446     "keyval",
447     "unit",
448     )
449   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
450   TYPE_CHECKER["identkeyval"] = check_ident_key_val
451   TYPE_CHECKER["keyval"] = check_key_val
452   TYPE_CHECKER["unit"] = check_unit
453
454
455 # optparse.py sets make_option, so we do it for our own option class, too
456 cli_option = CliOption
457
458
459 _YESNO = ("yes", "no")
460 _YORNO = "yes|no"
461
462 DEBUG_OPT = cli_option("-d", "--debug", default=False,
463                        action="store_true",
464                        help="Turn debugging on")
465
466 NOHDR_OPT = cli_option("--no-headers", default=False,
467                        action="store_true", dest="no_headers",
468                        help="Don't display column headers")
469
470 SEP_OPT = cli_option("--separator", default=None,
471                      action="store", dest="separator",
472                      help=("Separator between output fields"
473                            " (defaults to one space)"))
474
475 USEUNITS_OPT = cli_option("--units", default=None,
476                           dest="units", choices=('h', 'm', 'g', 't'),
477                           help="Specify units for output (one of hmgt)")
478
479 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
480                         type="string", metavar="FIELDS",
481                         help="Comma separated list of output fields")
482
483 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
484                        default=False, help="Force the operation")
485
486 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
487                          default=False, help="Do not require confirmation")
488
489 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
490                          default=None, help="File with tag names")
491
492 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
493                         default=False, action="store_true",
494                         help=("Submit the job and return the job ID, but"
495                               " don't wait for the job to finish"))
496
497 SYNC_OPT = cli_option("--sync", dest="do_locking",
498                       default=False, action="store_true",
499                       help=("Grab locks while doing the queries"
500                             " in order to ensure more consistent results"))
501
502 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
503                           action="store_true",
504                           help=("Do not execute the operation, just run the"
505                                 " check steps and verify it it could be"
506                                 " executed"))
507
508 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
509                          action="store_true",
510                          help="Increase the verbosity of the operation")
511
512 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
513                               action="store_true", dest="simulate_errors",
514                               help="Debugging option that makes the operation"
515                               " treat most runtime checks as failed")
516
517 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
518                         default=True, action="store_false",
519                         help="Don't wait for sync (DANGEROUS!)")
520
521 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
522                                help="Custom disk setup (diskless, file,"
523                                " plain or drbd)",
524                                default=None, metavar="TEMPL",
525                                choices=list(constants.DISK_TEMPLATES))
526
527 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
528                         help="Do not create any network cards for"
529                         " the instance")
530
531 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
532                                help="Relative path under default cluster-wide"
533                                " file storage dir to store file-based disks",
534                                default=None, metavar="<DIR>")
535
536 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
537                                   help="Driver to use for image files",
538                                   default="loop", metavar="<DRIVER>",
539                                   choices=list(constants.FILE_DRIVER))
540
541 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
542                             help="Select nodes for the instance automatically"
543                             " using the <NAME> iallocator plugin",
544                             default=None, type="string",
545                             completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
546
547 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
548                     metavar="<os>",
549                     completion_suggest=OPT_COMPL_ONE_OS)
550
551 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
552                          type="keyval", default={},
553                          help="Backend parameters")
554
555 HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
556                          default={}, dest="hvparams",
557                          help="Hypervisor parameters")
558
559 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
560                             help="Hypervisor and hypervisor options, in the"
561                             " format hypervisor:option=value,option=value,...",
562                             default=None, type="identkeyval")
563
564 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
565                         help="Hypervisor and hypervisor options, in the"
566                         " format hypervisor:option=value,option=value,...",
567                         default=[], action="append", type="identkeyval")
568
569 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
570                            action="store_false",
571                            help="Don't check that the instance's IP"
572                            " is alive")
573
574 NET_OPT = cli_option("--net",
575                      help="NIC parameters", default=[],
576                      dest="nics", action="append", type="identkeyval")
577
578 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
579                       dest="disks", action="append", type="identkeyval")
580
581 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
582                          help="Comma-separated list of disks"
583                          " indices to act on (e.g. 0,2) (optional,"
584                          " defaults to all disks)")
585
586 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
587                          help="Enforces a single-disk configuration using the"
588                          " given disk size, in MiB unless a suffix is used",
589                          default=None, type="unit", metavar="<size>")
590
591 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
592                                 dest="ignore_consistency",
593                                 action="store_true", default=False,
594                                 help="Ignore the consistency of the disks on"
595                                 " the secondary")
596
597 NONLIVE_OPT = cli_option("--non-live", dest="live",
598                          default=True, action="store_false",
599                          help="Do a non-live migration (this usually means"
600                          " freeze the instance, save the state, transfer and"
601                          " only then resume running on the secondary node)")
602
603 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
604                                 help="Target node and optional secondary node",
605                                 metavar="<pnode>[:<snode>]",
606                                 completion_suggest=OPT_COMPL_INST_ADD_NODES)
607
608 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
609                            action="append", metavar="<node>",
610                            help="Use only this node (can be used multiple"
611                            " times, if not given defaults to all nodes)",
612                            completion_suggest=OPT_COMPL_ONE_NODE)
613
614 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
615                              metavar="<node>",
616                              completion_suggest=OPT_COMPL_ONE_NODE)
617
618 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
619                          action="store_false",
620                          help="Don't start the instance after creation")
621
622 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
623                          action="store_true", default=False,
624                          help="Show command instead of executing it")
625
626 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
627                          default=False, action="store_true",
628                          help="Instead of performing the migration, try to"
629                          " recover from a failed cleanup. This is safe"
630                          " to run even if the instance is healthy, but it"
631                          " will create extra replication traffic and "
632                          " disrupt briefly the replication (like during the"
633                          " migration")
634
635 STATIC_OPT = cli_option("-s", "--static", dest="static",
636                         action="store_true", default=False,
637                         help="Only show configuration data, not runtime data")
638
639 ALL_OPT = cli_option("--all", dest="show_all",
640                      default=False, action="store_true",
641                      help="Show info on all instances on the cluster."
642                      " This can take a long time to run, use wisely")
643
644 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
645                            action="store_true", default=False,
646                            help="Interactive OS reinstall, lists available"
647                            " OS templates for selection")
648
649 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
650                                  action="store_true", default=False,
651                                  help="Remove the instance from the cluster"
652                                  " configuration even if there are failures"
653                                  " during the removal process")
654
655 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
656                                help="Specifies the new secondary node",
657                                metavar="NODE", default=None,
658                                completion_suggest=OPT_COMPL_ONE_NODE)
659
660 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
661                             default=False, action="store_true",
662                             help="Replace the disk(s) on the primary"
663                             " node (only for the drbd template)")
664
665 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
666                               default=False, action="store_true",
667                               help="Replace the disk(s) on the secondary"
668                               " node (only for the drbd template)")
669
670 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
671                               default=False, action="store_true",
672                               help="Automatically replace faulty disks"
673                               " (only for the drbd template)")
674
675 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
676                              default=False, action="store_true",
677                              help="Ignore current recorded size"
678                              " (useful for forcing activation when"
679                              " the recorded size is wrong)")
680
681 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
682                           metavar="<node>",
683                           completion_suggest=OPT_COMPL_ONE_NODE)
684
685 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
686                          metavar="<dir>")
687
688 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
689                               help="Specify the secondary ip for the node",
690                               metavar="ADDRESS", default=None)
691
692 READD_OPT = cli_option("--readd", dest="readd",
693                        default=False, action="store_true",
694                        help="Readd old node after replacing it")
695
696 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
697                                 default=True, action="store_false",
698                                 help="Disable SSH key fingerprint checking")
699
700
701 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
702                     choices=_YESNO, default=None, metavar=_YORNO,
703                     help="Set the master_candidate flag on the node")
704
705 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
706                          choices=_YESNO, default=None,
707                          help="Set the offline flag on the node")
708
709 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
710                          choices=_YESNO, default=None,
711                          help="Set the drained flag on the node")
712
713
714 def _ParseArgs(argv, commands, aliases):
715   """Parser for the command line arguments.
716
717   This function parses the arguments and returns the function which
718   must be executed together with its (modified) arguments.
719
720   @param argv: the command line
721   @param commands: dictionary with special contents, see the design
722       doc for cmdline handling
723   @param aliases: dictionary with command aliases {'alias': 'target, ...}
724
725   """
726   if len(argv) == 0:
727     binary = "<command>"
728   else:
729     binary = argv[0].split("/")[-1]
730
731   if len(argv) > 1 and argv[1] == "--version":
732     ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
733     # Quit right away. That way we don't have to care about this special
734     # argument. optparse.py does it the same.
735     sys.exit(0)
736
737   if len(argv) < 2 or not (argv[1] in commands or
738                            argv[1] in aliases):
739     # let's do a nice thing
740     sortedcmds = commands.keys()
741     sortedcmds.sort()
742
743     ToStdout("Usage: %s {command} [options...] [argument...]", binary)
744     ToStdout("%s <command> --help to see details, or man %s", binary, binary)
745     ToStdout("")
746
747     # compute the max line length for cmd + usage
748     mlen = max([len(" %s" % cmd) for cmd in commands])
749     mlen = min(60, mlen) # should not get here...
750
751     # and format a nice command list
752     ToStdout("Commands:")
753     for cmd in sortedcmds:
754       cmdstr = " %s" % (cmd,)
755       help_text = commands[cmd][4]
756       help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
757       ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
758       for line in help_lines:
759         ToStdout("%-*s   %s", mlen, "", line)
760
761     ToStdout("")
762
763     return None, None, None
764
765   # get command, unalias it, and look it up in commands
766   cmd = argv.pop(1)
767   if cmd in aliases:
768     if cmd in commands:
769       raise errors.ProgrammerError("Alias '%s' overrides an existing"
770                                    " command" % cmd)
771
772     if aliases[cmd] not in commands:
773       raise errors.ProgrammerError("Alias '%s' maps to non-existing"
774                                    " command '%s'" % (cmd, aliases[cmd]))
775
776     cmd = aliases[cmd]
777
778   func, args_def, parser_opts, usage, description = commands[cmd]
779   parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT],
780                         description=description,
781                         formatter=TitledHelpFormatter(),
782                         usage="%%prog %s %s" % (cmd, usage))
783   parser.disable_interspersed_args()
784   options, args = parser.parse_args()
785
786   if not _CheckArguments(cmd, args_def, args):
787     return None, None, None
788
789   return func, options, args
790
791
792 def _CheckArguments(cmd, args_def, args):
793   """Verifies the arguments using the argument definition.
794
795   Algorithm:
796
797     1. Abort with error if values specified by user but none expected.
798
799     1. For each argument in definition
800
801       1. Keep running count of minimum number of values (min_count)
802       1. Keep running count of maximum number of values (max_count)
803       1. If it has an unlimited number of values
804
805         1. Abort with error if it's not the last argument in the definition
806
807     1. If last argument has limited number of values
808
809       1. Abort with error if number of values doesn't match or is too large
810
811     1. Abort with error if user didn't pass enough values (min_count)
812
813   """
814   if args and not args_def:
815     ToStderr("Error: Command %s expects no arguments", cmd)
816     return False
817
818   min_count = None
819   max_count = None
820   check_max = None
821
822   last_idx = len(args_def) - 1
823
824   for idx, arg in enumerate(args_def):
825     if min_count is None:
826       min_count = arg.min
827     elif arg.min is not None:
828       min_count += arg.min
829
830     if max_count is None:
831       max_count = arg.max
832     elif arg.max is not None:
833       max_count += arg.max
834
835     if idx == last_idx:
836       check_max = (arg.max is not None)
837
838     elif arg.max is None:
839       raise errors.ProgrammerError("Only the last argument can have max=None")
840
841   if check_max:
842     # Command with exact number of arguments
843     if (min_count is not None and max_count is not None and
844         min_count == max_count and len(args) != min_count):
845       ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
846       return False
847
848     # Command with limited number of arguments
849     if max_count is not None and len(args) > max_count:
850       ToStderr("Error: Command %s expects only %d argument(s)",
851                cmd, max_count)
852       return False
853
854   # Command with some required arguments
855   if min_count is not None and len(args) < min_count:
856     ToStderr("Error: Command %s expects at least %d argument(s)",
857              cmd, min_count)
858     return False
859
860   return True
861
862
863 def SplitNodeOption(value):
864   """Splits the value of a --node option.
865
866   """
867   if value and ':' in value:
868     return value.split(':', 1)
869   else:
870     return (value, None)
871
872
873 def UsesRPC(fn):
874   def wrapper(*args, **kwargs):
875     rpc.Init()
876     try:
877       return fn(*args, **kwargs)
878     finally:
879       rpc.Shutdown()
880   return wrapper
881
882
883 def AskUser(text, choices=None):
884   """Ask the user a question.
885
886   @param text: the question to ask
887
888   @param choices: list with elements tuples (input_char, return_value,
889       description); if not given, it will default to: [('y', True,
890       'Perform the operation'), ('n', False, 'Do no do the operation')];
891       note that the '?' char is reserved for help
892
893   @return: one of the return values from the choices list; if input is
894       not possible (i.e. not running with a tty, we return the last
895       entry from the list
896
897   """
898   if choices is None:
899     choices = [('y', True, 'Perform the operation'),
900                ('n', False, 'Do not perform the operation')]
901   if not choices or not isinstance(choices, list):
902     raise errors.ProgrammerError("Invalid choices argument to AskUser")
903   for entry in choices:
904     if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
905       raise errors.ProgrammerError("Invalid choices element to AskUser")
906
907   answer = choices[-1][1]
908   new_text = []
909   for line in text.splitlines():
910     new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
911   text = "\n".join(new_text)
912   try:
913     f = file("/dev/tty", "a+")
914   except IOError:
915     return answer
916   try:
917     chars = [entry[0] for entry in choices]
918     chars[-1] = "[%s]" % chars[-1]
919     chars.append('?')
920     maps = dict([(entry[0], entry[1]) for entry in choices])
921     while True:
922       f.write(text)
923       f.write('\n')
924       f.write("/".join(chars))
925       f.write(": ")
926       line = f.readline(2).strip().lower()
927       if line in maps:
928         answer = maps[line]
929         break
930       elif line == '?':
931         for entry in choices:
932           f.write(" %s - %s\n" % (entry[0], entry[2]))
933         f.write("\n")
934         continue
935   finally:
936     f.close()
937   return answer
938
939
940 class JobSubmittedException(Exception):
941   """Job was submitted, client should exit.
942
943   This exception has one argument, the ID of the job that was
944   submitted. The handler should print this ID.
945
946   This is not an error, just a structured way to exit from clients.
947
948   """
949
950
951 def SendJob(ops, cl=None):
952   """Function to submit an opcode without waiting for the results.
953
954   @type ops: list
955   @param ops: list of opcodes
956   @type cl: luxi.Client
957   @param cl: the luxi client to use for communicating with the master;
958              if None, a new client will be created
959
960   """
961   if cl is None:
962     cl = GetClient()
963
964   job_id = cl.SubmitJob(ops)
965
966   return job_id
967
968
969 def PollJob(job_id, cl=None, feedback_fn=None):
970   """Function to poll for the result of a job.
971
972   @type job_id: job identified
973   @param job_id: the job to poll for results
974   @type cl: luxi.Client
975   @param cl: the luxi client to use for communicating with the master;
976              if None, a new client will be created
977
978   """
979   if cl is None:
980     cl = GetClient()
981
982   prev_job_info = None
983   prev_logmsg_serial = None
984
985   while True:
986     result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
987                                  prev_logmsg_serial)
988     if not result:
989       # job not found, go away!
990       raise errors.JobLost("Job with id %s lost" % job_id)
991
992     # Split result, a tuple of (field values, log entries)
993     (job_info, log_entries) = result
994     (status, ) = job_info
995
996     if log_entries:
997       for log_entry in log_entries:
998         (serial, timestamp, _, message) = log_entry
999         if callable(feedback_fn):
1000           feedback_fn(log_entry[1:])
1001         else:
1002           encoded = utils.SafeEncode(message)
1003           ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1004         prev_logmsg_serial = max(prev_logmsg_serial, serial)
1005
1006     # TODO: Handle canceled and archived jobs
1007     elif status in (constants.JOB_STATUS_SUCCESS,
1008                     constants.JOB_STATUS_ERROR,
1009                     constants.JOB_STATUS_CANCELING,
1010                     constants.JOB_STATUS_CANCELED):
1011       break
1012
1013     prev_job_info = job_info
1014
1015   jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1016   if not jobs:
1017     raise errors.JobLost("Job with id %s lost" % job_id)
1018
1019   status, opstatus, result = jobs[0]
1020   if status == constants.JOB_STATUS_SUCCESS:
1021     return result
1022   elif status in (constants.JOB_STATUS_CANCELING,
1023                   constants.JOB_STATUS_CANCELED):
1024     raise errors.OpExecError("Job was canceled")
1025   else:
1026     has_ok = False
1027     for idx, (status, msg) in enumerate(zip(opstatus, result)):
1028       if status == constants.OP_STATUS_SUCCESS:
1029         has_ok = True
1030       elif status == constants.OP_STATUS_ERROR:
1031         errors.MaybeRaise(msg)
1032         if has_ok:
1033           raise errors.OpExecError("partial failure (opcode %d): %s" %
1034                                    (idx, msg))
1035         else:
1036           raise errors.OpExecError(str(msg))
1037     # default failure mode
1038     raise errors.OpExecError(result)
1039
1040
1041 def SubmitOpCode(op, cl=None, feedback_fn=None):
1042   """Legacy function to submit an opcode.
1043
1044   This is just a simple wrapper over the construction of the processor
1045   instance. It should be extended to better handle feedback and
1046   interaction functions.
1047
1048   """
1049   if cl is None:
1050     cl = GetClient()
1051
1052   job_id = SendJob([op], cl)
1053
1054   op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1055
1056   return op_results[0]
1057
1058
1059 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1060   """Wrapper around SubmitOpCode or SendJob.
1061
1062   This function will decide, based on the 'opts' parameter, whether to
1063   submit and wait for the result of the opcode (and return it), or
1064   whether to just send the job and print its identifier. It is used in
1065   order to simplify the implementation of the '--submit' option.
1066
1067   It will also add the dry-run parameter from the options passed, if true.
1068
1069   """
1070   if opts and opts.dry_run:
1071     op.dry_run = opts.dry_run
1072   if opts and opts.submit_only:
1073     job_id = SendJob([op], cl=cl)
1074     raise JobSubmittedException(job_id)
1075   else:
1076     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1077
1078
1079 def GetClient():
1080   # TODO: Cache object?
1081   try:
1082     client = luxi.Client()
1083   except luxi.NoMasterError:
1084     master, myself = ssconf.GetMasterAndMyself()
1085     if master != myself:
1086       raise errors.OpPrereqError("This is not the master node, please connect"
1087                                  " to node '%s' and rerun the command" %
1088                                  master)
1089     else:
1090       raise
1091   return client
1092
1093
1094 def FormatError(err):
1095   """Return a formatted error message for a given error.
1096
1097   This function takes an exception instance and returns a tuple
1098   consisting of two values: first, the recommended exit code, and
1099   second, a string describing the error message (not
1100   newline-terminated).
1101
1102   """
1103   retcode = 1
1104   obuf = StringIO()
1105   msg = str(err)
1106   if isinstance(err, errors.ConfigurationError):
1107     txt = "Corrupt configuration file: %s" % msg
1108     logging.error(txt)
1109     obuf.write(txt + "\n")
1110     obuf.write("Aborting.")
1111     retcode = 2
1112   elif isinstance(err, errors.HooksAbort):
1113     obuf.write("Failure: hooks execution failed:\n")
1114     for node, script, out in err.args[0]:
1115       if out:
1116         obuf.write("  node: %s, script: %s, output: %s\n" %
1117                    (node, script, out))
1118       else:
1119         obuf.write("  node: %s, script: %s (no output)\n" %
1120                    (node, script))
1121   elif isinstance(err, errors.HooksFailure):
1122     obuf.write("Failure: hooks general failure: %s" % msg)
1123   elif isinstance(err, errors.ResolverError):
1124     this_host = utils.HostInfo.SysName()
1125     if err.args[0] == this_host:
1126       msg = "Failure: can't resolve my own hostname ('%s')"
1127     else:
1128       msg = "Failure: can't resolve hostname '%s'"
1129     obuf.write(msg % err.args[0])
1130   elif isinstance(err, errors.OpPrereqError):
1131     obuf.write("Failure: prerequisites not met for this"
1132                " operation:\n%s" % msg)
1133   elif isinstance(err, errors.OpExecError):
1134     obuf.write("Failure: command execution error:\n%s" % msg)
1135   elif isinstance(err, errors.TagError):
1136     obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1137   elif isinstance(err, errors.JobQueueDrainError):
1138     obuf.write("Failure: the job queue is marked for drain and doesn't"
1139                " accept new requests\n")
1140   elif isinstance(err, errors.JobQueueFull):
1141     obuf.write("Failure: the job queue is full and doesn't accept new"
1142                " job submissions until old jobs are archived\n")
1143   elif isinstance(err, errors.TypeEnforcementError):
1144     obuf.write("Parameter Error: %s" % msg)
1145   elif isinstance(err, errors.ParameterError):
1146     obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1147   elif isinstance(err, errors.GenericError):
1148     obuf.write("Unhandled Ganeti error: %s" % msg)
1149   elif isinstance(err, luxi.NoMasterError):
1150     obuf.write("Cannot communicate with the master daemon.\nIs it running"
1151                " and listening for connections?")
1152   elif isinstance(err, luxi.TimeoutError):
1153     obuf.write("Timeout while talking to the master daemon. Error:\n"
1154                "%s" % msg)
1155   elif isinstance(err, luxi.ProtocolError):
1156     obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1157                "%s" % msg)
1158   elif isinstance(err, JobSubmittedException):
1159     obuf.write("JobID: %s\n" % err.args[0])
1160     retcode = 0
1161   else:
1162     obuf.write("Unhandled exception: %s" % msg)
1163   return retcode, obuf.getvalue().rstrip('\n')
1164
1165
1166 def GenericMain(commands, override=None, aliases=None):
1167   """Generic main function for all the gnt-* commands.
1168
1169   Arguments:
1170     - commands: a dictionary with a special structure, see the design doc
1171                 for command line handling.
1172     - override: if not None, we expect a dictionary with keys that will
1173                 override command line options; this can be used to pass
1174                 options from the scripts to generic functions
1175     - aliases: dictionary with command aliases {'alias': 'target, ...}
1176
1177   """
1178   # save the program name and the entire command line for later logging
1179   if sys.argv:
1180     binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1181     if len(sys.argv) >= 2:
1182       binary += " " + sys.argv[1]
1183       old_cmdline = " ".join(sys.argv[2:])
1184     else:
1185       old_cmdline = ""
1186   else:
1187     binary = "<unknown program>"
1188     old_cmdline = ""
1189
1190   if aliases is None:
1191     aliases = {}
1192
1193   try:
1194     func, options, args = _ParseArgs(sys.argv, commands, aliases)
1195   except errors.ParameterError, err:
1196     result, err_msg = FormatError(err)
1197     ToStderr(err_msg)
1198     return 1
1199
1200   if func is None: # parse error
1201     return 1
1202
1203   if override is not None:
1204     for key, val in override.iteritems():
1205       setattr(options, key, val)
1206
1207   utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1208                      stderr_logging=True, program=binary)
1209
1210   if old_cmdline:
1211     logging.info("run with arguments '%s'", old_cmdline)
1212   else:
1213     logging.info("run with no arguments")
1214
1215   try:
1216     result = func(options, args)
1217   except (errors.GenericError, luxi.ProtocolError,
1218           JobSubmittedException), err:
1219     result, err_msg = FormatError(err)
1220     logging.exception("Error during command processing")
1221     ToStderr(err_msg)
1222
1223   return result
1224
1225
1226 def GenerateTable(headers, fields, separator, data,
1227                   numfields=None, unitfields=None,
1228                   units=None):
1229   """Prints a table with headers and different fields.
1230
1231   @type headers: dict
1232   @param headers: dictionary mapping field names to headers for
1233       the table
1234   @type fields: list
1235   @param fields: the field names corresponding to each row in
1236       the data field
1237   @param separator: the separator to be used; if this is None,
1238       the default 'smart' algorithm is used which computes optimal
1239       field width, otherwise just the separator is used between
1240       each field
1241   @type data: list
1242   @param data: a list of lists, each sublist being one row to be output
1243   @type numfields: list
1244   @param numfields: a list with the fields that hold numeric
1245       values and thus should be right-aligned
1246   @type unitfields: list
1247   @param unitfields: a list with the fields that hold numeric
1248       values that should be formatted with the units field
1249   @type units: string or None
1250   @param units: the units we should use for formatting, or None for
1251       automatic choice (human-readable for non-separator usage, otherwise
1252       megabytes); this is a one-letter string
1253
1254   """
1255   if units is None:
1256     if separator:
1257       units = "m"
1258     else:
1259       units = "h"
1260
1261   if numfields is None:
1262     numfields = []
1263   if unitfields is None:
1264     unitfields = []
1265
1266   numfields = utils.FieldSet(*numfields)
1267   unitfields = utils.FieldSet(*unitfields)
1268
1269   format_fields = []
1270   for field in fields:
1271     if headers and field not in headers:
1272       # TODO: handle better unknown fields (either revert to old
1273       # style of raising exception, or deal more intelligently with
1274       # variable fields)
1275       headers[field] = field
1276     if separator is not None:
1277       format_fields.append("%s")
1278     elif numfields.Matches(field):
1279       format_fields.append("%*s")
1280     else:
1281       format_fields.append("%-*s")
1282
1283   if separator is None:
1284     mlens = [0 for name in fields]
1285     format = ' '.join(format_fields)
1286   else:
1287     format = separator.replace("%", "%%").join(format_fields)
1288
1289   for row in data:
1290     if row is None:
1291       continue
1292     for idx, val in enumerate(row):
1293       if unitfields.Matches(fields[idx]):
1294         try:
1295           val = int(val)
1296         except ValueError:
1297           pass
1298         else:
1299           val = row[idx] = utils.FormatUnit(val, units)
1300       val = row[idx] = str(val)
1301       if separator is None:
1302         mlens[idx] = max(mlens[idx], len(val))
1303
1304   result = []
1305   if headers:
1306     args = []
1307     for idx, name in enumerate(fields):
1308       hdr = headers[name]
1309       if separator is None:
1310         mlens[idx] = max(mlens[idx], len(hdr))
1311         args.append(mlens[idx])
1312       args.append(hdr)
1313     result.append(format % tuple(args))
1314
1315   for line in data:
1316     args = []
1317     if line is None:
1318       line = ['-' for _ in fields]
1319     for idx in xrange(len(fields)):
1320       if separator is None:
1321         args.append(mlens[idx])
1322       args.append(line[idx])
1323     result.append(format % tuple(args))
1324
1325   return result
1326
1327
1328 def FormatTimestamp(ts):
1329   """Formats a given timestamp.
1330
1331   @type ts: timestamp
1332   @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1333
1334   @rtype: string
1335   @return: a string with the formatted timestamp
1336
1337   """
1338   if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1339     return '?'
1340   sec, usec = ts
1341   return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1342
1343
1344 def ParseTimespec(value):
1345   """Parse a time specification.
1346
1347   The following suffixed will be recognized:
1348
1349     - s: seconds
1350     - m: minutes
1351     - h: hours
1352     - d: day
1353     - w: weeks
1354
1355   Without any suffix, the value will be taken to be in seconds.
1356
1357   """
1358   value = str(value)
1359   if not value:
1360     raise errors.OpPrereqError("Empty time specification passed")
1361   suffix_map = {
1362     's': 1,
1363     'm': 60,
1364     'h': 3600,
1365     'd': 86400,
1366     'w': 604800,
1367     }
1368   if value[-1] not in suffix_map:
1369     try:
1370       value = int(value)
1371     except ValueError:
1372       raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1373   else:
1374     multiplier = suffix_map[value[-1]]
1375     value = value[:-1]
1376     if not value: # no data left after stripping the suffix
1377       raise errors.OpPrereqError("Invalid time specification (only"
1378                                  " suffix passed)")
1379     try:
1380       value = int(value) * multiplier
1381     except ValueError:
1382       raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1383   return value
1384
1385
1386 def GetOnlineNodes(nodes, cl=None, nowarn=False):
1387   """Returns the names of online nodes.
1388
1389   This function will also log a warning on stderr with the names of
1390   the online nodes.
1391
1392   @param nodes: if not empty, use only this subset of nodes (minus the
1393       offline ones)
1394   @param cl: if not None, luxi client to use
1395   @type nowarn: boolean
1396   @param nowarn: by default, this function will output a note with the
1397       offline nodes that are skipped; if this parameter is True the
1398       note is not displayed
1399
1400   """
1401   if cl is None:
1402     cl = GetClient()
1403
1404   result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1405                          use_locking=False)
1406   offline = [row[0] for row in result if row[1]]
1407   if offline and not nowarn:
1408     ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1409   return [row[0] for row in result if not row[1]]
1410
1411
1412 def _ToStream(stream, txt, *args):
1413   """Write a message to a stream, bypassing the logging system
1414
1415   @type stream: file object
1416   @param stream: the file to which we should write
1417   @type txt: str
1418   @param txt: the message
1419
1420   """
1421   if args:
1422     args = tuple(args)
1423     stream.write(txt % args)
1424   else:
1425     stream.write(txt)
1426   stream.write('\n')
1427   stream.flush()
1428
1429
1430 def ToStdout(txt, *args):
1431   """Write a message to stdout only, bypassing the logging system
1432
1433   This is just a wrapper over _ToStream.
1434
1435   @type txt: str
1436   @param txt: the message
1437
1438   """
1439   _ToStream(sys.stdout, txt, *args)
1440
1441
1442 def ToStderr(txt, *args):
1443   """Write a message to stderr only, bypassing the logging system
1444
1445   This is just a wrapper over _ToStream.
1446
1447   @type txt: str
1448   @param txt: the message
1449
1450   """
1451   _ToStream(sys.stderr, txt, *args)
1452
1453
1454 class JobExecutor(object):
1455   """Class which manages the submission and execution of multiple jobs.
1456
1457   Note that instances of this class should not be reused between
1458   GetResults() calls.
1459
1460   """
1461   def __init__(self, cl=None, verbose=True):
1462     self.queue = []
1463     if cl is None:
1464       cl = GetClient()
1465     self.cl = cl
1466     self.verbose = verbose
1467     self.jobs = []
1468
1469   def QueueJob(self, name, *ops):
1470     """Record a job for later submit.
1471
1472     @type name: string
1473     @param name: a description of the job, will be used in WaitJobSet
1474     """
1475     self.queue.append((name, ops))
1476
1477   def SubmitPending(self):
1478     """Submit all pending jobs.
1479
1480     """
1481     results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1482     for ((status, data), (name, _)) in zip(results, self.queue):
1483       self.jobs.append((status, data, name))
1484
1485   def GetResults(self):
1486     """Wait for and return the results of all jobs.
1487
1488     @rtype: list
1489     @return: list of tuples (success, job results), in the same order
1490         as the submitted jobs; if a job has failed, instead of the result
1491         there will be the error message
1492
1493     """
1494     if not self.jobs:
1495       self.SubmitPending()
1496     results = []
1497     if self.verbose:
1498       ok_jobs = [row[1] for row in self.jobs if row[0]]
1499       if ok_jobs:
1500         ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1501     for submit_status, jid, name in self.jobs:
1502       if not submit_status:
1503         ToStderr("Failed to submit job for %s: %s", name, jid)
1504         results.append((False, jid))
1505         continue
1506       if self.verbose:
1507         ToStdout("Waiting for job %s for %s...", jid, name)
1508       try:
1509         job_result = PollJob(jid, cl=self.cl)
1510         success = True
1511       except (errors.GenericError, luxi.ProtocolError), err:
1512         _, job_result = FormatError(err)
1513         success = False
1514         # the error message will always be shown, verbose or not
1515         ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1516
1517       results.append((success, job_result))
1518     return results
1519
1520   def WaitOrShow(self, wait):
1521     """Wait for job results or only print the job IDs.
1522
1523     @type wait: boolean
1524     @param wait: whether to wait or not
1525
1526     """
1527     if wait:
1528       return self.GetResults()
1529     else:
1530       if not self.jobs:
1531         self.SubmitPending()
1532       for status, result, name in self.jobs:
1533         if status:
1534           ToStdout("%s: %s", result, name)
1535         else:
1536           ToStderr("Failure for %s: %s", name, result)