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

@ -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 "