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
56 "CLUSTER_DOMAIN_SECRET_OPT",
72 "FILESTORE_DRIVER_OPT",
80 "DEFAULT_IALLOCATOR_OPT",
81 "IDENTIFY_DEFAULTS_OPT",
83 "IGNORE_FAILURES_OPT",
84 "IGNORE_REMOVE_FAILURES_OPT",
85 "IGNORE_SECONDARIES_OPT",
89 "MAINTAIN_NODE_HEALTH_OPT",
94 "NEW_CLUSTER_CERT_OPT",
95 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
96 "NEW_CONFD_HMAC_KEY_OPT",
101 "NODE_PLACEMENT_OPT",
102 "NODRBD_STORAGE_OPT",
108 "NOMODIFY_ETCHOSTS_OPT",
109 "NOMODIFY_SSH_SETUP_OPT",
115 "NOSSH_KEYCHECK_OPT",
127 "REMOVE_INSTANCE_OPT",
135 "SHUTDOWN_TIMEOUT_OPT",
150 # Generic functions for CLI programs
152 "GenericInstanceCreate",
156 "JobSubmittedException",
158 "RunWhileClusterStopped",
162 # Formatting functions
163 "ToStderr", "ToStdout",
173 # command line options support infrastructure
174 "ARGS_MANY_INSTANCES",
190 "OPT_COMPL_INST_ADD_NODES",
191 "OPT_COMPL_MANY_NODES",
192 "OPT_COMPL_ONE_IALLOCATOR",
193 "OPT_COMPL_ONE_INSTANCE",
194 "OPT_COMPL_ONE_NODE",
207 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
212 return ("<%s min=%s max=%s>" %
213 (self.__class__.__name__, self.min, self.max))
216 class ArgSuggest(_Argument):
217 """Suggesting argument.
219 Value can be any of the ones passed to the constructor.
222 # pylint: disable-msg=W0622
223 def __init__(self, min=0, max=None, choices=None):
224 _Argument.__init__(self, min=min, max=max)
225 self.choices = choices
228 return ("<%s min=%s max=%s choices=%r>" %
229 (self.__class__.__name__, self.min, self.max, self.choices))
232 class ArgChoice(ArgSuggest):
235 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
236 but value must be one of the choices.
241 class ArgUnknown(_Argument):
242 """Unknown argument to program (e.g. determined at runtime).
247 class ArgInstance(_Argument):
248 """Instances argument.
253 class ArgNode(_Argument):
258 class ArgJobId(_Argument):
264 class ArgFile(_Argument):
265 """File path argument.
270 class ArgCommand(_Argument):
276 class ArgHost(_Argument):
282 class ArgOs(_Argument):
289 ARGS_MANY_INSTANCES = [ArgInstance()]
290 ARGS_MANY_NODES = [ArgNode()]
291 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
292 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
293 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
296 def _ExtractTagsObject(opts, args):
297 """Extract the tag type object.
299 Note that this function will modify its args parameter.
302 if not hasattr(opts, "tag_type"):
303 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
305 if kind == constants.TAG_CLUSTER:
307 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
309 raise errors.OpPrereqError("no arguments passed to the command")
313 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
317 def _ExtendTags(opts, args):
318 """Extend the args if a source file has been given.
320 This function will extend the tags with the contents of the file
321 passed in the 'tags_source' attribute of the opts parameter. A file
322 named '-' will be replaced by stdin.
325 fname = opts.tags_source
331 new_fh = open(fname, "r")
334 # we don't use the nice 'new_data = [line.strip() for line in fh]'
335 # because of python bug 1633941
337 line = new_fh.readline()
340 new_data.append(line.strip())
343 args.extend(new_data)
346 def ListTags(opts, args):
347 """List the tags on a given object.
349 This is a generic implementation that knows how to deal with all
350 three cases of tag objects (cluster, node, instance). The opts
351 argument is expected to contain a tag_type field denoting what
352 object type we work on.
355 kind, name = _ExtractTagsObject(opts, args)
357 result = cl.QueryTags(kind, name)
358 result = list(result)
364 def AddTags(opts, args):
365 """Add tags on a given object.
367 This is a generic implementation that knows how to deal with all
368 three cases of tag objects (cluster, node, instance). The opts
369 argument is expected to contain a tag_type field denoting what
370 object type we work on.
373 kind, name = _ExtractTagsObject(opts, args)
374 _ExtendTags(opts, args)
376 raise errors.OpPrereqError("No tags to be added")
377 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
381 def RemoveTags(opts, args):
382 """Remove tags from a given object.
384 This is a generic implementation that knows how to deal with all
385 three cases of tag objects (cluster, node, instance). The opts
386 argument is expected to contain a tag_type field denoting what
387 object type we work on.
390 kind, name = _ExtractTagsObject(opts, args)
391 _ExtendTags(opts, args)
393 raise errors.OpPrereqError("No tags to be removed")
394 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
398 def check_unit(option, opt, value): # pylint: disable-msg=W0613
399 """OptParsers custom converter for units.
403 return utils.ParseUnit(value)
404 except errors.UnitParseError, err:
405 raise OptionValueError("option %s: %s" % (opt, err))
408 def _SplitKeyVal(opt, data):
409 """Convert a KeyVal string into a dict.
411 This function will convert a key=val[,...] string into a dict. Empty
412 values will be converted specially: keys which have the prefix 'no_'
413 will have the value=False and the prefix stripped, the others will
417 @param opt: a string holding the option name for which we process the
418 data, used in building error messages
420 @param data: a string of the format key=val,key=val,...
422 @return: {key=val, key=val}
423 @raises errors.ParameterError: if there are duplicate keys
428 for elem in utils.UnescapeAndSplit(data, sep=","):
430 key, val = elem.split("=", 1)
432 if elem.startswith(NO_PREFIX):
433 key, val = elem[len(NO_PREFIX):], False
434 elif elem.startswith(UN_PREFIX):
435 key, val = elem[len(UN_PREFIX):], None
437 key, val = elem, True
439 raise errors.ParameterError("Duplicate key '%s' in option %s" %
445 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
446 """Custom parser for ident:key=val,key=val options.
448 This will store the parsed values as a tuple (ident, {key: val}). As such,
449 multiple uses of this option via action=append is possible.
453 ident, rest = value, ''
455 ident, rest = value.split(":", 1)
457 if ident.startswith(NO_PREFIX):
459 msg = "Cannot pass options when removing parameter groups: %s" % value
460 raise errors.ParameterError(msg)
461 retval = (ident[len(NO_PREFIX):], False)
462 elif ident.startswith(UN_PREFIX):
464 msg = "Cannot pass options when removing parameter groups: %s" % value
465 raise errors.ParameterError(msg)
466 retval = (ident[len(UN_PREFIX):], None)
468 kv_dict = _SplitKeyVal(opt, rest)
469 retval = (ident, kv_dict)
473 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
474 """Custom parser class for key=val,key=val options.
476 This will store the parsed values as a dict {key: val}.
479 return _SplitKeyVal(opt, value)
482 def check_bool(option, opt, value): # pylint: disable-msg=W0613
483 """Custom parser for yes/no options.
485 This will store the parsed value as either True or False.
488 value = value.lower()
489 if value == constants.VALUE_FALSE or value == "no":
491 elif value == constants.VALUE_TRUE or value == "yes":
494 raise errors.ParameterError("Invalid boolean value '%s'" % value)
497 # completion_suggestion is normally a list. Using numeric values not evaluating
498 # to False for dynamic completion.
499 (OPT_COMPL_MANY_NODES,
501 OPT_COMPL_ONE_INSTANCE,
503 OPT_COMPL_ONE_IALLOCATOR,
504 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
506 OPT_COMPL_ALL = frozenset([
507 OPT_COMPL_MANY_NODES,
509 OPT_COMPL_ONE_INSTANCE,
511 OPT_COMPL_ONE_IALLOCATOR,
512 OPT_COMPL_INST_ADD_NODES,
516 class CliOption(Option):
517 """Custom option class for optparse.
520 ATTRS = Option.ATTRS + [
521 "completion_suggest",
523 TYPES = Option.TYPES + (
529 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
530 TYPE_CHECKER["identkeyval"] = check_ident_key_val
531 TYPE_CHECKER["keyval"] = check_key_val
532 TYPE_CHECKER["unit"] = check_unit
533 TYPE_CHECKER["bool"] = check_bool
536 # optparse.py sets make_option, so we do it for our own option class, too
537 cli_option = CliOption
542 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
543 help="Increase debugging level")
545 NOHDR_OPT = cli_option("--no-headers", default=False,
546 action="store_true", dest="no_headers",
547 help="Don't display column headers")
549 SEP_OPT = cli_option("--separator", default=None,
550 action="store", dest="separator",
551 help=("Separator between output fields"
552 " (defaults to one space)"))
554 USEUNITS_OPT = cli_option("--units", default=None,
555 dest="units", choices=('h', 'm', 'g', 't'),
556 help="Specify units for output (one of hmgt)")
558 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
559 type="string", metavar="FIELDS",
560 help="Comma separated list of output fields")
562 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
563 default=False, help="Force the operation")
565 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
566 default=False, help="Do not require confirmation")
568 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
569 default=None, help="File with tag names")
571 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
572 default=False, action="store_true",
573 help=("Submit the job and return the job ID, but"
574 " don't wait for the job to finish"))
576 SYNC_OPT = cli_option("--sync", dest="do_locking",
577 default=False, action="store_true",
578 help=("Grab locks while doing the queries"
579 " in order to ensure more consistent results"))
581 DRY_RUN_OPT = cli_option("--dry-run", default=False,
583 help=("Do not execute the operation, just run the"
584 " check steps and verify it it could be"
587 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
589 help="Increase the verbosity of the operation")
591 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
592 action="store_true", dest="simulate_errors",
593 help="Debugging option that makes the operation"
594 " treat most runtime checks as failed")
596 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
597 default=True, action="store_false",
598 help="Don't wait for sync (DANGEROUS!)")
600 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
601 help="Custom disk setup (diskless, file,"
603 default=None, metavar="TEMPL",
604 choices=list(constants.DISK_TEMPLATES))
606 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
607 help="Do not create any network cards for"
610 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
611 help="Relative path under default cluster-wide"
612 " file storage dir to store file-based disks",
613 default=None, metavar="<DIR>")
615 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
616 help="Driver to use for image files",
617 default="loop", metavar="<DRIVER>",
618 choices=list(constants.FILE_DRIVER))
620 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
621 help="Select nodes for the instance automatically"
622 " using the <NAME> iallocator plugin",
623 default=None, type="string",
624 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
626 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
628 help="Set the default instance allocator plugin",
629 default=None, type="string",
630 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
632 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
634 completion_suggest=OPT_COMPL_ONE_OS)
636 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
637 type="keyval", default={},
638 help="OS parameters")
640 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
641 action="store_true", default=False,
642 help="Force an unknown variant")
644 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
645 action="store_true", default=False,
646 help="Do not install the OS (will"
649 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
650 type="keyval", default={},
651 help="Backend parameters")
653 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
654 default={}, dest="hvparams",
655 help="Hypervisor parameters")
657 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
658 help="Hypervisor and hypervisor options, in the"
659 " format hypervisor:option=value,option=value,...",
660 default=None, type="identkeyval")
662 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
663 help="Hypervisor and hypervisor options, in the"
664 " format hypervisor:option=value,option=value,...",
665 default=[], action="append", type="identkeyval")
667 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
668 action="store_false",
669 help="Don't check that the instance's IP"
672 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
673 default=True, action="store_false",
674 help="Don't check that the instance's name"
677 NET_OPT = cli_option("--net",
678 help="NIC parameters", default=[],
679 dest="nics", action="append", type="identkeyval")
681 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
682 dest="disks", action="append", type="identkeyval")
684 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
685 help="Comma-separated list of disks"
686 " indices to act on (e.g. 0,2) (optional,"
687 " defaults to all disks)")
689 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
690 help="Enforces a single-disk configuration using the"
691 " given disk size, in MiB unless a suffix is used",
692 default=None, type="unit", metavar="<size>")
694 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
695 dest="ignore_consistency",
696 action="store_true", default=False,
697 help="Ignore the consistency of the disks on"
700 NONLIVE_OPT = cli_option("--non-live", dest="live",
701 default=True, action="store_false",
702 help="Do a non-live migration (this usually means"
703 " freeze the instance, save the state, transfer and"
704 " only then resume running on the secondary node)")
706 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
708 choices=list(constants.HT_MIGRATION_MODES),
709 help="Override default migration mode (choose"
710 " either live or non-live")
712 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
713 help="Target node and optional secondary node",
714 metavar="<pnode>[:<snode>]",
715 completion_suggest=OPT_COMPL_INST_ADD_NODES)
717 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
718 action="append", metavar="<node>",
719 help="Use only this node (can be used multiple"
720 " times, if not given defaults to all nodes)",
721 completion_suggest=OPT_COMPL_ONE_NODE)
723 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
725 completion_suggest=OPT_COMPL_ONE_NODE)
727 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
728 action="store_false",
729 help="Don't start the instance after creation")
731 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
732 action="store_true", default=False,
733 help="Show command instead of executing it")
735 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
736 default=False, action="store_true",
737 help="Instead of performing the migration, try to"
738 " recover from a failed cleanup. This is safe"
739 " to run even if the instance is healthy, but it"
740 " will create extra replication traffic and "
741 " disrupt briefly the replication (like during the"
744 STATIC_OPT = cli_option("-s", "--static", dest="static",
745 action="store_true", default=False,
746 help="Only show configuration data, not runtime data")
748 ALL_OPT = cli_option("--all", dest="show_all",
749 default=False, action="store_true",
750 help="Show info on all instances on the cluster."
751 " This can take a long time to run, use wisely")
753 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
754 action="store_true", default=False,
755 help="Interactive OS reinstall, lists available"
756 " OS templates for selection")
758 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
759 action="store_true", default=False,
760 help="Remove the instance from the cluster"
761 " configuration even if there are failures"
762 " during the removal process")
764 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
765 dest="ignore_remove_failures",
766 action="store_true", default=False,
767 help="Remove the instance from the"
768 " cluster configuration even if there"
769 " are failures during the removal"
772 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
773 action="store_true", default=False,
774 help="Remove the instance from the cluster")
776 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
777 help="Specifies the new secondary node",
778 metavar="NODE", default=None,
779 completion_suggest=OPT_COMPL_ONE_NODE)
781 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
782 default=False, action="store_true",
783 help="Replace the disk(s) on the primary"
784 " node (only for the drbd template)")
786 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
787 default=False, action="store_true",
788 help="Replace the disk(s) on the secondary"
789 " node (only for the drbd template)")
791 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
792 default=False, action="store_true",
793 help="Lock all nodes and auto-promote as needed"
796 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
797 default=False, action="store_true",
798 help="Automatically replace faulty disks"
799 " (only for the drbd template)")
801 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
802 default=False, action="store_true",
803 help="Ignore current recorded size"
804 " (useful for forcing activation when"
805 " the recorded size is wrong)")
807 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
809 completion_suggest=OPT_COMPL_ONE_NODE)
811 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
814 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
815 help="Specify the secondary ip for the node",
816 metavar="ADDRESS", default=None)
818 READD_OPT = cli_option("--readd", dest="readd",
819 default=False, action="store_true",
820 help="Readd old node after replacing it")
822 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
823 default=True, action="store_false",
824 help="Disable SSH key fingerprint checking")
827 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
828 type="bool", default=None, metavar=_YORNO,
829 help="Set the master_candidate flag on the node")
831 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
832 type="bool", default=None,
833 help="Set the offline flag on the node")
835 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
836 type="bool", default=None,
837 help="Set the drained flag on the node")
839 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
840 type="bool", default=None, metavar=_YORNO,
841 help="Set the allocatable flag on a volume")
843 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
844 help="Disable support for lvm based instances"
846 action="store_false", default=True)
848 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
849 dest="enabled_hypervisors",
850 help="Comma-separated list of hypervisors",
851 type="string", default=None)
853 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
854 type="keyval", default={},
855 help="NIC parameters")
857 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
858 dest="candidate_pool_size", type="int",
859 help="Set the candidate pool size")
861 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
862 help="Enables LVM and specifies the volume group"
863 " name (cluster-wide) for disk allocation [xenvg]",
864 metavar="VG", default=None)
866 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
867 help="Destroy cluster", action="store_true")
869 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
870 help="Skip node agreement check (dangerous)",
871 action="store_true", default=False)
873 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
874 help="Specify the mac prefix for the instance IP"
875 " addresses, in the format XX:XX:XX",
879 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
880 help="Specify the node interface (cluster-wide)"
881 " on which the master IP address will be added "
882 " [%s]" % constants.DEFAULT_BRIDGE,
884 default=constants.DEFAULT_BRIDGE)
886 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
887 help="Specify the default directory (cluster-"
888 "wide) for storing the file-based disks [%s]" %
889 constants.DEFAULT_FILE_STORAGE_DIR,
891 default=constants.DEFAULT_FILE_STORAGE_DIR)
893 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
894 help="Don't modify /etc/hosts",
895 action="store_false", default=True)
897 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
898 help="Don't initialize SSH keys",
899 action="store_false", default=True)
901 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
902 help="Enable parseable error messages",
903 action="store_true", default=False)
905 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
906 help="Skip N+1 memory redundancy tests",
907 action="store_true", default=False)
909 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
910 help="Type of reboot: soft/hard/full",
911 default=constants.INSTANCE_REBOOT_HARD,
913 choices=list(constants.REBOOT_TYPES))
915 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
916 dest="ignore_secondaries",
917 default=False, action="store_true",
918 help="Ignore errors from secondaries")
920 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
921 action="store_false", default=True,
922 help="Don't shutdown the instance (unsafe)")
924 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
925 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
926 help="Maximum time to wait")
928 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
929 dest="shutdown_timeout", type="int",
930 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
931 help="Maximum time to wait for instance shutdown")
933 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
935 help=("Number of seconds between repetions of the"
938 EARLY_RELEASE_OPT = cli_option("--early-release",
939 dest="early_release", default=False,
941 help="Release the locks on the secondary"
944 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
945 dest="new_cluster_cert",
946 default=False, action="store_true",
947 help="Generate a new cluster certificate")
949 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
951 help="File containing new RAPI certificate")
953 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
954 default=None, action="store_true",
955 help=("Generate a new self-signed RAPI"
958 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
959 dest="new_confd_hmac_key",
960 default=False, action="store_true",
961 help=("Create a new HMAC key for %s" %
964 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
965 dest="cluster_domain_secret",
967 help=("Load new new cluster domain"
968 " secret from file"))
970 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
971 dest="new_cluster_domain_secret",
972 default=False, action="store_true",
973 help=("Create a new cluster domain"
976 USE_REPL_NET_OPT = cli_option("--use-replication-network",
977 dest="use_replication_network",
978 help="Whether to use the replication network"
979 " for talking to the nodes",
980 action="store_true", default=False)
982 MAINTAIN_NODE_HEALTH_OPT = \
983 cli_option("--maintain-node-health", dest="maintain_node_health",
984 metavar=_YORNO, default=None, type="bool",
985 help="Configure the cluster to automatically maintain node"
986 " health, by shutting down unknown instances, shutting down"
987 " unknown DRBD devices, etc.")
989 IDENTIFY_DEFAULTS_OPT = \
990 cli_option("--identify-defaults", dest="identify_defaults",
991 default=False, action="store_true",
992 help="Identify which saved instance parameters are equal to"
993 " the current cluster defaults and set them as such, instead"
994 " of marking them as overridden")
996 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
997 action="store", dest="uid_pool",
998 help=("A list of user-ids or user-id"
999 " ranges separated by commas"))
1001 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1002 action="store", dest="add_uids",
1003 help=("A list of user-ids or user-id"
1004 " ranges separated by commas, to be"
1005 " added to the user-id pool"))
1007 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1008 action="store", dest="remove_uids",
1009 help=("A list of user-ids or user-id"
1010 " ranges separated by commas, to be"
1011 " removed from the user-id pool"))
1013 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1014 action="store", dest="reserved_lvs",
1015 help=("A comma-separated list of reserved"
1016 " logical volumes names, that will be"
1017 " ignored by cluster verify"))
1019 ROMAN_OPT = cli_option("--roman",
1020 dest="roman_integers", default=False,
1021 action="store_true",
1022 help="Use roman numbers for positive integers")
1024 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1025 action="store", default=None,
1026 help="Specifies usermode helper for DRBD")
1028 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1029 action="store_false", default=True,
1030 help="Disable support for DRBD")
1032 #: Options provided by all commands
1033 COMMON_OPTS = [DEBUG_OPT]
1036 def _ParseArgs(argv, commands, aliases):
1037 """Parser for the command line arguments.
1039 This function parses the arguments and returns the function which
1040 must be executed together with its (modified) arguments.
1042 @param argv: the command line
1043 @param commands: dictionary with special contents, see the design
1044 doc for cmdline handling
1045 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1049 binary = "<command>"
1051 binary = argv[0].split("/")[-1]
1053 if len(argv) > 1 and argv[1] == "--version":
1054 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1055 constants.RELEASE_VERSION)
1056 # Quit right away. That way we don't have to care about this special
1057 # argument. optparse.py does it the same.
1060 if len(argv) < 2 or not (argv[1] in commands or
1061 argv[1] in aliases):
1062 # let's do a nice thing
1063 sortedcmds = commands.keys()
1066 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1067 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1070 # compute the max line length for cmd + usage
1071 mlen = max([len(" %s" % cmd) for cmd in commands])
1072 mlen = min(60, mlen) # should not get here...
1074 # and format a nice command list
1075 ToStdout("Commands:")
1076 for cmd in sortedcmds:
1077 cmdstr = " %s" % (cmd,)
1078 help_text = commands[cmd][4]
1079 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1080 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1081 for line in help_lines:
1082 ToStdout("%-*s %s", mlen, "", line)
1086 return None, None, None
1088 # get command, unalias it, and look it up in commands
1092 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1095 if aliases[cmd] not in commands:
1096 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1097 " command '%s'" % (cmd, aliases[cmd]))
1101 func, args_def, parser_opts, usage, description = commands[cmd]
1102 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1103 description=description,
1104 formatter=TitledHelpFormatter(),
1105 usage="%%prog %s %s" % (cmd, usage))
1106 parser.disable_interspersed_args()
1107 options, args = parser.parse_args()
1109 if not _CheckArguments(cmd, args_def, args):
1110 return None, None, None
1112 return func, options, args
1115 def _CheckArguments(cmd, args_def, args):
1116 """Verifies the arguments using the argument definition.
1120 1. Abort with error if values specified by user but none expected.
1122 1. For each argument in definition
1124 1. Keep running count of minimum number of values (min_count)
1125 1. Keep running count of maximum number of values (max_count)
1126 1. If it has an unlimited number of values
1128 1. Abort with error if it's not the last argument in the definition
1130 1. If last argument has limited number of values
1132 1. Abort with error if number of values doesn't match or is too large
1134 1. Abort with error if user didn't pass enough values (min_count)
1137 if args and not args_def:
1138 ToStderr("Error: Command %s expects no arguments", cmd)
1145 last_idx = len(args_def) - 1
1147 for idx, arg in enumerate(args_def):
1148 if min_count is None:
1150 elif arg.min is not None:
1151 min_count += arg.min
1153 if max_count is None:
1155 elif arg.max is not None:
1156 max_count += arg.max
1159 check_max = (arg.max is not None)
1161 elif arg.max is None:
1162 raise errors.ProgrammerError("Only the last argument can have max=None")
1165 # Command with exact number of arguments
1166 if (min_count is not None and max_count is not None and
1167 min_count == max_count and len(args) != min_count):
1168 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1171 # Command with limited number of arguments
1172 if max_count is not None and len(args) > max_count:
1173 ToStderr("Error: Command %s expects only %d argument(s)",
1177 # Command with some required arguments
1178 if min_count is not None and len(args) < min_count:
1179 ToStderr("Error: Command %s expects at least %d argument(s)",
1186 def SplitNodeOption(value):
1187 """Splits the value of a --node option.
1190 if value and ':' in value:
1191 return value.split(':', 1)
1193 return (value, None)
1196 def CalculateOSNames(os_name, os_variants):
1197 """Calculates all the names an OS can be called, according to its variants.
1199 @type os_name: string
1200 @param os_name: base name of the os
1201 @type os_variants: list or None
1202 @param os_variants: list of supported variants
1204 @return: list of valid names
1208 return ['%s+%s' % (os_name, v) for v in os_variants]
1213 def ParseFields(selected, default):
1214 """Parses the values of "--field"-like options.
1216 @type selected: string or None
1217 @param selected: User-selected options
1219 @param default: Default fields
1222 if selected is None:
1225 if selected.startswith("+"):
1226 return default + selected[1:].split(",")
1228 return selected.split(",")
1231 UsesRPC = rpc.RunWithRPC
1234 def AskUser(text, choices=None):
1235 """Ask the user a question.
1237 @param text: the question to ask
1239 @param choices: list with elements tuples (input_char, return_value,
1240 description); if not given, it will default to: [('y', True,
1241 'Perform the operation'), ('n', False, 'Do no do the operation')];
1242 note that the '?' char is reserved for help
1244 @return: one of the return values from the choices list; if input is
1245 not possible (i.e. not running with a tty, we return the last
1250 choices = [('y', True, 'Perform the operation'),
1251 ('n', False, 'Do not perform the operation')]
1252 if not choices or not isinstance(choices, list):
1253 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1254 for entry in choices:
1255 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1256 raise errors.ProgrammerError("Invalid choices element to AskUser")
1258 answer = choices[-1][1]
1260 for line in text.splitlines():
1261 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1262 text = "\n".join(new_text)
1264 f = file("/dev/tty", "a+")
1268 chars = [entry[0] for entry in choices]
1269 chars[-1] = "[%s]" % chars[-1]
1271 maps = dict([(entry[0], entry[1]) for entry in choices])
1275 f.write("/".join(chars))
1277 line = f.readline(2).strip().lower()
1282 for entry in choices:
1283 f.write(" %s - %s\n" % (entry[0], entry[2]))
1291 class JobSubmittedException(Exception):
1292 """Job was submitted, client should exit.
1294 This exception has one argument, the ID of the job that was
1295 submitted. The handler should print this ID.
1297 This is not an error, just a structured way to exit from clients.
1302 def SendJob(ops, cl=None):
1303 """Function to submit an opcode without waiting for the results.
1306 @param ops: list of opcodes
1307 @type cl: luxi.Client
1308 @param cl: the luxi client to use for communicating with the master;
1309 if None, a new client will be created
1315 job_id = cl.SubmitJob(ops)
1320 def GenericPollJob(job_id, cbs, report_cbs):
1321 """Generic job-polling function.
1323 @type job_id: number
1324 @param job_id: Job ID
1325 @type cbs: Instance of L{JobPollCbBase}
1326 @param cbs: Data callbacks
1327 @type report_cbs: Instance of L{JobPollReportCbBase}
1328 @param report_cbs: Reporting callbacks
1331 prev_job_info = None
1332 prev_logmsg_serial = None
1337 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1340 # job not found, go away!
1341 raise errors.JobLost("Job with id %s lost" % job_id)
1343 if result == constants.JOB_NOTCHANGED:
1344 report_cbs.ReportNotChanged(job_id, status)
1349 # Split result, a tuple of (field values, log entries)
1350 (job_info, log_entries) = result
1351 (status, ) = job_info
1354 for log_entry in log_entries:
1355 (serial, timestamp, log_type, message) = log_entry
1356 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1358 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1360 # TODO: Handle canceled and archived jobs
1361 elif status in (constants.JOB_STATUS_SUCCESS,
1362 constants.JOB_STATUS_ERROR,
1363 constants.JOB_STATUS_CANCELING,
1364 constants.JOB_STATUS_CANCELED):
1367 prev_job_info = job_info
1369 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1371 raise errors.JobLost("Job with id %s lost" % job_id)
1373 status, opstatus, result = jobs[0]
1375 if status == constants.JOB_STATUS_SUCCESS:
1378 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1379 raise errors.OpExecError("Job was canceled")
1382 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1383 if status == constants.OP_STATUS_SUCCESS:
1385 elif status == constants.OP_STATUS_ERROR:
1386 errors.MaybeRaise(msg)
1389 raise errors.OpExecError("partial failure (opcode %d): %s" %
1392 raise errors.OpExecError(str(msg))
1394 # default failure mode
1395 raise errors.OpExecError(result)
1398 class JobPollCbBase:
1399 """Base class for L{GenericPollJob} callbacks.
1403 """Initializes this class.
1407 def WaitForJobChangeOnce(self, job_id, fields,
1408 prev_job_info, prev_log_serial):
1409 """Waits for changes on a job.
1412 raise NotImplementedError()
1414 def QueryJobs(self, job_ids, fields):
1415 """Returns the selected fields for the selected job IDs.
1417 @type job_ids: list of numbers
1418 @param job_ids: Job IDs
1419 @type fields: list of strings
1420 @param fields: Fields
1423 raise NotImplementedError()
1426 class JobPollReportCbBase:
1427 """Base class for L{GenericPollJob} reporting callbacks.
1431 """Initializes this class.
1435 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1436 """Handles a log message.
1439 raise NotImplementedError()
1441 def ReportNotChanged(self, job_id, status):
1442 """Called for if a job hasn't changed in a while.
1444 @type job_id: number
1445 @param job_id: Job ID
1446 @type status: string or None
1447 @param status: Job status if available
1450 raise NotImplementedError()
1453 class _LuxiJobPollCb(JobPollCbBase):
1454 def __init__(self, cl):
1455 """Initializes this class.
1458 JobPollCbBase.__init__(self)
1461 def WaitForJobChangeOnce(self, job_id, fields,
1462 prev_job_info, prev_log_serial):
1463 """Waits for changes on a job.
1466 return self.cl.WaitForJobChangeOnce(job_id, fields,
1467 prev_job_info, prev_log_serial)
1469 def QueryJobs(self, job_ids, fields):
1470 """Returns the selected fields for the selected job IDs.
1473 return self.cl.QueryJobs(job_ids, fields)
1476 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1477 def __init__(self, feedback_fn):
1478 """Initializes this class.
1481 JobPollReportCbBase.__init__(self)
1483 self.feedback_fn = feedback_fn
1485 assert callable(feedback_fn)
1487 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1488 """Handles a log message.
1491 self.feedback_fn((timestamp, log_type, log_msg))
1493 def ReportNotChanged(self, job_id, status):
1494 """Called if a job hasn't changed in a while.
1500 class StdioJobPollReportCb(JobPollReportCbBase):
1502 """Initializes this class.
1505 JobPollReportCbBase.__init__(self)
1507 self.notified_queued = False
1508 self.notified_waitlock = False
1510 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1511 """Handles a log message.
1514 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1515 FormatLogMessage(log_type, log_msg))
1517 def ReportNotChanged(self, job_id, status):
1518 """Called if a job hasn't changed in a while.
1524 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1525 ToStderr("Job %s is waiting in queue", job_id)
1526 self.notified_queued = True
1528 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1529 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1530 self.notified_waitlock = True
1533 def FormatLogMessage(log_type, log_msg):
1534 """Formats a job message according to its type.
1537 if log_type != constants.ELOG_MESSAGE:
1538 log_msg = str(log_msg)
1540 return utils.SafeEncode(log_msg)
1543 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1544 """Function to poll for the result of a job.
1546 @type job_id: job identified
1547 @param job_id: the job to poll for results
1548 @type cl: luxi.Client
1549 @param cl: the luxi client to use for communicating with the master;
1550 if None, a new client will be created
1556 if reporter is None:
1558 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1560 reporter = StdioJobPollReportCb()
1562 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1564 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1567 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1568 """Legacy function to submit an opcode.
1570 This is just a simple wrapper over the construction of the processor
1571 instance. It should be extended to better handle feedback and
1572 interaction functions.
1578 SetGenericOpcodeOpts([op], opts)
1580 job_id = SendJob([op], cl=cl)
1582 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1585 return op_results[0]
1588 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1589 """Wrapper around SubmitOpCode or SendJob.
1591 This function will decide, based on the 'opts' parameter, whether to
1592 submit and wait for the result of the opcode (and return it), or
1593 whether to just send the job and print its identifier. It is used in
1594 order to simplify the implementation of the '--submit' option.
1596 It will also process the opcodes if we're sending the via SendJob
1597 (otherwise SubmitOpCode does it).
1600 if opts and opts.submit_only:
1602 SetGenericOpcodeOpts(job, opts)
1603 job_id = SendJob(job, cl=cl)
1604 raise JobSubmittedException(job_id)
1606 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1609 def SetGenericOpcodeOpts(opcode_list, options):
1610 """Processor for generic options.
1612 This function updates the given opcodes based on generic command
1613 line options (like debug, dry-run, etc.).
1615 @param opcode_list: list of opcodes
1616 @param options: command line options or None
1617 @return: None (in-place modification)
1622 for op in opcode_list:
1623 if hasattr(options, "dry_run"):
1624 op.dry_run = options.dry_run
1625 op.debug_level = options.debug
1629 # TODO: Cache object?
1631 client = luxi.Client()
1632 except luxi.NoMasterError:
1633 ss = ssconf.SimpleStore()
1635 # Try to read ssconf file
1638 except errors.ConfigurationError:
1639 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1640 " not part of a cluster")
1642 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1643 if master != myself:
1644 raise errors.OpPrereqError("This is not the master node, please connect"
1645 " to node '%s' and rerun the command" %
1651 def FormatError(err):
1652 """Return a formatted error message for a given error.
1654 This function takes an exception instance and returns a tuple
1655 consisting of two values: first, the recommended exit code, and
1656 second, a string describing the error message (not
1657 newline-terminated).
1663 if isinstance(err, errors.ConfigurationError):
1664 txt = "Corrupt configuration file: %s" % msg
1666 obuf.write(txt + "\n")
1667 obuf.write("Aborting.")
1669 elif isinstance(err, errors.HooksAbort):
1670 obuf.write("Failure: hooks execution failed:\n")
1671 for node, script, out in err.args[0]:
1673 obuf.write(" node: %s, script: %s, output: %s\n" %
1674 (node, script, out))
1676 obuf.write(" node: %s, script: %s (no output)\n" %
1678 elif isinstance(err, errors.HooksFailure):
1679 obuf.write("Failure: hooks general failure: %s" % msg)
1680 elif isinstance(err, errors.ResolverError):
1681 this_host = netutils.HostInfo.SysName()
1682 if err.args[0] == this_host:
1683 msg = "Failure: can't resolve my own hostname ('%s')"
1685 msg = "Failure: can't resolve hostname '%s'"
1686 obuf.write(msg % err.args[0])
1687 elif isinstance(err, errors.OpPrereqError):
1688 if len(err.args) == 2:
1689 obuf.write("Failure: prerequisites not met for this"
1690 " operation:\nerror type: %s, error details:\n%s" %
1691 (err.args[1], err.args[0]))
1693 obuf.write("Failure: prerequisites not met for this"
1694 " operation:\n%s" % msg)
1695 elif isinstance(err, errors.OpExecError):
1696 obuf.write("Failure: command execution error:\n%s" % msg)
1697 elif isinstance(err, errors.TagError):
1698 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1699 elif isinstance(err, errors.JobQueueDrainError):
1700 obuf.write("Failure: the job queue is marked for drain and doesn't"
1701 " accept new requests\n")
1702 elif isinstance(err, errors.JobQueueFull):
1703 obuf.write("Failure: the job queue is full and doesn't accept new"
1704 " job submissions until old jobs are archived\n")
1705 elif isinstance(err, errors.TypeEnforcementError):
1706 obuf.write("Parameter Error: %s" % msg)
1707 elif isinstance(err, errors.ParameterError):
1708 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1709 elif isinstance(err, luxi.NoMasterError):
1710 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1711 " and listening for connections?")
1712 elif isinstance(err, luxi.TimeoutError):
1713 obuf.write("Timeout while talking to the master daemon. Error:\n"
1715 elif isinstance(err, luxi.PermissionError):
1716 obuf.write("It seems you don't have permissions to connect to the"
1717 " master daemon.\nPlease retry as a different user.")
1718 elif isinstance(err, luxi.ProtocolError):
1719 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1721 elif isinstance(err, errors.JobLost):
1722 obuf.write("Error checking job status: %s" % msg)
1723 elif isinstance(err, errors.GenericError):
1724 obuf.write("Unhandled Ganeti error: %s" % msg)
1725 elif isinstance(err, JobSubmittedException):
1726 obuf.write("JobID: %s\n" % err.args[0])
1729 obuf.write("Unhandled exception: %s" % msg)
1730 return retcode, obuf.getvalue().rstrip('\n')
1733 def GenericMain(commands, override=None, aliases=None):
1734 """Generic main function for all the gnt-* commands.
1737 - commands: a dictionary with a special structure, see the design doc
1738 for command line handling.
1739 - override: if not None, we expect a dictionary with keys that will
1740 override command line options; this can be used to pass
1741 options from the scripts to generic functions
1742 - aliases: dictionary with command aliases {'alias': 'target, ...}
1745 # save the program name and the entire command line for later logging
1747 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1748 if len(sys.argv) >= 2:
1749 binary += " " + sys.argv[1]
1750 old_cmdline = " ".join(sys.argv[2:])
1754 binary = "<unknown program>"
1761 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1762 except errors.ParameterError, err:
1763 result, err_msg = FormatError(err)
1767 if func is None: # parse error
1770 if override is not None:
1771 for key, val in override.iteritems():
1772 setattr(options, key, val)
1774 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1775 stderr_logging=True, program=binary)
1778 logging.info("run with arguments '%s'", old_cmdline)
1780 logging.info("run with no arguments")
1783 result = func(options, args)
1784 except (errors.GenericError, luxi.ProtocolError,
1785 JobSubmittedException), err:
1786 result, err_msg = FormatError(err)
1787 logging.exception("Error during command processing")
1793 def GenericInstanceCreate(mode, opts, args):
1794 """Add an instance to the cluster via either creation or import.
1796 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1797 @param opts: the command line options selected by the user
1799 @param args: should contain only one element, the new instance name
1801 @return: the desired exit code
1806 (pnode, snode) = SplitNodeOption(opts.node)
1811 hypervisor, hvparams = opts.hypervisor
1815 nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1816 except ValueError, err:
1817 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1818 nics = [{}] * nic_max
1819 for nidx, ndict in opts.nics:
1821 if not isinstance(ndict, dict):
1822 msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1823 raise errors.OpPrereqError(msg)
1828 elif mode == constants.INSTANCE_CREATE:
1829 # default of one nic, all auto
1835 if opts.disk_template == constants.DT_DISKLESS:
1836 if opts.disks or opts.sd_size is not None:
1837 raise errors.OpPrereqError("Diskless instance but disk"
1838 " information passed")
1841 if (not opts.disks and not opts.sd_size
1842 and mode == constants.INSTANCE_CREATE):
1843 raise errors.OpPrereqError("No disk information specified")
1844 if opts.disks and opts.sd_size is not None:
1845 raise errors.OpPrereqError("Please use either the '--disk' or"
1847 if opts.sd_size is not None:
1848 opts.disks = [(0, {"size": opts.sd_size})]
1852 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1853 except ValueError, err:
1854 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1855 disks = [{}] * disk_max
1858 for didx, ddict in opts.disks:
1860 if not isinstance(ddict, dict):
1861 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1862 raise errors.OpPrereqError(msg)
1863 elif "size" in ddict:
1864 if "adopt" in ddict:
1865 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1866 " (disk %d)" % didx)
1868 ddict["size"] = utils.ParseUnit(ddict["size"])
1869 except ValueError, err:
1870 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1872 elif "adopt" in ddict:
1873 if mode == constants.INSTANCE_IMPORT:
1874 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1878 raise errors.OpPrereqError("Missing size or adoption source for"
1882 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1883 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1885 if mode == constants.INSTANCE_CREATE:
1888 force_variant = opts.force_variant
1891 no_install = opts.no_install
1892 identify_defaults = False
1893 elif mode == constants.INSTANCE_IMPORT:
1896 force_variant = False
1897 src_node = opts.src_node
1898 src_path = opts.src_dir
1900 identify_defaults = opts.identify_defaults
1902 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1904 op = opcodes.OpCreateInstance(instance_name=instance,
1906 disk_template=opts.disk_template,
1908 pnode=pnode, snode=snode,
1909 ip_check=opts.ip_check,
1910 name_check=opts.name_check,
1911 wait_for_sync=opts.wait_for_sync,
1912 file_storage_dir=opts.file_storage_dir,
1913 file_driver=opts.file_driver,
1914 iallocator=opts.iallocator,
1915 hypervisor=hypervisor,
1917 beparams=opts.beparams,
1918 osparams=opts.osparams,
1922 force_variant=force_variant,
1925 no_install=no_install,
1926 identify_defaults=identify_defaults)
1928 SubmitOrSend(op, opts)
1932 class _RunWhileClusterStoppedHelper:
1933 """Helper class for L{RunWhileClusterStopped} to simplify state management
1936 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
1937 """Initializes this class.
1939 @type feedback_fn: callable
1940 @param feedback_fn: Feedback function
1941 @type cluster_name: string
1942 @param cluster_name: Cluster name
1943 @type master_node: string
1944 @param master_node Master node name
1945 @type online_nodes: list
1946 @param online_nodes: List of names of online nodes
1949 self.feedback_fn = feedback_fn
1950 self.cluster_name = cluster_name
1951 self.master_node = master_node
1952 self.online_nodes = online_nodes
1954 self.ssh = ssh.SshRunner(self.cluster_name)
1956 self.nonmaster_nodes = [name for name in online_nodes
1957 if name != master_node]
1959 assert self.master_node not in self.nonmaster_nodes
1961 def _RunCmd(self, node_name, cmd):
1962 """Runs a command on the local or a remote machine.
1964 @type node_name: string
1965 @param node_name: Machine name
1970 if node_name is None or node_name == self.master_node:
1971 # No need to use SSH
1972 result = utils.RunCmd(cmd)
1974 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
1977 errmsg = ["Failed to run command %s" % result.cmd]
1979 errmsg.append("on node %s" % node_name)
1980 errmsg.append(": exitcode %s and error %s" %
1981 (result.exit_code, result.output))
1982 raise errors.OpExecError(" ".join(errmsg))
1984 def Call(self, fn, *args):
1985 """Call function while all daemons are stopped.
1988 @param fn: Function to be called
1991 # Pause watcher by acquiring an exclusive lock on watcher state file
1992 self.feedback_fn("Blocking watcher")
1993 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
1995 # TODO: Currently, this just blocks. There's no timeout.
1996 # TODO: Should it be a shared lock?
1997 watcher_block.Exclusive(blocking=True)
1999 # Stop master daemons, so that no new jobs can come in and all running
2001 self.feedback_fn("Stopping master daemons")
2002 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2004 # Stop daemons on all nodes
2005 for node_name in self.online_nodes:
2006 self.feedback_fn("Stopping daemons on %s" % node_name)
2007 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2009 # All daemons are shut down now
2011 return fn(self, *args)
2012 except Exception, err:
2013 _, errmsg = FormatError(err)
2014 logging.exception("Caught exception")
2015 self.feedback_fn(errmsg)
2018 # Start cluster again, master node last
2019 for node_name in self.nonmaster_nodes + [self.master_node]:
2020 self.feedback_fn("Starting daemons on %s" % node_name)
2021 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2024 watcher_block.Close()
2027 def RunWhileClusterStopped(feedback_fn, fn, *args):
2028 """Calls a function while all cluster daemons are stopped.
2030 @type feedback_fn: callable
2031 @param feedback_fn: Feedback function
2033 @param fn: Function to be called when daemons are stopped
2036 feedback_fn("Gathering cluster information")
2038 # This ensures we're running on the master daemon
2041 (cluster_name, master_node) = \
2042 cl.QueryConfigValues(["cluster_name", "master_node"])
2044 online_nodes = GetOnlineNodes([], cl=cl)
2046 # Don't keep a reference to the client. The master daemon will go away.
2049 assert master_node in online_nodes
2051 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2052 online_nodes).Call(fn, *args)
2055 def GenerateTable(headers, fields, separator, data,
2056 numfields=None, unitfields=None,
2058 """Prints a table with headers and different fields.
2061 @param headers: dictionary mapping field names to headers for
2064 @param fields: the field names corresponding to each row in
2066 @param separator: the separator to be used; if this is None,
2067 the default 'smart' algorithm is used which computes optimal
2068 field width, otherwise just the separator is used between
2071 @param data: a list of lists, each sublist being one row to be output
2072 @type numfields: list
2073 @param numfields: a list with the fields that hold numeric
2074 values and thus should be right-aligned
2075 @type unitfields: list
2076 @param unitfields: a list with the fields that hold numeric
2077 values that should be formatted with the units field
2078 @type units: string or None
2079 @param units: the units we should use for formatting, or None for
2080 automatic choice (human-readable for non-separator usage, otherwise
2081 megabytes); this is a one-letter string
2090 if numfields is None:
2092 if unitfields is None:
2095 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2096 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2099 for field in fields:
2100 if headers and field not in headers:
2101 # TODO: handle better unknown fields (either revert to old
2102 # style of raising exception, or deal more intelligently with
2104 headers[field] = field
2105 if separator is not None:
2106 format_fields.append("%s")
2107 elif numfields.Matches(field):
2108 format_fields.append("%*s")
2110 format_fields.append("%-*s")
2112 if separator is None:
2113 mlens = [0 for name in fields]
2114 format_str = ' '.join(format_fields)
2116 format_str = separator.replace("%", "%%").join(format_fields)
2121 for idx, val in enumerate(row):
2122 if unitfields.Matches(fields[idx]):
2125 except (TypeError, ValueError):
2128 val = row[idx] = utils.FormatUnit(val, units)
2129 val = row[idx] = str(val)
2130 if separator is None:
2131 mlens[idx] = max(mlens[idx], len(val))
2136 for idx, name in enumerate(fields):
2138 if separator is None:
2139 mlens[idx] = max(mlens[idx], len(hdr))
2140 args.append(mlens[idx])
2142 result.append(format_str % tuple(args))
2144 if separator is None:
2145 assert len(mlens) == len(fields)
2147 if fields and not numfields.Matches(fields[-1]):
2153 line = ['-' for _ in fields]
2154 for idx in range(len(fields)):
2155 if separator is None:
2156 args.append(mlens[idx])
2157 args.append(line[idx])
2158 result.append(format_str % tuple(args))
2163 def FormatTimestamp(ts):
2164 """Formats a given timestamp.
2167 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2170 @return: a string with the formatted timestamp
2173 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2176 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2179 def ParseTimespec(value):
2180 """Parse a time specification.
2182 The following suffixed will be recognized:
2190 Without any suffix, the value will be taken to be in seconds.
2195 raise errors.OpPrereqError("Empty time specification passed")
2203 if value[-1] not in suffix_map:
2206 except (TypeError, ValueError):
2207 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2209 multiplier = suffix_map[value[-1]]
2211 if not value: # no data left after stripping the suffix
2212 raise errors.OpPrereqError("Invalid time specification (only"
2215 value = int(value) * multiplier
2216 except (TypeError, ValueError):
2217 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2221 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2222 filter_master=False):
2223 """Returns the names of online nodes.
2225 This function will also log a warning on stderr with the names of
2228 @param nodes: if not empty, use only this subset of nodes (minus the
2230 @param cl: if not None, luxi client to use
2231 @type nowarn: boolean
2232 @param nowarn: by default, this function will output a note with the
2233 offline nodes that are skipped; if this parameter is True the
2234 note is not displayed
2235 @type secondary_ips: boolean
2236 @param secondary_ips: if True, return the secondary IPs instead of the
2237 names, useful for doing network traffic over the replication interface
2239 @type filter_master: boolean
2240 @param filter_master: if True, do not return the master node in the list
2241 (useful in coordination with secondary_ips where we cannot check our
2242 node name against the list)
2254 master_node = cl.QueryConfigValues(["master_node"])[0]
2255 filter_fn = lambda x: x != master_node
2257 filter_fn = lambda _: True
2259 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2261 offline = [row[0] for row in result if row[1]]
2262 if offline and not nowarn:
2263 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2264 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2267 def _ToStream(stream, txt, *args):
2268 """Write a message to a stream, bypassing the logging system
2270 @type stream: file object
2271 @param stream: the file to which we should write
2273 @param txt: the message
2278 stream.write(txt % args)
2285 def ToStdout(txt, *args):
2286 """Write a message to stdout only, bypassing the logging system
2288 This is just a wrapper over _ToStream.
2291 @param txt: the message
2294 _ToStream(sys.stdout, txt, *args)
2297 def ToStderr(txt, *args):
2298 """Write a message to stderr only, bypassing the logging system
2300 This is just a wrapper over _ToStream.
2303 @param txt: the message
2306 _ToStream(sys.stderr, txt, *args)
2309 class JobExecutor(object):
2310 """Class which manages the submission and execution of multiple jobs.
2312 Note that instances of this class should not be reused between
2316 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2321 self.verbose = verbose
2324 self.feedback_fn = feedback_fn
2326 def QueueJob(self, name, *ops):
2327 """Record a job for later submit.
2330 @param name: a description of the job, will be used in WaitJobSet
2332 SetGenericOpcodeOpts(ops, self.opts)
2333 self.queue.append((name, ops))
2335 def SubmitPending(self, each=False):
2336 """Submit all pending jobs.
2341 for row in self.queue:
2342 # SubmitJob will remove the success status, but raise an exception if
2343 # the submission fails, so we'll notice that anyway.
2344 results.append([True, self.cl.SubmitJob(row[1])])
2346 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2347 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2349 self.jobs.append((idx, status, data, name))
2351 def _ChooseJob(self):
2352 """Choose a non-waiting/queued job to poll next.
2355 assert self.jobs, "_ChooseJob called with empty job list"
2357 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2360 for job_data, status in zip(self.jobs, result):
2361 if (isinstance(status, list) and status and
2362 status[0] in (constants.JOB_STATUS_QUEUED,
2363 constants.JOB_STATUS_WAITLOCK,
2364 constants.JOB_STATUS_CANCELING)):
2365 # job is still present and waiting
2367 # good candidate found (either running job or lost job)
2368 self.jobs.remove(job_data)
2372 return self.jobs.pop(0)
2374 def GetResults(self):
2375 """Wait for and return the results of all jobs.
2378 @return: list of tuples (success, job results), in the same order
2379 as the submitted jobs; if a job has failed, instead of the result
2380 there will be the error message
2384 self.SubmitPending()
2387 ok_jobs = [row[2] for row in self.jobs if row[1]]
2389 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2391 # first, remove any non-submitted jobs
2392 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2393 for idx, _, jid, name in failures:
2394 ToStderr("Failed to submit job for %s: %s", name, jid)
2395 results.append((idx, False, jid))
2398 (idx, _, jid, name) = self._ChooseJob()
2399 ToStdout("Waiting for job %s for %s...", jid, name)
2401 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2403 except errors.JobLost, err:
2404 _, job_result = FormatError(err)
2405 ToStderr("Job %s for %s has been archived, cannot check its result",
2408 except (errors.GenericError, luxi.ProtocolError), err:
2409 _, job_result = FormatError(err)
2411 # the error message will always be shown, verbose or not
2412 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2414 results.append((idx, success, job_result))
2416 # sort based on the index, then drop it
2418 results = [i[1:] for i in results]
2422 def WaitOrShow(self, wait):
2423 """Wait for job results or only print the job IDs.
2426 @param wait: whether to wait or not
2430 return self.GetResults()
2433 self.SubmitPending()
2434 for _, status, result, name in self.jobs:
2436 ToStdout("%s: %s", result, name)
2438 ToStderr("Failure for %s: %s", name, result)
2439 return [row[1:3] for row in self.jobs]