added logic to help provide dependent service ordering

This commit is contained in:
Blake J. Harnden 2018-06-19 18:36:53 -07:00
parent bf47e5fc0d
commit 37ce407460
4 changed files with 159 additions and 3 deletions

View file

@ -804,6 +804,8 @@ class CoreEmu(object):
os.umask(0) os.umask(0)
# configuration # configuration
if not config:
config = {}
self.config = config self.config = config
# session management # session management

View file

@ -211,6 +211,73 @@ class CoreServices(object):
self.defaultservices.clear() self.defaultservices.clear()
self.customservices.clear() self.customservices.clear()
def get_service_startups(self, services):
# generate service map and find starting points
node_services = {service.name: service for service in services}
is_dependency = set()
all_services = set()
for service in services:
all_services.add(service.name)
for service_name in service.dependencies:
# check service needed is valid
if service_name not in node_services:
raise ValueError("service(%s) dependency does not exist: %s" % (service.name, service_name))
is_dependency.add(service_name)
starting_points = all_services - is_dependency
# cycles means no starting points
if not starting_points:
raise ValueError("no valid service starting points")
stack = [iter(starting_points)]
# information used to traverse dependency graph
visited = set()
path = []
path_set = set()
# store startup orderings
startups = []
startup = []
logger.debug("starting points: %s", starting_points)
while stack:
for service_name in stack[-1]:
service = node_services[service_name]
logger.debug("evaluating: %s", service.name)
# check this is not a cycle
if service.name in path_set:
raise ValueError("service has a cyclic dependency: %s" % service.name)
# check that we have not already visited this node
elif service.name not in visited:
logger.debug("visiting: %s", service.name)
visited.add(service.name)
path.append(service.name)
path_set.add(service.name)
# retrieve and set dependencies to the stack
stack.append(iter(service.dependencies))
startup.append(service)
break
# for loop completed without a break
else:
logger.debug("finished a visit: path(%s)", path)
if path:
path_set.remove(path.pop())
if not path and startup:
startup.reverse()
startups.append(startup)
startup = []
stack.pop()
if visited != all_services:
raise ValueError("cycle encountered, services are being skipped")
return startups
def getdefaultservices(self, service_type): def getdefaultservices(self, service_type):
""" """
Get the list of default services that should be enabled for a Get the list of default services that should be enabled for a
@ -645,6 +712,9 @@ class CoreService(object):
# executables that must exist for service to run # executables that must exist for service to run
executables = () executables = ()
# sets service requirements that must be started prior to this service starting
dependencies = ()
# group string allows grouping services together # group string allows grouping services together
group = None group = None

View file

@ -47,6 +47,7 @@ class NrlService(CoreService):
class MgenSinkService(NrlService): class MgenSinkService(NrlService):
name = "MGEN_Sink" name = "MGEN_Sink"
executables = ("mgen",)
configs = ("sink.mgen",) configs = ("sink.mgen",)
startindex = 5 startindex = 5
startup = ("mgen input sink.mgen",) startup = ("mgen input sink.mgen",)
@ -73,6 +74,7 @@ class NrlNhdp(NrlService):
NeighborHood Discovery Protocol for MANET networks. NeighborHood Discovery Protocol for MANET networks.
""" """
name = "NHDP" name = "NHDP"
executables = ("nrlnhdp",)
startup = ("nrlnhdp",) startup = ("nrlnhdp",)
shutdown = ("killall nrlnhdp",) shutdown = ("killall nrlnhdp",)
validate = ("pidof nrlnhdp",) validate = ("pidof nrlnhdp",)
@ -105,6 +107,7 @@ class NrlSmf(NrlService):
Simplified Multicast Forwarding for MANET networks. Simplified Multicast Forwarding for MANET networks.
""" """
name = "SMF" name = "SMF"
executables = ("nrlsmf",)
startup = ("sh startsmf.sh",) startup = ("sh startsmf.sh",)
shutdown = ("killall nrlsmf",) shutdown = ("killall nrlsmf",)
validate = ("pidof nrlsmf",) validate = ("pidof nrlsmf",)
@ -156,6 +159,7 @@ class NrlOlsr(NrlService):
Optimized Link State Routing protocol for MANET networks. Optimized Link State Routing protocol for MANET networks.
""" """
name = "OLSR" name = "OLSR"
executables = ("nrlolsrd",)
startup = ("nrlolsrd",) startup = ("nrlolsrd",)
shutdown = ("killall nrlolsrd",) shutdown = ("killall nrlolsrd",)
validate = ("pidof nrlolsrd",) validate = ("pidof nrlolsrd",)
@ -189,6 +193,7 @@ class NrlOlsrv2(NrlService):
Optimized Link State Routing protocol version 2 for MANET networks. Optimized Link State Routing protocol version 2 for MANET networks.
""" """
name = "OLSRv2" name = "OLSRv2"
executables = ("nrlolsrv2",)
startup = ("nrlolsrv2",) startup = ("nrlolsrv2",)
shutdown = ("killall nrlolsrv2",) shutdown = ("killall nrlolsrv2",)
validate = ("pidof nrlolsrv2",) validate = ("pidof nrlolsrv2",)
@ -223,6 +228,7 @@ class OlsrOrg(NrlService):
Optimized Link State Routing protocol from olsr.org for MANET networks. Optimized Link State Routing protocol from olsr.org for MANET networks.
""" """
name = "OLSRORG" name = "OLSRORG"
executables = ("olsrd",)
configs = ("/etc/olsrd/olsrd.conf",) configs = ("/etc/olsrd/olsrd.conf",)
dirs = ("/etc/olsrd",) dirs = ("/etc/olsrd",)
startup = ("olsrd",) startup = ("olsrd",)
@ -572,6 +578,7 @@ class MgenActor(NrlService):
# a unique name is required, without spaces # a unique name is required, without spaces
name = "MgenActor" name = "MgenActor"
executables = ("mgen",)
# you can create your own group here # you can create your own group here
group = "ProtoSvc" group = "ProtoSvc"
# list of other services this service depends on # list of other services this service depends on
@ -616,6 +623,7 @@ class Arouted(NrlService):
Adaptive Routing Adaptive Routing
""" """
name = "arouted" name = "arouted"
executables = ("arouted",)
configs = ("startarouted.sh",) configs = ("startarouted.sh",)
startindex = NrlService.startindex + 10 startindex = NrlService.startindex + 10
startup = ("sh startarouted.sh",) startup = ("sh startarouted.sh",)

View file

@ -1,18 +1,94 @@
import os import os
import pytest
from core.service import CoreService
from core.service import ServiceManager from core.service import ServiceManager
_PATH = os.path.abspath(os.path.dirname(__file__)) _PATH = os.path.abspath(os.path.dirname(__file__))
_SERVICES_PATH = os.path.join(_PATH, "myservices") _SERVICES_PATH = os.path.join(_PATH, "myservices")
class ServiceA(CoreService):
name = "A"
dependencies = ("B",)
class ServiceB(CoreService):
name = "B"
dependencies = ("C",)
class ServiceC(CoreService):
name = "C"
dependencies = ()
class ServiceD(CoreService):
name = "D"
dependencies = ("A",)
class ServiceE(CoreService):
name = "E"
dependencies = ("Z",)
class ServiceF(CoreService):
name = "F"
dependencies = ()
class TestServices: class TestServices:
def test_import_service(self): def test_service_import(self):
""" """
Test importing a custom service. Test importing a custom service.
:param conftest.Core core: core fixture to test with
""" """
ServiceManager.add_services(_SERVICES_PATH) ServiceManager.add_services(_SERVICES_PATH)
assert ServiceManager.get("MyService") assert ServiceManager.get("MyService")
assert ServiceManager.get("MyService2") assert ServiceManager.get("MyService2")
def test_services_dependencies(self, session):
# given
services = [
ServiceA(),
ServiceB(),
ServiceC(),
ServiceD(),
ServiceF(),
]
# when
startups = session.services.get_service_startups(services)
# then
assert len(startups) == 2
def test_services_dependencies_not_present(self, session):
# given
services = [
ServiceA(),
ServiceB(),
ServiceC(),
ServiceE()
]
# when
with pytest.raises(ValueError):
session.services.get_service_startups(services)
def test_services_dependencies_cycle(self, session):
# given
service_c = ServiceC()
service_c.dependencies = ("D",)
services = [
ServiceA(),
ServiceB(),
service_c,
ServiceD(),
ServiceF()
]
# when
with pytest.raises(ValueError):
session.services.get_service_startups(services)