Commit 98cfbe42 authored by Pedro Tammela's avatar Pedro Tammela Committed by Paolo Abeni

selftests/tc-testing: localize test resources

As of today, the current tdc architecture creates one netns and uses it
to run all tests. This assumption was embedded into the nsPlugin which
carried over as how the tests were written.

The tdc tests are by definition self contained and can,
theoretically, run in parallel. Even though in the kernel they will
serialize over the rtnl lock, we should expect a significant speedup of the
total wall time for the entire test suite, which is hitting close to
1100 tests at this point.

A first step to achieve this goal is to remove sharing of global resources like
veth/dummy interfaces and the netns. In this patch we 'localize' these
resources on a per test basis. Each test gets it's own netns, VETH/dummy interfaces.
The resources are spawned in the pre_suite phase, where tdc will prepare
all netns and interfaces for all tests. This is done in order to avoid
concurrency issues with netns / interfaces spawning and commands using
them. As tdc progresses, the resources are deleted after each test finishes
executing.

Tests that don't use the nsPlugin still run under the root namespace,
but are now required to manage any external resources like interfaces.
These cannot be parallelized as their definition doesn't allow it.
On the other hand, when using the nsPlugin, tests don't need to create
dummy/veth interfaces as these are handled already.
Tested-by: default avatarDavide Caratti <dcaratti@redhat.com>
Signed-off-by: default avatarPedro Tammela <pctammela@mojatatu.com>
Acked-by: default avatarJamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent d387e34f
......@@ -5,10 +5,10 @@ class TdcPlugin:
super().__init__()
print(' -- {}.__init__'.format(self.sub_class))
def pre_suite(self, testcount, testidlist):
def pre_suite(self, testcount, testlist):
'''run commands before test_runner goes into a test loop'''
self.testcount = testcount
self.testidlist = testidlist
self.testlist = testlist
if self.args.verbose > 1:
print(' -- {}.pre_suite'.format(self.sub_class))
......
......@@ -3,6 +3,7 @@ import signal
from string import Template
import subprocess
import time
from functools import cached_property
from TdcPlugin import TdcPlugin
from tdc_config import *
......@@ -12,26 +13,77 @@ class SubPlugin(TdcPlugin):
self.sub_class = 'ns/SubPlugin'
super().__init__()
def pre_suite(self, testcount, testidlist):
'''run commands before test_runner goes into a test loop'''
super().pre_suite(testcount, testidlist)
def pre_suite(self, testcount, testlist):
super().pre_suite(testcount, testlist)
if self.args.namespace:
self._ns_create()
else:
self._ports_create()
print("Setting up namespaces and devices...")
def post_suite(self, index):
'''run commands after test_runner goes into a test loop'''
super().post_suite(index)
original = self.args.NAMES
for t in testlist:
if 'skip' in t and t['skip'] == 'yes':
continue
if 'nsPlugin' not in t['plugins']:
continue
shadow = {}
shadow['IP'] = original['IP']
shadow['TC'] = original['TC']
shadow['NS'] = '{}-{}'.format(original['NS'], t['random'])
shadow['DEV0'] = '{}id{}'.format(original['DEV0'], t['id'])
shadow['DEV1'] = '{}id{}'.format(original['DEV1'], t['id'])
shadow['DUMMY'] = '{}id{}'.format(original['DUMMY'], t['id'])
shadow['DEV2'] = original['DEV2']
self.args.NAMES = shadow
if self.args.namespace:
self._ns_create()
else:
self._ports_create()
self.args.NAMES = original
def pre_case(self, caseinfo, test_skip):
if self.args.verbose:
print('{}.post_suite'.format(self.sub_class))
print('{}.pre_case'.format(self.sub_class))
if test_skip:
return
# Make sure the netns is visible in the fs
while True:
self._proc_check()
try:
ns = self.args.NAMES['NS']
f = open('/run/netns/{}'.format(ns))
f.close()
break
except:
continue
def post_case(self):
if self.args.verbose:
print('{}.post_case'.format(self.sub_class))
if self.args.namespace:
self._ns_destroy()
else:
self._ports_destroy()
def post_suite(self, index):
if self.args.verbose:
print('{}.post_suite'.format(self.sub_class))
# Make sure we don't leak resources
for f in os.listdir('/run/netns/'):
cmd = self._replace_keywords("$IP netns del {}".format(f))
if self.args.verbose > 3:
print('_exec_cmd: command "{}"'.format(cmd))
subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def add_args(self, parser):
super().add_args(parser)
self.argparser_group = self.argparser.add_argument_group(
......@@ -77,18 +129,43 @@ class SubPlugin(TdcPlugin):
print('adjust_command: return command [{}]'.format(command))
return command
def _ports_create(self):
cmd = '$IP link add $DEV0 type veth peer name $DEV1'
self._exec_cmd('pre', cmd)
cmd = '$IP link set $DEV0 up'
self._exec_cmd('pre', cmd)
def _ports_create_cmds(self):
cmds = []
cmds.append(self._replace_keywords('link add $DEV0 type veth peer name $DEV1'))
cmds.append(self._replace_keywords('link set $DEV0 up'))
cmds.append(self._replace_keywords('link add $DUMMY type dummy'))
if not self.args.namespace:
cmd = '$IP link set $DEV1 up'
self._exec_cmd('pre', cmd)
cmds.append(self._replace_keywords('link set $DEV1 up'))
return cmds
def _ports_create(self):
self._exec_cmd_batched('pre', self._ports_create_cmds())
def _ports_destroy_cmd(self):
return self._replace_keywords('link del $DEV0')
def _ports_destroy(self):
cmd = '$IP link del $DEV0'
self._exec_cmd('post', cmd)
self._exec_cmd('post', self._ports_destroy_cmd())
def _ns_create_cmds(self):
cmds = []
if self.args.namespace:
ns = self.args.NAMES['NS']
cmds.append(self._replace_keywords('netns add {}'.format(ns)))
cmds.append(self._replace_keywords('link set $DEV1 netns {}'.format(ns)))
cmds.append(self._replace_keywords('link set $DUMMY netns {}'.format(ns)))
cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV1 up'.format(ns)))
cmds.append(self._replace_keywords('netns exec {} $IP link set $DUMMY up'.format(ns)))
if self.args.device:
cmds.append(self._replace_keywords('link set $DEV2 netns {}'.format(ns)))
cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV2 up'.format(ns)))
return cmds
def _ns_create(self):
'''
......@@ -96,18 +173,10 @@ class SubPlugin(TdcPlugin):
the required network devices for it.
'''
self._ports_create()
if self.args.namespace:
cmd = '$IP netns add {}'.format(self.args.NAMES['NS'])
self._exec_cmd('pre', cmd)
cmd = '$IP link set $DEV1 netns {}'.format(self.args.NAMES['NS'])
self._exec_cmd('pre', cmd)
cmd = '$IP -n {} link set $DEV1 up'.format(self.args.NAMES['NS'])
self._exec_cmd('pre', cmd)
if self.args.device:
cmd = '$IP link set $DEV2 netns {}'.format(self.args.NAMES['NS'])
self._exec_cmd('pre', cmd)
cmd = '$IP -n {} link set $DEV2 up'.format(self.args.NAMES['NS'])
self._exec_cmd('pre', cmd)
self._exec_cmd_batched('pre', self._ns_create_cmds())
def _ns_destroy_cmd(self):
return self._replace_keywords('netns delete {}'.format(self.args.NAMES['NS']))
def _ns_destroy(self):
'''
......@@ -115,35 +184,49 @@ class SubPlugin(TdcPlugin):
devices as well)
'''
if self.args.namespace:
cmd = '$IP netns delete {}'.format(self.args.NAMES['NS'])
self._exec_cmd('post', cmd)
self._exec_cmd('post', self._ns_destroy_cmd())
self._ports_destroy()
@cached_property
def _proc(self):
ip = self._replace_keywords("$IP -b -")
proc = subprocess.Popen(ip,
shell=True,
stdin=subprocess.PIPE,
env=ENVIR)
return proc
def _proc_check(self):
proc = self._proc
proc.poll()
if proc.returncode is not None and proc.returncode != 0:
raise RuntimeError("iproute2 exited with an error code")
def _exec_cmd(self, stage, command):
'''
Perform any required modifications on an executable command, then run
it in a subprocess and return the results.
'''
if '$' in command:
command = self._replace_keywords(command)
self.adjust_command(stage, command)
if self.args.verbose:
if self.args.verbose > 3:
print('_exec_cmd: command "{}"'.format(command))
proc = subprocess.Popen(command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=ENVIR)
(rawout, serr) = proc.communicate()
if proc.returncode != 0 and len(serr) > 0:
foutput = serr.decode("utf-8")
else:
foutput = rawout.decode("utf-8")
proc = self._proc
proc.stdin.write((command + '\n').encode())
proc.stdin.flush()
if self.args.verbose > 3:
print('_exec_cmd proc: {}'.format(proc))
self._proc_check()
proc.stdout.close()
proc.stderr.close()
return proc, foutput
def _exec_cmd_batched(self, stage, commands):
for cmd in commands:
self._exec_cmd(stage, cmd)
def _replace_keywords(self, cmd):
"""
......
......@@ -10,9 +10,9 @@ class SubPlugin(TdcPlugin):
self.sub_class = 'root/SubPlugin'
super().__init__()
def pre_suite(self, testcount, testidlist):
def pre_suite(self, testcount, testlist):
# run commands before test_runner goes into a test loop
super().pre_suite(testcount, testidlist)
super().pre_suite(testcount, testlist)
if os.geteuid():
print('This script must be run with root privileges', file=sys.stderr)
......
......@@ -25,9 +25,10 @@ class SubPlugin(TdcPlugin):
self._tsr = TestSuiteReport()
super().__init__()
def pre_suite(self, testcount, testidlist):
def pre_suite(self, testcount, testist):
'''run commands before test_runner goes into a test loop'''
super().pre_suite(testcount, testidlist)
self.testidlist = [tidx['id'] for tidx in testlist]
super().pre_suite(testcount, testlist)
if self.args.verbose > 1:
print('{}.pre_suite'.format(self.sub_class))
if self.args.valgrind:
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment