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

This commit is contained in:
Blake Harnden 2021-03-31 11:13:40 -07:00
parent bb3590fbde
commit 6086d1229b
14 changed files with 171 additions and 133 deletions

View file

@ -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,61 +17,55 @@ 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}"
)

View file

@ -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",
),

View file

@ -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"],
),

View file

@ -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.",
)

View file

@ -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)",
),

View file

@ -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,

View file

@ -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)",
)

View file

@ -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

View file

@ -46,3 +46,11 @@ class CoreServiceBootError(Exception):
"""
pass
class CoreConfigError(Exception):
"""
Used when there is an error defining a configurable option.
"""
pass

View file

@ -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",
),
]

View file

@ -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)

View file

@ -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),
]

View file

@ -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"],
),

View file

@ -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