gnt-job list: Add options for commonly used filters
[ganeti-local] / lib / cli.py
index 63f2dfb..f18e65b 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -69,6 +69,7 @@ __all__ = [
   "DEBUG_SIMERR_OPT",
   "DISKIDX_OPT",
   "DISK_OPT",
+  "DISK_PARAMS_OPT",
   "DISK_TEMPLATE_OPT",
   "DRAINED_OPT",
   "DRY_RUN_OPT",
@@ -131,6 +132,7 @@ __all__ = [
   "NONICS_OPT",
   "NONLIVE_OPT",
   "NONPLUS1_OPT",
+  "NORUNTIME_CHGS_OPT",
   "NOSHUTDOWN_OPT",
   "NOSTART_OPT",
   "NOSSH_KEYCHECK_OPT",
@@ -157,6 +159,7 @@ __all__ = [
   "REMOVE_INSTANCE_OPT",
   "REMOVE_UIDS_OPT",
   "RESERVED_LVS_OPT",
+  "RUNTIME_MEM_OPT",
   "ROMAN_OPT",
   "SECONDARY_IP_OPT",
   "SECONDARY_ONLY_OPT",
@@ -165,6 +168,13 @@ __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",
+  "IPOLICY_DISK_TEMPLATES",
+  "IPOLICY_VCPU_RATIO",
   "SPICE_CACERT_OPT",
   "SPICE_CERT_OPT",
   "SRC_DIR_OPT",
@@ -184,6 +194,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",
@@ -454,7 +468,7 @@ def AddTags(opts, args):
   if not args:
     raise errors.OpPrereqError("No tags to be added")
   op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
-  SubmitOpCode(op, opts=opts)
+  SubmitOrSend(op, opts)
 
 
 def RemoveTags(opts, args):
@@ -471,7 +485,7 @@ def RemoveTags(opts, args):
   if not args:
     raise errors.OpPrereqError("No tags to be removed")
   op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
-  SubmitOpCode(op, opts=opts)
+  SubmitOrSend(op, opts)
 
 
 def check_unit(option, opt, value): # pylint: disable=W0613
@@ -538,7 +552,9 @@ def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
       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):
+  elif (ident.startswith(UN_PREFIX) and
+        (len(ident) <= len(UN_PREFIX) or
+         not ident[len(UN_PREFIX)][0].isdigit())):
     if rest:
       msg = "Cannot pass options when removing parameter groups: %s" % value
       raise errors.ParameterError(msg)
@@ -573,6 +589,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,
@@ -606,12 +634,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
@@ -744,6 +774,11 @@ NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
                             help="Do not install the OS (will"
                             " enable no-start)")
 
+NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
+                                dest="allow_runtime_chgs",
+                                default=True, action="store_false",
+                                help="Don't allow runtime changes")
+
 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
                          type="keyval", default={},
                          help="Backend parameters")
@@ -752,6 +787,44 @@ HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
                         default={}, dest="hvparams",
                         help="Hypervisor parameters")
 
+DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
+                             help="Disk template parameters, in the format"
+                             " 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")
+
+IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
+                                 dest="ipolicy_disk_templates",
+                                 type="list", default=None,
+                                 help="Comma-separated list of"
+                                 " enabled disk templates")
+
+IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
+                                 dest="ipolicy_vcpu_ratio",
+                                 type="float", default=None,
+                                 help="The maximum allowed vcpu-to-cpu ratio")
+
 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
                             help="Hypervisor and hypervisor options, in the"
                             " format hypervisor:option=value,option=value,...",
@@ -1287,6 +1360,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]
@@ -1315,6 +1408,17 @@ 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,
+  IPOLICY_DISK_TEMPLATES,
+  IPOLICY_VCPU_RATIO,
+  ]
+
 
 def _ParseArgs(argv, commands, aliases, env_override):
   """Parser for the command line arguments.
@@ -2046,15 +2150,19 @@ def GenericMain(commands, override=None, aliases=None,
   """
   # save the program name and the entire command line for later logging
   if sys.argv:
-    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
+    binary = os.path.basename(sys.argv[0])
+    if not binary:
+      binary = sys.argv[0]
+
     if len(sys.argv) >= 2:
-      binary += " " + sys.argv[1]
-      old_cmdline = " ".join(sys.argv[2:])
+      logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
     else:
-      old_cmdline = ""
+      logname = binary
+
+    cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
   else:
     binary = "<unknown program>"
-    old_cmdline = ""
+    cmdline = "<unknown>"
 
   if aliases is None:
     aliases = {}
@@ -2073,13 +2181,10 @@ def GenericMain(commands, override=None, aliases=None,
     for key, val in override.iteritems():
       setattr(options, key, val)
 
-  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
+  utils.SetupLogging(constants.LOG_COMMANDS, logname, debug=options.debug,
                      stderr_logging=True)
 
-  if old_cmdline:
-    logging.info("run with arguments '%s'", old_cmdline)
-  else:
-    logging.info("run with no arguments")
+  logging.info("Command line: %s", cmdline)
 
   try:
     result = func(options, args)
@@ -2257,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
@@ -2693,7 +2799,8 @@ def _WarnUnknownFields(fdefs):
 
 
 def GenericList(resource, fields, names, unit, separator, header, cl=None,
-                format_override=None, verbose=False, force_filter=False):
+                format_override=None, verbose=False, force_filter=False,
+                namefield=None, qfilter=None):
   """Generic implementation for listing all items of a resource.
 
   @param resource: One of L{constants.QR_VIA_LUXI}
@@ -2716,12 +2823,22 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None,
     indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
   @type verbose: boolean
   @param verbose: whether to use verbose field descriptions or not
+  @type namefield: string
+  @param namefield: Name of field to use for simple filters (see
+    L{qlang.MakeFilter} for details)
+  @type qfilter: list or None
+  @param qfilter: Query filter (in addition to names)
 
   """
   if not names:
     names = None
 
-  qfilter = qlang.MakeFilter(names, force_filter)
+  namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield)
+
+  if qfilter is None:
+    qfilter = namefilter
+  elif namefilter is not None:
+    qfilter = [qlang.OP_AND, namefilter, qfilter]
 
   if cl is None:
     cl = GetClient()
@@ -2880,8 +2997,9 @@ def FormatTimestamp(ts):
   """
   if not isinstance(ts, (tuple, list)) or len(ts) != 2:
     return "?"
-  sec, usec = ts
-  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
+
+  (sec, usecs) = ts
+  return utils.FormatTime(sec, usecs=usecs)
 
 
 def ParseTimespec(value):
@@ -3106,7 +3224,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):