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:
Blake Harnden 2022-01-25 09:13:39 -08:00
parent bc8c49c573
commit e9b83b0d28
8 changed files with 348 additions and 252 deletions

View file

@ -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)

View file

@ -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)})"

View file

@ -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]:
"""

View file

@ -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

View file

@ -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):
"""

View file

@ -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

View file

@ -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:

View file

@ -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)