made use of threadpool for starting services, refactored services to support 3 validation models (blocking, non-blocking, timer)
This commit is contained in:
parent
37ce407460
commit
c6d2ca6b02
4 changed files with 121 additions and 111 deletions
|
@ -1527,15 +1527,15 @@ class CoreHandler(SocketServer.BaseRequestHandler):
|
|||
|
||||
if event_type == EventTypes.STOP.value or event_type == EventTypes.RESTART.value:
|
||||
status = self.session.services.stopnodeservice(node, service)
|
||||
if status != "0":
|
||||
if status:
|
||||
fail += "Stop %s," % service.name
|
||||
if event_type == EventTypes.START.value or event_type == EventTypes.RESTART.value:
|
||||
status = self.session.services.node_service_startup(node, service, services)
|
||||
if status != "0":
|
||||
if status:
|
||||
fail += "Start %s(%s)," % service.name
|
||||
if event_type == EventTypes.PAUSE.value:
|
||||
status = self.session.services.validatenodeservice(node, service, services)
|
||||
if status != 0:
|
||||
if status:
|
||||
fail += "%s," % service.name
|
||||
if event_type == EventTypes.RECONFIGURE.value:
|
||||
self.session.services.node_service_reconfigure(node, service, services)
|
||||
|
|
|
@ -7,6 +7,10 @@ a list of available services to the GUI and for configuring individual
|
|||
services.
|
||||
"""
|
||||
|
||||
import time
|
||||
from multiprocessing.pool import ThreadPool
|
||||
|
||||
import enum
|
||||
from core.constants import which
|
||||
|
||||
from core import CoreCommandError
|
||||
|
@ -17,6 +21,16 @@ from core.enumerations import RegisterTlvs
|
|||
from core.misc import utils
|
||||
|
||||
|
||||
class ServiceBootError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServiceMode(enum.Enum):
|
||||
BLOCKING = 0
|
||||
NON_BLOCKING = 1
|
||||
TIMER = 2
|
||||
|
||||
|
||||
class ServiceShim(object):
|
||||
keys = ["dirs", "files", "startidx", "cmdup", "cmddown", "cmdval", "meta", "starttime"]
|
||||
|
||||
|
@ -211,7 +225,7 @@ class CoreServices(object):
|
|||
self.defaultservices.clear()
|
||||
self.customservices.clear()
|
||||
|
||||
def get_service_startups(self, services):
|
||||
def node_service_startups(self, services):
|
||||
# generate service map and find starting points
|
||||
node_services = {service.name: service for service in services}
|
||||
is_dependency = set()
|
||||
|
@ -400,6 +414,9 @@ class CoreServices(object):
|
|||
:param core.netns.vnode.LxcNode node: node to start services on
|
||||
:return: nothing
|
||||
"""
|
||||
pool = ThreadPool()
|
||||
results = []
|
||||
|
||||
services = sorted(node.services, key=lambda x: x.startindex)
|
||||
use_startup_service = any(map(self.is_startup_service, services))
|
||||
for service in services:
|
||||
|
@ -412,7 +429,13 @@ class CoreServices(object):
|
|||
continue
|
||||
except ValueError:
|
||||
logger.exception("error converting start time to float")
|
||||
self.bootnodeservice(node, service, services, use_startup_service)
|
||||
result = pool.apply_async(self.bootnodeservice, (node, service, services, use_startup_service))
|
||||
results.append(result)
|
||||
|
||||
pool.close()
|
||||
pool.join()
|
||||
for result in results:
|
||||
result.get()
|
||||
|
||||
def bootnodeservice(self, node, service, services, use_startup_service):
|
||||
"""
|
||||
|
@ -425,62 +448,35 @@ class CoreServices(object):
|
|||
:param bool use_startup_service: flag to use startup services or not
|
||||
:return: nothing
|
||||
"""
|
||||
if service.custom:
|
||||
self.bootnodecustomservice(node, service, services, use_startup_service)
|
||||
return
|
||||
|
||||
logger.info("starting node(%s) service: %s (%s)", node.name, service.name, service.startindex)
|
||||
|
||||
# create service directories
|
||||
for directory in service.dirs:
|
||||
node.privatedir(directory)
|
||||
|
||||
for filename in service.getconfigfilenames(node.objid, services):
|
||||
cfg = service.generateconfig(node, filename, services)
|
||||
node.nodefile(filename, cfg)
|
||||
# create service files
|
||||
self.node_service_files(node, service, services)
|
||||
|
||||
# check for startup service
|
||||
if use_startup_service and not self.is_startup_service(service):
|
||||
return
|
||||
|
||||
for args in service.getstartup(node, services):
|
||||
# TODO: this wait=False can be problematic!
|
||||
node.cmd(args, wait=False)
|
||||
# run startup
|
||||
wait = service.validation_mode == ServiceMode.BLOCKING
|
||||
status = self.node_service_startup(node, service, services, wait)
|
||||
if status:
|
||||
raise ServiceBootError("node(%s) service(%s) error during startup" % (node.name, service.name))
|
||||
|
||||
def bootnodecustomservice(self, node, service, services, use_startup_service):
|
||||
"""
|
||||
Start a custom service on a node. Create private dirs, use supplied
|
||||
config files, and execute supplied startup commands.
|
||||
# wait for time if provided, default to a time previously used to provide a small buffer
|
||||
time.sleep(0.125)
|
||||
if service.validation_timer:
|
||||
time.sleep(service.validation_timer)
|
||||
|
||||
:param core.netns.vnode.LxcNode node: node to boot services on
|
||||
:param CoreService service: service to start
|
||||
:param list services: service list
|
||||
:param bool use_startup_service: flag to use startup services or not
|
||||
:return: nothing
|
||||
"""
|
||||
logger.info("starting node(%s) service(custom): %s (%s)", node.name, service.name, service.startindex)
|
||||
for directory in service.dirs:
|
||||
node.privatedir(directory)
|
||||
|
||||
logger.info("service configurations: %s", service.configs)
|
||||
for filename in service.configs:
|
||||
logger.info("generating service config: %s", filename)
|
||||
cfg = service.configtxt.get(filename)
|
||||
if cfg is None:
|
||||
cfg = service.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:
|
||||
logger.exception("error copying service file '%s'", filename)
|
||||
continue
|
||||
node.nodefile(filename, cfg)
|
||||
|
||||
if use_startup_service and not self.is_startup_service(service):
|
||||
return
|
||||
|
||||
for args in service.startup:
|
||||
# TODO: this wait=False can be problematic!
|
||||
node.cmd(args, wait=False)
|
||||
# run validation commands, if present and not timer mode
|
||||
if service.validation_mode != ServiceMode.TIMER:
|
||||
status = self.validatenodeservice(node, service, services)
|
||||
if status:
|
||||
raise ServiceBootError("node(%s) service(%s) failed validation" % (node.name, service.name))
|
||||
|
||||
def copyservicefile(self, node, filename, cfg):
|
||||
"""
|
||||
|
@ -510,7 +506,7 @@ class CoreServices(object):
|
|||
:param core.netns.vnode.LxcNode node: node to validate services for
|
||||
:return: nothing
|
||||
"""
|
||||
services = sorted(node.services, key=lambda service: service.startindex)
|
||||
services = sorted(node.services, key=lambda x: x.startindex)
|
||||
for service in services:
|
||||
self.validatenodeservice(node, service, services)
|
||||
|
||||
|
@ -524,17 +520,16 @@ class CoreServices(object):
|
|||
:return: service validation status
|
||||
:rtype: int
|
||||
"""
|
||||
logger.info("validating service for node (%s): %s (%s)", node.name, service.name, service.startindex)
|
||||
if service.custom:
|
||||
validate_cmds = service.validate
|
||||
else:
|
||||
validate_cmds = service.getvalidate(node, services)
|
||||
logger.info("validating node(%s) service(%s): %s", node.name, service.name, service.startindex)
|
||||
cmds = service.validate
|
||||
if not service.custom:
|
||||
cmds = service.getvalidate(node, services)
|
||||
|
||||
status = 0
|
||||
for args in validate_cmds:
|
||||
logger.info("validating service %s using: %s", service.name, args)
|
||||
for cmd in cmds:
|
||||
logger.info("validating service %s using: %s", service.name, cmd)
|
||||
try:
|
||||
node.check_cmd(args)
|
||||
node.check_cmd(cmd)
|
||||
except CoreCommandError:
|
||||
logger.exception("validate command failed")
|
||||
status = -1
|
||||
|
@ -561,14 +556,13 @@ class CoreServices(object):
|
|||
:return: status for stopping the services
|
||||
:rtype: str
|
||||
"""
|
||||
status = "0"
|
||||
status = 0
|
||||
for args in service.shutdown:
|
||||
try:
|
||||
node.check_cmd(args)
|
||||
except CoreCommandError:
|
||||
logger.exception("error running stop command %s", args)
|
||||
# TODO: determine if its ok to just return the bad exit status
|
||||
status = "-1"
|
||||
status = -1
|
||||
return status
|
||||
|
||||
def getservicefile(self, service_name, node, filename, services):
|
||||
|
@ -608,7 +602,7 @@ class CoreServices(object):
|
|||
# get the file data
|
||||
data = service.configtxt.get(filename)
|
||||
if data is None:
|
||||
data = "%s" % service.generateconfig(node, filename, services)
|
||||
data = "%s" % service.generateconfig(node, filename, node_services)
|
||||
else:
|
||||
data = "%s" % data
|
||||
|
||||
|
@ -637,45 +631,81 @@ class CoreServices(object):
|
|||
self.setcustomservice(node_id, service_name)
|
||||
|
||||
# retrieve custom service
|
||||
svc = self.getcustomservice(node_id, service_name)
|
||||
if svc is None:
|
||||
service = self.getcustomservice(node_id, service_name)
|
||||
if service is None:
|
||||
logger.warn("received filename for unknown service: %s", service_name)
|
||||
return
|
||||
|
||||
# validate file being set is valid
|
||||
cfgfiles = svc.configs
|
||||
cfgfiles = service.configs
|
||||
if filename not in cfgfiles:
|
||||
logger.warn("received unknown file '%s' for service '%s'", filename, service_name)
|
||||
return
|
||||
|
||||
# set custom service file data
|
||||
svc.configtxt[filename] = data
|
||||
service.configtxt[filename] = data
|
||||
|
||||
def node_service_startup(self, node, service, services):
|
||||
def node_service_startup(self, node, service, services, wait=False):
|
||||
"""
|
||||
Startup a node service.
|
||||
|
||||
:param PyCoreNode node: node to reconfigure service for
|
||||
:param CoreService service: service to reconfigure
|
||||
:param list[CoreService] services: node services
|
||||
:param bool wait: determines if we should wait to validate startup
|
||||
:return: status of startup
|
||||
:rtype: str
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
if service.custom:
|
||||
cmds = service.startup
|
||||
else:
|
||||
cmds = service.startup
|
||||
if not service.custom:
|
||||
cmds = service.getstartup(node, services)
|
||||
|
||||
status = "0"
|
||||
for args in cmds:
|
||||
status = 0
|
||||
for cmd in cmds:
|
||||
try:
|
||||
node.check_cmd(args)
|
||||
if wait:
|
||||
node.check_cmd(cmd)
|
||||
else:
|
||||
node.cmd(cmd, wait=False)
|
||||
except CoreCommandError:
|
||||
logger.exception("error starting command")
|
||||
status = "-1"
|
||||
status = -1
|
||||
return status
|
||||
|
||||
def node_service_files(self, node, service, services):
|
||||
"""
|
||||
Creates node service files.
|
||||
|
||||
:param PyCoreNode node: node to reconfigure service for
|
||||
:param CoreService service: service to reconfigure
|
||||
:param list[CoreService] services: node services
|
||||
:return: nothing
|
||||
"""
|
||||
# get values depending on if custom or not
|
||||
file_names = service.configs
|
||||
if not service.custom:
|
||||
file_names = service.getconfigfilenames(node.objid, services)
|
||||
|
||||
for file_name in file_names:
|
||||
logger.info("generating service config: %s", file_name)
|
||||
if service.custom:
|
||||
cfg = service.configtxt.get(file_name)
|
||||
if cfg is None:
|
||||
cfg = service.generateconfig(node, file_name, services)
|
||||
|
||||
# cfg may have a file:/// url for copying from a file
|
||||
try:
|
||||
if self.copyservicefile(node, file_name, cfg):
|
||||
continue
|
||||
except IOError:
|
||||
logger.exception("error copying service file: %s", file_name)
|
||||
continue
|
||||
else:
|
||||
cfg = service.generateconfig(node, file_name, services)
|
||||
|
||||
node.nodefile(file_name, cfg)
|
||||
|
||||
def node_service_reconfigure(self, node, service, services):
|
||||
"""
|
||||
Reconfigure a node service.
|
||||
|
@ -685,21 +715,20 @@ class CoreServices(object):
|
|||
:param list[CoreService] services: node services
|
||||
:return: nothing
|
||||
"""
|
||||
if service.custom:
|
||||
cfgfiles = service.configs
|
||||
else:
|
||||
cfgfiles = service.getconfigfilenames(node.objid, services)
|
||||
file_names = service.configs
|
||||
if not service.custom:
|
||||
file_names = service.getconfigfilenames(node.objid, services)
|
||||
|
||||
for filename in cfgfiles:
|
||||
if filename[:7] == "file:///":
|
||||
for file_name in file_names:
|
||||
if file_name[:7] == "file:///":
|
||||
# TODO: implement this
|
||||
raise NotImplementedError
|
||||
|
||||
cfg = service.configtxt.get(filename)
|
||||
cfg = service.configtxt.get(file_name)
|
||||
if cfg is None:
|
||||
cfg = service.generateconfig(node, filename, services)
|
||||
cfg = service.generateconfig(node, file_name, services)
|
||||
|
||||
node.nodefile(filename, cfg)
|
||||
node.nodefile(file_name, cfg)
|
||||
|
||||
|
||||
class CoreService(object):
|
||||
|
@ -742,6 +771,12 @@ class CoreService(object):
|
|||
# list of validate commands
|
||||
validate = ()
|
||||
|
||||
# validation mode, used to determine startup success
|
||||
validation_mode = ServiceMode.NON_BLOCKING
|
||||
|
||||
# time to wait for determining if service started successfully
|
||||
validation_timer = 0
|
||||
|
||||
# metadata associated with this service
|
||||
meta = None
|
||||
|
||||
|
|
|
@ -607,12 +607,6 @@ class Session(object):
|
|||
# boot the services on each node
|
||||
self.boot_nodes()
|
||||
|
||||
# allow time for processes to start
|
||||
time.sleep(0.125)
|
||||
|
||||
# validate nodes
|
||||
self.validate_nodes()
|
||||
|
||||
# set broker local instantiation to complete
|
||||
self.broker.local_instantiation_complete()
|
||||
|
||||
|
@ -732,24 +726,6 @@ class Session(object):
|
|||
|
||||
self.update_control_interface_hosts()
|
||||
|
||||
def validate_nodes(self):
|
||||
"""
|
||||
Validate all nodes that are known by the session.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
with self._objects_lock:
|
||||
for obj in self.objects.itervalues():
|
||||
# TODO: issues with checking PyCoreNode alone, validate is not a method
|
||||
# such as vnoded process, bridges, etc.
|
||||
if not isinstance(obj, nodes.PyCoreNode):
|
||||
continue
|
||||
|
||||
if nodeutils.is_node(obj, NodeTypes.RJ45):
|
||||
continue
|
||||
|
||||
obj.validate()
|
||||
|
||||
def get_control_net_prefixes(self):
|
||||
"""
|
||||
Retrieve control net prefixes.
|
||||
|
|
|
@ -39,7 +39,6 @@ class TestNodes:
|
|||
assert node.alive()
|
||||
assert node.up
|
||||
assert node.check_cmd(["ip", "addr", "show", "lo"])
|
||||
node.validate()
|
||||
|
||||
def test_node_update(self, session):
|
||||
# given
|
||||
|
|
Loading…
Reference in a new issue