Merge pull request #573 from coreemu/enhancement/emane-nem-process

EMANE process per nem
This commit is contained in:
bharnden 2021-05-26 16:08:51 -07:00 committed by GitHub
commit b508ad6406
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 481 additions and 881 deletions

View file

@ -28,10 +28,8 @@ from core.api.grpc.configservices_pb2 import (
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest
from core.api.grpc.emane_pb2 import ( from core.api.grpc.emane_pb2 import (
EmaneLinkRequest, EmaneLinkRequest,
GetEmaneConfigRequest,
GetEmaneEventChannelRequest, GetEmaneEventChannelRequest,
GetEmaneModelConfigRequest, GetEmaneModelConfigRequest,
SetEmaneConfigRequest,
SetEmaneModelConfigRequest, SetEmaneModelConfigRequest,
) )
from core.api.grpc.mobility_pb2 import ( from core.api.grpc.mobility_pb2 import (
@ -253,7 +251,6 @@ class CoreGrpcClient:
if asymmetric_links: if asymmetric_links:
asymmetric_links = [x.to_proto() for x in asymmetric_links] asymmetric_links = [x.to_proto() for x in asymmetric_links]
hooks = [x.to_proto() for x in session.hooks.values()] 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 = [] emane_model_configs = []
mobility_configs = [] mobility_configs = []
wlan_configs = [] wlan_configs = []
@ -313,7 +310,6 @@ class CoreGrpcClient:
links=links, links=links,
location=session.location.to_proto(), location=session.location.to_proto(),
hooks=hooks, hooks=hooks,
emane_config=emane_config,
emane_model_configs=emane_model_configs, emane_model_configs=emane_model_configs,
wlan_configs=wlan_configs, wlan_configs=wlan_configs,
mobility_configs=mobility_configs, mobility_configs=mobility_configs,
@ -917,31 +913,6 @@ class CoreGrpcClient:
response = self.stub.SetWlanConfig(request) response = self.stub.SetWlanConfig(request)
return response.result 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( def get_emane_model_config(
self, session_id: int, node_id: int, model: str, iface_id: int = -1 self, session_id: int, node_id: int, model: str, iface_id: int = -1
) -> Dict[str, wrappers.ConfigOption]: ) -> Dict[str, wrappers.ConfigOption]:
@ -1063,15 +1034,18 @@ class CoreGrpcClient:
response = self.stub.GetNodeConfigService(request) response = self.stub.GetNodeConfigService(request)
return dict(response.config) 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. Retrieves the current emane event channel being used for a session.
:param session_id: session to get emane event channel for :param session_id: session to get emane event channel for
:param nem_id: nem id for the desired event channel
:return: emane event channel :return: emane event channel
:raises grpc.RpcError: when session doesn't exist :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) response = self.stub.GetEmaneEventChannel(request)
return wrappers.EmaneEventChannel.from_proto(response) return wrappers.EmaneEventChannel.from_proto(response)

View file

@ -630,10 +630,6 @@ def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfi
return configs 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( def get_mobility_node(
session: Session, node_id: int, context: ServicerContext session: Session, node_id: int, context: ServicerContext
) -> Union[WlanNode, EmaneNet]: ) -> 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 x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale
) )
hooks = get_hooks(session) hooks = get_hooks(session)
emane_config = get_emane_config(session)
emane_model_configs = get_emane_model_configs(session) emane_model_configs = get_emane_model_configs(session)
wlan_configs = get_wlan_configs(session) wlan_configs = get_wlan_configs(session)
mobility_configs = get_mobility_configs(session) mobility_configs = get_mobility_configs(session)
@ -685,7 +680,6 @@ def convert_session(session: Session) -> wrappers.Session:
default_services=default_services, default_services=default_services,
location=location, location=location,
hooks=hooks, hooks=hooks,
emane_config=emane_config,
emane_model_configs=emane_model_configs, emane_model_configs=emane_model_configs,
wlan_configs=wlan_configs, wlan_configs=wlan_configs,
service_configs=service_configs, service_configs=service_configs,

View file

@ -33,14 +33,10 @@ from core.api.grpc.emane_pb2 import (
EmaneLinkResponse, EmaneLinkResponse,
EmanePathlossesRequest, EmanePathlossesRequest,
EmanePathlossesResponse, EmanePathlossesResponse,
GetEmaneConfigRequest,
GetEmaneConfigResponse,
GetEmaneEventChannelRequest, GetEmaneEventChannelRequest,
GetEmaneEventChannelResponse, GetEmaneEventChannelResponse,
GetEmaneModelConfigRequest, GetEmaneModelConfigRequest,
GetEmaneModelConfigResponse, GetEmaneModelConfigResponse,
SetEmaneConfigRequest,
SetEmaneConfigResponse,
SetEmaneModelConfigRequest, SetEmaneModelConfigRequest,
SetEmaneModelConfigResponse, SetEmaneModelConfigResponse,
) )
@ -266,7 +262,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
# emane configs # emane configs
session.emane.config.update(request.emane_config)
for config in request.emane_model_configs: for config in request.emane_model_configs:
_id = utils.iface_config_id(config.node_id, config.iface_id) _id = utils.iface_config_id(config.node_id, config.iface_id)
session.emane.set_config(_id, config.model, config.config) session.emane.set_config(_id, config.model, config.config)
@ -1045,36 +1040,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node.updatemodel(config) node.updatemodel(config)
return SetWlanConfigResponse(result=True) 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( def GetEmaneModelConfig(
self, request: GetEmaneModelConfigRequest, context: ServicerContext self, request: GetEmaneModelConfigRequest, context: ServicerContext
) -> GetEmaneModelConfigResponse: ) -> GetEmaneModelConfigResponse:
@ -1276,12 +1241,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
self, request: GetEmaneEventChannelRequest, context: ServicerContext self, request: GetEmaneEventChannelRequest, context: ServicerContext
) -> GetEmaneEventChannelResponse: ) -> GetEmaneEventChannelResponse:
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
group = None service = session.emane.nem_service.get(request.nem_id)
port = None if not service:
device = None context.abort(grpc.StatusCode.NOT_FOUND, f"unknown nem id {request.nem_id}")
if session.emane.eventchannel: return GetEmaneEventChannelResponse(
group, port, device = session.emane.eventchannel group=service.group, port=service.port, device=service.device
return GetEmaneEventChannelResponse(group=group, port=port, device=device) )
def ExecuteScript(self, request, context): def ExecuteScript(self, request, context):
existing_sessions = set(self.coreemu.sessions.keys()) existing_sessions = set(self.coreemu.sessions.keys())

View file

@ -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 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) 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) metadata: Dict[str, str] = field(default_factory=dict)
file: Path = None file: Path = None
options: Dict[str, ConfigOption] = field(default_factory=dict) options: Dict[str, ConfigOption] = field(default_factory=dict)
@ -836,7 +835,6 @@ class Session:
default_services=default_services, default_services=default_services,
location=SessionLocation.from_proto(proto.location), location=SessionLocation.from_proto(proto.location),
hooks=hooks, hooks=hooks,
emane_config=ConfigOption.from_dict(proto.emane_config),
metadata=dict(proto.metadata), metadata=dict(proto.metadata),
file=file_path, file=file_path,
options=options, options=options,
@ -889,11 +887,6 @@ class Session:
self.links.append(link) self.links.append(link)
return 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: def set_options(self, config: Dict[str, str]) -> None:
for key, value in config.items(): for key, value in config.items():
option = ConfigOption(name=key, value=value) option = ConfigOption(name=key, value=value)

View file

@ -1046,8 +1046,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
self.handle_config_mobility(message_type, config_data) self.handle_config_mobility(message_type, config_data)
elif config_data.object in self.session.mobility.models: elif config_data.object in self.session.mobility.models:
replies = self.handle_config_mobility_models(message_type, config_data) 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: elif config_data.object in EmaneModelManager.models:
replies = self.handle_config_emane_models(message_type, config_data) replies = self.handle_config_emane_models(message_type, config_data)
else: else:
@ -1379,36 +1377,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
return replies 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): def handle_config_emane_models(self, message_type, config_data):
replies = [] replies = []
node_id = config_data.node node_id = config_data.node
@ -1851,14 +1819,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
) )
self.session.broadcast_config(config_data) 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 # send emane model configs
for node_id, model_configs in self.session.emane.node_configs.items(): for node_id, model_configs in self.session.emane.node_configs.items():
for model_name, config in model_configs.items(): for model_name, config in model_configs.items():

View file

@ -227,12 +227,6 @@ class Ospfv3mdr(Ospfv3):
name: str = "OSPFv3MDR" 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: def quagga_iface_config(self, iface: CoreInterface) -> str:
config = super().quagga_iface_config(iface) config = super().quagga_iface_config(iface)
if isinstance(iface.net, (WlanNode, EmaneNet)): if isinstance(iface.net, (WlanNode, EmaneNet)):

View file

@ -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 logging
import os import os
import threading import threading
from collections import OrderedDict
from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
from core import utils from core import utils
from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest
from core.emane.emanemodel import EmaneModel from core.emane.emanemodel import EmaneModel
from core.emane.linkmonitor import EmaneLinkMonitor from core.emane.linkmonitor import EmaneLinkMonitor
from core.emane.modelmanager import EmaneModelManager from core.emane.modelmanager import EmaneModelManager
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData from core.emulator.data import LinkData
from core.emulator.enumerations import ( from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs
ConfigDataTypes,
LinkTypes,
MessageFlags,
RegisterTlvs,
)
from core.errors import CoreCommandError, CoreError 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.nodes.interface import CoreInterface, TunTap
from core.xml import emanexml from core.xml import emanexml
@ -36,15 +26,19 @@ if TYPE_CHECKING:
from core.emulator.session import Session from core.emulator.session import Session
try: try:
from emane.events import EventService, PathlossEvent from emane.events import EventService, PathlossEvent, CommEffectEvent, LocationEvent
from emane.events import LocationEvent
from emane.events.eventserviceexception import EventServiceException from emane.events.eventserviceexception import EventServiceException
except ImportError: except ImportError:
try: try:
from emanesh.events import EventService from emanesh.events import (
from emanesh.events import LocationEvent EventService,
PathlossEvent,
CommEffectEvent,
LocationEvent,
)
from emanesh.events.eventserviceexception import EventServiceException from emanesh.events.eventserviceexception import EventServiceException
except ImportError: except ImportError:
CommEffectEvent = None
EventService = None EventService = None
LocationEvent = None LocationEvent = None
PathlossEvent = None PathlossEvent = None
@ -62,10 +56,57 @@ class EmaneState(Enum):
NOT_READY = 2 NOT_READY = 2
@dataclass class EmaneEventService:
class StartData: def __init__(
node: CoreNodeBase self, manager: "EmaneManager", device: str, group: str, port: int
ifaces: List[CoreInterface] = field(default_factory=list) ) -> 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: class EmaneManager:
@ -102,22 +143,22 @@ class EmaneManager:
self.eventmonthread: Optional[threading.Thread] = None self.eventmonthread: Optional[threading.Thread] = None
# model for global EMANE configuration options # 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_configs: Dict[int, Dict[str, Dict[str, str]]] = {}
self.node_models: Dict[int, str] = {} self.node_models: Dict[int, str] = {}
# link monitor # link monitor
self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self) 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 def next_nem_id(self, iface: CoreInterface) -> int:
self.eventchannel: Optional[Tuple[str, int, str]] = None nem_id = self.session.options.get_config_int("nem_id_start")
self.event_device: Optional[str] = None
def next_nem_id(self) -> int:
nem_id = int(self.config["nem_id_start"])
while nem_id in self.nems_to_ifaces: while nem_id in self.nems_to_ifaces:
nem_id += 1 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 return nem_id
def get_config( def get_config(
@ -203,60 +244,12 @@ class EmaneManager:
def config_reset(self, node_id: int = None) -> None: def config_reset(self, node_id: int = None) -> None:
if node_id is None: if node_id is None:
self.config = self.emane_config.default_values()
self.node_configs.clear() self.node_configs.clear()
self.node_models.clear() self.node_models.clear()
else: else:
self.node_configs.get(node_id, {}).clear() self.node_configs.get(node_id, {}).clear()
self.node_models.pop(node_id, None) 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: def add_node(self, emane_net: EmaneNet) -> None:
""" """
Add EMANE network object to this manager. Add EMANE network object to this manager.
@ -301,41 +294,9 @@ class EmaneManager:
if not self._emane_nets: if not self._emane_nets:
logger.debug("no emane nodes in session") logger.debug("no emane nodes in session")
return EmaneState.NOT_NEEDED return EmaneState.NOT_NEEDED
# check if bindings were installed # check if bindings were installed
if EventService is None: if EventService is None:
raise CoreError("EMANE python bindings are not installed") 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() self.check_node_models()
return EmaneState.SUCCESS return EmaneState.SUCCESS
@ -351,21 +312,35 @@ class EmaneManager:
status = self.setup() status = self.setup()
if status != EmaneState.SUCCESS: if status != EmaneState.SUCCESS:
return status return status
self.starteventmonitor() self.startup_nodes()
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)
if self.links_enabled(): if self.links_enabled():
self.link_monitor.start() self.link_monitor.start()
return EmaneState.SUCCESS return EmaneState.SUCCESS
def get_start_data(self) -> List[StartData]: def startup_nodes(self) -> None:
node_map = {} with self._emane_node_lock:
for node_id in sorted(self._emane_nets): logger.info("emane building xmls...")
emane_net = self._emane_nets[node_id] 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: if not emane_net.model:
logger.error("emane net(%s) has no model", emane_net.name) logger.error("emane net(%s) has no model", emane_net.name)
continue continue
@ -377,27 +352,60 @@ class EmaneManager:
iface.name, iface.name,
) )
continue continue
start_node = node_map.setdefault(iface.node, StartData(iface.node)) ifaces.append((emane_net, iface))
start_node.ifaces.append(iface) return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].node_id))
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
def start_node(self, data: StartData) -> None: def setup_control_channels(
control_net = self.session.add_remove_control_net( self, nem_id: int, iface: CoreInterface, config: Dict[str, str]
0, remove=False, conf_required=False ) -> 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) if isinstance(node, CoreNode):
self.start_daemon(data.node) self.session.add_remove_control_iface(
for iface in data.ifaces: node, event_index, conf_required=False
self.install_iface(iface) )
# initialize emane event services
def set_nem(self, nem_id: int, iface: CoreInterface) -> None: service = self.services.get(event_net.brname)
if nem_id in self.nems_to_ifaces: if not service:
raise CoreError(f"adding duplicate nem: {nem_id}") try:
self.nems_to_ifaces[nem_id] = iface service = EmaneEventService(
self.ifaces_to_nems[iface] = nem_id 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]: def get_iface(self, nem_id: int) -> Optional[CoreInterface]:
return self.nems_to_ifaces.get(nem_id) return self.nems_to_ifaces.get(nem_id)
@ -405,6 +413,68 @@ class EmaneManager:
def get_nem_id(self, iface: CoreInterface) -> Optional[int]: def get_nem_id(self, iface: CoreInterface) -> Optional[int]:
return self.ifaces_to_nems.get(iface) 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: def write_nem(self, iface: CoreInterface, nem_id: int) -> None:
path = self.session.directory / "emane_nems" path = self.session.directory / "emane_nems"
try: try:
@ -414,23 +484,23 @@ class EmaneManager:
logger.exception("error writing to emane nem file") logger.exception("error writing to emane nem file")
def links_enabled(self) -> bool: 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: def poststartup(self) -> None:
""" """
Retransmit location events now that all NEMs are active. Retransmit location events now that all NEMs are active.
""" """
if not self.genlocationevents(): events_enabled = self.genlocationevents()
return
with self._emane_node_lock: with self._emane_node_lock:
for node_id in sorted(self._emane_nets): for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id] emane_net = self._emane_nets[node_id]
logger.debug( logger.debug(
"post startup for emane node: %s - %s", emane_net.id, emane_net.name "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(): for iface in emane_net.get_ifaces():
iface.setposition() emane_net.model.post_startup(iface)
if events_enabled:
iface.setposition()
def reset(self) -> None: def reset(self) -> None:
""" """
@ -441,6 +511,8 @@ class EmaneManager:
self._emane_nets.clear() self._emane_nets.clear()
self.nems_to_ifaces.clear() self.nems_to_ifaces.clear()
self.ifaces_to_nems.clear() self.ifaces_to_nems.clear()
self.nems_to_ifaces.clear()
self.services.clear()
def shutdown(self) -> None: def shutdown(self) -> None:
""" """
@ -452,22 +524,23 @@ class EmaneManager:
logger.info("stopping EMANE daemons") logger.info("stopping EMANE daemons")
if self.links_enabled(): if self.links_enabled():
self.link_monitor.stop() self.link_monitor.stop()
# shutdown interfaces and stop daemons # shutdown interfaces
kill_emaned = "killall -q emane" for _, iface in self.get_ifaces():
start_data = self.get_start_data() node = iface.node
for data in start_data:
node = data.node
if not node.up: if not node.up:
continue continue
for iface in data.ifaces: kill_cmd = f'pkill -f "emane.+{iface.name}"'
if isinstance(node, CoreNode):
iface.shutdown()
iface.poshook = None
if isinstance(node, CoreNode): if isinstance(node, CoreNode):
node.cmd(kill_emaned, wait=False) iface.shutdown()
node.cmd(kill_cmd, wait=False)
else: else:
node.host_cmd(kill_emaned, wait=False) node.host_cmd(kill_cmd, wait=False)
self.stopeventmonitor() 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: def check_node_models(self) -> None:
""" """
@ -520,41 +593,14 @@ class EmaneManager:
color=color, color=color,
) )
def buildeventservicexml(self) -> None: def start_daemon(self, iface: CoreInterface) -> None:
""" """
Build the libemaneeventservice.xml file if event service options Start emane daemon for a given nem/interface.
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
)
)
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. node = iface.node
Add a control network even if the user has not configured one.
"""
logger.info("starting emane daemons...")
loglevel = str(DEFAULT_LOG_LEVEL) loglevel = str(DEFAULT_LOG_LEVEL)
cfgloglevel = self.session.options.get_config_int("emane_log_level") cfgloglevel = self.session.options.get_config_int("emane_log_level")
realtime = self.session.options.get_config_bool("emane_realtime", default=True) realtime = self.session.options.get_config_bool("emane_realtime", default=True)
@ -565,60 +611,25 @@ class EmaneManager:
if realtime: if realtime:
emanecmd += " -r" emanecmd += " -r"
if isinstance(node, CoreNode): 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 # start emane
log_file = node.directory / f"{node.name}-emane.log" log_file = node.directory / f"{iface.name}-emane.log"
platform_xml = node.directory / f"{node.name}-platform.xml" platform_xml = node.directory / emanexml.platform_file_name(iface)
args = f"{emanecmd} -f {log_file} {platform_xml}" args = f"{emanecmd} -f {log_file} {platform_xml}"
node.cmd(args) node.cmd(args)
logger.info("node(%s) emane daemon running: %s", node.name, args)
else: else:
log_file = self.session.directory / f"{node.name}-emane.log" log_file = self.session.directory / f"{iface.name}-emane.log"
platform_xml = self.session.directory / f"{node.name}-platform.xml" platform_xml = self.session.directory / emanexml.platform_file_name(iface)
args = f"{emanecmd} -f {log_file} {platform_xml}" args = f"{emanecmd} -f {log_file} {platform_xml}"
node.host_cmd(args, cwd=self.session.directory) 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: def install_iface(self, iface: CoreInterface, config: Dict[str, str]) -> 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)
external = config.get("external", "0") external = config.get("external", "0")
if isinstance(iface, TunTap) and external == "0": if isinstance(iface, TunTap) and external == "0":
iface.set_ips() iface.set_ips()
# at this point we register location handlers for generating # at this point we register location handlers for generating
# EMANE location events # EMANE location events
if self.genlocationevents(): if self.genlocationevents():
iface.poshook = emane_net.setnemposition iface.poshook = self.set_nem_position
iface.setposition() iface.setposition()
def doeventmonitor(self) -> bool: def doeventmonitor(self) -> bool:
@ -640,68 +651,6 @@ class EmaneManager:
tmp = not self.doeventmonitor() tmp = not self.doeventmonitor()
return tmp 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: def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
""" """
Handle an EMANE location event. Handle an EMANE location event.
@ -717,7 +666,6 @@ class EmaneManager:
): ):
logger.warning("dropped invalid location event") logger.warning("dropped invalid location event")
continue continue
# yaw,pitch,roll,azimuth,elevation,velocity are unhandled # yaw,pitch,roll,azimuth,elevation,velocity are unhandled
lat = attrs["latitude"] lat = attrs["latitude"]
lon = attrs["longitude"] lon = attrs["longitude"]
@ -812,87 +760,19 @@ class EmaneManager:
event = PathlossEvent() event = PathlossEvent()
event.append(nem1, forward=rx1) event.append(nem1, forward=rx1)
event.append(nem2, forward=rx2) event.append(nem2, forward=rx2)
self.service.publish(nem1, event) self.publish_event(nem1, event)
self.service.publish(nem2, event) self.publish_event(nem2, event)
def publish_event(
class EmaneGlobalModel: self,
""" nem_id: int,
Global EMANE configuration options. event: Union[PathlossEvent, CommEffectEvent, LocationEvent],
""" send_all: bool = False,
) -> None:
name: str = "emane" service = self.nem_service.get(nem_id)
bitmap: Optional[str] = None if not service:
logger.error("no service to publish event nem(%s)", nem_id)
def __init__(self, session: "Session") -> None: return
self.session: "Session" = session if send_all:
self.core_config: List[Configuration] = [ nem_id = 0
Configuration( service.events.publish(nem_id, event)
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()]
)

View file

@ -7,7 +7,6 @@ from typing import Dict, List, Optional, Set
from core.config import ConfigGroup, Configuration from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest from core.emane import emanemanifest
from core.emane.nodes import EmaneNet
from core.emulator.data import LinkOptions from core.emulator.data import LinkOptions
from core.emulator.enumerations import ConfigDataTypes from core.emulator.enumerations import ConfigDataTypes
from core.errors import CoreError from core.errors import CoreError
@ -16,6 +15,8 @@ from core.nodes.interface import CoreInterface
from core.xml import emanexml from core.xml import emanexml
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
DEFAULT_DEV: str = "ctrl0"
MANIFEST_PATH: str = "share/emane/manifest"
class EmaneModel(WirelessModel): class EmaneModel(WirelessModel):
@ -25,6 +26,17 @@ class EmaneModel(WirelessModel):
configurable parameters. Helper functions also live here. 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 # default mac configuration settings
mac_library: Optional[str] = None mac_library: Optional[str] = None
mac_xml: Optional[str] = None mac_xml: Optional[str] = None
@ -57,20 +69,35 @@ class EmaneModel(WirelessModel):
@classmethod @classmethod
def load(cls, emane_prefix: Path) -> None: def load(cls, emane_prefix: Path) -> None:
""" """
Called after being loaded within the EmaneManager. Provides configured emane_prefix for Called after being loaded within the EmaneManager. Provides configured
parsing xml files. emane_prefix for parsing xml files.
:param emane_prefix: configured emane prefix path :param emane_prefix: configured emane prefix path
:return: nothing :return: nothing
""" """
manifest_path = "share/emane/manifest" cls._load_platform_config(emane_prefix)
# load mac configuration # 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) cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults)
# load phy configuration # 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) 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 @classmethod
def configurations(cls) -> List[Configuration]: def configurations(cls) -> List[Configuration]:
""" """
@ -78,7 +105,9 @@ class EmaneModel(WirelessModel):
:return: all configurations :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 @classmethod
def config_groups(cls) -> List[ConfigGroup]: def config_groups(cls) -> List[ConfigGroup]:
@ -87,11 +116,13 @@ class EmaneModel(WirelessModel):
:return: list of configuration groups. :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 phy_len = len(cls.phy_config) + mac_len
config_len = len(cls.configurations()) config_len = len(cls.configurations())
return [ 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("PHY Parameters", mac_len + 1, phy_len),
ConfigGroup("External Parameters", phy_len + 1, config_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_phy_xml(self, iface, config)
emanexml.create_transport_xml(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. Logic to execute after the emane manager is finished with startup.
:param iface: interface for post startup
:return: nothing :return: nothing
""" """
logger.debug("emane model(%s) has no post setup tasks", self.name) logger.debug("emane model(%s) has no post setup tasks", self.name)
@ -129,8 +161,7 @@ class EmaneModel(WirelessModel):
:return: nothing :return: nothing
""" """
try: try:
emane_net = self.session.get_node(self.id, EmaneNet) self.session.emane.set_nem_positions(moved_ifaces)
emane_net.setnempositions(moved_ifaces)
except CoreError: except CoreError:
logger.exception("error during update") logger.exception("error during update")

View file

@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from lxml import etree from lxml import etree
from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData from core.emulator.data import LinkData
from core.emulator.enumerations import LinkTypes, MessageFlags from core.emulator.enumerations import LinkTypes, MessageFlags
from core.nodes.network import CtrlNet from core.nodes.network import CtrlNet
@ -24,7 +25,6 @@ except ImportError:
if TYPE_CHECKING: if TYPE_CHECKING:
from core.emane.emanemanager import EmaneManager from core.emane.emanemanager import EmaneManager
DEFAULT_PORT: int = 47_000
MAC_COMPONENT_INDEX: int = 1 MAC_COMPONENT_INDEX: int = 1
EMANE_RFPIPE: str = "rfpipemaclayer" EMANE_RFPIPE: str = "rfpipemaclayer"
EMANE_80211: str = "ieee80211abgmaclayer" EMANE_80211: str = "ieee80211abgmaclayer"
@ -79,10 +79,10 @@ class EmaneLink:
class EmaneClient: class EmaneClient:
def __init__(self, address: str) -> None: def __init__(self, address: str, port: int) -> None:
self.address: str = address self.address: str = address
self.client: shell.ControlPortClient = shell.ControlPortClient( self.client: shell.ControlPortClient = shell.ControlPortClient(
self.address, DEFAULT_PORT self.address, port
) )
self.nems: Dict[int, LossTable] = {} self.nems: Dict[int, LossTable] = {}
self.setup() self.setup()
@ -189,9 +189,10 @@ class EmaneLinkMonitor:
self.running: bool = False self.running: bool = False
def start(self) -> None: def start(self) -> None:
self.loss_threshold = int(self.emane_manager.config["loss_threshold"]) options = self.emane_manager.session.options
self.link_interval = int(self.emane_manager.config["link_interval"]) self.loss_threshold = options.get_config_int("loss_threshold")
self.link_timeout = int(self.emane_manager.config["link_timeout"]) self.link_interval = options.get_config_int("link_interval")
self.link_timeout = options.get_config_int("link_timeout")
self.initialize() self.initialize()
if not self.clients: if not self.clients:
logger.info("no valid emane models to monitor links") logger.info("no valid emane models to monitor links")
@ -204,22 +205,28 @@ class EmaneLinkMonitor:
def initialize(self) -> None: def initialize(self) -> None:
addresses = self.get_addresses() addresses = self.get_addresses()
for address in addresses: for address, port in addresses:
client = EmaneClient(address) client = EmaneClient(address, port)
if client.nems: if client.nems:
self.clients.append(client) self.clients.append(client)
def get_addresses(self) -> List[str]: def get_addresses(self) -> List[Tuple[str, int]]:
addresses = [] addresses = []
nodes = self.emane_manager.getnodes() nodes = self.emane_manager.getnodes()
for node in nodes: for node in nodes:
control = None
ports = []
for iface in node.get_ifaces(): for iface in node.get_ifaces():
if isinstance(iface.net, CtrlNet): if isinstance(iface.net, CtrlNet):
ip4 = iface.get_ip4() ip4 = iface.get_ip4()
if ip4: if ip4:
address = str(ip4.ip) control = str(ip4.ip)
addresses.append(address) if isinstance(iface.net, EmaneNet):
break port = self.emane_manager.get_nem_port(iface)
ports.append(port)
if control:
for port in ports:
addresses.append((control, port))
return addresses return addresses
def check_links(self) -> None: def check_links(self) -> None:

View file

@ -1,6 +1,7 @@
""" """
EMANE Bypass model for CORE EMANE Bypass model for CORE
""" """
from pathlib import Path
from typing import List, Set from typing import List, Set
from core.config import Configuration from core.config import Configuration
@ -30,6 +31,5 @@ class EmaneBypassModel(emanemodel.EmaneModel):
phy_config: List[Configuration] = [] phy_config: List[Configuration] = []
@classmethod @classmethod
def load(cls, emane_prefix: str) -> None: def load(cls, emane_prefix: Path) -> None:
# ignore default logic cls._load_platform_config(emane_prefix)
pass

View file

@ -51,16 +51,25 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
@classmethod @classmethod
def load(cls, emane_prefix: Path) -> None: def load(cls, emane_prefix: Path) -> None:
cls._load_platform_config(emane_prefix)
shim_xml_path = emane_prefix / "share/emane/manifest" / cls.shim_xml shim_xml_path = emane_prefix / "share/emane/manifest" / cls.shim_xml
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults) cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
@classmethod @classmethod
def configurations(cls) -> List[Configuration]: def configurations(cls) -> List[Configuration]:
return cls.config_shim return cls.platform_config + cls.config_shim
@classmethod @classmethod
def config_groups(cls) -> List[ConfigGroup]: 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: 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 Generate CommEffect events when a Link Message is received having
link parameters. 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: if iface is None or iface2 is None:
logger.warning("%s: missing NEM information", self.name) logger.warning("%s: missing NEM information", self.name)
return return
# TODO: batch these into multiple events per transmission # TODO: batch these into multiple events per transmission
# TODO: may want to split out seconds portion of delay and jitter # TODO: may want to split out seconds portion of delay and jitter
event = CommEffectEvent() event = CommEffectEvent()
@ -137,4 +140,4 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
unicast=int(convert_none(options.bandwidth)), unicast=int(convert_none(options.bandwidth)),
broadcast=int(convert_none(options.bandwidth)), broadcast=int(convert_none(options.bandwidth)),
) )
service.publish(nem2, event) self.session.emane.publish_event(nem2, event)

View file

@ -9,7 +9,9 @@ from typing import Set
from core import constants, utils from core import constants, utils
from core.config import Configuration from core.config import Configuration
from core.emane import emanemodel from core.emane import emanemodel
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import ConfigDataTypes from core.emulator.enumerations import ConfigDataTypes
from core.nodes.interface import CoreInterface
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -44,23 +46,23 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
) )
cls.mac_config.insert(0, config_item) cls.mac_config.insert(0, config_item)
def post_startup(self) -> None: def post_startup(self, iface: CoreInterface) -> None:
"""
Logic to execute after the emane manager is finished with startup.
:return: nothing
"""
# get configured schedule # get configured schedule
config = self.session.emane.get_config(self.id, self.name) emane_net = self.session.get_node(self.id, EmaneNet)
if not config: config = self.session.emane.get_iface_config(emane_net, iface)
return
schedule = Path(config[self.schedule_name]) schedule = Path(config[self.schedule_name])
if not schedule.is_file(): if not schedule.is_file():
logger.warning("ignoring invalid tdma schedule: %s", schedule) logger.error("ignoring invalid tdma schedule: %s", schedule)
return return
# initiate tdma schedule # initiate tdma schedule
event_device = self.session.emane.event_device nem_id = self.session.emane.get_nem_id(iface)
logger.info( if not nem_id:
"setting up tdma schedule: schedule(%s) device(%s)", schedule, event_device logger.error("could not find nem for interface")
) return
utils.cmd(f"emaneevent-tdmaschedule -i {event_device} {schedule}") 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}")

View file

@ -4,7 +4,7 @@ share the same MAC+PHY model.
""" """
import logging 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.data import InterfaceData, LinkData, LinkOptions
from core.emulator.distributed import DistributedServer from core.emulator.distributed import DistributedServer
@ -110,67 +110,6 @@ class EmaneNet(CoreNetworkBase):
self.mobility = model(session=self.session, _id=self.id) self.mobility = model(session=self.session, _id=self.id)
self.mobility.update_config(config) 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]: def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
links = super().links(flags) links = super().links(flags)
emane_manager = self.session.emane emane_manager = self.session.emane

View file

@ -59,6 +59,42 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
Configuration( Configuration(
id="ovs", type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" 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 config_type: RegisterTlvs = RegisterTlvs.UTILITY

View file

@ -19,40 +19,6 @@ if TYPE_CHECKING:
from core.gui.app import Application 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): class EmaneModelDialog(Dialog):
def __init__( def __init__(
self, self,
@ -180,8 +146,7 @@ class EmaneConfigDialog(Dialog):
def draw_emane_buttons(self) -> None: def draw_emane_buttons(self) -> None:
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
frame.grid(sticky=tk.EW, pady=PADY) frame.grid(sticky=tk.EW, pady=PADY)
for i in range(2): frame.columnconfigure(0, weight=1)
frame.columnconfigure(i, weight=1)
image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE) image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
self.emane_model_button = ttk.Button( self.emane_model_button = ttk.Button(
@ -192,18 +157,7 @@ class EmaneConfigDialog(Dialog):
command=self.click_model_config, command=self.click_model_config,
) )
self.emane_model_button.image = image self.emane_model_button.image = image
self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) self.emane_model_button.grid(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)
def draw_apply_and_cancel(self) -> None: def draw_apply_and_cancel(self) -> None:
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
@ -216,10 +170,6 @@ class EmaneConfigDialog(Dialog):
button = ttk.Button(frame, text="Cancel", command=self.destroy) button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky=tk.EW) 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: def click_model_config(self) -> None:
""" """
draw emane model configuration draw emane model configuration

View file

@ -38,7 +38,7 @@ class LinuxNetClient:
:param device: device to add route to :param device: device to add route to
:return: nothing :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: def device_up(self, device: str) -> None:
""" """

View file

@ -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( def create_emane_model_config(
node_id: int, node_id: int,
model: "EmaneModelType", model: "EmaneModelType",
@ -104,22 +90,22 @@ def create_emane_model_config(
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, "iface", iface_id)
add_attribute(emane_element, "model", model.name) 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") mac_element = etree.SubElement(emane_element, "mac")
for mac_config in model.mac_config: for mac_config in model.mac_config:
value = config[mac_config.id] value = config[mac_config.id]
add_configuration(mac_element, mac_config.id, value) add_configuration(mac_element, mac_config.id, value)
phy_element = etree.SubElement(emane_element, "phy") phy_element = etree.SubElement(emane_element, "phy")
for phy_config in model.phy_config: for phy_config in model.phy_config:
value = config[phy_config.id] value = config[phy_config.id]
add_configuration(phy_element, phy_config.id, value) add_configuration(phy_element, phy_config.id, value)
external_element = etree.SubElement(emane_element, "external") external_element = etree.SubElement(emane_element, "external")
for external_config in model.external_config: for external_config in model.external_config:
value = config[external_config.id] value = config[external_config.id]
add_configuration(external_element, external_config.id, value) add_configuration(external_element, external_config.id, value)
return emane_element return emane_element
@ -376,8 +362,6 @@ class CoreXmlWriter:
self.scenario.append(metadata_elements) self.scenario.append(metadata_elements)
def write_emane_configs(self) -> None: 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") emane_configurations = etree.Element("emane_configurations")
for node_id, model_configs in self.session.emane.node_configs.items(): for node_id, model_configs in self.session.emane.node_configs.items():
node_id, iface_id = utils.parse_iface_config_id(node_id) node_id, iface_id = utils.parse_iface_config_id(node_id)
@ -591,7 +575,6 @@ class CoreXmlReader:
self.read_session_origin() self.read_session_origin()
self.read_service_configs() self.read_service_configs()
self.read_mobility_configs() self.read_mobility_configs()
self.read_emane_global_config()
self.read_nodes() self.read_nodes()
self.read_links() self.read_links()
self.read_emane_configs() self.read_emane_configs()
@ -729,28 +712,10 @@ class CoreXmlReader:
files.add(name) files.add(name)
service.configs = tuple(files) 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: def read_emane_configs(self) -> None:
emane_configurations = self.scenario.find("emane_configurations") emane_configurations = self.scenario.find("emane_configurations")
if emane_configurations is None: if emane_configurations is None:
return return
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") iface_id = get_int(emane_configuration, "iface")
@ -768,18 +733,21 @@ class CoreXmlReader:
) )
# read and set emane model configuration # 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") mac_configuration = emane_configuration.find("mac")
for config in mac_configuration.iterchildren(): for config in mac_configuration.iterchildren():
name = config.get("name") name = config.get("name")
value = config.get("value") value = config.get("value")
configs[name] = value configs[name] = value
phy_configuration = emane_configuration.find("phy") phy_configuration = emane_configuration.find("phy")
for config in phy_configuration.iterchildren(): for config in phy_configuration.iterchildren():
name = config.get("name") name = config.get("name")
value = config.get("value") value = config.get("value")
configs[name] = value configs[name] = value
external_configuration = emane_configuration.find("external") external_configuration = emane_configuration.find("external")
for config in external_configuration.iterchildren(): for config in external_configuration.iterchildren():
name = config.get("name") name = config.get("name")

View file

@ -12,13 +12,11 @@ from core.emulator.distributed import DistributedServer
from core.errors import CoreError from core.errors import CoreError
from core.nodes.base import CoreNode, CoreNodeBase from core.nodes.base import CoreNode, CoreNodeBase
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
from core.nodes.network import CtrlNet
from core.xml import corexml from core.xml import corexml
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
from core.emane.emanemanager import EmaneManager, StartData
from core.emane.emanemodel import EmaneModel from core.emane.emanemodel import EmaneModel
_MAC_PREFIX = "02:02" _MAC_PREFIX = "02:02"
@ -146,74 +144,67 @@ def add_configurations(
def build_platform_xml( 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: ) -> None:
""" """
Create platform xml for a specific node. Create platform xml for a nem/interface.
:param emane_manager: emane manager with emane :param nem_id: nem id for current node/interface
configurations :param nem_port: control port to configure for emane
:param control_net: control net node for this emane :param emane_net: emane network associate with node and interface
network :param iface: node interface to create platform xml for
:param data: start data for a node connected to emane and associated interfaces :param config: emane configuration for interface
:return: the next nem id that can be used for creating platform xml files :return: nothing
""" """
# create top level platform element # create top level platform element
transport_configs = {"otamanagerdevice", "eventservicedevice"}
platform_element = etree.Element("platform") 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 name = configuration.id
if not isinstance(data.node, CoreNode) and name in transport_configs: value = config[configuration.id]
value = control_net.brname
else:
value = emane_manager.config[name]
add_param(platform_element, name, value) 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 # build nem xml
for iface in data.ifaces: nem_definition = nem_file_name(iface)
emane_net = iface.net nem_element = etree.Element(
if not isinstance(emane_net, EmaneNet): "nem", id=str(nem_id), name=iface.localname, definition=nem_definition
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 # create model based xml files
nem_definition = nem_file_name(iface) emane_net.model.build_xml_files(config, iface)
nem_element = etree.Element(
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
)
# check if this is an external transport # check if this is an external transport
if is_external(config): if is_external(config):
nem_element.set("transport", "external") nem_element.set("transport", "external")
platform_endpoint = "platformendpoint" platform_endpoint = "platformendpoint"
add_param(nem_element, platform_endpoint, config[platform_endpoint]) add_param(nem_element, platform_endpoint, config[platform_endpoint])
transport_endpoint = "transportendpoint" transport_endpoint = "transportendpoint"
add_param(nem_element, transport_endpoint, config[transport_endpoint]) add_param(nem_element, transport_endpoint, config[transport_endpoint])
# define transport element # define transport element
transport_name = transport_file_name(iface) transport_name = transport_file_name(iface)
transport_element = etree.SubElement( transport_element = etree.SubElement(
nem_element, "transport", definition=transport_name nem_element, "transport", definition=transport_name
) )
add_param(transport_element, "device", iface.name) add_param(transport_element, "device", iface.name)
# add nem element to platform element # add nem element to platform element
platform_element.append(nem_element) platform_element.append(nem_element)
# generate and assign interface mac address based on nem id # generate and assign interface mac address based on nem id
mac = _MAC_PREFIX + ":00:00:" mac = _MAC_PREFIX + ":00:00:"
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
iface.set_mac(mac) iface.set_mac(mac)
doc_name = "platform" doc_name = "platform"
file_name = f"{data.node.name}-platform.xml" file_name = platform_file_name(iface)
create_node_file(data.node, platform_element, doc_name, file_name) create_node_file(iface.node, platform_element, doc_name, file_name)
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: 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: phy xml file name
""" """
return f"{iface.name}-phy.xml" return f"{iface.name}-phy.xml"
def platform_file_name(iface: CoreInterface) -> str:
return f"{iface.name}-platform.xml"

View file

@ -30,8 +30,9 @@ iface1 = iface_helper.create_iface(node2.id, 0)
session.add_link(node1=node2, node2=emane, iface1=iface1) session.add_link(node1=node2, node2=emane, iface1=iface1)
# setup emane configurations using a dict mapping currently support values as strings # setup emane configurations using a dict mapping currently support values as strings
session.set_emane({"eventservicettl": "2"}) emane.set_emane_model(
emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"}) EmaneIeee80211abgModel.name, {"eventservicettl": "2", "unicastrate": "3"}
)
# start session # start session
core.start_session(session) core.start_session(session)

View file

@ -95,10 +95,6 @@ service CoreApi {
} }
// emane rpc // emane rpc
rpc GetEmaneConfig (emane.GetEmaneConfigRequest) returns (emane.GetEmaneConfigResponse) {
}
rpc SetEmaneConfig (emane.SetEmaneConfigRequest) returns (emane.SetEmaneConfigResponse) {
}
rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) { rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) {
} }
rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) { rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) {
@ -144,19 +140,18 @@ message StartSessionRequest {
repeated Link links = 3; repeated Link links = 3;
repeated Hook hooks = 4; repeated Hook hooks = 4;
SessionLocation location = 5; SessionLocation location = 5;
map<string, string> emane_config = 6; repeated wlan.WlanConfig wlan_configs = 6;
repeated wlan.WlanConfig wlan_configs = 7; repeated emane.EmaneModelConfig emane_model_configs = 7;
repeated emane.EmaneModelConfig emane_model_configs = 8; repeated mobility.MobilityConfig mobility_configs = 8;
repeated mobility.MobilityConfig mobility_configs = 9; repeated services.ServiceConfig service_configs = 9;
repeated services.ServiceConfig service_configs = 10; repeated services.ServiceFileConfig service_file_configs = 10;
repeated services.ServiceFileConfig service_file_configs = 11; repeated Link asymmetric_links = 11;
repeated Link asymmetric_links = 12; repeated configservices.ConfigServiceConfig config_service_configs = 12;
repeated configservices.ConfigServiceConfig config_service_configs = 13; map<string, string> options = 13;
map<string, string> options = 14; string user = 14;
string user = 15; bool definition = 15;
bool definition = 16; map<string, string> metadata = 16;
map<string, string> metadata = 17; repeated Server servers = 17;
repeated Server servers = 18;
} }
message StartSessionResponse { message StartSessionResponse {

View file

@ -4,23 +4,6 @@ package emane;
import "core/api/grpc/common.proto"; 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 { message GetEmaneModelConfigRequest {
int32 session_id = 1; int32 session_id = 1;
int32 node_id = 2; int32 node_id = 2;
@ -50,6 +33,7 @@ message GetEmaneModelConfig {
message GetEmaneEventChannelRequest { message GetEmaneEventChannelRequest {
int32 session_id = 1; int32 session_id = 1;
int32 nem_id = 2;
} }
message GetEmaneEventChannelResponse { message GetEmaneEventChannelResponse {

View file

@ -61,6 +61,7 @@ eval "$ifcommand" | awk '
/tmp\./ {print "removing interface " $1; system("ip link del " $1);} /tmp\./ {print "removing interface " $1; system("ip link del " $1);}
/gt\./ {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);} /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 ' nft list ruleset | awk '

View file

@ -83,11 +83,6 @@ class TestGrpc:
scale=location_scale, 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 # setup wlan config
wlan_config_key = "range" wlan_config_key = "range"
wlan_config_value = "333" wlan_config_value = "333"
@ -151,7 +146,6 @@ class TestGrpc:
location_alt, location_alt,
) )
assert real_session.location.refscale == location_scale 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( set_wlan_config = real_session.mobility.get_model_config(
wlan_node.id, BasicRangeModel.name wlan_node.id, BasicRangeModel.name
) )
@ -518,35 +512,6 @@ class TestGrpc:
assert config[range_key] == range_value assert config[range_key] == range_value
assert wlan.model.range == int(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): def test_set_emane_model_config(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()

View file

@ -941,35 +941,3 @@ class TestGui:
config = coretlv.session.emane.get_config(wlan.id, EmaneIeee80211abgModel.name) config = coretlv.session.emane.get_config(wlan.id, EmaneIeee80211abgModel.name)
assert config[config_key] == config_value 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

View file

@ -328,10 +328,11 @@ session.add_link(node1=node1, node2=emane, iface1=iface1)
iface1 = iface_helper.create_iface(node2.id, 0) iface1 = iface_helper.create_iface(node2.id, 0)
session.add_link(node1=node2, node2=emane, iface1=iface1) session.add_link(node1=node2, node2=emane, iface1=iface1)
# setting global emane configuration
session.set_emane({"eventservicettl": "2"})
# setting emane specific emane model configuration # 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 # start session
core.start_session(session) core.start_session(session)

View file

@ -541,13 +541,7 @@ proc wlanConfigDialogHelper { wi target apply } {
ttk::button $opts.model -text "model options" \ ttk::button $opts.model -text "model options" \
-image $plugin_img_edit -compound right -command "" -state disabled \ -image $plugin_img_edit -compound right -command "" -state disabled \
-command "configCap $target \[set g_selected_model\]" -command "configCap $target \[set g_selected_model\]"
# global EMANE model uses no node in config request message, although any pack $opts.model -side left -padx 4 -pady 4
# 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 -side top -anchor c -padx 4 -pady 4 pack $opts -side top -anchor c -padx 4 -pady 4
# show correct tab basic/emane based on selection # show correct tab basic/emane based on selection