Merge pull request #484 from coreemu/enhancement/runtime-emane-link

Enhancement/runtime emane link
This commit is contained in:
bharnden 2020-07-06 21:18:29 -07:00 committed by GitHub
commit 3590f2c370
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 407 additions and 756 deletions

View file

@ -491,10 +491,13 @@ def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
)
def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int:
def get_nem_id(
session: Session, node: CoreNode, iface_id: int, context: ServicerContext
) -> int:
"""
Get nem id for a given node and interface id.
:param session: session node belongs to
:param node: node to get nem id for
:param iface_id: id of interface on node to get nem id for
:param context: request context
@ -508,7 +511,7 @@ def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int:
if not isinstance(net, EmaneNet):
message = f"{node.name} interface {iface_id} is not an EMANE network"
context.abort(grpc.StatusCode.INVALID_ARGUMENT, message)
nem_id = net.getnemid(iface)
nem_id = session.emane.get_nem_id(iface)
if nem_id is None:
message = f"{node.name} interface {iface_id} nem id does not exist"
context.abort(grpc.StatusCode.INVALID_ARGUMENT, message)

View file

@ -1551,29 +1551,29 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("emane link: %s", request)
session = self.get_session(request.session_id, context)
nem1 = request.nem1
emane1, iface = session.emane.nemlookup(nem1)
if not emane1 or not iface:
iface1 = session.emane.get_iface(nem1)
if not iface1:
context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found")
node1 = iface.node
node1 = iface1.node
nem2 = request.nem2
emane2, iface = session.emane.nemlookup(nem2)
if not emane2 or not iface:
iface2 = session.emane.get_iface(nem2)
if not iface2:
context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found")
node2 = iface.node
node2 = iface2.node
if emane1.id == emane2.id:
if iface1.net == iface2.net:
if request.linked:
flag = MessageFlags.ADD
else:
flag = MessageFlags.DELETE
color = session.get_link_color(emane1.id)
color = session.get_link_color(iface1.net.id)
link = LinkData(
message_type=flag,
type=LinkTypes.WIRELESS,
node1_id=node1.id,
node2_id=node2.id,
network_id=emane1.id,
network_id=iface1.net.id,
color=color,
)
session.broadcast_link(link)
@ -1796,8 +1796,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
for request in request_iterator:
session = self.get_session(request.session_id, context)
node1 = self.get_node(session, request.node1_id, context, CoreNode)
nem1 = grpcutils.get_nem_id(node1, request.iface1_id, context)
nem1 = grpcutils.get_nem_id(session, node1, request.iface1_id, context)
node2 = self.get_node(session, request.node2_id, context, CoreNode)
nem2 = grpcutils.get_nem_id(node2, request.iface2_id, context)
nem2 = grpcutils.get_nem_id(session, node2, request.iface2_id, context)
session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2)
return EmanePathlossesResponse()

View file

@ -10,9 +10,7 @@ from lxml import etree
from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest, emanemodel
from core.emane.nodes import EmaneNet
from core.emulator.data import LinkOptions
from core.emulator.enumerations import TransportType
from core.nodes.interface import CoreInterface
from core.xml import emanexml
@ -62,9 +60,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
def config_groups(cls) -> List[ConfigGroup]:
return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))]
def build_xml_files(
self, config: Dict[str, str], iface: CoreInterface = None
) -> None:
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
"""
Build the necessary nem and commeffect XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
@ -75,23 +71,16 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
:param iface: interface for the emane node
:return: nothing
"""
# retrieve xml names
nem_name = emanexml.nem_file_name(self, iface)
shim_name = emanexml.shim_file_name(self, iface)
# create and write nem document
nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured")
transport_type = TransportType.VIRTUAL
if iface and iface.transport_type == TransportType.RAW:
transport_type = TransportType.RAW
transport_file = emanexml.transport_file_name(self.id, transport_type)
etree.SubElement(nem_element, "transport", definition=transport_file)
transport_name = emanexml.transport_file_name(iface)
etree.SubElement(nem_element, "transport", definition=transport_name)
# set shim configuration
nem_name = emanexml.nem_file_name(iface)
shim_name = emanexml.shim_file_name(iface)
etree.SubElement(nem_element, "shim", definition=shim_name)
nem_file = os.path.join(self.session.session_dir, nem_name)
emanexml.create_file(nem_element, "nem", nem_file)
emanexml.create_iface_file(iface, nem_element, "nem", nem_name)
# create and write shim document
shim_element = etree.Element(
@ -110,9 +99,10 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
ff = config["filterfile"]
if ff.strip() != "":
emanexml.add_param(shim_element, "filterfile", ff)
emanexml.create_iface_file(iface, shim_element, "shim", shim_name)
shim_file = os.path.join(self.session.session_dir, shim_name)
emanexml.create_file(shim_element, "shim", shim_file)
# create transport xml
emanexml.create_transport_xml(iface, config)
def linkconfig(
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
@ -133,12 +123,11 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
# TODO: batch these into multiple events per transmission
# TODO: may want to split out seconds portion of delay and jitter
event = CommEffectEvent()
emane_node = self.session.get_node(self.id, EmaneNet)
nemid = emane_node.getnemid(iface)
nemid2 = emane_node.getnemid(iface2)
nem1 = self.session.emane.get_nem_id(iface)
nem2 = self.session.emane.get_nem_id(iface2)
logging.info("sending comm effect event")
event.append(
nemid,
nem1,
latency=convert_none(options.delay),
jitter=convert_none(options.jitter),
loss=convert_none(options.loss),
@ -146,4 +135,4 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
unicast=int(convert_none(options.bandwidth)),
broadcast=int(convert_none(options.bandwidth)),
)
service.publish(nemid2, event)
service.publish(nem2, event)

View file

@ -6,6 +6,7 @@ import logging
import os
import threading
from collections import OrderedDict
from enum import Enum
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
from core import utils
@ -28,9 +29,7 @@ from core.emulator.enumerations import (
)
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNode, NodeBase
from core.nodes.interface import CoreInterface
from core.nodes.network import CtrlNet
from core.nodes.physical import Rj45Node
from core.nodes.interface import CoreInterface, TunTap
from core.xml import emanexml
if TYPE_CHECKING:
@ -63,6 +62,12 @@ DEFAULT_EMANE_PREFIX = "/usr"
DEFAULT_DEV = "ctrl0"
class EmaneState(Enum):
SUCCESS = 0
NOT_NEEDED = 1
NOT_READY = 2
class EmaneManager(ModelManager):
"""
EMANE controller object. Lives in a Session instance and is used for
@ -72,8 +77,6 @@ class EmaneManager(ModelManager):
name: str = "emane"
config_type: RegisterTlvs = RegisterTlvs.EMULATION_SERVER
SUCCESS: int = 0
NOT_NEEDED: int = 1
NOT_READY: int = 2
EVENTCFGVAR: str = "LIBEMANEEVENTSERVICECONFIG"
DEFAULT_LOG_LEVEL: int = 3
@ -87,6 +90,8 @@ class EmaneManager(ModelManager):
"""
super().__init__()
self.session: "Session" = session
self.nems_to_ifaces: Dict[int, CoreInterface] = {}
self.ifaces_to_nems: Dict[CoreInterface, int] = {}
self._emane_nets: Dict[int, EmaneNet] = {}
self._emane_node_lock: threading.Lock = threading.Lock()
# port numbers are allocated from these counters
@ -111,47 +116,44 @@ class EmaneManager(ModelManager):
self.event_device: Optional[str] = None
self.emane_check()
def next_nem_id(self) -> int:
nem_id = int(self.get_config("nem_id_start"))
while nem_id in self.nems_to_ifaces:
nem_id += 1
return nem_id
def get_iface_config(
self, node_id: int, iface: CoreInterface, model_name: str
self, emane_net: EmaneNet, iface: CoreInterface
) -> Dict[str, str]:
"""
Retrieve interface configuration or node configuration if not provided.
Retrieve configuration for a given interface.
:param node_id: node id
:param iface: node interface
:param model_name: model to get configuration for
:return: node/interface model configuration
:param emane_net: emane network the interface is connected to
:param iface: interface running emane
:return: net, node, or interface model configuration
"""
# use the network-wide config values or interface(NEM)-specific values?
if iface is None:
return self.get_configs(node_id=node_id, config_type=model_name)
else:
# don"t use default values when interface config is the same as net
# note here that using iface.node.id as key allows for only one type
# of each model per node;
# TODO: use both node and interface as key
# Adamson change: first check for iface config keyed by "node:iface.name"
# (so that nodes w/ multiple interfaces of same conftype can have
# different configs for each separate interface)
key = 1000 * iface.node.id
if iface.node_id is not None:
key += iface.node_id
# try retrieve interface specific configuration, avoid getting defaults
config = self.get_configs(node_id=key, config_type=model_name)
# otherwise retrieve the interfaces node configuration, avoid using defaults
if not config:
config = self.get_configs(node_id=iface.node.id, config_type=model_name)
# get non interface config, when none found
if not config:
# with EMANE 0.9.2+, we need an extra NEM XML from
# model.buildnemxmlfiles(), so defaults are returned here
config = self.get_configs(node_id=node_id, config_type=model_name)
return config
model_name = emane_net.model.name
# don"t use default values when interface config is the same as net
# note here that using iface.node.id as key allows for only one type
# of each model per node;
# TODO: use both node and interface as key
# Adamson change: first check for iface config keyed by "node:iface.name"
# (so that nodes w/ multiple interfaces of same conftype can have
# different configs for each separate interface)
key = 1000 * iface.node.id
if iface.node_id is not None:
key += iface.node_id
# try retrieve interface specific configuration, avoid getting defaults
config = self.get_configs(node_id=key, config_type=model_name)
# otherwise retrieve the interfaces node configuration, avoid using defaults
if not config:
config = self.get_configs(node_id=iface.node.id, config_type=model_name)
# get non interface config, when none found
if not config:
# with EMANE 0.9.2+, we need an extra NEM XML from
# model.buildnemxmlfiles(), so defaults are returned here
config = self.get_configs(node_id=emane_net.id, config_type=model_name)
return config
def config_reset(self, node_id: int = None) -> None:
super().config_reset(node_id)
@ -250,8 +252,8 @@ class EmaneManager(ModelManager):
"""
with self._emane_node_lock:
if emane_net.id in self._emane_nets:
raise KeyError(
f"non-unique EMANE object id {emane_net.id} for {emane_net}"
raise CoreError(
f"duplicate emane network({emane_net.id}): {emane_net.name}"
)
self._emane_nets[emane_net.id] = emane_net
@ -260,14 +262,13 @@ class EmaneManager(ModelManager):
Return a set of CoreNodes that are linked to an EMANE network,
e.g. containers having one or more radio interfaces.
"""
# assumes self._objslock already held
nodes = set()
for emane_net in self._emane_nets.values():
for iface in emane_net.get_ifaces():
nodes.add(iface.node)
return nodes
def setup(self) -> int:
def setup(self) -> EmaneState:
"""
Setup duties for EMANE manager.
@ -275,8 +276,6 @@ class EmaneManager(ModelManager):
instantiation
"""
logging.debug("emane setup")
# TODO: drive this from the session object
with self.session.nodes_lock:
for node_id in self.session.nodes:
node = self.session.nodes[node_id]
@ -285,10 +284,9 @@ class EmaneManager(ModelManager):
"adding emane node: id(%s) name(%s)", node.id, node.name
)
self.add_node(node)
if not self._emane_nets:
logging.debug("no emane nodes in session")
return EmaneManager.NOT_NEEDED
return EmaneState.NOT_NEEDED
# check if bindings were installed
if EventService is None:
@ -304,7 +302,7 @@ class EmaneManager(ModelManager):
"EMANE cannot start, check core config. invalid OTA device provided: %s",
otadev,
)
return EmaneManager.NOT_READY
return EmaneState.NOT_READY
self.session.add_remove_control_net(
net_index=netidx, remove=False, conf_required=False
@ -316,19 +314,18 @@ class EmaneManager(ModelManager):
logging.debug("emane event service device index: %s", netidx)
if netidx < 0:
logging.error(
"EMANE cannot start, check core config. invalid event service device: %s",
"emane cannot start due to invalid event service device: %s",
eventdev,
)
return EmaneManager.NOT_READY
return EmaneState.NOT_READY
self.session.add_remove_control_net(
net_index=netidx, remove=False, conf_required=False
)
self.check_node_models()
return EmaneManager.SUCCESS
return EmaneState.SUCCESS
def startup(self) -> int:
def startup(self) -> EmaneState:
"""
After all the EMANE networks have been added, build XML files
and start the daemons.
@ -337,39 +334,63 @@ class EmaneManager(ModelManager):
instantiation
"""
self.reset()
r = self.setup()
# NOT_NEEDED or NOT_READY
if r != EmaneManager.SUCCESS:
return r
nems = []
status = self.setup()
if status != EmaneState.SUCCESS:
return status
self.starteventmonitor()
self.buildeventservicexml()
with self._emane_node_lock:
self.buildxml()
self.starteventmonitor()
if self.numnems() > 0:
self.startdaemons()
self.install_ifaces()
for node_id in self._emane_nets:
emane_node = self._emane_nets[node_id]
for iface in emane_node.get_ifaces():
nems.append(
(iface.node.name, iface.name, emane_node.getnemid(iface))
)
if nems:
emane_nems_filename = os.path.join(self.session.session_dir, "emane_nems")
try:
with open(emane_nems_filename, "w") as f:
for nodename, ifname, nemid in nems:
f.write(f"{nodename} {ifname} {nemid}\n")
except IOError:
logging.exception("Error writing EMANE NEMs file: %s")
logging.info("emane building xmls...")
for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id]
if not emane_net.model:
logging.error("emane net(%s) has no model", emane_net.name)
continue
for iface in emane_net.get_ifaces():
self.start_iface(emane_net, iface)
if self.links_enabled():
self.link_monitor.start()
return EmaneManager.SUCCESS
return EmaneState.SUCCESS
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
if not iface.node:
logging.error(
"emane net(%s) connected interface(%s) missing node",
emane_net.name,
iface.name,
)
return
control_net = self.session.add_remove_control_net(
0, remove=False, conf_required=False
)
nem_id = self.next_nem_id()
self.set_nem(nem_id, iface)
self.write_nem(iface, nem_id)
emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id)
config = self.get_iface_config(emane_net, iface)
emane_net.model.build_xml_files(config, iface)
self.start_daemon(iface)
self.install_iface(emane_net, 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
def get_iface(self, nem_id: int) -> Optional[CoreInterface]:
return self.nems_to_ifaces.get(nem_id)
def get_nem_id(self, iface: CoreInterface) -> Optional[int]:
return self.ifaces_to_nems.get(iface)
def write_nem(self, iface: CoreInterface, nem_id: int) -> None:
path = os.path.join(self.session.session_dir, "emane_nems")
try:
with open(path, "a") as f:
f.write(f"{iface.node.name} {iface.name} {nem_id}\n")
except IOError:
logging.exception("error writing to emane nem file")
def links_enabled(self) -> bool:
return self.get_config("link_enabled") == "1"
@ -380,17 +401,14 @@ class EmaneManager(ModelManager):
"""
if not self.genlocationevents():
return
with self._emane_node_lock:
for key in sorted(self._emane_nets.keys()):
emane_node = self._emane_nets[key]
for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id]
logging.debug(
"post startup for emane node: %s - %s",
emane_node.id,
emane_node.name,
"post startup for emane node: %s - %s", emane_net.id, emane_net.name
)
emane_node.model.post_startup()
for iface in emane_node.get_ifaces():
emane_net.model.post_startup()
for iface in emane_net.get_ifaces():
iface.setposition()
def reset(self) -> None:
@ -400,13 +418,8 @@ class EmaneManager(ModelManager):
"""
with self._emane_node_lock:
self._emane_nets.clear()
self.platformport = self.session.options.get_config_int(
"emane_platform_port", 8100
)
self.transformport = self.session.options.get_config_int(
"emane_transform_port", 8200
)
self.nems_to_ifaces.clear()
self.ifaces_to_nems.clear()
def shutdown(self) -> None:
"""
@ -422,40 +435,23 @@ class EmaneManager(ModelManager):
self.stopdaemons()
self.stopeventmonitor()
def buildxml(self) -> None:
"""
Build XML files required to run EMANE on each node.
NEMs run inside containers using the control network for passing
events and data.
"""
# assume self._objslock is already held here
logging.info("emane building xml...")
# on master, control network bridge added earlier in startup()
ctrlnet = self.session.add_remove_control_net(
net_index=0, remove=False, conf_required=False
)
self.buildplatformxml(ctrlnet)
self.buildnemxml()
self.buildeventservicexml()
def check_node_models(self) -> None:
"""
Associate EMANE model classes with EMANE network nodes.
"""
for node_id in self._emane_nets:
emane_node = self._emane_nets[node_id]
emane_net = self._emane_nets[node_id]
logging.debug("checking emane model for node: %s", node_id)
# skip nodes that already have a model set
if emane_node.model:
if emane_net.model:
logging.debug(
"node(%s) already has model(%s)",
emane_node.id,
emane_node.model.name,
"node(%s) already has model(%s)", emane_net.id, emane_net.model.name
)
continue
# set model configured for node, due to legacy messaging configuration before nodes exist
# set model configured for node, due to legacy messaging configuration
# before nodes exist
model_name = self.node_models.get(node_id)
if not model_name:
logging.error("emane node(%s) has no node model", node_id)
@ -464,81 +460,34 @@ class EmaneManager(ModelManager):
config = self.get_model_config(node_id=node_id, model_name=model_name)
logging.debug("setting emane model(%s) config(%s)", model_name, config)
model_class = self.models[model_name]
emane_node.setmodel(model_class, config)
def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]]:
"""
Look for the given numerical NEM ID and return the first matching
EMANE network and NEM interface.
"""
emane_node = None
iface = None
for node_id in self._emane_nets:
emane_node = self._emane_nets[node_id]
iface = emane_node.get_nem_iface(nemid)
if iface is not None:
break
else:
emane_node = None
return emane_node, iface
emane_net.setmodel(model_class, config)
def get_nem_link(
self, nem1: int, nem2: int, flags: MessageFlags = MessageFlags.NONE
) -> Optional[LinkData]:
emane1, iface = self.nemlookup(nem1)
if not emane1 or not iface:
iface1 = self.get_iface(nem1)
if not iface1:
logging.error("invalid nem: %s", nem1)
return None
node1 = iface.node
emane2, iface = self.nemlookup(nem2)
if not emane2 or not iface:
node1 = iface1.node
iface2 = self.get_iface(nem2)
if not iface2:
logging.error("invalid nem: %s", nem2)
return None
node2 = iface.node
color = self.session.get_link_color(emane1.id)
node2 = iface2.node
if iface1.net != iface2.net:
return None
emane_net = iface1.net
color = self.session.get_link_color(emane_net.id)
return LinkData(
message_type=flags,
type=LinkTypes.WIRELESS,
node1_id=node1.id,
node2_id=node2.id,
network_id=emane1.id,
network_id=emane_net.id,
color=color,
)
def numnems(self) -> int:
"""
Return the number of NEMs emulated locally.
"""
count = 0
for node_id in self._emane_nets:
emane_node = self._emane_nets[node_id]
count += len(emane_node.ifaces)
return count
def buildplatformxml(self, ctrlnet: CtrlNet) -> None:
"""
Build a platform.xml file now that all nodes are configured.
"""
nemid = int(self.get_config("nem_id_start"))
platform_xmls = {}
# assume self._objslock is already held here
for key in sorted(self._emane_nets.keys()):
emane_node = self._emane_nets[key]
nemid = emanexml.build_node_platform_xml(
self, ctrlnet, emane_node, nemid, platform_xmls
)
def buildnemxml(self) -> None:
"""
Builds the nem, mac, and phy xml files for each EMANE network.
"""
for key in sorted(self._emane_nets):
emane_net = self._emane_nets[key]
emanexml.build_xml_files(self, emane_net)
def buildeventservicexml(self) -> None:
"""
Build the libemaneeventservice.xml file if event service options
@ -571,7 +520,7 @@ class EmaneManager(ModelManager):
)
)
def startdaemons(self) -> None:
def start_daemon(self, iface: CoreInterface) -> None:
"""
Start one EMANE daemon per node having a radio.
Add a control network even if the user has not configured one.
@ -581,116 +530,91 @@ class EmaneManager(ModelManager):
cfgloglevel = self.session.options.get_config_int("emane_log_level")
realtime = self.session.options.get_config_bool("emane_realtime", default=True)
if cfgloglevel:
logging.info("setting user-defined EMANE log level: %d", cfgloglevel)
logging.info("setting user-defined emane log level: %d", cfgloglevel)
loglevel = str(cfgloglevel)
emanecmd = f"emane -d -l {loglevel}"
if realtime:
emanecmd += " -r"
otagroup, _otaport = self.get_config("otamanagergroup").split(":")
otadev = self.get_config("otamanagerdevice")
otanetidx = self.session.get_control_net_index(otadev)
eventgroup, _eventport = self.get_config("eventservicegroup").split(":")
eventdev = self.get_config("eventservicedevice")
eventservicenetidx = self.session.get_control_net_index(eventdev)
run_emane_on_host = False
for node in self.getnodes():
if isinstance(node, Rj45Node):
run_emane_on_host = True
continue
path = self.session.session_dir
n = node.id
node = iface.node
if iface.is_virtual():
otagroup, _otaport = self.get_config("otamanagergroup").split(":")
otadev = self.get_config("otamanagerdevice")
otanetidx = self.session.get_control_net_index(otadev)
eventgroup, _eventport = self.get_config("eventservicegroup").split(":")
eventdev = self.get_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:
logging.info("adding ota device ctrl%d", otanetidx)
self.session.add_remove_control_iface(
node, otanetidx, remove=False, conf_required=False
)
if eventservicenetidx >= 0:
logging.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
logging.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 = os.path.join(path, f"emane{n}.log")
platform_xml = os.path.join(path, f"platform{n}.xml")
log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log")
platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml")
args = f"{emanecmd} -f {log_file} {platform_xml}"
output = node.cmd(args)
node.cmd(args)
logging.info("node(%s) emane daemon running: %s", node.name, args)
logging.debug("node(%s) emane daemon output: %s", node.name, output)
if not run_emane_on_host:
return
path = self.session.session_dir
log_file = os.path.join(path, "emane.log")
platform_xml = os.path.join(path, "platform.xml")
emanecmd += f" -f {log_file} {platform_xml}"
utils.cmd(emanecmd, cwd=path)
self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path))
logging.info("host emane daemon running: %s", emanecmd)
else:
path = self.session.session_dir
log_file = os.path.join(path, f"{iface.name}-emane.log")
platform_xml = os.path.join(path, f"{iface.name}-platform.xml")
emanecmd += f" -f {log_file} {platform_xml}"
node.host_cmd(emanecmd, cwd=path)
logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd)
def stopdaemons(self) -> None:
"""
Kill the appropriate EMANE daemons.
"""
# TODO: we may want to improve this if we had the PIDs from the specific EMANE
# daemons that we"ve started
kill_emaned = "killall -q emane"
kill_transortd = "killall -q emanetransportd"
stop_emane_on_host = False
for node in self.getnodes():
if isinstance(node, Rj45Node):
stop_emane_on_host = True
continue
for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id]
for iface in emane_net.get_ifaces():
node = iface.node
if not node.up:
continue
if iface.is_raw():
node.host_cmd(kill_emaned, wait=False)
else:
node.cmd(kill_emaned, wait=False)
if node.up:
node.cmd(kill_emaned, wait=False)
# TODO: RJ45 node
if stop_emane_on_host:
try:
utils.cmd(kill_emaned)
utils.cmd(kill_transortd)
self.session.distributed.execute(lambda x: x.remote_cmd(kill_emaned))
self.session.distributed.execute(lambda x: x.remote_cmd(kill_transortd))
except CoreCommandError:
logging.exception("error shutting down emane daemons")
def install_ifaces(self) -> None:
"""
Install TUN/TAP virtual interfaces into their proper namespaces
now that the EMANE daemons are running.
"""
for key in sorted(self._emane_nets.keys()):
node = self._emane_nets[key]
logging.info("emane install interface for node(%s): %d", node.name, key)
node.install_ifaces()
def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
config = self.get_iface_config(emane_net, iface)
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.setposition()
def deinstall_ifaces(self) -> None:
"""
Uninstall TUN/TAP virtual interfaces.
"""
for key in sorted(self._emane_nets.keys()):
emane_node = self._emane_nets[key]
emane_node.deinstall_ifaces()
for key in sorted(self._emane_nets):
emane_net = self._emane_nets[key]
for iface in emane_net.get_ifaces():
if iface.is_virtual():
iface.shutdown()
iface.poshook = None
def doeventmonitor(self) -> bool:
"""
@ -718,7 +642,6 @@ class EmaneManager(ModelManager):
logging.info("emane start event monitor")
if not self.doeventmonitor():
return
if self.service is None:
logging.error(
"Warning: EMANE events will not be generated "
@ -806,7 +729,7 @@ class EmaneManager(ModelManager):
Returns True if successfully parsed and a Node Message was sent.
"""
# convert nemid to node number
_emanenode, iface = self.nemlookup(nemid)
iface = self.get_iface(nemid)
if iface is None:
logging.info("location event for unknown NEM %s", nemid)
return False

View file

@ -9,7 +9,7 @@ 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, TransportType
from core.emulator.enumerations import ConfigDataTypes
from core.errors import CoreError
from core.location.mobility import WirelessModel
from core.nodes.base import CoreNode
@ -96,45 +96,20 @@ class EmaneModel(WirelessModel):
ConfigGroup("External Parameters", phy_len + 1, config_len),
]
def build_xml_files(
self, config: Dict[str, str], iface: CoreInterface = None
) -> None:
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
"""
Builds xml files for this emane model. Creates a nem.xml file that points to
both mac.xml and phy.xml definitions.
:param config: emane model configuration for the node and interface
:param iface: interface for the emane node
:param iface: interface to run emane for
:return: nothing
"""
nem_name = emanexml.nem_file_name(self, iface)
mac_name = emanexml.mac_file_name(self, iface)
phy_name = emanexml.phy_file_name(self, iface)
# remote server for file
server = None
if iface is not None:
server = iface.node.server
# check if this is external
transport_type = TransportType.VIRTUAL
if iface and iface.transport_type == TransportType.RAW:
transport_type = TransportType.RAW
transport_name = emanexml.transport_file_name(self.id, transport_type)
# create nem xml file
nem_file = os.path.join(self.session.session_dir, nem_name)
emanexml.create_nem_xml(
self, config, nem_file, transport_name, mac_name, phy_name, server
)
# create mac xml file
mac_file = os.path.join(self.session.session_dir, mac_name)
emanexml.create_mac_xml(self, config, mac_file, server)
# create phy xml file
phy_file = os.path.join(self.session.session_dir, phy_name)
emanexml.create_phy_xml(self, config, phy_file, server)
# create nem, mac, and phy xml files
emanexml.create_nem_xml(self, iface, config)
emanexml.create_mac_xml(self, iface, config)
emanexml.create_phy_xml(self, iface, config)
emanexml.create_transport_xml(iface, config)
def post_startup(self) -> None:
"""

View file

@ -6,18 +6,18 @@ share the same MAC+PHY model.
import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
from core.emulator.data import LinkData, LinkOptions
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import (
EventTypes,
LinkTypes,
MessageFlags,
NodeTypes,
RegisterTlvs,
TransportType,
)
from core.errors import CoreError
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface, TunTap
from core.nodes.base import CoreNetworkBase, CoreNode
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emane.emanemodel import EmaneModel
@ -47,7 +47,7 @@ class EmaneNet(CoreNetworkBase):
apitype: NodeTypes = NodeTypes.EMANE
linktype: LinkTypes = LinkTypes.WIRED
type: str = "wlan"
is_emane: bool = True
has_custom_iface: bool = True
def __init__(
self,
@ -58,7 +58,6 @@ class EmaneNet(CoreNetworkBase):
) -> None:
super().__init__(session, _id, name, server)
self.conf: str = ""
self.nemidmap: Dict[CoreInterface, int] = {}
self.model: "OptionalEmaneModel" = None
self.mobility: Optional[WayPointMobility] = None
@ -102,7 +101,6 @@ class EmaneNet(CoreNetworkBase):
"""
set the EmaneModel associated with this node
"""
logging.info("adding model: %s", model.name)
if model.config_type == RegisterTlvs.WIRELESS:
# EmaneModel really uses values from ConfigurableManager
# when buildnemxml() is called, not during init()
@ -112,71 +110,6 @@ class EmaneNet(CoreNetworkBase):
self.mobility = model(session=self.session, _id=self.id)
self.mobility.update_config(config)
def setnemid(self, iface: CoreInterface, nemid: int) -> None:
"""
Record an interface to numerical ID mapping. The Emane controller
object manages and assigns these IDs for all NEMs.
"""
self.nemidmap[iface] = nemid
def getnemid(self, iface: CoreInterface) -> Optional[int]:
"""
Given an interface, return its numerical ID.
"""
if iface not in self.nemidmap:
return None
else:
return self.nemidmap[iface]
def get_nem_iface(self, nemid: int) -> Optional[CoreInterface]:
"""
Given a numerical NEM ID, return its interface. This returns the
first interface that matches the given NEM ID.
"""
for iface in self.nemidmap:
if self.nemidmap[iface] == nemid:
return iface
return None
def install_ifaces(self) -> None:
"""
Install TAP devices into their namespaces. This is done after
EMANE daemons have been started, because that is their only chance
to bind to the TAPs.
"""
if (
self.session.emane.genlocationevents()
and self.session.emane.service is None
):
warntxt = "unable to publish EMANE events because the eventservice "
warntxt += "Python bindings failed to load"
logging.error(warntxt)
for iface in self.get_ifaces():
config = self.session.emane.get_iface_config(
self.id, iface, self.model.name
)
external = config.get("external", "0")
if isinstance(iface, TunTap) and external == "0":
iface.set_ips()
if not self.session.emane.genlocationevents():
iface.poshook = None
continue
# at this point we register location handlers for generating
# EMANE location events
iface.poshook = self.setnemposition
iface.setposition()
def deinstall_ifaces(self) -> None:
"""
Uninstall TAP devices. This invokes their shutdown method for
any required cleanup; the device may be actually removed when
emanetransportd terminates.
"""
for iface in self.get_ifaces():
if iface.transport_type == TransportType.VIRTUAL:
iface.shutdown()
iface.poshook = None
def _nem_position(
self, iface: CoreInterface
) -> Optional[Tuple[int, float, float, float]]:
@ -186,9 +119,9 @@ class EmaneNet(CoreNetworkBase):
:param iface: interface to get nem emane position for
:return: nem position tuple, None otherwise
"""
nemid = self.getnemid(iface)
nem_id = self.session.emane.get_nem_id(iface)
ifname = iface.localname
if nemid is None:
if nem_id is None:
logging.info("nemid for %s is unknown", ifname)
return
node = iface.node
@ -199,7 +132,7 @@ class EmaneNet(CoreNetworkBase):
node.position.set_geo(lon, lat, alt)
# altitude must be an integer or warning is printed
alt = int(round(alt))
return nemid, lon, lat, alt
return nem_id, lon, lat, alt
def setnemposition(self, iface: CoreInterface) -> None:
"""
@ -210,7 +143,6 @@ class EmaneNet(CoreNetworkBase):
if self.session.emane.service is None:
logging.info("position service not available")
return
position = self._nem_position(iface)
if position:
nemid, lon, lat, alt = position
@ -241,9 +173,12 @@ class EmaneNet(CoreNetworkBase):
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
links = super().links(flags)
# gather current emane links
nem_ids = set(self.nemidmap.values())
emane_manager = self.session.emane
# gather current emane links
nem_ids = set()
for iface in self.get_ifaces():
nem_id = emane_manager.get_nem_id(iface)
nem_ids.add(nem_id)
emane_links = emane_manager.link_monitor.links
considered = set()
for link_key in emane_links:
@ -262,3 +197,18 @@ class EmaneNet(CoreNetworkBase):
if link:
links.append(link)
return links
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
# TUN/TAP is not ready for addressing yet; the device may
# take some time to appear, and installing it into a
# namespace after it has been bound removes addressing;
# save addresses with the interface now
iface_id = node.newtuntap(iface_data.id, iface_data.name)
node.attachnet(iface_id, self)
iface = node.get_iface(iface_id)
iface.set_mac(iface_data.mac)
for ip in iface_data.get_ips():
iface.add_ip(ip)
if self.session.state == EventTypes.RUNTIME_STATE:
self.session.emane.start_iface(self, iface)
return iface

View file

@ -15,7 +15,7 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVa
from core import constants, utils
from core.configservice.manager import ConfigServiceManager
from core.emane.emanemanager import EmaneManager
from core.emane.emanemanager import EmaneManager, EmaneState
from core.emane.nodes import EmaneNet
from core.emulator.data import (
ConfigData,
@ -531,7 +531,7 @@ class Session:
self.set_node_position(node, options)
# add services to needed nodes
if isinstance(node, (CoreNode, PhysicalNode, DockerNode, LxcNode)):
if isinstance(node, (CoreNode, PhysicalNode)):
node.type = options.model
logging.debug("set node type: %s", node.type)
self.services.add_services(node, node.type, options.services)
@ -545,6 +545,8 @@ class Session:
# ensure default emane configuration
if isinstance(node, EmaneNet) and options.emane:
self.emane.set_model_config(_id, options.emane)
if self.state == EventTypes.RUNTIME_STATE:
self.emane.add_node(node)
# set default wlan config if needed
if isinstance(node, WlanNode):
self.mobility.set_model_config(_id, BasicRangeModel.name)
@ -1181,7 +1183,7 @@ class Session:
self.distributed.start()
# instantiate will be invoked again upon emane configure
if self.emane.startup() == self.emane.NOT_READY:
if self.emane.startup() == EmaneState.NOT_READY:
return []
# boot node services and then start mobility

View file

@ -812,30 +812,18 @@ class CoreNode(CoreNodeBase):
:param iface_data: interface data for new interface
:return: interface index
"""
ips = iface_data.get_ips()
with self.lock:
# TODO: emane specific code
if net.is_emane is True:
iface_id = self.newtuntap(iface_data.id, iface_data.name)
# TUN/TAP is not ready for addressing yet; the device may
# take some time to appear, and installing it into a
# namespace after it has been bound removes addressing;
# save addresses with the interface now
self.attachnet(iface_id, net)
iface = self.get_iface(iface_id)
iface.set_mac(iface_data.mac)
for ip in ips:
iface.add_ip(ip)
if net.has_custom_iface:
return net.custom_iface(self, iface_data)
else:
iface_id = self.newveth(iface_data.id, iface_data.name)
self.attachnet(iface_id, net)
if iface_data.mac:
self.set_mac(iface_id, iface_data.mac)
for ip in ips:
for ip in iface_data.get_ips():
self.add_ip(iface_id, ip)
self.ifup(iface_id)
iface = self.get_iface(iface_id)
return iface
return self.get_iface(iface_id)
def addfile(self, srcname: str, filename: str) -> None:
"""
@ -925,7 +913,7 @@ class CoreNetworkBase(NodeBase):
"""
linktype: LinkTypes = LinkTypes.WIRED
is_emane: bool = False
has_custom_iface: bool = False
def __init__(
self,
@ -990,6 +978,9 @@ class CoreNetworkBase(NodeBase):
"""
raise NotImplementedError
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
raise NotImplementedError
def get_linked_iface(self, net: "CoreNetworkBase") -> Optional[CoreInterface]:
"""
Return the interface that links this net with another net.

View file

@ -60,7 +60,7 @@ class CoreInterface:
# placeholder position hook
self.poshook: Callable[[CoreInterface], None] = lambda x: None
# used with EMANE
self.transport_type: Optional[TransportType] = None
self.transport_type: TransportType = TransportType.VIRTUAL
# id of interface for node
self.node_id: Optional[int] = None
# id of interface for network
@ -310,6 +310,22 @@ class CoreInterface:
"""
return id(self) < id(other)
def is_raw(self) -> bool:
"""
Used to determine if this interface is considered a raw interface.
:return: True if raw interface, False otherwise
"""
return self.transport_type == TransportType.RAW
def is_virtual(self) -> bool:
"""
Used to determine if this interface is considered a virtual interface.
:return: True if virtual interface, False otherwise
"""
return self.transport_type == TransportType.VIRTUAL
class Veth(CoreInterface):
"""
@ -404,7 +420,6 @@ class TunTap(CoreInterface):
:param start: start flag
"""
super().__init__(session, node, name, localname, mtu, server)
self.transport_type = TransportType.VIRTUAL
if start:
self.startup()

View file

@ -1,7 +1,6 @@
from typing import Tuple
from core.emane.nodes import EmaneNet
from core.errors import CoreError
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService
from core.xml import emanexml
@ -14,37 +13,22 @@ class EmaneTransportService(CoreService):
dependencies: Tuple[str, ...] = ()
dirs: Tuple[str, ...] = ()
configs: Tuple[str, ...] = ("emanetransport.sh",)
startup: Tuple[str, ...] = ("sh %s" % configs[0],)
validate: Tuple[str, ...] = ("pidof %s" % executables[0],)
startup: Tuple[str, ...] = (f"sh {configs[0]}",)
validate: Tuple[str, ...] = (f"pidof {executables[0]}",)
validation_timer: float = 0.5
shutdown: Tuple[str, ...] = ("killall %s" % executables[0],)
shutdown: Tuple[str, ...] = (f"killall {executables[0]}",)
@classmethod
def generate_config(cls, node: CoreNode, filename: str) -> str:
if filename == cls.configs[0]:
transport_commands = []
for iface in node.get_ifaces():
try:
network_node = node.session.get_node(iface.net.id, EmaneNet)
config = node.session.emane.get_configs(
network_node.id, network_node.model.name
)
if config and emanexml.is_external(config):
nem_id = network_node.getnemid(iface)
command = (
"emanetransportd -r -l 0 -d ../transportdaemon%s.xml"
% nem_id
)
transport_commands.append(command)
except CoreError:
pass
transport_commands = "\n".join(transport_commands)
return """
emanegentransportxml -o ../ ../platform%s.xml
%s
""" % (
node.id,
transport_commands,
)
else:
raise ValueError
emane_manager = node.session.emane
cfg = ""
for iface in node.get_ifaces():
if not isinstance(iface.net, EmaneNet):
continue
emane_net = iface.net
config = emane_manager.get_iface_config(emane_net, iface)
if emanexml.is_external(config):
nem_id = emane_manager.get_nem_id(iface)
cfg += f"emanegentransportxml {iface.name}-platform.xml\n"
cfg += f"emanetransportd -r -l 0 -d transportdaemon{nem_id}.xml\n"
return cfg

View file

@ -501,8 +501,8 @@ class CoreXmlWriter:
iface = node.get_iface(iface_data.id)
# check if emane interface
if isinstance(iface.net, EmaneNet):
nem = iface.net.getnemid(iface)
add_attribute(iface_element, "nem", nem)
nem_id = self.session.emane.get_nem_id(iface)
add_attribute(iface_element, "nem", nem_id)
add_attribute(iface_element, "id", iface_data.id)
add_attribute(iface_element, "name", iface_data.name)
add_attribute(iface_element, "mac", iface_data.mac)

View file

@ -9,7 +9,6 @@ from core import utils
from core.emane.nodes import EmaneNet
from core.executables import IP
from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emulator.session import Session
@ -38,11 +37,10 @@ def add_mapping(parent_element: etree.Element, maptype: str, mapref: str) -> Non
def add_emane_iface(
host_element: etree.Element,
iface: CoreInterface,
nem_id: int,
platform_name: str = "p1",
transport_name: str = "t1",
) -> etree.Element:
nem_id = iface.net.nemidmap[iface]
host_id = host_element.get("id")
# platform data
@ -158,7 +156,8 @@ class CoreXmlDeployment:
for iface in node.get_ifaces():
emane_element = None
if isinstance(iface.net, EmaneNet):
emane_element = add_emane_iface(host_element, iface)
nem_id = self.session.emane.get_nem_id(iface)
emane_element = add_emane_iface(host_element, nem_id)
parent_element = host_element
if emane_element is not None:

View file

@ -9,7 +9,7 @@ from core import utils
from core.config import Configuration
from core.emane.nodes import EmaneNet
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import TransportType
from core.errors import CoreError
from core.nodes.interface import CoreInterface
from core.nodes.network import CtrlNet
from core.xml import corexml
@ -40,15 +40,11 @@ def _value_to_params(value: str) -> Optional[Tuple[str]]:
"""
try:
values = utils.make_tuple_fromstr(value, str)
if not hasattr(values, "__iter__"):
return None
if len(values) < 2:
return None
return values
except SyntaxError:
logging.exception("error in value string to param list")
return None
@ -66,16 +62,15 @@ def create_file(
:param xml_element: root element to write to file
:param doc_name: name to use in the emane doctype
:param file_path: file path to write xml file to
:param server: remote server node
will run on, default is None for localhost
:param server: remote server to create file on
:return: nothing
"""
doctype = (
f'<!DOCTYPE {doc_name} SYSTEM "file:///usr/share/emane/dtd/{doc_name}.dtd">'
)
if server is not None:
if server:
temp = NamedTemporaryFile(delete=False)
create_file(xml_element, doc_name, temp.name)
corexml.write_xml_file(xml_element, temp.name, doctype=doctype)
temp.close()
server.remote_put(temp.name, file_path)
os.unlink(temp.name)
@ -83,6 +78,26 @@ def create_file(
corexml.write_xml_file(xml_element, file_path, doctype=doctype)
def create_iface_file(
iface: CoreInterface, xml_element: etree.Element, doc_name: str, file_name: str
) -> None:
"""
Create emane xml for an interface.
:param iface: interface running emane
:param xml_element: root element to write to file
:param doc_name: name to use in the emane doctype
:param file_name: name of xml file
:return:
"""
node = iface.node
if iface.is_raw():
file_path = os.path.join(node.session.session_dir, file_name)
else:
file_path = os.path.join(node.nodedir, file_name)
create_file(xml_element, doc_name, file_path, node.server)
def add_param(xml_element: etree.Element, name: str, value: str) -> None:
"""
Add emane configuration parameter to xml element.
@ -127,13 +142,13 @@ def add_configurations(
add_param(xml_element, name, value)
def build_node_platform_xml(
def build_platform_xml(
emane_manager: "EmaneManager",
control_net: CtrlNet,
node: EmaneNet,
emane_net: EmaneNet,
iface: CoreInterface,
nem_id: int,
platform_xmls: Dict[str, etree.Element],
) -> int:
) -> None:
"""
Create platform xml for a specific node.
@ -141,313 +156,149 @@ def build_node_platform_xml(
configurations
:param control_net: control net node for this emane
network
:param node: node to write platform xml for
:param nem_id: nem id to use for interfaces for this node
:param platform_xmls: stores platform xml elements to append nem entries to
:param emane_net: emane network associated with interface
:param iface: interface running emane
:param nem_id: nem id to use for this interface
:return: the next nem id that can be used for creating platform xml files
"""
logging.debug(
"building emane platform xml for node(%s) nem_id(%s): %s",
node,
nem_id,
node.name,
# build nem xml
nem_definition = nem_file_name(iface)
nem_element = etree.Element(
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
)
nem_entries = {}
if node.model is None:
logging.warning("warning: EMANE network %s has no associated model", node.name)
return nem_id
for iface in node.get_ifaces():
logging.debug(
"building platform xml for interface(%s) nem_id(%s)", iface.name, nem_id
)
# build nem xml
nem_definition = nem_file_name(node.model, iface)
nem_element = etree.Element(
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
# check if this is an external transport, get default config if an interface
# specific one does not exist
config = emane_manager.get_iface_config(emane_net, iface)
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])
else:
transport_name = transport_file_name(iface)
transport_element = etree.SubElement(
nem_element, "transport", definition=transport_name
)
add_param(transport_element, "device", iface.name)
# check if this is an external transport, get default config if an interface
# specific one does not exist
config = emane_manager.get_iface_config(node.model.id, iface, node.model.name)
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])
transport_configs = {"otamanagerdevice", "eventservicedevice"}
platform_element = etree.Element("platform")
for configuration in emane_manager.emane_config.emulator_config:
name = configuration.id
if iface.is_raw() and name in transport_configs:
value = control_net.brname
else:
# build transport xml
transport_type = iface.transport_type
if not transport_type:
logging.info("warning: %s interface type unsupported!", iface.name)
transport_type = TransportType.RAW
transport_file = transport_file_name(node.id, transport_type)
transport_element = etree.SubElement(
nem_element, "transport", definition=transport_file
)
# add transport parameter
add_param(transport_element, "device", iface.name)
# add nem entry
nem_entries[iface] = nem_element
# merging code
key = iface.node.id
if iface.transport_type == TransportType.RAW:
key = "host"
otadev = control_net.brname
eventdev = control_net.brname
else:
otadev = None
eventdev = None
platform_element = platform_xmls.get(key)
if platform_element is None:
platform_element = etree.Element("platform")
if otadev:
emane_manager.set_config("otamanagerdevice", otadev)
if eventdev:
emane_manager.set_config("eventservicedevice", eventdev)
# append all platform options (except starting id) to doc
for configuration in emane_manager.emane_config.emulator_config:
name = configuration.id
if name == "platform_id_start":
continue
value = emane_manager.get_config(name)
add_param(platform_element, name, value)
# add platform xml
platform_xmls[key] = platform_element
platform_element.append(nem_element)
node.setnemid(iface, nem_id)
mac = _MAC_PREFIX + ":00:00:"
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
iface.set_mac(mac)
# increment nem id
nem_id += 1
value = emane_manager.get_config(name)
add_param(platform_element, name, value)
platform_element.append(nem_element)
mac = _MAC_PREFIX + ":00:00:"
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
iface.set_mac(mac)
doc_name = "platform"
for key in sorted(platform_xmls.keys()):
platform_element = platform_xmls[key]
if key == "host":
file_name = "platform.xml"
file_path = os.path.join(emane_manager.session.session_dir, file_name)
create_file(platform_element, doc_name, file_path)
else:
file_name = f"platform{key}.xml"
file_path = os.path.join(emane_manager.session.session_dir, file_name)
linked_node = emane_manager.session.nodes[key]
create_file(platform_element, doc_name, file_path, linked_node.server)
return nem_id
file_name = f"{iface.name}-platform.xml"
create_iface_file(iface, platform_element, doc_name, file_name)
def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None:
"""
Generate emane xml files required for node.
:param emane_manager: emane manager with emane
configurations
:param node: node to write platform xml for
:return: nothing
"""
logging.debug("building all emane xml for node(%s): %s", node, node.name)
if node.model is None:
return
# get model configurations
config = emane_manager.get_configs(node.model.id, node.model.name)
if not config:
return
# build XML for overall network EMANE configs
node.model.build_xml_files(config)
# build XML for specific interface (NEM) configs
need_virtual = False
need_raw = False
vtype = TransportType.VIRTUAL
rtype = TransportType.RAW
for iface in node.get_ifaces():
# check for interface specific emane configuration and write xml files
config = emane_manager.get_iface_config(node.model.id, iface, node.model.name)
if config:
node.model.build_xml_files(config, iface)
# check transport type needed for interface
if iface.transport_type == TransportType.VIRTUAL:
need_virtual = True
vtype = iface.transport_type
else:
need_raw = True
rtype = iface.transport_type
if need_virtual:
build_transport_xml(emane_manager, node, vtype)
if need_raw:
build_transport_xml(emane_manager, node, rtype)
def build_transport_xml(
emane_manager: "EmaneManager", node: EmaneNet, transport_type: TransportType
) -> None:
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
"""
Build transport xml file for node and transport type.
:param emane_manager: emane manager with emane
configurations
:param node: node to write platform xml for
:param transport_type: transport type to build xml for
:param iface: interface to build transport xml for
:param config: all current configuration values
:return: nothing
"""
transport_type = iface.transport_type
transport_element = etree.Element(
"transport",
name=f"{transport_type.value.capitalize()} Transport",
library=f"trans{transport_type.value.lower()}",
)
# add bitrate
add_param(transport_element, "bitrate", "0")
# get emane model cnfiguration
config = emane_manager.get_configs(node.id, node.model.name)
flowcontrol = config.get("flowcontrolenable", "0") == "1"
if transport_type == TransportType.VIRTUAL:
if iface.is_virtual():
device_path = "/dev/net/tun_flowctl"
if not os.path.exists(device_path):
device_path = "/dev/net/tun"
add_param(transport_element, "devicepath", device_path)
if flowcontrol:
add_param(transport_element, "flowcontrolenable", "on")
doc_name = "transport"
file_name = transport_file_name(node.id, transport_type)
file_path = os.path.join(emane_manager.session.session_dir, file_name)
create_file(transport_element, doc_name, file_path)
emane_manager.session.distributed.execute(
lambda x: create_file(transport_element, doc_name, file_path, x)
)
transport_name = transport_file_name(iface)
create_iface_file(iface, transport_element, doc_name, transport_name)
def create_phy_xml(
emane_model: "EmaneModel",
config: Dict[str, str],
file_path: str,
server: DistributedServer,
emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str]
) -> None:
"""
Create the phy xml document.
:param emane_model: emane model to create xml
:param iface: interface to create xml for
:param config: all current configuration values
:param file_path: path to write file to
:param server: remote server node
will run on, default is None for localhost
:return: nothing
"""
phy_element = etree.Element("phy", name=f"{emane_model.name} PHY")
if emane_model.phy_library:
phy_element.set("library", emane_model.phy_library)
add_configurations(
phy_element, emane_model.phy_config, config, emane_model.config_ignore
)
create_file(phy_element, "phy", file_path)
if server is not None:
create_file(phy_element, "phy", file_path, server)
else:
create_file(phy_element, "phy", file_path)
emane_model.session.distributed.execute(
lambda x: create_file(phy_element, "phy", file_path, x)
)
file_name = phy_file_name(iface)
create_iface_file(iface, phy_element, "phy", file_name)
def create_mac_xml(
emane_model: "EmaneModel",
config: Dict[str, str],
file_path: str,
server: DistributedServer,
emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str]
) -> None:
"""
Create the mac xml document.
:param emane_model: emane model to create xml
:param iface: interface to create xml for
:param config: all current configuration values
:param file_path: path to write file to
:param server: remote server node
will run on, default is None for localhost
:return: nothing
"""
if not emane_model.mac_library:
raise ValueError("must define emane model library")
raise CoreError("must define emane model library")
mac_element = etree.Element(
"mac", name=f"{emane_model.name} MAC", library=emane_model.mac_library
)
add_configurations(
mac_element, emane_model.mac_config, config, emane_model.config_ignore
)
create_file(mac_element, "mac", file_path)
if server is not None:
create_file(mac_element, "mac", file_path, server)
else:
create_file(mac_element, "mac", file_path)
emane_model.session.distributed.execute(
lambda x: create_file(mac_element, "mac", file_path, x)
)
file_name = mac_file_name(iface)
create_iface_file(iface, mac_element, "mac", file_name)
def create_nem_xml(
emane_model: "EmaneModel",
config: Dict[str, str],
nem_file: str,
transport_definition: str,
mac_definition: str,
phy_definition: str,
server: DistributedServer,
emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str]
) -> None:
"""
Create the nem xml document.
:param emane_model: emane model to create xml
:param iface: interface to create xml for
:param config: all current configuration values
:param nem_file: nem file path to write
:param transport_definition: transport file definition path
:param mac_definition: mac file definition path
:param phy_definition: phy file definition path
:param server: remote server node
will run on, default is None for localhost
:return: nothing
"""
nem_element = etree.Element("nem", name=f"{emane_model.name} NEM")
if is_external(config):
nem_element.set("type", "unstructured")
else:
etree.SubElement(nem_element, "transport", definition=transport_definition)
etree.SubElement(nem_element, "mac", definition=mac_definition)
etree.SubElement(nem_element, "phy", definition=phy_definition)
if server is not None:
create_file(nem_element, "nem", nem_file, server)
else:
create_file(nem_element, "nem", nem_file)
emane_model.session.distributed.execute(
lambda x: create_file(nem_element, "nem", nem_file, x)
)
transport_name = transport_file_name(iface)
etree.SubElement(nem_element, "transport", definition=transport_name)
mac_name = mac_file_name(iface)
etree.SubElement(nem_element, "mac", definition=mac_name)
phy_name = phy_file_name(iface)
etree.SubElement(nem_element, "phy", definition=phy_name)
nem_name = nem_file_name(iface)
create_iface_file(iface, nem_element, "nem", nem_name)
def create_event_service_xml(
@ -483,81 +334,52 @@ def create_event_service_xml(
create_file(event_element, "emaneeventmsgsvc", file_path, server)
def transport_file_name(node_id: int, transport_type: TransportType) -> str:
def transport_file_name(iface: CoreInterface) -> str:
"""
Create name for a transport xml file.
:param node_id: node id to generate transport file name for
:param transport_type: transport type to generate transport file
:return:
:param iface: interface running emane
:return: transport xml file name
"""
return f"n{node_id}trans{transport_type.value}.xml"
return f"{iface.name}-trans-{iface.transport_type.value}.xml"
def _basename(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
def nem_file_name(iface: CoreInterface) -> str:
"""
Create name that is leveraged for configuration file creation.
Return the string name for the NEM XML file, e.g. "eth0-nem.xml"
:param emane_model: emane model to create name for
:param iface: interface for this model
:return: basename used for file creation
:param iface: interface running emane
:return: nem xm file name
"""
name = f"n{emane_model.id}"
if iface:
node_id = iface.node.id
if emane_model.session.emane.get_iface_config(node_id, iface, emane_model.name):
name = iface.localname.replace(".", "_")
return f"{name}{emane_model.name}"
append = "-raw" if iface.is_raw() else ""
return f"{iface.name}-nem{append}.xml"
def nem_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
def shim_file_name(iface: CoreInterface = None) -> str:
"""
Return the string name for the NEM XML file, e.g. "n3rfpipenem.xml"
Return the string name for the SHIM XML file, e.g. "eth0-shim.xml"
:param emane_model: emane model to create file
:param iface: interface for this model
:return: nem xml filename
:param iface: interface running emane
:return: shim xml file name
"""
basename = _basename(emane_model, iface)
append = ""
if iface and iface.transport_type == TransportType.RAW:
append = "_raw"
return f"{basename}nem{append}.xml"
return f"{iface.name}-shim.xml"
def shim_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
def mac_file_name(iface: CoreInterface) -> str:
"""
Return the string name for the SHIM XML file, e.g. "commeffectshim.xml"
Return the string name for the MAC XML file, e.g. "eth0-mac.xml"
:param emane_model: emane model to create file
:param iface: interface for this model
:return: shim xml filename
:param iface: interface running emane
:return: mac xml file name
"""
name = _basename(emane_model, iface)
return f"{name}shim.xml"
return f"{iface.name}-mac.xml"
def mac_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
def phy_file_name(iface: CoreInterface) -> str:
"""
Return the string name for the MAC XML file, e.g. "n3rfpipemac.xml"
Return the string name for the PHY XML file, e.g. "eth0-phy.xml"
:param emane_model: emane model to create file
:param iface: interface for this model
:return: mac xml filename
:param iface: interface running emane
:return: phy xml file name
"""
name = _basename(emane_model, iface)
return f"{name}mac.xml"
def phy_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
"""
Return the string name for the PHY XML file, e.g. "n3rfpipephy.xml"
:param emane_model: emane model to create file
:param iface: interface for this model
:return: phy xml filename
"""
name = _basename(emane_model, iface)
return f"{name}phy.xml"
return f"{iface.name}-phy.xml"

View file

@ -12,7 +12,6 @@ from mock.mock import MagicMock
from core.api.grpc.client import InterfaceHelper
from core.api.grpc.server import CoreGrpcServer
from core.api.tlv.corehandlers import CoreHandler
from core.emane.emanemanager import EmaneManager
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes
from core.emulator.distributed import DistributedServer
@ -63,7 +62,6 @@ def patcher(request):
patch_manager.patch_obj(CoreNode, "nodefile")
patch_manager.patch_obj(Session, "write_state")
patch_manager.patch_obj(Session, "write_nodes")
patch_manager.patch_obj(EmaneManager, "buildxml")
yield patch_manager
patch_manager.shutdown()