Keep a fresh build's archives from the controller's own sweep (issue 1933)
mesh/delivery delivered
mesh/check checking 0741880d3137 on 760351b4ae1b
mesh/delivery delivered
mesh/check checking 0741880d3137 on 760351b4ae1b
A sweep read what it keeps before what was ever recorded, so a take-in's build recorded between the two reads, by a concurrent take-in off the plans' hold, was eligible and let go of seconds after the take-in read it; a new module's build is unkept until it is registered. Now the sweep reads the records first and keeps what a build recorded within the hour or an open plan names, and names every reference it lets go of; a take-in holds every artifact in the store before recording, failing on one the store lacks; and RecordBuild clears artifact_collected as RecordMirrored does.
This commit is contained in:
@@ -2,8 +2,10 @@ package inventory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
@@ -40,8 +42,17 @@ const (
|
||||
// builds of a module the mesh still holds stood on it (novox/hq ADR 0257). A copy has no builds of
|
||||
// its own; it is kept by the builds that used it.
|
||||
KeptStoodOn = "stood-on"
|
||||
// KeptByRecentRecord: a build recorded it within KeptLately, so a take-in or a build may still be registering
|
||||
// it, and no definition or count of recent builds can speak for it yet (novox/hq issue 1933).
|
||||
KeptByRecentRecord = "recorded-lately"
|
||||
// KeptByOpenPlan: a plan that has not ended names the build that made it (novox/hq issue 1933).
|
||||
KeptByOpenPlan = "open-plan"
|
||||
)
|
||||
|
||||
// KeptLately is how long after a build is recorded its artifacts are kept whatever else the records say (novox/hq
|
||||
// issue 1933).
|
||||
const KeptLately = time.Hour
|
||||
|
||||
// The states an artifact the mesh recorded making is in (novox/hq ADR 0251).
|
||||
const (
|
||||
ArtifactKept = "kept"
|
||||
@@ -76,15 +87,26 @@ type ArtifactState struct {
|
||||
// In a stated order, so two runs over the same records ask for the same things in the same
|
||||
// sequence, which is what makes a failed sweep safe to simply run again.
|
||||
func (i *Inventory) Artifacts(ctx context.Context) ([]ArtifactState, error) {
|
||||
keep, err := i.KeepSet(ctx)
|
||||
return artifactStates(ctx, i.everyReferenceRecorded, i.KeepSet, i.alreadyCollected)
|
||||
}
|
||||
|
||||
// artifactStates is Artifacts from its three reads, which a test gives.
|
||||
func artifactStates(ctx context.Context, recorded func(context.Context) ([]string, error),
|
||||
keepSet func(context.Context) (map[string][]string, error),
|
||||
alreadyCollected func(context.Context) (map[string]bool, error)) ([]ArtifactState, error) {
|
||||
// **What was recorded is read before what is kept** (novox/hq issue 1933). A build recorded between the two reads
|
||||
// is then either not among what was recorded, or kept as recorded lately; read the other way round, a take-in's
|
||||
// build landing between them was recorded and not kept, and the sweep let go of it seconds after the take-in read
|
||||
// its archive.
|
||||
all, err := recorded(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all, err := i.everyReferenceRecorded(ctx)
|
||||
keep, err := keepSet(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
collected, err := i.alreadyCollected(ctx)
|
||||
collected, err := alreadyCollected(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,6 +184,16 @@ func (i *Inventory) KeepSet(ctx context.Context) (map[string][]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// **What a build recorded lately, or a plan that has not ended names** (novox/hq issue 1933).
|
||||
lately, err := i.keptLatelyOrPlanned(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for reference, whys := range lately {
|
||||
for _, why := range whys {
|
||||
because(reference, why)
|
||||
}
|
||||
}
|
||||
var stoodOn []string
|
||||
for _, r := range recent {
|
||||
for _, a := range r.made {
|
||||
@@ -388,6 +420,77 @@ func asRecorded(reference string) string {
|
||||
return catalogue.Recorded(reference)
|
||||
}
|
||||
|
||||
// keptLately is, of these builds, the artifacts kept because their build was recorded within the window before now, or is
|
||||
// named by a plan that has not ended, each with its reasons.
|
||||
//
|
||||
// **Kept until the take-in or the build that recorded it is done** (novox/hq issue 1933). Between a build's record and
|
||||
// its module's registration, nothing else speaks for it: a new module is not yet held, so no count of its recent builds
|
||||
// keeps it, and no definition names it yet. A plan that has not ended may still send it, however long it runs.
|
||||
func keptLately(builds []Build, planned map[string]bool, now time.Time, within time.Duration) map[string][]string {
|
||||
out := map[string][]string{}
|
||||
because := func(reference, why string) {
|
||||
if !slices.Contains(out[reference], why) {
|
||||
out[reference] = append(out[reference], why)
|
||||
}
|
||||
}
|
||||
for _, b := range builds {
|
||||
if !b.Worked() {
|
||||
continue
|
||||
}
|
||||
lately := within > 0 && now.Sub(b.At) < within
|
||||
named := planned[b.ID]
|
||||
if !lately && !named {
|
||||
continue
|
||||
}
|
||||
for _, a := range b.Made {
|
||||
reference := asRecorded(a.Reference)
|
||||
if reference == "" {
|
||||
continue
|
||||
}
|
||||
if lately {
|
||||
because(reference, KeptByRecentRecord)
|
||||
}
|
||||
if named {
|
||||
because(reference, KeptByOpenPlan)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// keptLatelyOrPlanned is keptLately of every build the records hold, against the plans that have not ended.
|
||||
func (i *Inventory) keptLatelyOrPlanned(ctx context.Context) (map[string][]string, error) {
|
||||
c, err := i.theChain(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all, err := chainBuilds(ctx, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plans, err := i.OpenPlans(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keptLately(all, plannedBuilds(plans), time.Now().UTC(), i.lately()), nil
|
||||
}
|
||||
|
||||
// plannedBuilds is every build id these plans name: a module's build, and the builds a rollout carried.
|
||||
func plannedBuilds(plans []Plan) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for _, p := range plans {
|
||||
for _, m := range p.Modules {
|
||||
if m != nil && m.Build != "" {
|
||||
out[m.Build] = true
|
||||
}
|
||||
}
|
||||
for _, id := range p.Builds {
|
||||
out[id] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// digestIn is the `sha256:<hex>` a reference names, empty when it names none.
|
||||
func digestIn(reference string) string {
|
||||
for _, marker := range []string{"@sha256:", "/sha256:"} {
|
||||
|
||||
Reference in New Issue
Block a user