daemon: refactored how interfaces are configured, updated link edits to allow proper bi-directional support for network to network interfaces, improved and added more unit tests for link add/edit/delete
This commit is contained in:
parent
bc8c49c573
commit
e9b83b0d28
8 changed files with 348 additions and 252 deletions
|
@ -288,17 +288,25 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
|
||||
# create links
|
||||
links = []
|
||||
asym_links = []
|
||||
edit_links = []
|
||||
known_links = set()
|
||||
for link in request.session.links:
|
||||
if link.options.unidirectional:
|
||||
asym_links.append(link)
|
||||
iface1 = link.iface1.id if link.iface1 else None
|
||||
iface2 = link.iface2.id if link.iface2 else None
|
||||
if link.node1_id < link.node2_id:
|
||||
link_id = (link.node1_id, iface1, link.node2_id, iface2)
|
||||
else:
|
||||
link_id = (link.node2_id, iface2, link.node1_id, iface1)
|
||||
if link_id in known_links:
|
||||
edit_links.append(link)
|
||||
else:
|
||||
known_links.add(link_id)
|
||||
links.append(link)
|
||||
_, exceptions = grpcutils.create_links(session, links)
|
||||
if exceptions:
|
||||
exceptions = [str(x) for x in exceptions]
|
||||
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
|
||||
_, exceptions = grpcutils.edit_links(session, asym_links)
|
||||
_, exceptions = grpcutils.edit_links(session, edit_links)
|
||||
if exceptions:
|
||||
exceptions = [str(x) for x in exceptions]
|
||||
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
|
||||
|
|
|
@ -276,20 +276,22 @@ class Session:
|
|||
ptp = self.create_node(PtpNet, start)
|
||||
iface1 = node1.new_iface(ptp, iface1_data)
|
||||
iface2 = node2.new_iface(ptp, iface2_data)
|
||||
ptp.linkconfig(iface1, options)
|
||||
iface1.config(options)
|
||||
if not options.unidirectional:
|
||||
ptp.linkconfig(iface2, options)
|
||||
iface2.config(options)
|
||||
# link node to net
|
||||
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
|
||||
logger.info("linking node to net: %s - %s", node1.name, node2.name)
|
||||
iface1 = node1.new_iface(node2, iface1_data)
|
||||
if not isinstance(node2, (EmaneNet, WlanNode)):
|
||||
node2.linkconfig(iface1, options)
|
||||
iface1.config(options)
|
||||
# link net to node
|
||||
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
|
||||
logger.info("linking net to node: %s - %s", node1.name, node2.name)
|
||||
iface2 = node2.new_iface(node1, iface2_data)
|
||||
wireless_net = isinstance(node1, (EmaneNet, WlanNode))
|
||||
if not options.unidirectional and not wireless_net:
|
||||
node1.linkconfig(iface2, options)
|
||||
iface2.config(options)
|
||||
# network to network
|
||||
elif isinstance(node1, CoreNetworkBase) and isinstance(
|
||||
node2, CoreNetworkBase
|
||||
|
@ -298,11 +300,10 @@ class Session:
|
|||
"linking network to network: %s - %s", node1.name, node2.name
|
||||
)
|
||||
iface1 = node1.linknet(node2)
|
||||
node1.linkconfig(iface1, options)
|
||||
use_local = iface1.net == node1
|
||||
iface1.config(options, use_local=use_local)
|
||||
if not options.unidirectional:
|
||||
iface1.swapparams("_params_up")
|
||||
node2.linkconfig(iface1, options)
|
||||
iface1.swapparams("_params_up")
|
||||
iface1.config(options, use_local=not use_local)
|
||||
else:
|
||||
raise CoreError(
|
||||
f"cannot link node1({type(node1)}) node2({type(node2)})"
|
||||
|
@ -379,16 +380,18 @@ class Session:
|
|||
elif isinstance(node1, CoreNetworkBase) and isinstance(
|
||||
node2, CoreNetworkBase
|
||||
):
|
||||
for iface in node1.get_ifaces(control=False):
|
||||
if iface.othernet == node2:
|
||||
node1.detach(iface)
|
||||
iface.shutdown()
|
||||
break
|
||||
for iface in node2.get_ifaces(control=False):
|
||||
if iface.othernet == node1:
|
||||
node2.detach(iface)
|
||||
iface.shutdown()
|
||||
break
|
||||
iface1 = node1.get_linked_iface(node2)
|
||||
if iface1:
|
||||
node1.detach(iface1)
|
||||
iface1.shutdown()
|
||||
iface2 = node2.get_linked_iface(node1)
|
||||
if iface2:
|
||||
node2.detach(iface2)
|
||||
iface2.shutdown()
|
||||
if not iface1 and not iface2:
|
||||
raise CoreError(
|
||||
f"node1({node1.name}) and node2({node2.name}) are not connected"
|
||||
)
|
||||
self.sdt.delete_link(node1_id, node2_id)
|
||||
|
||||
def update_link(
|
||||
|
@ -432,11 +435,11 @@ class Session:
|
|||
else:
|
||||
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
|
||||
iface1 = node1.ifaces.get(iface1_id)
|
||||
iface2 = node2.ifaces.get(iface2_id)
|
||||
if not iface1:
|
||||
raise CoreError(
|
||||
f"node({node1.name}) missing interface({iface1_id})"
|
||||
)
|
||||
iface2 = node2.ifaces.get(iface2_id)
|
||||
if not iface2:
|
||||
raise CoreError(
|
||||
f"node({node2.name}) missing interface({iface2_id})"
|
||||
|
@ -446,39 +449,40 @@ class Session:
|
|||
f"node1({node1.name}) node2({node2.name}) "
|
||||
"not connected to same net"
|
||||
)
|
||||
ptp = iface1.net
|
||||
ptp.linkconfig(iface1, options, iface2)
|
||||
iface1.config(options)
|
||||
if not options.unidirectional:
|
||||
ptp.linkconfig(iface2, options, iface1)
|
||||
iface2.config(options)
|
||||
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
|
||||
iface = node1.get_iface(iface1_id)
|
||||
node2.linkconfig(iface, options)
|
||||
if iface.net != node2:
|
||||
raise CoreError(
|
||||
f"node1({node1.name}) iface1({iface1_id})"
|
||||
f" is not linked to node1({node2.name})"
|
||||
)
|
||||
iface.config(options)
|
||||
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
|
||||
iface = node2.get_iface(iface2_id)
|
||||
node1.linkconfig(iface, options)
|
||||
if iface.net != node1:
|
||||
raise CoreError(
|
||||
f"node2({node2.name}) iface2({iface2_id})"
|
||||
f" is not linked to node1({node1.name})"
|
||||
)
|
||||
iface.config(options)
|
||||
elif isinstance(node1, CoreNetworkBase) and isinstance(
|
||||
node2, CoreNetworkBase
|
||||
):
|
||||
iface = node1.get_linked_iface(node2)
|
||||
upstream = False
|
||||
if not iface:
|
||||
upstream = True
|
||||
iface = node2.get_linked_iface(node1)
|
||||
if not iface:
|
||||
raise CoreError("modify unknown link between nets")
|
||||
if upstream:
|
||||
iface.swapparams("_params_up")
|
||||
node1.linkconfig(iface, options)
|
||||
iface.swapparams("_params_up")
|
||||
else:
|
||||
node1.linkconfig(iface, options)
|
||||
if iface:
|
||||
use_local = iface.net == node1
|
||||
iface.config(options, use_local=use_local)
|
||||
if not options.unidirectional:
|
||||
if upstream:
|
||||
node2.linkconfig(iface, options)
|
||||
iface.config(options, use_local=not use_local)
|
||||
else:
|
||||
iface.swapparams("_params_up")
|
||||
node2.linkconfig(iface, options)
|
||||
iface.swapparams("_params_up")
|
||||
raise CoreError(
|
||||
f"node1({node1.name}) and node2({node2.name}) are not linked"
|
||||
)
|
||||
else:
|
||||
raise CoreError(
|
||||
f"cannot update link node1({type(node1)}) node2({type(node2)})"
|
||||
|
|
|
@ -321,7 +321,7 @@ class BasicRangeModel(WirelessModel):
|
|||
loss=self.loss,
|
||||
jitter=self.jitter,
|
||||
)
|
||||
self.wlan.linkconfig(iface, options)
|
||||
iface.config(options)
|
||||
|
||||
def get_position(self, iface: CoreInterface) -> Tuple[float, float, float]:
|
||||
"""
|
||||
|
|
|
@ -13,7 +13,7 @@ import netaddr
|
|||
|
||||
from core import utils
|
||||
from core.configservice.dependencies import ConfigServiceDependencies
|
||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||
from core.emulator.data import InterfaceData, LinkData
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.executables import MOUNT, TEST, VNODED
|
||||
|
@ -1000,20 +1000,6 @@ class CoreNetworkBase(NodeBase):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def linkconfig(
|
||||
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
Configure link parameters by applying tc queuing disciplines on the interface.
|
||||
|
||||
:param iface: interface one
|
||||
:param options: options for configuring link
|
||||
:param iface2: interface two
|
||||
:return: nothing
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ virtual ethernet classes that implement the interfaces available under Linux.
|
|||
"""
|
||||
|
||||
import logging
|
||||
import math
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
||||
|
@ -13,6 +14,7 @@ from core import utils
|
|||
from core.emulator.data import LinkOptions
|
||||
from core.emulator.enumerations import TransportType
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.executables import TC
|
||||
from core.nodes.netclient import LinuxNetClient, get_net_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -336,6 +338,92 @@ 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
|
||||
link options.
|
||||
|
||||
:param options: options to configure with
|
||||
: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:
|
||||
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.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%"
|
||||
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)
|
||||
|
||||
|
||||
class Veth(CoreInterface):
|
||||
"""
|
||||
|
|
|
@ -3,7 +3,6 @@ Defines network nodes used within core.
|
|||
"""
|
||||
|
||||
import logging
|
||||
import math
|
||||
import threading
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
|
@ -14,7 +13,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Type
|
|||
import netaddr
|
||||
|
||||
from core import utils
|
||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||
from core.emulator.data import InterfaceData, LinkData
|
||||
from core.emulator.enumerations import (
|
||||
LinkTypes,
|
||||
MessageFlags,
|
||||
|
@ -23,7 +22,7 @@ from core.emulator.enumerations import (
|
|||
RegisterTlvs,
|
||||
)
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.executables import NFTABLES, TC
|
||||
from core.executables import NFTABLES
|
||||
from core.nodes.base import CoreNetworkBase
|
||||
from core.nodes.interface import CoreInterface, GreTap, Veth
|
||||
from core.nodes.netclient import get_net_client
|
||||
|
@ -400,77 +399,6 @@ class CoreNetwork(CoreNetworkBase):
|
|||
self.linked[iface1][iface2] = True
|
||||
nft_queue.update(self)
|
||||
|
||||
def linkconfig(
|
||||
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
Configure link parameters by applying tc queuing disciplines on the interface.
|
||||
|
||||
:param iface: interface one
|
||||
:param options: options for configuring link
|
||||
:param iface2: interface two
|
||||
:return: nothing
|
||||
"""
|
||||
# determine if any settings have changed
|
||||
changed = any(
|
||||
[
|
||||
iface.setparam("bw", options.bandwidth),
|
||||
iface.setparam("delay", options.delay),
|
||||
iface.setparam("loss", options.loss),
|
||||
iface.setparam("duplicate", options.dup),
|
||||
iface.setparam("jitter", options.jitter),
|
||||
iface.setparam("buffer", options.buffer),
|
||||
]
|
||||
)
|
||||
if not changed:
|
||||
return
|
||||
|
||||
# delete tc configuration or create and add it
|
||||
devname = iface.localname
|
||||
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 iface.getparam("has_netem"):
|
||||
return
|
||||
if self.up:
|
||||
cmd = f"{TC} qdisc delete dev {devname} root handle 10:"
|
||||
iface.host_cmd(cmd)
|
||||
iface.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 * iface.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)}%"
|
||||
if self.up:
|
||||
cmd = f"{TC} qdisc replace dev {devname} root handle 10: netem {netem}"
|
||||
iface.host_cmd(cmd)
|
||||
iface.setparam("has_netem", True)
|
||||
|
||||
def linknet(self, net: CoreNetworkBase) -> CoreInterface:
|
||||
"""
|
||||
Link this bridge with another by creating a veth pair and installing
|
||||
|
|
|
@ -7,14 +7,13 @@ import threading
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||
|
||||
from core.emulator.data import InterfaceData, LinkOptions
|
||||
from core.emulator.data import InterfaceData
|
||||
from core.emulator.distributed import DistributedServer
|
||||
from core.emulator.enumerations import NodeTypes, TransportType
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.executables import MOUNT, TEST, UMOUNT
|
||||
from core.nodes.base import CoreNetworkBase, CoreNodeBase
|
||||
from core.nodes.interface import DEFAULT_MTU, CoreInterface
|
||||
from core.nodes.network import CoreNetwork
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -143,17 +142,6 @@ class PhysicalNode(CoreNodeBase):
|
|||
if self.up:
|
||||
self.net_client.device_up(iface.localname)
|
||||
|
||||
def linkconfig(
|
||||
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
Apply tc queing disciplines using linkconfig.
|
||||
"""
|
||||
linux_bridge = CoreNetwork(self.session)
|
||||
linux_bridge.up = True
|
||||
linux_bridge.linkconfig(iface, options, iface2)
|
||||
del linux_bridge
|
||||
|
||||
def next_iface_id(self) -> int:
|
||||
with self.lock:
|
||||
while self.iface_id in self.ifaces:
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
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
|
||||
LINK_OPTIONS: LinkOptions = LinkOptions(
|
||||
delay=50, bandwidth=5000000, loss=25, dup=25, jitter=10, buffer=100
|
||||
)
|
||||
|
||||
|
||||
def create_ptp_network(
|
||||
session: Session, ip_prefixes: IpPrefixes
|
||||
|
@ -24,8 +33,28 @@ 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_ptp(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
def test_add_node_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(CoreNode)
|
||||
node2 = session.add_node(CoreNode)
|
||||
|
@ -33,11 +62,17 @@ class TestLinks:
|
|||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
|
||||
# when
|
||||
session.add_link(node1.id, node2.id, iface1_data, iface2_data)
|
||||
iface1, iface2 = session.add_link(
|
||||
node1.id, node2.id, iface1_data, iface2_data, options=LINK_OPTIONS
|
||||
)
|
||||
|
||||
# then
|
||||
assert node1.get_iface(iface1_data.id)
|
||||
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)
|
||||
|
||||
def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -46,11 +81,15 @@ class TestLinks:
|
|||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
|
||||
# when
|
||||
session.add_link(node1.id, node2.id, iface1_data=iface1_data)
|
||||
iface, _ = session.add_link(
|
||||
node1.id, node2.id, iface1_data=iface1_data, options=LINK_OPTIONS
|
||||
)
|
||||
|
||||
# then
|
||||
assert node2.links()
|
||||
assert node1.get_iface(iface1_data.id)
|
||||
assert iface is not None
|
||||
assert check_iface_match(iface, LINK_OPTIONS)
|
||||
|
||||
def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
|
@ -59,11 +98,15 @@ class TestLinks:
|
|||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
|
||||
# when
|
||||
session.add_link(node1.id, node2.id, iface2_data=iface2_data)
|
||||
_, iface = session.add_link(
|
||||
node1.id, node2.id, iface2_data=iface2_data, options=LINK_OPTIONS
|
||||
)
|
||||
|
||||
# then
|
||||
assert node1.links()
|
||||
assert node2.get_iface(iface2_data.id)
|
||||
assert iface is not None
|
||||
assert check_iface_match(iface, LINK_OPTIONS)
|
||||
|
||||
def test_add_net_to_net(self, session):
|
||||
# given
|
||||
|
@ -71,147 +114,119 @@ class TestLinks:
|
|||
node2 = session.add_node(SwitchNode)
|
||||
|
||||
# when
|
||||
session.add_link(node1.id, node2.id)
|
||||
iface, _ = session.add_link(node1.id, node2.id, options=LINK_OPTIONS)
|
||||
|
||||
# then
|
||||
assert node1.links()
|
||||
assert iface is not None
|
||||
assert check_iface_match(iface, LINK_OPTIONS)
|
||||
|
||||
def test_add_node_to_node_uni(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(CoreNode)
|
||||
node2 = session.add_node(CoreNode)
|
||||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
link_options1 = LinkOptions(
|
||||
delay=50,
|
||||
bandwidth=5000000,
|
||||
loss=25,
|
||||
dup=25,
|
||||
jitter=10,
|
||||
buffer=100,
|
||||
unidirectional=True,
|
||||
)
|
||||
link_options2 = LinkOptions(
|
||||
delay=51,
|
||||
bandwidth=5000001,
|
||||
loss=26,
|
||||
dup=26,
|
||||
jitter=11,
|
||||
buffer=101,
|
||||
unidirectional=True,
|
||||
)
|
||||
|
||||
# when
|
||||
iface1, iface2 = session.add_link(
|
||||
node1.id, node2.id, iface1_data, iface2_data, link_options1
|
||||
)
|
||||
session.update_link(
|
||||
node2.id, node1.id, iface2_data.id, iface1_data.id, link_options2
|
||||
)
|
||||
|
||||
# then
|
||||
assert node1.get_iface(iface1_data.id)
|
||||
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)
|
||||
|
||||
def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
delay = 50
|
||||
bandwidth = 5000000
|
||||
loss = 25
|
||||
dup = 25
|
||||
jitter = 10
|
||||
buffer = 100
|
||||
node1 = session.add_node(CoreNode)
|
||||
node2 = session.add_node(SwitchNode)
|
||||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
session.add_link(node1.id, node2.id, iface1_data)
|
||||
iface1 = node1.get_iface(iface1_data.id)
|
||||
assert iface1.getparam("delay") != delay
|
||||
assert iface1.getparam("bw") != bandwidth
|
||||
assert iface1.getparam("loss") != loss
|
||||
assert iface1.getparam("duplicate") != dup
|
||||
assert iface1.getparam("jitter") != jitter
|
||||
assert iface1.getparam("buffer") != buffer
|
||||
iface1, _ = session.add_link(node1.id, node2.id, iface1_data)
|
||||
assert check_iface_diff(iface1, LINK_OPTIONS)
|
||||
|
||||
# when
|
||||
options = LinkOptions(
|
||||
delay=delay,
|
||||
bandwidth=bandwidth,
|
||||
loss=loss,
|
||||
dup=dup,
|
||||
jitter=jitter,
|
||||
buffer=buffer,
|
||||
)
|
||||
session.update_link(
|
||||
node1.id, node2.id, iface1_id=iface1_data.id, options=options
|
||||
node1.id, node2.id, iface1_id=iface1_data.id, options=LINK_OPTIONS
|
||||
)
|
||||
|
||||
# then
|
||||
assert iface1.getparam("delay") == delay
|
||||
assert iface1.getparam("bw") == bandwidth
|
||||
assert iface1.getparam("loss") == loss
|
||||
assert iface1.getparam("duplicate") == dup
|
||||
assert iface1.getparam("jitter") == jitter
|
||||
assert iface1.getparam("buffer") == buffer
|
||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
||||
|
||||
def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
delay = 50
|
||||
bandwidth = 5000000
|
||||
loss = 25
|
||||
dup = 25
|
||||
jitter = 10
|
||||
buffer = 100
|
||||
node1 = session.add_node(SwitchNode)
|
||||
node2 = session.add_node(CoreNode)
|
||||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
session.add_link(node1.id, node2.id, iface2_data=iface2_data)
|
||||
iface2 = node2.get_iface(iface2_data.id)
|
||||
assert iface2.getparam("delay") != delay
|
||||
assert iface2.getparam("bw") != bandwidth
|
||||
assert iface2.getparam("loss") != loss
|
||||
assert iface2.getparam("duplicate") != dup
|
||||
assert iface2.getparam("jitter") != jitter
|
||||
assert iface2.getparam("buffer") != buffer
|
||||
_, iface2 = session.add_link(node1.id, node2.id, iface2_data=iface2_data)
|
||||
assert check_iface_diff(iface2, LINK_OPTIONS)
|
||||
|
||||
# when
|
||||
options = LinkOptions(
|
||||
delay=delay,
|
||||
bandwidth=bandwidth,
|
||||
loss=loss,
|
||||
dup=dup,
|
||||
jitter=jitter,
|
||||
buffer=buffer,
|
||||
)
|
||||
session.update_link(
|
||||
node1.id, node2.id, iface2_id=iface2_data.id, options=options
|
||||
node1.id, node2.id, iface2_id=iface2_data.id, options=LINK_OPTIONS
|
||||
)
|
||||
|
||||
# then
|
||||
assert iface2.getparam("delay") == delay
|
||||
assert iface2.getparam("bw") == bandwidth
|
||||
assert iface2.getparam("loss") == loss
|
||||
assert iface2.getparam("duplicate") == dup
|
||||
assert iface2.getparam("jitter") == jitter
|
||||
assert iface2.getparam("buffer") == buffer
|
||||
assert check_iface_match(iface2, LINK_OPTIONS)
|
||||
|
||||
def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
delay = 50
|
||||
bandwidth = 5000000
|
||||
loss = 25
|
||||
dup = 25
|
||||
jitter = 10
|
||||
buffer = 100
|
||||
node1 = session.add_node(CoreNode)
|
||||
node2 = session.add_node(CoreNode)
|
||||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
session.add_link(node1.id, node2.id, iface1_data, iface2_data)
|
||||
iface1 = node1.get_iface(iface1_data.id)
|
||||
iface2 = node2.get_iface(iface2_data.id)
|
||||
assert iface1.getparam("delay") != delay
|
||||
assert iface1.getparam("bw") != bandwidth
|
||||
assert iface1.getparam("loss") != loss
|
||||
assert iface1.getparam("duplicate") != dup
|
||||
assert iface1.getparam("jitter") != jitter
|
||||
assert iface1.getparam("buffer") != buffer
|
||||
assert iface2.getparam("delay") != delay
|
||||
assert iface2.getparam("bw") != bandwidth
|
||||
assert iface2.getparam("loss") != loss
|
||||
assert iface2.getparam("duplicate") != dup
|
||||
assert iface2.getparam("jitter") != jitter
|
||||
assert iface2.getparam("buffer") != buffer
|
||||
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)
|
||||
|
||||
# when
|
||||
options = LinkOptions(
|
||||
delay=delay,
|
||||
bandwidth=bandwidth,
|
||||
loss=loss,
|
||||
dup=dup,
|
||||
jitter=jitter,
|
||||
buffer=buffer,
|
||||
session.update_link(
|
||||
node1.id, node2.id, iface1_data.id, iface2_data.id, LINK_OPTIONS
|
||||
)
|
||||
session.update_link(node1.id, node2.id, iface1_data.id, iface2_data.id, options)
|
||||
|
||||
# then
|
||||
assert iface1.getparam("delay") == delay
|
||||
assert iface1.getparam("bw") == bandwidth
|
||||
assert iface1.getparam("loss") == loss
|
||||
assert iface1.getparam("duplicate") == dup
|
||||
assert iface1.getparam("jitter") == jitter
|
||||
assert iface1.getparam("buffer") == buffer
|
||||
assert iface2.getparam("delay") == delay
|
||||
assert iface2.getparam("bw") == bandwidth
|
||||
assert iface2.getparam("loss") == loss
|
||||
assert iface2.getparam("duplicate") == dup
|
||||
assert iface2.getparam("jitter") == jitter
|
||||
assert iface2.getparam("buffer") == buffer
|
||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
||||
assert check_iface_match(iface2, LINK_OPTIONS)
|
||||
|
||||
def test_delete_ptp(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
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)
|
||||
|
||||
# when
|
||||
session.update_link(node1.id, node2.id, options=LINK_OPTIONS)
|
||||
|
||||
# then
|
||||
assert check_iface_match(iface1, LINK_OPTIONS)
|
||||
|
||||
def test_delete_node_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(CoreNode)
|
||||
node2 = session.add_node(CoreNode)
|
||||
|
@ -255,3 +270,82 @@ class TestLinks:
|
|||
|
||||
# then
|
||||
assert iface2_data.id not in node2.ifaces
|
||||
|
||||
def test_delete_net_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(SwitchNode)
|
||||
node2 = session.add_node(SwitchNode)
|
||||
session.add_link(node1.id, node2.id)
|
||||
assert node1.get_linked_iface(node2)
|
||||
|
||||
# when
|
||||
session.delete_link(node1.id, node2.id)
|
||||
|
||||
# then
|
||||
assert not node1.get_linked_iface(node2)
|
||||
|
||||
def test_delete_node_error(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(SwitchNode)
|
||||
node2 = session.add_node(SwitchNode)
|
||||
session.add_link(node1.id, node2.id)
|
||||
assert node1.get_linked_iface(node2)
|
||||
|
||||
# when
|
||||
with pytest.raises(CoreError):
|
||||
session.delete_link(node1.id, INVALID_ID)
|
||||
with pytest.raises(CoreError):
|
||||
session.delete_link(INVALID_ID, node2.id)
|
||||
|
||||
def test_delete_net_to_net_error(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(SwitchNode)
|
||||
node2 = session.add_node(SwitchNode)
|
||||
node3 = session.add_node(SwitchNode)
|
||||
session.add_link(node1.id, node2.id)
|
||||
assert node1.get_linked_iface(node2)
|
||||
|
||||
# when
|
||||
with pytest.raises(CoreError):
|
||||
session.delete_link(node1.id, node3.id)
|
||||
|
||||
def test_delete_node_to_net_error(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(CoreNode)
|
||||
node2 = session.add_node(SwitchNode)
|
||||
node3 = session.add_node(SwitchNode)
|
||||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
iface1, _ = session.add_link(node1.id, node2.id, iface1_data)
|
||||
assert iface1
|
||||
|
||||
# when
|
||||
with pytest.raises(CoreError):
|
||||
session.delete_link(node1.id, node3.id)
|
||||
|
||||
def test_delete_net_to_node_error(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(SwitchNode)
|
||||
node2 = session.add_node(CoreNode)
|
||||
node3 = session.add_node(SwitchNode)
|
||||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
_, iface2 = session.add_link(node1.id, node2.id, iface2_data=iface2_data)
|
||||
assert iface2
|
||||
|
||||
# when
|
||||
with pytest.raises(CoreError):
|
||||
session.delete_link(node1.id, node3.id)
|
||||
|
||||
def test_delete_node_to_node_error(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node1 = session.add_node(CoreNode)
|
||||
node2 = session.add_node(CoreNode)
|
||||
node3 = session.add_node(SwitchNode)
|
||||
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 iface1
|
||||
assert iface2
|
||||
|
||||
# when
|
||||
with pytest.raises(CoreError):
|
||||
session.delete_link(node1.id, node3.id)
|
||||
|
|
Loading…
Reference in a new issue