272 lines
13 KiB
Go
272 lines
13 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"github.com/novox/mesh-controller/internal/builderkeys"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// withTrunkList stands in for the catalogue's trunk head: an empty list of predefined keys, readable, or not.
|
|
func withTrunkList(t *testing.T, readable bool) {
|
|
t.Helper()
|
|
was := predefinedAtTrunk
|
|
predefinedAtTrunk = func(context.Context, *inventory.Inventory) (predefinedHead, error) {
|
|
if !readable {
|
|
return predefinedHead{}, errors.New("the forge did not answer")
|
|
}
|
|
list, err := catalogue.ParsePredefined([]byte(`{}`))
|
|
return predefinedHead{List: list, Repository: "novox/mesh-catalog", Commit: "abcdef0123456789",
|
|
Generation: 1 << 40}, err
|
|
}
|
|
t.Cleanup(func() { predefinedAtTrunk = was })
|
|
}
|
|
|
|
// aKeyFor is a done key of module at fp, of one archive with a reference, and the indexer's trunk value naming it at
|
|
// commit in repository/path.
|
|
func aKeyFor(module, version, fp, repository, path, commit string) (keyBuckets, sdkdelivery.Module) {
|
|
raw := `{"module":"` + module + `","version":"` + version + `","build":{"artifacts":[{"name":"a","kind":"archive","from":"files"}]},` +
|
|
`"resources":[{"id":"a","type":"archive","path":"/opt/` + module + `","artifact":"a"}]}`
|
|
digest := sdkdelivery.ManifestDigest([]byte(raw))
|
|
blob := shaOf(module + version)
|
|
k := builderkeys.Key{Module: module, Fingerprint: fp, Found: true, Revision: 1,
|
|
Build: sdkdelivery.BuilderBuild{State: sdkdelivery.BuildDone, By: "ace", Manifest: digest,
|
|
Artifacts: []sdkdelivery.BuiltArtifact{{Name: "a", Kind: "archive", Digest: blob,
|
|
Reference: "artifact-store://" + module + "/a/blobs/" + blob}}}}
|
|
return keyBuckets{keys: map[string]builderkeys.Key{module: k}, manifests: map[string]string{digest: raw}},
|
|
sdkdelivery.Module{FP2: fp, Manifest: digest, Repository: repository, Commit: commit, Path: path}
|
|
}
|
|
|
|
func fpOf(c byte) string { return "src2:" + strings.Repeat(string(c), 64) }
|
|
|
|
// A3, A4 (ADR 0498 §8, ADR 0501 §1-§3, §8): a done key of a trunk fingerprint records one build of today's layout,
|
|
// whose artifacts are the key's, and registers the module under the same rules of who may register as before.
|
|
func TestADoneKeyOfTheTrunkRegistersUnderTheSameRules(t *testing.T) {
|
|
open := theCatalogue(t) // sudo from novox/mesh-catalog modules/sudo, mesh-host from novox/mesh-host
|
|
ctx := t.Context()
|
|
inv := open.inventory
|
|
withTrunkList(t, true)
|
|
|
|
// A known module from the repository and path the catalogue builds it from: registered from its key.
|
|
keys, trunk := aKeyFor("sudo", "2", fpOf('a'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789")
|
|
_, kept, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}})
|
|
if err != nil {
|
|
t.Fatalf("0501 §1: a done key of sudo's trunk fingerprint was not taken in: %v", err)
|
|
}
|
|
if shelf, _ := inv.Catalogue(ctx); shelf["sudo"].Version != "2" {
|
|
t.Fatalf("0501 §1: sudo is %q", shelf["sudo"].Version)
|
|
}
|
|
got, found, err := inv.BuildByID(ctx, kept.ID)
|
|
if err != nil || !found || len(got.Made) != 1 || got.Made[0].Reference != kept.Made[0].Reference {
|
|
t.Fatalf("0498 §8: the recorded build is %+v (%v)", got, err)
|
|
}
|
|
if fps, err := inv.BuildSourceFingerprints(ctx, "sudo"); err != nil || fps["c0ffee0123456789"] != fpOf('a') {
|
|
t.Fatalf("0498 §8: the recorded build's fingerprint is %v (%v), not the key's", fps, err)
|
|
}
|
|
// Recording the same key again is one record.
|
|
if _, _, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
builds, _ := inv.Builds(ctx, "sudo", 20)
|
|
n := 0
|
|
for _, b := range builds {
|
|
if b.ID == kept.ID {
|
|
n++
|
|
}
|
|
}
|
|
if n != 1 {
|
|
t.Fatalf("0498 §8: one key taken in twice is %d records", n)
|
|
}
|
|
|
|
// A known module from another repository: refused, as a build of it was (ADR 0266).
|
|
other, otherTrunk := aKeyFor("sudo", "evil", fpOf('b'), "agent/sudo", "modules/sudo", "d00d0123456789ab")
|
|
if _, _, err := takeInKey(ctx, inv, other, keyTake{Module: "sudo", Trunk: otherTrunk,
|
|
Ask: registrationAsk{Merge: otherTrunk.Commit}}); !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("0501 §3: a known module from another repository was taken in: %v", err)
|
|
}
|
|
// A new module from a repository the catalogue builds from: registered.
|
|
zram, zramTrunk := aKeyFor("zram", "1", fpOf('c'), "novox/mesh-catalog", "modules/zram", "c0ffee0123456789")
|
|
if _, _, err := takeInKey(ctx, inv, zram, keyTake{Module: "zram", Trunk: zramTrunk, Ask: registrationAsk{Merge: zramTrunk.Commit}}); err != nil {
|
|
t.Fatalf("0501 §3: a new module from a built repository was refused: %v", err)
|
|
}
|
|
// A new module from a repository nothing is built from, with no Approve: refused.
|
|
tools, toolsTrunk := aKeyFor("agent-tools", "1", fpOf('d'), "agent/tools", "", "e0e0e0e0e0e0e0e0")
|
|
if _, _, err := takeInKey(ctx, inv, tools, keyTake{Module: "agent-tools", Trunk: toolsTrunk,
|
|
Ask: registrationAsk{Merge: toolsTrunk.Commit}}); !errors.Is(err, errNotItsSource) {
|
|
t.Fatalf("0501 §3: a new repository's module was taken in with no warrant: %v", err)
|
|
}
|
|
// At the terminal the operator registers it (ADR 0501 §4).
|
|
if _, _, err := takeInKey(ctx, inv, tools, keyTake{Module: "agent-tools", Trunk: toolsTrunk,
|
|
Ask: registrationAsk{Terminal: true}}); err != nil {
|
|
t.Fatalf("0501 §4: the terminal's registration was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// ADR 0501 §2: a key only a candidate names registers nothing: the trunk value names another fingerprint, whose key is
|
|
// not there, and the take-in waits.
|
|
func TestAKeyOnlyACandidateNamesRegistersNothing(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
withTrunkList(t, true)
|
|
keys, trunk := aKeyFor("sudo", "3", fpOf('e'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789")
|
|
trunk.FP2 = fpOf('f') // the trunk names another fingerprint than the candidate's done key
|
|
_, _, err := takeInKey(ctx, open.inventory, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}})
|
|
if !errors.Is(err, errKeyWaits) {
|
|
t.Fatalf("0501 §2: %v", err)
|
|
}
|
|
if shelf, _ := open.inventory.Catalogue(ctx); shelf["sudo"].Version != "1" {
|
|
t.Fatalf("0501 §2: a candidate's key registered sudo %q", shelf["sudo"].Version)
|
|
}
|
|
}
|
|
|
|
// ADR 0501 §8: a take-in that cannot read the list of predefined keys at the catalogue's trunk head waits, says so and
|
|
// registers nothing.
|
|
func TestATakeInWithoutTheTrunksListWaits(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
withTrunkList(t, false)
|
|
keys, trunk := aKeyFor("sudo", "4", fpOf('1'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789")
|
|
_, _, err := takeInKey(ctx, open.inventory, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}})
|
|
if !errors.Is(err, errKeyWaits) || !strings.Contains(err.Error(), "predefined keys") {
|
|
t.Fatalf("0501 §8: %v", err)
|
|
}
|
|
if shelf, _ := open.inventory.Catalogue(ctx); shelf["sudo"].Version != "1" {
|
|
t.Fatalf("0501 §8: registered %q", shelf["sudo"].Version)
|
|
}
|
|
}
|
|
|
|
// A3 (the lead's decision on Against and Read): a key's record keeps the collector's keep set as it was and its read
|
|
// repositories are its manifest's, fresh at every key.
|
|
func TestAKeysRecordKeepsTheKeepSetAndReadsItsManifest(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
inv := open.inventory
|
|
withTrunkList(t, true)
|
|
before, err := inv.KeepSet(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw := `{"module":"sudo","version":"5","build":{"artifacts":[{"name":"img","kind":"image","from":"Dockerfile",` +
|
|
`"context":{"repository":"novox/elsewhere","seat":"git"}}]},"resources":[{"id":"run","type":"container","artifact":"img"}]}`
|
|
digest := sdkdelivery.ManifestDigest([]byte(raw))
|
|
k := builderkeys.Key{Module: "sudo", Fingerprint: fpOf('2'), Found: true, Revision: 1,
|
|
Build: sdkdelivery.BuilderBuild{State: sdkdelivery.BuildDone, By: "g14", Manifest: digest,
|
|
Artifacts: []sdkdelivery.BuiltArtifact{{Name: "img", Kind: "image", Digest: shaOf("img5"),
|
|
Reference: "artifact-store://sudo/img@" + shaOf("img5")}}}}
|
|
keys := keyBuckets{keys: map[string]builderkeys.Key{"sudo": k}, manifests: map[string]string{digest: raw}}
|
|
trunk := sdkdelivery.Module{FP2: fpOf('2'), Manifest: digest, Repository: "novox/mesh-catalog",
|
|
Commit: "c0ffee0123456789", Path: "modules/sudo"}
|
|
if _, _, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
after, err := inv.KeepSet(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for reference, why := range before {
|
|
if !reflect.DeepEqual(after[reference], why) && !strings.Contains(strings.Join(after[reference], ","), strings.Join(why, ",")) {
|
|
t.Errorf("the keep set changed for %s: %v, was %v", reference, after[reference], why)
|
|
}
|
|
}
|
|
read, err := inv.ReadRepositories(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, r := range read["sudo"] {
|
|
if !r.Own && r.Repository == "novox/elsewhere" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
text, _ := json.Marshal(read["sudo"])
|
|
t.Fatalf("the key's record does not read its manifest's context: %s", text)
|
|
}
|
|
}
|
|
|
|
// A9 (ADR 0453 §6, ADR 0499 consequences): at the switch, a key's build whose every artifact has the digest of the
|
|
// build-agent build the mesh holds (`src1:`) is no move: it is registered with that build's artifacts, and a walk sends
|
|
// nothing for it.
|
|
func TestAKeyWithTheHeldBuildsDigestsIsNoMove(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
inv := open.inventory
|
|
withTrunkList(t, true)
|
|
keys, trunk := aKeyFor("sudo", "6", fpOf('6'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789")
|
|
k := keys.keys["sudo"]
|
|
held := inventory.Build{ID: "build-sudo-old", Module: "sudo", Repository: "novox/mesh-catalog", Path: "modules/sudo",
|
|
Commit: "0ld0ld0ld0ld0ld0", SourceFingerprint: "src1:" + strings.Repeat("9", 64),
|
|
Made: []inventory.Artifact{{Name: "a", Kind: "archive", Reference: k.Build.Artifacts[0].Reference}}}
|
|
held.Manifest, _ = json.Marshal(catalogue.Manifest{Module: "sudo", Version: "held"})
|
|
if err := inv.RecordBuild(ctx, held); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, kept, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
same, err := inv.SameSourceCommits(ctx, "sudo", kept.ID)
|
|
if err != nil || !same["0ld0ld0ld0ld0ld0"] {
|
|
t.Fatalf("A9: the key's build does not stand on the held build of the same digests: %v %v", same, err)
|
|
}
|
|
if shelf, _ := inv.Catalogue(ctx); shelf["sudo"].Version != "held" {
|
|
t.Fatalf("A9: sudo is registered as %q, not with the held build's artifacts", shelf["sudo"].Version)
|
|
}
|
|
}
|
|
|
|
// A13 against the store: the switch's list reads the build the mesh holds of each module and the builder's key of its
|
|
// trunk fingerprint, and names each module unmoved, moved or not known.
|
|
func TestTheSwitchsListReadsTheHeldBuildsAndTheKeys(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
inv := open.inventory
|
|
keys, trunk := aKeyFor("sudo", "1", fpOf('b'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789")
|
|
k := keys.keys["sudo"]
|
|
if err := inv.RecordBuild(ctx, inventory.Build{ID: "build-sudo-held", Module: "sudo", Repository: "novox/mesh-catalog",
|
|
Commit: "c0ffee0123456789", Made: []inventory.Artifact{{Name: "a", Kind: "archive",
|
|
Reference: k.Build.Artifacts[0].Reference}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reads := planKeysOf{values: map[string]sdkdelivery.Module{"sudo@": trunk}, keys: map[string]builderkeys.Key{"sudo": k}}
|
|
lines, err := switchList(ctx, inv, reads)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := map[string]string{}
|
|
for _, l := range lines {
|
|
got[l.Module] = l.Verdict
|
|
}
|
|
if got["sudo"] != switchUnmoved || got["mesh-host"] != switchUnknown {
|
|
t.Fatalf("A13: the switch's list is %+v", lines)
|
|
}
|
|
}
|
|
|
|
// Issue 1902: the take-in with the lookup of the catalogue's repository NOT stubbed - the catalogue's modules are registered
|
|
// with a URL source and no seat, as every live row is - reads the list from the forge's wire alone and registers the module.
|
|
func TestATakeInFindsTheCataloguesRepositoryInTheURLForm(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
was, wasForms := formsOfSources, askGitea
|
|
formsOfSources = func(context.Context, *inventory.Inventory) *sourceForms { return aFormsOnTheForge() }
|
|
t.Cleanup(func() { formsOfSources, askGitea = was, wasForms })
|
|
aForgeAnsweringTheCatalogue(t)
|
|
if err := open.inventory.RegisterModule(ctx, catalogue.Manifest{Module: "catalogue-anchor", Version: "1"},
|
|
inventory.Source{Repository: "http://anchor.internal:20000/novox/mesh-catalog.git", Path: "modules/catalogue-anchor",
|
|
Ref: "main", BuiltFrom: "c0", Head: "c0"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
keys, trunk := aKeyFor("sudo", "6", fpOf('7'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789")
|
|
if _, _, err := takeInKey(ctx, open.inventory, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}); err != nil {
|
|
t.Fatalf("a key was not taken in with the real lookup of the catalogue's repository: %v", err)
|
|
}
|
|
}
|