Ctrl K
ComunidadGuías…

iperfhint_container_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/pingular/pingularity/internal/settings"
	"github.com/pingular/pingularity/internal/store"
)

// iperfContainerHint is the container-networking matcher main.go injects into
// the iperf3 engine (Iperf.EnvHint) in a BRIDGED container (see iperfEnvHintFn).
// Each failure class from the docker-vs-native analysis must map to a hint
// naming the fix, and anything else must map to NO hint - the matcher may only
// explain, never guess. It is a pure function over the error text and the run's
// server/bind/family settings, so the classes are pinned here without a
// container.
func TestIperfHintContainerClasses(t *testing.T) {
	cases := []struct {
		name, errText, server, bind, ipver string
		want                               string // required substring; "" = exactly no hint
	}{
		// I1: loopback server target - the container has its own localhost.
		{"refused v4 loopback", "download: unable to connect to server: Connection refused", "127.0.0.1:5201", "", "auto", "host.docker.internal"},
		{"refused localhost", "connect failed: Connection refused", "localhost", "", "auto", "host.docker.internal"},
		{"refused bracketed v6 loopback", "iperf3: error - unable to connect to server: Connection refused", "[::1]:5201", "", "auto", "host.docker.internal"},
		// A refused NON-loopback server is a plain dead server, not a container issue.
		{"refused non-loopback stays bare", "unable to connect to server: Connection refused", "192.168.1.50:5201", "", "auto", ""},
		// I3: --bind with a host IP fails EADDRNOTAVAIL inside the namespace.
		{"bind EADDRNOTAVAIL", "iperf3: error - unable to connect to server: Cannot assign requested address", "10.0.0.2:5201", "192.168.1.23", "auto", `bind address "192.168.1.23"`},
		// I4: --bind-dev with a host NIC name fails ENODEV inside the namespace.
		{"bind-dev ENODEV", "iperf3: error - unable to connect to server: No such device", "10.0.0.2:5201", "eno1", "auto", `host interface "eno1"`},
		{"bind-dev bad interface", "unable to connect to server: Bad interface name", "10.0.0.2:5201", "eno1", "auto", `host interface "eno1"`},
		// I5: a forced -6 on the IPv6-less default bridge.
		{"forced v6 unreachable", "upload: unable to connect to server: Network unreachable", "iperf.example.com:5201", "", "6", "no IPv6"},
		{"forced v6 cannot assign", "unable to connect to server: Cannot assign requested address", "iperf.example.com:5201", "", "6", "no IPv6"},
		// An explicit bind is the likelier direct cause than the -6 pin.
		{"bind precedes v6 on cannot-assign", "Cannot assign requested address", "iperf.example.com:5201", "192.168.1.23", "6", `bind address "192.168.1.23"`},
		// cannot-assign with neither a bind nor a -6 pin matches no class.
		{"cannot assign, no bind, auto family", "Cannot assign requested address", "10.0.0.2:5201", "", "auto", ""},
		// Unrelated failures map to none, whatever the settings look like.
		{"unrelated auth", "test authorization failed", "127.0.0.1:5201", "", "auto", ""},
		{"unrelated busy", "the server is busy running a test. Try again later.", "localhost", "", "auto", ""},
		{"unrelated no-data with container-y settings", "no data transferred", "10.0.0.2:5201", "192.168.1.23", "6", ""},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got := iperfContainerHint(c.errText, c.server, c.bind, c.ipver)
			if c.want == "" {
				if got != "" {
					t.Fatalf("hint = %q, want none", got)
				}
				return
			}
			if !strings.Contains(got, c.want) {
				t.Fatalf("hint = %q, want substring %q", got, c.want)
			}
		})
	}
}

// The matcher's injection gate keys on BRIDGED (util.BridgedContainer), not
// merely containerized: in a host-network container localhost IS the host and
// host NICs exist in our namespace, so every bridged hint - host.docker.internal
// for a refused loopback server above all - would send the operator away from a
// server plain 127.0.0.1 does reach. iperfEnvHintFn is that gate as a named
// function, so the key is pinned here instead of buried in run()'s wiring.
func TestIperfEnvHintGateKeysOnBridged(t *testing.T) {
	ctx := context.Background()
	st, err := store.Open(":memory:")
	if err != nil {
		t.Fatalf("open store: %v", err)
	}
	t.Cleanup(func() { st.Close() })
	set, err := settings.New(ctx, st, settings.Values{
		Latency: 5 * time.Second, Speed: time.Hour, Timeout: 2 * time.Second,
		DownAfter: 3, UpAfter: 2, IperfServer: "127.0.0.1:5201",
	})
	if err != nil {
		t.Fatalf("new settings: %v", err)
	}

	// Host-network container (and native): no matcher at all - the engine's
	// nil-check keeps errors bare, exactly as they should read there.
	if fn := iperfEnvHintFn(false, set); fn != nil {
		t.Fatal("non-bridged got an EnvHint matcher; host-network hints would be wrong (localhost IS the host)")
	}

	// Bridged: the matcher fires on the loopback-server class using the live
	// settings (the configured server feeds the match).
	fn := iperfEnvHintFn(true, set)
	if fn == nil {
		t.Fatal("bridged container got no EnvHint matcher")
	}
	if h := fn("unable to connect to server: Connection refused"); !strings.Contains(h, "host.docker.internal") {
		t.Fatalf("bridged loopback-refused hint = %q, want host.docker.internal", h)
	}
}