Convert SnapshotBlockDevice's docstring to epydoc
[ganeti-local] / lib / cli.py
index 9f2b9bc..7832b29 100644 (file)
@@ -27,10 +27,10 @@ import textwrap
 import os.path
 import copy
 import time
+import logging
 from cStringIO import StringIO
 
 from ganeti import utils
-from ganeti import logger
 from ganeti import errors
 from ganeti import constants
 from ganeti import opcodes
@@ -42,12 +42,15 @@ from optparse import (OptionParser, make_option, TitledHelpFormatter,
 
 __all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain",
            "SubmitOpCode", "GetClient",
-           "cli_option", "GenerateTable", "AskUser",
+           "cli_option", "ikv_option", "keyval_option",
+           "GenerateTable", "AskUser",
            "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
            "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT", "SUBMIT_OPT",
            "ListTags", "AddTags", "RemoveTags", "TAG_SRC_OPT",
            "FormatError", "SplitNodeOption", "SubmitOrSend",
            "JobSubmittedException", "FormatTimestamp", "ParseTimespec",
+           "ValidateBeParams",
+           "ToStderr", "ToStdout",
            ]
 
 
@@ -221,8 +224,91 @@ class CliOption(Option):
   TYPE_CHECKER["unit"] = check_unit
 
 
+def _SplitKeyVal(opt, data):
+  """Convert a KeyVal string into a dict.
+
+  This function will convert a key=val[,...] string into a dict. Empty
+  values will be converted specially: keys which have the prefix 'no_'
+  will have the value=False and the prefix stripped, the others will
+  have value=True.
+
+  @type opt: string
+  @param opt: a string holding the option name for which we process the
+      data, used in building error messages
+  @type data: string
+  @param data: a string of the format key=val,key=val,...
+  @rtype: dict
+  @return: {key=val, key=val}
+  @raises errors.ParameterError: if there are duplicate keys
+
+  """
+  NO_PREFIX = "no_"
+  UN_PREFIX = "-"
+  kv_dict = {}
+  for elem in data.split(","):
+    if "=" in elem:
+      key, val = elem.split("=", 1)
+    else:
+      if elem.startswith(NO_PREFIX):
+        key, val = elem[len(NO_PREFIX):], False
+      elif elem.startswith(UN_PREFIX):
+        key, val = elem[len(UN_PREFIX):], None
+      else:
+        key, val = elem, True
+    if key in kv_dict:
+      raise errors.ParameterError("Duplicate key '%s' in option %s" %
+                                  (key, opt))
+    kv_dict[key] = val
+  return kv_dict
+
+
+def check_ident_key_val(option, opt, value):
+  """Custom parser for the IdentKeyVal option type.
+
+  """
+  if ":" not in value:
+    retval =  (value, {})
+  else:
+    ident, rest = value.split(":", 1)
+    kv_dict = _SplitKeyVal(opt, rest)
+    retval = (ident, kv_dict)
+  return retval
+
+
+class IdentKeyValOption(Option):
+  """Custom option class for ident:key=val,key=val options.
+
+  This will store the parsed values as a tuple (ident, {key: val}). As
+  such, multiple uses of this option via action=append is possible.
+
+  """
+  TYPES = Option.TYPES + ("identkeyval",)
+  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
+  TYPE_CHECKER["identkeyval"] = check_ident_key_val
+
+
+def check_key_val(option, opt, value):
+  """Custom parser for the KeyVal option type.
+
+  """
+  return _SplitKeyVal(opt, value)
+
+
+class KeyValOption(Option):
+  """Custom option class for key=val,key=val options.
+
+  This will store the parsed values as a dict {key: val}.
+
+  """
+  TYPES = Option.TYPES + ("keyval",)
+  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
+  TYPE_CHECKER["keyval"] = check_key_val
+
+
 # optparse.py sets make_option, so we do it for our own option class, too
 cli_option = CliOption
+ikv_option = IdentKeyValOption
+keyval_option = KeyValOption
 
 
 def _ParseArgs(argv, commands, aliases):
@@ -317,6 +403,27 @@ def SplitNodeOption(value):
     return (value, None)
 
 
+def ValidateBeParams(bep):
+  """Parse and check the given beparams.
+
+  The function will update in-place the given dictionary.
+
+  @type bep: dict
+  @param bep: input beparams
+  @raise errors.ParameterError: if the input values are not OK
+  @raise errors.UnitParseError: if the input values are not OK
+
+  """
+  if constants.BE_MEMORY in bep:
+    bep[constants.BE_MEMORY] = utils.ParseUnit(bep[constants.BE_MEMORY])
+
+  if constants.BE_VCPUS in bep:
+    try:
+      bep[constants.BE_VCPUS] = int(bep[constants.BE_VCPUS])
+    except ValueError:
+      raise errors.ParameterError("Invalid number of VCPUs")
+
+
 def AskUser(text, choices=None):
   """Ask the user a question.
 
@@ -520,7 +627,7 @@ def FormatError(err):
   msg = str(err)
   if isinstance(err, errors.ConfigurationError):
     txt = "Corrupt configuration file: %s" % msg
-    logger.Error(txt)
+    logging.error(txt)
     obuf.write(txt + "\n")
     obuf.write("Aborting.")
     retcode = 2
@@ -549,6 +656,9 @@ def FormatError(err):
     obuf.write("Failure: command execution error:\n%s" % msg)
   elif isinstance(err, errors.TagError):
     obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
+  elif isinstance(err, errors.JobQueueDrainError):
+    obuf.write("Failure: the job queue is marked for drain and doesn't"
+               " accept new requests\n")
   elif isinstance(err, errors.GenericError):
     obuf.write("Unhandled Ganeti error: %s" % msg)
   elif isinstance(err, luxi.NoMasterError):
@@ -603,21 +713,22 @@ def GenericMain(commands, override=None, aliases=None):
     for key, val in override.iteritems():
       setattr(options, key, val)
 
-  logger.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
-                      stderr_logging=True, program=binary)
+  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
+                     stderr_logging=True, program=binary)
 
   utils.debug = options.debug
 
   if old_cmdline:
-    logger.Info("run with arguments '%s'" % old_cmdline)
+    logging.info("run with arguments '%s'", old_cmdline)
   else:
-    logger.Info("run with no arguments")
+    logging.info("run with no arguments")
 
   try:
     result = func(options, args)
   except (errors.GenericError, luxi.ProtocolError), err:
     result, err_msg = FormatError(err)
-    logger.ToStderr(err_msg)
+    logging.exception("Error durring command processing")
+    ToStderr(err_msg)
 
   return result
 
@@ -749,3 +860,45 @@ def ParseTimespec(value):
     except ValueError:
       raise errors.OpPrereqError("Invalid time specification '%s'" % value)
   return value
+
+
+def _ToStream(stream, txt, *args):
+  """Write a message to a stream, bypassing the logging system
+
+  @type stream: file object
+  @param stream: the file to which we should write
+  @type txt: str
+  @param txt: the message
+
+  """
+  if args:
+    args = tuple(args)
+    stream.write(txt % args)
+  else:
+    stream.write(txt)
+  stream.write('\n')
+  stream.flush()
+
+
+def ToStdout(txt, *args):
+  """Write a message to stdout only, bypassing the logging system
+
+  This is just a wrapper over _ToStream.
+
+  @type txt: str
+  @param txt: the message
+
+  """
+  _ToStream(sys.stdout, txt, *args)
+
+
+def ToStderr(txt, *args):
+  """Write a message to stderr only, bypassing the logging system
+
+  This is just a wrapper over _ToStream.
+
+  @type txt: str
+  @param txt: the message
+
+  """
+  _ToStream(sys.stderr, txt, *args)