X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/d67e0a94f717fbbd98ae635a3ea69ef04aa6097b..a95c53eabe815fe478edcd79436fd0e572277814:/lib/cli.py diff --git a/lib/cli.py b/lib/cli.py index b794222..b7a4d31 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -44,6 +44,7 @@ from ganeti import compat from ganeti import netutils from ganeti import qlang from ganeti import objects +from ganeti import pathutils from optparse import (OptionParser, TitledHelpFormatter, Option, OptionValueError) @@ -53,6 +54,7 @@ __all__ = [ # Command line options "ABSOLUTE_OPT", "ADD_UIDS_OPT", + "ADD_RESERVED_IPS_OPT", "ALLOCATABLE_OPT", "ALLOC_POLICY_OPT", "ALL_OPT", @@ -86,6 +88,8 @@ __all__ = [ "FORCE_FILTER_OPT", "FORCE_OPT", "FORCE_VARIANT_OPT", + "GATEWAY_OPT", + "GATEWAY6_OPT", "GLOBAL_FILEDIR_OPT", "HID_OS_OPT", "GLOBAL_SHARED_FILEDIR_OPT", @@ -110,6 +114,9 @@ __all__ = [ "MC_OPT", "MIGRATION_MODE_OPT", "NET_OPT", + "NETWORK_OPT", + "NETWORK6_OPT", + "NETWORK_TYPE_OPT", "NEW_CLUSTER_CERT_OPT", "NEW_CLUSTER_DOMAIN_SECRET_OPT", "NEW_CONFD_HMAC_KEY_OPT", @@ -117,6 +124,7 @@ __all__ = [ "NEW_SECONDARY_OPT", "NEW_SPICE_CERT_OPT", "NIC_PARAMS_OPT", + "NOCONFLICTSCHECK_OPT", "NODE_FORCE_JOIN_OPT", "NODE_LIST_OPT", "NODE_PLACEMENT_OPT", @@ -159,6 +167,7 @@ __all__ = [ "READD_OPT", "REBOOT_TYPE_OPT", "REMOVE_INSTANCE_OPT", + "REMOVE_RESERVED_IPS_OPT", "REMOVE_UIDS_OPT", "RESERVED_LVS_OPT", "RUNTIME_MEM_OPT", @@ -168,6 +177,7 @@ __all__ = [ "SELECT_OS_OPT", "SEP_OPT", "SHOWCMD_OPT", + "SHOW_MACHINE_OPT", "SHUTDOWN_TIMEOUT_OPT", "SINGLE_NODE_OPT", "SPECS_CPU_COUNT_OPT", @@ -195,6 +205,7 @@ __all__ = [ "USE_REPL_NET_OPT", "VERBOSE_OPT", "VG_NAME_OPT", + "WFSYNC_OPT", "YES_DOIT_OPT", "DISK_STATE_OPT", "HV_STATE_OPT", @@ -233,11 +244,13 @@ __all__ = [ "ARGS_MANY_INSTANCES", "ARGS_MANY_NODES", "ARGS_MANY_GROUPS", + "ARGS_MANY_NETWORKS", "ARGS_NONE", "ARGS_ONE_INSTANCE", "ARGS_ONE_NODE", "ARGS_ONE_GROUP", "ARGS_ONE_OS", + "ARGS_ONE_NETWORK", "ArgChoice", "ArgCommand", "ArgFile", @@ -245,6 +258,7 @@ __all__ = [ "ArgHost", "ArgInstance", "ArgJobId", + "ArgNetwork", "ArgNode", "ArgOs", "ArgSuggest", @@ -255,6 +269,7 @@ __all__ = [ "OPT_COMPL_ONE_INSTANCE", "OPT_COMPL_ONE_NODE", "OPT_COMPL_ONE_NODEGROUP", + "OPT_COMPL_ONE_NETWORK", "OPT_COMPL_ONE_OS", "cli_option", "SplitNodeOption", @@ -353,6 +368,12 @@ class ArgNode(_Argument): """ +class ArgNetwork(_Argument): + """Network argument. + + """ + + class ArgGroup(_Argument): """Node group argument. @@ -391,9 +412,11 @@ class ArgOs(_Argument): ARGS_NONE = [] ARGS_MANY_INSTANCES = [ArgInstance()] +ARGS_MANY_NETWORKS = [ArgNetwork()] ARGS_MANY_NODES = [ArgNode()] ARGS_MANY_GROUPS = [ArgGroup()] ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)] +ARGS_ONE_NETWORK = [ArgNetwork(min=1, max=1)] ARGS_ONE_NODE = [ArgNode(min=1, max=1)] # TODO ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)] @@ -410,12 +433,14 @@ def _ExtractTagsObject(opts, args): raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject") kind = opts.tag_type if kind == constants.TAG_CLUSTER: - retval = kind, kind + retval = kind, None elif kind in (constants.TAG_NODEGROUP, constants.TAG_NODE, + constants.TAG_NETWORK, constants.TAG_INSTANCE): if not args: - raise errors.OpPrereqError("no arguments passed to the command") + raise errors.OpPrereqError("no arguments passed to the command", + errors.ECODE_INVAL) name = args.pop(0) retval = kind, name else: @@ -462,7 +487,7 @@ def ListTags(opts, args): """ kind, name = _ExtractTagsObject(opts, args) - cl = GetClient() + cl = GetClient(query=True) result = cl.QueryTags(kind, name) result = list(result) result.sort() @@ -482,7 +507,7 @@ def AddTags(opts, args): kind, name = _ExtractTagsObject(opts, args) _ExtendTags(opts, args) if not args: - raise errors.OpPrereqError("No tags to be added") + raise errors.OpPrereqError("No tags to be added", errors.ECODE_INVAL) op = opcodes.OpTagsSet(kind=kind, name=name, tags=args) SubmitOrSend(op, opts) @@ -499,7 +524,7 @@ def RemoveTags(opts, args): kind, name = _ExtractTagsObject(opts, args) _ExtendTags(opts, args) if not args: - raise errors.OpPrereqError("No tags to be removed") + raise errors.OpPrereqError("No tags to be removed", errors.ECODE_INVAL) op = opcodes.OpTagsDel(kind=kind, name=name, tags=args) SubmitOrSend(op, opts) @@ -617,6 +642,18 @@ def check_list(option, opt, value): # pylint: disable=W0613 return utils.UnescapeAndSplit(value) +def check_maybefloat(option, opt, value): # pylint: disable=W0613 + """Custom parser for float numbers which might be also defaults. + + """ + value = value.lower() + + if value == constants.VALUE_DEFAULT: + return value + else: + return float(value) + + # completion_suggestion is normally a list. Using numeric values not evaluating # to False for dynamic completion. (OPT_COMPL_MANY_NODES, @@ -624,8 +661,9 @@ def check_list(option, opt, value): # pylint: disable=W0613 OPT_COMPL_ONE_INSTANCE, OPT_COMPL_ONE_OS, OPT_COMPL_ONE_IALLOCATOR, + OPT_COMPL_ONE_NETWORK, OPT_COMPL_INST_ADD_NODES, - OPT_COMPL_ONE_NODEGROUP) = range(100, 107) + OPT_COMPL_ONE_NODEGROUP) = range(100, 108) OPT_COMPL_ALL = frozenset([ OPT_COMPL_MANY_NODES, @@ -633,6 +671,7 @@ OPT_COMPL_ALL = frozenset([ OPT_COMPL_ONE_INSTANCE, OPT_COMPL_ONE_OS, OPT_COMPL_ONE_IALLOCATOR, + OPT_COMPL_ONE_NETWORK, OPT_COMPL_INST_ADD_NODES, OPT_COMPL_ONE_NODEGROUP, ]) @@ -651,6 +690,7 @@ class CliOption(Option): "unit", "bool", "list", + "maybefloat", ) TYPE_CHECKER = Option.TYPE_CHECKER.copy() TYPE_CHECKER["identkeyval"] = check_ident_key_val @@ -658,6 +698,7 @@ class CliOption(Option): TYPE_CHECKER["unit"] = check_unit TYPE_CHECKER["bool"] = check_bool TYPE_CHECKER["list"] = check_list + TYPE_CHECKER["maybefloat"] = check_maybefloat # optparse.py sets make_option, so we do it for our own option class, too @@ -717,7 +758,7 @@ SYNC_OPT = cli_option("--sync", dest="do_locking", DRY_RUN_OPT = cli_option("--dry-run", default=False, action="store_true", help=("Do not execute the operation, just run the" - " check steps and verify it it could be" + " check steps and verify if it could be" " executed")) VERBOSE_OPT = cli_option("-v", "--verbose", default=False, @@ -733,6 +774,10 @@ NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync", default=True, action="store_false", help="Don't wait for sync (DANGEROUS!)") +WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync", + default=False, action="store_true", + help="Wait for disks to sync") + ONLINE_INST_OPT = cli_option("--online", dest="online_inst", action="store_true", default=False, help="Enable offline instance") @@ -768,18 +813,19 @@ IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="", completion_suggest=OPT_COMPL_ONE_IALLOCATOR) DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator", - metavar="", - help="Set the default instance allocator plugin", - default=None, type="string", - completion_suggest=OPT_COMPL_ONE_IALLOCATOR) + metavar="", + help="Set the default instance" + " allocator plugin", + default=None, type="string", + completion_suggest=OPT_COMPL_ONE_IALLOCATOR) OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run", metavar="", completion_suggest=OPT_COMPL_ONE_OS) OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams", - type="keyval", default={}, - help="OS parameters") + type="keyval", default={}, + help="OS parameters") FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant", action="store_true", default=False, @@ -828,7 +874,7 @@ SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count", SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size", type="keyval", default={}, help="Disk size specs: list of key=value," - " where key is one of min, max, std" + " where key is one of min, max, std" " (in MB or using a unit)") SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count", @@ -837,16 +883,22 @@ SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count", " where key is one of min, max, std") IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates", - dest="ipolicy_disk_templates", - type="list", default=None, - help="Comma-separated list of" - " enabled disk templates") + dest="ipolicy_disk_templates", + type="list", default=None, + help="Comma-separated list of" + " enabled disk templates") IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio", dest="ipolicy_vcpu_ratio", - type="float", default=None, + type="maybefloat", default=None, help="The maximum allowed vcpu-to-cpu ratio") +IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio", + dest="ipolicy_spindle_ratio", + type="maybefloat", default=None, + help=("The maximum allowed instances to" + " spindle ratio")) + HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor", help="Hypervisor and hypervisor options, in the" " format hypervisor:option=value,option=value,...", @@ -1061,12 +1113,12 @@ DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO, " (excluded from allocation operations)")) CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable", - type="bool", default=None, metavar=_YORNO, - help="Set the master_capable flag on the node") + type="bool", default=None, metavar=_YORNO, + help="Set the master_capable flag on the node") CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable", - type="bool", default=None, metavar=_YORNO, - help="Set the vm_capable flag on the node") + type="bool", default=None, metavar=_YORNO, + help="Set the vm_capable flag on the node") ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable", type="bool", default=None, metavar=_YORNO, @@ -1123,30 +1175,30 @@ MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask", default=None) USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script", - dest="use_external_mip_script", - help="Specify whether to run a user-provided" - " script for the master IP address turnup and" - " turndown operations", - type="bool", metavar=_YORNO, default=None) + dest="use_external_mip_script", + help="Specify whether to run a" + " user-provided script for the master" + " IP address turnup and" + " turndown operations", + type="bool", metavar=_YORNO, default=None) GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir", help="Specify the default directory (cluster-" "wide) for storing the file-based disks [%s]" % - constants.DEFAULT_FILE_STORAGE_DIR, + pathutils.DEFAULT_FILE_STORAGE_DIR, metavar="DIR", - default=constants.DEFAULT_FILE_STORAGE_DIR) + default=pathutils.DEFAULT_FILE_STORAGE_DIR) -GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir", - dest="shared_file_storage_dir", - help="Specify the default directory (cluster-" - "wide) for storing the shared file-based" - " disks [%s]" % - constants.DEFAULT_SHARED_FILE_STORAGE_DIR, - metavar="SHAREDDIR", - default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR) +GLOBAL_SHARED_FILEDIR_OPT = cli_option( + "--shared-file-storage-dir", + dest="shared_file_storage_dir", + help="Specify the default directory (cluster-wide) for storing the" + " shared file-based disks [%s]" % + pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR, + metavar="SHAREDDIR", default=pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR) NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts", - help="Don't modify /etc/hosts", + help="Don't modify %s" % pathutils.ETC_HOSTS, action="store_false", default=True) NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup", @@ -1181,9 +1233,10 @@ TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int", help="Maximum time to wait") SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout", - dest="shutdown_timeout", type="int", - default=constants.DEFAULT_SHUTDOWN_TIMEOUT, - help="Maximum time to wait for instance shutdown") + dest="shutdown_timeout", type="int", + default=constants.DEFAULT_SHUTDOWN_TIMEOUT, + help="Maximum time to wait for instance" + " shutdown") INTERVAL_OPT = cli_option("--interval", dest="interval", type="int", default=None, @@ -1211,19 +1264,19 @@ NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert", " certificate")) SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert", - default=None, - help="File containing new SPICE certificate") + default=None, + help="File containing new SPICE certificate") SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert", - default=None, - help="File containing the certificate of the CA" - " which signed the SPICE certificate") + default=None, + help="File containing the certificate of the CA" + " which signed the SPICE certificate") NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate", - dest="new_spice_cert", default=None, - action="store_true", - help=("Generate a new self-signed SPICE" - " certificate")) + dest="new_spice_cert", default=None, + action="store_true", + help=("Generate a new self-signed SPICE" + " certificate")) NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key", dest="new_confd_hmac_key", @@ -1281,10 +1334,10 @@ REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None, " removed from the user-id pool")) RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None, - action="store", dest="reserved_lvs", - help=("A comma-separated list of reserved" - " logical volumes names, that will be" - " ignored by cluster verify")) + action="store", dest="reserved_lvs", + help=("A comma-separated list of reserved" + " logical volumes names, that will be" + " ignored by cluster verify")) ROMAN_OPT = cli_option("--roman", dest="roman_integers", default=False, @@ -1306,9 +1359,25 @@ PRIMARY_IP_VERSION_OPT = \ constants.IP6_VERSION), help="Cluster-wide IP version for primary IP") +SHOW_MACHINE_OPT = cli_option("-M", "--show-machine-names", default=False, + action="store_true", + help="Show machine name for every line in output") + + +def _PriorityOptionCb(option, _, value, parser): + """Callback for processing C{--priority} option. + + """ + value = _PRIONAME_TO_VALUE[value] + + setattr(parser.values, option.dest, value) + + PRIORITY_OPT = cli_option("--priority", default=None, dest="priority", metavar="|".join(name for name, _ in _PRIORITY_NAMES), choices=_PRIONAME_TO_VALUE.keys(), + action="callback", type="choice", + callback=_PriorityOptionCb, help="Priority for opcode processing") HID_OS_OPT = cli_option("--hidden", dest="hidden", @@ -1339,8 +1408,8 @@ NODE_POWERED_OPT = cli_option("--node-powered", default=None, help="Specify if the SoR for node is powered") OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int", - default=constants.OOB_TIMEOUT, - help="Maximum time to wait for out-of-band helper") + default=constants.OOB_TIMEOUT, + help="Maximum time to wait for out-of-band helper") POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float", default=constants.OOB_POWER_DELAY, @@ -1384,14 +1453,17 @@ IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[], DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state", action="append", - help=("Specify disk state information in the format" - " storage_type/identifier:option=value,..."), + help=("Specify disk state information in the" + " format" + " storage_type/identifier:option=value,...;" + " note this is unused for now"), type="identkeyval") HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state", action="append", help=("Specify hypervisor state information in the" - " format hypervisor:option=value,..."), + " format hypervisor:option=value,...;" + " note this is unused for now"), type="identkeyval") IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy", @@ -1408,6 +1480,44 @@ ABSOLUTE_OPT = cli_option("--absolute", dest="absolute", help="Marks the grow as absolute instead of the" " (default) relative mode") +NETWORK_OPT = cli_option("--network", + action="store", default=None, dest="network", + help="IP network in CIDR notation") + +GATEWAY_OPT = cli_option("--gateway", + action="store", default=None, dest="gateway", + help="IP address of the router (gateway)") + +ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips", + action="store", default=None, + dest="add_reserved_ips", + help="Comma-separated list of" + " reserved IPs to add") + +REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips", + action="store", default=None, + dest="remove_reserved_ips", + help="Comma-delimited list of" + " reserved IPs to remove") + +NETWORK_TYPE_OPT = cli_option("--network-type", + action="store", default=None, dest="network_type", + help="Network type: private, public, None") + +NETWORK6_OPT = cli_option("--network6", + action="store", default=None, dest="network6", + help="IP network in CIDR notation") + +GATEWAY6_OPT = cli_option("--gateway6", + action="store", default=None, dest="gateway6", + help="IP6 address of the router (gateway)") + +NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check", + dest="conflicts_check", + default=True, + action="store_false", + help="Don't check for conflicting IPs") + #: Options provided by all commands COMMON_OPTS = [DEBUG_OPT] @@ -1424,6 +1534,7 @@ COMMON_CREATE_OPTS = [ NET_OPT, NODE_PLACEMENT_OPT, NOIPCHECK_OPT, + NOCONFLICTSCHECK_OPT, NONAMECHECK_OPT, NONICS_OPT, NWSYNC_OPT, @@ -1444,71 +1555,64 @@ INSTANCE_POLICY_OPTS = [ SPECS_NIC_COUNT_OPT, IPOLICY_DISK_TEMPLATES, IPOLICY_VCPU_RATIO, + IPOLICY_SPINDLE_RATIO, ] -def _ParseArgs(argv, commands, aliases, env_override): +class _ShowUsage(Exception): + """Exception class for L{_ParseArgs}. + + """ + def __init__(self, exit_error): + """Initializes instances of this class. + + @type exit_error: bool + @param exit_error: Whether to report failure on exit + + """ + Exception.__init__(self) + self.exit_error = exit_error + + +class _ShowVersion(Exception): + """Exception class for L{_ParseArgs}. + + """ + + +def _ParseArgs(binary, argv, commands, aliases, env_override): """Parser for the command line arguments. This function parses the arguments and returns the function which must be executed together with its (modified) arguments. - @param argv: the command line - @param commands: dictionary with special contents, see the design - doc for cmdline handling - @param aliases: dictionary with command aliases {'alias': 'target, ...} + @param binary: Script name + @param argv: Command line arguments + @param commands: Dictionary containing command definitions + @param aliases: dictionary with command aliases {"alias": "target", ...} @param env_override: list of env variables allowed for default args + @raise _ShowUsage: If usage description should be shown + @raise _ShowVersion: If version should be shown """ assert not (env_override - set(commands)) + assert not (set(aliases.keys()) & set(commands.keys())) - if len(argv) == 0: - binary = "" + if len(argv) > 1: + cmd = argv[1] else: - binary = argv[0].split("/")[-1] - - if len(argv) > 1 and argv[1] == "--version": - ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION, - constants.RELEASE_VERSION) - # Quit right away. That way we don't have to care about this special - # argument. optparse.py does it the same. - sys.exit(0) - - if len(argv) < 2 or not (argv[1] in commands or - argv[1] in aliases): - # let's do a nice thing - sortedcmds = commands.keys() - sortedcmds.sort() - - ToStdout("Usage: %s {command} [options...] [argument...]", binary) - ToStdout("%s --help to see details, or man %s", binary, binary) - ToStdout("") - - # compute the max line length for cmd + usage - mlen = max([len(" %s" % cmd) for cmd in commands]) - mlen = min(60, mlen) # should not get here... - - # and format a nice command list - ToStdout("Commands:") - for cmd in sortedcmds: - cmdstr = " %s" % (cmd,) - help_text = commands[cmd][4] - help_lines = textwrap.wrap(help_text, 79 - 3 - mlen) - ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0)) - for line in help_lines: - ToStdout("%-*s %s", mlen, "", line) - - ToStdout("") + # No option or command given + raise _ShowUsage(exit_error=True) - return None, None, None + if cmd == "--version": + raise _ShowVersion() + elif cmd == "--help": + raise _ShowUsage(exit_error=False) + elif not (cmd in commands or cmd in aliases): + raise _ShowUsage(exit_error=True) # get command, unalias it, and look it up in commands - cmd = argv.pop(1) if cmd in aliases: - if cmd in commands: - raise errors.ProgrammerError("Alias '%s' overrides an existing" - " command" % cmd) - if aliases[cmd] not in commands: raise errors.ProgrammerError("Alias '%s' maps to non-existing" " command '%s'" % (cmd, aliases[cmd])) @@ -1519,7 +1623,7 @@ def _ParseArgs(argv, commands, aliases, env_override): args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper() env_args = os.environ.get(args_env_name) if env_args: - argv = utils.InsertAtPos(argv, 1, shlex.split(env_args)) + argv = utils.InsertAtPos(argv, 2, shlex.split(env_args)) func, args_def, parser_opts, usage, description = commands[cmd] parser = OptionParser(option_list=parser_opts + COMMON_OPTS, @@ -1527,7 +1631,7 @@ def _ParseArgs(argv, commands, aliases, env_override): formatter=TitledHelpFormatter(), usage="%%prog %s %s" % (cmd, usage)) parser.disable_interspersed_args() - options, args = parser.parse_args(args=argv[1:]) + options, args = parser.parse_args(args=argv[2:]) if not _CheckArguments(cmd, args_def, args): return None, None, None @@ -1535,6 +1639,31 @@ def _ParseArgs(argv, commands, aliases, env_override): return func, options, args +def _FormatUsage(binary, commands): + """Generates a nice description of all commands. + + @param binary: Script name + @param commands: Dictionary containing command definitions + + """ + # compute the max line length for cmd + usage + mlen = min(60, max(map(len, commands))) + + yield "Usage: %s {command} [options...] [argument...]" % binary + yield "%s --help to see details, or man %s" % (binary, binary) + yield "" + yield "Commands:" + + # and format a nice command list + for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()): + help_lines = textwrap.wrap(help_text, 79 - 3 - mlen) + yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0)) + for line in help_lines: + yield " %-*s %s" % (mlen, "", line) + + yield "" + + def _CheckArguments(cmd, args_def, args): """Verifies the arguments using the argument definition. @@ -2047,13 +2176,26 @@ def SetGenericOpcodeOpts(opcode_list, options): if hasattr(options, "dry_run"): op.dry_run = options.dry_run if getattr(options, "priority", None) is not None: - op.priority = _PRIONAME_TO_VALUE[options.priority] + op.priority = options.priority -def GetClient(): +def GetClient(query=False): + """Connects to the a luxi socket and returns a client. + + @type query: boolean + @param query: this signifies that the client will only be + used for queries; if the build-time parameter + enable-split-queries is enabled, then the client will be + connected to the query socket instead of the masterd socket + + """ + if query and constants.ENABLE_SPLIT_QUERY: + address = pathutils.QUERY_SOCKET + else: + address = None # TODO: Cache object? try: - client = luxi.Client() + client = luxi.Client(address=address) except luxi.NoMasterError: ss = ssconf.SimpleStore() @@ -2062,13 +2204,14 @@ def GetClient(): ss.GetMasterNode() except errors.ConfigurationError: raise errors.OpPrereqError("Cluster not initialized or this machine is" - " not part of a cluster") + " not part of a cluster", + errors.ECODE_INVAL) master, myself = ssconf.GetMasterAndMyself(ss=ss) if master != myself: raise errors.OpPrereqError("This is not the master node, please connect" " to node '%s' and rerun the command" % - master) + master, errors.ECODE_INVAL) raise return client @@ -2112,7 +2255,7 @@ def FormatError(err): elif isinstance(err, errors.OpPrereqError): if len(err.args) == 2: obuf.write("Failure: prerequisites not met for this" - " operation:\nerror type: %s, error details:\n%s" % + " operation:\nerror type: %s, error details:\n%s" % (err.args[1], err.args[0])) else: obuf.write("Failure: prerequisites not met for this" @@ -2195,7 +2338,20 @@ def GenericMain(commands, override=None, aliases=None, aliases = {} try: - func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override) + (func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases, + env_override) + except _ShowVersion: + ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION, + constants.RELEASE_VERSION) + return constants.EXIT_SUCCESS + except _ShowUsage, err: + for line in _FormatUsage(binary, commands): + ToStdout(line) + + if err.exit_error: + return constants.EXIT_FAILURE + else: + return constants.EXIT_SUCCESS except errors.ParameterError, err: result, err_msg = FormatError(err) ToStderr(err_msg) @@ -2208,7 +2364,7 @@ def GenericMain(commands, override=None, aliases=None, for key, val in override.iteritems(): setattr(options, key, val) - utils.SetupLogging(constants.LOG_COMMANDS, logname, debug=options.debug, + utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug, stderr_logging=True) logging.info("Command line: %s", cmdline) @@ -2242,7 +2398,8 @@ def ParseNicOption(optvalue): try: nic_max = max(int(nidx[0]) + 1 for nidx in optvalue) except (TypeError, ValueError), err: - raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err)) + raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err), + errors.ECODE_INVAL) nics = [{}] * nic_max for nidx, ndict in optvalue: @@ -2250,7 +2407,7 @@ def ParseNicOption(optvalue): if not isinstance(ndict, dict): raise errors.OpPrereqError("Invalid nic/%d value: expected dict," - " got %s" % (nidx, ndict)) + " got %s" % (nidx, ndict), errors.ECODE_INVAL) utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES) @@ -2294,15 +2451,16 @@ def GenericInstanceCreate(mode, opts, args): if opts.disk_template == constants.DT_DISKLESS: if opts.disks or opts.sd_size is not None: raise errors.OpPrereqError("Diskless instance but disk" - " information passed") + " information passed", errors.ECODE_INVAL) disks = [] else: if (not opts.disks and not opts.sd_size and mode == constants.INSTANCE_CREATE): - raise errors.OpPrereqError("No disk information specified") + raise errors.OpPrereqError("No disk information specified", + errors.ECODE_INVAL) if opts.disks and opts.sd_size is not None: raise errors.OpPrereqError("Please use either the '--disk' or" - " '-s' option") + " '-s' option", errors.ECODE_INVAL) if opts.sd_size is not None: opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})] @@ -2310,7 +2468,8 @@ def GenericInstanceCreate(mode, opts, args): try: disk_max = max(int(didx[0]) + 1 for didx in opts.disks) except ValueError, err: - raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err)) + raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err), + errors.ECODE_INVAL) disks = [{}] * disk_max else: disks = [] @@ -2318,25 +2477,25 @@ def GenericInstanceCreate(mode, opts, args): didx = int(didx) if not isinstance(ddict, dict): msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict) - raise errors.OpPrereqError(msg) + raise errors.OpPrereqError(msg, errors.ECODE_INVAL) elif constants.IDISK_SIZE in ddict: if constants.IDISK_ADOPT in ddict: raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed" - " (disk %d)" % didx) + " (disk %d)" % didx, errors.ECODE_INVAL) try: ddict[constants.IDISK_SIZE] = \ utils.ParseUnit(ddict[constants.IDISK_SIZE]) except ValueError, err: raise errors.OpPrereqError("Invalid disk size for disk %d: %s" % - (didx, err)) + (didx, err), errors.ECODE_INVAL) elif constants.IDISK_ADOPT in ddict: if mode == constants.INSTANCE_IMPORT: raise errors.OpPrereqError("Disk adoption not allowed for instance" - " import") + " import", errors.ECODE_INVAL) ddict[constants.IDISK_SIZE] = 0 else: raise errors.OpPrereqError("Missing size or adoption source for" - " disk %d" % didx) + " disk %d" % didx, errors.ECODE_INVAL) disks[didx] = ddict if opts.tags is not None: @@ -2370,6 +2529,7 @@ def GenericInstanceCreate(mode, opts, args): disks=disks, disk_template=opts.disk_template, nics=nics, + conflicts_check=opts.conflicts_check, pnode=pnode, snode=snode, ip_check=opts.ip_check, name_check=opts.name_check, @@ -2438,7 +2598,8 @@ class _RunWhileClusterStoppedHelper: # No need to use SSH result = utils.RunCmd(cmd) else: - result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd)) + result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER, + utils.ShellQuoteArgs(cmd)) if result.failed: errmsg = ["Failed to run command %s" % result.cmd] @@ -2457,7 +2618,7 @@ class _RunWhileClusterStoppedHelper: """ # Pause watcher by acquiring an exclusive lock on watcher state file self.feedback_fn("Blocking watcher") - watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE) + watcher_block = utils.FileLock.Open(pathutils.WATCHER_LOCK_FILE) try: # TODO: Currently, this just blocks. There's no timeout. # TODO: Should it be a shared lock? @@ -2466,12 +2627,12 @@ class _RunWhileClusterStoppedHelper: # Stop master daemons, so that no new jobs can come in and all running # ones are finished self.feedback_fn("Stopping master daemons") - self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"]) + self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"]) try: # Stop daemons on all nodes for node_name in self.online_nodes: self.feedback_fn("Stopping daemons on %s" % node_name) - self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"]) + self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"]) # All daemons are shut down now try: @@ -2485,7 +2646,7 @@ class _RunWhileClusterStoppedHelper: # Start cluster again, master node last for node_name in self.nonmaster_nodes + [self.master_node]: self.feedback_fn("Starting daemons on %s" % node_name) - self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"]) + self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "start-all"]) finally: # Resume watcher watcher_block.Close() @@ -2827,7 +2988,7 @@ def _WarnUnknownFields(fdefs): def GenericList(resource, fields, names, unit, separator, header, cl=None, format_override=None, verbose=False, force_filter=False, - namefield=None, qfilter=None): + namefield=None, qfilter=None, isnumeric=False): """Generic implementation for listing all items of a resource. @param resource: One of L{constants.QR_VIA_LUXI} @@ -2855,12 +3016,17 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None, L{qlang.MakeFilter} for details) @type qfilter: list or None @param qfilter: Query filter (in addition to names) + @param isnumeric: bool + @param isnumeric: Whether the namefield's type is numeric, and therefore + any simple filters built by namefield should use integer values to + reflect that """ if not names: names = None - namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield) + namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield, + isnumeric=isnumeric) if qfilter is None: qfilter = namefilter @@ -3045,7 +3211,8 @@ def ParseTimespec(value): """ value = str(value) if not value: - raise errors.OpPrereqError("Empty time specification passed") + raise errors.OpPrereqError("Empty time specification passed", + errors.ECODE_INVAL) suffix_map = { "s": 1, "m": 60, @@ -3057,17 +3224,19 @@ def ParseTimespec(value): try: value = int(value) except (TypeError, ValueError): - raise errors.OpPrereqError("Invalid time specification '%s'" % value) + raise errors.OpPrereqError("Invalid time specification '%s'" % value, + errors.ECODE_INVAL) else: multiplier = suffix_map[value[-1]] value = value[:-1] if not value: # no data left after stripping the suffix raise errors.OpPrereqError("Invalid time specification (only" - " suffix passed)") + " suffix passed)", errors.ECODE_INVAL) try: value = int(value) * multiplier except (TypeError, ValueError): - raise errors.OpPrereqError("Invalid time specification '%s'" % value) + raise errors.OpPrereqError("Invalid time specification '%s'" % value, + errors.ECODE_INVAL) return value @@ -3416,6 +3585,19 @@ def ConfirmOperation(names, list_type, text, extra=""): return choice +def _MaybeParseUnit(elements): + """Parses and returns an array of potential values with units. + + """ + parsed = {} + for k, v in elements.items(): + if v == constants.VALUE_DEFAULT: + parsed[k] = v + else: + parsed[k] = utils.ParseUnit(v) + return parsed + + def CreateIPolicyFromOpts(ispecs_mem_size=None, ispecs_cpu_count=None, ispecs_disk_count=None, @@ -3423,6 +3605,7 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None, ispecs_nic_count=None, ipolicy_disk_templates=None, ipolicy_vcpu_ratio=None, + ipolicy_spindle_ratio=None, group_ipolicy=False, allowed_values=None, fill_all=False): @@ -3435,11 +3618,9 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None, """ try: if ispecs_mem_size: - for k in ispecs_mem_size: - ispecs_mem_size[k] = utils.ParseUnit(ispecs_mem_size[k]) + ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size) if ispecs_disk_size: - for k in ispecs_disk_size: - ispecs_disk_size[k] = utils.ParseUnit(ispecs_disk_size[k]) + ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size) except (TypeError, ValueError, errors.UnitParseError), err: raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size" " in policy: %s" % @@ -3478,10 +3659,15 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None, if ipolicy_vcpu_ratio is None: ipolicy_vcpu_ratio = \ constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO] + if ipolicy_spindle_ratio is None: + ipolicy_spindle_ratio = \ + constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO] if ipolicy_disk_templates is not None: ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates) if ipolicy_vcpu_ratio is not None: ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio + if ipolicy_spindle_ratio is not None: + ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)