4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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
42 from ganeti import qlang
44 from optparse import (OptionParser, TitledHelpFormatter,
45 Option, OptionValueError)
49 # Command line options
61 "CLUSTER_DOMAIN_SECRET_OPT",
77 "FILESTORE_DRIVER_OPT",
86 "DEFAULT_IALLOCATOR_OPT",
87 "IDENTIFY_DEFAULTS_OPT",
89 "IGNORE_FAILURES_OPT",
91 "IGNORE_REMOVE_FAILURES_OPT",
92 "IGNORE_SECONDARIES_OPT",
96 "MAINTAIN_NODE_HEALTH_OPT",
101 "NEW_CLUSTER_CERT_OPT",
102 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
103 "NEW_CONFD_HMAC_KEY_OPT",
107 "NODE_FORCE_JOIN_OPT",
109 "NODE_PLACEMENT_OPT",
113 "NODRBD_STORAGE_OPT",
119 "NOMODIFY_ETCHOSTS_OPT",
120 "NOMODIFY_SSH_SETUP_OPT",
126 "NOSSH_KEYCHECK_OPT",
135 "PREALLOC_WIPE_DISKS_OPT",
136 "PRIMARY_IP_VERSION_OPT",
141 "REMOVE_INSTANCE_OPT",
149 "SHUTDOWN_TIMEOUT_OPT",
164 # Generic functions for CLI programs
167 "GenericInstanceCreate",
173 "JobSubmittedException",
175 "RunWhileClusterStopped",
179 # Formatting functions
180 "ToStderr", "ToStdout",
183 "FormatParameterDict",
192 # command line options support infrastructure
193 "ARGS_MANY_INSTANCES",
212 "OPT_COMPL_INST_ADD_NODES",
213 "OPT_COMPL_MANY_NODES",
214 "OPT_COMPL_ONE_IALLOCATOR",
215 "OPT_COMPL_ONE_INSTANCE",
216 "OPT_COMPL_ONE_NODE",
217 "OPT_COMPL_ONE_NODEGROUP",
223 "COMMON_CREATE_OPTS",
229 #: Priorities (sorted)
231 ("low", constants.OP_PRIO_LOW),
232 ("normal", constants.OP_PRIO_NORMAL),
233 ("high", constants.OP_PRIO_HIGH),
236 #: Priority dictionary for easier lookup
237 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
238 # we migrate to Python 2.6
239 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
241 # Query result status for clients
244 QR_INCOMPLETE) = range(3)
248 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
253 return ("<%s min=%s max=%s>" %
254 (self.__class__.__name__, self.min, self.max))
257 class ArgSuggest(_Argument):
258 """Suggesting argument.
260 Value can be any of the ones passed to the constructor.
263 # pylint: disable-msg=W0622
264 def __init__(self, min=0, max=None, choices=None):
265 _Argument.__init__(self, min=min, max=max)
266 self.choices = choices
269 return ("<%s min=%s max=%s choices=%r>" %
270 (self.__class__.__name__, self.min, self.max, self.choices))
273 class ArgChoice(ArgSuggest):
276 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
277 but value must be one of the choices.
282 class ArgUnknown(_Argument):
283 """Unknown argument to program (e.g. determined at runtime).
288 class ArgInstance(_Argument):
289 """Instances argument.
294 class ArgNode(_Argument):
300 class ArgGroup(_Argument):
301 """Node group argument.
306 class ArgJobId(_Argument):
312 class ArgFile(_Argument):
313 """File path argument.
318 class ArgCommand(_Argument):
324 class ArgHost(_Argument):
330 class ArgOs(_Argument):
337 ARGS_MANY_INSTANCES = [ArgInstance()]
338 ARGS_MANY_NODES = [ArgNode()]
339 ARGS_MANY_GROUPS = [ArgGroup()]
340 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
341 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
342 ARGS_ONE_GROUP = [ArgInstance(min=1, max=1)]
343 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
346 def _ExtractTagsObject(opts, args):
347 """Extract the tag type object.
349 Note that this function will modify its args parameter.
352 if not hasattr(opts, "tag_type"):
353 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
355 if kind == constants.TAG_CLUSTER:
357 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
359 raise errors.OpPrereqError("no arguments passed to the command")
363 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
367 def _ExtendTags(opts, args):
368 """Extend the args if a source file has been given.
370 This function will extend the tags with the contents of the file
371 passed in the 'tags_source' attribute of the opts parameter. A file
372 named '-' will be replaced by stdin.
375 fname = opts.tags_source
381 new_fh = open(fname, "r")
384 # we don't use the nice 'new_data = [line.strip() for line in fh]'
385 # because of python bug 1633941
387 line = new_fh.readline()
390 new_data.append(line.strip())
393 args.extend(new_data)
396 def ListTags(opts, args):
397 """List the tags on a given object.
399 This is a generic implementation that knows how to deal with all
400 three cases of tag objects (cluster, node, instance). The opts
401 argument is expected to contain a tag_type field denoting what
402 object type we work on.
405 kind, name = _ExtractTagsObject(opts, args)
407 result = cl.QueryTags(kind, name)
408 result = list(result)
414 def AddTags(opts, args):
415 """Add tags on a given object.
417 This is a generic implementation that knows how to deal with all
418 three cases of tag objects (cluster, node, instance). The opts
419 argument is expected to contain a tag_type field denoting what
420 object type we work on.
423 kind, name = _ExtractTagsObject(opts, args)
424 _ExtendTags(opts, args)
426 raise errors.OpPrereqError("No tags to be added")
427 op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
428 SubmitOpCode(op, opts=opts)
431 def RemoveTags(opts, args):
432 """Remove tags from a given object.
434 This is a generic implementation that knows how to deal with all
435 three cases of tag objects (cluster, node, instance). The opts
436 argument is expected to contain a tag_type field denoting what
437 object type we work on.
440 kind, name = _ExtractTagsObject(opts, args)
441 _ExtendTags(opts, args)
443 raise errors.OpPrereqError("No tags to be removed")
444 op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
445 SubmitOpCode(op, opts=opts)
448 def check_unit(option, opt, value): # pylint: disable-msg=W0613
449 """OptParsers custom converter for units.
453 return utils.ParseUnit(value)
454 except errors.UnitParseError, err:
455 raise OptionValueError("option %s: %s" % (opt, err))
458 def _SplitKeyVal(opt, data):
459 """Convert a KeyVal string into a dict.
461 This function will convert a key=val[,...] string into a dict. Empty
462 values will be converted specially: keys which have the prefix 'no_'
463 will have the value=False and the prefix stripped, the others will
467 @param opt: a string holding the option name for which we process the
468 data, used in building error messages
470 @param data: a string of the format key=val,key=val,...
472 @return: {key=val, key=val}
473 @raises errors.ParameterError: if there are duplicate keys
478 for elem in utils.UnescapeAndSplit(data, sep=","):
480 key, val = elem.split("=", 1)
482 if elem.startswith(NO_PREFIX):
483 key, val = elem[len(NO_PREFIX):], False
484 elif elem.startswith(UN_PREFIX):
485 key, val = elem[len(UN_PREFIX):], None
487 key, val = elem, True
489 raise errors.ParameterError("Duplicate key '%s' in option %s" %
495 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
496 """Custom parser for ident:key=val,key=val options.
498 This will store the parsed values as a tuple (ident, {key: val}). As such,
499 multiple uses of this option via action=append is possible.
503 ident, rest = value, ''
505 ident, rest = value.split(":", 1)
507 if ident.startswith(NO_PREFIX):
509 msg = "Cannot pass options when removing parameter groups: %s" % value
510 raise errors.ParameterError(msg)
511 retval = (ident[len(NO_PREFIX):], False)
512 elif ident.startswith(UN_PREFIX):
514 msg = "Cannot pass options when removing parameter groups: %s" % value
515 raise errors.ParameterError(msg)
516 retval = (ident[len(UN_PREFIX):], None)
518 kv_dict = _SplitKeyVal(opt, rest)
519 retval = (ident, kv_dict)
523 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
524 """Custom parser class for key=val,key=val options.
526 This will store the parsed values as a dict {key: val}.
529 return _SplitKeyVal(opt, value)
532 def check_bool(option, opt, value): # pylint: disable-msg=W0613
533 """Custom parser for yes/no options.
535 This will store the parsed value as either True or False.
538 value = value.lower()
539 if value == constants.VALUE_FALSE or value == "no":
541 elif value == constants.VALUE_TRUE or value == "yes":
544 raise errors.ParameterError("Invalid boolean value '%s'" % value)
547 # completion_suggestion is normally a list. Using numeric values not evaluating
548 # to False for dynamic completion.
549 (OPT_COMPL_MANY_NODES,
551 OPT_COMPL_ONE_INSTANCE,
553 OPT_COMPL_ONE_IALLOCATOR,
554 OPT_COMPL_INST_ADD_NODES,
555 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
557 OPT_COMPL_ALL = frozenset([
558 OPT_COMPL_MANY_NODES,
560 OPT_COMPL_ONE_INSTANCE,
562 OPT_COMPL_ONE_IALLOCATOR,
563 OPT_COMPL_INST_ADD_NODES,
564 OPT_COMPL_ONE_NODEGROUP,
568 class CliOption(Option):
569 """Custom option class for optparse.
572 ATTRS = Option.ATTRS + [
573 "completion_suggest",
575 TYPES = Option.TYPES + (
581 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
582 TYPE_CHECKER["identkeyval"] = check_ident_key_val
583 TYPE_CHECKER["keyval"] = check_key_val
584 TYPE_CHECKER["unit"] = check_unit
585 TYPE_CHECKER["bool"] = check_bool
588 # optparse.py sets make_option, so we do it for our own option class, too
589 cli_option = CliOption
594 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
595 help="Increase debugging level")
597 NOHDR_OPT = cli_option("--no-headers", default=False,
598 action="store_true", dest="no_headers",
599 help="Don't display column headers")
601 SEP_OPT = cli_option("--separator", default=None,
602 action="store", dest="separator",
603 help=("Separator between output fields"
604 " (defaults to one space)"))
606 USEUNITS_OPT = cli_option("--units", default=None,
607 dest="units", choices=('h', 'm', 'g', 't'),
608 help="Specify units for output (one of h/m/g/t)")
610 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
611 type="string", metavar="FIELDS",
612 help="Comma separated list of output fields")
614 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
615 default=False, help="Force the operation")
617 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
618 default=False, help="Do not require confirmation")
620 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
621 action="store_true", default=False,
622 help=("Ignore offline nodes and do as much"
625 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
626 default=None, help="File with tag names")
628 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
629 default=False, action="store_true",
630 help=("Submit the job and return the job ID, but"
631 " don't wait for the job to finish"))
633 SYNC_OPT = cli_option("--sync", dest="do_locking",
634 default=False, action="store_true",
635 help=("Grab locks while doing the queries"
636 " in order to ensure more consistent results"))
638 DRY_RUN_OPT = cli_option("--dry-run", default=False,
640 help=("Do not execute the operation, just run the"
641 " check steps and verify it it could be"
644 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
646 help="Increase the verbosity of the operation")
648 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
649 action="store_true", dest="simulate_errors",
650 help="Debugging option that makes the operation"
651 " treat most runtime checks as failed")
653 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
654 default=True, action="store_false",
655 help="Don't wait for sync (DANGEROUS!)")
657 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
658 help="Custom disk setup (diskless, file,"
660 default=None, metavar="TEMPL",
661 choices=list(constants.DISK_TEMPLATES))
663 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
664 help="Do not create any network cards for"
667 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
668 help="Relative path under default cluster-wide"
669 " file storage dir to store file-based disks",
670 default=None, metavar="<DIR>")
672 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
673 help="Driver to use for image files",
674 default="loop", metavar="<DRIVER>",
675 choices=list(constants.FILE_DRIVER))
677 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
678 help="Select nodes for the instance automatically"
679 " using the <NAME> iallocator plugin",
680 default=None, type="string",
681 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
683 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
685 help="Set the default instance allocator plugin",
686 default=None, type="string",
687 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
689 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
691 completion_suggest=OPT_COMPL_ONE_OS)
693 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
694 type="keyval", default={},
695 help="OS parameters")
697 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
698 action="store_true", default=False,
699 help="Force an unknown variant")
701 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
702 action="store_true", default=False,
703 help="Do not install the OS (will"
706 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
707 type="keyval", default={},
708 help="Backend parameters")
710 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
711 default={}, dest="hvparams",
712 help="Hypervisor parameters")
714 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
715 help="Hypervisor and hypervisor options, in the"
716 " format hypervisor:option=value,option=value,...",
717 default=None, type="identkeyval")
719 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
720 help="Hypervisor and hypervisor options, in the"
721 " format hypervisor:option=value,option=value,...",
722 default=[], action="append", type="identkeyval")
724 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
725 action="store_false",
726 help="Don't check that the instance's IP"
729 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
730 default=True, action="store_false",
731 help="Don't check that the instance's name"
734 NET_OPT = cli_option("--net",
735 help="NIC parameters", default=[],
736 dest="nics", action="append", type="identkeyval")
738 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
739 dest="disks", action="append", type="identkeyval")
741 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
742 help="Comma-separated list of disks"
743 " indices to act on (e.g. 0,2) (optional,"
744 " defaults to all disks)")
746 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
747 help="Enforces a single-disk configuration using the"
748 " given disk size, in MiB unless a suffix is used",
749 default=None, type="unit", metavar="<size>")
751 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
752 dest="ignore_consistency",
753 action="store_true", default=False,
754 help="Ignore the consistency of the disks on"
757 NONLIVE_OPT = cli_option("--non-live", dest="live",
758 default=True, action="store_false",
759 help="Do a non-live migration (this usually means"
760 " freeze the instance, save the state, transfer and"
761 " only then resume running on the secondary node)")
763 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
765 choices=list(constants.HT_MIGRATION_MODES),
766 help="Override default migration mode (choose"
767 " either live or non-live")
769 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
770 help="Target node and optional secondary node",
771 metavar="<pnode>[:<snode>]",
772 completion_suggest=OPT_COMPL_INST_ADD_NODES)
774 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
775 action="append", metavar="<node>",
776 help="Use only this node (can be used multiple"
777 " times, if not given defaults to all nodes)",
778 completion_suggest=OPT_COMPL_ONE_NODE)
780 NODEGROUP_OPT = cli_option("-g", "--node-group",
782 help="Node group (name or uuid)",
783 metavar="<nodegroup>",
784 default=None, type="string",
785 completion_suggest=OPT_COMPL_ONE_NODEGROUP)
787 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
789 completion_suggest=OPT_COMPL_ONE_NODE)
791 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
792 action="store_false",
793 help="Don't start the instance after creation")
795 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
796 action="store_true", default=False,
797 help="Show command instead of executing it")
799 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
800 default=False, action="store_true",
801 help="Instead of performing the migration, try to"
802 " recover from a failed cleanup. This is safe"
803 " to run even if the instance is healthy, but it"
804 " will create extra replication traffic and "
805 " disrupt briefly the replication (like during the"
808 STATIC_OPT = cli_option("-s", "--static", dest="static",
809 action="store_true", default=False,
810 help="Only show configuration data, not runtime data")
812 ALL_OPT = cli_option("--all", dest="show_all",
813 default=False, action="store_true",
814 help="Show info on all instances on the cluster."
815 " This can take a long time to run, use wisely")
817 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
818 action="store_true", default=False,
819 help="Interactive OS reinstall, lists available"
820 " OS templates for selection")
822 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
823 action="store_true", default=False,
824 help="Remove the instance from the cluster"
825 " configuration even if there are failures"
826 " during the removal process")
828 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
829 dest="ignore_remove_failures",
830 action="store_true", default=False,
831 help="Remove the instance from the"
832 " cluster configuration even if there"
833 " are failures during the removal"
836 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
837 action="store_true", default=False,
838 help="Remove the instance from the cluster")
840 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
841 help="Specifies the new secondary node",
842 metavar="NODE", default=None,
843 completion_suggest=OPT_COMPL_ONE_NODE)
845 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
846 default=False, action="store_true",
847 help="Replace the disk(s) on the primary"
848 " node (only for the drbd template)")
850 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
851 default=False, action="store_true",
852 help="Replace the disk(s) on the secondary"
853 " node (only for the drbd template)")
855 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
856 default=False, action="store_true",
857 help="Lock all nodes and auto-promote as needed"
860 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
861 default=False, action="store_true",
862 help="Automatically replace faulty disks"
863 " (only for the drbd template)")
865 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
866 default=False, action="store_true",
867 help="Ignore current recorded size"
868 " (useful for forcing activation when"
869 " the recorded size is wrong)")
871 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
873 completion_suggest=OPT_COMPL_ONE_NODE)
875 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
878 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
879 help="Specify the secondary ip for the node",
880 metavar="ADDRESS", default=None)
882 READD_OPT = cli_option("--readd", dest="readd",
883 default=False, action="store_true",
884 help="Readd old node after replacing it")
886 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
887 default=True, action="store_false",
888 help="Disable SSH key fingerprint checking")
890 NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
891 default=False, action="store_true",
892 help="Force the joining of a node,"
893 " needed when merging clusters")
895 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
896 type="bool", default=None, metavar=_YORNO,
897 help="Set the master_candidate flag on the node")
899 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
900 type="bool", default=None,
901 help=("Set the offline flag on the node"
902 " (cluster does not communicate with offline"
905 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
906 type="bool", default=None,
907 help=("Set the drained flag on the node"
908 " (excluded from allocation operations)"))
910 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
911 type="bool", default=None, metavar=_YORNO,
912 help="Set the master_capable flag on the node")
914 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
915 type="bool", default=None, metavar=_YORNO,
916 help="Set the vm_capable flag on the node")
918 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
919 type="bool", default=None, metavar=_YORNO,
920 help="Set the allocatable flag on a volume")
922 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
923 help="Disable support for lvm based instances"
925 action="store_false", default=True)
927 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
928 dest="enabled_hypervisors",
929 help="Comma-separated list of hypervisors",
930 type="string", default=None)
932 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
933 type="keyval", default={},
934 help="NIC parameters")
936 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
937 dest="candidate_pool_size", type="int",
938 help="Set the candidate pool size")
940 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
941 help=("Enables LVM and specifies the volume group"
942 " name (cluster-wide) for disk allocation"
943 " [%s]" % constants.DEFAULT_VG),
944 metavar="VG", default=None)
946 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
947 help="Destroy cluster", action="store_true")
949 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
950 help="Skip node agreement check (dangerous)",
951 action="store_true", default=False)
953 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
954 help="Specify the mac prefix for the instance IP"
955 " addresses, in the format XX:XX:XX",
959 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
960 help="Specify the node interface (cluster-wide)"
961 " on which the master IP address will be added"
962 " (cluster init default: %s)" %
963 constants.DEFAULT_BRIDGE,
967 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
968 help="Specify the default directory (cluster-"
969 "wide) for storing the file-based disks [%s]" %
970 constants.DEFAULT_FILE_STORAGE_DIR,
972 default=constants.DEFAULT_FILE_STORAGE_DIR)
974 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
975 help="Don't modify /etc/hosts",
976 action="store_false", default=True)
978 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
979 help="Don't initialize SSH keys",
980 action="store_false", default=True)
982 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
983 help="Enable parseable error messages",
984 action="store_true", default=False)
986 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
987 help="Skip N+1 memory redundancy tests",
988 action="store_true", default=False)
990 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
991 help="Type of reboot: soft/hard/full",
992 default=constants.INSTANCE_REBOOT_HARD,
994 choices=list(constants.REBOOT_TYPES))
996 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
997 dest="ignore_secondaries",
998 default=False, action="store_true",
999 help="Ignore errors from secondaries")
1001 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1002 action="store_false", default=True,
1003 help="Don't shutdown the instance (unsafe)")
1005 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1006 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1007 help="Maximum time to wait")
1009 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1010 dest="shutdown_timeout", type="int",
1011 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1012 help="Maximum time to wait for instance shutdown")
1014 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1016 help=("Number of seconds between repetions of the"
1019 EARLY_RELEASE_OPT = cli_option("--early-release",
1020 dest="early_release", default=False,
1021 action="store_true",
1022 help="Release the locks on the secondary"
1025 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1026 dest="new_cluster_cert",
1027 default=False, action="store_true",
1028 help="Generate a new cluster certificate")
1030 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1032 help="File containing new RAPI certificate")
1034 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1035 default=None, action="store_true",
1036 help=("Generate a new self-signed RAPI"
1039 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1040 dest="new_confd_hmac_key",
1041 default=False, action="store_true",
1042 help=("Create a new HMAC key for %s" %
1045 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1046 dest="cluster_domain_secret",
1048 help=("Load new new cluster domain"
1049 " secret from file"))
1051 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1052 dest="new_cluster_domain_secret",
1053 default=False, action="store_true",
1054 help=("Create a new cluster domain"
1057 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1058 dest="use_replication_network",
1059 help="Whether to use the replication network"
1060 " for talking to the nodes",
1061 action="store_true", default=False)
1063 MAINTAIN_NODE_HEALTH_OPT = \
1064 cli_option("--maintain-node-health", dest="maintain_node_health",
1065 metavar=_YORNO, default=None, type="bool",
1066 help="Configure the cluster to automatically maintain node"
1067 " health, by shutting down unknown instances, shutting down"
1068 " unknown DRBD devices, etc.")
1070 IDENTIFY_DEFAULTS_OPT = \
1071 cli_option("--identify-defaults", dest="identify_defaults",
1072 default=False, action="store_true",
1073 help="Identify which saved instance parameters are equal to"
1074 " the current cluster defaults and set them as such, instead"
1075 " of marking them as overridden")
1077 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1078 action="store", dest="uid_pool",
1079 help=("A list of user-ids or user-id"
1080 " ranges separated by commas"))
1082 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1083 action="store", dest="add_uids",
1084 help=("A list of user-ids or user-id"
1085 " ranges separated by commas, to be"
1086 " added to the user-id pool"))
1088 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1089 action="store", dest="remove_uids",
1090 help=("A list of user-ids or user-id"
1091 " ranges separated by commas, to be"
1092 " removed from the user-id pool"))
1094 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1095 action="store", dest="reserved_lvs",
1096 help=("A comma-separated list of reserved"
1097 " logical volumes names, that will be"
1098 " ignored by cluster verify"))
1100 ROMAN_OPT = cli_option("--roman",
1101 dest="roman_integers", default=False,
1102 action="store_true",
1103 help="Use roman numbers for positive integers")
1105 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1106 action="store", default=None,
1107 help="Specifies usermode helper for DRBD")
1109 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1110 action="store_false", default=True,
1111 help="Disable support for DRBD")
1113 PRIMARY_IP_VERSION_OPT = \
1114 cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1115 action="store", dest="primary_ip_version",
1116 metavar="%d|%d" % (constants.IP4_VERSION,
1117 constants.IP6_VERSION),
1118 help="Cluster-wide IP version for primary IP")
1120 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1121 metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1122 choices=_PRIONAME_TO_VALUE.keys(),
1123 help="Priority for opcode processing")
1125 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1126 type="bool", default=None, metavar=_YORNO,
1127 help="Sets the hidden flag on the OS")
1129 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1130 type="bool", default=None, metavar=_YORNO,
1131 help="Sets the blacklisted flag on the OS")
1133 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1134 type="bool", metavar=_YORNO,
1135 dest="prealloc_wipe_disks",
1136 help=("Wipe disks prior to instance"
1139 NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1140 type="keyval", default=None,
1141 help="Node parameters")
1143 ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1144 action="store", metavar="POLICY", default=None,
1145 help="Allocation policy for the node group")
1147 NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1148 type="bool", metavar=_YORNO,
1149 dest="node_powered",
1150 help="Specify if the SoR for node is powered")
1153 #: Options provided by all commands
1154 COMMON_OPTS = [DEBUG_OPT]
1156 # common options for creating instances. add and import then add their own
1158 COMMON_CREATE_OPTS = [
1163 FILESTORE_DRIVER_OPT,
1180 def _ParseArgs(argv, commands, aliases):
1181 """Parser for the command line arguments.
1183 This function parses the arguments and returns the function which
1184 must be executed together with its (modified) arguments.
1186 @param argv: the command line
1187 @param commands: dictionary with special contents, see the design
1188 doc for cmdline handling
1189 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1193 binary = "<command>"
1195 binary = argv[0].split("/")[-1]
1197 if len(argv) > 1 and argv[1] == "--version":
1198 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1199 constants.RELEASE_VERSION)
1200 # Quit right away. That way we don't have to care about this special
1201 # argument. optparse.py does it the same.
1204 if len(argv) < 2 or not (argv[1] in commands or
1205 argv[1] in aliases):
1206 # let's do a nice thing
1207 sortedcmds = commands.keys()
1210 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1211 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1214 # compute the max line length for cmd + usage
1215 mlen = max([len(" %s" % cmd) for cmd in commands])
1216 mlen = min(60, mlen) # should not get here...
1218 # and format a nice command list
1219 ToStdout("Commands:")
1220 for cmd in sortedcmds:
1221 cmdstr = " %s" % (cmd,)
1222 help_text = commands[cmd][4]
1223 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1224 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1225 for line in help_lines:
1226 ToStdout("%-*s %s", mlen, "", line)
1230 return None, None, None
1232 # get command, unalias it, and look it up in commands
1236 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1239 if aliases[cmd] not in commands:
1240 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1241 " command '%s'" % (cmd, aliases[cmd]))
1245 func, args_def, parser_opts, usage, description = commands[cmd]
1246 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1247 description=description,
1248 formatter=TitledHelpFormatter(),
1249 usage="%%prog %s %s" % (cmd, usage))
1250 parser.disable_interspersed_args()
1251 options, args = parser.parse_args()
1253 if not _CheckArguments(cmd, args_def, args):
1254 return None, None, None
1256 return func, options, args
1259 def _CheckArguments(cmd, args_def, args):
1260 """Verifies the arguments using the argument definition.
1264 1. Abort with error if values specified by user but none expected.
1266 1. For each argument in definition
1268 1. Keep running count of minimum number of values (min_count)
1269 1. Keep running count of maximum number of values (max_count)
1270 1. If it has an unlimited number of values
1272 1. Abort with error if it's not the last argument in the definition
1274 1. If last argument has limited number of values
1276 1. Abort with error if number of values doesn't match or is too large
1278 1. Abort with error if user didn't pass enough values (min_count)
1281 if args and not args_def:
1282 ToStderr("Error: Command %s expects no arguments", cmd)
1289 last_idx = len(args_def) - 1
1291 for idx, arg in enumerate(args_def):
1292 if min_count is None:
1294 elif arg.min is not None:
1295 min_count += arg.min
1297 if max_count is None:
1299 elif arg.max is not None:
1300 max_count += arg.max
1303 check_max = (arg.max is not None)
1305 elif arg.max is None:
1306 raise errors.ProgrammerError("Only the last argument can have max=None")
1309 # Command with exact number of arguments
1310 if (min_count is not None and max_count is not None and
1311 min_count == max_count and len(args) != min_count):
1312 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1315 # Command with limited number of arguments
1316 if max_count is not None and len(args) > max_count:
1317 ToStderr("Error: Command %s expects only %d argument(s)",
1321 # Command with some required arguments
1322 if min_count is not None and len(args) < min_count:
1323 ToStderr("Error: Command %s expects at least %d argument(s)",
1330 def SplitNodeOption(value):
1331 """Splits the value of a --node option.
1334 if value and ':' in value:
1335 return value.split(':', 1)
1337 return (value, None)
1340 def CalculateOSNames(os_name, os_variants):
1341 """Calculates all the names an OS can be called, according to its variants.
1343 @type os_name: string
1344 @param os_name: base name of the os
1345 @type os_variants: list or None
1346 @param os_variants: list of supported variants
1348 @return: list of valid names
1352 return ['%s+%s' % (os_name, v) for v in os_variants]
1357 def ParseFields(selected, default):
1358 """Parses the values of "--field"-like options.
1360 @type selected: string or None
1361 @param selected: User-selected options
1363 @param default: Default fields
1366 if selected is None:
1369 if selected.startswith("+"):
1370 return default + selected[1:].split(",")
1372 return selected.split(",")
1375 UsesRPC = rpc.RunWithRPC
1378 def AskUser(text, choices=None):
1379 """Ask the user a question.
1381 @param text: the question to ask
1383 @param choices: list with elements tuples (input_char, return_value,
1384 description); if not given, it will default to: [('y', True,
1385 'Perform the operation'), ('n', False, 'Do no do the operation')];
1386 note that the '?' char is reserved for help
1388 @return: one of the return values from the choices list; if input is
1389 not possible (i.e. not running with a tty, we return the last
1394 choices = [('y', True, 'Perform the operation'),
1395 ('n', False, 'Do not perform the operation')]
1396 if not choices or not isinstance(choices, list):
1397 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1398 for entry in choices:
1399 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1400 raise errors.ProgrammerError("Invalid choices element to AskUser")
1402 answer = choices[-1][1]
1404 for line in text.splitlines():
1405 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1406 text = "\n".join(new_text)
1408 f = file("/dev/tty", "a+")
1412 chars = [entry[0] for entry in choices]
1413 chars[-1] = "[%s]" % chars[-1]
1415 maps = dict([(entry[0], entry[1]) for entry in choices])
1419 f.write("/".join(chars))
1421 line = f.readline(2).strip().lower()
1426 for entry in choices:
1427 f.write(" %s - %s\n" % (entry[0], entry[2]))
1435 class JobSubmittedException(Exception):
1436 """Job was submitted, client should exit.
1438 This exception has one argument, the ID of the job that was
1439 submitted. The handler should print this ID.
1441 This is not an error, just a structured way to exit from clients.
1446 def SendJob(ops, cl=None):
1447 """Function to submit an opcode without waiting for the results.
1450 @param ops: list of opcodes
1451 @type cl: luxi.Client
1452 @param cl: the luxi client to use for communicating with the master;
1453 if None, a new client will be created
1459 job_id = cl.SubmitJob(ops)
1464 def GenericPollJob(job_id, cbs, report_cbs):
1465 """Generic job-polling function.
1467 @type job_id: number
1468 @param job_id: Job ID
1469 @type cbs: Instance of L{JobPollCbBase}
1470 @param cbs: Data callbacks
1471 @type report_cbs: Instance of L{JobPollReportCbBase}
1472 @param report_cbs: Reporting callbacks
1475 prev_job_info = None
1476 prev_logmsg_serial = None
1481 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1484 # job not found, go away!
1485 raise errors.JobLost("Job with id %s lost" % job_id)
1487 if result == constants.JOB_NOTCHANGED:
1488 report_cbs.ReportNotChanged(job_id, status)
1493 # Split result, a tuple of (field values, log entries)
1494 (job_info, log_entries) = result
1495 (status, ) = job_info
1498 for log_entry in log_entries:
1499 (serial, timestamp, log_type, message) = log_entry
1500 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1502 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1504 # TODO: Handle canceled and archived jobs
1505 elif status in (constants.JOB_STATUS_SUCCESS,
1506 constants.JOB_STATUS_ERROR,
1507 constants.JOB_STATUS_CANCELING,
1508 constants.JOB_STATUS_CANCELED):
1511 prev_job_info = job_info
1513 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1515 raise errors.JobLost("Job with id %s lost" % job_id)
1517 status, opstatus, result = jobs[0]
1519 if status == constants.JOB_STATUS_SUCCESS:
1522 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1523 raise errors.OpExecError("Job was canceled")
1526 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1527 if status == constants.OP_STATUS_SUCCESS:
1529 elif status == constants.OP_STATUS_ERROR:
1530 errors.MaybeRaise(msg)
1533 raise errors.OpExecError("partial failure (opcode %d): %s" %
1536 raise errors.OpExecError(str(msg))
1538 # default failure mode
1539 raise errors.OpExecError(result)
1542 class JobPollCbBase:
1543 """Base class for L{GenericPollJob} callbacks.
1547 """Initializes this class.
1551 def WaitForJobChangeOnce(self, job_id, fields,
1552 prev_job_info, prev_log_serial):
1553 """Waits for changes on a job.
1556 raise NotImplementedError()
1558 def QueryJobs(self, job_ids, fields):
1559 """Returns the selected fields for the selected job IDs.
1561 @type job_ids: list of numbers
1562 @param job_ids: Job IDs
1563 @type fields: list of strings
1564 @param fields: Fields
1567 raise NotImplementedError()
1570 class JobPollReportCbBase:
1571 """Base class for L{GenericPollJob} reporting callbacks.
1575 """Initializes this class.
1579 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1580 """Handles a log message.
1583 raise NotImplementedError()
1585 def ReportNotChanged(self, job_id, status):
1586 """Called for if a job hasn't changed in a while.
1588 @type job_id: number
1589 @param job_id: Job ID
1590 @type status: string or None
1591 @param status: Job status if available
1594 raise NotImplementedError()
1597 class _LuxiJobPollCb(JobPollCbBase):
1598 def __init__(self, cl):
1599 """Initializes this class.
1602 JobPollCbBase.__init__(self)
1605 def WaitForJobChangeOnce(self, job_id, fields,
1606 prev_job_info, prev_log_serial):
1607 """Waits for changes on a job.
1610 return self.cl.WaitForJobChangeOnce(job_id, fields,
1611 prev_job_info, prev_log_serial)
1613 def QueryJobs(self, job_ids, fields):
1614 """Returns the selected fields for the selected job IDs.
1617 return self.cl.QueryJobs(job_ids, fields)
1620 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1621 def __init__(self, feedback_fn):
1622 """Initializes this class.
1625 JobPollReportCbBase.__init__(self)
1627 self.feedback_fn = feedback_fn
1629 assert callable(feedback_fn)
1631 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1632 """Handles a log message.
1635 self.feedback_fn((timestamp, log_type, log_msg))
1637 def ReportNotChanged(self, job_id, status):
1638 """Called if a job hasn't changed in a while.
1644 class StdioJobPollReportCb(JobPollReportCbBase):
1646 """Initializes this class.
1649 JobPollReportCbBase.__init__(self)
1651 self.notified_queued = False
1652 self.notified_waitlock = False
1654 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1655 """Handles a log message.
1658 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1659 FormatLogMessage(log_type, log_msg))
1661 def ReportNotChanged(self, job_id, status):
1662 """Called if a job hasn't changed in a while.
1668 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1669 ToStderr("Job %s is waiting in queue", job_id)
1670 self.notified_queued = True
1672 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1673 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1674 self.notified_waitlock = True
1677 def FormatLogMessage(log_type, log_msg):
1678 """Formats a job message according to its type.
1681 if log_type != constants.ELOG_MESSAGE:
1682 log_msg = str(log_msg)
1684 return utils.SafeEncode(log_msg)
1687 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1688 """Function to poll for the result of a job.
1690 @type job_id: job identified
1691 @param job_id: the job to poll for results
1692 @type cl: luxi.Client
1693 @param cl: the luxi client to use for communicating with the master;
1694 if None, a new client will be created
1700 if reporter is None:
1702 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1704 reporter = StdioJobPollReportCb()
1706 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1708 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1711 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1712 """Legacy function to submit an opcode.
1714 This is just a simple wrapper over the construction of the processor
1715 instance. It should be extended to better handle feedback and
1716 interaction functions.
1722 SetGenericOpcodeOpts([op], opts)
1724 job_id = SendJob([op], cl=cl)
1726 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1729 return op_results[0]
1732 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1733 """Wrapper around SubmitOpCode or SendJob.
1735 This function will decide, based on the 'opts' parameter, whether to
1736 submit and wait for the result of the opcode (and return it), or
1737 whether to just send the job and print its identifier. It is used in
1738 order to simplify the implementation of the '--submit' option.
1740 It will also process the opcodes if we're sending the via SendJob
1741 (otherwise SubmitOpCode does it).
1744 if opts and opts.submit_only:
1746 SetGenericOpcodeOpts(job, opts)
1747 job_id = SendJob(job, cl=cl)
1748 raise JobSubmittedException(job_id)
1750 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1753 def SetGenericOpcodeOpts(opcode_list, options):
1754 """Processor for generic options.
1756 This function updates the given opcodes based on generic command
1757 line options (like debug, dry-run, etc.).
1759 @param opcode_list: list of opcodes
1760 @param options: command line options or None
1761 @return: None (in-place modification)
1766 for op in opcode_list:
1767 op.debug_level = options.debug
1768 if hasattr(options, "dry_run"):
1769 op.dry_run = options.dry_run
1770 if getattr(options, "priority", None) is not None:
1771 op.priority = _PRIONAME_TO_VALUE[options.priority]
1775 # TODO: Cache object?
1777 client = luxi.Client()
1778 except luxi.NoMasterError:
1779 ss = ssconf.SimpleStore()
1781 # Try to read ssconf file
1784 except errors.ConfigurationError:
1785 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1786 " not part of a cluster")
1788 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1789 if master != myself:
1790 raise errors.OpPrereqError("This is not the master node, please connect"
1791 " to node '%s' and rerun the command" %
1797 def FormatError(err):
1798 """Return a formatted error message for a given error.
1800 This function takes an exception instance and returns a tuple
1801 consisting of two values: first, the recommended exit code, and
1802 second, a string describing the error message (not
1803 newline-terminated).
1809 if isinstance(err, errors.ConfigurationError):
1810 txt = "Corrupt configuration file: %s" % msg
1812 obuf.write(txt + "\n")
1813 obuf.write("Aborting.")
1815 elif isinstance(err, errors.HooksAbort):
1816 obuf.write("Failure: hooks execution failed:\n")
1817 for node, script, out in err.args[0]:
1819 obuf.write(" node: %s, script: %s, output: %s\n" %
1820 (node, script, out))
1822 obuf.write(" node: %s, script: %s (no output)\n" %
1824 elif isinstance(err, errors.HooksFailure):
1825 obuf.write("Failure: hooks general failure: %s" % msg)
1826 elif isinstance(err, errors.ResolverError):
1827 this_host = netutils.Hostname.GetSysName()
1828 if err.args[0] == this_host:
1829 msg = "Failure: can't resolve my own hostname ('%s')"
1831 msg = "Failure: can't resolve hostname '%s'"
1832 obuf.write(msg % err.args[0])
1833 elif isinstance(err, errors.OpPrereqError):
1834 if len(err.args) == 2:
1835 obuf.write("Failure: prerequisites not met for this"
1836 " operation:\nerror type: %s, error details:\n%s" %
1837 (err.args[1], err.args[0]))
1839 obuf.write("Failure: prerequisites not met for this"
1840 " operation:\n%s" % msg)
1841 elif isinstance(err, errors.OpExecError):
1842 obuf.write("Failure: command execution error:\n%s" % msg)
1843 elif isinstance(err, errors.TagError):
1844 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1845 elif isinstance(err, errors.JobQueueDrainError):
1846 obuf.write("Failure: the job queue is marked for drain and doesn't"
1847 " accept new requests\n")
1848 elif isinstance(err, errors.JobQueueFull):
1849 obuf.write("Failure: the job queue is full and doesn't accept new"
1850 " job submissions until old jobs are archived\n")
1851 elif isinstance(err, errors.TypeEnforcementError):
1852 obuf.write("Parameter Error: %s" % msg)
1853 elif isinstance(err, errors.ParameterError):
1854 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1855 elif isinstance(err, luxi.NoMasterError):
1856 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1857 " and listening for connections?")
1858 elif isinstance(err, luxi.TimeoutError):
1859 obuf.write("Timeout while talking to the master daemon. Jobs might have"
1860 " been submitted and will continue to run even if the call"
1861 " timed out. Useful commands in this situation are \"gnt-job"
1862 " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
1864 elif isinstance(err, luxi.PermissionError):
1865 obuf.write("It seems you don't have permissions to connect to the"
1866 " master daemon.\nPlease retry as a different user.")
1867 elif isinstance(err, luxi.ProtocolError):
1868 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1870 elif isinstance(err, errors.JobLost):
1871 obuf.write("Error checking job status: %s" % msg)
1872 elif isinstance(err, errors.GenericError):
1873 obuf.write("Unhandled Ganeti error: %s" % msg)
1874 elif isinstance(err, JobSubmittedException):
1875 obuf.write("JobID: %s\n" % err.args[0])
1878 obuf.write("Unhandled exception: %s" % msg)
1879 return retcode, obuf.getvalue().rstrip('\n')
1882 def GenericMain(commands, override=None, aliases=None):
1883 """Generic main function for all the gnt-* commands.
1886 - commands: a dictionary with a special structure, see the design doc
1887 for command line handling.
1888 - override: if not None, we expect a dictionary with keys that will
1889 override command line options; this can be used to pass
1890 options from the scripts to generic functions
1891 - aliases: dictionary with command aliases {'alias': 'target, ...}
1894 # save the program name and the entire command line for later logging
1896 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1897 if len(sys.argv) >= 2:
1898 binary += " " + sys.argv[1]
1899 old_cmdline = " ".join(sys.argv[2:])
1903 binary = "<unknown program>"
1910 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1911 except errors.ParameterError, err:
1912 result, err_msg = FormatError(err)
1916 if func is None: # parse error
1919 if override is not None:
1920 for key, val in override.iteritems():
1921 setattr(options, key, val)
1923 utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
1924 stderr_logging=True)
1927 logging.info("run with arguments '%s'", old_cmdline)
1929 logging.info("run with no arguments")
1932 result = func(options, args)
1933 except (errors.GenericError, luxi.ProtocolError,
1934 JobSubmittedException), err:
1935 result, err_msg = FormatError(err)
1936 logging.exception("Error during command processing")
1938 except KeyboardInterrupt:
1939 result = constants.EXIT_FAILURE
1940 ToStderr("Aborted. Note that if the operation created any jobs, they"
1941 " might have been submitted and"
1942 " will continue to run in the background.")
1947 def ParseNicOption(optvalue):
1948 """Parses the value of the --net option(s).
1952 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
1953 except (TypeError, ValueError), err:
1954 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1956 nics = [{}] * nic_max
1957 for nidx, ndict in optvalue:
1960 if not isinstance(ndict, dict):
1961 raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
1962 " got %s" % (nidx, ndict))
1964 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
1971 def GenericInstanceCreate(mode, opts, args):
1972 """Add an instance to the cluster via either creation or import.
1974 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1975 @param opts: the command line options selected by the user
1977 @param args: should contain only one element, the new instance name
1979 @return: the desired exit code
1984 (pnode, snode) = SplitNodeOption(opts.node)
1989 hypervisor, hvparams = opts.hypervisor
1992 nics = ParseNicOption(opts.nics)
1996 elif mode == constants.INSTANCE_CREATE:
1997 # default of one nic, all auto
2003 if opts.disk_template == constants.DT_DISKLESS:
2004 if opts.disks or opts.sd_size is not None:
2005 raise errors.OpPrereqError("Diskless instance but disk"
2006 " information passed")
2009 if (not opts.disks and not opts.sd_size
2010 and mode == constants.INSTANCE_CREATE):
2011 raise errors.OpPrereqError("No disk information specified")
2012 if opts.disks and opts.sd_size is not None:
2013 raise errors.OpPrereqError("Please use either the '--disk' or"
2015 if opts.sd_size is not None:
2016 opts.disks = [(0, {"size": opts.sd_size})]
2020 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2021 except ValueError, err:
2022 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
2023 disks = [{}] * disk_max
2026 for didx, ddict in opts.disks:
2028 if not isinstance(ddict, dict):
2029 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2030 raise errors.OpPrereqError(msg)
2031 elif "size" in ddict:
2032 if "adopt" in ddict:
2033 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2034 " (disk %d)" % didx)
2036 ddict["size"] = utils.ParseUnit(ddict["size"])
2037 except ValueError, err:
2038 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2040 elif "adopt" in ddict:
2041 if mode == constants.INSTANCE_IMPORT:
2042 raise errors.OpPrereqError("Disk adoption not allowed for instance"
2046 raise errors.OpPrereqError("Missing size or adoption source for"
2050 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2051 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2053 if mode == constants.INSTANCE_CREATE:
2056 force_variant = opts.force_variant
2059 no_install = opts.no_install
2060 identify_defaults = False
2061 elif mode == constants.INSTANCE_IMPORT:
2064 force_variant = False
2065 src_node = opts.src_node
2066 src_path = opts.src_dir
2068 identify_defaults = opts.identify_defaults
2070 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2072 op = opcodes.OpInstanceCreate(instance_name=instance,
2074 disk_template=opts.disk_template,
2076 pnode=pnode, snode=snode,
2077 ip_check=opts.ip_check,
2078 name_check=opts.name_check,
2079 wait_for_sync=opts.wait_for_sync,
2080 file_storage_dir=opts.file_storage_dir,
2081 file_driver=opts.file_driver,
2082 iallocator=opts.iallocator,
2083 hypervisor=hypervisor,
2085 beparams=opts.beparams,
2086 osparams=opts.osparams,
2090 force_variant=force_variant,
2093 no_install=no_install,
2094 identify_defaults=identify_defaults)
2096 SubmitOrSend(op, opts)
2100 class _RunWhileClusterStoppedHelper:
2101 """Helper class for L{RunWhileClusterStopped} to simplify state management
2104 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2105 """Initializes this class.
2107 @type feedback_fn: callable
2108 @param feedback_fn: Feedback function
2109 @type cluster_name: string
2110 @param cluster_name: Cluster name
2111 @type master_node: string
2112 @param master_node Master node name
2113 @type online_nodes: list
2114 @param online_nodes: List of names of online nodes
2117 self.feedback_fn = feedback_fn
2118 self.cluster_name = cluster_name
2119 self.master_node = master_node
2120 self.online_nodes = online_nodes
2122 self.ssh = ssh.SshRunner(self.cluster_name)
2124 self.nonmaster_nodes = [name for name in online_nodes
2125 if name != master_node]
2127 assert self.master_node not in self.nonmaster_nodes
2129 def _RunCmd(self, node_name, cmd):
2130 """Runs a command on the local or a remote machine.
2132 @type node_name: string
2133 @param node_name: Machine name
2138 if node_name is None or node_name == self.master_node:
2139 # No need to use SSH
2140 result = utils.RunCmd(cmd)
2142 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2145 errmsg = ["Failed to run command %s" % result.cmd]
2147 errmsg.append("on node %s" % node_name)
2148 errmsg.append(": exitcode %s and error %s" %
2149 (result.exit_code, result.output))
2150 raise errors.OpExecError(" ".join(errmsg))
2152 def Call(self, fn, *args):
2153 """Call function while all daemons are stopped.
2156 @param fn: Function to be called
2159 # Pause watcher by acquiring an exclusive lock on watcher state file
2160 self.feedback_fn("Blocking watcher")
2161 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2163 # TODO: Currently, this just blocks. There's no timeout.
2164 # TODO: Should it be a shared lock?
2165 watcher_block.Exclusive(blocking=True)
2167 # Stop master daemons, so that no new jobs can come in and all running
2169 self.feedback_fn("Stopping master daemons")
2170 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2172 # Stop daemons on all nodes
2173 for node_name in self.online_nodes:
2174 self.feedback_fn("Stopping daemons on %s" % node_name)
2175 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2177 # All daemons are shut down now
2179 return fn(self, *args)
2180 except Exception, err:
2181 _, errmsg = FormatError(err)
2182 logging.exception("Caught exception")
2183 self.feedback_fn(errmsg)
2186 # Start cluster again, master node last
2187 for node_name in self.nonmaster_nodes + [self.master_node]:
2188 self.feedback_fn("Starting daemons on %s" % node_name)
2189 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2192 watcher_block.Close()
2195 def RunWhileClusterStopped(feedback_fn, fn, *args):
2196 """Calls a function while all cluster daemons are stopped.
2198 @type feedback_fn: callable
2199 @param feedback_fn: Feedback function
2201 @param fn: Function to be called when daemons are stopped
2204 feedback_fn("Gathering cluster information")
2206 # This ensures we're running on the master daemon
2209 (cluster_name, master_node) = \
2210 cl.QueryConfigValues(["cluster_name", "master_node"])
2212 online_nodes = GetOnlineNodes([], cl=cl)
2214 # Don't keep a reference to the client. The master daemon will go away.
2217 assert master_node in online_nodes
2219 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2220 online_nodes).Call(fn, *args)
2223 def GenerateTable(headers, fields, separator, data,
2224 numfields=None, unitfields=None,
2226 """Prints a table with headers and different fields.
2229 @param headers: dictionary mapping field names to headers for
2232 @param fields: the field names corresponding to each row in
2234 @param separator: the separator to be used; if this is None,
2235 the default 'smart' algorithm is used which computes optimal
2236 field width, otherwise just the separator is used between
2239 @param data: a list of lists, each sublist being one row to be output
2240 @type numfields: list
2241 @param numfields: a list with the fields that hold numeric
2242 values and thus should be right-aligned
2243 @type unitfields: list
2244 @param unitfields: a list with the fields that hold numeric
2245 values that should be formatted with the units field
2246 @type units: string or None
2247 @param units: the units we should use for formatting, or None for
2248 automatic choice (human-readable for non-separator usage, otherwise
2249 megabytes); this is a one-letter string
2258 if numfields is None:
2260 if unitfields is None:
2263 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2264 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2267 for field in fields:
2268 if headers and field not in headers:
2269 # TODO: handle better unknown fields (either revert to old
2270 # style of raising exception, or deal more intelligently with
2272 headers[field] = field
2273 if separator is not None:
2274 format_fields.append("%s")
2275 elif numfields.Matches(field):
2276 format_fields.append("%*s")
2278 format_fields.append("%-*s")
2280 if separator is None:
2281 mlens = [0 for name in fields]
2282 format_str = ' '.join(format_fields)
2284 format_str = separator.replace("%", "%%").join(format_fields)
2289 for idx, val in enumerate(row):
2290 if unitfields.Matches(fields[idx]):
2293 except (TypeError, ValueError):
2296 val = row[idx] = utils.FormatUnit(val, units)
2297 val = row[idx] = str(val)
2298 if separator is None:
2299 mlens[idx] = max(mlens[idx], len(val))
2304 for idx, name in enumerate(fields):
2306 if separator is None:
2307 mlens[idx] = max(mlens[idx], len(hdr))
2308 args.append(mlens[idx])
2310 result.append(format_str % tuple(args))
2312 if separator is None:
2313 assert len(mlens) == len(fields)
2315 if fields and not numfields.Matches(fields[-1]):
2321 line = ['-' for _ in fields]
2322 for idx in range(len(fields)):
2323 if separator is None:
2324 args.append(mlens[idx])
2325 args.append(line[idx])
2326 result.append(format_str % tuple(args))
2331 def _FormatBool(value):
2332 """Formats a boolean value as a string.
2340 #: Default formatting for query results; (callback, align right)
2341 _DEFAULT_FORMAT_QUERY = {
2342 constants.QFT_TEXT: (str, False),
2343 constants.QFT_BOOL: (_FormatBool, False),
2344 constants.QFT_NUMBER: (str, True),
2345 constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2346 constants.QFT_OTHER: (str, False),
2347 constants.QFT_UNKNOWN: (str, False),
2351 def _GetColumnFormatter(fdef, override, unit):
2352 """Returns formatting function for a field.
2354 @type fdef: L{objects.QueryFieldDefinition}
2355 @type override: dict
2356 @param override: Dictionary for overriding field formatting functions,
2357 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2359 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2360 @rtype: tuple; (callable, bool)
2361 @return: Returns the function to format a value (takes one parameter) and a
2362 boolean for aligning the value on the right-hand side
2365 fmt = override.get(fdef.name, None)
2369 assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2371 if fdef.kind == constants.QFT_UNIT:
2372 # Can't keep this information in the static dictionary
2373 return (lambda value: utils.FormatUnit(value, unit), True)
2375 fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2379 raise NotImplementedError("Can't format column type '%s'" % fdef.kind)
2382 class _QueryColumnFormatter:
2383 """Callable class for formatting fields of a query.
2386 def __init__(self, fn, status_fn, verbose):
2387 """Initializes this class.
2390 @param fn: Formatting function
2391 @type status_fn: callable
2392 @param status_fn: Function to report fields' status
2393 @type verbose: boolean
2394 @param verbose: whether to use verbose field descriptions or not
2398 self._status_fn = status_fn
2399 self._verbose = verbose
2401 def __call__(self, data):
2402 """Returns a field's string representation.
2405 (status, value) = data
2408 self._status_fn(status)
2410 if status == constants.RS_NORMAL:
2411 return self._fn(value)
2413 assert value is None, \
2414 "Found value %r for abnormal status %s" % (value, status)
2416 return FormatResultError(status, self._verbose)
2419 def FormatResultError(status, verbose):
2420 """Formats result status other than L{constants.RS_NORMAL}.
2422 @param status: The result status
2423 @type verbose: boolean
2424 @param verbose: Whether to return the verbose text
2425 @return: Text of result status
2428 assert status != constants.RS_NORMAL, \
2429 "FormatResultError called with status equal to constants.RS_NORMAL"
2431 (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2433 raise NotImplementedError("Unknown status %s" % status)
2440 def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2441 header=False, verbose=False):
2442 """Formats data in L{objects.QueryResponse}.
2444 @type result: L{objects.QueryResponse}
2445 @param result: result of query operation
2447 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2448 see L{utils.text.FormatUnit}
2449 @type format_override: dict
2450 @param format_override: Dictionary for overriding field formatting functions,
2451 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2452 @type separator: string or None
2453 @param separator: String used to separate fields
2455 @param header: Whether to output header row
2456 @type verbose: boolean
2457 @param verbose: whether to use verbose field descriptions or not
2466 if format_override is None:
2467 format_override = {}
2469 stats = dict.fromkeys(constants.RS_ALL, 0)
2471 def _RecordStatus(status):
2476 for fdef in result.fields:
2477 assert fdef.title and fdef.name
2478 (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2479 columns.append(TableColumn(fdef.title,
2480 _QueryColumnFormatter(fn, _RecordStatus,
2484 table = FormatTable(result.data, columns, header, separator)
2486 # Collect statistics
2487 assert len(stats) == len(constants.RS_ALL)
2488 assert compat.all(count >= 0 for count in stats.values())
2490 # Determine overall status. If there was no data, unknown fields must be
2491 # detected via the field definitions.
2492 if (stats[constants.RS_UNKNOWN] or
2493 (not result.data and _GetUnknownFields(result.fields))):
2495 elif compat.any(count > 0 for key, count in stats.items()
2496 if key != constants.RS_NORMAL):
2497 status = QR_INCOMPLETE
2501 return (status, table)
2504 def _GetUnknownFields(fdefs):
2505 """Returns list of unknown fields included in C{fdefs}.
2507 @type fdefs: list of L{objects.QueryFieldDefinition}
2510 return [fdef for fdef in fdefs
2511 if fdef.kind == constants.QFT_UNKNOWN]
2514 def _WarnUnknownFields(fdefs):
2515 """Prints a warning to stderr if a query included unknown fields.
2517 @type fdefs: list of L{objects.QueryFieldDefinition}
2520 unknown = _GetUnknownFields(fdefs)
2522 ToStderr("Warning: Queried for unknown fields %s",
2523 utils.CommaJoin(fdef.name for fdef in unknown))
2529 def GenericList(resource, fields, names, unit, separator, header, cl=None,
2530 format_override=None, verbose=False):
2531 """Generic implementation for listing all items of a resource.
2533 @param resource: One of L{constants.QR_OP_LUXI}
2534 @type fields: list of strings
2535 @param fields: List of fields to query for
2536 @type names: list of strings
2537 @param names: Names of items to query for
2538 @type unit: string or None
2539 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2540 None for automatic choice (human-readable for non-separator usage,
2541 otherwise megabytes); this is a one-letter string
2542 @type separator: string or None
2543 @param separator: String used to separate fields
2545 @param header: Whether to show header row
2546 @type format_override: dict
2547 @param format_override: Dictionary for overriding field formatting functions,
2548 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2549 @type verbose: boolean
2550 @param verbose: whether to use verbose field descriptions or not
2559 response = cl.Query(resource, fields, qlang.MakeSimpleFilter("name", names))
2561 found_unknown = _WarnUnknownFields(response.fields)
2563 (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2565 format_override=format_override,
2571 assert ((found_unknown and status == QR_UNKNOWN) or
2572 (not found_unknown and status != QR_UNKNOWN))
2574 if status == QR_UNKNOWN:
2575 return constants.EXIT_UNKNOWN_FIELD
2577 # TODO: Should the list command fail if not all data could be collected?
2578 return constants.EXIT_SUCCESS
2581 def GenericListFields(resource, fields, separator, header, cl=None):
2582 """Generic implementation for listing fields for a resource.
2584 @param resource: One of L{constants.QR_OP_LUXI}
2585 @type fields: list of strings
2586 @param fields: List of fields to query for
2587 @type separator: string or None
2588 @param separator: String used to separate fields
2590 @param header: Whether to show header row
2599 response = cl.QueryFields(resource, fields)
2601 found_unknown = _WarnUnknownFields(response.fields)
2604 TableColumn("Name", str, False),
2605 TableColumn("Title", str, False),
2606 # TODO: Add field description to master daemon
2609 rows = [[fdef.name, fdef.title] for fdef in response.fields]
2611 for line in FormatTable(rows, columns, header, separator):
2615 return constants.EXIT_UNKNOWN_FIELD
2617 return constants.EXIT_SUCCESS
2621 """Describes a column for L{FormatTable}.
2624 def __init__(self, title, fn, align_right):
2625 """Initializes this class.
2628 @param title: Column title
2630 @param fn: Formatting function
2631 @type align_right: bool
2632 @param align_right: Whether to align values on the right-hand side
2637 self.align_right = align_right
2640 def _GetColFormatString(width, align_right):
2641 """Returns the format string for a field.
2649 return "%%%s%ss" % (sign, width)
2652 def FormatTable(rows, columns, header, separator):
2653 """Formats data as a table.
2655 @type rows: list of lists
2656 @param rows: Row data, one list per row
2657 @type columns: list of L{TableColumn}
2658 @param columns: Column descriptions
2660 @param header: Whether to show header row
2661 @type separator: string or None
2662 @param separator: String used to separate columns
2666 data = [[col.title for col in columns]]
2667 colwidth = [len(col.title) for col in columns]
2670 colwidth = [0 for _ in columns]
2674 assert len(row) == len(columns)
2676 formatted = [col.format(value) for value, col in zip(row, columns)]
2678 if separator is None:
2679 # Update column widths
2680 for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
2681 # Modifying a list's items while iterating is fine
2682 colwidth[idx] = max(oldwidth, len(value))
2684 data.append(formatted)
2686 if separator is not None:
2687 # Return early if a separator is used
2688 return [separator.join(row) for row in data]
2690 if columns and not columns[-1].align_right:
2691 # Avoid unnecessary spaces at end of line
2694 # Build format string
2695 fmt = " ".join([_GetColFormatString(width, col.align_right)
2696 for col, width in zip(columns, colwidth)])
2698 return [fmt % tuple(row) for row in data]
2701 def FormatTimestamp(ts):
2702 """Formats a given timestamp.
2705 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2708 @return: a string with the formatted timestamp
2711 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2714 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2717 def ParseTimespec(value):
2718 """Parse a time specification.
2720 The following suffixed will be recognized:
2728 Without any suffix, the value will be taken to be in seconds.
2733 raise errors.OpPrereqError("Empty time specification passed")
2741 if value[-1] not in suffix_map:
2744 except (TypeError, ValueError):
2745 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2747 multiplier = suffix_map[value[-1]]
2749 if not value: # no data left after stripping the suffix
2750 raise errors.OpPrereqError("Invalid time specification (only"
2753 value = int(value) * multiplier
2754 except (TypeError, ValueError):
2755 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2759 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2760 filter_master=False):
2761 """Returns the names of online nodes.
2763 This function will also log a warning on stderr with the names of
2766 @param nodes: if not empty, use only this subset of nodes (minus the
2768 @param cl: if not None, luxi client to use
2769 @type nowarn: boolean
2770 @param nowarn: by default, this function will output a note with the
2771 offline nodes that are skipped; if this parameter is True the
2772 note is not displayed
2773 @type secondary_ips: boolean
2774 @param secondary_ips: if True, return the secondary IPs instead of the
2775 names, useful for doing network traffic over the replication interface
2777 @type filter_master: boolean
2778 @param filter_master: if True, do not return the master node in the list
2779 (useful in coordination with secondary_ips where we cannot check our
2780 node name against the list)
2792 master_node = cl.QueryConfigValues(["master_node"])[0]
2793 filter_fn = lambda x: x != master_node
2795 filter_fn = lambda _: True
2797 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2799 offline = [row[0] for row in result if row[1]]
2800 if offline and not nowarn:
2801 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2802 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2805 def _ToStream(stream, txt, *args):
2806 """Write a message to a stream, bypassing the logging system
2808 @type stream: file object
2809 @param stream: the file to which we should write
2811 @param txt: the message
2816 stream.write(txt % args)
2823 def ToStdout(txt, *args):
2824 """Write a message to stdout only, bypassing the logging system
2826 This is just a wrapper over _ToStream.
2829 @param txt: the message
2832 _ToStream(sys.stdout, txt, *args)
2835 def ToStderr(txt, *args):
2836 """Write a message to stderr only, bypassing the logging system
2838 This is just a wrapper over _ToStream.
2841 @param txt: the message
2844 _ToStream(sys.stderr, txt, *args)
2847 class JobExecutor(object):
2848 """Class which manages the submission and execution of multiple jobs.
2850 Note that instances of this class should not be reused between
2854 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2859 self.verbose = verbose
2862 self.feedback_fn = feedback_fn
2864 def QueueJob(self, name, *ops):
2865 """Record a job for later submit.
2868 @param name: a description of the job, will be used in WaitJobSet
2870 SetGenericOpcodeOpts(ops, self.opts)
2871 self.queue.append((name, ops))
2873 def SubmitPending(self, each=False):
2874 """Submit all pending jobs.
2879 for row in self.queue:
2880 # SubmitJob will remove the success status, but raise an exception if
2881 # the submission fails, so we'll notice that anyway.
2882 results.append([True, self.cl.SubmitJob(row[1])])
2884 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2885 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2887 self.jobs.append((idx, status, data, name))
2889 def _ChooseJob(self):
2890 """Choose a non-waiting/queued job to poll next.
2893 assert self.jobs, "_ChooseJob called with empty job list"
2895 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2898 for job_data, status in zip(self.jobs, result):
2899 if (isinstance(status, list) and status and
2900 status[0] in (constants.JOB_STATUS_QUEUED,
2901 constants.JOB_STATUS_WAITLOCK,
2902 constants.JOB_STATUS_CANCELING)):
2903 # job is still present and waiting
2905 # good candidate found (either running job or lost job)
2906 self.jobs.remove(job_data)
2910 return self.jobs.pop(0)
2912 def GetResults(self):
2913 """Wait for and return the results of all jobs.
2916 @return: list of tuples (success, job results), in the same order
2917 as the submitted jobs; if a job has failed, instead of the result
2918 there will be the error message
2922 self.SubmitPending()
2925 ok_jobs = [row[2] for row in self.jobs if row[1]]
2927 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2929 # first, remove any non-submitted jobs
2930 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2931 for idx, _, jid, name in failures:
2932 ToStderr("Failed to submit job for %s: %s", name, jid)
2933 results.append((idx, False, jid))
2936 (idx, _, jid, name) = self._ChooseJob()
2937 ToStdout("Waiting for job %s for %s...", jid, name)
2939 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2941 except errors.JobLost, err:
2942 _, job_result = FormatError(err)
2943 ToStderr("Job %s for %s has been archived, cannot check its result",
2946 except (errors.GenericError, luxi.ProtocolError), err:
2947 _, job_result = FormatError(err)
2949 # the error message will always be shown, verbose or not
2950 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2952 results.append((idx, success, job_result))
2954 # sort based on the index, then drop it
2956 results = [i[1:] for i in results]
2960 def WaitOrShow(self, wait):
2961 """Wait for job results or only print the job IDs.
2964 @param wait: whether to wait or not
2968 return self.GetResults()
2971 self.SubmitPending()
2972 for _, status, result, name in self.jobs:
2974 ToStdout("%s: %s", result, name)
2976 ToStderr("Failure for %s: %s", name, result)
2977 return [row[1:3] for row in self.jobs]
2980 def FormatParameterDict(buf, param_dict, actual, level=1):
2981 """Formats a parameter dictionary.
2983 @type buf: L{StringIO}
2984 @param buf: the buffer into which to write
2985 @type param_dict: dict
2986 @param param_dict: the own parameters
2988 @param actual: the current parameter set (including defaults)
2989 @param level: Level of indent
2992 indent = " " * level
2993 for key in sorted(actual):
2994 val = param_dict.get(key, "default (%s)" % actual[key])
2995 buf.write("%s- %s: %s\n" % (indent, key, val))
2998 def ConfirmOperation(names, list_type, text, extra=""):
2999 """Ask the user to confirm an operation on a list of list_type.
3001 This function is used to request confirmation for doing an operation
3002 on a given list of list_type.
3005 @param names: the list of names that we display when
3006 we ask for confirmation
3007 @type list_type: str
3008 @param list_type: Human readable name for elements in the list (e.g. nodes)
3010 @param text: the operation that the user should confirm
3012 @return: True or False depending on user's confirmation.
3016 msg = ("The %s will operate on %d %s.\n%s"
3017 "Do you want to continue?" % (text, count, list_type, extra))
3018 affected = (("\nAffected %s:\n" % list_type) +
3019 "\n".join([" %s" % name for name in names]))
3021 choices = [("y", True, "Yes, execute the %s" % text),
3022 ("n", False, "No, abort the %s" % text)]
3025 choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3028 question = msg + affected
3030 choice = AskUser(question, choices)
3033 choice = AskUser(msg + affected, choices)