Include hvparams in ssconf files
[ganeti-local] / lib / cli.py
index b924e89..2e436b6 100644 (file)
@@ -165,6 +165,7 @@ __all__ = [
   "PREALLOC_WIPE_DISKS_OPT",
   "PRIMARY_IP_VERSION_OPT",
   "PRIMARY_ONLY_OPT",
   "PREALLOC_WIPE_DISKS_OPT",
   "PRIMARY_IP_VERSION_OPT",
   "PRIMARY_ONLY_OPT",
+  "PRINT_JOBID_OPT",
   "PRIORITY_OPT",
   "RAPI_CERT_OPT",
   "READD_OPT",
   "PRIORITY_OPT",
   "RAPI_CERT_OPT",
   "READD_OPT",
@@ -198,6 +199,7 @@ __all__ = [
   "SRC_DIR_OPT",
   "SRC_NODE_OPT",
   "SUBMIT_OPT",
   "SRC_DIR_OPT",
   "SRC_NODE_OPT",
   "SUBMIT_OPT",
+  "SUBMIT_OPTS",
   "STARTUP_PAUSED_OPT",
   "STATIC_OPT",
   "SYNC_OPT",
   "STARTUP_PAUSED_OPT",
   "STATIC_OPT",
   "SYNC_OPT",
@@ -680,14 +682,17 @@ def _SplitListKeyVal(opt, value):
   return retval
 
 
   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
 
   @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
 
 
 def check_bool(option, opt, value): # pylint: disable=W0613
@@ -762,7 +767,7 @@ class CliOption(Option):
     "completion_suggest",
     ]
   TYPES = Option.TYPES + (
     "completion_suggest",
     ]
   TYPES = Option.TYPES + (
-    "listidentkeyval",
+    "multilistidentkeyval",
     "identkeyval",
     "keyval",
     "unit",
     "identkeyval",
     "keyval",
     "unit",
@@ -771,7 +776,7 @@ class CliOption(Option):
     "maybefloat",
     )
   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
     "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
   TYPE_CHECKER["identkeyval"] = check_ident_key_val
   TYPE_CHECKER["keyval"] = check_key_val
   TYPE_CHECKER["unit"] = check_unit
@@ -829,6 +834,11 @@ SUBMIT_OPT = cli_option("--submit", dest="submit_only",
                         help=("Submit the job and return the job ID, but"
                               " don't wait for the job to finish"))
 
                         help=("Submit the job and return the job ID, but"
                               " don't wait for the job to finish"))
 
+PRINT_JOBID_OPT = cli_option("--print-jobid", dest="print_jobid",
+                             default=False, action="store_true",
+                             help=("Additionally print the job as first line"
+                                   " on stdout (for scripting)."))
+
 SYNC_OPT = cli_option("--sync", dest="do_locking",
                       default=False, action="store_true",
                       help=("Grab locks while doing the queries"
 SYNC_OPT = cli_option("--sync", dest="do_locking",
                       default=False, action="store_true",
                       help=("Grab locks while doing the queries"
@@ -964,7 +974,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",
 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"
                                       help="Complete instance specs limits")
 
 IPOLICY_STD_SPECS_STR = "--ipolicy-std-specs"
@@ -1631,6 +1641,13 @@ INCLUDEDEFAULTS_OPT = cli_option("--include-defaults", dest="include_defaults",
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
 
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
 
+# options related to asynchronous job handling
+
+SUBMIT_OPTS = [
+  SUBMIT_OPT,
+  PRINT_JOBID_OPT,
+  ]
+
 # common options for creating instances. add and import then add their own
 # specific ones.
 COMMON_CREATE_OPTS = [
 # common options for creating instances. add and import then add their own
 # specific ones.
 COMMON_CREATE_OPTS = [
@@ -1651,6 +1668,7 @@ COMMON_CREATE_OPTS = [
   OSPARAMS_OPT,
   OS_SIZE_OPT,
   SUBMIT_OPT,
   OSPARAMS_OPT,
   OS_SIZE_OPT,
   SUBMIT_OPT,
+  PRINT_JOBID_OPT,
   TAG_ADD_OPT,
   DRY_RUN_OPT,
   PRIORITY_OPT,
   TAG_ADD_OPT,
   DRY_RUN_OPT,
   PRIORITY_OPT,
@@ -2245,6 +2263,8 @@ def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
   SetGenericOpcodeOpts([op], opts)
 
   job_id = SendJob([op], cl=cl)
   SetGenericOpcodeOpts([op], opts)
 
   job_id = SendJob([op], cl=cl)
+  if hasattr(opts, "print_jobid") and opts.print_jobid:
+    ToStdout("%d" % job_id)
 
   op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
                        reporter=reporter)
 
   op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
                        reporter=reporter)
@@ -2268,6 +2288,8 @@ def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
     job = [op]
     SetGenericOpcodeOpts(job, opts)
     job_id = SendJob(job, cl=cl)
     job = [op]
     SetGenericOpcodeOpts(job, opts)
     job_id = SendJob(job, cl=cl)
+    if opts.print_jobid:
+      ToStdout("%d" % job_id)
     raise JobSubmittedException(job_id)
   else:
     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
     raise JobSubmittedException(job_id)
   else:
     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
@@ -2644,6 +2666,9 @@ def GenericInstanceCreate(mode, opts, args):
           raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
                                      (didx, err), errors.ECODE_INVAL)
       elif constants.IDISK_ADOPT in ddict:
           raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
                                      (didx, err), errors.ECODE_INVAL)
       elif constants.IDISK_ADOPT in ddict:
+        if constants.IDISK_SPINDLES in ddict:
+          raise errors.OpPrereqError("spindles is not a valid option when"
+                                     " adopting a disk", errors.ECODE_INVAL)
         if mode == constants.INSTANCE_IMPORT:
           raise errors.OpPrereqError("Disk adoption not allowed for instance"
                                      " import", errors.ECODE_INVAL)
         if mode == constants.INSTANCE_IMPORT:
           raise errors.OpPrereqError("Disk adoption not allowed for instance"
                                      " import", errors.ECODE_INVAL)
@@ -3762,7 +3787,7 @@ def FormatPolicyInfo(custom_ipolicy, eff_ipolicy, iscluster):
       )
 
   ret.append(
       )
 
   ret.append(
-    ("enabled disk templates",
+    ("allowed disk templates",
      _FormatListInfoDefault(custom_ipolicy.get(constants.IPOLICY_DTS),
                             eff_ipolicy[constants.IPOLICY_DTS]))
     )
      _FormatListInfoDefault(custom_ipolicy.get(constants.IPOLICY_DTS),
                             eff_ipolicy[constants.IPOLICY_DTS]))
     )
@@ -3796,12 +3821,17 @@ def PrintIPolicyCommand(buf, ipolicy, isgroup):
     if stdspecs:
       buf.write(" %s " % IPOLICY_STD_SPECS_STR)
       _PrintSpecsParameters(buf, stdspecs)
     if stdspecs:
       buf.write(" %s " % IPOLICY_STD_SPECS_STR)
       _PrintSpecsParameters(buf, stdspecs)
-  minmax = ipolicy.get("minmax")
-  if minmax:
-    minspecs = minmax[0].get("min")
-    maxspecs = minmax[0].get("max")
+  minmaxes = ipolicy.get("minmax", [])
+  first = True
+  for minmax in minmaxes:
+    minspecs = minmax.get("min")
+    maxspecs = minmax.get("max")
     if minspecs and maxspecs:
     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:")
       buf.write("min:")
       _PrintSpecsParameters(buf, minspecs)
       buf.write("/max:")
@@ -3945,8 +3975,9 @@ def _ParseISpec(spec, keyname, required):
 
 def _GetISpecsInAllowedValues(minmax_ispecs, allowed_values):
   ret = None
 
 def _GetISpecsInAllowedValues(minmax_ispecs, allowed_values):
   ret = None
-  if minmax_ispecs and allowed_values and len(minmax_ispecs) == 1:
-    for (key, spec) in minmax_ispecs.items():
+  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
       # This loop is executed exactly once
       if key in allowed_values and not spec:
         ret = key
@@ -3959,13 +3990,16 @@ def _InitISpecsFromFullOpts(ipolicy_out, minmax_ispecs, std_ispecs,
   if found_allowed is not None:
     ipolicy_out[constants.ISPECS_MINMAX] = found_allowed
   elif minmax_ispecs is not None:
   if found_allowed is not None:
     ipolicy_out[constants.ISPECS_MINMAX] = found_allowed
   elif 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, True)
-    ipolicy_out[constants.ISPECS_MINMAX] = [minmax_out]
+    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)
   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)