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
64 "FILESTORE_DRIVER_OPT",
73 "IGNORE_FAILURES_OPT",
74 "IGNORE_SECONDARIES_OPT",
87 "NOMODIFY_ETCHOSTS_OPT",
118 # Generic functions for CLI programs
120 "GenericInstanceCreate",
124 "JobSubmittedException",
129 # Formatting functions
130 "ToStderr", "ToStdout",
139 # command line options support infrastructure
140 "ARGS_MANY_INSTANCES",
154 "OPT_COMPL_INST_ADD_NODES",
155 "OPT_COMPL_MANY_NODES",
156 "OPT_COMPL_ONE_IALLOCATOR",
157 "OPT_COMPL_ONE_INSTANCE",
158 "OPT_COMPL_ONE_NODE",
170 def __init__(self, min=0, max=None):
175 return ("<%s min=%s max=%s>" %
176 (self.__class__.__name__, self.min, self.max))
179 class ArgSuggest(_Argument):
180 """Suggesting argument.
182 Value can be any of the ones passed to the constructor.
185 def __init__(self, min=0, max=None, choices=None):
186 _Argument.__init__(self, min=min, max=max)
187 self.choices = choices
190 return ("<%s min=%s max=%s choices=%r>" %
191 (self.__class__.__name__, self.min, self.max, self.choices))
194 class ArgChoice(ArgSuggest):
197 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
198 but value must be one of the choices.
203 class ArgUnknown(_Argument):
204 """Unknown argument to program (e.g. determined at runtime).
209 class ArgInstance(_Argument):
210 """Instances argument.
215 class ArgNode(_Argument):
220 class ArgJobId(_Argument):
226 class ArgFile(_Argument):
227 """File path argument.
232 class ArgCommand(_Argument):
238 class ArgHost(_Argument):
245 ARGS_MANY_INSTANCES = [ArgInstance()]
246 ARGS_MANY_NODES = [ArgNode()]
247 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
248 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
252 def _ExtractTagsObject(opts, args):
253 """Extract the tag type object.
255 Note that this function will modify its args parameter.
258 if not hasattr(opts, "tag_type"):
259 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
261 if kind == constants.TAG_CLUSTER:
263 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
265 raise errors.OpPrereqError("no arguments passed to the command")
269 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
273 def _ExtendTags(opts, args):
274 """Extend the args if a source file has been given.
276 This function will extend the tags with the contents of the file
277 passed in the 'tags_source' attribute of the opts parameter. A file
278 named '-' will be replaced by stdin.
281 fname = opts.tags_source
287 new_fh = open(fname, "r")
290 # we don't use the nice 'new_data = [line.strip() for line in fh]'
291 # because of python bug 1633941
293 line = new_fh.readline()
296 new_data.append(line.strip())
299 args.extend(new_data)
302 def ListTags(opts, args):
303 """List the tags on a given object.
305 This is a generic implementation that knows how to deal with all
306 three cases of tag objects (cluster, node, instance). The opts
307 argument is expected to contain a tag_type field denoting what
308 object type we work on.
311 kind, name = _ExtractTagsObject(opts, args)
312 op = opcodes.OpGetTags(kind=kind, name=name)
313 result = SubmitOpCode(op)
314 result = list(result)
320 def AddTags(opts, args):
321 """Add tags on a given object.
323 This is a generic implementation that knows how to deal with all
324 three cases of tag objects (cluster, node, instance). The opts
325 argument is expected to contain a tag_type field denoting what
326 object type we work on.
329 kind, name = _ExtractTagsObject(opts, args)
330 _ExtendTags(opts, args)
332 raise errors.OpPrereqError("No tags to be added")
333 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
337 def RemoveTags(opts, args):
338 """Remove tags from a given object.
340 This is a generic implementation that knows how to deal with all
341 three cases of tag objects (cluster, node, instance). The opts
342 argument is expected to contain a tag_type field denoting what
343 object type we work on.
346 kind, name = _ExtractTagsObject(opts, args)
347 _ExtendTags(opts, args)
349 raise errors.OpPrereqError("No tags to be removed")
350 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
354 def check_unit(option, opt, value):
355 """OptParsers custom converter for units.
359 return utils.ParseUnit(value)
360 except errors.UnitParseError, err:
361 raise OptionValueError("option %s: %s" % (opt, err))
364 def _SplitKeyVal(opt, data):
365 """Convert a KeyVal string into a dict.
367 This function will convert a key=val[,...] string into a dict. Empty
368 values will be converted specially: keys which have the prefix 'no_'
369 will have the value=False and the prefix stripped, the others will
373 @param opt: a string holding the option name for which we process the
374 data, used in building error messages
376 @param data: a string of the format key=val,key=val,...
378 @return: {key=val, key=val}
379 @raises errors.ParameterError: if there are duplicate keys
384 for elem in data.split(","):
386 key, val = elem.split("=", 1)
388 if elem.startswith(NO_PREFIX):
389 key, val = elem[len(NO_PREFIX):], False
390 elif elem.startswith(UN_PREFIX):
391 key, val = elem[len(UN_PREFIX):], None
393 key, val = elem, True
395 raise errors.ParameterError("Duplicate key '%s' in option %s" %
401 def check_ident_key_val(option, opt, value):
402 """Custom parser for ident:key=val,key=val options.
404 This will store the parsed values as a tuple (ident, {key: val}). As such,
405 multiple uses of this option via action=append is possible.
409 ident, rest = value, ''
411 ident, rest = value.split(":", 1)
413 if ident.startswith(NO_PREFIX):
415 msg = "Cannot pass options when removing parameter groups: %s" % value
416 raise errors.ParameterError(msg)
417 retval = (ident[len(NO_PREFIX):], False)
418 elif ident.startswith(UN_PREFIX):
420 msg = "Cannot pass options when removing parameter groups: %s" % value
421 raise errors.ParameterError(msg)
422 retval = (ident[len(UN_PREFIX):], None)
424 kv_dict = _SplitKeyVal(opt, rest)
425 retval = (ident, kv_dict)
429 def check_key_val(option, opt, value):
430 """Custom parser class for key=val,key=val options.
432 This will store the parsed values as a dict {key: val}.
435 return _SplitKeyVal(opt, value)
438 # completion_suggestion is normally a list. Using numeric values not evaluating
439 # to False for dynamic completion.
440 (OPT_COMPL_MANY_NODES,
442 OPT_COMPL_ONE_INSTANCE,
444 OPT_COMPL_ONE_IALLOCATOR,
445 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
447 OPT_COMPL_ALL = frozenset([
448 OPT_COMPL_MANY_NODES,
450 OPT_COMPL_ONE_INSTANCE,
452 OPT_COMPL_ONE_IALLOCATOR,
453 OPT_COMPL_INST_ADD_NODES,
457 class CliOption(Option):
458 """Custom option class for optparse.
461 ATTRS = Option.ATTRS + [
462 "completion_suggest",
464 TYPES = Option.TYPES + (
469 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
470 TYPE_CHECKER["identkeyval"] = check_ident_key_val
471 TYPE_CHECKER["keyval"] = check_key_val
472 TYPE_CHECKER["unit"] = check_unit
475 # optparse.py sets make_option, so we do it for our own option class, too
476 cli_option = CliOption
479 _YESNO = ("yes", "no")
482 DEBUG_OPT = cli_option("-d", "--debug", default=False,
484 help="Turn debugging on")
486 NOHDR_OPT = cli_option("--no-headers", default=False,
487 action="store_true", dest="no_headers",
488 help="Don't display column headers")
490 SEP_OPT = cli_option("--separator", default=None,
491 action="store", dest="separator",
492 help=("Separator between output fields"
493 " (defaults to one space)"))
495 USEUNITS_OPT = cli_option("--units", default=None,
496 dest="units", choices=('h', 'm', 'g', 't'),
497 help="Specify units for output (one of hmgt)")
499 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
500 type="string", metavar="FIELDS",
501 help="Comma separated list of output fields")
503 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
504 default=False, help="Force the operation")
506 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
507 default=False, help="Do not require confirmation")
509 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
510 default=None, help="File with tag names")
512 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
513 default=False, action="store_true",
514 help=("Submit the job and return the job ID, but"
515 " don't wait for the job to finish"))
517 SYNC_OPT = cli_option("--sync", dest="do_locking",
518 default=False, action="store_true",
519 help=("Grab locks while doing the queries"
520 " in order to ensure more consistent results"))
522 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
524 help=("Do not execute the operation, just run the"
525 " check steps and verify it it could be"
528 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
530 help="Increase the verbosity of the operation")
532 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
533 action="store_true", dest="simulate_errors",
534 help="Debugging option that makes the operation"
535 " treat most runtime checks as failed")
537 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
538 default=True, action="store_false",
539 help="Don't wait for sync (DANGEROUS!)")
541 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
542 help="Custom disk setup (diskless, file,"
544 default=None, metavar="TEMPL",
545 choices=list(constants.DISK_TEMPLATES))
547 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
548 help="Do not create any network cards for"
551 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
552 help="Relative path under default cluster-wide"
553 " file storage dir to store file-based disks",
554 default=None, metavar="<DIR>")
556 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
557 help="Driver to use for image files",
558 default="loop", metavar="<DRIVER>",
559 choices=list(constants.FILE_DRIVER))
561 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
562 help="Select nodes for the instance automatically"
563 " using the <NAME> iallocator plugin",
564 default=None, type="string",
565 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
567 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
569 completion_suggest=OPT_COMPL_ONE_OS)
571 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
572 action="store_true", default=False,
573 help="Force an unknown variant")
575 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
576 type="keyval", default={},
577 help="Backend parameters")
579 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
580 default={}, dest="hvparams",
581 help="Hypervisor parameters")
583 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
584 help="Hypervisor and hypervisor options, in the"
585 " format hypervisor:option=value,option=value,...",
586 default=None, type="identkeyval")
588 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
589 help="Hypervisor and hypervisor options, in the"
590 " format hypervisor:option=value,option=value,...",
591 default=[], action="append", type="identkeyval")
593 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
594 action="store_false",
595 help="Don't check that the instance's IP"
598 NET_OPT = cli_option("--net",
599 help="NIC parameters", default=[],
600 dest="nics", action="append", type="identkeyval")
602 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
603 dest="disks", action="append", type="identkeyval")
605 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
606 help="Comma-separated list of disks"
607 " indices to act on (e.g. 0,2) (optional,"
608 " defaults to all disks)")
610 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
611 help="Enforces a single-disk configuration using the"
612 " given disk size, in MiB unless a suffix is used",
613 default=None, type="unit", metavar="<size>")
615 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
616 dest="ignore_consistency",
617 action="store_true", default=False,
618 help="Ignore the consistency of the disks on"
621 NONLIVE_OPT = cli_option("--non-live", dest="live",
622 default=True, action="store_false",
623 help="Do a non-live migration (this usually means"
624 " freeze the instance, save the state, transfer and"
625 " only then resume running on the secondary node)")
627 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
628 help="Target node and optional secondary node",
629 metavar="<pnode>[:<snode>]",
630 completion_suggest=OPT_COMPL_INST_ADD_NODES)
632 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
633 action="append", metavar="<node>",
634 help="Use only this node (can be used multiple"
635 " times, if not given defaults to all nodes)",
636 completion_suggest=OPT_COMPL_ONE_NODE)
638 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
640 completion_suggest=OPT_COMPL_ONE_NODE)
642 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
643 action="store_false",
644 help="Don't start the instance after creation")
646 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
647 action="store_true", default=False,
648 help="Show command instead of executing it")
650 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
651 default=False, action="store_true",
652 help="Instead of performing the migration, try to"
653 " recover from a failed cleanup. This is safe"
654 " to run even if the instance is healthy, but it"
655 " will create extra replication traffic and "
656 " disrupt briefly the replication (like during the"
659 STATIC_OPT = cli_option("-s", "--static", dest="static",
660 action="store_true", default=False,
661 help="Only show configuration data, not runtime data")
663 ALL_OPT = cli_option("--all", dest="show_all",
664 default=False, action="store_true",
665 help="Show info on all instances on the cluster."
666 " This can take a long time to run, use wisely")
668 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
669 action="store_true", default=False,
670 help="Interactive OS reinstall, lists available"
671 " OS templates for selection")
673 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
674 action="store_true", default=False,
675 help="Remove the instance from the cluster"
676 " configuration even if there are failures"
677 " during the removal process")
679 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
680 help="Specifies the new secondary node",
681 metavar="NODE", default=None,
682 completion_suggest=OPT_COMPL_ONE_NODE)
684 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
685 default=False, action="store_true",
686 help="Replace the disk(s) on the primary"
687 " node (only for the drbd template)")
689 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
690 default=False, action="store_true",
691 help="Replace the disk(s) on the secondary"
692 " node (only for the drbd template)")
694 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
695 default=False, action="store_true",
696 help="Automatically replace faulty disks"
697 " (only for the drbd template)")
699 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
700 default=False, action="store_true",
701 help="Ignore current recorded size"
702 " (useful for forcing activation when"
703 " the recorded size is wrong)")
705 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
707 completion_suggest=OPT_COMPL_ONE_NODE)
709 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
712 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
713 help="Specify the secondary ip for the node",
714 metavar="ADDRESS", default=None)
716 READD_OPT = cli_option("--readd", dest="readd",
717 default=False, action="store_true",
718 help="Readd old node after replacing it")
720 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
721 default=True, action="store_false",
722 help="Disable SSH key fingerprint checking")
725 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
726 choices=_YESNO, default=None, metavar=_YORNO,
727 help="Set the master_candidate flag on the node")
729 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
730 choices=_YESNO, default=None,
731 help="Set the offline flag on the node")
733 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
734 choices=_YESNO, default=None,
735 help="Set the drained flag on the node")
737 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
738 choices=_YESNO, default=None, metavar=_YORNO,
739 help="Set the allocatable flag on a volume")
741 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
742 help="Disable support for lvm based instances"
744 action="store_false", default=True)
746 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
747 dest="enabled_hypervisors",
748 help="Comma-separated list of hypervisors",
749 type="string", default=None)
751 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
752 type="keyval", default={},
753 help="NIC parameters")
755 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
756 dest="candidate_pool_size", type="int",
757 help="Set the candidate pool size")
759 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
760 help="Enables LVM and specifies the volume group"
761 " name (cluster-wide) for disk allocation [xenvg]",
762 metavar="VG", default=None)
764 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
765 help="Destroy cluster", action="store_true")
767 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
768 help="Skip node agreement check (dangerous)",
769 action="store_true", default=False)
771 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
772 help="Specify the mac prefix for the instance IP"
773 " addresses, in the format XX:XX:XX",
777 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
778 help="Specify the node interface (cluster-wide)"
779 " on which the master IP address will be added "
780 " [%s]" % constants.DEFAULT_BRIDGE,
782 default=constants.DEFAULT_BRIDGE)
785 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
786 help="Specify the default directory (cluster-"
787 "wide) for storing the file-based disks [%s]" %
788 constants.DEFAULT_FILE_STORAGE_DIR,
790 default=constants.DEFAULT_FILE_STORAGE_DIR)
792 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
793 help="Don't modify /etc/hosts",
794 action="store_false", default=True)
796 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
797 help="Enable parseable error messages",
798 action="store_true", default=False)
800 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
801 help="Skip N+1 memory redundancy tests",
802 action="store_true", default=False)
804 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
805 help="Type of reboot: soft/hard/full",
806 default=constants.INSTANCE_REBOOT_HARD,
808 choices=list(constants.REBOOT_TYPES))
810 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
811 dest="ignore_secondaries",
812 default=False, action="store_true",
813 help="Ignore errors from secondaries")
815 NOSHUTDOWN_OPT = cli_option("","--noshutdown", dest="shutdown",
816 action="store_false", default=True,
817 help="Don't shutdown the instance (unsafe)")
821 def _ParseArgs(argv, commands, aliases):
822 """Parser for the command line arguments.
824 This function parses the arguments and returns the function which
825 must be executed together with its (modified) arguments.
827 @param argv: the command line
828 @param commands: dictionary with special contents, see the design
829 doc for cmdline handling
830 @param aliases: dictionary with command aliases {'alias': 'target, ...}
836 binary = argv[0].split("/")[-1]
838 if len(argv) > 1 and argv[1] == "--version":
839 ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
840 # Quit right away. That way we don't have to care about this special
841 # argument. optparse.py does it the same.
844 if len(argv) < 2 or not (argv[1] in commands or
846 # let's do a nice thing
847 sortedcmds = commands.keys()
850 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
851 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
854 # compute the max line length for cmd + usage
855 mlen = max([len(" %s" % cmd) for cmd in commands])
856 mlen = min(60, mlen) # should not get here...
858 # and format a nice command list
859 ToStdout("Commands:")
860 for cmd in sortedcmds:
861 cmdstr = " %s" % (cmd,)
862 help_text = commands[cmd][4]
863 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
864 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
865 for line in help_lines:
866 ToStdout("%-*s %s", mlen, "", line)
870 return None, None, None
872 # get command, unalias it, and look it up in commands
876 raise errors.ProgrammerError("Alias '%s' overrides an existing"
879 if aliases[cmd] not in commands:
880 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
881 " command '%s'" % (cmd, aliases[cmd]))
885 func, args_def, parser_opts, usage, description = commands[cmd]
886 parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
887 description=description,
888 formatter=TitledHelpFormatter(),
889 usage="%%prog %s %s" % (cmd, usage))
890 parser.disable_interspersed_args()
891 options, args = parser.parse_args()
893 if not _CheckArguments(cmd, args_def, args):
894 return None, None, None
896 return func, options, args
899 def _CheckArguments(cmd, args_def, args):
900 """Verifies the arguments using the argument definition.
904 1. Abort with error if values specified by user but none expected.
906 1. For each argument in definition
908 1. Keep running count of minimum number of values (min_count)
909 1. Keep running count of maximum number of values (max_count)
910 1. If it has an unlimited number of values
912 1. Abort with error if it's not the last argument in the definition
914 1. If last argument has limited number of values
916 1. Abort with error if number of values doesn't match or is too large
918 1. Abort with error if user didn't pass enough values (min_count)
921 if args and not args_def:
922 ToStderr("Error: Command %s expects no arguments", cmd)
929 last_idx = len(args_def) - 1
931 for idx, arg in enumerate(args_def):
932 if min_count is None:
934 elif arg.min is not None:
937 if max_count is None:
939 elif arg.max is not None:
943 check_max = (arg.max is not None)
945 elif arg.max is None:
946 raise errors.ProgrammerError("Only the last argument can have max=None")
949 # Command with exact number of arguments
950 if (min_count is not None and max_count is not None and
951 min_count == max_count and len(args) != min_count):
952 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
955 # Command with limited number of arguments
956 if max_count is not None and len(args) > max_count:
957 ToStderr("Error: Command %s expects only %d argument(s)",
961 # Command with some required arguments
962 if min_count is not None and len(args) < min_count:
963 ToStderr("Error: Command %s expects at least %d argument(s)",
970 def SplitNodeOption(value):
971 """Splits the value of a --node option.
974 if value and ':' in value:
975 return value.split(':', 1)
980 def CalculateOSNames(os_name, os_variants):
981 """Calculates all the names an OS can be called, according to its variants.
983 @type os_name: string
984 @param os_name: base name of the os
985 @type os_variants: list or None
986 @param os_variants: list of supported variants
988 @return: list of valid names
992 return ['%s+%s' % (os_name, v) for v in os_variants]
998 def wrapper(*args, **kwargs):
1001 return fn(*args, **kwargs)
1007 def AskUser(text, choices=None):
1008 """Ask the user a question.
1010 @param text: the question to ask
1012 @param choices: list with elements tuples (input_char, return_value,
1013 description); if not given, it will default to: [('y', True,
1014 'Perform the operation'), ('n', False, 'Do no do the operation')];
1015 note that the '?' char is reserved for help
1017 @return: one of the return values from the choices list; if input is
1018 not possible (i.e. not running with a tty, we return the last
1023 choices = [('y', True, 'Perform the operation'),
1024 ('n', False, 'Do not perform the operation')]
1025 if not choices or not isinstance(choices, list):
1026 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1027 for entry in choices:
1028 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1029 raise errors.ProgrammerError("Invalid choices element to AskUser")
1031 answer = choices[-1][1]
1033 for line in text.splitlines():
1034 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1035 text = "\n".join(new_text)
1037 f = file("/dev/tty", "a+")
1041 chars = [entry[0] for entry in choices]
1042 chars[-1] = "[%s]" % chars[-1]
1044 maps = dict([(entry[0], entry[1]) for entry in choices])
1048 f.write("/".join(chars))
1050 line = f.readline(2).strip().lower()
1055 for entry in choices:
1056 f.write(" %s - %s\n" % (entry[0], entry[2]))
1064 class JobSubmittedException(Exception):
1065 """Job was submitted, client should exit.
1067 This exception has one argument, the ID of the job that was
1068 submitted. The handler should print this ID.
1070 This is not an error, just a structured way to exit from clients.
1075 def SendJob(ops, cl=None):
1076 """Function to submit an opcode without waiting for the results.
1079 @param ops: list of opcodes
1080 @type cl: luxi.Client
1081 @param cl: the luxi client to use for communicating with the master;
1082 if None, a new client will be created
1088 job_id = cl.SubmitJob(ops)
1093 def PollJob(job_id, cl=None, feedback_fn=None):
1094 """Function to poll for the result of a job.
1096 @type job_id: job identified
1097 @param job_id: the job to poll for results
1098 @type cl: luxi.Client
1099 @param cl: the luxi client to use for communicating with the master;
1100 if None, a new client will be created
1106 prev_job_info = None
1107 prev_logmsg_serial = None
1110 result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1113 # job not found, go away!
1114 raise errors.JobLost("Job with id %s lost" % job_id)
1116 # Split result, a tuple of (field values, log entries)
1117 (job_info, log_entries) = result
1118 (status, ) = job_info
1121 for log_entry in log_entries:
1122 (serial, timestamp, _, message) = log_entry
1123 if callable(feedback_fn):
1124 feedback_fn(log_entry[1:])
1126 encoded = utils.SafeEncode(message)
1127 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1128 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1130 # TODO: Handle canceled and archived jobs
1131 elif status in (constants.JOB_STATUS_SUCCESS,
1132 constants.JOB_STATUS_ERROR,
1133 constants.JOB_STATUS_CANCELING,
1134 constants.JOB_STATUS_CANCELED):
1137 prev_job_info = job_info
1139 jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1141 raise errors.JobLost("Job with id %s lost" % job_id)
1143 status, opstatus, result = jobs[0]
1144 if status == constants.JOB_STATUS_SUCCESS:
1146 elif status in (constants.JOB_STATUS_CANCELING,
1147 constants.JOB_STATUS_CANCELED):
1148 raise errors.OpExecError("Job was canceled")
1151 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1152 if status == constants.OP_STATUS_SUCCESS:
1154 elif status == constants.OP_STATUS_ERROR:
1155 errors.MaybeRaise(msg)
1157 raise errors.OpExecError("partial failure (opcode %d): %s" %
1160 raise errors.OpExecError(str(msg))
1161 # default failure mode
1162 raise errors.OpExecError(result)
1165 def SubmitOpCode(op, cl=None, feedback_fn=None):
1166 """Legacy function to submit an opcode.
1168 This is just a simple wrapper over the construction of the processor
1169 instance. It should be extended to better handle feedback and
1170 interaction functions.
1176 job_id = SendJob([op], cl)
1178 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1180 return op_results[0]
1183 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1184 """Wrapper around SubmitOpCode or SendJob.
1186 This function will decide, based on the 'opts' parameter, whether to
1187 submit and wait for the result of the opcode (and return it), or
1188 whether to just send the job and print its identifier. It is used in
1189 order to simplify the implementation of the '--submit' option.
1191 It will also add the dry-run parameter from the options passed, if true.
1194 if opts and opts.dry_run:
1195 op.dry_run = opts.dry_run
1196 if opts and opts.submit_only:
1197 job_id = SendJob([op], cl=cl)
1198 raise JobSubmittedException(job_id)
1200 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1204 # TODO: Cache object?
1206 client = luxi.Client()
1207 except luxi.NoMasterError:
1208 master, myself = ssconf.GetMasterAndMyself()
1209 if master != myself:
1210 raise errors.OpPrereqError("This is not the master node, please connect"
1211 " to node '%s' and rerun the command" %
1218 def FormatError(err):
1219 """Return a formatted error message for a given error.
1221 This function takes an exception instance and returns a tuple
1222 consisting of two values: first, the recommended exit code, and
1223 second, a string describing the error message (not
1224 newline-terminated).
1230 if isinstance(err, errors.ConfigurationError):
1231 txt = "Corrupt configuration file: %s" % msg
1233 obuf.write(txt + "\n")
1234 obuf.write("Aborting.")
1236 elif isinstance(err, errors.HooksAbort):
1237 obuf.write("Failure: hooks execution failed:\n")
1238 for node, script, out in err.args[0]:
1240 obuf.write(" node: %s, script: %s, output: %s\n" %
1241 (node, script, out))
1243 obuf.write(" node: %s, script: %s (no output)\n" %
1245 elif isinstance(err, errors.HooksFailure):
1246 obuf.write("Failure: hooks general failure: %s" % msg)
1247 elif isinstance(err, errors.ResolverError):
1248 this_host = utils.HostInfo.SysName()
1249 if err.args[0] == this_host:
1250 msg = "Failure: can't resolve my own hostname ('%s')"
1252 msg = "Failure: can't resolve hostname '%s'"
1253 obuf.write(msg % err.args[0])
1254 elif isinstance(err, errors.OpPrereqError):
1255 obuf.write("Failure: prerequisites not met for this"
1256 " operation:\n%s" % msg)
1257 elif isinstance(err, errors.OpExecError):
1258 obuf.write("Failure: command execution error:\n%s" % msg)
1259 elif isinstance(err, errors.TagError):
1260 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1261 elif isinstance(err, errors.JobQueueDrainError):
1262 obuf.write("Failure: the job queue is marked for drain and doesn't"
1263 " accept new requests\n")
1264 elif isinstance(err, errors.JobQueueFull):
1265 obuf.write("Failure: the job queue is full and doesn't accept new"
1266 " job submissions until old jobs are archived\n")
1267 elif isinstance(err, errors.TypeEnforcementError):
1268 obuf.write("Parameter Error: %s" % msg)
1269 elif isinstance(err, errors.ParameterError):
1270 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1271 elif isinstance(err, errors.GenericError):
1272 obuf.write("Unhandled Ganeti error: %s" % msg)
1273 elif isinstance(err, luxi.NoMasterError):
1274 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1275 " and listening for connections?")
1276 elif isinstance(err, luxi.TimeoutError):
1277 obuf.write("Timeout while talking to the master daemon. Error:\n"
1279 elif isinstance(err, luxi.ProtocolError):
1280 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1282 elif isinstance(err, JobSubmittedException):
1283 obuf.write("JobID: %s\n" % err.args[0])
1286 obuf.write("Unhandled exception: %s" % msg)
1287 return retcode, obuf.getvalue().rstrip('\n')
1290 def GenericMain(commands, override=None, aliases=None):
1291 """Generic main function for all the gnt-* commands.
1294 - commands: a dictionary with a special structure, see the design doc
1295 for command line handling.
1296 - override: if not None, we expect a dictionary with keys that will
1297 override command line options; this can be used to pass
1298 options from the scripts to generic functions
1299 - aliases: dictionary with command aliases {'alias': 'target, ...}
1302 # save the program name and the entire command line for later logging
1304 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1305 if len(sys.argv) >= 2:
1306 binary += " " + sys.argv[1]
1307 old_cmdline = " ".join(sys.argv[2:])
1311 binary = "<unknown program>"
1318 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1319 except errors.ParameterError, err:
1320 result, err_msg = FormatError(err)
1324 if func is None: # parse error
1327 if override is not None:
1328 for key, val in override.iteritems():
1329 setattr(options, key, val)
1331 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1332 stderr_logging=True, program=binary)
1335 logging.info("run with arguments '%s'", old_cmdline)
1337 logging.info("run with no arguments")
1340 result = func(options, args)
1341 except (errors.GenericError, luxi.ProtocolError,
1342 JobSubmittedException), err:
1343 result, err_msg = FormatError(err)
1344 logging.exception("Error during command processing")
1350 def GenericInstanceCreate(mode, opts, args):
1351 """Add an instance to the cluster via either creation or import.
1353 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1354 @param opts: the command line options selected by the user
1356 @param args: should contain only one element, the new instance name
1358 @return: the desired exit code
1363 (pnode, snode) = SplitNodeOption(opts.node)
1368 hypervisor, hvparams = opts.hypervisor
1372 nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
1373 except ValueError, err:
1374 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1375 nics = [{}] * nic_max
1376 for nidx, ndict in opts.nics:
1378 if not isinstance(ndict, dict):
1379 msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1380 raise errors.OpPrereqError(msg)
1386 # default of one nic, all auto
1389 if opts.disk_template == constants.DT_DISKLESS:
1390 if opts.disks or opts.sd_size is not None:
1391 raise errors.OpPrereqError("Diskless instance but disk"
1392 " information passed")
1395 if not opts.disks and not opts.sd_size:
1396 raise errors.OpPrereqError("No disk information specified")
1397 if opts.disks and opts.sd_size is not None:
1398 raise errors.OpPrereqError("Please use either the '--disk' or"
1400 if opts.sd_size is not None:
1401 opts.disks = [(0, {"size": opts.sd_size})]
1403 disk_max = max(int(didx[0])+1 for didx in opts.disks)
1404 except ValueError, err:
1405 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1406 disks = [{}] * disk_max
1407 for didx, ddict in opts.disks:
1409 if not isinstance(ddict, dict):
1410 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1411 raise errors.OpPrereqError(msg)
1412 elif "size" not in ddict:
1413 raise errors.OpPrereqError("Missing size for disk %d" % didx)
1415 ddict["size"] = utils.ParseUnit(ddict["size"])
1416 except ValueError, err:
1417 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1421 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1422 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1424 if mode == constants.INSTANCE_CREATE:
1429 elif mode == constants.INSTANCE_IMPORT:
1432 src_node = opts.src_node
1433 src_path = opts.src_dir
1435 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1437 op = opcodes.OpCreateInstance(instance_name=instance,
1439 disk_template=opts.disk_template,
1441 pnode=pnode, snode=snode,
1442 ip_check=opts.ip_check,
1443 wait_for_sync=opts.wait_for_sync,
1444 file_storage_dir=opts.file_storage_dir,
1445 file_driver=opts.file_driver,
1446 iallocator=opts.iallocator,
1447 hypervisor=hypervisor,
1449 beparams=opts.beparams,
1456 SubmitOrSend(op, opts)
1460 def GenerateTable(headers, fields, separator, data,
1461 numfields=None, unitfields=None,
1463 """Prints a table with headers and different fields.
1466 @param headers: dictionary mapping field names to headers for
1469 @param fields: the field names corresponding to each row in
1471 @param separator: the separator to be used; if this is None,
1472 the default 'smart' algorithm is used which computes optimal
1473 field width, otherwise just the separator is used between
1476 @param data: a list of lists, each sublist being one row to be output
1477 @type numfields: list
1478 @param numfields: a list with the fields that hold numeric
1479 values and thus should be right-aligned
1480 @type unitfields: list
1481 @param unitfields: a list with the fields that hold numeric
1482 values that should be formatted with the units field
1483 @type units: string or None
1484 @param units: the units we should use for formatting, or None for
1485 automatic choice (human-readable for non-separator usage, otherwise
1486 megabytes); this is a one-letter string
1495 if numfields is None:
1497 if unitfields is None:
1500 numfields = utils.FieldSet(*numfields)
1501 unitfields = utils.FieldSet(*unitfields)
1504 for field in fields:
1505 if headers and field not in headers:
1506 # TODO: handle better unknown fields (either revert to old
1507 # style of raising exception, or deal more intelligently with
1509 headers[field] = field
1510 if separator is not None:
1511 format_fields.append("%s")
1512 elif numfields.Matches(field):
1513 format_fields.append("%*s")
1515 format_fields.append("%-*s")
1517 if separator is None:
1518 mlens = [0 for name in fields]
1519 format = ' '.join(format_fields)
1521 format = separator.replace("%", "%%").join(format_fields)
1526 for idx, val in enumerate(row):
1527 if unitfields.Matches(fields[idx]):
1533 val = row[idx] = utils.FormatUnit(val, units)
1534 val = row[idx] = str(val)
1535 if separator is None:
1536 mlens[idx] = max(mlens[idx], len(val))
1541 for idx, name in enumerate(fields):
1543 if separator is None:
1544 mlens[idx] = max(mlens[idx], len(hdr))
1545 args.append(mlens[idx])
1547 result.append(format % tuple(args))
1552 line = ['-' for _ in fields]
1553 for idx in range(len(fields)):
1554 if separator is None:
1555 args.append(mlens[idx])
1556 args.append(line[idx])
1557 result.append(format % tuple(args))
1562 def FormatTimestamp(ts):
1563 """Formats a given timestamp.
1566 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1569 @return: a string with the formatted timestamp
1572 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1575 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1578 def ParseTimespec(value):
1579 """Parse a time specification.
1581 The following suffixed will be recognized:
1589 Without any suffix, the value will be taken to be in seconds.
1594 raise errors.OpPrereqError("Empty time specification passed")
1602 if value[-1] not in suffix_map:
1606 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1608 multiplier = suffix_map[value[-1]]
1610 if not value: # no data left after stripping the suffix
1611 raise errors.OpPrereqError("Invalid time specification (only"
1614 value = int(value) * multiplier
1616 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1620 def GetOnlineNodes(nodes, cl=None, nowarn=False):
1621 """Returns the names of online nodes.
1623 This function will also log a warning on stderr with the names of
1626 @param nodes: if not empty, use only this subset of nodes (minus the
1628 @param cl: if not None, luxi client to use
1629 @type nowarn: boolean
1630 @param nowarn: by default, this function will output a note with the
1631 offline nodes that are skipped; if this parameter is True the
1632 note is not displayed
1638 result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1640 offline = [row[0] for row in result if row[1]]
1641 if offline and not nowarn:
1642 ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1643 return [row[0] for row in result if not row[1]]
1646 def _ToStream(stream, txt, *args):
1647 """Write a message to a stream, bypassing the logging system
1649 @type stream: file object
1650 @param stream: the file to which we should write
1652 @param txt: the message
1657 stream.write(txt % args)
1664 def ToStdout(txt, *args):
1665 """Write a message to stdout only, bypassing the logging system
1667 This is just a wrapper over _ToStream.
1670 @param txt: the message
1673 _ToStream(sys.stdout, txt, *args)
1676 def ToStderr(txt, *args):
1677 """Write a message to stderr only, bypassing the logging system
1679 This is just a wrapper over _ToStream.
1682 @param txt: the message
1685 _ToStream(sys.stderr, txt, *args)
1688 class JobExecutor(object):
1689 """Class which manages the submission and execution of multiple jobs.
1691 Note that instances of this class should not be reused between
1695 def __init__(self, cl=None, verbose=True):
1700 self.verbose = verbose
1703 def QueueJob(self, name, *ops):
1704 """Record a job for later submit.
1707 @param name: a description of the job, will be used in WaitJobSet
1709 self.queue.append((name, ops))
1711 def SubmitPending(self):
1712 """Submit all pending jobs.
1715 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1716 for ((status, data), (name, _)) in zip(results, self.queue):
1717 self.jobs.append((status, data, name))
1719 def GetResults(self):
1720 """Wait for and return the results of all jobs.
1723 @return: list of tuples (success, job results), in the same order
1724 as the submitted jobs; if a job has failed, instead of the result
1725 there will be the error message
1729 self.SubmitPending()
1732 ok_jobs = [row[1] for row in self.jobs if row[0]]
1734 ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1735 for submit_status, jid, name in self.jobs:
1736 if not submit_status:
1737 ToStderr("Failed to submit job for %s: %s", name, jid)
1738 results.append((False, jid))
1741 ToStdout("Waiting for job %s for %s...", jid, name)
1743 job_result = PollJob(jid, cl=self.cl)
1745 except (errors.GenericError, luxi.ProtocolError), err:
1746 _, job_result = FormatError(err)
1748 # the error message will always be shown, verbose or not
1749 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1751 results.append((success, job_result))
1754 def WaitOrShow(self, wait):
1755 """Wait for job results or only print the job IDs.
1758 @param wait: whether to wait or not
1762 return self.GetResults()
1765 self.SubmitPending()
1766 for status, result, name in self.jobs:
1768 ToStdout("%s: %s", result, name)
1770 ToStderr("Failure for %s: %s", name, result)