added config service manager, added custom loading of subdirs for config based services, added configurations for config services

This commit is contained in:
Blake Harnden 2020-01-17 11:40:29 -08:00
parent cf7dda816c
commit 433327c0ae
7 changed files with 194 additions and 52 deletions

6
daemon/Pipfile.lock generated
View file

@ -649,11 +649,11 @@
},
"pytest": {
"hashes": [
"sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa",
"sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4"
"sha256:9f8d44f4722b3d06b41afaeb8d177cfbe0700f8351b1fc755dd27eedaa3eb9e0",
"sha256:f5d3d0e07333119fe7d4af4ce122362dc4053cdd34a71d2766290cf5369c64ad"
],
"index": "pypi",
"version": "==5.3.2"
"version": "==5.3.3"
},
"pyyaml": {
"hashes": [

View file

@ -9,6 +9,8 @@ from typing import Any, Dict, List
from mako import exceptions
from mako.lookup import TemplateLookup
from core import utils
from core.config import Configuration
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNode
@ -21,6 +23,42 @@ class ConfigServiceMode(enum.Enum):
TIMER = 2
class ConfigServiceManager:
def __init__(self):
self.services = {}
def add(self, service: "ConfigService") -> None:
name = service.name
logging.debug("loading service: class(%s) name(%s)", service.__class__, name)
# avoid duplicate services
if name in self.services:
raise CoreError(f"duplicate service being added: {name}")
# validate dependent executables are present
for executable in service.executables:
utils.which(executable, required=True)
# make service available
self.services[name] = service
def load(self, path: str) -> List[str]:
path = pathlib.Path(path)
subdirs = [x for x in path.iterdir() if x.is_dir()]
service_errors = []
for subdir in subdirs:
logging.info("loading config services from: %s", subdir)
services = utils.load_classes(str(subdir), ConfigService)
for service in services:
logging.info("found service: %s", service)
try:
self.add(service)
except CoreError as e:
service_errors.append(service.name)
logging.debug("not loading service(%s): %s", service.name, e)
return service_errors
class ConfigService(abc.ABC):
# validation period in seconds, how frequent validation is attempted
validation_period = 0.5
@ -34,6 +72,9 @@ class ConfigService(abc.ABC):
templates_path = pathlib.Path(class_file).parent.joinpath(TEMPLATES_DIR)
logging.info(templates_path)
self.templates = TemplateLookup(directories=templates_path)
self.config = {}
configs = self.default_configs[:]
self._define_config(configs)
@property
@abc.abstractmethod
@ -50,6 +91,11 @@ class ConfigService(abc.ABC):
def directories(self) -> List[str]:
raise NotImplementedError
@property
@abc.abstractmethod
def default_configs(self) -> List[Configuration]:
raise NotImplementedError
@property
@abc.abstractmethod
def executables(self) -> List[str]:
@ -127,7 +173,7 @@ class ConfigService(abc.ABC):
wait = self.validation_mode == ConfigServiceMode.BLOCKING
start = time.monotonic()
index = 0
cmds = self.startup[:]
cmds = self.validate[:]
while cmds:
cmd = cmds[index]
try:
@ -152,7 +198,9 @@ class ConfigService(abc.ABC):
data = {}
try:
template = self.templates.get_template(name)
rendered = template.render_unicode(node=self.node, **data)
rendered = template.render_unicode(
node=self.node, config=self.render_config(), **data
)
logging.info(
"node(%s) service(%s) template(%s): \n%s",
self.node.name,
@ -160,10 +208,24 @@ class ConfigService(abc.ABC):
name,
rendered,
)
# self.node.nodefile(name, rendered)
self.node.nodefile(name, rendered)
except Exception:
raise CoreError(
f"node({self.node.name}) service({self.name}) "
f"error rendering template({name}): "
f"{exceptions.text_error_template().render()}"
)
def _define_config(self, configs: List[Configuration]) -> None:
for config in configs:
self.config[config.id] = config
def render_config(self) -> Dict[str, str]:
return {k: v.default for k, v in self.config.items()}
def set_config(self, data: Dict[str, str]) -> None:
for key, value in data.items():
config = self.config.get(key)
if config is None:
raise CoreError(f"unknown config: {key}")
config.default = value

View file

@ -1,46 +0,0 @@
import logging
import netaddr
from core.configservice.base import ConfigService, ConfigServiceMode
from core.emulator.session import Session
from core.nodes.base import CoreNode
from core.nodes.interface import Veth
class DefaultRoute(ConfigService):
name = "DefaultRoute"
group = "Utility"
directories = []
executables = []
dependencies = []
startup = []
validate = []
shutdown = []
validation_mode = ConfigServiceMode.BLOCKING
def create_files(self):
self.create_default_route()
def create_default_route(self):
addresses = []
for netif in self.node.netifs():
if getattr(netif, "control", False):
continue
for addr in netif.addrlist:
net = netaddr.IPNetwork(addr)
if net[1] != net[-2]:
addresses.append(net[1])
data = dict(addresses=addresses)
self.render("defaultroute.sh", data)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
session = Session(1, mkdir=False)
node = CoreNode(session, _id=1, start=False)
netif = Veth(session, node, "eth0", "eth0", start=False)
netif.addaddr("10.0.0.1/24")
node.addnetif(netif, 0)
service = DefaultRoute(node)
service.start()

View file

@ -1,5 +1,6 @@
#!/bin/sh
# auto-generated by DefaultRoute service
# config: ${config}
% for address in addresses:
ip route add default via ${address}
% endfor

View file

@ -0,0 +1,16 @@
#!/bin/sh
# auto-generated by IPForward service (utility.py)
sysctl -w net.ipv4.conf.all.forwarding=1
sysctl -w net.ipv4.conf.default.forwarding=1
sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv6.conf.default.forwarding=1
sysctl -w net.ipv4.conf.all.send_redirects=0
sysctl -w net.ipv4.conf.default.send_redirects=0
sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.default.rp_filter=0
# setup forwarding for node interfaces
% for devname in devnames:
sysctl -w net.ipv4.conf.${devname}.forwarding=1
sysctl -w net.ipv4.conf.${devname}.send_redirects=0
sysctl -w net.ipv4.conf.${devname}.rp_filter=0
% endfor

View file

@ -0,0 +1,109 @@
import logging
import os
import netaddr
from core import utils
from core.config import Configuration
from core.configservice.base import (
ConfigService,
ConfigServiceManager,
ConfigServiceMode,
)
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import ConfigDataTypes, EventTypes, NodeTypes
GROUP_NAME = "Utility"
class DefaultRoute(ConfigService):
name = "DefaultRoute"
group = GROUP_NAME
directories = []
executables = []
dependencies = []
startup = ["sh defaultroute.sh"]
validate = []
shutdown = []
validation_mode = ConfigServiceMode.BLOCKING
default_configs = [
Configuration(_id="value1", _type=ConfigDataTypes.STRING, label="Value 1"),
Configuration(_id="value2", _type=ConfigDataTypes.STRING, label="Value 2"),
Configuration(_id="value3", _type=ConfigDataTypes.STRING, label="Value 3"),
]
def create_files(self):
addresses = []
for netif in self.node.netifs():
if getattr(netif, "control", False):
continue
for addr in netif.addrlist:
net = netaddr.IPNetwork(addr)
if net[1] != net[-2]:
addresses.append(net[1])
data = dict(addresses=addresses)
self.render("defaultroute.sh", data)
class IpForwardService(ConfigService):
name = "IPForward"
group = GROUP_NAME
directories = []
executables = ["sysctl"]
dependencies = []
startup = ["sh ipforward.sh"]
validate = []
shutdown = []
validation_mode = ConfigServiceMode.BLOCKING
default_configs = []
def create_files(self) -> None:
devnames = []
for ifc in self.node.netifs():
devname = utils.sysctl_devname(ifc.name)
devnames.append(devname)
data = dict(devnames=devnames)
self.render("ipforward.sh", data)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
# setup basic network
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
options = NodeOptions(model="nothing")
# options.services = []
coreemu = CoreEmu()
session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
switch = session.add_node(_type=NodeTypes.SWITCH)
# node one
node_one = session.add_node(options=options)
interface = prefixes.create_interface(node_one)
session.add_link(node_one.id, switch.id, interface_one=interface)
# node two
node_two = session.add_node(options=options)
interface = prefixes.create_interface(node_two)
session.add_link(node_two.id, switch.id, interface_one=interface)
session.instantiate()
# manager load config services
manager = ConfigServiceManager()
path = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
manager.load(path)
clazz = manager.services["DefaultRoute"]
dr_service = clazz(node_one)
dr_service.set_config({"value1": "custom"})
dr_service.start()
clazz = manager.services["IPForward"]
dr_service = clazz(node_one)
dr_service.start()
input("press enter to exit")
session.shutdown()