Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conntrack: insertion rate recipe, full table insertion rate recipe #370

Merged
merged 10 commits into from
Aug 22, 2024
26 changes: 26 additions & 0 deletions lnst/Common/BaseModule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from abc import abstractmethod


class BaseModule:
def __init__(self, **kwargs):
self._orig_kwargs = kwargs.copy()
self._res_data = None

@abstractmethod
def run(self):
pass

def _get_res_data(self):
return self._res_data

def __repr__(self):
return "{}({})".format(
self.__class__.__name__,
", ".join(
[
"{}={}".format(k, repr(v))
for k, v in self._orig_kwargs.items()
]
),
)

5 changes: 5 additions & 0 deletions lnst/Common/IpAddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,8 @@ def ipaddress(addr, flags=None):
else:
raise LnstError("Value must be a BaseIpAddress or string object."
" Not {}".format(type(addr)))


def ip_version_string(ip_address: BaseIpAddress) -> str:
return "ipv4" if isinstance(ip_address, Ip4Address) else "ipv6"

48 changes: 48 additions & 0 deletions lnst/Common/conditions/WaitForConditionModule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import time
import signal
import logging
from abc import abstractmethod

from ..BaseModule import BaseModule


class WaitForConditionModule(BaseModule):
def __init__(self, timeout: int = 30, **kwargs):
super().__init__(**kwargs)

self._timeout = timeout

@property
def timeout(self):
return self._timeout

def run(self):
logging.info(f"Waiting for condition {self.__class__.__name__} to be met")
signal.signal(signal.SIGALRM, self._sig_handler)
signal.alarm(self._timeout)

counter = 0
try:
while not self._condition():
time.sleep(1)
logging.debug(f"Waiting for condition: {counter}/{self._timeout}")
counter += 1
except TimeoutError:
logging.exception("Timeout of conditional wait reached!")
return False
finally:
signal.alarm(0)
logging.info(f"Condition {self.__class__.__name__} met/timeouted")

return True

@staticmethod
def _sig_handler(signum, _):
if signum == signal.SIGALRM:
raise TimeoutError("Timeout reached")

return

@abstractmethod
def _condition(self):
pass
58 changes: 58 additions & 0 deletions lnst/Common/conditions/WaitForEstablishedConnections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import os
import psutil
import logging
from typing import Literal, Union
from ipaddress import IPv4Network, IPv6Network, IPv4Address, IPv6Address

from ..IpAddress import BaseIpAddress
from .WaitForConditionModule import WaitForConditionModule


class WaitForEstablishedConnections(WaitForConditionModule):
def __init__(
self,
destination_net: Union[IPv4Network, IPv6Network],
stream: str,
total_connections: int,
**kwargs,
):
super().__init__(**kwargs)

self._kind = self.get_kind(destination_net, stream)
self._total_connections = total_connections

self._destination = destination_net

@staticmethod
def get_kind(destination, stream):
"""
Converts tcp_{stream,crr,...} (self.params.perf_tests)
IPv4 or 6 network (self.params.ip_versions) to tcp4, tcp6, udp4, udp6.
"""
kind = "tcp"
if "udp" in stream:
kind = "udp"

if isinstance(destination, IPv4Network):
kind += "4"
else:
kind += "6"

return kind

def _condition(self):
connections = psutil.net_connections(kind=self._kind)
addr_family = (
IPv4Address if isinstance(self._destination, IPv4Network) else IPv6Address
)

filtered = filter(
lambda conn: addr_family(conn.laddr.ip) in self._destination, connections
)

establised_connections = len(list(filtered))
logging.debug(
f"Established connections: {establised_connections} / {self._total_connections}"
)

return establised_connections >= self._total_connections
4 changes: 2 additions & 2 deletions lnst/Controller/Job.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

import logging
import signal
from lnst.Common.BaseModule import BaseModule
from lnst.Common.JobError import JobError
from lnst.Tests.BaseTestModule import BaseTestModule
from lnst.Controller.RecipeResults import ResultLevel, ResultType

DEFAULT_TIMEOUT = 60
Expand Down Expand Up @@ -68,7 +68,7 @@ def what(self):

@property
def type(self):
if isinstance(self.what, BaseTestModule):
if isinstance(self.what, BaseModule):
return "module"
elif isinstance(self.what, str):
return "shell"
Expand Down
7 changes: 7 additions & 0 deletions lnst/Controller/Namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from lnst.Controller.Common import ControllerError
from lnst.Controller.Job import Job
from lnst.Controller.RecipeResults import ResultLevel
from lnst.Common.conditions.WaitForConditionModule import WaitForConditionModule

class HostError(ControllerError):
pass
Expand Down Expand Up @@ -128,6 +129,12 @@ def run(self, what, fail=False, json=False, desc=None,
job = self.prepare_job(what, fail, json, desc, job_level)
job.start(bg, timeout)
return job

def wait_for_condition(self, condition: WaitForConditionModule):
job = self.prepare_job(condition)
job.start(bg=True)

return self._machine.wait_for_job(job, condition.timeout)

def __getattr__(self, name):
"""direct access to Device objects
Expand Down
6 changes: 6 additions & 0 deletions lnst/Devices/Device.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,12 @@ def alt_if_names(self):
return self._nl_msg.get_attr("IFLA_PROP_LIST").get_attrs("IFLA_ALT_IFNAME")
except:
return []

def keep_addrs_on_down(self):
exec_cmd(f"echo 1 > /proc/sys/net/ipv6/conf/{self.name}/keep_addr_on_down")

def remove_addrs_on_down(self):
exec_cmd(f"echo 0 > /proc/sys/net/ipv6/conf/{self.name}/keep_addr_on_down")

#TODO implement proper Route objects
#consider the same as with tc?
Expand Down
11 changes: 11 additions & 0 deletions lnst/Recipes/ENRT/CTFulltableInsertionRateRecipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from .CTInsertionRateNftablesRecipe import CTInsertionRateNftablesRecipe
from .ConfigMixins.LongLivedConnectionsMixin import LongLivedConnectionsMixin

class CTFulltableInsertionRateRecipe(LongLivedConnectionsMixin, CTInsertionRateNftablesRecipe):
jtluka marked this conversation as resolved.
Show resolved Hide resolved
"""
The recipe measures the insertion rate of new entries to already full conntrack table.
Since conntrack table is implemented as hash table, "full" depends on total count of
available buckets. Should be more than 75%.
"""
pass

30 changes: 30 additions & 0 deletions lnst/Recipes/ENRT/CTInsertionRateNftablesRecipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from .SimpleNetworkRecipe import SimpleNetworkRecipe
from .ConfigMixins.NftablesConntrackMixin import NftablesConntrackMixin

from lnst.Common.Parameters import ConstParam
from lnst.RecipeCommon.Perf.Evaluators import NonzeroFlowEvaluator


class CTInsertionRateNftablesRecipe(NftablesConntrackMixin, SimpleNetworkRecipe):
jtluka marked this conversation as resolved.
Show resolved Hide resolved
"""
The recipe measures insertion rate of conntrack entries on receiver side.
This is done by using tcp_crr test which opens connections (that trigger
adding a new conntrack entry), sends request, waits for response and closes
the socket. Thats done in a sequence, so we indirectly measure performance
of conntrack table.

It's important to keep perf_msg_sizes as small as possible, as transferring
any amount of data take some time and tcp_crr works in a sequence, so data
transfer blocks the process of opening new connections and affects the
results.
"""
net_perf_tool = ConstParam("neper")
perf_tests = ConstParam(["tcp_crr"])
perf_msg_sizes = ConstParam([1])
enhaut marked this conversation as resolved.
Show resolved Hide resolved

@property
def net_perf_evaluators(self):
return [
NonzeroFlowEvaluator(["generator_results"])
] # only generator measures CCs, receiver always reports 0

4 changes: 2 additions & 2 deletions lnst/Recipes/ENRT/ConfigMixins/FirewallMixin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from abc import ABC, abstractmethod
from abc import abstractmethod
from lnst.Recipes.ENRT.ConfigMixins import BaseSubConfigMixin
from lnst.RecipeCommon.FirewallControl import FirewallControl
import copy

class FirewallMixin(BaseSubConfigMixin, ABC):
class FirewallMixin(BaseSubConfigMixin):
"""
A config mixin to apply custom firewall rulesets on hosts.
Do not inherit directly, use one of the derived classes below instead.
Expand Down
Loading
Loading