cli.GenerateTable: Don't write EOL spaces
[ganeti-local] / lib / cli.py
index 698f03f..8a41e2d 100644 (file)
@@ -25,7 +25,6 @@
 import sys
 import textwrap
 import os.path
-import copy
 import time
 import logging
 from cStringIO import StringIO
@@ -62,6 +61,8 @@ __all__ = [
   "FIELDS_OPT",
   "FILESTORE_DIR_OPT",
   "FILESTORE_DRIVER_OPT",
+  "FORCE_OPT",
+  "FORCE_VARIANT_OPT",
   "GLOBAL_FILEDIR_OPT",
   "HVLIST_OPT",
   "HVOPTS_OPT",
@@ -71,7 +72,6 @@ __all__ = [
   "IGNORE_FAILURES_OPT",
   "IGNORE_SECONDARIES_OPT",
   "IGNORE_SIZE_OPT",
-  "FORCE_OPT",
   "MAC_PREFIX_OPT",
   "MASTER_NETDEV_OPT",
   "MC_OPT",
@@ -82,8 +82,10 @@ __all__ = [
   "NODE_PLACEMENT_OPT",
   "NOHDR_OPT",
   "NOIPCHECK_OPT",
+  "NONAMECHECK_OPT",
   "NOLVM_STORAGE_OPT",
   "NOMODIFY_ETCHOSTS_OPT",
+  "NOMODIFY_SSH_SETUP_OPT",
   "NONICS_OPT",
   "NONLIVE_OPT",
   "NONPLUS1_OPT",
@@ -103,6 +105,7 @@ __all__ = [
   "SELECT_OS_OPT",
   "SEP_OPT",
   "SHOWCMD_OPT",
+  "SHUTDOWN_TIMEOUT_OPT",
   "SINGLE_NODE_OPT",
   "SRC_DIR_OPT",
   "SRC_NODE_OPT",
@@ -110,6 +113,7 @@ __all__ = [
   "STATIC_OPT",
   "SYNC_OPT",
   "TAG_SRC_OPT",
+  "TIMEOUT_OPT",
   "USEUNITS_OPT",
   "VERBOSE_OPT",
   "VG_NAME_OPT",
@@ -158,6 +162,7 @@ __all__ = [
   "OPT_COMPL_ONE_OS",
   "cli_option",
   "SplitNodeOption",
+  "CalculateOSNames",
   ]
 
 NO_PREFIX = "no_"
@@ -165,7 +170,7 @@ UN_PREFIX = "-"
 
 
 class _Argument:
-  def __init__(self, min=0, max=None):
+  def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
     self.min = min
     self.max = max
 
@@ -180,6 +185,7 @@ class ArgSuggest(_Argument):
   Value can be any of the ones passed to the constructor.
 
   """
+  # pylint: disable-msg=W0622
   def __init__(self, min=0, max=None, choices=None):
     _Argument.__init__(self, min=min, max=max)
     self.choices = choices
@@ -246,7 +252,6 @@ ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
 
 
-
 def _ExtractTagsObject(opts, args):
   """Extract the tag type object.
 
@@ -307,8 +312,8 @@ def ListTags(opts, args):
 
   """
   kind, name = _ExtractTagsObject(opts, args)
-  op = opcodes.OpGetTags(kind=kind, name=name)
-  result = SubmitOpCode(op)
+  cl = GetClient()
+  result = cl.QueryTags(kind, name)
   result = list(result)
   result.sort()
   for tag in result:
@@ -349,7 +354,7 @@ def RemoveTags(opts, args):
   SubmitOpCode(op)
 
 
-def check_unit(option, opt, value):
+def check_unit(option, opt, value): # pylint: disable-msg=W0613
   """OptParsers custom converter for units.
 
   """
@@ -396,7 +401,7 @@ def _SplitKeyVal(opt, data):
   return kv_dict
 
 
-def check_ident_key_val(option, opt, value):
+def check_ident_key_val(option, opt, value):  # pylint: disable-msg=W0613
   """Custom parser for ident:key=val,key=val options.
 
   This will store the parsed values as a tuple (ident, {key: val}). As such,
@@ -424,7 +429,7 @@ def check_ident_key_val(option, opt, value):
   return retval
 
 
-def check_key_val(option, opt, value):
+def check_key_val(option, opt, value):  # pylint: disable-msg=W0613
   """Custom parser class for key=val,key=val options.
 
   This will store the parsed values as a dict {key: val}.
@@ -566,6 +571,10 @@ OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
                     metavar="<os>",
                     completion_suggest=OPT_COMPL_ONE_OS)
 
+FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
+                               action="store_true", default=False,
+                               help="Force an unknown variant")
+
 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
                          type="keyval", default={},
                          help="Backend parameters")
@@ -589,6 +598,11 @@ NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
                            help="Don't check that the instance's IP"
                            " is alive")
 
+NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
+                             default=True, action="store_false",
+                             help="Don't check that the instance's name"
+                             " is resolvable")
+
 NET_OPT = cli_option("--net",
                      help="NIC parameters", default=[],
                      dest="nics", action="append", type="identkeyval")
@@ -787,6 +801,10 @@ NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
                                    help="Don't modify /etc/hosts",
                                    action="store_false", default=True)
 
+NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
+                                    help="Don't initialize SSH keys",
+                                    action="store_false", default=True)
+
 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
                              help="Enable parseable error messages",
                              action="store_true", default=False)
@@ -806,10 +824,18 @@ IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
                                     default=False, action="store_true",
                                     help="Ignore errors from secondaries")
 
-NOSHUTDOWN_OPT = cli_option("","--noshutdown", dest="shutdown",
+NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
                             action="store_false", default=True,
                             help="Don't shutdown the instance (unsafe)")
 
+TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
+                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
+                         help="Maximum time to wait")
+
+SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
+                         dest="shutdown_timeout", type="int",
+                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
+                         help="Maximum time to wait for instance shutdown")
 
 
 def _ParseArgs(argv, commands, aliases):
@@ -971,6 +997,23 @@ def SplitNodeOption(value):
     return (value, None)
 
 
+def CalculateOSNames(os_name, os_variants):
+  """Calculates all the names an OS can be called, according to its variants.
+
+  @type os_name: string
+  @param os_name: base name of the os
+  @type os_variants: list or None
+  @param os_variants: list of supported variants
+  @rtype: list
+  @return: list of valid names
+
+  """
+  if os_variants:
+    return ['%s+%s' % (os_name, v) for v in os_variants]
+  else:
+    return [os_name]
+
+
 def UsesRPC(fn):
   def wrapper(*args, **kwargs):
     rpc.Init()
@@ -1182,13 +1225,21 @@ def GetClient():
   try:
     client = luxi.Client()
   except luxi.NoMasterError:
-    master, myself = ssconf.GetMasterAndMyself()
+    ss = ssconf.SimpleStore()
+
+    # Try to read ssconf file
+    try:
+      ss.GetMasterNode()
+    except errors.ConfigurationError:
+      raise errors.OpPrereqError("Cluster not initialized or this machine is"
+                                 " not part of a cluster")
+
+    master, myself = ssconf.GetMasterAndMyself(ss=ss)
     if master != myself:
       raise errors.OpPrereqError("This is not the master node, please connect"
                                  " to node '%s' and rerun the command" %
                                  master)
-    else:
-      raise
+    raise
   return client
 
 
@@ -1229,8 +1280,13 @@ def FormatError(err):
       msg = "Failure: can't resolve hostname '%s'"
     obuf.write(msg % err.args[0])
   elif isinstance(err, errors.OpPrereqError):
-    obuf.write("Failure: prerequisites not met for this"
-               " operation:\n%s" % msg)
+    if len(err.args) == 2:
+      obuf.write("Failure: prerequisites not met for this"
+               " operation:\nerror type: %s, error details:\n%s" %
+                 (err.args[1], err.args[0]))
+    else:
+      obuf.write("Failure: prerequisites not met for this"
+                 " operation:\n%s" % msg)
   elif isinstance(err, errors.OpExecError):
     obuf.write("Failure: command execution error:\n%s" % msg)
   elif isinstance(err, errors.TagError):
@@ -1346,7 +1402,7 @@ def GenericInstanceCreate(mode, opts, args):
 
   if opts.nics:
     try:
-      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
+      nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
     except ValueError, err:
       raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
     nics = [{}] * nic_max
@@ -1377,7 +1433,7 @@ def GenericInstanceCreate(mode, opts, args):
     if opts.sd_size is not None:
       opts.disks = [(0, {"size": opts.sd_size})]
     try:
-      disk_max = max(int(didx[0])+1 for didx in opts.disks)
+      disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
     except ValueError, err:
       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
     disks = [{}] * disk_max
@@ -1417,6 +1473,7 @@ def GenericInstanceCreate(mode, opts, args):
                                 nics=nics,
                                 pnode=pnode, snode=snode,
                                 ip_check=opts.ip_check,
+                                name_check=opts.name_check,
                                 wait_for_sync=opts.wait_for_sync,
                                 file_storage_dir=opts.file_storage_dir,
                                 file_driver=opts.file_driver,
@@ -1474,8 +1531,8 @@ def GenerateTable(headers, fields, separator, data,
   if unitfields is None:
     unitfields = []
 
-  numfields = utils.FieldSet(*numfields)
-  unitfields = utils.FieldSet(*unitfields)
+  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
+  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
 
   format_fields = []
   for field in fields:
@@ -1518,7 +1575,10 @@ def GenerateTable(headers, fields, separator, data,
     for idx, name in enumerate(fields):
       hdr = headers[name]
       if separator is None:
-        mlens[idx] = max(mlens[idx], len(hdr))
+        if idx == len(fields) - 1 and not numfields.Matches(name):
+          mlens[idx] = 0
+        else:
+          mlens[idx] = max(mlens[idx], len(hdr))
         args.append(mlens[idx])
       args.append(hdr)
     result.append(format % tuple(args))
@@ -1616,7 +1676,7 @@ def GetOnlineNodes(nodes, cl=None, nowarn=False):
                          use_locking=False)
   offline = [row[0] for row in result if row[1]]
   if offline and not nowarn:
-    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
+    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
   return [row[0] for row in result if not row[1]]
 
 
@@ -1708,7 +1768,7 @@ class JobExecutor(object):
     if self.verbose:
       ok_jobs = [row[1] for row in self.jobs if row[0]]
       if ok_jobs:
-        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
+        ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
     for submit_status, jid, name in self.jobs:
       if not submit_status:
         ToStderr("Failed to submit job for %s: %s", name, jid)