Merge pull request #573 from coreemu/enhancement/emane-nem-process
EMANE process per nem
This commit is contained in:
commit
b508ad6406
26 changed files with 481 additions and 881 deletions
|
@ -28,10 +28,8 @@ from core.api.grpc.configservices_pb2 import (
|
|||
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest
|
||||
from core.api.grpc.emane_pb2 import (
|
||||
EmaneLinkRequest,
|
||||
GetEmaneConfigRequest,
|
||||
GetEmaneEventChannelRequest,
|
||||
GetEmaneModelConfigRequest,
|
||||
SetEmaneConfigRequest,
|
||||
SetEmaneModelConfigRequest,
|
||||
)
|
||||
from core.api.grpc.mobility_pb2 import (
|
||||
|
@ -253,7 +251,6 @@ class CoreGrpcClient:
|
|||
if asymmetric_links:
|
||||
asymmetric_links = [x.to_proto() for x in asymmetric_links]
|
||||
hooks = [x.to_proto() for x in session.hooks.values()]
|
||||
emane_config = {k: v.value for k, v in session.emane_config.items()}
|
||||
emane_model_configs = []
|
||||
mobility_configs = []
|
||||
wlan_configs = []
|
||||
|
@ -313,7 +310,6 @@ class CoreGrpcClient:
|
|||
links=links,
|
||||
location=session.location.to_proto(),
|
||||
hooks=hooks,
|
||||
emane_config=emane_config,
|
||||
emane_model_configs=emane_model_configs,
|
||||
wlan_configs=wlan_configs,
|
||||
mobility_configs=mobility_configs,
|
||||
|
@ -917,31 +913,6 @@ class CoreGrpcClient:
|
|||
response = self.stub.SetWlanConfig(request)
|
||||
return response.result
|
||||
|
||||
def get_emane_config(self, session_id: int) -> Dict[str, wrappers.ConfigOption]:
|
||||
"""
|
||||
Get session emane configuration.
|
||||
|
||||
:param session_id: session id
|
||||
:return: response with a list of configuration groups
|
||||
:raises grpc.RpcError: when session doesn't exist
|
||||
"""
|
||||
request = GetEmaneConfigRequest(session_id=session_id)
|
||||
response = self.stub.GetEmaneConfig(request)
|
||||
return wrappers.ConfigOption.from_dict(response.config)
|
||||
|
||||
def set_emane_config(self, session_id: int, config: Dict[str, str]) -> bool:
|
||||
"""
|
||||
Set session emane configuration.
|
||||
|
||||
:param session_id: session id
|
||||
:param config: emane configuration
|
||||
:return: True for success, False otherwise
|
||||
:raises grpc.RpcError: when session doesn't exist
|
||||
"""
|
||||
request = SetEmaneConfigRequest(session_id=session_id, config=config)
|
||||
response = self.stub.SetEmaneConfig(request)
|
||||
return response.result
|
||||
|
||||
def get_emane_model_config(
|
||||
self, session_id: int, node_id: int, model: str, iface_id: int = -1
|
||||
) -> Dict[str, wrappers.ConfigOption]:
|
||||
|
@ -1063,15 +1034,18 @@ class CoreGrpcClient:
|
|||
response = self.stub.GetNodeConfigService(request)
|
||||
return dict(response.config)
|
||||
|
||||
def get_emane_event_channel(self, session_id: int) -> wrappers.EmaneEventChannel:
|
||||
def get_emane_event_channel(
|
||||
self, session_id: int, nem_id: int
|
||||
) -> wrappers.EmaneEventChannel:
|
||||
"""
|
||||
Retrieves the current emane event channel being used for a session.
|
||||
|
||||
:param session_id: session to get emane event channel for
|
||||
:param nem_id: nem id for the desired event channel
|
||||
:return: emane event channel
|
||||
:raises grpc.RpcError: when session doesn't exist
|
||||
"""
|
||||
request = GetEmaneEventChannelRequest(session_id=session_id)
|
||||
request = GetEmaneEventChannelRequest(session_id=session_id, nem_id=nem_id)
|
||||
response = self.stub.GetEmaneEventChannel(request)
|
||||
return wrappers.EmaneEventChannel.from_proto(response)
|
||||
|
||||
|
|
|
@ -630,10 +630,6 @@ def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfi
|
|||
return configs
|
||||
|
||||
|
||||
def get_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]:
|
||||
return get_config_options(session.emane.config, session.emane.emane_config)
|
||||
|
||||
|
||||
def get_mobility_node(
|
||||
session: Session, node_id: int, context: ServicerContext
|
||||
) -> Union[WlanNode, EmaneNet]:
|
||||
|
@ -663,7 +659,6 @@ def convert_session(session: Session) -> wrappers.Session:
|
|||
x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale
|
||||
)
|
||||
hooks = get_hooks(session)
|
||||
emane_config = get_emane_config(session)
|
||||
emane_model_configs = get_emane_model_configs(session)
|
||||
wlan_configs = get_wlan_configs(session)
|
||||
mobility_configs = get_mobility_configs(session)
|
||||
|
@ -685,7 +680,6 @@ def convert_session(session: Session) -> wrappers.Session:
|
|||
default_services=default_services,
|
||||
location=location,
|
||||
hooks=hooks,
|
||||
emane_config=emane_config,
|
||||
emane_model_configs=emane_model_configs,
|
||||
wlan_configs=wlan_configs,
|
||||
service_configs=service_configs,
|
||||
|
|
|
@ -33,14 +33,10 @@ from core.api.grpc.emane_pb2 import (
|
|||
EmaneLinkResponse,
|
||||
EmanePathlossesRequest,
|
||||
EmanePathlossesResponse,
|
||||
GetEmaneConfigRequest,
|
||||
GetEmaneConfigResponse,
|
||||
GetEmaneEventChannelRequest,
|
||||
GetEmaneEventChannelResponse,
|
||||
GetEmaneModelConfigRequest,
|
||||
GetEmaneModelConfigResponse,
|
||||
SetEmaneConfigRequest,
|
||||
SetEmaneConfigResponse,
|
||||
SetEmaneModelConfigRequest,
|
||||
SetEmaneModelConfigResponse,
|
||||
)
|
||||
|
@ -266,7 +262,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
|
||||
|
||||
# emane configs
|
||||
session.emane.config.update(request.emane_config)
|
||||
for config in request.emane_model_configs:
|
||||
_id = utils.iface_config_id(config.node_id, config.iface_id)
|
||||
session.emane.set_config(_id, config.model, config.config)
|
||||
|
@ -1045,36 +1040,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
node.updatemodel(config)
|
||||
return SetWlanConfigResponse(result=True)
|
||||
|
||||
def GetEmaneConfig(
|
||||
self, request: GetEmaneConfigRequest, context: ServicerContext
|
||||
) -> GetEmaneConfigResponse:
|
||||
"""
|
||||
Retrieve EMANE configuration of a session
|
||||
|
||||
:param request: get-EMANE-configuration request
|
||||
:param context: context object
|
||||
:return: get-EMANE-configuration response
|
||||
"""
|
||||
logger.debug("get emane config: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
config = grpcutils.get_emane_config(session)
|
||||
return GetEmaneConfigResponse(config=config)
|
||||
|
||||
def SetEmaneConfig(
|
||||
self, request: SetEmaneConfigRequest, context: ServicerContext
|
||||
) -> SetEmaneConfigResponse:
|
||||
"""
|
||||
Set EMANE configuration of a session
|
||||
|
||||
:param request: set-EMANE-configuration request
|
||||
:param context: context object
|
||||
:return: set-EMANE-configuration response
|
||||
"""
|
||||
logger.debug("set emane config: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
session.emane.config.update(request.config)
|
||||
return SetEmaneConfigResponse(result=True)
|
||||
|
||||
def GetEmaneModelConfig(
|
||||
self, request: GetEmaneModelConfigRequest, context: ServicerContext
|
||||
) -> GetEmaneModelConfigResponse:
|
||||
|
@ -1276,12 +1241,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
self, request: GetEmaneEventChannelRequest, context: ServicerContext
|
||||
) -> GetEmaneEventChannelResponse:
|
||||
session = self.get_session(request.session_id, context)
|
||||
group = None
|
||||
port = None
|
||||
device = None
|
||||
if session.emane.eventchannel:
|
||||
group, port, device = session.emane.eventchannel
|
||||
return GetEmaneEventChannelResponse(group=group, port=port, device=device)
|
||||
service = session.emane.nem_service.get(request.nem_id)
|
||||
if not service:
|
||||
context.abort(grpc.StatusCode.NOT_FOUND, f"unknown nem id {request.nem_id}")
|
||||
return GetEmaneEventChannelResponse(
|
||||
group=service.group, port=service.port, device=service.device
|
||||
)
|
||||
|
||||
def ExecuteScript(self, request, context):
|
||||
existing_sessions = set(self.coreemu.sessions.keys())
|
||||
|
|
|
@ -783,7 +783,6 @@ class Session:
|
|||
x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0
|
||||
)
|
||||
hooks: Dict[str, Hook] = field(default_factory=dict)
|
||||
emane_config: Dict[str, ConfigOption] = field(default_factory=dict)
|
||||
metadata: Dict[str, str] = field(default_factory=dict)
|
||||
file: Path = None
|
||||
options: Dict[str, ConfigOption] = field(default_factory=dict)
|
||||
|
@ -836,7 +835,6 @@ class Session:
|
|||
default_services=default_services,
|
||||
location=SessionLocation.from_proto(proto.location),
|
||||
hooks=hooks,
|
||||
emane_config=ConfigOption.from_dict(proto.emane_config),
|
||||
metadata=dict(proto.metadata),
|
||||
file=file_path,
|
||||
options=options,
|
||||
|
@ -889,11 +887,6 @@ class Session:
|
|||
self.links.append(link)
|
||||
return link
|
||||
|
||||
def set_emane(self, config: Dict[str, str]) -> None:
|
||||
for key, value in config.items():
|
||||
option = ConfigOption(name=key, value=value)
|
||||
self.emane_config[key] = option
|
||||
|
||||
def set_options(self, config: Dict[str, str]) -> None:
|
||||
for key, value in config.items():
|
||||
option = ConfigOption(name=key, value=value)
|
||||
|
|
|
@ -1046,8 +1046,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
self.handle_config_mobility(message_type, config_data)
|
||||
elif config_data.object in self.session.mobility.models:
|
||||
replies = self.handle_config_mobility_models(message_type, config_data)
|
||||
elif config_data.object == self.session.emane.name:
|
||||
replies = self.handle_config_emane(message_type, config_data)
|
||||
elif config_data.object in EmaneModelManager.models:
|
||||
replies = self.handle_config_emane_models(message_type, config_data)
|
||||
else:
|
||||
|
@ -1379,36 +1377,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
|
||||
return replies
|
||||
|
||||
def handle_config_emane(self, message_type, config_data):
|
||||
replies = []
|
||||
node_id = config_data.node
|
||||
object_name = config_data.object
|
||||
iface_id = config_data.iface_id
|
||||
values_str = config_data.data_values
|
||||
|
||||
node_id = utils.iface_config_id(node_id, iface_id)
|
||||
logger.debug(
|
||||
"received configure message for %s nodenum: %s", object_name, node_id
|
||||
)
|
||||
if message_type == ConfigFlags.REQUEST:
|
||||
logger.info("replying to configure request for %s model", object_name)
|
||||
typeflags = ConfigFlags.NONE.value
|
||||
config = self.session.emane.config
|
||||
config_response = ConfigShim.config_data(
|
||||
0, node_id, typeflags, self.session.emane.emane_config, config
|
||||
)
|
||||
replies.append(config_response)
|
||||
elif message_type != ConfigFlags.RESET:
|
||||
if not object_name:
|
||||
logger.info("no configuration object for node %s", node_id)
|
||||
return []
|
||||
|
||||
if values_str:
|
||||
config = ConfigShim.str_to_dict(values_str)
|
||||
self.session.emane.config = config
|
||||
|
||||
return replies
|
||||
|
||||
def handle_config_emane_models(self, message_type, config_data):
|
||||
replies = []
|
||||
node_id = config_data.node
|
||||
|
@ -1851,14 +1819,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
)
|
||||
self.session.broadcast_config(config_data)
|
||||
|
||||
# send global emane config
|
||||
config = self.session.emane.config
|
||||
logger.debug("global emane config: values(%s)", config)
|
||||
config_data = ConfigShim.config_data(
|
||||
0, None, ConfigFlags.UPDATE.value, self.session.emane.emane_config, config
|
||||
)
|
||||
self.session.broadcast_config(config_data)
|
||||
|
||||
# send emane model configs
|
||||
for node_id, model_configs in self.session.emane.node_configs.items():
|
||||
for model_name, config in model_configs.items():
|
||||
|
|
|
@ -227,12 +227,6 @@ class Ospfv3mdr(Ospfv3):
|
|||
|
||||
name: str = "OSPFv3MDR"
|
||||
|
||||
def data(self) -> Dict[str, Any]:
|
||||
for iface in self.node.get_ifaces():
|
||||
is_wireless = isinstance(iface.net, (WlanNode, EmaneNet))
|
||||
logger.info("MDR wireless: %s", is_wireless)
|
||||
return dict()
|
||||
|
||||
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
||||
config = super().quagga_iface_config(iface)
|
||||
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
||||
|
|
|
@ -1,32 +1,22 @@
|
|||
"""
|
||||
emane.py: definition of an Emane class for implementing configuration control of an EMANE emulation.
|
||||
Implements configuration and control of an EMANE emulation.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from collections import OrderedDict
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union
|
||||
|
||||
from core import utils
|
||||
from core.config import ConfigGroup, Configuration
|
||||
from core.emane import emanemanifest
|
||||
from core.emane.emanemodel import EmaneModel
|
||||
from core.emane.linkmonitor import EmaneLinkMonitor
|
||||
from core.emane.modelmanager import EmaneModelManager
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.data import LinkData
|
||||
from core.emulator.enumerations import (
|
||||
ConfigDataTypes,
|
||||
LinkTypes,
|
||||
MessageFlags,
|
||||
RegisterTlvs,
|
||||
)
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
|
||||
from core.nodes.base import CoreNetworkBase, CoreNode, NodeBase
|
||||
from core.nodes.interface import CoreInterface, TunTap
|
||||
from core.xml import emanexml
|
||||
|
||||
|
@ -36,15 +26,19 @@ if TYPE_CHECKING:
|
|||
from core.emulator.session import Session
|
||||
|
||||
try:
|
||||
from emane.events import EventService, PathlossEvent
|
||||
from emane.events import LocationEvent
|
||||
from emane.events import EventService, PathlossEvent, CommEffectEvent, LocationEvent
|
||||
from emane.events.eventserviceexception import EventServiceException
|
||||
except ImportError:
|
||||
try:
|
||||
from emanesh.events import EventService
|
||||
from emanesh.events import LocationEvent
|
||||
from emanesh.events import (
|
||||
EventService,
|
||||
PathlossEvent,
|
||||
CommEffectEvent,
|
||||
LocationEvent,
|
||||
)
|
||||
from emanesh.events.eventserviceexception import EventServiceException
|
||||
except ImportError:
|
||||
CommEffectEvent = None
|
||||
EventService = None
|
||||
LocationEvent = None
|
||||
PathlossEvent = None
|
||||
|
@ -62,10 +56,57 @@ class EmaneState(Enum):
|
|||
NOT_READY = 2
|
||||
|
||||
|
||||
@dataclass
|
||||
class StartData:
|
||||
node: CoreNodeBase
|
||||
ifaces: List[CoreInterface] = field(default_factory=list)
|
||||
class EmaneEventService:
|
||||
def __init__(
|
||||
self, manager: "EmaneManager", device: str, group: str, port: int
|
||||
) -> None:
|
||||
self.manager: "EmaneManager" = manager
|
||||
self.device: str = device
|
||||
self.group: str = group
|
||||
self.port: int = port
|
||||
self.running: bool = False
|
||||
self.thread: Optional[threading.Thread] = None
|
||||
logger.info("starting emane event service %s %s:%s", device, group, port)
|
||||
self.events: EventService = EventService(
|
||||
eventchannel=(group, port, device), otachannel=None
|
||||
)
|
||||
|
||||
def start(self) -> None:
|
||||
self.running = True
|
||||
self.thread = threading.Thread(target=self.run, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Run and monitor events.
|
||||
"""
|
||||
logger.info("subscribing to emane location events")
|
||||
while self.running:
|
||||
_uuid, _seq, events = self.events.nextEvent()
|
||||
# this occurs with 0.9.1 event service
|
||||
if not self.running:
|
||||
break
|
||||
for event in events:
|
||||
nem, eid, data = event
|
||||
if eid == LocationEvent.IDENTIFIER:
|
||||
self.manager.handlelocationevent(nem, eid, data)
|
||||
logger.info("unsubscribing from emane location events")
|
||||
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
Stop service and monitoring events.
|
||||
"""
|
||||
self.events.breakloop()
|
||||
self.running = False
|
||||
if self.thread:
|
||||
self.thread.join()
|
||||
self.thread = None
|
||||
for fd in self.events._readFd, self.events._writeFd:
|
||||
if fd >= 0:
|
||||
os.close(fd)
|
||||
for f in self.events._socket, self.events._socketOTA:
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
|
||||
class EmaneManager:
|
||||
|
@ -102,22 +143,22 @@ class EmaneManager:
|
|||
self.eventmonthread: Optional[threading.Thread] = None
|
||||
|
||||
# model for global EMANE configuration options
|
||||
self.emane_config: EmaneGlobalModel = EmaneGlobalModel(session)
|
||||
self.config: Dict[str, str] = self.emane_config.default_values()
|
||||
self.node_configs: Dict[int, Dict[str, Dict[str, str]]] = {}
|
||||
self.node_models: Dict[int, str] = {}
|
||||
|
||||
# link monitor
|
||||
self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self)
|
||||
# emane event monitoring
|
||||
self.services: Dict[str, EmaneEventService] = {}
|
||||
self.nem_service: Dict[int, EmaneEventService] = {}
|
||||
|
||||
self.service: Optional[EventService] = None
|
||||
self.eventchannel: Optional[Tuple[str, int, str]] = None
|
||||
self.event_device: Optional[str] = None
|
||||
|
||||
def next_nem_id(self) -> int:
|
||||
nem_id = int(self.config["nem_id_start"])
|
||||
def next_nem_id(self, iface: CoreInterface) -> int:
|
||||
nem_id = self.session.options.get_config_int("nem_id_start")
|
||||
while nem_id in self.nems_to_ifaces:
|
||||
nem_id += 1
|
||||
self.nems_to_ifaces[nem_id] = iface
|
||||
self.ifaces_to_nems[iface] = nem_id
|
||||
self.write_nem(iface, nem_id)
|
||||
return nem_id
|
||||
|
||||
def get_config(
|
||||
|
@ -203,60 +244,12 @@ class EmaneManager:
|
|||
|
||||
def config_reset(self, node_id: int = None) -> None:
|
||||
if node_id is None:
|
||||
self.config = self.emane_config.default_values()
|
||||
self.node_configs.clear()
|
||||
self.node_models.clear()
|
||||
else:
|
||||
self.node_configs.get(node_id, {}).clear()
|
||||
self.node_models.pop(node_id, None)
|
||||
|
||||
def deleteeventservice(self) -> None:
|
||||
if self.service:
|
||||
for fd in self.service._readFd, self.service._writeFd:
|
||||
if fd >= 0:
|
||||
os.close(fd)
|
||||
for f in self.service._socket, self.service._socketOTA:
|
||||
if f:
|
||||
f.close()
|
||||
self.service = None
|
||||
self.event_device = None
|
||||
|
||||
def initeventservice(self, filename: str = None, shutdown: bool = False) -> None:
|
||||
"""
|
||||
Re-initialize the EMANE Event service.
|
||||
The multicast group and/or port may be configured.
|
||||
"""
|
||||
self.deleteeventservice()
|
||||
if shutdown:
|
||||
return
|
||||
|
||||
# Get the control network to be used for events
|
||||
group, port = self.config["eventservicegroup"].split(":")
|
||||
self.event_device = self.config["eventservicedevice"]
|
||||
eventnetidx = self.session.get_control_net_index(self.event_device)
|
||||
if eventnetidx < 0:
|
||||
logger.error(
|
||||
"invalid emane event service device provided: %s", self.event_device
|
||||
)
|
||||
return
|
||||
|
||||
# make sure the event control network is in place
|
||||
eventnet = self.session.add_remove_control_net(
|
||||
net_index=eventnetidx, remove=False, conf_required=False
|
||||
)
|
||||
if eventnet is not None:
|
||||
# direct EMANE events towards control net bridge
|
||||
self.event_device = eventnet.brname
|
||||
self.eventchannel = (group, int(port), self.event_device)
|
||||
|
||||
# disabled otachannel for event service
|
||||
# only needed for e.g. antennaprofile events xmit by models
|
||||
logger.info("using %s for event service traffic", self.event_device)
|
||||
try:
|
||||
self.service = EventService(eventchannel=self.eventchannel, otachannel=None)
|
||||
except EventServiceException:
|
||||
logger.exception("error instantiating emane EventService")
|
||||
|
||||
def add_node(self, emane_net: EmaneNet) -> None:
|
||||
"""
|
||||
Add EMANE network object to this manager.
|
||||
|
@ -301,41 +294,9 @@ class EmaneManager:
|
|||
if not self._emane_nets:
|
||||
logger.debug("no emane nodes in session")
|
||||
return EmaneState.NOT_NEEDED
|
||||
|
||||
# check if bindings were installed
|
||||
if EventService is None:
|
||||
raise CoreError("EMANE python bindings are not installed")
|
||||
|
||||
# control network bridge required for EMANE 0.9.2
|
||||
# - needs to exist when eventservice binds to it (initeventservice)
|
||||
otadev = self.config["otamanagerdevice"]
|
||||
netidx = self.session.get_control_net_index(otadev)
|
||||
logger.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev)
|
||||
if netidx < 0:
|
||||
logger.error(
|
||||
"EMANE cannot start, check core config. invalid OTA device provided: %s",
|
||||
otadev,
|
||||
)
|
||||
return EmaneState.NOT_READY
|
||||
|
||||
self.session.add_remove_control_net(
|
||||
net_index=netidx, remove=False, conf_required=False
|
||||
)
|
||||
eventdev = self.config["eventservicedevice"]
|
||||
logger.debug("emane event service device: eventdev(%s)", eventdev)
|
||||
if eventdev != otadev:
|
||||
netidx = self.session.get_control_net_index(eventdev)
|
||||
logger.debug("emane event service device index: %s", netidx)
|
||||
if netidx < 0:
|
||||
logger.error(
|
||||
"emane cannot start due to invalid event service device: %s",
|
||||
eventdev,
|
||||
)
|
||||
return EmaneState.NOT_READY
|
||||
|
||||
self.session.add_remove_control_net(
|
||||
net_index=netidx, remove=False, conf_required=False
|
||||
)
|
||||
self.check_node_models()
|
||||
return EmaneState.SUCCESS
|
||||
|
||||
|
@ -351,21 +312,35 @@ class EmaneManager:
|
|||
status = self.setup()
|
||||
if status != EmaneState.SUCCESS:
|
||||
return status
|
||||
self.starteventmonitor()
|
||||
self.buildeventservicexml()
|
||||
with self._emane_node_lock:
|
||||
logger.info("emane building xmls...")
|
||||
start_data = self.get_start_data()
|
||||
for data in start_data:
|
||||
self.start_node(data)
|
||||
self.startup_nodes()
|
||||
if self.links_enabled():
|
||||
self.link_monitor.start()
|
||||
return EmaneState.SUCCESS
|
||||
|
||||
def get_start_data(self) -> List[StartData]:
|
||||
node_map = {}
|
||||
for node_id in sorted(self._emane_nets):
|
||||
emane_net = self._emane_nets[node_id]
|
||||
def startup_nodes(self) -> None:
|
||||
with self._emane_node_lock:
|
||||
logger.info("emane building xmls...")
|
||||
for emane_net, iface in self.get_ifaces():
|
||||
self.start_iface(emane_net, iface)
|
||||
|
||||
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
|
||||
nem_id = self.next_nem_id(iface)
|
||||
nem_port = self.get_nem_port(iface)
|
||||
logger.info(
|
||||
"starting emane for node(%s) iface(%s) nem(%s)",
|
||||
iface.node.name,
|
||||
iface.name,
|
||||
nem_id,
|
||||
)
|
||||
config = self.get_iface_config(emane_net, iface)
|
||||
self.setup_control_channels(nem_id, iface, config)
|
||||
emanexml.build_platform_xml(nem_id, nem_port, emane_net, iface, config)
|
||||
self.start_daemon(iface)
|
||||
self.install_iface(iface, config)
|
||||
|
||||
def get_ifaces(self) -> List[Tuple[EmaneNet, CoreInterface]]:
|
||||
ifaces = []
|
||||
for emane_net in self._emane_nets.values():
|
||||
if not emane_net.model:
|
||||
logger.error("emane net(%s) has no model", emane_net.name)
|
||||
continue
|
||||
|
@ -377,27 +352,60 @@ class EmaneManager:
|
|||
iface.name,
|
||||
)
|
||||
continue
|
||||
start_node = node_map.setdefault(iface.node, StartData(iface.node))
|
||||
start_node.ifaces.append(iface)
|
||||
start_nodes = sorted(node_map.values(), key=lambda x: x.node.id)
|
||||
for start_node in start_nodes:
|
||||
start_node.ifaces = sorted(start_node.ifaces, key=lambda x: x.node_id)
|
||||
return start_nodes
|
||||
ifaces.append((emane_net, iface))
|
||||
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].node_id))
|
||||
|
||||
def start_node(self, data: StartData) -> None:
|
||||
control_net = self.session.add_remove_control_net(
|
||||
0, remove=False, conf_required=False
|
||||
def setup_control_channels(
|
||||
self, nem_id: int, iface: CoreInterface, config: Dict[str, str]
|
||||
) -> None:
|
||||
node = iface.node
|
||||
# setup ota device
|
||||
otagroup, _otaport = config["otamanagergroup"].split(":")
|
||||
otadev = config["otamanagerdevice"]
|
||||
ota_index = self.session.get_control_net_index(otadev)
|
||||
self.session.add_remove_control_net(ota_index, conf_required=False)
|
||||
if isinstance(node, CoreNode):
|
||||
self.session.add_remove_control_iface(node, ota_index, conf_required=False)
|
||||
# setup event device
|
||||
eventgroup, eventport = config["eventservicegroup"].split(":")
|
||||
eventdev = config["eventservicedevice"]
|
||||
event_index = self.session.get_control_net_index(eventdev)
|
||||
event_net = self.session.add_remove_control_net(
|
||||
event_index, conf_required=False
|
||||
)
|
||||
emanexml.build_platform_xml(self, control_net, data)
|
||||
self.start_daemon(data.node)
|
||||
for iface in data.ifaces:
|
||||
self.install_iface(iface)
|
||||
|
||||
def set_nem(self, nem_id: int, iface: CoreInterface) -> None:
|
||||
if nem_id in self.nems_to_ifaces:
|
||||
raise CoreError(f"adding duplicate nem: {nem_id}")
|
||||
self.nems_to_ifaces[nem_id] = iface
|
||||
self.ifaces_to_nems[iface] = nem_id
|
||||
if isinstance(node, CoreNode):
|
||||
self.session.add_remove_control_iface(
|
||||
node, event_index, conf_required=False
|
||||
)
|
||||
# initialize emane event services
|
||||
service = self.services.get(event_net.brname)
|
||||
if not service:
|
||||
try:
|
||||
service = EmaneEventService(
|
||||
self, event_net.brname, eventgroup, int(eventport)
|
||||
)
|
||||
self.services[event_net.brname] = service
|
||||
self.nem_service[nem_id] = service
|
||||
except EventServiceException:
|
||||
raise CoreError(
|
||||
"failed to start emane event services "
|
||||
f"{event_net.brname} {eventgroup}:{eventport}"
|
||||
)
|
||||
else:
|
||||
self.nem_service[nem_id] = service
|
||||
# setup multicast routes as needed
|
||||
logger.info(
|
||||
"node(%s) interface(%s) ota(%s:%s) event(%s:%s)",
|
||||
node.name,
|
||||
iface.name,
|
||||
otagroup,
|
||||
otadev,
|
||||
eventgroup,
|
||||
eventdev,
|
||||
)
|
||||
node.node_net_client.create_route(otagroup, otadev)
|
||||
if eventgroup != otagroup:
|
||||
node.node_net_client.create_route(eventgroup, eventdev)
|
||||
|
||||
def get_iface(self, nem_id: int) -> Optional[CoreInterface]:
|
||||
return self.nems_to_ifaces.get(nem_id)
|
||||
|
@ -405,6 +413,68 @@ class EmaneManager:
|
|||
def get_nem_id(self, iface: CoreInterface) -> Optional[int]:
|
||||
return self.ifaces_to_nems.get(iface)
|
||||
|
||||
def get_nem_port(self, iface: CoreInterface) -> int:
|
||||
nem_id = self.get_nem_id(iface)
|
||||
return int(f"47{nem_id:03}")
|
||||
|
||||
def get_nem_position(
|
||||
self, iface: CoreInterface
|
||||
) -> Optional[Tuple[int, float, float, int]]:
|
||||
"""
|
||||
Retrieves nem position for a given interface.
|
||||
|
||||
:param iface: interface to get nem emane position for
|
||||
:return: nem position tuple, None otherwise
|
||||
"""
|
||||
nem_id = self.get_nem_id(iface)
|
||||
if nem_id is None:
|
||||
logger.info("nem for %s is unknown", iface.localname)
|
||||
return
|
||||
node = iface.node
|
||||
x, y, z = node.getposition()
|
||||
lat, lon, alt = self.session.location.getgeo(x, y, z)
|
||||
if node.position.alt is not None:
|
||||
alt = node.position.alt
|
||||
node.position.set_geo(lon, lat, alt)
|
||||
# altitude must be an integer or warning is printed
|
||||
alt = int(round(alt))
|
||||
return nem_id, lon, lat, alt
|
||||
|
||||
def set_nem_position(self, iface: CoreInterface) -> None:
|
||||
"""
|
||||
Publish a NEM location change event using the EMANE event service.
|
||||
|
||||
:param iface: interface to set nem position for
|
||||
"""
|
||||
position = self.get_nem_position(iface)
|
||||
if position:
|
||||
nemid, lon, lat, alt = position
|
||||
event = LocationEvent()
|
||||
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
||||
self.publish_event(nemid, event, send_all=True)
|
||||
|
||||
def set_nem_positions(self, moved_ifaces: List[CoreInterface]) -> None:
|
||||
"""
|
||||
Several NEMs have moved, from e.g. a WaypointMobilityModel
|
||||
calculation. Generate an EMANE Location Event having several
|
||||
entries for each interface that has moved.
|
||||
"""
|
||||
if not moved_ifaces:
|
||||
return
|
||||
services = {}
|
||||
for iface in moved_ifaces:
|
||||
position = self.get_nem_position(iface)
|
||||
if not position:
|
||||
continue
|
||||
nem_id, lon, lat, alt = position
|
||||
service = self.nem_service.get(nem_id)
|
||||
if not service:
|
||||
continue
|
||||
event = services.setdefault(service, LocationEvent())
|
||||
event.append(nem_id, latitude=lat, longitude=lon, altitude=alt)
|
||||
for service, event in services.items():
|
||||
service.events.publish(0, event)
|
||||
|
||||
def write_nem(self, iface: CoreInterface, nem_id: int) -> None:
|
||||
path = self.session.directory / "emane_nems"
|
||||
try:
|
||||
|
@ -414,23 +484,23 @@ class EmaneManager:
|
|||
logger.exception("error writing to emane nem file")
|
||||
|
||||
def links_enabled(self) -> bool:
|
||||
return self.config["link_enabled"] == "1"
|
||||
return self.session.options.get_config_int("link_enabled") == 1
|
||||
|
||||
def poststartup(self) -> None:
|
||||
"""
|
||||
Retransmit location events now that all NEMs are active.
|
||||
"""
|
||||
if not self.genlocationevents():
|
||||
return
|
||||
events_enabled = self.genlocationevents()
|
||||
with self._emane_node_lock:
|
||||
for node_id in sorted(self._emane_nets):
|
||||
emane_net = self._emane_nets[node_id]
|
||||
logger.debug(
|
||||
"post startup for emane node: %s - %s", emane_net.id, emane_net.name
|
||||
)
|
||||
emane_net.model.post_startup()
|
||||
for iface in emane_net.get_ifaces():
|
||||
iface.setposition()
|
||||
emane_net.model.post_startup(iface)
|
||||
if events_enabled:
|
||||
iface.setposition()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
|
@ -441,6 +511,8 @@ class EmaneManager:
|
|||
self._emane_nets.clear()
|
||||
self.nems_to_ifaces.clear()
|
||||
self.ifaces_to_nems.clear()
|
||||
self.nems_to_ifaces.clear()
|
||||
self.services.clear()
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
|
@ -452,22 +524,23 @@ class EmaneManager:
|
|||
logger.info("stopping EMANE daemons")
|
||||
if self.links_enabled():
|
||||
self.link_monitor.stop()
|
||||
# shutdown interfaces and stop daemons
|
||||
kill_emaned = "killall -q emane"
|
||||
start_data = self.get_start_data()
|
||||
for data in start_data:
|
||||
node = data.node
|
||||
# shutdown interfaces
|
||||
for _, iface in self.get_ifaces():
|
||||
node = iface.node
|
||||
if not node.up:
|
||||
continue
|
||||
for iface in data.ifaces:
|
||||
if isinstance(node, CoreNode):
|
||||
iface.shutdown()
|
||||
iface.poshook = None
|
||||
kill_cmd = f'pkill -f "emane.+{iface.name}"'
|
||||
if isinstance(node, CoreNode):
|
||||
node.cmd(kill_emaned, wait=False)
|
||||
iface.shutdown()
|
||||
node.cmd(kill_cmd, wait=False)
|
||||
else:
|
||||
node.host_cmd(kill_emaned, wait=False)
|
||||
self.stopeventmonitor()
|
||||
node.host_cmd(kill_cmd, wait=False)
|
||||
iface.poshook = None
|
||||
# stop emane event services
|
||||
while self.services:
|
||||
_, service = self.services.popitem()
|
||||
service.stop()
|
||||
self.nem_service.clear()
|
||||
|
||||
def check_node_models(self) -> None:
|
||||
"""
|
||||
|
@ -520,41 +593,14 @@ class EmaneManager:
|
|||
color=color,
|
||||
)
|
||||
|
||||
def buildeventservicexml(self) -> None:
|
||||
def start_daemon(self, iface: CoreInterface) -> None:
|
||||
"""
|
||||
Build the libemaneeventservice.xml file if event service options
|
||||
were changed in the global config.
|
||||
"""
|
||||
need_xml = False
|
||||
default_values = self.emane_config.default_values()
|
||||
for name in ["eventservicegroup", "eventservicedevice"]:
|
||||
a = default_values[name]
|
||||
b = self.config[name]
|
||||
if a != b:
|
||||
need_xml = True
|
||||
if not need_xml:
|
||||
# reset to using default config
|
||||
self.initeventservice()
|
||||
return
|
||||
try:
|
||||
group, port = self.config["eventservicegroup"].split(":")
|
||||
except ValueError:
|
||||
logger.exception("invalid eventservicegroup in EMANE config")
|
||||
return
|
||||
dev = self.config["eventservicedevice"]
|
||||
emanexml.create_event_service_xml(group, port, dev, self.session.directory)
|
||||
self.session.distributed.execute(
|
||||
lambda x: emanexml.create_event_service_xml(
|
||||
group, port, dev, self.session.directory, x
|
||||
)
|
||||
)
|
||||
Start emane daemon for a given nem/interface.
|
||||
|
||||
def start_daemon(self, node: CoreNodeBase) -> None:
|
||||
:param iface: interface to start emane daemon for
|
||||
:return: nothing
|
||||
"""
|
||||
Start one EMANE daemon per node having a radio.
|
||||
Add a control network even if the user has not configured one.
|
||||
"""
|
||||
logger.info("starting emane daemons...")
|
||||
node = iface.node
|
||||
loglevel = str(DEFAULT_LOG_LEVEL)
|
||||
cfgloglevel = self.session.options.get_config_int("emane_log_level")
|
||||
realtime = self.session.options.get_config_bool("emane_realtime", default=True)
|
||||
|
@ -565,60 +611,25 @@ class EmaneManager:
|
|||
if realtime:
|
||||
emanecmd += " -r"
|
||||
if isinstance(node, CoreNode):
|
||||
otagroup, _otaport = self.config["otamanagergroup"].split(":")
|
||||
otadev = self.config["otamanagerdevice"]
|
||||
otanetidx = self.session.get_control_net_index(otadev)
|
||||
eventgroup, _eventport = self.config["eventservicegroup"].split(":")
|
||||
eventdev = self.config["eventservicedevice"]
|
||||
eventservicenetidx = self.session.get_control_net_index(eventdev)
|
||||
|
||||
# control network not yet started here
|
||||
self.session.add_remove_control_iface(
|
||||
node, 0, remove=False, conf_required=False
|
||||
)
|
||||
if otanetidx > 0:
|
||||
logger.info("adding ota device ctrl%d", otanetidx)
|
||||
self.session.add_remove_control_iface(
|
||||
node, otanetidx, remove=False, conf_required=False
|
||||
)
|
||||
if eventservicenetidx >= 0:
|
||||
logger.info("adding event service device ctrl%d", eventservicenetidx)
|
||||
self.session.add_remove_control_iface(
|
||||
node, eventservicenetidx, remove=False, conf_required=False
|
||||
)
|
||||
# multicast route is needed for OTA data
|
||||
logger.info("OTA GROUP(%s) OTA DEV(%s)", otagroup, otadev)
|
||||
node.node_net_client.create_route(otagroup, otadev)
|
||||
# multicast route is also needed for event data if on control network
|
||||
if eventservicenetidx >= 0 and eventgroup != otagroup:
|
||||
node.node_net_client.create_route(eventgroup, eventdev)
|
||||
# start emane
|
||||
log_file = node.directory / f"{node.name}-emane.log"
|
||||
platform_xml = node.directory / f"{node.name}-platform.xml"
|
||||
log_file = node.directory / f"{iface.name}-emane.log"
|
||||
platform_xml = node.directory / emanexml.platform_file_name(iface)
|
||||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||
node.cmd(args)
|
||||
logger.info("node(%s) emane daemon running: %s", node.name, args)
|
||||
else:
|
||||
log_file = self.session.directory / f"{node.name}-emane.log"
|
||||
platform_xml = self.session.directory / f"{node.name}-platform.xml"
|
||||
log_file = self.session.directory / f"{iface.name}-emane.log"
|
||||
platform_xml = self.session.directory / emanexml.platform_file_name(iface)
|
||||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||
node.host_cmd(args, cwd=self.session.directory)
|
||||
logger.info("node(%s) host emane daemon running: %s", node.name, args)
|
||||
|
||||
def install_iface(self, iface: CoreInterface) -> None:
|
||||
emane_net = iface.net
|
||||
if not isinstance(emane_net, EmaneNet):
|
||||
raise CoreError(
|
||||
f"emane interface not connected to emane net: {emane_net.name}"
|
||||
)
|
||||
config = self.get_iface_config(emane_net, iface)
|
||||
def install_iface(self, iface: CoreInterface, config: Dict[str, str]) -> None:
|
||||
external = config.get("external", "0")
|
||||
if isinstance(iface, TunTap) and external == "0":
|
||||
iface.set_ips()
|
||||
# at this point we register location handlers for generating
|
||||
# EMANE location events
|
||||
if self.genlocationevents():
|
||||
iface.poshook = emane_net.setnemposition
|
||||
iface.poshook = self.set_nem_position
|
||||
iface.setposition()
|
||||
|
||||
def doeventmonitor(self) -> bool:
|
||||
|
@ -640,68 +651,6 @@ class EmaneManager:
|
|||
tmp = not self.doeventmonitor()
|
||||
return tmp
|
||||
|
||||
def starteventmonitor(self) -> None:
|
||||
"""
|
||||
Start monitoring EMANE location events if configured to do so.
|
||||
"""
|
||||
logger.info("emane start event monitor")
|
||||
if not self.doeventmonitor():
|
||||
return
|
||||
if self.service is None:
|
||||
logger.error(
|
||||
"Warning: EMANE events will not be generated "
|
||||
"because the emaneeventservice\n binding was "
|
||||
"unable to load "
|
||||
"(install the python-emaneeventservice bindings)"
|
||||
)
|
||||
return
|
||||
self.doeventloop = True
|
||||
self.eventmonthread = threading.Thread(
|
||||
target=self.eventmonitorloop, daemon=True
|
||||
)
|
||||
self.eventmonthread.start()
|
||||
|
||||
def stopeventmonitor(self) -> None:
|
||||
"""
|
||||
Stop monitoring EMANE location events.
|
||||
"""
|
||||
self.doeventloop = False
|
||||
if self.service is not None:
|
||||
self.service.breakloop()
|
||||
# reset the service, otherwise nextEvent won"t work
|
||||
self.initeventservice(shutdown=True)
|
||||
|
||||
if self.eventmonthread is not None:
|
||||
self.eventmonthread.join()
|
||||
self.eventmonthread = None
|
||||
|
||||
def eventmonitorloop(self) -> None:
|
||||
"""
|
||||
Thread target that monitors EMANE location events.
|
||||
"""
|
||||
if self.service is None:
|
||||
return
|
||||
logger.info(
|
||||
"subscribing to EMANE location events. (%s)",
|
||||
threading.currentThread().getName(),
|
||||
)
|
||||
while self.doeventloop is True:
|
||||
_uuid, _seq, events = self.service.nextEvent()
|
||||
|
||||
# this occurs with 0.9.1 event service
|
||||
if not self.doeventloop:
|
||||
break
|
||||
|
||||
for event in events:
|
||||
nem, eid, data = event
|
||||
if eid == LocationEvent.IDENTIFIER:
|
||||
self.handlelocationevent(nem, eid, data)
|
||||
|
||||
logger.info(
|
||||
"unsubscribing from EMANE location events. (%s)",
|
||||
threading.currentThread().getName(),
|
||||
)
|
||||
|
||||
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
|
||||
"""
|
||||
Handle an EMANE location event.
|
||||
|
@ -717,7 +666,6 @@ class EmaneManager:
|
|||
):
|
||||
logger.warning("dropped invalid location event")
|
||||
continue
|
||||
|
||||
# yaw,pitch,roll,azimuth,elevation,velocity are unhandled
|
||||
lat = attrs["latitude"]
|
||||
lon = attrs["longitude"]
|
||||
|
@ -812,87 +760,19 @@ class EmaneManager:
|
|||
event = PathlossEvent()
|
||||
event.append(nem1, forward=rx1)
|
||||
event.append(nem2, forward=rx2)
|
||||
self.service.publish(nem1, event)
|
||||
self.service.publish(nem2, event)
|
||||
self.publish_event(nem1, event)
|
||||
self.publish_event(nem2, event)
|
||||
|
||||
|
||||
class EmaneGlobalModel:
|
||||
"""
|
||||
Global EMANE configuration options.
|
||||
"""
|
||||
|
||||
name: str = "emane"
|
||||
bitmap: Optional[str] = None
|
||||
|
||||
def __init__(self, session: "Session") -> None:
|
||||
self.session: "Session" = session
|
||||
self.core_config: List[Configuration] = [
|
||||
Configuration(
|
||||
id="platform_id_start",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="1",
|
||||
label="Starting Platform ID",
|
||||
),
|
||||
Configuration(
|
||||
id="nem_id_start",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="1",
|
||||
label="Starting NEM ID",
|
||||
),
|
||||
Configuration(
|
||||
id="link_enabled",
|
||||
type=ConfigDataTypes.BOOL,
|
||||
default="1",
|
||||
label="Enable Links?",
|
||||
),
|
||||
Configuration(
|
||||
id="loss_threshold",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="30",
|
||||
label="Link Loss Threshold (%)",
|
||||
),
|
||||
Configuration(
|
||||
id="link_interval",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="1",
|
||||
label="Link Check Interval (sec)",
|
||||
),
|
||||
Configuration(
|
||||
id="link_timeout",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="4",
|
||||
label="Link Timeout (sec)",
|
||||
),
|
||||
]
|
||||
self.emulator_config = None
|
||||
self.parse_config()
|
||||
|
||||
def parse_config(self) -> None:
|
||||
emane_prefix = self.session.options.get_config(
|
||||
"emane_prefix", default=DEFAULT_EMANE_PREFIX
|
||||
)
|
||||
emane_prefix = Path(emane_prefix)
|
||||
emulator_xml = emane_prefix / "share/emane/manifest/nemmanager.xml"
|
||||
emulator_defaults = {
|
||||
"eventservicedevice": DEFAULT_DEV,
|
||||
"eventservicegroup": "224.1.2.8:45703",
|
||||
"otamanagerdevice": DEFAULT_DEV,
|
||||
"otamanagergroup": "224.1.2.8:45702",
|
||||
}
|
||||
self.emulator_config = emanemanifest.parse(emulator_xml, emulator_defaults)
|
||||
|
||||
def configurations(self) -> List[Configuration]:
|
||||
return self.emulator_config + self.core_config
|
||||
|
||||
def config_groups(self) -> List[ConfigGroup]:
|
||||
emulator_len = len(self.emulator_config)
|
||||
config_len = len(self.configurations())
|
||||
return [
|
||||
ConfigGroup("Platform Attributes", 1, emulator_len),
|
||||
ConfigGroup("CORE Configuration", emulator_len + 1, config_len),
|
||||
]
|
||||
|
||||
def default_values(self) -> Dict[str, str]:
|
||||
return OrderedDict(
|
||||
[(config.id, config.default) for config in self.configurations()]
|
||||
)
|
||||
def publish_event(
|
||||
self,
|
||||
nem_id: int,
|
||||
event: Union[PathlossEvent, CommEffectEvent, LocationEvent],
|
||||
send_all: bool = False,
|
||||
) -> None:
|
||||
service = self.nem_service.get(nem_id)
|
||||
if not service:
|
||||
logger.error("no service to publish event nem(%s)", nem_id)
|
||||
return
|
||||
if send_all:
|
||||
nem_id = 0
|
||||
service.events.publish(nem_id, event)
|
||||
|
|
|
@ -7,7 +7,6 @@ from typing import Dict, List, Optional, Set
|
|||
|
||||
from core.config import ConfigGroup, Configuration
|
||||
from core.emane import emanemanifest
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.data import LinkOptions
|
||||
from core.emulator.enumerations import ConfigDataTypes
|
||||
from core.errors import CoreError
|
||||
|
@ -16,6 +15,8 @@ from core.nodes.interface import CoreInterface
|
|||
from core.xml import emanexml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
DEFAULT_DEV: str = "ctrl0"
|
||||
MANIFEST_PATH: str = "share/emane/manifest"
|
||||
|
||||
|
||||
class EmaneModel(WirelessModel):
|
||||
|
@ -25,6 +26,17 @@ class EmaneModel(WirelessModel):
|
|||
configurable parameters. Helper functions also live here.
|
||||
"""
|
||||
|
||||
# default platform configuration settings
|
||||
platform_controlport: str = "controlportendpoint"
|
||||
platform_xml: str = "nemmanager.xml"
|
||||
platform_defaults: Dict[str, str] = {
|
||||
"eventservicedevice": DEFAULT_DEV,
|
||||
"eventservicegroup": "224.1.2.8:45703",
|
||||
"otamanagerdevice": DEFAULT_DEV,
|
||||
"otamanagergroup": "224.1.2.8:45702",
|
||||
}
|
||||
platform_config: List[Configuration] = []
|
||||
|
||||
# default mac configuration settings
|
||||
mac_library: Optional[str] = None
|
||||
mac_xml: Optional[str] = None
|
||||
|
@ -57,20 +69,35 @@ class EmaneModel(WirelessModel):
|
|||
@classmethod
|
||||
def load(cls, emane_prefix: Path) -> None:
|
||||
"""
|
||||
Called after being loaded within the EmaneManager. Provides configured emane_prefix for
|
||||
parsing xml files.
|
||||
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"
|
||||
cls._load_platform_config(emane_prefix)
|
||||
# load mac configuration
|
||||
mac_xml_path = emane_prefix / manifest_path / cls.mac_xml
|
||||
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
|
||||
phy_xml_path = emane_prefix / MANIFEST_PATH / cls.phy_xml
|
||||
cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults)
|
||||
|
||||
@classmethod
|
||||
def _load_platform_config(cls, emane_prefix: Path) -> None:
|
||||
platform_xml_path = emane_prefix / MANIFEST_PATH / cls.platform_xml
|
||||
cls.platform_config = emanemanifest.parse(
|
||||
platform_xml_path, cls.platform_defaults
|
||||
)
|
||||
# remove controlport configuration, since core will set this directly
|
||||
controlport_index = None
|
||||
for index, configuration in enumerate(cls.platform_config):
|
||||
if configuration.id == cls.platform_controlport:
|
||||
controlport_index = index
|
||||
break
|
||||
if controlport_index is not None:
|
||||
cls.platform_config.pop(controlport_index)
|
||||
|
||||
@classmethod
|
||||
def configurations(cls) -> List[Configuration]:
|
||||
"""
|
||||
|
@ -78,7 +105,9 @@ class EmaneModel(WirelessModel):
|
|||
|
||||
:return: all configurations
|
||||
"""
|
||||
return cls.mac_config + cls.phy_config + cls.external_config
|
||||
return (
|
||||
cls.platform_config + cls.mac_config + cls.phy_config + cls.external_config
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def config_groups(cls) -> List[ConfigGroup]:
|
||||
|
@ -87,11 +116,13 @@ class EmaneModel(WirelessModel):
|
|||
|
||||
:return: list of configuration groups.
|
||||
"""
|
||||
mac_len = len(cls.mac_config)
|
||||
platform_len = len(cls.platform_config)
|
||||
mac_len = len(cls.mac_config) + platform_len
|
||||
phy_len = len(cls.phy_config) + mac_len
|
||||
config_len = len(cls.configurations())
|
||||
return [
|
||||
ConfigGroup("MAC Parameters", 1, mac_len),
|
||||
ConfigGroup("Platform Parameters", 1, platform_len),
|
||||
ConfigGroup("MAC Parameters", platform_len + 1, mac_len),
|
||||
ConfigGroup("PHY Parameters", mac_len + 1, phy_len),
|
||||
ConfigGroup("External Parameters", phy_len + 1, config_len),
|
||||
]
|
||||
|
@ -111,10 +142,11 @@ class EmaneModel(WirelessModel):
|
|||
emanexml.create_phy_xml(self, iface, config)
|
||||
emanexml.create_transport_xml(iface, config)
|
||||
|
||||
def post_startup(self) -> None:
|
||||
def post_startup(self, iface: CoreInterface) -> None:
|
||||
"""
|
||||
Logic to execute after the emane manager is finished with startup.
|
||||
|
||||
:param iface: interface for post startup
|
||||
:return: nothing
|
||||
"""
|
||||
logger.debug("emane model(%s) has no post setup tasks", self.name)
|
||||
|
@ -129,8 +161,7 @@ class EmaneModel(WirelessModel):
|
|||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
emane_net = self.session.get_node(self.id, EmaneNet)
|
||||
emane_net.setnempositions(moved_ifaces)
|
||||
self.session.emane.set_nem_positions(moved_ifaces)
|
||||
except CoreError:
|
||||
logger.exception("error during update")
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
|||
|
||||
from lxml import etree
|
||||
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.data import LinkData
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags
|
||||
from core.nodes.network import CtrlNet
|
||||
|
@ -24,7 +25,6 @@ except ImportError:
|
|||
if TYPE_CHECKING:
|
||||
from core.emane.emanemanager import EmaneManager
|
||||
|
||||
DEFAULT_PORT: int = 47_000
|
||||
MAC_COMPONENT_INDEX: int = 1
|
||||
EMANE_RFPIPE: str = "rfpipemaclayer"
|
||||
EMANE_80211: str = "ieee80211abgmaclayer"
|
||||
|
@ -79,10 +79,10 @@ class EmaneLink:
|
|||
|
||||
|
||||
class EmaneClient:
|
||||
def __init__(self, address: str) -> None:
|
||||
def __init__(self, address: str, port: int) -> None:
|
||||
self.address: str = address
|
||||
self.client: shell.ControlPortClient = shell.ControlPortClient(
|
||||
self.address, DEFAULT_PORT
|
||||
self.address, port
|
||||
)
|
||||
self.nems: Dict[int, LossTable] = {}
|
||||
self.setup()
|
||||
|
@ -189,9 +189,10 @@ class EmaneLinkMonitor:
|
|||
self.running: bool = False
|
||||
|
||||
def start(self) -> None:
|
||||
self.loss_threshold = int(self.emane_manager.config["loss_threshold"])
|
||||
self.link_interval = int(self.emane_manager.config["link_interval"])
|
||||
self.link_timeout = int(self.emane_manager.config["link_timeout"])
|
||||
options = self.emane_manager.session.options
|
||||
self.loss_threshold = options.get_config_int("loss_threshold")
|
||||
self.link_interval = options.get_config_int("link_interval")
|
||||
self.link_timeout = options.get_config_int("link_timeout")
|
||||
self.initialize()
|
||||
if not self.clients:
|
||||
logger.info("no valid emane models to monitor links")
|
||||
|
@ -204,22 +205,28 @@ class EmaneLinkMonitor:
|
|||
|
||||
def initialize(self) -> None:
|
||||
addresses = self.get_addresses()
|
||||
for address in addresses:
|
||||
client = EmaneClient(address)
|
||||
for address, port in addresses:
|
||||
client = EmaneClient(address, port)
|
||||
if client.nems:
|
||||
self.clients.append(client)
|
||||
|
||||
def get_addresses(self) -> List[str]:
|
||||
def get_addresses(self) -> List[Tuple[str, int]]:
|
||||
addresses = []
|
||||
nodes = self.emane_manager.getnodes()
|
||||
for node in nodes:
|
||||
control = None
|
||||
ports = []
|
||||
for iface in node.get_ifaces():
|
||||
if isinstance(iface.net, CtrlNet):
|
||||
ip4 = iface.get_ip4()
|
||||
if ip4:
|
||||
address = str(ip4.ip)
|
||||
addresses.append(address)
|
||||
break
|
||||
control = str(ip4.ip)
|
||||
if isinstance(iface.net, EmaneNet):
|
||||
port = self.emane_manager.get_nem_port(iface)
|
||||
ports.append(port)
|
||||
if control:
|
||||
for port in ports:
|
||||
addresses.append((control, port))
|
||||
return addresses
|
||||
|
||||
def check_links(self) -> None:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
EMANE Bypass model for CORE
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import List, Set
|
||||
|
||||
from core.config import Configuration
|
||||
|
@ -30,6 +31,5 @@ class EmaneBypassModel(emanemodel.EmaneModel):
|
|||
phy_config: List[Configuration] = []
|
||||
|
||||
@classmethod
|
||||
def load(cls, emane_prefix: str) -> None:
|
||||
# ignore default logic
|
||||
pass
|
||||
def load(cls, emane_prefix: Path) -> None:
|
||||
cls._load_platform_config(emane_prefix)
|
||||
|
|
|
@ -51,16 +51,25 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
|||
|
||||
@classmethod
|
||||
def load(cls, emane_prefix: Path) -> None:
|
||||
cls._load_platform_config(emane_prefix)
|
||||
shim_xml_path = emane_prefix / "share/emane/manifest" / cls.shim_xml
|
||||
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
|
||||
|
||||
@classmethod
|
||||
def configurations(cls) -> List[Configuration]:
|
||||
return cls.config_shim
|
||||
return cls.platform_config + cls.config_shim
|
||||
|
||||
@classmethod
|
||||
def config_groups(cls) -> List[ConfigGroup]:
|
||||
return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))]
|
||||
platform_len = len(cls.platform_config)
|
||||
return [
|
||||
ConfigGroup("Platform Parameters", 1, platform_len),
|
||||
ConfigGroup(
|
||||
"CommEffect SHIM Parameters",
|
||||
platform_len + 1,
|
||||
len(cls.configurations()),
|
||||
),
|
||||
]
|
||||
|
||||
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
|
||||
"""
|
||||
|
@ -113,15 +122,9 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
|||
Generate CommEffect events when a Link Message is received having
|
||||
link parameters.
|
||||
"""
|
||||
service = self.session.emane.service
|
||||
if service is None:
|
||||
logger.warning("%s: EMANE event service unavailable", self.name)
|
||||
return
|
||||
|
||||
if iface is None or iface2 is None:
|
||||
logger.warning("%s: missing NEM information", self.name)
|
||||
return
|
||||
|
||||
# TODO: batch these into multiple events per transmission
|
||||
# TODO: may want to split out seconds portion of delay and jitter
|
||||
event = CommEffectEvent()
|
||||
|
@ -137,4 +140,4 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
|||
unicast=int(convert_none(options.bandwidth)),
|
||||
broadcast=int(convert_none(options.bandwidth)),
|
||||
)
|
||||
service.publish(nem2, event)
|
||||
self.session.emane.publish_event(nem2, event)
|
||||
|
|
|
@ -9,7 +9,9 @@ from typing import Set
|
|||
from core import constants, utils
|
||||
from core.config import Configuration
|
||||
from core.emane import emanemodel
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.enumerations import ConfigDataTypes
|
||||
from core.nodes.interface import CoreInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -44,23 +46,23 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
|
|||
)
|
||||
cls.mac_config.insert(0, config_item)
|
||||
|
||||
def post_startup(self) -> None:
|
||||
"""
|
||||
Logic to execute after the emane manager is finished with startup.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
def post_startup(self, iface: CoreInterface) -> None:
|
||||
# get configured schedule
|
||||
config = self.session.emane.get_config(self.id, self.name)
|
||||
if not config:
|
||||
return
|
||||
emane_net = self.session.get_node(self.id, EmaneNet)
|
||||
config = self.session.emane.get_iface_config(emane_net, iface)
|
||||
schedule = Path(config[self.schedule_name])
|
||||
if not schedule.is_file():
|
||||
logger.warning("ignoring invalid tdma schedule: %s", schedule)
|
||||
logger.error("ignoring invalid tdma schedule: %s", schedule)
|
||||
return
|
||||
# initiate tdma schedule
|
||||
event_device = self.session.emane.event_device
|
||||
logger.info(
|
||||
"setting up tdma schedule: schedule(%s) device(%s)", schedule, event_device
|
||||
)
|
||||
utils.cmd(f"emaneevent-tdmaschedule -i {event_device} {schedule}")
|
||||
nem_id = self.session.emane.get_nem_id(iface)
|
||||
if not nem_id:
|
||||
logger.error("could not find nem for interface")
|
||||
return
|
||||
service = self.session.emane.nem_service.get(nem_id)
|
||||
if service:
|
||||
device = service.device
|
||||
logger.info(
|
||||
"setting up tdma schedule: schedule(%s) device(%s)", schedule, device
|
||||
)
|
||||
utils.cmd(f"emaneevent-tdmaschedule -i {device} {schedule}")
|
||||
|
|
|
@ -4,7 +4,7 @@ share the same MAC+PHY model.
|
|||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Type
|
||||
|
||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||
from core.emulator.distributed import DistributedServer
|
||||
|
@ -110,67 +110,6 @@ class EmaneNet(CoreNetworkBase):
|
|||
self.mobility = model(session=self.session, _id=self.id)
|
||||
self.mobility.update_config(config)
|
||||
|
||||
def _nem_position(
|
||||
self, iface: CoreInterface
|
||||
) -> Optional[Tuple[int, float, float, float]]:
|
||||
"""
|
||||
Creates nem position for emane event for a given interface.
|
||||
|
||||
:param iface: interface to get nem emane position for
|
||||
:return: nem position tuple, None otherwise
|
||||
"""
|
||||
nem_id = self.session.emane.get_nem_id(iface)
|
||||
ifname = iface.localname
|
||||
if nem_id is None:
|
||||
logger.info("nemid for %s is unknown", ifname)
|
||||
return
|
||||
node = iface.node
|
||||
x, y, z = node.getposition()
|
||||
lat, lon, alt = self.session.location.getgeo(x, y, z)
|
||||
if node.position.alt is not None:
|
||||
alt = node.position.alt
|
||||
node.position.set_geo(lon, lat, alt)
|
||||
# altitude must be an integer or warning is printed
|
||||
alt = int(round(alt))
|
||||
return nem_id, lon, lat, alt
|
||||
|
||||
def setnemposition(self, iface: CoreInterface) -> None:
|
||||
"""
|
||||
Publish a NEM location change event using the EMANE event service.
|
||||
|
||||
:param iface: interface to set nem position for
|
||||
"""
|
||||
if self.session.emane.service is None:
|
||||
logger.info("position service not available")
|
||||
return
|
||||
position = self._nem_position(iface)
|
||||
if position:
|
||||
nemid, lon, lat, alt = position
|
||||
event = LocationEvent()
|
||||
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
||||
self.session.emane.service.publish(0, event)
|
||||
|
||||
def setnempositions(self, moved_ifaces: List[CoreInterface]) -> None:
|
||||
"""
|
||||
Several NEMs have moved, from e.g. a WaypointMobilityModel
|
||||
calculation. Generate an EMANE Location Event having several
|
||||
entries for each interface that has moved.
|
||||
"""
|
||||
if len(moved_ifaces) == 0:
|
||||
return
|
||||
|
||||
if self.session.emane.service is None:
|
||||
logger.info("position service not available")
|
||||
return
|
||||
|
||||
event = LocationEvent()
|
||||
for iface in moved_ifaces:
|
||||
position = self._nem_position(iface)
|
||||
if position:
|
||||
nemid, lon, lat, alt = position
|
||||
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
||||
self.session.emane.service.publish(0, event)
|
||||
|
||||
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
||||
links = super().links(flags)
|
||||
emane_manager = self.session.emane
|
||||
|
|
|
@ -59,6 +59,42 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
|||
Configuration(
|
||||
id="ovs", type=ConfigDataTypes.BOOL, default="0", label="Enable OVS"
|
||||
),
|
||||
Configuration(
|
||||
id="platform_id_start",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="1",
|
||||
label="EMANE Platform ID Start",
|
||||
),
|
||||
Configuration(
|
||||
id="nem_id_start",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="1",
|
||||
label="EMANE NEM ID Start",
|
||||
),
|
||||
Configuration(
|
||||
id="link_enabled",
|
||||
type=ConfigDataTypes.BOOL,
|
||||
default="1",
|
||||
label="EMANE Links?",
|
||||
),
|
||||
Configuration(
|
||||
id="loss_threshold",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="30",
|
||||
label="EMANE Link Loss Threshold (%)",
|
||||
),
|
||||
Configuration(
|
||||
id="link_interval",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="1",
|
||||
label="EMANE Link Check Interval (sec)",
|
||||
),
|
||||
Configuration(
|
||||
id="link_timeout",
|
||||
type=ConfigDataTypes.INT32,
|
||||
default="4",
|
||||
label="EMANE Link Timeout (sec)",
|
||||
),
|
||||
]
|
||||
config_type: RegisterTlvs = RegisterTlvs.UTILITY
|
||||
|
||||
|
|
|
@ -19,40 +19,6 @@ if TYPE_CHECKING:
|
|||
from core.gui.app import Application
|
||||
|
||||
|
||||
class GlobalEmaneDialog(Dialog):
|
||||
def __init__(self, master: tk.BaseWidget, app: "Application") -> None:
|
||||
super().__init__(app, "EMANE Configuration", master=master)
|
||||
self.config_frame: Optional[ConfigFrame] = None
|
||||
self.enabled: bool = not self.app.core.is_runtime()
|
||||
self.draw()
|
||||
|
||||
def draw(self) -> None:
|
||||
self.top.columnconfigure(0, weight=1)
|
||||
self.top.rowconfigure(0, weight=1)
|
||||
session = self.app.core.session
|
||||
self.config_frame = ConfigFrame(
|
||||
self.top, self.app, session.emane_config, self.enabled
|
||||
)
|
||||
self.config_frame.draw_config()
|
||||
self.config_frame.grid(sticky=tk.NSEW, pady=PADY)
|
||||
self.draw_buttons()
|
||||
|
||||
def draw_buttons(self) -> None:
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.grid(sticky=tk.EW)
|
||||
for i in range(2):
|
||||
frame.columnconfigure(i, weight=1)
|
||||
state = tk.NORMAL if self.enabled else tk.DISABLED
|
||||
button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state)
|
||||
button.grid(row=0, column=0, sticky=tk.EW, padx=PADX)
|
||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||
button.grid(row=0, column=1, sticky=tk.EW)
|
||||
|
||||
def click_apply(self) -> None:
|
||||
self.config_frame.parse_config()
|
||||
self.destroy()
|
||||
|
||||
|
||||
class EmaneModelDialog(Dialog):
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -180,8 +146,7 @@ class EmaneConfigDialog(Dialog):
|
|||
def draw_emane_buttons(self) -> None:
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.grid(sticky=tk.EW, pady=PADY)
|
||||
for i in range(2):
|
||||
frame.columnconfigure(i, weight=1)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
|
||||
image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
|
||||
self.emane_model_button = ttk.Button(
|
||||
|
@ -192,18 +157,7 @@ class EmaneConfigDialog(Dialog):
|
|||
command=self.click_model_config,
|
||||
)
|
||||
self.emane_model_button.image = image
|
||||
self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky=tk.EW)
|
||||
|
||||
image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
|
||||
button = ttk.Button(
|
||||
frame,
|
||||
text="EMANE options",
|
||||
image=image,
|
||||
compound=tk.RIGHT,
|
||||
command=self.click_emane_config,
|
||||
)
|
||||
button.image = image
|
||||
button.grid(row=0, column=1, sticky=tk.EW)
|
||||
self.emane_model_button.grid(padx=PADX, sticky=tk.EW)
|
||||
|
||||
def draw_apply_and_cancel(self) -> None:
|
||||
frame = ttk.Frame(self.top)
|
||||
|
@ -216,10 +170,6 @@ class EmaneConfigDialog(Dialog):
|
|||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||
button.grid(row=0, column=1, sticky=tk.EW)
|
||||
|
||||
def click_emane_config(self) -> None:
|
||||
dialog = GlobalEmaneDialog(self, self.app)
|
||||
dialog.show()
|
||||
|
||||
def click_model_config(self) -> None:
|
||||
"""
|
||||
draw emane model configuration
|
||||
|
|
|
@ -38,7 +38,7 @@ class LinuxNetClient:
|
|||
:param device: device to add route to
|
||||
:return: nothing
|
||||
"""
|
||||
self.run(f"{IP} route add {route} dev {device}")
|
||||
self.run(f"{IP} route replace {route} dev {device}")
|
||||
|
||||
def device_up(self, device: str) -> None:
|
||||
"""
|
||||
|
|
|
@ -80,20 +80,6 @@ def create_iface_data(iface_element: etree.Element) -> InterfaceData:
|
|||
)
|
||||
|
||||
|
||||
def create_emane_config(session: "Session") -> etree.Element:
|
||||
emane_configuration = etree.Element("emane_global_configuration")
|
||||
config = session.emane.config
|
||||
emulator_element = etree.SubElement(emane_configuration, "emulator")
|
||||
for emulator_config in session.emane.emane_config.emulator_config:
|
||||
value = config[emulator_config.id]
|
||||
add_configuration(emulator_element, emulator_config.id, value)
|
||||
core_element = etree.SubElement(emane_configuration, "core")
|
||||
for core_config in session.emane.emane_config.core_config:
|
||||
value = config[core_config.id]
|
||||
add_configuration(core_element, core_config.id, value)
|
||||
return emane_configuration
|
||||
|
||||
|
||||
def create_emane_model_config(
|
||||
node_id: int,
|
||||
model: "EmaneModelType",
|
||||
|
@ -104,22 +90,22 @@ def create_emane_model_config(
|
|||
add_attribute(emane_element, "node", node_id)
|
||||
add_attribute(emane_element, "iface", iface_id)
|
||||
add_attribute(emane_element, "model", model.name)
|
||||
|
||||
platform_element = etree.SubElement(emane_element, "platform")
|
||||
for platform_config in model.platform_config:
|
||||
value = config[platform_config.id]
|
||||
add_configuration(platform_element, platform_config.id, value)
|
||||
mac_element = etree.SubElement(emane_element, "mac")
|
||||
for mac_config in model.mac_config:
|
||||
value = config[mac_config.id]
|
||||
add_configuration(mac_element, mac_config.id, value)
|
||||
|
||||
phy_element = etree.SubElement(emane_element, "phy")
|
||||
for phy_config in model.phy_config:
|
||||
value = config[phy_config.id]
|
||||
add_configuration(phy_element, phy_config.id, value)
|
||||
|
||||
external_element = etree.SubElement(emane_element, "external")
|
||||
for external_config in model.external_config:
|
||||
value = config[external_config.id]
|
||||
add_configuration(external_element, external_config.id, value)
|
||||
|
||||
return emane_element
|
||||
|
||||
|
||||
|
@ -376,8 +362,6 @@ class CoreXmlWriter:
|
|||
self.scenario.append(metadata_elements)
|
||||
|
||||
def write_emane_configs(self) -> None:
|
||||
emane_global_configuration = create_emane_config(self.session)
|
||||
self.scenario.append(emane_global_configuration)
|
||||
emane_configurations = etree.Element("emane_configurations")
|
||||
for node_id, model_configs in self.session.emane.node_configs.items():
|
||||
node_id, iface_id = utils.parse_iface_config_id(node_id)
|
||||
|
@ -591,7 +575,6 @@ class CoreXmlReader:
|
|||
self.read_session_origin()
|
||||
self.read_service_configs()
|
||||
self.read_mobility_configs()
|
||||
self.read_emane_global_config()
|
||||
self.read_nodes()
|
||||
self.read_links()
|
||||
self.read_emane_configs()
|
||||
|
@ -729,28 +712,10 @@ class CoreXmlReader:
|
|||
files.add(name)
|
||||
service.configs = tuple(files)
|
||||
|
||||
def read_emane_global_config(self) -> None:
|
||||
emane_global_configuration = self.scenario.find("emane_global_configuration")
|
||||
if emane_global_configuration is None:
|
||||
return
|
||||
emulator_configuration = emane_global_configuration.find("emulator")
|
||||
configs = {}
|
||||
for config in emulator_configuration.iterchildren():
|
||||
name = config.get("name")
|
||||
value = config.get("value")
|
||||
configs[name] = value
|
||||
core_configuration = emane_global_configuration.find("core")
|
||||
for config in core_configuration.iterchildren():
|
||||
name = config.get("name")
|
||||
value = config.get("value")
|
||||
configs[name] = value
|
||||
self.session.emane.config = configs
|
||||
|
||||
def read_emane_configs(self) -> None:
|
||||
emane_configurations = self.scenario.find("emane_configurations")
|
||||
if emane_configurations is None:
|
||||
return
|
||||
|
||||
for emane_configuration in emane_configurations.iterchildren():
|
||||
node_id = get_int(emane_configuration, "node")
|
||||
iface_id = get_int(emane_configuration, "iface")
|
||||
|
@ -768,18 +733,21 @@ class CoreXmlReader:
|
|||
)
|
||||
|
||||
# read and set emane model configuration
|
||||
platform_configuration = emane_configuration.find("platform")
|
||||
for config in platform_configuration.iterchildren():
|
||||
name = config.get("name")
|
||||
value = config.get("value")
|
||||
configs[name] = value
|
||||
mac_configuration = emane_configuration.find("mac")
|
||||
for config in mac_configuration.iterchildren():
|
||||
name = config.get("name")
|
||||
value = config.get("value")
|
||||
configs[name] = value
|
||||
|
||||
phy_configuration = emane_configuration.find("phy")
|
||||
for config in phy_configuration.iterchildren():
|
||||
name = config.get("name")
|
||||
value = config.get("value")
|
||||
configs[name] = value
|
||||
|
||||
external_configuration = emane_configuration.find("external")
|
||||
for config in external_configuration.iterchildren():
|
||||
name = config.get("name")
|
||||
|
|
|
@ -12,13 +12,11 @@ from core.emulator.distributed import DistributedServer
|
|||
from core.errors import CoreError
|
||||
from core.nodes.base import CoreNode, CoreNodeBase
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import CtrlNet
|
||||
from core.xml import corexml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.emane.emanemanager import EmaneManager, StartData
|
||||
from core.emane.emanemodel import EmaneModel
|
||||
|
||||
_MAC_PREFIX = "02:02"
|
||||
|
@ -146,74 +144,67 @@ def add_configurations(
|
|||
|
||||
|
||||
def build_platform_xml(
|
||||
emane_manager: "EmaneManager", control_net: CtrlNet, data: "StartData"
|
||||
nem_id: int,
|
||||
nem_port: int,
|
||||
emane_net: EmaneNet,
|
||||
iface: CoreInterface,
|
||||
config: Dict[str, str],
|
||||
) -> None:
|
||||
"""
|
||||
Create platform xml for a specific node.
|
||||
Create platform xml for a nem/interface.
|
||||
|
||||
:param emane_manager: emane manager with emane
|
||||
configurations
|
||||
:param control_net: control net node for this emane
|
||||
network
|
||||
:param data: start data for a node connected to emane and associated interfaces
|
||||
:return: the next nem id that can be used for creating platform xml files
|
||||
:param nem_id: nem id for current node/interface
|
||||
:param nem_port: control port to configure for emane
|
||||
:param emane_net: emane network associate with node and interface
|
||||
:param iface: node interface to create platform xml for
|
||||
:param config: emane configuration for interface
|
||||
:return: nothing
|
||||
"""
|
||||
# create top level platform element
|
||||
transport_configs = {"otamanagerdevice", "eventservicedevice"}
|
||||
platform_element = etree.Element("platform")
|
||||
for configuration in emane_manager.emane_config.emulator_config:
|
||||
for configuration in emane_net.model.platform_config:
|
||||
name = configuration.id
|
||||
if not isinstance(data.node, CoreNode) and name in transport_configs:
|
||||
value = control_net.brname
|
||||
else:
|
||||
value = emane_manager.config[name]
|
||||
value = config[configuration.id]
|
||||
add_param(platform_element, name, value)
|
||||
add_param(
|
||||
platform_element, emane_net.model.platform_controlport, f"0.0.0.0:{nem_port}"
|
||||
)
|
||||
|
||||
# create nem xml entries for all interfaces
|
||||
for iface in data.ifaces:
|
||||
emane_net = iface.net
|
||||
if not isinstance(emane_net, EmaneNet):
|
||||
raise CoreError(
|
||||
f"emane interface not connected to emane net: {emane_net.name}"
|
||||
)
|
||||
nem_id = emane_manager.next_nem_id()
|
||||
emane_manager.set_nem(nem_id, iface)
|
||||
emane_manager.write_nem(iface, nem_id)
|
||||
config = emane_manager.get_iface_config(emane_net, iface)
|
||||
emane_net.model.build_xml_files(config, iface)
|
||||
# build nem xml
|
||||
nem_definition = nem_file_name(iface)
|
||||
nem_element = etree.Element(
|
||||
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
|
||||
)
|
||||
|
||||
# build nem xml
|
||||
nem_definition = nem_file_name(iface)
|
||||
nem_element = etree.Element(
|
||||
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
|
||||
)
|
||||
# create model based xml files
|
||||
emane_net.model.build_xml_files(config, iface)
|
||||
|
||||
# check if this is an external transport
|
||||
if is_external(config):
|
||||
nem_element.set("transport", "external")
|
||||
platform_endpoint = "platformendpoint"
|
||||
add_param(nem_element, platform_endpoint, config[platform_endpoint])
|
||||
transport_endpoint = "transportendpoint"
|
||||
add_param(nem_element, transport_endpoint, config[transport_endpoint])
|
||||
# check if this is an external transport
|
||||
if is_external(config):
|
||||
nem_element.set("transport", "external")
|
||||
platform_endpoint = "platformendpoint"
|
||||
add_param(nem_element, platform_endpoint, config[platform_endpoint])
|
||||
transport_endpoint = "transportendpoint"
|
||||
add_param(nem_element, transport_endpoint, config[transport_endpoint])
|
||||
|
||||
# define transport element
|
||||
transport_name = transport_file_name(iface)
|
||||
transport_element = etree.SubElement(
|
||||
nem_element, "transport", definition=transport_name
|
||||
)
|
||||
add_param(transport_element, "device", iface.name)
|
||||
# define transport element
|
||||
transport_name = transport_file_name(iface)
|
||||
transport_element = etree.SubElement(
|
||||
nem_element, "transport", definition=transport_name
|
||||
)
|
||||
add_param(transport_element, "device", iface.name)
|
||||
|
||||
# add nem element to platform element
|
||||
platform_element.append(nem_element)
|
||||
# add nem element to platform element
|
||||
platform_element.append(nem_element)
|
||||
|
||||
# generate and assign interface mac address based on nem id
|
||||
mac = _MAC_PREFIX + ":00:00:"
|
||||
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
|
||||
iface.set_mac(mac)
|
||||
# generate and assign interface mac address based on nem id
|
||||
mac = _MAC_PREFIX + ":00:00:"
|
||||
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
|
||||
iface.set_mac(mac)
|
||||
|
||||
doc_name = "platform"
|
||||
file_name = f"{data.node.name}-platform.xml"
|
||||
create_node_file(data.node, platform_element, doc_name, file_name)
|
||||
file_name = platform_file_name(iface)
|
||||
create_node_file(iface.node, platform_element, doc_name, file_name)
|
||||
|
||||
|
||||
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
|
||||
|
@ -396,3 +387,7 @@ def phy_file_name(iface: CoreInterface) -> str:
|
|||
:return: phy xml file name
|
||||
"""
|
||||
return f"{iface.name}-phy.xml"
|
||||
|
||||
|
||||
def platform_file_name(iface: CoreInterface) -> str:
|
||||
return f"{iface.name}-platform.xml"
|
||||
|
|
|
@ -30,8 +30,9 @@ iface1 = iface_helper.create_iface(node2.id, 0)
|
|||
session.add_link(node1=node2, node2=emane, iface1=iface1)
|
||||
|
||||
# setup emane configurations using a dict mapping currently support values as strings
|
||||
session.set_emane({"eventservicettl": "2"})
|
||||
emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"})
|
||||
emane.set_emane_model(
|
||||
EmaneIeee80211abgModel.name, {"eventservicettl": "2", "unicastrate": "3"}
|
||||
)
|
||||
|
||||
# start session
|
||||
core.start_session(session)
|
||||
|
|
|
@ -95,10 +95,6 @@ service CoreApi {
|
|||
}
|
||||
|
||||
// emane rpc
|
||||
rpc GetEmaneConfig (emane.GetEmaneConfigRequest) returns (emane.GetEmaneConfigResponse) {
|
||||
}
|
||||
rpc SetEmaneConfig (emane.SetEmaneConfigRequest) returns (emane.SetEmaneConfigResponse) {
|
||||
}
|
||||
rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) {
|
||||
}
|
||||
rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) {
|
||||
|
@ -144,19 +140,18 @@ message StartSessionRequest {
|
|||
repeated Link links = 3;
|
||||
repeated Hook hooks = 4;
|
||||
SessionLocation location = 5;
|
||||
map<string, string> emane_config = 6;
|
||||
repeated wlan.WlanConfig wlan_configs = 7;
|
||||
repeated emane.EmaneModelConfig emane_model_configs = 8;
|
||||
repeated mobility.MobilityConfig mobility_configs = 9;
|
||||
repeated services.ServiceConfig service_configs = 10;
|
||||
repeated services.ServiceFileConfig service_file_configs = 11;
|
||||
repeated Link asymmetric_links = 12;
|
||||
repeated configservices.ConfigServiceConfig config_service_configs = 13;
|
||||
map<string, string> options = 14;
|
||||
string user = 15;
|
||||
bool definition = 16;
|
||||
map<string, string> metadata = 17;
|
||||
repeated Server servers = 18;
|
||||
repeated wlan.WlanConfig wlan_configs = 6;
|
||||
repeated emane.EmaneModelConfig emane_model_configs = 7;
|
||||
repeated mobility.MobilityConfig mobility_configs = 8;
|
||||
repeated services.ServiceConfig service_configs = 9;
|
||||
repeated services.ServiceFileConfig service_file_configs = 10;
|
||||
repeated Link asymmetric_links = 11;
|
||||
repeated configservices.ConfigServiceConfig config_service_configs = 12;
|
||||
map<string, string> options = 13;
|
||||
string user = 14;
|
||||
bool definition = 15;
|
||||
map<string, string> metadata = 16;
|
||||
repeated Server servers = 17;
|
||||
}
|
||||
|
||||
message StartSessionResponse {
|
||||
|
|
|
@ -4,23 +4,6 @@ package emane;
|
|||
|
||||
import "core/api/grpc/common.proto";
|
||||
|
||||
message GetEmaneConfigRequest {
|
||||
int32 session_id = 1;
|
||||
}
|
||||
|
||||
message GetEmaneConfigResponse {
|
||||
map<string, common.ConfigOption> config = 1;
|
||||
}
|
||||
|
||||
message SetEmaneConfigRequest {
|
||||
int32 session_id = 1;
|
||||
map<string, string> config = 2;
|
||||
}
|
||||
|
||||
message SetEmaneConfigResponse {
|
||||
bool result = 1;
|
||||
}
|
||||
|
||||
message GetEmaneModelConfigRequest {
|
||||
int32 session_id = 1;
|
||||
int32 node_id = 2;
|
||||
|
@ -50,6 +33,7 @@ message GetEmaneModelConfig {
|
|||
|
||||
message GetEmaneEventChannelRequest {
|
||||
int32 session_id = 1;
|
||||
int32 nem_id = 2;
|
||||
}
|
||||
|
||||
message GetEmaneEventChannelResponse {
|
||||
|
|
|
@ -61,6 +61,7 @@ eval "$ifcommand" | awk '
|
|||
/tmp\./ {print "removing interface " $1; system("ip link del " $1);}
|
||||
/gt\./ {print "removing interface " $1; system("ip link del " $1);}
|
||||
/b\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);}
|
||||
/ctrl[0-9]+\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);}
|
||||
'
|
||||
|
||||
nft list ruleset | awk '
|
||||
|
|
|
@ -83,11 +83,6 @@ class TestGrpc:
|
|||
scale=location_scale,
|
||||
)
|
||||
|
||||
# setup global emane config
|
||||
emane_config_key = "platform_id_start"
|
||||
emane_config_value = "2"
|
||||
session.set_emane({emane_config_key: emane_config_value})
|
||||
|
||||
# setup wlan config
|
||||
wlan_config_key = "range"
|
||||
wlan_config_value = "333"
|
||||
|
@ -151,7 +146,6 @@ class TestGrpc:
|
|||
location_alt,
|
||||
)
|
||||
assert real_session.location.refscale == location_scale
|
||||
assert real_session.emane.config[emane_config_key] == emane_config_value
|
||||
set_wlan_config = real_session.mobility.get_model_config(
|
||||
wlan_node.id, BasicRangeModel.name
|
||||
)
|
||||
|
@ -518,35 +512,6 @@ class TestGrpc:
|
|||
assert config[range_key] == range_value
|
||||
assert wlan.model.range == int(range_value)
|
||||
|
||||
def test_get_emane_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
config = client.get_emane_config(session.id)
|
||||
|
||||
# then
|
||||
assert len(config) > 0
|
||||
|
||||
def test_set_emane_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
config_key = "platform_id_start"
|
||||
config_value = "2"
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
result = client.set_emane_config(session.id, {config_key: config_value})
|
||||
|
||||
# then
|
||||
assert result is True
|
||||
config = session.emane.config
|
||||
assert len(config) > 1
|
||||
assert config[config_key] == config_value
|
||||
|
||||
def test_set_emane_model_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
|
|
|
@ -941,35 +941,3 @@ class TestGui:
|
|||
|
||||
config = coretlv.session.emane.get_config(wlan.id, EmaneIeee80211abgModel.name)
|
||||
assert config[config_key] == config_value
|
||||
|
||||
def test_config_emane_request(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
(ConfigTlvs.OBJECT, "emane"),
|
||||
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
|
||||
],
|
||||
)
|
||||
coretlv.handle_broadcast_config = mock.MagicMock()
|
||||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
coretlv.handle_broadcast_config.assert_called_once()
|
||||
|
||||
def test_config_emane_update(self, coretlv: CoreHandler):
|
||||
config_key = "eventservicedevice"
|
||||
config_value = "eth4"
|
||||
values = {config_key: config_value}
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
(ConfigTlvs.OBJECT, "emane"),
|
||||
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
|
||||
(ConfigTlvs.VALUES, dict_to_str(values)),
|
||||
],
|
||||
)
|
||||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
config = coretlv.session.emane.config
|
||||
assert config[config_key] == config_value
|
||||
|
|
|
@ -328,10 +328,11 @@ session.add_link(node1=node1, node2=emane, iface1=iface1)
|
|||
iface1 = iface_helper.create_iface(node2.id, 0)
|
||||
session.add_link(node1=node2, node2=emane, iface1=iface1)
|
||||
|
||||
# setting global emane configuration
|
||||
session.set_emane({"eventservicettl": "2"})
|
||||
# setting emane specific emane model configuration
|
||||
emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"})
|
||||
emane.set_emane_model(EmaneIeee80211abgModel.name, {
|
||||
"eventservicettl": "2",
|
||||
"unicastrate": "3",
|
||||
})
|
||||
|
||||
# start session
|
||||
core.start_session(session)
|
||||
|
|
|
@ -541,13 +541,7 @@ proc wlanConfigDialogHelper { wi target apply } {
|
|||
ttk::button $opts.model -text "model options" \
|
||||
-image $plugin_img_edit -compound right -command "" -state disabled \
|
||||
-command "configCap $target \[set g_selected_model\]"
|
||||
# global EMANE model uses no node in config request message, although any
|
||||
# config will be stored with the EMANE node having the lowest ID
|
||||
ttk::button $opts.gen -text "EMANE options" \
|
||||
-image $plugin_img_edit -compound right \
|
||||
-command "configCap -1 emane"
|
||||
#-command "popupPluginsCapConfigHelper $wi config $target"
|
||||
pack $opts.model $opts.gen -side left -padx 4 -pady 4
|
||||
pack $opts.model -side left -padx 4 -pady 4
|
||||
pack $opts -side top -anchor c -padx 4 -pady 4
|
||||
|
||||
# show correct tab basic/emane based on selection
|
||||
|
|
Loading…
Reference in a new issue