Generate a shared HMAC key at cluster init time
[ganeti-local] / lib / cli.py
index d351f2f..b08506a 100644 (file)
@@ -51,9 +51,11 @@ __all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain",
            "FormatError", "SplitNodeOption", "SubmitOrSend",
            "JobSubmittedException", "FormatTimestamp", "ParseTimespec",
            "ToStderr", "ToStdout", "UsesRPC",
-           "GetOnlineNodes", "JobExecutor", "SYNC_OPT",
+           "GetOnlineNodes", "JobExecutor", "SYNC_OPT", "CONFIRM_OPT",
            ]
 
+NO_PREFIX = "no_"
+UN_PREFIX = "-"
 
 def _ExtractTagsObject(opts, args):
   """Extract the tag type object.
@@ -182,6 +184,9 @@ FIELDS_OPT = make_option("-o", "--output", dest="output", action="store",
 FORCE_OPT = make_option("-f", "--force", dest="force", action="store_true",
                         default=False, help="Force the operation")
 
+CONFIRM_OPT = make_option("--yes", dest="confirm", action="store_true",
+                          default=False, help="Do not require confirmation")
+
 TAG_SRC_OPT = make_option("--from", dest="tags_source",
                           default=None, help="File with tag names")
 
@@ -195,6 +200,11 @@ SYNC_OPT = make_option("--sync", dest="do_locking",
                        help="Grab locks while doing the queries"
                        " in order to ensure more consistent results")
 
+_DRY_RUN_OPT = make_option("--dry-run", default=False,
+                          action="store_true",
+                          help="Do not execute the operation, just run the"
+                          " check steps and verify it it could be executed")
+
 
 def ARGS_FIXED(val):
   """Macro-like function denoting a fixed number of arguments"""
@@ -248,23 +258,22 @@ def _SplitKeyVal(opt, data):
   @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
+  if data:
+    for elem in data.split(","):
+      if "=" in elem:
+        key, val = elem.split("=", 1)
       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
+        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
 
 
@@ -273,9 +282,21 @@ def check_ident_key_val(option, opt, value):
 
   """
   if ":" not in value:
-    retval =  (value, {})
+    ident, rest = value, ''
   else:
     ident, rest = value.split(":", 1)
+
+  if ident.startswith(NO_PREFIX):
+    if rest:
+      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):
+    if rest:
+      msg = "Cannot pass options when removing parameter groups: %s" % value
+      raise errors.ParameterError(msg)
+    retval = (ident[len(UN_PREFIX):], None)
+  else:
     kv_dict = _SplitKeyVal(opt, rest)
     retval = (ident, kv_dict)
   return retval
@@ -320,7 +341,7 @@ keyval_option = KeyValOption
 def _ParseArgs(argv, commands, aliases):
   """Parser for the command line arguments.
 
-  This function parses the arguements and returns the function which
+  This function parses the arguments and returns the function which
   must be executed together with its (modified) arguments.
 
   @param argv: the command line
@@ -377,7 +398,7 @@ def _ParseArgs(argv, commands, aliases):
     cmd = aliases[cmd]
 
   func, nargs, parser_opts, usage, description = commands[cmd]
-  parser = OptionParser(option_list=parser_opts,
+  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT],
                         description=description,
                         formatter=TitledHelpFormatter(),
                         usage="%%prog %s %s" % (cmd, usage))
@@ -438,10 +459,10 @@ def AskUser(text, choices=None):
     choices = [('y', True, 'Perform the operation'),
                ('n', False, 'Do not perform the operation')]
   if not choices or not isinstance(choices, list):
-    raise errors.ProgrammerError("Invalid choiches argument to AskUser")
+    raise errors.ProgrammerError("Invalid choices argument to AskUser")
   for entry in choices:
     if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
-      raise errors.ProgrammerError("Invalid choiches element to AskUser")
+      raise errors.ProgrammerError("Invalid choices element to AskUser")
 
   answer = choices[-1][1]
   new_text = []
@@ -602,7 +623,11 @@ def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
   whether to just send the job and print its identifier. It is used in
   order to simplify the implementation of the '--submit' option.
 
+  It will also add the dry-run parameter from the options passed, if true.
+
   """
+  if opts and opts.dry_run:
+    op.dry_run = opts.dry_run
   if opts and opts.submit_only:
     job_id = SendJob([op], cl=cl)
     raise JobSubmittedException(job_id)
@@ -724,7 +749,13 @@ def GenericMain(commands, override=None, aliases=None):
   if aliases is None:
     aliases = {}
 
-  func, options, args = _ParseArgs(sys.argv, commands, aliases)
+  try:
+    func, options, args = _ParseArgs(sys.argv, commands, aliases)
+  except errors.ParameterError, err:
+    result, err_msg = FormatError(err)
+    ToStderr(err_msg)
+    return 1
+
   if func is None: # parse error
     return 1
 
@@ -747,7 +778,7 @@ def GenericMain(commands, override=None, aliases=None):
   except (errors.GenericError, luxi.ProtocolError,
           JobSubmittedException), err:
     result, err_msg = FormatError(err)
-    logging.exception("Error durring command processing")
+    logging.exception("Error during command processing")
     ToStderr(err_msg)
 
   return result
@@ -994,15 +1025,23 @@ class JobExecutor(object):
       cl = GetClient()
     self.cl = cl
     self.verbose = verbose
+    self.jobs = []
 
   def QueueJob(self, name, *ops):
-    """Submit a job for execution.
+    """Record a job for later submit.
 
     @type name: string
     @param name: a description of the job, will be used in WaitJobSet
     """
-    job_id = SendJob(ops, cl=self.cl)
-    self.queue.append((job_id, name))
+    self.queue.append((name, ops))
+
+  def SubmitPending(self):
+    """Submit all pending jobs.
+
+    """
+    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
+    for ((status, data), (name, _)) in zip(results, self.queue):
+      self.jobs.append((status, data, name))
 
   def GetResults(self):
     """Wait for and return the results of all jobs.
@@ -1013,10 +1052,18 @@ class JobExecutor(object):
         there will be the error message
 
     """
+    if not self.jobs:
+      self.SubmitPending()
     results = []
     if self.verbose:
-      ToStdout("Submitted jobs %s", ", ".join(row[0] for row in self.queue))
-    for jid, name in self.queue:
+      ok_jobs = [row[1] for row in self.jobs if row[0]]
+      if ok_jobs:
+        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
+    for submit_status, jid, name in self.jobs:
+      if not submit_status:
+        ToStderr("Failed to submit job for %s: %s", name, jid)
+        results.append((False, jid))
+        continue
       if self.verbose:
         ToStdout("Waiting for job %s for %s...", jid, name)
       try:
@@ -1041,5 +1088,10 @@ class JobExecutor(object):
     if wait:
       return self.GetResults()
     else:
-      for jid, name in self.queue:
-        ToStdout("%s: %s", jid, name)
+      if not self.jobs:
+        self.SubmitPending()
+      for status, result, name in self.jobs:
+        if status:
+          ToStdout("%s: %s", result, name)
+        else:
+          ToStderr("Failure for %s: %s", name, result)