From 66a3dc2a3c63bce9c99c7a480c55da58cd0fbc43 Mon Sep 17 00:00:00 2001 From: Naeel Date: Sun, 26 Apr 2026 10:02:03 +0300 Subject: [PATCH] layer1: derive namespace phases step 10 --- .../2026-04-26-namespace-manager-step10.md | 22 +++++++++++++ pkg/utils/namespace_manager.go | 27 ++++++++++++++++ pkg/utils/namespace_manager_model.go | 6 ++++ pkg/utils/namespace_manager_test.go | 32 ++++++++++++++++--- 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 doc/thinking/2026-04-26-namespace-manager-step10.md diff --git a/doc/thinking/2026-04-26-namespace-manager-step10.md b/doc/thinking/2026-04-26-namespace-manager-step10.md new file mode 100644 index 00000000..46c23e46 --- /dev/null +++ b/doc/thinking/2026-04-26-namespace-manager-step10.md @@ -0,0 +1,22 @@ +# 2026-04-26 — NamespaceManager rewrite, step 10 + +## Цель шага + +Научить skeleton manager выводить общую phase namespace-а из part states. + +## Что меняем + +1. Добавляем константы состояний частей: + - `registering` + - `active` + - `failed` +2. После `MarkPartState()` manager пересчитывает общую phase namespace-а. +3. Добавляем unit tests на переходы: + - registering -> active + - failed -> NamespacePhaseFailed + +## Что НЕ меняем + +- не запускаем реальный reconcile loop; +- не вызываем subscriber-ов автоматически; +- не подключаем manager к runtime. \ No newline at end of file diff --git a/pkg/utils/namespace_manager.go b/pkg/utils/namespace_manager.go index 489c948f..d67e2bce 100644 --- a/pkg/utils/namespace_manager.go +++ b/pkg/utils/namespace_manager.go @@ -152,6 +152,7 @@ func (m *inMemoryNamespaceManager) MarkPartState(namespace string, part string, state.UpdatedAt = time.Now().UTC() } record.RegisteredParts[part] = state + record.Phase = deriveNamespacePhase(record) record.UpdatedAt = state.UpdatedAt m.records[namespace] = record return record.Clone(), true @@ -166,4 +167,30 @@ func (m *inMemoryNamespaceManager) Remove(name string) bool { } delete(m.records, name) return true +} + +func deriveNamespacePhase(record NamespaceRecord) NamespacePhase { + if len(record.RegisteredParts) == 0 { + return record.Phase + } + + hasRegistering := false + for _, part := range record.RegisteredParts { + switch part.State { + case NamespacePartStateFailed: + return NamespacePhaseFailed + case NamespacePartStateRegistering: + hasRegistering = true + case NamespacePartStateActive: + continue + default: + hasRegistering = true + } + } + + if hasRegistering { + return NamespacePhaseRegistering + } + + return NamespacePhaseActive } \ No newline at end of file diff --git a/pkg/utils/namespace_manager_model.go b/pkg/utils/namespace_manager_model.go index b610a08a..25cf0bd3 100644 --- a/pkg/utils/namespace_manager_model.go +++ b/pkg/utils/namespace_manager_model.go @@ -2,6 +2,12 @@ package utils import "time" +const ( + NamespacePartStateRegistering string = "registering" + NamespacePartStateActive string = "active" + NamespacePartStateFailed string = "failed" +) + type NamespacePhase string const ( diff --git a/pkg/utils/namespace_manager_test.go b/pkg/utils/namespace_manager_test.go index 47db9c54..bd252bd7 100644 --- a/pkg/utils/namespace_manager_test.go +++ b/pkg/utils/namespace_manager_test.go @@ -53,13 +53,16 @@ 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: "active"}) + 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 != "active" { + 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) { @@ -85,7 +88,7 @@ func TestNamespaceManagerSnapshotRecordsReturnsCopies(t *testing.T) { Source: NamespaceSourceWatcher, Labels: map[string]string{"fission.io/managed": "true"}, }) - _, _ = manager.MarkPartState("tenant-a", "router", NamespacePartState{State: "active"}) + _, _ = manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateActive}) records := manager.SnapshotRecords() records[0].Labels["fission.io/managed"] = "false" @@ -98,7 +101,7 @@ func TestNamespaceManagerSnapshotRecordsReturnsCopies(t *testing.T) { if record.Labels["fission.io/managed"] != "true" { t.Fatalf("expected snapshot records to be detached copies") } - if record.RegisteredParts["router"].State != "active" { + if record.RegisteredParts["router"].State != NamespacePartStateActive { t.Fatalf("expected part states to be detached copies") } } @@ -113,4 +116,25 @@ func TestNamespaceManagerSubscribers(t *testing.T) { 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) + } } \ No newline at end of file