From bd48e14348c05800930618aaa02f1bf616fb393c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 15:37:51 -0700 Subject: [PATCH] daemon: initial changes to rework logic to start emane for a given interface --- daemon/core/emane/commeffect.py | 19 +- daemon/core/emane/emanemanager.py | 279 +++++++++-------------- daemon/core/emane/emanemodel.py | 27 +-- daemon/core/emane/nodes.py | 66 +----- daemon/core/emulator/session.py | 4 +- daemon/core/xml/emanexml.py | 363 +++++++++++------------------- 6 files changed, 262 insertions(+), 496 deletions(-) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 610099f1..100af9a7 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -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( diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index fc561b5f..3317a5db 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -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 " diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 43fbc0fb..0576d6c3 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -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: diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index be95e6d0..dca85785 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -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 diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index c2573578..9f5364b9 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -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 diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index eece57c9..32ca0f67 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -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"