[ovs-dev] [PATCH V11 12/17] python tests: Prepare porting Python daemon on Windows
Guru Shetty
guru at ovn.org
Wed Aug 3 16:07:03 UTC 2016
On 2 August 2016 at 10:45, Paul Boca <pboca at cloudbasesolutions.com> wrote:
> Renamed daemon.py to daemon_unix.py and implemented a wrapper over it.
>
> Signed-off-by: Paul-Daniel Boca <pboca at cloudbasesolutions.com>
> Acked-by: Alin Gabriel Serdean <aserdean at cloudbasesolutions.com>
>
As a standalone commit, this likely fails because it
calls ovs.daemon_windows which does not exist. I also notice that you have
made additional changes in daemon_unix.py. Please do that as a separate
commit with proper commit message.
Also, to be sure, please add the following to your .gitconfig
[diff]
renames = copies
> ---
> V8: Initial commit.
> V9: No changes
> V10: Fixed exception on Unix
> V11: No changes
> ---
> python/automake.mk | 1 +
> python/ovs/daemon.py | 489 ++----------------------------------------
> python/ovs/daemon_unix.py | 530
> ++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 551 insertions(+), 469 deletions(-)
> create mode 100644 python/ovs/daemon_unix.py
>
> diff --git a/python/automake.mk b/python/automake.mk
> index 1c8fa38..4d3fcb6 100644
> --- a/python/automake.mk
> +++ b/python/automake.mk
> @@ -11,6 +11,7 @@ ovstest_pyfiles = \
> ovs_pyfiles = \
> python/ovs/__init__.py \
> python/ovs/daemon.py \
> + python/ovs/daemon_unix.py \
> python/ovs/fcntl_win.py \
> python/ovs/db/__init__.py \
> python/ovs/db/data.py \
> diff --git a/python/ovs/daemon.py b/python/ovs/daemon.py
> index bd06195..b1d6c36 100644
> --- a/python/ovs/daemon.py
> +++ b/python/ovs/daemon.py
> @@ -1,4 +1,4 @@
> -# Copyright (c) 2010, 2011, 2012 Nicira, Inc.
> +# Copyright (c) 2016 Cloudbase Solutions Srl
> #
> # Licensed under the Apache License, Version 2.0 (the "License");
> # you may not use this file except in compliance with the License.
> @@ -12,515 +12,66 @@
> # See the License for the specific language governing permissions and
> # limitations under the License.
>
> -import errno
> -import fcntl
> -import os
> -import resource
> -import signal
> import sys
> -import time
>
> -import ovs.dirs
> -import ovs.fatal_signal
> -import ovs.process
> -import ovs.socket_util
> -import ovs.timeval
> -import ovs.util
> -import ovs.vlog
> +# This is only a wrapper over Linux implementations
> +if sys.platform != "win32":
> + import ovs.daemon_unix as daemon_util
>
> -vlog = ovs.vlog.Vlog("daemon")
> -
> -# --detach: Should we run in the background?
> -_detach = False
> -
> -# --pidfile: Name of pidfile (null if none).
> -_pidfile = None
> -
> -# Our pidfile's inode and device, if we have created one.
> -_pidfile_dev = None
> -_pidfile_ino = None
> -
> -# --overwrite-pidfile: Create pidfile even if one already exists and is
> locked?
> -_overwrite_pidfile = False
> -
> -# --no-chdir: Should we chdir to "/"?
> -_chdir = True
> -
> -# --monitor: Should a supervisory process monitor the daemon and restart
> it if
> -# it dies due to an error signal?
> -_monitor = False
> -
> -# File descriptor used by daemonize_start() and daemonize_complete().
> -_daemonize_fd = None
> -
> -RESTART_EXIT_CODE = 5
> +RESTART_EXIT_CODE = daemon_util.RESTART_EXIT_CODE
>
>
> def make_pidfile_name(name):
> - """Returns the file name that would be used for a pidfile if 'name'
> were
> - provided to set_pidfile()."""
> - if name is None or name == "":
> - return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)
> - else:
> - return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)
> + return daemon_util.make_pidfile_name(name)
>
>
> def set_pidfile(name):
> - """Sets up a following call to daemonize() to create a pidfile named
> - 'name'. If 'name' begins with '/', then it is treated as an absolute
> path.
> - Otherwise, it is taken relative to ovs.util.RUNDIR, which is
> - $(prefix)/var/run by default.
> -
> - If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is
> - used."""
> - global _pidfile
> - _pidfile = make_pidfile_name(name)
> + daemon_util.set_pidfile(name)
>
>
> def set_no_chdir():
> - """Sets that we do not chdir to "/"."""
> - global _chdir
> - _chdir = False
> + daemon_util.set_no_chdir()
>
>
> def ignore_existing_pidfile():
> - """Normally, daemonize() or daemonize_start() will terminate the
> program
> - with a message if a locked pidfile already exists. If this function
> is
> - called, an existing pidfile will be replaced, with a warning."""
> - global _overwrite_pidfile
> - _overwrite_pidfile = True
> + daemon_util.ignore_existing_pidfile()
>
>
> def set_detach():
> - """Sets up a following call to daemonize() to detach from the
> foreground
> - session, running this process in the background."""
> - global _detach
> - _detach = True
> + daemon_util.set_detach()
>
>
> def get_detach():
> - """Will daemonize() really detach?"""
> - return _detach
> + return daemon_util.get_detach()
>
>
> def set_monitor():
> - """Sets up a following call to daemonize() to fork a supervisory
> process to
> - monitor the daemon and restart it if it dies due to an error
> signal."""
> - global _monitor
> - _monitor = True
> -
> -
> -def _fatal(msg):
> - vlog.err(msg)
> - sys.stderr.write("%s\n" % msg)
> - sys.exit(1)
> -
> -
> -def _make_pidfile():
> - """If a pidfile has been configured, creates it and stores the running
> - process's pid in it. Ensures that the pidfile will be deleted when
> the
> - process exits."""
> - pid = os.getpid()
> -
> - # Create a temporary pidfile.
> - tmpfile = "%s.tmp%d" % (_pidfile, pid)
> - ovs.fatal_signal.add_file_to_unlink(tmpfile)
> - try:
> - # This is global to keep Python from garbage-collecting and
> - # therefore closing our file after this function exits. That
> would
> - # unlock the lock for us, and we don't want that.
> - global file_handle
> -
> - file_handle = open(tmpfile, "w")
> - except IOError as e:
> - _fatal("%s: create failed (%s)" % (tmpfile, e.strerror))
> -
> - try:
> - s = os.fstat(file_handle.fileno())
> - except IOError as e:
> - _fatal("%s: fstat failed (%s)" % (tmpfile, e.strerror))
> -
> - try:
> - file_handle.write("%s\n" % pid)
> - file_handle.flush()
> - except OSError as e:
> - _fatal("%s: write failed: %s" % (tmpfile, e.strerror))
> -
> - try:
> - fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
> - except IOError as e:
> - _fatal("%s: fcntl failed: %s" % (tmpfile, e.strerror))
> -
> - # Rename or link it to the correct name.
> - if _overwrite_pidfile:
> - try:
> - os.rename(tmpfile, _pidfile)
> - except OSError as e:
> - _fatal("failed to rename \"%s\" to \"%s\" (%s)"
> - % (tmpfile, _pidfile, e.strerror))
> - else:
> - while True:
> - try:
> - os.link(tmpfile, _pidfile)
> - error = 0
> - except OSError as e:
> - error = e.errno
> - if error == errno.EEXIST:
> - _check_already_running()
> - elif error != errno.EINTR:
> - break
> - if error:
> - _fatal("failed to link \"%s\" as \"%s\" (%s)"
> - % (tmpfile, _pidfile, os.strerror(error)))
> -
> - # Ensure that the pidfile will get deleted on exit.
> - ovs.fatal_signal.add_file_to_unlink(_pidfile)
> -
> - # Delete the temporary pidfile if it still exists.
> - if not _overwrite_pidfile:
> - error = ovs.fatal_signal.unlink_file_now(tmpfile)
> - if error:
> - _fatal("%s: unlink failed (%s)" % (tmpfile,
> os.strerror(error)))
> -
> - global _pidfile_dev
> - global _pidfile_ino
> - _pidfile_dev = s.st_dev
> - _pidfile_ino = s.st_ino
> + daemon_util.set_monitor()
>
>
> def daemonize():
> - """If configured with set_pidfile() or set_detach(), creates the pid
> file
> - and detaches from the foreground session."""
> - daemonize_start()
> - daemonize_complete()
> -
> -
> -def _waitpid(pid, options):
> - while True:
> - try:
> - return os.waitpid(pid, options)
> - except OSError as e:
> - if e.errno == errno.EINTR:
> - pass
> - return -e.errno, 0
> -
> -
> -def _fork_and_wait_for_startup():
> - try:
> - rfd, wfd = os.pipe()
> - except OSError as e:
> - sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))
> - sys.exit(1)
> -
> - try:
> - pid = os.fork()
> - except OSError as e:
> - sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))
> - sys.exit(1)
> -
> - if pid > 0:
> - # Running in parent process.
> - os.close(wfd)
> - ovs.fatal_signal.fork()
> - while True:
> - try:
> - s = os.read(rfd, 1)
> - error = 0
> - except OSError as e:
> - s = ""
> - error = e.errno
> - if error != errno.EINTR:
> - break
> - if len(s) != 1:
> - retval, status = _waitpid(pid, 0)
> - if retval == pid:
> - if os.WIFEXITED(status) and os.WEXITSTATUS(status):
> - # Child exited with an error. Convey the same error
> to
> - # our parent process as a courtesy.
> - sys.exit(os.WEXITSTATUS(status))
> - else:
> - sys.stderr.write("fork child failed to signal "
> - "startup (%s)\n"
> - % ovs.process.status_msg(status))
> - else:
> - assert retval < 0
> - sys.stderr.write("waitpid failed (%s)\n"
> - % os.strerror(-retval))
> - sys.exit(1)
> -
> - os.close(rfd)
> - else:
> - # Running in parent process.
> - os.close(rfd)
> - ovs.timeval.postfork()
> -
> - global _daemonize_fd
> - _daemonize_fd = wfd
> - return pid
> -
> -
> -def _fork_notify_startup(fd):
> - if fd is not None:
> - error, bytes_written = ovs.socket_util.write_fully(fd, "0")
> - if error:
> - sys.stderr.write("could not write to pipe\n")
> - sys.exit(1)
> - os.close(fd)
> -
> -
> -def _should_restart(status):
> - global RESTART_EXIT_CODE
> -
> - if os.WIFEXITED(status) and os.WEXITSTATUS(status) ==
> RESTART_EXIT_CODE:
> - return True
> -
> - if os.WIFSIGNALED(status):
> - for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE",
> "SIGILL",
> - "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):
> - if os.WTERMSIG(status) == getattr(signal, signame, None):
> - return True
> - return False
> -
> -
> -def _monitor_daemon(daemon_pid):
> - # XXX should log daemon's stderr output at startup time
> - # XXX should use setproctitle module if available
> - last_restart = None
> - while True:
> - retval, status = _waitpid(daemon_pid, 0)
> - if retval < 0:
> - sys.stderr.write("waitpid failed\n")
> - sys.exit(1)
> - elif retval == daemon_pid:
> - status_msg = ("pid %d died, %s"
> - % (daemon_pid, ovs.process.status_msg(status)))
> -
> - if _should_restart(status):
> - if os.WCOREDUMP(status):
> - # Disable further core dumps to save disk space.
> - try:
> - resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
> - except resource.error:
> - vlog.warn("failed to disable core dumps")
> -
> - # Throttle restarts to no more than once every 10 seconds.
> - if (last_restart is not None and
> - ovs.timeval.msec() < last_restart + 10000):
> - vlog.warn("%s, waiting until 10 seconds since last "
> - "restart" % status_msg)
> - while True:
> - now = ovs.timeval.msec()
> - wakeup = last_restart + 10000
> - if now > wakeup:
> - break
> - sys.stdout.write("sleep %f\n" % (
> - (wakeup - now) / 1000.0))
> - time.sleep((wakeup - now) / 1000.0)
> - last_restart = ovs.timeval.msec()
> -
> - vlog.err("%s, restarting" % status_msg)
> - daemon_pid = _fork_and_wait_for_startup()
> - if not daemon_pid:
> - break
> - else:
> - vlog.info("%s, exiting" % status_msg)
> - sys.exit(0)
> -
> - # Running in new daemon process.
> -
> -
> -def _close_standard_fds():
> - """Close stdin, stdout, stderr. If we're started from e.g. an SSH
> session,
> - then this keeps us from holding that session open artificially."""
> - null_fd = ovs.socket_util.get_null_fd()
> - if null_fd >= 0:
> - os.dup2(null_fd, 0)
> - os.dup2(null_fd, 1)
> - os.dup2(null_fd, 2)
> + daemon_util.daemonize()
>
>
> def daemonize_start():
> - """If daemonization is configured, then starts daemonization, by
> forking
> - and returning in the child process. The parent process hangs around
> until
> - the child lets it know either that it completed startup successfully
> (by
> - calling daemon_complete()) or that it failed to start up (by exiting
> with a
> - nonzero exit code)."""
> -
> - if _detach:
> - if _fork_and_wait_for_startup() > 0:
> - # Running in parent process.
> - sys.exit(0)
> -
> - # Running in daemon or monitor process.
> - os.setsid()
> -
> - if _monitor:
> - saved_daemonize_fd = _daemonize_fd
> - daemon_pid = _fork_and_wait_for_startup()
> - if daemon_pid > 0:
> - # Running in monitor process.
> - _fork_notify_startup(saved_daemonize_fd)
> - _close_standard_fds()
> - _monitor_daemon(daemon_pid)
> - # Running in daemon process
> -
> - if _pidfile:
> - _make_pidfile()
> + daemon_util.daemonize_start()
>
>
> def daemonize_complete():
> - """If daemonization is configured, then this function notifies the
> parent
> - process that the child process has completed startup successfully."""
> - _fork_notify_startup(_daemonize_fd)
> -
> - if _detach:
> - if _chdir:
> - os.chdir("/")
> - _close_standard_fds()
> + daemon_util.daemonize_complete()
>
>
> def usage():
> - sys.stdout.write("""
> -Daemon options:
> - --detach run in background as daemon
> - --no-chdir do not chdir to '/'
> - --pidfile[=FILE] create pidfile (default: %s/%s.pid)
> - --overwrite-pidfile with --pidfile, start even if already running
> -""" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))
> -
> -
> -def __read_pidfile(pidfile, delete_if_stale):
> - if _pidfile_dev is not None:
> - try:
> - s = os.stat(pidfile)
> - if s.st_ino == _pidfile_ino and s.st_dev == _pidfile_dev:
> - # It's our own pidfile. We can't afford to open it,
> - # because closing *any* fd for a file that a process
> - # has locked also releases all the locks on that file.
> - #
> - # Fortunately, we know the associated pid anyhow.
> - return os.getpid()
> - except OSError:
> - pass
> + daemon_util.usage()
>
> - try:
> - file_handle = open(pidfile, "r+")
> - except IOError as e:
> - if e.errno == errno.ENOENT and delete_if_stale:
> - return 0
> - vlog.warn("%s: open: %s" % (pidfile, e.strerror))
> - return -e.errno
>
> - # Python fcntl doesn't directly support F_GETLK so we have to just try
> - # to lock it.
> - try:
> - fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
> -
> - # pidfile exists but wasn't locked by anyone. Now we have the
> lock.
> - if not delete_if_stale:
> - file_handle.close()
> - vlog.warn("%s: pid file is stale" % pidfile)
> - return -errno.ESRCH
> -
> - # Is the file we have locked still named 'pidfile'?
> - try:
> - raced = False
> - s = os.stat(pidfile)
> - s2 = os.fstat(file_handle.fileno())
> - if s.st_ino != s2.st_ino or s.st_dev != s2.st_dev:
> - raced = True
> - except IOError:
> - raced = True
> - if raced:
> - vlog.warn("%s: lost race to delete pidfile" % pidfile)
> - return -errno.EALREADY
> -
> - # We won the right to delete the stale pidfile.
> - try:
> - os.unlink(pidfile)
> - except IOError as e:
> - vlog.warn("%s: failed to delete stale pidfile (%s)"
> - % (pidfile, e.strerror))
> - return -e.errno
> - else:
> - vlog.dbg("%s: deleted stale pidfile" % pidfile)
> - file_handle.close()
> - return 0
> - except IOError as e:
> - if e.errno not in [errno.EACCES, errno.EAGAIN]:
> - vlog.warn("%s: fcntl: %s" % (pidfile, e.strerror))
> - return -e.errno
> -
> - # Someone else has the pidfile locked.
> - try:
> - try:
> - error = int(file_handle.readline())
> - except IOError as e:
> - vlog.warn("%s: read: %s" % (pidfile, e.strerror))
> - error = -e.errno
> - except ValueError:
> - vlog.warn("%s does not contain a pid" % pidfile)
> - error = -errno.EINVAL
> -
> - return error
> - finally:
> - try:
> - file_handle.close()
> - except IOError:
> - pass
> -
> -
> -def read_pidfile(pidfile):
> - """Opens and reads a PID from 'pidfile'. Returns the positive PID if
> - successful, otherwise a negative errno value."""
> - return __read_pidfile(pidfile, False)
> -
> -
> -def _check_already_running():
> - pid = __read_pidfile(_pidfile, True)
> - if pid > 0:
> - _fatal("%s: already running as pid %d, aborting" % (_pidfile,
> pid))
> - elif pid < 0:
> - _fatal("%s: pidfile check failed (%s), aborting"
> - % (_pidfile, os.strerror(pid)))
> +def read_pidfile(pidfile_name):
> + return daemon_util.read_pidfile(pidfile_name)
>
>
> def add_args(parser):
> - """Populates 'parser', an ArgumentParser allocated using the argparse
> - module, with the command line arguments required by the daemon
> module."""
> -
> - pidfile = make_pidfile_name(None)
> -
> - group = parser.add_argument_group(title="Daemon Options")
> - group.add_argument("--detach", action="store_true",
> - help="Run in background as a daemon.")
> - group.add_argument("--no-chdir", action="store_true",
> - help="Do not chdir to '/'.")
> - group.add_argument("--monitor", action="store_true",
> - help="Monitor %s process." % ovs.util.PROGRAM_NAME)
> - group.add_argument("--pidfile", nargs="?", const=pidfile,
> - help="Create pidfile (default %s)." % pidfile)
> - group.add_argument("--overwrite-pidfile", action="store_true",
> - help="With --pidfile, start even if already running.")
> + daemon_util.add_args(parser)
>
>
> def handle_args(args):
> - """Handles daemon module settings in 'args'. 'args' is an object
> - containing values parsed by the parse_args() method of
> ArgumentParser. The
> - parent ArgumentParser should have been prepared by add_args() before
> - calling parse_args()."""
> -
> - if args.detach:
> - set_detach()
> -
> - if args.no_chdir:
> - set_no_chdir()
> -
> - if args.pidfile:
> - set_pidfile(args.pidfile)
> -
> - if args.overwrite_pidfile:
> - ignore_existing_pidfile()
> -
> - if args.monitor:
> - set_monitor()
> + daemon_util.handle_args(args)
> diff --git a/python/ovs/daemon_unix.py b/python/ovs/daemon_unix.py
> new file mode 100644
> index 0000000..3bd56af
> --- /dev/null
> +++ b/python/ovs/daemon_unix.py
> @@ -0,0 +1,530 @@
> +# Copyright (c) 2010, 2011, 2012, 2016 Nicira, Inc.
> +#
> +# 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 errno
> +import os
> +import signal
> +import sys
> +import time
> +
> +import ovs.dirs
> +import ovs.fatal_signal
> +import ovs.process
> +import ovs.socket_util
> +import ovs.timeval
> +import ovs.util
> +import ovs.vlog
> +
> +import fcntl
> +import resource
> +
> +vlog = ovs.vlog.Vlog("daemon")
> +
> +# --detach: Should we run in the background?
> +_detach = False
> +
> +# --pidfile: Name of pidfile (null if none).
> +_pidfile = None
> +
> +# Our pidfile's inode and device, if we have created one.
> +_pidfile_dev = None
> +_pidfile_ino = None
> +
> +# --overwrite-pidfile: Create pidfile even if one already exists and is
> locked?
> +_overwrite_pidfile = False
> +
> +# --no-chdir: Should we chdir to "/"?
> +_chdir = True
> +
> +# --monitor: Should a supervisory process monitor the daemon and restart
> it if
> +# it dies due to an error signal?
> +_monitor = False
> +
> +# File descriptor used by daemonize_start() and daemonize_complete().
> +_daemonize_fd = None
> +
> +
> +RESTART_EXIT_CODE = 5
> +
> +
> +def make_pidfile_name(name):
> + """Returns the file name that would be used for a pidfile if 'name'
> were
> + provided to set_pidfile()."""
> + if name is None or name == "":
> + return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)
> + else:
> + return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)
> +
> +
> +def set_pidfile(name):
> + """Sets up a following call to daemonize() to create a pidfile named
> + 'name'. If 'name' begins with '/', then it is treated as an absolute
> path.
> + Otherwise, it is taken relative to ovs.util.RUNDIR, which is
> + $(prefix)/var/run by default.
> +
> + If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is
> + used."""
> + global _pidfile
> + _pidfile = make_pidfile_name(name)
> +
> +
> +def set_no_chdir():
> + """Sets that we do not chdir to "/"."""
> + global _chdir
> + _chdir = False
> +
> +
> +def ignore_existing_pidfile():
> + """Normally, daemonize() or daemonize_start() will terminate the
> program
> + with a message if a locked pidfile already exists. If this function
> is
> + called, an existing pidfile will be replaced, with a warning."""
> + global _overwrite_pidfile
> + _overwrite_pidfile = True
> +
> +
> +def set_detach():
> + """Sets up a following call to daemonize() to detach from the
> foreground
> + session, running this process in the background."""
> + global _detach
> + _detach = True
> +
> +
> +def get_detach():
> + """Will daemonize() really detach?"""
> + return _detach
> +
> +
> +def set_monitor():
> + """Sets up a following call to daemonize() to fork a supervisory
> process to
> + monitor the daemon and restart it if it dies due to an error
> signal."""
> + global _monitor
> + _monitor = True
> +
> +
> +def _fatal(msg):
> + vlog.err(msg)
> + sys.stderr.write("%s\n" % msg)
> + sys.exit(1)
> +
> +
> +def _make_pidfile():
> + """If a pidfile has been configured, creates it and stores the running
> + process's pid in it. Ensures that the pidfile will be deleted when
> the
> + process exits."""
> + pid = os.getpid()
> +
> + # Create a temporary pidfile.
> + tmpfile = "%s.tmp%d" % (_pidfile, pid)
> + ovs.fatal_signal.add_file_to_unlink(tmpfile)
> +
> + try:
> + # This is global to keep Python from garbage-collecting and
> + # therefore closing our file after this function exits. That
> would
> + # unlock the lock for us, and we don't want that.
> + global file_handle
> +
> + file_handle = open(tmpfile, "w")
> + except IOError as e:
> + _fatal("%s: create failed (%s)" % (tmpfile, e.strerror))
> +
> + try:
> + s = os.fstat(file_handle.fileno())
> + except IOError as e:
> + _fatal("%s: fstat failed (%s)" % (tmpfile, e.strerror))
> +
> + try:
> + file_handle.write("%s\n" % pid)
> + file_handle.flush()
> + except OSError as e:
> + _fatal("%s: write failed: %s" % (tmpfile, e.strerror))
> +
> + try:
> + fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
> + except IOError as e:
> + _fatal("%s: fcntl failed: %s" % (tmpfile, e.strerror))
> +
> + # Rename or link it to the correct name.
> + if _overwrite_pidfile:
> + try:
> + os.rename(tmpfile, _pidfile)
> + except OSError as e:
> + _fatal("failed to rename \"%s\" to \"%s\" (%s)"
> + % (tmpfile, _pidfile, e.strerror))
> + else:
> + while True:
> + try:
> + os.link(tmpfile, _pidfile)
> + error = 0
> + except OSError as e:
> + error = e.errno
> + if error == errno.EEXIST:
> + _check_already_running()
> + elif error != errno.EINTR:
> + break
> + if error:
> + _fatal("failed to link \"%s\" as \"%s\" (%s)"
> + % (tmpfile, _pidfile, os.strerror(error)))
> +
> + # Ensure that the pidfile will get deleted on exit.
> + ovs.fatal_signal.add_file_to_unlink(_pidfile)
> +
> + # Delete the temporary pidfile if it still exists.
> + if not _overwrite_pidfile:
> + error = ovs.fatal_signal.unlink_file_now(tmpfile)
> + if error:
> + _fatal("%s: unlink failed (%s)"
> + % (tmpfile, os.strerror(error)))
> +
> + global _pidfile_dev
> + global _pidfile_ino
> + _pidfile_dev = s.st_dev
> + _pidfile_ino = s.st_ino
> +
> +
> +def daemonize():
> + """If configured with set_pidfile() or set_detach(), creates the pid
> file
> + and detaches from the foreground session."""
> + daemonize_start()
> + daemonize_complete()
> +
> +
> +def _waitpid(pid, options):
> + while True:
> + try:
> + return os.waitpid(pid, options)
> + except OSError as e:
> + if e.errno == errno.EINTR:
> + pass
> + return -e.errno, 0
> +
> +
> +def _fork_and_wait_for_startup():
> + try:
> + rfd, wfd = os.pipe()
> + except OSError as e:
> + sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))
> + sys.exit(1)
> +
> + try:
> + pid = os.fork()
> + except OSError as e:
> + sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))
> + sys.exit(1)
> +
> + if pid > 0:
> + # Running in parent process.
> + os.close(wfd)
> + ovs.fatal_signal.fork()
> + while True:
> + try:
> + s = os.read(rfd, 1)
> + error = 0
> + except OSError as e:
> + s = ""
> + error = e.errno
> + if error != errno.EINTR:
> + break
> + if len(s) != 1:
> + retval, status = _waitpid(pid, 0)
> + if retval == pid:
> + if os.WIFEXITED(status) and os.WEXITSTATUS(status):
> + # Child exited with an error. Convey the same error
> to
> + # our parent process as a courtesy.
> + sys.exit(os.WEXITSTATUS(status))
> + else:
> + sys.stderr.write("fork child failed to signal "
> + "startup (%s)\n"
> + % ovs.process.status_msg(status))
> + else:
> + assert retval < 0
> + sys.stderr.write("waitpid failed (%s)\n"
> + % os.strerror(-retval))
> + sys.exit(1)
> +
> + os.close(rfd)
> + else:
> + # Running in child process.
> + os.close(rfd)
> + ovs.timeval.postfork()
> +
> + global _daemonize_fd
> + _daemonize_fd = wfd
> + return pid
> +
> +
> +def _fork_notify_startup(fd):
> + if fd is not None:
> + error, bytes_written = ovs.socket_util.write_fully(fd, "0")
> + if error:
> + sys.stderr.write("could not write to pipe\n")
> + sys.exit(1)
> + os.close(fd)
> +
> +
> +def _should_restart(status):
> + global RESTART_EXIT_CODE
> +
> + if os.WIFEXITED(status) and os.WEXITSTATUS(status) ==
> RESTART_EXIT_CODE:
> + return True
> +
> + if os.WIFSIGNALED(status):
> + for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE",
> "SIGILL",
> + "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):
> + if os.WTERMSIG(status) == getattr(signal, signame, None):
> + return True
> + return False
> +
> +
> +def _monitor_daemon(daemon_pid):
> + # XXX should log daemon's stderr output at startup time
> + # XXX should use setproctitle module if available
> + last_restart = None
> + while True:
> + retval, status = _waitpid(daemon_pid, 0)
> + if retval < 0:
> + sys.stderr.write("waitpid failed\n")
> + sys.exit(1)
> + elif retval == daemon_pid:
> + status_msg = ("pid %d died, %s"
> + % (daemon_pid, ovs.process.status_msg(status)))
> +
> + if _should_restart(status):
> + if os.WCOREDUMP(status):
> + # Disable further core dumps to save disk space.
> + try:
> + resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
> + except resource.error:
> + vlog.warn("failed to disable core dumps")
> +
> + # Throttle restarts to no more than once every 10 seconds.
> + if (last_restart is not None and
> + ovs.timeval.msec() < last_restart + 10000):
> + vlog.warn("%s, waiting until 10 seconds since last "
> + "restart" % status_msg)
> + while True:
> + now = ovs.timeval.msec()
> + wakeup = last_restart + 10000
> + if now > wakeup:
> + break
> + sys.stdout.write("sleep %f\n" % (
> + (wakeup - now) / 1000.0))
> + time.sleep((wakeup - now) / 1000.0)
> + last_restart = ovs.timeval.msec()
> +
> + vlog.err("%s, restarting" % status_msg)
> + daemon_pid = _fork_and_wait_for_startup()
> + if not daemon_pid:
> + break
> + else:
> + vlog.info("%s, exiting" % status_msg)
> + sys.exit(0)
> +
> + # Running in new daemon process.
> +
> +
> +def _close_standard_fds():
> + """Close stdin, stdout, stderr. If we're started from e.g. an SSH
> session,
> + then this keeps us from holding that session open artificially."""
> + null_fd = ovs.socket_util.get_null_fd()
> + if null_fd >= 0:
> + os.dup2(null_fd, 0)
> + os.dup2(null_fd, 1)
> + os.dup2(null_fd, 2)
> +
> +
> +def daemonize_start():
> + """If daemonization is configured, then starts daemonization, by
> forking
> + and returning in the child process. The parent process hangs around
> until
> + the child lets it know either that it completed startup successfully
> (by
> + calling daemon_complete()) or that it failed to start up (by exiting
> with a
> + nonzero exit code)."""
> +
> + if _detach:
> + if _fork_and_wait_for_startup() > 0:
> + # Running in parent process.
> + sys.exit(0)
> +
> + # Running in daemon or monitor process.
> + os.setsid()
> +
> + if _monitor:
> + saved_daemonize_fd = _daemonize_fd
> + daemon_pid = _fork_and_wait_for_startup()
> + if daemon_pid > 0:
> + # Running in monitor process.
> + _fork_notify_startup(saved_daemonize_fd)
> + _close_standard_fds()
> + _monitor_daemon(daemon_pid)
> + # Running in daemon process
> +
> + if _pidfile:
> + _make_pidfile()
> +
> +
> +def daemonize_complete():
> + """If daemonization is configured, then this function notifies the
> parent
> + process that the child process has completed startup successfully."""
> + _fork_notify_startup(_daemonize_fd)
> +
> + if _detach:
> + if _chdir:
> + os.chdir("/")
> + _close_standard_fds()
> +
> +
> +def usage():
> + sys.stdout.write("""
> +Daemon options:
> + --detach run in background as daemon
> + --no-chdir do not chdir to '/'
> + --pidfile[=FILE] create pidfile (default: %s/%s.pid)
> + --overwrite-pidfile with --pidfile, start even if already running
> +""" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))
> +
> +
> +def __read_pidfile(pidfile, delete_if_stale):
> + if _pidfile_dev is not None:
> + try:
> + s = os.stat(pidfile)
> + if s.st_ino == _pidfile_ino and s.st_dev == _pidfile_dev:
> + # It's our own pidfile. We can't afford to open it,
> + # because closing *any* fd for a file that a process
> + # has locked also releases all the locks on that file.
> + #
> + # Fortunately, we know the associated pid anyhow.
> + return os.getpid()
> + except OSError:
> + pass
> +
> + try:
> + file_handle = open(pidfile, "r+")
> + except IOError as e:
> + if e.errno == errno.ENOENT and delete_if_stale:
> + return 0
> + vlog.warn("%s: open: %s" % (pidfile, e.strerror))
> + return -e.errno
> +
> + # Python fcntl doesn't directly support F_GETLK so we have to just try
> + # to lock it.
> + try:
> + fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
> +
> + # pidfile exists but wasn't locked by anyone. Now we have the
> lock.
> + if not delete_if_stale:
> + file_handle.close()
> + vlog.warn("%s: pid file is stale" % pidfile)
> + return -errno.ESRCH
> +
> + # Is the file we have locked still named 'pidfile'?
> + try:
> + raced = False
> + s = os.stat(pidfile)
> + s2 = os.fstat(file_handle.fileno())
> + if s.st_ino != s2.st_ino or s.st_dev != s2.st_dev:
> + raced = True
> + except IOError:
> + raced = True
> + if raced:
> + vlog.warn("%s: lost race to delete pidfile" % pidfile)
> + return -errno.EALREADY
> +
> + # We won the right to delete the stale pidfile.
> + try:
> + os.unlink(pidfile)
> + except IOError as e:
> + vlog.warn("%s: failed to delete stale pidfile (%s)"
> + % (pidfile, e.strerror))
> + return -e.errno
> + else:
> + vlog.dbg("%s: deleted stale pidfile" % pidfile)
> + file_handle.close()
> + return 0
> + except IOError as e:
> + if e.errno not in [errno.EACCES, errno.EAGAIN]:
> + vlog.warn("%s: fcntl: %s" % (pidfile, e.strerror))
> + return -e.errno
> +
> + # Someone else has the pidfile locked.
> + try:
> + try:
> + error = int(file_handle.readline())
> + except IOError as e:
> + vlog.warn("%s: read: %s" % (pidfile, e.strerror))
> + error = -e.errno
> + except ValueError:
> + vlog.warn("%s does not contain a pid" % pidfile)
> + error = -errno.EINVAL
> +
> + return error
> + finally:
> + try:
> + file_handle.close()
> + except IOError:
> + pass
> +
> +
> +def read_pidfile(pidfile):
> + """Opens and reads a PID from 'pidfile'. Returns the positive PID if
> + successful, otherwise a negative errno value."""
> + return __read_pidfile(pidfile, False)
> +
> +
> +def _check_already_running():
> + pid = __read_pidfile(_pidfile, True)
> + if pid > 0:
> + _fatal("%s: already running as pid %d, aborting" % (_pidfile,
> pid))
> + elif pid < 0:
> + _fatal("%s: pidfile check failed (%s), aborting"
> + % (_pidfile, os.strerror(pid)))
> +
> +
> +def add_args(parser):
> + """Populates 'parser', an ArgumentParser allocated using the argparse
> + module, with the command line arguments required by the daemon
> module."""
> +
> + pidfile = make_pidfile_name(None)
> +
> + group = parser.add_argument_group(title="Daemon Options")
> + group.add_argument("--detach", action="store_true",
> + help="Run in background as a daemon.")
> + group.add_argument("--no-chdir", action="store_true",
> + help="Do not chdir to '/'.")
> + group.add_argument("--monitor", action="store_true",
> + help="Monitor %s process." % ovs.util.PROGRAM_NAME)
> + group.add_argument("--pidfile", nargs="?", const=pidfile,
> + help="Create pidfile (default %s)." % pidfile)
> + group.add_argument("--overwrite-pidfile", action="store_true",
> + help="With --pidfile, start even if already running.")
> +
> +
> +def handle_args(args):
> + """Handles daemon module settings in 'args'. 'args' is an object
> + containing values parsed by the parse_args() method of
> ArgumentParser. The
> + parent ArgumentParser should have been prepared by add_args() before
> + calling parse_args()."""
> +
> + if args.detach:
> + set_detach()
> +
> + if args.no_chdir:
> + set_no_chdir()
> +
> + if args.pidfile:
> + set_pidfile(args.pidfile)
> +
> + if args.overwrite_pidfile:
> + ignore_existing_pidfile()
> +
> + if args.monitor:
> + set_monitor()
> --
> 2.7.2.windows.1
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
>
More information about the dev
mailing list