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",
71 "IGNORE_FAILURES_OPT",
72 "IGNORE_SECONDARIES_OPT",
86 "NOMODIFY_ETCHOSTS_OPT",
117 # Generic functions for CLI programs
119 "GenericInstanceCreate",
123 "JobSubmittedException",
128 # Formatting functions
129 "ToStderr", "ToStdout",
138 # command line options support infrastructure
139 "ARGS_MANY_INSTANCES",
153 "OPT_COMPL_INST_ADD_NODES",
154 "OPT_COMPL_MANY_NODES",
155 "OPT_COMPL_ONE_IALLOCATOR",
156 "OPT_COMPL_ONE_INSTANCE",
157 "OPT_COMPL_ONE_NODE",
169 def __init__(self, min=0, max=None):
174 return ("<%s min=%s max=%s>" %
175 (self.__class__.__name__, self.min, self.max))
178 class ArgSuggest(_Argument):
179 """Suggesting argument.
181 Value can be any of the ones passed to the constructor.
184 def __init__(self, min=0, max=None, choices=None):
185 _Argument.__init__(self, min=min, max=max)
186 self.choices = choices
189 return ("<%s min=%s max=%s choices=%r>" %
190 (self.__class__.__name__, self.min, self.max, self.choices))
193 class ArgChoice(ArgSuggest):
196 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
197 but value must be one of the choices.
202 class ArgUnknown(_Argument):
203 """Unknown argument to program (e.g. determined at runtime).
208 class ArgInstance(_Argument):
209 """Instances argument.
214 class ArgNode(_Argument):
219 class ArgJobId(_Argument):
225 class ArgFile(_Argument):
226 """File path argument.
231 class ArgCommand(_Argument):
237 class ArgHost(_Argument):
244 ARGS_MANY_INSTANCES = [ArgInstance()]
245 ARGS_MANY_NODES = [ArgNode()]
246 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
247 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
251 def _ExtractTagsObject(opts, args):
252 """Extract the tag type object.
254 Note that this function will modify its args parameter.
257 if not hasattr(opts, "tag_type"):
258 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
260 if kind == constants.TAG_CLUSTER:
262 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
264 raise errors.OpPrereqError("no arguments passed to the command")
268 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
272 def _ExtendTags(opts, args):
273 """Extend the args if a source file has been given.
275 This function will extend the tags with the contents of the file
276 passed in the 'tags_source' attribute of the opts parameter. A file
277 named '-' will be replaced by stdin.
280 fname = opts.tags_source
286 new_fh = open(fname, "r")
289 # we don't use the nice 'new_data = [line.strip() for line in fh]'
290 # because of python bug 1633941
292 line = new_fh.readline()
295 new_data.append(line.strip())
298 args.extend(new_data)
301 def ListTags(opts, args):
302 """List the tags on a given object.
304 This is a generic implementation that knows how to deal with all
305 three cases of tag objects (cluster, node, instance). The opts
306 argument is expected to contain a tag_type field denoting what
307 object type we work on.
310 kind, name = _ExtractTagsObject(opts, args)
311 op = opcodes.OpGetTags(kind=kind, name=name)
312 result = SubmitOpCode(op)
313 result = list(result)
319 def AddTags(opts, args):
320 """Add tags on a given object.
322 This is a generic implementation that knows how to deal with all
323 three cases of tag objects (cluster, node, instance). The opts
324 argument is expected to contain a tag_type field denoting what
325 object type we work on.
328 kind, name = _ExtractTagsObject(opts, args)
329 _ExtendTags(opts, args)
331 raise errors.OpPrereqError("No tags to be added")
332 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
336 def RemoveTags(opts, args):
337 """Remove tags from a given object.
339 This is a generic implementation that knows how to deal with all
340 three cases of tag objects (cluster, node, instance). The opts
341 argument is expected to contain a tag_type field denoting what
342 object type we work on.
345 kind, name = _ExtractTagsObject(opts, args)
346 _ExtendTags(opts, args)
348 raise errors.OpPrereqError("No tags to be removed")
349 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
353 def check_unit(option, opt, value):
354 """OptParsers custom converter for units.
358 return utils.ParseUnit(value)
359 except errors.UnitParseError, err:
360 raise OptionValueError("option %s: %s" % (opt, err))
363 def _SplitKeyVal(opt, data):
364 """Convert a KeyVal string into a dict.
366 This function will convert a key=val[,...] string into a dict. Empty
367 values will be converted specially: keys which have the prefix 'no_'
368 will have the value=False and the prefix stripped, the others will
372 @param opt: a string holding the option name for which we process the
373 data, used in building error messages
375 @param data: a string of the format key=val,key=val,...
377 @return: {key=val, key=val}
378 @raises errors.ParameterError: if there are duplicate keys
383 for elem in data.split(","):
385 key, val = elem.split("=", 1)
387 if elem.startswith(NO_PREFIX):
388 key, val = elem[len(NO_PREFIX):], False
389 elif elem.startswith(UN_PREFIX):
390 key, val = elem[len(UN_PREFIX):], None
392 key, val = elem, True
394 raise errors.ParameterError("Duplicate key '%s' in option %s" %
400 def check_ident_key_val(option, opt, value):
401 """Custom parser for ident:key=val,key=val options.
403 This will store the parsed values as a tuple (ident, {key: val}). As such,
404 multiple uses of this option via action=append is possible.
408 ident, rest = value, ''
410 ident, rest = value.split(":", 1)
412 if ident.startswith(NO_PREFIX):
414 msg = "Cannot pass options when removing parameter groups: %s" % value
415 raise errors.ParameterError(msg)
416 retval = (ident[len(NO_PREFIX):], False)
417 elif ident.startswith(UN_PREFIX):
419 msg = "Cannot pass options when removing parameter groups: %s" % value
420 raise errors.ParameterError(msg)
421 retval = (ident[len(UN_PREFIX):], None)
423 kv_dict = _SplitKeyVal(opt, rest)
424 retval = (ident, kv_dict)
428 def check_key_val(option, opt, value):
429 """Custom parser class for key=val,key=val options.
431 This will store the parsed values as a dict {key: val}.
434 return _SplitKeyVal(opt, value)
437 # completion_suggestion is normally a list. Using numeric values not evaluating
438 # to False for dynamic completion.
439 (OPT_COMPL_MANY_NODES,
441 OPT_COMPL_ONE_INSTANCE,
443 OPT_COMPL_ONE_IALLOCATOR,
444 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
446 OPT_COMPL_ALL = frozenset([
447 OPT_COMPL_MANY_NODES,
449 OPT_COMPL_ONE_INSTANCE,
451 OPT_COMPL_ONE_IALLOCATOR,
452 OPT_COMPL_INST_ADD_NODES,
456 class CliOption(Option):
457 """Custom option class for optparse.
460 ATTRS = Option.ATTRS + [
461 "completion_suggest",
463 TYPES = Option.TYPES + (
468 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
469 TYPE_CHECKER["identkeyval"] = check_ident_key_val
470 TYPE_CHECKER["keyval"] = check_key_val
471 TYPE_CHECKER["unit"] = check_unit
474 # optparse.py sets make_option, so we do it for our own option class, too
475 cli_option = CliOption
478 _YESNO = ("yes", "no")
481 DEBUG_OPT = cli_option("-d", "--debug", default=False,
483 help="Turn debugging on")
485 NOHDR_OPT = cli_option("--no-headers", default=False,
486 action="store_true", dest="no_headers",
487 help="Don't display column headers")
489 SEP_OPT = cli_option("--separator", default=None,
490 action="store", dest="separator",
491 help=("Separator between output fields"
492 " (defaults to one space)"))
494 USEUNITS_OPT = cli_option("--units", default=None,
495 dest="units", choices=('h', 'm', 'g', 't'),
496 help="Specify units for output (one of hmgt)")
498 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
499 type="string", metavar="FIELDS",
500 help="Comma separated list of output fields")
502 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
503 default=False, help="Force the operation")
505 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
506 default=False, help="Do not require confirmation")
508 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
509 default=None, help="File with tag names")
511 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
512 default=False, action="store_true",
513 help=("Submit the job and return the job ID, but"
514 " don't wait for the job to finish"))
516 SYNC_OPT = cli_option("--sync", dest="do_locking",
517 default=False, action="store_true",
518 help=("Grab locks while doing the queries"
519 " in order to ensure more consistent results"))
521 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
523 help=("Do not execute the operation, just run the"
524 " check steps and verify it it could be"
527 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
529 help="Increase the verbosity of the operation")
531 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
532 action="store_true", dest="simulate_errors",
533 help="Debugging option that makes the operation"
534 " treat most runtime checks as failed")
536 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
537 default=True, action="store_false",
538 help="Don't wait for sync (DANGEROUS!)")
540 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
541 help="Custom disk setup (diskless, file,"
543 default=None, metavar="TEMPL",
544 choices=list(constants.DISK_TEMPLATES))
546 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
547 help="Do not create any network cards for"
550 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
551 help="Relative path under default cluster-wide"
552 " file storage dir to store file-based disks",
553 default=None, metavar="<DIR>")
555 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
556 help="Driver to use for image files",
557 default="loop", metavar="<DRIVER>",
558 choices=list(constants.FILE_DRIVER))
560 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
561 help="Select nodes for the instance automatically"
562 " using the <NAME> iallocator plugin",
563 default=None, type="string",
564 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
566 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
568 completion_suggest=OPT_COMPL_ONE_OS)
570 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
571 type="keyval", default={},
572 help="Backend parameters")
574 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
575 default={}, dest="hvparams",
576 help="Hypervisor parameters")
578 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
579 help="Hypervisor and hypervisor options, in the"
580 " format hypervisor:option=value,option=value,...",
581 default=None, type="identkeyval")
583 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
584 help="Hypervisor and hypervisor options, in the"
585 " format hypervisor:option=value,option=value,...",
586 default=[], action="append", type="identkeyval")
588 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
589 action="store_false",
590 help="Don't check that the instance's IP"
593 NET_OPT = cli_option("--net",
594 help="NIC parameters", default=[],
595 dest="nics", action="append", type="identkeyval")
597 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
598 dest="disks", action="append", type="identkeyval")
600 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
601 help="Comma-separated list of disks"
602 " indices to act on (e.g. 0,2) (optional,"
603 " defaults to all disks)")
605 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
606 help="Enforces a single-disk configuration using the"
607 " given disk size, in MiB unless a suffix is used",
608 default=None, type="unit", metavar="<size>")
610 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
611 dest="ignore_consistency",
612 action="store_true", default=False,
613 help="Ignore the consistency of the disks on"
616 NONLIVE_OPT = cli_option("--non-live", dest="live",
617 default=True, action="store_false",
618 help="Do a non-live migration (this usually means"
619 " freeze the instance, save the state, transfer and"
620 " only then resume running on the secondary node)")
622 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
623 help="Target node and optional secondary node",
624 metavar="<pnode>[:<snode>]",
625 completion_suggest=OPT_COMPL_INST_ADD_NODES)
627 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
628 action="append", metavar="<node>",
629 help="Use only this node (can be used multiple"
630 " times, if not given defaults to all nodes)",
631 completion_suggest=OPT_COMPL_ONE_NODE)
633 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
635 completion_suggest=OPT_COMPL_ONE_NODE)
637 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
638 action="store_false",
639 help="Don't start the instance after creation")
641 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
642 action="store_true", default=False,
643 help="Show command instead of executing it")
645 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
646 default=False, action="store_true",
647 help="Instead of performing the migration, try to"
648 " recover from a failed cleanup. This is safe"
649 " to run even if the instance is healthy, but it"
650 " will create extra replication traffic and "
651 " disrupt briefly the replication (like during the"
654 STATIC_OPT = cli_option("-s", "--static", dest="static",
655 action="store_true", default=False,
656 help="Only show configuration data, not runtime data")
658 ALL_OPT = cli_option("--all", dest="show_all",
659 default=False, action="store_true",
660 help="Show info on all instances on the cluster."
661 " This can take a long time to run, use wisely")
663 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
664 action="store_true", default=False,
665 help="Interactive OS reinstall, lists available"
666 " OS templates for selection")
668 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
669 action="store_true", default=False,
670 help="Remove the instance from the cluster"
671 " configuration even if there are failures"
672 " during the removal process")
674 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
675 help="Specifies the new secondary node",
676 metavar="NODE", default=None,
677 completion_suggest=OPT_COMPL_ONE_NODE)
679 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
680 default=False, action="store_true",
681 help="Replace the disk(s) on the primary"
682 " node (only for the drbd template)")
684 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
685 default=False, action="store_true",
686 help="Replace the disk(s) on the secondary"
687 " node (only for the drbd template)")
689 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
690 default=False, action="store_true",
691 help="Automatically replace faulty disks"
692 " (only for the drbd template)")
694 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
695 default=False, action="store_true",
696 help="Ignore current recorded size"
697 " (useful for forcing activation when"
698 " the recorded size is wrong)")
700 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
702 completion_suggest=OPT_COMPL_ONE_NODE)
704 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
707 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
708 help="Specify the secondary ip for the node",
709 metavar="ADDRESS", default=None)
711 READD_OPT = cli_option("--readd", dest="readd",
712 default=False, action="store_true",
713 help="Readd old node after replacing it")
715 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
716 default=True, action="store_false",
717 help="Disable SSH key fingerprint checking")
720 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
721 choices=_YESNO, default=None, metavar=_YORNO,
722 help="Set the master_candidate flag on the node")
724 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
725 choices=_YESNO, default=None,
726 help="Set the offline flag on the node")
728 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
729 choices=_YESNO, default=None,
730 help="Set the drained flag on the node")
732 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
733 choices=_YESNO, default=None, metavar=_YORNO,
734 help="Set the allocatable flag on a volume")
736 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
737 help="Disable support for lvm based instances"
739 action="store_false", default=True)
741 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
742 dest="enabled_hypervisors",
743 help="Comma-separated list of hypervisors",
744 type="string", default=None)
746 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
747 type="keyval", default={},
748 help="NIC parameters")
750 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
751 dest="candidate_pool_size", type="int",
752 help="Set the candidate pool size")
754 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
755 help="Enables LVM and specifies the volume group"
756 " name (cluster-wide) for disk allocation [xenvg]",
757 metavar="VG", default=None)
759 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
760 help="Destroy cluster", action="store_true")
762 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
763 help="Skip node agreement check (dangerous)",
764 action="store_true", default=False)
766 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
767 help="Specify the mac prefix for the instance IP"
768 " addresses, in the format XX:XX:XX",
772 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
773 help="Specify the node interface (cluster-wide)"
774 " on which the master IP address will be added "
775 " [%s]" % constants.DEFAULT_BRIDGE,
777 default=constants.DEFAULT_BRIDGE)
780 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
781 help="Specify the default directory (cluster-"
782 "wide) for storing the file-based disks [%s]" %
783 constants.DEFAULT_FILE_STORAGE_DIR,
785 default=constants.DEFAULT_FILE_STORAGE_DIR)
787 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
788 help="Don't modify /etc/hosts",
789 action="store_false", default=True)
791 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
792 help="Enable parseable error messages",
793 action="store_true", default=False)
795 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
796 help="Skip N+1 memory redundancy tests",
797 action="store_true", default=False)
799 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
800 help="Type of reboot: soft/hard/full",
801 default=constants.INSTANCE_REBOOT_HARD,
803 choices=list(constants.REBOOT_TYPES))
805 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
806 dest="ignore_secondaries",
807 default=False, action="store_true",
808 help="Ignore errors from secondaries")
810 NOSHUTDOWN_OPT = cli_option("","--noshutdown", dest="shutdown",
811 action="store_false", default=True,
812 help="Don't shutdown the instance (unsafe)")
816 def _ParseArgs(argv, commands, aliases):
817 """Parser for the command line arguments.
819 This function parses the arguments and returns the function which
820 must be executed together with its (modified) arguments.
822 @param argv: the command line
823 @param commands: dictionary with special contents, see the design
824 doc for cmdline handling
825 @param aliases: dictionary with command aliases {'alias': 'target, ...}
831 binary = argv[0].split("/")[-1]
833 if len(argv) > 1 and argv[1] == "--version":
834 ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
835 # Quit right away. That way we don't have to care about this special
836 # argument. optparse.py does it the same.
839 if len(argv) < 2 or not (argv[1] in commands or
841 # let's do a nice thing
842 sortedcmds = commands.keys()
845 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
846 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
849 # compute the max line length for cmd + usage
850 mlen = max([len(" %s" % cmd) for cmd in commands])
851 mlen = min(60, mlen) # should not get here...
853 # and format a nice command list
854 ToStdout("Commands:")
855 for cmd in sortedcmds:
856 cmdstr = " %s" % (cmd,)
857 help_text = commands[cmd][4]
858 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
859 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
860 for line in help_lines:
861 ToStdout("%-*s %s", mlen, "", line)
865 return None, None, None
867 # get command, unalias it, and look it up in commands
871 raise errors.ProgrammerError("Alias '%s' overrides an existing"
874 if aliases[cmd] not in commands:
875 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
876 " command '%s'" % (cmd, aliases[cmd]))
880 func, args_def, parser_opts, usage, description = commands[cmd]
881 parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
882 description=description,
883 formatter=TitledHelpFormatter(),
884 usage="%%prog %s %s" % (cmd, usage))
885 parser.disable_interspersed_args()
886 options, args = parser.parse_args()
888 if not _CheckArguments(cmd, args_def, args):
889 return None, None, None
891 return func, options, args
894 def _CheckArguments(cmd, args_def, args):
895 """Verifies the arguments using the argument definition.
899 1. Abort with error if values specified by user but none expected.
901 1. For each argument in definition
903 1. Keep running count of minimum number of values (min_count)
904 1. Keep running count of maximum number of values (max_count)
905 1. If it has an unlimited number of values
907 1. Abort with error if it's not the last argument in the definition
909 1. If last argument has limited number of values
911 1. Abort with error if number of values doesn't match or is too large
913 1. Abort with error if user didn't pass enough values (min_count)
916 if args and not args_def:
917 ToStderr("Error: Command %s expects no arguments", cmd)
924 last_idx = len(args_def) - 1
926 for idx, arg in enumerate(args_def):
927 if min_count is None:
929 elif arg.min is not None:
932 if max_count is None:
934 elif arg.max is not None:
938 check_max = (arg.max is not None)
940 elif arg.max is None:
941 raise errors.ProgrammerError("Only the last argument can have max=None")
944 # Command with exact number of arguments
945 if (min_count is not None and max_count is not None and
946 min_count == max_count and len(args) != min_count):
947 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
950 # Command with limited number of arguments
951 if max_count is not None and len(args) > max_count:
952 ToStderr("Error: Command %s expects only %d argument(s)",
956 # Command with some required arguments
957 if min_count is not None and len(args) < min_count:
958 ToStderr("Error: Command %s expects at least %d argument(s)",
965 def SplitNodeOption(value):
966 """Splits the value of a --node option.
969 if value and ':' in value:
970 return value.split(':', 1)
975 def CalculateOSNames(os_name, os_variants):
976 """Calculates all the names an OS can be called, according to its variants.
978 @type os_name: string
979 @param os_name: base name of the os
980 @type os_variants: list or None
981 @param os_variants: list of supported variants
983 @return: list of valid names
987 return ['%s+%s' % (os_name, v) for v in os_variants]
993 def wrapper(*args, **kwargs):
996 return fn(*args, **kwargs)
1002 def AskUser(text, choices=None):
1003 """Ask the user a question.
1005 @param text: the question to ask
1007 @param choices: list with elements tuples (input_char, return_value,
1008 description); if not given, it will default to: [('y', True,
1009 'Perform the operation'), ('n', False, 'Do no do the operation')];
1010 note that the '?' char is reserved for help
1012 @return: one of the return values from the choices list; if input is
1013 not possible (i.e. not running with a tty, we return the last
1018 choices = [('y', True, 'Perform the operation'),
1019 ('n', False, 'Do not perform the operation')]
1020 if not choices or not isinstance(choices, list):
1021 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1022 for entry in choices:
1023 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1024 raise errors.ProgrammerError("Invalid choices element to AskUser")
1026 answer = choices[-1][1]
1028 for line in text.splitlines():
1029 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1030 text = "\n".join(new_text)
1032 f = file("/dev/tty", "a+")
1036 chars = [entry[0] for entry in choices]
1037 chars[-1] = "[%s]" % chars[-1]
1039 maps = dict([(entry[0], entry[1]) for entry in choices])
1043 f.write("/".join(chars))
1045 line = f.readline(2).strip().lower()
1050 for entry in choices:
1051 f.write(" %s - %s\n" % (entry[0], entry[2]))
1059 class JobSubmittedException(Exception):
1060 """Job was submitted, client should exit.
1062 This exception has one argument, the ID of the job that was
1063 submitted. The handler should print this ID.
1065 This is not an error, just a structured way to exit from clients.
1070 def SendJob(ops, cl=None):
1071 """Function to submit an opcode without waiting for the results.
1074 @param ops: list of opcodes
1075 @type cl: luxi.Client
1076 @param cl: the luxi client to use for communicating with the master;
1077 if None, a new client will be created
1083 job_id = cl.SubmitJob(ops)
1088 def PollJob(job_id, cl=None, feedback_fn=None):
1089 """Function to poll for the result of a job.
1091 @type job_id: job identified
1092 @param job_id: the job to poll for results
1093 @type cl: luxi.Client
1094 @param cl: the luxi client to use for communicating with the master;
1095 if None, a new client will be created
1101 prev_job_info = None
1102 prev_logmsg_serial = None
1105 result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
1108 # job not found, go away!
1109 raise errors.JobLost("Job with id %s lost" % job_id)
1111 # Split result, a tuple of (field values, log entries)
1112 (job_info, log_entries) = result
1113 (status, ) = job_info
1116 for log_entry in log_entries:
1117 (serial, timestamp, _, message) = log_entry
1118 if callable(feedback_fn):
1119 feedback_fn(log_entry[1:])
1121 encoded = utils.SafeEncode(message)
1122 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1123 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1125 # TODO: Handle canceled and archived jobs
1126 elif status in (constants.JOB_STATUS_SUCCESS,
1127 constants.JOB_STATUS_ERROR,
1128 constants.JOB_STATUS_CANCELING,
1129 constants.JOB_STATUS_CANCELED):
1132 prev_job_info = job_info
1134 jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1136 raise errors.JobLost("Job with id %s lost" % job_id)
1138 status, opstatus, result = jobs[0]
1139 if status == constants.JOB_STATUS_SUCCESS:
1141 elif status in (constants.JOB_STATUS_CANCELING,
1142 constants.JOB_STATUS_CANCELED):
1143 raise errors.OpExecError("Job was canceled")
1146 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1147 if status == constants.OP_STATUS_SUCCESS:
1149 elif status == constants.OP_STATUS_ERROR:
1150 errors.MaybeRaise(msg)
1152 raise errors.OpExecError("partial failure (opcode %d): %s" %
1155 raise errors.OpExecError(str(msg))
1156 # default failure mode
1157 raise errors.OpExecError(result)
1160 def SubmitOpCode(op, cl=None, feedback_fn=None):
1161 """Legacy function to submit an opcode.
1163 This is just a simple wrapper over the construction of the processor
1164 instance. It should be extended to better handle feedback and
1165 interaction functions.
1171 job_id = SendJob([op], cl)
1173 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1175 return op_results[0]
1178 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1179 """Wrapper around SubmitOpCode or SendJob.
1181 This function will decide, based on the 'opts' parameter, whether to
1182 submit and wait for the result of the opcode (and return it), or
1183 whether to just send the job and print its identifier. It is used in
1184 order to simplify the implementation of the '--submit' option.
1186 It will also add the dry-run parameter from the options passed, if true.
1189 if opts and opts.dry_run:
1190 op.dry_run = opts.dry_run
1191 if opts and opts.submit_only:
1192 job_id = SendJob([op], cl=cl)
1193 raise JobSubmittedException(job_id)
1195 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
1199 # TODO: Cache object?
1201 client = luxi.Client()
1202 except luxi.NoMasterError:
1203 master, myself = ssconf.GetMasterAndMyself()
1204 if master != myself:
1205 raise errors.OpPrereqError("This is not the master node, please connect"
1206 " to node '%s' and rerun the command" %
1213 def FormatError(err):
1214 """Return a formatted error message for a given error.
1216 This function takes an exception instance and returns a tuple
1217 consisting of two values: first, the recommended exit code, and
1218 second, a string describing the error message (not
1219 newline-terminated).
1225 if isinstance(err, errors.ConfigurationError):
1226 txt = "Corrupt configuration file: %s" % msg
1228 obuf.write(txt + "\n")
1229 obuf.write("Aborting.")
1231 elif isinstance(err, errors.HooksAbort):
1232 obuf.write("Failure: hooks execution failed:\n")
1233 for node, script, out in err.args[0]:
1235 obuf.write(" node: %s, script: %s, output: %s\n" %
1236 (node, script, out))
1238 obuf.write(" node: %s, script: %s (no output)\n" %
1240 elif isinstance(err, errors.HooksFailure):
1241 obuf.write("Failure: hooks general failure: %s" % msg)
1242 elif isinstance(err, errors.ResolverError):
1243 this_host = utils.HostInfo.SysName()
1244 if err.args[0] == this_host:
1245 msg = "Failure: can't resolve my own hostname ('%s')"
1247 msg = "Failure: can't resolve hostname '%s'"
1248 obuf.write(msg % err.args[0])
1249 elif isinstance(err, errors.OpPrereqError):
1250 obuf.write("Failure: prerequisites not met for this"
1251 " operation:\n%s" % msg)
1252 elif isinstance(err, errors.OpExecError):
1253 obuf.write("Failure: command execution error:\n%s" % msg)
1254 elif isinstance(err, errors.TagError):
1255 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1256 elif isinstance(err, errors.JobQueueDrainError):
1257 obuf.write("Failure: the job queue is marked for drain and doesn't"
1258 " accept new requests\n")
1259 elif isinstance(err, errors.JobQueueFull):
1260 obuf.write("Failure: the job queue is full and doesn't accept new"
1261 " job submissions until old jobs are archived\n")
1262 elif isinstance(err, errors.TypeEnforcementError):
1263 obuf.write("Parameter Error: %s" % msg)
1264 elif isinstance(err, errors.ParameterError):
1265 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1266 elif isinstance(err, errors.GenericError):
1267 obuf.write("Unhandled Ganeti error: %s" % msg)
1268 elif isinstance(err, luxi.NoMasterError):
1269 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1270 " and listening for connections?")
1271 elif isinstance(err, luxi.TimeoutError):
1272 obuf.write("Timeout while talking to the master daemon. Error:\n"
1274 elif isinstance(err, luxi.ProtocolError):
1275 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1277 elif isinstance(err, JobSubmittedException):
1278 obuf.write("JobID: %s\n" % err.args[0])
1281 obuf.write("Unhandled exception: %s" % msg)
1282 return retcode, obuf.getvalue().rstrip('\n')
1285 def GenericMain(commands, override=None, aliases=None):
1286 """Generic main function for all the gnt-* commands.
1289 - commands: a dictionary with a special structure, see the design doc
1290 for command line handling.
1291 - override: if not None, we expect a dictionary with keys that will
1292 override command line options; this can be used to pass
1293 options from the scripts to generic functions
1294 - aliases: dictionary with command aliases {'alias': 'target, ...}
1297 # save the program name and the entire command line for later logging
1299 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1300 if len(sys.argv) >= 2:
1301 binary += " " + sys.argv[1]
1302 old_cmdline = " ".join(sys.argv[2:])
1306 binary = "<unknown program>"
1313 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1314 except errors.ParameterError, err:
1315 result, err_msg = FormatError(err)
1319 if func is None: # parse error
1322 if override is not None:
1323 for key, val in override.iteritems():
1324 setattr(options, key, val)
1326 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1327 stderr_logging=True, program=binary)
1330 logging.info("run with arguments '%s'", old_cmdline)
1332 logging.info("run with no arguments")
1335 result = func(options, args)
1336 except (errors.GenericError, luxi.ProtocolError,
1337 JobSubmittedException), err:
1338 result, err_msg = FormatError(err)
1339 logging.exception("Error during command processing")
1345 def GenericInstanceCreate(mode, opts, args):
1346 """Add an instance to the cluster via either creation or import.
1348 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1349 @param opts: the command line options selected by the user
1351 @param args: should contain only one element, the new instance name
1353 @return: the desired exit code
1358 (pnode, snode) = SplitNodeOption(opts.node)
1363 hypervisor, hvparams = opts.hypervisor
1367 nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
1368 except ValueError, err:
1369 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1370 nics = [{}] * nic_max
1371 for nidx, ndict in opts.nics:
1373 if not isinstance(ndict, dict):
1374 msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1375 raise errors.OpPrereqError(msg)
1381 # default of one nic, all auto
1384 if opts.disk_template == constants.DT_DISKLESS:
1385 if opts.disks or opts.sd_size is not None:
1386 raise errors.OpPrereqError("Diskless instance but disk"
1387 " information passed")
1390 if not opts.disks and not opts.sd_size:
1391 raise errors.OpPrereqError("No disk information specified")
1392 if opts.disks and opts.sd_size is not None:
1393 raise errors.OpPrereqError("Please use either the '--disk' or"
1395 if opts.sd_size is not None:
1396 opts.disks = [(0, {"size": opts.sd_size})]
1398 disk_max = max(int(didx[0])+1 for didx in opts.disks)
1399 except ValueError, err:
1400 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1401 disks = [{}] * disk_max
1402 for didx, ddict in opts.disks:
1404 if not isinstance(ddict, dict):
1405 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1406 raise errors.OpPrereqError(msg)
1407 elif "size" not in ddict:
1408 raise errors.OpPrereqError("Missing size for disk %d" % didx)
1410 ddict["size"] = utils.ParseUnit(ddict["size"])
1411 except ValueError, err:
1412 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1416 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1417 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1419 if mode == constants.INSTANCE_CREATE:
1424 elif mode == constants.INSTANCE_IMPORT:
1427 src_node = opts.src_node
1428 src_path = opts.src_dir
1430 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1432 op = opcodes.OpCreateInstance(instance_name=instance,
1434 disk_template=opts.disk_template,
1436 pnode=pnode, snode=snode,
1437 ip_check=opts.ip_check,
1438 wait_for_sync=opts.wait_for_sync,
1439 file_storage_dir=opts.file_storage_dir,
1440 file_driver=opts.file_driver,
1441 iallocator=opts.iallocator,
1442 hypervisor=hypervisor,
1444 beparams=opts.beparams,
1451 SubmitOrSend(op, opts)
1455 def GenerateTable(headers, fields, separator, data,
1456 numfields=None, unitfields=None,
1458 """Prints a table with headers and different fields.
1461 @param headers: dictionary mapping field names to headers for
1464 @param fields: the field names corresponding to each row in
1466 @param separator: the separator to be used; if this is None,
1467 the default 'smart' algorithm is used which computes optimal
1468 field width, otherwise just the separator is used between
1471 @param data: a list of lists, each sublist being one row to be output
1472 @type numfields: list
1473 @param numfields: a list with the fields that hold numeric
1474 values and thus should be right-aligned
1475 @type unitfields: list
1476 @param unitfields: a list with the fields that hold numeric
1477 values that should be formatted with the units field
1478 @type units: string or None
1479 @param units: the units we should use for formatting, or None for
1480 automatic choice (human-readable for non-separator usage, otherwise
1481 megabytes); this is a one-letter string
1490 if numfields is None:
1492 if unitfields is None:
1495 numfields = utils.FieldSet(*numfields)
1496 unitfields = utils.FieldSet(*unitfields)
1499 for field in fields:
1500 if headers and field not in headers:
1501 # TODO: handle better unknown fields (either revert to old
1502 # style of raising exception, or deal more intelligently with
1504 headers[field] = field
1505 if separator is not None:
1506 format_fields.append("%s")
1507 elif numfields.Matches(field):
1508 format_fields.append("%*s")
1510 format_fields.append("%-*s")
1512 if separator is None:
1513 mlens = [0 for name in fields]
1514 format = ' '.join(format_fields)
1516 format = separator.replace("%", "%%").join(format_fields)
1521 for idx, val in enumerate(row):
1522 if unitfields.Matches(fields[idx]):
1528 val = row[idx] = utils.FormatUnit(val, units)
1529 val = row[idx] = str(val)
1530 if separator is None:
1531 mlens[idx] = max(mlens[idx], len(val))
1536 for idx, name in enumerate(fields):
1538 if separator is None:
1539 mlens[idx] = max(mlens[idx], len(hdr))
1540 args.append(mlens[idx])
1542 result.append(format % tuple(args))
1547 line = ['-' for _ in fields]
1548 for idx in range(len(fields)):
1549 if separator is None:
1550 args.append(mlens[idx])
1551 args.append(line[idx])
1552 result.append(format % tuple(args))
1557 def FormatTimestamp(ts):
1558 """Formats a given timestamp.
1561 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1564 @return: a string with the formatted timestamp
1567 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1570 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1573 def ParseTimespec(value):
1574 """Parse a time specification.
1576 The following suffixed will be recognized:
1584 Without any suffix, the value will be taken to be in seconds.
1589 raise errors.OpPrereqError("Empty time specification passed")
1597 if value[-1] not in suffix_map:
1601 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1603 multiplier = suffix_map[value[-1]]
1605 if not value: # no data left after stripping the suffix
1606 raise errors.OpPrereqError("Invalid time specification (only"
1609 value = int(value) * multiplier
1611 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1615 def GetOnlineNodes(nodes, cl=None, nowarn=False):
1616 """Returns the names of online nodes.
1618 This function will also log a warning on stderr with the names of
1621 @param nodes: if not empty, use only this subset of nodes (minus the
1623 @param cl: if not None, luxi client to use
1624 @type nowarn: boolean
1625 @param nowarn: by default, this function will output a note with the
1626 offline nodes that are skipped; if this parameter is True the
1627 note is not displayed
1633 result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1635 offline = [row[0] for row in result if row[1]]
1636 if offline and not nowarn:
1637 ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1638 return [row[0] for row in result if not row[1]]
1641 def _ToStream(stream, txt, *args):
1642 """Write a message to a stream, bypassing the logging system
1644 @type stream: file object
1645 @param stream: the file to which we should write
1647 @param txt: the message
1652 stream.write(txt % args)
1659 def ToStdout(txt, *args):
1660 """Write a message to stdout only, bypassing the logging system
1662 This is just a wrapper over _ToStream.
1665 @param txt: the message
1668 _ToStream(sys.stdout, txt, *args)
1671 def ToStderr(txt, *args):
1672 """Write a message to stderr only, bypassing the logging system
1674 This is just a wrapper over _ToStream.
1677 @param txt: the message
1680 _ToStream(sys.stderr, txt, *args)
1683 class JobExecutor(object):
1684 """Class which manages the submission and execution of multiple jobs.
1686 Note that instances of this class should not be reused between
1690 def __init__(self, cl=None, verbose=True):
1695 self.verbose = verbose
1698 def QueueJob(self, name, *ops):
1699 """Record a job for later submit.
1702 @param name: a description of the job, will be used in WaitJobSet
1704 self.queue.append((name, ops))
1706 def SubmitPending(self):
1707 """Submit all pending jobs.
1710 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1711 for ((status, data), (name, _)) in zip(results, self.queue):
1712 self.jobs.append((status, data, name))
1714 def GetResults(self):
1715 """Wait for and return the results of all jobs.
1718 @return: list of tuples (success, job results), in the same order
1719 as the submitted jobs; if a job has failed, instead of the result
1720 there will be the error message
1724 self.SubmitPending()
1727 ok_jobs = [row[1] for row in self.jobs if row[0]]
1729 ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1730 for submit_status, jid, name in self.jobs:
1731 if not submit_status:
1732 ToStderr("Failed to submit job for %s: %s", name, jid)
1733 results.append((False, jid))
1736 ToStdout("Waiting for job %s for %s...", jid, name)
1738 job_result = PollJob(jid, cl=self.cl)
1740 except (errors.GenericError, luxi.ProtocolError), err:
1741 _, job_result = FormatError(err)
1743 # the error message will always be shown, verbose or not
1744 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1746 results.append((success, job_result))
1749 def WaitOrShow(self, wait):
1750 """Wait for job results or only print the job IDs.
1753 @param wait: whether to wait or not
1757 return self.GetResults()
1760 self.SubmitPending()
1761 for status, result, name in self.jobs:
1763 ToStdout("%s: %s", result, name)
1765 ToStderr("Failure for %s: %s", name, result)