cmdlib: Fix typo, s/nick/NIC/
[ganeti-local] / lib / cli.py
index e2e1e31..61c0e14 100644 (file)
@@ -27,6 +27,7 @@ import textwrap
 import os.path
 import time
 import logging
+import errno
 from cStringIO import StringIO
 
 from ganeti import utils
@@ -104,6 +105,7 @@ __all__ = [
   "NEW_RAPI_CERT_OPT",
   "NEW_SECONDARY_OPT",
   "NIC_PARAMS_OPT",
+  "NODE_FORCE_JOIN_OPT",
   "NODE_LIST_OPT",
   "NODE_PLACEMENT_OPT",
   "NODEGROUP_OPT",
@@ -178,6 +180,7 @@ __all__ = [
   "ToStderr", "ToStdout",
   "FormatError",
   "FormatQueryResult",
+  "FormatParameterDict",
   "GenerateTable",
   "AskUser",
   "FormatTimestamp",
@@ -336,7 +339,8 @@ ARGS_MANY_NODES = [ArgNode()]
 ARGS_MANY_GROUPS = [ArgGroup()]
 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
-ARGS_ONE_GROUP = [ArgInstance(min=1, max=1)]
+# TODO
+ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
 
 
@@ -421,7 +425,7 @@ def AddTags(opts, args):
   _ExtendTags(opts, args)
   if not args:
     raise errors.OpPrereqError("No tags to be added")
-  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
+  op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
   SubmitOpCode(op, opts=opts)
 
 
@@ -438,7 +442,7 @@ def RemoveTags(opts, args):
   _ExtendTags(opts, args)
   if not args:
     raise errors.OpPrereqError("No tags to be removed")
-  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
+  op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
   SubmitOpCode(op, opts=opts)
 
 
@@ -884,6 +888,9 @@ NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
                                 default=True, action="store_false",
                                 help="Disable SSH key fingerprint checking")
 
+NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
+                                 default=False, action="store_true",
+                                 help="Force the joining of a node")
 
 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
                     type="bool", default=None, metavar=_YORNO,
@@ -891,11 +898,14 @@ MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
 
 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
                          type="bool", default=None,
-                         help="Set the offline flag on the node")
+                         help=("Set the offline flag on the node"
+                               " (cluster does not communicate with offline"
+                               " nodes)"))
 
 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
                          type="bool", default=None,
-                         help="Set the drained flag on the node")
+                         help=("Set the drained flag on the node"
+                               " (excluded from allocation operations)"))
 
 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
                     type="bool", default=None, metavar=_YORNO,
@@ -928,8 +938,9 @@ CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
                          help="Set the candidate pool size")
 
 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
-                         help="Enables LVM and specifies the volume group"
-                         " name (cluster-wide) for disk allocation [xenvg]",
+                         help=("Enables LVM and specifies the volume group"
+                               " name (cluster-wide) for disk allocation"
+                               " [%s]" % constants.DEFAULT_VG),
                          metavar="VG", default=None)
 
 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
@@ -1909,8 +1920,8 @@ def GenericMain(commands, override=None, aliases=None):
     for key, val in override.iteritems():
       setattr(options, key, val)
 
-  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
-                     stderr_logging=True, program=binary)
+  utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
+                     stderr_logging=True)
 
   if old_cmdline:
     logging.info("run with arguments '%s'", old_cmdline)
@@ -1924,6 +1935,17 @@ def GenericMain(commands, override=None, aliases=None):
     result, err_msg = FormatError(err)
     logging.exception("Error during command processing")
     ToStderr(err_msg)
+  except KeyboardInterrupt:
+    result = constants.EXIT_FAILURE
+    ToStderr("Aborted. Note that if the operation created any jobs, they"
+             " might have been submitted and"
+             " will continue to run in the background.")
+  except IOError, err:
+    if err.errno == errno.EPIPE:
+      # our terminal went away, we'll exit
+      sys.exit(constants.EXIT_FAILURE)
+    else:
+      raise
 
   return result
 
@@ -2053,7 +2075,7 @@ def GenericInstanceCreate(mode, opts, args):
   else:
     raise errors.ProgrammerError("Invalid creation mode %s" % mode)
 
-  op = opcodes.OpCreateInstance(instance_name=instance,
+  op = opcodes.OpInstanceCreate(instance_name=instance,
                                 disks=disks,
                                 disk_template=opts.disk_template,
                                 nics=nics,
@@ -2367,17 +2389,23 @@ class _QueryColumnFormatter:
   """Callable class for formatting fields of a query.
 
   """
-  def __init__(self, fn, status_fn):
+  def __init__(self, fn, status_fn, verbose):
     """Initializes this class.
 
     @type fn: callable
     @param fn: Formatting function
     @type status_fn: callable
     @param status_fn: Function to report fields' status
+    @type verbose: boolean
+    @param verbose: whether to use verbose field descriptions or not
 
     """
     self._fn = fn
     self._status_fn = status_fn
+    if verbose:
+      self._desc_index = 0
+    else:
+      self._desc_index = 1
 
   def __call__(self, data):
     """Returns a field's string representation.
@@ -2388,29 +2416,20 @@ class _QueryColumnFormatter:
     # Report status
     self._status_fn(status)
 
-    if status == constants.QRFS_NORMAL:
+    if status == constants.RS_NORMAL:
       return self._fn(value)
 
     assert value is None, \
            "Found value %r for abnormal status %s" % (value, status)
 
-    if status == constants.QRFS_UNKNOWN:
-      return "(unknown)"
-
-    if status == constants.QRFS_NODATA:
-      return "(nodata)"
-
-    if status == constants.QRFS_UNAVAIL:
-      return "(unavail)"
-
-    if status == constants.QRFS_OFFLINE:
-      return "(offline)"
+    if status in constants.RSS_DESCRIPTION:
+      return constants.RSS_DESCRIPTION[status][self._desc_index]
 
     raise NotImplementedError("Unknown status %s" % status)
 
 
 def FormatQueryResult(result, unit=None, format_override=None, separator=None,
-                      header=False):
+                      header=False, verbose=False):
   """Formats data in L{objects.QueryResponse}.
 
   @type result: L{objects.QueryResponse}
@@ -2425,6 +2444,8 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None,
   @param separator: String used to separate fields
   @type header: bool
   @param header: Whether to output header row
+  @type verbose: boolean
+  @param verbose: whether to use verbose field descriptions or not
 
   """
   if unit is None:
@@ -2436,7 +2457,7 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None,
   if format_override is None:
     format_override = {}
 
-  stats = dict.fromkeys(constants.QRFS_ALL, 0)
+  stats = dict.fromkeys(constants.RS_ALL, 0)
 
   def _RecordStatus(status):
     if status in stats:
@@ -2447,22 +2468,23 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None,
     assert fdef.title and fdef.name
     (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
     columns.append(TableColumn(fdef.title,
-                               _QueryColumnFormatter(fn, _RecordStatus),
+                               _QueryColumnFormatter(fn, _RecordStatus,
+                                                     verbose),
                                align_right))
 
   table = FormatTable(result.data, columns, header, separator)
 
   # Collect statistics
-  assert len(stats) == len(constants.QRFS_ALL)
+  assert len(stats) == len(constants.RS_ALL)
   assert compat.all(count >= 0 for count in stats.values())
 
   # Determine overall status. If there was no data, unknown fields must be
   # detected via the field definitions.
-  if (stats[constants.QRFS_UNKNOWN] or
+  if (stats[constants.RS_UNKNOWN] or
       (not result.data and _GetUnknownFields(result.fields))):
     status = QR_UNKNOWN
   elif compat.any(count > 0 for key, count in stats.items()
-                  if key != constants.QRFS_NORMAL):
+                  if key != constants.RS_NORMAL):
     status = QR_INCOMPLETE
   else:
     status = QR_NORMAL
@@ -2496,7 +2518,7 @@ def _WarnUnknownFields(fdefs):
 
 
 def GenericList(resource, fields, names, unit, separator, header, cl=None,
-                format_override=None):
+                format_override=None, verbose=False):
   """Generic implementation for listing all items of a resource.
 
   @param resource: One of L{constants.QR_OP_LUXI}
@@ -2515,6 +2537,8 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None,
   @type format_override: dict
   @param format_override: Dictionary for overriding field formatting functions,
     indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
+  @type verbose: boolean
+  @param verbose: whether to use verbose field descriptions or not
 
   """
   if cl is None:
@@ -2529,7 +2553,8 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None,
 
   (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
                                      header=header,
-                                     format_override=format_override)
+                                     format_override=format_override,
+                                     verbose=verbose)
 
   for line in data:
     ToStdout(line)
@@ -2777,13 +2802,20 @@ def _ToStream(stream, txt, *args):
   @param txt: the message
 
   """
-  if args:
-    args = tuple(args)
-    stream.write(txt % args)
-  else:
-    stream.write(txt)
-  stream.write('\n')
-  stream.flush()
+  try:
+    if args:
+      args = tuple(args)
+      stream.write(txt % args)
+    else:
+      stream.write(txt)
+    stream.write('\n')
+    stream.flush()
+  except IOError, err:
+    if err.errno == errno.EPIPE:
+      # our terminal went away, we'll exit
+      sys.exit(constants.EXIT_FAILURE)
+    else:
+      raise
 
 
 def ToStdout(txt, *args):
@@ -2941,3 +2973,21 @@ class JobExecutor(object):
         else:
           ToStderr("Failure for %s: %s", name, result)
       return [row[1:3] for row in self.jobs]
+
+
+def FormatParameterDict(buf, param_dict, actual, level=1):
+  """Formats a parameter dictionary.
+
+  @type buf: L{StringIO}
+  @param buf: the buffer into which to write
+  @type param_dict: dict
+  @param param_dict: the own parameters
+  @type actual: dict
+  @param actual: the current parameter set (including defaults)
+  @param level: Level of indent
+
+  """
+  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))