blob: 6c0e28bc02954d3a8a88bf5d6f693b2af774b223 [file] [log] [blame]
# 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.
"""The base class of shopfloor launcher services."""
# Python twisted's module creates definition dynamically
# pylint: disable=E1101
import logging
import os
import time
from twisted.internet import protocol, reactor
import factory_common # pylint: disable=W0611
from cros.factory.shopfloor.launcher import ShopFloorLauncherException
from cros.factory.shopfloor.launcher import env
class ServiceBase(protocol.ProcessProtocol):
"""Base class of shopfloor launcher service.
The launcher runs external executables and hooks their standard input and
outputs. The derived class should call ServiceBase.__init__() to setup
ServiceBase and then call SetConfig(param_dict) to setup attributes.
Attributes:
start_time: Records the time on Start().
connection: True if the spawned process didn't close its stdin.
subprocess: Twisted transport object that controls the spawned subprocess.
pid: Process ID in int.
stopping: Flag indicates this subprocess is on the halfway to stop.
# For setting up spawn parameters:
executable: Executable full pathname.
name: Service name for logging.
args: List of arguments, not including executable itself.
path: Startup path.
# Optional spawn parameters:
logpipe: True to redirect stderr to launcher log.
auto_restart: True when the service needs to be restarted.
daemon: True when the executable daemonize itself.
"""
def __init__(self):
self.start_time = 0
self.connection = False
self.subprocess = None
self.pid = None
self.stopping = False
# Spawn parameters
self.executable = None
self.name = 'svc'
self.args = []
self.path = env.runtime_dir
self.uid = os.getuid()
self.gid = os.getgid()
self.logpipe = False
self.auto_restart = False
self.daemon = False
# Twisted process protocol callbacks
def connectionMade(self):
"""On process started and its stdin is in a good place to write data."""
self.connection = True
def outReceived(self, data):
"""On data received from the process' stdout pipe."""
if self.logpipe:
logging.info('%s: %s', self.name, data)
def errReceived(self, data):
"""On data received from stderr pipe."""
if self.logpipe:
logging.warn('%s: %s', self.name, data.rstrip())
def inConnectionLost(self):
"""Subprocess' stdin has closed."""
self._ConnectionLost('stdin')
self.connection = False
def outConnectionLost(self):
"""Subprocess' stdout has closed."""
self._ConnectionLost('stdout')
def errConnectionLost(self):
"""Subprocess' stderr has closed."""
self._ConnectionLost('stderr')
def processEnded(self, status):
"""Subprocess has been ended properly."""
if self.daemon:
return
self.subprocess = None
if status.value.exitCode != 0:
logging.warn('process ended with status %s', status)
if self.auto_restart:
if self.stopping:
logging.info('%s ended', self.name)
else:
logging.info('%s restarting', self.name)
if time.time() - self.start_time > 5:
self.Start()
else:
logging.error('%s respawn too fast, restart failed', self.name)
def _ConnectionLost(self, pipe):
"""Genetic handler for broken pipes."""
if not self.daemon and not self.stopping :
logging.info('%s was closed', pipe)
def SetConfig(self, conf):
"""Sets service configuration."""
attrs = ['name', 'executable', 'path', 'args', 'uid', 'pid',
'logpipe', 'auto_restart', 'daemon']
for key in attrs:
if key in conf:
setattr(self, key, conf[key])
def Start(self):
"""Starts background service."""
self.stopping = False
self.subprocess = reactor.spawnProcess(self, self.executable,
[self.executable] + self.args,
{}, self.path)
self.pid = self.subprocess.pid
self.start_time = time.time()
def Stop(self):
"""Stops background service."""
self.stopping = True
self.transport.loseConnection()
self.transport.signalProcess('KILL')
def CheckPortPrivilage(self, port):
"""Check binding port number if not run as root."""
if port < 1024:
if os.getuid() != 0:
raise ShopFloorLauncherException('%s port < 1024' % self.name)