package inventory import ( "encoding/json" "reflect" "strings" "testing" ) // A send keeps the provisioner's plans it handed (design 57 P6, issue 672, migration 0100): the assignments mirror // carries the plan of the build that machine was sent, in the module's own assignment and no other, and a walk // reads which templates the send handed. func TestASendKeepsThePlansItHandedAndTheMirrorCarriesThem(t *testing.T) { inv, node := aNodeWithModules(t, "dunst", "web") ctx := t.Context() for _, m := range []string{"dunst", "web"} { if _, err := inv.Assign(ctx, node, m); err != nil { t.Fatal(err) } } record, err := inv.NodeByName(ctx, node) if err != nil { t.Fatal(err) } digest := "sha256:" + strings.Repeat("d", 64) plan := Provisioning{Templates: TemplatesArchive{Source: "http://store:5000/v2/dunst/files/blobs/" + digest, Digest: digest}, Files: []ProvisionedFile{{ID: "configuration", Path: "/home/op/.config/dunst/dunstrc", Template: "dunstrc", OwnerAtRuntime: "owned", Takes: "reload-on"}}} // A plan for a module the machine no longer has assigned is kept with the send and mirrored nowhere; one for a // module only pulled in by another is mirrored under its own key, saying what requires it (issue 765). raw := []byte(`{"dunst":` + mustJSON(t, plan) + `,"gone":` + mustJSON(t, plan) + `,"notify":` + strings.Replace(mustJSON(t, plan), `{`, `{"required-by":"desk",`, 1) + `}`) if err := inv.RecordSentWith(ctx, record.ID, "first", nil, 0, 0, Send{Digest: "first", Provisioning: raw}); err != nil { t.Fatal(err) } sent, _, err := inv.SentTo(ctx, node) if err != nil || !reflect.DeepEqual(sent.Provisioned, map[string]string{"dunst": digest, "gone": digest, "notify": digest}) { t.Fatalf("the send says it handed %v (%v)", sent.Provisioned, err) } plans, err := inv.SentProvisioningPlans(ctx) required := plan required.RequiredBy = "desk" if err != nil || !reflect.DeepEqual(plans, map[string]Provisioning{node + ".dunst": plan, node + ".notify": required}) { t.Fatalf("the plans mirrored are %+v (%v)", plans, err) } inv.SetProvisioningPlans(inv.SentProvisioningPlans) mirror, err := inv.AssignmentsMirror(ctx) if err != nil { t.Fatal(err) } var dunst, web AssignmentMirrored if err := json.Unmarshal(mirror[node+".dunst"], &dunst); err != nil { t.Fatal(err) } if err := json.Unmarshal(mirror[node+".web"], &web); err != nil { t.Fatal(err) } if dunst.Provisioning == nil || !reflect.DeepEqual(*dunst.Provisioning, plan) || web.Provisioning != nil { t.Fatalf("the mirror carries %+v for dunst and %+v for web", dunst.Provisioning, web.Provisioning) } var notify AssignmentMirrored if err := json.Unmarshal(mirror[node+".notify"], ¬ify); err != nil { t.Fatalf("no value for the pulled-in module: %s (%v)", mirror[node+".notify"], err) } if notify.RequiredBy != "desk" || !notify.Assigned.IsZero() || notify.Provisioning == nil || !reflect.DeepEqual(*notify.Provisioning, plan) { t.Fatalf("the mirror carries %s for the pulled-in module", mirror[node+".notify"]) } if _, has := mirror[node+".gone"]; has { t.Fatalf("a module no longer assigned is mirrored: %s", mirror[node+".gone"]) } // The next send hands nothing: nothing is kept, and the assignment's value is the bytes it was. if err := inv.RecordSentWith(ctx, record.ID, "second", nil, 0, 0, Send{Digest: "second"}); err != nil { t.Fatal(err) } if sent, _, err := inv.SentTo(ctx, node); err != nil || sent.Provisioned != nil { t.Fatalf("a send handing nothing says it handed %v (%v)", sent.Provisioned, err) } if mirror, err := inv.AssignmentsMirror(ctx); err != nil || strings.Contains(string(mirror[node+".dunst"]), "provisioning") { t.Fatalf("dunst's assignment still carries a plan: %s (%v)", mirror[node+".dunst"], err) } else if _, has := mirror[node+".notify"]; has { t.Fatalf("a module the machine no longer runs is mirrored: %s", mirror[node+".notify"]) } } func mustJSON(t *testing.T, v any) string { t.Helper() raw, err := json.Marshal(v) if err != nil { t.Fatal(err) } return string(raw) }