Merge branch 'stable-2.6' into devel-2.6
[ganeti-local] / lib / cli.py
index f18e65b..a7a4742 100644 (file)
@@ -43,6 +43,7 @@ from ganeti import ssh
 from ganeti import compat
 from ganeti import netutils
 from ganeti import qlang
+from ganeti import objects
 
 from optparse import (OptionParser, TitledHelpFormatter,
                       Option, OptionValueError)
@@ -50,6 +51,7 @@ from optparse import (OptionParser, TitledHelpFormatter,
 
 __all__ = [
   # Command line options
+  "ABSOLUTE_OPT",
   "ADD_UIDS_OPT",
   "ALLOCATABLE_OPT",
   "ALLOC_POLICY_OPT",
@@ -200,6 +202,7 @@ __all__ = [
   "INSTANCE_POLICY_OPTS",
   # Generic functions for CLI programs
   "ConfirmOperation",
+  "CreateIPolicyFromOpts",
   "GenericMain",
   "GenericInstanceCreate",
   "GenericList",
@@ -284,6 +287,19 @@ _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
 _CHOOSE_BATCH = 25
 
 
+# constants used to create InstancePolicy dictionary
+TISPECS_GROUP_TYPES = {
+  constants.ISPECS_MIN: constants.VTYPE_INT,
+  constants.ISPECS_MAX: constants.VTYPE_INT,
+  }
+
+TISPECS_CLUSTER_TYPES = {
+  constants.ISPECS_MIN: constants.VTYPE_INT,
+  constants.ISPECS_MAX: constants.VTYPE_INT,
+  constants.ISPECS_STD: constants.VTYPE_INT,
+  }
+
+
 class _Argument:
   def __init__(self, min=0, max=None): # pylint: disable=W0622
     self.min = min
@@ -601,6 +617,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,
@@ -635,6 +663,7 @@ class CliOption(Option):
     "unit",
     "bool",
     "list",
+    "maybefloat",
     )
   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
   TYPE_CHECKER["identkeyval"] = check_ident_key_val
@@ -642,6 +671,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
@@ -701,7 +731,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,
@@ -794,25 +824,31 @@ DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
 
 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)")
+                                 help="Memory size specs: list of key=value,"
+                                " where key is one of min, max, std"
+                                 " (in MB or using a unit)")
 
 SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
                                  type="keyval", default={},
-                                 help="CPU count specs: min, max, std")
+                                 help="CPU count specs: list of key=value,"
+                                 " where key is one of 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")
+                                  help="Disk count specs: list of key=value,"
+                                  " where key is one of 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)")
+                                 help="Disk size specs: list of key=value,"
+                                " 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",
                                  type="keyval", default={},
-                                 help="NIC count specs: min, max, std")
+                                 help="NIC count specs: list of key=value,"
+                                 " where key is one of min, max, std")
 
 IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
                                  dest="ipolicy_disk_templates",
@@ -822,9 +858,15 @@ IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-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,...",
@@ -1362,14 +1404,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",
@@ -1381,6 +1426,11 @@ RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
                              " ballooning it up or down to the new value",
                              default=None, type="unit", metavar="<size>")
 
+ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
+                          action="store_true", default=False,
+                          help="Marks the grow as absolute instead of the"
+                          " (default) relative mode")
+
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT]
 
@@ -1417,6 +1467,7 @@ INSTANCE_POLICY_OPTS = [
   SPECS_NIC_COUNT_OPT,
   IPOLICY_DISK_TEMPLATES,
   IPOLICY_VCPU_RATIO,
+  IPOLICY_SPINDLE_RATIO,
   ]
 
 
@@ -3336,9 +3387,18 @@ def FormatParameterDict(buf, param_dict, actual, level=1):
 
   """
   indent = "  " * level
+
   for key in sorted(actual):
-    val = param_dict.get(key, "default (%s)" % actual[key])
-    buf.write("%s- %s: %s\n" % (indent, key, val))
+    data = actual[key]
+    buf.write("%s- %s:" % (indent, key))
+
+    if isinstance(data, dict) and data:
+      buf.write("\n")
+      FormatParameterDict(buf, param_dict.get(key, {}), data,
+                          level=level + 1)
+    else:
+      val = param_dict.get(key, "default (%s)" % data)
+      buf.write(" %s\n" % val)
 
 
 def ConfirmOperation(names, list_type, text, extra=""):
@@ -3378,3 +3438,92 @@ def ConfirmOperation(names, list_type, text, extra=""):
     choices.pop(1)
     choice = AskUser(msg + affected, choices)
   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,
+                          ispecs_disk_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):
+  """Creation of instance policy based on command line options.
+
+  @param fill_all: whether for cluster policies we should ensure that
+    all values are filled
+
+
+  """
+  try:
+    if ispecs_mem_size:
+      ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
+    if ispecs_disk_size:
+      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" %
+                               (ispecs_disk_size, ispecs_mem_size, err),
+                               errors.ECODE_INVAL)
+
+  # prepare ipolicy dict
+  ipolicy_transposed = {
+    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
+    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
+    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
+    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
+    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
+    }
+
+  # first, check that the values given are correct
+  if group_ipolicy:
+    forced_type = TISPECS_GROUP_TYPES
+  else:
+    forced_type = TISPECS_CLUSTER_TYPES
+
+  for specs in ipolicy_transposed.values():
+    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
+
+  # then transpose
+  ipolicy_out = objects.MakeEmptyIPolicy()
+  for name, specs in ipolicy_transposed.iteritems():
+    assert name in constants.ISPECS_PARAMETERS
+    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
+      ipolicy_out[key][name] = val
+
+  # no filldict for non-dicts
+  if not group_ipolicy and fill_all:
+    if ipolicy_disk_templates is None:
+      ipolicy_disk_templates = constants.DISK_TEMPLATES
+    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)
+
+  return ipolicy_out