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