daemon: initial changes to rework logic to start emane for a given interface

This commit is contained in:
Blake Harnden 2020-07-02 15:37:51 -07:00
parent da9c0d0660
commit bd48e14348
6 changed files with 262 additions and 496 deletions

View file

@ -62,9 +62,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,22 +73,25 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
:param iface: interface for the emane node
:return: nothing
"""
# interface node
node = iface.node
# retrieve xml names
nem_name = emanexml.nem_file_name(self, iface)
shim_name = emanexml.shim_file_name(self, iface)
nem_name = emanexml.nem_file_name(iface)
shim_name = emanexml.shim_file_name(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:
if iface.transport_type == TransportType.RAW:
transport_type = TransportType.RAW
transport_file = emanexml.transport_file_name(self.id, transport_type)
transport_file = emanexml.transport_file_name(iface, transport_type)
etree.SubElement(nem_element, "transport", definition=transport_file)
# set shim configuration
etree.SubElement(nem_element, "shim", definition=shim_name)
nem_file = os.path.join(self.session.session_dir, nem_name)
nem_file = os.path.join(node.nodedir, nem_name)
emanexml.create_file(nem_element, "nem", nem_file)
# create and write shim document
@ -111,7 +112,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
if ff.strip() != "":
emanexml.add_param(shim_element, "filterfile", ff)
shim_file = os.path.join(self.session.session_dir, shim_name)
shim_file = os.path.join(node.nodedir, shim_name)
emanexml.create_file(shim_element, "shim", shim_file)
def linkconfig(

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
@ -25,11 +26,11 @@ from core.emulator.enumerations import (
LinkTypes,
MessageFlags,
RegisterTlvs,
TransportType,
)
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.interface import CoreInterface, TunTap
from core.nodes.physical import Rj45Node
from core.xml import emanexml
@ -63,6 +64,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 +79,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 +92,7 @@ class EmaneManager(ModelManager):
"""
super().__init__()
self.session: "Session" = session
self.nems: Dict[int, CoreInterface] = {}
self._emane_nets: Dict[int, EmaneNet] = {}
self._emane_node_lock: threading.Lock = threading.Lock()
# port numbers are allocated from these counters
@ -111,46 +117,47 @@ 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:
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
"""
model_name = emane_net.model.name
# 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)
return self.get_configs(node_id=emane_net.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)
config = self.get_configs(node_id=emane_net.id, config_type=model_name)
return config
def config_reset(self, node_id: int = None) -> None:
@ -260,14 +267,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.
@ -288,7 +294,7 @@ class EmaneManager(ModelManager):
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 +310,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
@ -319,16 +325,16 @@ class EmaneManager(ModelManager):
"EMANE cannot start, check core config. 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 +343,49 @@ 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))
# on master, control network bridge added earlier in startup()
control_net = self.session.add_remove_control_net(
0, remove=False, conf_required=False
)
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():
if not iface.node:
logging.error(
"emane net(%s) connected interface missing node",
emane_net.name,
)
continue
nem_id = self.next_nem_id()
self.nems[nem_id] = iface
self.write_nem(iface, nem_id)
emanexml.build_platform_xml(
self, control_net, emane_net, iface, nem_id
)
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")
emanexml.build_model_xmls(self, emane_net, iface)
self.start_daemon(iface)
self.install_iface(emane_net, iface)
if self.links_enabled():
self.link_monitor.start()
return EmaneManager.SUCCESS
return EmaneState.SUCCESS
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 +396,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 +413,7 @@ 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.clear()
def shutdown(self) -> None:
"""
@ -422,40 +429,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,7 +454,7 @@ 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)
emane_net.setmodel(model_class, config)
def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]]:
"""
@ -473,7 +463,6 @@ class EmaneManager(ModelManager):
"""
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)
@ -481,7 +470,6 @@ class EmaneManager(ModelManager):
break
else:
emane_node = None
return emane_node, iface
def get_nem_link(
@ -507,38 +495,6 @@ class EmaneManager(ModelManager):
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 +527,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.
@ -583,69 +539,51 @@ class EmaneManager(ModelManager):
if 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 not isinstance(node, Rj45Node):
# 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
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)
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}"
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)
def stopdaemons(self) -> None:
"""
@ -674,23 +612,27 @@ class EmaneManager(ModelManager):
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.transport_type == TransportType.VIRTUAL:
iface.shutdown()
iface.poshook = None
def doeventmonitor(self) -> bool:
"""
@ -718,7 +660,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 "

View file

@ -96,9 +96,7 @@ 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.
@ -107,33 +105,30 @@ class EmaneModel(WirelessModel):
:param iface: interface for the emane node
: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
nem_name = emanexml.nem_file_name(iface)
mac_name = emanexml.mac_file_name(iface)
phy_name = emanexml.phy_file_name(iface)
# check if this is external
transport_type = TransportType.VIRTUAL
if iface and iface.transport_type == TransportType.RAW:
if iface.transport_type == TransportType.RAW:
transport_type = TransportType.RAW
transport_name = emanexml.transport_file_name(self.id, transport_type)
transport_name = emanexml.transport_file_name(iface, transport_type)
node = iface.node
server = node.server
# create nem xml file
nem_file = os.path.join(self.session.session_dir, nem_name)
nem_file = os.path.join(node.nodedir, 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)
mac_file = os.path.join(node.nodedir, 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)
phy_file = os.path.join(node.nodedir, phy_name)
emanexml.create_phy_xml(self, config, phy_file, server)
def post_startup(self) -> None:

View file

@ -8,17 +8,10 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
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.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes, RegisterTlvs
from core.errors import CoreError
from core.nodes.base import CoreNetworkBase, CoreNode
from core.nodes.interface import CoreInterface, TunTap
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emane.emanemodel import EmaneModel
@ -139,45 +132,6 @@ class EmaneNet(CoreNetworkBase):
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]]:
@ -275,20 +229,4 @@ class EmaneNet(CoreNetworkBase):
iface.set_mac(iface_data.mac)
for ip in iface_data.get_ips():
iface.add_ip(ip)
# TODO: if added during runtime start EMANE
if self.session.state == EventTypes.RUNTIME_STATE:
logging.info("startup emane for node: %s", node.name)
# create specific xml if needed
config = self.session.emane.get_iface_config(
self.model.id, iface, self.model.name
)
if config:
self.model.build_xml_files(config, iface)
# start emane daemon
# install netif
# add nem to nemfile
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,
@ -1181,7 +1181,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

@ -10,6 +10,7 @@ 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 +41,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
@ -127,13 +124,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,175 +138,121 @@ 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:
# 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(iface, transport_type)
transport_element = etree.SubElement(
nem_element, "transport", definition=transport_file
)
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])
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
# determine platform element to add xml to
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 = etree.Element("platform")
if otadev:
emane_manager.set_config("otamanagerdevice", otadev)
if eventdev:
emane_manager.set_config("eventservicedevice", eventdev)
for configuration in emane_manager.emane_config.emulator_config:
name = configuration.id
value = emane_manager.get_config(name)
add_param(platform_element, name, value)
platform_element.append(nem_element)
emane_net.setnemid(iface, nem_id)
mac = _MAC_PREFIX + ":00:00:"
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
iface.set_mac(mac)
doc_name = "platform"
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
server = None
if key == "host":
file_name = "platform.xml"
file_path = os.path.join(emane_manager.session.session_dir, file_name)
else:
node = iface.node
file_name = f"{iface.name}-platform.xml"
file_path = os.path.join(node.nodedir, file_name)
server = node.server
create_file(platform_element, doc_name, file_path, server)
def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None:
def build_model_xmls(
manager: "EmaneManager", emane_net: EmaneNet, iface: CoreInterface
) -> None:
"""
Generate emane xml files required for node.
:param emane_manager: emane manager with emane
:param manager: emane manager with emane
configurations
:param node: node to write platform xml for
:param emane_net: emane network associated with interface
:param iface: interface to create emane 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
# check for interface specific emane configuration and write xml files
config = manager.get_iface_config(emane_net, iface)
emane_net.model.build_xml_files(config, iface)
# check transport type needed for interface
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 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)
build_transport_xml(manager, emane_net, iface, vtype)
if need_raw:
build_transport_xml(emane_manager, node, rtype)
build_transport_xml(manager, emane_net, iface, rtype)
def build_transport_xml(
emane_manager: "EmaneManager", node: EmaneNet, transport_type: TransportType
manager: "EmaneManager",
emane_net: EmaneNet,
iface: CoreInterface,
transport_type: TransportType,
) -> 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 manager: emane manager with emane configurations
:param emane_net: emane network associated with interface
:param iface: interface to build transport xml for
:param transport_type: transport type to build xml for
:return: nothing
"""
@ -318,28 +261,24 @@ def build_transport_xml(
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)
config = manager.get_iface_config(emane_net, iface)
flowcontrol = config.get("flowcontrolenable", "0") == "1"
if transport_type == TransportType.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)
node = iface.node
file_name = transport_file_name(iface, transport_type)
file_path = os.path.join(node.nodedir, file_name)
create_file(transport_element, doc_name, file_path)
emane_manager.session.distributed.execute(
manager.session.distributed.execute(
lambda x: create_file(transport_element, doc_name, file_path, x)
)
@ -348,7 +287,7 @@ def create_phy_xml(
emane_model: "EmaneModel",
config: Dict[str, str],
file_path: str,
server: DistributedServer,
server: Optional[DistributedServer],
) -> None:
"""
Create the phy xml document.
@ -363,25 +302,17 @@ def create_phy_xml(
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)
)
create_file(phy_element, "phy", file_path, server)
def create_mac_xml(
emane_model: "EmaneModel",
config: Dict[str, str],
file_path: str,
server: DistributedServer,
server: Optional[DistributedServer],
) -> None:
"""
Create the mac xml document.
@ -394,22 +325,14 @@ def create_mac_xml(
: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)
)
create_file(mac_element, "mac", file_path, server)
def create_nem_xml(
@ -419,7 +342,7 @@ def create_nem_xml(
transport_definition: str,
mac_definition: str,
phy_definition: str,
server: DistributedServer,
server: Optional[DistributedServer],
) -> None:
"""
Create the nem xml document.
@ -441,13 +364,7 @@ def create_nem_xml(
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)
)
create_file(nem_element, "nem", nem_file, server)
def create_event_service_xml(
@ -483,81 +400,55 @@ 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, transport_type: TransportType) -> str:
"""
Create name for a transport xml file.
:param node_id: node id to generate transport file name for
:param iface: interface running emane
:param transport_type: transport type to generate transport file
:return:
:return: transport xml file name
"""
return f"n{node_id}trans{transport_type.value}.xml"
return f"{iface.name}-trans-{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}"
def nem_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
"""
Return the string name for the NEM XML file, e.g. "n3rfpipenem.xml"
:param emane_model: emane model to create file
:param iface: interface for this model
:return: nem xml filename
"""
basename = _basename(emane_model, iface)
append = ""
if iface and iface.transport_type == TransportType.RAW:
append = "_raw"
return f"{basename}nem{append}.xml"
append = "-raw"
return f"{iface.name}-nem{append}.xml"
def shim_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
def shim_file_name(iface: CoreInterface = None) -> str:
"""
Return the string name for the SHIM XML file, e.g. "commeffectshim.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: shim xml filename
:param iface: interface running emane
:return: shim xml file name
"""
name = _basename(emane_model, iface)
return f"{name}shim.xml"
return f"{iface.name}-shim.xml"
def mac_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
def mac_file_name(iface: CoreInterface) -> str:
"""
Return the string name for the MAC XML file, e.g. "n3rfpipemac.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: mac xml filename
:param iface: interface running emane
:return: mac xml file name
"""
name = _basename(emane_model, iface)
return f"{name}mac.xml"
return f"{iface.name}-mac.xml"
def phy_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str:
def phy_file_name(iface: CoreInterface) -> str:
"""
Return the string name for the PHY XML file, e.g. "n3rfpipephy.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: phy xml filename
:param iface: interface running emane
:return: phy xml file name
"""
name = _basename(emane_model, iface)
return f"{name}phy.xml"
return f"{iface.name}-phy.xml"