From e9b83b0d284e3eb25463eb189020925ab57d15cb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 25 Jan 2022 09:13:39 -0800 Subject: [PATCH] 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 --- daemon/core/api/grpc/server.py | 16 +- daemon/core/emulator/session.py | 84 +++++---- daemon/core/location/mobility.py | 2 +- daemon/core/nodes/base.py | 16 +- daemon/core/nodes/interface.py | 88 +++++++++ daemon/core/nodes/network.py | 76 +------- daemon/core/nodes/physical.py | 14 +- daemon/tests/test_links.py | 304 ++++++++++++++++++++----------- 8 files changed, 348 insertions(+), 252 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index ba20d4ed..060bc4b6 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -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) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 4f41a5e5..46f9a04f 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -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") + if iface: + use_local = iface.net == node1 + iface.config(options, use_local=use_local) + if not options.unidirectional: + iface.config(options, use_local=not use_local) else: - node1.linkconfig(iface, options) - if not options.unidirectional: - if upstream: - node2.linkconfig(iface, options) - 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)})" diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 4c61c065..0464ebc7 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -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]: """ diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 91c1fdcc..ca6a43ef 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -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 diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 7fda18c7..7fc0a386 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -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): """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 34f0f878..2128c9b6 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -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 diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index dab2a954..4dd5fb57 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -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: diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 94c8c699..1d9b54ce 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -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)