feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
924f111e48
commit
f8e0520817
197 files changed
+18655
-1416
No files matched your search
@@ -0,0 +1,120 @@
|
||||
package bundle
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// legacyManifestV1 mirrors the decoder fields present in the locally available
|
||||
// v1.0.0 source. It deliberately has no future capability or deployment fields.
|
||||
type legacyManifestV1 struct {
|
||||
RevisionID string `json:"revision_id"`
|
||||
SpeakerID string `json:"speaker_id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Files []struct {
|
||||
Path string `json:"path"`
|
||||
SHA256 string `json:"sha256"`
|
||||
} `json:"files"`
|
||||
Algorithm string `json:"signature_algorithm"`
|
||||
PublicKeyBase64 string `json:"public_key_base64"`
|
||||
}
|
||||
|
||||
func TestLegacyCompatibilityFrozenSignedCurrentBaseline(t *testing.T) {
|
||||
raw, err := os.ReadFile("testdata/current_baseline_signed_bundle.tar.gz")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pub := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{0x42}, ed25519.SeedSize)).Public().(ed25519.PublicKey)
|
||||
verified, err := VerifyGzippedTar(raw, pub)
|
||||
if err != nil {
|
||||
t.Fatalf("frozen signed baseline rejected: %v", err)
|
||||
}
|
||||
if verified.Manifest.RevisionID != "revision-legacy" || verified.Manifest.SpeakerID != "speaker-legacy" || len(verified.Files) != 2 {
|
||||
t.Fatalf("frozen baseline contents: %+v", verified.Manifest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyCompatibilityBundleWire(t *testing.T) {
|
||||
seed := bytes.Repeat([]byte{0x42}, ed25519.SeedSize)
|
||||
priv := ed25519.NewKeyFromSeed(seed)
|
||||
pub := priv.Public().(ed25519.PublicKey)
|
||||
fragments := map[string]string{
|
||||
"bird.conf": "router id 192.0.2.1;\n",
|
||||
"bird.d/evobgp_prefixes_v4.conf": "# revision\n",
|
||||
"_preview": "must not ship",
|
||||
}
|
||||
raw, err := BuildGzippedTar("revision-legacy", "speaker-legacy", fragments, priv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gr, err := gzip.NewReader(bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = gr.Close() }()
|
||||
tr := tar.NewReader(gr)
|
||||
entries := map[string][]byte{}
|
||||
var order []string
|
||||
for {
|
||||
h, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
order = append(order, h.Name)
|
||||
entries[h.Name] = body
|
||||
}
|
||||
wantOrder := []string{"manifest.json", "bundle.sig", "bird.conf", "bird.d/evobgp_prefixes_v4.conf"}
|
||||
if !reflect.DeepEqual(order, wantOrder) {
|
||||
t.Fatalf("legacy tar layout: got %v, want %v", order, wantOrder)
|
||||
}
|
||||
var top map[string]json.RawMessage
|
||||
if err := json.Unmarshal(entries["manifest.json"], &top); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantFields := []string{"created_at", "files", "public_key_base64", "revision_id", "signature_algorithm", "speaker_id"}
|
||||
fields := make([]string, 0, len(top))
|
||||
for k := range top {
|
||||
fields = append(fields, k)
|
||||
}
|
||||
sort.Strings(fields)
|
||||
if !reflect.DeepEqual(fields, wantFields) {
|
||||
t.Fatalf("manifest fields: got %v, want %v", fields, wantFields)
|
||||
}
|
||||
var m legacyManifestV1
|
||||
if err := json.Unmarshal(entries["manifest.json"], &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.RevisionID != "revision-legacy" || m.SpeakerID != "speaker-legacy" || m.CreatedAt == "" || m.Algorithm != "ed25519" || m.PublicKeyBase64 != base64.StdEncoding.EncodeToString(pub) {
|
||||
t.Fatalf("legacy manifest fields: %+v", m)
|
||||
}
|
||||
if !ed25519.Verify(pub, entries["manifest.json"], entries["bundle.sig"]) {
|
||||
t.Fatal("legacy Ed25519 verifier rejected current signed manifest")
|
||||
}
|
||||
if len(m.Files) != 2 {
|
||||
t.Fatalf("legacy manifest file count: %d", len(m.Files))
|
||||
}
|
||||
for _, f := range m.Files {
|
||||
sum := sha256.Sum256(entries[f.Path])
|
||||
if hex.EncodeToString(sum[:]) != f.SHA256 {
|
||||
t.Fatalf("legacy SHA-256 mismatch for %q", f.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user