Instance startup: lock primary node
[ganeti-local] / lib / cli.py
index cde9991..a920fb0 100644 (file)
@@ -30,6 +30,7 @@ import logging
 import errno
 import itertools
 import shlex
+import optparse
 from cStringIO import StringIO
 
 from ganeti import utils
@@ -44,7 +45,7 @@ from ganeti import compat
 from ganeti import netutils
 from ganeti import qlang
 
-from optparse import (OptionParser, TitledHelpFormatter,
+from optparse import (TitledHelpFormatter,
                       Option, OptionValueError)
 
 
@@ -158,6 +159,7 @@ __all__ = [
   "REMOVE_INSTANCE_OPT",
   "REMOVE_UIDS_OPT",
   "RESERVED_LVS_OPT",
+  "RUNTIME_MEM_OPT",
   "ROMAN_OPT",
   "SECONDARY_IP_OPT",
   "SECONDARY_ONLY_OPT",
@@ -166,6 +168,12 @@ __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",
+  "SPECS_DISK_TEMPLATES",
   "SPICE_CACERT_OPT",
   "SPICE_CERT_OPT",
   "SRC_DIR_OPT",
@@ -185,6 +193,10 @@ __all__ = [
   "VERBOSE_OPT",
   "VG_NAME_OPT",
   "YES_DOIT_OPT",
+  "DISK_STATE_OPT",
+  "HV_STATE_OPT",
+  "IGNORE_IPOLICY_OPT",
+  "INSTANCE_POLICY_OPTS",
   # Generic functions for CLI programs
   "ConfirmOperation",
   "GenericMain",
@@ -574,6 +586,18 @@ def check_bool(option, opt, value): # pylint: disable=W0613
     raise errors.ParameterError("Invalid boolean value '%s'" % value)
 
 
+def check_list(option, opt, value): # pylint: disable=W0613
+  """Custom parser for comma-separated lists.
+
+  """
+  # we have to make this explicit check since "".split(",") is [""],
+  # not an empty list :(
+  if not value:
+    return []
+  else:
+    return utils.UnescapeAndSplit(value)
+
+
 # completion_suggestion is normally a list. Using numeric values not evaluating
 # to False for dynamic completion.
 (OPT_COMPL_MANY_NODES,
@@ -607,12 +631,14 @@ class CliOption(Option):
     "keyval",
     "unit",
     "bool",
+    "list",
     )
   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
   TYPE_CHECKER["identkeyval"] = check_ident_key_val
   TYPE_CHECKER["keyval"] = check_key_val
   TYPE_CHECKER["unit"] = check_unit
   TYPE_CHECKER["bool"] = check_bool
+  TYPE_CHECKER["list"] = check_list
 
 
 # optparse.py sets make_option, so we do it for our own option class, too
@@ -758,6 +784,34 @@ DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
                              " 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")
+
+SPECS_DISK_TEMPLATES = cli_option("--specs-disk-templates",
+                                  dest="ispecs_disk_templates",
+                                  type="list", default=None,
+                                  help="Comma-separated list of"
+                                  " enabled disk templates")
+
 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
                             help="Hypervisor and hypervisor options, in the"
                             " format hypervisor:option=value,option=value,...",
@@ -1293,6 +1347,26 @@ IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
                                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")
+
+IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
+                                action="store_true", default=False,
+                                help="Ignore instance policy violations")
+
+RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
+                             help="Sets the instance's runtime memory,"
+                             " ballooning it up or down to the new value",
+                             default=None, type="unit", metavar="<size>")
 
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT]
@@ -1321,6 +1395,16 @@ COMMON_CREATE_OPTS = [
   PRIORITY_OPT,
   ]
 
+# common instance policy options
+INSTANCE_POLICY_OPTS = [
+  SPECS_CPU_COUNT_OPT,
+  SPECS_DISK_COUNT_OPT,
+  SPECS_DISK_SIZE_OPT,
+  SPECS_MEM_SIZE_OPT,
+  SPECS_NIC_COUNT_OPT,
+  SPECS_DISK_TEMPLATES,
+  ]
+
 
 def _ParseArgs(argv, commands, aliases, env_override):
   """Parser for the command line arguments.
@@ -1397,10 +1481,10 @@ def _ParseArgs(argv, commands, aliases, env_override):
       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 = CustomOptionParser(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(args=argv[1:])
 
@@ -1410,6 +1494,21 @@ def _ParseArgs(argv, commands, aliases, env_override):
   return func, options, args
 
 
+class CustomOptionParser(optparse.OptionParser):
+  def _match_long_opt(self, opt):
+    """Override C{OptionParser}'s function for matching long options.
+
+    The default implementation does prefix-based abbreviation matching. We
+    disable such behaviour as it can can lead to confusing conflicts (e.g.
+    C{--force} and C{--force-multi}).
+
+    """
+    if opt in self._long_opt:
+      return opt
+    else:
+      raise optparse.BadOptionError(opt)
+
+
 def _CheckArguments(cmd, args_def, args):
   """Verifies the arguments using the argument definition.
 
@@ -2263,7 +2362,8 @@ def GenericInstanceCreate(mode, opts, args):
                                 src_path=src_path,
                                 tags=tags,
                                 no_install=no_install,
-                                identify_defaults=identify_defaults)
+                                identify_defaults=identify_defaults,
+                                ignore_ipolicy=opts.ignore_ipolicy)
 
   SubmitOrSend(op, opts)
   return 0
@@ -3112,7 +3212,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):