| OLD | NEW |
| (Empty) |
| 1 # Copyright (C) 2011 Google Inc. All rights reserved. | |
| 2 # | |
| 3 # Redistribution and use in source and binary forms, with or without | |
| 4 # modification, are permitted provided that the following conditions are | |
| 5 # met: | |
| 6 # | |
| 7 # * Redistributions of source code must retain the above copyright | |
| 8 # notice, this list of conditions and the following disclaimer. | |
| 9 # * Redistributions in binary form must reproduce the above | |
| 10 # copyright notice, this list of conditions and the following disclaimer | |
| 11 # in the documentation and/or other materials provided with the | |
| 12 # distribution. | |
| 13 # * Neither the name of Google Inc. nor the names of its | |
| 14 # contributors may be used to endorse or promote products derived from | |
| 15 # this software without specific prior written permission. | |
| 16 # | |
| 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 28 | |
| 29 """Base class with common routines between the Apache, Lighttpd, and websocket s
ervers.""" | |
| 30 | |
| 31 import errno | |
| 32 import logging | |
| 33 import socket | |
| 34 import sys | |
| 35 import tempfile | |
| 36 import time | |
| 37 | |
| 38 | |
| 39 _log = logging.getLogger(__name__) | |
| 40 | |
| 41 | |
| 42 class ServerError(Exception): | |
| 43 pass | |
| 44 | |
| 45 | |
| 46 class HttpServerBase(object): | |
| 47 """A skeleton class for starting and stopping servers used by the layout tes
ts.""" | |
| 48 | |
| 49 def __init__(self, port_obj, number_of_servers=None): | |
| 50 self._executive = port_obj._executive | |
| 51 self._filesystem = port_obj._filesystem | |
| 52 self._name = '<virtual>' | |
| 53 self._mappings = {} | |
| 54 self._pid = None | |
| 55 self._pid_file = None | |
| 56 self._port_obj = port_obj | |
| 57 self._number_of_servers = number_of_servers | |
| 58 | |
| 59 # We need a non-checkout-dependent place to put lock files, etc. We | |
| 60 # don't use the Python default on the Mac because it defaults to a | |
| 61 # randomly-generated directory under /var/folders and no one would ever | |
| 62 # look there. | |
| 63 tmpdir = tempfile.gettempdir() | |
| 64 if port_obj.host.platform.is_mac(): | |
| 65 tmpdir = '/tmp' | |
| 66 | |
| 67 self._runtime_path = self._filesystem.join(tmpdir, "WebKit") | |
| 68 self._filesystem.maybe_make_directory(self._runtime_path) | |
| 69 | |
| 70 def start(self): | |
| 71 """Starts the server. It is an error to start an already started server. | |
| 72 | |
| 73 This method also stops any stale servers started by a previous instance.
""" | |
| 74 assert not self._pid, '%s server is already running' % self._name | |
| 75 | |
| 76 # Stop any stale servers left over from previous instances. | |
| 77 if self._filesystem.exists(self._pid_file): | |
| 78 try: | |
| 79 self._pid = int(self._filesystem.read_text_file(self._pid_file)) | |
| 80 self._stop_running_server() | |
| 81 except (ValueError, UnicodeDecodeError): | |
| 82 # These could be raised if the pid file is corrupt. | |
| 83 self._remove_pid_file() | |
| 84 self._pid = None | |
| 85 | |
| 86 self._remove_stale_logs() | |
| 87 self._prepare_config() | |
| 88 self._check_that_all_ports_are_available() | |
| 89 | |
| 90 self._pid = self._spawn_process() | |
| 91 | |
| 92 if self._wait_for_action(self._is_server_running_on_all_ports): | |
| 93 _log.debug("%s successfully started (pid = %d)" % (self._name, self.
_pid)) | |
| 94 else: | |
| 95 self._stop_running_server() | |
| 96 raise ServerError('Failed to start %s server' % self._name) | |
| 97 | |
| 98 def stop(self): | |
| 99 """Stops the server. Stopping a server that isn't started is harmless.""
" | |
| 100 actual_pid = None | |
| 101 try: | |
| 102 if self._filesystem.exists(self._pid_file): | |
| 103 try: | |
| 104 actual_pid = int(self._filesystem.read_text_file(self._pid_f
ile)) | |
| 105 except (ValueError, UnicodeDecodeError): | |
| 106 # These could be raised if the pid file is corrupt. | |
| 107 pass | |
| 108 if not self._pid: | |
| 109 self._pid = actual_pid | |
| 110 | |
| 111 if not self._pid: | |
| 112 return | |
| 113 | |
| 114 if not actual_pid: | |
| 115 _log.warning('Failed to stop %s: pid file is missing' % self._na
me) | |
| 116 return | |
| 117 if self._pid != actual_pid: | |
| 118 _log.warning('Failed to stop %s: pid file contains %d, not %d' % | |
| 119 (self._name, actual_pid, self._pid)) | |
| 120 # Try to kill the existing pid, anyway, in case it got orphaned. | |
| 121 self._executive.kill_process(self._pid) | |
| 122 self._pid = None | |
| 123 return | |
| 124 | |
| 125 _log.debug("Attempting to shut down %s server at pid %d" % (self._na
me, self._pid)) | |
| 126 self._stop_running_server() | |
| 127 _log.debug("%s server at pid %d stopped" % (self._name, self._pid)) | |
| 128 self._pid = None | |
| 129 finally: | |
| 130 # Make sure we delete the pid file no matter what happens. | |
| 131 self._remove_pid_file() | |
| 132 | |
| 133 def _prepare_config(self): | |
| 134 """This routine can be overridden by subclasses to do any sort | |
| 135 of initialization required prior to starting the server that may fail.""
" | |
| 136 pass | |
| 137 | |
| 138 def _remove_stale_logs(self): | |
| 139 """This routine can be overridden by subclasses to try and remove logs | |
| 140 left over from a prior run. This routine should log warnings if the | |
| 141 files cannot be deleted, but should not fail unless failure to | |
| 142 delete the logs will actually cause start() to fail.""" | |
| 143 pass | |
| 144 | |
| 145 def _spawn_process(self): | |
| 146 """This routine must be implemented by subclasses to actually start the
server. | |
| 147 | |
| 148 This routine returns the pid of the started process, and also ensures th
at that | |
| 149 pid has been written to self._pid_file.""" | |
| 150 raise NotImplementedError() | |
| 151 | |
| 152 def _stop_running_server(self): | |
| 153 """This routine must be implemented by subclasses to actually stop the r
unning server listed in self._pid_file.""" | |
| 154 raise NotImplementedError() | |
| 155 | |
| 156 # Utility routines. | |
| 157 | |
| 158 def _remove_pid_file(self): | |
| 159 if self._filesystem.exists(self._pid_file): | |
| 160 self._filesystem.remove(self._pid_file) | |
| 161 | |
| 162 def _remove_log_files(self, folder, starts_with): | |
| 163 files = self._filesystem.listdir(folder) | |
| 164 for file in files: | |
| 165 if file.startswith(starts_with): | |
| 166 full_path = self._filesystem.join(folder, file) | |
| 167 self._filesystem.remove(full_path) | |
| 168 | |
| 169 def _wait_for_action(self, action, wait_secs=20.0, sleep_secs=1.0): | |
| 170 """Repeat the action for wait_sec or until it succeeds, sleeping for sle
ep_secs | |
| 171 in between each attempt. Returns whether it succeeded.""" | |
| 172 start_time = time.time() | |
| 173 while time.time() - start_time < wait_secs: | |
| 174 if action(): | |
| 175 return True | |
| 176 _log.debug("Waiting for action: %s" % action) | |
| 177 time.sleep(sleep_secs) | |
| 178 | |
| 179 return False | |
| 180 | |
| 181 def _is_server_running_on_all_ports(self): | |
| 182 """Returns whether the server is running on all the desired ports.""" | |
| 183 if not self._executive.check_running_pid(self._pid): | |
| 184 _log.debug("Server isn't running at all") | |
| 185 raise ServerError("Server exited") | |
| 186 | |
| 187 for mapping in self._mappings: | |
| 188 s = socket.socket() | |
| 189 port = mapping['port'] | |
| 190 try: | |
| 191 s.connect(('localhost', port)) | |
| 192 _log.debug("Server running on %d" % port) | |
| 193 except IOError, e: | |
| 194 if e.errno not in (errno.ECONNREFUSED, errno.ECONNRESET): | |
| 195 raise | |
| 196 _log.debug("Server NOT running on %d: %s" % (port, e)) | |
| 197 return False | |
| 198 finally: | |
| 199 s.close() | |
| 200 return True | |
| 201 | |
| 202 def _check_that_all_ports_are_available(self): | |
| 203 for mapping in self._mappings: | |
| 204 s = socket.socket() | |
| 205 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
| 206 port = mapping['port'] | |
| 207 try: | |
| 208 s.bind(('localhost', port)) | |
| 209 except IOError, e: | |
| 210 if e.errno in (errno.EALREADY, errno.EADDRINUSE): | |
| 211 raise ServerError('Port %d is already in use.' % port) | |
| 212 elif sys.platform == 'win32' and e.errno in (errno.WSAEACCES,):
# pylint: disable=E1101 | |
| 213 raise ServerError('Port %d is already in use.' % port) | |
| 214 else: | |
| 215 raise | |
| 216 finally: | |
| 217 s.close() | |
| OLD | NEW |