hub v0.51.0: DR-tier-by-default — per-customer dr_tier flag (default ON, legacy backfill from reality), cascade stages, WG-registration auto-provision hook, offsite-requires-DR guard (F-6 policy), host-page capability chips (inactive=neutral)
Claude-Session: https://claude.ai/code/session_01NptTCFtu7dz2Ru89qHRagN
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package store
|
||||
|
||||
// v0.51.0 dr_tier one-time legacy backfill — "initialize from reality": on the migration that
|
||||
// ADDS the column, a customer whose host already carries an ENABLED pbs_dr descriptor flips ON;
|
||||
// everyone else stays OFF (never auto-cascade a legacy box). Simulated against a genuine
|
||||
// pre-v0.51.0 database file (tables without dr_tier), then opened through store.New so the REAL
|
||||
// migrate() path runs. Red-proof partner: make the backfill ignore `"enabled":false` vs true
|
||||
// (set every pbs_dr customer ON) → the disabled-descriptor case fails.
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"io"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func TestDRTierBackfill_InitializesFromReality(t *testing.T) {
|
||||
dbPath := filepath.Join(t.TempDir(), "legacy.db")
|
||||
|
||||
// 1. Build a PRE-v0.51.0 database: customer_configs + hosts WITHOUT dr_tier.
|
||||
raw, err := sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mustExec := func(q string, args ...any) {
|
||||
t.Helper()
|
||||
if _, err := raw.Exec(q, args...); err != nil {
|
||||
t.Fatalf("legacy seed: %v (%s)", err, q)
|
||||
}
|
||||
}
|
||||
mustExec(`CREATE TABLE customer_configs (
|
||||
customer_id TEXT PRIMARY KEY,
|
||||
customer_name TEXT NOT NULL DEFAULT '',
|
||||
domain TEXT NOT NULL DEFAULT '',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
retrieval_password TEXT NOT NULL,
|
||||
api_key TEXT NOT NULL,
|
||||
config_json TEXT NOT NULL DEFAULT '{}',
|
||||
created_at DATETIME NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at DATETIME NOT NULL DEFAULT (datetime('now'))
|
||||
)`)
|
||||
mustExec(`CREATE TABLE hosts (
|
||||
host_id TEXT PRIMARY KEY,
|
||||
customer_id TEXT NOT NULL,
|
||||
api_key TEXT NOT NULL,
|
||||
agent_version TEXT NOT NULL DEFAULT '',
|
||||
last_report_at DATETIME,
|
||||
desired_json TEXT NOT NULL DEFAULT '{}',
|
||||
desired_generation INTEGER NOT NULL DEFAULT 0,
|
||||
dr_record_json TEXT NOT NULL DEFAULT '{}',
|
||||
created_at DATETIME NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at DATETIME NOT NULL DEFAULT (datetime('now'))
|
||||
)`)
|
||||
seedCustomer := func(id string) {
|
||||
mustExec(`INSERT INTO customer_configs (customer_id, retrieval_password, api_key) VALUES (?, ?, ?)`,
|
||||
id, "pw", "key-"+id)
|
||||
}
|
||||
seedCustomer("applied") // host carries an ENABLED descriptor → must flip ON
|
||||
seedCustomer("disabled") // descriptor present but enabled:false → must stay OFF
|
||||
seedCustomer("plain") // no descriptor at all → must stay OFF
|
||||
seedCustomer("hostless") // no host row → must stay OFF
|
||||
mustExec(`INSERT INTO hosts (host_id, customer_id, api_key, desired_json) VALUES (?, ?, ?, ?)`,
|
||||
"applied-01", "applied", "h1",
|
||||
`{"pbs_dr":{"enabled":true,"storage_id":"felhom-pbs","namespace":"applied"}}`)
|
||||
mustExec(`INSERT INTO hosts (host_id, customer_id, api_key, desired_json) VALUES (?, ?, ?, ?)`,
|
||||
"disabled-01", "disabled", "h2",
|
||||
`{"pbs_dr":{"enabled":false,"storage_id":"felhom-pbs","namespace":"disabled"}}`)
|
||||
mustExec(`INSERT INTO hosts (host_id, customer_id, api_key, desired_json) VALUES (?, ?, ?, ?)`,
|
||||
"plain-01", "plain", "h3", `{}`)
|
||||
if err := raw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 2. Open through the real store — migrate() adds dr_tier and runs the one-time backfill.
|
||||
st, err := New(dbPath, log.New(io.Discard, "", 0))
|
||||
if err != nil {
|
||||
t.Fatalf("store.New over the legacy db: %v", err)
|
||||
}
|
||||
defer st.Close()
|
||||
|
||||
want := map[string]bool{"applied": true, "disabled": false, "plain": false, "hostless": false}
|
||||
for id, wantOn := range want {
|
||||
cfg, err := st.GetCustomerConfig(id)
|
||||
if err != nil || cfg == nil {
|
||||
t.Fatalf("read %s: %v", id, err)
|
||||
}
|
||||
if cfg.DRTier != wantOn {
|
||||
t.Errorf("customer %s: dr_tier=%v, want %v (initialize from reality)", id, cfg.DRTier, wantOn)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. The backfill is ONE-TIME: a later flag change must survive a re-open (the ALTER now
|
||||
// fails → no re-backfill stomping operator decisions).
|
||||
cfg, _ := st.GetCustomerConfig("applied")
|
||||
cfg.DRTier = false // operator opts the customer out
|
||||
if err := st.SaveCustomerConfig(cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st.Close()
|
||||
st2, err := New(dbPath, log.New(io.Discard, "", 0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer st2.Close()
|
||||
cfg2, _ := st2.GetCustomerConfig("applied")
|
||||
if cfg2.DRTier {
|
||||
t.Fatal("re-open re-ran the backfill and stomped the operator's opt-out")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user