From 37ce407460021a85f6c05634e2ac6bcfe50e3304 Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Tue, 19 Jun 2018 18:36:53 -0700 Subject: [PATCH] added logic to help provide dependent service ordering --- daemon/core/emulator/coreemu.py | 2 + daemon/core/service.py | 70 ++++++++++++++++++++++++++++ daemon/core/services/nrl.py | 8 ++++ daemon/tests/test_services.py | 82 +++++++++++++++++++++++++++++++-- 4 files changed, 159 insertions(+), 3 deletions(-) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 4e207bf7..5b8d9301 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -804,6 +804,8 @@ class CoreEmu(object): os.umask(0) # configuration + if not config: + config = {} self.config = config # session management diff --git a/daemon/core/service.py b/daemon/core/service.py index 4f367dc2..ab0dbe5d 100644 --- a/daemon/core/service.py +++ b/daemon/core/service.py @@ -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 diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 07152404..d446ef81 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -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",) diff --git a/daemon/tests/test_services.py b/daemon/tests/test_services.py index 7b60ff1b..3cefd7bf 100644 --- a/daemon/tests/test_services.py +++ b/daemon/tests/test_services.py @@ -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)