cli.SubmitOpCode: Pass keyword parameter as keyword
[ganeti-local] / lib / cli.py
index 92680cb..e52f8e5 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010 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
@@ -38,6 +38,7 @@ from ganeti import ssconf
 from ganeti import rpc
 from ganeti import ssh
 from ganeti import compat
+from ganeti import netutils
 
 from optparse import (OptionParser, TitledHelpFormatter,
                       Option, OptionValueError)
@@ -86,6 +87,7 @@ __all__ = [
   "MAINTAIN_NODE_HEALTH_OPT",
   "MASTER_NETDEV_OPT",
   "MC_OPT",
+  "MIGRATION_MODE_OPT",
   "NET_OPT",
   "NEW_CLUSTER_CERT_OPT",
   "NEW_CLUSTER_DOMAIN_SECRET_OPT",
@@ -122,6 +124,7 @@ __all__ = [
   "REBOOT_TYPE_OPT",
   "REMOVE_INSTANCE_OPT",
   "REMOVE_UIDS_OPT",
+  "RESERVED_LVS_OPT",
   "ROMAN_OPT",
   "SECONDARY_IP_OPT",
   "SELECT_OS_OPT",
@@ -160,6 +163,7 @@ __all__ = [
   "GenerateTable",
   "AskUser",
   "FormatTimestamp",
+  "FormatLogMessage",
   # Tags functions
   "ListTags",
   "AddTags",
@@ -696,6 +700,12 @@ NONLIVE_OPT = cli_option("--non-live", dest="live",
                          " freeze the instance, save the state, transfer and"
                          " only then resume running on the secondary node)")
 
+MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
+                                default=None,
+                                choices=list(constants.HT_MIGRATION_MODES),
+                                help="Override default migration mode (choose"
+                                " either live or non-live")
+
 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
                                 help="Target node and optional secondary node",
                                 metavar="<pnode>[:<snode>]",
@@ -992,6 +1002,12 @@ REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
                                    " ranges separated by commas, to be"
                                    " removed from the user-id pool"))
 
+RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
+                             action="store", dest="reserved_lvs",
+                             help=("A comma-separated list of reserved"
+                                   " logical volumes names, that will be"
+                                   " ignored by cluster verify"))
+
 ROMAN_OPT = cli_option("--roman",
                        dest="roman_integers", default=False,
                        action="store_true",
@@ -1024,7 +1040,8 @@ def _ParseArgs(argv, commands, aliases):
     binary = argv[0].split("/")[-1]
 
   if len(argv) > 1 and argv[1] == "--version":
-    ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
+    ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
+             constants.RELEASE_VERSION)
     # Quit right away. That way we don't have to care about this special
     # argument. optparse.py does it the same.
     sys.exit(0)
@@ -1182,14 +1199,7 @@ def CalculateOSNames(os_name, os_variants):
     return [os_name]
 
 
-def UsesRPC(fn):
-  def wrapper(*args, **kwargs):
-    rpc.Init()
-    try:
-      return fn(*args, **kwargs)
-    finally:
-      rpc.Shutdown()
-  return wrapper
+UsesRPC = rpc.RunWithRPC
 
 
 def AskUser(text, choices=None):
@@ -1473,7 +1483,7 @@ class StdioJobPollReportCb(JobPollReportCbBase):
 
     """
     ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
-             utils.SafeEncode(log_msg))
+             FormatLogMessage(log_type, log_msg))
 
   def ReportNotChanged(self, job_id, status):
     """Called if a job hasn't changed in a while.
@@ -1491,7 +1501,17 @@ class StdioJobPollReportCb(JobPollReportCbBase):
       self.notified_waitlock = True
 
 
-def PollJob(job_id, cl=None, feedback_fn=None):
+def FormatLogMessage(log_type, log_msg):
+  """Formats a job message according to its type.
+
+  """
+  if log_type != constants.ELOG_MESSAGE:
+    log_msg = str(log_msg)
+
+  return utils.SafeEncode(log_msg)
+
+
+def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
   """Function to poll for the result of a job.
 
   @type job_id: job identified
@@ -1504,15 +1524,18 @@ def PollJob(job_id, cl=None, feedback_fn=None):
   if cl is None:
     cl = GetClient()
 
-  if feedback_fn:
-    reporter = FeedbackFnJobPollReportCb(feedback_fn)
-  else:
-    reporter = StdioJobPollReportCb()
+  if reporter is None:
+    if feedback_fn:
+      reporter = FeedbackFnJobPollReportCb(feedback_fn)
+    else:
+      reporter = StdioJobPollReportCb()
+  elif feedback_fn:
+    raise errors.ProgrammerError("Can't specify reporter and feedback function")
 
   return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
 
 
-def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
+def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
   """Legacy function to submit an opcode.
 
   This is just a simple wrapper over the construction of the processor
@@ -1525,9 +1548,10 @@ def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
 
   SetGenericOpcodeOpts([op], opts)
 
-  job_id = SendJob([op], cl)
+  job_id = SendJob([op], cl=cl)
 
-  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
+  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
+                       reporter=reporter)
 
   return op_results[0]
 
@@ -1624,7 +1648,7 @@ def FormatError(err):
   elif isinstance(err, errors.HooksFailure):
     obuf.write("Failure: hooks general failure: %s" % msg)
   elif isinstance(err, errors.ResolverError):
-    this_host = utils.HostInfo.SysName()
+    this_host = netutils.HostInfo.SysName()
     if err.args[0] == this_host:
       msg = "Failure: can't resolve my own hostname ('%s')"
     else:
@@ -1658,9 +1682,14 @@ def FormatError(err):
   elif isinstance(err, luxi.TimeoutError):
     obuf.write("Timeout while talking to the master daemon. Error:\n"
                "%s" % msg)
+  elif isinstance(err, luxi.PermissionError):
+    obuf.write("It seems you don't have permissions to connect to the"
+               " master daemon.\nPlease retry as a different user.")
   elif isinstance(err, luxi.ProtocolError):
     obuf.write("Unhandled protocol error while talking to the master daemon:\n"
                "%s" % msg)
+  elif isinstance(err, errors.JobLost):
+    obuf.write("Error checking job status: %s" % msg)
   elif isinstance(err, errors.GenericError):
     obuf.write("Unhandled Ganeti error: %s" % msg)
   elif isinstance(err, JobSubmittedException):
@@ -2299,12 +2328,13 @@ class JobExecutor(object):
     assert result
 
     for job_data, status in zip(self.jobs, result):
-      if status[0] in (constants.JOB_STATUS_QUEUED,
-                    constants.JOB_STATUS_WAITLOCK,
-                    constants.JOB_STATUS_CANCELING):
-        # job is still waiting
+      if (isinstance(status, list) and status and
+          status[0] in (constants.JOB_STATUS_QUEUED,
+                        constants.JOB_STATUS_WAITLOCK,
+                        constants.JOB_STATUS_CANCELING)):
+        # job is still present and waiting
         continue
-      # good candidate found
+      # good candidate found (either running job or lost job)
       self.jobs.remove(job_data)
       return job_data
 
@@ -2340,6 +2370,11 @@ class JobExecutor(object):
       try:
         job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
         success = True
+      except errors.JobLost, err:
+        _, job_result = FormatError(err)
+        ToStderr("Job %s for %s has been archived, cannot check its result",
+                 jid, name)
+        success = False
       except (errors.GenericError, luxi.ProtocolError), err:
         _, job_result = FormatError(err)
         success = False