4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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"""
33 from cStringIO import StringIO
35 from ganeti import utils
36 from ganeti import errors
37 from ganeti import constants
38 from ganeti import opcodes
39 from ganeti import luxi
40 from ganeti import ssconf
41 from ganeti import rpc
42 from ganeti import ssh
43 from ganeti import compat
44 from ganeti import netutils
45 from ganeti import qlang
46 from ganeti import objects
47 from ganeti import pathutils
49 from optparse import (OptionParser, TitledHelpFormatter,
50 Option, OptionValueError)
54 # Command line options
68 "CLUSTER_DOMAIN_SECRET_OPT",
86 "FILESTORE_DRIVER_OPT",
92 "GLOBAL_SHARED_FILEDIR_OPT",
97 "DEFAULT_IALLOCATOR_OPT",
98 "IDENTIFY_DEFAULTS_OPT",
101 "IGNORE_FAILURES_OPT",
102 "IGNORE_OFFLINE_OPT",
103 "IGNORE_REMOVE_FAILURES_OPT",
104 "IGNORE_SECONDARIES_OPT",
108 "MAINTAIN_NODE_HEALTH_OPT",
110 "MASTER_NETMASK_OPT",
112 "MIGRATION_MODE_OPT",
114 "NEW_CLUSTER_CERT_OPT",
115 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
116 "NEW_CONFD_HMAC_KEY_OPT",
119 "NEW_SPICE_CERT_OPT",
121 "NODE_FORCE_JOIN_OPT",
123 "NODE_PLACEMENT_OPT",
127 "NODRBD_STORAGE_OPT",
133 "NOMODIFY_ETCHOSTS_OPT",
134 "NOMODIFY_SSH_SETUP_OPT",
138 "NORUNTIME_CHGS_OPT",
141 "NOSSH_KEYCHECK_OPT",
155 "PREALLOC_WIPE_DISKS_OPT",
156 "PRIMARY_IP_VERSION_OPT",
162 "REMOVE_INSTANCE_OPT",
168 "SECONDARY_ONLY_OPT",
172 "SHUTDOWN_TIMEOUT_OPT",
174 "SPECS_CPU_COUNT_OPT",
175 "SPECS_DISK_COUNT_OPT",
176 "SPECS_DISK_SIZE_OPT",
177 "SPECS_MEM_SIZE_OPT",
178 "SPECS_NIC_COUNT_OPT",
179 "IPOLICY_DISK_TEMPLATES",
180 "IPOLICY_VCPU_RATIO",
186 "STARTUP_PAUSED_OPT",
195 "USE_EXTERNAL_MIP_SCRIPT",
203 "IGNORE_IPOLICY_OPT",
204 "INSTANCE_POLICY_OPTS",
205 # Generic functions for CLI programs
207 "CreateIPolicyFromOpts",
209 "GenericInstanceCreate",
215 "JobSubmittedException",
217 "RunWhileClusterStopped",
221 # Formatting functions
222 "ToStderr", "ToStdout",
225 "FormatParameterDict",
234 # command line options support infrastructure
235 "ARGS_MANY_INSTANCES",
254 "OPT_COMPL_INST_ADD_NODES",
255 "OPT_COMPL_MANY_NODES",
256 "OPT_COMPL_ONE_IALLOCATOR",
257 "OPT_COMPL_ONE_INSTANCE",
258 "OPT_COMPL_ONE_NODE",
259 "OPT_COMPL_ONE_NODEGROUP",
265 "COMMON_CREATE_OPTS",
271 #: Priorities (sorted)
273 ("low", constants.OP_PRIO_LOW),
274 ("normal", constants.OP_PRIO_NORMAL),
275 ("high", constants.OP_PRIO_HIGH),
278 #: Priority dictionary for easier lookup
279 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
280 # we migrate to Python 2.6
281 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
283 # Query result status for clients
286 QR_INCOMPLETE) = range(3)
288 #: Maximum batch size for ChooseJob
292 # constants used to create InstancePolicy dictionary
293 TISPECS_GROUP_TYPES = {
294 constants.ISPECS_MIN: constants.VTYPE_INT,
295 constants.ISPECS_MAX: constants.VTYPE_INT,
298 TISPECS_CLUSTER_TYPES = {
299 constants.ISPECS_MIN: constants.VTYPE_INT,
300 constants.ISPECS_MAX: constants.VTYPE_INT,
301 constants.ISPECS_STD: constants.VTYPE_INT,
306 def __init__(self, min=0, max=None): # pylint: disable=W0622
311 return ("<%s min=%s max=%s>" %
312 (self.__class__.__name__, self.min, self.max))
315 class ArgSuggest(_Argument):
316 """Suggesting argument.
318 Value can be any of the ones passed to the constructor.
321 # pylint: disable=W0622
322 def __init__(self, min=0, max=None, choices=None):
323 _Argument.__init__(self, min=min, max=max)
324 self.choices = choices
327 return ("<%s min=%s max=%s choices=%r>" %
328 (self.__class__.__name__, self.min, self.max, self.choices))
331 class ArgChoice(ArgSuggest):
334 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
335 but value must be one of the choices.
340 class ArgUnknown(_Argument):
341 """Unknown argument to program (e.g. determined at runtime).
346 class ArgInstance(_Argument):
347 """Instances argument.
352 class ArgNode(_Argument):
358 class ArgGroup(_Argument):
359 """Node group argument.
364 class ArgJobId(_Argument):
370 class ArgFile(_Argument):
371 """File path argument.
376 class ArgCommand(_Argument):
382 class ArgHost(_Argument):
388 class ArgOs(_Argument):
395 ARGS_MANY_INSTANCES = [ArgInstance()]
396 ARGS_MANY_NODES = [ArgNode()]
397 ARGS_MANY_GROUPS = [ArgGroup()]
398 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
399 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
401 ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
402 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
405 def _ExtractTagsObject(opts, args):
406 """Extract the tag type object.
408 Note that this function will modify its args parameter.
411 if not hasattr(opts, "tag_type"):
412 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
414 if kind == constants.TAG_CLUSTER:
416 elif kind in (constants.TAG_NODEGROUP,
418 constants.TAG_INSTANCE):
420 raise errors.OpPrereqError("no arguments passed to the command",
425 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
429 def _ExtendTags(opts, args):
430 """Extend the args if a source file has been given.
432 This function will extend the tags with the contents of the file
433 passed in the 'tags_source' attribute of the opts parameter. A file
434 named '-' will be replaced by stdin.
437 fname = opts.tags_source
443 new_fh = open(fname, "r")
446 # we don't use the nice 'new_data = [line.strip() for line in fh]'
447 # because of python bug 1633941
449 line = new_fh.readline()
452 new_data.append(line.strip())
455 args.extend(new_data)
458 def ListTags(opts, args):
459 """List the tags on a given object.
461 This is a generic implementation that knows how to deal with all
462 three cases of tag objects (cluster, node, instance). The opts
463 argument is expected to contain a tag_type field denoting what
464 object type we work on.
467 kind, name = _ExtractTagsObject(opts, args)
468 cl = GetClient(query=True)
469 result = cl.QueryTags(kind, name)
470 result = list(result)
476 def AddTags(opts, args):
477 """Add tags on a given object.
479 This is a generic implementation that knows how to deal with all
480 three cases of tag objects (cluster, node, instance). The opts
481 argument is expected to contain a tag_type field denoting what
482 object type we work on.
485 kind, name = _ExtractTagsObject(opts, args)
486 _ExtendTags(opts, args)
488 raise errors.OpPrereqError("No tags to be added", errors.ECODE_INVAL)
489 op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
490 SubmitOrSend(op, opts)
493 def RemoveTags(opts, args):
494 """Remove tags from a given object.
496 This is a generic implementation that knows how to deal with all
497 three cases of tag objects (cluster, node, instance). The opts
498 argument is expected to contain a tag_type field denoting what
499 object type we work on.
502 kind, name = _ExtractTagsObject(opts, args)
503 _ExtendTags(opts, args)
505 raise errors.OpPrereqError("No tags to be removed", errors.ECODE_INVAL)
506 op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
507 SubmitOrSend(op, opts)
510 def check_unit(option, opt, value): # pylint: disable=W0613
511 """OptParsers custom converter for units.
515 return utils.ParseUnit(value)
516 except errors.UnitParseError, err:
517 raise OptionValueError("option %s: %s" % (opt, err))
520 def _SplitKeyVal(opt, data):
521 """Convert a KeyVal string into a dict.
523 This function will convert a key=val[,...] string into a dict. Empty
524 values will be converted specially: keys which have the prefix 'no_'
525 will have the value=False and the prefix stripped, the others will
529 @param opt: a string holding the option name for which we process the
530 data, used in building error messages
532 @param data: a string of the format key=val,key=val,...
534 @return: {key=val, key=val}
535 @raises errors.ParameterError: if there are duplicate keys
540 for elem in utils.UnescapeAndSplit(data, sep=","):
542 key, val = elem.split("=", 1)
544 if elem.startswith(NO_PREFIX):
545 key, val = elem[len(NO_PREFIX):], False
546 elif elem.startswith(UN_PREFIX):
547 key, val = elem[len(UN_PREFIX):], None
549 key, val = elem, True
551 raise errors.ParameterError("Duplicate key '%s' in option %s" %
557 def check_ident_key_val(option, opt, value): # pylint: disable=W0613
558 """Custom parser for ident:key=val,key=val options.
560 This will store the parsed values as a tuple (ident, {key: val}). As such,
561 multiple uses of this option via action=append is possible.
565 ident, rest = value, ""
567 ident, rest = value.split(":", 1)
569 if ident.startswith(NO_PREFIX):
571 msg = "Cannot pass options when removing parameter groups: %s" % value
572 raise errors.ParameterError(msg)
573 retval = (ident[len(NO_PREFIX):], False)
574 elif (ident.startswith(UN_PREFIX) and
575 (len(ident) <= len(UN_PREFIX) or
576 not ident[len(UN_PREFIX)][0].isdigit())):
578 msg = "Cannot pass options when removing parameter groups: %s" % value
579 raise errors.ParameterError(msg)
580 retval = (ident[len(UN_PREFIX):], None)
582 kv_dict = _SplitKeyVal(opt, rest)
583 retval = (ident, kv_dict)
587 def check_key_val(option, opt, value): # pylint: disable=W0613
588 """Custom parser class for key=val,key=val options.
590 This will store the parsed values as a dict {key: val}.
593 return _SplitKeyVal(opt, value)
596 def check_bool(option, opt, value): # pylint: disable=W0613
597 """Custom parser for yes/no options.
599 This will store the parsed value as either True or False.
602 value = value.lower()
603 if value == constants.VALUE_FALSE or value == "no":
605 elif value == constants.VALUE_TRUE or value == "yes":
608 raise errors.ParameterError("Invalid boolean value '%s'" % value)
611 def check_list(option, opt, value): # pylint: disable=W0613
612 """Custom parser for comma-separated lists.
615 # we have to make this explicit check since "".split(",") is [""],
616 # not an empty list :(
620 return utils.UnescapeAndSplit(value)
623 def check_maybefloat(option, opt, value): # pylint: disable=W0613
624 """Custom parser for float numbers which might be also defaults.
627 value = value.lower()
629 if value == constants.VALUE_DEFAULT:
635 # completion_suggestion is normally a list. Using numeric values not evaluating
636 # to False for dynamic completion.
637 (OPT_COMPL_MANY_NODES,
639 OPT_COMPL_ONE_INSTANCE,
641 OPT_COMPL_ONE_IALLOCATOR,
642 OPT_COMPL_INST_ADD_NODES,
643 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
645 OPT_COMPL_ALL = frozenset([
646 OPT_COMPL_MANY_NODES,
648 OPT_COMPL_ONE_INSTANCE,
650 OPT_COMPL_ONE_IALLOCATOR,
651 OPT_COMPL_INST_ADD_NODES,
652 OPT_COMPL_ONE_NODEGROUP,
656 class CliOption(Option):
657 """Custom option class for optparse.
660 ATTRS = Option.ATTRS + [
661 "completion_suggest",
663 TYPES = Option.TYPES + (
671 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
672 TYPE_CHECKER["identkeyval"] = check_ident_key_val
673 TYPE_CHECKER["keyval"] = check_key_val
674 TYPE_CHECKER["unit"] = check_unit
675 TYPE_CHECKER["bool"] = check_bool
676 TYPE_CHECKER["list"] = check_list
677 TYPE_CHECKER["maybefloat"] = check_maybefloat
680 # optparse.py sets make_option, so we do it for our own option class, too
681 cli_option = CliOption
686 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
687 help="Increase debugging level")
689 NOHDR_OPT = cli_option("--no-headers", default=False,
690 action="store_true", dest="no_headers",
691 help="Don't display column headers")
693 SEP_OPT = cli_option("--separator", default=None,
694 action="store", dest="separator",
695 help=("Separator between output fields"
696 " (defaults to one space)"))
698 USEUNITS_OPT = cli_option("--units", default=None,
699 dest="units", choices=("h", "m", "g", "t"),
700 help="Specify units for output (one of h/m/g/t)")
702 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
703 type="string", metavar="FIELDS",
704 help="Comma separated list of output fields")
706 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
707 default=False, help="Force the operation")
709 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
710 default=False, help="Do not require confirmation")
712 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
713 action="store_true", default=False,
714 help=("Ignore offline nodes and do as much"
717 TAG_ADD_OPT = cli_option("--tags", dest="tags",
718 default=None, help="Comma-separated list of instance"
721 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
722 default=None, help="File with tag names")
724 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
725 default=False, action="store_true",
726 help=("Submit the job and return the job ID, but"
727 " don't wait for the job to finish"))
729 SYNC_OPT = cli_option("--sync", dest="do_locking",
730 default=False, action="store_true",
731 help=("Grab locks while doing the queries"
732 " in order to ensure more consistent results"))
734 DRY_RUN_OPT = cli_option("--dry-run", default=False,
736 help=("Do not execute the operation, just run the"
737 " check steps and verify it it could be"
740 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
742 help="Increase the verbosity of the operation")
744 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
745 action="store_true", dest="simulate_errors",
746 help="Debugging option that makes the operation"
747 " treat most runtime checks as failed")
749 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
750 default=True, action="store_false",
751 help="Don't wait for sync (DANGEROUS!)")
753 WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync",
754 default=False, action="store_true",
755 help="Wait for disks to sync")
757 ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
758 action="store_true", default=False,
759 help="Enable offline instance")
761 OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
762 action="store_true", default=False,
763 help="Disable down instance")
765 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
766 help=("Custom disk setup (%s)" %
767 utils.CommaJoin(constants.DISK_TEMPLATES)),
768 default=None, metavar="TEMPL",
769 choices=list(constants.DISK_TEMPLATES))
771 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
772 help="Do not create any network cards for"
775 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
776 help="Relative path under default cluster-wide"
777 " file storage dir to store file-based disks",
778 default=None, metavar="<DIR>")
780 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
781 help="Driver to use for image files",
782 default="loop", metavar="<DRIVER>",
783 choices=list(constants.FILE_DRIVER))
785 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
786 help="Select nodes for the instance automatically"
787 " using the <NAME> iallocator plugin",
788 default=None, type="string",
789 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
791 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
793 help="Set the default instance"
795 default=None, type="string",
796 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
798 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
800 completion_suggest=OPT_COMPL_ONE_OS)
802 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
803 type="keyval", default={},
804 help="OS parameters")
806 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
807 action="store_true", default=False,
808 help="Force an unknown variant")
810 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
811 action="store_true", default=False,
812 help="Do not install the OS (will"
815 NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
816 dest="allow_runtime_chgs",
817 default=True, action="store_false",
818 help="Don't allow runtime changes")
820 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
821 type="keyval", default={},
822 help="Backend parameters")
824 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
825 default={}, dest="hvparams",
826 help="Hypervisor parameters")
828 DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
829 help="Disk template parameters, in the format"
830 " template:option=value,option=value,...",
831 type="identkeyval", action="append", default=[])
833 SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
834 type="keyval", default={},
835 help="Memory size specs: list of key=value,"
836 " where key is one of min, max, std"
837 " (in MB or using a unit)")
839 SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
840 type="keyval", default={},
841 help="CPU count specs: list of key=value,"
842 " where key is one of min, max, std")
844 SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
845 dest="ispecs_disk_count",
846 type="keyval", default={},
847 help="Disk count specs: list of key=value,"
848 " where key is one of min, max, std")
850 SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
851 type="keyval", default={},
852 help="Disk size specs: list of key=value,"
853 " where key is one of min, max, std"
854 " (in MB or using a unit)")
856 SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
857 type="keyval", default={},
858 help="NIC count specs: list of key=value,"
859 " where key is one of min, max, std")
861 IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
862 dest="ipolicy_disk_templates",
863 type="list", default=None,
864 help="Comma-separated list of"
865 " enabled disk templates")
867 IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
868 dest="ipolicy_vcpu_ratio",
869 type="maybefloat", default=None,
870 help="The maximum allowed vcpu-to-cpu ratio")
872 IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
873 dest="ipolicy_spindle_ratio",
874 type="maybefloat", default=None,
875 help=("The maximum allowed instances to"
878 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
879 help="Hypervisor and hypervisor options, in the"
880 " format hypervisor:option=value,option=value,...",
881 default=None, type="identkeyval")
883 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
884 help="Hypervisor and hypervisor options, in the"
885 " format hypervisor:option=value,option=value,...",
886 default=[], action="append", type="identkeyval")
888 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
889 action="store_false",
890 help="Don't check that the instance's IP"
893 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
894 default=True, action="store_false",
895 help="Don't check that the instance's name"
898 NET_OPT = cli_option("--net",
899 help="NIC parameters", default=[],
900 dest="nics", action="append", type="identkeyval")
902 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
903 dest="disks", action="append", type="identkeyval")
905 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
906 help="Comma-separated list of disks"
907 " indices to act on (e.g. 0,2) (optional,"
908 " defaults to all disks)")
910 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
911 help="Enforces a single-disk configuration using the"
912 " given disk size, in MiB unless a suffix is used",
913 default=None, type="unit", metavar="<size>")
915 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
916 dest="ignore_consistency",
917 action="store_true", default=False,
918 help="Ignore the consistency of the disks on"
921 ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
922 dest="allow_failover",
923 action="store_true", default=False,
924 help="If migration is not possible fallback to"
927 NONLIVE_OPT = cli_option("--non-live", dest="live",
928 default=True, action="store_false",
929 help="Do a non-live migration (this usually means"
930 " freeze the instance, save the state, transfer and"
931 " only then resume running on the secondary node)")
933 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
935 choices=list(constants.HT_MIGRATION_MODES),
936 help="Override default migration mode (choose"
937 " either live or non-live")
939 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
940 help="Target node and optional secondary node",
941 metavar="<pnode>[:<snode>]",
942 completion_suggest=OPT_COMPL_INST_ADD_NODES)
944 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
945 action="append", metavar="<node>",
946 help="Use only this node (can be used multiple"
947 " times, if not given defaults to all nodes)",
948 completion_suggest=OPT_COMPL_ONE_NODE)
950 NODEGROUP_OPT_NAME = "--node-group"
951 NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
953 help="Node group (name or uuid)",
954 metavar="<nodegroup>",
955 default=None, type="string",
956 completion_suggest=OPT_COMPL_ONE_NODEGROUP)
958 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
960 completion_suggest=OPT_COMPL_ONE_NODE)
962 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
963 action="store_false",
964 help="Don't start the instance after creation")
966 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
967 action="store_true", default=False,
968 help="Show command instead of executing it")
970 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
971 default=False, action="store_true",
972 help="Instead of performing the migration, try to"
973 " recover from a failed cleanup. This is safe"
974 " to run even if the instance is healthy, but it"
975 " will create extra replication traffic and "
976 " disrupt briefly the replication (like during the"
979 STATIC_OPT = cli_option("-s", "--static", dest="static",
980 action="store_true", default=False,
981 help="Only show configuration data, not runtime data")
983 ALL_OPT = cli_option("--all", dest="show_all",
984 default=False, action="store_true",
985 help="Show info on all instances on the cluster."
986 " This can take a long time to run, use wisely")
988 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
989 action="store_true", default=False,
990 help="Interactive OS reinstall, lists available"
991 " OS templates for selection")
993 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
994 action="store_true", default=False,
995 help="Remove the instance from the cluster"
996 " configuration even if there are failures"
997 " during the removal process")
999 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
1000 dest="ignore_remove_failures",
1001 action="store_true", default=False,
1002 help="Remove the instance from the"
1003 " cluster configuration even if there"
1004 " are failures during the removal"
1007 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
1008 action="store_true", default=False,
1009 help="Remove the instance from the cluster")
1011 DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
1012 help="Specifies the new node for the instance",
1013 metavar="NODE", default=None,
1014 completion_suggest=OPT_COMPL_ONE_NODE)
1016 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
1017 help="Specifies the new secondary node",
1018 metavar="NODE", default=None,
1019 completion_suggest=OPT_COMPL_ONE_NODE)
1021 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
1022 default=False, action="store_true",
1023 help="Replace the disk(s) on the primary"
1024 " node (applies only to internally mirrored"
1025 " disk templates, e.g. %s)" %
1026 utils.CommaJoin(constants.DTS_INT_MIRROR))
1028 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
1029 default=False, action="store_true",
1030 help="Replace the disk(s) on the secondary"
1031 " node (applies only to internally mirrored"
1032 " disk templates, e.g. %s)" %
1033 utils.CommaJoin(constants.DTS_INT_MIRROR))
1035 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
1036 default=False, action="store_true",
1037 help="Lock all nodes and auto-promote as needed"
1040 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
1041 default=False, action="store_true",
1042 help="Automatically replace faulty disks"
1043 " (applies only to internally mirrored"
1044 " disk templates, e.g. %s)" %
1045 utils.CommaJoin(constants.DTS_INT_MIRROR))
1047 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
1048 default=False, action="store_true",
1049 help="Ignore current recorded size"
1050 " (useful for forcing activation when"
1051 " the recorded size is wrong)")
1053 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1055 completion_suggest=OPT_COMPL_ONE_NODE)
1057 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1060 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1061 help="Specify the secondary ip for the node",
1062 metavar="ADDRESS", default=None)
1064 READD_OPT = cli_option("--readd", dest="readd",
1065 default=False, action="store_true",
1066 help="Readd old node after replacing it")
1068 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1069 default=True, action="store_false",
1070 help="Disable SSH key fingerprint checking")
1072 NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1073 default=False, action="store_true",
1074 help="Force the joining of a node")
1076 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1077 type="bool", default=None, metavar=_YORNO,
1078 help="Set the master_candidate flag on the node")
1080 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1081 type="bool", default=None,
1082 help=("Set the offline flag on the node"
1083 " (cluster does not communicate with offline"
1086 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1087 type="bool", default=None,
1088 help=("Set the drained flag on the node"
1089 " (excluded from allocation operations)"))
1091 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1092 type="bool", default=None, metavar=_YORNO,
1093 help="Set the master_capable flag on the node")
1095 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1096 type="bool", default=None, metavar=_YORNO,
1097 help="Set the vm_capable flag on the node")
1099 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1100 type="bool", default=None, metavar=_YORNO,
1101 help="Set the allocatable flag on a volume")
1103 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1104 help="Disable support for lvm based instances"
1106 action="store_false", default=True)
1108 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1109 dest="enabled_hypervisors",
1110 help="Comma-separated list of hypervisors",
1111 type="string", default=None)
1113 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1114 type="keyval", default={},
1115 help="NIC parameters")
1117 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1118 dest="candidate_pool_size", type="int",
1119 help="Set the candidate pool size")
1121 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1122 help=("Enables LVM and specifies the volume group"
1123 " name (cluster-wide) for disk allocation"
1124 " [%s]" % constants.DEFAULT_VG),
1125 metavar="VG", default=None)
1127 YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1128 help="Destroy cluster", action="store_true")
1130 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1131 help="Skip node agreement check (dangerous)",
1132 action="store_true", default=False)
1134 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1135 help="Specify the mac prefix for the instance IP"
1136 " addresses, in the format XX:XX:XX",
1140 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1141 help="Specify the node interface (cluster-wide)"
1142 " on which the master IP address will be added"
1143 " (cluster init default: %s)" %
1144 constants.DEFAULT_BRIDGE,
1148 MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1149 help="Specify the netmask of the master IP",
1153 USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1154 dest="use_external_mip_script",
1155 help="Specify whether to run a"
1156 " user-provided script for the master"
1157 " IP address turnup and"
1158 " turndown operations",
1159 type="bool", metavar=_YORNO, default=None)
1161 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1162 help="Specify the default directory (cluster-"
1163 "wide) for storing the file-based disks [%s]" %
1164 pathutils.DEFAULT_FILE_STORAGE_DIR,
1166 default=pathutils.DEFAULT_FILE_STORAGE_DIR)
1168 GLOBAL_SHARED_FILEDIR_OPT = cli_option(
1169 "--shared-file-storage-dir",
1170 dest="shared_file_storage_dir",
1171 help="Specify the default directory (cluster-wide) for storing the"
1172 " shared file-based disks [%s]" %
1173 pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
1174 metavar="SHAREDDIR", default=pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR)
1176 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1177 help="Don't modify /etc/hosts",
1178 action="store_false", default=True)
1180 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1181 help="Don't initialize SSH keys",
1182 action="store_false", default=True)
1184 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1185 help="Enable parseable error messages",
1186 action="store_true", default=False)
1188 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1189 help="Skip N+1 memory redundancy tests",
1190 action="store_true", default=False)
1192 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1193 help="Type of reboot: soft/hard/full",
1194 default=constants.INSTANCE_REBOOT_HARD,
1196 choices=list(constants.REBOOT_TYPES))
1198 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1199 dest="ignore_secondaries",
1200 default=False, action="store_true",
1201 help="Ignore errors from secondaries")
1203 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1204 action="store_false", default=True,
1205 help="Don't shutdown the instance (unsafe)")
1207 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1208 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1209 help="Maximum time to wait")
1211 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1212 dest="shutdown_timeout", type="int",
1213 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1214 help="Maximum time to wait for instance"
1217 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1219 help=("Number of seconds between repetions of the"
1222 EARLY_RELEASE_OPT = cli_option("--early-release",
1223 dest="early_release", default=False,
1224 action="store_true",
1225 help="Release the locks on the secondary"
1228 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1229 dest="new_cluster_cert",
1230 default=False, action="store_true",
1231 help="Generate a new cluster certificate")
1233 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1235 help="File containing new RAPI certificate")
1237 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1238 default=None, action="store_true",
1239 help=("Generate a new self-signed RAPI"
1242 SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1244 help="File containing new SPICE certificate")
1246 SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1248 help="File containing the certificate of the CA"
1249 " which signed the SPICE certificate")
1251 NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1252 dest="new_spice_cert", default=None,
1253 action="store_true",
1254 help=("Generate a new self-signed SPICE"
1257 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1258 dest="new_confd_hmac_key",
1259 default=False, action="store_true",
1260 help=("Create a new HMAC key for %s" %
1263 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1264 dest="cluster_domain_secret",
1266 help=("Load new new cluster domain"
1267 " secret from file"))
1269 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1270 dest="new_cluster_domain_secret",
1271 default=False, action="store_true",
1272 help=("Create a new cluster domain"
1275 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1276 dest="use_replication_network",
1277 help="Whether to use the replication network"
1278 " for talking to the nodes",
1279 action="store_true", default=False)
1281 MAINTAIN_NODE_HEALTH_OPT = \
1282 cli_option("--maintain-node-health", dest="maintain_node_health",
1283 metavar=_YORNO, default=None, type="bool",
1284 help="Configure the cluster to automatically maintain node"
1285 " health, by shutting down unknown instances, shutting down"
1286 " unknown DRBD devices, etc.")
1288 IDENTIFY_DEFAULTS_OPT = \
1289 cli_option("--identify-defaults", dest="identify_defaults",
1290 default=False, action="store_true",
1291 help="Identify which saved instance parameters are equal to"
1292 " the current cluster defaults and set them as such, instead"
1293 " of marking them as overridden")
1295 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1296 action="store", dest="uid_pool",
1297 help=("A list of user-ids or user-id"
1298 " ranges separated by commas"))
1300 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1301 action="store", dest="add_uids",
1302 help=("A list of user-ids or user-id"
1303 " ranges separated by commas, to be"
1304 " added to the user-id pool"))
1306 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1307 action="store", dest="remove_uids",
1308 help=("A list of user-ids or user-id"
1309 " ranges separated by commas, to be"
1310 " removed from the user-id pool"))
1312 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1313 action="store", dest="reserved_lvs",
1314 help=("A comma-separated list of reserved"
1315 " logical volumes names, that will be"
1316 " ignored by cluster verify"))
1318 ROMAN_OPT = cli_option("--roman",
1319 dest="roman_integers", default=False,
1320 action="store_true",
1321 help="Use roman numbers for positive integers")
1323 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1324 action="store", default=None,
1325 help="Specifies usermode helper for DRBD")
1327 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1328 action="store_false", default=True,
1329 help="Disable support for DRBD")
1331 PRIMARY_IP_VERSION_OPT = \
1332 cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1333 action="store", dest="primary_ip_version",
1334 metavar="%d|%d" % (constants.IP4_VERSION,
1335 constants.IP6_VERSION),
1336 help="Cluster-wide IP version for primary IP")
1338 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1339 metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1340 choices=_PRIONAME_TO_VALUE.keys(),
1341 help="Priority for opcode processing")
1343 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1344 type="bool", default=None, metavar=_YORNO,
1345 help="Sets the hidden flag on the OS")
1347 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1348 type="bool", default=None, metavar=_YORNO,
1349 help="Sets the blacklisted flag on the OS")
1351 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1352 type="bool", metavar=_YORNO,
1353 dest="prealloc_wipe_disks",
1354 help=("Wipe disks prior to instance"
1357 NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1358 type="keyval", default=None,
1359 help="Node parameters")
1361 ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1362 action="store", metavar="POLICY", default=None,
1363 help="Allocation policy for the node group")
1365 NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1366 type="bool", metavar=_YORNO,
1367 dest="node_powered",
1368 help="Specify if the SoR for node is powered")
1370 OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1371 default=constants.OOB_TIMEOUT,
1372 help="Maximum time to wait for out-of-band helper")
1374 POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1375 default=constants.OOB_POWER_DELAY,
1376 help="Time in seconds to wait between power-ons")
1378 FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1379 action="store_true", default=False,
1380 help=("Whether command argument should be treated"
1383 NO_REMEMBER_OPT = cli_option("--no-remember",
1385 action="store_true", default=False,
1386 help="Perform but do not record the change"
1387 " in the configuration")
1389 PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1390 default=False, action="store_true",
1391 help="Evacuate primary instances only")
1393 SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1394 default=False, action="store_true",
1395 help="Evacuate secondary instances only"
1396 " (applies only to internally mirrored"
1397 " disk templates, e.g. %s)" %
1398 utils.CommaJoin(constants.DTS_INT_MIRROR))
1400 STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1401 action="store_true", default=False,
1402 help="Pause instance at startup")
1404 TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1405 help="Destination node group (name or uuid)",
1406 default=None, action="append",
1407 completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1409 IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1410 action="append", dest="ignore_errors",
1411 choices=list(constants.CV_ALL_ECODES_STRINGS),
1412 help="Error code to be ignored")
1414 DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1416 help=("Specify disk state information in the"
1418 " storage_type/identifier:option=value,...;"
1419 " note this is unused for now"),
1422 HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1424 help=("Specify hypervisor state information in the"
1425 " format hypervisor:option=value,...;"
1426 " note this is unused for now"),
1429 IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1430 action="store_true", default=False,
1431 help="Ignore instance policy violations")
1433 RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1434 help="Sets the instance's runtime memory,"
1435 " ballooning it up or down to the new value",
1436 default=None, type="unit", metavar="<size>")
1438 ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1439 action="store_true", default=False,
1440 help="Marks the grow as absolute instead of the"
1441 " (default) relative mode")
1443 #: Options provided by all commands
1444 COMMON_OPTS = [DEBUG_OPT]
1446 # common options for creating instances. add and import then add their own
1448 COMMON_CREATE_OPTS = [
1453 FILESTORE_DRIVER_OPT,
1470 # common instance policy options
1471 INSTANCE_POLICY_OPTS = [
1472 SPECS_CPU_COUNT_OPT,
1473 SPECS_DISK_COUNT_OPT,
1474 SPECS_DISK_SIZE_OPT,
1476 SPECS_NIC_COUNT_OPT,
1477 IPOLICY_DISK_TEMPLATES,
1479 IPOLICY_SPINDLE_RATIO,
1483 class _ShowUsage(Exception):
1484 """Exception class for L{_ParseArgs}.
1487 def __init__(self, exit_error):
1488 """Initializes instances of this class.
1490 @type exit_error: bool
1491 @param exit_error: Whether to report failure on exit
1494 Exception.__init__(self)
1495 self.exit_error = exit_error
1498 class _ShowVersion(Exception):
1499 """Exception class for L{_ParseArgs}.
1504 def _ParseArgs(binary, argv, commands, aliases, env_override):
1505 """Parser for the command line arguments.
1507 This function parses the arguments and returns the function which
1508 must be executed together with its (modified) arguments.
1510 @param binary: Script name
1511 @param argv: Command line arguments
1512 @param commands: Dictionary containing command definitions
1513 @param aliases: dictionary with command aliases {"alias": "target", ...}
1514 @param env_override: list of env variables allowed for default args
1515 @raise _ShowUsage: If usage description should be shown
1516 @raise _ShowVersion: If version should be shown
1519 assert not (env_override - set(commands))
1520 assert not (set(aliases.keys()) & set(commands.keys()))
1525 # No option or command given
1526 raise _ShowUsage(exit_error=True)
1528 if cmd == "--version":
1529 raise _ShowVersion()
1530 elif cmd == "--help":
1531 raise _ShowUsage(exit_error=False)
1532 elif not (cmd in commands or cmd in aliases):
1533 raise _ShowUsage(exit_error=True)
1535 # get command, unalias it, and look it up in commands
1537 if aliases[cmd] not in commands:
1538 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1539 " command '%s'" % (cmd, aliases[cmd]))
1543 if cmd in env_override:
1544 args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1545 env_args = os.environ.get(args_env_name)
1547 argv = utils.InsertAtPos(argv, 2, shlex.split(env_args))
1549 func, args_def, parser_opts, usage, description = commands[cmd]
1550 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1551 description=description,
1552 formatter=TitledHelpFormatter(),
1553 usage="%%prog %s %s" % (cmd, usage))
1554 parser.disable_interspersed_args()
1555 options, args = parser.parse_args(args=argv[2:])
1557 if not _CheckArguments(cmd, args_def, args):
1558 return None, None, None
1560 return func, options, args
1563 def _FormatUsage(binary, commands):
1564 """Generates a nice description of all commands.
1566 @param binary: Script name
1567 @param commands: Dictionary containing command definitions
1570 # compute the max line length for cmd + usage
1571 mlen = min(60, max(map(len, commands)))
1573 yield "Usage: %s {command} [options...] [argument...]" % binary
1574 yield "%s <command> --help to see details, or man %s" % (binary, binary)
1578 # and format a nice command list
1579 for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()):
1580 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1581 yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0))
1582 for line in help_lines:
1583 yield " %-*s %s" % (mlen, "", line)
1588 def _CheckArguments(cmd, args_def, args):
1589 """Verifies the arguments using the argument definition.
1593 1. Abort with error if values specified by user but none expected.
1595 1. For each argument in definition
1597 1. Keep running count of minimum number of values (min_count)
1598 1. Keep running count of maximum number of values (max_count)
1599 1. If it has an unlimited number of values
1601 1. Abort with error if it's not the last argument in the definition
1603 1. If last argument has limited number of values
1605 1. Abort with error if number of values doesn't match or is too large
1607 1. Abort with error if user didn't pass enough values (min_count)
1610 if args and not args_def:
1611 ToStderr("Error: Command %s expects no arguments", cmd)
1618 last_idx = len(args_def) - 1
1620 for idx, arg in enumerate(args_def):
1621 if min_count is None:
1623 elif arg.min is not None:
1624 min_count += arg.min
1626 if max_count is None:
1628 elif arg.max is not None:
1629 max_count += arg.max
1632 check_max = (arg.max is not None)
1634 elif arg.max is None:
1635 raise errors.ProgrammerError("Only the last argument can have max=None")
1638 # Command with exact number of arguments
1639 if (min_count is not None and max_count is not None and
1640 min_count == max_count and len(args) != min_count):
1641 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1644 # Command with limited number of arguments
1645 if max_count is not None and len(args) > max_count:
1646 ToStderr("Error: Command %s expects only %d argument(s)",
1650 # Command with some required arguments
1651 if min_count is not None and len(args) < min_count:
1652 ToStderr("Error: Command %s expects at least %d argument(s)",
1659 def SplitNodeOption(value):
1660 """Splits the value of a --node option.
1663 if value and ":" in value:
1664 return value.split(":", 1)
1666 return (value, None)
1669 def CalculateOSNames(os_name, os_variants):
1670 """Calculates all the names an OS can be called, according to its variants.
1672 @type os_name: string
1673 @param os_name: base name of the os
1674 @type os_variants: list or None
1675 @param os_variants: list of supported variants
1677 @return: list of valid names
1681 return ["%s+%s" % (os_name, v) for v in os_variants]
1686 def ParseFields(selected, default):
1687 """Parses the values of "--field"-like options.
1689 @type selected: string or None
1690 @param selected: User-selected options
1692 @param default: Default fields
1695 if selected is None:
1698 if selected.startswith("+"):
1699 return default + selected[1:].split(",")
1701 return selected.split(",")
1704 UsesRPC = rpc.RunWithRPC
1707 def AskUser(text, choices=None):
1708 """Ask the user a question.
1710 @param text: the question to ask
1712 @param choices: list with elements tuples (input_char, return_value,
1713 description); if not given, it will default to: [('y', True,
1714 'Perform the operation'), ('n', False, 'Do no do the operation')];
1715 note that the '?' char is reserved for help
1717 @return: one of the return values from the choices list; if input is
1718 not possible (i.e. not running with a tty, we return the last
1723 choices = [("y", True, "Perform the operation"),
1724 ("n", False, "Do not perform the operation")]
1725 if not choices or not isinstance(choices, list):
1726 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1727 for entry in choices:
1728 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1729 raise errors.ProgrammerError("Invalid choices element to AskUser")
1731 answer = choices[-1][1]
1733 for line in text.splitlines():
1734 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1735 text = "\n".join(new_text)
1737 f = file("/dev/tty", "a+")
1741 chars = [entry[0] for entry in choices]
1742 chars[-1] = "[%s]" % chars[-1]
1744 maps = dict([(entry[0], entry[1]) for entry in choices])
1748 f.write("/".join(chars))
1750 line = f.readline(2).strip().lower()
1755 for entry in choices:
1756 f.write(" %s - %s\n" % (entry[0], entry[2]))
1764 class JobSubmittedException(Exception):
1765 """Job was submitted, client should exit.
1767 This exception has one argument, the ID of the job that was
1768 submitted. The handler should print this ID.
1770 This is not an error, just a structured way to exit from clients.
1775 def SendJob(ops, cl=None):
1776 """Function to submit an opcode without waiting for the results.
1779 @param ops: list of opcodes
1780 @type cl: luxi.Client
1781 @param cl: the luxi client to use for communicating with the master;
1782 if None, a new client will be created
1788 job_id = cl.SubmitJob(ops)
1793 def GenericPollJob(job_id, cbs, report_cbs):
1794 """Generic job-polling function.
1796 @type job_id: number
1797 @param job_id: Job ID
1798 @type cbs: Instance of L{JobPollCbBase}
1799 @param cbs: Data callbacks
1800 @type report_cbs: Instance of L{JobPollReportCbBase}
1801 @param report_cbs: Reporting callbacks
1804 prev_job_info = None
1805 prev_logmsg_serial = None
1810 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1813 # job not found, go away!
1814 raise errors.JobLost("Job with id %s lost" % job_id)
1816 if result == constants.JOB_NOTCHANGED:
1817 report_cbs.ReportNotChanged(job_id, status)
1822 # Split result, a tuple of (field values, log entries)
1823 (job_info, log_entries) = result
1824 (status, ) = job_info
1827 for log_entry in log_entries:
1828 (serial, timestamp, log_type, message) = log_entry
1829 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1831 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1833 # TODO: Handle canceled and archived jobs
1834 elif status in (constants.JOB_STATUS_SUCCESS,
1835 constants.JOB_STATUS_ERROR,
1836 constants.JOB_STATUS_CANCELING,
1837 constants.JOB_STATUS_CANCELED):
1840 prev_job_info = job_info
1842 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1844 raise errors.JobLost("Job with id %s lost" % job_id)
1846 status, opstatus, result = jobs[0]
1848 if status == constants.JOB_STATUS_SUCCESS:
1851 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1852 raise errors.OpExecError("Job was canceled")
1855 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1856 if status == constants.OP_STATUS_SUCCESS:
1858 elif status == constants.OP_STATUS_ERROR:
1859 errors.MaybeRaise(msg)
1862 raise errors.OpExecError("partial failure (opcode %d): %s" %
1865 raise errors.OpExecError(str(msg))
1867 # default failure mode
1868 raise errors.OpExecError(result)
1871 class JobPollCbBase:
1872 """Base class for L{GenericPollJob} callbacks.
1876 """Initializes this class.
1880 def WaitForJobChangeOnce(self, job_id, fields,
1881 prev_job_info, prev_log_serial):
1882 """Waits for changes on a job.
1885 raise NotImplementedError()
1887 def QueryJobs(self, job_ids, fields):
1888 """Returns the selected fields for the selected job IDs.
1890 @type job_ids: list of numbers
1891 @param job_ids: Job IDs
1892 @type fields: list of strings
1893 @param fields: Fields
1896 raise NotImplementedError()
1899 class JobPollReportCbBase:
1900 """Base class for L{GenericPollJob} reporting callbacks.
1904 """Initializes this class.
1908 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1909 """Handles a log message.
1912 raise NotImplementedError()
1914 def ReportNotChanged(self, job_id, status):
1915 """Called for if a job hasn't changed in a while.
1917 @type job_id: number
1918 @param job_id: Job ID
1919 @type status: string or None
1920 @param status: Job status if available
1923 raise NotImplementedError()
1926 class _LuxiJobPollCb(JobPollCbBase):
1927 def __init__(self, cl):
1928 """Initializes this class.
1931 JobPollCbBase.__init__(self)
1934 def WaitForJobChangeOnce(self, job_id, fields,
1935 prev_job_info, prev_log_serial):
1936 """Waits for changes on a job.
1939 return self.cl.WaitForJobChangeOnce(job_id, fields,
1940 prev_job_info, prev_log_serial)
1942 def QueryJobs(self, job_ids, fields):
1943 """Returns the selected fields for the selected job IDs.
1946 return self.cl.QueryJobs(job_ids, fields)
1949 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1950 def __init__(self, feedback_fn):
1951 """Initializes this class.
1954 JobPollReportCbBase.__init__(self)
1956 self.feedback_fn = feedback_fn
1958 assert callable(feedback_fn)
1960 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1961 """Handles a log message.
1964 self.feedback_fn((timestamp, log_type, log_msg))
1966 def ReportNotChanged(self, job_id, status):
1967 """Called if a job hasn't changed in a while.
1973 class StdioJobPollReportCb(JobPollReportCbBase):
1975 """Initializes this class.
1978 JobPollReportCbBase.__init__(self)
1980 self.notified_queued = False
1981 self.notified_waitlock = False
1983 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1984 """Handles a log message.
1987 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1988 FormatLogMessage(log_type, log_msg))
1990 def ReportNotChanged(self, job_id, status):
1991 """Called if a job hasn't changed in a while.
1997 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1998 ToStderr("Job %s is waiting in queue", job_id)
1999 self.notified_queued = True
2001 elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
2002 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
2003 self.notified_waitlock = True
2006 def FormatLogMessage(log_type, log_msg):
2007 """Formats a job message according to its type.
2010 if log_type != constants.ELOG_MESSAGE:
2011 log_msg = str(log_msg)
2013 return utils.SafeEncode(log_msg)
2016 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
2017 """Function to poll for the result of a job.
2019 @type job_id: job identified
2020 @param job_id: the job to poll for results
2021 @type cl: luxi.Client
2022 @param cl: the luxi client to use for communicating with the master;
2023 if None, a new client will be created
2029 if reporter is None:
2031 reporter = FeedbackFnJobPollReportCb(feedback_fn)
2033 reporter = StdioJobPollReportCb()
2035 raise errors.ProgrammerError("Can't specify reporter and feedback function")
2037 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2040 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2041 """Legacy function to submit an opcode.
2043 This is just a simple wrapper over the construction of the processor
2044 instance. It should be extended to better handle feedback and
2045 interaction functions.
2051 SetGenericOpcodeOpts([op], opts)
2053 job_id = SendJob([op], cl=cl)
2055 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2058 return op_results[0]
2061 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2062 """Wrapper around SubmitOpCode or SendJob.
2064 This function will decide, based on the 'opts' parameter, whether to
2065 submit and wait for the result of the opcode (and return it), or
2066 whether to just send the job and print its identifier. It is used in
2067 order to simplify the implementation of the '--submit' option.
2069 It will also process the opcodes if we're sending the via SendJob
2070 (otherwise SubmitOpCode does it).
2073 if opts and opts.submit_only:
2075 SetGenericOpcodeOpts(job, opts)
2076 job_id = SendJob(job, cl=cl)
2077 raise JobSubmittedException(job_id)
2079 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2082 def SetGenericOpcodeOpts(opcode_list, options):
2083 """Processor for generic options.
2085 This function updates the given opcodes based on generic command
2086 line options (like debug, dry-run, etc.).
2088 @param opcode_list: list of opcodes
2089 @param options: command line options or None
2090 @return: None (in-place modification)
2095 for op in opcode_list:
2096 op.debug_level = options.debug
2097 if hasattr(options, "dry_run"):
2098 op.dry_run = options.dry_run
2099 if getattr(options, "priority", None) is not None:
2100 op.priority = _PRIONAME_TO_VALUE[options.priority]
2103 def GetClient(query=False):
2104 """Connects to the a luxi socket and returns a client.
2106 @type query: boolean
2107 @param query: this signifies that the client will only be
2108 used for queries; if the build-time parameter
2109 enable-split-queries is enabled, then the client will be
2110 connected to the query socket instead of the masterd socket
2113 if query and constants.ENABLE_SPLIT_QUERY:
2114 address = pathutils.QUERY_SOCKET
2117 # TODO: Cache object?
2119 client = luxi.Client(address=address)
2120 except luxi.NoMasterError:
2121 ss = ssconf.SimpleStore()
2123 # Try to read ssconf file
2126 except errors.ConfigurationError:
2127 raise errors.OpPrereqError("Cluster not initialized or this machine is"
2128 " not part of a cluster",
2131 master, myself = ssconf.GetMasterAndMyself(ss=ss)
2132 if master != myself:
2133 raise errors.OpPrereqError("This is not the master node, please connect"
2134 " to node '%s' and rerun the command" %
2135 master, errors.ECODE_INVAL)
2140 def FormatError(err):
2141 """Return a formatted error message for a given error.
2143 This function takes an exception instance and returns a tuple
2144 consisting of two values: first, the recommended exit code, and
2145 second, a string describing the error message (not
2146 newline-terminated).
2152 if isinstance(err, errors.ConfigurationError):
2153 txt = "Corrupt configuration file: %s" % msg
2155 obuf.write(txt + "\n")
2156 obuf.write("Aborting.")
2158 elif isinstance(err, errors.HooksAbort):
2159 obuf.write("Failure: hooks execution failed:\n")
2160 for node, script, out in err.args[0]:
2162 obuf.write(" node: %s, script: %s, output: %s\n" %
2163 (node, script, out))
2165 obuf.write(" node: %s, script: %s (no output)\n" %
2167 elif isinstance(err, errors.HooksFailure):
2168 obuf.write("Failure: hooks general failure: %s" % msg)
2169 elif isinstance(err, errors.ResolverError):
2170 this_host = netutils.Hostname.GetSysName()
2171 if err.args[0] == this_host:
2172 msg = "Failure: can't resolve my own hostname ('%s')"
2174 msg = "Failure: can't resolve hostname '%s'"
2175 obuf.write(msg % err.args[0])
2176 elif isinstance(err, errors.OpPrereqError):
2177 if len(err.args) == 2:
2178 obuf.write("Failure: prerequisites not met for this"
2179 " operation:\nerror type: %s, error details:\n%s" %
2180 (err.args[1], err.args[0]))
2182 obuf.write("Failure: prerequisites not met for this"
2183 " operation:\n%s" % msg)
2184 elif isinstance(err, errors.OpExecError):
2185 obuf.write("Failure: command execution error:\n%s" % msg)
2186 elif isinstance(err, errors.TagError):
2187 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
2188 elif isinstance(err, errors.JobQueueDrainError):
2189 obuf.write("Failure: the job queue is marked for drain and doesn't"
2190 " accept new requests\n")
2191 elif isinstance(err, errors.JobQueueFull):
2192 obuf.write("Failure: the job queue is full and doesn't accept new"
2193 " job submissions until old jobs are archived\n")
2194 elif isinstance(err, errors.TypeEnforcementError):
2195 obuf.write("Parameter Error: %s" % msg)
2196 elif isinstance(err, errors.ParameterError):
2197 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
2198 elif isinstance(err, luxi.NoMasterError):
2199 obuf.write("Cannot communicate with the master daemon.\nIs it running"
2200 " and listening for connections?")
2201 elif isinstance(err, luxi.TimeoutError):
2202 obuf.write("Timeout while talking to the master daemon. Jobs might have"
2203 " been submitted and will continue to run even if the call"
2204 " timed out. Useful commands in this situation are \"gnt-job"
2205 " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
2207 elif isinstance(err, luxi.PermissionError):
2208 obuf.write("It seems you don't have permissions to connect to the"
2209 " master daemon.\nPlease retry as a different user.")
2210 elif isinstance(err, luxi.ProtocolError):
2211 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
2213 elif isinstance(err, errors.JobLost):
2214 obuf.write("Error checking job status: %s" % msg)
2215 elif isinstance(err, errors.QueryFilterParseError):
2216 obuf.write("Error while parsing query filter: %s\n" % err.args[0])
2217 obuf.write("\n".join(err.GetDetails()))
2218 elif isinstance(err, errors.GenericError):
2219 obuf.write("Unhandled Ganeti error: %s" % msg)
2220 elif isinstance(err, JobSubmittedException):
2221 obuf.write("JobID: %s\n" % err.args[0])
2224 obuf.write("Unhandled exception: %s" % msg)
2225 return retcode, obuf.getvalue().rstrip("\n")
2228 def GenericMain(commands, override=None, aliases=None,
2229 env_override=frozenset()):
2230 """Generic main function for all the gnt-* commands.
2232 @param commands: a dictionary with a special structure, see the design doc
2233 for command line handling.
2234 @param override: if not None, we expect a dictionary with keys that will
2235 override command line options; this can be used to pass
2236 options from the scripts to generic functions
2237 @param aliases: dictionary with command aliases {'alias': 'target, ...}
2238 @param env_override: list of environment names which are allowed to submit
2239 default args for commands
2242 # save the program name and the entire command line for later logging
2244 binary = os.path.basename(sys.argv[0])
2246 binary = sys.argv[0]
2248 if len(sys.argv) >= 2:
2249 logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2253 cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2255 binary = "<unknown program>"
2256 cmdline = "<unknown>"
2262 (func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases,
2264 except _ShowVersion:
2265 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
2266 constants.RELEASE_VERSION)
2267 return constants.EXIT_SUCCESS
2268 except _ShowUsage, err:
2269 for line in _FormatUsage(binary, commands):
2273 return constants.EXIT_FAILURE
2275 return constants.EXIT_SUCCESS
2276 except errors.ParameterError, err:
2277 result, err_msg = FormatError(err)
2281 if func is None: # parse error
2284 if override is not None:
2285 for key, val in override.iteritems():
2286 setattr(options, key, val)
2288 utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug,
2289 stderr_logging=True)
2291 logging.info("Command line: %s", cmdline)
2294 result = func(options, args)
2295 except (errors.GenericError, luxi.ProtocolError,
2296 JobSubmittedException), err:
2297 result, err_msg = FormatError(err)
2298 logging.exception("Error during command processing")
2300 except KeyboardInterrupt:
2301 result = constants.EXIT_FAILURE
2302 ToStderr("Aborted. Note that if the operation created any jobs, they"
2303 " might have been submitted and"
2304 " will continue to run in the background.")
2305 except IOError, err:
2306 if err.errno == errno.EPIPE:
2307 # our terminal went away, we'll exit
2308 sys.exit(constants.EXIT_FAILURE)
2315 def ParseNicOption(optvalue):
2316 """Parses the value of the --net option(s).
2320 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2321 except (TypeError, ValueError), err:
2322 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err),
2325 nics = [{}] * nic_max
2326 for nidx, ndict in optvalue:
2329 if not isinstance(ndict, dict):
2330 raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2331 " got %s" % (nidx, ndict), errors.ECODE_INVAL)
2333 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2340 def GenericInstanceCreate(mode, opts, args):
2341 """Add an instance to the cluster via either creation or import.
2343 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2344 @param opts: the command line options selected by the user
2346 @param args: should contain only one element, the new instance name
2348 @return: the desired exit code
2353 (pnode, snode) = SplitNodeOption(opts.node)
2358 hypervisor, hvparams = opts.hypervisor
2361 nics = ParseNicOption(opts.nics)
2365 elif mode == constants.INSTANCE_CREATE:
2366 # default of one nic, all auto
2372 if opts.disk_template == constants.DT_DISKLESS:
2373 if opts.disks or opts.sd_size is not None:
2374 raise errors.OpPrereqError("Diskless instance but disk"
2375 " information passed", errors.ECODE_INVAL)
2378 if (not opts.disks and not opts.sd_size
2379 and mode == constants.INSTANCE_CREATE):
2380 raise errors.OpPrereqError("No disk information specified",
2382 if opts.disks and opts.sd_size is not None:
2383 raise errors.OpPrereqError("Please use either the '--disk' or"
2384 " '-s' option", errors.ECODE_INVAL)
2385 if opts.sd_size is not None:
2386 opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2390 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2391 except ValueError, err:
2392 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
2394 disks = [{}] * disk_max
2397 for didx, ddict in opts.disks:
2399 if not isinstance(ddict, dict):
2400 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2401 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2402 elif constants.IDISK_SIZE in ddict:
2403 if constants.IDISK_ADOPT in ddict:
2404 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2405 " (disk %d)" % didx, errors.ECODE_INVAL)
2407 ddict[constants.IDISK_SIZE] = \
2408 utils.ParseUnit(ddict[constants.IDISK_SIZE])
2409 except ValueError, err:
2410 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2411 (didx, err), errors.ECODE_INVAL)
2412 elif constants.IDISK_ADOPT in ddict:
2413 if mode == constants.INSTANCE_IMPORT:
2414 raise errors.OpPrereqError("Disk adoption not allowed for instance"
2415 " import", errors.ECODE_INVAL)
2416 ddict[constants.IDISK_SIZE] = 0
2418 raise errors.OpPrereqError("Missing size or adoption source for"
2419 " disk %d" % didx, errors.ECODE_INVAL)
2422 if opts.tags is not None:
2423 tags = opts.tags.split(",")
2427 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2428 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2430 if mode == constants.INSTANCE_CREATE:
2433 force_variant = opts.force_variant
2436 no_install = opts.no_install
2437 identify_defaults = False
2438 elif mode == constants.INSTANCE_IMPORT:
2441 force_variant = False
2442 src_node = opts.src_node
2443 src_path = opts.src_dir
2445 identify_defaults = opts.identify_defaults
2447 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2449 op = opcodes.OpInstanceCreate(instance_name=instance,
2451 disk_template=opts.disk_template,
2453 pnode=pnode, snode=snode,
2454 ip_check=opts.ip_check,
2455 name_check=opts.name_check,
2456 wait_for_sync=opts.wait_for_sync,
2457 file_storage_dir=opts.file_storage_dir,
2458 file_driver=opts.file_driver,
2459 iallocator=opts.iallocator,
2460 hypervisor=hypervisor,
2462 beparams=opts.beparams,
2463 osparams=opts.osparams,
2467 force_variant=force_variant,
2471 no_install=no_install,
2472 identify_defaults=identify_defaults,
2473 ignore_ipolicy=opts.ignore_ipolicy)
2475 SubmitOrSend(op, opts)
2479 class _RunWhileClusterStoppedHelper:
2480 """Helper class for L{RunWhileClusterStopped} to simplify state management
2483 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2484 """Initializes this class.
2486 @type feedback_fn: callable
2487 @param feedback_fn: Feedback function
2488 @type cluster_name: string
2489 @param cluster_name: Cluster name
2490 @type master_node: string
2491 @param master_node Master node name
2492 @type online_nodes: list
2493 @param online_nodes: List of names of online nodes
2496 self.feedback_fn = feedback_fn
2497 self.cluster_name = cluster_name
2498 self.master_node = master_node
2499 self.online_nodes = online_nodes
2501 self.ssh = ssh.SshRunner(self.cluster_name)
2503 self.nonmaster_nodes = [name for name in online_nodes
2504 if name != master_node]
2506 assert self.master_node not in self.nonmaster_nodes
2508 def _RunCmd(self, node_name, cmd):
2509 """Runs a command on the local or a remote machine.
2511 @type node_name: string
2512 @param node_name: Machine name
2517 if node_name is None or node_name == self.master_node:
2518 # No need to use SSH
2519 result = utils.RunCmd(cmd)
2521 result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER,
2522 utils.ShellQuoteArgs(cmd))
2525 errmsg = ["Failed to run command %s" % result.cmd]
2527 errmsg.append("on node %s" % node_name)
2528 errmsg.append(": exitcode %s and error %s" %
2529 (result.exit_code, result.output))
2530 raise errors.OpExecError(" ".join(errmsg))
2532 def Call(self, fn, *args):
2533 """Call function while all daemons are stopped.
2536 @param fn: Function to be called
2539 # Pause watcher by acquiring an exclusive lock on watcher state file
2540 self.feedback_fn("Blocking watcher")
2541 watcher_block = utils.FileLock.Open(pathutils.WATCHER_LOCK_FILE)
2543 # TODO: Currently, this just blocks. There's no timeout.
2544 # TODO: Should it be a shared lock?
2545 watcher_block.Exclusive(blocking=True)
2547 # Stop master daemons, so that no new jobs can come in and all running
2549 self.feedback_fn("Stopping master daemons")
2550 self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"])
2552 # Stop daemons on all nodes
2553 for node_name in self.online_nodes:
2554 self.feedback_fn("Stopping daemons on %s" % node_name)
2555 self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"])
2557 # All daemons are shut down now
2559 return fn(self, *args)
2560 except Exception, err:
2561 _, errmsg = FormatError(err)
2562 logging.exception("Caught exception")
2563 self.feedback_fn(errmsg)
2566 # Start cluster again, master node last
2567 for node_name in self.nonmaster_nodes + [self.master_node]:
2568 self.feedback_fn("Starting daemons on %s" % node_name)
2569 self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "start-all"])
2572 watcher_block.Close()
2575 def RunWhileClusterStopped(feedback_fn, fn, *args):
2576 """Calls a function while all cluster daemons are stopped.
2578 @type feedback_fn: callable
2579 @param feedback_fn: Feedback function
2581 @param fn: Function to be called when daemons are stopped
2584 feedback_fn("Gathering cluster information")
2586 # This ensures we're running on the master daemon
2589 (cluster_name, master_node) = \
2590 cl.QueryConfigValues(["cluster_name", "master_node"])
2592 online_nodes = GetOnlineNodes([], cl=cl)
2594 # Don't keep a reference to the client. The master daemon will go away.
2597 assert master_node in online_nodes
2599 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2600 online_nodes).Call(fn, *args)
2603 def GenerateTable(headers, fields, separator, data,
2604 numfields=None, unitfields=None,
2606 """Prints a table with headers and different fields.
2609 @param headers: dictionary mapping field names to headers for
2612 @param fields: the field names corresponding to each row in
2614 @param separator: the separator to be used; if this is None,
2615 the default 'smart' algorithm is used which computes optimal
2616 field width, otherwise just the separator is used between
2619 @param data: a list of lists, each sublist being one row to be output
2620 @type numfields: list
2621 @param numfields: a list with the fields that hold numeric
2622 values and thus should be right-aligned
2623 @type unitfields: list
2624 @param unitfields: a list with the fields that hold numeric
2625 values that should be formatted with the units field
2626 @type units: string or None
2627 @param units: the units we should use for formatting, or None for
2628 automatic choice (human-readable for non-separator usage, otherwise
2629 megabytes); this is a one-letter string
2638 if numfields is None:
2640 if unitfields is None:
2643 numfields = utils.FieldSet(*numfields) # pylint: disable=W0142
2644 unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2647 for field in fields:
2648 if headers and field not in headers:
2649 # TODO: handle better unknown fields (either revert to old
2650 # style of raising exception, or deal more intelligently with
2652 headers[field] = field
2653 if separator is not None:
2654 format_fields.append("%s")
2655 elif numfields.Matches(field):
2656 format_fields.append("%*s")
2658 format_fields.append("%-*s")
2660 if separator is None:
2661 mlens = [0 for name in fields]
2662 format_str = " ".join(format_fields)
2664 format_str = separator.replace("%", "%%").join(format_fields)
2669 for idx, val in enumerate(row):
2670 if unitfields.Matches(fields[idx]):
2673 except (TypeError, ValueError):
2676 val = row[idx] = utils.FormatUnit(val, units)
2677 val = row[idx] = str(val)
2678 if separator is None:
2679 mlens[idx] = max(mlens[idx], len(val))
2684 for idx, name in enumerate(fields):
2686 if separator is None:
2687 mlens[idx] = max(mlens[idx], len(hdr))
2688 args.append(mlens[idx])
2690 result.append(format_str % tuple(args))
2692 if separator is None:
2693 assert len(mlens) == len(fields)
2695 if fields and not numfields.Matches(fields[-1]):
2701 line = ["-" for _ in fields]
2702 for idx in range(len(fields)):
2703 if separator is None:
2704 args.append(mlens[idx])
2705 args.append(line[idx])
2706 result.append(format_str % tuple(args))
2711 def _FormatBool(value):
2712 """Formats a boolean value as a string.
2720 #: Default formatting for query results; (callback, align right)
2721 _DEFAULT_FORMAT_QUERY = {
2722 constants.QFT_TEXT: (str, False),
2723 constants.QFT_BOOL: (_FormatBool, False),
2724 constants.QFT_NUMBER: (str, True),
2725 constants.QFT_TIMESTAMP: (utils.FormatTime, False),
2726 constants.QFT_OTHER: (str, False),
2727 constants.QFT_UNKNOWN: (str, False),
2731 def _GetColumnFormatter(fdef, override, unit):
2732 """Returns formatting function for a field.
2734 @type fdef: L{objects.QueryFieldDefinition}
2735 @type override: dict
2736 @param override: Dictionary for overriding field formatting functions,
2737 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2739 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
2740 @rtype: tuple; (callable, bool)
2741 @return: Returns the function to format a value (takes one parameter) and a
2742 boolean for aligning the value on the right-hand side
2745 fmt = override.get(fdef.name, None)
2749 assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
2751 if fdef.kind == constants.QFT_UNIT:
2752 # Can't keep this information in the static dictionary
2753 return (lambda value: utils.FormatUnit(value, unit), True)
2755 fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
2759 raise NotImplementedError("Can't format column type '%s'" % fdef.kind)
2762 class _QueryColumnFormatter:
2763 """Callable class for formatting fields of a query.
2766 def __init__(self, fn, status_fn, verbose):
2767 """Initializes this class.
2770 @param fn: Formatting function
2771 @type status_fn: callable
2772 @param status_fn: Function to report fields' status
2773 @type verbose: boolean
2774 @param verbose: whether to use verbose field descriptions or not
2778 self._status_fn = status_fn
2779 self._verbose = verbose
2781 def __call__(self, data):
2782 """Returns a field's string representation.
2785 (status, value) = data
2788 self._status_fn(status)
2790 if status == constants.RS_NORMAL:
2791 return self._fn(value)
2793 assert value is None, \
2794 "Found value %r for abnormal status %s" % (value, status)
2796 return FormatResultError(status, self._verbose)
2799 def FormatResultError(status, verbose):
2800 """Formats result status other than L{constants.RS_NORMAL}.
2802 @param status: The result status
2803 @type verbose: boolean
2804 @param verbose: Whether to return the verbose text
2805 @return: Text of result status
2808 assert status != constants.RS_NORMAL, \
2809 "FormatResultError called with status equal to constants.RS_NORMAL"
2811 (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
2813 raise NotImplementedError("Unknown status %s" % status)
2820 def FormatQueryResult(result, unit=None, format_override=None, separator=None,
2821 header=False, verbose=False):
2822 """Formats data in L{objects.QueryResponse}.
2824 @type result: L{objects.QueryResponse}
2825 @param result: result of query operation
2827 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
2828 see L{utils.text.FormatUnit}
2829 @type format_override: dict
2830 @param format_override: Dictionary for overriding field formatting functions,
2831 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2832 @type separator: string or None
2833 @param separator: String used to separate fields
2835 @param header: Whether to output header row
2836 @type verbose: boolean
2837 @param verbose: whether to use verbose field descriptions or not
2846 if format_override is None:
2847 format_override = {}
2849 stats = dict.fromkeys(constants.RS_ALL, 0)
2851 def _RecordStatus(status):
2856 for fdef in result.fields:
2857 assert fdef.title and fdef.name
2858 (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
2859 columns.append(TableColumn(fdef.title,
2860 _QueryColumnFormatter(fn, _RecordStatus,
2864 table = FormatTable(result.data, columns, header, separator)
2866 # Collect statistics
2867 assert len(stats) == len(constants.RS_ALL)
2868 assert compat.all(count >= 0 for count in stats.values())
2870 # Determine overall status. If there was no data, unknown fields must be
2871 # detected via the field definitions.
2872 if (stats[constants.RS_UNKNOWN] or
2873 (not result.data and _GetUnknownFields(result.fields))):
2875 elif compat.any(count > 0 for key, count in stats.items()
2876 if key != constants.RS_NORMAL):
2877 status = QR_INCOMPLETE
2881 return (status, table)
2884 def _GetUnknownFields(fdefs):
2885 """Returns list of unknown fields included in C{fdefs}.
2887 @type fdefs: list of L{objects.QueryFieldDefinition}
2890 return [fdef for fdef in fdefs
2891 if fdef.kind == constants.QFT_UNKNOWN]
2894 def _WarnUnknownFields(fdefs):
2895 """Prints a warning to stderr if a query included unknown fields.
2897 @type fdefs: list of L{objects.QueryFieldDefinition}
2900 unknown = _GetUnknownFields(fdefs)
2902 ToStderr("Warning: Queried for unknown fields %s",
2903 utils.CommaJoin(fdef.name for fdef in unknown))
2909 def GenericList(resource, fields, names, unit, separator, header, cl=None,
2910 format_override=None, verbose=False, force_filter=False,
2911 namefield=None, qfilter=None, isnumeric=False):
2912 """Generic implementation for listing all items of a resource.
2914 @param resource: One of L{constants.QR_VIA_LUXI}
2915 @type fields: list of strings
2916 @param fields: List of fields to query for
2917 @type names: list of strings
2918 @param names: Names of items to query for
2919 @type unit: string or None
2920 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
2921 None for automatic choice (human-readable for non-separator usage,
2922 otherwise megabytes); this is a one-letter string
2923 @type separator: string or None
2924 @param separator: String used to separate fields
2926 @param header: Whether to show header row
2927 @type force_filter: bool
2928 @param force_filter: Whether to always treat names as filter
2929 @type format_override: dict
2930 @param format_override: Dictionary for overriding field formatting functions,
2931 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
2932 @type verbose: boolean
2933 @param verbose: whether to use verbose field descriptions or not
2934 @type namefield: string
2935 @param namefield: Name of field to use for simple filters (see
2936 L{qlang.MakeFilter} for details)
2937 @type qfilter: list or None
2938 @param qfilter: Query filter (in addition to names)
2939 @param isnumeric: bool
2940 @param isnumeric: Whether the namefield's type is numeric, and therefore
2941 any simple filters built by namefield should use integer values to
2948 namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield,
2949 isnumeric=isnumeric)
2952 qfilter = namefilter
2953 elif namefilter is not None:
2954 qfilter = [qlang.OP_AND, namefilter, qfilter]
2959 response = cl.Query(resource, fields, qfilter)
2961 found_unknown = _WarnUnknownFields(response.fields)
2963 (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
2965 format_override=format_override,
2971 assert ((found_unknown and status == QR_UNKNOWN) or
2972 (not found_unknown and status != QR_UNKNOWN))
2974 if status == QR_UNKNOWN:
2975 return constants.EXIT_UNKNOWN_FIELD
2977 # TODO: Should the list command fail if not all data could be collected?
2978 return constants.EXIT_SUCCESS
2981 def GenericListFields(resource, fields, separator, header, cl=None):
2982 """Generic implementation for listing fields for a resource.
2984 @param resource: One of L{constants.QR_VIA_LUXI}
2985 @type fields: list of strings
2986 @param fields: List of fields to query for
2987 @type separator: string or None
2988 @param separator: String used to separate fields
2990 @param header: Whether to show header row
2999 response = cl.QueryFields(resource, fields)
3001 found_unknown = _WarnUnknownFields(response.fields)
3004 TableColumn("Name", str, False),
3005 TableColumn("Title", str, False),
3006 TableColumn("Description", str, False),
3009 rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields]
3011 for line in FormatTable(rows, columns, header, separator):
3015 return constants.EXIT_UNKNOWN_FIELD
3017 return constants.EXIT_SUCCESS
3021 """Describes a column for L{FormatTable}.
3024 def __init__(self, title, fn, align_right):
3025 """Initializes this class.
3028 @param title: Column title
3030 @param fn: Formatting function
3031 @type align_right: bool
3032 @param align_right: Whether to align values on the right-hand side
3037 self.align_right = align_right
3040 def _GetColFormatString(width, align_right):
3041 """Returns the format string for a field.
3049 return "%%%s%ss" % (sign, width)
3052 def FormatTable(rows, columns, header, separator):
3053 """Formats data as a table.
3055 @type rows: list of lists
3056 @param rows: Row data, one list per row
3057 @type columns: list of L{TableColumn}
3058 @param columns: Column descriptions
3060 @param header: Whether to show header row
3061 @type separator: string or None
3062 @param separator: String used to separate columns
3066 data = [[col.title for col in columns]]
3067 colwidth = [len(col.title) for col in columns]
3070 colwidth = [0 for _ in columns]
3074 assert len(row) == len(columns)
3076 formatted = [col.format(value) for value, col in zip(row, columns)]
3078 if separator is None:
3079 # Update column widths
3080 for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3081 # Modifying a list's items while iterating is fine
3082 colwidth[idx] = max(oldwidth, len(value))
3084 data.append(formatted)
3086 if separator is not None:
3087 # Return early if a separator is used
3088 return [separator.join(row) for row in data]
3090 if columns and not columns[-1].align_right:
3091 # Avoid unnecessary spaces at end of line
3094 # Build format string
3095 fmt = " ".join([_GetColFormatString(width, col.align_right)
3096 for col, width in zip(columns, colwidth)])
3098 return [fmt % tuple(row) for row in data]
3101 def FormatTimestamp(ts):
3102 """Formats a given timestamp.
3105 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3108 @return: a string with the formatted timestamp
3111 if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3115 return utils.FormatTime(sec, usecs=usecs)
3118 def ParseTimespec(value):
3119 """Parse a time specification.
3121 The following suffixed will be recognized:
3129 Without any suffix, the value will be taken to be in seconds.
3134 raise errors.OpPrereqError("Empty time specification passed",
3143 if value[-1] not in suffix_map:
3146 except (TypeError, ValueError):
3147 raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3150 multiplier = suffix_map[value[-1]]
3152 if not value: # no data left after stripping the suffix
3153 raise errors.OpPrereqError("Invalid time specification (only"
3154 " suffix passed)", errors.ECODE_INVAL)
3156 value = int(value) * multiplier
3157 except (TypeError, ValueError):
3158 raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3163 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3164 filter_master=False, nodegroup=None):
3165 """Returns the names of online nodes.
3167 This function will also log a warning on stderr with the names of
3170 @param nodes: if not empty, use only this subset of nodes (minus the
3172 @param cl: if not None, luxi client to use
3173 @type nowarn: boolean
3174 @param nowarn: by default, this function will output a note with the
3175 offline nodes that are skipped; if this parameter is True the
3176 note is not displayed
3177 @type secondary_ips: boolean
3178 @param secondary_ips: if True, return the secondary IPs instead of the
3179 names, useful for doing network traffic over the replication interface
3181 @type filter_master: boolean
3182 @param filter_master: if True, do not return the master node in the list
3183 (useful in coordination with secondary_ips where we cannot check our
3184 node name against the list)
3185 @type nodegroup: string
3186 @param nodegroup: If set, only return nodes in this node group
3195 qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3197 if nodegroup is not None:
3198 qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3199 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3202 qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3205 if len(qfilter) > 1:
3206 final_filter = [qlang.OP_AND] + qfilter
3208 assert len(qfilter) == 1
3209 final_filter = qfilter[0]
3213 result = cl.Query(constants.QR_NODE, ["name", "offline", "sip"], final_filter)
3215 def _IsOffline(row):
3216 (_, (_, offline), _) = row
3220 ((_, name), _, _) = row
3224 (_, _, (_, sip)) = row
3227 (offline, online) = compat.partition(result.data, _IsOffline)
3229 if offline and not nowarn:
3230 ToStderr("Note: skipping offline node(s): %s" %
3231 utils.CommaJoin(map(_GetName, offline)))
3238 return map(fn, online)
3241 def _ToStream(stream, txt, *args):
3242 """Write a message to a stream, bypassing the logging system
3244 @type stream: file object
3245 @param stream: the file to which we should write
3247 @param txt: the message
3253 stream.write(txt % args)
3258 except IOError, err:
3259 if err.errno == errno.EPIPE:
3260 # our terminal went away, we'll exit
3261 sys.exit(constants.EXIT_FAILURE)
3266 def ToStdout(txt, *args):
3267 """Write a message to stdout only, bypassing the logging system
3269 This is just a wrapper over _ToStream.
3272 @param txt: the message
3275 _ToStream(sys.stdout, txt, *args)
3278 def ToStderr(txt, *args):
3279 """Write a message to stderr only, bypassing the logging system
3281 This is just a wrapper over _ToStream.
3284 @param txt: the message
3287 _ToStream(sys.stderr, txt, *args)
3290 class JobExecutor(object):
3291 """Class which manages the submission and execution of multiple jobs.
3293 Note that instances of this class should not be reused between
3297 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3302 self.verbose = verbose
3305 self.feedback_fn = feedback_fn
3306 self._counter = itertools.count()
3309 def _IfName(name, fmt):
3310 """Helper function for formatting name.
3318 def QueueJob(self, name, *ops):
3319 """Record a job for later submit.
3322 @param name: a description of the job, will be used in WaitJobSet
3325 SetGenericOpcodeOpts(ops, self.opts)
3326 self.queue.append((self._counter.next(), name, ops))
3328 def AddJobId(self, name, status, job_id):
3329 """Adds a job ID to the internal queue.
3332 self.jobs.append((self._counter.next(), status, job_id, name))
3334 def SubmitPending(self, each=False):
3335 """Submit all pending jobs.
3340 for (_, _, ops) in self.queue:
3341 # SubmitJob will remove the success status, but raise an exception if
3342 # the submission fails, so we'll notice that anyway.
3343 results.append([True, self.cl.SubmitJob(ops)[0]])
3345 results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3346 for ((status, data), (idx, name, _)) in zip(results, self.queue):
3347 self.jobs.append((idx, status, data, name))
3349 def _ChooseJob(self):
3350 """Choose a non-waiting/queued job to poll next.
3353 assert self.jobs, "_ChooseJob called with empty job list"
3355 result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3359 for job_data, status in zip(self.jobs, result):
3360 if (isinstance(status, list) and status and
3361 status[0] in (constants.JOB_STATUS_QUEUED,
3362 constants.JOB_STATUS_WAITING,
3363 constants.JOB_STATUS_CANCELING)):
3364 # job is still present and waiting
3366 # good candidate found (either running job or lost job)
3367 self.jobs.remove(job_data)
3371 return self.jobs.pop(0)
3373 def GetResults(self):
3374 """Wait for and return the results of all jobs.
3377 @return: list of tuples (success, job results), in the same order
3378 as the submitted jobs; if a job has failed, instead of the result
3379 there will be the error message
3383 self.SubmitPending()
3386 ok_jobs = [row[2] for row in self.jobs if row[1]]
3388 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3390 # first, remove any non-submitted jobs
3391 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3392 for idx, _, jid, name in failures:
3393 ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3394 results.append((idx, False, jid))
3397 (idx, _, jid, name) = self._ChooseJob()
3398 ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3400 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3402 except errors.JobLost, err:
3403 _, job_result = FormatError(err)
3404 ToStderr("Job %s%s has been archived, cannot check its result",
3405 jid, self._IfName(name, " for %s"))
3407 except (errors.GenericError, luxi.ProtocolError), err:
3408 _, job_result = FormatError(err)
3410 # the error message will always be shown, verbose or not
3411 ToStderr("Job %s%s has failed: %s",
3412 jid, self._IfName(name, " for %s"), job_result)
3414 results.append((idx, success, job_result))
3416 # sort based on the index, then drop it
3418 results = [i[1:] for i in results]
3422 def WaitOrShow(self, wait):
3423 """Wait for job results or only print the job IDs.
3426 @param wait: whether to wait or not
3430 return self.GetResults()
3433 self.SubmitPending()
3434 for _, status, result, name in self.jobs:
3436 ToStdout("%s: %s", result, name)
3438 ToStderr("Failure for %s: %s", name, result)
3439 return [row[1:3] for row in self.jobs]
3442 def FormatParameterDict(buf, param_dict, actual, level=1):
3443 """Formats a parameter dictionary.
3445 @type buf: L{StringIO}
3446 @param buf: the buffer into which to write
3447 @type param_dict: dict
3448 @param param_dict: the own parameters
3450 @param actual: the current parameter set (including defaults)
3451 @param level: Level of indent
3454 indent = " " * level
3456 for key in sorted(actual):
3458 buf.write("%s- %s:" % (indent, key))
3460 if isinstance(data, dict) and data:
3462 FormatParameterDict(buf, param_dict.get(key, {}), data,
3465 val = param_dict.get(key, "default (%s)" % data)
3466 buf.write(" %s\n" % val)
3469 def ConfirmOperation(names, list_type, text, extra=""):
3470 """Ask the user to confirm an operation on a list of list_type.
3472 This function is used to request confirmation for doing an operation
3473 on a given list of list_type.
3476 @param names: the list of names that we display when
3477 we ask for confirmation
3478 @type list_type: str
3479 @param list_type: Human readable name for elements in the list (e.g. nodes)
3481 @param text: the operation that the user should confirm
3483 @return: True or False depending on user's confirmation.
3487 msg = ("The %s will operate on %d %s.\n%s"
3488 "Do you want to continue?" % (text, count, list_type, extra))
3489 affected = (("\nAffected %s:\n" % list_type) +
3490 "\n".join([" %s" % name for name in names]))
3492 choices = [("y", True, "Yes, execute the %s" % text),
3493 ("n", False, "No, abort the %s" % text)]
3496 choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3499 question = msg + affected
3501 choice = AskUser(question, choices)
3504 choice = AskUser(msg + affected, choices)
3508 def _MaybeParseUnit(elements):
3509 """Parses and returns an array of potential values with units.
3513 for k, v in elements.items():
3514 if v == constants.VALUE_DEFAULT:
3517 parsed[k] = utils.ParseUnit(v)
3521 def CreateIPolicyFromOpts(ispecs_mem_size=None,
3522 ispecs_cpu_count=None,
3523 ispecs_disk_count=None,
3524 ispecs_disk_size=None,
3525 ispecs_nic_count=None,
3526 ipolicy_disk_templates=None,
3527 ipolicy_vcpu_ratio=None,
3528 ipolicy_spindle_ratio=None,
3529 group_ipolicy=False,
3530 allowed_values=None,
3532 """Creation of instance policy based on command line options.
3534 @param fill_all: whether for cluster policies we should ensure that
3535 all values are filled
3541 ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3542 if ispecs_disk_size:
3543 ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3544 except (TypeError, ValueError, errors.UnitParseError), err:
3545 raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3547 (ispecs_disk_size, ispecs_mem_size, err),
3550 # prepare ipolicy dict
3551 ipolicy_transposed = {
3552 constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3553 constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3554 constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3555 constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3556 constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3559 # first, check that the values given are correct
3561 forced_type = TISPECS_GROUP_TYPES
3563 forced_type = TISPECS_CLUSTER_TYPES
3565 for specs in ipolicy_transposed.values():
3566 utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
3569 ipolicy_out = objects.MakeEmptyIPolicy()
3570 for name, specs in ipolicy_transposed.iteritems():
3571 assert name in constants.ISPECS_PARAMETERS
3572 for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3573 ipolicy_out[key][name] = val
3575 # no filldict for non-dicts
3576 if not group_ipolicy and fill_all:
3577 if ipolicy_disk_templates is None:
3578 ipolicy_disk_templates = constants.DISK_TEMPLATES
3579 if ipolicy_vcpu_ratio is None:
3580 ipolicy_vcpu_ratio = \
3581 constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
3582 if ipolicy_spindle_ratio is None:
3583 ipolicy_spindle_ratio = \
3584 constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
3585 if ipolicy_disk_templates is not None:
3586 ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
3587 if ipolicy_vcpu_ratio is not None:
3588 ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
3589 if ipolicy_spindle_ratio is not None:
3590 ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
3592 assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)