initial code for a wrapped grpc client, fix for pygui node emane config, fix for xml reading emane configs specific to nodes/interfaces, fix for adding emane nodes and setting the emane model properly
This commit is contained in:
parent
b0bac1d319
commit
570ad9522c
9 changed files with 1583 additions and 19 deletions
1411
daemon/core/api/grpc/clientw.py
Normal file
1411
daemon/core/api/grpc/clientw.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1438,7 +1438,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("get emane model config: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
model = session.emane.models[request.model]
|
||||
model = session.emane.models.get(request.model)
|
||||
if not model:
|
||||
raise CoreError(f"invalid emane model: {request.model}")
|
||||
_id = get_emane_model_id(request.node_id, request.iface_id)
|
||||
current_config = session.emane.get_model_config(_id, request.model)
|
||||
config = get_config_options(current_config, model)
|
||||
|
@ -1483,7 +1485,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
self, request: core_pb2.SaveXmlRequest, context: ServicerContext
|
||||
) -> core_pb2.SaveXmlResponse:
|
||||
"""
|
||||
Export the session nto the EmulationScript XML format
|
||||
Export the session into the EmulationScript XML format
|
||||
|
||||
:param request: save xml request
|
||||
:param context: context object
|
||||
|
|
|
@ -3,7 +3,13 @@ from enum import Enum
|
|||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
|
||||
from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2
|
||||
from core.api.grpc import (
|
||||
common_pb2,
|
||||
configservices_pb2,
|
||||
core_pb2,
|
||||
emane_pb2,
|
||||
services_pb2,
|
||||
)
|
||||
|
||||
|
||||
class ConfigServiceValidationMode(Enum):
|
||||
|
@ -87,6 +93,13 @@ class MessageType(Enum):
|
|||
TTY = 64
|
||||
|
||||
|
||||
class ServiceAction(Enum):
|
||||
START = 0
|
||||
STOP = 1
|
||||
RESTART = 2
|
||||
VALIDATE = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigService:
|
||||
group: str
|
||||
|
@ -120,12 +133,67 @@ class ConfigService:
|
|||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigServiceConfig:
|
||||
node_id: int
|
||||
name: str
|
||||
templates: Dict[str, str]
|
||||
config: Dict[str, str]
|
||||
|
||||
@classmethod
|
||||
def from_proto(
|
||||
cls, proto: configservices_pb2.ConfigServiceConfig
|
||||
) -> "ConfigServiceConfig":
|
||||
return ConfigServiceConfig(
|
||||
node_id=proto.node_id,
|
||||
name=proto.name,
|
||||
templates=dict(proto.templates),
|
||||
config=dict(proto.config),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigServiceData:
|
||||
templates: Dict[str, str] = field(default_factory=dict)
|
||||
config: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigServiceDefaults:
|
||||
templates: Dict[str, str]
|
||||
config: Dict[str, "ConfigOption"]
|
||||
modes: List[str]
|
||||
|
||||
@classmethod
|
||||
def from_proto(
|
||||
cls, proto: configservices_pb2.GetConfigServicesResponse
|
||||
) -> "ConfigServiceDefaults":
|
||||
config = ConfigOption.from_dict(proto.config)
|
||||
return ConfigServiceDefaults(
|
||||
templates=dict(proto.templates), config=config, modes=list(proto.modes)
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Service:
|
||||
group: str
|
||||
name: str
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: services_pb2.Service) -> "Service":
|
||||
return Service(group=proto.group, name=proto.name)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceDefault:
|
||||
node_type: str
|
||||
services: List[str]
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: services_pb2.ServiceDefaults) -> "ServiceDefault":
|
||||
return ServiceDefault(node_type=proto.node_type, services=list(proto.services))
|
||||
|
||||
|
||||
@dataclass
|
||||
class NodeServiceData:
|
||||
executables: List[str]
|
||||
|
@ -155,6 +223,28 @@ class NodeServiceData:
|
|||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceConfig:
|
||||
node_id: int
|
||||
service: str
|
||||
files: List[str] = None
|
||||
directories: List[str] = None
|
||||
startup: List[str] = None
|
||||
validate: List[str] = None
|
||||
shutdown: List[str] = None
|
||||
|
||||
def to_proto(self) -> services_pb2.ServiceConfig:
|
||||
return services_pb2.ServiceConfig(
|
||||
node_id=self.node_id,
|
||||
service=self.service,
|
||||
files=self.files,
|
||||
directories=self.directories,
|
||||
startup=self.startup,
|
||||
validate=self.validate,
|
||||
shutdown=self.shutdown,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BridgeThroughput:
|
||||
node_id: int
|
||||
|
@ -471,6 +561,30 @@ class Hook:
|
|||
return core_pb2.Hook(state=self.state.value, file=self.file, data=self.data)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmaneModelConfig:
|
||||
node_id: int
|
||||
model: str
|
||||
iface_id: int = -1
|
||||
config: Dict[str, ConfigOption] = None
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: emane_pb2.GetEmaneModelConfig) -> "EmaneModelConfig":
|
||||
iface_id = proto.iface_id if proto.iface_id != -1 else None
|
||||
config = ConfigOption.from_dict(proto.config)
|
||||
return EmaneModelConfig(
|
||||
node_id=proto.node_id, iface_id=iface_id, model=proto.model, config=config
|
||||
)
|
||||
|
||||
def to_proto(self) -> emane_pb2.EmaneModelConfig:
|
||||
return emane_pb2.EmaneModelConfig(
|
||||
node_id=self.node_id,
|
||||
model=self.model,
|
||||
iface_id=self.iface_id,
|
||||
config=self.config,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Position:
|
||||
x: float
|
||||
|
@ -660,3 +774,18 @@ class NodeEvent:
|
|||
message_type=MessageType(proto.message_type),
|
||||
node=Node.from_proto(proto.node),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmaneEventChannel:
|
||||
group: str
|
||||
port: int
|
||||
device: str
|
||||
|
||||
@classmethod
|
||||
def from_proto(
|
||||
cls, proto: emane_pb2.GetEmaneEventChannelResponse
|
||||
) -> "EmaneEventChannel":
|
||||
return EmaneEventChannel(
|
||||
group=proto.group, port=proto.port, device=proto.device
|
||||
)
|
||||
|
|
|
@ -212,7 +212,7 @@ class ConfigurableManager:
|
|||
|
||||
def get_configs(
|
||||
self, node_id: int = _default_node, config_type: str = _default_type
|
||||
) -> Dict[str, str]:
|
||||
) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Retrieve configurations for a node and configuration type.
|
||||
|
||||
|
|
|
@ -145,14 +145,17 @@ class EmaneManager(ModelManager):
|
|||
key += iface.node_id
|
||||
# try retrieve interface specific configuration, avoid getting defaults
|
||||
config = self.get_configs(node_id=key, config_type=model_name)
|
||||
# otherwise retrieve the interfaces node configuration, avoid using defaults
|
||||
# attempt to retrieve node specific conifg, when iface config is not present
|
||||
if not config:
|
||||
config = self.get_configs(node_id=iface.node.id, config_type=model_name)
|
||||
# get non interface config, when none found
|
||||
# attempt to get emane net specific config, when node config is not present
|
||||
if not config:
|
||||
# with EMANE 0.9.2+, we need an extra NEM XML from
|
||||
# model.buildnemxmlfiles(), so defaults are returned here
|
||||
config = self.get_configs(node_id=emane_net.id, config_type=model_name)
|
||||
# return default config values, when a config is not present
|
||||
if not config:
|
||||
config = emane_net.model.default_values()
|
||||
return config
|
||||
|
||||
def config_reset(self, node_id: int = None) -> None:
|
||||
|
|
|
@ -545,7 +545,12 @@ class Session:
|
|||
|
||||
# ensure default emane configuration
|
||||
if isinstance(node, EmaneNet) and options.emane:
|
||||
self.emane.set_model_config(_id, options.emane)
|
||||
model = self.emane.models.get(options.emane)
|
||||
if not model:
|
||||
raise CoreError(
|
||||
f"node({node.name}) emane model({options.emane}) does not exist"
|
||||
)
|
||||
node.setmodel(model, {})
|
||||
if self.state == EventTypes.RUNTIME_STATE:
|
||||
self.emane.add_node(node)
|
||||
# set default wlan config if needed
|
||||
|
|
|
@ -937,8 +937,6 @@ class CoreClient:
|
|||
def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
|
||||
configs = []
|
||||
for node in self.session.nodes.values():
|
||||
if node.type != NodeType.EMANE:
|
||||
continue
|
||||
for key, config in node.emane_model_configs.items():
|
||||
model, iface_id = key
|
||||
config = ConfigOption.to_dict(config)
|
||||
|
|
|
@ -282,9 +282,7 @@ class NodeConfigDialog(Dialog):
|
|||
button.grid(row=0, column=1, sticky=tk.EW)
|
||||
|
||||
def click_emane_config(self, emane_model: str, iface_id: int) -> None:
|
||||
dialog = EmaneModelDialog(
|
||||
self, self.app, self.canvas_node, emane_model, iface_id
|
||||
)
|
||||
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, iface_id)
|
||||
dialog.show()
|
||||
|
||||
def click_icon(self) -> None:
|
||||
|
|
|
@ -91,10 +91,14 @@ def create_emane_config(session: "Session") -> etree.Element:
|
|||
|
||||
|
||||
def create_emane_model_config(
|
||||
node_id: int, model: "EmaneModelType", config: Dict[str, str]
|
||||
node_id: int,
|
||||
model: "EmaneModelType",
|
||||
config: Dict[str, str],
|
||||
iface_id: Optional[int],
|
||||
) -> etree.Element:
|
||||
emane_element = etree.Element("emane_configuration")
|
||||
add_attribute(emane_element, "node", node_id)
|
||||
add_attribute(emane_element, "iface", iface_id)
|
||||
add_attribute(emane_element, "model", model.name)
|
||||
|
||||
mac_element = etree.SubElement(emane_element, "mac")
|
||||
|
@ -378,13 +382,19 @@ class CoreXmlWriter:
|
|||
all_configs = self.session.emane.get_all_configs(node_id)
|
||||
if not all_configs:
|
||||
continue
|
||||
iface_id = None
|
||||
if node_id >= 1000:
|
||||
iface_id = node_id % 1000
|
||||
node_id = node_id // 1000
|
||||
for model_name in all_configs:
|
||||
config = all_configs[model_name]
|
||||
logging.debug(
|
||||
"writing emane config node(%s) model(%s)", node_id, model_name
|
||||
)
|
||||
model = self.session.emane.models[model_name]
|
||||
emane_configuration = create_emane_model_config(node_id, model, config)
|
||||
emane_configuration = create_emane_model_config(
|
||||
node_id, model, config, iface_id
|
||||
)
|
||||
emane_configurations.append(emane_configuration)
|
||||
if emane_configurations.getchildren():
|
||||
self.scenario.append(emane_configurations)
|
||||
|
@ -588,9 +598,9 @@ class CoreXmlReader:
|
|||
self.read_mobility_configs()
|
||||
self.read_emane_global_config()
|
||||
self.read_nodes()
|
||||
self.read_links()
|
||||
self.read_emane_configs()
|
||||
self.read_configservice_configs()
|
||||
self.read_links()
|
||||
|
||||
def read_default_services(self) -> None:
|
||||
default_services = self.scenario.find("default_services")
|
||||
|
@ -748,6 +758,7 @@ class CoreXmlReader:
|
|||
|
||||
for emane_configuration in emane_configurations.iterchildren():
|
||||
node_id = get_int(emane_configuration, "node")
|
||||
iface_id = get_int(emane_configuration, "iface")
|
||||
model_name = emane_configuration.get("model")
|
||||
configs = {}
|
||||
|
||||
|
@ -755,12 +766,13 @@ class CoreXmlReader:
|
|||
node = self.session.nodes.get(node_id)
|
||||
if not node:
|
||||
raise CoreXmlError(f"node for emane config doesn't exist: {node_id}")
|
||||
if not isinstance(node, EmaneNet):
|
||||
raise CoreXmlError(f"invalid node for emane config: {node.name}")
|
||||
model = self.session.emane.models.get(model_name)
|
||||
if not model:
|
||||
raise CoreXmlError(f"invalid emane model: {model_name}")
|
||||
node.setmodel(model, {})
|
||||
if iface_id is not None and iface_id not in node.ifaces:
|
||||
raise CoreXmlError(
|
||||
f"invalid interface id({iface_id}) for node({node.name})"
|
||||
)
|
||||
|
||||
# read and set emane model configuration
|
||||
mac_configuration = emane_configuration.find("mac")
|
||||
|
@ -784,7 +796,10 @@ class CoreXmlReader:
|
|||
logging.info(
|
||||
"reading emane configuration node(%s) model(%s)", node_id, model_name
|
||||
)
|
||||
self.session.emane.set_model_config(node_id, model_name, configs)
|
||||
key = node_id
|
||||
if iface_id is not None:
|
||||
key = node_id * 1000 + iface_id
|
||||
self.session.emane.set_model_config(key, model_name, configs)
|
||||
|
||||
def read_mobility_configs(self) -> None:
|
||||
mobility_configurations = self.scenario.find("mobility_configurations")
|
||||
|
@ -869,6 +884,9 @@ class CoreXmlReader:
|
|||
icon = network_element.get("icon")
|
||||
server = network_element.get("server")
|
||||
options = NodeOptions(name=name, icon=icon, server=server)
|
||||
if node_type == NodeTypes.EMANE:
|
||||
model = network_element.get("model")
|
||||
options.emane = model
|
||||
|
||||
position_element = network_element.find("position")
|
||||
if position_element is not None:
|
||||
|
|
Loading…
Reference in a new issue