ara-clients/ara/clients/offline.py

119 lines
4.0 KiB
Python

# Copyright (c) 2018 Red Hat, Inc.
#
# This file is part of ARA: Ansible Run Analysis.
#
# ARA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ARA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ARA. If not, see <http://www.gnu.org/licenses/>.
# This is an "offline" API client that does not require standing up
# an API server and does not execute actual HTTP calls.
import logging
import os
import threading
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import ServerHandler as BaseServerHandler, ThreadedWSGIServer, WSGIRequestHandler
from .http import AraHttpClient
class AraOfflineClient(AraHttpClient):
def __init__(self):
self.log = logging.getLogger(__name__)
try:
from django import setup as django_setup
from django.core.management import execute_from_command_line
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ara.server.settings")
# Automatically create the database and run migrations (is there a better way?)
execute_from_command_line(["django", "migrate"])
# Set up the things Django needs
django_setup()
self._start_server()
super().__init__(endpoint="http://localhost:%d" % self.server_thread.port)
except ImportError:
self.log.error("The offline client requires ara-server to be installed")
raise
def _start_server(self):
self.server_thread = ServerThread("localhost")
self.server_thread.start()
# Wait for the live server to be ready
self.server_thread.is_ready.wait()
if self.server_thread.error:
raise self.server_thread.error
class ServerHandler(BaseServerHandler):
def cleanup_headers(self):
super().cleanup_headers()
self.headers["Connection"] = "close"
class QuietWSGIRequestHandler(WSGIRequestHandler):
def log_message(*args):
pass
def handle(self):
"""Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ""
self.request_version = ""
self.command = ""
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())
class ServerThread(threading.Thread):
def __init__(self, host, port=0):
self.host = host
self.port = port
self.is_ready = threading.Event()
self.error = None
super().__init__(daemon=True)
def run(self):
"""
Set up the live server and databases, and then loop over handling
HTTP requests.
"""
try:
# Create the handler for serving static and media files
self.httpd = self._create_server()
# If binding to port zero, assign the port allocated by the OS.
if self.port == 0:
self.port = self.httpd.server_address[1]
self.httpd.set_app(WSGIHandler())
self.is_ready.set()
self.httpd.serve_forever()
except Exception as e:
self.error = e
self.is_ready.set()
def _create_server(self):
return ThreadedWSGIServer((self.host, self.port), QuietWSGIRequestHandler, allow_reuse_address=False)