daemon/grpc/gui: further updates for a new working wireless node, added grpc support for control and additions to gui for creation

This commit is contained in:
Blake Harnden 2022-03-30 21:13:28 -07:00
parent e4a6ecf3c2
commit 84acb82c18
19 changed files with 399 additions and 90 deletions

View file

@ -766,8 +766,9 @@ class CoreNode(CoreNodeBase):
raise CoreError(f"adopting unknown iface({iface.name})")
# add iface to container namespace
self.net_client.device_ns(iface.name, str(self.pid))
# update iface name to container name
name = name if name else f"eth{iface_id}"
# use default iface name for container, if a unique name was not provided
if iface.name == name:
name = f"eth{iface_id}"
self.node_net_client.device_name(iface.name, name)
iface.name = name
# turn checksums off

View file

@ -1,13 +1,19 @@
"""
Defines a wireless node that allows programmatic link connectivity and
configuration between pairs of nodes.
"""
import logging
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Dict, Tuple
from core.emulator.data import LinkOptions
from core.emulator.links import CoreLink
from core.emulator.data import LinkData, LinkOptions
from core.emulator.enumerations import LinkTypes, MessageFlags
from core.errors import CoreError
from core.executables import NFTABLES
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface
from core.nodes.network import PtpNet
if TYPE_CHECKING:
from core.emulator.session import Session
@ -16,6 +22,24 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
def calc_distance(
point1: Tuple[float, float, float], point2: Tuple[float, float, float]
) -> float:
a = point1[0] - point2[0]
b = point1[1] - point2[1]
c = 0
if point1[2] is not None and point2[2] is not None:
c = point1[2] - point2[2]
return math.hypot(math.hypot(a, b), c)
@dataclass
class WirelessLink:
bridge1: str
bridge2: str
iface: CoreInterface
class WirelessNode(CoreNetworkBase):
def __init__(
self,
@ -26,7 +50,8 @@ class WirelessNode(CoreNetworkBase):
):
super().__init__(session, _id, name, server)
self.bridges: Dict[int, Tuple[CoreInterface, str]] = {}
self.links: Dict[Tuple[int, int], CoreLink] = {}
self.links: Dict[Tuple[int, int], WirelessLink] = {}
self.labels: Dict[Tuple[int, int], str] = {}
def startup(self) -> None:
if self.up:
@ -39,10 +64,8 @@ class WirelessNode(CoreNetworkBase):
self.net_client.delete_bridge(bridge_name)
self.host_cmd(f"{NFTABLES} delete table bridge {bridge_name}")
while self.links:
key, core_link = self.links.popitem()
core_link.iface1.shutdown()
core_link.iface2.shutdown()
core_link.ptp.shutdown()
_, link = self.links.popitem()
link.iface.shutdown()
self.up = False
def attach(self, iface: CoreInterface) -> None:
@ -90,26 +113,20 @@ class WirelessNode(CoreNetworkBase):
session_id = self.session.short_session_id()
name1 = f"we{self.id}.{node1.id}.{node2.id}.{session_id}"
name2 = f"we{self.id}.{node2.id}.{node1.id}.{session_id}"
iface1 = CoreInterface(0, name1, f"{name1}p", self.session.use_ovs())
iface1.startup()
iface2 = CoreInterface(0, name2, f"{name2}p", self.session.use_ovs())
iface2.startup()
link_name = f"wl{node1.id}.{node2.id}.{self.session.id}"
ptp = PtpNet(self.session)
ptp.brname = link_name
ptp.startup()
ptp.attach(iface1)
ptp.attach(iface2)
core_link = CoreLink(node1, iface1, node2, iface2, ptp)
self.links[key] = core_link
link_iface = CoreInterface(0, name1, name2, self.session.use_ovs())
link_iface.startup()
link = WirelessLink(bridge1, bridge2, link_iface)
self.links[key] = link
# assign ifaces to respective bridges
self.net_client.set_iface_master(bridge1, iface1.name)
self.net_client.set_iface_master(bridge2, iface2.name)
self.net_client.set_iface_master(bridge1, link_iface.name)
self.net_client.set_iface_master(bridge2, link_iface.localname)
# track bridge routes
node1_routes = routes.setdefault(node1.id, set())
node1_routes.add(name1)
node2_routes = routes.setdefault(node2.id, set())
node2_routes.add(name2)
# send link
self.send_link(node1.id, node2.id, MessageFlags.ADD)
for node_id, ifaces in routes.items():
iface, bridge_name = self.bridges[node_id]
ifaces = ",".join(ifaces)
@ -130,41 +147,94 @@ class WirelessNode(CoreNetworkBase):
def link_control(self, node1_id: int, node2_id: int, linked: bool) -> None:
key = (node1_id, node2_id) if node1_id < node2_id else (node2_id, node1_id)
core_link = self.links.get(key)
if not core_link:
link = self.links.get(key)
if not link:
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})")
ptp = core_link.ptp
iface1, iface2 = core_link.iface1, core_link.iface2
bridge1, bridge2 = link.bridge1, link.bridge2
iface = link.iface
if linked:
ptp.attach(iface1)
ptp.attach(iface2)
self.net_client.set_iface_master(bridge1, iface.name)
self.net_client.set_iface_master(bridge2, iface.localname)
message_type = MessageFlags.ADD
else:
ptp.detach(iface1)
ptp.detach(iface2)
self.net_client.delete_iface(bridge1, iface.name)
self.net_client.delete_iface(bridge2, iface.localname)
message_type = MessageFlags.DELETE
label = self.labels.get(key)
self.send_link(key[0], key[1], message_type, label)
def link_config(
self, node1_id: int, node2_id: int, options1: LinkOptions, options2: LinkOptions
) -> None:
key = (node1_id, node2_id) if node1_id < node2_id else (node2_id, node1_id)
link = self.links.get(key)
if not link:
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})")
iface = link.iface
iface.options.update(options1)
iface.set_config()
name, localname = iface.name, iface.localname
iface.name, iface.localname = localname, name
iface.options.update(options2)
iface.set_config()
iface.name, iface.localname = name, localname
if options1 == options2:
if options1.is_clear():
label = ""
else:
label = f"{options1.loss:.2f}%/{options1.delay}us"
else:
label = (
f"({options1.loss:.2f}%/{options1.delay}us) "
f"({options2.loss:.2f}%/{options2.delay}us)"
)
self.labels[key] = label
self.send_link(key[0], key[1], MessageFlags.NONE, label)
def send_link(
self,
node1_id: int,
node2_id: int,
iface1_options: LinkOptions,
iface2_options: LinkOptions,
message_type: MessageFlags,
label: str = None,
) -> None:
key = (node1_id, node2_id) if node1_id < node2_id else (node2_id, node1_id)
core_link = self.links.get(key)
if not core_link:
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})")
iface1, iface2 = core_link.iface1, core_link.iface2
iface1.options.update(iface1_options)
iface1.set_config()
iface2.options.update(iface2_options)
iface2.set_config()
"""
Broadcasts out a wireless link/unlink message.
:param node1_id: first node in link
:param node2_id: second node in link
:param message_type: type of link message to send
:param label: label to display for link
:return: nothing
"""
color = self.session.get_link_color(self.id)
link_data = LinkData(
message_type=message_type,
type=LinkTypes.WIRELESS,
node1_id=node1_id,
node2_id=node2_id,
network_id=self.id,
color=color,
label=label,
)
self.session.broadcast_link(link_data)
def position_callback(self, iface: CoreInterface) -> None:
logger.info(
"received position callback for node(%s) iface(%s)",
iface.node.name,
iface.name,
)
for oiface, bridge_name in self.bridges.values():
if iface == oiface:
continue
point1 = iface.node.position.get()
point2 = oiface.node.position.get()
distance = calc_distance(point1, point2) - 250
distance = max(distance, 0.0)
logger.info(
"n1(%s) n2(%s) d(%s)", iface.node.name, oiface.node.name, distance
)
loss = min((distance / 500) * 100.0, 100.0)
node1_id = iface.node.id
node2_id = oiface.node.id
options = LinkOptions(loss=loss, delay=0)
self.link_config(node1_id, node2_id, options, options)
def adopt_iface(self, iface: CoreInterface, name: str) -> None:
raise CoreError(f"{type(self)} does not support adopt interface")