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:
parent
82d87445b6
commit
d981d88a6f
8 changed files with 227 additions and 127 deletions
|
@ -80,7 +80,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
nem_name = emanexml.nem_file_name(iface)
|
nem_name = emanexml.nem_file_name(iface)
|
||||||
shim_name = emanexml.shim_file_name(iface)
|
shim_name = emanexml.shim_file_name(iface)
|
||||||
etree.SubElement(nem_element, "shim", definition=shim_name)
|
etree.SubElement(nem_element, "shim", definition=shim_name)
|
||||||
emanexml.create_iface_file(iface, nem_element, "nem", nem_name)
|
emanexml.create_node_file(iface.node, nem_element, "nem", nem_name)
|
||||||
|
|
||||||
# create and write shim document
|
# create and write shim document
|
||||||
shim_element = etree.Element(
|
shim_element = etree.Element(
|
||||||
|
@ -99,7 +99,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
ff = config["filterfile"]
|
ff = config["filterfile"]
|
||||||
if ff.strip() != "":
|
if ff.strip() != "":
|
||||||
emanexml.add_param(shim_element, "filterfile", ff)
|
emanexml.add_param(shim_element, "filterfile", ff)
|
||||||
emanexml.create_iface_file(iface, shim_element, "shim", shim_name)
|
emanexml.create_node_file(iface.node, shim_element, "shim", shim_name)
|
||||||
|
|
||||||
# create transport xml
|
# create transport xml
|
||||||
emanexml.create_transport_xml(iface, config)
|
emanexml.create_transport_xml(iface, config)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ from core.emulator.enumerations import (
|
||||||
RegisterTlvs,
|
RegisterTlvs,
|
||||||
)
|
)
|
||||||
from core.errors import CoreCommandError, CoreError
|
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.nodes.interface import CoreInterface, TunTap
|
||||||
from core.xml import emanexml
|
from core.xml import emanexml
|
||||||
|
|
||||||
|
@ -68,6 +69,13 @@ class EmaneState(Enum):
|
||||||
NOT_READY = 2
|
NOT_READY = 2
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StartData:
|
||||||
|
emane_net: EmaneNet
|
||||||
|
node: CoreNodeBase
|
||||||
|
ifaces: List[CoreInterface] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class EmaneManager(ModelManager):
|
class EmaneManager(ModelManager):
|
||||||
"""
|
"""
|
||||||
EMANE controller object. Lives in a Session instance and is used for
|
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
|
self, emane_net: EmaneNet, iface: CoreInterface
|
||||||
) -> Dict[str, str]:
|
) -> 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 emane_net: emane network the interface is connected to
|
||||||
:param iface: interface running emane
|
:param iface: interface running emane
|
||||||
:return: net, node, or interface model configuration
|
:return: net, node, or interface model configuration
|
||||||
"""
|
"""
|
||||||
model_name = emane_net.model.name
|
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
|
config = None
|
||||||
# try to retrieve interface specific configuration
|
# try to retrieve interface specific configuration
|
||||||
if iface.node_id is not None:
|
if iface.node_id is not None:
|
||||||
|
@ -345,36 +348,45 @@ class EmaneManager(ModelManager):
|
||||||
self.buildeventservicexml()
|
self.buildeventservicexml()
|
||||||
with self._emane_node_lock:
|
with self._emane_node_lock:
|
||||||
logging.info("emane building xmls...")
|
logging.info("emane building xmls...")
|
||||||
for node_id in sorted(self._emane_nets):
|
start_data = self.get_start_data()
|
||||||
emane_net = self._emane_nets[node_id]
|
for data in start_data:
|
||||||
if not emane_net.model:
|
self.start_node(data)
|
||||||
logging.error("emane net(%s) has no model", emane_net.name)
|
|
||||||
continue
|
|
||||||
for iface in emane_net.get_ifaces():
|
|
||||||
self.start_iface(emane_net, iface)
|
|
||||||
if self.links_enabled():
|
if self.links_enabled():
|
||||||
self.link_monitor.start()
|
self.link_monitor.start()
|
||||||
return EmaneState.SUCCESS
|
return EmaneState.SUCCESS
|
||||||
|
|
||||||
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
|
def get_start_data(self) -> List[StartData]:
|
||||||
if not iface.node:
|
node_map = {}
|
||||||
logging.error(
|
for node_id in sorted(self._emane_nets):
|
||||||
"emane net(%s) connected interface(%s) missing node",
|
emane_net = self._emane_nets[node_id]
|
||||||
emane_net.name,
|
if not emane_net.model:
|
||||||
iface.name,
|
logging.error("emane net(%s) has no model", emane_net.name)
|
||||||
)
|
continue
|
||||||
return
|
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(
|
control_net = self.session.add_remove_control_net(
|
||||||
0, remove=False, conf_required=False
|
0, remove=False, conf_required=False
|
||||||
)
|
)
|
||||||
nem_id = self.next_nem_id()
|
emanexml.build_platform_xml(self, control_net, data)
|
||||||
self.set_nem(nem_id, iface)
|
self.start_daemon(data.node)
|
||||||
self.write_nem(iface, nem_id)
|
for iface in data.ifaces:
|
||||||
emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id)
|
self.install_iface(data.emane_net, iface)
|
||||||
config = self.get_iface_config(emane_net, iface)
|
|
||||||
emane_net.model.build_xml_files(config, iface)
|
|
||||||
self.start_daemon(iface)
|
|
||||||
self.install_iface(emane_net, iface)
|
|
||||||
|
|
||||||
def set_nem(self, nem_id: int, iface: CoreInterface) -> None:
|
def set_nem(self, nem_id: int, iface: CoreInterface) -> None:
|
||||||
if nem_id in self.nems_to_ifaces:
|
if nem_id in self.nems_to_ifaces:
|
||||||
|
@ -435,8 +447,21 @@ class EmaneManager(ModelManager):
|
||||||
logging.info("stopping EMANE daemons")
|
logging.info("stopping EMANE daemons")
|
||||||
if self.links_enabled():
|
if self.links_enabled():
|
||||||
self.link_monitor.stop()
|
self.link_monitor.stop()
|
||||||
self.deinstall_ifaces()
|
# shutdown interfaces and stop daemons
|
||||||
self.stopdaemons()
|
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()
|
self.stopeventmonitor()
|
||||||
|
|
||||||
def check_node_models(self) -> None:
|
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.
|
Start one EMANE daemon per node having a radio.
|
||||||
Add a control network even if the user has not configured one.
|
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}"
|
emanecmd = f"emane -d -l {loglevel}"
|
||||||
if realtime:
|
if realtime:
|
||||||
emanecmd += " -r"
|
emanecmd += " -r"
|
||||||
node = iface.node
|
if isinstance(node, CoreNode):
|
||||||
if iface.is_virtual():
|
|
||||||
otagroup, _otaport = self.get_config("otamanagergroup").split(":")
|
otagroup, _otaport = self.get_config("otamanagergroup").split(":")
|
||||||
otadev = self.get_config("otamanagerdevice")
|
otadev = self.get_config("otamanagerdevice")
|
||||||
otanetidx = self.session.get_control_net_index(otadev)
|
otanetidx = self.session.get_control_net_index(otadev)
|
||||||
|
@ -569,35 +593,19 @@ class EmaneManager(ModelManager):
|
||||||
if eventservicenetidx >= 0 and eventgroup != otagroup:
|
if eventservicenetidx >= 0 and eventgroup != otagroup:
|
||||||
node.node_net_client.create_route(eventgroup, eventdev)
|
node.node_net_client.create_route(eventgroup, eventdev)
|
||||||
# start emane
|
# start emane
|
||||||
log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log")
|
log_file = os.path.join(node.nodedir, f"{node.name}-emane.log")
|
||||||
platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml")
|
platform_xml = os.path.join(node.nodedir, f"{node.name}-platform.xml")
|
||||||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||||
node.cmd(args)
|
node.cmd(args)
|
||||||
logging.info("node(%s) emane daemon running: %s", node.name, args)
|
logging.info("node(%s) emane daemon running: %s", node.name, args)
|
||||||
else:
|
else:
|
||||||
path = self.session.session_dir
|
path = self.session.session_dir
|
||||||
log_file = os.path.join(path, f"{iface.name}-emane.log")
|
log_file = os.path.join(path, f"{node.name}-emane.log")
|
||||||
platform_xml = os.path.join(path, f"{iface.name}-platform.xml")
|
platform_xml = os.path.join(path, f"{node.name}-platform.xml")
|
||||||
emanecmd += f" -f {log_file} {platform_xml}"
|
emanecmd += f" -f {log_file} {platform_xml}"
|
||||||
node.host_cmd(emanecmd, cwd=path)
|
node.host_cmd(emanecmd, cwd=path)
|
||||||
logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd)
|
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:
|
def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
|
||||||
config = self.get_iface_config(emane_net, iface)
|
config = self.get_iface_config(emane_net, iface)
|
||||||
external = config.get("external", "0")
|
external = config.get("external", "0")
|
||||||
|
@ -609,17 +617,6 @@ class EmaneManager(ModelManager):
|
||||||
iface.poshook = emane_net.setnemposition
|
iface.poshook = emane_net.setnemposition
|
||||||
iface.setposition()
|
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:
|
def doeventmonitor(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns boolean whether or not EMANE events will be monitored.
|
Returns boolean whether or not EMANE events will be monitored.
|
||||||
|
@ -783,6 +780,9 @@ class EmaneManager(ModelManager):
|
||||||
self.session.broadcast_node(node)
|
self.session.broadcast_node(node)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def is_emane_net(self, net: Optional[CoreNetworkBase]) -> bool:
|
||||||
|
return isinstance(net, EmaneNet)
|
||||||
|
|
||||||
def emanerunning(self, node: CoreNode) -> bool:
|
def emanerunning(self, node: CoreNode) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if an EMANE process associated with the given node is running,
|
Return True if an EMANE process associated with the given node is running,
|
||||||
|
|
|
@ -11,6 +11,7 @@ EBTABLES: str = "ebtables"
|
||||||
MOUNT: str = "mount"
|
MOUNT: str = "mount"
|
||||||
UMOUNT: str = "umount"
|
UMOUNT: str = "umount"
|
||||||
OVS_VSCTL: str = "ovs-vsctl"
|
OVS_VSCTL: str = "ovs-vsctl"
|
||||||
|
TEST: str = "test"
|
||||||
|
|
||||||
COMMON_REQUIREMENTS: List[str] = [
|
COMMON_REQUIREMENTS: List[str] = [
|
||||||
BASH,
|
BASH,
|
||||||
|
@ -21,6 +22,7 @@ COMMON_REQUIREMENTS: List[str] = [
|
||||||
SYSCTL,
|
SYSCTL,
|
||||||
TC,
|
TC,
|
||||||
UMOUNT,
|
UMOUNT,
|
||||||
|
TEST,
|
||||||
]
|
]
|
||||||
VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD]
|
VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD]
|
||||||
OVS_REQUIREMENTS: List[str] = [OVS_VSCTL]
|
OVS_REQUIREMENTS: List[str] = [OVS_VSCTL]
|
||||||
|
|
|
@ -16,7 +16,7 @@ from core.configservice.dependencies import ConfigServiceDependencies
|
||||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||||
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
|
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.executables import MOUNT, VNODED
|
from core.executables import MOUNT, TEST, VNODED
|
||||||
from core.nodes.client import VnodeClient
|
from core.nodes.client import VnodeClient
|
||||||
from core.nodes.interface import CoreInterface, TunTap, Veth
|
from core.nodes.interface import CoreInterface, TunTap, Veth
|
||||||
from core.nodes.netclient import LinuxNetClient, get_net_client
|
from core.nodes.netclient import LinuxNetClient, get_net_client
|
||||||
|
@ -294,6 +294,16 @@ class CoreNodeBase(NodeBase):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def path_exists(self, path: str) -> bool:
|
||||||
|
"""
|
||||||
|
Determines if a file or directory path exists.
|
||||||
|
|
||||||
|
:param path: path to file or directory
|
||||||
|
:return: True if path exists, False otherwise
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def add_config_service(self, service_class: "ConfigServiceType") -> None:
|
def add_config_service(self, service_class: "ConfigServiceType") -> None:
|
||||||
"""
|
"""
|
||||||
Adds a configuration service to the node.
|
Adds a configuration service to the node.
|
||||||
|
@ -602,6 +612,19 @@ class CoreNode(CoreNodeBase):
|
||||||
args = self.client.create_cmd(args, shell)
|
args = self.client.create_cmd(args, shell)
|
||||||
return self.server.remote_cmd(args, wait=wait)
|
return self.server.remote_cmd(args, wait=wait)
|
||||||
|
|
||||||
|
def path_exists(self, path: str) -> bool:
|
||||||
|
"""
|
||||||
|
Determines if a file or directory path exists.
|
||||||
|
|
||||||
|
:param path: path to file or directory
|
||||||
|
:return: True if path exists, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.cmd(f"{TEST} -e {path}")
|
||||||
|
return True
|
||||||
|
except CoreCommandError:
|
||||||
|
return False
|
||||||
|
|
||||||
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
||||||
"""
|
"""
|
||||||
Create a terminal command string.
|
Create a terminal command string.
|
||||||
|
|
|
@ -528,8 +528,9 @@ class TunTap(CoreInterface):
|
||||||
# check if this is an EMANE interface; if so, continue
|
# check if this is an EMANE interface; if so, continue
|
||||||
# waiting if EMANE is still running
|
# waiting if EMANE is still running
|
||||||
should_retry = count < 5
|
should_retry = count < 5
|
||||||
is_emane_running = self.node.session.emane.emanerunning(self.node)
|
is_emane = self.session.emane.is_emane_net(self.net)
|
||||||
if all([should_retry, self.net.is_emane, is_emane_running]):
|
is_emane_running = self.session.emane.emanerunning(self.node)
|
||||||
|
if all([should_retry, is_emane, is_emane_running]):
|
||||||
count += 1
|
count += 1
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("node device failed to exist")
|
raise RuntimeError("node device failed to exist")
|
||||||
|
|
|
@ -11,7 +11,7 @@ from core.emulator.data import InterfaceData, LinkOptions
|
||||||
from core.emulator.distributed import DistributedServer
|
from core.emulator.distributed import DistributedServer
|
||||||
from core.emulator.enumerations import NodeTypes, TransportType
|
from core.emulator.enumerations import NodeTypes, TransportType
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.executables import MOUNT, UMOUNT
|
from core.executables import MOUNT, TEST, UMOUNT
|
||||||
from core.nodes.base import CoreNetworkBase, CoreNodeBase
|
from core.nodes.base import CoreNetworkBase, CoreNodeBase
|
||||||
from core.nodes.interface import CoreInterface
|
from core.nodes.interface import CoreInterface
|
||||||
from core.nodes.network import CoreNetwork, GreTap
|
from core.nodes.network import CoreNetwork, GreTap
|
||||||
|
@ -55,6 +55,19 @@ class PhysicalNode(CoreNodeBase):
|
||||||
|
|
||||||
self.rmnodedir()
|
self.rmnodedir()
|
||||||
|
|
||||||
|
def path_exists(self, path: str) -> bool:
|
||||||
|
"""
|
||||||
|
Determines if a file or directory path exists.
|
||||||
|
|
||||||
|
:param path: path to file or directory
|
||||||
|
:return: True if path exists, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.host_cmd(f"{TEST} -e {path}")
|
||||||
|
return True
|
||||||
|
except CoreCommandError:
|
||||||
|
return False
|
||||||
|
|
||||||
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
||||||
"""
|
"""
|
||||||
Create a terminal command string.
|
Create a terminal command string.
|
||||||
|
@ -291,6 +304,19 @@ class Rj45Node(CoreNodeBase):
|
||||||
self.up = False
|
self.up = False
|
||||||
self.restorestate()
|
self.restorestate()
|
||||||
|
|
||||||
|
def path_exists(self, path: str) -> bool:
|
||||||
|
"""
|
||||||
|
Determines if a file or directory path exists.
|
||||||
|
|
||||||
|
:param path: path to file or directory
|
||||||
|
:return: True if path exists, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.host_cmd(f"{TEST} -e {path}")
|
||||||
|
return True
|
||||||
|
except CoreCommandError:
|
||||||
|
return False
|
||||||
|
|
||||||
def new_iface(
|
def new_iface(
|
||||||
self, net: CoreNetworkBase, iface_data: InterfaceData
|
self, net: CoreNetworkBase, iface_data: InterfaceData
|
||||||
) -> CoreInterface:
|
) -> CoreInterface:
|
||||||
|
|
|
@ -7,15 +7,15 @@ from lxml import etree
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
from core.emane.nodes import EmaneNet
|
|
||||||
from core.emulator.distributed import DistributedServer
|
from core.emulator.distributed import DistributedServer
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
|
from core.nodes.base import CoreNode, CoreNodeBase
|
||||||
from core.nodes.interface import CoreInterface
|
from core.nodes.interface import CoreInterface
|
||||||
from core.nodes.network import CtrlNet
|
from core.nodes.network import CtrlNet
|
||||||
from core.xml import corexml
|
from core.xml import corexml
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.emane.emanemanager import EmaneManager
|
from core.emane.emanemanager import EmaneManager, StartData
|
||||||
from core.emane.emanemodel import EmaneModel
|
from core.emane.emanemodel import EmaneModel
|
||||||
|
|
||||||
_MAC_PREFIX = "02:02"
|
_MAC_PREFIX = "02:02"
|
||||||
|
@ -78,23 +78,22 @@ def create_file(
|
||||||
corexml.write_xml_file(xml_element, file_path, doctype=doctype)
|
corexml.write_xml_file(xml_element, file_path, doctype=doctype)
|
||||||
|
|
||||||
|
|
||||||
def create_iface_file(
|
def create_node_file(
|
||||||
iface: CoreInterface, xml_element: etree.Element, doc_name: str, file_name: str
|
node: CoreNodeBase, xml_element: etree.Element, doc_name: str, file_name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create emane xml for an interface.
|
Create emane xml for an interface.
|
||||||
|
|
||||||
:param iface: interface running emane
|
:param node: node running emane
|
||||||
:param xml_element: root element to write to file
|
:param xml_element: root element to write to file
|
||||||
:param doc_name: name to use in the emane doctype
|
:param doc_name: name to use in the emane doctype
|
||||||
:param file_name: name of xml file
|
:param file_name: name of xml file
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
node = iface.node
|
if isinstance(node, CoreNode):
|
||||||
if iface.is_raw():
|
|
||||||
file_path = os.path.join(node.session.session_dir, file_name)
|
|
||||||
else:
|
|
||||||
file_path = os.path.join(node.nodedir, file_name)
|
file_path = os.path.join(node.nodedir, file_name)
|
||||||
|
else:
|
||||||
|
file_path = os.path.join(node.session.session_dir, file_name)
|
||||||
create_file(xml_element, doc_name, file_path, node.server)
|
create_file(xml_element, doc_name, file_path, node.server)
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,11 +142,7 @@ def add_configurations(
|
||||||
|
|
||||||
|
|
||||||
def build_platform_xml(
|
def build_platform_xml(
|
||||||
emane_manager: "EmaneManager",
|
emane_manager: "EmaneManager", control_net: CtrlNet, data: "StartData"
|
||||||
control_net: CtrlNet,
|
|
||||||
emane_net: EmaneNet,
|
|
||||||
iface: CoreInterface,
|
|
||||||
nem_id: int,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create platform xml for a specific node.
|
Create platform xml for a specific node.
|
||||||
|
@ -156,50 +151,62 @@ def build_platform_xml(
|
||||||
configurations
|
configurations
|
||||||
:param control_net: control net node for this emane
|
:param control_net: control net node for this emane
|
||||||
network
|
network
|
||||||
:param emane_net: emane network associated with interface
|
:param data: start data for a node connected to emane and associated interfaces
|
||||||
: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
|
:return: the next nem id that can be used for creating platform xml files
|
||||||
"""
|
"""
|
||||||
# build nem xml
|
# create top level platform element
|
||||||
nem_definition = nem_file_name(iface)
|
|
||||||
nem_element = etree.Element(
|
|
||||||
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
|
|
||||||
)
|
|
||||||
|
|
||||||
# check if this is an external transport, get default config if an interface
|
|
||||||
# specific one does not exist
|
|
||||||
config = emane_manager.get_iface_config(emane_net, iface)
|
|
||||||
if is_external(config):
|
|
||||||
nem_element.set("transport", "external")
|
|
||||||
platform_endpoint = "platformendpoint"
|
|
||||||
add_param(nem_element, platform_endpoint, config[platform_endpoint])
|
|
||||||
transport_endpoint = "transportendpoint"
|
|
||||||
add_param(nem_element, transport_endpoint, config[transport_endpoint])
|
|
||||||
else:
|
|
||||||
transport_name = transport_file_name(iface)
|
|
||||||
transport_element = etree.SubElement(
|
|
||||||
nem_element, "transport", definition=transport_name
|
|
||||||
)
|
|
||||||
add_param(transport_element, "device", iface.name)
|
|
||||||
|
|
||||||
transport_configs = {"otamanagerdevice", "eventservicedevice"}
|
transport_configs = {"otamanagerdevice", "eventservicedevice"}
|
||||||
platform_element = etree.Element("platform")
|
platform_element = etree.Element("platform")
|
||||||
for configuration in emane_manager.emane_config.emulator_config:
|
for configuration in emane_manager.emane_config.emulator_config:
|
||||||
name = configuration.id
|
name = configuration.id
|
||||||
if iface.is_raw() and name in transport_configs:
|
if not isinstance(data.node, CoreNode) and name in transport_configs:
|
||||||
value = control_net.brname
|
value = control_net.brname
|
||||||
else:
|
else:
|
||||||
value = emane_manager.get_config(name)
|
value = emane_manager.get_config(name)
|
||||||
add_param(platform_element, name, value)
|
add_param(platform_element, name, value)
|
||||||
platform_element.append(nem_element)
|
|
||||||
mac = _MAC_PREFIX + ":00:00:"
|
# create nem xml entries for all interfaces
|
||||||
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
|
emane_net = data.emane_net
|
||||||
iface.set_mac(mac)
|
for iface in data.ifaces:
|
||||||
|
nem_id = emane_manager.next_nem_id()
|
||||||
|
emane_manager.set_nem(nem_id, iface)
|
||||||
|
emane_manager.write_nem(iface, nem_id)
|
||||||
|
config = emane_manager.get_iface_config(emane_net, iface)
|
||||||
|
emane_net.model.build_xml_files(config, iface)
|
||||||
|
|
||||||
|
# build nem xml
|
||||||
|
nem_definition = nem_file_name(iface)
|
||||||
|
nem_element = etree.Element(
|
||||||
|
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
|
||||||
|
)
|
||||||
|
|
||||||
|
# check if this is an external transport, get default config if an interface
|
||||||
|
# specific one does not exist
|
||||||
|
config = emane_manager.get_iface_config(emane_net, iface)
|
||||||
|
if is_external(config):
|
||||||
|
nem_element.set("transport", "external")
|
||||||
|
platform_endpoint = "platformendpoint"
|
||||||
|
add_param(nem_element, platform_endpoint, config[platform_endpoint])
|
||||||
|
transport_endpoint = "transportendpoint"
|
||||||
|
add_param(nem_element, transport_endpoint, config[transport_endpoint])
|
||||||
|
else:
|
||||||
|
transport_name = transport_file_name(iface)
|
||||||
|
transport_element = etree.SubElement(
|
||||||
|
nem_element, "transport", definition=transport_name
|
||||||
|
)
|
||||||
|
add_param(transport_element, "device", iface.name)
|
||||||
|
|
||||||
|
# add nem element to platform element
|
||||||
|
platform_element.append(nem_element)
|
||||||
|
|
||||||
|
# generate and assign interface mac address based on 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"
|
doc_name = "platform"
|
||||||
file_name = f"{iface.name}-platform.xml"
|
file_name = f"{data.node.name}-platform.xml"
|
||||||
create_iface_file(iface, platform_element, doc_name, file_name)
|
create_node_file(data.node, platform_element, doc_name, file_name)
|
||||||
|
|
||||||
|
|
||||||
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
|
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
|
||||||
|
@ -220,16 +227,16 @@ def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
|
||||||
|
|
||||||
# get emane model cnfiguration
|
# get emane model cnfiguration
|
||||||
flowcontrol = config.get("flowcontrolenable", "0") == "1"
|
flowcontrol = config.get("flowcontrolenable", "0") == "1"
|
||||||
if iface.is_virtual():
|
if isinstance(iface.node, CoreNode):
|
||||||
device_path = "/dev/net/tun_flowctl"
|
device_path = "/dev/net/tun_flowctl"
|
||||||
if not os.path.exists(device_path):
|
if not iface.node.path_exists(device_path):
|
||||||
device_path = "/dev/net/tun"
|
device_path = "/dev/net/tun"
|
||||||
add_param(transport_element, "devicepath", device_path)
|
add_param(transport_element, "devicepath", device_path)
|
||||||
if flowcontrol:
|
if flowcontrol:
|
||||||
add_param(transport_element, "flowcontrolenable", "on")
|
add_param(transport_element, "flowcontrolenable", "on")
|
||||||
doc_name = "transport"
|
doc_name = "transport"
|
||||||
transport_name = transport_file_name(iface)
|
transport_name = transport_file_name(iface)
|
||||||
create_iface_file(iface, transport_element, doc_name, transport_name)
|
create_node_file(iface.node, transport_element, doc_name, transport_name)
|
||||||
|
|
||||||
|
|
||||||
def create_phy_xml(
|
def create_phy_xml(
|
||||||
|
@ -250,7 +257,7 @@ def create_phy_xml(
|
||||||
phy_element, emane_model.phy_config, config, emane_model.config_ignore
|
phy_element, emane_model.phy_config, config, emane_model.config_ignore
|
||||||
)
|
)
|
||||||
file_name = phy_file_name(iface)
|
file_name = phy_file_name(iface)
|
||||||
create_iface_file(iface, phy_element, "phy", file_name)
|
create_node_file(iface.node, phy_element, "phy", file_name)
|
||||||
|
|
||||||
|
|
||||||
def create_mac_xml(
|
def create_mac_xml(
|
||||||
|
@ -273,7 +280,7 @@ def create_mac_xml(
|
||||||
mac_element, emane_model.mac_config, config, emane_model.config_ignore
|
mac_element, emane_model.mac_config, config, emane_model.config_ignore
|
||||||
)
|
)
|
||||||
file_name = mac_file_name(iface)
|
file_name = mac_file_name(iface)
|
||||||
create_iface_file(iface, mac_element, "mac", file_name)
|
create_node_file(iface.node, mac_element, "mac", file_name)
|
||||||
|
|
||||||
|
|
||||||
def create_nem_xml(
|
def create_nem_xml(
|
||||||
|
@ -298,7 +305,7 @@ def create_nem_xml(
|
||||||
phy_name = phy_file_name(iface)
|
phy_name = phy_file_name(iface)
|
||||||
etree.SubElement(nem_element, "phy", definition=phy_name)
|
etree.SubElement(nem_element, "phy", definition=phy_name)
|
||||||
nem_name = nem_file_name(iface)
|
nem_name = nem_file_name(iface)
|
||||||
create_iface_file(iface, nem_element, "nem", nem_name)
|
create_node_file(iface.node, nem_element, "nem", nem_name)
|
||||||
|
|
||||||
|
|
||||||
def create_event_service_xml(
|
def create_event_service_xml(
|
||||||
|
@ -351,7 +358,7 @@ def nem_file_name(iface: CoreInterface) -> str:
|
||||||
:param iface: interface running emane
|
:param iface: interface running emane
|
||||||
:return: nem xm file name
|
:return: nem xm file name
|
||||||
"""
|
"""
|
||||||
append = "-raw" if iface.is_raw() else ""
|
append = "-raw" if not isinstance(iface.node, CoreNode) else ""
|
||||||
return f"{iface.name}-nem{append}.xml"
|
return f"{iface.name}-nem{append}.xml"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,47 @@ def ping(
|
||||||
|
|
||||||
|
|
||||||
class TestEmane:
|
class TestEmane:
|
||||||
|
def test_two_emane_interfaces(self, session: Session):
|
||||||
|
"""
|
||||||
|
Test nodes running multiple emane interfaces.
|
||||||
|
|
||||||
|
:param core.emulator.coreemu.EmuSession session: session for test
|
||||||
|
"""
|
||||||
|
# create emane node for networking the core nodes
|
||||||
|
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||||
|
options = NodeOptions()
|
||||||
|
options.set_position(80, 50)
|
||||||
|
options.emane = EmaneIeee80211abgModel.name
|
||||||
|
emane_net1 = session.add_node(EmaneNet, options=options)
|
||||||
|
options.emane = EmaneRfPipeModel.name
|
||||||
|
emane_net2 = session.add_node(EmaneNet, options=options)
|
||||||
|
|
||||||
|
# create nodes
|
||||||
|
options = NodeOptions(model="mdr")
|
||||||
|
options.set_position(150, 150)
|
||||||
|
node1 = session.add_node(CoreNode, options=options)
|
||||||
|
options.set_position(300, 150)
|
||||||
|
node2 = session.add_node(CoreNode, options=options)
|
||||||
|
|
||||||
|
# create interfaces
|
||||||
|
ip_prefix1 = IpPrefixes("10.0.0.0/24")
|
||||||
|
ip_prefix2 = IpPrefixes("10.0.1.0/24")
|
||||||
|
for i, node in enumerate([node1, node2]):
|
||||||
|
node.setposition(x=150 * (i + 1), y=150)
|
||||||
|
iface_data = ip_prefix1.create_iface(node)
|
||||||
|
session.add_link(node.id, emane_net1.id, iface1_data=iface_data)
|
||||||
|
iface_data = ip_prefix2.create_iface(node)
|
||||||
|
session.add_link(node.id, emane_net2.id, iface1_data=iface_data)
|
||||||
|
|
||||||
|
# instantiate session
|
||||||
|
session.instantiate()
|
||||||
|
|
||||||
|
# ping node2 from node1 on both interfaces and check success
|
||||||
|
status = ping(node1, node2, ip_prefix1, count=5)
|
||||||
|
assert not status
|
||||||
|
status = ping(node1, node2, ip_prefix2, count=5)
|
||||||
|
assert not status
|
||||||
|
|
||||||
@pytest.mark.parametrize("model", _EMANE_MODELS)
|
@pytest.mark.parametrize("model", _EMANE_MODELS)
|
||||||
def test_models(
|
def test_models(
|
||||||
self, session: Session, model: Type[EmaneModel], ip_prefixes: IpPrefixes
|
self, session: Session, model: Type[EmaneModel], ip_prefixes: IpPrefixes
|
||||||
|
|
Loading…
Add table
Reference in a new issue