Add option WithSetNsHandles for testutil SetupTestOSContextEx

Allow tests to run in parallel with separate network namespaces,
without modifying the global-state namespace/netlink handles in
the "ns" package ... only useful for tests that don't depend on
package "ns".

Use the new option in iptabler/nftabler tests.

Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
Rob Murray
2025-09-22 10:19:42 +01:00
parent 362d4d9538
commit de5e64b3bd
3 changed files with 42 additions and 15 deletions

View File

@@ -149,7 +149,7 @@ func TestIptabler(t *testing.T) {
}
func testIptabler(t *testing.T, tn string, config firewaller.Config, netConfig firewaller.NetworkConfig, bindLocalhost bool, resName string) {
defer netnsutils.SetupTestOSContext(t)()
defer netnsutils.SetupTestOSContext(t, netnsutils.WithSetNsHandles(false))()
stripComments := func(text string) string {
lines := strings.Split(text, "\n")

View File

@@ -91,7 +91,7 @@ func TestNftabler(t *testing.T) {
}
func testNftabler(t *testing.T, tn string, config firewaller.Config, netConfig firewaller.NetworkConfig, bindLocalhost bool, resName string) {
defer netnsutils.SetupTestOSContext(t)()
defer netnsutils.SetupTestOSContext(t, netnsutils.WithSetNsHandles(false))()
checkResults := func(family, name string, en bool) {
t.Helper()

View File

@@ -25,8 +25,22 @@ var osContextLock sync.Mutex
type OSContext struct {
origNS, newNS netns.NsHandle
tid int
caller string // The file:line where SetupTestOSContextEx was called, for interpolating into error messages.
tid int
caller string // The file:line where SetupTestOSContextEx was called, for interpolating into error messages.
setNsHandles bool // See [WithSetNsHandles].
}
// WithSetNsHandles is an option for [SetupTestOSContext]/[SetupTestOSContextEx],
// value false prevents reset of the namespace and netlink handles in package ns.
// This allows multiple OSContext instances to be active at the same time,
// but means code that relies on package ns, including [ns.NlHandle], will
// not work correctly. So, it's useful for tests that want clean network
// namespaces for parallel subtests, as long as they don't use package ns.
// The default is true.
func WithSetNsHandles(p bool) func(*OSContext) {
return func(c *OSContext) {
c.setNsHandles = p
}
}
// SetupTestOSContext joins the current goroutine to a new network namespace,
@@ -35,8 +49,8 @@ type OSContext struct {
// Example usage:
//
// defer SetupTestOSContext(t)()
func SetupTestOSContext(t *testing.T) func() {
c := SetupTestOSContextEx(t)
func SetupTestOSContext(t *testing.T, opts ...func(*OSContext)) func() {
c := SetupTestOSContextEx(t, opts...)
return func() { c.Cleanup(t) }
}
@@ -50,8 +64,19 @@ func SetupTestOSContext(t *testing.T) func() {
//
// c := SetupTestOSContext(t)
// defer c.Cleanup(t)
func SetupTestOSContextEx(t *testing.T) *OSContext {
osContextLock.Lock()
func SetupTestOSContextEx(t *testing.T, opts ...func(*OSContext)) *OSContext {
c := OSContext{
setNsHandles: true,
}
for _, o := range opts {
o(&c)
}
// If the namespace/netlink handles in package ns are to be reset, make sure
// only one OSContext is active at a time.
if c.setNsHandles {
osContextLock.Lock()
}
runtime.LockOSThread()
origNS, err := netns.Get()
if err != nil {
@@ -59,10 +84,8 @@ func SetupTestOSContextEx(t *testing.T) *OSContext {
t.Fatalf("Failed to open initial netns: %v", err)
}
c := OSContext{
tid: unix.Gettid(),
origNS: origNS,
}
c.tid = unix.Gettid()
c.origNS = origNS
c.newNS, err = netns.New()
if err != nil {
// netns.New() is not atomic: it could have encountered an error
@@ -73,7 +96,9 @@ func SetupTestOSContextEx(t *testing.T) *OSContext {
// Since we are switching to a new test namespace make
// sure to re-initialize initNs context
ns.ResetHandles()
if c.setNsHandles {
ns.ResetHandles()
}
nl := ns.NlHandle()
lo, err := nl.LinkByName("lo")
@@ -108,8 +133,10 @@ func (c *OSContext) Cleanup(t *testing.T) {
t.Logf("Warning: netns closing failed (%v)", err)
}
c.restore(t)
ns.ResetHandles()
osContextLock.Unlock()
if c.setNsHandles {
ns.ResetHandles()
osContextLock.Unlock()
}
}
func (c *OSContext) restore(t *testing.T) {