diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_test.go index 3fff5401..8841044f 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_test.go @@ -259,7 +259,7 @@ func TestTheToolsReadAndReleaseAnOutsideWait(t *testing.T) { r.cond("machine.hub.unreachable", "ace") r.review(false, nil) var acts []string - s := newPutTools(memStore{waitsState: &memKV{values: map[string][]byte{}}}, func(key string, body any) (json.RawMessage, error) { + s := newPutTools(memStore{waitsState: &memKV{values: map[string][]byte{}}, buckets.DeliveryWaitsState: &memKV{values: map[string][]byte{}}}, func(key string, body any) (json.RawMessage, error) { acts = append(acts, key) return json.RawMessage(`{"ok":true,"log":"logged"}`), nil }) @@ -296,4 +296,26 @@ func TestTheToolsReadAndReleaseAnOutsideWait(t *testing.T) { if r.bodies[len(r.bodies)-1].How != "go-on" || n.Haves["ace"]["nats"].Health != nodeengineclient.HealthOK { t.Errorf("last %+v health %s", r.bodies[len(r.bodies)-1], n.Haves["ace"]["nats"].Health) } + // A-246: a dependent's delivery wait is listed beside them, and released by its module and step delivery, its hand act first. + dw, _ := json.Marshal(deliveryWaiting{Wait: meshwants.Wait{Step: meshwants.WaitStepDelivery, Module: "app", Part: "db", On: "db@1.1.0", Machines: []string{"g14"}}}) + s.st.put(buckets.DeliveryWaitsState, "app", dw) + got, err = s.doWaits(map[string]any{"node": "g14"}) + if d := got.(map[string]any)["delivery"].([]deliveryWaiting); err != nil || len(d) != 1 || d[0].Module != "app" { + t.Errorf("the delivery waits listed = %+v %v", got, err) + } + if _, err := s.doRelease(map[string]any{"module": "app", "step": "build", "why": "x"}, "shanks.node-tools"); err == nil { + t.Error("a release of another step is taken") + } + if _, err := s.doRelease(map[string]any{"module": "web", "step": "delivery", "why": "x"}, "shanks.node-tools"); err == nil { + t.Error("a release of a module waiting for no delivery is taken") + } + before := len(acts) + if _, err := s.doRelease(map[string]any{"module": "app", "step": "delivery", "why": "db is fine as it runs"}, "shanks.node-tools"); err != nil { + t.Fatal(err) + } + raw, _, _, _ = s.st.get(buckets.DeliveryWaitsState, "app") + var released deliveryWaiting + if json.Unmarshal(raw, &released) != nil || !released.Released || !strings.Contains(released.By, "db is fine") || len(acts) != before+1 { + t.Errorf("the delivery wait after its release = %+v, hand acts %v", released, acts[before:]) + } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_tools.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_tools.go index 9d22b933..bd913301 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_tools.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_tools.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "errors" "fmt" "slices" @@ -9,7 +10,9 @@ import ( "time" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/keys" "mesh-delivery-assigner/internal/buckets" "mesh-delivery-assigner/internal/outside" @@ -58,7 +61,59 @@ func (s *putTools) doWaits(args map[string]any) (any, error) { } out = append(out, said) } - return map[string]any{"waits": out}, nil + // And each dependent's wait for its dependency's delivery (design 47 G §376, A-246), from the state delivery-waits. + delivery := []deliveryWaiting{} + dkeys, err := s.st.keys(buckets.DeliveryWaitsState) + if err != nil { + return nil, fmt.Errorf("the delivery waits cannot be listed: %w", err) + } + sort.Strings(dkeys) + for _, key := range dkeys { + raw, _, found, err := s.st.get(buckets.DeliveryWaitsState, key) + if err != nil || !found { + continue + } + var w deliveryWaiting + if json.Unmarshal(raw, &w) != nil || (arg(args, "module") != "" && w.Module != arg(args, "module")) || + (arg(args, "node") != "" && !slices.Contains(w.Machines, arg(args, "node"))) { + continue + } + delivery = append(delivery, w) + } + return map[string]any{"waits": out, "delivery": delivery}, nil +} + +// doReleaseDelivery is assigner_release with step delivery: the hand act is recorded first, then the release is written on the +// module's delivery wait, which the assigner's process ends released at its next pass, writing the module's want. +func (s *putTools) doReleaseDelivery(args map[string]any, by string) (any, error) { + module, why := arg(args, "module"), arg(args, "why") + if why == "" { + return nil, errors.New("release says why: it is kept in the hand-act log") + } + key := keys.EscapeName(module) + raw, _, found, err := s.st.get(buckets.DeliveryWaitsState, key) + if err != nil { + return nil, fmt.Errorf("the delivery wait of %s is not read: %w", module, err) + } + var w deliveryWaiting + if !found || json.Unmarshal(raw, &w) != nil || w.Released { + return nil, fmt.Errorf("%s waits for no delivery: assigner_waits lists the delivery waits", module) + } + rec := meshhandactsclient.Recorder{Ask: s.ask} + taken, err := rec.Record(meshhandactsclient.Request{What: fmt.Sprintf("assigner_release %s step delivery (waits on %s)", module, w.On), Why: why, + Cause: "delivery-wait", Verb: "mesh-wants.assigner_release"}) + if err != nil { + return nil, fmt.Errorf("the hand act could not be recorded, so nothing was released: %w", err) + } + w.Released, w.By = true, by+": "+why + data, err := json.Marshal(w) + if err != nil { + return nil, err + } + if err := s.st.put(buckets.DeliveryWaitsState, key, data); err != nil { + return nil, fmt.Errorf("the release of %s's delivery wait is not written: %w", module, err) + } + return map[string]any{"released": module, "step": meshwants.WaitStepDelivery, "by": by, "hand-act": taken.Log}, nil } // doReleaseWait is assigner_release with how: the hand act is recorded first, then the release is written on the wait of node and diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go index 4d82608c..849d3b66 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go @@ -19,6 +19,7 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/catalogkey" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/seatlist" @@ -209,6 +210,12 @@ func (s *putTools) doRelease(args map[string]any, by string) (any, error) { if arg(args, "how") != "" { return s.doReleaseWait(args, by) // a wait on an outside cause (outside_tools.go, design 47 G §706) } + if step := arg(args, "step"); step != "" { + if step != meshwants.WaitStepDelivery { + return nil, fmt.Errorf("step is %s, the one wait released by its step, not %q", meshwants.WaitStepDelivery, step) + } + return s.doReleaseDelivery(args, by) // a dependent's delivery wait (delivered.go, design 47 G §376, A-246) + } node, module, why := arg(args, "node"), arg(args, "module"), arg(args, "why") key, err := puts.Key(node, module) if err != nil { @@ -341,8 +348,9 @@ func (s *putTools) tools() []stdio.Tool { "rules write the machine's want again from their next pass. Nothing changes on the machine until they move it.", Input: schema(map[string]string{"node": "the machine", "module": "the module", "why": "why, kept with the put", "how": "with how, the wait of the delivery on an outside cause (assigner_waits) is released, not a put: go-on (the module counts healthy there), " + - "failed (the failing check is the delivery's) or rejudge (judge it again now); written in the hand-act log first"}, - []string{"node", "module", "why"}), + "failed (the failing check is the delivery's) or rejudge (judge it again now); written in the hand-act log first", + "step": "delivery: the module's wait for its dependency's delivery (assigner_waits) is released, its want written now; no machine is named; written in the hand-act log first"}, + []string{"module", "why"}), Run: func(a map[string]any) (any, error) { return s.doRelease(a, stdio.CallerOf(a)) }}, s.waitsTool(), {Name: toolPuts, Description: "Every put and where it stands (novox/hq ADR 0546): its version, hold, why, each step with " +