utils.x509: Factorize code to extract X509 certificate
[ganeti-local] / lib / utils / process.py
index be7d774..8b865d6 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2010, 2011, 2012 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
@@ -36,6 +36,7 @@ from cStringIO import StringIO
 
 from ganeti import errors
 from ganeti import constants
+from ganeti import compat
 
 from ganeti.utils import retry as utils_retry
 from ganeti.utils import wrapper as utils_wrapper
@@ -56,7 +57,7 @@ def DisableFork():
   """Disables the use of fork(2).
 
   """
-  global _no_fork # pylint: disable-msg=W0603
+  global _no_fork # pylint: disable=W0603
 
   _no_fork = True
 
@@ -83,7 +84,6 @@ class RunResult(object):
   __slots__ = ["exit_code", "signal", "stdout", "stderr",
                "failed", "fail_reason", "cmd"]
 
-
   def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
                timeout):
     self.cmd = cmd
@@ -141,7 +141,8 @@ def _BuildCmdEnvironment(env, reset):
 
 
 def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
-           interactive=False, timeout=None, noclose_fds=None):
+           interactive=False, timeout=None, noclose_fds=None,
+           _postfork_fn=None):
   """Execute a (shell) command.
 
   The command should not read from its standard input, as it will be
@@ -161,7 +162,7 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
   @type reset_env: boolean
   @param reset_env: whether to reset or keep the default os environment
   @type interactive: boolean
-  @param interactive: weather we pipe stdin, stdout and stderr
+  @param interactive: whether we pipe stdin, stdout and stderr
                       (default behaviour) or run the command interactive
   @type timeout: int
   @param timeout: If not None, timeout in seconds until child process gets
@@ -169,6 +170,7 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
   @type noclose_fds: list
   @param noclose_fds: list of additional (fd >=3) file descriptors to leave
                       open for the child process
+  @param _postfork_fn: Callback run after fork but before timeout (unittest)
   @rtype: L{RunResult}
   @return: RunResult instance
   @raise errors.ProgrammerError: if we call this when forks are disabled
@@ -190,9 +192,9 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
     shell = False
 
   if output:
-    logging.debug("RunCmd %s, output file '%s'", strcmd, output)
+    logging.info("RunCmd %s, output file '%s'", strcmd, output)
   else:
-    logging.debug("RunCmd %s", strcmd)
+    logging.info("RunCmd %s", strcmd)
 
   cmd_env = _BuildCmdEnvironment(env, reset_env)
 
@@ -200,8 +202,11 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
     if output is None:
       out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
                                                      interactive, timeout,
-                                                     noclose_fds)
+                                                     noclose_fds,
+                                                     _postfork_fn=_postfork_fn)
     else:
+      assert _postfork_fn is None, \
+          "_postfork_fn not supported if output provided"
       timeout_action = _TIMEOUT_NONE
       status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds)
       out = err = ""
@@ -251,8 +256,10 @@ def SetupDaemonFDs(output_file, output_fd):
   # Open /dev/null (read-only, only for stdin)
   devnull_fd = os.open(os.devnull, os.O_RDONLY)
 
+  output_close = True
+
   if output_fd is not None:
-    pass
+    output_close = False
   elif output_file is not None:
     # Open output file
     try:
@@ -268,6 +275,12 @@ def SetupDaemonFDs(output_file, output_fd):
   os.dup2(output_fd, 1)
   os.dup2(output_fd, 2)
 
+  if devnull_fd > 2:
+    utils_wrapper.CloseFdNoError(devnull_fd)
+
+  if output_close and output_fd > 2:
+    utils_wrapper.CloseFdNoError(output_fd)
+
 
 def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
                 pidfile=None):
@@ -329,7 +342,7 @@ def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
                                 output, output_fd, pidfile)
             finally:
               # Well, maybe child process failed
-              os._exit(1) # pylint: disable-msg=W0212
+              os._exit(1) # pylint: disable=W0212
         finally:
           utils_wrapper.CloseFdNoError(errpipe_write)
 
@@ -383,7 +396,7 @@ def _StartDaemonChild(errpipe_read, errpipe_write,
     pid = os.fork()
     if pid != 0:
       # Exit first child process
-      os._exit(0) # pylint: disable-msg=W0212
+      os._exit(0) # pylint: disable=W0212
 
     # Make sure pipe is closed on execv* (and thereby notifies
     # original process)
@@ -418,15 +431,15 @@ def _StartDaemonChild(errpipe_read, errpipe_write,
       os.execvp(args[0], args)
     else:
       os.execvpe(args[0], args, env)
-  except: # pylint: disable-msg=W0702
+  except: # pylint: disable=W0702
     try:
       # Report errors to original process
       WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
-    except: # pylint: disable-msg=W0702
+    except: # pylint: disable=W0702
       # Ignore errors in error handling
       pass
 
-  os._exit(1) # pylint: disable-msg=W0212
+  os._exit(1) # pylint: disable=W0212
 
 
 def WriteErrorToFD(fd, err):
@@ -468,7 +481,8 @@ def _WaitForProcess(child, timeout):
 
 
 def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
-                _linger_timeout=constants.CHILD_LINGER_TIMEOUT):
+                _linger_timeout=constants.CHILD_LINGER_TIMEOUT,
+                _postfork_fn=None):
   """Run a command and return its output.
 
   @type  cmd: string or list
@@ -486,6 +500,7 @@ def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
   @type noclose_fds: list
   @param noclose_fds: list of additional (fd >=3) file descriptors to leave
                       open for the child process
+  @param _postfork_fn: Function run after fork but before timeout (unittest)
   @rtype: tuple
   @return: (out, err, status)
 
@@ -514,6 +529,9 @@ def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
                            cwd=cwd,
                            preexec_fn=preexec_fn)
 
+  if _postfork_fn:
+    _postfork_fn(child.pid)
+
   out = StringIO()
   err = StringIO()
 
@@ -681,7 +699,7 @@ def RunParts(dir_name, env=None, reset_env=False):
     else:
       try:
         result = RunCmd([fname], env=env, reset_env=reset_env)
-      except Exception, err: # pylint: disable-msg=W0703
+      except Exception, err: # pylint: disable=W0703
         rr.append((relname, constants.RUNPARTS_ERR, str(err)))
       else:
         rr.append((relname, constants.RUNPARTS_RUN, result))
@@ -827,11 +845,12 @@ def Daemonize(logfile):
 
   @type logfile: str
   @param logfile: the logfile to which we should redirect stdout/stderr
-  @rtype: int
-  @return: the value zero
+  @rtype: tuple; (int, callable)
+  @return: File descriptor of pipe(2) which must be closed to notify parent
+    process and a callable to reopen log files
 
   """
-  # pylint: disable-msg=W0212
+  # pylint: disable=W0212
   # yes, we really want os._exit
 
   # TODO: do another attempt to merge Daemonize and StartDaemon, or at
@@ -864,8 +883,12 @@ def Daemonize(logfile):
       rcode = 0
     os._exit(rcode) # Exit parent of the first child.
 
-  SetupDaemonFDs(logfile, None)
-  return wpipe
+  reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)
+
+  # Open logs for the first time
+  reopen_fn()
+
+  return (wpipe, reopen_fn)
 
 
 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
@@ -952,12 +975,12 @@ def RunInSeparateProcess(fn, *args):
       # Call function
       result = int(bool(fn(*args)))
       assert result in (0, 1)
-    except: # pylint: disable-msg=W0702
+    except: # pylint: disable=W0702
       logging.exception("Error while calling function in separate process")
       # 0 and 1 are reserved for the return value
       result = 33
 
-    os._exit(result) # pylint: disable-msg=W0212
+    os._exit(result) # pylint: disable=W0212
 
   # Parent process