2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Definition of CoreService class that is subclassed to define
|
2013-08-29 15:21:13 +01:00
|
|
|
startup services and routing for nodes. A service is typically a daemon
|
2017-06-20 02:09:28 +01:00
|
|
|
program launched when a node starts that provides some sort of service.
|
|
|
|
The CoreServices class handles configuration messages for sending
|
2013-08-29 15:21:13 +01:00
|
|
|
a list of available services to the GUI and for configuring individual
|
|
|
|
services.
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
|
2019-06-03 22:36:21 +01:00
|
|
|
import enum
|
2019-02-16 17:50:19 +00:00
|
|
|
import logging
|
2018-06-20 20:59:07 +01:00
|
|
|
import time
|
|
|
|
from multiprocessing.pool import ThreadPool
|
|
|
|
|
2019-04-30 07:31:47 +01:00
|
|
|
from core import CoreCommandError, utils
|
2019-06-03 22:36:21 +01:00
|
|
|
from core.constants import which
|
2019-04-30 07:31:47 +01:00
|
|
|
from core.emulator.data import FileData
|
2019-09-10 22:20:51 +01:00
|
|
|
from core.emulator.enumerations import MessageFlags, RegisterTlvs
|
2017-06-20 02:09:28 +01:00
|
|
|
|
|
|
|
|
2018-06-20 20:59:07 +01:00
|
|
|
class ServiceBootError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceMode(enum.Enum):
|
|
|
|
BLOCKING = 0
|
|
|
|
NON_BLOCKING = 1
|
|
|
|
TIMER = 2
|
|
|
|
|
|
|
|
|
2018-08-01 18:13:57 +01:00
|
|
|
class ServiceDependencies(object):
|
|
|
|
"""
|
|
|
|
Can generate boot paths for services, based on their dependencies. Will validate
|
|
|
|
that all services will be booted and that all dependencies exist within the services provided.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, services):
|
|
|
|
# helpers to check validity
|
|
|
|
self.dependents = {}
|
|
|
|
self.booted = set()
|
|
|
|
self.node_services = {}
|
|
|
|
for service in services:
|
|
|
|
self.node_services[service.name] = service
|
|
|
|
for dependency in service.dependencies:
|
|
|
|
dependents = self.dependents.setdefault(dependency, set())
|
|
|
|
dependents.add(service.name)
|
|
|
|
|
|
|
|
# used to find paths
|
|
|
|
self.path = []
|
|
|
|
self.visited = set()
|
|
|
|
self.visiting = set()
|
|
|
|
|
|
|
|
def boot_paths(self):
|
|
|
|
"""
|
|
|
|
Generates the boot paths for the services provided to the class.
|
|
|
|
|
|
|
|
:return: list of services to boot, in order
|
2019-04-30 07:31:47 +01:00
|
|
|
:rtype: list[core.coreservices.CoreService]
|
2018-08-01 18:13:57 +01:00
|
|
|
"""
|
|
|
|
paths = []
|
2019-05-06 05:23:43 +01:00
|
|
|
for name in self.node_services:
|
|
|
|
service = self.node_services[name]
|
2018-08-01 18:13:57 +01:00
|
|
|
if service.name in self.booted:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.debug(
|
|
|
|
"skipping service that will already be booted: %s", service.name
|
|
|
|
)
|
2018-08-01 18:13:57 +01:00
|
|
|
continue
|
|
|
|
|
|
|
|
path = self._start(service)
|
|
|
|
if path:
|
|
|
|
paths.append(path)
|
|
|
|
|
2019-05-06 05:23:43 +01:00
|
|
|
if self.booted != set(self.node_services):
|
2019-09-10 23:10:24 +01:00
|
|
|
raise ValueError(
|
|
|
|
"failure to boot all services: %s != %s"
|
|
|
|
% (self.booted, self.node_services.keys())
|
|
|
|
)
|
2018-08-01 18:13:57 +01:00
|
|
|
|
|
|
|
return paths
|
|
|
|
|
|
|
|
def _reset(self):
|
|
|
|
self.path = []
|
|
|
|
self.visited.clear()
|
|
|
|
self.visiting.clear()
|
|
|
|
|
|
|
|
def _start(self, service):
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("starting service dependency check: %s", service.name)
|
2018-08-01 18:13:57 +01:00
|
|
|
self._reset()
|
|
|
|
return self._visit(service)
|
|
|
|
|
|
|
|
def _visit(self, current_service):
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("visiting service(%s): %s", current_service.name, self.path)
|
2018-08-01 18:13:57 +01:00
|
|
|
self.visited.add(current_service.name)
|
|
|
|
self.visiting.add(current_service.name)
|
|
|
|
|
|
|
|
# dive down
|
|
|
|
for service_name in current_service.dependencies:
|
|
|
|
if service_name not in self.node_services:
|
2019-09-10 23:10:24 +01:00
|
|
|
raise ValueError(
|
|
|
|
"required dependency was not included in node services: %s"
|
|
|
|
% service_name
|
|
|
|
)
|
2018-08-01 18:13:57 +01:00
|
|
|
|
|
|
|
if service_name in self.visiting:
|
2019-09-10 23:10:24 +01:00
|
|
|
raise ValueError(
|
|
|
|
"cyclic dependency at service(%s): %s"
|
|
|
|
% (current_service.name, service_name)
|
|
|
|
)
|
2018-08-01 18:13:57 +01:00
|
|
|
|
|
|
|
if service_name not in self.visited:
|
|
|
|
service = self.node_services[service_name]
|
|
|
|
self._visit(service)
|
|
|
|
|
|
|
|
# add service when bottom is found
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("adding service to boot path: %s", current_service.name)
|
2018-08-01 18:13:57 +01:00
|
|
|
self.booted.add(current_service.name)
|
|
|
|
self.path.append(current_service)
|
|
|
|
self.visiting.remove(current_service.name)
|
|
|
|
|
|
|
|
# rise back up
|
|
|
|
for service_name in self.dependents.get(current_service.name, []):
|
|
|
|
if service_name not in self.visited:
|
|
|
|
service = self.node_services[service_name]
|
|
|
|
self._visit(service)
|
|
|
|
|
|
|
|
return self.path
|
|
|
|
|
|
|
|
|
2018-06-15 22:03:27 +01:00
|
|
|
class ServiceShim(object):
|
2019-09-10 23:10:24 +01:00
|
|
|
keys = [
|
|
|
|
"dirs",
|
|
|
|
"files",
|
|
|
|
"startidx",
|
|
|
|
"cmdup",
|
|
|
|
"cmddown",
|
|
|
|
"cmdval",
|
|
|
|
"meta",
|
|
|
|
"starttime",
|
|
|
|
]
|
2018-06-15 22:03:27 +01:00
|
|
|
|
|
|
|
@classmethod
|
2018-06-21 22:56:30 +01:00
|
|
|
def tovaluelist(cls, node, service):
|
2018-06-15 22:03:27 +01:00
|
|
|
"""
|
|
|
|
Convert service properties into a string list of key=value pairs,
|
|
|
|
separated by "|".
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to get value list for
|
2018-06-21 22:56:30 +01:00
|
|
|
:param CoreService service: service to get value list for
|
2018-06-15 22:03:27 +01:00
|
|
|
:return: value list string
|
|
|
|
:rtype: str
|
|
|
|
"""
|
2018-06-22 16:16:59 +01:00
|
|
|
start_time = 0
|
|
|
|
start_index = 0
|
2019-09-10 23:10:24 +01:00
|
|
|
valmap = [
|
|
|
|
service.dirs,
|
|
|
|
service.configs,
|
|
|
|
start_index,
|
|
|
|
service.startup,
|
|
|
|
service.shutdown,
|
|
|
|
service.validate,
|
|
|
|
service.meta,
|
|
|
|
start_time,
|
|
|
|
]
|
2018-06-15 22:03:27 +01:00
|
|
|
if not service.custom:
|
2018-06-22 23:47:02 +01:00
|
|
|
valmap[1] = service.get_configs(node)
|
|
|
|
valmap[3] = service.get_startup(node)
|
2018-10-11 21:28:02 +01:00
|
|
|
vals = ["%s=%s" % (x, y) for x, y in zip(cls.keys, valmap)]
|
2018-06-15 22:03:27 +01:00
|
|
|
return "|".join(vals)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def fromvaluelist(cls, service, values):
|
|
|
|
"""
|
|
|
|
Convert list of values into properties for this instantiated
|
|
|
|
(customized) service.
|
|
|
|
|
|
|
|
:param CoreService service: service to get value list for
|
|
|
|
:param dict values: value list to set properties from
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
# TODO: support empty value? e.g. override default meta with ''
|
|
|
|
for key in cls.keys:
|
|
|
|
try:
|
|
|
|
cls.setvalue(service, key, values[cls.keys.index(key)])
|
|
|
|
except IndexError:
|
|
|
|
# old config does not need to have new keys
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("error indexing into key")
|
2018-06-15 22:03:27 +01:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def setvalue(cls, service, key, value):
|
|
|
|
"""
|
|
|
|
Set values for this service.
|
|
|
|
|
|
|
|
:param CoreService service: service to get value list for
|
|
|
|
:param str key: key to set value for
|
|
|
|
:param value: value of key to set
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
if key not in cls.keys:
|
2018-08-02 02:22:41 +01:00
|
|
|
raise ValueError("key `%s` not in `%s`" % (key, cls.keys))
|
2018-06-15 22:03:27 +01:00
|
|
|
# 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 = utils.make_tuple_fromstr(value, str)
|
|
|
|
|
|
|
|
if key == "dirs":
|
|
|
|
service.dirs = value
|
|
|
|
elif key == "files":
|
|
|
|
service.configs = value
|
|
|
|
elif key == "cmdup":
|
|
|
|
service.startup = value
|
|
|
|
elif key == "cmddown":
|
|
|
|
service.shutdown = value
|
|
|
|
elif key == "cmdval":
|
|
|
|
service.validate = value
|
|
|
|
elif key == "meta":
|
|
|
|
service.meta = value
|
|
|
|
|
|
|
|
@classmethod
|
2018-06-21 22:56:30 +01:00
|
|
|
def servicesfromopaque(cls, opaque):
|
2018-06-15 22:03:27 +01:00
|
|
|
"""
|
|
|
|
Build a list of services from an opaque data string.
|
|
|
|
|
|
|
|
:param str opaque: opaque data string
|
|
|
|
:return: services
|
|
|
|
:rtype: list
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
servicesstring = opaque.split(":")
|
2018-06-15 22:03:27 +01:00
|
|
|
if servicesstring[0] != "service":
|
|
|
|
return []
|
2019-09-10 23:10:24 +01:00
|
|
|
return servicesstring[1].split(",")
|
2018-06-15 22:03:27 +01:00
|
|
|
|
|
|
|
|
2017-06-20 02:09:28 +01:00
|
|
|
class ServiceManager(object):
|
|
|
|
"""
|
|
|
|
Manages services available for CORE nodes to use.
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
|
2018-06-14 20:50:48 +01:00
|
|
|
services = {}
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-06-20 02:09:28 +01:00
|
|
|
@classmethod
|
|
|
|
def add(cls, service):
|
|
|
|
"""
|
|
|
|
Add a service to manager.
|
|
|
|
|
|
|
|
:param CoreService service: service to add
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
name = service.name
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("loading service: class(%s) name(%s)", service.__name__, name)
|
2018-06-19 17:19:49 +01:00
|
|
|
|
|
|
|
# avoid duplicate services
|
2018-06-14 20:50:48 +01:00
|
|
|
if name in cls.services:
|
|
|
|
raise ValueError("duplicate service being added: %s" % name)
|
2018-06-19 17:19:49 +01:00
|
|
|
|
|
|
|
# validate dependent executables are present
|
|
|
|
for executable in service.executables:
|
|
|
|
if not which(executable):
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.debug(
|
|
|
|
"service(%s) missing executable: %s", service.name, executable
|
|
|
|
)
|
|
|
|
raise ValueError(
|
|
|
|
"service(%s) missing executable: %s" % (service.name, executable)
|
|
|
|
)
|
2018-06-19 17:19:49 +01:00
|
|
|
|
|
|
|
# make service available
|
2018-06-14 20:50:48 +01:00
|
|
|
cls.services[name] = service
|
2017-06-20 02:09:28 +01:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get(cls, name):
|
|
|
|
"""
|
|
|
|
Retrieve a service from the manager.
|
|
|
|
|
|
|
|
:param str name: name of the service to retrieve
|
|
|
|
:return: service if it exists, None otherwise
|
2018-06-15 22:03:27 +01:00
|
|
|
:rtype: CoreService.class
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
2018-06-14 20:50:48 +01:00
|
|
|
return cls.services.get(name)
|
2014-04-30 22:27:41 +01:00
|
|
|
|
2017-08-04 00:33:54 +01:00
|
|
|
@classmethod
|
|
|
|
def add_services(cls, path):
|
|
|
|
"""
|
|
|
|
Method for retrieving all CoreServices from a given path.
|
|
|
|
|
|
|
|
:param str path: path to retrieve services from
|
2018-06-19 17:19:49 +01:00
|
|
|
:return: list of core services that failed to load
|
|
|
|
:rtype: list[str]
|
2017-08-04 00:33:54 +01:00
|
|
|
"""
|
2018-06-19 17:19:49 +01:00
|
|
|
service_errors = []
|
2018-02-05 19:22:01 +00:00
|
|
|
services = utils.load_classes(path, CoreService)
|
|
|
|
for service in services:
|
2018-06-15 22:03:27 +01:00
|
|
|
if not service.name:
|
2018-04-21 05:40:55 +01:00
|
|
|
continue
|
2018-02-05 19:22:01 +00:00
|
|
|
service.on_load()
|
2018-06-19 17:19:49 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
cls.add(service)
|
|
|
|
except ValueError as e:
|
|
|
|
service_errors.append(service.name)
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("not loading service: %s", e)
|
2018-06-19 17:19:49 +01:00
|
|
|
return service_errors
|
2017-08-04 00:33:54 +01:00
|
|
|
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-06 22:51:45 +01:00
|
|
|
class CoreServices(object):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
|
2017-06-20 02:09:28 +01:00
|
|
|
name = "services"
|
|
|
|
config_type = RegisterTlvs.UTILITY.value
|
2017-02-24 00:20:57 +00:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def __init__(self, session):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Creates a CoreServices instance.
|
|
|
|
|
|
|
|
:param core.session.Session session: session this manager is tied to
|
|
|
|
"""
|
|
|
|
self.session = session
|
2013-08-29 15:21:13 +01:00
|
|
|
# dict of default services tuples, key is node type
|
2018-06-22 22:41:06 +01:00
|
|
|
self.default_services = {}
|
|
|
|
# dict of node ids to dict of custom services by name
|
|
|
|
self.custom_services = {}
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def reset(self):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Called when config message with reset flag is received
|
|
|
|
"""
|
2018-06-22 22:41:06 +01:00
|
|
|
self.custom_services.clear()
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
def get_default_services(self, node_type):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Get the list of default services that should be enabled for a
|
|
|
|
node for the given node type.
|
|
|
|
|
2018-06-21 22:56:30 +01:00
|
|
|
:param node_type: node type to get default services for
|
2017-06-20 02:09:28 +01:00
|
|
|
:return: default services
|
2018-06-15 22:03:27 +01:00
|
|
|
:rtype: list[CoreService]
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("getting default services for type: %s", node_type)
|
2017-06-20 02:09:28 +01:00
|
|
|
results = []
|
2018-06-22 22:41:06 +01:00
|
|
|
defaults = self.default_services.get(node_type, [])
|
2018-06-15 22:03:27 +01:00
|
|
|
for name in defaults:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("checking for service with service manager: %s", name)
|
2018-06-15 22:03:27 +01:00
|
|
|
service = ServiceManager.get(name)
|
|
|
|
if not service:
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("default service %s is unknown", name)
|
2018-06-15 22:03:27 +01:00
|
|
|
else:
|
|
|
|
results.append(service)
|
2017-06-20 02:09:28 +01:00
|
|
|
return results
|
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
def get_service(self, node_id, service_name, default_service=False):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
|
2018-06-15 22:03:27 +01:00
|
|
|
:param int node_id: object id to get service from
|
|
|
|
:param str service_name: name of service to retrieve
|
|
|
|
:param bool default_service: True to return default service when custom does not exist, False returns None
|
2017-06-20 02:09:28 +01:00
|
|
|
:return: custom service from the node
|
|
|
|
:rtype: CoreService
|
|
|
|
"""
|
2018-06-22 22:41:06 +01:00
|
|
|
node_services = self.custom_services.setdefault(node_id, {})
|
2018-06-15 22:03:27 +01:00
|
|
|
default = None
|
|
|
|
if default_service:
|
|
|
|
default = ServiceManager.get(service_name)
|
|
|
|
return node_services.get(service_name, default)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
def set_service(self, node_id, service_name):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Store service customizations in an instantiated service object
|
|
|
|
using a list of values that came from a config message.
|
|
|
|
|
2018-06-15 22:03:27 +01:00
|
|
|
:param int node_id: object id to set custom service for
|
|
|
|
:param str service_name: name of service to set
|
|
|
|
:return: nothing
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("setting custom service(%s) for node: %s", service_name, node_id)
|
2018-06-22 22:41:06 +01:00
|
|
|
service = self.get_service(node_id, service_name)
|
2018-06-15 22:03:27 +01:00
|
|
|
if not service:
|
|
|
|
service_class = ServiceManager.get(service_name)
|
|
|
|
service = service_class()
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-15 22:03:27 +01:00
|
|
|
# add the custom service to dict
|
2018-06-22 22:41:06 +01:00
|
|
|
node_services = self.custom_services.setdefault(node_id, {})
|
2018-06-15 22:03:27 +01:00
|
|
|
node_services[service.name] = service
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
def add_services(self, node, node_type, services=None):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
Add services to a node.
|
2017-06-20 02:09:28 +01:00
|
|
|
|
|
|
|
:param core.coreobj.PyCoreNode node: node to add services to
|
2018-06-15 22:03:27 +01:00
|
|
|
:param str node_type: node type to add services to
|
2018-06-21 22:56:30 +01:00
|
|
|
:param list[str] services: names of services to add to node
|
2017-06-20 02:09:28 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
if not services:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.info(
|
|
|
|
"using default services for node(%s) type(%s)", node.name, node_type
|
|
|
|
)
|
2018-06-22 22:41:06 +01:00
|
|
|
services = self.default_services.get(node_type, [])
|
2018-06-15 22:03:27 +01:00
|
|
|
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("setting services for node(%s): %s", node.name, services)
|
2018-06-15 22:03:27 +01:00
|
|
|
for service_name in services:
|
2019-04-27 06:07:51 +01:00
|
|
|
service = self.get_service(node.id, service_name, default_service=True)
|
2018-06-15 22:03:27 +01:00
|
|
|
if not service:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.warning(
|
|
|
|
"unknown service(%s) for node(%s)", service_name, node.name
|
|
|
|
)
|
2018-06-15 22:03:27 +01:00
|
|
|
continue
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("adding service to node(%s): %s", node.name, service_name)
|
2018-06-15 22:03:27 +01:00
|
|
|
node.addservice(service)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
def all_configs(self):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
2018-06-22 22:41:06 +01:00
|
|
|
Return (node_id, service) tuples for all stored configs. Used when reconnecting to a
|
2017-06-20 02:09:28 +01:00
|
|
|
session or opening XML.
|
|
|
|
|
|
|
|
:return: list of tuples of node ids and services
|
2018-06-22 22:41:06 +01:00
|
|
|
:rtype: list[tuple]
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
configs = []
|
2019-05-06 05:23:43 +01:00
|
|
|
for node_id in self.custom_services:
|
|
|
|
custom_services = self.custom_services[node_id]
|
|
|
|
for name in custom_services:
|
|
|
|
service = custom_services[name]
|
2018-06-15 22:03:27 +01:00
|
|
|
configs.append((node_id, service))
|
2017-06-20 02:09:28 +01:00
|
|
|
return configs
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
def all_files(self, service):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Return all customized files stored with a service.
|
2013-08-29 15:21:13 +01:00
|
|
|
Used when reconnecting to a session or opening XML.
|
2017-06-20 02:09:28 +01:00
|
|
|
|
|
|
|
:param CoreService service: service to get files for
|
2018-06-22 22:41:06 +01:00
|
|
|
:return: list of all custom service files
|
|
|
|
:rtype: list[tuple]
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
files = []
|
2018-06-15 22:03:27 +01:00
|
|
|
if not service.custom:
|
2017-06-20 02:09:28 +01:00
|
|
|
return files
|
|
|
|
|
2018-06-15 22:03:27 +01:00
|
|
|
for filename in service.configs:
|
2018-06-22 22:41:06 +01:00
|
|
|
data = service.config_data.get(filename)
|
2013-08-29 15:21:13 +01:00
|
|
|
if data is None:
|
|
|
|
continue
|
2017-06-20 02:09:28 +01:00
|
|
|
files.append((filename, data))
|
|
|
|
|
|
|
|
return files
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def boot_services(self, node):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Start all services on a node.
|
|
|
|
|
2018-02-28 00:28:28 +00:00
|
|
|
:param core.netns.vnode.LxcNode node: node to start services on
|
|
|
|
:return: nothing
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
2018-06-20 20:59:07 +01:00
|
|
|
pool = ThreadPool()
|
|
|
|
results = []
|
|
|
|
|
2018-08-01 18:13:57 +01:00
|
|
|
boot_paths = ServiceDependencies(node.services).boot_paths()
|
2018-06-21 19:20:08 +01:00
|
|
|
for boot_path in boot_paths:
|
2018-06-22 23:47:02 +01:00
|
|
|
result = pool.apply_async(self._start_boot_paths, (node, boot_path))
|
2018-06-20 20:59:07 +01:00
|
|
|
results.append(result)
|
|
|
|
|
|
|
|
pool.close()
|
|
|
|
pool.join()
|
|
|
|
for result in results:
|
|
|
|
result.get()
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def _start_boot_paths(self, node, boot_path):
|
2018-06-22 22:41:06 +01:00
|
|
|
"""
|
|
|
|
Start all service boot paths found, based on dependencies.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to start services on
|
2018-06-22 22:41:06 +01:00
|
|
|
:param list[CoreService] boot_path: service to start in dependent order
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.info(
|
|
|
|
"booting node services: %s", " -> ".join([x.name for x in boot_path])
|
|
|
|
)
|
2018-06-21 19:20:08 +01:00
|
|
|
for service in boot_path:
|
2018-09-12 20:15:53 +01:00
|
|
|
try:
|
|
|
|
self.boot_service(node, service)
|
2019-09-11 05:01:51 +01:00
|
|
|
except Exception:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("exception booting service: %s", service.name)
|
2018-09-12 20:15:53 +01:00
|
|
|
raise
|
2018-06-21 19:20:08 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def boot_service(self, node, service):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Start a service on a node. Create private dirs, generate config
|
|
|
|
files, and execute startup commands.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to boot services on
|
2017-06-20 02:09:28 +01:00
|
|
|
:param CoreService service: service to start
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.info(
|
|
|
|
"starting node(%s) service(%s) validation(%s)",
|
|
|
|
node.name,
|
|
|
|
service.name,
|
|
|
|
service.validation_mode.name,
|
|
|
|
)
|
2018-06-20 20:59:07 +01:00
|
|
|
|
|
|
|
# create service directories
|
2018-06-15 22:03:27 +01:00
|
|
|
for directory in service.dirs:
|
2018-10-10 17:43:32 +01:00
|
|
|
try:
|
|
|
|
node.privatedir(directory)
|
2018-10-10 23:21:26 +01:00
|
|
|
except (CoreCommandError, ValueError) as e:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.warning(
|
|
|
|
"error mounting private dir '%s' for service '%s': %s",
|
|
|
|
directory,
|
|
|
|
service.name,
|
|
|
|
e,
|
|
|
|
)
|
2018-10-10 17:43:32 +01:00
|
|
|
|
2018-06-20 20:59:07 +01:00
|
|
|
# create service files
|
2018-06-22 23:47:02 +01:00
|
|
|
self.create_service_files(node, service)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-20 20:59:07 +01:00
|
|
|
# run startup
|
|
|
|
wait = service.validation_mode == ServiceMode.BLOCKING
|
2018-06-22 23:47:02 +01:00
|
|
|
status = self.startup_service(node, service, wait)
|
2018-06-20 20:59:07 +01:00
|
|
|
if status:
|
2019-09-10 23:10:24 +01:00
|
|
|
raise ServiceBootError(
|
|
|
|
"node(%s) service(%s) error during startup" % (node.name, service.name)
|
|
|
|
)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-09-26 22:20:19 +01:00
|
|
|
# blocking mode is finished
|
|
|
|
if wait:
|
|
|
|
return
|
|
|
|
|
|
|
|
# timer mode, sleep and return
|
|
|
|
if service.validation_mode == ServiceMode.TIMER:
|
2018-06-20 20:59:07 +01:00
|
|
|
time.sleep(service.validation_timer)
|
2018-09-26 22:20:19 +01:00
|
|
|
# non-blocking, attempt to validate periodically, up to validation_timer time
|
|
|
|
elif service.validation_mode == ServiceMode.NON_BLOCKING:
|
|
|
|
start = time.time()
|
|
|
|
while True:
|
|
|
|
status = self.validate_service(node, service)
|
|
|
|
if not status:
|
|
|
|
break
|
|
|
|
|
|
|
|
if time.time() - start > service.validation_timer:
|
|
|
|
break
|
|
|
|
|
|
|
|
time.sleep(service.validation_period)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-20 20:59:07 +01:00
|
|
|
if status:
|
2019-09-10 23:10:24 +01:00
|
|
|
raise ServiceBootError(
|
|
|
|
"node(%s) service(%s) failed validation" % (node.name, service.name)
|
|
|
|
)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
def copy_service_file(self, node, filename, cfg):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Given a configured service filename and config, determine if the
|
2013-08-29 15:21:13 +01:00
|
|
|
config references an existing file that should be copied.
|
|
|
|
Returns True for local files, False for generated.
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to copy service for
|
2017-06-20 02:09:28 +01:00
|
|
|
:param str filename: file name for a configured service
|
|
|
|
:param str cfg: configuration string
|
|
|
|
:return: True if successful, False otherwise
|
|
|
|
:rtype: bool
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
if cfg[:7] == "file://":
|
2013-08-29 15:21:13 +01:00
|
|
|
src = cfg[7:]
|
2019-09-10 23:10:24 +01:00
|
|
|
src = src.split("\n")[0]
|
2018-03-02 17:15:52 +00:00
|
|
|
src = utils.expand_corepath(src, node.session, node)
|
2013-08-29 15:21:13 +01:00
|
|
|
# TODO: glob here
|
2019-05-06 01:11:07 +01:00
|
|
|
node.nodefilecopy(filename, src, mode=0o644)
|
2013-08-29 15:21:13 +01:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def validate_service(self, node, service):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Run the validation command(s) for a service.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to validate service for
|
2017-06-20 02:09:28 +01:00
|
|
|
:param CoreService service: service to validate
|
|
|
|
:return: service validation status
|
|
|
|
:rtype: int
|
|
|
|
"""
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("validating node(%s) service(%s)", node.name, service.name)
|
2018-06-20 20:59:07 +01:00
|
|
|
cmds = service.validate
|
|
|
|
if not service.custom:
|
2018-06-22 23:47:02 +01:00
|
|
|
cmds = service.get_validate(node)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
|
|
|
status = 0
|
2018-06-20 20:59:07 +01:00
|
|
|
for cmd in cmds:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("validating service(%s) using: %s", service.name, cmd)
|
2018-06-15 22:03:27 +01:00
|
|
|
try:
|
2018-06-20 20:59:07 +01:00
|
|
|
node.check_cmd(cmd)
|
2018-09-26 22:20:19 +01:00
|
|
|
except CoreCommandError as e:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.error(
|
|
|
|
"node(%s) service(%s) validate failed", node.name, service.name
|
|
|
|
)
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.error("cmd(%s): %s", e.cmd, e.output)
|
2018-06-15 22:03:27 +01:00
|
|
|
status = -1
|
2018-09-26 22:20:19 +01:00
|
|
|
break
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2014-03-05 16:28:32 +00:00
|
|
|
return status
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def stop_services(self, node):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Stop all services on a node.
|
|
|
|
|
2019-04-30 07:31:47 +01:00
|
|
|
:param core.netns.vnode.CoreNode node: node to stop services on
|
2017-06-20 02:09:28 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2018-06-22 16:16:59 +01:00
|
|
|
for service in node.services:
|
2018-06-22 23:47:02 +01:00
|
|
|
self.stop_service(node, service)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def stop_service(self, node, service):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Stop a service on a node.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to stop a service on
|
2017-06-20 02:09:28 +01:00
|
|
|
:param CoreService service: service to stop
|
|
|
|
:return: status for stopping the services
|
|
|
|
:rtype: str
|
|
|
|
"""
|
2018-06-20 20:59:07 +01:00
|
|
|
status = 0
|
2018-06-15 22:03:27 +01:00
|
|
|
for args in service.shutdown:
|
|
|
|
try:
|
|
|
|
node.check_cmd(args)
|
|
|
|
except CoreCommandError:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("error running stop command %s", args)
|
2018-06-20 20:59:07 +01:00
|
|
|
status = -1
|
2014-03-05 16:28:32 +00:00
|
|
|
return status
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
def get_service_file(self, node, service_name, filename):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Send a File Message when the GUI has requested a service file.
|
2013-08-29 15:21:13 +01:00
|
|
|
The file data is either auto-generated or comes from an existing config.
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to get service file from
|
2018-06-21 22:56:30 +01:00
|
|
|
:param str service_name: service to get file from
|
2017-06-20 02:09:28 +01:00
|
|
|
:param str filename: file name to retrieve
|
|
|
|
:return: file message for node
|
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
# get service to get file from
|
2019-04-27 06:07:51 +01:00
|
|
|
service = self.get_service(node.id, service_name, default_service=True)
|
2018-06-15 22:03:27 +01:00
|
|
|
if not service:
|
|
|
|
raise ValueError("invalid service: %s", service_name)
|
|
|
|
|
|
|
|
# retrieve config files for default/custom service
|
|
|
|
if service.custom:
|
|
|
|
config_files = service.configs
|
2013-08-29 15:21:13 +01:00
|
|
|
else:
|
2018-06-22 23:47:02 +01:00
|
|
|
config_files = service.get_configs(node)
|
2018-06-15 22:03:27 +01:00
|
|
|
|
|
|
|
if filename not in config_files:
|
2019-09-10 23:10:24 +01:00
|
|
|
raise ValueError(
|
|
|
|
"unknown service(%s) config file: %s", service_name, filename
|
|
|
|
)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# get the file data
|
2018-06-22 22:41:06 +01:00
|
|
|
data = service.config_data.get(filename)
|
2013-08-29 15:21:13 +01:00
|
|
|
if data is None:
|
2018-06-22 23:47:02 +01:00
|
|
|
data = "%s" % service.generate_config(node, filename)
|
2013-08-29 15:21:13 +01:00
|
|
|
else:
|
|
|
|
data = "%s" % data
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-15 22:03:27 +01:00
|
|
|
filetypestr = "service:%s" % service.name
|
2017-07-11 23:51:56 +01:00
|
|
|
return FileData(
|
|
|
|
message_type=MessageFlags.ADD.value,
|
2019-04-27 06:07:51 +01:00
|
|
|
node=node.id,
|
2017-07-11 23:51:56 +01:00
|
|
|
name=filename,
|
|
|
|
type=filetypestr,
|
2019-09-10 23:10:24 +01:00
|
|
|
data=data,
|
2017-07-11 23:51:56 +01:00
|
|
|
)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def set_service_file(self, node_id, service_name, file_name, data):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Receive a File Message from the GUI and store the customized file
|
2013-08-29 15:21:13 +01:00
|
|
|
in the service config. The filename must match one from the list of
|
|
|
|
config files in the service.
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-15 22:03:27 +01:00
|
|
|
:param int node_id: node id to set service file
|
|
|
|
:param str service_name: service name to set file for
|
2018-06-22 23:47:02 +01:00
|
|
|
:param str file_name: file name to set
|
2017-06-20 02:09:28 +01:00
|
|
|
:param data: data for file to set
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
# attempt to set custom service, if needed
|
2018-06-22 22:41:06 +01:00
|
|
|
self.set_service(node_id, service_name)
|
2018-06-15 22:03:27 +01:00
|
|
|
|
|
|
|
# retrieve custom service
|
2018-06-22 22:41:06 +01:00
|
|
|
service = self.get_service(node_id, service_name)
|
2018-06-20 20:59:07 +01:00
|
|
|
if service is None:
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("received file name for unknown service: %s", service_name)
|
2013-08-29 15:21:13 +01:00
|
|
|
return
|
2018-06-15 22:03:27 +01:00
|
|
|
|
|
|
|
# validate file being set is valid
|
2018-06-22 23:47:02 +01:00
|
|
|
config_files = service.configs
|
|
|
|
if file_name not in config_files:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.warning(
|
|
|
|
"received unknown file(%s) for service(%s)", file_name, service_name
|
|
|
|
)
|
2013-08-29 15:21:13 +01:00
|
|
|
return
|
2018-06-15 22:03:27 +01:00
|
|
|
|
|
|
|
# set custom service file data
|
2018-06-22 23:47:02 +01:00
|
|
|
service.config_data[file_name] = data
|
2018-06-15 22:03:27 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def startup_service(self, node, service, wait=False):
|
2018-06-15 22:03:27 +01:00
|
|
|
"""
|
|
|
|
Startup a node service.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to reconfigure service for
|
2018-06-15 22:03:27 +01:00
|
|
|
:param CoreService service: service to reconfigure
|
2018-06-20 20:59:07 +01:00
|
|
|
:param bool wait: determines if we should wait to validate startup
|
2018-06-15 22:03:27 +01:00
|
|
|
:return: status of startup
|
2018-06-20 20:59:07 +01:00
|
|
|
:rtype: int
|
2018-06-15 22:03:27 +01:00
|
|
|
"""
|
|
|
|
|
2018-06-20 20:59:07 +01:00
|
|
|
cmds = service.startup
|
|
|
|
if not service.custom:
|
2018-06-22 23:47:02 +01:00
|
|
|
cmds = service.get_startup(node)
|
2018-06-15 22:03:27 +01:00
|
|
|
|
2018-06-20 20:59:07 +01:00
|
|
|
status = 0
|
|
|
|
for cmd in cmds:
|
2018-06-15 22:03:27 +01:00
|
|
|
try:
|
2018-06-20 20:59:07 +01:00
|
|
|
if wait:
|
|
|
|
node.check_cmd(cmd)
|
|
|
|
else:
|
|
|
|
node.cmd(cmd, wait=False)
|
2018-06-15 22:03:27 +01:00
|
|
|
except CoreCommandError:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("error starting command")
|
2018-06-20 20:59:07 +01:00
|
|
|
status = -1
|
2018-06-15 22:03:27 +01:00
|
|
|
return status
|
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def create_service_files(self, node, service):
|
2018-06-20 20:59:07 +01:00
|
|
|
"""
|
|
|
|
Creates node service files.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to reconfigure service for
|
2018-06-20 20:59:07 +01:00
|
|
|
:param CoreService service: service to reconfigure
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.info(
|
|
|
|
"node(%s) service(%s) creating config files", node.name, service.name
|
|
|
|
)
|
2018-06-20 20:59:07 +01:00
|
|
|
# get values depending on if custom or not
|
2018-06-22 23:47:02 +01:00
|
|
|
config_files = service.configs
|
2018-06-20 20:59:07 +01:00
|
|
|
if not service.custom:
|
2018-06-22 23:47:02 +01:00
|
|
|
config_files = service.get_configs(node)
|
2018-06-20 20:59:07 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
for file_name in config_files:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("generating service config: %s", file_name)
|
2018-06-20 20:59:07 +01:00
|
|
|
if service.custom:
|
2018-06-22 22:41:06 +01:00
|
|
|
cfg = service.config_data.get(file_name)
|
2018-06-20 20:59:07 +01:00
|
|
|
if cfg is None:
|
2018-06-22 23:47:02 +01:00
|
|
|
cfg = service.generate_config(node, file_name)
|
2018-06-20 20:59:07 +01:00
|
|
|
|
|
|
|
# cfg may have a file:/// url for copying from a file
|
|
|
|
try:
|
2018-06-22 22:41:06 +01:00
|
|
|
if self.copy_service_file(node, file_name, cfg):
|
2018-06-20 20:59:07 +01:00
|
|
|
continue
|
|
|
|
except IOError:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("error copying service file: %s", file_name)
|
2018-06-20 20:59:07 +01:00
|
|
|
continue
|
|
|
|
else:
|
2018-06-22 23:47:02 +01:00
|
|
|
cfg = service.generate_config(node, file_name)
|
2018-06-20 20:59:07 +01:00
|
|
|
|
|
|
|
node.nodefile(file_name, cfg)
|
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
def service_reconfigure(self, node, service):
|
2018-06-15 22:03:27 +01:00
|
|
|
"""
|
|
|
|
Reconfigure a node service.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to reconfigure service for
|
2018-06-15 22:03:27 +01:00
|
|
|
:param CoreService service: service to reconfigure
|
2017-06-20 02:09:28 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2018-06-22 23:47:02 +01:00
|
|
|
config_files = service.configs
|
2018-06-20 20:59:07 +01:00
|
|
|
if not service.custom:
|
2018-06-22 23:47:02 +01:00
|
|
|
config_files = service.get_configs(node)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 23:47:02 +01:00
|
|
|
for file_name in config_files:
|
2018-06-20 20:59:07 +01:00
|
|
|
if file_name[:7] == "file:///":
|
2018-06-15 22:03:27 +01:00
|
|
|
# TODO: implement this
|
|
|
|
raise NotImplementedError
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
cfg = service.config_data.get(file_name)
|
2018-06-15 22:03:27 +01:00
|
|
|
if cfg is None:
|
2018-06-22 23:47:02 +01:00
|
|
|
cfg = service.generate_config(node, file_name)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2018-06-20 20:59:07 +01:00
|
|
|
node.nodefile(file_name, cfg)
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
class CoreService(object):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Parent class used for defining services.
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# service name should not include spaces
|
2018-06-15 22:03:27 +01:00
|
|
|
name = None
|
2018-06-14 20:50:48 +01:00
|
|
|
|
2018-06-19 17:19:49 +01:00
|
|
|
# executables that must exist for service to run
|
|
|
|
executables = ()
|
|
|
|
|
2018-06-20 02:36:53 +01:00
|
|
|
# sets service requirements that must be started prior to this service starting
|
|
|
|
dependencies = ()
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# group string allows grouping services together
|
2018-06-15 22:03:27 +01:00
|
|
|
group = None
|
2018-06-14 20:50:48 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# private, per-node directories required by this service
|
2018-06-15 22:03:27 +01:00
|
|
|
dirs = ()
|
2018-06-14 20:50:48 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# config files written by this service
|
2018-06-15 22:03:27 +01:00
|
|
|
configs = ()
|
2018-06-14 20:50:48 +01:00
|
|
|
|
2018-06-22 22:41:06 +01:00
|
|
|
# config file data
|
|
|
|
config_data = {}
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# list of startup commands
|
2018-06-15 22:03:27 +01:00
|
|
|
startup = ()
|
2018-06-14 20:50:48 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# list of shutdown commands
|
2018-06-15 22:03:27 +01:00
|
|
|
shutdown = ()
|
2018-06-14 20:50:48 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# list of validate commands
|
2018-06-15 22:03:27 +01:00
|
|
|
validate = ()
|
2018-06-14 20:50:48 +01:00
|
|
|
|
2018-06-20 20:59:07 +01:00
|
|
|
# validation mode, used to determine startup success
|
|
|
|
validation_mode = ServiceMode.NON_BLOCKING
|
|
|
|
|
2018-09-26 22:20:19 +01:00
|
|
|
# time to wait in seconds for determining if service started successfully
|
|
|
|
validation_timer = 5
|
|
|
|
|
|
|
|
# validation period in seconds, how frequent validation is attempted
|
|
|
|
validation_period = 0.5
|
2018-06-20 20:59:07 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# metadata associated with this service
|
2018-06-15 22:03:27 +01:00
|
|
|
meta = None
|
2018-06-14 20:50:48 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# custom configuration text
|
2018-06-15 22:03:27 +01:00
|
|
|
custom = False
|
|
|
|
custom_needed = False
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
def __init__(self):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
self.custom = True
|
2018-07-25 17:37:59 +01:00
|
|
|
self.config_data = self.__class__.config_data.copy()
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2017-08-08 00:40:39 +01:00
|
|
|
@classmethod
|
|
|
|
def on_load(cls):
|
|
|
|
pass
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
@classmethod
|
2018-06-22 23:47:02 +01:00
|
|
|
def get_configs(cls, node):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to generate config for
|
2018-06-21 22:56:30 +01:00
|
|
|
:return: configuration files
|
2017-06-20 02:09:28 +01:00
|
|
|
:rtype: tuple
|
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
return cls.configs
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
@classmethod
|
2018-06-22 23:47:02 +01:00
|
|
|
def generate_config(cls, node, filename):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Generate configuration file given a node object. The filename is
|
2018-06-22 22:41:06 +01:00
|
|
|
provided to allow for multiple config files.
|
2017-06-20 02:09:28 +01:00
|
|
|
Return the configuration string to be written to a file or sent
|
|
|
|
to the GUI for customization.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to generate config for
|
2017-06-20 02:09:28 +01:00
|
|
|
:param str filename: file name to generate config for
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
raise NotImplementedError
|
2017-06-20 02:09:28 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
@classmethod
|
2018-06-22 23:47:02 +01:00
|
|
|
def get_startup(cls, node):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Return the tuple of startup commands. This default method
|
2018-06-22 22:41:06 +01:00
|
|
|
returns the cls.startup tuple, but this method may be
|
2017-06-20 02:09:28 +01:00
|
|
|
overridden to provide node-specific commands that may be
|
|
|
|
based on other services.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to get startup for
|
2017-06-20 02:09:28 +01:00
|
|
|
:return: startup commands
|
|
|
|
:rtype: tuple
|
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
return cls.startup
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
@classmethod
|
2018-06-22 23:47:02 +01:00
|
|
|
def get_validate(cls, node):
|
2017-06-20 02:09:28 +01:00
|
|
|
"""
|
|
|
|
Return the tuple of validate commands. This default method
|
2018-06-22 22:41:06 +01:00
|
|
|
returns the cls.validate tuple, but this method may be
|
|
|
|
overridden to provide node-specific commands that may be
|
2017-06-20 02:09:28 +01:00
|
|
|
based on other services.
|
|
|
|
|
2019-06-07 16:59:16 +01:00
|
|
|
:param core.nodes.base.CoreNode node: node to validate
|
2017-06-20 02:09:28 +01:00
|
|
|
:return: validation commands
|
|
|
|
:rtype: tuple
|
|
|
|
"""
|
2018-06-15 22:03:27 +01:00
|
|
|
return cls.validate
|