metropolis/test/e2e: add self-test image for networking

We don't have any networking tests in our E2E tests. This adds an image
which de-facto implements one. Or at least, will implement one once we
move to split workers/controllers and contacting a Kubernetes apiserver
from a pod will mean we're actually testing cross-node traffic.

Change-Id: I3d7be3824ac041d72e1c19cd468d30dbcb71fa03
Reviewed-on: https://review.monogon.dev/c/monogon/+/1481
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/test/util/runners.go b/metropolis/test/util/runners.go
index a2b1663..e25fe10 100644
--- a/metropolis/test/util/runners.go
+++ b/metropolis/test/util/runners.go
@@ -5,6 +5,7 @@
 import (
 	"context"
 	"errors"
+	"fmt"
 	"testing"
 	"time"
 
@@ -31,6 +32,9 @@
 			if err == ctx.Err() {
 				t.Fatal(lastErr)
 			}
+			if errors.Is(err, &PermanentError{}) {
+				t.Fatal(err)
+			}
 			lastErr = err
 			select {
 			case <-ctx.Done():
@@ -40,3 +44,37 @@
 		}
 	})
 }
+
+// PermanentError can be returned inside TestEventual to indicate that the test
+// is 'stuck', that it will not make progress anymore and that it should be
+// failed immediately.
+type PermanentError struct {
+	Err error
+}
+
+func (p *PermanentError) Error() string {
+	return fmt.Sprintf("test permanently failed: %v", p.Err)
+}
+
+func (p *PermanentError) Unwrap() error {
+	return p.Err
+}
+
+func (p *PermanentError) Is(o error) bool {
+	op, ok := o.(*PermanentError)
+	if !ok {
+		return false
+	}
+	if p.Err == nil || op.Err == nil {
+		return true
+	}
+	return errors.Is(p.Err, op.Err)
+}
+
+// Permanent wraps the given error into a PermanentError, which will cause
+// TestEventual to immediately fail the test it's returned within.
+func Permanent(err error) error {
+	return &PermanentError{
+		Err: err,
+	}
+}