daemon: initial pass to revamp how node linking and link management is done, provides a consistent way to link all wired nodes and allows them to be configured for tc for the same behavior across the board
This commit is contained in:
parent
d684b8eb5a
commit
cd7f1a641e
19 changed files with 1393 additions and 1556 deletions
|
@ -12,12 +12,12 @@ from core import utils
|
|||
from core.emane.emanemodel import EmaneModel
|
||||
from core.emane.linkmonitor import EmaneLinkMonitor
|
||||
from core.emane.modelmanager import EmaneModelManager
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emane.nodes import EmaneNet, TunTap
|
||||
from core.emulator.data import LinkData
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes.base import CoreNetworkBase, CoreNode, NodeBase
|
||||
from core.nodes.interface import CoreInterface, TunTap
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.xml import emanexml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -224,11 +224,9 @@ class EmaneManager:
|
|||
:return: net, node, or interface model configuration
|
||||
"""
|
||||
model_name = emane_net.model.name
|
||||
config = None
|
||||
# try to retrieve interface specific configuration
|
||||
if iface.node_id is not None:
|
||||
key = utils.iface_config_id(iface.node.id, iface.node_id)
|
||||
config = self.get_config(key, model_name, default=False)
|
||||
key = utils.iface_config_id(iface.node.id, iface.id)
|
||||
config = self.get_config(key, model_name, default=False)
|
||||
# attempt to retrieve node specific config, when iface config is not present
|
||||
if not config:
|
||||
config = self.get_config(iface.node.id, model_name, default=False)
|
||||
|
@ -272,7 +270,8 @@ class EmaneManager:
|
|||
nodes = set()
|
||||
for emane_net in self._emane_nets.values():
|
||||
for iface in emane_net.get_ifaces():
|
||||
nodes.add(iface.node)
|
||||
if isinstance(iface.node, CoreNode):
|
||||
nodes.add(iface.node)
|
||||
return nodes
|
||||
|
||||
def setup(self) -> EmaneState:
|
||||
|
@ -323,7 +322,7 @@ class EmaneManager:
|
|||
for emane_net, iface in self.get_ifaces():
|
||||
self.start_iface(emane_net, iface)
|
||||
|
||||
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
|
||||
def start_iface(self, emane_net: EmaneNet, iface: TunTap) -> None:
|
||||
nem_id = self.next_nem_id(iface)
|
||||
nem_port = self.get_nem_port(iface)
|
||||
logger.info(
|
||||
|
@ -338,7 +337,7 @@ class EmaneManager:
|
|||
self.start_daemon(iface)
|
||||
self.install_iface(iface, config)
|
||||
|
||||
def get_ifaces(self) -> List[Tuple[EmaneNet, CoreInterface]]:
|
||||
def get_ifaces(self) -> List[Tuple[EmaneNet, TunTap]]:
|
||||
ifaces = []
|
||||
for emane_net in self._emane_nets.values():
|
||||
if not emane_net.model:
|
||||
|
@ -352,8 +351,9 @@ class EmaneManager:
|
|||
iface.name,
|
||||
)
|
||||
continue
|
||||
ifaces.append((emane_net, iface))
|
||||
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].node_id))
|
||||
if isinstance(iface, TunTap):
|
||||
ifaces.append((emane_net, iface))
|
||||
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].id))
|
||||
|
||||
def setup_control_channels(
|
||||
self, nem_id: int, iface: CoreInterface, config: Dict[str, str]
|
||||
|
@ -622,9 +622,9 @@ class EmaneManager:
|
|||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||
node.host_cmd(args, cwd=self.session.directory)
|
||||
|
||||
def install_iface(self, iface: CoreInterface, config: Dict[str, str]) -> None:
|
||||
def install_iface(self, iface: TunTap, config: Dict[str, str]) -> None:
|
||||
external = config.get("external", "0")
|
||||
if isinstance(iface, TunTap) and external == "0":
|
||||
if external == "0":
|
||||
iface.set_ips()
|
||||
# at this point we register location handlers for generating
|
||||
# EMANE location events
|
||||
|
@ -732,9 +732,6 @@ class EmaneManager:
|
|||
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,
|
||||
|
|
|
@ -4,7 +4,8 @@ share the same MAC+PHY model.
|
|||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Type
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
|
||||
|
||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||
from core.emulator.distributed import DistributedServer
|
||||
|
@ -15,7 +16,7 @@ from core.emulator.enumerations import (
|
|||
NodeTypes,
|
||||
RegisterTlvs,
|
||||
)
|
||||
from core.errors import CoreError
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes.base import CoreNetworkBase, CoreNode
|
||||
from core.nodes.interface import CoreInterface
|
||||
|
||||
|
@ -39,6 +40,114 @@ except ImportError:
|
|||
logger.debug("compatible emane python bindings not installed")
|
||||
|
||||
|
||||
class TunTap(CoreInterface):
|
||||
"""
|
||||
TUN/TAP virtual device in TAP mode
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
_id: int,
|
||||
name: str,
|
||||
localname: str,
|
||||
use_ovs: bool,
|
||||
node: CoreNode = None,
|
||||
server: "DistributedServer" = None,
|
||||
) -> None:
|
||||
super().__init__(_id, name, localname, use_ovs, node=node, server=server)
|
||||
self.node: CoreNode = node
|
||||
|
||||
def startup(self) -> None:
|
||||
"""
|
||||
Startup logic for a tunnel tap.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.up = True
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Shutdown functionality for a tunnel tap.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
self.up = False
|
||||
|
||||
def waitfor(
|
||||
self, func: Callable[[], int], attempts: int = 10, maxretrydelay: float = 0.25
|
||||
) -> bool:
|
||||
"""
|
||||
Wait for func() to return zero with exponential backoff.
|
||||
|
||||
:param func: function to wait for a result of zero
|
||||
:param attempts: number of attempts to wait for a zero result
|
||||
:param maxretrydelay: maximum retry delay
|
||||
:return: True if wait succeeded, False otherwise
|
||||
"""
|
||||
delay = 0.01
|
||||
result = False
|
||||
for i in range(1, attempts + 1):
|
||||
r = func()
|
||||
if r == 0:
|
||||
result = True
|
||||
break
|
||||
msg = f"attempt {i} failed with nonzero exit status {r}"
|
||||
if i < attempts + 1:
|
||||
msg += ", retrying..."
|
||||
logger.info(msg)
|
||||
time.sleep(delay)
|
||||
delay += delay
|
||||
if delay > maxretrydelay:
|
||||
delay = maxretrydelay
|
||||
else:
|
||||
msg += ", giving up"
|
||||
logger.info(msg)
|
||||
return result
|
||||
|
||||
def nodedevexists(self) -> int:
|
||||
"""
|
||||
Checks if device exists.
|
||||
|
||||
:return: 0 if device exists, 1 otherwise
|
||||
"""
|
||||
try:
|
||||
self.node.node_net_client.device_show(self.name)
|
||||
return 0
|
||||
except CoreCommandError:
|
||||
return 1
|
||||
|
||||
def waitfordevicenode(self) -> None:
|
||||
"""
|
||||
Check for presence of a node device - tap device may not appear right away waits.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
logger.debug("waiting for device node: %s", self.name)
|
||||
count = 0
|
||||
while True:
|
||||
result = self.waitfor(self.nodedevexists)
|
||||
if result:
|
||||
break
|
||||
should_retry = count < 5
|
||||
is_emane_running = self.node.session.emane.emanerunning(self.node)
|
||||
if all([should_retry, is_emane_running]):
|
||||
count += 1
|
||||
else:
|
||||
raise RuntimeError("node device failed to exist")
|
||||
|
||||
def set_ips(self) -> None:
|
||||
"""
|
||||
Set interface ip addresses.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.waitfordevicenode()
|
||||
for ip in self.ips():
|
||||
self.node.node_net_client.create_address(self.name, str(ip))
|
||||
|
||||
|
||||
class EmaneNet(CoreNetworkBase):
|
||||
"""
|
||||
EMANE node contains NEM configuration and causes connected nodes
|
||||
|
@ -49,7 +158,6 @@ class EmaneNet(CoreNetworkBase):
|
|||
apitype: NodeTypes = NodeTypes.EMANE
|
||||
linktype: LinkTypes = LinkTypes.WIRED
|
||||
type: str = "wlan"
|
||||
has_custom_iface: bool = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -77,10 +185,10 @@ class EmaneNet(CoreNetworkBase):
|
|||
self.conf = conf
|
||||
|
||||
def startup(self) -> None:
|
||||
pass
|
||||
self.up = True
|
||||
|
||||
def shutdown(self) -> None:
|
||||
pass
|
||||
self.up = False
|
||||
|
||||
def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||
pass
|
||||
|
@ -88,10 +196,13 @@ class EmaneNet(CoreNetworkBase):
|
|||
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||
pass
|
||||
|
||||
def linknet(self, net: "CoreNetworkBase") -> CoreInterface:
|
||||
raise CoreError("emane networks cannot be linked to other networks")
|
||||
|
||||
def updatemodel(self, config: Dict[str, str]) -> None:
|
||||
"""
|
||||
Update configuration for the current model.
|
||||
|
||||
:param config: configuration to update model with
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.model:
|
||||
raise CoreError(f"no model set to update for node({self.name})")
|
||||
logger.info("node(%s) updating model(%s): %s", self.id, self.model.name, config)
|
||||
|
@ -137,17 +248,39 @@ class EmaneNet(CoreNetworkBase):
|
|||
links.append(link)
|
||||
return links
|
||||
|
||||
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
|
||||
# TUN/TAP is not ready for addressing yet; the device may
|
||||
# take some time to appear, and installing it into a
|
||||
# namespace after it has been bound removes addressing;
|
||||
# save addresses with the interface now
|
||||
iface_id = node.newtuntap(iface_data.id, iface_data.name)
|
||||
node.attachnet(iface_id, self)
|
||||
iface = node.get_iface(iface_id)
|
||||
iface.set_mac(iface_data.mac)
|
||||
for ip in iface_data.get_ips():
|
||||
iface.add_ip(ip)
|
||||
def create_tuntap(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
|
||||
"""
|
||||
Create a tuntap interface for the provided node.
|
||||
|
||||
:param node: node to create tuntap interface for
|
||||
:param iface_data: interface data to create interface with
|
||||
:return: created tuntap interface
|
||||
"""
|
||||
with node.lock:
|
||||
if iface_data.id is not None and iface_data.id in node.ifaces:
|
||||
raise CoreError(
|
||||
f"node({self.id}) interface({iface_data.id}) already exists"
|
||||
)
|
||||
iface_id = (
|
||||
iface_data.id if iface_data.id is not None else node.next_iface_id()
|
||||
)
|
||||
name = iface_data.name if iface_data.name is not None else f"eth{iface_id}"
|
||||
session_id = self.session.short_session_id()
|
||||
localname = f"tap{node.id}.{iface_id}.{session_id}"
|
||||
iface = TunTap(iface_id, name, localname, self.session.use_ovs(), node=node)
|
||||
if iface_data.mac:
|
||||
iface.set_mac(iface_data.mac)
|
||||
for ip in iface_data.get_ips():
|
||||
iface.add_ip(ip)
|
||||
node.ifaces[iface_id] = iface
|
||||
self.attach(iface)
|
||||
if self.up:
|
||||
iface.startup()
|
||||
if self.session.state == EventTypes.RUNTIME_STATE:
|
||||
self.session.emane.start_iface(self, iface)
|
||||
return iface
|
||||
|
||||
def adopt_iface(self, iface: CoreInterface, name: str) -> None:
|
||||
raise CoreError(
|
||||
f"emane network({self.name}) do not support adopting interfaces"
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue