package main import ( "bytes" "encoding/json" "os" "path/filepath" "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" ) // novox/hq ADR 0225, issue 263: a consumer's identity is bounded by the provision it requires, an // overflow is refused before merge by `module check`, and a provider's machine is never refused for // one consumer's identity. // `module check` refuses the pull request that introduces an overflow, naming the module. func TestModuleCheckRefusesAnIdentityThatOverflowsWhatItRequires(t *testing.T) { dir := t.TempDir() write := func(name, body string) string { p := filepath.Join(dir, name+".json") if err := os.WriteFile(p, []byte(body), 0o600); err != nil { t.Fatal(err) } return p } objects := write("objects", `{"module":"objects","version":"1", "provides":[{"name":"s3-bucket","scope":"mesh","identity":{"max":20,"in":"an S3 access key"}}], "receives":{"s3-bucket":"/var/lib/mesh/objects/mesh.json"}}`) resolver := write("resolver", `{"module":"resolver","version":"1", "provides":[{"name":"wildcard-resolution","scope":"mesh","identity":false}]}`) album := write("photoalbum", `{"module":"photoalbum","version":"1","requires":["s3-bucket"]}`) nm := write("networkmanager", `{"module":"networkmanager","version":"1","requires":["wildcard-resolution"]}`) var out bytes.Buffer if err := moduleCheckFor([]string{resolver, nm}, 6, &out); err != nil { t.Fatalf("a long name requiring a keyless provision was refused (issue 263): %v\n%s", err, out.String()) } out.Reset() err := moduleCheckFor([]string{objects, album, resolver, nm}, 6, &out) if err == nil { t.Fatalf("an identity overflowing an S3 access key passed:\n%s", out.String()) } if !strings.Contains(out.String(), "photoalbum wants s3-bucket") || !strings.Contains(out.String(), "`slug` of at most 8 characters") || strings.Contains(out.String(), "networkmanager wants") { t.Fatalf("the refusal does not name the one overflowing module and its remedy:\n%s", out.String()) } } // Tonight's case, through the commands: networkmanager on a six-character machine requires the // resolver provision, and a second consumer there overflows an object store's access key. The // provider's machine still composes; the overflowing consumer is left out of its grants and named, // by push and by `status`, and the keyless consumer is granted with its long name. func TestAnOverflowingConsumerNeverRefusesItsProvidersMachine(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, catalogue.Manifest{Module: "objects", Version: "1", Provides: []catalogue.Offer{{Name: "s3-bucket", Scope: catalogue.ScopeMesh, Identity: &catalogue.OfferIdentity{Max: 20, In: "an S3 access key"}}}, Receives: map[string]string{"s3-bucket": "/var/lib/mesh/objects/mesh.json"}}) register(t, open, catalogue.Manifest{Module: "resolver", Version: "1", Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}}}) register(t, open, catalogue.Manifest{Module: "networkmanager", Version: "1", Requires: []string{"wildcard-resolution"}}) register(t, open, catalogue.Manifest{Module: "photoalbum", Version: "1", Requires: []string{"s3-bucket"}}) register(t, open, catalogue.Manifest{Module: "files", Version: "1", Requires: []string{"s3-bucket"}}) for _, a := range [][2]string{{"anchor", "objects"}, {"anchor", "resolver"}, {"laptop", "networkmanager"}, {"laptop", "photoalbum"}, {"laptop", "files"}} { if _, err := assign(ctx, open, a[0], a[1]); err != nil { t.Fatalf("assign %s %s: %v", a[0], a[1], err) } } // The consumer's machine resolves, and says which of its modules no provider will grant. consumer, _, err := planFor(ctx, open, "laptop") if err != nil { t.Fatal(err) } over := consumer.Overflowing() if len(over) != 1 || over[0].Module != "photoalbum" || over[0].Provision != "s3-bucket" { t.Fatalf("the consumer's side does not name exactly photoalbum: %+v", over) } // The provider's machine composes. Under ADR 0049's one bound this was a refusal naming // networkmanager, and no push to the provider could go through. plan, settings, err := planFor(ctx, open, "anchor") if err != nil { t.Fatal(err) } declared, err := declarationFor(ctx, open, "anchor", plan, settings) if err != nil { t.Fatalf("one consumer's identity refused its provider's whole machine: %v", err) } if len(declared.withheld) != 1 || declared.withheld[0].Identity != "mesh_laptop_photoalbum" { t.Fatalf("the overflowing consumer is not the one withheld: %+v", declared.withheld) } grants, _, _, err := grantsFor(ctx, open, "anchor") if err != nil { t.Fatal(err) } var keyless bool for _, g := range grants { keyless = keyless || g.Provision == "wildcard-resolution" && g.From == "networkmanager" } if !keyless { t.Fatalf("networkmanager, 26 characters, is not granted the keyless resolver provision: %+v", grants) } var granted []string for _, c := range declared.Received["objects"]["s3-bucket"] { granted = append(granted, c.From) } if strings.Join(granted, ",") != "files" { t.Fatalf("the object store grants %v; files and only files fit", granted) } said := printed(t, func() error { reportLeftOut("anchor", declared); return nil }) if !strings.Contains(said, `photoalbum on laptop requires s3-bucket from anchor`) || !strings.Contains(said, "left out of anchor's grants") { t.Fatalf("the push does not say whom it leaves out:\n%s", said) } // And `status` names it, and does not call the mesh well while it stands. asked, err := theThreeQuestions(ctx, open) if err != nil { t.Fatal(err) } if len(asked.overflowing) != 1 || asked.overflowing[0].Module != "photoalbum" { t.Fatalf("status does not carry the overflow: %+v", asked.overflowing) } if asked.well() { t.Fatal("a mesh with a consumer left out of its grants reads as well") } shown := printed(t, func() error { return printStatus(asked) }) if !strings.Contains(shown, "identified too long for a provision they require") || !strings.Contains(shown, "mesh_laptop_photoalbum") { t.Fatalf("status does not say it:\n%s", shown) } body, err := statusAsJSON(asked) if err != nil { t.Fatal(err) } var doc struct { Overflowing []catalogue.Overflow `json:"overflowing"` } if err := json.Unmarshal(body, &doc); err != nil || len(doc.Overflowing) != 1 || doc.Overflowing[0].Bound.Max != 20 { t.Fatalf("the document does not carry it: %v\n%s", err, body) } }