Ejemplo n.º 1
0
class WebApplicationService(StreamServerEndpointService):
    """Service encapsulating the Django web application.

    This shows a default "MAAS is starting" web page until Django is up. If
    Django cannot be started, the page is replaced by the error that caused
    start-up to fail.

    :ivar site: The site object that wraps a WSGI resource.
    :ivar threadpool: The thread-pool used for servicing requests to
        the web application.
    """

    def __init__(self, port, listener, status_worker):
        self.port = port
        self.starting = False
        # Start with an empty `Resource`, `installApplication` will configure
        # the root resource. This must be seperated because Django must be
        # start from inside a thread with database access.
        self.site = OverlaySite(
            Resource(), logFormatter=reducedWebLogFormatter, timeout=None
        )
        self.site.requestFactory = CleanPathRequest
        # `endpoint` is set in `privilegedStartService`, at this point the
        # `endpoint` is None.
        super(WebApplicationService, self).__init__(None, self.site)
        self.websocket = WebSocketFactory(listener)
        self.threadpool = ThreadPoolLimiter(
            reactor.threadpoolForDatabase, concurrency.webapp
        )
        self.status_worker = status_worker

    def prepareApplication(self):
        """Return the WSGI application.

        If we run servers on multiple endpoints this ought to be extracted
        into a separate function, so that each server uses the same
        application.
        """
        return WebApplicationHandler()

    def startWebsocket(self):
        """Start the websocket factory for the `WebSocketsResource`."""
        self.websocket.startFactory()

    def installApplication(self, application):
        """Install the WSGI application into the Twisted site.

        It's installed as a child with path "MAAS". This matches the default
        front-end configuration (i.e. Apache) so that there's no need to force
        script names.
        """
        # Setup resources to process paths that twisted handles.
        metadata = Resource()
        metadata.putChild(b"status", StatusHandlerResource(self.status_worker))

        maas = Resource()
        maas.putChild(b"metadata", metadata)
        maas.putChild(b"static", File(settings.STATIC_ROOT))
        maas.putChild(
            b"ws", WebSocketsResource(lookupProtocolForFactory(self.websocket))
        )

        root = Resource()
        root.putChild(b"", Redirect(b"MAAS/"))
        root.putChild(b"MAAS", maas)

        # Setup the resources to process paths that django handles.
        underlay_maas = ResourceOverlay(
            WSGIResource(reactor, self.threadpool, application)
        )
        underlay_root = Resource()
        underlay_root.putChild(b"MAAS", underlay_maas)
        underlay_site = Site(
            underlay_root, logFormatter=reducedWebLogFormatter
        )
        underlay_site.requestFactory = CleanPathRequest

        # Setup the main resource as the twisted handler and the underlay
        # resource as the django handler.
        self.site.resource = root
        self.site.underlay = underlay_site

    @inlineCallbacks
    def startApplication(self):
        """Start the Django application, and install it."""
        application = yield deferToDatabase(self.prepareApplication)
        self.startWebsocket()
        self.installApplication(application)

    def _makeEndpoint(self):
        """Make the endpoint for the webapp."""
        # Make a socket with SO_REUSEPORT set so that we can run multiple web
        # applications. This is easier to do from outside of Twisted as there's
        # not yet official support for setting socket options.
        s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        # N.B, using the IPv6 INADDR_ANY means that getpeername() returns
        # something like: ('::ffff:192.168.133.32', 40588, 0, 0)
        s.bind(("::", self.port))
        # Use a backlog of 50, which seems to be fairly common.
        s.listen(50)

        # Adopt this socket into Twisted's reactor setting the endpoint.
        endpoint = AdoptedStreamServerEndpoint(reactor, s.fileno(), s.family)
        endpoint.port = self.port  # Make it easy to get the port number.
        endpoint.socket = s  # Prevent garbage collection.
        return endpoint

    @asynchronous(timeout=30)
    @inlineCallbacks
    def privilegedStartService(self):
        # Twisted will call `privilegedStartService` followed by `startService`
        # that also calls `privilegedStartService`. Since this method now
        # performs start-up work its possible that it will be called twice.
        # Then endpoint can only be created once or a bad file descriptor
        # error will occur.
        if self.starting:
            return
        self.starting = True

        # Start the application first before starting the service. This ensures
        # that the application is running correctly before any requests
        # can be handled.
        yield self.startApplication()

        # Create the endpoint now that the application is started.
        self.endpoint = self._makeEndpoint()

        # Start the service now that the endpoint has been created.
        super(WebApplicationService, self).privilegedStartService()

    @asynchronous(timeout=30)
    def stopService(self):
        def _cleanup(_):
            self.starting = False

        d = super(WebApplicationService, self).stopService()
        d.addCallback(lambda _: self.websocket.stopFactory())
        d.addCallback(_cleanup)
        return d
Ejemplo n.º 2
0
class WebApplicationService(StreamServerEndpointService):
    """Service encapsulating the Django web application.

    This shows a default "MAAS is starting" web page until Django is up. If
    Django cannot be started, the page is replaced by the error that caused
    start-up to fail.

    :ivar site: The site object that wraps a WSGI resource.
    :ivar threadpool: The thread-pool used for servicing requests to
        the web application.
    """
    def __init__(self, endpoint, listener, status_worker):
        self.site = OverlaySite(StartPage(),
                                logFormatter=reducedWebLogFormatter)
        self.site.requestFactory = CleanPathRequest
        super(WebApplicationService, self).__init__(endpoint, self.site)
        self.websocket = WebSocketFactory(listener)
        self.threadpool = ThreadPoolLimiter(reactor.threadpoolForDatabase,
                                            concurrency.webapp)
        self.status_worker = status_worker

    def prepareApplication(self):
        """Return the WSGI application.

        If we run servers on multiple endpoints this ought to be extracted
        into a separate function, so that each server uses the same
        application.
        """
        return WebApplicationHandler()

    def startWebsocket(self):
        """Start the websocket factory for the `WebSocketsResource`."""
        self.websocket.startFactory()

    def installApplication(self, application):
        """Install the WSGI application into the Twisted site.

        It's installed as a child with path "MAAS". This matches the default
        front-end configuration (i.e. Apache) so that there's no need to force
        script names.
        """
        # Setup resources to process paths that twisted handles.
        metadata = Resource()
        metadata.putChild(b'status', StatusHandlerResource(self.status_worker))

        maas = Resource()
        maas.putChild(b'metadata', metadata)
        maas.putChild(b'static', File(settings.STATIC_ROOT))
        maas.putChild(
            b'ws',
            WebSocketsResource(lookupProtocolForFactory(self.websocket)))

        root = Resource()
        root.putChild(b'', Redirect(b"MAAS/"))
        root.putChild(b'MAAS', maas)

        # Setup the resources to process paths that django handles.
        underlay_maas = ResourceOverlay(
            WSGIResource(reactor, self.threadpool, application))
        underlay_root = Resource()
        underlay_root.putChild(b'MAAS', underlay_maas)
        underlay_site = Site(underlay_root,
                             logFormatter=reducedWebLogFormatter)
        underlay_site.requestFactory = CleanPathRequest

        # Setup the main resource as the twisted handler and the underlay
        # resource as the django handler.
        self.site.resource = root
        self.site.underlay = underlay_site

    def installFailed(self, failure):
        """Display a page explaining why the web app could not start."""
        self.site.resource = StartFailedPage(failure)
        log.err(failure, "MAAS web application failed to start")

    def startApplication(self):
        """Start the Django application, and install it."""
        try:
            application = self.prepareApplication()
            self.startWebsocket()
            self.installApplication(application)
        except:
            self.installFailed(failure.Failure())
        return defer.succeed(None)

    @asynchronous(timeout=30)
    def startService(self):
        super(WebApplicationService, self).startService()
        return self.startApplication()

    @asynchronous(timeout=30)
    def stopService(self):
        d = super(WebApplicationService, self).stopService()
        d.addCallback(lambda _: self.websocket.stopFactory())
        return d