Please ignore this patch, I will send another one soon.

Thanks,
Alin Balutoiu.

> -----Original Message-----
> From: Alin Balutoiu
> Sent: Tuesday, January 3, 2017 5:17 PM
> To: d...@openvswitch.org
> Cc: Alin Balutoiu <abalut...@cloudbasesolutions.com>; Paul Boca
> <pb...@cloudbasesolutions.com>
> Subject: [PATCH V2 3/5] Python tests: Daemon ported to Windows
> 
> From: Alin Balutoiu <abalut...@cloudbasesolutions.com>
> 
> Instead of using os.fork (not supported on Windows), subprocess.Popen is
> used and os.pipe was replaced with Windows pipes.
> 
> To be able to identify the child process, an extra parameter was added to
> daemon process '--pipe-handle'.
> This parameter contains the parent Windows pipe handle which is used by
> the child to notify the parent about the startup.
> 
> The PID file is created directly on Windows, without using a temporary file
> because the symbolic link does not inherit the file lok set on the temporary
> file.
> 
> Signed-off-by: Paul-Daniel Boca <pb...@cloudbasesolutions.com>
> Signed-off-by: Alin-Gheorghe Balutoiu <abalut...@cloudbasesolutions.com>
> ---
> V2: No changes.
> ---
>  python/ovs/daemon.py       | 194
> +++++++++++++++++++++++++++++++++++++--------
>  python/ovs/fatal_signal.py |  13 +++
>  python/ovs/vlog.py         |  26 +++++-
>  tests/test-daemon.py       |   4 +-
>  4 files changed, 199 insertions(+), 38 deletions(-)
> 
> diff --git a/python/ovs/daemon.py b/python/ovs/daemon.py index
> bd06195..06ef92b 100644
> --- a/python/ovs/daemon.py
> +++ b/python/ovs/daemon.py
> @@ -13,9 +13,7 @@
>  # limitations under the License.
> 
>  import errno
> -import fcntl
>  import os
> -import resource
>  import signal
>  import sys
>  import time
> @@ -28,11 +26,24 @@ import ovs.timeval
>  import ovs.util
>  import ovs.vlog
> 
> +if sys.platform != 'win32':
> +    import fcntl
> +    import resource
> +else:
> +    import ovs.winutils as winutils
> +    import ovs.fcntl_win as fcntl
> +    import pywintypes
> +    import subprocess
> +    import win32process
> +
>  vlog = ovs.vlog.Vlog("daemon")
> 
>  # --detach: Should we run in the background?
>  _detach = False
> 
> +# Running as the child process - Windows only.
> +_detached = False
> +
>  # --pidfile: Name of pidfile (null if none).
>  _pidfile = None
> 
> @@ -98,6 +109,16 @@ def set_detach():
>      _detach = True
> 
> 
> +def set_detached(wp):
> +    """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. Used on Windows only."""
> +    global _detached
> +    global _daemonize_fd
> +    _detached = True
> +    _daemonize_fd = int(wp)
> +
> +
>  def get_detach():
>      """Will daemonize() really detach?"""
>      return _detach
> @@ -123,8 +144,12 @@ def _make_pidfile():
>      pid = os.getpid()
> 
>      # Create a temporary pidfile.
> -    tmpfile = "%s.tmp%d" % (_pidfile, pid)
> -    ovs.fatal_signal.add_file_to_unlink(tmpfile)
> +    if sys.platform != 'win32':
> +        tmpfile = "%s.tmp%d" % (_pidfile, pid)
> +        ovs.fatal_signal.add_file_to_unlink(tmpfile)
> +    else:
> +        tmpfile = "%s" % _pidfile
> +
>      try:
>          # This is global to keep Python from garbage-collecting and
>          # therefore closing our file after this function exits.  That would 
> @@ -
> 147,40 +172,48 @@ def _make_pidfile():
>          _fatal("%s: write failed: %s" % (tmpfile, e.strerror))
> 
>      try:
> -        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
> +        if sys.platform != 'win32':
> +            fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
> +        else:
> +            fcntl.lockf(file_handle, fcntl.LOCK_SH | 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))
> +    if sys.platform == 'win32':
> +        # Ensure that the pidfile will gets closed and deleted on exit.
> +        ovs.fatal_signal.add_file_to_close_and_unlink(_pidfile,
> + file_handle)
>      else:
> -        while True:
> +        # Rename or link it to the correct name.
> +        if _overwrite_pidfile:
>              try:
> -                os.link(tmpfile, _pidfile)
> -                error = 0
> +                os.rename(tmpfile, _pidfile)
>              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)))
> +                _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)
> +        # 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)))
> +        # 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
> @@ -206,6 +239,9 @@ def _waitpid(pid, options):
> 
> 
>  def _fork_and_wait_for_startup():
> +    if sys.platform == 'win32':
> +        return _fork_and_wait_for_startup_windows()
> +
>      try:
>          rfd, wfd = os.pipe()
>      except OSError as e:
> @@ -259,7 +295,65 @@ def _fork_and_wait_for_startup():
>      return pid
> 
> 
> +def _fork_and_wait_for_startup_windows():
> +    global _detached
> +    if _detached:
> +        # Running in child process
> +        ovs.timeval.postfork()
> +        return 0
> +
> +    """ close the log file, on Windows cannot be moved while the parent has
> +    a reference on it."""
> +    vlog.close_log_file()
> +
> +    try:
> +        (rfd, wfd) = winutils.windows_create_pipe()
> +    except pywintypes.error as e:
> +        sys.stderr.write("pipe failed to create: %s\n" % e.strerror)
> +        sys.exit(1)
> +
> +    try:
> +        creationFlags = win32process.DETACHED_PROCESS
> +        args = ("%s %s --pipe-handle=%ld" % (
> +            sys.executable, " ".join(sys.argv), int(wfd)))
> +        proc = subprocess.Popen(
> +            args=args,
> +            close_fds=False,
> +            shell=False,
> +            creationflags=creationFlags,
> +            stdout=sys.stdout,
> +            stderr=sys.stderr)
> +        pid = proc.pid
> +    except OSError as e:
> +        sys.stderr.write("CreateProcess failed (%s)\n" % 
> os.strerror(e.errno))
> +        sys.exit(1)
> +
> +    # Running in parent process.
> +    winutils.win32file.CloseHandle(wfd)
> +    ovs.fatal_signal.fork()
> +
> +    error, s = winutils.windows_read_pipe(rfd, 1)
> +    if error:
> +        s = ""
> +
> +    if len(s) != 1:
> +        retval = proc.wait()
> +        if retval == 0:
> +            sys.stderr.write("fork child failed to signal startup\n")
> +        else:
> +            # Child exited with an error. Convey the same error to
> +            # our parent process as a courtesy.
> +            sys.exit(retval)
> +    winutils.win32file.CloseHandle(rfd)
> +
> +    return pid
> +
> +
>  def _fork_notify_startup(fd):
> +    if sys.platform == 'win32':
> +        _fork_notify_startup_windows(fd)
> +        return
> +
>      if fd is not None:
>          error, bytes_written = ovs.socket_util.write_fully(fd, "0")
>          if error:
> @@ -268,9 +362,31 @@ def _fork_notify_startup(fd):
>          os.close(fd)
> 
> 
> +def _fork_notify_startup_windows(fd):
> +    if fd is not None:
> +        try:
> +            # Python 2 requires a string as second parameter, while
> +            # Python 3 requires a bytes-like object. b"0" fits for both
> +            # python versions.
> +            winutils.win32file.WriteFile(fd, b"0", None)
> +        except winutils.pywintypes.error as e:
> +            sys.stderr.write("could not write to pipe: %s\n" %
> +                             os.strerror(e.winerror))
> +            sys.exit(1)
> +
> +
>  def _should_restart(status):
>      global RESTART_EXIT_CODE
> 
> +    if sys.platform == 'win32':
> +        # The exit status is encoded in the high byte of the
> +        # 16-bit number 'status'.
> +        exit_status = status >> 8
> +
> +        if exit_status == RESTART_EXIT_CODE:
> +            return True
> +        return False
> +
>      if os.WIFEXITED(status) and os.WEXITSTATUS(status) ==
> RESTART_EXIT_CODE:
>          return True
> 
> @@ -296,7 +412,7 @@ def _monitor_daemon(daemon_pid):
>                            % (daemon_pid, ovs.process.status_msg(status)))
> 
>              if _should_restart(status):
> -                if os.WCOREDUMP(status):
> +                if sys.platform != 'win32' and os.WCOREDUMP(status):
>                      # Disable further core dumps to save disk space.
>                      try:
>                          resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) @@ 
> -351,8
> +467,9 @@ def daemonize_start():
>              # Running in parent process.
>              sys.exit(0)
> 
> -        # Running in daemon or monitor process.
> -        os.setsid()
> +        if sys.platform != 'win32':
> +            # Running in daemon or monitor process.
> +            os.setsid()
> 
>      if _monitor:
>          saved_daemonize_fd = _daemonize_fd @@ -360,7 +477,8 @@ def
> daemonize_start():
>          if daemon_pid > 0:
>              # Running in monitor process.
>              _fork_notify_startup(saved_daemonize_fd)
> -            _close_standard_fds()
> +            if sys.platform != 'win32':
> +                _close_standard_fds()
>              _monitor_daemon(daemon_pid)
>          # Running in daemon process
> 
> @@ -502,6 +620,10 @@ def add_args(parser):
>              help="Create pidfile (default %s)." % pidfile)
>      group.add_argument("--overwrite-pidfile", action="store_true",
>              help="With --pidfile, start even if already running.")
> +    if sys.platform == 'win32':
> +        group.add_argument("--pipe-handle",
> +                           help=("With --pidfile, start even if "
> +                                 "already running."))
> 
> 
>  def handle_args(args):
> @@ -510,6 +632,10 @@ def handle_args(args):
>      parent ArgumentParser should have been prepared by add_args() before
>      calling parse_args()."""
> 
> +    if sys.platform == 'win32':
> +        if args.pipe_handle:
> +            set_detached(args.pipe_handle)
> +
>      if args.detach:
>          set_detach()
> 
> diff --git a/python/ovs/fatal_signal.py b/python/ovs/fatal_signal.py index
> a143e24..eb4b663 100644
> --- a/python/ovs/fatal_signal.py
> +++ b/python/ovs/fatal_signal.py
> @@ -59,6 +59,17 @@ def add_file_to_unlink(file):
>      _files[file] = None
> 
> 
> +def add_file_to_close_and_unlink(file, fd=None):
> +    """Registers 'file' to be unlinked when the program terminates via
> +    sys.exit() or a fatal signal and the 'fd' to be closed. On Windows a file
> +    cannot be removed while it is open for writing."""
> +    global _added_hook
> +    if not _added_hook:
> +        _added_hook = True
> +        add_hook(_unlink_files, _cancel_files, True)
> +    _files[file] = fd
> +
> +
>  def remove_file_to_unlink(file):
>      """Unregisters 'file' from being unlinked when the program terminates via
>      sys.exit() or a fatal signal."""
> @@ -78,6 +89,8 @@ def unlink_file_now(file):
> 
>  def _unlink_files():
>      for file_ in _files:
> +        if sys.platform == "win32" and _files[file_]:
> +            _files[file_].close()
>          _unlink(file_)
> 
> 
> diff --git a/python/ovs/vlog.py b/python/ovs/vlog.py index
> 48d52ad..99bd36b 100644
> --- a/python/ovs/vlog.py
> +++ b/python/ovs/vlog.py
> @@ -243,7 +243,14 @@ class Vlog(object):
>                                       Vlog._unixctl_vlog_reopen, None)
>          ovs.unixctl.command_register("vlog/close", "", 0, 0,
>                                       Vlog._unixctl_vlog_close, None)
> -        ovs.unixctl.command_register("vlog/set", "spec", 1, sys.maxsize,
> +        try:
> +            # Windows limitation on Python 2, sys.maxsize is a long number
> +            # on 64 bits environments, while sys.maxint is the maximum int
> +            # number. Python 3 works as expected.
> +            maxsize_int = sys.maxint
> +        except AttributeError:
> +            maxsize_int = sys.maxsize
> +        ovs.unixctl.command_register("vlog/set", "spec", 1,
> + maxsize_int,
>                                       Vlog._unixctl_vlog_set, None)
>          ovs.unixctl.command_register("vlog/list", "", 0, 0,
>                                       Vlog._unixctl_vlog_list, None) @@ 
> -384,6 +391,16 @@
> class Vlog(object):
>              logger.addHandler(Vlog.__file_handler)
> 
>      @staticmethod
> +    def close_log_file():
> +        """Closes the current log file. (This is useful on Windows, to ensure
> +        that a reference to the file is not kept by the daemon in case of
> +        detach.)"""
> +        if Vlog.__log_file:
> +            logger = logging.getLogger("file")
> +            logger.removeHandler(Vlog.__file_handler)
> +            Vlog.__file_handler.close()
> +
> +    @staticmethod
>      def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
>          if Vlog.__log_file:
>              Vlog.reopen_log_file()
> @@ -394,8 +411,11 @@ class Vlog(object):
>      @staticmethod
>      def _unixctl_vlog_close(conn, unused_argv, unused_aux):
>          if Vlog.__log_file:
> -            logger = logging.getLogger("file")
> -            logger.removeHandler(Vlog.__file_handler)
> +            if sys.platform != 'win32':
> +                logger = logging.getLogger("file")
> +                logger.removeHandler(Vlog.__file_handler)
> +            else:
> +                Vlog.close_log_file()
>          conn.reply(None)
> 
>      @staticmethod
> diff --git a/tests/test-daemon.py b/tests/test-daemon.py index
> 63c1f70..3f4200d 100644
> --- a/tests/test-daemon.py
> +++ b/tests/test-daemon.py
> @@ -27,7 +27,9 @@ def handler(signum, _):
> 
>  def main():
> 
> -    signal.signal(signal.SIGHUP, handler)
> +    if sys.platform != 'win32':
> +        # signal.SIGHUP does not exist on Windows
> +        signal.signal(signal.SIGHUP, handler)
> 
>      parser = argparse.ArgumentParser(
>              description="Open vSwitch daemonization test program for 
> Python.")
> --
> 2.10.0.windows.1
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to