mininet-sample/core/experience.py

463 lines
18 KiB
Python

from .parameter import Parameter
from topos.multi_interface import MultiInterfaceTopo
class ExperienceParameter(Parameter):
"""
Handler for experience parameters stored in configuration files
"""
RMEM = "rmem"
WMEM = "wmem"
SCHED = "sched"
CC = "congctrl"
AUTOCORK = "autocork"
EARLYRETRANS = "earlyRetrans"
KERNELPM = "kpm"
KERNELPMC = "kpmc" #kernel path manager client / server
KERNELPMS = "kpms"
USERPMC = "upmc"
USERPMS = "upms" #userspace path manager client / server
USERPMCARGS = "upmc_args"
USERPMSARGS = "upms_args"
CLIENTPCAP = "clientPcap"
SERVERPCAP = "serverPcap"
SNAPLENPCAP = "snaplenPcap"
XPTYPE = "xpType"
PINGCOUNT = "pingCount"
NETPERFTESTLEN = "netperfTestlen"
NETPERFTESTNAME = "netperfTestname"
NETPERFREQRESSIZE = "netperfReqresSize"
VLCFILE = "vlcFile"
VLCTIME = "vlcTime"
QUICMULTIPATH = "quicMultipath"
QUICSIRIRUNTIME = "quicSiriRunTime"
PRIOPATH0 = "prioPath0"
PRIOPATH1 = "prioPath1"
BACKUPPATH0 = "backupPath0"
BACKUPPATH1 = "backupPath1"
EXPIRATION = "expiration"
BUFFERAUTOTUNING = "bufferAutotuning"
METRIC = "metric"
# Global sysctl keys
SYSCTL_KEY = {
RMEM: "net.ipv4.tcp_rmem",
WMEM: "net.ipv4.tcp_wmem",
KERNELPM: "net.mptcp.mptcp_path_manager",
SCHED: "net.mptcp.mptcp_scheduler",
CC: "net.ipv4.tcp_congestion_control",
AUTOCORK: "net.ipv4.tcp_autocorking",
EARLYRETRANS: "net.ipv4.tcp_early_retrans",
EXPIRATION: "net.mptcp.mptcp_sched_expiration",
BUFFERAUTOTUNING: "net.ipv4.tcp_moderate_rcvbuf",
}
# sysctl keys specific to client and server, independently
SYSCTL_KEY_CLIENT = {
KERNELPMC: "net.mptcp.mptcp_path_manager",
}
SYSCTL_KEY_SERVER = {
KERNELPMS: "net.mptcp.mptcp_path_manager",
}
# Default values for unspecified experience parameters
DEFAULT_PARAMETERS = {
RMEM: "10240 87380 16777216",
WMEM: "4096 16384 4194304",
KERNELPM: "fullmesh",
KERNELPMC: "fullmesh",
KERNELPMS: "fullmesh",
USERPMC: "fullmesh",
USERPMS: "fullmesh",
USERPMCARGS: "",
USERPMSARGS: "",
CC: "olia",
SCHED: "default",
AUTOCORK: "1",
EARLYRETRANS: "3",
EXPIRATION: "300",
BUFFERAUTOTUNING: "1",
METRIC: "-1",
CLIENTPCAP: "no",
SERVERPCAP: "no",
SNAPLENPCAP: "65535", # Default snapping value of tcpdump
XPTYPE: "none",
PINGCOUNT: "5",
NETPERFTESTLEN: "10",
NETPERFTESTNAME: "TCP_RR",
NETPERFREQRESSIZE: "2K,256",
VLCFILE: "bunny_ibmff_360.mpd",
VLCTIME: "0",
QUICMULTIPATH: "0",
PRIOPATH0: "0",
PRIOPATH1: "0",
BACKUPPATH0: "0",
BACKUPPATH1: "0",
}
def __init__(self, parameter_filename):
super(ExperienceParameter, self).__init__(parameter_filename)
self.default_parameters = ExperienceParameter.DEFAULT_PARAMETERS
def get(self, key):
val = super(ExperienceParameter, self).get(key)
if val is None:
if key in self.default_parameters:
return self.default_parameters[key]
else:
raise Exception("Parameter not found " + key)
else:
return val
class Experience(object):
"""
Base class to instantiate an experience to perform.
This class is not instantiable as it. You must define a child class with the
`NAME` attribute.
By default, an Experience relies on an instance of ExperienceParameter to
collect the parameters from the experience configuration file. However, an
experience may introduce specific parameters in the configuration file. In
such case, the inherinting class must override the `PARAMETER_CLASS` class
variable to point to another class inheriting from ExperienceParameter.
Attributes:
experience_parameter Instance of ExperienceParameter
topo Instance of Topo
topo_config Instance of TopoConfig
"""
PARAMETER_CLASS = ExperienceParameter
def __init__(self, experience_parameter_filename, topo, topo_config):
"""
Instantiation of this base class only load the experience parameter
"""
self.experience_parameter = self.__class__.PARAMETER_CLASS(experience_parameter_filename)
self.topo = topo
self.topo_config = topo_config
def load_parameters(self):
"""
Load the parameter of interest from self.experience_parameter
"""
# Nothing to do in the base class
pass
def classic_run(self):
"""
Default function to perform the experiment. It consists into three phases:
- A preparation phase through `prepare()` (generating experiment files,...)
- A running phase through `run()` (where the actual experience takes place)
- A cleaning phase through `clean()` (stopping traffic, removing generated files,...)
"""
self.prepare()
self.run()
self.clean()
def prepare(self):
"""
Prepare the environment to run the experience.
Typically, when you inherit from this class, you want to extend this
method, while still calling this parent function.
TODO: split experience traffic and protocol configuration
"""
self.setup_sysctl()
self.run_userspace_path_manager() # TODO to move elsewhere
self.topo_config.configure_network()
self.change_metric() # TODO to move elsewhere
self.put_priority_on_paths() # TODO to move elsewhere
self.disable_tso()
self.runTcpDump()
self.runNetemAt()
def change_metric(self):
"""
Function only meaningful for MPTCP and its specific scheduler
"""
metric = self.experience_parameter.get(ExperienceParameter.METRIC)
if int(metric) >= 0:
self.topo.command_global(
"echo {} > /sys/module/mptcp_sched_metric/parameters/metric".format(metric))
def put_priority_on_paths(self):
"""
Function only meaningful for MPTCP
"""
# Only meaningful if mpTopo is instance of MultiInterfaceTopo
if isinstance(self.topo, MultiInterfaceTopo):
prioPath0 = self.experience_parameter.get(ExperienceParameter.PRIOPATH0)
prioPath1 = self.experience_parameter.get(ExperienceParameter.PRIOPATH1)
if not prioPath0 == prioPath1:
self.topo.command_to(self.topo_config.client, "/home/mininet/iproute/ip/ip link set dev " +
self.topo_config.getClientInterface(0) + " priority " + str(prioPath0))
self.topo.command_to(self.topo_config.router, "/home/mininet/iproute/ip/ip link set dev " +
self.topo_config.getRouterInterfaceSwitch(0) + " priority " +
str(prioPath0))
self.topo.command_to(self.topo_config.client, "/home/mininet/iproute/ip/ip link set dev " +
self.topo_config.getClientInterface(1) + " priority " + str(prioPath1))
self.topo.command_to(self.topo_config.router, "/home/mininet/iproute/ip/ip link set dev " +
self.topo_config.getRouterInterfaceSwitch(1) + " priority " +
str(prioPath1))
backupPath0 = self.experience_parameter.get(ExperienceParameter.BACKUPPATH0)
if int(backupPath0) > 0:
self.topo.command_to(self.topo_config.client, self.topo_config.interfaceBUPCommand(self.topo_config.getClientInterface(0)))
self.topo.command_to(self.topo_config.router, self.topo_config.interfaceBUPCommand(self.topo_config.getRouterInterfaceSwitch(0)))
backupPath1 = self.experience_parameter.get(ExperienceParameter.BACKUPPATH1)
if int(backupPath1) > 0:
self.topo.command_to(self.topo_config.client, self.topo_config.interfaceBUPCommand(self.topo_config.getClientInterface(1)))
self.topo.command_to(self.topo_config.router, self.topo_config.interfaceBUPCommand(self.topo_config.getRouterInterfaceSwitch(1)))
def disable_tso(self):
links = self.topo.getLinkCharacteristics()
i = 0
for l in links:
lname = self.topo_config.getMidLeftName(i)
rname = self.topo_config.getMidRightName(i)
lbox = self.topo.get_host(lname)
lif = self.topo_config.getMidL2RInterface(i)
rif = self.topo_config.getMidR2LInterface(i)
rbox = self.topo.get_host(rname)
print(str(lname) + " " + str(lif))
print(str(rname) + " " + str(rif))
print("boxes " + str(lbox) + " " + str(rbox))
cmd = "ethtool -K " + lif + " tso off"
print(cmd)
self.topo.command_to(lbox, cmd)
cmd = "ethtool -K " + rif + " tso off"
print(cmd)
self.topo.command_to(rbox, cmd)
i = i + 1
# And for the server
cmd = "ethtool -K " + self.topo_config.getServerInterface() + " tso off"
print(cmd)
self.topo.command_to(self.topo_config.server, cmd)
cmd = "ethtool -K " + self.topo_config.getRouterInterfaceSwitch(self.topo_config.getClientInterfaceCount()) + " tso off"
print(cmd)
self.topo.command_to(self.topo_config.router, cmd)
def run_userspace_path_manager(self):
if self.experience_parameter.get(ExperienceParameter.KERNELPMC) != "netlink":
print("Client : Error, I can't change the userspace pm if the kernel pm is not netlink !")
else:
upmc = self.experience_parameter.get(ExperienceParameter.USERPMC)
upmca = self.experience_parameter.get(ExperienceParameter.USERPMCARGS)
self.topo.command_to(self.topo_config.client, upmc + \
" " + upmca + " &>upmc.log &")
if self.experience_parameter.get(ExperienceParameter.KERNELPMS) != "netlink":
print("Server : Error, I can't change the userspace pm if the kernel pm is not netlink !")
else:
upms = self.experience_parameter.get(ExperienceParameter.USERPMS)
upmsa = self.experience_parameter.get(ExperienceParameter.USERPMSARGS)
self.topo.command_to(self.topo_config.server, upms + \
" " + upmsa + " &>upms.log &")
def cleanUserspacePM(self):
if self.experience_parameter.get(ExperienceParameter.KERNELPMC) != "netlink":
print("Client : Error, I can't change the userspace pm if the kernel pm is not netlink !")
else:
upmc = self.experience_parameter.get(ExperienceParameter.USERPMC)
self.topo.command_to(self.topo_config.client, "killall " + upmc)
if self.experience_parameter.get(ExperienceParameter.KERNELPMS) != "netlink":
print("Server : Error, I can't change the userspace pm if the kernel pm is not netlink !")
else:
upms = self.experience_parameter.get(ExperienceParameter.USERPMS)
self.topo.command_to(self.topo_config.server, "killall " + upms)
def runNetemAt(self):
if not self.topo.changeNetem == "yes":
print("I don't need to change netem")
return
print("Will change netem config on the fly")
links = self.topo.getLinkCharacteristics()
i = 0
for l in links:
lname = self.topo_config.getMidLeftName(i)
rname = self.topo_config.getMidRightName(i)
lbox = self.topo.get_host(lname)
lif = self.topo_config.getMidL2RInterface(i)
rif = self.topo_config.getMidR2LInterface(i)
rbox = self.topo.get_host(rname)
print(str(lname) + " " + str(lif))
print(str(rname) + " " + str(rif))
print("boxes " + str(lbox) + " " + str(rbox))
cmd = l.buildBwCmd(lif)
print(cmd)
self.topo.command_to(lbox, cmd)
cmd = l.buildBwCmd(rif)
print(cmd)
self.topo.command_to(rbox, cmd)
ilif = self.topo_config.getMidL2RIncomingInterface(i)
irif = self.topo_config.getMidR2LIncomingInterface(i)
cmd = l.buildPolicingCmd(ilif)
print(cmd)
self.topo.command_to(lbox, cmd)
cmd = l.buildPolicingCmd(irif)
print(cmd)
self.topo.command_to(rbox, cmd)
cmd = l.buildNetemCmd(irif)
print(cmd)
self.topo.command_to(rbox, cmd)
cmd = l.buildNetemCmd(ilif)
print(cmd)
self.topo.command_to(lbox, cmd)
i = i + 1
def run(self):
pass
def clean(self):
self.topo.command_to(self.topo_config.client,
"killall tcpdump")
self.topo.command_to(self.topo_config.server,
"killall tcpdump")
self.backUpSysctl()
self.cleanUserspacePM()
pass
def setup_sysctl(self):
self.save_sysctl()
self.write_sysctl()
def save_sysctl(self):
self.sysctlBUP = {}
self._save_sysctl(ExperienceParameter.SYSCTL_KEY, self.sysctlBUP)
self.sysctlBUPC = {}
self._save_sysctl(ExperienceParameter.SYSCTL_KEY_CLIENT, self.sysctlBUPC,
ns = True, who = self.topo_config.client)
self.sysctlBUPS = {}
self._save_sysctl(ExperienceParameter.SYSCTL_KEY_SERVER, self.sysctlBUPS,
ns = True, who = self.topo_config.server)
def _save_sysctl(self, sysctlDic, sysctlBUP, ns = False, who = None):
for k in sysctlDic:
SYSCTL_KEY = sysctlDic[k]
cmd = self.cmdReadSysctl(SYSCTL_KEY)
if not ns:
val = self.topo.command_global(cmd)
else:
val = self.topo.command_to(who, cmd)
if val == "Error":
print("oooops can't get sysctl " + SYSCTL_KEY)
else:
# For Python3 compatibility
if type(val) is bytes:
val = val.decode()
sysctlBUP[k] = val.split(" ",2)[2][:-1]
def cmdReadSysctl(self, key):
s = "sysctl " + key
return s
def cmd_write_sysctl(self, key, value):
s = self.cmdReadSysctl(key)
s = s + "=\"" + str(value) + "\""
return s
def write_sysctl(self):
self._write_sysctl(ExperienceParameter.SYSCTL_KEY, self.sysctlBUP)
self._write_sysctl(ExperienceParameter.SYSCTL_KEY_CLIENT, self.sysctlBUPC,
ns = True, who = self.topo_config.client)
self._write_sysctl(ExperienceParameter.SYSCTL_KEY_SERVER, self.sysctlBUPS,
ns = True, who = self.topo_config.server)
def _write_sysctl(self, sysctlDic, sysctlBUP, ns = False, who = None):
for k in sysctlBUP:
SYSCTL_KEY = sysctlDic[k]
sysctlValue = self.experience_parameter.get(k)
cmd = self.cmd_write_sysctl(SYSCTL_KEY,sysctlValue)
if not ns:
val = self.topo.command_global(cmd)
else:
val = self.topo.command_to(who, cmd)
if val == "Error":
print("oooops can't set sysctl " + SYSCTL_KEY)
def backUpSysctl(self):
self._backUpSysctl(ExperienceParameter.SYSCTL_KEY, self.sysctlBUP)
self._backUpSysctl(ExperienceParameter.SYSCTL_KEY_CLIENT, self.sysctlBUPC,
ns = True, who = self.topo_config.client)
self._backUpSysctl(ExperienceParameter.SYSCTL_KEY_SERVER, self.sysctlBUPS,
ns = True, who = self.topo_config.server)
def _backUpSysctl(self, sysctlDic, sysctlBUP, ns = False, who = None):
for k in sysctlBUP:
SYSCTL_KEY = sysctlDic[k]
sysctlValue = sysctlBUP[k]
cmd = self.cmd_write_sysctl(SYSCTL_KEY,sysctlValue)
if not ns:
val = self.topo.command_global(cmd)
else:
val = self.topo.command_to(who, cmd)
if val == "Error":
print("oooops can't set sysctl " + SYSCTL_KEY)
def runTcpDump(self):
#todo : replace filename by cst
cpcap = self.experience_parameter.get(ExperienceParameter.CLIENTPCAP)
spcap = self.experience_parameter.get(ExperienceParameter.SERVERPCAP)
snaplenpcap = self.experience_parameter.get(ExperienceParameter.SNAPLENPCAP)
if cpcap == "yes" :
self.topo.command_to(self.topo_config.client,
"tcpdump -i any -s " + snaplenpcap + " -w client.pcap &")
if spcap == "yes" :
self.topo.command_to(self.topo_config.server,
"tcpdump -i any -s " + snaplenpcap + " -w server.pcap &")
if spcap == "yes" or cpcap == "yes":
self.topo.command_to(self.topo_config.client,"sleep 5")
class RandomFileParameter(ExperienceParameter):
"""
Parameters for the RandomFileExperience
"""
FILE = "file" # file to fetch; if random, we create a file with random data called random.
RANDOM_SIZE = "file_size" # in KB
def __init__(self, experience_parameter_filename):
super(RandomFileParameter, self).__init__(experience_parameter_filename)
self.default_parameters.update({
RandomFileParameter.FILE: "random",
RandomFileParameter.RANDOM_SIZE: "1024",
})
class RandomFileExperience(Experience):
"""
Enable a experience to use random files
This class is not directly instantiable
"""
PARAMETER_CLASS = RandomFileParameter
def __init__(self, experience_parameter_filename, topo, topo_config):
super(RandomFileExperience, self).__init__(experience_parameter_filename, topo, topo_config)
self.load_parameters()
self.ping()
def load_parameters(self):
super(RandomFileExperience, self).load_parameters()
self.file = self.experience_parameter.get(RandomFileParameter.FILE)
self.random_size = self.experience_parameter.get(RandomFileParameter.RANDOM_SIZE)
def prepare(self):
super(RandomFileExperience, self).prepare()
if self.file == "random":
self.topo.command_to(self.topo_config.client,
"dd if=/dev/urandom of=random bs=1K count={}".format(self.random_size))
def clean(self):
super(RandomFileExperience, self).clean()
if self.file == "random":
self.topo.command_to(self.topo_config.client, "rm random*")