import select
import fcntl
import resource
+import logging
+import signal
from cStringIO import StringIO
-from ganeti import logger
from ganeti import errors
from ganeti import constants
"failed", "fail_reason", "cmd"]
- def __init__(self, exit_code, signal, stdout, stderr, cmd):
+ def __init__(self, exit_code, signal_, stdout, stderr, cmd):
self.cmd = cmd
self.exit_code = exit_code
- self.signal = signal
+ self.signal = signal_
self.stdout = stdout
self.stderr = stderr
- self.failed = (signal is not None or exit_code != 0)
+ self.failed = (signal_ is not None or exit_code != 0)
if self.signal is not None:
self.fail_reason = "terminated by signal %s" % self.signal
else:
self.fail_reason = "unable to determine termination reason"
- if debug and self.failed:
- logger.Debug("Command '%s' failed (%s); output: %s" %
- (self.cmd, self.fail_reason, self.output))
+ if self.failed:
+ logging.debug("Command '%s' failed (%s); output: %s",
+ self.cmd, self.fail_reason, self.output)
def _GetOutput(self):
"""Returns the combined stdout and stderr for easier usage.
else:
strcmd = cmd
shell = True
+ logging.debug("RunCmd '%s'", strcmd)
env = os.environ.copy()
env["LC_ALL"] = "C"
poller = select.poll()
status = child.wait()
if status >= 0:
exitcode = status
- signal = None
+ signal_ = None
else:
exitcode = None
- signal = -status
+ signal_ = -status
- return RunResult(exitcode, signal, out, err, strcmd)
+ return RunResult(exitcode, signal_, out, err, strcmd)
def RemoveFile(filename):
target[k] = template[k]
if missing and logname:
- logger.Debug('%s missing keys %s' %
- (logname, ', '.join(missing)))
+ logging.warning('%s missing keys %s', logname, ', '.join(missing))
def IsProcessAlive(pid):
Returns: true or false, depending on if the pid exists or not
- Remarks: zombie processes treated as not alive
+ Remarks: zombie processes treated as not alive, and giving a pid <=
+ 0 makes the function to return False.
"""
+ if pid <= 0:
+ return False
+
try:
f = open("/proc/%d/status" % pid)
except IOError, err:
return alive
+def ReadPidFile(pidfile):
+ """Read the pid from a file.
+
+ @param pidfile: Path to a file containing the pid to be checked
+ @type pidfile: string (filename)
+ @return: The process id, if the file exista and contains a valid PID,
+ otherwise 0
+ @rtype: int
+
+ """
+ try:
+ pf = open(pidfile, 'r')
+ except EnvironmentError, err:
+ if err.errno != errno.ENOENT:
+ logging.exception("Can't read pid file?!")
+ return 0
+
+ try:
+ pid = int(pf.read())
+ except ValueError, err:
+ logging.info("Can't parse pid file contents", exc_info=True)
+ return 0
+
+ return pid
+
+
def MatchNameComponent(key, name_list):
"""Try to match a name against a list.
name, size = line.split()
size = int(float(size))
except (IndexError, ValueError), err:
- logger.Error("Invalid output from vgs (%s): %s" % (err, line))
+ logging.error("Invalid output from vgs (%s): %s", err, line)
continue
retval[name] = size
return 0
+def DaemonPidFileName(name):
+ """Compute a ganeti pid file absolute path, given the daemon name.
+
+ """
+ return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
+
+
+def WritePidFile(name):
+ """Write the current process pidfile.
+
+ The file will be written to constants.RUN_GANETI_DIR/name.pid
+
+ """
+ pid = os.getpid()
+ pidfilename = DaemonPidFileName(name)
+ if IsProcessAlive(ReadPidFile(pidfilename)):
+ raise errors.GenericError("%s contains a live process" % pidfilename)
+
+ WriteFile(pidfilename, data="%d\n" % pid)
+
+
+def RemovePidFile(name):
+ """Remove the current process pidfile.
+
+ Any errors are ignored.
+
+ """
+ pid = os.getpid()
+ pidfilename = DaemonPidFileName(name)
+ # TODO: we could check here that the file contains our pid
+ try:
+ RemoveFile(pidfilename)
+ except:
+ pass
+
+
+def KillProcess(pid, signal_=signal.SIGTERM, timeout=30):
+ """Kill a process given by its pid.
+
+ @type pid: int
+ @param pid: The PID to terminate.
+ @type signal_: int
+ @param signal_: The signal to send, by default SIGTERM
+ @type timeout: int
+ @param timeout: The timeout after which, if the process is still alive,
+ a SIGKILL will be sent. If not positive, no such checking
+ will be done
+
+ """
+ if pid <= 0:
+ # kill with pid=0 == suicide
+ raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
+
+ if not IsProcessAlive(pid):
+ return
+ os.kill(pid, signal_)
+ if timeout <= 0:
+ return
+ end = time.time() + timeout
+ while time.time() < end and IsProcessAlive(pid):
+ time.sleep(0.1)
+ if IsProcessAlive(pid):
+ os.kill(pid, signal.SIGKILL)
+
+
def FindFile(name, search_path, test=os.path.exists):
"""Look for a filesystem object in a given path.
return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
(vgname, minsize, vgsize))
return None
+
+
+def LockedMethod(fn):
+ """Synchronized object access decorator.
+
+ This decorator is intended to protect access to an object using the
+ object's own lock which is hardcoded to '_lock'.
+
+ """
+ def wrapper(self, *args, **kwargs):
+ assert hasattr(self, '_lock')
+ lock = self._lock
+ lock.acquire()
+ try:
+ result = fn(self, *args, **kwargs)
+ finally:
+ lock.release()
+ return result
+ return wrapper
+
+
+def LockFile(fd):
+ """Locks a file using POSIX locks.
+
+ """
+ try:
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except IOError, err:
+ if err.errno == errno.EAGAIN:
+ raise errors.LockError("File already locked")
+ raise
+
+
+class SignalHandler(object):
+ """Generic signal handler class.
+
+ It automatically restores the original handler when deconstructed or when
+ Reset() is called. You can either pass your own handler function in or query
+ the "called" attribute to detect whether the signal was sent.
+
+ """
+ def __init__(self, signum):
+ """Constructs a new SignalHandler instance.
+
+ @param signum: Single signal number or set of signal numbers
+
+ """
+ if isinstance(signum, (int, long)):
+ self.signum = set([signum])
+ else:
+ self.signum = set(signum)
+
+ self.called = False
+
+ self._previous = {}
+ try:
+ for signum in self.signum:
+ # Setup handler
+ prev_handler = signal.signal(signum, self._HandleSignal)
+ try:
+ self._previous[signum] = prev_handler
+ except:
+ # Restore previous handler
+ signal.signal(signum, prev_handler)
+ raise
+ except:
+ # Reset all handlers
+ self.Reset()
+ # Here we have a race condition: a handler may have already been called,
+ # but there's not much we can do about it at this point.
+ raise
+
+ def __del__(self):
+ self.Reset()
+
+ def Reset(self):
+ """Restore previous handler.
+
+ """
+ for signum, prev_handler in self._previous.items():
+ signal.signal(signum, prev_handler)
+ # If successful, remove from dict
+ del self._previous[signum]
+
+ def Clear(self):
+ """Unsets "called" flag.
+
+ This function can be used in case a signal may arrive several times.
+
+ """
+ self.called = False
+
+ def _HandleSignal(self, signum, frame):
+ """Actual signal handling function.
+
+ """
+ # This is not nice and not absolutely atomic, but it appears to be the only
+ # solution in Python -- there are no atomic types.
+ self.called = True