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
|
@ -2,7 +2,7 @@
|
|||
CORE data objects.
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
|
||||
|
||||
import netaddr
|
||||
|
||||
|
@ -176,6 +176,67 @@ class LinkOptions:
|
|||
key: int = None
|
||||
buffer: int = None
|
||||
|
||||
def update(self, options: "LinkOptions") -> bool:
|
||||
"""
|
||||
Updates current options with values from other options.
|
||||
|
||||
:param options: options to update with
|
||||
:return: True if any value has changed, False otherwise
|
||||
"""
|
||||
changed = False
|
||||
if options.delay is not None and 0 <= options.delay != self.delay:
|
||||
self.delay = options.delay
|
||||
changed = True
|
||||
if options.bandwidth is not None and 0 <= options.bandwidth != self.bandwidth:
|
||||
self.bandwidth = options.bandwidth
|
||||
changed = True
|
||||
if options.loss is not None and 0 <= options.loss != self.loss:
|
||||
self.loss = options.loss
|
||||
changed = True
|
||||
if options.dup is not None and 0 <= options.dup != self.dup:
|
||||
self.dup = options.dup
|
||||
changed = True
|
||||
if options.jitter is not None and 0 <= options.jitter != self.jitter:
|
||||
self.jitter = options.jitter
|
||||
changed = True
|
||||
if options.buffer is not None and 0 <= options.buffer != self.buffer:
|
||||
self.buffer = options.buffer
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def is_clear(self) -> bool:
|
||||
"""
|
||||
Checks if the current option values represent a clear state.
|
||||
|
||||
:return: True if the current values should clear, False otherwise
|
||||
"""
|
||||
clear = self.delay is None or self.delay <= 0
|
||||
clear &= self.jitter is None or self.jitter <= 0
|
||||
clear &= self.loss is None or self.loss <= 0
|
||||
clear &= self.dup is None or self.dup <= 0
|
||||
clear &= self.bandwidth is None or self.bandwidth <= 0
|
||||
clear &= self.buffer is None or self.buffer <= 0
|
||||
return clear
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
"""
|
||||
Custom logic to check if this link options is equivalent to another.
|
||||
|
||||
:param other: other object to check
|
||||
:return: True if they are both link options with the same values,
|
||||
False otherwise
|
||||
"""
|
||||
if not isinstance(other, LinkOptions):
|
||||
return False
|
||||
return (
|
||||
self.delay == other.delay
|
||||
and self.jitter == other.jitter
|
||||
and self.loss == other.loss
|
||||
and self.dup == other.dup
|
||||
and self.bandwidth == other.bandwidth
|
||||
and self.buffer == other.buffer
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinkData:
|
||||
|
|
|
@ -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,52 +1061,28 @@ 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:
|
||||
if iface.local_options != iface.options:
|
||||
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)
|
||||
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)
|
||||
if unidirectional:
|
||||
link_data = LinkData(
|
||||
message_type=MessageFlags.NONE,
|
||||
type=self.linktype,
|
||||
node1_id=linked_node.id,
|
||||
node2_id=self.id,
|
||||
options=options_data,
|
||||
options=iface.options,
|
||||
)
|
||||
iface.swapparams("_params_up")
|
||||
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
|
||||
# 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
|
||||
# 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)
|
||||
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,
|
||||
)
|
||||
self.has_local_netem = False
|
||||
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:
|
||||
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:"
|
||||
self.has_netem = False
|
||||
# set updated settings
|
||||
else:
|
||||
cmd = tc_cmd(name, current_options, self.mtu)
|
||||
self.host_cmd(cmd)
|
||||
self.setparam("has_netem", False)
|
||||
if use_local:
|
||||
self.has_local_netem = True
|
||||
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%"
|
||||
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:
|
||||
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)
|
||||
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
|
||||
|
||||
|
|
|
@ -130,12 +130,6 @@ class TestCore:
|
|||
assert 0 in node1.ifaces
|
||||
assert 0 in node2.ifaces
|
||||
|
||||
# check interface parameters
|
||||
iface = node1.get_iface(0)
|
||||
iface.setparam("test", 1)
|
||||
assert iface.getparam("test") == 1
|
||||
assert iface.getparams()
|
||||
|
||||
# delete interface and test that if no longer exists
|
||||
node1.delete_iface(0)
|
||||
assert 0 not in node1.ifaces
|
||||
|
|
|
@ -6,7 +6,6 @@ from core.emulator.data import IpPrefixes, LinkOptions
|
|||
from core.emulator.session import Session
|
||||
from core.errors import CoreError
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
INVALID_ID: int = 100
|
||||
|
@ -33,26 +32,6 @@ def create_ptp_network(
|
|||
return node1, node2
|
||||
|
||||
|
||||
def check_iface_match(iface: CoreInterface, options: LinkOptions) -> bool:
|
||||
result = iface.getparam("delay") == options.delay
|
||||
result &= iface.getparam("bw") == options.bandwidth
|
||||
result &= iface.getparam("loss") == options.loss
|
||||
result &= iface.getparam("duplicate") == options.dup
|
||||
result &= iface.getparam("jitter") == options.jitter
|
||||
result &= iface.getparam("buffer") == options.buffer
|
||||
return result
|
||||
|
||||
|
||||
def check_iface_diff(iface: CoreInterface, options: LinkOptions) -> bool:
|
||||
result = iface.getparam("delay") != options.delay
|
||||
result &= iface.getparam("bw") != options.bandwidth
|
||||
result &= iface.getparam("loss") != options.loss
|
||||
result &= iface.getparam("duplicate") != options.dup
|
||||
result &= iface.getparam("jitter") != options.jitter
|
||||
result &= iface.getparam("buffer") != options.buffer
|
||||
return result
|
||||
|
||||
|
||||
class TestLinks:
|
||||
def test_add_node_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -71,8 +50,10 @@ class TestLinks:
|
|||
assert node2.get_iface(iface2_data.id)
|
||||
assert iface1 is not None
|
||||
assert iface2 is not None
|
||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
||||
assert check_iface_match(iface2, LINK_OPTIONS)
|
||||
assert iface1.local_options == LINK_OPTIONS
|
||||
assert iface1.has_local_netem
|
||||
assert iface2.local_options == LINK_OPTIONS
|
||||
assert iface2.has_local_netem
|
||||
|
||||
def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -89,7 +70,8 @@ class TestLinks:
|
|||
assert node2.links()
|
||||
assert node1.get_iface(iface1_data.id)
|
||||
assert iface is not None
|
||||
assert check_iface_match(iface, LINK_OPTIONS)
|
||||
assert iface.local_options == LINK_OPTIONS
|
||||
assert iface.has_local_netem
|
||||
|
||||
def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -106,7 +88,8 @@ class TestLinks:
|
|||
assert node1.links()
|
||||
assert node2.get_iface(iface2_data.id)
|
||||
assert iface is not None
|
||||
assert check_iface_match(iface, LINK_OPTIONS)
|
||||
assert iface.local_options == LINK_OPTIONS
|
||||
assert iface.has_local_netem
|
||||
|
||||
def test_add_net_to_net(self, session):
|
||||
# given
|
||||
|
@ -119,7 +102,10 @@ class TestLinks:
|
|||
# then
|
||||
assert node1.links()
|
||||
assert iface is not None
|
||||
assert check_iface_match(iface, LINK_OPTIONS)
|
||||
assert iface.local_options == LINK_OPTIONS
|
||||
assert iface.options == LINK_OPTIONS
|
||||
assert iface.has_local_netem
|
||||
assert iface.has_netem
|
||||
|
||||
def test_add_node_to_node_uni(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -159,8 +145,10 @@ class TestLinks:
|
|||
assert node2.get_iface(iface2_data.id)
|
||||
assert iface1 is not None
|
||||
assert iface2 is not None
|
||||
assert check_iface_match(iface1, link_options1)
|
||||
assert check_iface_match(iface2, link_options2)
|
||||
assert iface1.local_options == link_options1
|
||||
assert iface1.has_local_netem
|
||||
assert iface2.local_options == link_options2
|
||||
assert iface2.has_local_netem
|
||||
|
||||
def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -168,7 +156,7 @@ class TestLinks:
|
|||
node2 = session.add_node(SwitchNode)
|
||||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
iface1, _ = session.add_link(node1.id, node2.id, iface1_data)
|
||||
assert check_iface_diff(iface1, LINK_OPTIONS)
|
||||
assert iface1.local_options != LINK_OPTIONS
|
||||
|
||||
# when
|
||||
session.update_link(
|
||||
|
@ -176,7 +164,8 @@ class TestLinks:
|
|||
)
|
||||
|
||||
# then
|
||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
||||
assert iface1.local_options == LINK_OPTIONS
|
||||
assert iface1.has_local_netem
|
||||
|
||||
def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -184,7 +173,7 @@ class TestLinks:
|
|||
node2 = session.add_node(CoreNode)
|
||||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
_, iface2 = session.add_link(node1.id, node2.id, iface2_data=iface2_data)
|
||||
assert check_iface_diff(iface2, LINK_OPTIONS)
|
||||
assert iface2.local_options != LINK_OPTIONS
|
||||
|
||||
# when
|
||||
session.update_link(
|
||||
|
@ -192,7 +181,8 @@ class TestLinks:
|
|||
)
|
||||
|
||||
# then
|
||||
assert check_iface_match(iface2, LINK_OPTIONS)
|
||||
assert iface2.local_options == LINK_OPTIONS
|
||||
assert iface2.has_local_netem
|
||||
|
||||
def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -201,8 +191,8 @@ class TestLinks:
|
|||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
iface1, iface2 = session.add_link(node1.id, node2.id, iface1_data, iface2_data)
|
||||
assert check_iface_diff(iface1, LINK_OPTIONS)
|
||||
assert check_iface_diff(iface2, LINK_OPTIONS)
|
||||
assert iface1.local_options != LINK_OPTIONS
|
||||
assert iface2.local_options != LINK_OPTIONS
|
||||
|
||||
# when
|
||||
session.update_link(
|
||||
|
@ -210,21 +200,46 @@ class TestLinks:
|
|||
)
|
||||
|
||||
# then
|
||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
||||
assert check_iface_match(iface2, LINK_OPTIONS)
|
||||
assert iface1.local_options == LINK_OPTIONS
|
||||
assert iface1.has_local_netem
|
||||
assert iface2.local_options == LINK_OPTIONS
|
||||
assert iface2.has_local_netem
|
||||
|
||||
def test_update_net_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(SwitchNode)
|
||||
node2 = session.add_node(SwitchNode)
|
||||
iface1, _ = session.add_link(node1.id, node2.id)
|
||||
assert check_iface_diff(iface1, LINK_OPTIONS)
|
||||
assert iface1.local_options != LINK_OPTIONS
|
||||
|
||||
# when
|
||||
session.update_link(node1.id, node2.id, options=LINK_OPTIONS)
|
||||
|
||||
# then
|
||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
||||
assert iface1.local_options == LINK_OPTIONS
|
||||
assert iface1.has_local_netem
|
||||
assert iface1.options == LINK_OPTIONS
|
||||
assert iface1.has_netem
|
||||
|
||||
def test_clear_net_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(SwitchNode)
|
||||
node2 = session.add_node(SwitchNode)
|
||||
iface1, _ = session.add_link(node1.id, node2.id, options=LINK_OPTIONS)
|
||||
assert iface1.local_options == LINK_OPTIONS
|
||||
assert iface1.has_local_netem
|
||||
assert iface1.options == LINK_OPTIONS
|
||||
assert iface1.has_netem
|
||||
|
||||
# when
|
||||
options = LinkOptions(delay=0, bandwidth=0, loss=0.0, dup=0, jitter=0, buffer=0)
|
||||
session.update_link(node1.id, node2.id, options=options)
|
||||
|
||||
# then
|
||||
assert iface1.local_options.is_clear()
|
||||
assert not iface1.has_local_netem
|
||||
assert iface1.options.is_clear()
|
||||
assert not iface1.has_netem
|
||||
|
||||
def test_delete_node_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
|
Loading…
Reference in a new issue