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)
|
logging.debug("get emane model config: %s", request)
|
||||||
session = self.get_session(request.session_id, context)
|
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)
|
_id = get_emane_model_id(request.node_id, request.iface_id)
|
||||||
current_config = session.emane.get_model_config(_id, request.model)
|
current_config = session.emane.get_model_config(_id, request.model)
|
||||||
config = get_config_options(current_config, 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
|
self, request: core_pb2.SaveXmlRequest, context: ServicerContext
|
||||||
) -> core_pb2.SaveXmlResponse:
|
) -> 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 request: save xml request
|
||||||
:param context: context object
|
:param context: context object
|
||||||
|
|
|
@ -3,7 +3,13 @@ from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Set, Tuple
|
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):
|
class ConfigServiceValidationMode(Enum):
|
||||||
|
@ -87,6 +93,13 @@ class MessageType(Enum):
|
||||||
TTY = 64
|
TTY = 64
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceAction(Enum):
|
||||||
|
START = 0
|
||||||
|
STOP = 1
|
||||||
|
RESTART = 2
|
||||||
|
VALIDATE = 3
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ConfigService:
|
class ConfigService:
|
||||||
group: str
|
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
|
@dataclass
|
||||||
class ConfigServiceData:
|
class ConfigServiceData:
|
||||||
templates: Dict[str, str] = field(default_factory=dict)
|
templates: Dict[str, str] = field(default_factory=dict)
|
||||||
config: 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
|
@dataclass
|
||||||
class NodeServiceData:
|
class NodeServiceData:
|
||||||
executables: List[str]
|
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
|
@dataclass
|
||||||
class BridgeThroughput:
|
class BridgeThroughput:
|
||||||
node_id: int
|
node_id: int
|
||||||
|
@ -471,6 +561,30 @@ class Hook:
|
||||||
return core_pb2.Hook(state=self.state.value, file=self.file, data=self.data)
|
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
|
@dataclass
|
||||||
class Position:
|
class Position:
|
||||||
x: float
|
x: float
|
||||||
|
@ -660,3 +774,18 @@ class NodeEvent:
|
||||||
message_type=MessageType(proto.message_type),
|
message_type=MessageType(proto.message_type),
|
||||||
node=Node.from_proto(proto.node),
|
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(
|
def get_configs(
|
||||||
self, node_id: int = _default_node, config_type: str = _default_type
|
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.
|
Retrieve configurations for a node and configuration type.
|
||||||
|
|
||||||
|
|
|
@ -145,14 +145,17 @@ class EmaneManager(ModelManager):
|
||||||
key += iface.node_id
|
key += iface.node_id
|
||||||
# try retrieve interface specific configuration, avoid getting defaults
|
# try retrieve interface specific configuration, avoid getting defaults
|
||||||
config = self.get_configs(node_id=key, config_type=model_name)
|
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:
|
if not config:
|
||||||
config = self.get_configs(node_id=iface.node.id, config_type=model_name)
|
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:
|
if not config:
|
||||||
# with EMANE 0.9.2+, we need an extra NEM XML from
|
# with EMANE 0.9.2+, we need an extra NEM XML from
|
||||||
# model.buildnemxmlfiles(), so defaults are returned here
|
# model.buildnemxmlfiles(), so defaults are returned here
|
||||||
config = self.get_configs(node_id=emane_net.id, config_type=model_name)
|
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
|
return config
|
||||||
|
|
||||||
def config_reset(self, node_id: int = None) -> None:
|
def config_reset(self, node_id: int = None) -> None:
|
||||||
|
|
|
@ -545,7 +545,12 @@ class Session:
|
||||||
|
|
||||||
# ensure default emane configuration
|
# ensure default emane configuration
|
||||||
if isinstance(node, EmaneNet) and options.emane:
|
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:
|
if self.state == EventTypes.RUNTIME_STATE:
|
||||||
self.emane.add_node(node)
|
self.emane.add_node(node)
|
||||||
# set default wlan config if needed
|
# set default wlan config if needed
|
||||||
|
|
|
@ -937,8 +937,6 @@ class CoreClient:
|
||||||
def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
|
def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
if node.type != NodeType.EMANE:
|
|
||||||
continue
|
|
||||||
for key, config in node.emane_model_configs.items():
|
for key, config in node.emane_model_configs.items():
|
||||||
model, iface_id = key
|
model, iface_id = key
|
||||||
config = ConfigOption.to_dict(config)
|
config = ConfigOption.to_dict(config)
|
||||||
|
|
|
@ -282,9 +282,7 @@ class NodeConfigDialog(Dialog):
|
||||||
button.grid(row=0, column=1, sticky=tk.EW)
|
button.grid(row=0, column=1, sticky=tk.EW)
|
||||||
|
|
||||||
def click_emane_config(self, emane_model: str, iface_id: int) -> None:
|
def click_emane_config(self, emane_model: str, iface_id: int) -> None:
|
||||||
dialog = EmaneModelDialog(
|
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, iface_id)
|
||||||
self, self.app, self.canvas_node, emane_model, iface_id
|
|
||||||
)
|
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def click_icon(self) -> None:
|
def click_icon(self) -> None:
|
||||||
|
|
|
@ -91,10 +91,14 @@ def create_emane_config(session: "Session") -> etree.Element:
|
||||||
|
|
||||||
|
|
||||||
def create_emane_model_config(
|
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:
|
) -> etree.Element:
|
||||||
emane_element = etree.Element("emane_configuration")
|
emane_element = etree.Element("emane_configuration")
|
||||||
add_attribute(emane_element, "node", node_id)
|
add_attribute(emane_element, "node", node_id)
|
||||||
|
add_attribute(emane_element, "iface", iface_id)
|
||||||
add_attribute(emane_element, "model", model.name)
|
add_attribute(emane_element, "model", model.name)
|
||||||
|
|
||||||
mac_element = etree.SubElement(emane_element, "mac")
|
mac_element = etree.SubElement(emane_element, "mac")
|
||||||
|
@ -378,13 +382,19 @@ class CoreXmlWriter:
|
||||||
all_configs = self.session.emane.get_all_configs(node_id)
|
all_configs = self.session.emane.get_all_configs(node_id)
|
||||||
if not all_configs:
|
if not all_configs:
|
||||||
continue
|
continue
|
||||||
|
iface_id = None
|
||||||
|
if node_id >= 1000:
|
||||||
|
iface_id = node_id % 1000
|
||||||
|
node_id = node_id // 1000
|
||||||
for model_name in all_configs:
|
for model_name in all_configs:
|
||||||
config = all_configs[model_name]
|
config = all_configs[model_name]
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"writing emane config node(%s) model(%s)", node_id, model_name
|
"writing emane config node(%s) model(%s)", node_id, model_name
|
||||||
)
|
)
|
||||||
model = self.session.emane.models[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)
|
emane_configurations.append(emane_configuration)
|
||||||
if emane_configurations.getchildren():
|
if emane_configurations.getchildren():
|
||||||
self.scenario.append(emane_configurations)
|
self.scenario.append(emane_configurations)
|
||||||
|
@ -588,9 +598,9 @@ class CoreXmlReader:
|
||||||
self.read_mobility_configs()
|
self.read_mobility_configs()
|
||||||
self.read_emane_global_config()
|
self.read_emane_global_config()
|
||||||
self.read_nodes()
|
self.read_nodes()
|
||||||
|
self.read_links()
|
||||||
self.read_emane_configs()
|
self.read_emane_configs()
|
||||||
self.read_configservice_configs()
|
self.read_configservice_configs()
|
||||||
self.read_links()
|
|
||||||
|
|
||||||
def read_default_services(self) -> None:
|
def read_default_services(self) -> None:
|
||||||
default_services = self.scenario.find("default_services")
|
default_services = self.scenario.find("default_services")
|
||||||
|
@ -748,6 +758,7 @@ class CoreXmlReader:
|
||||||
|
|
||||||
for emane_configuration in emane_configurations.iterchildren():
|
for emane_configuration in emane_configurations.iterchildren():
|
||||||
node_id = get_int(emane_configuration, "node")
|
node_id = get_int(emane_configuration, "node")
|
||||||
|
iface_id = get_int(emane_configuration, "iface")
|
||||||
model_name = emane_configuration.get("model")
|
model_name = emane_configuration.get("model")
|
||||||
configs = {}
|
configs = {}
|
||||||
|
|
||||||
|
@ -755,12 +766,13 @@ class CoreXmlReader:
|
||||||
node = self.session.nodes.get(node_id)
|
node = self.session.nodes.get(node_id)
|
||||||
if not node:
|
if not node:
|
||||||
raise CoreXmlError(f"node for emane config doesn't exist: {node_id}")
|
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)
|
model = self.session.emane.models.get(model_name)
|
||||||
if not model:
|
if not model:
|
||||||
raise CoreXmlError(f"invalid emane model: {model_name}")
|
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
|
# read and set emane model configuration
|
||||||
mac_configuration = emane_configuration.find("mac")
|
mac_configuration = emane_configuration.find("mac")
|
||||||
|
@ -784,7 +796,10 @@ class CoreXmlReader:
|
||||||
logging.info(
|
logging.info(
|
||||||
"reading emane configuration node(%s) model(%s)", node_id, model_name
|
"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:
|
def read_mobility_configs(self) -> None:
|
||||||
mobility_configurations = self.scenario.find("mobility_configurations")
|
mobility_configurations = self.scenario.find("mobility_configurations")
|
||||||
|
@ -869,6 +884,9 @@ class CoreXmlReader:
|
||||||
icon = network_element.get("icon")
|
icon = network_element.get("icon")
|
||||||
server = network_element.get("server")
|
server = network_element.get("server")
|
||||||
options = NodeOptions(name=name, icon=icon, server=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")
|
position_element = network_element.find("position")
|
||||||
if position_element is not None:
|
if position_element is not None:
|
||||||
|
|
Loading…
Reference in a new issue