4 # Copyright (C) 2006, 2007 Google Inc.
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.
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.
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
22 """Module dealing with command line parsing"""
30 from cStringIO import StringIO
32 from ganeti import utils
33 from ganeti import errors
34 from ganeti import constants
35 from ganeti import opcodes
36 from ganeti import luxi
37 from ganeti import ssconf
38 from ganeti import rpc
40 from optparse import (OptionParser, TitledHelpFormatter,
41 Option, OptionValueError)
45 # Command line options
64 "FILESTORE_DRIVER_OPT",
73 "IGNORE_FAILURES_OPT",
74 "IGNORE_REMOVE_FAILURES_OPT",
75 "IGNORE_SECONDARIES_OPT",
89 "NOMODIFY_ETCHOSTS_OPT",
90 "NOMODIFY_SSH_SETUP_OPT",
106 "REMOVE_INSTANCE_OPT",
111 "SHUTDOWN_TIMEOUT_OPT",
124 # Generic functions for CLI programs
126 "GenericInstanceCreate",
130 "JobSubmittedException",
135 # Formatting functions
136 "ToStderr", "ToStdout",
145 # command line options support infrastructure
146 "ARGS_MANY_INSTANCES",
160 "OPT_COMPL_INST_ADD_NODES",
161 "OPT_COMPL_MANY_NODES",
162 "OPT_COMPL_ONE_IALLOCATOR",
163 "OPT_COMPL_ONE_INSTANCE",
164 "OPT_COMPL_ONE_NODE",
176 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
181 return ("<%s min=%s max=%s>" %
182 (self.__class__.__name__, self.min, self.max))
185 class ArgSuggest(_Argument):
186 """Suggesting argument.
188 Value can be any of the ones passed to the constructor.
191 # pylint: disable-msg=W0622
192 def __init__(self, min=0, max=None, choices=None):
193 _Argument.__init__(self, min=min, max=max)
194 self.choices = choices
197 return ("<%s min=%s max=%s choices=%r>" %
198 (self.__class__.__name__, self.min, self.max, self.choices))
201 class ArgChoice(ArgSuggest):
204 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
205 but value must be one of the choices.
210 class ArgUnknown(_Argument):
211 """Unknown argument to program (e.g. determined at runtime).
216 class ArgInstance(_Argument):
217 """Instances argument.
222 class ArgNode(_Argument):
227 class ArgJobId(_Argument):
233 class ArgFile(_Argument):
234 """File path argument.
239 class ArgCommand(_Argument):
245 class ArgHost(_Argument):
252 ARGS_MANY_INSTANCES = [ArgInstance()]
253 ARGS_MANY_NODES = [ArgNode()]
254 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
255 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
258 def _ExtractTagsObject(opts, args):
259 """Extract the tag type object.
261 Note that this function will modify its args parameter.
264 if not hasattr(opts, "tag_type"):
265 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
267 if kind == constants.TAG_CLUSTER:
269 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
271 raise errors.OpPrereqError("no arguments passed to the command")
275 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
279 def _ExtendTags(opts, args):
280 """Extend the args if a source file has been given.
282 This function will extend the tags with the contents of the file
283 passed in the 'tags_source' attribute of the opts parameter. A file
284 named '-' will be replaced by stdin.
287 fname = opts.tags_source
293 new_fh = open(fname, "r")
296 # we don't use the nice 'new_data = [line.strip() for line in fh]'
297 # because of python bug 1633941
299 line = new_fh.readline()
302 new_data.append(line.strip())
305 args.extend(new_data)
308 def ListTags(opts, args):
309 """List the tags on a given object.
311 This is a generic implementation that knows how to deal with all
312 three cases of tag objects (cluster, node, instance). The opts
313 argument is expected to contain a tag_type field denoting what
314 object type we work on.
317 kind, name = _ExtractTagsObject(opts, args)
319 result = cl.QueryTags(kind, name)
320 result = list(result)
326 def AddTags(opts, args):
327 """Add tags on a given object.
329 This is a generic implementation that knows how to deal with all
330 three cases of tag objects (cluster, node, instance). The opts
331 argument is expected to contain a tag_type field denoting what
332 object type we work on.
335 kind, name = _ExtractTagsObject(opts, args)
336 _ExtendTags(opts, args)
338 raise errors.OpPrereqError("No tags to be added")
339 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
343 def RemoveTags(opts, args):
344 """Remove tags from a given object.
346 This is a generic implementation that knows how to deal with all
347 three cases of tag objects (cluster, node, instance). The opts
348 argument is expected to contain a tag_type field denoting what
349 object type we work on.
352 kind, name = _ExtractTagsObject(opts, args)
353 _ExtendTags(opts, args)
355 raise errors.OpPrereqError("No tags to be removed")
356 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
360 def check_unit(option, opt, value): # pylint: disable-msg=W0613
361 """OptParsers custom converter for units.
365 return utils.ParseUnit(value)
366 except errors.UnitParseError, err:
367 raise OptionValueError("option %s: %s" % (opt, err))
370 def _SplitKeyVal(opt, data):
371 """Convert a KeyVal string into a dict.
373 This function will convert a key=val[,...] string into a dict. Empty
374 values will be converted specially: keys which have the prefix 'no_'
375 will have the value=False and the prefix stripped, the others will
379 @param opt: a string holding the option name for which we process the
380 data, used in building error messages
382 @param data: a string of the format key=val,key=val,...
384 @return: {key=val, key=val}
385 @raises errors.ParameterError: if there are duplicate keys
390 for elem in utils.UnescapeAndSplit(data, sep=","):
392 key, val = elem.split("=", 1)
394 if elem.startswith(NO_PREFIX):
395 key, val = elem[len(NO_PREFIX):], False
396 elif elem.startswith(UN_PREFIX):
397 key, val = elem[len(UN_PREFIX):], None
399 key, val = elem, True
401 raise errors.ParameterError("Duplicate key '%s' in option %s" %
407 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
408 """Custom parser for ident:key=val,key=val options.
410 This will store the parsed values as a tuple (ident, {key: val}). As such,
411 multiple uses of this option via action=append is possible.
415 ident, rest = value, ''
417 ident, rest = value.split(":", 1)
419 if ident.startswith(NO_PREFIX):
421 msg = "Cannot pass options when removing parameter groups: %s" % value
422 raise errors.ParameterError(msg)
423 retval = (ident[len(NO_PREFIX):], False)
424 elif ident.startswith(UN_PREFIX):
426 msg = "Cannot pass options when removing parameter groups: %s" % value
427 raise errors.ParameterError(msg)
428 retval = (ident[len(UN_PREFIX):], None)
430 kv_dict = _SplitKeyVal(opt, rest)
431 retval = (ident, kv_dict)
435 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
436 """Custom parser class for key=val,key=val options.
438 This will store the parsed values as a dict {key: val}.
441 return _SplitKeyVal(opt, value)
444 # completion_suggestion is normally a list. Using numeric values not evaluating
445 # to False for dynamic completion.
446 (OPT_COMPL_MANY_NODES,
448 OPT_COMPL_ONE_INSTANCE,
450 OPT_COMPL_ONE_IALLOCATOR,
451 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
453 OPT_COMPL_ALL = frozenset([
454 OPT_COMPL_MANY_NODES,
456 OPT_COMPL_ONE_INSTANCE,
458 OPT_COMPL_ONE_IALLOCATOR,
459 OPT_COMPL_INST_ADD_NODES,
463 class CliOption(Option):
464 """Custom option class for optparse.
467 ATTRS = Option.ATTRS + [
468 "completion_suggest",
470 TYPES = Option.TYPES + (
475 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
476 TYPE_CHECKER["identkeyval"] = check_ident_key_val
477 TYPE_CHECKER["keyval"] = check_key_val
478 TYPE_CHECKER["unit"] = check_unit
481 # optparse.py sets make_option, so we do it for our own option class, too
482 cli_option = CliOption
485 _YESNO = ("yes", "no")
488 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
489 help="Increase debugging level")
491 NOHDR_OPT = cli_option("--no-headers", default=False,
492 action="store_true", dest="no_headers",
493 help="Don't display column headers")
495 SEP_OPT = cli_option("--separator", default=None,
496 action="store", dest="separator",
497 help=("Separator between output fields"
498 " (defaults to one space)"))
500 USEUNITS_OPT = cli_option("--units", default=None,
501 dest="units", choices=('h', 'm', 'g', 't'),
502 help="Specify units for output (one of hmgt)")
504 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
505 type="string", metavar="FIELDS",
506 help="Comma separated list of output fields")
508 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
509 default=False, help="Force the operation")
511 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
512 default=False, help="Do not require confirmation")
514 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
515 default=None, help="File with tag names")
517 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
518 default=False, action="store_true",
519 help=("Submit the job and return the job ID, but"
520 " don't wait for the job to finish"))
522 SYNC_OPT = cli_option("--sync", dest="do_locking",
523 default=False, action="store_true",
524 help=("Grab locks while doing the queries"
525 " in order to ensure more consistent results"))
527 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
529 help=("Do not execute the operation, just run the"
530 " check steps and verify it it could be"
533 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
535 help="Increase the verbosity of the operation")
537 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
538 action="store_true", dest="simulate_errors",
539 help="Debugging option that makes the operation"
540 " treat most runtime checks as failed")
542 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
543 default=True, action="store_false",
544 help="Don't wait for sync (DANGEROUS!)")
546 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
547 help="Custom disk setup (diskless, file,"
549 default=None, metavar="TEMPL",
550 choices=list(constants.DISK_TEMPLATES))
552 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
553 help="Do not create any network cards for"
556 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
557 help="Relative path under default cluster-wide"
558 " file storage dir to store file-based disks",
559 default=None, metavar="<DIR>")
561 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
562 help="Driver to use for image files",
563 default="loop", metavar="<DRIVER>",
564 choices=list(constants.FILE_DRIVER))
566 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
567 help="Select nodes for the instance automatically"
568 " using the <NAME> iallocator plugin",
569 default=None, type="string",
570 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
572 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
574 completion_suggest=OPT_COMPL_ONE_OS)
576 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
577 action="store_true", default=False,
578 help="Force an unknown variant")
580 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
581 type="keyval", default={},
582 help="Backend parameters")
584 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
585 default={}, dest="hvparams",
586 help="Hypervisor parameters")
588 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
589 help="Hypervisor and hypervisor options, in the"
590 " format hypervisor:option=value,option=value,...",
591 default=None, type="identkeyval")
593 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
594 help="Hypervisor and hypervisor options, in the"
595 " format hypervisor:option=value,option=value,...",
596 default=[], action="append", type="identkeyval")
598 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
599 action="store_false",
600 help="Don't check that the instance's IP"
603 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
604 default=True, action="store_false",
605 help="Don't check that the instance's name"
608 NET_OPT = cli_option("--net",
609 help="NIC parameters", default=[],
610 dest="nics", action="append", type="identkeyval")
612 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
613 dest="disks", action="append", type="identkeyval")
615 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
616 help="Comma-separated list of disks"
617 " indices to act on (e.g. 0,2) (optional,"
618 " defaults to all disks)")
620 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
621 help="Enforces a single-disk configuration using the"
622 " given disk size, in MiB unless a suffix is used",
623 default=None, type="unit", metavar="<size>")
625 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
626 dest="ignore_consistency",
627 action="store_true", default=False,
628 help="Ignore the consistency of the disks on"
631 NONLIVE_OPT = cli_option("--non-live", dest="live",
632 default=True, action="store_false",
633 help="Do a non-live migration (this usually means"
634 " freeze the instance, save the state, transfer and"
635 " only then resume running on the secondary node)")
637 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
638 help="Target node and optional secondary node",
639 metavar="<pnode>[:<snode>]",
640 completion_suggest=OPT_COMPL_INST_ADD_NODES)
642 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
643 action="append", metavar="<node>",
644 help="Use only this node (can be used multiple"
645 " times, if not given defaults to all nodes)",
646 completion_suggest=OPT_COMPL_ONE_NODE)
648 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
650 completion_suggest=OPT_COMPL_ONE_NODE)
652 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
653 action="store_false",
654 help="Don't start the instance after creation")
656 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
657 action="store_true", default=False,
658 help="Show command instead of executing it")
660 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
661 default=False, action="store_true",
662 help="Instead of performing the migration, try to"
663 " recover from a failed cleanup. This is safe"
664 " to run even if the instance is healthy, but it"
665 " will create extra replication traffic and "
666 " disrupt briefly the replication (like during the"
669 STATIC_OPT = cli_option("-s", "--static", dest="static",
670 action="store_true", default=False,
671 help="Only show configuration data, not runtime data")
673 ALL_OPT = cli_option("--all", dest="show_all",
674 default=False, action="store_true",
675 help="Show info on all instances on the cluster."
676 " This can take a long time to run, use wisely")
678 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
679 action="store_true", default=False,
680 help="Interactive OS reinstall, lists available"
681 " OS templates for selection")
683 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
684 action="store_true", default=False,
685 help="Remove the instance from the cluster"
686 " configuration even if there are failures"
687 " during the removal process")
689 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
690 dest="ignore_remove_failures",
691 action="store_true", default=False,
692 help="Remove the instance from the"
693 " cluster configuration even if there"
694 " are failures during the removal"
697 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
698 action="store_true", default=False,
699 help="Remove the instance from the cluster")
701 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
702 help="Specifies the new secondary node",
703 metavar="NODE", default=None,
704 completion_suggest=OPT_COMPL_ONE_NODE)
706 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
707 default=False, action="store_true",
708 help="Replace the disk(s) on the primary"
709 " node (only for the drbd template)")
711 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
712 default=False, action="store_true",
713 help="Replace the disk(s) on the secondary"
714 " node (only for the drbd template)")
716 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
717 default=False, action="store_true",
718 help="Automatically replace faulty disks"
719 " (only for the drbd template)")
721 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
722 default=False, action="store_true",
723 help="Ignore current recorded size"
724 " (useful for forcing activation when"
725 " the recorded size is wrong)")
727 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
729 completion_suggest=OPT_COMPL_ONE_NODE)
731 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
734 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
735 help="Specify the secondary ip for the node",
736 metavar="ADDRESS", default=None)
738 READD_OPT = cli_option("--readd", dest="readd",
739 default=False, action="store_true",
740 help="Readd old node after replacing it")
742 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
743 default=True, action="store_false",
744 help="Disable SSH key fingerprint checking")
747 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
748 choices=_YESNO, default=None, metavar=_YORNO,
749 help="Set the master_candidate flag on the node")
751 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
752 choices=_YESNO, default=None,
753 help="Set the offline flag on the node")
755 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
756 choices=_YESNO, default=None,
757 help="Set the drained flag on the node")
759 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
760 choices=_YESNO, default=None, metavar=_YORNO,
761 help="Set the allocatable flag on a volume")
763 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
764 help="Disable support for lvm based instances"
766 action="store_false", default=True)
768 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
769 dest="enabled_hypervisors",
770 help="Comma-separated list of hypervisors",
771 type="string", default=None)
773 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
774 type="keyval", default={},
775 help="NIC parameters")
777 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
778 dest="candidate_pool_size", type="int",
779 help="Set the candidate pool size")
781 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
782 help="Enables LVM and specifies the volume group"
783 " name (cluster-wide) for disk allocation [xenvg]",
784 metavar="VG", default=None)
786 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
787 help="Destroy cluster", action="store_true")
789 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
790 help="Skip node agreement check (dangerous)",
791 action="store_true", default=False)
793 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
794 help="Specify the mac prefix for the instance IP"
795 " addresses, in the format XX:XX:XX",
799 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
800 help="Specify the node interface (cluster-wide)"
801 " on which the master IP address will be added "
802 " [%s]" % constants.DEFAULT_BRIDGE,
804 default=constants.DEFAULT_BRIDGE)
807 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
808 help="Specify the default directory (cluster-"
809 "wide) for storing the file-based disks [%s]" %
810 constants.DEFAULT_FILE_STORAGE_DIR,
812 default=constants.DEFAULT_FILE_STORAGE_DIR)
814 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
815 help="Don't modify /etc/hosts",
816 action="store_false", default=True)
818 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
819 help="Don't initialize SSH keys",
820 action="store_false", default=True)
822 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
823 help="Enable parseable error messages",
824 action="store_true", default=False)
826 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
827 help="Skip N+1 memory redundancy tests",
828 action="store_true", default=False)
830 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
831 help="Type of reboot: soft/hard/full",
832 default=constants.INSTANCE_REBOOT_HARD,
834 choices=list(constants.REBOOT_TYPES))
836 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
837 dest="ignore_secondaries",
838 default=False, action="store_true",
839 help="Ignore errors from secondaries")
841 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
842 action="store_false", default=True,
843 help="Don't shutdown the instance (unsafe)")
845 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
846 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
847 help="Maximum time to wait")
849 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
850 dest="shutdown_timeout", type="int",
851 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
852 help="Maximum time to wait for instance shutdown")
854 EARLY_RELEASE_OPT = cli_option("--early-release",
855 dest="early_release", default=False,
857 help="Release the locks on the secondary"
861 def _ParseArgs(argv, commands, aliases):
862 """Parser for the command line arguments.
864 This function parses the arguments and returns the function which
865 must be executed together with its (modified) arguments.
867 @param argv: the command line
868 @param commands: dictionary with special contents, see the design
869 doc for cmdline handling
870 @param aliases: dictionary with command aliases {'alias': 'target, ...}
876 binary = argv[0].split("/")[-1]
878 if len(argv) > 1 and argv[1] == "--version":
879 ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
880 # Quit right away. That way we don't have to care about this special
881 # argument. optparse.py does it the same.
884 if len(argv) < 2 or not (argv[1] in commands or
886 # let's do a nice thing
887 sortedcmds = commands.keys()
890 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
891 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
894 # compute the max line length for cmd + usage
895 mlen = max([len(" %s" % cmd) for cmd in commands])
896 mlen = min(60, mlen) # should not get here...
898 # and format a nice command list
899 ToStdout("Commands:")
900 for cmd in sortedcmds:
901 cmdstr = " %s" % (cmd,)
902 help_text = commands[cmd][4]
903 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
904 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
905 for line in help_lines:
906 ToStdout("%-*s %s", mlen, "", line)
910 return None, None, None
912 # get command, unalias it, and look it up in commands
916 raise errors.ProgrammerError("Alias '%s' overrides an existing"
919 if aliases[cmd] not in commands:
920 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
921 " command '%s'" % (cmd, aliases[cmd]))
925 func, args_def, parser_opts, usage, description = commands[cmd]
926 parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
927 description=description,
928 formatter=TitledHelpFormatter(),
929 usage="%%prog %s %s" % (cmd, usage))
930 parser.disable_interspersed_args()
931 options, args = parser.parse_args()
933 if not _CheckArguments(cmd, args_def, args):
934 return None, None, None
936 return func, options, args
939 def _CheckArguments(cmd, args_def, args):
940 """Verifies the arguments using the argument definition.
944 1. Abort with error if values specified by user but none expected.
946 1. For each argument in definition
948 1. Keep running count of minimum number of values (min_count)
949 1. Keep running count of maximum number of values (max_count)
950 1. If it has an unlimited number of values
952 1. Abort with error if it's not the last argument in the definition
954 1. If last argument has limited number of values
956 1. Abort with error if number of values doesn't match or is too large
958 1. Abort with error if user didn't pass enough values (min_count)
961 if args and not args_def:
962 ToStderr("Error: Command %s expects no arguments", cmd)
969 last_idx = len(args_def) - 1
971 for idx, arg in enumerate(args_def):
972 if min_count is None:
974 elif arg.min is not None:
977 if max_count is None:
979 elif arg.max is not None:
983 check_max = (arg.max is not None)
985 elif arg.max is None:
986 raise errors.ProgrammerError("Only the last argument can have max=None")
989 # Command with exact number of arguments
990 if (min_count is not None and max_count is not None and
991 min_count == max_count and len(args) != min_count):
992 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
995 # Command with limited number of arguments
996 if max_count is not None and len(args) > max_count:
997 ToStderr("Error: Command %s expects only %d argument(s)",
1001 # Command with some required arguments
1002 if min_count is not None and len(args) < min_count:
1003 ToStderr("Error: Command %s expects at least %d argument(s)",
1010 def SplitNodeOption(value):
1011 """Splits the value of a --node option.
1014 if value and ':' in value:
1015 return value.split(':', 1)
1017 return (value, None)
1020 def CalculateOSNames(os_name, os_variants):
1021 """Calculates all the names an OS can be called, according to its variants.
1023 @type os_name: string
1024 @param os_name: base name of the os
1025 @type os_variants: list or None
1026 @param os_variants: list of supported variants
1028 @return: list of valid names
1032 return ['%s+%s' % (os_name, v) for v in os_variants]
1038 def wrapper(*args, **kwargs):
1041 return fn(*args, **kwargs)
1047 def AskUser(text, choices=None):
1048 """Ask the user a question.
1050 @param text: the question to ask
1052 @param choices: list with elements tuples (input_char, return_value,
1053 description); if not given, it will default to: [('y', True,
1054 'Perform the operation'), ('n', False, 'Do no do the operation')];
1055 note that the '?' char is reserved for help
1057 @return: one of the return values from the choices list; if input is
1058 not possible (i.e. not running with a tty, we return the last
1063 choices = [('y', True, 'Perform the operation'),
1064 ('n', False, 'Do not perform the operation')]
1065 if not choices or not isinstance(choices, list):
1066 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1067 for entry in choices:
1068 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1069 raise errors.ProgrammerError("Invalid choices element to AskUser")
1071 answer = choices[-1][1]
1073 for line in text.splitlines():
1074 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1075 text = "\n".join(new_text)
1077 f = file("/dev/tty", "a+")
1081 chars = [entry[0] for entry in choices]
1082 chars[-1] = "[%s]" % chars[-1]
1084 maps = dict([(entry[0], entry[1]) for entry in choices])
1088 f.write("/".join(chars))
1090 line = f.readline(2).strip().lower()
1095 for entry in choices:
1096 f.write(" %s - %s\n" % (entry[0], entry[2]))
1104 class JobSubmittedException(Exception):
1105 """Job was submitted, client should exit.
1107 This exception has one argument, the ID of the job that was
1108 submitted. The handler should print this ID.
1110 This is not an error, just a structured way to exit from clients.
1115 def SendJob(ops, cl=None):
1116 """Function to submit an opcode without waiting for the results.
1119 @param ops: list of opcodes
1120 @type cl: luxi.Client
1121 @param cl: the luxi client to use for communicating with the master;
1122 if None, a new client will be created
1128 job_id = cl.SubmitJob(ops)
1133 def PollJob(job_id, cl=None, feedback_fn=None):
1134 """Function to poll for the result of a job.
1136 @type job_id: job identified
1137 @param job_id: the job to poll for results
1138 @type cl: luxi.Client
1139 @param cl: the luxi client to use for communicating with the master;
1140 if None, a new client will be created
1146 prev_job_info = None
1147 prev_logmsg_serial = None
1150 result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1153 # job not found, go away!
1154 raise errors.JobLost("Job with id %s lost" % job_id)
1156 # Split result, a tuple of (field values, log entries)
1157 (job_info, log_entries) = result
1158 (status, ) = job_info
1161 for log_entry in log_entries:
1162 (serial, timestamp, _, message) = log_entry
1163 if callable(feedback_fn):
1164 feedback_fn(log_entry[1:])
1166 encoded = utils.SafeEncode(message)
1167 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1168 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1170 # TODO: Handle canceled and archived jobs
1171 elif status in (constants.JOB_STATUS_SUCCESS,
1172 constants.JOB_STATUS_ERROR,
1173 constants.JOB_STATUS_CANCELING,
1174 constants.JOB_STATUS_CANCELED):
1177 prev_job_info = job_info
1179 jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1181 raise errors.JobLost("Job with id %s lost" % job_id)
1183 status, opstatus, result = jobs[0]
1184 if status == constants.JOB_STATUS_SUCCESS:
1186 elif status in (constants.JOB_STATUS_CANCELING,
1187 constants.JOB_STATUS_CANCELED):
1188 raise errors.OpExecError("Job was canceled")
1191 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1192 if status == constants.OP_STATUS_SUCCESS:
1194 elif status == constants.OP_STATUS_ERROR:
1195 errors.MaybeRaise(msg)
1197 raise errors.OpExecError("partial failure (opcode %d): %s" %
1200 raise errors.OpExecError(str(msg))
1201 # default failure mode
1202 raise errors.OpExecError(result)
1205 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
1206 """Legacy function to submit an opcode.
1208 This is just a simple wrapper over the construction of the processor
1209 instance. It should be extended to better handle feedback and
1210 interaction functions.
1216 SetGenericOpcodeOpts([op], opts)
1218 job_id = SendJob([op], cl)
1220 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1222 return op_results[0]
1225 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1226 """Wrapper around SubmitOpCode or SendJob.
1228 This function will decide, based on the 'opts' parameter, whether to
1229 submit and wait for the result of the opcode (and return it), or
1230 whether to just send the job and print its identifier. It is used in
1231 order to simplify the implementation of the '--submit' option.
1233 It will also process the opcodes if we're sending the via SendJob
1234 (otherwise SubmitOpCode does it).
1237 if opts and opts.submit_only:
1239 SetGenericOpcodeOpts(job, opts)
1240 job_id = SendJob(job, cl=cl)
1241 raise JobSubmittedException(job_id)
1243 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1246 def SetGenericOpcodeOpts(opcode_list, options):
1247 """Processor for generic options.
1249 This function updates the given opcodes based on generic command
1250 line options (like debug, dry-run, etc.).
1252 @param opcode_list: list of opcodes
1253 @param options: command line options or None
1254 @return: None (in-place modification)
1259 for op in opcode_list:
1260 op.dry_run = options.dry_run
1261 op.debug_level = options.debug
1265 # TODO: Cache object?
1267 client = luxi.Client()
1268 except luxi.NoMasterError:
1269 ss = ssconf.SimpleStore()
1271 # Try to read ssconf file
1274 except errors.ConfigurationError:
1275 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1276 " not part of a cluster")
1278 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1279 if master != myself:
1280 raise errors.OpPrereqError("This is not the master node, please connect"
1281 " to node '%s' and rerun the command" %
1287 def FormatError(err):
1288 """Return a formatted error message for a given error.
1290 This function takes an exception instance and returns a tuple
1291 consisting of two values: first, the recommended exit code, and
1292 second, a string describing the error message (not
1293 newline-terminated).
1299 if isinstance(err, errors.ConfigurationError):
1300 txt = "Corrupt configuration file: %s" % msg
1302 obuf.write(txt + "\n")
1303 obuf.write("Aborting.")
1305 elif isinstance(err, errors.HooksAbort):
1306 obuf.write("Failure: hooks execution failed:\n")
1307 for node, script, out in err.args[0]:
1309 obuf.write(" node: %s, script: %s, output: %s\n" %
1310 (node, script, out))
1312 obuf.write(" node: %s, script: %s (no output)\n" %
1314 elif isinstance(err, errors.HooksFailure):
1315 obuf.write("Failure: hooks general failure: %s" % msg)
1316 elif isinstance(err, errors.ResolverError):
1317 this_host = utils.HostInfo.SysName()
1318 if err.args[0] == this_host:
1319 msg = "Failure: can't resolve my own hostname ('%s')"
1321 msg = "Failure: can't resolve hostname '%s'"
1322 obuf.write(msg % err.args[0])
1323 elif isinstance(err, errors.OpPrereqError):
1324 if len(err.args) == 2:
1325 obuf.write("Failure: prerequisites not met for this"
1326 " operation:\nerror type: %s, error details:\n%s" %
1327 (err.args[1], err.args[0]))
1329 obuf.write("Failure: prerequisites not met for this"
1330 " operation:\n%s" % msg)
1331 elif isinstance(err, errors.OpExecError):
1332 obuf.write("Failure: command execution error:\n%s" % msg)
1333 elif isinstance(err, errors.TagError):
1334 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1335 elif isinstance(err, errors.JobQueueDrainError):
1336 obuf.write("Failure: the job queue is marked for drain and doesn't"
1337 " accept new requests\n")
1338 elif isinstance(err, errors.JobQueueFull):
1339 obuf.write("Failure: the job queue is full and doesn't accept new"
1340 " job submissions until old jobs are archived\n")
1341 elif isinstance(err, errors.TypeEnforcementError):
1342 obuf.write("Parameter Error: %s" % msg)
1343 elif isinstance(err, errors.ParameterError):
1344 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1345 elif isinstance(err, luxi.NoMasterError):
1346 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1347 " and listening for connections?")
1348 elif isinstance(err, luxi.TimeoutError):
1349 obuf.write("Timeout while talking to the master daemon. Error:\n"
1351 elif isinstance(err, luxi.ProtocolError):
1352 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1354 elif isinstance(err, errors.GenericError):
1355 obuf.write("Unhandled Ganeti error: %s" % msg)
1356 elif isinstance(err, JobSubmittedException):
1357 obuf.write("JobID: %s\n" % err.args[0])
1360 obuf.write("Unhandled exception: %s" % msg)
1361 return retcode, obuf.getvalue().rstrip('\n')
1364 def GenericMain(commands, override=None, aliases=None):
1365 """Generic main function for all the gnt-* commands.
1368 - commands: a dictionary with a special structure, see the design doc
1369 for command line handling.
1370 - override: if not None, we expect a dictionary with keys that will
1371 override command line options; this can be used to pass
1372 options from the scripts to generic functions
1373 - aliases: dictionary with command aliases {'alias': 'target, ...}
1376 # save the program name and the entire command line for later logging
1378 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1379 if len(sys.argv) >= 2:
1380 binary += " " + sys.argv[1]
1381 old_cmdline = " ".join(sys.argv[2:])
1385 binary = "<unknown program>"
1392 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1393 except errors.ParameterError, err:
1394 result, err_msg = FormatError(err)
1398 if func is None: # parse error
1401 if override is not None:
1402 for key, val in override.iteritems():
1403 setattr(options, key, val)
1405 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1406 stderr_logging=True, program=binary)
1409 logging.info("run with arguments '%s'", old_cmdline)
1411 logging.info("run with no arguments")
1414 result = func(options, args)
1415 except (errors.GenericError, luxi.ProtocolError,
1416 JobSubmittedException), err:
1417 result, err_msg = FormatError(err)
1418 logging.exception("Error during command processing")
1424 def GenericInstanceCreate(mode, opts, args):
1425 """Add an instance to the cluster via either creation or import.
1427 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1428 @param opts: the command line options selected by the user
1430 @param args: should contain only one element, the new instance name
1432 @return: the desired exit code
1437 (pnode, snode) = SplitNodeOption(opts.node)
1442 hypervisor, hvparams = opts.hypervisor
1446 nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1447 except ValueError, err:
1448 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1449 nics = [{}] * nic_max
1450 for nidx, ndict in opts.nics:
1452 if not isinstance(ndict, dict):
1453 msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1454 raise errors.OpPrereqError(msg)
1460 # default of one nic, all auto
1463 if opts.disk_template == constants.DT_DISKLESS:
1464 if opts.disks or opts.sd_size is not None:
1465 raise errors.OpPrereqError("Diskless instance but disk"
1466 " information passed")
1469 if not opts.disks and not opts.sd_size:
1470 raise errors.OpPrereqError("No disk information specified")
1471 if opts.disks and opts.sd_size is not None:
1472 raise errors.OpPrereqError("Please use either the '--disk' or"
1474 if opts.sd_size is not None:
1475 opts.disks = [(0, {"size": opts.sd_size})]
1477 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1478 except ValueError, err:
1479 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1480 disks = [{}] * disk_max
1481 for didx, ddict in opts.disks:
1483 if not isinstance(ddict, dict):
1484 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1485 raise errors.OpPrereqError(msg)
1486 elif "size" not in ddict:
1487 raise errors.OpPrereqError("Missing size for disk %d" % didx)
1489 ddict["size"] = utils.ParseUnit(ddict["size"])
1490 except ValueError, err:
1491 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1495 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1496 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1498 if mode == constants.INSTANCE_CREATE:
1503 elif mode == constants.INSTANCE_IMPORT:
1506 src_node = opts.src_node
1507 src_path = opts.src_dir
1509 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1511 op = opcodes.OpCreateInstance(instance_name=instance,
1513 disk_template=opts.disk_template,
1515 pnode=pnode, snode=snode,
1516 ip_check=opts.ip_check,
1517 name_check=opts.name_check,
1518 wait_for_sync=opts.wait_for_sync,
1519 file_storage_dir=opts.file_storage_dir,
1520 file_driver=opts.file_driver,
1521 iallocator=opts.iallocator,
1522 hypervisor=hypervisor,
1524 beparams=opts.beparams,
1531 SubmitOrSend(op, opts)
1535 def GenerateTable(headers, fields, separator, data,
1536 numfields=None, unitfields=None,
1538 """Prints a table with headers and different fields.
1541 @param headers: dictionary mapping field names to headers for
1544 @param fields: the field names corresponding to each row in
1546 @param separator: the separator to be used; if this is None,
1547 the default 'smart' algorithm is used which computes optimal
1548 field width, otherwise just the separator is used between
1551 @param data: a list of lists, each sublist being one row to be output
1552 @type numfields: list
1553 @param numfields: a list with the fields that hold numeric
1554 values and thus should be right-aligned
1555 @type unitfields: list
1556 @param unitfields: a list with the fields that hold numeric
1557 values that should be formatted with the units field
1558 @type units: string or None
1559 @param units: the units we should use for formatting, or None for
1560 automatic choice (human-readable for non-separator usage, otherwise
1561 megabytes); this is a one-letter string
1570 if numfields is None:
1572 if unitfields is None:
1575 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
1576 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1579 for field in fields:
1580 if headers and field not in headers:
1581 # TODO: handle better unknown fields (either revert to old
1582 # style of raising exception, or deal more intelligently with
1584 headers[field] = field
1585 if separator is not None:
1586 format_fields.append("%s")
1587 elif numfields.Matches(field):
1588 format_fields.append("%*s")
1590 format_fields.append("%-*s")
1592 if separator is None:
1593 mlens = [0 for name in fields]
1594 format = ' '.join(format_fields)
1596 format = separator.replace("%", "%%").join(format_fields)
1601 for idx, val in enumerate(row):
1602 if unitfields.Matches(fields[idx]):
1605 except (TypeError, ValueError):
1608 val = row[idx] = utils.FormatUnit(val, units)
1609 val = row[idx] = str(val)
1610 if separator is None:
1611 mlens[idx] = max(mlens[idx], len(val))
1616 for idx, name in enumerate(fields):
1618 if separator is None:
1619 mlens[idx] = max(mlens[idx], len(hdr))
1620 args.append(mlens[idx])
1622 result.append(format % tuple(args))
1624 if separator is None:
1625 assert len(mlens) == len(fields)
1627 if fields and not numfields.Matches(fields[-1]):
1633 line = ['-' for _ in fields]
1634 for idx in range(len(fields)):
1635 if separator is None:
1636 args.append(mlens[idx])
1637 args.append(line[idx])
1638 result.append(format % tuple(args))
1643 def FormatTimestamp(ts):
1644 """Formats a given timestamp.
1647 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1650 @return: a string with the formatted timestamp
1653 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1656 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1659 def ParseTimespec(value):
1660 """Parse a time specification.
1662 The following suffixed will be recognized:
1670 Without any suffix, the value will be taken to be in seconds.
1675 raise errors.OpPrereqError("Empty time specification passed")
1683 if value[-1] not in suffix_map:
1686 except (TypeError, ValueError):
1687 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1689 multiplier = suffix_map[value[-1]]
1691 if not value: # no data left after stripping the suffix
1692 raise errors.OpPrereqError("Invalid time specification (only"
1695 value = int(value) * multiplier
1696 except (TypeError, ValueError):
1697 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1701 def GetOnlineNodes(nodes, cl=None, nowarn=False):
1702 """Returns the names of online nodes.
1704 This function will also log a warning on stderr with the names of
1707 @param nodes: if not empty, use only this subset of nodes (minus the
1709 @param cl: if not None, luxi client to use
1710 @type nowarn: boolean
1711 @param nowarn: by default, this function will output a note with the
1712 offline nodes that are skipped; if this parameter is True the
1713 note is not displayed
1719 result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1721 offline = [row[0] for row in result if row[1]]
1722 if offline and not nowarn:
1723 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1724 return [row[0] for row in result if not row[1]]
1727 def _ToStream(stream, txt, *args):
1728 """Write a message to a stream, bypassing the logging system
1730 @type stream: file object
1731 @param stream: the file to which we should write
1733 @param txt: the message
1738 stream.write(txt % args)
1745 def ToStdout(txt, *args):
1746 """Write a message to stdout only, bypassing the logging system
1748 This is just a wrapper over _ToStream.
1751 @param txt: the message
1754 _ToStream(sys.stdout, txt, *args)
1757 def ToStderr(txt, *args):
1758 """Write a message to stderr only, bypassing the logging system
1760 This is just a wrapper over _ToStream.
1763 @param txt: the message
1766 _ToStream(sys.stderr, txt, *args)
1769 class JobExecutor(object):
1770 """Class which manages the submission and execution of multiple jobs.
1772 Note that instances of this class should not be reused between
1776 def __init__(self, cl=None, verbose=True, opts=None):
1781 self.verbose = verbose
1785 def QueueJob(self, name, *ops):
1786 """Record a job for later submit.
1789 @param name: a description of the job, will be used in WaitJobSet
1791 SetGenericOpcodeOpts(ops, self.opts)
1792 self.queue.append((name, ops))
1794 def SubmitPending(self):
1795 """Submit all pending jobs.
1798 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1799 for ((status, data), (name, _)) in zip(results, self.queue):
1800 self.jobs.append((status, data, name))
1802 def GetResults(self):
1803 """Wait for and return the results of all jobs.
1806 @return: list of tuples (success, job results), in the same order
1807 as the submitted jobs; if a job has failed, instead of the result
1808 there will be the error message
1812 self.SubmitPending()
1815 ok_jobs = [row[1] for row in self.jobs if row[0]]
1817 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
1818 for submit_status, jid, name in self.jobs:
1819 if not submit_status:
1820 ToStderr("Failed to submit job for %s: %s", name, jid)
1821 results.append((False, jid))
1824 ToStdout("Waiting for job %s for %s...", jid, name)
1826 job_result = PollJob(jid, cl=self.cl)
1828 except (errors.GenericError, luxi.ProtocolError), err:
1829 _, job_result = FormatError(err)
1831 # the error message will always be shown, verbose or not
1832 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1834 results.append((success, job_result))
1837 def WaitOrShow(self, wait):
1838 """Wait for job results or only print the job IDs.
1841 @param wait: whether to wait or not
1845 return self.GetResults()
1848 self.SubmitPending()
1849 for status, result, name in self.jobs:
1851 ToStdout("%s: %s", result, name)
1853 ToStderr("Failure for %s: %s", name, result)