mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
243 lines
8.2 KiB
Go
243 lines
8.2 KiB
Go
//go:build linux
|
|
|
|
package scenario
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"net/http"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/client"
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/dashboard"
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/fixture"
|
|
)
|
|
|
|
func TestHeldNATSessionRejectsInvalidInput(t *testing.T) {
|
|
plan := heldNATTestPlan(t)
|
|
patClient, err := client.New(client.Config{BaseURL: "http://127.0.0.1"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = newHeldNATSession(context.Background(), heldNATInput{PATClient: patClient, Plan: plan})
|
|
if !errors.Is(err, ErrInvalidHeldNATSession) {
|
|
t.Fatalf("error=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestHeldNATSessionRejectsNilPATBeforeRemoteMutation(t *testing.T) {
|
|
plan := heldNATTestPlan(t)
|
|
|
|
_, err := newHeldNATSession(context.Background(), heldNATInput{Plan: plan})
|
|
|
|
if !errors.Is(err, ErrInvalidHeldPATClient) {
|
|
t.Fatalf("error=%v, want ErrInvalidHeldPATClient before other validation", err)
|
|
}
|
|
}
|
|
|
|
func TestHeldNATSessionCloseBeforeLiveRetainsLifecycleError(t *testing.T) {
|
|
session := newTestHeldNATSession(t)
|
|
if err := session.Close(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := session.WaitLive(context.Background()); !errors.Is(err, ErrHeldSessionClosedBeforeLive) {
|
|
t.Fatalf("WaitLive=%v", err)
|
|
}
|
|
if err := session.WaitClosed(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestHeldNATSessionCanceledWaiterDoesNotCancelOwner(t *testing.T) {
|
|
session := newTestHeldNATSession(t)
|
|
if err := session.lifecycle.markLive(nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
canceled, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if err := session.Close(canceled); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("Close=%v", err)
|
|
}
|
|
if err := session.WaitClosed(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestHeldNATProofRejectsSensitiveHeaders(t *testing.T) {
|
|
observed := fixture.NATEchoRecord{Method: http.MethodPatch, Path: "/held/held-nat", Host: "held-nat.agentcompat-nat.invalid", HeaderValue: "held-nat", Body: []byte("held-body-held-nat"), SensitiveHeadersPresent: true}
|
|
|
|
err := proveHeldNATRequest(observed, observed.Host, "held-nat")
|
|
|
|
if err == nil {
|
|
t.Fatal("proof accepted sensitive backend headers")
|
|
}
|
|
}
|
|
|
|
func TestHeldNATInputRejectsNilPATBeforeReadinessOrPlan(t *testing.T) {
|
|
_, err := newHeldNATSession(context.Background(), heldNATInput{PATClient: nil})
|
|
|
|
if !errors.Is(err, ErrInvalidHeldPATClient) {
|
|
t.Fatalf("error=%v, want PAT validation before readiness and plan validation", err)
|
|
}
|
|
}
|
|
|
|
func TestHeldNATCleanupOrderIsLIFOForRequiredResources(t *testing.T) {
|
|
stack := newHeldCleanupStack()
|
|
var order []string
|
|
for _, name := range []string{"baseline", "backend", "profile", "unregister", "absence", "cancel", "request"} {
|
|
name := name
|
|
if err := stack.Push(heldCleanupAction{name: name, cleanup: func(context.Context) error {
|
|
order = append(order, name)
|
|
return nil
|
|
}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if err := stack.Run(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []string{"request", "cancel", "absence", "unregister", "profile", "backend", "baseline"}
|
|
if !reflect.DeepEqual(order, want) {
|
|
t.Fatalf("cleanup order=%v, want %v", order, want)
|
|
}
|
|
}
|
|
|
|
func TestHeldNATRequestCloseRetainsConnectionError(t *testing.T) {
|
|
closeFailure := errors.New("request connection close failed")
|
|
request := &heldNATRequest{connection: closeErrorConn{err: closeFailure}, result: make(chan error, 1)}
|
|
|
|
if err := request.close(); !errors.Is(err, closeFailure) {
|
|
t.Fatalf("first close error=%v, want %v", err, closeFailure)
|
|
}
|
|
if err := request.close(); !errors.Is(err, closeFailure) {
|
|
t.Fatalf("repeated close error=%v, want %v", err, closeFailure)
|
|
}
|
|
}
|
|
|
|
func TestHeldNATSessionConcurrentCloseRetainsOneCleanupResult(t *testing.T) {
|
|
session := newTestHeldNATSession(t)
|
|
cleanupFailure := errors.New("NAT cleanup failed")
|
|
if err := session.cleanup.Push(heldCleanupAction{name: "request", cleanup: func(context.Context) error { return cleanupFailure }}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := session.lifecycle.markLive(nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const callers = 8
|
|
errorsSeen := make(chan error, callers)
|
|
var group sync.WaitGroup
|
|
group.Add(callers)
|
|
for range callers {
|
|
go func() {
|
|
defer group.Done()
|
|
errorsSeen <- session.Close(context.Background())
|
|
}()
|
|
}
|
|
group.Wait()
|
|
for range callers {
|
|
if err := <-errorsSeen; !errors.Is(err, cleanupFailure) {
|
|
t.Fatalf("Close error=%v, want %v", err, cleanupFailure)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHeldNATRollbackJoinsOriginalAndCleanupFailures(t *testing.T) {
|
|
session := newTestHeldNATSession(t)
|
|
original := errors.New("constructor failure")
|
|
rollbackFailure := errors.New("rollback failure")
|
|
if err := session.cleanup.Push(heldCleanupAction{name: "backend", cleanup: func(context.Context) error { return rollbackFailure }}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err := rollbackHeldNAT(session, original)
|
|
|
|
if !errors.Is(err, original) || !errors.Is(err, rollbackFailure) {
|
|
t.Fatalf("joined error=%v, want original and rollback failures", err)
|
|
}
|
|
}
|
|
|
|
func TestHeldNATConstructorRegistrationFailureRollsBackProfileBeforeBackend(t *testing.T) {
|
|
const profileID = uint64(77)
|
|
registrationFailure := errors.New("capability registration failed")
|
|
profileDeleteFailure := errors.New("profile deletion failed")
|
|
var cleanupOrder []string
|
|
|
|
agentInstance := newHeldReadinessTestAgent(t, "00000000-0000-0000-0000-000000000401")
|
|
plan := heldNATTestPlan(t)
|
|
plan.ID, _ = NewStressSessionID("constructor-rollback")
|
|
input := heldNATInput{
|
|
Dashboard: &dashboard.Dashboard{},
|
|
PATClient: &client.Client{},
|
|
Agent: agentInstance,
|
|
Readiness: completeHeldReadiness(agentInstance.UUID()),
|
|
Plan: plan,
|
|
}
|
|
dependencies := defaultHeldNATDependencies()
|
|
dependencies.snapshotState = func(context.Context, *client.Client) (client.IOStreamState, error) {
|
|
return client.IOStreamState{}, nil
|
|
}
|
|
dependencies.createProfile = func(context.Context, *dashboard.Dashboard, *fixture.NATHoldBackend, uint64, string, string) (uint64, error) {
|
|
return profileID, nil
|
|
}
|
|
dependencies.deleteProfile = func(context.Context, *dashboard.Dashboard, uint64) error {
|
|
cleanupOrder = append(cleanupOrder, "profile")
|
|
return profileDeleteFailure
|
|
}
|
|
dependencies.register = func(context.Context, *client.Client, heldIOStreamCapabilityIdentity) (*heldIOStreamCapability, error) {
|
|
return nil, registrationFailure
|
|
}
|
|
|
|
_, err := newHeldNATSessionWithDependencies(context.Background(), input, dependencies)
|
|
|
|
if !errors.Is(err, registrationFailure) || !errors.Is(err, profileDeleteFailure) {
|
|
t.Fatalf("constructor error=%v, want registration and profile cleanup failures", err)
|
|
}
|
|
if !reflect.DeepEqual(cleanupOrder, []string{"profile"}) {
|
|
t.Fatalf("cleanup order=%v, want profile before backend cleanup", cleanupOrder)
|
|
}
|
|
}
|
|
|
|
type closeErrorConn struct{ err error }
|
|
|
|
func (connection closeErrorConn) Read([]byte) (int, error) { return 0, net.ErrClosed }
|
|
func (connection closeErrorConn) Write([]byte) (int, error) { return 0, net.ErrClosed }
|
|
func (connection closeErrorConn) Close() error { return connection.err }
|
|
func (connection closeErrorConn) LocalAddr() net.Addr { return heldNATTestAddr{} }
|
|
func (connection closeErrorConn) RemoteAddr() net.Addr { return heldNATTestAddr{} }
|
|
func (connection closeErrorConn) SetDeadline(time.Time) error { return nil }
|
|
func (connection closeErrorConn) SetReadDeadline(time.Time) error { return nil }
|
|
func (connection closeErrorConn) SetWriteDeadline(time.Time) error { return nil }
|
|
|
|
type heldNATTestAddr struct{}
|
|
|
|
func (heldNATTestAddr) Network() string { return "held-nat-test" }
|
|
func (heldNATTestAddr) String() string { return "held-nat-test" }
|
|
|
|
func heldNATTestPlan(t *testing.T) StressSessionPlan {
|
|
t.Helper()
|
|
id, err := NewStressSessionID("held-nat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
agent, err := NewStressAgentOrdinal(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return StressSessionPlan{ID: id, Kind: StressSessionNAT, Ordinal: 1, Agent: agent}
|
|
}
|
|
|
|
func newTestHeldNATSession(t *testing.T) *heldNATSession {
|
|
t.Helper()
|
|
lifecycle, err := newHeldSessionLifecycle(context.Background(), heldNATTestPlan(t), "", time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return &heldNATSession{lifecycle: lifecycle, cleanup: newHeldCleanupStack()}
|
|
}
|