From 6086d1229bbced66f31dcbcfd1a2a14673c5d1c8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 31 Mar 2021 11:13:40 -0700 Subject: [PATCH] daemon: updated config.py to use dataclasses for config classes, updated naming and referencing. updated configurable options to self validate default values align with the config type. updated the example emane model to better align with the current state of things --- daemon/core/config.py | 82 +++++++++---------- .../securityservices/services.py | 24 +++--- daemon/core/configservices/simpleservice.py | 8 +- daemon/core/emane/bypass.py | 4 +- daemon/core/emane/emanemanager.py | 24 +++--- daemon/core/emane/emanemanifest.py | 4 +- daemon/core/emane/tdma.py | 4 +- daemon/core/emulator/sessionconfig.py | 32 ++++---- daemon/core/errors.py | 8 ++ daemon/core/location/mobility.py | 52 ++++++------ daemon/examples/myemane/examplemodel.py | 26 +++++- daemon/tests/test_conf.py | 4 +- daemon/tests/test_config_services.py | 8 +- docs/emane.md | 24 +++++- 14 files changed, 171 insertions(+), 133 deletions(-) diff --git a/daemon/core/config.py b/daemon/core/config.py index 222abf01..1fe32adc 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -4,10 +4,12 @@ Common support for configurable CORE objects. import logging from collections import OrderedDict -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Type, Union from core.emane.nodes import EmaneNet from core.emulator.enumerations import ConfigDataTypes +from core.errors import CoreConfigError from core.nodes.network import WlanNode if TYPE_CHECKING: @@ -15,62 +17,56 @@ if TYPE_CHECKING: WirelessModelType = Type[WirelessModel] +_BOOL_OPTIONS: Set[str] = {"0", "1"} + +@dataclass class ConfigGroup: """ Defines configuration group tabs used for display by ConfigurationOptions. """ - def __init__(self, name: str, start: int, stop: int) -> None: - """ - Creates a ConfigGroup object. - - :param name: configuration group display name - :param start: configurations start index for this group - :param stop: configurations stop index for this group - """ - self.name: str = name - self.start: int = start - self.stop: int = stop + name: str + start: int + stop: int +@dataclass class Configuration: """ Represents a configuration options. """ - def __init__( - self, - _id: str, - _type: ConfigDataTypes, - label: str = None, - default: str = "", - options: List[str] = None, - ) -> None: - """ - Creates a Configuration object. + id: str + type: ConfigDataTypes + label: str = None + default: str = "" + options: List[str] = field(default_factory=list) - :param _id: unique name for configuration - :param _type: configuration data type - :param label: configuration label for display - :param default: default value for configuration - :param options: list options if this is a configuration with a combobox - """ - self.id: str = _id - self.type: ConfigDataTypes = _type - self.default: str = default - if not options: - options = [] - self.options: List[str] = options - if not label: - label = _id - self.label: str = label - - def __str__(self): - return ( - f"{self.__class__.__name__}(id={self.id}, type={self.type}, " - f"default={self.default}, options={self.options})" - ) + def __post_init__(self) -> None: + self.label = self.label if self.label else self.id + if self.type == ConfigDataTypes.BOOL: + if self.default and self.default not in _BOOL_OPTIONS: + raise CoreConfigError( + f"{self.id} bool value must be one of: {_BOOL_OPTIONS}: " + f"{self.default}" + ) + elif self.type == ConfigDataTypes.FLOAT: + if self.default: + try: + float(self.default) + except ValueError: + raise CoreConfigError( + f"{self.id} is not a valid float: {self.default}" + ) + elif self.type != ConfigDataTypes.STRING: + if self.default: + try: + int(self.default) + except ValueError: + raise CoreConfigError( + f"{self.id} is not a valid int: {self.default}" + ) class ConfigurableOptions: diff --git a/daemon/core/configservices/securityservices/services.py b/daemon/core/configservices/securityservices/services.py index c656f5ca..9da27010 100644 --- a/daemon/core/configservices/securityservices/services.py +++ b/daemon/core/configservices/securityservices/services.py @@ -20,20 +20,20 @@ class VpnClient(ConfigService): validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING default_configs: List[Configuration] = [ Configuration( - _id="keydir", - _type=ConfigDataTypes.STRING, + id="keydir", + type=ConfigDataTypes.STRING, label="Key Dir", default="/etc/core/keys", ), Configuration( - _id="keyname", - _type=ConfigDataTypes.STRING, + id="keyname", + type=ConfigDataTypes.STRING, label="Key Name", default="client1", ), Configuration( - _id="server", - _type=ConfigDataTypes.STRING, + id="server", + type=ConfigDataTypes.STRING, label="Server", default="10.0.2.10", ), @@ -54,20 +54,20 @@ class VpnServer(ConfigService): validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING default_configs: List[Configuration] = [ Configuration( - _id="keydir", - _type=ConfigDataTypes.STRING, + id="keydir", + type=ConfigDataTypes.STRING, label="Key Dir", default="/etc/core/keys", ), Configuration( - _id="keyname", - _type=ConfigDataTypes.STRING, + id="keyname", + type=ConfigDataTypes.STRING, label="Key Name", default="server", ), Configuration( - _id="subnet", - _type=ConfigDataTypes.STRING, + id="subnet", + type=ConfigDataTypes.STRING, label="Subnet", default="10.0.200.0", ), diff --git a/daemon/core/configservices/simpleservice.py b/daemon/core/configservices/simpleservice.py index c2e7242f..4370977d 100644 --- a/daemon/core/configservices/simpleservice.py +++ b/daemon/core/configservices/simpleservice.py @@ -17,11 +17,11 @@ class SimpleService(ConfigService): shutdown: List[str] = [] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING default_configs: List[Configuration] = [ - Configuration(_id="value1", _type=ConfigDataTypes.STRING, label="Text"), - Configuration(_id="value2", _type=ConfigDataTypes.BOOL, label="Boolean"), + Configuration(id="value1", type=ConfigDataTypes.STRING, label="Text"), + Configuration(id="value2", type=ConfigDataTypes.BOOL, label="Boolean"), Configuration( - _id="value3", - _type=ConfigDataTypes.STRING, + id="value3", + type=ConfigDataTypes.STRING, label="Multiple Choice", options=["value1", "value2", "value3"], ), diff --git a/daemon/core/emane/bypass.py b/daemon/core/emane/bypass.py index 8aabc3f9..aebdde21 100644 --- a/daemon/core/emane/bypass.py +++ b/daemon/core/emane/bypass.py @@ -18,8 +18,8 @@ class EmaneBypassModel(emanemodel.EmaneModel): mac_library: str = "bypassmaclayer" mac_config: List[Configuration] = [ Configuration( - _id="none", - _type=ConfigDataTypes.BOOL, + id="none", + type=ConfigDataTypes.BOOL, default="0", label="There are no parameters for the bypass model.", ) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 5d8e0c07..2eb28c28 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -825,38 +825,38 @@ class EmaneGlobalModel: self.session: "Session" = session self.core_config: List[Configuration] = [ Configuration( - _id="platform_id_start", - _type=ConfigDataTypes.INT32, + id="platform_id_start", + type=ConfigDataTypes.INT32, default="1", label="Starting Platform ID", ), Configuration( - _id="nem_id_start", - _type=ConfigDataTypes.INT32, + id="nem_id_start", + type=ConfigDataTypes.INT32, default="1", label="Starting NEM ID", ), Configuration( - _id="link_enabled", - _type=ConfigDataTypes.BOOL, + id="link_enabled", + type=ConfigDataTypes.BOOL, default="1", label="Enable Links?", ), Configuration( - _id="loss_threshold", - _type=ConfigDataTypes.INT32, + id="loss_threshold", + type=ConfigDataTypes.INT32, default="30", label="Link Loss Threshold (%)", ), Configuration( - _id="link_interval", - _type=ConfigDataTypes.INT32, + id="link_interval", + type=ConfigDataTypes.INT32, default="1", label="Link Check Interval (sec)", ), Configuration( - _id="link_timeout", - _type=ConfigDataTypes.INT32, + id="link_timeout", + type=ConfigDataTypes.INT32, default="4", label="Link Timeout (sec)", ), diff --git a/daemon/core/emane/emanemanifest.py b/daemon/core/emane/emanemanifest.py index 8e09d040..6ada2da7 100644 --- a/daemon/core/emane/emanemanifest.py +++ b/daemon/core/emane/emanemanifest.py @@ -118,8 +118,8 @@ def parse(manifest_path: Path, defaults: Dict[str, str]) -> List[Configuration]: config_descriptions = f"{config_descriptions} file" configuration = Configuration( - _id=config_name, - _type=config_type_value, + id=config_name, + type=config_type_value, default=config_default, options=possible, label=config_descriptions, diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index 1ddb14ad..35ea55b0 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -35,8 +35,8 @@ class EmaneTdmaModel(emanemodel.EmaneModel): ) super().load(emane_prefix) config_item = Configuration( - _id=cls.schedule_name, - _type=ConfigDataTypes.STRING, + id=cls.schedule_name, + type=ConfigDataTypes.STRING, default=str(cls.default_schedule), label="TDMA schedule file (core)", ) diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index 9b22bcc7..c4099fcd 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -13,51 +13,51 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): name: str = "session" options: List[Configuration] = [ Configuration( - _id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network" + id="controlnet", type=ConfigDataTypes.STRING, label="Control Network" ), Configuration( - _id="controlnet0", _type=ConfigDataTypes.STRING, label="Control Network 0" + id="controlnet0", type=ConfigDataTypes.STRING, label="Control Network 0" ), Configuration( - _id="controlnet1", _type=ConfigDataTypes.STRING, label="Control Network 1" + id="controlnet1", type=ConfigDataTypes.STRING, label="Control Network 1" ), Configuration( - _id="controlnet2", _type=ConfigDataTypes.STRING, label="Control Network 2" + id="controlnet2", type=ConfigDataTypes.STRING, label="Control Network 2" ), Configuration( - _id="controlnet3", _type=ConfigDataTypes.STRING, label="Control Network 3" + id="controlnet3", type=ConfigDataTypes.STRING, label="Control Network 3" ), Configuration( - _id="controlnet_updown_script", - _type=ConfigDataTypes.STRING, + id="controlnet_updown_script", + type=ConfigDataTypes.STRING, label="Control Network Script", ), Configuration( - _id="enablerj45", - _type=ConfigDataTypes.BOOL, + id="enablerj45", + type=ConfigDataTypes.BOOL, default="1", label="Enable RJ45s", ), Configuration( - _id="preservedir", - _type=ConfigDataTypes.BOOL, + id="preservedir", + type=ConfigDataTypes.BOOL, default="0", label="Preserve session dir", ), Configuration( - _id="enablesdt", - _type=ConfigDataTypes.BOOL, + id="enablesdt", + type=ConfigDataTypes.BOOL, default="0", label="Enable SDT3D output", ), Configuration( - _id="sdturl", - _type=ConfigDataTypes.STRING, + id="sdturl", + type=ConfigDataTypes.STRING, default=Sdt.DEFAULT_SDT_URL, label="SDT3D URL", ), Configuration( - _id="ovs", _type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" + id="ovs", type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" ), ] config_type: RegisterTlvs = RegisterTlvs.UTILITY diff --git a/daemon/core/errors.py b/daemon/core/errors.py index a75bd536..20ffc3a9 100644 --- a/daemon/core/errors.py +++ b/daemon/core/errors.py @@ -46,3 +46,11 @@ class CoreServiceBootError(Exception): """ pass + + +class CoreConfigError(Exception): + """ + Used when there is an error defining a configurable option. + """ + + pass diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 6acd6be7..8861d7f6 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -236,35 +236,35 @@ class BasicRangeModel(WirelessModel): name: str = "basic_range" options: List[Configuration] = [ Configuration( - _id="range", - _type=ConfigDataTypes.UINT32, + id="range", + type=ConfigDataTypes.UINT32, default="275", label="wireless range (pixels)", ), Configuration( - _id="bandwidth", - _type=ConfigDataTypes.UINT64, + id="bandwidth", + type=ConfigDataTypes.UINT64, default="54000000", label="bandwidth (bps)", ), Configuration( - _id="jitter", - _type=ConfigDataTypes.UINT64, + id="jitter", + type=ConfigDataTypes.UINT64, default="0", label="transmission jitter (usec)", ), Configuration( - _id="delay", - _type=ConfigDataTypes.UINT64, + id="delay", + type=ConfigDataTypes.UINT64, default="5000", label="transmission delay (usec)", ), Configuration( - _id="error", _type=ConfigDataTypes.STRING, default="0", label="loss (%)" + id="error", type=ConfigDataTypes.STRING, default="0", label="loss (%)" ), Configuration( - _id="promiscuous", - _type=ConfigDataTypes.BOOL, + id="promiscuous", + type=ConfigDataTypes.BOOL, default="0", label="promiscuous mode", ), @@ -868,40 +868,38 @@ class Ns2ScriptedMobility(WayPointMobility): name: str = "ns2script" options: List[Configuration] = [ Configuration( - _id="file", _type=ConfigDataTypes.STRING, label="mobility script file" + id="file", type=ConfigDataTypes.STRING, label="mobility script file" ), Configuration( - _id="refresh_ms", - _type=ConfigDataTypes.UINT32, + id="refresh_ms", + type=ConfigDataTypes.UINT32, default="50", label="refresh time (ms)", ), + Configuration(id="loop", type=ConfigDataTypes.BOOL, default="1", label="loop"), Configuration( - _id="loop", _type=ConfigDataTypes.BOOL, default="1", label="loop" - ), - Configuration( - _id="autostart", - _type=ConfigDataTypes.STRING, + id="autostart", + type=ConfigDataTypes.STRING, label="auto-start seconds (0.0 for runtime)", ), Configuration( - _id="map", - _type=ConfigDataTypes.STRING, + id="map", + type=ConfigDataTypes.STRING, label="node mapping (optional, e.g. 0:1,1:2,2:3)", ), Configuration( - _id="script_start", - _type=ConfigDataTypes.STRING, + id="script_start", + type=ConfigDataTypes.STRING, label="script file to run upon start", ), Configuration( - _id="script_pause", - _type=ConfigDataTypes.STRING, + id="script_pause", + type=ConfigDataTypes.STRING, label="script file to run upon pause", ), Configuration( - _id="script_stop", - _type=ConfigDataTypes.STRING, + id="script_stop", + type=ConfigDataTypes.STRING, label="script file to run upon stop", ), ] diff --git a/daemon/examples/myemane/examplemodel.py b/daemon/examples/myemane/examplemodel.py index b9e6e148..c33ac166 100644 --- a/daemon/examples/myemane/examplemodel.py +++ b/daemon/examples/myemane/examplemodel.py @@ -1,6 +1,7 @@ """ Example custom emane model. """ +from pathlib import Path from typing import Dict, List, Optional, Set from core.config import Configuration @@ -39,17 +40,34 @@ class ExampleModel(emanemodel.EmaneModel): name: str = "emane_example" mac_library: str = "rfpipemaclayer" - mac_xml: str = "/usr/share/emane/manifest/rfpipemaclayer.xml" + mac_xml: str = "rfpipemaclayer.xml" mac_defaults: Dict[str, str] = { "pcrcurveuri": "/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml" } - mac_config: List[Configuration] = emanemanifest.parse(mac_xml, mac_defaults) + mac_config: List[Configuration] = [] phy_library: Optional[str] = None - phy_xml: str = "/usr/share/emane/manifest/emanephy.xml" + phy_xml: str = "emanephy.xml" phy_defaults: Dict[str, str] = { "subid": "1", "propagationmodel": "2ray", "noisemode": "none", } - phy_config: List[Configuration] = emanemanifest.parse(phy_xml, phy_defaults) + phy_config: List[Configuration] = [] config_ignore: Set[str] = set() + + @classmethod + def load(cls, emane_prefix: Path) -> None: + """ + Called after being loaded within the EmaneManager. Provides configured + emane_prefix for parsing xml files. + + :param emane_prefix: configured emane prefix path + :return: nothing + """ + manifest_path = "share/emane/manifest" + # load mac configuration + mac_xml_path = emane_prefix / manifest_path / cls.mac_xml + cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults) + # load phy configuration + phy_xml_path = emane_prefix / manifest_path / cls.phy_xml + cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults) diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index e90acfbd..994a09f3 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -17,8 +17,8 @@ class TestConfigurableOptions(ConfigurableOptions): name1 = "value1" name2 = "value2" options = [ - Configuration(_id=name1, _type=ConfigDataTypes.STRING, label=name1), - Configuration(_id=name2, _type=ConfigDataTypes.STRING, label=name2), + Configuration(id=name1, type=ConfigDataTypes.STRING, label=name1), + Configuration(id=name2, type=ConfigDataTypes.STRING, label=name2), ] diff --git a/daemon/tests/test_config_services.py b/daemon/tests/test_config_services.py index 7efde087..432f2089 100644 --- a/daemon/tests/test_config_services.py +++ b/daemon/tests/test_config_services.py @@ -27,11 +27,11 @@ class MyService(ConfigService): shutdown = [f"pkill {files[0]}"] validation_mode = ConfigServiceMode.BLOCKING default_configs = [ - Configuration(_id="value1", _type=ConfigDataTypes.STRING, label="Text"), - Configuration(_id="value2", _type=ConfigDataTypes.BOOL, label="Boolean"), + Configuration(id="value1", type=ConfigDataTypes.STRING, label="Text"), + Configuration(id="value2", type=ConfigDataTypes.BOOL, label="Boolean"), Configuration( - _id="value3", - _type=ConfigDataTypes.STRING, + id="value3", + type=ConfigDataTypes.STRING, label="Multiple Choice", options=["value1", "value2", "value3"], ), diff --git a/docs/emane.md b/docs/emane.md index f589f834..9e2e5b0b 100644 --- a/docs/emane.md +++ b/docs/emane.md @@ -120,7 +120,8 @@ Here is an example model with documentation describing functionality: """ Example custom emane model. """ -from typing import Dict, List, Optional, Set +from pathlib import Path +from typing import Dict, Optional, Set, List from core.config import Configuration from core.emane import emanemanifest, emanemodel @@ -162,14 +163,31 @@ class ExampleModel(emanemodel.EmaneModel): mac_defaults: Dict[str, str] = { "pcrcurveuri": "/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml" } - mac_config: List[Configuration] = emanemanifest.parse(mac_xml, mac_defaults) + mac_config: List[Configuration] = [] phy_library: Optional[str] = None phy_xml: str = "/usr/share/emane/manifest/emanephy.xml" phy_defaults: Dict[str, str] = { "subid": "1", "propagationmodel": "2ray", "noisemode": "none" } - phy_config: List[Configuration] = emanemanifest.parse(phy_xml, phy_defaults) + phy_config: List[Configuration] = [] config_ignore: Set[str] = set() + + @classmethod + def load(cls, emane_prefix: Path) -> None: + """ + Called after being loaded within the EmaneManager. Provides configured + emane_prefix for parsing xml files. + + :param emane_prefix: configured emane prefix path + :return: nothing + """ + manifest_path = "share/emane/manifest" + # load mac configuration + mac_xml_path = emane_prefix / manifest_path / cls.mac_xml + cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults) + # load phy configuration + phy_xml_path = emane_prefix / manifest_path / cls.phy_xml + cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults) ``` ## Single PC with EMANE