4 # Copyright (C) 2006, 2007 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Module dealing with command line parsing"""
30 from cStringIO import StringIO
32 from ganeti import utils
33 from ganeti import errors
34 from ganeti import constants
35 from ganeti import opcodes
36 from ganeti import luxi
37 from ganeti import ssconf
38 from ganeti import rpc
39 from ganeti import ssh
41 from optparse import (OptionParser, TitledHelpFormatter,
42 Option, OptionValueError)
46 # Command line options
66 "FILESTORE_DRIVER_OPT",
74 "IDENTIFY_DEFAULTS_OPT",
76 "IGNORE_FAILURES_OPT",
77 "IGNORE_SECONDARIES_OPT",
80 "MAINTAIN_NODE_HEALTH_OPT",
84 "NEW_CLUSTER_CERT_OPT",
85 "NEW_CONFD_HMAC_KEY_OPT",
96 "NOMODIFY_ETCHOSTS_OPT",
97 "NOMODIFY_SSH_SETUP_OPT",
103 "NOSSH_KEYCHECK_OPT",
118 "SHUTDOWN_TIMEOUT_OPT",
133 # Generic functions for CLI programs
135 "GenericInstanceCreate",
139 "JobSubmittedException",
141 "RunWhileClusterStopped",
145 # Formatting functions
146 "ToStderr", "ToStdout",
155 # command line options support infrastructure
156 "ARGS_MANY_INSTANCES",
172 "OPT_COMPL_INST_ADD_NODES",
173 "OPT_COMPL_MANY_NODES",
174 "OPT_COMPL_ONE_IALLOCATOR",
175 "OPT_COMPL_ONE_INSTANCE",
176 "OPT_COMPL_ONE_NODE",
188 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
193 return ("<%s min=%s max=%s>" %
194 (self.__class__.__name__, self.min, self.max))
197 class ArgSuggest(_Argument):
198 """Suggesting argument.
200 Value can be any of the ones passed to the constructor.
203 # pylint: disable-msg=W0622
204 def __init__(self, min=0, max=None, choices=None):
205 _Argument.__init__(self, min=min, max=max)
206 self.choices = choices
209 return ("<%s min=%s max=%s choices=%r>" %
210 (self.__class__.__name__, self.min, self.max, self.choices))
213 class ArgChoice(ArgSuggest):
216 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
217 but value must be one of the choices.
222 class ArgUnknown(_Argument):
223 """Unknown argument to program (e.g. determined at runtime).
228 class ArgInstance(_Argument):
229 """Instances argument.
234 class ArgNode(_Argument):
239 class ArgJobId(_Argument):
245 class ArgFile(_Argument):
246 """File path argument.
251 class ArgCommand(_Argument):
257 class ArgHost(_Argument):
263 class ArgOs(_Argument):
270 ARGS_MANY_INSTANCES = [ArgInstance()]
271 ARGS_MANY_NODES = [ArgNode()]
272 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
273 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
274 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
277 def _ExtractTagsObject(opts, args):
278 """Extract the tag type object.
280 Note that this function will modify its args parameter.
283 if not hasattr(opts, "tag_type"):
284 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
286 if kind == constants.TAG_CLUSTER:
288 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
290 raise errors.OpPrereqError("no arguments passed to the command")
294 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
298 def _ExtendTags(opts, args):
299 """Extend the args if a source file has been given.
301 This function will extend the tags with the contents of the file
302 passed in the 'tags_source' attribute of the opts parameter. A file
303 named '-' will be replaced by stdin.
306 fname = opts.tags_source
312 new_fh = open(fname, "r")
315 # we don't use the nice 'new_data = [line.strip() for line in fh]'
316 # because of python bug 1633941
318 line = new_fh.readline()
321 new_data.append(line.strip())
324 args.extend(new_data)
327 def ListTags(opts, args):
328 """List the tags on a given object.
330 This is a generic implementation that knows how to deal with all
331 three cases of tag objects (cluster, node, instance). The opts
332 argument is expected to contain a tag_type field denoting what
333 object type we work on.
336 kind, name = _ExtractTagsObject(opts, args)
338 result = cl.QueryTags(kind, name)
339 result = list(result)
345 def AddTags(opts, args):
346 """Add tags on a given object.
348 This is a generic implementation that knows how to deal with all
349 three cases of tag objects (cluster, node, instance). The opts
350 argument is expected to contain a tag_type field denoting what
351 object type we work on.
354 kind, name = _ExtractTagsObject(opts, args)
355 _ExtendTags(opts, args)
357 raise errors.OpPrereqError("No tags to be added")
358 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
362 def RemoveTags(opts, args):
363 """Remove tags from a given object.
365 This is a generic implementation that knows how to deal with all
366 three cases of tag objects (cluster, node, instance). The opts
367 argument is expected to contain a tag_type field denoting what
368 object type we work on.
371 kind, name = _ExtractTagsObject(opts, args)
372 _ExtendTags(opts, args)
374 raise errors.OpPrereqError("No tags to be removed")
375 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
379 def check_unit(option, opt, value): # pylint: disable-msg=W0613
380 """OptParsers custom converter for units.
384 return utils.ParseUnit(value)
385 except errors.UnitParseError, err:
386 raise OptionValueError("option %s: %s" % (opt, err))
389 def _SplitKeyVal(opt, data):
390 """Convert a KeyVal string into a dict.
392 This function will convert a key=val[,...] string into a dict. Empty
393 values will be converted specially: keys which have the prefix 'no_'
394 will have the value=False and the prefix stripped, the others will
398 @param opt: a string holding the option name for which we process the
399 data, used in building error messages
401 @param data: a string of the format key=val,key=val,...
403 @return: {key=val, key=val}
404 @raises errors.ParameterError: if there are duplicate keys
409 for elem in utils.UnescapeAndSplit(data, sep=","):
411 key, val = elem.split("=", 1)
413 if elem.startswith(NO_PREFIX):
414 key, val = elem[len(NO_PREFIX):], False
415 elif elem.startswith(UN_PREFIX):
416 key, val = elem[len(UN_PREFIX):], None
418 key, val = elem, True
420 raise errors.ParameterError("Duplicate key '%s' in option %s" %
426 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
427 """Custom parser for ident:key=val,key=val options.
429 This will store the parsed values as a tuple (ident, {key: val}). As such,
430 multiple uses of this option via action=append is possible.
434 ident, rest = value, ''
436 ident, rest = value.split(":", 1)
438 if ident.startswith(NO_PREFIX):
440 msg = "Cannot pass options when removing parameter groups: %s" % value
441 raise errors.ParameterError(msg)
442 retval = (ident[len(NO_PREFIX):], False)
443 elif ident.startswith(UN_PREFIX):
445 msg = "Cannot pass options when removing parameter groups: %s" % value
446 raise errors.ParameterError(msg)
447 retval = (ident[len(UN_PREFIX):], None)
449 kv_dict = _SplitKeyVal(opt, rest)
450 retval = (ident, kv_dict)
454 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
455 """Custom parser class for key=val,key=val options.
457 This will store the parsed values as a dict {key: val}.
460 return _SplitKeyVal(opt, value)
463 def check_bool(option, opt, value): # pylint: disable-msg=W0613
464 """Custom parser for yes/no options.
466 This will store the parsed value as either True or False.
469 value = value.lower()
470 if value == constants.VALUE_FALSE or value == "no":
472 elif value == constants.VALUE_TRUE or value == "yes":
475 raise errors.ParameterError("Invalid boolean value '%s'" % value)
478 # completion_suggestion is normally a list. Using numeric values not evaluating
479 # to False for dynamic completion.
480 (OPT_COMPL_MANY_NODES,
482 OPT_COMPL_ONE_INSTANCE,
484 OPT_COMPL_ONE_IALLOCATOR,
485 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
487 OPT_COMPL_ALL = frozenset([
488 OPT_COMPL_MANY_NODES,
490 OPT_COMPL_ONE_INSTANCE,
492 OPT_COMPL_ONE_IALLOCATOR,
493 OPT_COMPL_INST_ADD_NODES,
497 class CliOption(Option):
498 """Custom option class for optparse.
501 ATTRS = Option.ATTRS + [
502 "completion_suggest",
504 TYPES = Option.TYPES + (
510 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
511 TYPE_CHECKER["identkeyval"] = check_ident_key_val
512 TYPE_CHECKER["keyval"] = check_key_val
513 TYPE_CHECKER["unit"] = check_unit
514 TYPE_CHECKER["bool"] = check_bool
517 # optparse.py sets make_option, so we do it for our own option class, too
518 cli_option = CliOption
523 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
524 help="Increase debugging level")
526 NOHDR_OPT = cli_option("--no-headers", default=False,
527 action="store_true", dest="no_headers",
528 help="Don't display column headers")
530 SEP_OPT = cli_option("--separator", default=None,
531 action="store", dest="separator",
532 help=("Separator between output fields"
533 " (defaults to one space)"))
535 USEUNITS_OPT = cli_option("--units", default=None,
536 dest="units", choices=('h', 'm', 'g', 't'),
537 help="Specify units for output (one of hmgt)")
539 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
540 type="string", metavar="FIELDS",
541 help="Comma separated list of output fields")
543 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
544 default=False, help="Force the operation")
546 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
547 default=False, help="Do not require confirmation")
549 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
550 default=None, help="File with tag names")
552 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
553 default=False, action="store_true",
554 help=("Submit the job and return the job ID, but"
555 " don't wait for the job to finish"))
557 SYNC_OPT = cli_option("--sync", dest="do_locking",
558 default=False, action="store_true",
559 help=("Grab locks while doing the queries"
560 " in order to ensure more consistent results"))
562 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
564 help=("Do not execute the operation, just run the"
565 " check steps and verify it it could be"
568 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
570 help="Increase the verbosity of the operation")
572 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
573 action="store_true", dest="simulate_errors",
574 help="Debugging option that makes the operation"
575 " treat most runtime checks as failed")
577 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
578 default=True, action="store_false",
579 help="Don't wait for sync (DANGEROUS!)")
581 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
582 help="Custom disk setup (diskless, file,"
584 default=None, metavar="TEMPL",
585 choices=list(constants.DISK_TEMPLATES))
587 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
588 help="Do not create any network cards for"
591 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
592 help="Relative path under default cluster-wide"
593 " file storage dir to store file-based disks",
594 default=None, metavar="<DIR>")
596 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
597 help="Driver to use for image files",
598 default="loop", metavar="<DRIVER>",
599 choices=list(constants.FILE_DRIVER))
601 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
602 help="Select nodes for the instance automatically"
603 " using the <NAME> iallocator plugin",
604 default=None, type="string",
605 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
607 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
609 completion_suggest=OPT_COMPL_ONE_OS)
611 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
612 action="store_true", default=False,
613 help="Force an unknown variant")
615 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
616 action="store_true", default=False,
617 help="Do not install the OS (will"
620 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
621 type="keyval", default={},
622 help="Backend parameters")
624 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
625 default={}, dest="hvparams",
626 help="Hypervisor parameters")
628 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
629 help="Hypervisor and hypervisor options, in the"
630 " format hypervisor:option=value,option=value,...",
631 default=None, type="identkeyval")
633 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
634 help="Hypervisor and hypervisor options, in the"
635 " format hypervisor:option=value,option=value,...",
636 default=[], action="append", type="identkeyval")
638 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
639 action="store_false",
640 help="Don't check that the instance's IP"
643 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
644 default=True, action="store_false",
645 help="Don't check that the instance's name"
648 NET_OPT = cli_option("--net",
649 help="NIC parameters", default=[],
650 dest="nics", action="append", type="identkeyval")
652 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
653 dest="disks", action="append", type="identkeyval")
655 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
656 help="Comma-separated list of disks"
657 " indices to act on (e.g. 0,2) (optional,"
658 " defaults to all disks)")
660 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
661 help="Enforces a single-disk configuration using the"
662 " given disk size, in MiB unless a suffix is used",
663 default=None, type="unit", metavar="<size>")
665 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
666 dest="ignore_consistency",
667 action="store_true", default=False,
668 help="Ignore the consistency of the disks on"
671 NONLIVE_OPT = cli_option("--non-live", dest="live",
672 default=True, action="store_false",
673 help="Do a non-live migration (this usually means"
674 " freeze the instance, save the state, transfer and"
675 " only then resume running on the secondary node)")
677 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
678 help="Target node and optional secondary node",
679 metavar="<pnode>[:<snode>]",
680 completion_suggest=OPT_COMPL_INST_ADD_NODES)
682 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
683 action="append", metavar="<node>",
684 help="Use only this node (can be used multiple"
685 " times, if not given defaults to all nodes)",
686 completion_suggest=OPT_COMPL_ONE_NODE)
688 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
690 completion_suggest=OPT_COMPL_ONE_NODE)
692 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
693 action="store_false",
694 help="Don't start the instance after creation")
696 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
697 action="store_true", default=False,
698 help="Show command instead of executing it")
700 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
701 default=False, action="store_true",
702 help="Instead of performing the migration, try to"
703 " recover from a failed cleanup. This is safe"
704 " to run even if the instance is healthy, but it"
705 " will create extra replication traffic and "
706 " disrupt briefly the replication (like during the"
709 STATIC_OPT = cli_option("-s", "--static", dest="static",
710 action="store_true", default=False,
711 help="Only show configuration data, not runtime data")
713 ALL_OPT = cli_option("--all", dest="show_all",
714 default=False, action="store_true",
715 help="Show info on all instances on the cluster."
716 " This can take a long time to run, use wisely")
718 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
719 action="store_true", default=False,
720 help="Interactive OS reinstall, lists available"
721 " OS templates for selection")
723 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
724 action="store_true", default=False,
725 help="Remove the instance from the cluster"
726 " configuration even if there are failures"
727 " during the removal process")
729 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
730 help="Specifies the new secondary node",
731 metavar="NODE", default=None,
732 completion_suggest=OPT_COMPL_ONE_NODE)
734 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
735 default=False, action="store_true",
736 help="Replace the disk(s) on the primary"
737 " node (only for the drbd template)")
739 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
740 default=False, action="store_true",
741 help="Replace the disk(s) on the secondary"
742 " node (only for the drbd template)")
744 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
745 default=False, action="store_true",
746 help="Lock all nodes and auto-promote as needed"
749 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
750 default=False, action="store_true",
751 help="Automatically replace faulty disks"
752 " (only for the drbd template)")
754 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
755 default=False, action="store_true",
756 help="Ignore current recorded size"
757 " (useful for forcing activation when"
758 " the recorded size is wrong)")
760 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
762 completion_suggest=OPT_COMPL_ONE_NODE)
764 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
767 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
768 help="Specify the secondary ip for the node",
769 metavar="ADDRESS", default=None)
771 READD_OPT = cli_option("--readd", dest="readd",
772 default=False, action="store_true",
773 help="Readd old node after replacing it")
775 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
776 default=True, action="store_false",
777 help="Disable SSH key fingerprint checking")
780 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
781 type="bool", default=None, metavar=_YORNO,
782 help="Set the master_candidate flag on the node")
784 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
785 type="bool", default=None,
786 help="Set the offline flag on the node")
788 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
789 type="bool", default=None,
790 help="Set the drained flag on the node")
792 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
793 type="bool", default=None, metavar=_YORNO,
794 help="Set the allocatable flag on a volume")
796 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
797 help="Disable support for lvm based instances"
799 action="store_false", default=True)
801 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
802 dest="enabled_hypervisors",
803 help="Comma-separated list of hypervisors",
804 type="string", default=None)
806 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
807 type="keyval", default={},
808 help="NIC parameters")
810 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
811 dest="candidate_pool_size", type="int",
812 help="Set the candidate pool size")
814 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
815 help="Enables LVM and specifies the volume group"
816 " name (cluster-wide) for disk allocation [xenvg]",
817 metavar="VG", default=None)
819 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
820 help="Destroy cluster", action="store_true")
822 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
823 help="Skip node agreement check (dangerous)",
824 action="store_true", default=False)
826 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
827 help="Specify the mac prefix for the instance IP"
828 " addresses, in the format XX:XX:XX",
832 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
833 help="Specify the node interface (cluster-wide)"
834 " on which the master IP address will be added "
835 " [%s]" % constants.DEFAULT_BRIDGE,
837 default=constants.DEFAULT_BRIDGE)
840 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
841 help="Specify the default directory (cluster-"
842 "wide) for storing the file-based disks [%s]" %
843 constants.DEFAULT_FILE_STORAGE_DIR,
845 default=constants.DEFAULT_FILE_STORAGE_DIR)
847 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
848 help="Don't modify /etc/hosts",
849 action="store_false", default=True)
851 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
852 help="Don't initialize SSH keys",
853 action="store_false", default=True)
855 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
856 help="Enable parseable error messages",
857 action="store_true", default=False)
859 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
860 help="Skip N+1 memory redundancy tests",
861 action="store_true", default=False)
863 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
864 help="Type of reboot: soft/hard/full",
865 default=constants.INSTANCE_REBOOT_HARD,
867 choices=list(constants.REBOOT_TYPES))
869 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
870 dest="ignore_secondaries",
871 default=False, action="store_true",
872 help="Ignore errors from secondaries")
874 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
875 action="store_false", default=True,
876 help="Don't shutdown the instance (unsafe)")
878 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
879 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
880 help="Maximum time to wait")
882 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
883 dest="shutdown_timeout", type="int",
884 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
885 help="Maximum time to wait for instance shutdown")
887 EARLY_RELEASE_OPT = cli_option("--early-release",
888 dest="early_release", default=False,
890 help="Release the locks on the secondary"
893 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
894 dest="new_cluster_cert",
895 default=False, action="store_true",
896 help="Generate a new cluster certificate")
898 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
900 help="File containing new RAPI certificate")
902 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
903 default=None, action="store_true",
904 help=("Generate a new self-signed RAPI"
907 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
908 dest="new_confd_hmac_key",
909 default=False, action="store_true",
910 help=("Create a new HMAC key for %s" %
913 USE_REPL_NET_OPT = cli_option("--use-replication-network",
914 dest="use_replication_network",
915 help="Whether to use the replication network"
916 " for talking to the nodes",
917 action="store_true", default=False)
919 MAINTAIN_NODE_HEALTH_OPT = \
920 cli_option("--maintain-node-health", dest="maintain_node_health",
921 metavar=_YORNO, default=None, type="bool",
922 help="Configure the cluster to automatically maintain node"
923 " health, by shutting down unknown instances, shutting down"
924 " unknown DRBD devices, etc.")
926 IDENTIFY_DEFAULTS_OPT = \
927 cli_option("--identify-defaults", dest="identify_defaults",
928 default=False, action="store_true",
929 help="Identify which saved instance parameters are equal to"
930 " the current cluster defaults and set them as such, instead"
931 " of marking them as overridden")
933 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
934 action="store", dest="uid_pool",
935 help=("A list of user-ids or user-id"
936 " ranges separated by commas"))
939 def _ParseArgs(argv, commands, aliases):
940 """Parser for the command line arguments.
942 This function parses the arguments and returns the function which
943 must be executed together with its (modified) arguments.
945 @param argv: the command line
946 @param commands: dictionary with special contents, see the design
947 doc for cmdline handling
948 @param aliases: dictionary with command aliases {'alias': 'target, ...}
954 binary = argv[0].split("/")[-1]
956 if len(argv) > 1 and argv[1] == "--version":
957 ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
958 # Quit right away. That way we don't have to care about this special
959 # argument. optparse.py does it the same.
962 if len(argv) < 2 or not (argv[1] in commands or
964 # let's do a nice thing
965 sortedcmds = commands.keys()
968 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
969 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
972 # compute the max line length for cmd + usage
973 mlen = max([len(" %s" % cmd) for cmd in commands])
974 mlen = min(60, mlen) # should not get here...
976 # and format a nice command list
977 ToStdout("Commands:")
978 for cmd in sortedcmds:
979 cmdstr = " %s" % (cmd,)
980 help_text = commands[cmd][4]
981 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
982 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
983 for line in help_lines:
984 ToStdout("%-*s %s", mlen, "", line)
988 return None, None, None
990 # get command, unalias it, and look it up in commands
994 raise errors.ProgrammerError("Alias '%s' overrides an existing"
997 if aliases[cmd] not in commands:
998 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
999 " command '%s'" % (cmd, aliases[cmd]))
1003 func, args_def, parser_opts, usage, description = commands[cmd]
1004 parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
1005 description=description,
1006 formatter=TitledHelpFormatter(),
1007 usage="%%prog %s %s" % (cmd, usage))
1008 parser.disable_interspersed_args()
1009 options, args = parser.parse_args()
1011 if not _CheckArguments(cmd, args_def, args):
1012 return None, None, None
1014 return func, options, args
1017 def _CheckArguments(cmd, args_def, args):
1018 """Verifies the arguments using the argument definition.
1022 1. Abort with error if values specified by user but none expected.
1024 1. For each argument in definition
1026 1. Keep running count of minimum number of values (min_count)
1027 1. Keep running count of maximum number of values (max_count)
1028 1. If it has an unlimited number of values
1030 1. Abort with error if it's not the last argument in the definition
1032 1. If last argument has limited number of values
1034 1. Abort with error if number of values doesn't match or is too large
1036 1. Abort with error if user didn't pass enough values (min_count)
1039 if args and not args_def:
1040 ToStderr("Error: Command %s expects no arguments", cmd)
1047 last_idx = len(args_def) - 1
1049 for idx, arg in enumerate(args_def):
1050 if min_count is None:
1052 elif arg.min is not None:
1053 min_count += arg.min
1055 if max_count is None:
1057 elif arg.max is not None:
1058 max_count += arg.max
1061 check_max = (arg.max is not None)
1063 elif arg.max is None:
1064 raise errors.ProgrammerError("Only the last argument can have max=None")
1067 # Command with exact number of arguments
1068 if (min_count is not None and max_count is not None and
1069 min_count == max_count and len(args) != min_count):
1070 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1073 # Command with limited number of arguments
1074 if max_count is not None and len(args) > max_count:
1075 ToStderr("Error: Command %s expects only %d argument(s)",
1079 # Command with some required arguments
1080 if min_count is not None and len(args) < min_count:
1081 ToStderr("Error: Command %s expects at least %d argument(s)",
1088 def SplitNodeOption(value):
1089 """Splits the value of a --node option.
1092 if value and ':' in value:
1093 return value.split(':', 1)
1095 return (value, None)
1098 def CalculateOSNames(os_name, os_variants):
1099 """Calculates all the names an OS can be called, according to its variants.
1101 @type os_name: string
1102 @param os_name: base name of the os
1103 @type os_variants: list or None
1104 @param os_variants: list of supported variants
1106 @return: list of valid names
1110 return ['%s+%s' % (os_name, v) for v in os_variants]
1116 def wrapper(*args, **kwargs):
1119 return fn(*args, **kwargs)
1125 def AskUser(text, choices=None):
1126 """Ask the user a question.
1128 @param text: the question to ask
1130 @param choices: list with elements tuples (input_char, return_value,
1131 description); if not given, it will default to: [('y', True,
1132 'Perform the operation'), ('n', False, 'Do no do the operation')];
1133 note that the '?' char is reserved for help
1135 @return: one of the return values from the choices list; if input is
1136 not possible (i.e. not running with a tty, we return the last
1141 choices = [('y', True, 'Perform the operation'),
1142 ('n', False, 'Do not perform the operation')]
1143 if not choices or not isinstance(choices, list):
1144 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1145 for entry in choices:
1146 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1147 raise errors.ProgrammerError("Invalid choices element to AskUser")
1149 answer = choices[-1][1]
1151 for line in text.splitlines():
1152 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1153 text = "\n".join(new_text)
1155 f = file("/dev/tty", "a+")
1159 chars = [entry[0] for entry in choices]
1160 chars[-1] = "[%s]" % chars[-1]
1162 maps = dict([(entry[0], entry[1]) for entry in choices])
1166 f.write("/".join(chars))
1168 line = f.readline(2).strip().lower()
1173 for entry in choices:
1174 f.write(" %s - %s\n" % (entry[0], entry[2]))
1182 class JobSubmittedException(Exception):
1183 """Job was submitted, client should exit.
1185 This exception has one argument, the ID of the job that was
1186 submitted. The handler should print this ID.
1188 This is not an error, just a structured way to exit from clients.
1193 def SendJob(ops, cl=None):
1194 """Function to submit an opcode without waiting for the results.
1197 @param ops: list of opcodes
1198 @type cl: luxi.Client
1199 @param cl: the luxi client to use for communicating with the master;
1200 if None, a new client will be created
1206 job_id = cl.SubmitJob(ops)
1211 def PollJob(job_id, cl=None, feedback_fn=None):
1212 """Function to poll for the result of a job.
1214 @type job_id: job identified
1215 @param job_id: the job to poll for results
1216 @type cl: luxi.Client
1217 @param cl: the luxi client to use for communicating with the master;
1218 if None, a new client will be created
1224 prev_job_info = None
1225 prev_logmsg_serial = None
1229 notified_queued = False
1230 notified_waitlock = False
1233 result = cl.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1236 # job not found, go away!
1237 raise errors.JobLost("Job with id %s lost" % job_id)
1238 elif result == constants.JOB_NOTCHANGED:
1239 if status is not None and not callable(feedback_fn):
1240 if status == constants.JOB_STATUS_QUEUED and not notified_queued:
1241 ToStderr("Job %s is waiting in queue", job_id)
1242 notified_queued = True
1243 elif status == constants.JOB_STATUS_WAITLOCK and not notified_waitlock:
1244 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1245 notified_waitlock = True
1250 # Split result, a tuple of (field values, log entries)
1251 (job_info, log_entries) = result
1252 (status, ) = job_info
1255 for log_entry in log_entries:
1256 (serial, timestamp, _, message) = log_entry
1257 if callable(feedback_fn):
1258 feedback_fn(log_entry[1:])
1260 encoded = utils.SafeEncode(message)
1261 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
1262 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1264 # TODO: Handle canceled and archived jobs
1265 elif status in (constants.JOB_STATUS_SUCCESS,
1266 constants.JOB_STATUS_ERROR,
1267 constants.JOB_STATUS_CANCELING,
1268 constants.JOB_STATUS_CANCELED):
1271 prev_job_info = job_info
1273 jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1275 raise errors.JobLost("Job with id %s lost" % job_id)
1277 status, opstatus, result = jobs[0]
1278 if status == constants.JOB_STATUS_SUCCESS:
1280 elif status in (constants.JOB_STATUS_CANCELING,
1281 constants.JOB_STATUS_CANCELED):
1282 raise errors.OpExecError("Job was canceled")
1285 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1286 if status == constants.OP_STATUS_SUCCESS:
1288 elif status == constants.OP_STATUS_ERROR:
1289 errors.MaybeRaise(msg)
1291 raise errors.OpExecError("partial failure (opcode %d): %s" %
1294 raise errors.OpExecError(str(msg))
1295 # default failure mode
1296 raise errors.OpExecError(result)
1299 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
1300 """Legacy function to submit an opcode.
1302 This is just a simple wrapper over the construction of the processor
1303 instance. It should be extended to better handle feedback and
1304 interaction functions.
1310 SetGenericOpcodeOpts([op], opts)
1312 job_id = SendJob([op], cl)
1314 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1316 return op_results[0]
1319 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1320 """Wrapper around SubmitOpCode or SendJob.
1322 This function will decide, based on the 'opts' parameter, whether to
1323 submit and wait for the result of the opcode (and return it), or
1324 whether to just send the job and print its identifier. It is used in
1325 order to simplify the implementation of the '--submit' option.
1327 It will also process the opcodes if we're sending the via SendJob
1328 (otherwise SubmitOpCode does it).
1331 if opts and opts.submit_only:
1333 SetGenericOpcodeOpts(job, opts)
1334 job_id = SendJob(job, cl=cl)
1335 raise JobSubmittedException(job_id)
1337 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1340 def SetGenericOpcodeOpts(opcode_list, options):
1341 """Processor for generic options.
1343 This function updates the given opcodes based on generic command
1344 line options (like debug, dry-run, etc.).
1346 @param opcode_list: list of opcodes
1347 @param options: command line options or None
1348 @return: None (in-place modification)
1353 for op in opcode_list:
1354 op.dry_run = options.dry_run
1355 op.debug_level = options.debug
1359 # TODO: Cache object?
1361 client = luxi.Client()
1362 except luxi.NoMasterError:
1363 ss = ssconf.SimpleStore()
1365 # Try to read ssconf file
1368 except errors.ConfigurationError:
1369 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1370 " not part of a cluster")
1372 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1373 if master != myself:
1374 raise errors.OpPrereqError("This is not the master node, please connect"
1375 " to node '%s' and rerun the command" %
1381 def FormatError(err):
1382 """Return a formatted error message for a given error.
1384 This function takes an exception instance and returns a tuple
1385 consisting of two values: first, the recommended exit code, and
1386 second, a string describing the error message (not
1387 newline-terminated).
1393 if isinstance(err, errors.ConfigurationError):
1394 txt = "Corrupt configuration file: %s" % msg
1396 obuf.write(txt + "\n")
1397 obuf.write("Aborting.")
1399 elif isinstance(err, errors.HooksAbort):
1400 obuf.write("Failure: hooks execution failed:\n")
1401 for node, script, out in err.args[0]:
1403 obuf.write(" node: %s, script: %s, output: %s\n" %
1404 (node, script, out))
1406 obuf.write(" node: %s, script: %s (no output)\n" %
1408 elif isinstance(err, errors.HooksFailure):
1409 obuf.write("Failure: hooks general failure: %s" % msg)
1410 elif isinstance(err, errors.ResolverError):
1411 this_host = utils.HostInfo.SysName()
1412 if err.args[0] == this_host:
1413 msg = "Failure: can't resolve my own hostname ('%s')"
1415 msg = "Failure: can't resolve hostname '%s'"
1416 obuf.write(msg % err.args[0])
1417 elif isinstance(err, errors.OpPrereqError):
1418 if len(err.args) == 2:
1419 obuf.write("Failure: prerequisites not met for this"
1420 " operation:\nerror type: %s, error details:\n%s" %
1421 (err.args[1], err.args[0]))
1423 obuf.write("Failure: prerequisites not met for this"
1424 " operation:\n%s" % msg)
1425 elif isinstance(err, errors.OpExecError):
1426 obuf.write("Failure: command execution error:\n%s" % msg)
1427 elif isinstance(err, errors.TagError):
1428 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1429 elif isinstance(err, errors.JobQueueDrainError):
1430 obuf.write("Failure: the job queue is marked for drain and doesn't"
1431 " accept new requests\n")
1432 elif isinstance(err, errors.JobQueueFull):
1433 obuf.write("Failure: the job queue is full and doesn't accept new"
1434 " job submissions until old jobs are archived\n")
1435 elif isinstance(err, errors.TypeEnforcementError):
1436 obuf.write("Parameter Error: %s" % msg)
1437 elif isinstance(err, errors.ParameterError):
1438 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1439 elif isinstance(err, errors.GenericError):
1440 obuf.write("Unhandled Ganeti error: %s" % msg)
1441 elif isinstance(err, luxi.NoMasterError):
1442 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1443 " and listening for connections?")
1444 elif isinstance(err, luxi.TimeoutError):
1445 obuf.write("Timeout while talking to the master daemon. Error:\n"
1447 elif isinstance(err, luxi.ProtocolError):
1448 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1450 elif isinstance(err, JobSubmittedException):
1451 obuf.write("JobID: %s\n" % err.args[0])
1454 obuf.write("Unhandled exception: %s" % msg)
1455 return retcode, obuf.getvalue().rstrip('\n')
1458 def GenericMain(commands, override=None, aliases=None):
1459 """Generic main function for all the gnt-* commands.
1462 - commands: a dictionary with a special structure, see the design doc
1463 for command line handling.
1464 - override: if not None, we expect a dictionary with keys that will
1465 override command line options; this can be used to pass
1466 options from the scripts to generic functions
1467 - aliases: dictionary with command aliases {'alias': 'target, ...}
1470 # save the program name and the entire command line for later logging
1472 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1473 if len(sys.argv) >= 2:
1474 binary += " " + sys.argv[1]
1475 old_cmdline = " ".join(sys.argv[2:])
1479 binary = "<unknown program>"
1486 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1487 except errors.ParameterError, err:
1488 result, err_msg = FormatError(err)
1492 if func is None: # parse error
1495 if override is not None:
1496 for key, val in override.iteritems():
1497 setattr(options, key, val)
1499 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1500 stderr_logging=True, program=binary)
1503 logging.info("run with arguments '%s'", old_cmdline)
1505 logging.info("run with no arguments")
1508 result = func(options, args)
1509 except (errors.GenericError, luxi.ProtocolError,
1510 JobSubmittedException), err:
1511 result, err_msg = FormatError(err)
1512 logging.exception("Error during command processing")
1518 def GenericInstanceCreate(mode, opts, args):
1519 """Add an instance to the cluster via either creation or import.
1521 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1522 @param opts: the command line options selected by the user
1524 @param args: should contain only one element, the new instance name
1526 @return: the desired exit code
1531 (pnode, snode) = SplitNodeOption(opts.node)
1536 hypervisor, hvparams = opts.hypervisor
1540 nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1541 except ValueError, err:
1542 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1543 nics = [{}] * nic_max
1544 for nidx, ndict in opts.nics:
1546 if not isinstance(ndict, dict):
1547 msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1548 raise errors.OpPrereqError(msg)
1553 elif mode == constants.INSTANCE_CREATE:
1554 # default of one nic, all auto
1560 if opts.disk_template == constants.DT_DISKLESS:
1561 if opts.disks or opts.sd_size is not None:
1562 raise errors.OpPrereqError("Diskless instance but disk"
1563 " information passed")
1566 if (not opts.disks and not opts.sd_size
1567 and mode == constants.INSTANCE_CREATE):
1568 raise errors.OpPrereqError("No disk information specified")
1569 if opts.disks and opts.sd_size is not None:
1570 raise errors.OpPrereqError("Please use either the '--disk' or"
1572 if opts.sd_size is not None:
1573 opts.disks = [(0, {"size": opts.sd_size})]
1577 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1578 except ValueError, err:
1579 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1580 disks = [{}] * disk_max
1583 for didx, ddict in opts.disks:
1585 if not isinstance(ddict, dict):
1586 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1587 raise errors.OpPrereqError(msg)
1588 elif "size" in ddict:
1589 if "adopt" in ddict:
1590 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1591 " (disk %d)" % didx)
1593 ddict["size"] = utils.ParseUnit(ddict["size"])
1594 except ValueError, err:
1595 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1597 elif "adopt" in ddict:
1598 if mode == constants.INSTANCE_IMPORT:
1599 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1603 raise errors.OpPrereqError("Missing size or adoption source for"
1607 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1608 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1610 if mode == constants.INSTANCE_CREATE:
1615 no_install = opts.no_install
1616 identify_defaults = False
1617 elif mode == constants.INSTANCE_IMPORT:
1620 src_node = opts.src_node
1621 src_path = opts.src_dir
1623 identify_defaults = opts.identify_defaults
1625 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1627 op = opcodes.OpCreateInstance(instance_name=instance,
1629 disk_template=opts.disk_template,
1631 pnode=pnode, snode=snode,
1632 ip_check=opts.ip_check,
1633 name_check=opts.name_check,
1634 wait_for_sync=opts.wait_for_sync,
1635 file_storage_dir=opts.file_storage_dir,
1636 file_driver=opts.file_driver,
1637 iallocator=opts.iallocator,
1638 hypervisor=hypervisor,
1640 beparams=opts.beparams,
1646 no_install=no_install,
1647 identify_defaults=identify_defaults)
1649 SubmitOrSend(op, opts)
1653 class _RunWhileClusterStoppedHelper:
1654 """Helper class for L{RunWhileClusterStopped} to simplify state management
1657 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
1658 """Initializes this class.
1660 @type feedback_fn: callable
1661 @param feedback_fn: Feedback function
1662 @type cluster_name: string
1663 @param cluster_name: Cluster name
1664 @type master_node: string
1665 @param master_node Master node name
1666 @type online_nodes: list
1667 @param online_nodes: List of names of online nodes
1670 self.feedback_fn = feedback_fn
1671 self.cluster_name = cluster_name
1672 self.master_node = master_node
1673 self.online_nodes = online_nodes
1675 self.ssh = ssh.SshRunner(self.cluster_name)
1677 self.nonmaster_nodes = [name for name in online_nodes
1678 if name != master_node]
1680 assert self.master_node not in self.nonmaster_nodes
1682 def _RunCmd(self, node_name, cmd):
1683 """Runs a command on the local or a remote machine.
1685 @type node_name: string
1686 @param node_name: Machine name
1691 if node_name is None or node_name == self.master_node:
1692 # No need to use SSH
1693 result = utils.RunCmd(cmd)
1695 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
1698 errmsg = ["Failed to run command %s" % result.cmd]
1700 errmsg.append("on node %s" % node_name)
1701 errmsg.append(": exitcode %s and error %s" %
1702 (result.exit_code, result.output))
1703 raise errors.OpExecError(" ".join(errmsg))
1705 def Call(self, fn, *args):
1706 """Call function while all daemons are stopped.
1709 @param fn: Function to be called
1712 # Pause watcher by acquiring an exclusive lock on watcher state file
1713 self.feedback_fn("Blocking watcher")
1714 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
1716 # TODO: Currently, this just blocks. There's no timeout.
1717 # TODO: Should it be a shared lock?
1718 watcher_block.Exclusive(blocking=True)
1720 # Stop master daemons, so that no new jobs can come in and all running
1722 self.feedback_fn("Stopping master daemons")
1723 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
1725 # Stop daemons on all nodes
1726 for node_name in self.online_nodes:
1727 self.feedback_fn("Stopping daemons on %s" % node_name)
1728 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
1730 # All daemons are shut down now
1732 return fn(self, *args)
1733 except Exception, err:
1734 _, errmsg = FormatError(err)
1735 logging.exception("Caught exception")
1736 self.feedback_fn(errmsg)
1739 # Start cluster again, master node last
1740 for node_name in self.nonmaster_nodes + [self.master_node]:
1741 self.feedback_fn("Starting daemons on %s" % node_name)
1742 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
1745 watcher_block.Close()
1748 def RunWhileClusterStopped(feedback_fn, fn, *args):
1749 """Calls a function while all cluster daemons are stopped.
1751 @type feedback_fn: callable
1752 @param feedback_fn: Feedback function
1754 @param fn: Function to be called when daemons are stopped
1757 feedback_fn("Gathering cluster information")
1759 # This ensures we're running on the master daemon
1762 (cluster_name, master_node) = \
1763 cl.QueryConfigValues(["cluster_name", "master_node"])
1765 online_nodes = GetOnlineNodes([], cl=cl)
1767 # Don't keep a reference to the client. The master daemon will go away.
1770 assert master_node in online_nodes
1772 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
1773 online_nodes).Call(fn, *args)
1776 def GenerateTable(headers, fields, separator, data,
1777 numfields=None, unitfields=None,
1779 """Prints a table with headers and different fields.
1782 @param headers: dictionary mapping field names to headers for
1785 @param fields: the field names corresponding to each row in
1787 @param separator: the separator to be used; if this is None,
1788 the default 'smart' algorithm is used which computes optimal
1789 field width, otherwise just the separator is used between
1792 @param data: a list of lists, each sublist being one row to be output
1793 @type numfields: list
1794 @param numfields: a list with the fields that hold numeric
1795 values and thus should be right-aligned
1796 @type unitfields: list
1797 @param unitfields: a list with the fields that hold numeric
1798 values that should be formatted with the units field
1799 @type units: string or None
1800 @param units: the units we should use for formatting, or None for
1801 automatic choice (human-readable for non-separator usage, otherwise
1802 megabytes); this is a one-letter string
1811 if numfields is None:
1813 if unitfields is None:
1816 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
1817 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
1820 for field in fields:
1821 if headers and field not in headers:
1822 # TODO: handle better unknown fields (either revert to old
1823 # style of raising exception, or deal more intelligently with
1825 headers[field] = field
1826 if separator is not None:
1827 format_fields.append("%s")
1828 elif numfields.Matches(field):
1829 format_fields.append("%*s")
1831 format_fields.append("%-*s")
1833 if separator is None:
1834 mlens = [0 for name in fields]
1835 format = ' '.join(format_fields)
1837 format = separator.replace("%", "%%").join(format_fields)
1842 for idx, val in enumerate(row):
1843 if unitfields.Matches(fields[idx]):
1846 except (TypeError, ValueError):
1849 val = row[idx] = utils.FormatUnit(val, units)
1850 val = row[idx] = str(val)
1851 if separator is None:
1852 mlens[idx] = max(mlens[idx], len(val))
1857 for idx, name in enumerate(fields):
1859 if separator is None:
1860 mlens[idx] = max(mlens[idx], len(hdr))
1861 args.append(mlens[idx])
1863 result.append(format % tuple(args))
1865 if separator is None:
1866 assert len(mlens) == len(fields)
1868 if fields and not numfields.Matches(fields[-1]):
1874 line = ['-' for _ in fields]
1875 for idx in range(len(fields)):
1876 if separator is None:
1877 args.append(mlens[idx])
1878 args.append(line[idx])
1879 result.append(format % tuple(args))
1884 def FormatTimestamp(ts):
1885 """Formats a given timestamp.
1888 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1891 @return: a string with the formatted timestamp
1894 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1897 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1900 def ParseTimespec(value):
1901 """Parse a time specification.
1903 The following suffixed will be recognized:
1911 Without any suffix, the value will be taken to be in seconds.
1916 raise errors.OpPrereqError("Empty time specification passed")
1924 if value[-1] not in suffix_map:
1927 except (TypeError, ValueError):
1928 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1930 multiplier = suffix_map[value[-1]]
1932 if not value: # no data left after stripping the suffix
1933 raise errors.OpPrereqError("Invalid time specification (only"
1936 value = int(value) * multiplier
1937 except (TypeError, ValueError):
1938 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1942 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
1943 filter_master=False):
1944 """Returns the names of online nodes.
1946 This function will also log a warning on stderr with the names of
1949 @param nodes: if not empty, use only this subset of nodes (minus the
1951 @param cl: if not None, luxi client to use
1952 @type nowarn: boolean
1953 @param nowarn: by default, this function will output a note with the
1954 offline nodes that are skipped; if this parameter is True the
1955 note is not displayed
1956 @type secondary_ips: boolean
1957 @param secondary_ips: if True, return the secondary IPs instead of the
1958 names, useful for doing network traffic over the replication interface
1960 @type filter_master: boolean
1961 @param filter_master: if True, do not return the master node in the list
1962 (useful in coordination with secondary_ips where we cannot check our
1963 node name against the list)
1975 master_node = cl.QueryConfigValues(["master_node"])[0]
1976 filter_fn = lambda x: x != master_node
1978 filter_fn = lambda _: True
1980 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
1982 offline = [row[0] for row in result if row[1]]
1983 if offline and not nowarn:
1984 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
1985 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
1988 def _ToStream(stream, txt, *args):
1989 """Write a message to a stream, bypassing the logging system
1991 @type stream: file object
1992 @param stream: the file to which we should write
1994 @param txt: the message
1999 stream.write(txt % args)
2006 def ToStdout(txt, *args):
2007 """Write a message to stdout only, bypassing the logging system
2009 This is just a wrapper over _ToStream.
2012 @param txt: the message
2015 _ToStream(sys.stdout, txt, *args)
2018 def ToStderr(txt, *args):
2019 """Write a message to stderr only, bypassing the logging system
2021 This is just a wrapper over _ToStream.
2024 @param txt: the message
2027 _ToStream(sys.stderr, txt, *args)
2030 class JobExecutor(object):
2031 """Class which manages the submission and execution of multiple jobs.
2033 Note that instances of this class should not be reused between
2037 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2042 self.verbose = verbose
2045 self.feedback_fn = feedback_fn
2047 def QueueJob(self, name, *ops):
2048 """Record a job for later submit.
2051 @param name: a description of the job, will be used in WaitJobSet
2053 SetGenericOpcodeOpts(ops, self.opts)
2054 self.queue.append((name, ops))
2056 def SubmitPending(self):
2057 """Submit all pending jobs.
2060 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2061 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2063 self.jobs.append((idx, status, data, name))
2065 def _ChooseJob(self):
2066 """Choose a non-waiting/queued job to poll next.
2069 assert self.jobs, "_ChooseJob called with empty job list"
2071 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2074 for job_data, status in zip(self.jobs, result):
2075 if status[0] in (constants.JOB_STATUS_QUEUED,
2076 constants.JOB_STATUS_WAITLOCK,
2077 constants.JOB_STATUS_CANCELING):
2078 # job is still waiting
2080 # good candidate found
2081 self.jobs.remove(job_data)
2085 return self.jobs.pop(0)
2087 def GetResults(self):
2088 """Wait for and return the results of all jobs.
2091 @return: list of tuples (success, job results), in the same order
2092 as the submitted jobs; if a job has failed, instead of the result
2093 there will be the error message
2097 self.SubmitPending()
2100 ok_jobs = [row[2] for row in self.jobs if row[1]]
2102 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2104 # first, remove any non-submitted jobs
2105 self.jobs, failures = utils.partition(self.jobs, lambda x: x[1])
2106 for idx, _, jid, name in failures:
2107 ToStderr("Failed to submit job for %s: %s", name, jid)
2108 results.append((idx, False, jid))
2111 (idx, _, jid, name) = self._ChooseJob()
2112 ToStdout("Waiting for job %s for %s...", jid, name)
2114 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2116 except (errors.GenericError, luxi.ProtocolError), err:
2117 _, job_result = FormatError(err)
2119 # the error message will always be shown, verbose or not
2120 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2122 results.append((idx, success, job_result))
2124 # sort based on the index, then drop it
2126 results = [i[1:] for i in results]
2130 def WaitOrShow(self, wait):
2131 """Wait for job results or only print the job IDs.
2134 @param wait: whether to wait or not
2138 return self.GetResults()
2141 self.SubmitPending()
2142 for _, status, result, name in self.jobs:
2144 ToStdout("%s: %s", result, name)
2146 ToStderr("Failure for %s: %s", name, result)