Services
Overview
CORE uses the concept of services to specify what processes or scripts to run on a node when it is started. Ultimately, providing a convenience for creating reusable dynamic scripts to run on nodes, for carrying out specific tasks.
Services leverage a templating engine, for robust service file creation. They also have the power of configuration key/value pairs, that can be defined and displayed within the GUI, to help further configure a service, as needed.
This boils down to the following functions:
- generating files the service will use, either directly for commands or for configuration
- command(s) for starting a service
- command(s) for validating a service
- command(s) for stopping a service
Most CORE nodes will have a default set of services to run, associated with them. You can however customize the set of services a node will use. Or even further define a new node type within the GUI, with a set of services, that will allow quickly dragging and dropping that node type during creation.
Available Services
Service Group | Services |
---|---|
BIRD | BGP, OSPF, RADV, RIP, Static |
EMANE | Transport Service |
FRR | BABEL, BGP, OSPFv2, OSPFv3, PIMD, RIP, RIPNG, Zebra |
NRL | arouted, MGEN Sink, MGEN Actor, NHDP, OLSR, OLSRORG, OLSRv2, SMF |
Quagga | BABEL, BGP, OSPFv2, OSPFv3, OSPFv3 MDR, RIP, RIPNG, XPIMD, Zebra |
SDN | OVS, RYU |
Security | Firewall, IPsec, NAT, VPN Client, VPN Server |
Utility | ATD, Routing Utils, DHCP, FTP, IP Forward, PCAP, RADVD, SSF, UCARP |
XORP | BGP, OLSR, OSPFv2, OSPFv3, PIMSM4, PIMSM6, RIP, RIPNG, Router Manager |
Node Types and Default Services
Here are the default node types and their services:
Node Type | Services |
---|---|
router | zebra, OSFPv2, OSPFv3, and IPForward services for IGP link-state routing. |
PC | DefaultRoute service for having a default route when connected directly to a router. |
mdr | zebra, OSPFv3MDR, and IPForward services for wireless-optimized MANET Designated Router routing. |
prouter | a physical router, having the same default services as the router node type; for incorporating Linux testbed machines into an emulation. |
Configuration files can be automatically generated by each service. For example, CORE automatically generates routing protocol configuration for the router nodes in order to simplify the creation of virtual networks.
To change the services associated with a node, right-click a node a choose Services... from the menu button. Services are enabled or disabled by selecting through the service groups and enabling the checkboxes on services. Select a selected service and click the Configure button to further configure a given service.
To change the default services associated with a node type, use the Custom Nodes option under the Edit menu option. Here you can define new node types, with a custom icon, and a custom set of services to start on nodes of this type. This node type will be added to the container node options on the left toolbar, allowing for easy drag and drop creation for nodes of this type.
The node types are saved in the GUI config file ~/.coregui/config.yaml. Keep this in mind when changing the default services for existing node types; it may be better to simply create a new node type. It is recommended that you do not change the default built-in node types.
New Services
Services can save time required to configure nodes, especially if a number of nodes require similar configuration procedures. New services can be introduced to automate tasks.
Creating New Services
Note
The directory base name used in custom_services_dir below should be unique and should not correspond to any existing Python module name. For example, don't use the name subprocess or services.
-
Modify the example service shown below to do what you want. It could generate config/script files, mount per-node directories, start processes/scripts, etc. Your file can define one or more classes to be imported. You can create multiple Python files that will be imported.
-
Put these files in a directory such as ~/.coregui/custom_services.
-
Set the custom_services_dir = ~/.coregui/custom_services entry to the /opt/core/etc/core.conf file.
-
Restart the CORE daemon (core-daemon). Any import errors (Python syntax) should be displayed in the terminal (or service log, like journalctl).
-
Start using your custom service on your nodes. You can create a new node type that uses your service, or change the default services for an existing node type, or change individual nodes.
Example Custom Service
Below is the skeleton for a custom service with some documentation. Most people would likely only setup the required class variables (name/group). Then define the files to generate and implement the get_text_template function to dynamically create the files wanted. Finally, the startup commands would be supplied, which typically tend to be running the shell files generated.
This is a very simple service using the bare minimum needed.
from core.services.base import CoreService
class ExampleService(CoreService):
name: str = "Node Name"
group: str = "ExampleGroup"
files: list[str] = ["node_name.sh"]
startup: list[str] = [f"bash {files[0]}"]
def get_text_template(self, name: str) -> str:
return """
echo '${node.name}' > node_name.log
"""
This fleshes out all the fields and helps document their purpose.
from core.config import ConfigString, ConfigBool, Configuration
from core.services.base import CoreService, ShadowDir, ServiceMode
# class that subclasses CoreService
class ExampleService(CoreService):
# unique name for your service within CORE
name: str = "Example"
# the group your service is associated with, used for display in GUI
group: str = "ExampleGroup"
# directories that the service should shadow mount, hiding the system directory
directories: list[str] = ["/usr/local/core"]
# files that this service should generate, defaults to nodes home directory
# or can provide an absolute path to a mounted directory
files: list[str] = ["example-start.sh"]
# executables that should exist on path, that this service depends on
executables: list[str] = []
# other services that this service depends on, defines service start order
dependencies: list[str] = []
# commands to run to start this service
startup: list[str] = []
# commands to run to validate this service
validate: list[str] = []
# commands to run to stop this service
shutdown: list[str] = []
# validation mode, blocking, non-blocking, and timer
validation_mode: ServiceMode = ServiceMode.BLOCKING
# configurable values that this service can use, for file generation
default_configs: list[Configuration] = [
ConfigString(id="value1", label="Text"),
ConfigBool(id="value2", label="Boolean"),
ConfigString(id="value3", label="Multiple Choice",
options=["value1", "value2", "value3"]),
]
# sets of values to set for the configuration defined above, can be used to
# provide convenient sets of values to typically use
modes: dict[str, dict[str, str]] = {
"mode1": {"value1": "value1", "value2": "0", "value3": "value2"},
"mode2": {"value1": "value2", "value2": "1", "value3": "value3"},
"mode3": {"value1": "value3", "value2": "0", "value3": "value1"},
}
# defines directories that this service can help shadow within a node
shadow_directories: list[ShadowDir] = []
def get_text_template(self, name: str) -> str:
"""
This function is used to return a string template that will be rendered
by the templating engine. Available variables will be node and any other
key/value pairs returned by the "data()" function.
:param name: name of file to get template for
:return: string template
"""
return """
# sample script 1
# node id(${node.id}) name(${node.name})
# config: ${config}
echo hello
"""
Validation Mode
Validation modes are used to determine if a service has started up successfully.
- blocking - startup commands are expected to run til completion and return 0 exit code
- non-blocking - startup commands are ran, but do not wait for completion
- timer - startup commands are ran, and an arbitrary amount of time is waited to consider started
Shadow Directories
Shadow directories provide a convenience for copying a directory and the files within it to a nodes home directory, to allow a unique set of per node files.
ShadowDir(path="/user/local/core")
- copies files at the given location into the nodeShadowDir(path="/user/local/core", src="/opt/core")
- copies files to the given location, but sourced from the provided locationShadowDir(path="/user/local/core", templates=True)
- copies files and treats them as templates for generationShadowDir(path="/user/local/core", has_node_paths=True)
- copies files from the given location, and looks for unique node names directories within it, using a directory named default, when not preset