Core services

服务

CORE 使用服务的概念来指定节点启动时运行哪些进程或脚本。路由器和PC等第三层节点是由他们所运行的服务来定义的。

可以为每个节点定制服务,也可以创造新的自定义服务。可以创建具有不同名称、图标和默认服务集的新节点类型。 每个服务定义每个节点的路径、配置文件、启动索引、启动命令、验证命令、关闭命令和与节点关联的元数据。

注意: 使用 init, upstart, 或 systemd 框架时,网络名称节点空间不会经历正常的Linux引导过程 ,这些轻量节点使用已经配置好的CORE服务。

提供的服务

服务组服务总结
BIRDBGP, OSPF, RADV, RIP, Static
EMANETransport Service
FRRBABEL, BGP, OSPFv2, OSPFv3, PIMD, RIP, RIPNG, Zebra
NRLarouted, MGEN Sink, MGEN Actor, NHDP, OLSR, OLSRORG, OLSRv2, SMF
QuaggaBABEL, BGP, OSPFv2, OSPFv3, OSPFv3 MDR, RIP, RIPNG, XPIMD, Zebra
SDNOVS, RYU
SecurityFirewall, IPsec, NAT, VPN Client, VPN Server
UtilityATD, Routing Utils, DHCP, FTP, IP Forward, PCAP, RADVD, SSF, UCARP
XORPBGP, OLSR, OSPFv2, OSPFv3, PIMSM4, PIMSM6, RIP, RIPNG, Router Manager

节点类型和默认服务

以下是默认节点类型和他们的服务:

节点类型服务
router针对IGP链路状态路由的 zebra, OSFPv2, OSPFv3, and IPForward 服务。
host默认路由和SSH服务, 其表示直接连接到路由器时,SSH具有默认路由。
PC为拥有默认路由且直接连接到路由器的节点提供默认路由服务.
mdr针对无线优化的 MANET 指定路由的 zebra、 OSPFv3MDR 和 IPForward 服务。
prouter路由器 节点类型具有相同默认服务的物理路由器; 用于将Linux测试平台设备合并到仿真中。

配置文件可以由每个服务自动生成。例如,CORE 会自动为路由器节点生成路由协议配置,来简化虚拟网络的创建。

更改与节点相关联的服务,可以双击节点来调用配置对话框, 然后单击 服务… 按钮,或者右键单击某个节点,从右键菜单中选择 服务… 选项。通过单击服务的名称可以启用或禁止该服务。每个服务名称旁边的按钮允许您为该节点自定义此服务的所有方面。例如,可以将特殊的路由重分发命令插入到与 zebra 服务关联的 Quagga 路由配置中。

若要更改与节点类型相关的默认服务, 请使用第三层节点工具栏末端的 编辑 按钮中的 节点类型… 对话框,或是从 会话 菜单中选择 节点类型…。 注意,如果已经定制了节点,那么所选择的任何新服务都不会应用于现有节点。

节点类型被保存在 ~/.core/nodes.conf 文件中,而不是 .imn 文件。在更改现有节点的默认服务时请记住这一点; 最好只创建一个新的节点类型。并且建议不要更改默认的内置节点类型。可以在 CORE 设备之间复制nodes.conf 文件来保存你的自定义类型。

定制服务

可以为特定节点完全定制服务。 从节点的配置对话框中,单击服务名称旁边的按钮,来调用该服务的服务定制对话框。该对话框有三个选项卡用于配置服务的不同方面:Files, Directories和tartup/shutdown。

服务旁边的 黄色 自定义图标表示服务需要自定义(例如 Firewall 服务)。绿色 的自定义图标表示存在自定义配置。在自定义服务时单击 default 按钮会移除所有自定义选项。

Files选项卡用于显示或编辑用于该服务的配置文件或脚本。文件可以从下拉列表中选择,它们的内容将显示在下面的文本框中。文件的内容由 CORE daemon根据自定义对话框调用时存在的网络拓扑进程生成。

Directories选项卡显示该服务的每个节点的路径。对于默认类型,CORE节点共享相同的文件系统树,但被服务定义的每个节点除外。例如,对于每个运行 Zebra 服务的节点,其 /var/run/quagga 路径必须是唯一的,因为在每个节点运行的 Quagga 需要向该路径写入单独的 PID 文件。

默认情况下, /var/log/var/run 路径按照每个节点唯一挂载。每个节点的挂载目标可以在 /tmp/pycore.nnnnn/nN.conf/ (其中 nnnnn 是会话编号,N是节点编号)中找到。

Startup/shutdown 选项卡列出用于启动和停止该服务的命令。 startup index允许在该服务启动时对为该节点启用的其他相关服务进行配置;Startup较低的服务先于Startup较高的服务. 由于Files选项卡生成的shell脚本没有执行权限设置,因此启动命令应包含shell名称,类似于 sh script.sh

注意! 其中的 start time 及index 选项我简单浏览了 core/daemon/core/services/coreservices.py 的 CoreService类,似乎作者移除了这两项的实现。在后续的 ServiceShim 的 tovaluelist 方法中直接填入写死的index = 0以及time = 0。可能是原有的bug导致作者放弃了实现这个功能。

Shutdown 命令可选择终止与此服务关联的进程。通常,它们使用 killkillall 命令向正在运行的进程发送kill 信号。如果服务没有使用 Shutdown 命令终止正在运行的进程,那么当终止 vnoded 守护进程(使用 kill -9命令)并摧毁命名空间时,进程将被杀死。指定 shutdown 命令是一个很好的实践,这将允许适当的进程终止,以及停止和重启服务的运行控制。

Validate 命令按照启动命令执行。 Validate 命令可以执行应返回0值的流程或者脚本,对于启动出现问题的服务返回非0值。例如,pidof 命令将检查某个进程是否正在运行,是则返回0值。当 Validate 命令生成了一个非零返回值,这将产生一个异常而导致在 Check Emulation Light 中显示一个错误。

在运行时启动、停止和重启服务,需要右键单击节点并使用 服务… 菜单。

新的服务

服务可以节省配置节点所需的时间,特别是当大量节点都需要类似的配置程序时。此时可以引入新的服务来使任务自动化。

如果使用tlv等界面api配置如下所示:

其中File name和startup为必选项,其作用为节点容器初始化后复制一份脚本名为File name的脚本至于temp中的节点文件夹,startup等命令除了初始化仿真会调用也可通过界面选项来使用,具体参考其他server选项做法。

利用用户自定义

将新流程的配置捕获到服务中的最简单方法是使用 UserDefined 服务。这是一个空白服务,可以自定义其中的任何方面。 UserDefined 服务便于在添加新的服务类型之前测试服务。

创建新的服务

  1. 修改如下所示的实例服务,以便做您想做的事情。它可以生成配置/脚本文件、每个节点的挂载路径、启动进程/脚本等。sample.py 是一个 Python 文件,它定义了一个或更多需要导入的类。您可以创建多个将被导入的Python文件。将任何新文件名添加到 init.py 文件中。

  2. 把这些文件放在诸如 /home/username/.core/myservices 这样的路径中。但应注意路径最后的名称 myservices 不应该命名为类似于与现有的Python名称冲突的 服务(使用的语法是 from myservices import *’)

  3. 添加命令 custom_services_dir = /home/username/.core/myservices 到 /etc/core/core.conf 文件中。

    注意:custom_services_dir 使用的路径名应该是唯一的,并且不应该对应于任何现有的Python模块名。例如,不要使用 subprocess 或是 services 名称。

  4. 重启 CORE 守护进程 (core-daemon). 任何导入错误 (Python 语法)都应显示在 /var/log/core-daemon.log 日志文件上(或显示在屏幕上)。

  5. 开始在节点上使用自定义服务吧。您可以创建使用您的服务的新节点类型,或者更改现有节点的默认服务,又或者更 改单个节点。

如果您已经创建了一个可能对他人有用的新服务类型,请考虑将其贡献给 CORE 项目。

自定义服务示例

下面是带有一些说明文档的自定义服务框架。大多数人可能只会设置所需的类变量 (name/group)。然后定义 configs (他们想要生成的文件),并实现 generate_config 函数来动态创建所需的文件。最后,被提供的 startup 命令通常倾向于运行生成的shell文件。

"""
Simple example custom service, used to drive shell commands on a node.
"""
from typing import Tuple

from core.nodes.base import CoreNode
from core.services.coreservices import CoreService, ServiceMode


class ExampleService(CoreService):
    """
    Example Custom CORE Service

    :cvar name: name used as a unique ID for this service and is required, no spaces
    :cvar group: allows you to group services within the GUI under a common name
    :cvar executables: executables this service depends on to function, if executable is
        not on the path, service will not be loaded
    :cvar dependencies: services that this service depends on for startup, tuple of
        service names
    :cvar dirs: directories that this service will create within a node
    :cvar configs: files that this service will generate, without a full path this file
        goes in the node's directory e.g. /tmp/pycore.12345/n1.conf/myfile
    :cvar startup: commands used to start this service, any non-zero exit code will
        cause a failure
    :cvar validate: commands used to validate that a service was started, any non-zero
        exit code will cause a failure
    :cvar validation_mode: validation mode, used to determine startup success.
        NON_BLOCKING    - runs startup commands, and validates success with validation commands
        BLOCKING        - runs startup commands, and validates success with the startup commands themselves
        TIMER           - runs startup commands, and validates success by waiting for "validation_timer" alone
    :cvar validation_timer: time in seconds for a service to wait for validation, before
        determining success in TIMER/NON_BLOCKING modes.
    :cvar validation_period: period in seconds to wait before retrying validation,
        only used in NON_BLOCKING mode
    :cvar shutdown: shutdown commands to stop this service
    """

    name: str = "ExampleService"
    group: str = "Utility"
    executables: Tuple[str, ...] = ()
    dependencies: Tuple[str, ...] = ()
    dirs: Tuple[str, ...] = ()
    configs: Tuple[str, ...] = ("myservice1.sh", "myservice2.sh")
    startup: Tuple[str, ...] = tuple(f"sh {x}" for x in configs)
    validate: Tuple[str, ...] = ()
    validation_mode: ServiceMode = ServiceMode.NON_BLOCKING
    validation_timer: int = 5
    validation_period: float = 0.5
    shutdown: Tuple[str, ...] = ()

    @classmethod
    def on_load(cls) -> None:
        """
        Provides a way to run some arbitrary logic when the service is loaded, possibly
        to help facilitate dynamic settings for the environment.

        :return: nothing
        """
        pass

    @classmethod
    def get_configs(cls, node: CoreNode) -> Tuple[str, ...]:
        """
        Provides a way to dynamically generate the config files from the node a service
        will run. Defaults to the class definition and can be left out entirely if not
        needed.

        :param node: core node that the service is being ran on
        :return: tuple of config files to create
        """
        return cls.configs

    @classmethod
    def generate_config(cls, node: CoreNode, filename: str) -> str:
        """
        Returns a string representation for a file, given the node the service is
        starting on the config filename that this information will be used for. This
        must be defined, if "configs" are defined.

        :param node: core node that the service is being ran on
        :param filename: configuration file to generate
        :return: configuration file content
        """
        cfg = "#!/bin/sh\n"
        if filename == cls.configs[0]:
            cfg += "# auto-generated by MyService (sample.py)\n"
            for iface in node.get_ifaces():
                cfg += f'echo "Node {node.name} has interface {iface.name}"\n'
        elif filename == cls.configs[1]:
            cfg += "echo hello"
        return cfg

    @classmethod
    def get_startup(cls, node: CoreNode) -> Tuple[str, ...]:
        """
        Provides a way to dynamically generate the startup commands from the node a
        service will run. Defaults to the class definition and can be left out entirely
        if not needed.

        :param node: core node that the service is being ran on
        :return: tuple of startup commands to run
        """
        return cls.startup

    @classmethod
    def get_validate(cls, node: CoreNode) -> Tuple[str, ...]:
        """
        Provides a way to dynamically generate the validate commands from the node a
        service will run. Defaults to the class definition and can be left out entirely
        if not needed.

        :param node: core node that the service is being ran on
        :return: tuple of commands to validate service startup with
        """
        return cls.validate