Add cleanup parameter to instance failover
[ganeti-local] / lib / cli.py
index 1c4873e..aec722c 100644 (file)
@@ -81,7 +81,7 @@ __all__ = [
   "DST_NODE_OPT",
   "EARLY_RELEASE_OPT",
   "ENABLED_HV_OPT",
-  "ENABLED_STORAGE_TYPES_OPT",
+  "ENABLED_DISK_TEMPLATES_OPT",
   "ERROR_CODES_OPT",
   "FAILURE_ONLY_OPT",
   "FIELDS_OPT",
@@ -108,6 +108,7 @@ __all__ = [
   "IGNORE_REMOVE_FAILURES_OPT",
   "IGNORE_SECONDARIES_OPT",
   "IGNORE_SIZE_OPT",
+  "INCLUDEDEFAULTS_OPT",
   "INTERVAL_OPT",
   "MAC_PREFIX_OPT",
   "MAINTAIN_NODE_HEALTH_OPT",
@@ -115,6 +116,7 @@ __all__ = [
   "MASTER_NETMASK_OPT",
   "MC_OPT",
   "MIGRATION_MODE_OPT",
+  "MODIFY_ETCHOSTS_OPT",
   "NET_OPT",
   "NETWORK_OPT",
   "NETWORK6_OPT",
@@ -188,6 +190,8 @@ __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",
   "SPICE_CACERT_OPT",
@@ -236,6 +240,7 @@ __all__ = [
   "FormatQueryResult",
   "FormatParamsDictInfo",
   "FormatPolicyInfo",
+  "PrintIPolicyCommand",
   "PrintGenericInfo",
   "GenerateTable",
   "AskUser",
@@ -563,12 +568,13 @@ def check_unit(option, opt, value): # pylint: disable=W0613
     raise OptionValueError("option %s: %s" % (opt, err))
 
 
-def _SplitKeyVal(opt, data):
+def _SplitKeyVal(opt, data, parse_prefixes):
   """Convert a KeyVal string into a dict.
 
   This function will convert a key=val[,...] string into a dict. Empty
   values will be converted specially: keys which have the prefix 'no_'
-  will have the value=False and the prefix stripped, the others will
+  will have the value=False and the prefix stripped, keys with the prefix
+  "-" will have value=None and the prefix stripped, and the others will
   have value=True.
 
   @type opt: string
@@ -576,6 +582,8 @@ def _SplitKeyVal(opt, data):
       data, used in building error messages
   @type data: string
   @param data: a string of the format key=val,key=val,...
+  @type parse_prefixes: bool
+  @param parse_prefixes: whether to handle prefixes specially
   @rtype: dict
   @return: {key=val, key=val}
   @raises errors.ParameterError: if there are duplicate keys
@@ -586,13 +594,16 @@ def _SplitKeyVal(opt, data):
     for elem in utils.UnescapeAndSplit(data, sep=","):
       if "=" in elem:
         key, val = elem.split("=", 1)
-      else:
+      elif parse_prefixes:
         if elem.startswith(NO_PREFIX):
           key, val = elem[len(NO_PREFIX):], False
         elif elem.startswith(UN_PREFIX):
           key, val = elem[len(UN_PREFIX):], None
         else:
           key, val = elem, True
+      else:
+        raise errors.ParameterError("Missing value for key '%s' in option %s" %
+                                    (elem, opt))
       if key in kv_dict:
         raise errors.ParameterError("Duplicate key '%s' in option %s" %
                                     (key, opt))
@@ -600,11 +611,19 @@ def _SplitKeyVal(opt, data):
   return kv_dict
 
 
-def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
-  """Custom parser for ident:key=val,key=val options.
+def _SplitIdentKeyVal(opt, value, parse_prefixes):
+  """Helper function to parse "ident:key=val,key=val" options.
 
-  This will store the parsed values as a tuple (ident, {key: val}). As such,
-  multiple uses of this option via action=append is possible.
+  @type opt: string
+  @param opt: option name, used in error messages
+  @type value: string
+  @param value: expected to be in the format "ident:key=val,key=val,..."
+  @type parse_prefixes: bool
+  @param parse_prefixes: whether to handle prefixes specially (see
+      L{_SplitKeyVal})
+  @rtype: tuple
+  @return: (ident, {key=val, key=val})
+  @raises errors.ParameterError: in case of duplicates or other parsing errors
 
   """
   if ":" not in value:
@@ -612,31 +631,67 @@ def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
   else:
     ident, rest = value.split(":", 1)
 
-  if ident.startswith(NO_PREFIX):
+  if parse_prefixes and ident.startswith(NO_PREFIX):
     if rest:
       msg = "Cannot pass options when removing parameter groups: %s" % value
       raise errors.ParameterError(msg)
     retval = (ident[len(NO_PREFIX):], False)
-  elif (ident.startswith(UN_PREFIX) and
-        (len(ident) <= len(UN_PREFIX) or
-         not ident[len(UN_PREFIX)][0].isdigit())):
+  elif (parse_prefixes and ident.startswith(UN_PREFIX) and
+        (len(ident) <= len(UN_PREFIX) or not ident[len(UN_PREFIX)].isdigit())):
     if rest:
       msg = "Cannot pass options when removing parameter groups: %s" % value
       raise errors.ParameterError(msg)
     retval = (ident[len(UN_PREFIX):], None)
   else:
-    kv_dict = _SplitKeyVal(opt, rest)
+    kv_dict = _SplitKeyVal(opt, rest, parse_prefixes)
     retval = (ident, kv_dict)
   return retval
 
 
+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,
+  multiple uses of this option via action=append is possible.
+
+  """
+  return _SplitIdentKeyVal(opt, value, True)
+
+
 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}.
 
   """
-  return _SplitKeyVal(opt, value)
+  return _SplitKeyVal(opt, value, True)
+
+
+def _SplitListKeyVal(opt, value):
+  retval = {}
+  for elem in value.split("/"):
+    if not elem:
+      raise errors.ParameterError("Empty section in option '%s'" % opt)
+    (ident, valdict) = _SplitIdentKeyVal(opt, elem, False)
+    if ident in retval:
+      msg = ("Duplicated parameter '%s' in parsing %s: %s" %
+             (ident, opt, elem))
+      raise errors.ParameterError(msg)
+    retval[ident] = valdict
+  return retval
+
+
+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}}, {ident:..}]
+
+  """
+  retval = []
+  for line in value.split("//"):
+    retval.append(_SplitListKeyVal(opt, line))
+  return retval
 
 
 def check_bool(option, opt, value): # pylint: disable=W0613
@@ -711,6 +766,7 @@ class CliOption(Option):
     "completion_suggest",
     ]
   TYPES = Option.TYPES + (
+    "multilistidentkeyval",
     "identkeyval",
     "keyval",
     "unit",
@@ -719,6 +775,7 @@ class CliOption(Option):
     "maybefloat",
     )
   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
+  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
@@ -908,6 +965,18 @@ SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
                                  help="NIC count specs: list of key=value,"
                                  " where key is one of min, max, std")
 
+IPOLICY_BOUNDS_SPECS_STR = "--ipolicy-bounds-specs"
+IPOLICY_BOUNDS_SPECS_OPT = cli_option(IPOLICY_BOUNDS_SPECS_STR,
+                                      dest="ipolicy_bounds_specs",
+                                      type="multilistidentkeyval", default=None,
+                                      help="Complete instance specs limits")
+
+IPOLICY_STD_SPECS_STR = "--ipolicy-std-specs"
+IPOLICY_STD_SPECS_OPT = cli_option(IPOLICY_STD_SPECS_STR,
+                                   dest="ipolicy_std_specs",
+                                   type="keyval", default=None,
+                                   help="Complte standard instance specs")
+
 IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
                                     dest="ipolicy_disk_templates",
                                     type="list", default=None,
@@ -1019,12 +1088,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,
@@ -1165,11 +1234,11 @@ ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
                             help="Comma-separated list of hypervisors",
                             type="string", default=None)
 
-ENABLED_STORAGE_TYPES_OPT = cli_option("--enabled-storage-types",
-                                       dest="enabled_storage_types",
-                                       help="Comma-separated list of "
-                                            "storage methods",
-                                       type="string", default=None)
+ENABLED_DISK_TEMPLATES_OPT = cli_option("--enabled-disk-templates",
+                                        dest="enabled_disk_templates",
+                                        help="Comma-separated list of "
+                                             "disk templates",
+                                        type="string", default=None)
 
 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
                             type="keyval", default={},
@@ -1238,6 +1307,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)
@@ -1406,8 +1481,7 @@ FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
                                     " only (determined by the exit code)"))
 
 REASON_OPT = cli_option("--reason", default=None,
-                        help="The reason for executing a VM-state-changing"
-                             " operation")
+                        help="The reason for executing the command")
 
 
 def _PriorityOptionCb(option, _, value, parser):
@@ -1560,8 +1634,12 @@ NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
                                   action="store_false",
                                   help="Don't check for conflicting IPs")
 
+INCLUDEDEFAULTS_OPT = cli_option("--include-defaults", dest="include_defaults",
+                                 default=False, action="store_true",
+                                 help="Include default values")
+
 #: Options provided by all commands
-COMMON_OPTS = [DEBUG_OPT]
+COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
 
 # common options for creating instances. add and import then add their own
 # specific ones.
@@ -1590,14 +1668,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_DISK_TEMPLATES,
-  IPOLICY_VCPU_RATIO,
-  IPOLICY_SPINDLE_RATIO,
   ]
 
 
@@ -2200,6 +2283,31 @@ def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
 
 
+def _InitReasonTrail(op, opts):
+  """Builds the first part of the reason trail
+
+  Builds the initial part of the reason trail, adding the user provided reason
+  (if it exists) and the name of the command starting the operation.
+
+  @param op: the opcode the reason trail will be added to
+  @param opts: the command line options selected by the user
+
+  """
+  assert len(sys.argv) >= 2
+  trail = []
+
+  if opts.reason:
+    trail.append((constants.OPCODE_REASON_SRC_USER,
+                  opts.reason,
+                  utils.EpochNano()))
+
+  binary = os.path.basename(sys.argv[0])
+  source = "%s:%s" % (constants.OPCODE_REASON_SRC_CLIENT, binary)
+  command = sys.argv[1]
+  trail.append((source, command, utils.EpochNano()))
+  op.reason = trail
+
+
 def SetGenericOpcodeOpts(opcode_list, options):
   """Processor for generic options.
 
@@ -2219,6 +2327,7 @@ def SetGenericOpcodeOpts(opcode_list, options):
       op.dry_run = options.dry_run
     if getattr(options, "priority", None) is not None:
       op.priority = options.priority
+    _InitReasonTrail(op, options)
 
 
 def GetClient(query=False):
@@ -3637,13 +3746,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(
@@ -3652,7 +3772,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]))
     )
@@ -3663,6 +3783,46 @@ def FormatPolicyInfo(custom_ipolicy, eff_ipolicy, iscluster):
   return ret
 
 
+def _PrintSpecsParameters(buf, specs):
+  values = ("%s=%s" % (par, val) for (par, val) in sorted(specs.items()))
+  buf.write(",".join(values))
+
+
+def PrintIPolicyCommand(buf, ipolicy, isgroup):
+  """Print the command option used to generate the given instance policy.
+
+  Currently only the parts dealing with specs are supported.
+
+  @type buf: StringIO
+  @param buf: stream to write into
+  @type ipolicy: dict
+  @param ipolicy: instance policy
+  @type isgroup: bool
+  @param isgroup: whether the policy is at group level
+
+  """
+  if not isgroup:
+    stdspecs = ipolicy.get("std")
+    if stdspecs:
+      buf.write(" %s " % IPOLICY_STD_SPECS_STR)
+      _PrintSpecsParameters(buf, stdspecs)
+  minmaxes = ipolicy.get("minmax", [])
+  first = True
+  for minmax in minmaxes:
+    minspecs = minmax.get("min")
+    maxspecs = minmax.get("max")
+    if minspecs and maxspecs:
+      if first:
+        buf.write(" %s " % IPOLICY_BOUNDS_SPECS_STR)
+        first = False
+      else:
+        buf.write("//")
+      buf.write("min:")
+      _PrintSpecsParameters(buf, minspecs)
+      buf.write("/max:")
+      _PrintSpecsParameters(buf, maxspecs)
+
+
 def ConfirmOperation(names, list_type, text, extra=""):
   """Ask the user to confirm an operation on a list of list_type.
 
@@ -3715,9 +3875,9 @@ def _MaybeParseUnit(elements):
   return parsed
 
 
-def _InitIspecsFromOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count,
-                        ispecs_disk_count, ispecs_disk_size, ispecs_nic_count,
-                        group_ipolicy, allowed_values):
+def _InitISpecsFromSplitOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count,
+                             ispecs_disk_count, ispecs_disk_size,
+                             ispecs_nic_count, group_ipolicy, fill_all):
   try:
     if ispecs_mem_size:
       ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
@@ -3744,7 +3904,8 @@ def _InitIspecsFromOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count,
   else:
     forced_type = TISPECS_CLUSTER_TYPES
   for specs in ispecs_transposed.values():
-    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
+    assert type(specs) is dict
+    utils.ForceDictType(specs, forced_type)
 
   # then transpose
   ispecs = {
@@ -3757,9 +3918,76 @@ def _InitIspecsFromOpts(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:
+      try:
+        ret[k] = utils.ParseUnit(ret[k])
+      except (TypeError, ValueError, errors.UnitParseError), err:
+        raise errors.OpPrereqError(("Invalid parameter %s (%s) in %s instance"
+                                    " specs: %s" % (k, ret[k], keyname, err)),
+                                   errors.ECODE_INVAL)
+  return ret
+
+
+def _ParseISpec(spec, keyname, required):
+  ret = _ParseSpecUnit(spec, keyname)
+  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):
+  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", False)
 
 
 def CreateIPolicyFromOpts(ispecs_mem_size=None,
@@ -3767,6 +3995,8 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None,
                           ispecs_disk_count=None,
                           ispecs_disk_size=None,
                           ispecs_nic_count=None,
+                          minmax_ispecs=None,
+                          std_ispecs=None,
                           ipolicy_disk_templates=None,
                           ipolicy_vcpu_ratio=None,
                           ipolicy_spindle_ratio=None,
@@ -3778,16 +4008,31 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None,
   @param fill_all: whether for cluster policies we should ensure that
     all values are filled
 
-
   """
+  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()
-  _InitIspecsFromOpts(ipolicy_out, ispecs_mem_size, ispecs_cpu_count,
-                      ispecs_disk_count, ispecs_disk_size, ispecs_nic_count,
-                      group_ipolicy, allowed_values)
+  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, 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)
 
   if ipolicy_disk_templates is not None:
-    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
+    if allowed_values and ipolicy_disk_templates in allowed_values:
+      ipolicy_out[constants.IPOLICY_DTS] = ipolicy_disk_templates
+    else:
+      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: