daemon: update how emane is started on nodes, fixing issue with multiple interfaces running emane, added test case to check on this in the future

This commit is contained in:
Blake Harnden 2020-09-09 10:27:06 -07:00
parent 82d87445b6
commit d981d88a6f
8 changed files with 227 additions and 127 deletions

View file

@ -6,6 +6,7 @@ import logging
import os
import threading
from collections import OrderedDict
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
@ -28,7 +29,7 @@ from core.emulator.enumerations import (
RegisterTlvs,
)
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNode, NodeBase
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
from core.nodes.interface import CoreInterface, TunTap
from core.xml import emanexml
@ -68,6 +69,13 @@ class EmaneState(Enum):
NOT_READY = 2
@dataclass
class StartData:
emane_net: EmaneNet
node: CoreNodeBase
ifaces: List[CoreInterface] = field(default_factory=list)
class EmaneManager(ModelManager):
"""
EMANE controller object. Lives in a Session instance and is used for
@ -126,20 +134,15 @@ class EmaneManager(ModelManager):
self, emane_net: EmaneNet, iface: CoreInterface
) -> Dict[str, str]:
"""
Retrieve configuration for a given interface.
Retrieve configuration for a given interface, first checking for interface
specific config, node specific config, network specific config, and finally
falling back to the default configuration settings.
: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
# 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)
config = None
# try to retrieve interface specific configuration
if iface.node_id is not None:
@ -345,36 +348,45 @@ class EmaneManager(ModelManager):
self.buildeventservicexml()
with self._emane_node_lock:
logging.info("emane building xmls...")
for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id]
if not emane_net.model:
logging.error("emane net(%s) has no model", emane_net.name)
continue
for iface in emane_net.get_ifaces():
self.start_iface(emane_net, iface)
start_data = self.get_start_data()
for data in start_data:
self.start_node(data)
if self.links_enabled():
self.link_monitor.start()
return EmaneState.SUCCESS
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
if not iface.node:
logging.error(
"emane net(%s) connected interface(%s) missing node",
emane_net.name,
iface.name,
)
return
def get_start_data(self) -> List[StartData]:
node_map = {}
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(%s) missing node",
emane_net.name,
iface.name,
)
continue
start_node = node_map.setdefault(
iface.node, StartData(emane_net, iface.node)
)
start_node.ifaces.append(iface)
start_nodes = sorted(node_map.values(), key=lambda x: x.node.id)
for start_node in start_nodes:
start_node.ifaces = sorted(start_node.ifaces, key=lambda x: x.node_id)
return start_nodes
def start_node(self, data: StartData) -> None:
control_net = self.session.add_remove_control_net(
0, remove=False, conf_required=False
)
nem_id = self.next_nem_id()
self.set_nem(nem_id, iface)
self.write_nem(iface, nem_id)
emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id)
config = self.get_iface_config(emane_net, iface)
emane_net.model.build_xml_files(config, iface)
self.start_daemon(iface)
self.install_iface(emane_net, iface)
emanexml.build_platform_xml(self, control_net, data)
self.start_daemon(data.node)
for iface in data.ifaces:
self.install_iface(data.emane_net, iface)
def set_nem(self, nem_id: int, iface: CoreInterface) -> None:
if nem_id in self.nems_to_ifaces:
@ -435,8 +447,21 @@ class EmaneManager(ModelManager):
logging.info("stopping EMANE daemons")
if self.links_enabled():
self.link_monitor.stop()
self.deinstall_ifaces()
self.stopdaemons()
# shutdown interfaces and stop daemons
kill_emaned = "killall -q emane"
start_data = self.get_start_data()
for data in start_data:
node = data.node
if not node.up:
continue
for iface in data.ifaces:
if isinstance(node, CoreNode):
iface.shutdown()
iface.poshook = None
if isinstance(node, CoreNode):
node.cmd(kill_emaned, wait=False)
else:
node.host_cmd(kill_emaned, wait=False)
self.stopeventmonitor()
def check_node_models(self) -> None:
@ -524,7 +549,7 @@ class EmaneManager(ModelManager):
)
)
def start_daemon(self, iface: CoreInterface) -> None:
def start_daemon(self, node: CoreNodeBase) -> None:
"""
Start one EMANE daemon per node having a radio.
Add a control network even if the user has not configured one.
@ -539,8 +564,7 @@ class EmaneManager(ModelManager):
emanecmd = f"emane -d -l {loglevel}"
if realtime:
emanecmd += " -r"
node = iface.node
if iface.is_virtual():
if isinstance(node, CoreNode):
otagroup, _otaport = self.get_config("otamanagergroup").split(":")
otadev = self.get_config("otamanagerdevice")
otanetidx = self.session.get_control_net_index(otadev)
@ -569,35 +593,19 @@ class EmaneManager(ModelManager):
if eventservicenetidx >= 0 and eventgroup != otagroup:
node.node_net_client.create_route(eventgroup, eventdev)
# start emane
log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log")
platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml")
log_file = os.path.join(node.nodedir, f"{node.name}-emane.log")
platform_xml = os.path.join(node.nodedir, f"{node.name}-platform.xml")
args = f"{emanecmd} -f {log_file} {platform_xml}"
node.cmd(args)
logging.info("node(%s) emane daemon running: %s", node.name, args)
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")
log_file = os.path.join(path, f"{node.name}-emane.log")
platform_xml = os.path.join(path, f"{node.name}-platform.xml")
emanecmd += f" -f {log_file} {platform_xml}"
node.host_cmd(emanecmd, cwd=path)
logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd)
def stopdaemons(self) -> None:
"""
Kill the appropriate EMANE daemons.
"""
kill_emaned = "killall -q emane"
for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id]
for iface in emane_net.get_ifaces():
node = iface.node
if not node.up:
continue
if iface.is_raw():
node.host_cmd(kill_emaned, wait=False)
else:
node.cmd(kill_emaned, wait=False)
def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
config = self.get_iface_config(emane_net, iface)
external = config.get("external", "0")
@ -609,17 +617,6 @@ class EmaneManager(ModelManager):
iface.poshook = emane_net.setnemposition
iface.setposition()
def deinstall_ifaces(self) -> None:
"""
Uninstall TUN/TAP virtual interfaces.
"""
for key in sorted(self._emane_nets):
emane_net = self._emane_nets[key]
for iface in emane_net.get_ifaces():
if iface.is_virtual():
iface.shutdown()
iface.poshook = None
def doeventmonitor(self) -> bool:
"""
Returns boolean whether or not EMANE events will be monitored.
@ -783,6 +780,9 @@ class EmaneManager(ModelManager):
self.session.broadcast_node(node)
return True
def is_emane_net(self, net: Optional[CoreNetworkBase]) -> bool:
return isinstance(net, EmaneNet)
def emanerunning(self, node: CoreNode) -> bool:
"""
Return True if an EMANE process associated with the given node is running,