X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/ea9d3b408f8675083a52ffda9fa6f1e15754cf44..d0d1f2c459be0e90183f474b8fff708fd4cf3a99:/lib/cli.py diff --git a/lib/cli.py b/lib/cli.py index 2113308..8e8402c 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -95,6 +95,7 @@ __all__ = [ "GLOBAL_FILEDIR_OPT", "HID_OS_OPT", "GLOBAL_SHARED_FILEDIR_OPT", + "HOTPLUG_OPT", "HVLIST_OPT", "HVOPTS_OPT", "HYPERVISOR_OPT", @@ -116,6 +117,7 @@ __all__ = [ "MASTER_NETMASK_OPT", "MC_OPT", "MIGRATION_MODE_OPT", + "MODIFY_ETCHOSTS_OPT", "NET_OPT", "NETWORK_OPT", "NETWORK6_OPT", @@ -189,6 +191,7 @@ __all__ = [ "SPECS_DISK_SIZE_OPT", "SPECS_MEM_SIZE_OPT", "SPECS_NIC_COUNT_OPT", + "SPLIT_ISPECS_OPTS", "IPOLICY_STD_SPECS_OPT", "IPOLICY_DISK_TEMPLATES", "IPOLICY_VCPU_RATIO", @@ -282,6 +285,7 @@ __all__ = [ "OPT_COMPL_ONE_OS", "OPT_COMPL_ONE_EXTSTORAGE", "cli_option", + "FixHvParams", "SplitNodeOption", "CalculateOSNames", "ParseFields", @@ -679,14 +683,17 @@ def _SplitListKeyVal(opt, value): return retval -def check_list_ident_key_val(_, opt, value): - """Custom parser for "ident:key=val,key=val/ident:key=val" options. +def check_multilist_ident_key_val(_, opt, value): + """Custom parser for "ident:key=val,key=val/ident:key=val//ident:.." options. @rtype: list of dictionary - @return: {ident: {key: val, key: val}, ident: {key: val}} + @return: [{ident: {key: val, key: val}, ident: {key: val}}, {ident:..}] """ - return _SplitListKeyVal(opt, value) + retval = [] + for line in value.split("//"): + retval.append(_SplitListKeyVal(opt, line)) + return retval def check_bool(option, opt, value): # pylint: disable=W0613 @@ -761,7 +768,7 @@ class CliOption(Option): "completion_suggest", ] TYPES = Option.TYPES + ( - "listidentkeyval", + "multilistidentkeyval", "identkeyval", "keyval", "unit", @@ -770,7 +777,7 @@ class CliOption(Option): "maybefloat", ) TYPE_CHECKER = Option.TYPE_CHECKER.copy() - TYPE_CHECKER["listidentkeyval"] = check_list_ident_key_val + TYPE_CHECKER["multilistidentkeyval"] = check_multilist_ident_key_val TYPE_CHECKER["identkeyval"] = check_ident_key_val TYPE_CHECKER["keyval"] = check_key_val TYPE_CHECKER["unit"] = check_unit @@ -881,7 +888,7 @@ FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir", FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver", help="Driver to use for image files", - default="loop", metavar="", + default=None, metavar="", choices=list(constants.FILE_DRIVER)) IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="", @@ -963,7 +970,7 @@ SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count", IPOLICY_BOUNDS_SPECS_STR = "--ipolicy-bounds-specs" IPOLICY_BOUNDS_SPECS_OPT = cli_option(IPOLICY_BOUNDS_SPECS_STR, dest="ipolicy_bounds_specs", - type="listidentkeyval", default=None, + type="multilistidentkeyval", default=None, help="Complete instance specs limits") IPOLICY_STD_SPECS_STR = "--ipolicy-std-specs" @@ -1083,12 +1090,12 @@ SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command", CLEANUP_OPT = cli_option("--cleanup", dest="cleanup", default=False, action="store_true", - help="Instead of performing the migration, try to" - " recover from a failed cleanup. This is safe" + help="Instead of performing the migration/failover," + " try to recover from a failed cleanup. This is safe" " to run even if the instance is healthy, but it" " will create extra replication traffic and " " disrupt briefly the replication (like during the" - " migration") + " migration/failover") STATIC_OPT = cli_option("-s", "--static", dest="static", action="store_true", default=False, @@ -1302,6 +1309,12 @@ NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts", help="Don't modify %s" % pathutils.ETC_HOSTS, action="store_false", default=True) +MODIFY_ETCHOSTS_OPT = \ + cli_option("--modify-etc-hosts", dest="modify_etc_hosts", metavar=_YORNO, + default=None, type="bool", + help="Defines whether the cluster should autonomously modify" + " and keep in sync the /etc/hosts file of the nodes") + NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup", help="Don't initialize SSH keys", action="store_false", default=True) @@ -1627,6 +1640,10 @@ INCLUDEDEFAULTS_OPT = cli_option("--include-defaults", dest="include_defaults", default=False, action="store_true", help="Include default values") +HOTPLUG_OPT = cli_option("--hotplug", dest="hotplug", + action="store_true", default=False, + help="Hotplug supported devices (NICs and Disks)") + #: Options provided by all commands COMMON_OPTS = [DEBUG_OPT, REASON_OPT] @@ -1657,15 +1674,19 @@ COMMON_CREATE_OPTS = [ # common instance policy options INSTANCE_POLICY_OPTS = [ + IPOLICY_BOUNDS_SPECS_OPT, + IPOLICY_DISK_TEMPLATES, + IPOLICY_VCPU_RATIO, + IPOLICY_SPINDLE_RATIO, + ] + +# instance policy split specs options +SPLIT_ISPECS_OPTS = [ SPECS_CPU_COUNT_OPT, SPECS_DISK_COUNT_OPT, SPECS_DISK_SIZE_OPT, SPECS_MEM_SIZE_OPT, SPECS_NIC_COUNT_OPT, - IPOLICY_BOUNDS_SPECS_OPT, - IPOLICY_DISK_TEMPLATES, - IPOLICY_VCPU_RATIO, - IPOLICY_SPINDLE_RATIO, ] @@ -2566,6 +2587,21 @@ def ParseNicOption(optvalue): return nics +def FixHvParams(hvparams): + # In Ganeti 2.8.4 the separator for the usb_devices hvparam was changed from + # comma to space because commas cannot be accepted on the command line + # (they already act as the separator between different hvparams). Still, + # RAPI should be able to accept commas for backwards compatibility. + # Therefore, we convert spaces into commas here, and we keep the old + # parsing logic everywhere else. + try: + new_usb_devices = hvparams[constants.HV_USB_DEVICES].replace(" ", ",") + hvparams[constants.HV_USB_DEVICES] = new_usb_devices + except KeyError: + #No usb_devices, no modification required + pass + + def GenericInstanceCreate(mode, opts, args): """Add an instance to the cluster via either creation or import. @@ -2655,6 +2691,7 @@ def GenericInstanceCreate(mode, opts, args): utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT) utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES) + FixHvParams(hvparams) if mode == constants.INSTANCE_CREATE: start = opts.start @@ -3731,13 +3768,24 @@ def FormatPolicyInfo(custom_ipolicy, eff_ipolicy, iscluster): if iscluster: eff_ipolicy = custom_ipolicy + minmax_out = [] custom_minmax = custom_ipolicy.get(constants.ISPECS_MINMAX) - ret = [ - (key, - FormatParamsDictInfo(custom_minmax.get(key, {}), - eff_ipolicy[constants.ISPECS_MINMAX][key])) - for key in constants.ISPECS_MINMAX_KEYS - ] + if custom_minmax: + for (k, minmax) in enumerate(custom_minmax): + minmax_out.append([ + ("%s/%s" % (key, k), + FormatParamsDictInfo(minmax[key], minmax[key])) + for key in constants.ISPECS_MINMAX_KEYS + ]) + else: + for (k, minmax) in enumerate(eff_ipolicy[constants.ISPECS_MINMAX]): + minmax_out.append([ + ("%s/%s" % (key, k), + FormatParamsDictInfo({}, minmax[key])) + for key in constants.ISPECS_MINMAX_KEYS + ]) + ret = [("bounds specs", minmax_out)] + if iscluster: stdspecs = custom_ipolicy[constants.ISPECS_STD] ret.append( @@ -3746,7 +3794,7 @@ def FormatPolicyInfo(custom_ipolicy, eff_ipolicy, iscluster): ) ret.append( - ("enabled disk templates", + ("allowed disk templates", _FormatListInfoDefault(custom_ipolicy.get(constants.IPOLICY_DTS), eff_ipolicy[constants.IPOLICY_DTS])) ) @@ -3780,12 +3828,17 @@ def PrintIPolicyCommand(buf, ipolicy, isgroup): if stdspecs: buf.write(" %s " % IPOLICY_STD_SPECS_STR) _PrintSpecsParameters(buf, stdspecs) - minmax = ipolicy.get("minmax") - if minmax: + minmaxes = ipolicy.get("minmax", []) + first = True + for minmax in minmaxes: minspecs = minmax.get("min") maxspecs = minmax.get("max") if minspecs and maxspecs: - buf.write(" %s " % IPOLICY_BOUNDS_SPECS_STR) + if first: + buf.write(" %s " % IPOLICY_BOUNDS_SPECS_STR) + first = False + else: + buf.write("//") buf.write("min:") _PrintSpecsParameters(buf, minspecs) buf.write("/max:") @@ -3846,7 +3899,7 @@ def _MaybeParseUnit(elements): def _InitISpecsFromSplitOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count, ispecs_disk_count, ispecs_disk_size, - ispecs_nic_count, group_ipolicy, allowed_values): + ispecs_nic_count, group_ipolicy, fill_all): try: if ispecs_mem_size: ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size) @@ -3874,7 +3927,7 @@ def _InitISpecsFromSplitOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count, forced_type = TISPECS_CLUSTER_TYPES for specs in ispecs_transposed.values(): assert type(specs) is dict - utils.ForceDictType(specs, forced_type, allowed_values=allowed_values) + utils.ForceDictType(specs, forced_type) # then transpose ispecs = { @@ -3887,15 +3940,26 @@ def _InitISpecsFromSplitOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count, for key, val in specs.items(): # {min: .. ,max: .., std: ..} assert key in ispecs ispecs[key][name] = val + minmax_out = {} for key in constants.ISPECS_MINMAX_KEYS: - ipolicy[constants.ISPECS_MINMAX][key] = ispecs[key] - ipolicy[constants.ISPECS_STD] = ispecs[constants.ISPECS_STD] + if fill_all: + minmax_out[key] = \ + objects.FillDict(constants.ISPECS_MINMAX_DEFAULTS[key], ispecs[key]) + else: + minmax_out[key] = ispecs[key] + ipolicy[constants.ISPECS_MINMAX] = [minmax_out] + if fill_all: + ipolicy[constants.ISPECS_STD] = \ + objects.FillDict(constants.IPOLICY_DEFAULTS[constants.ISPECS_STD], + ispecs[constants.ISPECS_STD]) + else: + ipolicy[constants.ISPECS_STD] = ispecs[constants.ISPECS_STD] def _ParseSpecUnit(spec, keyname): ret = spec.copy() for k in [constants.ISPEC_DISK_SIZE, constants.ISPEC_MEM_SIZE]: - if k in ret and ret[k] != constants.VALUE_DEFAULT: + if k in ret: try: ret[k] = utils.ParseUnit(ret[k]) except (TypeError, ValueError, errors.UnitParseError), err: @@ -3905,27 +3969,47 @@ def _ParseSpecUnit(spec, keyname): return ret -def _ParseISpec(spec, keyname, allowed_values): +def _ParseISpec(spec, keyname, required): ret = _ParseSpecUnit(spec, keyname) - utils.ForceDictType(ret, constants.ISPECS_PARAMETER_TYPES, - allowed_values=allowed_values) + utils.ForceDictType(ret, constants.ISPECS_PARAMETER_TYPES) + missing = constants.ISPECS_PARAMETERS - frozenset(ret.keys()) + if required and missing: + raise errors.OpPrereqError("Missing parameters in ipolicy spec %s: %s" % + (keyname, utils.CommaJoin(missing)), + errors.ECODE_INVAL) + return ret + + +def _GetISpecsInAllowedValues(minmax_ispecs, allowed_values): + ret = None + if (minmax_ispecs and allowed_values and len(minmax_ispecs) == 1 and + len(minmax_ispecs[0]) == 1): + for (key, spec) in minmax_ispecs[0].items(): + # This loop is executed exactly once + if key in allowed_values and not spec: + ret = key return ret def _InitISpecsFromFullOpts(ipolicy_out, minmax_ispecs, std_ispecs, group_ipolicy, allowed_values): - if minmax_ispecs is not None: - minmax_out = {} - for (key, spec) in minmax_ispecs.items(): - if key not in constants.ISPECS_MINMAX_KEYS: - msg = "Invalid key in bounds instance specifications: %s" % key - raise errors.OpPrereqError(msg, errors.ECODE_INVAL) - minmax_out[key] = _ParseISpec(spec, key, allowed_values) + found_allowed = _GetISpecsInAllowedValues(minmax_ispecs, allowed_values) + if found_allowed is not None: + ipolicy_out[constants.ISPECS_MINMAX] = found_allowed + elif minmax_ispecs is not None: + minmax_out = [] + for mmpair in minmax_ispecs: + mmpair_out = {} + for (key, spec) in mmpair.items(): + if key not in constants.ISPECS_MINMAX_KEYS: + msg = "Invalid key in bounds instance specifications: %s" % key + raise errors.OpPrereqError(msg, errors.ECODE_INVAL) + mmpair_out[key] = _ParseISpec(spec, key, True) + minmax_out.append(mmpair_out) ipolicy_out[constants.ISPECS_MINMAX] = minmax_out if std_ispecs is not None: assert not group_ipolicy # This is not an option for gnt-group - ipolicy_out[constants.ISPECS_STD] = _ParseISpec(std_ispecs, "std", - allowed_values) + ipolicy_out[constants.ISPECS_STD] = _ParseISpec(std_ispecs, "std", False) def CreateIPolicyFromOpts(ispecs_mem_size=None, @@ -3946,21 +4030,23 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None, @param fill_all: whether for cluster policies we should ensure that all values are filled - """ - if ((ispecs_mem_size or ispecs_cpu_count or ispecs_disk_count or - ispecs_disk_size or ispecs_nic_count) and - (minmax_ispecs is not None or std_ispecs is not None)): + assert not (fill_all and allowed_values) + + split_specs = (ispecs_mem_size or ispecs_cpu_count or ispecs_disk_count or + ispecs_disk_size or ispecs_nic_count) + if (split_specs and (minmax_ispecs is not None or std_ispecs is not None)): raise errors.OpPrereqError("A --specs-xxx option cannot be specified" " together with any --ipolicy-xxx-specs option", errors.ECODE_INVAL) ipolicy_out = objects.MakeEmptyIPolicy() - if minmax_ispecs is None and std_ispecs is None: + if split_specs: + assert fill_all _InitISpecsFromSplitOpts(ipolicy_out, ispecs_mem_size, ispecs_cpu_count, ispecs_disk_count, ispecs_disk_size, - ispecs_nic_count, group_ipolicy, allowed_values) - else: + ispecs_nic_count, group_ipolicy, fill_all) + elif (minmax_ispecs is not None or std_ispecs is not None): _InitISpecsFromFullOpts(ipolicy_out, minmax_ispecs, std_ispecs, group_ipolicy, allowed_values)