# 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 . # 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)