Judge the store's mirror copies by what the builder's log says each build stood on, and the images a manifest pins by itself, so mirrors are recorded and image-missing covers what the controller judged (issue 1955, ADR 0571 clause 2)
This commit is contained in:
@@ -66,6 +66,19 @@ func (runtimeBus) Get(state, key string) ([]byte, bool, error) {
|
||||
func (runtimeBus) History(state, key string) ([]stdio.StateRevision, error) {
|
||||
return stdio.State(state).History(key)
|
||||
}
|
||||
func (runtimeBus) LogSince(read string, at time.Time) ([]stdio.LogEntry, error) {
|
||||
var out []stdio.LogEntry
|
||||
page, err := stdio.Log(read).Since(at, "", 0)
|
||||
for err == nil {
|
||||
out = append(out, page.Entries...)
|
||||
if page.Next == 0 {
|
||||
return out, nil
|
||||
}
|
||||
page, err = stdio.Log(read).Read(page.Next, "", 0)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (runtimeBus) Put(state, key string, value any) error {
|
||||
_, err := stdio.State(state).Put(key, value)
|
||||
return err
|
||||
|
||||
@@ -38,7 +38,9 @@ const (
|
||||
WantsRead = delivery.AssignerModule + ".wants"
|
||||
BuildsRead = delivery.BuilderModule + ".builds"
|
||||
HavesRead = "node-engine.have"
|
||||
LeaseRead = buscompose.ControllerLease
|
||||
// ManifestsRead is the indexer's manifests: the images a manifest pins by itself.
|
||||
ManifestsRead = delivery.IndexerModule + ".manifests"
|
||||
LeaseRead = buscompose.ControllerLease
|
||||
// FindingsState is the holder's own findings (health.FindingsState): image-missing.
|
||||
FindingsState = health.FindingsState
|
||||
// Module is this module, the observer its findings name.
|
||||
@@ -62,6 +64,8 @@ type Bus interface {
|
||||
Keys(state string) ([]string, error)
|
||||
Get(state, key string) ([]byte, bool, error)
|
||||
History(state, key string) ([]stdio.StateRevision, error)
|
||||
// LogSince is every entry of a read log from a time on (stdio.Log(read).Since).
|
||||
LogSince(read string, at time.Time) ([]stdio.LogEntry, error)
|
||||
Put(state, key string, value any) error
|
||||
Delete(state, key string) error
|
||||
Publish(subject string, body any) error
|
||||
@@ -98,6 +102,8 @@ type Build struct {
|
||||
Module, FP, Key string
|
||||
At time.Time
|
||||
Artifacts []delivery.BuiltArtifact
|
||||
// Manifest is the digest of the manifest it built, in the indexer's `manifests`.
|
||||
Manifest string
|
||||
}
|
||||
|
||||
// LeaseAway says the controller's lease names a holder that is no controller: then the seat sweeps.
|
||||
@@ -134,7 +140,7 @@ func (h *Holder) Builds() ([]Build, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, Build{Module: module, FP: fp, Key: k, At: at, Artifacts: b.Artifacts})
|
||||
out = append(out, Build{Module: module, FP: fp, Key: k, At: at, Artifacts: b.Artifacts, Manifest: b.Manifest})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -216,6 +222,44 @@ type Ref struct {
|
||||
// KeepSet judges every artifact the done builds name in the store: kept, and why, or eligible. Packages and
|
||||
// references into no store of the mesh's are no business of the store's.
|
||||
func KeepSet(builds []Build, wants []Want) map[string]*Ref {
|
||||
keeps := keptBuilds(builds, wants)
|
||||
out := map[string]*Ref{}
|
||||
for _, b := range builds {
|
||||
for _, a := range b.Artifacts {
|
||||
reference := catalogue.Recorded(a.Reference)
|
||||
if _, ours := catalogue.InArtifactStore(reference); !ours || a.Kind == "package" {
|
||||
continue
|
||||
}
|
||||
r := out[reference]
|
||||
if r == nil {
|
||||
_, _, kind := splitReference(reference)
|
||||
r = &Ref{Reference: reference, Kind: kind}
|
||||
out[reference] = r
|
||||
}
|
||||
r.Builds = append(r.Builds, b)
|
||||
r.keptBy(b, keeps[b.Key])
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// keptBy adds a build naming r, kept for why (none: eligible).
|
||||
func (r *Ref) keptBy(b Build, why []string) {
|
||||
if b.At.After(r.At) {
|
||||
r.At = b.At
|
||||
}
|
||||
if len(why) > 0 {
|
||||
r.Kept = true
|
||||
for _, w := range why {
|
||||
if !contains(r.Why, w) {
|
||||
r.Why = append(r.Why, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keptBuilds is, by build key, why each kept build is kept: one of its module's five newest, or a want names it.
|
||||
func keptBuilds(builds []Build, wants []Want) map[string][]string {
|
||||
wanted := map[string][]string{}
|
||||
for _, w := range wants {
|
||||
wanted[w.Module+" "+w.FP] = append(wanted[w.Module+" "+w.FP], w.Node)
|
||||
@@ -242,34 +286,7 @@ func KeepSet(builds []Build, wants []Want) map[string]*Ref {
|
||||
}
|
||||
}
|
||||
}
|
||||
out := map[string]*Ref{}
|
||||
for _, b := range builds {
|
||||
for _, a := range b.Artifacts {
|
||||
reference := catalogue.Recorded(a.Reference)
|
||||
if _, ours := catalogue.InArtifactStore(reference); !ours || a.Kind == "package" {
|
||||
continue
|
||||
}
|
||||
r := out[reference]
|
||||
if r == nil {
|
||||
_, _, kind := splitReference(reference)
|
||||
r = &Ref{Reference: reference, Kind: kind}
|
||||
out[reference] = r
|
||||
}
|
||||
r.Builds = append(r.Builds, b)
|
||||
if b.At.After(r.At) {
|
||||
r.At = b.At
|
||||
}
|
||||
if why := keeps[b.Key]; len(why) > 0 {
|
||||
r.Kept = true
|
||||
for _, w := range why {
|
||||
if !contains(r.Why, w) {
|
||||
r.Why = append(r.Why, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
return keeps
|
||||
}
|
||||
|
||||
func contains(s []string, v string) bool {
|
||||
@@ -304,6 +321,10 @@ type judged struct {
|
||||
view *View
|
||||
wants []Want
|
||||
built []Build
|
||||
// mirrors are the copies in the store's mirror repositories, by reference: recorded ones also in refs.
|
||||
mirrors map[string]*Ref
|
||||
// mirrorsHeld says why no copy is eligible now, when one is not: a kept build whose bases the log does not name.
|
||||
mirrorsHeld string
|
||||
}
|
||||
|
||||
func (h *Holder) judge(ctx context.Context) (*judged, error) {
|
||||
@@ -322,9 +343,89 @@ func (h *Holder) judge(ctx context.Context) (*judged, error) {
|
||||
j.view = nil
|
||||
}
|
||||
}
|
||||
if j.view != nil {
|
||||
against, err := h.against()
|
||||
if err != nil {
|
||||
h.logf("the builder's log could not be read, so every copy is kept as unrecorded: %v", err)
|
||||
against = nil
|
||||
}
|
||||
j.mirrors, j.mirrorsHeld = Mirrors(j.view, builds, keptBuilds(builds, wants), against)
|
||||
for r, m := range j.mirrors {
|
||||
if len(m.Builds) > 0 {
|
||||
j.refs[r] = m
|
||||
}
|
||||
}
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// against is what each build the builder's log says ended stood on, by build key: the bases' references.
|
||||
func (h *Holder) against() (map[string][]string, error) {
|
||||
entries, err := h.Bus.LogSince(delivery.BuildLogRead, h.now().Add(-delivery.BuilderLogDays*24*time.Hour))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := map[string][]string{}
|
||||
for _, raw := range entries {
|
||||
e, err := delivery.DecodeBuildLogEntry(raw.Value)
|
||||
if err != nil || e.Event != delivery.BuildEndedEvent {
|
||||
continue
|
||||
}
|
||||
key, err := delivery.BuilderBuildKey(e.Module, e.FP)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out[key] = e.Against
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Mirrors judges each copy in the store's mirror repositories (upstream/<host>/<path>, <module>/on-<argument>; ADR
|
||||
// 0257) by what the builder's log says each build stood on (ADR 0571 clause 1): named by a kept build, kept; named
|
||||
// only by builds no longer kept, eligible; named by no build the log holds, unrecorded and never removed (ADR 0189
|
||||
// §10). While a kept build's bases are not in the log (it ended before the log's 30 days), no copy is eligible: what
|
||||
// it stood on cannot be told, and held says so.
|
||||
func Mirrors(v *View, builds []Build, kept map[string][]string, against map[string][]string) (map[string]*Ref, string) {
|
||||
out := map[string]*Ref{}
|
||||
for _, name := range v.L.RepoNames() {
|
||||
if !mirrorRepository.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
for d := range v.L.Repos[name].Revisions {
|
||||
r := catalogue.ArtifactStoreScheme + name + "@" + d
|
||||
out[r] = &Ref{Reference: r, Kind: "image"}
|
||||
}
|
||||
}
|
||||
held := ""
|
||||
for _, b := range builds {
|
||||
bases, logged := against[b.Key]
|
||||
if !logged {
|
||||
if len(kept[b.Key]) > 0 && held == "" {
|
||||
held = "the builder's log does not say what the kept build " + b.Key + " stood on, so every copy is kept"
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, base := range bases {
|
||||
if m := out[catalogue.Recorded(base)]; m != nil {
|
||||
m.Builds = append(m.Builds, b)
|
||||
why := kept[b.Key]
|
||||
if len(why) > 0 {
|
||||
why = []string{"a kept build stood on it: " + b.Key}
|
||||
}
|
||||
m.keptBy(b, why)
|
||||
}
|
||||
}
|
||||
}
|
||||
if held != "" {
|
||||
for _, m := range out {
|
||||
if len(m.Builds) > 0 && !m.Kept {
|
||||
m.Kept, m.Why = true, []string{held}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, held
|
||||
}
|
||||
|
||||
// Artifacts is the seat's `artifacts`, in the controller's answer's shape (Records): every reference the mesh made that
|
||||
// lives in the store, kept with why or eligible, and with collected the ones the store no longer holds.
|
||||
func (h *Holder) Artifacts(repository string) (*Records, error) {
|
||||
@@ -651,11 +752,17 @@ func (h *Holder) ImagesOf(node string, address string) (Images, error) {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
images := []string{}
|
||||
for _, a := range b.Artifacts {
|
||||
if a.Kind != "image" && a.Kind != "upstream" || a.Reference == "" {
|
||||
continue
|
||||
if (a.Kind == "image" || a.Kind == "upstream") && a.Reference != "" {
|
||||
images = append(images, catalogue.Routed(catalogue.Recorded(a.Reference), address))
|
||||
}
|
||||
image := catalogue.Routed(catalogue.Recorded(a.Reference), address)
|
||||
}
|
||||
pinned, err := h.Pinned(b.Manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, image := range append(images, pinned...) {
|
||||
i := byImage[image]
|
||||
if i == nil {
|
||||
i = &Image{Image: image}
|
||||
@@ -708,6 +815,54 @@ func (h *Holder) ImagesOf(node string, address string) (Images, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Pinned is every container image a manifest names by itself, by digest (`<registry>/<path>@sha256:…`), read from the
|
||||
// indexer's manifests: what a definition pins outright rather than through a build's artifact. A placeholder is not one.
|
||||
func (h *Holder) Pinned(manifest string) ([]string, error) {
|
||||
if manifest == "" {
|
||||
return nil, nil
|
||||
}
|
||||
key, err := delivery.ManifestsKey(manifest)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
raw, found, err := h.Bus.Get(ManifestsRead, key)
|
||||
if err != nil || !found {
|
||||
return nil, err
|
||||
}
|
||||
m, err := delivery.DecodeManifest(raw)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
var parsed struct {
|
||||
Resources []map[string]any `json:"resources"`
|
||||
}
|
||||
if json.Unmarshal([]byte(m.JSON), &parsed) != nil {
|
||||
return nil, nil
|
||||
}
|
||||
var out []string
|
||||
for _, r := range parsed.Resources {
|
||||
image, _ := r["image"].(string)
|
||||
if fmt.Sprint(r["type"]) == "container" && strings.Contains(image, "@sha256:") && !strings.Contains(image, "${") &&
|
||||
!contains(out, image) {
|
||||
out = append(out, image)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// inStoreAt is the store's reference of an image its address names, "" for an image of another registry (the
|
||||
// controller's imageInStore).
|
||||
func inStoreAt(image, address string) string {
|
||||
if address == "" || !strings.HasPrefix(image, address+"/") || !strings.Contains(image, "@sha256:") {
|
||||
return ""
|
||||
}
|
||||
r := catalogue.Recorded(image)
|
||||
if _, ours := catalogue.InArtifactStore(r); !ours {
|
||||
return ""
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Observe writes S25's findings (image-missing): each want whose build names an image the store does not hold, on the
|
||||
// want's machine, and deletes each finding that no longer holds. Nothing is said without a listing.
|
||||
func (h *Holder) Observe(ctx context.Context) error {
|
||||
@@ -718,6 +873,10 @@ func (h *Holder) Observe(ctx context.Context) error {
|
||||
if j.view == nil {
|
||||
return nil
|
||||
}
|
||||
address := ""
|
||||
if d, err := h.Door(); err == nil {
|
||||
address = d.Address()
|
||||
}
|
||||
builds := map[string]Build{}
|
||||
for _, b := range j.built {
|
||||
builds[b.Module+" "+b.FP] = b
|
||||
@@ -728,11 +887,21 @@ func (h *Holder) Observe(ctx context.Context) error {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var refs []string
|
||||
for _, a := range b.Artifacts {
|
||||
reference := catalogue.Recorded(a.Reference)
|
||||
if a.Kind != "image" {
|
||||
continue
|
||||
if a.Kind == "image" {
|
||||
refs = append(refs, catalogue.Recorded(a.Reference))
|
||||
}
|
||||
}
|
||||
// And what its manifest pins by itself under the store's address, as the controller judged S25.
|
||||
if pinned, err := h.Pinned(b.Manifest); err == nil && address != "" {
|
||||
for _, image := range pinned {
|
||||
if r := inStoreAt(image, address); r != "" {
|
||||
refs = append(refs, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, reference := range refs {
|
||||
if held, known := inStore(j.view, reference); !known || held {
|
||||
continue
|
||||
}
|
||||
@@ -808,3 +977,44 @@ func (h *Holder) logf(format string, a ...any) {
|
||||
h.Logf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// Mirror is one copy as the seat's `mirrors` answers it.
|
||||
type Mirror struct {
|
||||
Reference string `json:"reference"`
|
||||
State string `json:"state"`
|
||||
Why []string `json:"why,omitempty"`
|
||||
StoodOn []string `json:"stood_on_by,omitempty"`
|
||||
}
|
||||
|
||||
// MirrorsAnswer is the seat's `mirrors`: every copy, and why none is eligible when a kept build's bases are unknown.
|
||||
type MirrorsAnswer struct {
|
||||
Copies []Mirror `json:"copies"`
|
||||
Held string `json:"held,omitempty"`
|
||||
}
|
||||
|
||||
// MirrorsNow is every copy in the store's mirror repositories as the keep set judges it now.
|
||||
func (h *Holder) MirrorsNow() (MirrorsAnswer, error) {
|
||||
j, err := h.judge(context.Background())
|
||||
if err != nil {
|
||||
return MirrorsAnswer{}, err
|
||||
}
|
||||
if j.view == nil {
|
||||
return MirrorsAnswer{}, errors.New("the store's listing could not be read, so no copy can be named")
|
||||
}
|
||||
out := MirrorsAnswer{Copies: []Mirror{}, Held: j.mirrorsHeld}
|
||||
for _, r := range j.mirrors {
|
||||
m := Mirror{Reference: r.Reference, State: "unrecorded", Why: r.Why}
|
||||
switch {
|
||||
case r.Kept:
|
||||
m.State = "kept"
|
||||
case len(r.Builds) > 0:
|
||||
m.State = "eligible"
|
||||
}
|
||||
for _, b := range r.Builds {
|
||||
m.StoodOn = append(m.StoodOn, b.Key)
|
||||
}
|
||||
out.Copies = append(out.Copies, m)
|
||||
}
|
||||
sort.Slice(out.Copies, func(a, b int) bool { return out.Copies[a].Reference < out.Copies[b].Reference })
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -30,6 +30,19 @@ type realBus struct {
|
||||
ctx context.Context
|
||||
nc *nats.Conn
|
||||
js jetstream.JetStream
|
||||
// log is the builder's log as the runtime's log verbs would read it (mesh-tools adds them; the sdk's own read of a
|
||||
// log is tested in mesh-sdk's process package).
|
||||
log []stdio.LogEntry
|
||||
}
|
||||
|
||||
func (b realBus) LogSince(_ string, at time.Time) ([]stdio.LogEntry, error) {
|
||||
var out []stdio.LogEntry
|
||||
for _, e := range b.log {
|
||||
if !e.At.Before(at) {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func bucketOf(state string) string { return strings.Replace(state, ".", "_", 1) }
|
||||
@@ -116,7 +129,7 @@ func TestTheSeatSweepsAtABuildsEndOnARealBusOnceTheLeaseIsAway(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, s := range []string{BuildsRead, WantsRead, HavesRead, LeaseRead, "artifact-store-tools." + FindingsState} {
|
||||
for _, s := range []string{BuildsRead, WantsRead, HavesRead, LeaseRead, ManifestsRead, "artifact-store-tools." + FindingsState} {
|
||||
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: bucketOf(s), History: 5}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -125,7 +138,8 @@ func TestTheSeatSweepsAtABuildsEndOnARealBusOnceTheLeaseIsAway(t *testing.T) {
|
||||
|
||||
// The mesh as aMesh lays it, copied onto the real buckets: letta's seven builds, a want, the store's listing.
|
||||
mem, memb, door, _ := aMesh(t, &buscompose.LeaseHolder{Instance: "c1", Host: "novox", Epoch: 3})
|
||||
for _, s := range []string{BuildsRead, WantsRead, LeaseRead} {
|
||||
bus.log = memb.log
|
||||
for _, s := range []string{BuildsRead, WantsRead, LeaseRead, ManifestsRead} {
|
||||
keys, _ := memb.Keys(s)
|
||||
for _, k := range keys[:min(len(keys), 6)] { // the seventh build ends later, on the bus
|
||||
raw, _, _ := memb.Get(s, k)
|
||||
@@ -207,7 +221,8 @@ func TestTheSeatSweepsAtABuildsEndOnARealBusOnceTheLeaseIsAway(t *testing.T) {
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if len(acts) != 2 || !strings.Contains(acts[0], archiveRef("letta", 2)) || !strings.Contains(acts[1], imageRef("letta", 2)) {
|
||||
if len(acts) != 3 || !strings.Contains(acts[0], archiveRef("letta", 2)) || !strings.Contains(acts[1], imageRef("letta", 2)) ||
|
||||
!strings.Contains(acts[2], mirror(1)) {
|
||||
t.Fatalf("the hand acts recorded are %v; the store was asked %v", acts, door.asked)
|
||||
}
|
||||
if len(gone) != 1 || !strings.Contains(gone[0], `"fingerprint":"`+fp(2)+`"`) {
|
||||
|
||||
@@ -24,9 +24,20 @@ type memBus struct {
|
||||
mu sync.Mutex
|
||||
states map[string]map[string][]byte
|
||||
history map[string][]stdio.StateRevision
|
||||
log []stdio.LogEntry
|
||||
published []string
|
||||
}
|
||||
|
||||
func (b *memBus) LogSince(read string, at time.Time) ([]stdio.LogEntry, error) {
|
||||
var out []stdio.LogEntry
|
||||
for _, e := range b.log {
|
||||
if read == delivery.BuildLogRead && !e.At.Before(at) {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (b *memBus) Keys(state string) ([]string, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
@@ -107,6 +118,14 @@ func (d *memDoor) SpareKept(context.Context, []string, []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mirror is a copy of an upstream base in the store, the n-th.
|
||||
func mirror(n int) string {
|
||||
return "artifact-store://upstream/docker.io/library/alpine@sha256:" + strings.Repeat(fmt.Sprintf("%x", n%16), 64)
|
||||
}
|
||||
|
||||
// pinnedImage is the image letta's manifest names by itself, at the store's address.
|
||||
const pinnedImage = "10.0.0.5:5000/letta/pinned@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
|
||||
func digestOf(ref string) string { _, d, _ := splitReference(ref); return d }
|
||||
|
||||
func fp(n int) string { return "src2:" + strings.Repeat(fmt.Sprintf("%x", n%16), 64) }
|
||||
@@ -149,6 +168,30 @@ func aMesh(t *testing.T, lease *buscompose.LeaseHolder) (*Holder, *memBus, *memD
|
||||
}
|
||||
}
|
||||
listing.repo("letta/image").Revisions["sha256:"+strings.Repeat("e", 64)] = true // no build names it
|
||||
listing.repo("letta/pinned").Revisions["sha256:"+strings.Repeat("c", 64)] = true
|
||||
// Three copies of a base: letta's 2nd stood on the first, its 7th on the second, and no build on the third.
|
||||
for n := 1; n <= 3; n++ {
|
||||
repo, digest, _ := splitReference(mirror(n))
|
||||
listing.repo(repo).Revisions[digest] = true
|
||||
door.holds[mirror(n)] = true
|
||||
}
|
||||
for n := 1; n <= 7; n++ {
|
||||
e := delivery.BuildLogEntry{Event: delivery.BuildEndedEvent, Module: "letta", FP: fp(n), Repository: "novox/x", Commit: "c",
|
||||
Node: "ace", At: day.Add(time.Duration(n) * time.Hour), Outcome: "done"}
|
||||
switch n {
|
||||
case 2:
|
||||
e.Against = []string{mirror(1)}
|
||||
case 7:
|
||||
e.Against = []string{mirror(2)}
|
||||
}
|
||||
raw, _ := delivery.EncodeBuildLogEntry(e)
|
||||
b.log = append(b.log, stdio.LogEntry{Sequence: uint64(n), Key: "letta", Value: raw, At: e.At})
|
||||
}
|
||||
manifest, _ := delivery.EncodeManifest(delivery.Manifest{Module: "letta",
|
||||
JSON: `{"module":"letta","resources":[{"id":"app","type":"container","image":"` + pinnedImage + `"},` +
|
||||
`{"id":"built","type":"container","image":"${artifact:image}"}]}`})
|
||||
mk, _ := delivery.ManifestsKey(strings.Repeat("d", 64))
|
||||
_ = b.Put(ManifestsRead, mk, json.RawMessage(manifest))
|
||||
want, _ := json.Marshal(delivery.Want{V: delivery.Version, FP: fp(1), Self: true})
|
||||
_ = b.Put(WantsRead, "want.ace.letta", json.RawMessage(want))
|
||||
if lease != nil {
|
||||
@@ -190,7 +233,14 @@ func TestTheSeatKeepsTheFiveNewestAndTheWantedAndSweepsOnlyOnceTheLeaseIsAway(t
|
||||
t.Errorf("letta's build %d is %s and %s, not %s", n, states[imageRef("letta", n)], states[archiveRef("letta", n)], want)
|
||||
}
|
||||
}
|
||||
if len(r.References) != 14 || r.Counts["kept"] != 12 || r.Counts["eligible"] != 2 || r.Store != "10.0.0.5:5000" {
|
||||
// And the copies: the one letta's 2nd stood on is eligible with it, the 7th's kept; the third no build names.
|
||||
if states[mirror(1)] != "eligible" || states[mirror(2)] != "kept" || states[mirror(3)] != "" {
|
||||
t.Errorf("the copies are %s, %s and %q", states[mirror(1)], states[mirror(2)], states[mirror(3)])
|
||||
}
|
||||
if m, err := h.MirrorsNow(); err != nil || len(m.Copies) != 3 || m.Copies[2].State != "unrecorded" || m.Held != "" {
|
||||
t.Errorf("mirrors answered %+v: %v", m, err)
|
||||
}
|
||||
if len(r.References) != 16 || r.Counts["kept"] != 13 || r.Counts["eligible"] != 3 || r.Store != "10.0.0.5:5000" {
|
||||
t.Errorf("the references are %d, counted %v, at %q", len(r.References), r.Counts, r.Store)
|
||||
}
|
||||
|
||||
@@ -202,16 +252,22 @@ func TestTheSeatKeepsTheFiveNewestAndTheWantedAndSweepsOnlyOnceTheLeaseIsAway(t
|
||||
letGo []string
|
||||
left int
|
||||
skipped int
|
||||
unlog int
|
||||
}{
|
||||
{name: "the lease names a controller", lease: controller},
|
||||
{name: "no lease key", lease: nil},
|
||||
{name: "handed away", lease: handed, most: MostPerSweep, letGo: []string{archiveRef("letta", 2), imageRef("letta", 2)}},
|
||||
{name: "an index waits", lease: handed, index: true, most: MostPerSweep, letGo: []string{archiveRef("letta", 2)}, skipped: 1},
|
||||
{name: "bounded", lease: handed, most: 1, letGo: []string{archiveRef("letta", 2)}, left: 1},
|
||||
{name: "handed away", lease: handed, most: MostPerSweep, letGo: []string{archiveRef("letta", 2), imageRef("letta", 2), mirror(1)}},
|
||||
{name: "an index waits", lease: handed, index: true, most: MostPerSweep, letGo: []string{archiveRef("letta", 2), mirror(1)}, skipped: 1},
|
||||
{name: "bounded", lease: handed, most: 1, letGo: []string{archiveRef("letta", 2)}, left: 2},
|
||||
// A kept build the log no longer holds: what it stood on is unknown, so no copy goes.
|
||||
{name: "a kept build's bases unknown", lease: handed, most: MostPerSweep, letGo: []string{archiveRef("letta", 2), imageRef("letta", 2)}, unlog: 3},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
h, b, door, acts := aMesh(t, c.lease)
|
||||
door.indexes[imageRef("letta", 2)] = c.index
|
||||
if c.unlog > 0 {
|
||||
b.log = append(b.log[:c.unlog-1:c.unlog-1], b.log[c.unlog:]...)
|
||||
}
|
||||
if c.most == 0 {
|
||||
h.AfterBuild("letta.x")
|
||||
if _, err := h.Collect("tidy", true, 0); err == nil {
|
||||
@@ -275,12 +331,21 @@ func TestAWantedImageTheStoreLacksIsAFindingAndImagesNamesWantsAndHaves(t *testi
|
||||
if _, found, _ := b.Get(FindingsState, key); found {
|
||||
t.Fatal("the finding stayed once the store held the image again")
|
||||
}
|
||||
// An image the manifest pins by itself, under the store's address, is judged as the controller judged it.
|
||||
delete(view.L.Repos["letta/pinned"].Revisions, "sha256:"+strings.Repeat("c", 64))
|
||||
if err := h.Observe(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if raw, found, _ := b.Get(FindingsState, key); !found || !strings.Contains(string(raw), "letta/pinned@sha256:") {
|
||||
t.Fatalf("the pinnedImage image missing is %s", raw)
|
||||
}
|
||||
|
||||
have, _ := json.Marshal(delivery.Have{V: delivery.Version, FP: fp(7), Phase: "running"})
|
||||
_ = b.Put(HavesRead, "ace.letta", json.RawMessage(have))
|
||||
images, err := h.ImagesOf("ace", "10.0.0.5:5000")
|
||||
if err != nil || len(images.Images) != 2 || !images.SentKnown || images.Images[0].Image != "10.0.0.5:5000/letta/image@sha256:"+strings.Repeat("1", 64) ||
|
||||
fmt.Sprint(images.Images[0].In) != "[declared]" || fmt.Sprint(images.Images[1].In) != "[sent]" {
|
||||
if err != nil || len(images.Images) != 3 || !images.SentKnown || images.Images[0].Image != "10.0.0.5:5000/letta/image@sha256:"+strings.Repeat("1", 64) ||
|
||||
fmt.Sprint(images.Images[0].In) != "[declared]" || fmt.Sprint(images.Images[1].In) != "[sent]" ||
|
||||
images.Images[2].Image != pinnedImage || fmt.Sprint(images.Images[2].In) != "[declared sent]" {
|
||||
t.Fatalf("the images of ace are %+v: %v", images, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ package main
|
||||
// ok, answer}.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
@@ -67,29 +65,19 @@ func SeatTools(h *Holder, s *Store) []stdio.Tool {
|
||||
return verbAnswer{Output: fmt.Sprintf("let go of %d, would let go of %d, %d left\n", len(a.LetGo),
|
||||
len(a.WouldLetGo), a.Left), OK: true, Answer: a}, nil
|
||||
}},
|
||||
{Name: seat + "mirrors", Description: "The bases the mesh copied into its store, as the store lists them.",
|
||||
{Name: seat + "mirrors", Description: "The bases the mesh copied into its store, each kept (a kept build stood on " +
|
||||
"it), eligible (only builds no longer kept stood on it) or unrecorded (no build the builder's log holds stood " +
|
||||
"on it: never removed).",
|
||||
Run: func(args map[string]any) (any, error) {
|
||||
if text(args, "record") != "" {
|
||||
return nil, fmt.Errorf("mirrors records nothing on this seat: a copy is kept or let go of by the builds " +
|
||||
"the builder's log says stood on it (novox/hq ADR 0571 clause 1), and until that log names them every " +
|
||||
"copy is unrecorded and never removed")
|
||||
return nil, fmt.Errorf("mirrors records nothing by hand on this seat: a copy is recorded by the builds the " +
|
||||
"builder's log says stood on it (novox/hq ADR 0571 clause 1)")
|
||||
}
|
||||
v, err := s.View(context.Background())
|
||||
m, err := h.MirrorsNow()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var copies []string
|
||||
for _, name := range v.L.RepoNames() {
|
||||
if !mirrorRepository.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
for d := range v.L.Repos[name].Revisions {
|
||||
copies = append(copies, catalogue.ArtifactStoreScheme+name+"@"+d)
|
||||
}
|
||||
}
|
||||
sort.Strings(copies)
|
||||
return verbAnswer{Output: fmt.Sprintf("%d copies, each unrecorded and never removed\n", len(copies)), OK: true,
|
||||
Answer: map[string]any{"unrecorded": nonNil(copies)}}, nil
|
||||
return verbAnswer{Output: fmt.Sprintf("%d copies\n", len(m.Copies)), OK: true, Answer: m}, nil
|
||||
}},
|
||||
{Name: seat + "images", Description: "Every container image one machine's wants and haves name.",
|
||||
Run: func(args map[string]any) (any, error) {
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
"reads": [
|
||||
"mesh-delivery-assigner.wants",
|
||||
"mesh-delivery-builder.builds",
|
||||
"mesh-delivery-builder.build-log",
|
||||
"mesh-delivery-indexer.manifests",
|
||||
"node-engine.have",
|
||||
"mesh-controller.lease"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user