blob: 979a084e6a7b133b6e870b6afdb19e9a097b3326 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Python twisted's module creates definition dynamically 7
# pylint: disable=E1101
"""Shopfloor daemon.
The launcher is a daemon that manages underlying services. Including HTTPD
frontend, Shopfloor server FastCGI, update server and log minitor service.
Example:
# Run the daemon in user mode with test data set
./shopfloord.py -t
"""
import logging
import optparse
import os
import signal
from twisted.internet import error
from twisted.internet import reactor
import factory_common # pylint: disable=W0611
from cros.factory.shopfloor.launcher import constants
from cros.factory.shopfloor.launcher import env
from cros.factory.shopfloor.launcher import ShopFloorLauncherException
from cros.factory.shopfloor.launcher import utils
from cros.factory.shopfloor.launcher.commands import LauncherCommandFactory
# The string for detecting if we're inside a .par file
_RESOURCE_FACTORY_PAR = '/resources/factory.par'
def Run(config_file):
"""ShopFloor daemon loop."""
utils.UpdateConfig(config_file)
logging.info('Command port: %d', constants.COMMAND_PORT)
reactor.listenTCP(constants.COMMAND_PORT, LauncherCommandFactory())
# Start twisted, and prevent reactor from install signal handlers.
reactor.run(installSignalHandlers=0)
def main():
parser = optparse.OptionParser()
parser.add_option('-c', '--config', dest='yaml_config',
default='shopfloor.yaml')
parser.add_option('-t', '--test', dest='test_run', action='store_true',
default=False)
parser.add_option('-l', '--local', dest='local_dir', action='store_true',
default=False)
(options, args) = parser.parse_args()
if args:
parser.error('Invalid args: %s' % ' '.join(args))
log_format = '%(asctime)s %(levelname)s '
log_verbosity = logging.INFO
if options.test_run:
log_format += '(%(filename)s:%(lineno)d) '
log_verbosity = logging.DEBUG
log_format += '%(message)s'
logging.basicConfig(level=log_verbosity, format=log_format)
server_path = os.path.realpath(__file__)
search_dirs = []
# Set runtime_dir when running locally.
if options.local_dir:
env.runtime_dir = os.getcwd()
search_dirs.append(os.path.dirname(server_path))
search_dirs += [env.runtime_dir, env.GetResourcesDir()]
config_file = utils.SearchFile(options.yaml_config, search_dirs)
if config_file and os.path.isfile(config_file):
Run(config_file)
else:
raise ShopFloorLauncherException('Launcher YAML config file not found: %s' %
options.yaml_config)
def ReactorStop():
"""Forces reactor to stop."""
logging.info('Stopping reactor.')
try:
reactor.stop()
except error.ReactorNotRunning:
pass
def DelayedStop(count_down):
"""Waits for services to end and stops the reactor.
Args:
count_down: seconds to wait before force shutdown.
"""
# Forces stop when count to zero
if count_down <= 0:
ReactorStop()
for svc in env.launcher_services:
if svc.subprocess:
logging.info('Wait for %s ... %d', svc.name, count_down)
reactor.callLater(1, DelayedStop, count_down - 1)
ReactorStop()
def SignalHandler(sig, unused_frame):
"""Initiates stopping sequence.
Launcher holds multiple subprocess, runs the event loop in twisted reactor,
hence it could not stop gracefully with system before shutdown handler. The
correct sequence is:
SIG[TERM|INT]
--> stop subprocesses (call utils.StopServices())
--> wait for subprocesses end (reactor.callLater())
--> stop reactor and ignore not running error.
"""
logging.info('Received signal %d', sig)
logging.info('Stopping system...')
utils.StopServices()
reactor.callLater(3, DelayedStop, 60)
if __name__ == '__main__':
signal.signal(signal.SIGTERM, SignalHandler)
signal.signal(signal.SIGINT, SignalHandler)
main()