Skip to content

Commit

Permalink
daemon: adjustments for core nodes to support a net cmd, to help orch…
Browse files Browse the repository at this point in the history
…estrate docker/podman containers using only the network namespace
  • Loading branch information
bharnden committed Dec 13, 2023
1 parent 3e051a3 commit 094790a
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 39 deletions.
4 changes: 1 addition & 3 deletions daemon/core/emulator/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ def clear(self) -> None:
self.location.reset()
self.mobility.config_reset()
self.link_colors.clear()
self.control_net_manager.remove_nets()

def set_location(self, lat: float, lon: float, alt: float, scale: float) -> None:
"""
Expand Down Expand Up @@ -1035,9 +1036,6 @@ def data_collect(self) -> None:
# update control interface hosts
self.control_net_manager.clear_etc_hosts()

# remove control networks
self.control_net_manager.remove_nets()

def short_session_id(self) -> str:
"""
Return a shorter version of the session ID, appropriate for
Expand Down
13 changes: 13 additions & 0 deletions daemon/core/nodes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,19 @@ def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
"""
return self.host_cmd(args, wait=wait, shell=shell)

def net_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
"""
Runs a network command that is in the context of a node, default is to run a
standard host command.
:param args: command to run
:param wait: True to wait for status, False otherwise
:param shell: True to use shell, False otherwise
:return: combined stdout and stderr
:raises CoreCommandError: when a non-zero exit status occurs
"""
return self.cmd(args, wait, shell)

def setposition(self, x: float = None, y: float = None, z: float = None) -> bool:
"""
Set the (x,y,z) position of the object.
Expand Down
40 changes: 40 additions & 0 deletions daemon/core/nodes/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from core.errors import CoreCommandError, CoreError
from core.executables import BASH
from core.nodes.base import CoreNode, CoreNodeOptions
from core.nodes.netclient import LinuxNetClient, get_net_client

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -99,6 +100,16 @@ def create_options(cls) -> DockerOptions:
"""
return DockerOptions()

def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient:
"""
Create node network client for running network commands within the nodes
container.
:param use_ovs: True for OVS bridges, False for Linux bridges
:return: node network client
"""
return get_net_client(use_ovs, self.cmd, self.net_cmd)

def create_cmd(self, args: str, shell: bool = False) -> str:
"""
Create command used to run commands within the context of a node.
Expand Down Expand Up @@ -128,6 +139,35 @@ def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
else:
return self.server.remote_cmd(args, wait=wait, env=self.env)

def create_net_cmd(self, args: str, shell: bool = False) -> str:
"""
Create command used to run network commands within the context of a node.
:param args: command arguments
:param shell: True to run shell like, False otherwise
:return: node command
"""
if shell:
args = f"{BASH} -c {shlex.quote(args)}"
return f"nsenter -t {self.pid} -n -- {args}"

def net_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
"""
Runs a command that is used to configure and setup the network within a
node.
:param args: command to run
:param wait: True to wait for status, False otherwise
:param shell: True to use shell, False otherwise
:return: combined stdout and stderr
:raises CoreCommandError: when a non-zero exit status occurs
"""
args = self.create_net_cmd(args, shell)
if self.server is None:
return utils.cmd(args, wait=wait, shell=shell, env=self.env)
else:
return self.server.remote_cmd(args, wait=wait, env=self.env)

def _unique_name(self, name: str) -> str:
"""
Creates a session/node unique prefixed name for the provided input.
Expand Down
4 changes: 2 additions & 2 deletions daemon/core/nodes/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,15 @@ def set_config(self) -> None:
if self.has_netem:
cmd = tc_clear_cmd(self.name)
if self.node:
self.node.cmd(cmd)
self.node.net_cmd(cmd)
else:
self.host_cmd(cmd)
self.has_netem = False
# set updated settings
else:
cmd = tc_cmd(self.name, self.options, self.mtu)
if self.node:
self.node.cmd(cmd)
self.node.net_cmd(cmd)
else:
self.host_cmd(cmd)
self.has_netem = True
Expand Down
77 changes: 43 additions & 34 deletions daemon/core/nodes/netclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ class LinuxNetClient:
Client for creating Linux bridges and ip interfaces for nodes.
"""

def __init__(self, run: Callable[..., str]) -> None:
def __init__(self, run: Callable[..., str], run_net: Callable[..., str]) -> None:
"""
Create LinuxNetClient instance.
:param run: function to run commands with
:param run: function to run commands within node context
:param run_net: function to run commands within network context only
"""
self.run: Callable[..., str] = run
self.run_net: Callable[..., str] = run_net

def set_hostname(self, name: str) -> None:
"""
Expand All @@ -40,7 +42,7 @@ def create_route(self, route: str, device: str) -> None:
:param device: device to add route to
:return: nothing
"""
self.run(f"{IP} route replace {route} dev {device}")
self.run_net(f"{IP} route replace {route} dev {device}")

def device_up(self, device: str) -> None:
"""
Expand All @@ -49,7 +51,7 @@ def device_up(self, device: str) -> None:
:param device: device to bring up
:return: nothing
"""
self.run(f"{IP} link set {device} up")
self.run_net(f"{IP} link set {device} up")

def device_down(self, device: str) -> None:
"""
Expand All @@ -58,7 +60,7 @@ def device_down(self, device: str) -> None:
:param device: device to bring down
:return: nothing
"""
self.run(f"{IP} link set {device} down")
self.run_net(f"{IP} link set {device} down")

def device_name(self, device: str, name: str) -> None:
"""
Expand All @@ -68,7 +70,7 @@ def device_name(self, device: str, name: str) -> None:
:param name: name to set
:return: nothing
"""
self.run(f"{IP} link set {device} name {name}")
self.run_net(f"{IP} link set {device} name {name}")

def device_show(self, device: str) -> str:
"""
Expand All @@ -77,7 +79,7 @@ def device_show(self, device: str) -> str:
:param device: device to get information for
:return: device information
"""
return self.run(f"{IP} link show {device}")
return self.run_net(f"{IP} link show {device}")

def address_show(self, device: str) -> str:
"""
Expand All @@ -86,7 +88,7 @@ def address_show(self, device: str) -> str:
:param device: device name
:return: address information
"""
return self.run(f"{IP} address show {device}")
return self.run_net(f"{IP} address show {device}")

def get_mac(self, device: str) -> str:
"""
Expand Down Expand Up @@ -114,7 +116,7 @@ def device_ns(self, device: str, namespace: str) -> None:
:param namespace: namespace to set device to
:return: nothing
"""
self.run(f"{IP} link set {device} netns {namespace}")
self.run_net(f"{IP} link set {device} netns {namespace}")

def device_flush(self, device: str) -> None:
"""
Expand All @@ -123,7 +125,7 @@ def device_flush(self, device: str) -> None:
:param device: device to flush
:return: nothing
"""
self.run(f"{IP} address flush dev {device}")
self.run_net(f"{IP} address flush dev {device}")

def device_mac(self, device: str, mac: str) -> None:
"""
Expand All @@ -133,7 +135,7 @@ def device_mac(self, device: str, mac: str) -> None:
:param mac: mac to set
:return: nothing
"""
self.run(f"{IP} link set dev {device} address {mac}")
self.run_net(f"{IP} link set dev {device} address {mac}")

def delete_device(self, device: str) -> None:
"""
Expand All @@ -142,7 +144,7 @@ def delete_device(self, device: str) -> None:
:param device: device to delete
:return: nothing
"""
self.run(f"{IP} link delete {device}")
self.run_net(f"{IP} link delete {device}")

def delete_tc(self, device: str) -> None:
"""
Expand All @@ -151,7 +153,7 @@ def delete_tc(self, device: str) -> None:
:param device: device to remove tc
:return: nothing
"""
self.run(f"{TC} qdisc delete dev {device} root")
self.run_net(f"{TC} qdisc delete dev {device} root")

def checksums_off(self, iface_name: str) -> None:
"""
Expand All @@ -160,7 +162,7 @@ def checksums_off(self, iface_name: str) -> None:
:param iface_name: interface to update
:return: nothing
"""
self.run(f"{ETHTOOL} -K {iface_name} rx off tx off")
self.run_net(f"{ETHTOOL} -K {iface_name} rx off tx off")

def create_address(self, device: str, address: str, broadcast: str = None) -> None:
"""
Expand All @@ -172,9 +174,11 @@ def create_address(self, device: str, address: str, broadcast: str = None) -> No
:return: nothing
"""
if broadcast is not None:
self.run(f"{IP} address add {address} broadcast {broadcast} dev {device}")
self.run_net(
f"{IP} address add {address} broadcast {broadcast} dev {device}"
)
else:
self.run(f"{IP} address add {address} dev {device}")
self.run_net(f"{IP} address add {address} dev {device}")
if netaddr.valid_ipv6(address.split("/")[0]):
# IPv6 addresses are removed by default on interface down.
# Make sure that the IPv6 address we add is not removed
Expand All @@ -189,7 +193,7 @@ def delete_address(self, device: str, address: str) -> None:
:param address: address to remove
:return: nothing
"""
self.run(f"{IP} address delete {address} dev {device}")
self.run_net(f"{IP} address delete {address} dev {device}")

def create_veth(self, name: str, peer: str) -> None:
"""
Expand All @@ -199,7 +203,7 @@ def create_veth(self, name: str, peer: str) -> None:
:param peer: peer name
:return: nothing
"""
self.run(f"{IP} link add name {name} type veth peer name {peer}")
self.run_net(f"{IP} link add name {name} type veth peer name {peer}")

def create_gretap(
self, device: str, address: str, local: str, ttl: int, key: int
Expand All @@ -221,7 +225,7 @@ def create_gretap(
cmd += f" ttl {ttl}"
if key is not None:
cmd += f" key {key}"
self.run(cmd)
self.run_net(cmd)

def create_bridge(self, name: str) -> None:
"""
Expand All @@ -230,11 +234,11 @@ def create_bridge(self, name: str) -> None:
:param name: bridge name
:return: nothing
"""
self.run(f"{IP} link add name {name} type bridge")
self.run(f"{IP} link set {name} type bridge stp_state 0")
self.run(f"{IP} link set {name} type bridge forward_delay 0")
self.run(f"{IP} link set {name} type bridge mcast_snooping 0")
self.run(f"{IP} link set {name} type bridge group_fwd_mask 65528")
self.run_net(f"{IP} link add name {name} type bridge")
self.run_net(f"{IP} link set {name} type bridge stp_state 0")
self.run_net(f"{IP} link set {name} type bridge forward_delay 0")
self.run_net(f"{IP} link set {name} type bridge mcast_snooping 0")
self.run_net(f"{IP} link set {name} type bridge group_fwd_mask 65528")
self.device_up(name)

def delete_bridge(self, name: str) -> None:
Expand All @@ -245,7 +249,7 @@ def delete_bridge(self, name: str) -> None:
:return: nothing
"""
self.device_down(name)
self.run(f"{IP} link delete {name} type bridge")
self.run_net(f"{IP} link delete {name} type bridge")

def set_iface_master(self, bridge_name: str, iface_name: str) -> None:
"""
Expand All @@ -255,7 +259,7 @@ def set_iface_master(self, bridge_name: str, iface_name: str) -> None:
:param iface_name: interface name
:return: nothing
"""
self.run(f"{IP} link set dev {iface_name} master {bridge_name}")
self.run_net(f"{IP} link set dev {iface_name} master {bridge_name}")
self.device_up(iface_name)

def delete_iface(self, bridge_name: str, iface_name: str) -> None:
Expand All @@ -266,7 +270,7 @@ def delete_iface(self, bridge_name: str, iface_name: str) -> None:
:param iface_name: interface name
:return: nothing
"""
self.run(f"{IP} link set dev {iface_name} nomaster")
self.run_net(f"{IP} link set dev {iface_name} nomaster")

def existing_bridges(self, _id: int) -> bool:
"""
Expand All @@ -275,7 +279,7 @@ def existing_bridges(self, _id: int) -> bool:
:param _id: node id to check bridges for
:return: True if there are existing bridges, False otherwise
"""
output = self.run(f"{IP} -o link show type bridge")
output = self.run_net(f"{IP} -o link show type bridge")
lines = output.split("\n")
for line in lines:
values = line.split(":")
Expand All @@ -297,7 +301,7 @@ def set_mac_learning(self, name: str, value: int) -> None:
:param value: ageing time value
:return: nothing
"""
self.run(f"{IP} link set {name} type bridge ageing_time {value}")
self.run_net(f"{IP} link set {name} type bridge ageing_time {value}")

def set_mtu(self, name: str, value: int) -> None:
"""
Expand All @@ -307,7 +311,7 @@ def set_mtu(self, name: str, value: int) -> None:
:param value: mtu value to set
:return: nothing
"""
self.run(f"{IP} link set {name} mtu {value}")
self.run_net(f"{IP} link set {name} mtu {value}")


class OvsNetClient(LinuxNetClient):
Expand Down Expand Up @@ -385,15 +389,20 @@ def set_mac_learning(self, name: str, value: int) -> None:
self.run(f"{OVS_VSCTL} set bridge {name} other_config:mac-aging-time={value}")


def get_net_client(use_ovs: bool, run: Callable[..., str]) -> LinuxNetClient:
def get_net_client(
use_ovs: bool, run: Callable[..., str], run_net: Callable[..., str] = None
) -> LinuxNetClient:
"""
Retrieve desired net client for running network commands.
:param use_ovs: True for OVS bridges, False for Linux bridges
:param run: function used to run net client commands
:param run: function to run commands within node context
:param run_net: function to run commands within network context only
:return: net client class
"""
if run_net is None:
run_net = run
if use_ovs:
return OvsNetClient(run)
return OvsNetClient(run, run_net)
else:
return LinuxNetClient(run)
return LinuxNetClient(run, run_net)
Loading

0 comments on commit 094790a

Please sign in to comment.