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
63 "FILESTORE_DRIVER_OPT",
72 "IGNORE_FAILURES_OPT",
73 "IGNORE_SECONDARIES_OPT",
86 "NOMODIFY_ETCHOSTS_OPT",
106 "SHUTDOWN_TIMEOUT_OPT",
119 # Generic functions for CLI programs
121 "GenericInstanceCreate",
125 "JobSubmittedException",
130 # Formatting functions
131 "ToStderr", "ToStdout",
140 # command line options support infrastructure
141 "ARGS_MANY_INSTANCES",
155 "OPT_COMPL_INST_ADD_NODES",
156 "OPT_COMPL_MANY_NODES",
157 "OPT_COMPL_ONE_IALLOCATOR",
158 "OPT_COMPL_ONE_INSTANCE",
159 "OPT_COMPL_ONE_NODE",
171 def __init__(self, min=0, max=None):
176 return ("<%s min=%s max=%s>" %
177 (self.__class__.__name__, self.min, self.max))
180 class ArgSuggest(_Argument):
181 """Suggesting argument.
183 Value can be any of the ones passed to the constructor.
186 def __init__(self, min=0, max=None, choices=None):
187 _Argument.__init__(self, min=min, max=max)
188 self.choices = choices
191 return ("<%s min=%s max=%s choices=%r>" %
192 (self.__class__.__name__, self.min, self.max, self.choices))
195 class ArgChoice(ArgSuggest):
198 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
199 but value must be one of the choices.
204 class ArgUnknown(_Argument):
205 """Unknown argument to program (e.g. determined at runtime).
210 class ArgInstance(_Argument):
211 """Instances argument.
216 class ArgNode(_Argument):
221 class ArgJobId(_Argument):
227 class ArgFile(_Argument):
228 """File path argument.
233 class ArgCommand(_Argument):
239 class ArgHost(_Argument):
246 ARGS_MANY_INSTANCES = [ArgInstance()]
247 ARGS_MANY_NODES = [ArgNode()]
248 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
249 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
253 def _ExtractTagsObject(opts, args):
254 """Extract the tag type object.
256 Note that this function will modify its args parameter.
259 if not hasattr(opts, "tag_type"):
260 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
262 if kind == constants.TAG_CLUSTER:
264 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
266 raise errors.OpPrereqError("no arguments passed to the command")
270 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
274 def _ExtendTags(opts, args):
275 """Extend the args if a source file has been given.
277 This function will extend the tags with the contents of the file
278 passed in the 'tags_source' attribute of the opts parameter. A file
279 named '-' will be replaced by stdin.
282 fname = opts.tags_source
288 new_fh = open(fname, "r")
291 # we don't use the nice 'new_data = [line.strip() for line in fh]'
292 # because of python bug 1633941
294 line = new_fh.readline()
297 new_data.append(line.strip())
300 args.extend(new_data)
303 def ListTags(opts, args):
304 """List the tags on a given object.
306 This is a generic implementation that knows how to deal with all
307 three cases of tag objects (cluster, node, instance). The opts
308 argument is expected to contain a tag_type field denoting what
309 object type we work on.
312 kind, name = _ExtractTagsObject(opts, args)
313 op = opcodes.OpGetTags(kind=kind, name=name)
314 result = SubmitOpCode(op)
315 result = list(result)
321 def AddTags(opts, args):
322 """Add tags on a given object.
324 This is a generic implementation that knows how to deal with all
325 three cases of tag objects (cluster, node, instance). The opts
326 argument is expected to contain a tag_type field denoting what
327 object type we work on.
330 kind, name = _ExtractTagsObject(opts, args)
331 _ExtendTags(opts, args)
333 raise errors.OpPrereqError("No tags to be added")
334 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
338 def RemoveTags(opts, args):
339 """Remove tags from a given object.
341 This is a generic implementation that knows how to deal with all
342 three cases of tag objects (cluster, node, instance). The opts
343 argument is expected to contain a tag_type field denoting what
344 object type we work on.
347 kind, name = _ExtractTagsObject(opts, args)
348 _ExtendTags(opts, args)
350 raise errors.OpPrereqError("No tags to be removed")
351 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
355 def check_unit(option, opt, value):
356 """OptParsers custom converter for units.
360 return utils.ParseUnit(value)
361 except errors.UnitParseError, err:
362 raise OptionValueError("option %s: %s" % (opt, err))
365 def _SplitKeyVal(opt, data):
366 """Convert a KeyVal string into a dict.
368 This function will convert a key=val[,...] string into a dict. Empty
369 values will be converted specially: keys which have the prefix 'no_'
370 will have the value=False and the prefix stripped, the others will
374 @param opt: a string holding the option name for which we process the
375 data, used in building error messages
377 @param data: a string of the format key=val,key=val,...
379 @return: {key=val, key=val}
380 @raises errors.ParameterError: if there are duplicate keys
385 for elem in data.split(","):
387 key, val = elem.split("=", 1)
389 if elem.startswith(NO_PREFIX):
390 key, val = elem[len(NO_PREFIX):], False
391 elif elem.startswith(UN_PREFIX):
392 key, val = elem[len(UN_PREFIX):], None
394 key, val = elem, True
396 raise errors.ParameterError("Duplicate key '%s' in option %s" %
402 def check_ident_key_val(option, opt, value):
403 """Custom parser for ident:key=val,key=val options.
405 This will store the parsed values as a tuple (ident, {key: val}). As such,
406 multiple uses of this option via action=append is possible.
410 ident, rest = value, ''
412 ident, rest = value.split(":", 1)
414 if ident.startswith(NO_PREFIX):
416 msg = "Cannot pass options when removing parameter groups: %s" % value
417 raise errors.ParameterError(msg)
418 retval = (ident[len(NO_PREFIX):], False)
419 elif ident.startswith(UN_PREFIX):
421 msg = "Cannot pass options when removing parameter groups: %s" % value
422 raise errors.ParameterError(msg)
423 retval = (ident[len(UN_PREFIX):], None)
425 kv_dict = _SplitKeyVal(opt, rest)
426 retval = (ident, kv_dict)
430 def check_key_val(option, opt, value):
431 """Custom parser class for key=val,key=val options.
433 This will store the parsed values as a dict {key: val}.
436 return _SplitKeyVal(opt, value)
439 # completion_suggestion is normally a list. Using numeric values not evaluating
440 # to False for dynamic completion.
441 (OPT_COMPL_MANY_NODES,
443 OPT_COMPL_ONE_INSTANCE,
445 OPT_COMPL_ONE_IALLOCATOR,
446 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
448 OPT_COMPL_ALL = frozenset([
449 OPT_COMPL_MANY_NODES,
451 OPT_COMPL_ONE_INSTANCE,
453 OPT_COMPL_ONE_IALLOCATOR,
454 OPT_COMPL_INST_ADD_NODES,
458 class CliOption(Option):
459 """Custom option class for optparse.
462 ATTRS = Option.ATTRS + [
463 "completion_suggest",
465 TYPES = Option.TYPES + (
470 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
471 TYPE_CHECKER["identkeyval"] = check_ident_key_val
472 TYPE_CHECKER["keyval"] = check_key_val
473 TYPE_CHECKER["unit"] = check_unit
476 # optparse.py sets make_option, so we do it for our own option class, too
477 cli_option = CliOption
480 _YESNO = ("yes", "no")
483 DEBUG_OPT = cli_option("-d", "--debug", default=False,
485 help="Turn debugging on")
487 NOHDR_OPT = cli_option("--no-headers", default=False,
488 action="store_true", dest="no_headers",
489 help="Don't display column headers")
491 SEP_OPT = cli_option("--separator", default=None,
492 action="store", dest="separator",
493 help=("Separator between output fields"
494 " (defaults to one space)"))
496 USEUNITS_OPT = cli_option("--units", default=None,
497 dest="units", choices=('h', 'm', 'g', 't'),
498 help="Specify units for output (one of hmgt)")
500 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
501 type="string", metavar="FIELDS",
502 help="Comma separated list of output fields")
504 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
505 default=False, help="Force the operation")
507 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
508 default=False, help="Do not require confirmation")
510 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
511 default=None, help="File with tag names")
513 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
514 default=False, action="store_true",
515 help=("Submit the job and return the job ID, but"
516 " don't wait for the job to finish"))
518 SYNC_OPT = cli_option("--sync", dest="do_locking",
519 default=False, action="store_true",
520 help=("Grab locks while doing the queries"
521 " in order to ensure more consistent results"))
523 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
525 help=("Do not execute the operation, just run the"
526 " check steps and verify it it could be"
529 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
531 help="Increase the verbosity of the operation")
533 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
534 action="store_true", dest="simulate_errors",
535 help="Debugging option that makes the operation"
536 " treat most runtime checks as failed")
538 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
539 default=True, action="store_false",
540 help="Don't wait for sync (DANGEROUS!)")
542 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
543 help="Custom disk setup (diskless, file,"
545 default=None, metavar="TEMPL",
546 choices=list(constants.DISK_TEMPLATES))
548 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
549 help="Do not create any network cards for"
552 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
553 help="Relative path under default cluster-wide"
554 " file storage dir to store file-based disks",
555 default=None, metavar="<DIR>")
557 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
558 help="Driver to use for image files",
559 default="loop", metavar="<DRIVER>",
560 choices=list(constants.FILE_DRIVER))
562 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
563 help="Select nodes for the instance automatically"
564 " using the <NAME> iallocator plugin",
565 default=None, type="string",
566 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
568 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
570 completion_suggest=OPT_COMPL_ONE_OS)
572 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
573 action="store_true", default=False,
574 help="Force an unknown variant")
576 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
577 type="keyval", default={},
578 help="Backend parameters")
580 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
581 default={}, dest="hvparams",
582 help="Hypervisor parameters")
584 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
585 help="Hypervisor and hypervisor options, in the"
586 " format hypervisor:option=value,option=value,...",
587 default=None, type="identkeyval")
589 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
590 help="Hypervisor and hypervisor options, in the"
591 " format hypervisor:option=value,option=value,...",
592 default=[], action="append", type="identkeyval")
594 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
595 action="store_false",
596 help="Don't check that the instance's IP"
599 NET_OPT = cli_option("--net",
600 help="NIC parameters", default=[],
601 dest="nics", action="append", type="identkeyval")
603 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
604 dest="disks", action="append", type="identkeyval")
606 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
607 help="Comma-separated list of disks"
608 " indices to act on (e.g. 0,2) (optional,"
609 " defaults to all disks)")
611 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
612 help="Enforces a single-disk configuration using the"
613 " given disk size, in MiB unless a suffix is used",
614 default=None, type="unit", metavar="<size>")
616 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
617 dest="ignore_consistency",
618 action="store_true", default=False,
619 help="Ignore the consistency of the disks on"
622 NONLIVE_OPT = cli_option("--non-live", dest="live",
623 default=True, action="store_false",
624 help="Do a non-live migration (this usually means"
625 " freeze the instance, save the state, transfer and"
626 " only then resume running on the secondary node)")
628 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
629 help="Target node and optional secondary node",
630 metavar="<pnode>[:<snode>]",
631 completion_suggest=OPT_COMPL_INST_ADD_NODES)
633 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
634 action="append", metavar="<node>",
635 help="Use only this node (can be used multiple"
636 " times, if not given defaults to all nodes)",
637 completion_suggest=OPT_COMPL_ONE_NODE)
639 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
641 completion_suggest=OPT_COMPL_ONE_NODE)
643 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
644 action="store_false",
645 help="Don't start the instance after creation")
647 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
648 action="store_true", default=False,
649 help="Show command instead of executing it")
651 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
652 default=False, action="store_true",
653 help="Instead of performing the migration, try to"
654 " recover from a failed cleanup. This is safe"
655 " to run even if the instance is healthy, but it"
656 " will create extra replication traffic and "
657 " disrupt briefly the replication (like during the"
660 STATIC_OPT = cli_option("-s", "--static", dest="static",
661 action="store_true", default=False,
662 help="Only show configuration data, not runtime data")
664 ALL_OPT = cli_option("--all", dest="show_all",
665 default=False, action="store_true",
666 help="Show info on all instances on the cluster."
667 " This can take a long time to run, use wisely")
669 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
670 action="store_true", default=False,
671 help="Interactive OS reinstall, lists available"
672 " OS templates for selection")
674 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
675 action="store_true", default=False,
676 help="Remove the instance from the cluster"
677 " configuration even if there are failures"
678 " during the removal process")
680 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
681 help="Specifies the new secondary node",
682 metavar="NODE", default=None,
683 completion_suggest=OPT_COMPL_ONE_NODE)
685 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
686 default=False, action="store_true",
687 help="Replace the disk(s) on the primary"
688 " node (only for the drbd template)")
690 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
691 default=False, action="store_true",
692 help="Replace the disk(s) on the secondary"
693 " node (only for the drbd template)")
695 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
696 default=False, action="store_true",
697 help="Automatically replace faulty disks"
698 " (only for the drbd template)")
700 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
701 default=False, action="store_true",
702 help="Ignore current recorded size"
703 " (useful for forcing activation when"
704 " the recorded size is wrong)")
706 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
708 completion_suggest=OPT_COMPL_ONE_NODE)
710 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
713 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
714 help="Specify the secondary ip for the node",
715 metavar="ADDRESS", default=None)
717 READD_OPT = cli_option("--readd", dest="readd",
718 default=False, action="store_true",
719 help="Readd old node after replacing it")
721 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
722 default=True, action="store_false",
723 help="Disable SSH key fingerprint checking")
726 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
727 choices=_YESNO, default=None, metavar=_YORNO,
728 help="Set the master_candidate flag on the node")
730 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
731 choices=_YESNO, default=None,
732 help="Set the offline flag on the node")
734 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
735 choices=_YESNO, default=None,
736 help="Set the drained flag on the node")
738 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
739 choices=_YESNO, default=None, metavar=_YORNO,
740 help="Set the allocatable flag on a volume")
742 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
743 help="Disable support for lvm based instances"
745 action="store_false", default=True)
747 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
748 dest="enabled_hypervisors",
749 help="Comma-separated list of hypervisors",
750 type="string", default=None)
752 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
753 type="keyval", default={},
754 help="NIC parameters")
756 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
757 dest="candidate_pool_size", type="int",
758 help="Set the candidate pool size")
760 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
761 help="Enables LVM and specifies the volume group"
762 " name (cluster-wide) for disk allocation [xenvg]",
763 metavar="VG", default=None)
765 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
766 help="Destroy cluster", action="store_true")
768 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
769 help="Skip node agreement check (dangerous)",
770 action="store_true", default=False)
772 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
773 help="Specify the mac prefix for the instance IP"
774 " addresses, in the format XX:XX:XX",
778 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
779 help="Specify the node interface (cluster-wide)"
780 " on which the master IP address will be added "
781 " [%s]" % constants.DEFAULT_BRIDGE,
783 default=constants.DEFAULT_BRIDGE)
786 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
787 help="Specify the default directory (cluster-"
788 "wide) for storing the file-based disks [%s]" %
789 constants.DEFAULT_FILE_STORAGE_DIR,
791 default=constants.DEFAULT_FILE_STORAGE_DIR)
793 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
794 help="Don't modify /etc/hosts",
795 action="store_false", default=True)
797 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
798 help="Enable parseable error messages",
799 action="store_true", default=False)
801 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
802 help="Skip N+1 memory redundancy tests",
803 action="store_true", default=False)
805 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
806 help="Type of reboot: soft/hard/full",
807 default=constants.INSTANCE_REBOOT_HARD,
809 choices=list(constants.REBOOT_TYPES))
811 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
812 dest="ignore_secondaries",
813 default=False, action="store_true",
814 help="Ignore errors from secondaries")
816 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
817 action="store_false", default=True,
818 help="Don't shutdown the instance (unsafe)")
820 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
821 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
822 help="Maximum time to wait")
824 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
825 dest="shutdown_timeout", type="int",
826 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
827 help="Maximum time to wait for instance shutdown")
830 def _ParseArgs(argv, commands, aliases):
831 """Parser for the command line arguments.
833 This function parses the arguments and returns the function which
834 must be executed together with its (modified) arguments.
836 @param argv: the command line
837 @param commands: dictionary with special contents, see the design
838 doc for cmdline handling
839 @param aliases: dictionary with command aliases {'alias': 'target, ...}
845 binary = argv[0].split("/")[-1]
847 if len(argv) > 1 and argv[1] == "--version":
848 ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
849 # Quit right away. That way we don't have to care about this special
850 # argument. optparse.py does it the same.
853 if len(argv) < 2 or not (argv[1] in commands or
855 # let's do a nice thing
856 sortedcmds = commands.keys()
859 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
860 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
863 # compute the max line length for cmd + usage
864 mlen = max([len(" %s" % cmd) for cmd in commands])
865 mlen = min(60, mlen) # should not get here...
867 # and format a nice command list
868 ToStdout("Commands:")
869 for cmd in sortedcmds:
870 cmdstr = " %s" % (cmd,)
871 help_text = commands[cmd][4]
872 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
873 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
874 for line in help_lines:
875 ToStdout("%-*s %s", mlen, "", line)
879 return None, None, None
881 # get command, unalias it, and look it up in commands
885 raise errors.ProgrammerError("Alias '%s' overrides an existing"
888 if aliases[cmd] not in commands:
889 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
890 " command '%s'" % (cmd, aliases[cmd]))
894 func, args_def, parser_opts, usage, description = commands[cmd]
895 parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
896 description=description,
897 formatter=TitledHelpFormatter(),
898 usage="%%prog %s %s" % (cmd, usage))
899 parser.disable_interspersed_args()
900 options, args = parser.parse_args()
902 if not _CheckArguments(cmd, args_def, args):
903 return None, None, None
905 return func, options, args
908 def _CheckArguments(cmd, args_def, args):
909 """Verifies the arguments using the argument definition.
913 1. Abort with error if values specified by user but none expected.
915 1. For each argument in definition
917 1. Keep running count of minimum number of values (min_count)
918 1. Keep running count of maximum number of values (max_count)
919 1. If it has an unlimited number of values
921 1. Abort with error if it's not the last argument in the definition
923 1. If last argument has limited number of values
925 1. Abort with error if number of values doesn't match or is too large
927 1. Abort with error if user didn't pass enough values (min_count)
930 if args and not args_def:
931 ToStderr("Error: Command %s expects no arguments", cmd)
938 last_idx = len(args_def) - 1
940 for idx, arg in enumerate(args_def):
941 if min_count is None:
943 elif arg.min is not None:
946 if max_count is None:
948 elif arg.max is not None:
952 check_max = (arg.max is not None)
954 elif arg.max is None:
955 raise errors.ProgrammerError("Only the last argument can have max=None")
958 # Command with exact number of arguments
959 if (min_count is not None and max_count is not None and
960 min_count == max_count and len(args) != min_count):
961 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
964 # Command with limited number of arguments
965 if max_count is not None and len(args) > max_count:
966 ToStderr("Error: Command %s expects only %d argument(s)",
970 # Command with some required arguments
971 if min_count is not None and len(args) < min_count:
972 ToStderr("Error: Command %s expects at least %d argument(s)",
979 def SplitNodeOption(value):
980 """Splits the value of a --node option.
983 if value and ':' in value:
984 return value.split(':', 1)
989 def CalculateOSNames(os_name, os_variants):
990 """Calculates all the names an OS can be called, according to its variants.
992 @type os_name: string
993 @param os_name: base name of the os
994 @type os_variants: list or None
995 @param os_variants: list of supported variants
997 @return: list of valid names
1001 return ['%s+%s' % (os_name, v) for v in os_variants]
1007 def wrapper(*args, **kwargs):
1010 return fn(*args, **kwargs)
1016 def AskUser(text, choices=None):
1017 """Ask the user a question.
1019 @param text: the question to ask
1021 @param choices: list with elements tuples (input_char, return_value,
1022 description); if not given, it will default to: [('y', True,
1023 'Perform the operation'), ('n', False, 'Do no do the operation')];
1024 note that the '?' char is reserved for help
1026 @return: one of the return values from the choices list; if input is
1027 not possible (i.e. not running with a tty, we return the last
1032 choices = [('y', True, 'Perform the operation'),
1033 ('n', False, 'Do not perform the operation')]
1034 if not choices or not isinstance(choices, list):
1035 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1036 for entry in choices:
1037 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1038 raise errors.ProgrammerError("Invalid choices element to AskUser")
1040 answer = choices[-1][1]
1042 for line in text.splitlines():
1043 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1044 text = "\n".join(new_text)
1046 f = file("/dev/tty", "a+")
1050 chars = [entry[0] for entry in choices]
1051 chars[-1] = "[%s]" % chars[-1]
1053 maps = dict([(entry[0], entry[1]) for entry in choices])
1057 f.write("/".join(chars))
1059 line = f.readline(2).strip().lower()
1064 for entry in choices:
1065 f.write(" %s - %s\n" % (entry[0], entry[2]))
1073 class JobSubmittedException(Exception):
1074 """Job was submitted, client should exit.
1076 This exception has one argument, the ID of the job that was
1077 submitted. The handler should print this ID.
1079 This is not an error, just a structured way to exit from clients.
1084 def SendJob(ops, cl=None):
1085 """Function to submit an opcode without waiting for the results.
1088 @param ops: list of opcodes
1089 @type cl: luxi.Client
1090 @param cl: the luxi client to use for communicating with the master;
1091 if None, a new client will be created
1097 job_id = cl.SubmitJob(ops)
1102 def PollJob(job_id, cl=None, feedback_fn=None):
1103 """Function to poll for the result of a job.
1105 @type job_id: job identified
1106 @param job_id: the job to poll for results
1107 @type cl: luxi.Client
1108 @param cl: the luxi client to use for communicating with the master;
1109 if None, a new client will be created
1115 prev_job_info = None
1116 prev_logmsg_serial = None
1119 result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1122 # job not found, go away!
1123 raise errors.JobLost("Job with id %s lost" % job_id)
1125 # Split result, a tuple of (field values, log entries)
1126 (job_info, log_entries) = result
1127 (status, ) = job_info
1130 for log_entry in log_entries:
1131 (serial, timestamp, _, message) = log_entry
1132 if callable(feedback_fn):
1133 feedback_fn(log_entry[1:])
1135 encoded = utils.SafeEncode(message)
1136 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1137 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1139 # TODO: Handle canceled and archived jobs
1140 elif status in (constants.JOB_STATUS_SUCCESS,
1141 constants.JOB_STATUS_ERROR,
1142 constants.JOB_STATUS_CANCELING,
1143 constants.JOB_STATUS_CANCELED):
1146 prev_job_info = job_info
1148 jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1150 raise errors.JobLost("Job with id %s lost" % job_id)
1152 status, opstatus, result = jobs[0]
1153 if status == constants.JOB_STATUS_SUCCESS:
1155 elif status in (constants.JOB_STATUS_CANCELING,
1156 constants.JOB_STATUS_CANCELED):
1157 raise errors.OpExecError("Job was canceled")
1160 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1161 if status == constants.OP_STATUS_SUCCESS:
1163 elif status == constants.OP_STATUS_ERROR:
1164 errors.MaybeRaise(msg)
1166 raise errors.OpExecError("partial failure (opcode %d): %s" %
1169 raise errors.OpExecError(str(msg))
1170 # default failure mode
1171 raise errors.OpExecError(result)
1174 def SubmitOpCode(op, cl=None, feedback_fn=None):
1175 """Legacy function to submit an opcode.
1177 This is just a simple wrapper over the construction of the processor
1178 instance. It should be extended to better handle feedback and
1179 interaction functions.
1185 job_id = SendJob([op], cl)
1187 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1189 return op_results[0]
1192 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1193 """Wrapper around SubmitOpCode or SendJob.
1195 This function will decide, based on the 'opts' parameter, whether to
1196 submit and wait for the result of the opcode (and return it), or
1197 whether to just send the job and print its identifier. It is used in
1198 order to simplify the implementation of the '--submit' option.
1200 It will also add the dry-run parameter from the options passed, if true.
1203 if opts and opts.dry_run:
1204 op.dry_run = opts.dry_run
1205 if opts and opts.submit_only:
1206 job_id = SendJob([op], cl=cl)
1207 raise JobSubmittedException(job_id)
1209 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1213 # TODO: Cache object?
1215 client = luxi.Client()
1216 except luxi.NoMasterError:
1217 master, myself = ssconf.GetMasterAndMyself()
1218 if master != myself:
1219 raise errors.OpPrereqError("This is not the master node, please connect"
1220 " to node '%s' and rerun the command" %
1227 def FormatError(err):
1228 """Return a formatted error message for a given error.
1230 This function takes an exception instance and returns a tuple
1231 consisting of two values: first, the recommended exit code, and
1232 second, a string describing the error message (not
1233 newline-terminated).
1239 if isinstance(err, errors.ConfigurationError):
1240 txt = "Corrupt configuration file: %s" % msg
1242 obuf.write(txt + "\n")
1243 obuf.write("Aborting.")
1245 elif isinstance(err, errors.HooksAbort):
1246 obuf.write("Failure: hooks execution failed:\n")
1247 for node, script, out in err.args[0]:
1249 obuf.write(" node: %s, script: %s, output: %s\n" %
1250 (node, script, out))
1252 obuf.write(" node: %s, script: %s (no output)\n" %
1254 elif isinstance(err, errors.HooksFailure):
1255 obuf.write("Failure: hooks general failure: %s" % msg)
1256 elif isinstance(err, errors.ResolverError):
1257 this_host = utils.HostInfo.SysName()
1258 if err.args[0] == this_host:
1259 msg = "Failure: can't resolve my own hostname ('%s')"
1261 msg = "Failure: can't resolve hostname '%s'"
1262 obuf.write(msg % err.args[0])
1263 elif isinstance(err, errors.OpPrereqError):
1264 obuf.write("Failure: prerequisites not met for this"
1265 " operation:\n%s" % msg)
1266 elif isinstance(err, errors.OpExecError):
1267 obuf.write("Failure: command execution error:\n%s" % msg)
1268 elif isinstance(err, errors.TagError):
1269 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1270 elif isinstance(err, errors.JobQueueDrainError):
1271 obuf.write("Failure: the job queue is marked for drain and doesn't"
1272 " accept new requests\n")
1273 elif isinstance(err, errors.JobQueueFull):
1274 obuf.write("Failure: the job queue is full and doesn't accept new"
1275 " job submissions until old jobs are archived\n")
1276 elif isinstance(err, errors.TypeEnforcementError):
1277 obuf.write("Parameter Error: %s" % msg)
1278 elif isinstance(err, errors.ParameterError):
1279 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1280 elif isinstance(err, errors.GenericError):
1281 obuf.write("Unhandled Ganeti error: %s" % msg)
1282 elif isinstance(err, luxi.NoMasterError):
1283 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1284 " and listening for connections?")
1285 elif isinstance(err, luxi.TimeoutError):
1286 obuf.write("Timeout while talking to the master daemon. Error:\n"
1288 elif isinstance(err, luxi.ProtocolError):
1289 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1291 elif isinstance(err, JobSubmittedException):
1292 obuf.write("JobID: %s\n" % err.args[0])
1295 obuf.write("Unhandled exception: %s" % msg)
1296 return retcode, obuf.getvalue().rstrip('\n')
1299 def GenericMain(commands, override=None, aliases=None):
1300 """Generic main function for all the gnt-* commands.
1303 - commands: a dictionary with a special structure, see the design doc
1304 for command line handling.
1305 - override: if not None, we expect a dictionary with keys that will
1306 override command line options; this can be used to pass
1307 options from the scripts to generic functions
1308 - aliases: dictionary with command aliases {'alias': 'target, ...}
1311 # save the program name and the entire command line for later logging
1313 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1314 if len(sys.argv) >= 2:
1315 binary += " " + sys.argv[1]
1316 old_cmdline = " ".join(sys.argv[2:])
1320 binary = "<unknown program>"
1327 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1328 except errors.ParameterError, err:
1329 result, err_msg = FormatError(err)
1333 if func is None: # parse error
1336 if override is not None:
1337 for key, val in override.iteritems():
1338 setattr(options, key, val)
1340 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1341 stderr_logging=True, program=binary)
1344 logging.info("run with arguments '%s'", old_cmdline)
1346 logging.info("run with no arguments")
1349 result = func(options, args)
1350 except (errors.GenericError, luxi.ProtocolError,
1351 JobSubmittedException), err:
1352 result, err_msg = FormatError(err)
1353 logging.exception("Error during command processing")
1359 def GenericInstanceCreate(mode, opts, args):
1360 """Add an instance to the cluster via either creation or import.
1362 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1363 @param opts: the command line options selected by the user
1365 @param args: should contain only one element, the new instance name
1367 @return: the desired exit code
1372 (pnode, snode) = SplitNodeOption(opts.node)
1377 hypervisor, hvparams = opts.hypervisor
1381 nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
1382 except ValueError, err:
1383 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1384 nics = [{}] * nic_max
1385 for nidx, ndict in opts.nics:
1387 if not isinstance(ndict, dict):
1388 msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1389 raise errors.OpPrereqError(msg)
1395 # default of one nic, all auto
1398 if opts.disk_template == constants.DT_DISKLESS:
1399 if opts.disks or opts.sd_size is not None:
1400 raise errors.OpPrereqError("Diskless instance but disk"
1401 " information passed")
1404 if not opts.disks and not opts.sd_size:
1405 raise errors.OpPrereqError("No disk information specified")
1406 if opts.disks and opts.sd_size is not None:
1407 raise errors.OpPrereqError("Please use either the '--disk' or"
1409 if opts.sd_size is not None:
1410 opts.disks = [(0, {"size": opts.sd_size})]
1412 disk_max = max(int(didx[0])+1 for didx in opts.disks)
1413 except ValueError, err:
1414 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1415 disks = [{}] * disk_max
1416 for didx, ddict in opts.disks:
1418 if not isinstance(ddict, dict):
1419 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1420 raise errors.OpPrereqError(msg)
1421 elif "size" not in ddict:
1422 raise errors.OpPrereqError("Missing size for disk %d" % didx)
1424 ddict["size"] = utils.ParseUnit(ddict["size"])
1425 except ValueError, err:
1426 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1430 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1431 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1433 if mode == constants.INSTANCE_CREATE:
1438 elif mode == constants.INSTANCE_IMPORT:
1441 src_node = opts.src_node
1442 src_path = opts.src_dir
1444 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1446 op = opcodes.OpCreateInstance(instance_name=instance,
1448 disk_template=opts.disk_template,
1450 pnode=pnode, snode=snode,
1451 ip_check=opts.ip_check,
1452 wait_for_sync=opts.wait_for_sync,
1453 file_storage_dir=opts.file_storage_dir,
1454 file_driver=opts.file_driver,
1455 iallocator=opts.iallocator,
1456 hypervisor=hypervisor,
1458 beparams=opts.beparams,
1465 SubmitOrSend(op, opts)
1469 def GenerateTable(headers, fields, separator, data,
1470 numfields=None, unitfields=None,
1472 """Prints a table with headers and different fields.
1475 @param headers: dictionary mapping field names to headers for
1478 @param fields: the field names corresponding to each row in
1480 @param separator: the separator to be used; if this is None,
1481 the default 'smart' algorithm is used which computes optimal
1482 field width, otherwise just the separator is used between
1485 @param data: a list of lists, each sublist being one row to be output
1486 @type numfields: list
1487 @param numfields: a list with the fields that hold numeric
1488 values and thus should be right-aligned
1489 @type unitfields: list
1490 @param unitfields: a list with the fields that hold numeric
1491 values that should be formatted with the units field
1492 @type units: string or None
1493 @param units: the units we should use for formatting, or None for
1494 automatic choice (human-readable for non-separator usage, otherwise
1495 megabytes); this is a one-letter string
1504 if numfields is None:
1506 if unitfields is None:
1509 numfields = utils.FieldSet(*numfields)
1510 unitfields = utils.FieldSet(*unitfields)
1513 for field in fields:
1514 if headers and field not in headers:
1515 # TODO: handle better unknown fields (either revert to old
1516 # style of raising exception, or deal more intelligently with
1518 headers[field] = field
1519 if separator is not None:
1520 format_fields.append("%s")
1521 elif numfields.Matches(field):
1522 format_fields.append("%*s")
1524 format_fields.append("%-*s")
1526 if separator is None:
1527 mlens = [0 for name in fields]
1528 format = ' '.join(format_fields)
1530 format = separator.replace("%", "%%").join(format_fields)
1535 for idx, val in enumerate(row):
1536 if unitfields.Matches(fields[idx]):
1542 val = row[idx] = utils.FormatUnit(val, units)
1543 val = row[idx] = str(val)
1544 if separator is None:
1545 mlens[idx] = max(mlens[idx], len(val))
1550 for idx, name in enumerate(fields):
1552 if separator is None:
1553 mlens[idx] = max(mlens[idx], len(hdr))
1554 args.append(mlens[idx])
1556 result.append(format % tuple(args))
1561 line = ['-' for _ in fields]
1562 for idx in range(len(fields)):
1563 if separator is None:
1564 args.append(mlens[idx])
1565 args.append(line[idx])
1566 result.append(format % tuple(args))
1571 def FormatTimestamp(ts):
1572 """Formats a given timestamp.
1575 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1578 @return: a string with the formatted timestamp
1581 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1584 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1587 def ParseTimespec(value):
1588 """Parse a time specification.
1590 The following suffixed will be recognized:
1598 Without any suffix, the value will be taken to be in seconds.
1603 raise errors.OpPrereqError("Empty time specification passed")
1611 if value[-1] not in suffix_map:
1615 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1617 multiplier = suffix_map[value[-1]]
1619 if not value: # no data left after stripping the suffix
1620 raise errors.OpPrereqError("Invalid time specification (only"
1623 value = int(value) * multiplier
1625 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1629 def GetOnlineNodes(nodes, cl=None, nowarn=False):
1630 """Returns the names of online nodes.
1632 This function will also log a warning on stderr with the names of
1635 @param nodes: if not empty, use only this subset of nodes (minus the
1637 @param cl: if not None, luxi client to use
1638 @type nowarn: boolean
1639 @param nowarn: by default, this function will output a note with the
1640 offline nodes that are skipped; if this parameter is True the
1641 note is not displayed
1647 result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1649 offline = [row[0] for row in result if row[1]]
1650 if offline and not nowarn:
1651 ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1652 return [row[0] for row in result if not row[1]]
1655 def _ToStream(stream, txt, *args):
1656 """Write a message to a stream, bypassing the logging system
1658 @type stream: file object
1659 @param stream: the file to which we should write
1661 @param txt: the message
1666 stream.write(txt % args)
1673 def ToStdout(txt, *args):
1674 """Write a message to stdout only, bypassing the logging system
1676 This is just a wrapper over _ToStream.
1679 @param txt: the message
1682 _ToStream(sys.stdout, txt, *args)
1685 def ToStderr(txt, *args):
1686 """Write a message to stderr only, bypassing the logging system
1688 This is just a wrapper over _ToStream.
1691 @param txt: the message
1694 _ToStream(sys.stderr, txt, *args)
1697 class JobExecutor(object):
1698 """Class which manages the submission and execution of multiple jobs.
1700 Note that instances of this class should not be reused between
1704 def __init__(self, cl=None, verbose=True):
1709 self.verbose = verbose
1712 def QueueJob(self, name, *ops):
1713 """Record a job for later submit.
1716 @param name: a description of the job, will be used in WaitJobSet
1718 self.queue.append((name, ops))
1720 def SubmitPending(self):
1721 """Submit all pending jobs.
1724 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1725 for ((status, data), (name, _)) in zip(results, self.queue):
1726 self.jobs.append((status, data, name))
1728 def GetResults(self):
1729 """Wait for and return the results of all jobs.
1732 @return: list of tuples (success, job results), in the same order
1733 as the submitted jobs; if a job has failed, instead of the result
1734 there will be the error message
1738 self.SubmitPending()
1741 ok_jobs = [row[1] for row in self.jobs if row[0]]
1743 ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1744 for submit_status, jid, name in self.jobs:
1745 if not submit_status:
1746 ToStderr("Failed to submit job for %s: %s", name, jid)
1747 results.append((False, jid))
1750 ToStdout("Waiting for job %s for %s...", jid, name)
1752 job_result = PollJob(jid, cl=self.cl)
1754 except (errors.GenericError, luxi.ProtocolError), err:
1755 _, job_result = FormatError(err)
1757 # the error message will always be shown, verbose or not
1758 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1760 results.append((success, job_result))
1763 def WaitOrShow(self, wait):
1764 """Wait for job results or only print the job IDs.
1767 @param wait: whether to wait or not
1771 return self.GetResults()
1774 self.SubmitPending()
1775 for status, result, name in self.jobs:
1777 ToStdout("%s: %s", result, name)
1779 ToStderr("Failure for %s: %s", name, result)