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 # create links
links = [] links = []
asym_links = [] edit_links = []
known_links = set()
for link in request.session.links: for link in request.session.links:
if link.options.unidirectional: iface1 = link.iface1.id if link.iface1 else None
asym_links.append(link) 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: 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) links.append(link)
_, exceptions = grpcutils.create_links(session, links) _, exceptions = grpcutils.create_links(session, links)
if exceptions: if exceptions:
exceptions = [str(x) for x in exceptions] exceptions = [str(x) for x in exceptions]
return core_pb2.StartSessionResponse(result=False, exceptions=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: if exceptions:
exceptions = [str(x) for x in exceptions] exceptions = [str(x) for x in exceptions]
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)

View file

@ -276,20 +276,22 @@ class Session:
ptp = self.create_node(PtpNet, start) ptp = self.create_node(PtpNet, start)
iface1 = node1.new_iface(ptp, iface1_data) iface1 = node1.new_iface(ptp, iface1_data)
iface2 = node2.new_iface(ptp, iface2_data) iface2 = node2.new_iface(ptp, iface2_data)
ptp.linkconfig(iface1, options) iface1.config(options)
if not options.unidirectional: if not options.unidirectional:
ptp.linkconfig(iface2, options) iface2.config(options)
# link node to net # link node to net
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): 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) iface1 = node1.new_iface(node2, iface1_data)
if not isinstance(node2, (EmaneNet, WlanNode)): if not isinstance(node2, (EmaneNet, WlanNode)):
node2.linkconfig(iface1, options) iface1.config(options)
# link net to node # link net to node
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): 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) iface2 = node2.new_iface(node1, iface2_data)
wireless_net = isinstance(node1, (EmaneNet, WlanNode)) wireless_net = isinstance(node1, (EmaneNet, WlanNode))
if not options.unidirectional and not wireless_net: if not options.unidirectional and not wireless_net:
node1.linkconfig(iface2, options) iface2.config(options)
# network to network # network to network
elif isinstance(node1, CoreNetworkBase) and isinstance( elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase node2, CoreNetworkBase
@ -298,11 +300,10 @@ class Session:
"linking network to network: %s - %s", node1.name, node2.name "linking network to network: %s - %s", node1.name, node2.name
) )
iface1 = node1.linknet(node2) iface1 = node1.linknet(node2)
node1.linkconfig(iface1, options) use_local = iface1.net == node1
iface1.config(options, use_local=use_local)
if not options.unidirectional: if not options.unidirectional:
iface1.swapparams("_params_up") iface1.config(options, use_local=not use_local)
node2.linkconfig(iface1, options)
iface1.swapparams("_params_up")
else: else:
raise CoreError( raise CoreError(
f"cannot link node1({type(node1)}) node2({type(node2)})" f"cannot link node1({type(node1)}) node2({type(node2)})"
@ -379,16 +380,18 @@ class Session:
elif isinstance(node1, CoreNetworkBase) and isinstance( elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase node2, CoreNetworkBase
): ):
for iface in node1.get_ifaces(control=False): iface1 = node1.get_linked_iface(node2)
if iface.othernet == node2: if iface1:
node1.detach(iface) node1.detach(iface1)
iface.shutdown() iface1.shutdown()
break iface2 = node2.get_linked_iface(node1)
for iface in node2.get_ifaces(control=False): if iface2:
if iface.othernet == node1: node2.detach(iface2)
node2.detach(iface) iface2.shutdown()
iface.shutdown() if not iface1 and not iface2:
break raise CoreError(
f"node1({node1.name}) and node2({node2.name}) are not connected"
)
self.sdt.delete_link(node1_id, node2_id) self.sdt.delete_link(node1_id, node2_id)
def update_link( def update_link(
@ -432,11 +435,11 @@ class Session:
else: else:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
iface1 = node1.ifaces.get(iface1_id) iface1 = node1.ifaces.get(iface1_id)
iface2 = node2.ifaces.get(iface2_id)
if not iface1: if not iface1:
raise CoreError( raise CoreError(
f"node({node1.name}) missing interface({iface1_id})" f"node({node1.name}) missing interface({iface1_id})"
) )
iface2 = node2.ifaces.get(iface2_id)
if not iface2: if not iface2:
raise CoreError( raise CoreError(
f"node({node2.name}) missing interface({iface2_id})" f"node({node2.name}) missing interface({iface2_id})"
@ -446,39 +449,40 @@ class Session:
f"node1({node1.name}) node2({node2.name}) " f"node1({node1.name}) node2({node2.name}) "
"not connected to same net" "not connected to same net"
) )
ptp = iface1.net iface1.config(options)
ptp.linkconfig(iface1, options, iface2)
if not options.unidirectional: if not options.unidirectional:
ptp.linkconfig(iface2, options, iface1) iface2.config(options)
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
iface = node1.get_iface(iface1_id) 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): elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
iface = node2.get_iface(iface2_id) 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( elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase node2, CoreNetworkBase
): ):
iface = node1.get_linked_iface(node2) iface = node1.get_linked_iface(node2)
upstream = False
if not iface: if not iface:
upstream = True
iface = node2.get_linked_iface(node1) iface = node2.get_linked_iface(node1)
if not iface: if iface:
raise CoreError("modify unknown link between nets") use_local = iface.net == node1
if upstream: iface.config(options, use_local=use_local)
iface.swapparams("_params_up")
node1.linkconfig(iface, options)
iface.swapparams("_params_up")
else:
node1.linkconfig(iface, options)
if not options.unidirectional: if not options.unidirectional:
if upstream: iface.config(options, use_local=not use_local)
node2.linkconfig(iface, options)
else: else:
iface.swapparams("_params_up") raise CoreError(
node2.linkconfig(iface, options) f"node1({node1.name}) and node2({node2.name}) are not linked"
iface.swapparams("_params_up") )
else: else:
raise CoreError( raise CoreError(
f"cannot update link node1({type(node1)}) node2({type(node2)})" f"cannot update link node1({type(node1)}) node2({type(node2)})"

View file

@ -321,7 +321,7 @@ class BasicRangeModel(WirelessModel):
loss=self.loss, loss=self.loss,
jitter=self.jitter, jitter=self.jitter,
) )
self.wlan.linkconfig(iface, options) iface.config(options)
def get_position(self, iface: CoreInterface) -> Tuple[float, float, float]: def get_position(self, iface: CoreInterface) -> Tuple[float, float, float]:
""" """

View file

@ -13,7 +13,7 @@ import netaddr
from core import utils from core import utils
from core.configservice.dependencies import ConfigServiceDependencies 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.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.executables import MOUNT, TEST, VNODED from core.executables import MOUNT, TEST, VNODED
@ -1000,20 +1000,6 @@ class CoreNetworkBase(NodeBase):
""" """
raise NotImplementedError 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: def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
raise NotImplementedError raise NotImplementedError

View file

@ -3,6 +3,7 @@ virtual ethernet classes that implement the interfaces available under Linux.
""" """
import logging import logging
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, Tuple
@ -13,6 +14,7 @@ from core import utils
from core.emulator.data import LinkOptions from core.emulator.data import 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.nodes.netclient import LinuxNetClient, get_net_client from core.nodes.netclient import LinuxNetClient, get_net_client
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -336,6 +338,92 @@ 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:
"""
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): class Veth(CoreInterface):
""" """

View file

@ -3,7 +3,6 @@ Defines network nodes used within core.
""" """
import logging import logging
import math
import threading import threading
import time import time
from collections import OrderedDict from collections import OrderedDict
@ -14,7 +13,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Type
import netaddr import netaddr
from core import utils from core import utils
from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.data import InterfaceData, LinkData
from core.emulator.enumerations import ( from core.emulator.enumerations import (
LinkTypes, LinkTypes,
MessageFlags, MessageFlags,
@ -23,7 +22,7 @@ from core.emulator.enumerations import (
RegisterTlvs, RegisterTlvs,
) )
from core.errors import CoreCommandError, CoreError 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.base import CoreNetworkBase
from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.interface import CoreInterface, GreTap, Veth
from core.nodes.netclient import get_net_client from core.nodes.netclient import get_net_client
@ -400,77 +399,6 @@ class CoreNetwork(CoreNetworkBase):
self.linked[iface1][iface2] = True self.linked[iface1][iface2] = True
nft_queue.update(self) 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: def linknet(self, net: CoreNetworkBase) -> CoreInterface:
""" """
Link this bridge with another by creating a veth pair and installing 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 pathlib import Path
from typing import TYPE_CHECKING, List, Optional, Tuple 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.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes, TransportType from core.emulator.enumerations import NodeTypes, TransportType
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.executables import MOUNT, TEST, UMOUNT from core.executables import MOUNT, TEST, UMOUNT
from core.nodes.base import CoreNetworkBase, CoreNodeBase from core.nodes.base import CoreNetworkBase, CoreNodeBase
from core.nodes.interface import DEFAULT_MTU, CoreInterface from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import CoreNetwork
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -143,17 +142,6 @@ class PhysicalNode(CoreNodeBase):
if self.up: if self.up:
self.net_client.device_up(iface.localname) 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: def next_iface_id(self) -> int:
with self.lock: with self.lock:
while self.iface_id in self.ifaces: while self.iface_id in self.ifaces:

View file

@ -1,10 +1,19 @@
from typing import Tuple from typing import Tuple
import pytest
from core.emulator.data import IpPrefixes, LinkOptions 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.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
LINK_OPTIONS: LinkOptions = LinkOptions(
delay=50, bandwidth=5000000, loss=25, dup=25, jitter=10, buffer=100
)
def create_ptp_network( def create_ptp_network(
session: Session, ip_prefixes: IpPrefixes session: Session, ip_prefixes: IpPrefixes
@ -24,8 +33,28 @@ 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_ptp(self, session: Session, ip_prefixes: IpPrefixes): def test_add_node_to_node(self, session: Session, ip_prefixes: IpPrefixes):
# given # given
node1 = session.add_node(CoreNode) node1 = session.add_node(CoreNode)
node2 = session.add_node(CoreNode) node2 = session.add_node(CoreNode)
@ -33,11 +62,17 @@ class TestLinks:
iface2_data = ip_prefixes.create_iface(node2) iface2_data = ip_prefixes.create_iface(node2)
# when # 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 # then
assert node1.get_iface(iface1_data.id) assert node1.get_iface(iface1_data.id)
assert node2.get_iface(iface2_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): def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
# given # given
@ -46,11 +81,15 @@ class TestLinks:
iface1_data = ip_prefixes.create_iface(node1) iface1_data = ip_prefixes.create_iface(node1)
# when # 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 # then
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 check_iface_match(iface, LINK_OPTIONS)
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
@ -59,11 +98,15 @@ class TestLinks:
iface2_data = ip_prefixes.create_iface(node2) iface2_data = ip_prefixes.create_iface(node2)
# when # 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 # then
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 check_iface_match(iface, LINK_OPTIONS)
def test_add_net_to_net(self, session): def test_add_net_to_net(self, session):
# given # given
@ -71,147 +114,119 @@ class TestLinks:
node2 = session.add_node(SwitchNode) node2 = session.add_node(SwitchNode)
# when # when
session.add_link(node1.id, node2.id) iface, _ = session.add_link(node1.id, node2.id, options=LINK_OPTIONS)
# then # then
assert node1.links() 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): def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
# given # given
delay = 50
bandwidth = 5000000
loss = 25
dup = 25
jitter = 10
buffer = 100
node1 = session.add_node(CoreNode) node1 = session.add_node(CoreNode)
node2 = session.add_node(SwitchNode) node2 = session.add_node(SwitchNode)
iface1_data = ip_prefixes.create_iface(node1) iface1_data = ip_prefixes.create_iface(node1)
session.add_link(node1.id, node2.id, iface1_data) iface1, _ = session.add_link(node1.id, node2.id, iface1_data)
iface1 = node1.get_iface(iface1_data.id) assert check_iface_diff(iface1, LINK_OPTIONS)
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
# when # when
options = LinkOptions(
delay=delay,
bandwidth=bandwidth,
loss=loss,
dup=dup,
jitter=jitter,
buffer=buffer,
)
session.update_link( 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 # then
assert iface1.getparam("delay") == delay assert check_iface_match(iface1, LINK_OPTIONS)
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
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
delay = 50
bandwidth = 5000000
loss = 25
dup = 25
jitter = 10
buffer = 100
node1 = session.add_node(SwitchNode) node1 = session.add_node(SwitchNode)
node2 = session.add_node(CoreNode) node2 = session.add_node(CoreNode)
iface2_data = ip_prefixes.create_iface(node2) iface2_data = ip_prefixes.create_iface(node2)
session.add_link(node1.id, node2.id, iface2_data=iface2_data) _, iface2 = session.add_link(node1.id, node2.id, iface2_data=iface2_data)
iface2 = node2.get_iface(iface2_data.id) assert check_iface_diff(iface2, LINK_OPTIONS)
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
# when # when
options = LinkOptions(
delay=delay,
bandwidth=bandwidth,
loss=loss,
dup=dup,
jitter=jitter,
buffer=buffer,
)
session.update_link( 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 # then
assert iface2.getparam("delay") == delay assert check_iface_match(iface2, LINK_OPTIONS)
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
def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes):
# given # given
delay = 50
bandwidth = 5000000
loss = 25
dup = 25
jitter = 10
buffer = 100
node1 = session.add_node(CoreNode) node1 = session.add_node(CoreNode)
node2 = session.add_node(CoreNode) node2 = session.add_node(CoreNode)
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)
session.add_link(node1.id, node2.id, iface1_data, iface2_data) iface1, iface2 = session.add_link(node1.id, node2.id, iface1_data, iface2_data)
iface1 = node1.get_iface(iface1_data.id) assert check_iface_diff(iface1, LINK_OPTIONS)
iface2 = node2.get_iface(iface2_data.id) assert check_iface_diff(iface2, LINK_OPTIONS)
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
# when # when
options = LinkOptions( session.update_link(
delay=delay, node1.id, node2.id, iface1_data.id, iface2_data.id, LINK_OPTIONS
bandwidth=bandwidth,
loss=loss,
dup=dup,
jitter=jitter,
buffer=buffer,
) )
session.update_link(node1.id, node2.id, iface1_data.id, iface2_data.id, options)
# then # then
assert iface1.getparam("delay") == delay assert check_iface_match(iface1, LINK_OPTIONS)
assert iface1.getparam("bw") == bandwidth assert check_iface_match(iface2, LINK_OPTIONS)
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
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 # given
node1 = session.add_node(CoreNode) node1 = session.add_node(CoreNode)
node2 = session.add_node(CoreNode) node2 = session.add_node(CoreNode)
@ -255,3 +270,82 @@ class TestLinks:
# then # then
assert iface2_data.id not in node2.ifaces 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)