chromium/chrome/updater/test/service/win/updater_test_service.py

# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import os
import sys
import xmlrpc.server

import pywintypes
import servicemanager
import win32api
import win32service
import win32serviceutil

import rpc_handler

# Ideally we should pick an unused port instead of the hard-coded value. But a
# dynamic service port number means:
#    1. We need to bring dependencies on some python libraries, say portpicker.
#    2. We need to broadcast the port number to clients.
#    3. The port number probably changes every time the service is restarted.
# Those additional complexity seems outweigh the benefits it brings. And
# empirical results show that a pre-defined port works well enough.
_XML_RPC_SERVER_PORT = 9090


class UpdaterTestRequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
    def log_message(self, format, *args):
        # Overrides base class's implementation which writes the messages to
        # sys.stderr.
        # When XML RPC server runs within the service, sys.stderr is None. This
        # crashes the server and aborts connection. Workaround this issue by
        # using python logging.
        logging.error(format, *args)


class UpdaterTestXmlRpcServer(xmlrpc.server.SimpleXMLRPCServer):
    """Customized XML-RPC server for updater tests."""

    def __init__(self):
        super().__init__(('localhost', _XML_RPC_SERVER_PORT),
                         requestHandler=UpdaterTestRequestHandler,
                         allow_none=True)

    def run(self):
        """xml-rpc server main loop."""
        self.register_introspection_functions()
        self.register_instance(rpc_handler.UpdaterTestRPCHandler())
        self.serve_forever()


class UpdaterTestService(win32serviceutil.ServiceFramework):
    """Customizes updater tests behavior."""

    # Do not change these class variables names, these are required by the base
    # class.
    _svc_name_ = 'UpdaterTestService'
    _svc_display_name_ = 'Updater Test Service'
    _svc_description_ = 'Service for browser updater tests'

    def SvcStop(self):
        """Called by service framework to stop this service."""
        logging.info('Updater test service stopping...')
        self._xmlrpc_server.shutdown()
        self.ReportServiceStatus(win32service.SERVICE_STOPPED)

    def SvcDoRun(self):
        """Called by service framework to start this service."""

        try:
            logging.info('%s starting...', self._svc_name_)
            servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                                  servicemanager.PYS_SERVICE_STARTED,
                                  (self._svc_name_, ''))
            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
            self._xmlrpc_server = UpdaterTestXmlRpcServer()
            self._xmlrpc_server.run()
            servicemanager.LogInfoMsg(self._svc_name_ + ' - Ended')
        except pywintypes.error as err:
            logging.exception(err)
            servicemanager.LogErrorMsg(err)
            self.ReportServiceStatus(win32service.SERVICE_ERROR_SEVERE)


if __name__ == "__main__":
    logging.info('Command: %s', sys.argv)

    # Prefer the pythonservice.exe in the same directory as the interpreter.
    # This is mainly for the vpython case.
    destination = os.path.join(
        os.path.dirname(os.path.abspath(sys.executable)), 'pythonservice.exe')
    if os.path.exists(destination):
        os.environ['PYTHON_SERVICE_EXE'] = destination

    try:
        win32api.SetConsoleCtrlHandler(lambda _: True, True)
        win32serviceutil.HandleCommandLine(UpdaterTestService)
    except Exception as err:
        servicemanager.LogErrorMsg(err)