From 42acce308f2ab8639c86373fa5f65d2bfcc6c747 Mon Sep 17 00:00:00 2001 From: Naeel Date: Sun, 26 Apr 2026 10:02:37 +0300 Subject: [PATCH] layer1: add namespace bootstrap step 11 --- .../2026-04-26-namespace-manager-step11.md | 18 +++++++++++++++++ pkg/utils/namespace_manager.go | 17 ++++++++++++++++ pkg/utils/namespace_manager_test.go | 20 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 doc/thinking/2026-04-26-namespace-manager-step11.md diff --git a/doc/thinking/2026-04-26-namespace-manager-step11.md b/doc/thinking/2026-04-26-namespace-manager-step11.md new file mode 100644 index 00000000..f2ecbdf8 --- /dev/null +++ b/doc/thinking/2026-04-26-namespace-manager-step11.md @@ -0,0 +1,18 @@ +# 2026-04-26 — NamespaceManager rewrite, step 11 + +## Цель шага + +Добавить bootstrap helper для массовой загрузки initial namespace set в manager. + +## Что меняем + +1. Добавляем `Bootstrap()` в manager interface и реализацию. +2. Метод принимает список namespace-ов и `NamespaceSource`. +3. Метод прогоняет namespaces через `Upsert()` как initial discovered set. +4. Добавляем unit tests на bootstrap. + +## Что НЕ меняем + +- не подключаем bootstrap к runtime startup path; +- не меняем watcher-ы; +- не трогаем resolver/SA/runtime. \ No newline at end of file diff --git a/pkg/utils/namespace_manager.go b/pkg/utils/namespace_manager.go index d67e2bce..a024cd4f 100644 --- a/pkg/utils/namespace_manager.go +++ b/pkg/utils/namespace_manager.go @@ -14,6 +14,7 @@ type NamespaceManager interface { Snapshot() []string SnapshotRecords() []NamespaceRecord Get(name string) (NamespaceRecord, bool) + Bootstrap(namespaces []string, source NamespaceSource, observedAt time.Time) []NamespaceRecord Subscribe(subscriber NamespaceSubscriber) SnapshotSubscribers() []string Upsert(event NamespaceEvent) NamespaceRecord @@ -40,6 +41,22 @@ func (m *inMemoryNamespaceManager) Subscribe(subscriber NamespaceSubscriber) { m.subs[subscriber.Name()] = subscriber } +func (m *inMemoryNamespaceManager) Bootstrap(namespaces []string, source NamespaceSource, observedAt time.Time) []NamespaceRecord { + records := make([]NamespaceRecord, 0, len(namespaces)) + for _, namespace := range namespaces { + records = append(records, m.Upsert(NamespaceEvent{ + Type: NamespaceEventAdd, + Name: namespace, + Source: source, + ObservedAt: observedAt, + })) + } + sort.Slice(records, func(i, j int) bool { + return records[i].Name < records[j].Name + }) + return records +} + func (m *inMemoryNamespaceManager) SnapshotSubscribers() []string { m.mu.RLock() defer m.mu.RUnlock() diff --git a/pkg/utils/namespace_manager_test.go b/pkg/utils/namespace_manager_test.go index bd252bd7..1efa2191 100644 --- a/pkg/utils/namespace_manager_test.go +++ b/pkg/utils/namespace_manager_test.go @@ -137,4 +137,24 @@ func TestNamespaceManagerMarkPartStateDerivesPhase(t *testing.T) { 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) + } } \ No newline at end of file