diff --git a/cmd/mesh-controller/rollout_first_test.go b/cmd/mesh-controller/rollout_first_test.go index 15587bb..cb8ce79 100644 --- a/cmd/mesh-controller/rollout_first_test.go +++ b/cmd/mesh-controller/rollout_first_test.go @@ -82,3 +82,22 @@ func TestAPlanSendsOneMachineFirstAndTheRestAfterItsReport(t *testing.T) { t.Fatalf("a module on one machine waited for more: %+v", step) } } + +// An announced move of a module an open plan is still rolling out is left to the plan: sending it +// here as well put the bundle on every machine at once (novox/hq issue 249). +func TestAnAnnouncedMoveIsLeftToThePlanRollingItOut(t *testing.T) { + sent := time.Now() + plans := []inventory.Plan{ + {ID: "plan-done", State: inventory.PlanDone, Modules: map[string]*inventory.PlanModule{"agent": {}}}, + {ID: "plan-1", State: inventory.PlanRolling, Modules: map[string]*inventory.PlanModule{ + "agent": {State: "built", First: []string{"ace"}, FirstAt: &sent}, "gitea": {State: "built", SentAt: &sent}}}, + } + if got := rolledOutByAPlan(plans, "agent"); got != "plan-1" { + t.Fatalf("a module the plan is rolling out was not left to it: %q", got) + } + for _, m := range []string{"gitea", "keycloak"} { + if got := rolledOutByAPlan(plans, m); got != "" { + t.Errorf("%s, which no plan will send, was left to %s", m, got) + } + } +} diff --git a/cmd/mesh-controller/upgrades.go b/cmd/mesh-controller/upgrades.go index 50389b6..3c71189 100644 --- a/cmd/mesh-controller/upgrades.go +++ b/cmd/mesh-controller/upgrades.go @@ -56,6 +56,18 @@ func (f following) Upgraded(ctx context.Context, u link.Upgraded) error { return nil } + // **A plan that holds the module rolls it out, and this does not** (novox/hq issue 249, ADR + // 0218). A merge's plan builds the module and sends it one machine first, the rest once that one + // has applied it; this announcement arrives as the build registers, and sending here too — one + // machine after another without waiting for any to apply — put the new bundle on every machine in + // the same minute, whatever the plan was waiting for. A move no plan answers (a build asked by + // hand) is still this handler's. + if plans, err := inv.OpenPlans(ctx); err != nil { + return notNow(err) + } else if id := rolledOutByAPlan(plans, u.Module); id != "" { + fmt.Printf("%s moved to %s; %s rolls it out to %s\n", u.Module, shortCommit(u.Commit), id, readableList(on)) + return nil + } if decision.Together { fmt.Printf("%s moved to %s; sending %s together\n", u.Module, shortCommit(u.Commit), readableList(on)) @@ -77,6 +89,17 @@ func (f following) Upgraded(ctx context.Context, u link.Upgraded) error { return nil } +// rolledOutByAPlan is the open plan that will send a module's machines its new build — one holding +// the module that has not finished sending it — or empty when none will (novox/hq issue 249). +func rolledOutByAPlan(plans []inventory.Plan, module string) string { + for _, p := range plans { + if s, holds := p.Modules[module]; p.Open() && holds && (s == nil || s.SentAt == nil) { + return p.ID + } + } + return "" +} + // readableList names machines the way a sentence does, because this is read by a person deciding // whether an upgrade went where they expected. func readableList(names []string) string {