daemon: Add a startup service.

When enabled, the startup service ensures that other node services
start in order (according to the service _startindex value) and that
the prior service completes before the next service starts.  It also
captures any output from startup commands in a file named
'startup.log'.
This commit is contained in:
Tom Goff 2015-09-11 17:27:08 -04:00
parent 2c8744f14e
commit ac19cfa7ff
3 changed files with 51 additions and 7 deletions

View file

@ -63,7 +63,8 @@ class CoreServices(ConfigurableManager):
for path in paths.split(','): for path in paths.split(','):
path = path.strip() path = path.strip()
self.importcustom(path) self.importcustom(path)
self.isStartupService = startup.Startup.isStartupService
def importcustom(self, path): def importcustom(self, path):
''' Import services from a myservices directory. ''' Import services from a myservices directory.
''' '''
@ -217,24 +218,25 @@ class CoreServices(ConfigurableManager):
''' '''
services = sorted(node.services, services = sorted(node.services,
key=lambda service: service._startindex) key=lambda service: service._startindex)
useStartupService = any(map(self.isStartupService, services))
for s in services: for s in services:
if len(str(s._starttime)) > 0: if len(str(s._starttime)) > 0:
try: try:
t = float(s._starttime) t = float(s._starttime)
if t > 0.0: if t > 0.0:
fn = self.bootnodeservice fn = self.bootnodeservice
self.session.evq.add_event(t, fn, node, s, services) self.session.evq.add_event(t, fn, node, s, services, False)
continue continue
except ValueError: except ValueError:
pass pass
self.bootnodeservice(node, s, services) self.bootnodeservice(node, s, services, useStartupService)
def bootnodeservice(self, node, s, services): def bootnodeservice(self, node, s, services, useStartupService):
''' Start a service on a node. Create private dirs, generate config ''' Start a service on a node. Create private dirs, generate config
files, and execute startup commands. files, and execute startup commands.
''' '''
if s._custom: if s._custom:
self.bootnodecustomservice(node, s, services) self.bootnodecustomservice(node, s, services, useStartupService)
return return
if node.verbose: if node.verbose:
node.info("starting service %s (%s)" % (s._name, s._startindex)) node.info("starting service %s (%s)" % (s._name, s._startindex))
@ -247,6 +249,8 @@ class CoreServices(ConfigurableManager):
for filename in s.getconfigfilenames(node.objid, services): for filename in s.getconfigfilenames(node.objid, services):
cfg = s.generateconfig(node, filename, services) cfg = s.generateconfig(node, filename, services)
node.nodefile(filename, cfg) node.nodefile(filename, cfg)
if useStartupService and not self.isStartupService(s):
return
for cmd in s.getstartup(node, services): for cmd in s.getstartup(node, services):
try: try:
# NOTE: this wait=False can be problematic! # NOTE: this wait=False can be problematic!
@ -254,7 +258,7 @@ class CoreServices(ConfigurableManager):
except Exception, e: except Exception, e:
node.warn("error starting command %s: %s" % (cmd, e)) node.warn("error starting command %s: %s" % (cmd, e))
def bootnodecustomservice(self, node, s, services): def bootnodecustomservice(self, node, s, services, useStartupService):
''' Start a custom service on a node. Create private dirs, use supplied ''' Start a custom service on a node. Create private dirs, use supplied
config files, and execute supplied startup commands. config files, and execute supplied startup commands.
''' '''
@ -284,6 +288,9 @@ class CoreServices(ConfigurableManager):
continue continue
node.nodefile(filename, cfg) node.nodefile(filename, cfg)
if useStartupService and not self.isStartupService(s):
return
for cmd in s._startup: for cmd in s._startup:
try: try:
# NOTE: this wait=False can be problematic! # NOTE: this wait=False can be problematic!

View file

@ -3,4 +3,4 @@
Services available to nodes can be put in this directory. Everything listed in Services available to nodes can be put in this directory. Everything listed in
__all__ is automatically loaded by the main core module. __all__ is automatically loaded by the main core module.
""" """
__all__ = ["quagga", "nrl", "xorp", "bird", "utility", "security", "ucarp", "dockersvc"] __all__ = ["quagga", "nrl", "xorp", "bird", "utility", "security", "ucarp", "dockersvc", 'startup']

View file

@ -0,0 +1,37 @@
from core.service import CoreService, addservice
from sys import maxint
from inspect import isclass
class Startup(CoreService):
'A CORE service to start other services in order, serially'
_name = 'startup'
_group = 'Utility'
_depends = ()
_dirs = ()
_configs = ('startup.sh', )
_startindex = maxint
_startup = ('sh startup.sh', )
_shutdown = ()
_validate = ()
@staticmethod
def isStartupService(s):
return isinstance(s, Startup) or \
(isclass(s) and issubclass(s, Startup))
@classmethod
def generateconfig(cls, node, filename, services):
if filename != cls._configs[0]:
return ''
script = '#!/bin/sh\n' \
'# auto-generated by Startup (startup.py)\n\n' \
'exec > startup.log 2>&1\n\n'
for s in sorted(services, key = lambda x: x._startindex):
if cls.isStartupService(s) or len(str(s._starttime)) > 0:
continue
start = '\n'.join(s.getstartup(node, services))
if start:
script += start + '\n'
return script
addservice(Startup)