initial import (Boeing r1752, NRL r878)
This commit is contained in:
commit
f8f46d28be
394 changed files with 99738 additions and 0 deletions
760
daemon/core/service.py
Normal file
760
daemon/core/service.py
Normal file
|
@ -0,0 +1,760 @@
|
|||
#
|
||||
# CORE
|
||||
# Copyright (c)2010-2012 the Boeing Company.
|
||||
# See the LICENSE file included in this distribution.
|
||||
#
|
||||
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
||||
#
|
||||
'''
|
||||
service.py: definition of CoreService class that is subclassed to define
|
||||
startup services and routing for nodes. A service is typically a daemon
|
||||
program launched when a node starts that provides some sort of
|
||||
service. The CoreServices class handles configuration messages for sending
|
||||
a list of available services to the GUI and for configuring individual
|
||||
services.
|
||||
'''
|
||||
|
||||
import sys, os, shlex
|
||||
|
||||
from itertools import repeat
|
||||
from core.api import coreapi
|
||||
from core.conf import ConfigurableManager, Configurable
|
||||
from core.misc.utils import maketuplefromstr, expandcorepath
|
||||
|
||||
servicelist = []
|
||||
|
||||
def addservice(service):
|
||||
global servicelist
|
||||
i = 0
|
||||
found = -1
|
||||
for s in servicelist:
|
||||
if s._group == service._group:
|
||||
found = i
|
||||
elif (found >= 0):
|
||||
# insert service into list next to existing group
|
||||
i = found + 1
|
||||
break
|
||||
i += 1
|
||||
servicelist.insert(i, service)
|
||||
|
||||
class CoreServices(ConfigurableManager):
|
||||
''' Class for interacting with a list of available startup services for
|
||||
nodes. Mostly used to convert a CoreService into a Config API
|
||||
message. This class lives in the Session object and remembers
|
||||
the default services configured for each node type, and any
|
||||
custom service configuration. A CoreService is not a Configurable.
|
||||
'''
|
||||
_name = "services"
|
||||
_type = coreapi.CORE_TLV_REG_UTILITY
|
||||
|
||||
def __init__(self, session):
|
||||
ConfigurableManager.__init__(self, session)
|
||||
# dict of default services tuples, key is node type
|
||||
self.defaultservices = {}
|
||||
# dict of tuple of service objects, key is node number
|
||||
self.customservices = {}
|
||||
importcmd = "from core.services import *"
|
||||
exec(importcmd)
|
||||
paths = self.session.getcfgitem('custom_services_dir')
|
||||
if paths:
|
||||
for path in paths.split(','):
|
||||
path = path.strip()
|
||||
self.importcustom(path)
|
||||
|
||||
def importcustom(self, path):
|
||||
''' Import services from a myservices directory.
|
||||
'''
|
||||
if not path or len(path) == 0:
|
||||
return
|
||||
if not os.path.isdir(path):
|
||||
self.session.warn("invalid custom service directory specified" \
|
||||
": %s" % path)
|
||||
return
|
||||
try:
|
||||
parentdir, childdir = os.path.split(path)
|
||||
if childdir == "services":
|
||||
raise ValueError, "use a unique custom services dir name, " \
|
||||
"not 'services'"
|
||||
sys.path.append(parentdir)
|
||||
exec("from %s import *" % childdir)
|
||||
except Exception, e:
|
||||
self.session.warn("error importing custom services from " \
|
||||
"%s:\n%s" % (path, e))
|
||||
|
||||
def reset(self):
|
||||
''' Called when config message with reset flag is received
|
||||
'''
|
||||
self.defaultservices.clear()
|
||||
self.customservices.clear()
|
||||
|
||||
def get(self):
|
||||
''' Get the list of available services.
|
||||
'''
|
||||
global servicelist
|
||||
return servicelist
|
||||
|
||||
def getservicebyname(self, name):
|
||||
''' Get a service class from the global servicelist given its name.
|
||||
Returns None when the name is not found.
|
||||
'''
|
||||
global servicelist
|
||||
for s in servicelist:
|
||||
if s._name == name:
|
||||
return s
|
||||
return None
|
||||
|
||||
def getdefaultservices(self, type):
|
||||
''' Get the list of default services that should be enabled for a
|
||||
node for the given node type.
|
||||
'''
|
||||
r = []
|
||||
if type in self.defaultservices:
|
||||
defaults = self.defaultservices[type]
|
||||
for name in defaults:
|
||||
s = self.getservicebyname(name)
|
||||
if s is None:
|
||||
self.session.warn("default service %s is unknown" % name)
|
||||
else:
|
||||
r.append(s)
|
||||
return r
|
||||
|
||||
def getcustomservice(self, objid, service):
|
||||
''' Get any custom service configured for the given node that
|
||||
matches the specified service name. If no custom service
|
||||
is found, return the specified service.
|
||||
'''
|
||||
if objid in self.customservices:
|
||||
for s in self.customservices[objid]:
|
||||
if s._name == service._name:
|
||||
return s
|
||||
return service
|
||||
|
||||
def setcustomservice(self, objid, service, values):
|
||||
''' Store service customizations in an instantiated service object
|
||||
using a list of values that came from a config message.
|
||||
'''
|
||||
if service._custom:
|
||||
s = service
|
||||
else:
|
||||
# instantiate the class, for storing config customization
|
||||
s = service()
|
||||
# values are new key=value format; not all keys need to be present
|
||||
# a missing key means go with the default
|
||||
if Configurable.haskeyvalues(values):
|
||||
for v in values:
|
||||
key, value = v.split('=', 1)
|
||||
s.setvalue(key, value)
|
||||
# old-style config, list of values
|
||||
else:
|
||||
s.fromvaluelist(values)
|
||||
|
||||
# assume custom service already in dict
|
||||
if service._custom:
|
||||
return
|
||||
# add the custom service to dict
|
||||
if objid in self.customservices:
|
||||
self.customservices[objid] += (s, )
|
||||
else:
|
||||
self.customservices[objid] = (s, )
|
||||
|
||||
def addservicestonode(self, node, nodetype, services_str, verbose):
|
||||
''' Populate the node.service list using (1) the list of services
|
||||
requested from the services TLV, (2) using any custom service
|
||||
configuration, or (3) using the default services for this node type.
|
||||
'''
|
||||
if services_str is not None:
|
||||
services = services_str.split('|')
|
||||
for name in services:
|
||||
s = self.getservicebyname(name)
|
||||
if s is None:
|
||||
self.session.warn("configured service %s for node %s is " \
|
||||
"unknown" % (name, node.name))
|
||||
continue
|
||||
if verbose:
|
||||
self.session.info("adding configured service %s to " \
|
||||
"node %s" % (s._name, node.name))
|
||||
s = self.getcustomservice(node.objid, s)
|
||||
node.addservice(s)
|
||||
else:
|
||||
services = self.getdefaultservices(nodetype)
|
||||
for s in services:
|
||||
if verbose:
|
||||
self.session.info("adding default service %s to node %s" % \
|
||||
(s._name, node.name))
|
||||
s = self.getcustomservice(node.objid, s)
|
||||
node.addservice(s)
|
||||
|
||||
def getallconfigs(self):
|
||||
''' Return (nodenum, service) tuples for all stored configs.
|
||||
Used when reconnecting to a session or opening XML.
|
||||
'''
|
||||
r = []
|
||||
for nodenum in self.customservices:
|
||||
for s in self.customservices[nodenum]:
|
||||
r.append( (nodenum, s) )
|
||||
return r
|
||||
|
||||
def getallfiles(self, service):
|
||||
''' Return all customized files stored with a service.
|
||||
Used when reconnecting to a session or opening XML.
|
||||
'''
|
||||
r = []
|
||||
if not service._custom:
|
||||
return r
|
||||
for filename in service._configs:
|
||||
data = self.getservicefiledata(service, filename)
|
||||
if data is None:
|
||||
continue
|
||||
r.append( (filename, data) )
|
||||
return r
|
||||
|
||||
def bootnodeservices(self, node):
|
||||
''' Start all services on a node.
|
||||
'''
|
||||
services = sorted(node.services,
|
||||
key=lambda service: service._startindex)
|
||||
for s in services:
|
||||
try:
|
||||
t = float(s._starttime)
|
||||
if t > 0.0:
|
||||
fn = self.bootnodeservice
|
||||
self.session.evq.add_event(t, fn, node, s, services)
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
self.bootnodeservice(node, s, services)
|
||||
|
||||
def bootnodeservice(self, node, s, services):
|
||||
''' Start a service on a node. Create private dirs, generate config
|
||||
files, and execute startup commands.
|
||||
'''
|
||||
if s._custom:
|
||||
self.bootnodecustomservice(node, s, services)
|
||||
return
|
||||
if node.verbose:
|
||||
node.info("starting service %s (%s)" % (s._name, s._startindex))
|
||||
for d in s._dirs:
|
||||
try:
|
||||
node.privatedir(d)
|
||||
except Exception, e:
|
||||
node.warn("Error making node %s dir %s: %s" % \
|
||||
(node.name, d, e))
|
||||
for filename in s.getconfigfilenames(node.objid, services):
|
||||
cfg = s.generateconfig(node, filename, services)
|
||||
node.nodefile(filename, cfg)
|
||||
for cmd in s.getstartup(node, services):
|
||||
try:
|
||||
# NOTE: this wait=False can be problematic!
|
||||
node.cmd(shlex.split(cmd), wait = False)
|
||||
except:
|
||||
node.warn("error starting command %s" % cmd)
|
||||
|
||||
def bootnodecustomservice(self, node, s, services):
|
||||
''' Start a custom service on a node. Create private dirs, use supplied
|
||||
config files, and execute supplied startup commands.
|
||||
'''
|
||||
if node.verbose:
|
||||
node.info("starting service %s (%s)(custom)" % (s._name, s._startindex))
|
||||
for d in s._dirs:
|
||||
try:
|
||||
node.privatedir(d)
|
||||
except Exception, e:
|
||||
node.warn("Error making node %s dir %s: %s" % \
|
||||
(node.name, d, e))
|
||||
for i, filename in enumerate(s._configs):
|
||||
if len(filename) == 0:
|
||||
continue
|
||||
cfg = self.getservicefiledata(s, filename)
|
||||
if cfg is None:
|
||||
cfg = s.generateconfig(node, filename, services)
|
||||
# cfg may have a file:/// url for copying from a file
|
||||
try:
|
||||
if self.copyservicefile(node, filename, cfg):
|
||||
continue
|
||||
except IOError, e:
|
||||
node.warn("Error copying service file %s" % filename)
|
||||
node.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
|
||||
"service:%s" % s._name,
|
||||
"error copying service file '%s': %s" % (filename, e))
|
||||
continue
|
||||
node.nodefile(filename, cfg)
|
||||
|
||||
for cmd in s._startup:
|
||||
try:
|
||||
# NOTE: this wait=False can be problematic!
|
||||
node.cmd(shlex.split(cmd), wait = False)
|
||||
except:
|
||||
node.warn("error starting command %s" % cmd)
|
||||
|
||||
def copyservicefile(self, node, filename, cfg):
|
||||
''' Given a configured service filename and config, determine if the
|
||||
config references an existing file that should be copied.
|
||||
Returns True for local files, False for generated.
|
||||
'''
|
||||
if cfg[:7] == 'file://':
|
||||
src = cfg[7:]
|
||||
src = src.split('\n')[0]
|
||||
src = expandcorepath(src, node.session, node)
|
||||
# TODO: glob here
|
||||
node.nodefilecopy(filename, src, mode = 0644)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def validatenodeservices(self, node):
|
||||
''' Run validation commands for all services on a node.
|
||||
'''
|
||||
services = sorted(node.services,
|
||||
key=lambda service: service._startindex)
|
||||
for s in services:
|
||||
self.validatenodeservice(node, s, services)
|
||||
|
||||
def validatenodeservice(self, node, s, services):
|
||||
''' Run the validation command(s) for a service.
|
||||
'''
|
||||
if node.verbose:
|
||||
node.info("validating service %s (%s)" % (s._name, s._startindex))
|
||||
if s._custom:
|
||||
validate_cmds = s._validate
|
||||
else:
|
||||
validate_cmds = s.getvalidate(node, services)
|
||||
for cmd in validate_cmds:
|
||||
if node.verbose:
|
||||
node.info("validating service %s using: %s" % (s._name, cmd))
|
||||
try:
|
||||
(status, result) = node.cmdresult(shlex.split(cmd))
|
||||
if status != 0:
|
||||
raise ValueError, "non-zero exit status"
|
||||
except:
|
||||
node.warn("validation command '%s' failed" % cmd)
|
||||
node.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
|
||||
"service:%s" % s._name,
|
||||
"validate command failed: %s" % cmd)
|
||||
|
||||
def stopnodeservices(self, node):
|
||||
''' Stop all services on a node.
|
||||
'''
|
||||
services = sorted(node.services,
|
||||
key=lambda service: service._startindex)
|
||||
for s in services:
|
||||
self.stopnodeservice(node, s)
|
||||
|
||||
def stopnodeservice(self, node, s):
|
||||
''' Stop a service on a node.
|
||||
'''
|
||||
for cmd in s._shutdown:
|
||||
try:
|
||||
# NOTE: this wait=False can be problematic!
|
||||
node.cmd(shlex.split(cmd), wait = False)
|
||||
except:
|
||||
node.warn("error running stop command %s" % cmd)
|
||||
|
||||
|
||||
def configure_request(self, msg):
|
||||
''' Receive configuration message for configuring services.
|
||||
With a request flag set, a list of services has been requested.
|
||||
When the opaque field is present, a specific service is being
|
||||
configured or requested.
|
||||
'''
|
||||
objname = msg.gettlv(coreapi.CORE_TLV_CONF_OBJ)
|
||||
conftype = msg.gettlv(coreapi.CORE_TLV_CONF_TYPE)
|
||||
nodenum = msg.gettlv(coreapi.CORE_TLV_CONF_NODE)
|
||||
sessionnum = msg.gettlv(coreapi.CORE_TLV_CONF_SESSION)
|
||||
opaque = msg.gettlv(coreapi.CORE_TLV_CONF_OPAQUE)
|
||||
|
||||
# send back a list of available services
|
||||
if opaque is None:
|
||||
global servicelist
|
||||
tf = coreapi.CONF_TYPE_FLAGS_NONE
|
||||
datatypes = tuple(repeat(coreapi.CONF_DATA_TYPE_BOOL,
|
||||
len(servicelist)))
|
||||
vals = "|".join(repeat('0', len(servicelist)))
|
||||
names = map(lambda x: x._name, servicelist)
|
||||
captions = "|".join(names)
|
||||
possiblevals = ""
|
||||
for s in servicelist:
|
||||
if s._custom_needed:
|
||||
possiblevals += '1'
|
||||
possiblevals += '|'
|
||||
groups = self.buildgroups(servicelist)
|
||||
# send back the properties for this service
|
||||
else:
|
||||
if nodenum is None:
|
||||
return None
|
||||
n = self.session.obj(nodenum)
|
||||
if n is None:
|
||||
self.session.warn("Request to configure service %s for " \
|
||||
"unknown node %s" % (svc._name, nodenum))
|
||||
return None
|
||||
servicesstring = opaque.split(':')
|
||||
services = self.servicesfromopaque(opaque, n.objid)
|
||||
if len(services) < 1:
|
||||
return None
|
||||
if len(servicesstring) == 3:
|
||||
# a file request: e.g. "service:zebra:quagga.conf"
|
||||
return self.getservicefile(services, n, servicesstring[2])
|
||||
|
||||
# the first service in the list is the one being configured
|
||||
svc = services[0]
|
||||
# send back:
|
||||
# dirs, configs, startindex, startup, shutdown, metadata, config
|
||||
tf = coreapi.CONF_TYPE_FLAGS_UPDATE
|
||||
datatypes = tuple(repeat(coreapi.CONF_DATA_TYPE_STRING,
|
||||
len(svc.keys)))
|
||||
vals = svc.tovaluelist(n, services)
|
||||
captions = None
|
||||
possiblevals = None
|
||||
groups = None
|
||||
|
||||
tlvdata = ""
|
||||
if nodenum is not None:
|
||||
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_NODE,
|
||||
nodenum)
|
||||
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OBJ,
|
||||
self._name)
|
||||
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_TYPE, tf)
|
||||
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_DATA_TYPES,
|
||||
datatypes)
|
||||
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_VALUES,
|
||||
vals)
|
||||
if captions:
|
||||
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_CAPTIONS,
|
||||
captions)
|
||||
if possiblevals:
|
||||
tlvdata += coreapi.CoreConfTlv.pack(
|
||||
coreapi.CORE_TLV_CONF_POSSIBLE_VALUES, possiblevals)
|
||||
if groups:
|
||||
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_GROUPS,
|
||||
groups)
|
||||
if sessionnum is not None:
|
||||
tlvdata += coreapi.CoreConfTlv.pack(
|
||||
coreapi.CORE_TLV_CONF_SESSION, sessionnum)
|
||||
if opaque:
|
||||
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OPAQUE,
|
||||
opaque)
|
||||
return coreapi.CoreConfMessage.pack(0, tlvdata)
|
||||
|
||||
|
||||
def configure_values(self, msg, values):
|
||||
''' Receive configuration message for configuring services.
|
||||
With a request flag set, a list of services has been requested.
|
||||
When the opaque field is present, a specific service is being
|
||||
configured or requested.
|
||||
'''
|
||||
nodenum = msg.gettlv(coreapi.CORE_TLV_CONF_NODE)
|
||||
opaque = msg.gettlv(coreapi.CORE_TLV_CONF_OPAQUE)
|
||||
|
||||
errmsg = "services config message that I don't know how to handle"
|
||||
if values is None:
|
||||
self.session.info(errmsg)
|
||||
return None
|
||||
else:
|
||||
values = values.split('|')
|
||||
|
||||
if opaque is None:
|
||||
# store default services for a node type in self.defaultservices[]
|
||||
data_types = msg.gettlv(coreapi.CORE_TLV_CONF_DATA_TYPES)
|
||||
if values is None or data_types is None or \
|
||||
data_types[0] != coreapi.CONF_DATA_TYPE_STRING:
|
||||
self.session.info(errmsg)
|
||||
return None
|
||||
key = values.pop(0)
|
||||
self.defaultservices[key] = values
|
||||
self.session.info("default services for type %s set to %s" % \
|
||||
(key, values))
|
||||
else:
|
||||
# store service customized config in self.customservices[]
|
||||
if nodenum is None:
|
||||
return None
|
||||
services = self.servicesfromopaque(opaque, nodenum)
|
||||
if len(services) < 1:
|
||||
return None
|
||||
svc = services[0]
|
||||
self.setcustomservice(nodenum, svc, values)
|
||||
return None
|
||||
|
||||
def servicesfromopaque(self, opaque, objid):
|
||||
''' Build a list of services from an opaque data string.
|
||||
'''
|
||||
services = []
|
||||
servicesstring = opaque.split(':')
|
||||
if servicesstring[0] != "service":
|
||||
return []
|
||||
servicenames = servicesstring[1].split(',')
|
||||
for name in servicenames:
|
||||
s = self.getservicebyname(name)
|
||||
s = self.getcustomservice(objid, s)
|
||||
if s is None:
|
||||
self.session.warn("Request for unknown service '%s'" % name)
|
||||
return []
|
||||
services.append(s)
|
||||
return services
|
||||
|
||||
def buildgroups(self, servicelist):
|
||||
''' Build a string of groups for use in a configuration message given
|
||||
a list of services. The group list string has the format
|
||||
"title1:1-5|title2:6-9|10-12", where title is an optional group title
|
||||
and i-j is a numeric range of value indices; groups are
|
||||
separated by commas.
|
||||
'''
|
||||
i = 0
|
||||
r = ""
|
||||
lastgroup = "<undefined>"
|
||||
for service in servicelist:
|
||||
i += 1
|
||||
group = service._group
|
||||
if group != lastgroup:
|
||||
lastgroup = group
|
||||
# finish previous group
|
||||
if i > 1:
|
||||
r += "-%d|" % (i -1)
|
||||
# optionally include group title
|
||||
if group == "":
|
||||
r += "%d" % i
|
||||
else:
|
||||
r += "%s:%d" % (group, i)
|
||||
# finish the last group list
|
||||
if i > 0:
|
||||
r += "-%d" % i
|
||||
return r
|
||||
|
||||
def getservicefile(self, services, node, filename):
|
||||
''' Send a File Message when the GUI has requested a service file.
|
||||
The file data is either auto-generated or comes from an existing config.
|
||||
'''
|
||||
svc = services[0]
|
||||
# get the filename and determine the config file index
|
||||
if svc._custom:
|
||||
cfgfiles = svc._configs
|
||||
else:
|
||||
cfgfiles = svc.getconfigfilenames(node.objid, services)
|
||||
if filename not in cfgfiles:
|
||||
self.session.warn("Request for unknown file '%s' for service '%s'" \
|
||||
% (filename, services[0]))
|
||||
return None
|
||||
|
||||
# get the file data
|
||||
data = self.getservicefiledata(svc, filename)
|
||||
if data is None:
|
||||
data = "%s" % (svc.generateconfig(node, filename, services))
|
||||
else:
|
||||
data = "%s" % data
|
||||
filetypestr = "service:%s" % svc._name
|
||||
|
||||
# send a file message
|
||||
flags = coreapi.CORE_API_ADD_FLAG
|
||||
tlvdata = coreapi.CoreFileTlv.pack(coreapi.CORE_TLV_FILE_NODE, node.objid)
|
||||
tlvdata += coreapi.CoreFileTlv.pack(coreapi.CORE_TLV_FILE_NAME, filename)
|
||||
tlvdata += coreapi.CoreFileTlv.pack(coreapi.CORE_TLV_FILE_TYPE, filetypestr)
|
||||
tlvdata += coreapi.CoreFileTlv.pack(coreapi.CORE_TLV_FILE_DATA, data)
|
||||
reply = coreapi.CoreFileMessage.pack(flags, tlvdata)
|
||||
return reply
|
||||
|
||||
def getservicefiledata(self, service, filename):
|
||||
''' Get the customized file data associated with a service. Return None
|
||||
for invalid filenames or missing file data.
|
||||
'''
|
||||
try:
|
||||
i = service._configs.index(filename)
|
||||
except ValueError:
|
||||
return None
|
||||
if i >= len(service._configtxt) or service._configtxt[i] is None:
|
||||
return None
|
||||
return service._configtxt[i]
|
||||
|
||||
def setservicefile(self, nodenum, type, filename, srcname, data):
|
||||
''' Receive a File Message from the GUI and store the customized file
|
||||
in the service config. The filename must match one from the list of
|
||||
config files in the service.
|
||||
'''
|
||||
if len(type.split(':')) < 2:
|
||||
self.session.warn("Received file type did not contain service info.")
|
||||
return
|
||||
if srcname is not None:
|
||||
raise NotImplementedError
|
||||
(svcid, svcname) = type.split(':')[:2]
|
||||
svc = self.getservicebyname(svcname)
|
||||
svc = self.getcustomservice(nodenum, svc)
|
||||
if svc is None:
|
||||
self.session.warn("Received filename for unknown service '%s'" % \
|
||||
svcname)
|
||||
return
|
||||
cfgfiles = svc._configs
|
||||
if filename not in cfgfiles:
|
||||
self.session.warn("Received unknown file '%s' for service '%s'" \
|
||||
% (filename, svcname))
|
||||
return
|
||||
i = cfgfiles.index(filename)
|
||||
configtxtlist = list(svc._configtxt)
|
||||
numitems = len(configtxtlist)
|
||||
if numitems < i+1:
|
||||
# add empty elements to list to support index assignment
|
||||
for j in range(1, (i + 2) - numitems):
|
||||
configtxtlist += None,
|
||||
configtxtlist[i] = data
|
||||
svc._configtxt = configtxtlist
|
||||
|
||||
def handleevent(self, msg):
|
||||
''' Handle an Event Message used to start, stop, restart, or validate
|
||||
a service on a given node.
|
||||
'''
|
||||
eventtype = msg.gettlv(coreapi.CORE_TLV_EVENT_TYPE)
|
||||
nodenum = msg.gettlv(coreapi.CORE_TLV_EVENT_NODE)
|
||||
name = msg.gettlv(coreapi.CORE_TLV_EVENT_NAME)
|
||||
try:
|
||||
node = self.session.obj(nodenum)
|
||||
except KeyError:
|
||||
self.session.warn("Ignoring event for service '%s', unknown node " \
|
||||
"'%s'" % (name, nodenum))
|
||||
return
|
||||
|
||||
services = self.servicesfromopaque(name, nodenum)
|
||||
for s in services:
|
||||
if eventtype == coreapi.CORE_EVENT_STOP or \
|
||||
eventtype == coreapi.CORE_EVENT_RESTART:
|
||||
self.stopnodeservice(node, s)
|
||||
if eventtype == coreapi.CORE_EVENT_START or \
|
||||
eventtype == coreapi.CORE_EVENT_RESTART:
|
||||
if s._custom:
|
||||
cmds = s._startup
|
||||
else:
|
||||
cmds = s.getstartup(node, services)
|
||||
for cmd in cmds:
|
||||
try:
|
||||
node.cmd(shlex.split(cmd), wait = False)
|
||||
except:
|
||||
node.warn("error starting command %s" % cmd)
|
||||
if eventtype == coreapi.CORE_EVENT_PAUSE:
|
||||
self.validatenodeservice(node, s, services)
|
||||
|
||||
|
||||
class CoreService(object):
|
||||
''' Parent class used for defining services.
|
||||
'''
|
||||
# service name should not include spaces
|
||||
_name = ""
|
||||
# group string allows grouping services together
|
||||
_group = ""
|
||||
# list name(s) of services that this service depends upon
|
||||
_depends = ()
|
||||
keys = ["dirs","files","startidx","cmdup","cmddown","cmdval","meta","starttime"]
|
||||
# private, per-node directories required by this service
|
||||
_dirs = ()
|
||||
# config files written by this service
|
||||
_configs = ()
|
||||
# index used to determine start order with other services
|
||||
_startindex = 0
|
||||
# time in seconds after runtime to run startup commands
|
||||
_starttime = ""
|
||||
# list of startup commands
|
||||
_startup = ()
|
||||
# list of shutdown commands
|
||||
_shutdown = ()
|
||||
# list of validate commands
|
||||
_validate = ()
|
||||
# metadata associated with this service
|
||||
_meta = ""
|
||||
# custom configuration text
|
||||
_configtxt = ()
|
||||
_custom = False
|
||||
_custom_needed = False
|
||||
|
||||
def __init__(self):
|
||||
''' Services are not necessarily instantiated. Classmethods may be used
|
||||
against their config. Services are instantiated when a custom
|
||||
configuration is used to override their default parameters.
|
||||
'''
|
||||
self._custom = True
|
||||
|
||||
@classmethod
|
||||
def getconfigfilenames(cls, nodenum, services):
|
||||
''' Return the tuple of configuration file filenames. This default method
|
||||
returns the cls._configs tuple, but this method may be overriden to
|
||||
provide node-specific filenames that may be based on other services.
|
||||
'''
|
||||
return cls._configs
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
''' Generate configuration file given a node object. The filename is
|
||||
provided to allow for multiple config files. The other services are
|
||||
provided to allow interdependencies (e.g. zebra and OSPF).
|
||||
Return the configuration string to be written to a file or sent
|
||||
to the GUI for customization.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def getstartup(cls, node, services):
|
||||
''' Return the tuple of startup commands. This default method
|
||||
returns the cls._startup tuple, but this method may be
|
||||
overriden to provide node-specific commands that may be
|
||||
based on other services.
|
||||
'''
|
||||
return cls._startup
|
||||
|
||||
@classmethod
|
||||
def getvalidate(cls, node, services):
|
||||
''' Return the tuple of validate commands. This default method
|
||||
returns the cls._validate tuple, but this method may be
|
||||
overriden to provide node-specific commands that may be
|
||||
based on other services.
|
||||
'''
|
||||
return cls._validate
|
||||
|
||||
@classmethod
|
||||
def tovaluelist(cls, node, services):
|
||||
''' Convert service properties into a string list of key=value pairs,
|
||||
separated by "|".
|
||||
'''
|
||||
valmap = [cls._dirs, cls._configs, cls._startindex, cls._startup,
|
||||
cls._shutdown, cls._validate, cls._meta, cls._starttime]
|
||||
if not cls._custom:
|
||||
# this is always reached due to classmethod
|
||||
valmap[valmap.index(cls._configs)] = \
|
||||
cls.getconfigfilenames(node.objid, services)
|
||||
valmap[valmap.index(cls._startup)] = \
|
||||
cls.getstartup(node, services)
|
||||
vals = map( lambda a,b: "%s=%s" % (a, str(b)), cls.keys, valmap)
|
||||
return "|".join(vals)
|
||||
|
||||
def fromvaluelist(self, values):
|
||||
''' Convert list of values into properties for this instantiated
|
||||
(customized) service.
|
||||
'''
|
||||
# TODO: support empty value? e.g. override default meta with ''
|
||||
for key in self.keys:
|
||||
try:
|
||||
self.setvalue(key, values[self.keys.index(key)])
|
||||
except IndexError:
|
||||
# old config does not need to have new keys
|
||||
pass
|
||||
|
||||
def setvalue(self, key, value):
|
||||
if key not in self.keys:
|
||||
raise ValueError
|
||||
# this handles data conversion to int, string, and tuples
|
||||
if value:
|
||||
if key == "startidx":
|
||||
value = int(value)
|
||||
elif key == "meta":
|
||||
value = str(value)
|
||||
else:
|
||||
value = maketuplefromstr(value, str)
|
||||
|
||||
if key == "dirs":
|
||||
self._dirs = value
|
||||
elif key == "files":
|
||||
self._configs = value
|
||||
elif key == "startidx":
|
||||
self._startindex = value
|
||||
elif key == "cmdup":
|
||||
self._startup = value
|
||||
elif key == "cmddown":
|
||||
self._shutdown = value
|
||||
elif key == "cmdval":
|
||||
self._validate = value
|
||||
elif key == "meta":
|
||||
self._meta = value
|
||||
elif key == "starttime":
|
||||
self._starttime = value
|
Loading…
Add table
Add a link
Reference in a new issue