X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/e687ec0110d984ef53afc2bfc2a69c2708b98ceb..30a83755c34e058b685bcad6364d318479c3f179:/lib/cli.py diff --git a/lib/cli.py b/lib/cli.py index f55804d..30f6771 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -29,6 +29,7 @@ import time import logging import errno import itertools +import shlex from cStringIO import StringIO from ganeti import utils @@ -68,6 +69,7 @@ __all__ = [ "DEBUG_SIMERR_OPT", "DISKIDX_OPT", "DISK_OPT", + "DISK_PARAMS_OPT", "DISK_TEMPLATE_OPT", "DRAINED_OPT", "DRY_RUN_OPT", @@ -92,6 +94,7 @@ __all__ = [ "DEFAULT_IALLOCATOR_OPT", "IDENTIFY_DEFAULTS_OPT", "IGNORE_CONSIST_OPT", + "IGNORE_ERRORS_OPT", "IGNORE_FAILURES_OPT", "IGNORE_OFFLINE_OPT", "IGNORE_REMOVE_FAILURES_OPT", @@ -101,6 +104,7 @@ __all__ = [ "MAC_PREFIX_OPT", "MAINTAIN_NODE_HEALTH_OPT", "MASTER_NETDEV_OPT", + "MASTER_NETMASK_OPT", "MC_OPT", "MIGRATION_MODE_OPT", "NET_OPT", @@ -109,6 +113,7 @@ __all__ = [ "NEW_CONFD_HMAC_KEY_OPT", "NEW_RAPI_CERT_OPT", "NEW_SECONDARY_OPT", + "NEW_SPICE_CERT_OPT", "NIC_PARAMS_OPT", "NODE_FORCE_JOIN_OPT", "NODE_LIST_OPT", @@ -133,6 +138,8 @@ __all__ = [ "NOVOTING_OPT", "NO_REMEMBER_OPT", "NWSYNC_OPT", + "OFFLINE_INST_OPT", + "ONLINE_INST_OPT", "ON_PRIMARY_OPT", "ON_SECONDARY_OPT", "OFFLINE_OPT", @@ -159,6 +166,13 @@ __all__ = [ "SHOWCMD_OPT", "SHUTDOWN_TIMEOUT_OPT", "SINGLE_NODE_OPT", + "SPECS_CPU_COUNT_OPT", + "SPECS_DISK_COUNT_OPT", + "SPECS_DISK_SIZE_OPT", + "SPECS_MEM_SIZE_OPT", + "SPECS_NIC_COUNT_OPT", + "SPICE_CACERT_OPT", + "SPICE_CERT_OPT", "SRC_DIR_OPT", "SRC_NODE_OPT", "SUBMIT_OPT", @@ -171,10 +185,13 @@ __all__ = [ "TO_GROUP_OPT", "UIDPOOL_OPT", "USEUNITS_OPT", + "USE_EXTERNAL_MIP_SCRIPT", "USE_REPL_NET_OPT", "VERBOSE_OPT", "VG_NAME_OPT", "YES_DOIT_OPT", + "DISK_STATE_OPT", + "HV_STATE_OPT", # Generic functions for CLI programs "ConfirmOperation", "GenericMain", @@ -257,9 +274,12 @@ _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES) QR_UNKNOWN, QR_INCOMPLETE) = range(3) +#: Maximum batch size for ChooseJob +_CHOOSE_BATCH = 25 + class _Argument: - def __init__(self, min=0, max=None): # pylint: disable-msg=W0622 + def __init__(self, min=0, max=None): # pylint: disable=W0622 self.min = min self.max = max @@ -274,7 +294,7 @@ class ArgSuggest(_Argument): Value can be any of the ones passed to the constructor. """ - # pylint: disable-msg=W0622 + # pylint: disable=W0622 def __init__(self, min=0, max=None, choices=None): _Argument.__init__(self, min=min, max=max) self.choices = choices @@ -462,7 +482,7 @@ def RemoveTags(opts, args): SubmitOpCode(op, opts=opts) -def check_unit(option, opt, value): # pylint: disable-msg=W0613 +def check_unit(option, opt, value): # pylint: disable=W0613 """OptParsers custom converter for units. """ @@ -509,7 +529,7 @@ def _SplitKeyVal(opt, data): return kv_dict -def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613 +def check_ident_key_val(option, opt, value): # pylint: disable=W0613 """Custom parser for ident:key=val,key=val options. This will store the parsed values as a tuple (ident, {key: val}). As such, @@ -537,7 +557,7 @@ def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613 return retval -def check_key_val(option, opt, value): # pylint: disable-msg=W0613 +def check_key_val(option, opt, value): # pylint: disable=W0613 """Custom parser class for key=val,key=val options. This will store the parsed values as a dict {key: val}. @@ -546,7 +566,7 @@ def check_key_val(option, opt, value): # pylint: disable-msg=W0613 return _SplitKeyVal(opt, value) -def check_bool(option, opt, value): # pylint: disable-msg=W0613 +def check_bool(option, opt, value): # pylint: disable=W0613 """Custom parser for yes/no options. This will store the parsed value as either True or False. @@ -675,6 +695,14 @@ NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync", default=True, action="store_false", help="Don't wait for sync (DANGEROUS!)") +ONLINE_INST_OPT = cli_option("--online", dest="online_inst", + action="store_true", default=False, + help="Enable offline instance") + +OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst", + action="store_true", default=False, + help="Disable down instance") + DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template", help=("Custom disk setup (%s)" % utils.CommaJoin(constants.DISK_TEMPLATES)), @@ -732,6 +760,33 @@ HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval", default={}, dest="hvparams", help="Hypervisor parameters") +DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams", + help="Disk template parameters, in the format" + " template:option=value,option=value,...", + type="identkeyval", action="append", default=[]) + +SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size", + type="keyval", default={}, + help="Memory count specs: min, max, std" + " (in MB)") + +SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count", + type="keyval", default={}, + help="CPU count specs: min, max, std") + +SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count", + dest="ispecs_disk_count", + type="keyval", default={}, + help="Disk count specs: min, max, std") + +SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size", + type="keyval", default={}, + help="Disk size specs: min, max, std (in MB)") + +SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count", + type="keyval", default={}, + help="NIC count specs: min, max, std") + HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor", help="Hypervisor and hypervisor options, in the" " format hypervisor:option=value,option=value,...", @@ -1002,6 +1057,18 @@ MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev", metavar="NETDEV", default=None) +MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask", + help="Specify the netmask of the master IP", + metavar="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) + 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]" % @@ -1083,6 +1150,21 @@ NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert", help=("Generate a new self-signed RAPI" " certificate")) +SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert", + 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") + +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")) + NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key", dest="new_confd_hmac_key", default=False, action="store_true", @@ -1235,6 +1317,23 @@ TO_GROUP_OPT = cli_option("--to", dest="to", metavar="", default=None, action="append", completion_suggest=OPT_COMPL_ONE_NODEGROUP) +IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[], + action="append", dest="ignore_errors", + choices=list(constants.CV_ALL_ECODES_STRINGS), + help="Error code to be ignored") + +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,..."), + 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,..."), + type="identkeyval") + #: Options provided by all commands COMMON_OPTS = [DEBUG_OPT] @@ -1264,7 +1363,7 @@ COMMON_CREATE_OPTS = [ ] -def _ParseArgs(argv, commands, aliases): +def _ParseArgs(argv, commands, aliases, env_override): """Parser for the command line arguments. This function parses the arguments and returns the function which @@ -1274,8 +1373,11 @@ def _ParseArgs(argv, commands, aliases): @param commands: dictionary with special contents, see the design doc for cmdline handling @param aliases: dictionary with command aliases {'alias': 'target, ...} + @param env_override: list of env variables allowed for default args """ + assert not (env_override - set(commands)) + if len(argv) == 0: binary = "" else: @@ -1329,13 +1431,19 @@ def _ParseArgs(argv, commands, aliases): cmd = aliases[cmd] + if cmd in 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)) + func, args_def, parser_opts, usage, description = commands[cmd] parser = OptionParser(option_list=parser_opts + COMMON_OPTS, description=description, formatter=TitledHelpFormatter(), usage="%%prog %s %s" % (cmd, usage)) parser.disable_interspersed_args() - options, args = parser.parse_args() + options, args = parser.parse_args(args=argv[1:]) if not _CheckArguments(cmd, args_def, args): return None, None, None @@ -1969,16 +2077,18 @@ def FormatError(err): return retcode, obuf.getvalue().rstrip("\n") -def GenericMain(commands, override=None, aliases=None): +def GenericMain(commands, override=None, aliases=None, + env_override=frozenset()): """Generic main function for all the gnt-* commands. - Arguments: - - commands: a dictionary with a special structure, see the design doc - for command line handling. - - override: if not None, we expect a dictionary with keys that will - override command line options; this can be used to pass - options from the scripts to generic functions - - aliases: dictionary with command aliases {'alias': 'target, ...} + @param commands: a dictionary with a special structure, see the design doc + for command line handling. + @param override: if not None, we expect a dictionary with keys that will + override command line options; this can be used to pass + options from the scripts to generic functions + @param aliases: dictionary with command aliases {'alias': 'target, ...} + @param env_override: list of environment names which are allowed to submit + default args for commands """ # save the program name and the entire command line for later logging @@ -1997,7 +2107,7 @@ def GenericMain(commands, override=None, aliases=None): aliases = {} try: - func, options, args = _ParseArgs(sys.argv, commands, aliases) + func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override) except errors.ParameterError, err: result, err_msg = FormatError(err) ToStderr(err_msg) @@ -2149,7 +2259,7 @@ def GenericInstanceCreate(mode, opts, args): else: tags = [] - utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES) + utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT) utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES) if mode == constants.INSTANCE_CREATE: @@ -2363,8 +2473,8 @@ def GenerateTable(headers, fields, separator, data, if unitfields is None: unitfields = [] - numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142 - unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142 + numfields = utils.FieldSet(*numfields) # pylint: disable=W0142 + unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142 format_fields = [] for field in fields: @@ -2655,15 +2765,15 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None, @param verbose: whether to use verbose field descriptions or not """ - if cl is None: - cl = GetClient() - if not names: names = None - filter_ = qlang.MakeFilter(names, force_filter) + qfilter = qlang.MakeFilter(names, force_filter) + + if cl is None: + cl = GetClient() - response = cl.Query(resource, fields, filter_) + response = cl.Query(resource, fields, qfilter) found_unknown = _WarnUnknownFields(response.fields) @@ -2892,24 +3002,24 @@ def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False, if cl is None: cl = GetClient() - filter_ = [] + qfilter = [] if nodes: - filter_.append(qlang.MakeSimpleFilter("name", nodes)) + qfilter.append(qlang.MakeSimpleFilter("name", nodes)) if nodegroup is not None: - filter_.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup], + qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup], [qlang.OP_EQUAL, "group.uuid", nodegroup]]) if filter_master: - filter_.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]]) + qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]]) - if filter_: - if len(filter_) > 1: - final_filter = [qlang.OP_AND] + filter_ + if qfilter: + if len(qfilter) > 1: + final_filter = [qlang.OP_AND] + qfilter else: - assert len(filter_) == 1 - final_filter = filter_[0] + assert len(qfilter) == 1 + final_filter = qfilter[0] else: final_filter = None @@ -3043,7 +3153,7 @@ class JobExecutor(object): for (_, _, ops) in self.queue: # SubmitJob will remove the success status, but raise an exception if # the submission fails, so we'll notice that anyway. - results.append([True, self.cl.SubmitJob(ops)]) + results.append([True, self.cl.SubmitJob(ops)[0]]) else: results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue]) for ((status, data), (idx, name, _)) in zip(results, self.queue): @@ -3055,7 +3165,8 @@ class JobExecutor(object): """ assert self.jobs, "_ChooseJob called with empty job list" - result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"]) + result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]], + ["status"]) assert result for job_data, status in zip(self.jobs, result):