layer1: add namespace manager model step 7
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
# 2026-04-26 — NamespaceManager rewrite, step 7
|
||||||
|
|
||||||
|
## Цель шага
|
||||||
|
|
||||||
|
Добавить минимальную модель данных для будущего `NamespaceManager`, не меняя пока production wiring.
|
||||||
|
|
||||||
|
## Почему это отдельный шаг
|
||||||
|
|
||||||
|
После шагов 1-6 уже стало ясно, что следующая стадия — не ещё один patch по месту, а переход к явной модели lifecycle.
|
||||||
|
|
||||||
|
Но сразу подключать новый manager к watcher-ам и компонентам рано. Сначала нужна опорная модель:
|
||||||
|
|
||||||
|
- `NamespacePhase`
|
||||||
|
- `NamespaceSource`
|
||||||
|
- `NamespaceEventType`
|
||||||
|
- `NamespaceRecord`
|
||||||
|
- `NamespacePartState`
|
||||||
|
|
||||||
|
## Что меняем
|
||||||
|
|
||||||
|
1. Добавляем новый файл с типами model layer.
|
||||||
|
2. Добавляем helper-методы:
|
||||||
|
- `Clone()`
|
||||||
|
- `IsActive()`
|
||||||
|
- `IsTerminal()`
|
||||||
|
3. Добавляем unit tests на:
|
||||||
|
- корректный deep copy;
|
||||||
|
- active semantics;
|
||||||
|
- terminal semantics.
|
||||||
|
|
||||||
|
## Что НЕ меняем
|
||||||
|
|
||||||
|
- не подключаем manager к production path;
|
||||||
|
- не меняем watcher-ы;
|
||||||
|
- не меняем resolver;
|
||||||
|
- не затрагиваем текущее изменение в `serviceaccount.go`.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type NamespacePhase string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NamespacePhaseDiscovered NamespacePhase = "discovered"
|
||||||
|
NamespacePhaseRegistering NamespacePhase = "registering"
|
||||||
|
NamespacePhaseActive NamespacePhase = "active"
|
||||||
|
NamespacePhaseDeregistering NamespacePhase = "deregistering"
|
||||||
|
NamespacePhaseRemoved NamespacePhase = "removed"
|
||||||
|
NamespacePhaseFailed NamespacePhase = "failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamespaceSource string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NamespaceSourceEnv NamespaceSource = "env"
|
||||||
|
NamespaceSourceWatcher NamespaceSource = "watcher"
|
||||||
|
NamespaceSourceBackfill NamespaceSource = "backfill"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamespaceEventType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NamespaceEventAdd NamespaceEventType = "add"
|
||||||
|
NamespaceEventUpdate NamespaceEventType = "update"
|
||||||
|
NamespaceEventRemove NamespaceEventType = "remove"
|
||||||
|
NamespaceEventResync NamespaceEventType = "resync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamespacePartState struct {
|
||||||
|
State string
|
||||||
|
LastError string
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamespaceRecord struct {
|
||||||
|
Name string
|
||||||
|
Source NamespaceSource
|
||||||
|
Labels map[string]string
|
||||||
|
Phase NamespacePhase
|
||||||
|
LastError string
|
||||||
|
Generation int64
|
||||||
|
UpdatedAt time.Time
|
||||||
|
RegisteredParts map[string]NamespacePartState
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamespaceEvent struct {
|
||||||
|
Type NamespaceEventType
|
||||||
|
Name string
|
||||||
|
Labels map[string]string
|
||||||
|
Source NamespaceSource
|
||||||
|
ObservedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr NamespaceRecord) Clone() NamespaceRecord {
|
||||||
|
clone := nr
|
||||||
|
clone.Labels = cloneStringMap(nr.Labels)
|
||||||
|
clone.RegisteredParts = cloneNamespacePartStates(nr.RegisteredParts)
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr NamespaceRecord) IsActive() bool {
|
||||||
|
return nr.Phase == NamespacePhaseActive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr NamespaceRecord) IsTerminal() bool {
|
||||||
|
return nr.Phase == NamespacePhaseRemoved || nr.Phase == NamespacePhaseFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneStringMap(input map[string]string) map[string]string {
|
||||||
|
if input == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clone := make(map[string]string, len(input))
|
||||||
|
for key, value := range input {
|
||||||
|
clone[key] = value
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneNamespacePartStates(input map[string]NamespacePartState) map[string]NamespacePartState {
|
||||||
|
if input == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clone := make(map[string]NamespacePartState, len(input))
|
||||||
|
for key, value := range input {
|
||||||
|
clone[key] = value
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNamespaceRecordClone(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
record := NamespaceRecord{
|
||||||
|
Name: "tenant-a",
|
||||||
|
Source: NamespaceSourceWatcher,
|
||||||
|
Phase: NamespacePhaseRegistering,
|
||||||
|
Generation: 7,
|
||||||
|
UpdatedAt: now,
|
||||||
|
Labels: map[string]string{
|
||||||
|
"fission.io/managed": "true",
|
||||||
|
},
|
||||||
|
RegisteredParts: map[string]NamespacePartState{
|
||||||
|
"router": {
|
||||||
|
State: "active",
|
||||||
|
UpdatedAt: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
clone := record.Clone()
|
||||||
|
clone.Labels["fission.io/managed"] = "false"
|
||||||
|
clone.RegisteredParts["router"] = NamespacePartState{State: "failed", LastError: "boom"}
|
||||||
|
|
||||||
|
if record.Labels["fission.io/managed"] != "true" {
|
||||||
|
t.Fatalf("expected labels to be deep copied")
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.RegisteredParts["router"].State != "active" {
|
||||||
|
t.Fatalf("expected registered parts to be deep copied")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamespaceRecordIsActive(t *testing.T) {
|
||||||
|
record := NamespaceRecord{Phase: NamespacePhaseActive}
|
||||||
|
if !record.IsActive() {
|
||||||
|
t.Fatalf("expected active phase to be reported as active")
|
||||||
|
}
|
||||||
|
|
||||||
|
record.Phase = NamespacePhaseRegistering
|
||||||
|
if record.IsActive() {
|
||||||
|
t.Fatalf("expected non-active phase to be reported as inactive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamespaceRecordIsTerminal(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
phase NamespacePhase
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{phase: NamespacePhaseRemoved, expected: true},
|
||||||
|
{phase: NamespacePhaseFailed, expected: true},
|
||||||
|
{phase: NamespacePhaseActive, expected: false},
|
||||||
|
{phase: NamespacePhaseRegistering, expected: false},
|
||||||
|
} {
|
||||||
|
record := NamespaceRecord{Phase: test.phase}
|
||||||
|
if record.IsTerminal() != test.expected {
|
||||||
|
t.Fatalf("expected terminal=%v for phase %s", test.expected, test.phase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user