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"""
31 from cStringIO import StringIO
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
41 from optparse import (OptionParser, TitledHelpFormatter,
42 Option, OptionValueError)
46 # Command line options
63 "FILESTORE_DRIVER_OPT",
70 "IGNORE_FAILURES_OPT",
84 "NOMODIFY_ETCHOSTS_OPT",
112 # Generic functions for CLI programs
117 "JobSubmittedException",
122 # Formatting functions
123 "ToStderr", "ToStdout",
132 # command line options support infrastructure
133 "ARGS_MANY_INSTANCES",
147 "OPT_COMPL_INST_ADD_NODES",
148 "OPT_COMPL_MANY_NODES",
149 "OPT_COMPL_ONE_IALLOCATOR",
150 "OPT_COMPL_ONE_INSTANCE",
151 "OPT_COMPL_ONE_NODE",
162 def __init__(self, min=0, max=None):
167 return ("<%s min=%s max=%s>" %
168 (self.__class__.__name__, self.min, self.max))
171 class ArgSuggest(_Argument):
172 """Suggesting argument.
174 Value can be any of the ones passed to the constructor.
177 def __init__(self, min=0, max=None, choices=None):
178 _Argument.__init__(self, min=min, max=max)
179 self.choices = choices
182 return ("<%s min=%s max=%s choices=%r>" %
183 (self.__class__.__name__, self.min, self.max, self.choices))
186 class ArgChoice(ArgSuggest):
189 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
190 but value must be one of the choices.
195 class ArgUnknown(_Argument):
196 """Unknown argument to program (e.g. determined at runtime).
201 class ArgInstance(_Argument):
202 """Instances argument.
207 class ArgNode(_Argument):
212 class ArgJobId(_Argument):
218 class ArgFile(_Argument):
219 """File path argument.
224 class ArgCommand(_Argument):
230 class ArgHost(_Argument):
237 ARGS_MANY_INSTANCES = [ArgInstance()]
238 ARGS_MANY_NODES = [ArgNode()]
239 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
240 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
244 def _ExtractTagsObject(opts, args):
245 """Extract the tag type object.
247 Note that this function will modify its args parameter.
250 if not hasattr(opts, "tag_type"):
251 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
253 if kind == constants.TAG_CLUSTER:
255 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
257 raise errors.OpPrereqError("no arguments passed to the command")
261 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
265 def _ExtendTags(opts, args):
266 """Extend the args if a source file has been given.
268 This function will extend the tags with the contents of the file
269 passed in the 'tags_source' attribute of the opts parameter. A file
270 named '-' will be replaced by stdin.
273 fname = opts.tags_source
279 new_fh = open(fname, "r")
282 # we don't use the nice 'new_data = [line.strip() for line in fh]'
283 # because of python bug 1633941
285 line = new_fh.readline()
288 new_data.append(line.strip())
291 args.extend(new_data)
294 def ListTags(opts, args):
295 """List the tags on a given object.
297 This is a generic implementation that knows how to deal with all
298 three cases of tag objects (cluster, node, instance). The opts
299 argument is expected to contain a tag_type field denoting what
300 object type we work on.
303 kind, name = _ExtractTagsObject(opts, args)
304 op = opcodes.OpGetTags(kind=kind, name=name)
305 result = SubmitOpCode(op)
306 result = list(result)
312 def AddTags(opts, args):
313 """Add tags on a given object.
315 This is a generic implementation that knows how to deal with all
316 three cases of tag objects (cluster, node, instance). The opts
317 argument is expected to contain a tag_type field denoting what
318 object type we work on.
321 kind, name = _ExtractTagsObject(opts, args)
322 _ExtendTags(opts, args)
324 raise errors.OpPrereqError("No tags to be added")
325 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
329 def RemoveTags(opts, args):
330 """Remove tags from a given object.
332 This is a generic implementation that knows how to deal with all
333 three cases of tag objects (cluster, node, instance). The opts
334 argument is expected to contain a tag_type field denoting what
335 object type we work on.
338 kind, name = _ExtractTagsObject(opts, args)
339 _ExtendTags(opts, args)
341 raise errors.OpPrereqError("No tags to be removed")
342 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
346 def check_unit(option, opt, value):
347 """OptParsers custom converter for units.
351 return utils.ParseUnit(value)
352 except errors.UnitParseError, err:
353 raise OptionValueError("option %s: %s" % (opt, err))
356 def _SplitKeyVal(opt, data):
357 """Convert a KeyVal string into a dict.
359 This function will convert a key=val[,...] string into a dict. Empty
360 values will be converted specially: keys which have the prefix 'no_'
361 will have the value=False and the prefix stripped, the others will
365 @param opt: a string holding the option name for which we process the
366 data, used in building error messages
368 @param data: a string of the format key=val,key=val,...
370 @return: {key=val, key=val}
371 @raises errors.ParameterError: if there are duplicate keys
376 for elem in data.split(","):
378 key, val = elem.split("=", 1)
380 if elem.startswith(NO_PREFIX):
381 key, val = elem[len(NO_PREFIX):], False
382 elif elem.startswith(UN_PREFIX):
383 key, val = elem[len(UN_PREFIX):], None
385 key, val = elem, True
387 raise errors.ParameterError("Duplicate key '%s' in option %s" %
393 def check_ident_key_val(option, opt, value):
394 """Custom parser for ident:key=val,key=val options.
396 This will store the parsed values as a tuple (ident, {key: val}). As such,
397 multiple uses of this option via action=append is possible.
401 ident, rest = value, ''
403 ident, rest = value.split(":", 1)
405 if ident.startswith(NO_PREFIX):
407 msg = "Cannot pass options when removing parameter groups: %s" % value
408 raise errors.ParameterError(msg)
409 retval = (ident[len(NO_PREFIX):], False)
410 elif ident.startswith(UN_PREFIX):
412 msg = "Cannot pass options when removing parameter groups: %s" % value
413 raise errors.ParameterError(msg)
414 retval = (ident[len(UN_PREFIX):], None)
416 kv_dict = _SplitKeyVal(opt, rest)
417 retval = (ident, kv_dict)
421 def check_key_val(option, opt, value):
422 """Custom parser class for key=val,key=val options.
424 This will store the parsed values as a dict {key: val}.
427 return _SplitKeyVal(opt, value)
430 # completion_suggestion is normally a list. Using numeric values not evaluating
431 # to False for dynamic completion.
432 (OPT_COMPL_MANY_NODES,
434 OPT_COMPL_ONE_INSTANCE,
436 OPT_COMPL_ONE_IALLOCATOR,
437 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
439 OPT_COMPL_ALL = frozenset([
440 OPT_COMPL_MANY_NODES,
442 OPT_COMPL_ONE_INSTANCE,
444 OPT_COMPL_ONE_IALLOCATOR,
445 OPT_COMPL_INST_ADD_NODES,
449 class CliOption(Option):
450 """Custom option class for optparse.
453 ATTRS = Option.ATTRS + [
454 "completion_suggest",
456 TYPES = Option.TYPES + (
461 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
462 TYPE_CHECKER["identkeyval"] = check_ident_key_val
463 TYPE_CHECKER["keyval"] = check_key_val
464 TYPE_CHECKER["unit"] = check_unit
467 # optparse.py sets make_option, so we do it for our own option class, too
468 cli_option = CliOption
471 _YESNO = ("yes", "no")
474 DEBUG_OPT = cli_option("-d", "--debug", default=False,
476 help="Turn debugging on")
478 NOHDR_OPT = cli_option("--no-headers", default=False,
479 action="store_true", dest="no_headers",
480 help="Don't display column headers")
482 SEP_OPT = cli_option("--separator", default=None,
483 action="store", dest="separator",
484 help=("Separator between output fields"
485 " (defaults to one space)"))
487 USEUNITS_OPT = cli_option("--units", default=None,
488 dest="units", choices=('h', 'm', 'g', 't'),
489 help="Specify units for output (one of hmgt)")
491 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
492 type="string", metavar="FIELDS",
493 help="Comma separated list of output fields")
495 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
496 default=False, help="Force the operation")
498 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
499 default=False, help="Do not require confirmation")
501 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
502 default=None, help="File with tag names")
504 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
505 default=False, action="store_true",
506 help=("Submit the job and return the job ID, but"
507 " don't wait for the job to finish"))
509 SYNC_OPT = cli_option("--sync", dest="do_locking",
510 default=False, action="store_true",
511 help=("Grab locks while doing the queries"
512 " in order to ensure more consistent results"))
514 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
516 help=("Do not execute the operation, just run the"
517 " check steps and verify it it could be"
520 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
522 help="Increase the verbosity of the operation")
524 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
525 action="store_true", dest="simulate_errors",
526 help="Debugging option that makes the operation"
527 " treat most runtime checks as failed")
529 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
530 default=True, action="store_false",
531 help="Don't wait for sync (DANGEROUS!)")
533 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
534 help="Custom disk setup (diskless, file,"
536 default=None, metavar="TEMPL",
537 choices=list(constants.DISK_TEMPLATES))
539 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
540 help="Do not create any network cards for"
543 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
544 help="Relative path under default cluster-wide"
545 " file storage dir to store file-based disks",
546 default=None, metavar="<DIR>")
548 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
549 help="Driver to use for image files",
550 default="loop", metavar="<DRIVER>",
551 choices=list(constants.FILE_DRIVER))
553 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
554 help="Select nodes for the instance automatically"
555 " using the <NAME> iallocator plugin",
556 default=None, type="string",
557 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
559 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
561 completion_suggest=OPT_COMPL_ONE_OS)
563 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
564 type="keyval", default={},
565 help="Backend parameters")
567 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
568 default={}, dest="hvparams",
569 help="Hypervisor parameters")
571 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
572 help="Hypervisor and hypervisor options, in the"
573 " format hypervisor:option=value,option=value,...",
574 default=None, type="identkeyval")
576 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
577 help="Hypervisor and hypervisor options, in the"
578 " format hypervisor:option=value,option=value,...",
579 default=[], action="append", type="identkeyval")
581 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
582 action="store_false",
583 help="Don't check that the instance's IP"
586 NET_OPT = cli_option("--net",
587 help="NIC parameters", default=[],
588 dest="nics", action="append", type="identkeyval")
590 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
591 dest="disks", action="append", type="identkeyval")
593 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
594 help="Comma-separated list of disks"
595 " indices to act on (e.g. 0,2) (optional,"
596 " defaults to all disks)")
598 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
599 help="Enforces a single-disk configuration using the"
600 " given disk size, in MiB unless a suffix is used",
601 default=None, type="unit", metavar="<size>")
603 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
604 dest="ignore_consistency",
605 action="store_true", default=False,
606 help="Ignore the consistency of the disks on"
609 NONLIVE_OPT = cli_option("--non-live", dest="live",
610 default=True, action="store_false",
611 help="Do a non-live migration (this usually means"
612 " freeze the instance, save the state, transfer and"
613 " only then resume running on the secondary node)")
615 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
616 help="Target node and optional secondary node",
617 metavar="<pnode>[:<snode>]",
618 completion_suggest=OPT_COMPL_INST_ADD_NODES)
620 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
621 action="append", metavar="<node>",
622 help="Use only this node (can be used multiple"
623 " times, if not given defaults to all nodes)",
624 completion_suggest=OPT_COMPL_ONE_NODE)
626 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
628 completion_suggest=OPT_COMPL_ONE_NODE)
630 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
631 action="store_false",
632 help="Don't start the instance after creation")
634 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
635 action="store_true", default=False,
636 help="Show command instead of executing it")
638 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
639 default=False, action="store_true",
640 help="Instead of performing the migration, try to"
641 " recover from a failed cleanup. This is safe"
642 " to run even if the instance is healthy, but it"
643 " will create extra replication traffic and "
644 " disrupt briefly the replication (like during the"
647 STATIC_OPT = cli_option("-s", "--static", dest="static",
648 action="store_true", default=False,
649 help="Only show configuration data, not runtime data")
651 ALL_OPT = cli_option("--all", dest="show_all",
652 default=False, action="store_true",
653 help="Show info on all instances on the cluster."
654 " This can take a long time to run, use wisely")
656 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
657 action="store_true", default=False,
658 help="Interactive OS reinstall, lists available"
659 " OS templates for selection")
661 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
662 action="store_true", default=False,
663 help="Remove the instance from the cluster"
664 " configuration even if there are failures"
665 " during the removal process")
667 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
668 help="Specifies the new secondary node",
669 metavar="NODE", default=None,
670 completion_suggest=OPT_COMPL_ONE_NODE)
672 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
673 default=False, action="store_true",
674 help="Replace the disk(s) on the primary"
675 " node (only for the drbd template)")
677 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
678 default=False, action="store_true",
679 help="Replace the disk(s) on the secondary"
680 " node (only for the drbd template)")
682 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
683 default=False, action="store_true",
684 help="Automatically replace faulty disks"
685 " (only for the drbd template)")
687 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
688 default=False, action="store_true",
689 help="Ignore current recorded size"
690 " (useful for forcing activation when"
691 " the recorded size is wrong)")
693 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
695 completion_suggest=OPT_COMPL_ONE_NODE)
697 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
700 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
701 help="Specify the secondary ip for the node",
702 metavar="ADDRESS", default=None)
704 READD_OPT = cli_option("--readd", dest="readd",
705 default=False, action="store_true",
706 help="Readd old node after replacing it")
708 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
709 default=True, action="store_false",
710 help="Disable SSH key fingerprint checking")
713 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
714 choices=_YESNO, default=None, metavar=_YORNO,
715 help="Set the master_candidate flag on the node")
717 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
718 choices=_YESNO, default=None,
719 help="Set the offline flag on the node")
721 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
722 choices=_YESNO, default=None,
723 help="Set the drained flag on the node")
725 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
726 choices=_YESNO, default=None, metavar=_YORNO,
727 help="Set the allocatable flag on a volume")
729 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
730 help="Disable support for lvm based instances"
732 action="store_false", default=True)
734 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
735 dest="enabled_hypervisors",
736 help="Comma-separated list of hypervisors",
737 type="string", default=None)
739 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
740 type="keyval", default={},
741 help="NIC parameters")
743 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
744 dest="candidate_pool_size", type="int",
745 help="Set the candidate pool size")
747 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
748 help="Enables LVM and specifies the volume group"
749 " name (cluster-wide) for disk allocation [xenvg]",
750 metavar="VG", default=None)
752 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
753 help="Destroy cluster", action="store_true")
755 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
756 help="Skip node agreement check (dangerous)",
757 action="store_true", default=False)
759 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
760 help="Specify the mac prefix for the instance IP"
761 " addresses, in the format XX:XX:XX",
765 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
766 help="Specify the node interface (cluster-wide)"
767 " on which the master IP address will be added "
768 " [%s]" % constants.DEFAULT_BRIDGE,
770 default=constants.DEFAULT_BRIDGE)
773 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
774 help="Specify the default directory (cluster-"
775 "wide) for storing the file-based disks [%s]" %
776 constants.DEFAULT_FILE_STORAGE_DIR,
778 default=constants.DEFAULT_FILE_STORAGE_DIR)
780 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
781 help="Don't modify /etc/hosts",
782 action="store_false", default=True)
785 def _ParseArgs(argv, commands, aliases):
786 """Parser for the command line arguments.
788 This function parses the arguments and returns the function which
789 must be executed together with its (modified) arguments.
791 @param argv: the command line
792 @param commands: dictionary with special contents, see the design
793 doc for cmdline handling
794 @param aliases: dictionary with command aliases {'alias': 'target, ...}
800 binary = argv[0].split("/")[-1]
802 if len(argv) > 1 and argv[1] == "--version":
803 ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
804 # Quit right away. That way we don't have to care about this special
805 # argument. optparse.py does it the same.
808 if len(argv) < 2 or not (argv[1] in commands or
810 # let's do a nice thing
811 sortedcmds = commands.keys()
814 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
815 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
818 # compute the max line length for cmd + usage
819 mlen = max([len(" %s" % cmd) for cmd in commands])
820 mlen = min(60, mlen) # should not get here...
822 # and format a nice command list
823 ToStdout("Commands:")
824 for cmd in sortedcmds:
825 cmdstr = " %s" % (cmd,)
826 help_text = commands[cmd][4]
827 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
828 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
829 for line in help_lines:
830 ToStdout("%-*s %s", mlen, "", line)
834 return None, None, None
836 # get command, unalias it, and look it up in commands
840 raise errors.ProgrammerError("Alias '%s' overrides an existing"
843 if aliases[cmd] not in commands:
844 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
845 " command '%s'" % (cmd, aliases[cmd]))
849 func, args_def, parser_opts, usage, description = commands[cmd]
850 parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT],
851 description=description,
852 formatter=TitledHelpFormatter(),
853 usage="%%prog %s %s" % (cmd, usage))
854 parser.disable_interspersed_args()
855 options, args = parser.parse_args()
857 if not _CheckArguments(cmd, args_def, args):
858 return None, None, None
860 return func, options, args
863 def _CheckArguments(cmd, args_def, args):
864 """Verifies the arguments using the argument definition.
868 1. Abort with error if values specified by user but none expected.
870 1. For each argument in definition
872 1. Keep running count of minimum number of values (min_count)
873 1. Keep running count of maximum number of values (max_count)
874 1. If it has an unlimited number of values
876 1. Abort with error if it's not the last argument in the definition
878 1. If last argument has limited number of values
880 1. Abort with error if number of values doesn't match or is too large
882 1. Abort with error if user didn't pass enough values (min_count)
885 if args and not args_def:
886 ToStderr("Error: Command %s expects no arguments", cmd)
893 last_idx = len(args_def) - 1
895 for idx, arg in enumerate(args_def):
896 if min_count is None:
898 elif arg.min is not None:
901 if max_count is None:
903 elif arg.max is not None:
907 check_max = (arg.max is not None)
909 elif arg.max is None:
910 raise errors.ProgrammerError("Only the last argument can have max=None")
913 # Command with exact number of arguments
914 if (min_count is not None and max_count is not None and
915 min_count == max_count and len(args) != min_count):
916 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
919 # Command with limited number of arguments
920 if max_count is not None and len(args) > max_count:
921 ToStderr("Error: Command %s expects only %d argument(s)",
925 # Command with some required arguments
926 if min_count is not None and len(args) < min_count:
927 ToStderr("Error: Command %s expects at least %d argument(s)",
934 def SplitNodeOption(value):
935 """Splits the value of a --node option.
938 if value and ':' in value:
939 return value.split(':', 1)
945 def wrapper(*args, **kwargs):
948 return fn(*args, **kwargs)
954 def AskUser(text, choices=None):
955 """Ask the user a question.
957 @param text: the question to ask
959 @param choices: list with elements tuples (input_char, return_value,
960 description); if not given, it will default to: [('y', True,
961 'Perform the operation'), ('n', False, 'Do no do the operation')];
962 note that the '?' char is reserved for help
964 @return: one of the return values from the choices list; if input is
965 not possible (i.e. not running with a tty, we return the last
970 choices = [('y', True, 'Perform the operation'),
971 ('n', False, 'Do not perform the operation')]
972 if not choices or not isinstance(choices, list):
973 raise errors.ProgrammerError("Invalid choices argument to AskUser")
974 for entry in choices:
975 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
976 raise errors.ProgrammerError("Invalid choices element to AskUser")
978 answer = choices[-1][1]
980 for line in text.splitlines():
981 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
982 text = "\n".join(new_text)
984 f = file("/dev/tty", "a+")
988 chars = [entry[0] for entry in choices]
989 chars[-1] = "[%s]" % chars[-1]
991 maps = dict([(entry[0], entry[1]) for entry in choices])
995 f.write("/".join(chars))
997 line = f.readline(2).strip().lower()
1002 for entry in choices:
1003 f.write(" %s - %s\n" % (entry[0], entry[2]))
1011 class JobSubmittedException(Exception):
1012 """Job was submitted, client should exit.
1014 This exception has one argument, the ID of the job that was
1015 submitted. The handler should print this ID.
1017 This is not an error, just a structured way to exit from clients.
1022 def SendJob(ops, cl=None):
1023 """Function to submit an opcode without waiting for the results.
1026 @param ops: list of opcodes
1027 @type cl: luxi.Client
1028 @param cl: the luxi client to use for communicating with the master;
1029 if None, a new client will be created
1035 job_id = cl.SubmitJob(ops)
1040 def PollJob(job_id, cl=None, feedback_fn=None):
1041 """Function to poll for the result of a job.
1043 @type job_id: job identified
1044 @param job_id: the job to poll for results
1045 @type cl: luxi.Client
1046 @param cl: the luxi client to use for communicating with the master;
1047 if None, a new client will be created
1053 prev_job_info = None
1054 prev_logmsg_serial = None
1057 result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1060 # job not found, go away!
1061 raise errors.JobLost("Job with id %s lost" % job_id)
1063 # Split result, a tuple of (field values, log entries)
1064 (job_info, log_entries) = result
1065 (status, ) = job_info
1068 for log_entry in log_entries:
1069 (serial, timestamp, _, message) = log_entry
1070 if callable(feedback_fn):
1071 feedback_fn(log_entry[1:])
1073 encoded = utils.SafeEncode(message)
1074 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1075 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1077 # TODO: Handle canceled and archived jobs
1078 elif status in (constants.JOB_STATUS_SUCCESS,
1079 constants.JOB_STATUS_ERROR,
1080 constants.JOB_STATUS_CANCELING,
1081 constants.JOB_STATUS_CANCELED):
1084 prev_job_info = job_info
1086 jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1088 raise errors.JobLost("Job with id %s lost" % job_id)
1090 status, opstatus, result = jobs[0]
1091 if status == constants.JOB_STATUS_SUCCESS:
1093 elif status in (constants.JOB_STATUS_CANCELING,
1094 constants.JOB_STATUS_CANCELED):
1095 raise errors.OpExecError("Job was canceled")
1098 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1099 if status == constants.OP_STATUS_SUCCESS:
1101 elif status == constants.OP_STATUS_ERROR:
1102 errors.MaybeRaise(msg)
1104 raise errors.OpExecError("partial failure (opcode %d): %s" %
1107 raise errors.OpExecError(str(msg))
1108 # default failure mode
1109 raise errors.OpExecError(result)
1112 def SubmitOpCode(op, cl=None, feedback_fn=None):
1113 """Legacy function to submit an opcode.
1115 This is just a simple wrapper over the construction of the processor
1116 instance. It should be extended to better handle feedback and
1117 interaction functions.
1123 job_id = SendJob([op], cl)
1125 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1127 return op_results[0]
1130 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1131 """Wrapper around SubmitOpCode or SendJob.
1133 This function will decide, based on the 'opts' parameter, whether to
1134 submit and wait for the result of the opcode (and return it), or
1135 whether to just send the job and print its identifier. It is used in
1136 order to simplify the implementation of the '--submit' option.
1138 It will also add the dry-run parameter from the options passed, if true.
1141 if opts and opts.dry_run:
1142 op.dry_run = opts.dry_run
1143 if opts and opts.submit_only:
1144 job_id = SendJob([op], cl=cl)
1145 raise JobSubmittedException(job_id)
1147 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1151 # TODO: Cache object?
1153 client = luxi.Client()
1154 except luxi.NoMasterError:
1155 master, myself = ssconf.GetMasterAndMyself()
1156 if master != myself:
1157 raise errors.OpPrereqError("This is not the master node, please connect"
1158 " to node '%s' and rerun the command" %
1165 def FormatError(err):
1166 """Return a formatted error message for a given error.
1168 This function takes an exception instance and returns a tuple
1169 consisting of two values: first, the recommended exit code, and
1170 second, a string describing the error message (not
1171 newline-terminated).
1177 if isinstance(err, errors.ConfigurationError):
1178 txt = "Corrupt configuration file: %s" % msg
1180 obuf.write(txt + "\n")
1181 obuf.write("Aborting.")
1183 elif isinstance(err, errors.HooksAbort):
1184 obuf.write("Failure: hooks execution failed:\n")
1185 for node, script, out in err.args[0]:
1187 obuf.write(" node: %s, script: %s, output: %s\n" %
1188 (node, script, out))
1190 obuf.write(" node: %s, script: %s (no output)\n" %
1192 elif isinstance(err, errors.HooksFailure):
1193 obuf.write("Failure: hooks general failure: %s" % msg)
1194 elif isinstance(err, errors.ResolverError):
1195 this_host = utils.HostInfo.SysName()
1196 if err.args[0] == this_host:
1197 msg = "Failure: can't resolve my own hostname ('%s')"
1199 msg = "Failure: can't resolve hostname '%s'"
1200 obuf.write(msg % err.args[0])
1201 elif isinstance(err, errors.OpPrereqError):
1202 obuf.write("Failure: prerequisites not met for this"
1203 " operation:\n%s" % msg)
1204 elif isinstance(err, errors.OpExecError):
1205 obuf.write("Failure: command execution error:\n%s" % msg)
1206 elif isinstance(err, errors.TagError):
1207 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1208 elif isinstance(err, errors.JobQueueDrainError):
1209 obuf.write("Failure: the job queue is marked for drain and doesn't"
1210 " accept new requests\n")
1211 elif isinstance(err, errors.JobQueueFull):
1212 obuf.write("Failure: the job queue is full and doesn't accept new"
1213 " job submissions until old jobs are archived\n")
1214 elif isinstance(err, errors.TypeEnforcementError):
1215 obuf.write("Parameter Error: %s" % msg)
1216 elif isinstance(err, errors.ParameterError):
1217 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1218 elif isinstance(err, errors.GenericError):
1219 obuf.write("Unhandled Ganeti error: %s" % msg)
1220 elif isinstance(err, luxi.NoMasterError):
1221 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1222 " and listening for connections?")
1223 elif isinstance(err, luxi.TimeoutError):
1224 obuf.write("Timeout while talking to the master daemon. Error:\n"
1226 elif isinstance(err, luxi.ProtocolError):
1227 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1229 elif isinstance(err, JobSubmittedException):
1230 obuf.write("JobID: %s\n" % err.args[0])
1233 obuf.write("Unhandled exception: %s" % msg)
1234 return retcode, obuf.getvalue().rstrip('\n')
1237 def GenericMain(commands, override=None, aliases=None):
1238 """Generic main function for all the gnt-* commands.
1241 - commands: a dictionary with a special structure, see the design doc
1242 for command line handling.
1243 - override: if not None, we expect a dictionary with keys that will
1244 override command line options; this can be used to pass
1245 options from the scripts to generic functions
1246 - aliases: dictionary with command aliases {'alias': 'target, ...}
1249 # save the program name and the entire command line for later logging
1251 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1252 if len(sys.argv) >= 2:
1253 binary += " " + sys.argv[1]
1254 old_cmdline = " ".join(sys.argv[2:])
1258 binary = "<unknown program>"
1265 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1266 except errors.ParameterError, err:
1267 result, err_msg = FormatError(err)
1271 if func is None: # parse error
1274 if override is not None:
1275 for key, val in override.iteritems():
1276 setattr(options, key, val)
1278 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1279 stderr_logging=True, program=binary)
1282 logging.info("run with arguments '%s'", old_cmdline)
1284 logging.info("run with no arguments")
1287 result = func(options, args)
1288 except (errors.GenericError, luxi.ProtocolError,
1289 JobSubmittedException), err:
1290 result, err_msg = FormatError(err)
1291 logging.exception("Error during command processing")
1297 def GenerateTable(headers, fields, separator, data,
1298 numfields=None, unitfields=None,
1300 """Prints a table with headers and different fields.
1303 @param headers: dictionary mapping field names to headers for
1306 @param fields: the field names corresponding to each row in
1308 @param separator: the separator to be used; if this is None,
1309 the default 'smart' algorithm is used which computes optimal
1310 field width, otherwise just the separator is used between
1313 @param data: a list of lists, each sublist being one row to be output
1314 @type numfields: list
1315 @param numfields: a list with the fields that hold numeric
1316 values and thus should be right-aligned
1317 @type unitfields: list
1318 @param unitfields: a list with the fields that hold numeric
1319 values that should be formatted with the units field
1320 @type units: string or None
1321 @param units: the units we should use for formatting, or None for
1322 automatic choice (human-readable for non-separator usage, otherwise
1323 megabytes); this is a one-letter string
1332 if numfields is None:
1334 if unitfields is None:
1337 numfields = utils.FieldSet(*numfields)
1338 unitfields = utils.FieldSet(*unitfields)
1341 for field in fields:
1342 if headers and field not in headers:
1343 # TODO: handle better unknown fields (either revert to old
1344 # style of raising exception, or deal more intelligently with
1346 headers[field] = field
1347 if separator is not None:
1348 format_fields.append("%s")
1349 elif numfields.Matches(field):
1350 format_fields.append("%*s")
1352 format_fields.append("%-*s")
1354 if separator is None:
1355 mlens = [0 for name in fields]
1356 format = ' '.join(format_fields)
1358 format = separator.replace("%", "%%").join(format_fields)
1363 for idx, val in enumerate(row):
1364 if unitfields.Matches(fields[idx]):
1370 val = row[idx] = utils.FormatUnit(val, units)
1371 val = row[idx] = str(val)
1372 if separator is None:
1373 mlens[idx] = max(mlens[idx], len(val))
1378 for idx, name in enumerate(fields):
1380 if separator is None:
1381 mlens[idx] = max(mlens[idx], len(hdr))
1382 args.append(mlens[idx])
1384 result.append(format % tuple(args))
1389 line = ['-' for _ in fields]
1390 for idx in xrange(len(fields)):
1391 if separator is None:
1392 args.append(mlens[idx])
1393 args.append(line[idx])
1394 result.append(format % tuple(args))
1399 def FormatTimestamp(ts):
1400 """Formats a given timestamp.
1403 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1406 @return: a string with the formatted timestamp
1409 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1412 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1415 def ParseTimespec(value):
1416 """Parse a time specification.
1418 The following suffixed will be recognized:
1426 Without any suffix, the value will be taken to be in seconds.
1431 raise errors.OpPrereqError("Empty time specification passed")
1439 if value[-1] not in suffix_map:
1443 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1445 multiplier = suffix_map[value[-1]]
1447 if not value: # no data left after stripping the suffix
1448 raise errors.OpPrereqError("Invalid time specification (only"
1451 value = int(value) * multiplier
1453 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1457 def GetOnlineNodes(nodes, cl=None, nowarn=False):
1458 """Returns the names of online nodes.
1460 This function will also log a warning on stderr with the names of
1463 @param nodes: if not empty, use only this subset of nodes (minus the
1465 @param cl: if not None, luxi client to use
1466 @type nowarn: boolean
1467 @param nowarn: by default, this function will output a note with the
1468 offline nodes that are skipped; if this parameter is True the
1469 note is not displayed
1475 result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1477 offline = [row[0] for row in result if row[1]]
1478 if offline and not nowarn:
1479 ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1480 return [row[0] for row in result if not row[1]]
1483 def _ToStream(stream, txt, *args):
1484 """Write a message to a stream, bypassing the logging system
1486 @type stream: file object
1487 @param stream: the file to which we should write
1489 @param txt: the message
1494 stream.write(txt % args)
1501 def ToStdout(txt, *args):
1502 """Write a message to stdout only, bypassing the logging system
1504 This is just a wrapper over _ToStream.
1507 @param txt: the message
1510 _ToStream(sys.stdout, txt, *args)
1513 def ToStderr(txt, *args):
1514 """Write a message to stderr only, bypassing the logging system
1516 This is just a wrapper over _ToStream.
1519 @param txt: the message
1522 _ToStream(sys.stderr, txt, *args)
1525 class JobExecutor(object):
1526 """Class which manages the submission and execution of multiple jobs.
1528 Note that instances of this class should not be reused between
1532 def __init__(self, cl=None, verbose=True):
1537 self.verbose = verbose
1540 def QueueJob(self, name, *ops):
1541 """Record a job for later submit.
1544 @param name: a description of the job, will be used in WaitJobSet
1546 self.queue.append((name, ops))
1548 def SubmitPending(self):
1549 """Submit all pending jobs.
1552 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1553 for ((status, data), (name, _)) in zip(results, self.queue):
1554 self.jobs.append((status, data, name))
1556 def GetResults(self):
1557 """Wait for and return the results of all jobs.
1560 @return: list of tuples (success, job results), in the same order
1561 as the submitted jobs; if a job has failed, instead of the result
1562 there will be the error message
1566 self.SubmitPending()
1569 ok_jobs = [row[1] for row in self.jobs if row[0]]
1571 ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1572 for submit_status, jid, name in self.jobs:
1573 if not submit_status:
1574 ToStderr("Failed to submit job for %s: %s", name, jid)
1575 results.append((False, jid))
1578 ToStdout("Waiting for job %s for %s...", jid, name)
1580 job_result = PollJob(jid, cl=self.cl)
1582 except (errors.GenericError, luxi.ProtocolError), err:
1583 _, job_result = FormatError(err)
1585 # the error message will always be shown, verbose or not
1586 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1588 results.append((success, job_result))
1591 def WaitOrShow(self, wait):
1592 """Wait for job results or only print the job IDs.
1595 @param wait: whether to wait or not
1599 return self.GetResults()
1602 self.SubmitPending()
1603 for status, result, name in self.jobs:
1605 ToStdout("%s: %s", result, name)
1607 ToStderr("Failure for %s: %s", name, result)