added logic to help provide dependent service ordering
This commit is contained in:
parent
bf47e5fc0d
commit
37ce407460
4 changed files with 159 additions and 3 deletions
|
@ -804,6 +804,8 @@ class CoreEmu(object):
|
|||
os.umask(0)
|
||||
|
||||
# configuration
|
||||
if not config:
|
||||
config = {}
|
||||
self.config = config
|
||||
|
||||
# session management
|
||||
|
|
|
@ -211,6 +211,73 @@ class CoreServices(object):
|
|||
self.defaultservices.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):
|
||||
"""
|
||||
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 = ()
|
||||
|
||||
# sets service requirements that must be started prior to this service starting
|
||||
dependencies = ()
|
||||
|
||||
# group string allows grouping services together
|
||||
group = None
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ class NrlService(CoreService):
|
|||
|
||||
class MgenSinkService(NrlService):
|
||||
name = "MGEN_Sink"
|
||||
executables = ("mgen",)
|
||||
configs = ("sink.mgen",)
|
||||
startindex = 5
|
||||
startup = ("mgen input sink.mgen",)
|
||||
|
@ -73,6 +74,7 @@ class NrlNhdp(NrlService):
|
|||
NeighborHood Discovery Protocol for MANET networks.
|
||||
"""
|
||||
name = "NHDP"
|
||||
executables = ("nrlnhdp",)
|
||||
startup = ("nrlnhdp",)
|
||||
shutdown = ("killall nrlnhdp",)
|
||||
validate = ("pidof nrlnhdp",)
|
||||
|
@ -105,6 +107,7 @@ class NrlSmf(NrlService):
|
|||
Simplified Multicast Forwarding for MANET networks.
|
||||
"""
|
||||
name = "SMF"
|
||||
executables = ("nrlsmf",)
|
||||
startup = ("sh startsmf.sh",)
|
||||
shutdown = ("killall nrlsmf",)
|
||||
validate = ("pidof nrlsmf",)
|
||||
|
@ -156,6 +159,7 @@ class NrlOlsr(NrlService):
|
|||
Optimized Link State Routing protocol for MANET networks.
|
||||
"""
|
||||
name = "OLSR"
|
||||
executables = ("nrlolsrd",)
|
||||
startup = ("nrlolsrd",)
|
||||
shutdown = ("killall nrlolsrd",)
|
||||
validate = ("pidof nrlolsrd",)
|
||||
|
@ -189,6 +193,7 @@ class NrlOlsrv2(NrlService):
|
|||
Optimized Link State Routing protocol version 2 for MANET networks.
|
||||
"""
|
||||
name = "OLSRv2"
|
||||
executables = ("nrlolsrv2",)
|
||||
startup = ("nrlolsrv2",)
|
||||
shutdown = ("killall nrlolsrv2",)
|
||||
validate = ("pidof nrlolsrv2",)
|
||||
|
@ -223,6 +228,7 @@ class OlsrOrg(NrlService):
|
|||
Optimized Link State Routing protocol from olsr.org for MANET networks.
|
||||
"""
|
||||
name = "OLSRORG"
|
||||
executables = ("olsrd",)
|
||||
configs = ("/etc/olsrd/olsrd.conf",)
|
||||
dirs = ("/etc/olsrd",)
|
||||
startup = ("olsrd",)
|
||||
|
@ -572,6 +578,7 @@ class MgenActor(NrlService):
|
|||
|
||||
# a unique name is required, without spaces
|
||||
name = "MgenActor"
|
||||
executables = ("mgen",)
|
||||
# you can create your own group here
|
||||
group = "ProtoSvc"
|
||||
# list of other services this service depends on
|
||||
|
@ -616,6 +623,7 @@ class Arouted(NrlService):
|
|||
Adaptive Routing
|
||||
"""
|
||||
name = "arouted"
|
||||
executables = ("arouted",)
|
||||
configs = ("startarouted.sh",)
|
||||
startindex = NrlService.startindex + 10
|
||||
startup = ("sh startarouted.sh",)
|
||||
|
|
|
@ -1,18 +1,94 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from core.service import CoreService
|
||||
from core.service import ServiceManager
|
||||
|
||||
_PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
_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:
|
||||
def test_import_service(self):
|
||||
def test_service_import(self):
|
||||
"""
|
||||
Test importing a custom service.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
ServiceManager.add_services(_SERVICES_PATH)
|
||||
assert ServiceManager.get("MyService")
|
||||
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)
|
||||
|
|
Loading…
Add table
Reference in a new issue