daemon: refactored interfaces to store configuration options as link options, instead of using a dictionary
This commit is contained in:
parent
e9b83b0d28
commit
6791269eeb
6 changed files with 238 additions and 286 deletions
|
@ -1049,11 +1049,10 @@ class CoreNetworkBase(NodeBase):
|
|||
:return: list of link data
|
||||
"""
|
||||
all_links = []
|
||||
|
||||
# build a link message from this network node to each node having a
|
||||
# connected interface
|
||||
for iface in self.get_ifaces():
|
||||
uni = False
|
||||
unidirectional = 0
|
||||
linked_node = iface.node
|
||||
if linked_node is None:
|
||||
# two layer-2 switches/hubs linked together via linknet()
|
||||
|
@ -1062,53 +1061,29 @@ class CoreNetworkBase(NodeBase):
|
|||
linked_node = iface.othernet
|
||||
if linked_node.id == self.id:
|
||||
continue
|
||||
iface.swapparams("_params_up")
|
||||
upstream_params = iface.getparams()
|
||||
iface.swapparams("_params_up")
|
||||
if iface.getparams() != upstream_params:
|
||||
uni = True
|
||||
|
||||
unidirectional = 0
|
||||
if uni:
|
||||
unidirectional = 1
|
||||
|
||||
mac = str(iface.mac) if iface.mac else None
|
||||
iface2_data = InterfaceData(
|
||||
id=linked_node.get_iface_id(iface), name=iface.name, mac=mac
|
||||
)
|
||||
ip4 = iface.get_ip4()
|
||||
if ip4:
|
||||
iface2_data.ip4 = str(ip4.ip)
|
||||
iface2_data.ip4_mask = ip4.prefixlen
|
||||
ip6 = iface.get_ip6()
|
||||
if ip6:
|
||||
iface2_data.ip6 = str(ip6.ip)
|
||||
iface2_data.ip6_mask = ip6.prefixlen
|
||||
|
||||
options_data = iface.get_link_options(unidirectional)
|
||||
if iface.local_options != iface.options:
|
||||
unidirectional = 1
|
||||
iface_data = iface.get_data()
|
||||
link_data = LinkData(
|
||||
message_type=flags,
|
||||
type=self.linktype,
|
||||
node1_id=self.id,
|
||||
node2_id=linked_node.id,
|
||||
iface2=iface2_data,
|
||||
options=options_data,
|
||||
iface2=iface_data,
|
||||
options=iface.local_options,
|
||||
)
|
||||
link_data.options.unidirectional = unidirectional
|
||||
all_links.append(link_data)
|
||||
|
||||
if not uni:
|
||||
continue
|
||||
iface.swapparams("_params_up")
|
||||
options_data = iface.get_link_options(unidirectional)
|
||||
link_data = LinkData(
|
||||
message_type=MessageFlags.NONE,
|
||||
type=self.linktype,
|
||||
node1_id=linked_node.id,
|
||||
node2_id=self.id,
|
||||
options=options_data,
|
||||
)
|
||||
iface.swapparams("_params_up")
|
||||
all_links.append(link_data)
|
||||
if unidirectional:
|
||||
link_data = LinkData(
|
||||
message_type=MessageFlags.NONE,
|
||||
type=self.linktype,
|
||||
node1_id=linked_node.id,
|
||||
node2_id=self.id,
|
||||
options=iface.options,
|
||||
)
|
||||
link_data.options.unidirectional = unidirectional
|
||||
all_links.append(link_data)
|
||||
return all_links
|
||||
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@ import logging
|
|||
import math
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional
|
||||
|
||||
import netaddr
|
||||
|
||||
from core import utils
|
||||
from core.emulator.data import LinkOptions
|
||||
from core.emulator.data import InterfaceData, LinkOptions
|
||||
from core.emulator.enumerations import TransportType
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.executables import TC
|
||||
|
@ -27,6 +27,50 @@ if TYPE_CHECKING:
|
|||
DEFAULT_MTU: int = 1500
|
||||
|
||||
|
||||
def tc_clear_cmd(name: str) -> str:
|
||||
"""
|
||||
Create tc command to clear device configuration.
|
||||
|
||||
:param name: name of device to clear
|
||||
:return: tc command
|
||||
"""
|
||||
return f"{TC} qdisc delete dev {name} root handle 10:"
|
||||
|
||||
|
||||
def tc_cmd(name: str, options: LinkOptions, mtu: int) -> str:
|
||||
"""
|
||||
Create tc command to configure a device with given name and options.
|
||||
|
||||
:param name: name of device to configure
|
||||
:param options: options to configure with
|
||||
:param mtu: mtu for configuration
|
||||
:return: tc command
|
||||
"""
|
||||
netem = ""
|
||||
if options.bandwidth is not None:
|
||||
limit = 1000
|
||||
bw = options.bandwidth / 1000
|
||||
if options.buffer is not None and options.buffer > 0:
|
||||
limit = options.buffer
|
||||
elif options.delay and options.bandwidth:
|
||||
delay = options.delay / 1000
|
||||
limit = max(2, math.ceil((2 * bw * delay) / (8 * mtu)))
|
||||
netem += f" rate {bw}kbit"
|
||||
netem += f" limit {limit}"
|
||||
if options.delay is not None:
|
||||
netem += f" delay {options.delay}us"
|
||||
if options.jitter is not None:
|
||||
if options.delay is None:
|
||||
netem += f" delay 0us {options.jitter}us 25%"
|
||||
else:
|
||||
netem += f" {options.jitter}us 25%"
|
||||
if options.loss is not None and options.loss > 0:
|
||||
netem += f" loss {min(options.loss, 100)}%"
|
||||
if options.dup is not None and options.dup > 0:
|
||||
netem += f" duplicate {min(options.dup, 100)}%"
|
||||
return f"{TC} qdisc replace dev {name} root handle 10: netem {netem}"
|
||||
|
||||
|
||||
class CoreInterface:
|
||||
"""
|
||||
Base class for network interfaces.
|
||||
|
@ -63,7 +107,6 @@ class CoreInterface:
|
|||
self.mtu: int = mtu
|
||||
self.net: Optional[CoreNetworkBase] = None
|
||||
self.othernet: Optional[CoreNetworkBase] = None
|
||||
self._params: Dict[str, float] = {}
|
||||
self.ip4s: List[netaddr.IPNetwork] = []
|
||||
self.ip6s: List[netaddr.IPNetwork] = []
|
||||
self.mac: Optional[netaddr.EUI] = None
|
||||
|
@ -82,6 +125,11 @@ class CoreInterface:
|
|||
self.session.use_ovs(), self.host_cmd
|
||||
)
|
||||
self.control: bool = False
|
||||
# configuration data
|
||||
self.has_local_netem: bool = False
|
||||
self.local_options: LinkOptions = LinkOptions()
|
||||
self.has_netem: bool = False
|
||||
self.options: LinkOptions = LinkOptions()
|
||||
|
||||
def host_cmd(
|
||||
self,
|
||||
|
@ -221,89 +269,6 @@ class CoreInterface:
|
|||
except netaddr.AddrFormatError as e:
|
||||
raise CoreError(f"invalid mac address({mac}): {e}")
|
||||
|
||||
def getparam(self, key: str) -> float:
|
||||
"""
|
||||
Retrieve a parameter from the, or None if the parameter does not exist.
|
||||
|
||||
:param key: parameter to get value for
|
||||
:return: parameter value
|
||||
"""
|
||||
return self._params.get(key)
|
||||
|
||||
def get_link_options(self, unidirectional: int) -> LinkOptions:
|
||||
"""
|
||||
Get currently set params as link options.
|
||||
|
||||
:param unidirectional: unidirectional setting
|
||||
:return: link options
|
||||
"""
|
||||
delay = self.getparam("delay")
|
||||
if delay is not None:
|
||||
delay = int(delay)
|
||||
bandwidth = self.getparam("bw")
|
||||
if bandwidth is not None:
|
||||
bandwidth = int(bandwidth)
|
||||
dup = self.getparam("duplicate")
|
||||
if dup is not None:
|
||||
dup = int(dup)
|
||||
jitter = self.getparam("jitter")
|
||||
if jitter is not None:
|
||||
jitter = int(jitter)
|
||||
buffer = self.getparam("buffer")
|
||||
if buffer is not None:
|
||||
buffer = int(buffer)
|
||||
return LinkOptions(
|
||||
delay=delay,
|
||||
bandwidth=bandwidth,
|
||||
dup=dup,
|
||||
jitter=jitter,
|
||||
loss=self.getparam("loss"),
|
||||
buffer=buffer,
|
||||
unidirectional=unidirectional,
|
||||
)
|
||||
|
||||
def getparams(self) -> List[Tuple[str, float]]:
|
||||
"""
|
||||
Return (key, value) pairs for parameters.
|
||||
"""
|
||||
parameters = []
|
||||
for k in sorted(self._params.keys()):
|
||||
parameters.append((k, self._params[k]))
|
||||
return parameters
|
||||
|
||||
def setparam(self, key: str, value: float) -> bool:
|
||||
"""
|
||||
Set a parameter value, returns True if the parameter has changed.
|
||||
|
||||
:param key: parameter name to set
|
||||
:param value: parameter value
|
||||
:return: True if parameter changed, False otherwise
|
||||
"""
|
||||
# treat None and 0 as unchanged values
|
||||
logger.debug("setting param: %s - %s", key, value)
|
||||
if value is None or value < 0:
|
||||
return False
|
||||
current_value = self._params.get(key)
|
||||
if current_value is not None and current_value == value:
|
||||
return False
|
||||
self._params[key] = value
|
||||
return True
|
||||
|
||||
def swapparams(self, name: str) -> None:
|
||||
"""
|
||||
Swap out parameters dict for name. If name does not exist,
|
||||
intialize it. This is for supporting separate upstream/downstream
|
||||
parameters when two layer-2 nodes are linked together.
|
||||
|
||||
:param name: name of parameter to swap
|
||||
:return: nothing
|
||||
"""
|
||||
tmp = self._params
|
||||
if not hasattr(self, name):
|
||||
setattr(self, name, {})
|
||||
self._params = getattr(self, name)
|
||||
setattr(self, name, tmp)
|
||||
|
||||
def setposition(self) -> None:
|
||||
"""
|
||||
Dispatch position hook handler when possible.
|
||||
|
@ -338,15 +303,6 @@ class CoreInterface:
|
|||
"""
|
||||
return self.transport_type == TransportType.VIRTUAL
|
||||
|
||||
def _set_params_change(self, **kwargs: float) -> bool:
|
||||
"""
|
||||
Set parameters to change.
|
||||
|
||||
:param kwargs: parameter name and values to change
|
||||
:return: True if any parameter changed, False otherwise
|
||||
"""
|
||||
return any([self.setparam(k, v) for k, v in kwargs.items()])
|
||||
|
||||
def config(self, options: LinkOptions, use_local: bool = True) -> None:
|
||||
"""
|
||||
Configure interface using tc based on existing state and provided
|
||||
|
@ -356,73 +312,55 @@ class CoreInterface:
|
|||
:param use_local: True to use localname for device, False for name
|
||||
:return: nothing
|
||||
"""
|
||||
# determine if any settings have changed
|
||||
if use_local:
|
||||
devname = self.localname
|
||||
changed = self._set_params_change(
|
||||
bw=options.bandwidth,
|
||||
delay=options.delay,
|
||||
loss=options.loss,
|
||||
duplicate=options.dup,
|
||||
jitter=options.jitter,
|
||||
buffer=options.buffer,
|
||||
)
|
||||
else:
|
||||
devname = self.name
|
||||
changed = self._set_params_change(
|
||||
n_bw=options.bandwidth,
|
||||
n_delay=options.delay,
|
||||
n_loss=options.loss,
|
||||
n_duplicate=options.dup,
|
||||
n_jitter=options.jitter,
|
||||
n_buffer=options.buffer,
|
||||
)
|
||||
if not changed:
|
||||
# determine name, options, and if anything has changed
|
||||
name = self.localname if use_local else self.name
|
||||
current_options = self.local_options if use_local else self.options
|
||||
changed = current_options.update(options)
|
||||
# nothing more to do when nothing has changed or not up
|
||||
if not changed or not self.up:
|
||||
return
|
||||
# delete tc configuration or create and add it
|
||||
if all(
|
||||
[
|
||||
options.delay is None or options.delay <= 0,
|
||||
options.jitter is None or options.jitter <= 0,
|
||||
options.loss is None or options.loss <= 0,
|
||||
options.dup is None or options.dup <= 0,
|
||||
options.bandwidth is None or options.bandwidth <= 0,
|
||||
options.buffer is None or options.buffer <= 0,
|
||||
]
|
||||
):
|
||||
if not self.getparam("has_netem"):
|
||||
return
|
||||
if self.up:
|
||||
cmd = f"{TC} qdisc delete dev {devname} root handle 10:"
|
||||
# clear current settings
|
||||
if current_options.is_clear():
|
||||
clear_local_netem = use_local and self.has_local_netem
|
||||
clear_netem = not use_local and self.has_netem
|
||||
if clear_local_netem or clear_netem:
|
||||
cmd = tc_clear_cmd(name)
|
||||
self.host_cmd(cmd)
|
||||
self.setparam("has_netem", False)
|
||||
else:
|
||||
netem = ""
|
||||
if options.bandwidth is not None:
|
||||
limit = 1000
|
||||
bw = options.bandwidth / 1000
|
||||
if options.buffer is not None and options.buffer > 0:
|
||||
limit = options.buffer
|
||||
elif options.delay and options.bandwidth:
|
||||
delay = options.delay / 1000
|
||||
limit = max(2, math.ceil((2 * bw * delay) / (8 * self.mtu)))
|
||||
netem += f" rate {bw}kbit"
|
||||
netem += f" limit {limit}"
|
||||
if options.delay is not None:
|
||||
netem += f" delay {options.delay}us"
|
||||
if options.jitter is not None:
|
||||
if options.delay is None:
|
||||
netem += f" delay 0us {options.jitter}us 25%"
|
||||
if use_local:
|
||||
self.has_local_netem = False
|
||||
else:
|
||||
netem += f" {options.jitter}us 25%"
|
||||
if options.loss is not None and options.loss > 0:
|
||||
netem += f" loss {min(options.loss, 100)}%"
|
||||
if options.dup is not None and options.dup > 0:
|
||||
netem += f" duplicate {min(options.dup, 100)}%"
|
||||
if self.up:
|
||||
cmd = f"{TC} qdisc replace dev {devname} root handle 10: netem {netem}"
|
||||
self.host_cmd(cmd)
|
||||
self.setparam("has_netem", True)
|
||||
self.has_netem = False
|
||||
# set updated settings
|
||||
else:
|
||||
cmd = tc_cmd(name, current_options, self.mtu)
|
||||
self.host_cmd(cmd)
|
||||
if use_local:
|
||||
self.has_local_netem = True
|
||||
else:
|
||||
self.has_netem = True
|
||||
|
||||
def get_data(self) -> InterfaceData:
|
||||
"""
|
||||
Retrieve the data representation of this interface.
|
||||
|
||||
:return: interface data
|
||||
"""
|
||||
if self.node:
|
||||
iface_id = self.node.get_iface_id(self)
|
||||
else:
|
||||
iface_id = self.othernet.get_iface_id(self)
|
||||
data = InterfaceData(
|
||||
id=iface_id, name=self.name, mac=str(self.mac) if self.mac else None
|
||||
)
|
||||
ip4 = self.get_ip4()
|
||||
if ip4:
|
||||
data.ip4 = str(ip4.ip)
|
||||
data.ip4_mask = ip4.prefixlen
|
||||
ip6 = self.get_ip6()
|
||||
if ip6:
|
||||
data.ip6 = str(ip6.ip)
|
||||
data.ip6_mask = ip6.prefixlen
|
||||
return data
|
||||
|
||||
|
||||
class Veth(CoreInterface):
|
||||
|
|
|
@ -743,41 +743,12 @@ class PtpNet(CoreNetwork):
|
|||
all_links = []
|
||||
if len(self.ifaces) != 2:
|
||||
return all_links
|
||||
|
||||
ifaces = self.get_ifaces()
|
||||
iface1 = ifaces[0]
|
||||
iface2 = ifaces[1]
|
||||
unidirectional = 0
|
||||
if iface1.getparams() != iface2.getparams():
|
||||
unidirectional = 1
|
||||
|
||||
mac = str(iface1.mac) if iface1.mac else None
|
||||
iface1_data = InterfaceData(
|
||||
id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=mac
|
||||
)
|
||||
ip4 = iface1.get_ip4()
|
||||
if ip4:
|
||||
iface1_data.ip4 = str(ip4.ip)
|
||||
iface1_data.ip4_mask = ip4.prefixlen
|
||||
ip6 = iface1.get_ip6()
|
||||
if ip6:
|
||||
iface1_data.ip6 = str(ip6.ip)
|
||||
iface1_data.ip6_mask = ip6.prefixlen
|
||||
|
||||
mac = str(iface2.mac) if iface2.mac else None
|
||||
iface2_data = InterfaceData(
|
||||
id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=mac
|
||||
)
|
||||
ip4 = iface2.get_ip4()
|
||||
if ip4:
|
||||
iface2_data.ip4 = str(ip4.ip)
|
||||
iface2_data.ip4_mask = ip4.prefixlen
|
||||
ip6 = iface2.get_ip6()
|
||||
if ip6:
|
||||
iface2_data.ip6 = str(ip6.ip)
|
||||
iface2_data.ip6_mask = ip6.prefixlen
|
||||
|
||||
options_data = iface1.get_link_options(unidirectional)
|
||||
unidirectional = 0 if iface1.local_options == iface2.local_options else 1
|
||||
iface1_data = iface1.get_data()
|
||||
iface2_data = iface2.get_data()
|
||||
link_data = LinkData(
|
||||
message_type=flags,
|
||||
type=self.linktype,
|
||||
|
@ -785,25 +756,23 @@ class PtpNet(CoreNetwork):
|
|||
node2_id=iface2.node.id,
|
||||
iface1=iface1_data,
|
||||
iface2=iface2_data,
|
||||
options=options_data,
|
||||
options=iface1.local_options,
|
||||
)
|
||||
link_data.options.unidirectional = unidirectional
|
||||
all_links.append(link_data)
|
||||
|
||||
# build a 2nd link message for the upstream link parameters
|
||||
# (swap if1 and if2)
|
||||
if unidirectional:
|
||||
iface1_data = InterfaceData(id=iface2.node.get_iface_id(iface2))
|
||||
iface2_data = InterfaceData(id=iface1.node.get_iface_id(iface1))
|
||||
options_data = iface2.get_link_options(unidirectional)
|
||||
link_data = LinkData(
|
||||
message_type=MessageFlags.NONE,
|
||||
type=self.linktype,
|
||||
node1_id=iface2.node.id,
|
||||
node2_id=iface1.node.id,
|
||||
iface1=iface1_data,
|
||||
iface2=iface2_data,
|
||||
options=options_data,
|
||||
iface1=InterfaceData(id=iface2_data.id),
|
||||
iface2=InterfaceData(id=iface1_data.id),
|
||||
options=iface2.local_options,
|
||||
)
|
||||
link_data.options.unidirectional = unidirectional
|
||||
all_links.append(link_data)
|
||||
return all_links
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue