Files
fission-src/pkg/utils/namespace_manager_test.go
T

384 lines
13 KiB
Go

package utils
import (
"context"
"errors"
"reflect"
"testing"
"time"
)
type testNamespaceSubscriber struct {
name string
addErr error
removeErr error
resyncErr error
addCalls int
removeCalls int
resyncCalls int
}
func (s *testNamespaceSubscriber) Name() string {
return s.name
}
func (s *testNamespaceSubscriber) OnNamespaceAdd(ctx context.Context, record NamespaceRecord) error {
s.addCalls++
return s.addErr
}
func (s *testNamespaceSubscriber) OnNamespaceRemove(ctx context.Context, record NamespaceRecord) error {
s.removeCalls++
return s.removeErr
}
func (s *testNamespaceSubscriber) OnNamespaceResync(ctx context.Context, record NamespaceRecord) error {
s.resyncCalls++
return s.resyncErr
}
func TestNamespaceManagerSnapshotAndGet(t *testing.T) {
manager := NewNamespaceManager()
now := time.Now().UTC()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-b", Source: NamespaceSourceWatcher, ObservedAt: now})
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher, ObservedAt: now})
expected := []string{"tenant-a", "tenant-b"}
if !reflect.DeepEqual(expected, manager.Snapshot()) {
t.Fatalf("expected snapshot %v, got %v", expected, manager.Snapshot())
}
record, ok := manager.Get("tenant-a")
if !ok {
t.Fatalf("expected tenant-a to exist")
}
if record.Name != "tenant-a" || record.Phase != NamespacePhaseDiscovered {
t.Fatalf("unexpected record returned: %+v", record)
}
}
func TestNamespaceManagerUpsertIncrementsGeneration(t *testing.T) {
manager := NewNamespaceManager()
now := time.Now().UTC()
first := manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher, ObservedAt: now})
second := manager.Upsert(NamespaceEvent{Type: NamespaceEventUpdate, Name: "tenant-a", Source: NamespaceSourceWatcher, ObservedAt: now.Add(time.Second)})
if first.Generation != 1 {
t.Fatalf("expected first generation to be 1, got %d", first.Generation)
}
if second.Generation != 2 {
t.Fatalf("expected second generation to be 2, got %d", second.Generation)
}
}
func TestNamespaceManagerMarkPartState(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
record, ok := manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateActive})
if !ok {
t.Fatalf("expected mark part state to succeed")
}
if record.RegisteredParts["router"].State != NamespacePartStateActive {
t.Fatalf("expected router part state to be stored")
}
if record.Phase != NamespacePhaseActive {
t.Fatalf("expected phase to become active, got %s", record.Phase)
}
}
func TestNamespaceManagerRemove(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
if !manager.Remove("tenant-a") {
t.Fatalf("expected remove to succeed")
}
if _, ok := manager.Get("tenant-a"); ok {
t.Fatalf("expected record to be removed")
}
if manager.Remove("tenant-a") {
t.Fatalf("expected second remove to report false")
}
}
func TestNamespaceManagerSnapshotRecordsReturnsCopies(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{
Type: NamespaceEventAdd,
Name: "tenant-a",
Source: NamespaceSourceWatcher,
Labels: map[string]string{"fission.io/managed": "true"},
})
_, _ = manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateActive})
records := manager.SnapshotRecords()
records[0].Labels["fission.io/managed"] = "false"
records[0].RegisteredParts["router"] = NamespacePartState{State: "failed"}
record, ok := manager.Get("tenant-a")
if !ok {
t.Fatalf("expected tenant-a to exist")
}
if record.Labels["fission.io/managed"] != "true" {
t.Fatalf("expected snapshot records to be detached copies")
}
if record.RegisteredParts["router"].State != NamespacePartStateActive {
t.Fatalf("expected part states to be detached copies")
}
}
func TestNamespaceManagerSubscribers(t *testing.T) {
manager := NewNamespaceManager()
manager.Subscribe(&testNamespaceSubscriber{name: "router"})
manager.Subscribe(&testNamespaceSubscriber{name: "buildermgr"})
manager.Subscribe(&testNamespaceSubscriber{name: "router"})
expected := []string{"buildermgr", "router"}
if !reflect.DeepEqual(expected, manager.SnapshotSubscribers()) {
t.Fatalf("expected subscribers %v, got %v", expected, manager.SnapshotSubscribers())
}
}
func TestNamespaceManagerMarkPartStateDerivesPhase(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
record, ok := manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateRegistering})
if !ok {
t.Fatalf("expected registering state update to succeed")
}
if record.Phase != NamespacePhaseRegistering {
t.Fatalf("expected phase registering, got %s", record.Phase)
}
record, ok = manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateFailed})
if !ok {
t.Fatalf("expected failed state update to succeed")
}
if record.Phase != NamespacePhaseFailed {
t.Fatalf("expected phase failed, got %s", record.Phase)
}
}
func TestNamespaceManagerBootstrap(t *testing.T) {
manager := NewNamespaceManager()
now := time.Now().UTC()
records := manager.Bootstrap([]string{"tenant-b", "tenant-a"}, NamespaceSourceBackfill, now)
if len(records) != 2 {
t.Fatalf("expected 2 bootstrap records, got %d", len(records))
}
if !reflect.DeepEqual([]string{"tenant-a", "tenant-b"}, manager.Snapshot()) {
t.Fatalf("expected manager snapshot to contain bootstrapped namespaces")
}
record, ok := manager.Get("tenant-a")
if !ok {
t.Fatalf("expected tenant-a after bootstrap")
}
if record.Source != NamespaceSourceBackfill {
t.Fatalf("expected bootstrap source backfill, got %s", record.Source)
}
}
func TestNamespaceManagerBootstrapAndDispatch(t *testing.T) {
manager := NewNamespaceManager()
router := &testNamespaceSubscriber{name: "router"}
builder := &testNamespaceSubscriber{name: "buildermgr"}
manager.Subscribe(router)
manager.Subscribe(builder)
records, err := manager.BootstrapAndDispatch(context.Background(), []string{"tenant-b", "tenant-a"}, NamespaceSourceBackfill, time.Now().UTC())
if err != nil {
t.Fatalf("expected bootstrap and dispatch success: %v", err)
}
if len(records) != 2 {
t.Fatalf("expected 2 records, got %d", len(records))
}
if router.addCalls != 2 || builder.addCalls != 2 {
t.Fatalf("expected both subscribers to be called for each namespace")
}
if records[0].Phase != NamespacePhaseActive || records[1].Phase != NamespacePhaseActive {
t.Fatalf("expected active records after bootstrap dispatch")
}
}
func TestNamespaceManagerBootstrapAndDispatchFailure(t *testing.T) {
manager := NewNamespaceManager()
router := &testNamespaceSubscriber{name: "router", addErr: errors.New("router failed")}
builder := &testNamespaceSubscriber{name: "buildermgr"}
manager.Subscribe(router)
manager.Subscribe(builder)
records, err := manager.BootstrapAndDispatch(context.Background(), []string{"tenant-a"}, NamespaceSourceBackfill, time.Now().UTC())
if err == nil {
t.Fatalf("expected bootstrap dispatch failure")
}
if len(records) != 1 {
t.Fatalf("expected single record result")
}
if records[0].Phase != NamespacePhaseFailed {
t.Fatalf("expected failed phase after subscriber error, got %s", records[0].Phase)
}
if records[0].RegisteredParts["router"].LastError != "router failed" {
t.Fatalf("expected router failure to be stored")
}
}
func TestNamespaceManagerPartStateHelpers(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
record, ok := manager.MarkPartRegistering("tenant-a", "router")
if !ok || record.Phase != NamespacePhaseRegistering {
t.Fatalf("expected registering helper to set registering phase")
}
record, ok = manager.MarkPartActive("tenant-a", "router")
if !ok || record.Phase != NamespacePhaseActive {
t.Fatalf("expected active helper to set active phase")
}
record, ok = manager.MarkPartFailed("tenant-a", "router", errors.New("boom"))
if !ok || record.Phase != NamespacePhaseFailed {
t.Fatalf("expected failed helper to set failed phase")
}
if record.RegisteredParts["router"].LastError != "boom" {
t.Fatalf("expected failed helper to persist error")
}
}
func TestNewBootstrappedNamespaceManager(t *testing.T) {
resolver := &NamespaceResolver{
FissionResourceNS: map[string]string{
"tenant-b": "tenant-b",
"tenant-a": "tenant-a",
},
}
manager := NewBootstrappedNamespaceManager(resolver, NamespaceSourceEnv, time.Now().UTC())
if !reflect.DeepEqual([]string{"tenant-a", "tenant-b"}, manager.Snapshot()) {
t.Fatalf("expected bootstrapped manager snapshot from resolver")
}
record, ok := manager.Get("tenant-a")
if !ok {
t.Fatalf("expected tenant-a in bootstrapped manager")
}
if record.Source != NamespaceSourceEnv {
t.Fatalf("expected env source, got %s", record.Source)
}
}
func TestNamespaceManagerDispatchAdd(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
router := &testNamespaceSubscriber{name: "router"}
builder := &testNamespaceSubscriber{name: "buildermgr"}
manager.Subscribe(router)
manager.Subscribe(builder)
record, ok, err := manager.DispatchAdd(context.Background(), "tenant-a")
if !ok || err != nil {
t.Fatalf("expected dispatch add success, ok=%v err=%v", ok, err)
}
if router.addCalls != 1 || builder.addCalls != 1 {
t.Fatalf("expected both subscribers to receive add call")
}
if record.Phase != NamespacePhaseActive {
t.Fatalf("expected active phase after successful dispatch, got %s", record.Phase)
}
}
func TestNamespaceManagerDispatchResyncFailure(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
router := &testNamespaceSubscriber{name: "router"}
builder := &testNamespaceSubscriber{name: "buildermgr", resyncErr: errors.New("resync failed")}
manager.Subscribe(router)
manager.Subscribe(builder)
record, ok, err := manager.DispatchResync(context.Background(), "tenant-a")
if !ok || err == nil {
t.Fatalf("expected dispatch resync failure, ok=%v err=%v", ok, err)
}
if builder.resyncCalls != 1 {
t.Fatalf("expected failing subscriber to receive resync call")
}
if record.Phase != NamespacePhaseFailed {
t.Fatalf("expected failed phase after dispatch error, got %s", record.Phase)
}
if record.RegisteredParts["buildermgr"].LastError != "resync failed" {
t.Fatalf("expected subscriber error to be stored")
}
}
func TestNamespaceManagerDispatchRemove(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
router := &testNamespaceSubscriber{name: "router"}
builder := &testNamespaceSubscriber{name: "buildermgr"}
manager.Subscribe(router)
manager.Subscribe(builder)
record, ok, err := manager.DispatchRemove(context.Background(), "tenant-a")
if !ok || err != nil {
t.Fatalf("expected dispatch remove success, ok=%v err=%v", ok, err)
}
if router.removeCalls != 1 || builder.removeCalls != 1 {
t.Fatalf("expected both subscribers to receive remove call")
}
if record.Phase != NamespacePhaseRemoved {
t.Fatalf("expected removed phase after dispatch remove, got %s", record.Phase)
}
}
func TestNamespaceManagerDispatchRemoveFailure(t *testing.T) {
manager := NewNamespaceManager()
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
router := &testNamespaceSubscriber{name: "router", removeErr: errors.New("remove failed")}
builder := &testNamespaceSubscriber{name: "buildermgr"}
manager.Subscribe(router)
manager.Subscribe(builder)
record, ok, err := manager.DispatchRemove(context.Background(), "tenant-a")
if !ok || err == nil {
t.Fatalf("expected dispatch remove failure, ok=%v err=%v", ok, err)
}
if record.Phase != NamespacePhaseRemoved {
t.Fatalf("expected removed phase even on dispatch remove error, got %s", record.Phase)
}
}
func TestNamespaceSubscriberFuncs(t *testing.T) {
addCalls := 0
removeCalls := 0
resyncCalls := 0
subscriber := NamespaceSubscriberFuncs{
SubscriberName: "router",
AddFunc: func(ctx context.Context, record NamespaceRecord) error {
addCalls++
return nil
},
RemoveFunc: func(ctx context.Context, record NamespaceRecord) error {
removeCalls++
return nil
},
ResyncFunc: func(ctx context.Context, record NamespaceRecord) error {
resyncCalls++
return nil
},
}
if subscriber.Name() != "router" {
t.Fatalf("expected subscriber name router")
}
_ = subscriber.OnNamespaceAdd(context.Background(), NamespaceRecord{Name: "tenant-a"})
_ = subscriber.OnNamespaceRemove(context.Background(), NamespaceRecord{Name: "tenant-a"})
_ = subscriber.OnNamespaceResync(context.Background(), NamespaceRecord{Name: "tenant-a"})
if addCalls != 1 || removeCalls != 1 || resyncCalls != 1 {
t.Fatalf("expected all functional callbacks to run once")
}
}