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.
|
CORE data objects.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
|
@ -176,6 +176,67 @@ class LinkOptions:
|
||||||
key: int = None
|
key: int = None
|
||||||
buffer: 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
|
@dataclass
|
||||||
class LinkData:
|
class LinkData:
|
||||||
|
|
|
@ -1049,11 +1049,10 @@ class CoreNetworkBase(NodeBase):
|
||||||
:return: list of link data
|
:return: list of link data
|
||||||
"""
|
"""
|
||||||
all_links = []
|
all_links = []
|
||||||
|
|
||||||
# build a link message from this network node to each node having a
|
# build a link message from this network node to each node having a
|
||||||
# connected interface
|
# connected interface
|
||||||
for iface in self.get_ifaces():
|
for iface in self.get_ifaces():
|
||||||
uni = False
|
unidirectional = 0
|
||||||
linked_node = iface.node
|
linked_node = iface.node
|
||||||
if linked_node is None:
|
if linked_node is None:
|
||||||
# two layer-2 switches/hubs linked together via linknet()
|
# two layer-2 switches/hubs linked together via linknet()
|
||||||
|
@ -1062,53 +1061,29 @@ class CoreNetworkBase(NodeBase):
|
||||||
linked_node = iface.othernet
|
linked_node = iface.othernet
|
||||||
if linked_node.id == self.id:
|
if linked_node.id == self.id:
|
||||||
continue
|
continue
|
||||||
iface.swapparams("_params_up")
|
if iface.local_options != iface.options:
|
||||||
upstream_params = iface.getparams()
|
unidirectional = 1
|
||||||
iface.swapparams("_params_up")
|
iface_data = iface.get_data()
|
||||||
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)
|
|
||||||
link_data = LinkData(
|
link_data = LinkData(
|
||||||
message_type=flags,
|
message_type=flags,
|
||||||
type=self.linktype,
|
type=self.linktype,
|
||||||
node1_id=self.id,
|
node1_id=self.id,
|
||||||
node2_id=linked_node.id,
|
node2_id=linked_node.id,
|
||||||
iface2=iface2_data,
|
iface2=iface_data,
|
||||||
options=options_data,
|
options=iface.local_options,
|
||||||
)
|
)
|
||||||
|
link_data.options.unidirectional = unidirectional
|
||||||
all_links.append(link_data)
|
all_links.append(link_data)
|
||||||
|
if unidirectional:
|
||||||
if not uni:
|
link_data = LinkData(
|
||||||
continue
|
message_type=MessageFlags.NONE,
|
||||||
iface.swapparams("_params_up")
|
type=self.linktype,
|
||||||
options_data = iface.get_link_options(unidirectional)
|
node1_id=linked_node.id,
|
||||||
link_data = LinkData(
|
node2_id=self.id,
|
||||||
message_type=MessageFlags.NONE,
|
options=iface.options,
|
||||||
type=self.linktype,
|
)
|
||||||
node1_id=linked_node.id,
|
link_data.options.unidirectional = unidirectional
|
||||||
node2_id=self.id,
|
all_links.append(link_data)
|
||||||
options=options_data,
|
|
||||||
)
|
|
||||||
iface.swapparams("_params_up")
|
|
||||||
all_links.append(link_data)
|
|
||||||
return all_links
|
return all_links
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,12 @@ import logging
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
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
|
import netaddr
|
||||||
|
|
||||||
from core import utils
|
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.emulator.enumerations import TransportType
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.executables import TC
|
from core.executables import TC
|
||||||
|
@ -27,6 +27,50 @@ if TYPE_CHECKING:
|
||||||
DEFAULT_MTU: int = 1500
|
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:
|
class CoreInterface:
|
||||||
"""
|
"""
|
||||||
Base class for network interfaces.
|
Base class for network interfaces.
|
||||||
|
@ -63,7 +107,6 @@ class CoreInterface:
|
||||||
self.mtu: int = mtu
|
self.mtu: int = mtu
|
||||||
self.net: Optional[CoreNetworkBase] = None
|
self.net: Optional[CoreNetworkBase] = None
|
||||||
self.othernet: Optional[CoreNetworkBase] = None
|
self.othernet: Optional[CoreNetworkBase] = None
|
||||||
self._params: Dict[str, float] = {}
|
|
||||||
self.ip4s: List[netaddr.IPNetwork] = []
|
self.ip4s: List[netaddr.IPNetwork] = []
|
||||||
self.ip6s: List[netaddr.IPNetwork] = []
|
self.ip6s: List[netaddr.IPNetwork] = []
|
||||||
self.mac: Optional[netaddr.EUI] = None
|
self.mac: Optional[netaddr.EUI] = None
|
||||||
|
@ -82,6 +125,11 @@ class CoreInterface:
|
||||||
self.session.use_ovs(), self.host_cmd
|
self.session.use_ovs(), self.host_cmd
|
||||||
)
|
)
|
||||||
self.control: bool = False
|
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(
|
def host_cmd(
|
||||||
self,
|
self,
|
||||||
|
@ -221,89 +269,6 @@ class CoreInterface:
|
||||||
except netaddr.AddrFormatError as e:
|
except netaddr.AddrFormatError as e:
|
||||||
raise CoreError(f"invalid mac address({mac}): {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:
|
def setposition(self) -> None:
|
||||||
"""
|
"""
|
||||||
Dispatch position hook handler when possible.
|
Dispatch position hook handler when possible.
|
||||||
|
@ -338,15 +303,6 @@ class CoreInterface:
|
||||||
"""
|
"""
|
||||||
return self.transport_type == TransportType.VIRTUAL
|
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:
|
def config(self, options: LinkOptions, use_local: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Configure interface using tc based on existing state and provided
|
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
|
:param use_local: True to use localname for device, False for name
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
# determine if any settings have changed
|
# determine name, options, and if anything has changed
|
||||||
if use_local:
|
name = self.localname if use_local else self.name
|
||||||
devname = self.localname
|
current_options = self.local_options if use_local else self.options
|
||||||
changed = self._set_params_change(
|
changed = current_options.update(options)
|
||||||
bw=options.bandwidth,
|
# nothing more to do when nothing has changed or not up
|
||||||
delay=options.delay,
|
if not changed or not self.up:
|
||||||
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:
|
|
||||||
return
|
return
|
||||||
# delete tc configuration or create and add it
|
# clear current settings
|
||||||
if all(
|
if current_options.is_clear():
|
||||||
[
|
clear_local_netem = use_local and self.has_local_netem
|
||||||
options.delay is None or options.delay <= 0,
|
clear_netem = not use_local and self.has_netem
|
||||||
options.jitter is None or options.jitter <= 0,
|
if clear_local_netem or clear_netem:
|
||||||
options.loss is None or options.loss <= 0,
|
cmd = tc_clear_cmd(name)
|
||||||
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.host_cmd(cmd)
|
self.host_cmd(cmd)
|
||||||
self.setparam("has_netem", False)
|
if use_local:
|
||||||
else:
|
self.has_local_netem = False
|
||||||
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%"
|
|
||||||
else:
|
else:
|
||||||
netem += f" {options.jitter}us 25%"
|
self.has_netem = False
|
||||||
if options.loss is not None and options.loss > 0:
|
# set updated settings
|
||||||
netem += f" loss {min(options.loss, 100)}%"
|
else:
|
||||||
if options.dup is not None and options.dup > 0:
|
cmd = tc_cmd(name, current_options, self.mtu)
|
||||||
netem += f" duplicate {min(options.dup, 100)}%"
|
self.host_cmd(cmd)
|
||||||
if self.up:
|
if use_local:
|
||||||
cmd = f"{TC} qdisc replace dev {devname} root handle 10: netem {netem}"
|
self.has_local_netem = True
|
||||||
self.host_cmd(cmd)
|
else:
|
||||||
self.setparam("has_netem", True)
|
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):
|
class Veth(CoreInterface):
|
||||||
|
|
|
@ -743,41 +743,12 @@ class PtpNet(CoreNetwork):
|
||||||
all_links = []
|
all_links = []
|
||||||
if len(self.ifaces) != 2:
|
if len(self.ifaces) != 2:
|
||||||
return all_links
|
return all_links
|
||||||
|
|
||||||
ifaces = self.get_ifaces()
|
ifaces = self.get_ifaces()
|
||||||
iface1 = ifaces[0]
|
iface1 = ifaces[0]
|
||||||
iface2 = ifaces[1]
|
iface2 = ifaces[1]
|
||||||
unidirectional = 0
|
unidirectional = 0 if iface1.local_options == iface2.local_options else 1
|
||||||
if iface1.getparams() != iface2.getparams():
|
iface1_data = iface1.get_data()
|
||||||
unidirectional = 1
|
iface2_data = iface2.get_data()
|
||||||
|
|
||||||
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)
|
|
||||||
link_data = LinkData(
|
link_data = LinkData(
|
||||||
message_type=flags,
|
message_type=flags,
|
||||||
type=self.linktype,
|
type=self.linktype,
|
||||||
|
@ -785,25 +756,23 @@ class PtpNet(CoreNetwork):
|
||||||
node2_id=iface2.node.id,
|
node2_id=iface2.node.id,
|
||||||
iface1=iface1_data,
|
iface1=iface1_data,
|
||||||
iface2=iface2_data,
|
iface2=iface2_data,
|
||||||
options=options_data,
|
options=iface1.local_options,
|
||||||
)
|
)
|
||||||
|
link_data.options.unidirectional = unidirectional
|
||||||
all_links.append(link_data)
|
all_links.append(link_data)
|
||||||
|
|
||||||
# build a 2nd link message for the upstream link parameters
|
# build a 2nd link message for the upstream link parameters
|
||||||
# (swap if1 and if2)
|
# (swap if1 and if2)
|
||||||
if unidirectional:
|
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(
|
link_data = LinkData(
|
||||||
message_type=MessageFlags.NONE,
|
message_type=MessageFlags.NONE,
|
||||||
type=self.linktype,
|
type=self.linktype,
|
||||||
node1_id=iface2.node.id,
|
node1_id=iface2.node.id,
|
||||||
node2_id=iface1.node.id,
|
node2_id=iface1.node.id,
|
||||||
iface1=iface1_data,
|
iface1=InterfaceData(id=iface2_data.id),
|
||||||
iface2=iface2_data,
|
iface2=InterfaceData(id=iface1_data.id),
|
||||||
options=options_data,
|
options=iface2.local_options,
|
||||||
)
|
)
|
||||||
|
link_data.options.unidirectional = unidirectional
|
||||||
all_links.append(link_data)
|
all_links.append(link_data)
|
||||||
return all_links
|
return all_links
|
||||||
|
|
||||||
|
|
|
@ -130,12 +130,6 @@ class TestCore:
|
||||||
assert 0 in node1.ifaces
|
assert 0 in node1.ifaces
|
||||||
assert 0 in node2.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
|
# delete interface and test that if no longer exists
|
||||||
node1.delete_iface(0)
|
node1.delete_iface(0)
|
||||||
assert 0 not in node1.ifaces
|
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.emulator.session import Session
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
from core.nodes.base import CoreNode
|
from core.nodes.base import CoreNode
|
||||||
from core.nodes.interface import CoreInterface
|
|
||||||
from core.nodes.network import SwitchNode
|
from core.nodes.network import SwitchNode
|
||||||
|
|
||||||
INVALID_ID: int = 100
|
INVALID_ID: int = 100
|
||||||
|
@ -33,26 +32,6 @@ def create_ptp_network(
|
||||||
return node1, node2
|
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:
|
class TestLinks:
|
||||||
def test_add_node_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
def test_add_node_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
|
@ -71,8 +50,10 @@ class TestLinks:
|
||||||
assert node2.get_iface(iface2_data.id)
|
assert node2.get_iface(iface2_data.id)
|
||||||
assert iface1 is not None
|
assert iface1 is not None
|
||||||
assert iface2 is not None
|
assert iface2 is not None
|
||||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
assert iface1.local_options == LINK_OPTIONS
|
||||||
assert check_iface_match(iface2, 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):
|
def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
|
@ -89,7 +70,8 @@ class TestLinks:
|
||||||
assert node2.links()
|
assert node2.links()
|
||||||
assert node1.get_iface(iface1_data.id)
|
assert node1.get_iface(iface1_data.id)
|
||||||
assert iface is not None
|
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):
|
def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
|
@ -106,7 +88,8 @@ class TestLinks:
|
||||||
assert node1.links()
|
assert node1.links()
|
||||||
assert node2.get_iface(iface2_data.id)
|
assert node2.get_iface(iface2_data.id)
|
||||||
assert iface is not None
|
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):
|
def test_add_net_to_net(self, session):
|
||||||
# given
|
# given
|
||||||
|
@ -119,7 +102,10 @@ class TestLinks:
|
||||||
# then
|
# then
|
||||||
assert node1.links()
|
assert node1.links()
|
||||||
assert iface is not None
|
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):
|
def test_add_node_to_node_uni(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
|
@ -159,8 +145,10 @@ class TestLinks:
|
||||||
assert node2.get_iface(iface2_data.id)
|
assert node2.get_iface(iface2_data.id)
|
||||||
assert iface1 is not None
|
assert iface1 is not None
|
||||||
assert iface2 is not None
|
assert iface2 is not None
|
||||||
assert check_iface_match(iface1, link_options1)
|
assert iface1.local_options == link_options1
|
||||||
assert check_iface_match(iface2, link_options2)
|
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):
|
def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
|
@ -168,7 +156,7 @@ class TestLinks:
|
||||||
node2 = session.add_node(SwitchNode)
|
node2 = session.add_node(SwitchNode)
|
||||||
iface1_data = ip_prefixes.create_iface(node1)
|
iface1_data = ip_prefixes.create_iface(node1)
|
||||||
iface1, _ = session.add_link(node1.id, node2.id, iface1_data)
|
iface1, _ = session.add_link(node1.id, node2.id, iface1_data)
|
||||||
assert check_iface_diff(iface1, LINK_OPTIONS)
|
assert iface1.local_options != LINK_OPTIONS
|
||||||
|
|
||||||
# when
|
# when
|
||||||
session.update_link(
|
session.update_link(
|
||||||
|
@ -176,7 +164,8 @@ class TestLinks:
|
||||||
)
|
)
|
||||||
|
|
||||||
# then
|
# 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):
|
def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
|
@ -184,7 +173,7 @@ class TestLinks:
|
||||||
node2 = session.add_node(CoreNode)
|
node2 = session.add_node(CoreNode)
|
||||||
iface2_data = ip_prefixes.create_iface(node2)
|
iface2_data = ip_prefixes.create_iface(node2)
|
||||||
_, iface2 = session.add_link(node1.id, node2.id, iface2_data=iface2_data)
|
_, 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
|
# when
|
||||||
session.update_link(
|
session.update_link(
|
||||||
|
@ -192,7 +181,8 @@ class TestLinks:
|
||||||
)
|
)
|
||||||
|
|
||||||
# then
|
# 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):
|
def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
|
@ -201,8 +191,8 @@ class TestLinks:
|
||||||
iface1_data = ip_prefixes.create_iface(node1)
|
iface1_data = ip_prefixes.create_iface(node1)
|
||||||
iface2_data = ip_prefixes.create_iface(node2)
|
iface2_data = ip_prefixes.create_iface(node2)
|
||||||
iface1, iface2 = session.add_link(node1.id, node2.id, iface1_data, iface2_data)
|
iface1, iface2 = session.add_link(node1.id, node2.id, iface1_data, iface2_data)
|
||||||
assert check_iface_diff(iface1, LINK_OPTIONS)
|
assert iface1.local_options != LINK_OPTIONS
|
||||||
assert check_iface_diff(iface2, LINK_OPTIONS)
|
assert iface2.local_options != LINK_OPTIONS
|
||||||
|
|
||||||
# when
|
# when
|
||||||
session.update_link(
|
session.update_link(
|
||||||
|
@ -210,21 +200,46 @@ class TestLinks:
|
||||||
)
|
)
|
||||||
|
|
||||||
# then
|
# then
|
||||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
assert iface1.local_options == LINK_OPTIONS
|
||||||
assert check_iface_match(iface2, 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):
|
def test_update_net_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
node1 = session.add_node(SwitchNode)
|
node1 = session.add_node(SwitchNode)
|
||||||
node2 = session.add_node(SwitchNode)
|
node2 = session.add_node(SwitchNode)
|
||||||
iface1, _ = session.add_link(node1.id, node2.id)
|
iface1, _ = session.add_link(node1.id, node2.id)
|
||||||
assert check_iface_diff(iface1, LINK_OPTIONS)
|
assert iface1.local_options != LINK_OPTIONS
|
||||||
|
|
||||||
# when
|
# when
|
||||||
session.update_link(node1.id, node2.id, options=LINK_OPTIONS)
|
session.update_link(node1.id, node2.id, options=LINK_OPTIONS)
|
||||||
|
|
||||||
# then
|
# 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):
|
def test_delete_node_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||||
# given
|
# given
|
||||||
|
|
Loading…
Reference in a new issue