Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
213
tools/python/test_server/server/testserver.py
Normal file
213
tools/python/test_server/server/testserver.py
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
"""
|
||||
This is a simple web-server that does very few things. It is necessary for
|
||||
the downloader tests.
|
||||
|
||||
Here is the logic behind the initialization:
|
||||
Because several instances of the test can run simultaneously on the Build
|
||||
machine, we have to take this into account and not start another server if
|
||||
one is already running. However, there is a chance that a server will not
|
||||
terminate correctly, and will still hold the port, so we will not be able
|
||||
to initialize another server.
|
||||
|
||||
So before initializing the server, we check if any processes are using the port
|
||||
that we want to use. If we find such a process, we assume that it might be
|
||||
working, and wait for about 10 seconds for it to start serving. If it does not,
|
||||
we kill it.
|
||||
|
||||
Next, we check the name of our process and see if there are other processes
|
||||
with the same name. If there are, we assume that they might start serving any
|
||||
moment. So we iterate over the ones that have PID lower than ours, and wait
|
||||
for them to start serving. If a process doesn't serve, we kill it.
|
||||
|
||||
If we have killed (or someone has) all the processes with PIDs lower than ours,
|
||||
we try to start serving. If we succeed, we kill all other processes with the
|
||||
same name as ours. If we don't someone else will kill us.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from http.server import HTTPServer
|
||||
from ResponseProvider import Payload
|
||||
from ResponseProvider import ResponseProvider
|
||||
from ResponseProvider import ResponseProviderMixin
|
||||
from SiblingKiller import SiblingKiller
|
||||
from threading import Timer
|
||||
from config import LIFESPAN, PING_TIMEOUT, PORT
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
|
||||
import logging
|
||||
import logging.config
|
||||
|
||||
try:
|
||||
from tornado_handler import MainHandler
|
||||
USE_TORNADO = True
|
||||
except:
|
||||
USE_TORNADO = False
|
||||
|
||||
|
||||
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
|
||||
|
||||
|
||||
class InternalServer(HTTPServer):
|
||||
|
||||
def kill_me(self):
|
||||
self.shutdown()
|
||||
logging.info(f"The server's life has come to an end, pid: {os.getpid()}")
|
||||
|
||||
|
||||
def reset_selfdestruct_timer(self):
|
||||
if self.self_destruct_timer:
|
||||
self.self_destruct_timer.cancel()
|
||||
|
||||
self.self_destruct_timer = Timer(LIFESPAN, self.kill_me)
|
||||
self.self_destruct_timer.start()
|
||||
|
||||
|
||||
def __init__(self, server_address, RequestHandlerClass,
|
||||
bind_and_activate=True):
|
||||
|
||||
HTTPServer.__init__(self, server_address, RequestHandlerClass,
|
||||
bind_and_activate=bind_and_activate)
|
||||
|
||||
self.self_destruct_timer = None
|
||||
self.clients = 1
|
||||
self.reset_selfdestruct_timer()
|
||||
|
||||
|
||||
def suicide(self):
|
||||
self.clients -= 1
|
||||
if self.clients == 0:
|
||||
if self.self_destruct_timer is not None:
|
||||
self.self_destruct_timer.cancel()
|
||||
|
||||
quick_and_painless_timer = Timer(0.1, self.kill_me)
|
||||
quick_and_painless_timer.start()
|
||||
|
||||
|
||||
class TestServer:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.may_serve = False
|
||||
|
||||
pid = os.getpid()
|
||||
logging.info(f"Init server. Pid: {pid}")
|
||||
|
||||
self.server = None
|
||||
|
||||
killer = SiblingKiller(PORT, PING_TIMEOUT)
|
||||
killer.kill_siblings()
|
||||
if killer.allow_serving():
|
||||
try:
|
||||
self.init_server()
|
||||
logging.info(f"Started server with pid: {pid}")
|
||||
self.may_serve = True
|
||||
|
||||
except socket.error:
|
||||
logging.info("Failed to start the server: Port is in use")
|
||||
except Exception as e:
|
||||
logging.debug(e)
|
||||
logging.info("Failed to start serving for unknown reason")
|
||||
traceback.print_exc()
|
||||
else:
|
||||
logging.info(f"Not allowed to start serving for process: {pid}")
|
||||
|
||||
def init_server(self):
|
||||
|
||||
if USE_TORNADO:
|
||||
MainHandler.init_server(PORT, LIFESPAN)
|
||||
else:
|
||||
print("""
|
||||
*************
|
||||
WARNING: Using the python's built-in BaseHTTPServer!
|
||||
It is all right if you run the tests on your local machine, but if you are running tests on a server,
|
||||
please consider installing Tornado. It is a much more powerful web-server. Otherwise you will find
|
||||
that some of your downloader tests either fail or hang.
|
||||
|
||||
do
|
||||
|
||||
sudo pip install tornado
|
||||
|
||||
or go to http://www.tornadoweb.org/en/stable/ for more detail.
|
||||
*************
|
||||
""")
|
||||
|
||||
self.server = InternalServer(('localhost', PORT), PostHandler)
|
||||
|
||||
|
||||
|
||||
def start_serving(self):
|
||||
if not self.may_serve:
|
||||
return
|
||||
|
||||
if USE_TORNADO:
|
||||
MainHandler.start_serving()
|
||||
|
||||
else:
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
thread.deamon = True
|
||||
thread.start()
|
||||
|
||||
|
||||
class PostHandler(BaseHTTPRequestHandler, ResponseProviderMixin):
|
||||
|
||||
def dispatch_response(self, payload):
|
||||
|
||||
self.send_response(payload.response_code())
|
||||
for h in payload.headers():
|
||||
self.send_header(h, payload.headers()[h])
|
||||
self.send_header("Content-Length", payload.length())
|
||||
self.end_headers()
|
||||
self.wfile.write(payload.message())
|
||||
|
||||
|
||||
def init_vars(self):
|
||||
self.response_provider = ResponseProvider(self)
|
||||
|
||||
|
||||
def do_POST(self):
|
||||
self.init_vars()
|
||||
self.server.reset_selfdestruct_timer()
|
||||
headers = self.prepare_headers()
|
||||
payload = self.response_provider.response_for_url_and_headers(self.path, headers)
|
||||
if payload.response_code() >= 300:
|
||||
length = int(self.headers.get('content-length'))
|
||||
self.dispatch_response(Payload(self.rfile.read(length)))
|
||||
else:
|
||||
self.dispatch_response(payload)
|
||||
|
||||
|
||||
def do_GET(self):
|
||||
headers = self.prepare_headers()
|
||||
self.init_vars()
|
||||
self.dispatch_response(self.response_provider.response_for_url_and_headers(self.path, headers))
|
||||
|
||||
|
||||
def prepare_headers(self):
|
||||
ret = dict()
|
||||
for h in self.headers:
|
||||
ret[h.lower()] = self.headers.get(h)
|
||||
return ret
|
||||
|
||||
|
||||
def got_pinged(self):
|
||||
self.server.clients += 1
|
||||
|
||||
|
||||
def kill(self):
|
||||
logging.debug("Kill called in testserver")
|
||||
self.server.suicide()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
server = TestServer()
|
||||
server.start_serving()
|
||||
Loading…
Add table
Add a link
Reference in a new issue