Files
anvil/devstack/runners/fork.py

166 lines
6.2 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
import resource
import signal
import errno
import time
from devstack import exceptions as excp
from devstack import log as logging
from devstack import runner
from devstack import shell as sh
from devstack import trace as tr
from devstack import utils
# Maximum for the number of available file descriptors (when not found)
MAXFD = 2048
MAX_KILL_TRY = 5
SLEEP_TIME = 1
LOG = logging.getLogger("devstack.runners.foreground")
#trace constants
RUN = runner.RUN_TYPE
RUN_TYPE = "FORK"
PID_FN = "PID_FN"
STDOUT_FN = "STDOUT_FN"
STDERR_FN = "STDERR_FN"
NAME = "NAME"
FORK_TEMPL = "%s.fork"
class ForkRunner(runner.Runner):
def __init__(self):
runner.Runner.__init__(self)
def _stop_pid(self, pid):
killed = False
lastmsg = ""
for attempt in range(0, MAX_KILL_TRY):
try:
LOG.info("Attempting to kill pid %s" % (pid))
os.kill(pid, signal.SIGKILL)
LOG.info("Sleeping for %s seconds before next attempt to kill pid %s" % (SLEEP_TIME, pid))
time.sleep(SLEEP_TIME)
except OSError as (ec, msg):
if(ec == errno.ESRCH):
killed = True
break
else:
lastmsg = "[Errno: %s] %s" % (ec, msg)
LOG.info("Sleeping for %s seconds before next attempt to kill pid %s" % (SLEEP_TIME, pid))
time.sleep(SLEEP_TIME)
return killed
def stop(self, name, *args, **kargs):
tracedir = kargs.get("trace_dir")
fn_name = FORK_TEMPL % (name)
(pidfile, stderr, stdout) = self._form_file_names(tracedir, fn_name)
tfname = tr.trace_fn(tracedir, fn_name)
if(isfile(pidfile) and isfile(tfname)):
pid = int(load_file(pidfile).strip())
killed = self._stop_pid(pid)
#trash the files
if(killed):
LOG.info("Killed pid %s" % (pid))
LOG.info("Removing pid file %s" % (pidfile))
unlink(pidfile)
LOG.info("Removing stderr file %s" % (stderr))
unlink(stderr)
LOG.info("Removing stdout file %s" % (stdout))
unlink(stdout)
LOG.info("Removing %s trace file %s" % (name, tfname))
unlink(tfname)
else:
msg = "Could not stop program named %s after %s attempts" % (name, MAX_KILL_TRY)
raise StopException(msg)
else:
msg = "No pid or trace file could be found to terminate at %s" % (tracedir)
raise StopException(msg)
def _form_file_names(self, tracedir, file_name):
pidfile = joinpths(tracedir, file_name + ".pid")
stderr = joinpths(tracedir, file_name + ".stderr")
stdout = joinpths(tracedir, file_name + ".stdout")
return (pidfile, stderr, stdout)
def _fork_start(self, program, appdir, pid_fn, stdout_fn, stderr_fn, *args):
#first child, not the real program
pid = os.fork()
if(pid == 0):
#upon return the calling process shall be the session
#leader of this new session,
#shall be the process group leader of a new process group,
#and shall have no controlling terminal.
os.setsid()
pid = os.fork()
#fork to get daemon out - this time under init control
#and now fully detached (no shell possible)
if(pid == 0):
#move to where application should be
if(appdir):
os.chdir(appdir)
#close other fds
limits = resource.getrlimit(resource.RLIMIT_NOFILE)
mkfd = limits[1]
if(mkfd == resource.RLIM_INFINITY):
mkfd = MAXFD
for fd in range(0, mkfd):
try:
os.close(fd)
except OSError:
#not open, thats ok
pass
#now adjust stderr and stdout
if(stdout_fn):
stdoh = open(stdout_fn, "w")
os.dup2(stdoh.fileno(), sys.stdout.fileno())
if(stderr_fn):
stdeh = open(stderr_fn, "w")
os.dup2(stdeh.fileno(), sys.stderr.fileno())
#now exec...
#the arguments to the child process should
#start with the name of the command being run
actualargs = [program] + list(args)
os.execlp(program, *actualargs)
else:
#write out the child pid
contents = str(pid) + os.linesep
write_file(pid_fn, contents)
#not exit or sys.exit, this is recommended
#since it will do the right cleanups that we want
#not calling any atexit functions, which would
#be bad right now
os._exit(0)
def start(self, name, program, *args, **kargs):
tracedir = kargs.get("trace_dir")
appdir = kargs.get("app_dir")
fn_name = FORK_TEMPL % (name)
(pidfile, stderrfn, stdoutfn) = self._form_file_names(tracedir, fn_name)
tracefn = tr.touch_trace(tracedir, fn_name)
runtrace = tr.Trace(tracefn)
runtrace.trace(RUN, RUN_TYPE)
runtrace.trace(PID_FN, pidfile)
runtrace.trace(STDERR_FN, stderrfn)
runtrace.trace(STDOUT_FN, stdoutfn)
LOG.info("Forking [%s] by running command [%s]" % (name, program))
self._fork_start(program, appdir, pidfile, stdoutfn, stderrfn, *args)
return tracefn