4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 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
39 from ganeti import ssh
40 from ganeti import compat
41 from ganeti import netutils
43 from optparse import (OptionParser, TitledHelpFormatter,
44 Option, OptionValueError)
48 # Command line options
58 "CLUSTER_DOMAIN_SECRET_OPT",
74 "FILESTORE_DRIVER_OPT",
83 "DEFAULT_IALLOCATOR_OPT",
84 "IDENTIFY_DEFAULTS_OPT",
86 "IGNORE_FAILURES_OPT",
88 "IGNORE_REMOVE_FAILURES_OPT",
89 "IGNORE_SECONDARIES_OPT",
93 "MAINTAIN_NODE_HEALTH_OPT",
98 "NEW_CLUSTER_CERT_OPT",
99 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
100 "NEW_CONFD_HMAC_KEY_OPT",
105 "NODE_PLACEMENT_OPT",
107 "NODRBD_STORAGE_OPT",
113 "NOMODIFY_ETCHOSTS_OPT",
114 "NOMODIFY_SSH_SETUP_OPT",
120 "NOSSH_KEYCHECK_OPT",
129 "PREALLOC_WIPE_DISKS_OPT",
130 "PRIMARY_IP_VERSION_OPT",
135 "REMOVE_INSTANCE_OPT",
143 "SHUTDOWN_TIMEOUT_OPT",
158 # Generic functions for CLI programs
160 "GenericInstanceCreate",
164 "JobSubmittedException",
166 "RunWhileClusterStopped",
170 # Formatting functions
171 "ToStderr", "ToStdout",
181 # command line options support infrastructure
182 "ARGS_MANY_INSTANCES",
198 "OPT_COMPL_INST_ADD_NODES",
199 "OPT_COMPL_MANY_NODES",
200 "OPT_COMPL_ONE_IALLOCATOR",
201 "OPT_COMPL_ONE_INSTANCE",
202 "OPT_COMPL_ONE_NODE",
203 "OPT_COMPL_ONE_NODEGROUP",
214 #: Priorities (sorted)
216 ("low", constants.OP_PRIO_LOW),
217 ("normal", constants.OP_PRIO_NORMAL),
218 ("high", constants.OP_PRIO_HIGH),
221 #: Priority dictionary for easier lookup
222 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
223 # we migrate to Python 2.6
224 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
228 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
233 return ("<%s min=%s max=%s>" %
234 (self.__class__.__name__, self.min, self.max))
237 class ArgSuggest(_Argument):
238 """Suggesting argument.
240 Value can be any of the ones passed to the constructor.
243 # pylint: disable-msg=W0622
244 def __init__(self, min=0, max=None, choices=None):
245 _Argument.__init__(self, min=min, max=max)
246 self.choices = choices
249 return ("<%s min=%s max=%s choices=%r>" %
250 (self.__class__.__name__, self.min, self.max, self.choices))
253 class ArgChoice(ArgSuggest):
256 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
257 but value must be one of the choices.
262 class ArgUnknown(_Argument):
263 """Unknown argument to program (e.g. determined at runtime).
268 class ArgInstance(_Argument):
269 """Instances argument.
274 class ArgNode(_Argument):
279 class ArgJobId(_Argument):
285 class ArgFile(_Argument):
286 """File path argument.
291 class ArgCommand(_Argument):
297 class ArgHost(_Argument):
303 class ArgOs(_Argument):
310 ARGS_MANY_INSTANCES = [ArgInstance()]
311 ARGS_MANY_NODES = [ArgNode()]
312 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
313 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
314 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
317 def _ExtractTagsObject(opts, args):
318 """Extract the tag type object.
320 Note that this function will modify its args parameter.
323 if not hasattr(opts, "tag_type"):
324 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
326 if kind == constants.TAG_CLUSTER:
328 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
330 raise errors.OpPrereqError("no arguments passed to the command")
334 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
338 def _ExtendTags(opts, args):
339 """Extend the args if a source file has been given.
341 This function will extend the tags with the contents of the file
342 passed in the 'tags_source' attribute of the opts parameter. A file
343 named '-' will be replaced by stdin.
346 fname = opts.tags_source
352 new_fh = open(fname, "r")
355 # we don't use the nice 'new_data = [line.strip() for line in fh]'
356 # because of python bug 1633941
358 line = new_fh.readline()
361 new_data.append(line.strip())
364 args.extend(new_data)
367 def ListTags(opts, args):
368 """List the tags on a given object.
370 This is a generic implementation that knows how to deal with all
371 three cases of tag objects (cluster, node, instance). The opts
372 argument is expected to contain a tag_type field denoting what
373 object type we work on.
376 kind, name = _ExtractTagsObject(opts, args)
378 result = cl.QueryTags(kind, name)
379 result = list(result)
385 def AddTags(opts, args):
386 """Add tags on a given object.
388 This is a generic implementation that knows how to deal with all
389 three cases of tag objects (cluster, node, instance). The opts
390 argument is expected to contain a tag_type field denoting what
391 object type we work on.
394 kind, name = _ExtractTagsObject(opts, args)
395 _ExtendTags(opts, args)
397 raise errors.OpPrereqError("No tags to be added")
398 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
399 SubmitOpCode(op, opts=opts)
402 def RemoveTags(opts, args):
403 """Remove tags from a given object.
405 This is a generic implementation that knows how to deal with all
406 three cases of tag objects (cluster, node, instance). The opts
407 argument is expected to contain a tag_type field denoting what
408 object type we work on.
411 kind, name = _ExtractTagsObject(opts, args)
412 _ExtendTags(opts, args)
414 raise errors.OpPrereqError("No tags to be removed")
415 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
416 SubmitOpCode(op, opts=opts)
419 def check_unit(option, opt, value): # pylint: disable-msg=W0613
420 """OptParsers custom converter for units.
424 return utils.ParseUnit(value)
425 except errors.UnitParseError, err:
426 raise OptionValueError("option %s: %s" % (opt, err))
429 def _SplitKeyVal(opt, data):
430 """Convert a KeyVal string into a dict.
432 This function will convert a key=val[,...] string into a dict. Empty
433 values will be converted specially: keys which have the prefix 'no_'
434 will have the value=False and the prefix stripped, the others will
438 @param opt: a string holding the option name for which we process the
439 data, used in building error messages
441 @param data: a string of the format key=val,key=val,...
443 @return: {key=val, key=val}
444 @raises errors.ParameterError: if there are duplicate keys
449 for elem in utils.UnescapeAndSplit(data, sep=","):
451 key, val = elem.split("=", 1)
453 if elem.startswith(NO_PREFIX):
454 key, val = elem[len(NO_PREFIX):], False
455 elif elem.startswith(UN_PREFIX):
456 key, val = elem[len(UN_PREFIX):], None
458 key, val = elem, True
460 raise errors.ParameterError("Duplicate key '%s' in option %s" %
466 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
467 """Custom parser for ident:key=val,key=val options.
469 This will store the parsed values as a tuple (ident, {key: val}). As such,
470 multiple uses of this option via action=append is possible.
474 ident, rest = value, ''
476 ident, rest = value.split(":", 1)
478 if ident.startswith(NO_PREFIX):
480 msg = "Cannot pass options when removing parameter groups: %s" % value
481 raise errors.ParameterError(msg)
482 retval = (ident[len(NO_PREFIX):], False)
483 elif ident.startswith(UN_PREFIX):
485 msg = "Cannot pass options when removing parameter groups: %s" % value
486 raise errors.ParameterError(msg)
487 retval = (ident[len(UN_PREFIX):], None)
489 kv_dict = _SplitKeyVal(opt, rest)
490 retval = (ident, kv_dict)
494 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
495 """Custom parser class for key=val,key=val options.
497 This will store the parsed values as a dict {key: val}.
500 return _SplitKeyVal(opt, value)
503 def check_bool(option, opt, value): # pylint: disable-msg=W0613
504 """Custom parser for yes/no options.
506 This will store the parsed value as either True or False.
509 value = value.lower()
510 if value == constants.VALUE_FALSE or value == "no":
512 elif value == constants.VALUE_TRUE or value == "yes":
515 raise errors.ParameterError("Invalid boolean value '%s'" % value)
518 # completion_suggestion is normally a list. Using numeric values not evaluating
519 # to False for dynamic completion.
520 (OPT_COMPL_MANY_NODES,
522 OPT_COMPL_ONE_INSTANCE,
524 OPT_COMPL_ONE_IALLOCATOR,
525 OPT_COMPL_INST_ADD_NODES,
526 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
528 OPT_COMPL_ALL = frozenset([
529 OPT_COMPL_MANY_NODES,
531 OPT_COMPL_ONE_INSTANCE,
533 OPT_COMPL_ONE_IALLOCATOR,
534 OPT_COMPL_INST_ADD_NODES,
535 OPT_COMPL_ONE_NODEGROUP,
539 class CliOption(Option):
540 """Custom option class for optparse.
543 ATTRS = Option.ATTRS + [
544 "completion_suggest",
546 TYPES = Option.TYPES + (
552 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
553 TYPE_CHECKER["identkeyval"] = check_ident_key_val
554 TYPE_CHECKER["keyval"] = check_key_val
555 TYPE_CHECKER["unit"] = check_unit
556 TYPE_CHECKER["bool"] = check_bool
559 # optparse.py sets make_option, so we do it for our own option class, too
560 cli_option = CliOption
565 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
566 help="Increase debugging level")
568 NOHDR_OPT = cli_option("--no-headers", default=False,
569 action="store_true", dest="no_headers",
570 help="Don't display column headers")
572 SEP_OPT = cli_option("--separator", default=None,
573 action="store", dest="separator",
574 help=("Separator between output fields"
575 " (defaults to one space)"))
577 USEUNITS_OPT = cli_option("--units", default=None,
578 dest="units", choices=('h', 'm', 'g', 't'),
579 help="Specify units for output (one of hmgt)")
581 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
582 type="string", metavar="FIELDS",
583 help="Comma separated list of output fields")
585 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
586 default=False, help="Force the operation")
588 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
589 default=False, help="Do not require confirmation")
591 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
592 action="store_true", default=False,
593 help=("Ignore offline nodes and do as much"
596 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
597 default=None, help="File with tag names")
599 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
600 default=False, action="store_true",
601 help=("Submit the job and return the job ID, but"
602 " don't wait for the job to finish"))
604 SYNC_OPT = cli_option("--sync", dest="do_locking",
605 default=False, action="store_true",
606 help=("Grab locks while doing the queries"
607 " in order to ensure more consistent results"))
609 DRY_RUN_OPT = cli_option("--dry-run", default=False,
611 help=("Do not execute the operation, just run the"
612 " check steps and verify it it could be"
615 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
617 help="Increase the verbosity of the operation")
619 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
620 action="store_true", dest="simulate_errors",
621 help="Debugging option that makes the operation"
622 " treat most runtime checks as failed")
624 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
625 default=True, action="store_false",
626 help="Don't wait for sync (DANGEROUS!)")
628 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
629 help="Custom disk setup (diskless, file,"
631 default=None, metavar="TEMPL",
632 choices=list(constants.DISK_TEMPLATES))
634 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
635 help="Do not create any network cards for"
638 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
639 help="Relative path under default cluster-wide"
640 " file storage dir to store file-based disks",
641 default=None, metavar="<DIR>")
643 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
644 help="Driver to use for image files",
645 default="loop", metavar="<DRIVER>",
646 choices=list(constants.FILE_DRIVER))
648 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
649 help="Select nodes for the instance automatically"
650 " using the <NAME> iallocator plugin",
651 default=None, type="string",
652 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
654 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
656 help="Set the default instance allocator plugin",
657 default=None, type="string",
658 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
660 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
662 completion_suggest=OPT_COMPL_ONE_OS)
664 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
665 type="keyval", default={},
666 help="OS parameters")
668 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
669 action="store_true", default=False,
670 help="Force an unknown variant")
672 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
673 action="store_true", default=False,
674 help="Do not install the OS (will"
677 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
678 type="keyval", default={},
679 help="Backend parameters")
681 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
682 default={}, dest="hvparams",
683 help="Hypervisor parameters")
685 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
686 help="Hypervisor and hypervisor options, in the"
687 " format hypervisor:option=value,option=value,...",
688 default=None, type="identkeyval")
690 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
691 help="Hypervisor and hypervisor options, in the"
692 " format hypervisor:option=value,option=value,...",
693 default=[], action="append", type="identkeyval")
695 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
696 action="store_false",
697 help="Don't check that the instance's IP"
700 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
701 default=True, action="store_false",
702 help="Don't check that the instance's name"
705 NET_OPT = cli_option("--net",
706 help="NIC parameters", default=[],
707 dest="nics", action="append", type="identkeyval")
709 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
710 dest="disks", action="append", type="identkeyval")
712 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
713 help="Comma-separated list of disks"
714 " indices to act on (e.g. 0,2) (optional,"
715 " defaults to all disks)")
717 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
718 help="Enforces a single-disk configuration using the"
719 " given disk size, in MiB unless a suffix is used",
720 default=None, type="unit", metavar="<size>")
722 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
723 dest="ignore_consistency",
724 action="store_true", default=False,
725 help="Ignore the consistency of the disks on"
728 NONLIVE_OPT = cli_option("--non-live", dest="live",
729 default=True, action="store_false",
730 help="Do a non-live migration (this usually means"
731 " freeze the instance, save the state, transfer and"
732 " only then resume running on the secondary node)")
734 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
736 choices=list(constants.HT_MIGRATION_MODES),
737 help="Override default migration mode (choose"
738 " either live or non-live")
740 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
741 help="Target node and optional secondary node",
742 metavar="<pnode>[:<snode>]",
743 completion_suggest=OPT_COMPL_INST_ADD_NODES)
745 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
746 action="append", metavar="<node>",
747 help="Use only this node (can be used multiple"
748 " times, if not given defaults to all nodes)",
749 completion_suggest=OPT_COMPL_ONE_NODE)
751 NODEGROUP_OPT = cli_option("-g", "--node-group",
753 help="Node group (name or uuid)",
754 metavar="<nodegroup>",
755 default=None, type="string",
756 completion_suggest=OPT_COMPL_ONE_NODEGROUP)
758 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
760 completion_suggest=OPT_COMPL_ONE_NODE)
762 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
763 action="store_false",
764 help="Don't start the instance after creation")
766 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
767 action="store_true", default=False,
768 help="Show command instead of executing it")
770 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
771 default=False, action="store_true",
772 help="Instead of performing the migration, try to"
773 " recover from a failed cleanup. This is safe"
774 " to run even if the instance is healthy, but it"
775 " will create extra replication traffic and "
776 " disrupt briefly the replication (like during the"
779 STATIC_OPT = cli_option("-s", "--static", dest="static",
780 action="store_true", default=False,
781 help="Only show configuration data, not runtime data")
783 ALL_OPT = cli_option("--all", dest="show_all",
784 default=False, action="store_true",
785 help="Show info on all instances on the cluster."
786 " This can take a long time to run, use wisely")
788 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
789 action="store_true", default=False,
790 help="Interactive OS reinstall, lists available"
791 " OS templates for selection")
793 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
794 action="store_true", default=False,
795 help="Remove the instance from the cluster"
796 " configuration even if there are failures"
797 " during the removal process")
799 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
800 dest="ignore_remove_failures",
801 action="store_true", default=False,
802 help="Remove the instance from the"
803 " cluster configuration even if there"
804 " are failures during the removal"
807 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
808 action="store_true", default=False,
809 help="Remove the instance from the cluster")
811 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
812 help="Specifies the new secondary node",
813 metavar="NODE", default=None,
814 completion_suggest=OPT_COMPL_ONE_NODE)
816 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
817 default=False, action="store_true",
818 help="Replace the disk(s) on the primary"
819 " node (only for the drbd template)")
821 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
822 default=False, action="store_true",
823 help="Replace the disk(s) on the secondary"
824 " node (only for the drbd template)")
826 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
827 default=False, action="store_true",
828 help="Lock all nodes and auto-promote as needed"
831 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
832 default=False, action="store_true",
833 help="Automatically replace faulty disks"
834 " (only for the drbd template)")
836 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
837 default=False, action="store_true",
838 help="Ignore current recorded size"
839 " (useful for forcing activation when"
840 " the recorded size is wrong)")
842 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
844 completion_suggest=OPT_COMPL_ONE_NODE)
846 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
849 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
850 help="Specify the secondary ip for the node",
851 metavar="ADDRESS", default=None)
853 READD_OPT = cli_option("--readd", dest="readd",
854 default=False, action="store_true",
855 help="Readd old node after replacing it")
857 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
858 default=True, action="store_false",
859 help="Disable SSH key fingerprint checking")
862 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
863 type="bool", default=None, metavar=_YORNO,
864 help="Set the master_candidate flag on the node")
866 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
867 type="bool", default=None,
868 help="Set the offline flag on the node")
870 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
871 type="bool", default=None,
872 help="Set the drained flag on the node")
874 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
875 type="bool", default=None, metavar=_YORNO,
876 help="Set the master_capable flag on the node")
878 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
879 type="bool", default=None, metavar=_YORNO,
880 help="Set the allocatable flag on a volume")
882 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
883 help="Disable support for lvm based instances"
885 action="store_false", default=True)
887 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
888 dest="enabled_hypervisors",
889 help="Comma-separated list of hypervisors",
890 type="string", default=None)
892 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
893 type="keyval", default={},
894 help="NIC parameters")
896 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
897 dest="candidate_pool_size", type="int",
898 help="Set the candidate pool size")
900 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
901 help="Enables LVM and specifies the volume group"
902 " name (cluster-wide) for disk allocation [xenvg]",
903 metavar="VG", default=None)
905 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
906 help="Destroy cluster", action="store_true")
908 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
909 help="Skip node agreement check (dangerous)",
910 action="store_true", default=False)
912 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
913 help="Specify the mac prefix for the instance IP"
914 " addresses, in the format XX:XX:XX",
918 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
919 help="Specify the node interface (cluster-wide)"
920 " on which the master IP address will be added "
921 " [%s]" % constants.DEFAULT_BRIDGE,
923 default=constants.DEFAULT_BRIDGE)
925 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
926 help="Specify the default directory (cluster-"
927 "wide) for storing the file-based disks [%s]" %
928 constants.DEFAULT_FILE_STORAGE_DIR,
930 default=constants.DEFAULT_FILE_STORAGE_DIR)
932 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
933 help="Don't modify /etc/hosts",
934 action="store_false", default=True)
936 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
937 help="Don't initialize SSH keys",
938 action="store_false", default=True)
940 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
941 help="Enable parseable error messages",
942 action="store_true", default=False)
944 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
945 help="Skip N+1 memory redundancy tests",
946 action="store_true", default=False)
948 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
949 help="Type of reboot: soft/hard/full",
950 default=constants.INSTANCE_REBOOT_HARD,
952 choices=list(constants.REBOOT_TYPES))
954 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
955 dest="ignore_secondaries",
956 default=False, action="store_true",
957 help="Ignore errors from secondaries")
959 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
960 action="store_false", default=True,
961 help="Don't shutdown the instance (unsafe)")
963 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
964 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
965 help="Maximum time to wait")
967 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
968 dest="shutdown_timeout", type="int",
969 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
970 help="Maximum time to wait for instance shutdown")
972 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
974 help=("Number of seconds between repetions of the"
977 EARLY_RELEASE_OPT = cli_option("--early-release",
978 dest="early_release", default=False,
980 help="Release the locks on the secondary"
983 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
984 dest="new_cluster_cert",
985 default=False, action="store_true",
986 help="Generate a new cluster certificate")
988 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
990 help="File containing new RAPI certificate")
992 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
993 default=None, action="store_true",
994 help=("Generate a new self-signed RAPI"
997 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
998 dest="new_confd_hmac_key",
999 default=False, action="store_true",
1000 help=("Create a new HMAC key for %s" %
1003 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1004 dest="cluster_domain_secret",
1006 help=("Load new new cluster domain"
1007 " secret from file"))
1009 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1010 dest="new_cluster_domain_secret",
1011 default=False, action="store_true",
1012 help=("Create a new cluster domain"
1015 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1016 dest="use_replication_network",
1017 help="Whether to use the replication network"
1018 " for talking to the nodes",
1019 action="store_true", default=False)
1021 MAINTAIN_NODE_HEALTH_OPT = \
1022 cli_option("--maintain-node-health", dest="maintain_node_health",
1023 metavar=_YORNO, default=None, type="bool",
1024 help="Configure the cluster to automatically maintain node"
1025 " health, by shutting down unknown instances, shutting down"
1026 " unknown DRBD devices, etc.")
1028 IDENTIFY_DEFAULTS_OPT = \
1029 cli_option("--identify-defaults", dest="identify_defaults",
1030 default=False, action="store_true",
1031 help="Identify which saved instance parameters are equal to"
1032 " the current cluster defaults and set them as such, instead"
1033 " of marking them as overridden")
1035 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1036 action="store", dest="uid_pool",
1037 help=("A list of user-ids or user-id"
1038 " ranges separated by commas"))
1040 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1041 action="store", dest="add_uids",
1042 help=("A list of user-ids or user-id"
1043 " ranges separated by commas, to be"
1044 " added to the user-id pool"))
1046 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1047 action="store", dest="remove_uids",
1048 help=("A list of user-ids or user-id"
1049 " ranges separated by commas, to be"
1050 " removed from the user-id pool"))
1052 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1053 action="store", dest="reserved_lvs",
1054 help=("A comma-separated list of reserved"
1055 " logical volumes names, that will be"
1056 " ignored by cluster verify"))
1058 ROMAN_OPT = cli_option("--roman",
1059 dest="roman_integers", default=False,
1060 action="store_true",
1061 help="Use roman numbers for positive integers")
1063 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1064 action="store", default=None,
1065 help="Specifies usermode helper for DRBD")
1067 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1068 action="store_false", default=True,
1069 help="Disable support for DRBD")
1071 PRIMARY_IP_VERSION_OPT = \
1072 cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1073 action="store", dest="primary_ip_version",
1074 metavar="%d|%d" % (constants.IP4_VERSION,
1075 constants.IP6_VERSION),
1076 help="Cluster-wide IP version for primary IP")
1078 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1079 metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1080 choices=_PRIONAME_TO_VALUE.keys(),
1081 help="Priority for opcode processing")
1083 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1084 type="bool", default=None, metavar=_YORNO,
1085 help="Sets the hidden flag on the OS")
1087 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1088 type="bool", default=None, metavar=_YORNO,
1089 help="Sets the blacklisted flag on the OS")
1091 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1092 type="bool", metavar=_YORNO,
1093 dest="prealloc_wipe_disks",
1094 help=("Wipe disks prior to instance"
1098 #: Options provided by all commands
1099 COMMON_OPTS = [DEBUG_OPT]
1102 def _ParseArgs(argv, commands, aliases):
1103 """Parser for the command line arguments.
1105 This function parses the arguments and returns the function which
1106 must be executed together with its (modified) arguments.
1108 @param argv: the command line
1109 @param commands: dictionary with special contents, see the design
1110 doc for cmdline handling
1111 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1115 binary = "<command>"
1117 binary = argv[0].split("/")[-1]
1119 if len(argv) > 1 and argv[1] == "--version":
1120 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1121 constants.RELEASE_VERSION)
1122 # Quit right away. That way we don't have to care about this special
1123 # argument. optparse.py does it the same.
1126 if len(argv) < 2 or not (argv[1] in commands or
1127 argv[1] in aliases):
1128 # let's do a nice thing
1129 sortedcmds = commands.keys()
1132 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1133 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1136 # compute the max line length for cmd + usage
1137 mlen = max([len(" %s" % cmd) for cmd in commands])
1138 mlen = min(60, mlen) # should not get here...
1140 # and format a nice command list
1141 ToStdout("Commands:")
1142 for cmd in sortedcmds:
1143 cmdstr = " %s" % (cmd,)
1144 help_text = commands[cmd][4]
1145 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1146 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1147 for line in help_lines:
1148 ToStdout("%-*s %s", mlen, "", line)
1152 return None, None, None
1154 # get command, unalias it, and look it up in commands
1158 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1161 if aliases[cmd] not in commands:
1162 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1163 " command '%s'" % (cmd, aliases[cmd]))
1167 func, args_def, parser_opts, usage, description = commands[cmd]
1168 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1169 description=description,
1170 formatter=TitledHelpFormatter(),
1171 usage="%%prog %s %s" % (cmd, usage))
1172 parser.disable_interspersed_args()
1173 options, args = parser.parse_args()
1175 if not _CheckArguments(cmd, args_def, args):
1176 return None, None, None
1178 return func, options, args
1181 def _CheckArguments(cmd, args_def, args):
1182 """Verifies the arguments using the argument definition.
1186 1. Abort with error if values specified by user but none expected.
1188 1. For each argument in definition
1190 1. Keep running count of minimum number of values (min_count)
1191 1. Keep running count of maximum number of values (max_count)
1192 1. If it has an unlimited number of values
1194 1. Abort with error if it's not the last argument in the definition
1196 1. If last argument has limited number of values
1198 1. Abort with error if number of values doesn't match or is too large
1200 1. Abort with error if user didn't pass enough values (min_count)
1203 if args and not args_def:
1204 ToStderr("Error: Command %s expects no arguments", cmd)
1211 last_idx = len(args_def) - 1
1213 for idx, arg in enumerate(args_def):
1214 if min_count is None:
1216 elif arg.min is not None:
1217 min_count += arg.min
1219 if max_count is None:
1221 elif arg.max is not None:
1222 max_count += arg.max
1225 check_max = (arg.max is not None)
1227 elif arg.max is None:
1228 raise errors.ProgrammerError("Only the last argument can have max=None")
1231 # Command with exact number of arguments
1232 if (min_count is not None and max_count is not None and
1233 min_count == max_count and len(args) != min_count):
1234 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1237 # Command with limited number of arguments
1238 if max_count is not None and len(args) > max_count:
1239 ToStderr("Error: Command %s expects only %d argument(s)",
1243 # Command with some required arguments
1244 if min_count is not None and len(args) < min_count:
1245 ToStderr("Error: Command %s expects at least %d argument(s)",
1252 def SplitNodeOption(value):
1253 """Splits the value of a --node option.
1256 if value and ':' in value:
1257 return value.split(':', 1)
1259 return (value, None)
1262 def CalculateOSNames(os_name, os_variants):
1263 """Calculates all the names an OS can be called, according to its variants.
1265 @type os_name: string
1266 @param os_name: base name of the os
1267 @type os_variants: list or None
1268 @param os_variants: list of supported variants
1270 @return: list of valid names
1274 return ['%s+%s' % (os_name, v) for v in os_variants]
1279 def ParseFields(selected, default):
1280 """Parses the values of "--field"-like options.
1282 @type selected: string or None
1283 @param selected: User-selected options
1285 @param default: Default fields
1288 if selected is None:
1291 if selected.startswith("+"):
1292 return default + selected[1:].split(",")
1294 return selected.split(",")
1297 UsesRPC = rpc.RunWithRPC
1300 def AskUser(text, choices=None):
1301 """Ask the user a question.
1303 @param text: the question to ask
1305 @param choices: list with elements tuples (input_char, return_value,
1306 description); if not given, it will default to: [('y', True,
1307 'Perform the operation'), ('n', False, 'Do no do the operation')];
1308 note that the '?' char is reserved for help
1310 @return: one of the return values from the choices list; if input is
1311 not possible (i.e. not running with a tty, we return the last
1316 choices = [('y', True, 'Perform the operation'),
1317 ('n', False, 'Do not perform the operation')]
1318 if not choices or not isinstance(choices, list):
1319 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1320 for entry in choices:
1321 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1322 raise errors.ProgrammerError("Invalid choices element to AskUser")
1324 answer = choices[-1][1]
1326 for line in text.splitlines():
1327 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1328 text = "\n".join(new_text)
1330 f = file("/dev/tty", "a+")
1334 chars = [entry[0] for entry in choices]
1335 chars[-1] = "[%s]" % chars[-1]
1337 maps = dict([(entry[0], entry[1]) for entry in choices])
1341 f.write("/".join(chars))
1343 line = f.readline(2).strip().lower()
1348 for entry in choices:
1349 f.write(" %s - %s\n" % (entry[0], entry[2]))
1357 class JobSubmittedException(Exception):
1358 """Job was submitted, client should exit.
1360 This exception has one argument, the ID of the job that was
1361 submitted. The handler should print this ID.
1363 This is not an error, just a structured way to exit from clients.
1368 def SendJob(ops, cl=None):
1369 """Function to submit an opcode without waiting for the results.
1372 @param ops: list of opcodes
1373 @type cl: luxi.Client
1374 @param cl: the luxi client to use for communicating with the master;
1375 if None, a new client will be created
1381 job_id = cl.SubmitJob(ops)
1386 def GenericPollJob(job_id, cbs, report_cbs):
1387 """Generic job-polling function.
1389 @type job_id: number
1390 @param job_id: Job ID
1391 @type cbs: Instance of L{JobPollCbBase}
1392 @param cbs: Data callbacks
1393 @type report_cbs: Instance of L{JobPollReportCbBase}
1394 @param report_cbs: Reporting callbacks
1397 prev_job_info = None
1398 prev_logmsg_serial = None
1403 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1406 # job not found, go away!
1407 raise errors.JobLost("Job with id %s lost" % job_id)
1409 if result == constants.JOB_NOTCHANGED:
1410 report_cbs.ReportNotChanged(job_id, status)
1415 # Split result, a tuple of (field values, log entries)
1416 (job_info, log_entries) = result
1417 (status, ) = job_info
1420 for log_entry in log_entries:
1421 (serial, timestamp, log_type, message) = log_entry
1422 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1424 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1426 # TODO: Handle canceled and archived jobs
1427 elif status in (constants.JOB_STATUS_SUCCESS,
1428 constants.JOB_STATUS_ERROR,
1429 constants.JOB_STATUS_CANCELING,
1430 constants.JOB_STATUS_CANCELED):
1433 prev_job_info = job_info
1435 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1437 raise errors.JobLost("Job with id %s lost" % job_id)
1439 status, opstatus, result = jobs[0]
1441 if status == constants.JOB_STATUS_SUCCESS:
1444 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1445 raise errors.OpExecError("Job was canceled")
1448 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1449 if status == constants.OP_STATUS_SUCCESS:
1451 elif status == constants.OP_STATUS_ERROR:
1452 errors.MaybeRaise(msg)
1455 raise errors.OpExecError("partial failure (opcode %d): %s" %
1458 raise errors.OpExecError(str(msg))
1460 # default failure mode
1461 raise errors.OpExecError(result)
1464 class JobPollCbBase:
1465 """Base class for L{GenericPollJob} callbacks.
1469 """Initializes this class.
1473 def WaitForJobChangeOnce(self, job_id, fields,
1474 prev_job_info, prev_log_serial):
1475 """Waits for changes on a job.
1478 raise NotImplementedError()
1480 def QueryJobs(self, job_ids, fields):
1481 """Returns the selected fields for the selected job IDs.
1483 @type job_ids: list of numbers
1484 @param job_ids: Job IDs
1485 @type fields: list of strings
1486 @param fields: Fields
1489 raise NotImplementedError()
1492 class JobPollReportCbBase:
1493 """Base class for L{GenericPollJob} reporting callbacks.
1497 """Initializes this class.
1501 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1502 """Handles a log message.
1505 raise NotImplementedError()
1507 def ReportNotChanged(self, job_id, status):
1508 """Called for if a job hasn't changed in a while.
1510 @type job_id: number
1511 @param job_id: Job ID
1512 @type status: string or None
1513 @param status: Job status if available
1516 raise NotImplementedError()
1519 class _LuxiJobPollCb(JobPollCbBase):
1520 def __init__(self, cl):
1521 """Initializes this class.
1524 JobPollCbBase.__init__(self)
1527 def WaitForJobChangeOnce(self, job_id, fields,
1528 prev_job_info, prev_log_serial):
1529 """Waits for changes on a job.
1532 return self.cl.WaitForJobChangeOnce(job_id, fields,
1533 prev_job_info, prev_log_serial)
1535 def QueryJobs(self, job_ids, fields):
1536 """Returns the selected fields for the selected job IDs.
1539 return self.cl.QueryJobs(job_ids, fields)
1542 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1543 def __init__(self, feedback_fn):
1544 """Initializes this class.
1547 JobPollReportCbBase.__init__(self)
1549 self.feedback_fn = feedback_fn
1551 assert callable(feedback_fn)
1553 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1554 """Handles a log message.
1557 self.feedback_fn((timestamp, log_type, log_msg))
1559 def ReportNotChanged(self, job_id, status):
1560 """Called if a job hasn't changed in a while.
1566 class StdioJobPollReportCb(JobPollReportCbBase):
1568 """Initializes this class.
1571 JobPollReportCbBase.__init__(self)
1573 self.notified_queued = False
1574 self.notified_waitlock = False
1576 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1577 """Handles a log message.
1580 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1581 FormatLogMessage(log_type, log_msg))
1583 def ReportNotChanged(self, job_id, status):
1584 """Called if a job hasn't changed in a while.
1590 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1591 ToStderr("Job %s is waiting in queue", job_id)
1592 self.notified_queued = True
1594 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1595 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1596 self.notified_waitlock = True
1599 def FormatLogMessage(log_type, log_msg):
1600 """Formats a job message according to its type.
1603 if log_type != constants.ELOG_MESSAGE:
1604 log_msg = str(log_msg)
1606 return utils.SafeEncode(log_msg)
1609 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1610 """Function to poll for the result of a job.
1612 @type job_id: job identified
1613 @param job_id: the job to poll for results
1614 @type cl: luxi.Client
1615 @param cl: the luxi client to use for communicating with the master;
1616 if None, a new client will be created
1622 if reporter is None:
1624 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1626 reporter = StdioJobPollReportCb()
1628 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1630 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1633 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1634 """Legacy function to submit an opcode.
1636 This is just a simple wrapper over the construction of the processor
1637 instance. It should be extended to better handle feedback and
1638 interaction functions.
1644 SetGenericOpcodeOpts([op], opts)
1646 job_id = SendJob([op], cl=cl)
1648 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1651 return op_results[0]
1654 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1655 """Wrapper around SubmitOpCode or SendJob.
1657 This function will decide, based on the 'opts' parameter, whether to
1658 submit and wait for the result of the opcode (and return it), or
1659 whether to just send the job and print its identifier. It is used in
1660 order to simplify the implementation of the '--submit' option.
1662 It will also process the opcodes if we're sending the via SendJob
1663 (otherwise SubmitOpCode does it).
1666 if opts and opts.submit_only:
1668 SetGenericOpcodeOpts(job, opts)
1669 job_id = SendJob(job, cl=cl)
1670 raise JobSubmittedException(job_id)
1672 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1675 def SetGenericOpcodeOpts(opcode_list, options):
1676 """Processor for generic options.
1678 This function updates the given opcodes based on generic command
1679 line options (like debug, dry-run, etc.).
1681 @param opcode_list: list of opcodes
1682 @param options: command line options or None
1683 @return: None (in-place modification)
1688 for op in opcode_list:
1689 op.debug_level = options.debug
1690 if hasattr(options, "dry_run"):
1691 op.dry_run = options.dry_run
1692 if getattr(options, "priority", None) is not None:
1693 op.priority = _PRIONAME_TO_VALUE[options.priority]
1697 # TODO: Cache object?
1699 client = luxi.Client()
1700 except luxi.NoMasterError:
1701 ss = ssconf.SimpleStore()
1703 # Try to read ssconf file
1706 except errors.ConfigurationError:
1707 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1708 " not part of a cluster")
1710 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1711 if master != myself:
1712 raise errors.OpPrereqError("This is not the master node, please connect"
1713 " to node '%s' and rerun the command" %
1719 def FormatError(err):
1720 """Return a formatted error message for a given error.
1722 This function takes an exception instance and returns a tuple
1723 consisting of two values: first, the recommended exit code, and
1724 second, a string describing the error message (not
1725 newline-terminated).
1731 if isinstance(err, errors.ConfigurationError):
1732 txt = "Corrupt configuration file: %s" % msg
1734 obuf.write(txt + "\n")
1735 obuf.write("Aborting.")
1737 elif isinstance(err, errors.HooksAbort):
1738 obuf.write("Failure: hooks execution failed:\n")
1739 for node, script, out in err.args[0]:
1741 obuf.write(" node: %s, script: %s, output: %s\n" %
1742 (node, script, out))
1744 obuf.write(" node: %s, script: %s (no output)\n" %
1746 elif isinstance(err, errors.HooksFailure):
1747 obuf.write("Failure: hooks general failure: %s" % msg)
1748 elif isinstance(err, errors.ResolverError):
1749 this_host = netutils.Hostname.GetSysName()
1750 if err.args[0] == this_host:
1751 msg = "Failure: can't resolve my own hostname ('%s')"
1753 msg = "Failure: can't resolve hostname '%s'"
1754 obuf.write(msg % err.args[0])
1755 elif isinstance(err, errors.OpPrereqError):
1756 if len(err.args) == 2:
1757 obuf.write("Failure: prerequisites not met for this"
1758 " operation:\nerror type: %s, error details:\n%s" %
1759 (err.args[1], err.args[0]))
1761 obuf.write("Failure: prerequisites not met for this"
1762 " operation:\n%s" % msg)
1763 elif isinstance(err, errors.OpExecError):
1764 obuf.write("Failure: command execution error:\n%s" % msg)
1765 elif isinstance(err, errors.TagError):
1766 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1767 elif isinstance(err, errors.JobQueueDrainError):
1768 obuf.write("Failure: the job queue is marked for drain and doesn't"
1769 " accept new requests\n")
1770 elif isinstance(err, errors.JobQueueFull):
1771 obuf.write("Failure: the job queue is full and doesn't accept new"
1772 " job submissions until old jobs are archived\n")
1773 elif isinstance(err, errors.TypeEnforcementError):
1774 obuf.write("Parameter Error: %s" % msg)
1775 elif isinstance(err, errors.ParameterError):
1776 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1777 elif isinstance(err, luxi.NoMasterError):
1778 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1779 " and listening for connections?")
1780 elif isinstance(err, luxi.TimeoutError):
1781 obuf.write("Timeout while talking to the master daemon. Error:\n"
1783 elif isinstance(err, luxi.PermissionError):
1784 obuf.write("It seems you don't have permissions to connect to the"
1785 " master daemon.\nPlease retry as a different user.")
1786 elif isinstance(err, luxi.ProtocolError):
1787 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1789 elif isinstance(err, errors.JobLost):
1790 obuf.write("Error checking job status: %s" % msg)
1791 elif isinstance(err, errors.GenericError):
1792 obuf.write("Unhandled Ganeti error: %s" % msg)
1793 elif isinstance(err, JobSubmittedException):
1794 obuf.write("JobID: %s\n" % err.args[0])
1797 obuf.write("Unhandled exception: %s" % msg)
1798 return retcode, obuf.getvalue().rstrip('\n')
1801 def GenericMain(commands, override=None, aliases=None):
1802 """Generic main function for all the gnt-* commands.
1805 - commands: a dictionary with a special structure, see the design doc
1806 for command line handling.
1807 - override: if not None, we expect a dictionary with keys that will
1808 override command line options; this can be used to pass
1809 options from the scripts to generic functions
1810 - aliases: dictionary with command aliases {'alias': 'target, ...}
1813 # save the program name and the entire command line for later logging
1815 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1816 if len(sys.argv) >= 2:
1817 binary += " " + sys.argv[1]
1818 old_cmdline = " ".join(sys.argv[2:])
1822 binary = "<unknown program>"
1829 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1830 except errors.ParameterError, err:
1831 result, err_msg = FormatError(err)
1835 if func is None: # parse error
1838 if override is not None:
1839 for key, val in override.iteritems():
1840 setattr(options, key, val)
1842 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1843 stderr_logging=True, program=binary)
1846 logging.info("run with arguments '%s'", old_cmdline)
1848 logging.info("run with no arguments")
1851 result = func(options, args)
1852 except (errors.GenericError, luxi.ProtocolError,
1853 JobSubmittedException), err:
1854 result, err_msg = FormatError(err)
1855 logging.exception("Error during command processing")
1861 def ParseNicOption(optvalue):
1862 """Parses the value of the --net option(s).
1866 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
1867 except (TypeError, ValueError), err:
1868 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1870 nics = [{}] * nic_max
1871 for nidx, ndict in optvalue:
1874 if not isinstance(ndict, dict):
1875 raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
1876 " got %s" % (nidx, ndict))
1878 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
1885 def GenericInstanceCreate(mode, opts, args):
1886 """Add an instance to the cluster via either creation or import.
1888 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1889 @param opts: the command line options selected by the user
1891 @param args: should contain only one element, the new instance name
1893 @return: the desired exit code
1898 (pnode, snode) = SplitNodeOption(opts.node)
1903 hypervisor, hvparams = opts.hypervisor
1906 nics = ParseNicOption(opts.nics)
1910 elif mode == constants.INSTANCE_CREATE:
1911 # default of one nic, all auto
1917 if opts.disk_template == constants.DT_DISKLESS:
1918 if opts.disks or opts.sd_size is not None:
1919 raise errors.OpPrereqError("Diskless instance but disk"
1920 " information passed")
1923 if (not opts.disks and not opts.sd_size
1924 and mode == constants.INSTANCE_CREATE):
1925 raise errors.OpPrereqError("No disk information specified")
1926 if opts.disks and opts.sd_size is not None:
1927 raise errors.OpPrereqError("Please use either the '--disk' or"
1929 if opts.sd_size is not None:
1930 opts.disks = [(0, {"size": opts.sd_size})]
1934 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1935 except ValueError, err:
1936 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1937 disks = [{}] * disk_max
1940 for didx, ddict in opts.disks:
1942 if not isinstance(ddict, dict):
1943 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1944 raise errors.OpPrereqError(msg)
1945 elif "size" in ddict:
1946 if "adopt" in ddict:
1947 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1948 " (disk %d)" % didx)
1950 ddict["size"] = utils.ParseUnit(ddict["size"])
1951 except ValueError, err:
1952 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1954 elif "adopt" in ddict:
1955 if mode == constants.INSTANCE_IMPORT:
1956 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1960 raise errors.OpPrereqError("Missing size or adoption source for"
1964 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1965 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1967 if mode == constants.INSTANCE_CREATE:
1970 force_variant = opts.force_variant
1973 no_install = opts.no_install
1974 identify_defaults = False
1975 elif mode == constants.INSTANCE_IMPORT:
1978 force_variant = False
1979 src_node = opts.src_node
1980 src_path = opts.src_dir
1982 identify_defaults = opts.identify_defaults
1984 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1986 op = opcodes.OpCreateInstance(instance_name=instance,
1988 disk_template=opts.disk_template,
1990 pnode=pnode, snode=snode,
1991 ip_check=opts.ip_check,
1992 name_check=opts.name_check,
1993 wait_for_sync=opts.wait_for_sync,
1994 file_storage_dir=opts.file_storage_dir,
1995 file_driver=opts.file_driver,
1996 iallocator=opts.iallocator,
1997 hypervisor=hypervisor,
1999 beparams=opts.beparams,
2000 osparams=opts.osparams,
2004 force_variant=force_variant,
2007 no_install=no_install,
2008 identify_defaults=identify_defaults)
2010 SubmitOrSend(op, opts)
2014 class _RunWhileClusterStoppedHelper:
2015 """Helper class for L{RunWhileClusterStopped} to simplify state management
2018 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2019 """Initializes this class.
2021 @type feedback_fn: callable
2022 @param feedback_fn: Feedback function
2023 @type cluster_name: string
2024 @param cluster_name: Cluster name
2025 @type master_node: string
2026 @param master_node Master node name
2027 @type online_nodes: list
2028 @param online_nodes: List of names of online nodes
2031 self.feedback_fn = feedback_fn
2032 self.cluster_name = cluster_name
2033 self.master_node = master_node
2034 self.online_nodes = online_nodes
2036 self.ssh = ssh.SshRunner(self.cluster_name)
2038 self.nonmaster_nodes = [name for name in online_nodes
2039 if name != master_node]
2041 assert self.master_node not in self.nonmaster_nodes
2043 def _RunCmd(self, node_name, cmd):
2044 """Runs a command on the local or a remote machine.
2046 @type node_name: string
2047 @param node_name: Machine name
2052 if node_name is None or node_name == self.master_node:
2053 # No need to use SSH
2054 result = utils.RunCmd(cmd)
2056 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2059 errmsg = ["Failed to run command %s" % result.cmd]
2061 errmsg.append("on node %s" % node_name)
2062 errmsg.append(": exitcode %s and error %s" %
2063 (result.exit_code, result.output))
2064 raise errors.OpExecError(" ".join(errmsg))
2066 def Call(self, fn, *args):
2067 """Call function while all daemons are stopped.
2070 @param fn: Function to be called
2073 # Pause watcher by acquiring an exclusive lock on watcher state file
2074 self.feedback_fn("Blocking watcher")
2075 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2077 # TODO: Currently, this just blocks. There's no timeout.
2078 # TODO: Should it be a shared lock?
2079 watcher_block.Exclusive(blocking=True)
2081 # Stop master daemons, so that no new jobs can come in and all running
2083 self.feedback_fn("Stopping master daemons")
2084 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2086 # Stop daemons on all nodes
2087 for node_name in self.online_nodes:
2088 self.feedback_fn("Stopping daemons on %s" % node_name)
2089 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2091 # All daemons are shut down now
2093 return fn(self, *args)
2094 except Exception, err:
2095 _, errmsg = FormatError(err)
2096 logging.exception("Caught exception")
2097 self.feedback_fn(errmsg)
2100 # Start cluster again, master node last
2101 for node_name in self.nonmaster_nodes + [self.master_node]:
2102 self.feedback_fn("Starting daemons on %s" % node_name)
2103 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2106 watcher_block.Close()
2109 def RunWhileClusterStopped(feedback_fn, fn, *args):
2110 """Calls a function while all cluster daemons are stopped.
2112 @type feedback_fn: callable
2113 @param feedback_fn: Feedback function
2115 @param fn: Function to be called when daemons are stopped
2118 feedback_fn("Gathering cluster information")
2120 # This ensures we're running on the master daemon
2123 (cluster_name, master_node) = \
2124 cl.QueryConfigValues(["cluster_name", "master_node"])
2126 online_nodes = GetOnlineNodes([], cl=cl)
2128 # Don't keep a reference to the client. The master daemon will go away.
2131 assert master_node in online_nodes
2133 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2134 online_nodes).Call(fn, *args)
2137 def GenerateTable(headers, fields, separator, data,
2138 numfields=None, unitfields=None,
2140 """Prints a table with headers and different fields.
2143 @param headers: dictionary mapping field names to headers for
2146 @param fields: the field names corresponding to each row in
2148 @param separator: the separator to be used; if this is None,
2149 the default 'smart' algorithm is used which computes optimal
2150 field width, otherwise just the separator is used between
2153 @param data: a list of lists, each sublist being one row to be output
2154 @type numfields: list
2155 @param numfields: a list with the fields that hold numeric
2156 values and thus should be right-aligned
2157 @type unitfields: list
2158 @param unitfields: a list with the fields that hold numeric
2159 values that should be formatted with the units field
2160 @type units: string or None
2161 @param units: the units we should use for formatting, or None for
2162 automatic choice (human-readable for non-separator usage, otherwise
2163 megabytes); this is a one-letter string
2172 if numfields is None:
2174 if unitfields is None:
2177 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2178 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2181 for field in fields:
2182 if headers and field not in headers:
2183 # TODO: handle better unknown fields (either revert to old
2184 # style of raising exception, or deal more intelligently with
2186 headers[field] = field
2187 if separator is not None:
2188 format_fields.append("%s")
2189 elif numfields.Matches(field):
2190 format_fields.append("%*s")
2192 format_fields.append("%-*s")
2194 if separator is None:
2195 mlens = [0 for name in fields]
2196 format_str = ' '.join(format_fields)
2198 format_str = separator.replace("%", "%%").join(format_fields)
2203 for idx, val in enumerate(row):
2204 if unitfields.Matches(fields[idx]):
2207 except (TypeError, ValueError):
2210 val = row[idx] = utils.FormatUnit(val, units)
2211 val = row[idx] = str(val)
2212 if separator is None:
2213 mlens[idx] = max(mlens[idx], len(val))
2218 for idx, name in enumerate(fields):
2220 if separator is None:
2221 mlens[idx] = max(mlens[idx], len(hdr))
2222 args.append(mlens[idx])
2224 result.append(format_str % tuple(args))
2226 if separator is None:
2227 assert len(mlens) == len(fields)
2229 if fields and not numfields.Matches(fields[-1]):
2235 line = ['-' for _ in fields]
2236 for idx in range(len(fields)):
2237 if separator is None:
2238 args.append(mlens[idx])
2239 args.append(line[idx])
2240 result.append(format_str % tuple(args))
2245 def FormatTimestamp(ts):
2246 """Formats a given timestamp.
2249 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2252 @return: a string with the formatted timestamp
2255 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2258 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2261 def ParseTimespec(value):
2262 """Parse a time specification.
2264 The following suffixed will be recognized:
2272 Without any suffix, the value will be taken to be in seconds.
2277 raise errors.OpPrereqError("Empty time specification passed")
2285 if value[-1] not in suffix_map:
2288 except (TypeError, ValueError):
2289 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2291 multiplier = suffix_map[value[-1]]
2293 if not value: # no data left after stripping the suffix
2294 raise errors.OpPrereqError("Invalid time specification (only"
2297 value = int(value) * multiplier
2298 except (TypeError, ValueError):
2299 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2303 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2304 filter_master=False):
2305 """Returns the names of online nodes.
2307 This function will also log a warning on stderr with the names of
2310 @param nodes: if not empty, use only this subset of nodes (minus the
2312 @param cl: if not None, luxi client to use
2313 @type nowarn: boolean
2314 @param nowarn: by default, this function will output a note with the
2315 offline nodes that are skipped; if this parameter is True the
2316 note is not displayed
2317 @type secondary_ips: boolean
2318 @param secondary_ips: if True, return the secondary IPs instead of the
2319 names, useful for doing network traffic over the replication interface
2321 @type filter_master: boolean
2322 @param filter_master: if True, do not return the master node in the list
2323 (useful in coordination with secondary_ips where we cannot check our
2324 node name against the list)
2336 master_node = cl.QueryConfigValues(["master_node"])[0]
2337 filter_fn = lambda x: x != master_node
2339 filter_fn = lambda _: True
2341 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2343 offline = [row[0] for row in result if row[1]]
2344 if offline and not nowarn:
2345 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2346 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2349 def _ToStream(stream, txt, *args):
2350 """Write a message to a stream, bypassing the logging system
2352 @type stream: file object
2353 @param stream: the file to which we should write
2355 @param txt: the message
2360 stream.write(txt % args)
2367 def ToStdout(txt, *args):
2368 """Write a message to stdout only, bypassing the logging system
2370 This is just a wrapper over _ToStream.
2373 @param txt: the message
2376 _ToStream(sys.stdout, txt, *args)
2379 def ToStderr(txt, *args):
2380 """Write a message to stderr only, bypassing the logging system
2382 This is just a wrapper over _ToStream.
2385 @param txt: the message
2388 _ToStream(sys.stderr, txt, *args)
2391 class JobExecutor(object):
2392 """Class which manages the submission and execution of multiple jobs.
2394 Note that instances of this class should not be reused between
2398 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2403 self.verbose = verbose
2406 self.feedback_fn = feedback_fn
2408 def QueueJob(self, name, *ops):
2409 """Record a job for later submit.
2412 @param name: a description of the job, will be used in WaitJobSet
2414 SetGenericOpcodeOpts(ops, self.opts)
2415 self.queue.append((name, ops))
2417 def SubmitPending(self, each=False):
2418 """Submit all pending jobs.
2423 for row in self.queue:
2424 # SubmitJob will remove the success status, but raise an exception if
2425 # the submission fails, so we'll notice that anyway.
2426 results.append([True, self.cl.SubmitJob(row[1])])
2428 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2429 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2431 self.jobs.append((idx, status, data, name))
2433 def _ChooseJob(self):
2434 """Choose a non-waiting/queued job to poll next.
2437 assert self.jobs, "_ChooseJob called with empty job list"
2439 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2442 for job_data, status in zip(self.jobs, result):
2443 if (isinstance(status, list) and status and
2444 status[0] in (constants.JOB_STATUS_QUEUED,
2445 constants.JOB_STATUS_WAITLOCK,
2446 constants.JOB_STATUS_CANCELING)):
2447 # job is still present and waiting
2449 # good candidate found (either running job or lost job)
2450 self.jobs.remove(job_data)
2454 return self.jobs.pop(0)
2456 def GetResults(self):
2457 """Wait for and return the results of all jobs.
2460 @return: list of tuples (success, job results), in the same order
2461 as the submitted jobs; if a job has failed, instead of the result
2462 there will be the error message
2466 self.SubmitPending()
2469 ok_jobs = [row[2] for row in self.jobs if row[1]]
2471 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2473 # first, remove any non-submitted jobs
2474 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2475 for idx, _, jid, name in failures:
2476 ToStderr("Failed to submit job for %s: %s", name, jid)
2477 results.append((idx, False, jid))
2480 (idx, _, jid, name) = self._ChooseJob()
2481 ToStdout("Waiting for job %s for %s...", jid, name)
2483 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2485 except errors.JobLost, err:
2486 _, job_result = FormatError(err)
2487 ToStderr("Job %s for %s has been archived, cannot check its result",
2490 except (errors.GenericError, luxi.ProtocolError), err:
2491 _, job_result = FormatError(err)
2493 # the error message will always be shown, verbose or not
2494 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2496 results.append((idx, success, job_result))
2498 # sort based on the index, then drop it
2500 results = [i[1:] for i in results]
2504 def WaitOrShow(self, wait):
2505 """Wait for job results or only print the job IDs.
2508 @param wait: whether to wait or not
2512 return self.GetResults()
2515 self.SubmitPending()
2516 for _, status, result, name in self.jobs:
2518 ToStdout("%s: %s", result, name)
2520 ToStderr("Failure for %s: %s", name, result)
2521 return [row[1:3] for row in self.jobs]