diff --git a/cmd/mesh-controller/container_groups_test.go b/cmd/mesh-controller/container_groups_test.go new file mode 100644 index 00000000..6fd9f9ba --- /dev/null +++ b/cmd/mesh-controller/container_groups_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "io" + "os" + "strings" + "testing" + + "git.novox.be/novox/mesh-sdk/go/catalogue" +) + +// Issue 1638 (novox/hq ADR 0480 clause 7), taken in with mesh-sdk's container `groups`: the controller sends a container's +// groups only to a node-engine that reports the capability container-groups, and holds the module on any other, naming it. + +const n8nWithAGroup = `{"module":"n8n","version":"1", + "config":{"media-gid":{"type":"number","minimum":1,"required":true,"description":"the group that owns the media library"}}, + "resources":[{"id":"server","type":"container","name":"n8n", + "image":"n8nio/n8n@sha256:4846eb2f4b874ab04cde7fc1e249d2ddaec66e9aea64439beb2972cfea88e3c0", + "groups":["${config:media-gid}"]}]}` + +// printedBy runs reportUnhostable and returns what it printed. +func printedBy(t *testing.T, node string, plan catalogue.Resolution) string { + t.Helper() + stdout := os.Stdout + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + os.Stdout = w + reportUnhostable(node, plan) + w.Close() + os.Stdout = stdout + out, _ := io.ReadAll(r) + return string(out) +} + +// 1638/AC1: a node that reports the capability gets the module; one that does not has it held, the capability named. +func TestAModuleWithGroupsIsHeldOnANodeThatDoesNotReportTheCapability(t *testing.T) { + m, err := catalogue.ParseManifest([]byte(n8nWithAGroup)) + if err != nil { + t.Fatal(err) + } + shelf := catalogue.Shelf{"n8n": m} + reads := catalogue.Node{Name: "ace", Capabilities: map[string]bool{"container-runtime": true, "container-groups": true}} + old := catalogue.Node{Name: "g14", Capabilities: map[string]bool{"container-runtime": true}} + + got, err := catalogue.Resolve(shelf, []string{"n8n"}, reads, catalogue.World{}) + if err != nil || len(got.Unhostable) != 0 || len(got.Modules) != 1 { + t.Fatalf("a node that reads groups: %v, held %+v", err, got.Unhostable) + } + if out := printedBy(t, "ace", got); out != "" { + t.Errorf("a node that reads groups was told: %q", out) + } + + got, err = catalogue.Resolve(shelf, []string{"n8n"}, old, catalogue.World{}) + if err != nil { + t.Fatal(err) + } + if len(got.Modules) != 0 || len(got.Unhostable) != 1 { + t.Fatalf("n8n was sent to a node that does not report container-groups: hosted %d, held %+v", len(got.Modules), got.Unhostable) + } + if out := printedBy(t, "g14", got); !strings.Contains(out, "n8n") || !strings.Contains(out, "container-groups") { + t.Errorf("the hold does not name the module and the capability: %q", out) + } +} diff --git a/go.mod b/go.mod index daef6dbe..dd814bad 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/novox/mesh-controller go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021132116-90b20f5a3bda + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021144048-d4deecdb380b github.com/jackc/pgx/v5 v5.10.0 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/go.sum b/go.sum index a1899956..e67f5187 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5 h1:E5stfIeCN6uYgA9TCv+4K8JgaURg3zQppvUgJLaDMP4= git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5/go.mod h1:4wyoCr6IIIIarEUuROThWDj7DAvJvIidqMjWcpSnOwE= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021132116-90b20f5a3bda h1:i1n7nr8fZ0f7qnPYnrhHO1yc7FSyrkT8a61/ac5WuW0= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021132116-90b20f5a3bda/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021144048-d4deecdb380b h1:5UlQ8ytlHNgqlm5AbwJEnZwuf1EWUiNyXX2hcOV9Yro= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021144048-d4deecdb380b/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/testdata/beside/CAPTURED b/testdata/beside/CAPTURED index f51f92cf..786b4ff3 100644 --- a/testdata/beside/CAPTURED +++ b/testdata/beside/CAPTURED @@ -9,7 +9,7 @@ a second place to clean besides the catalogue itself. mesh-catalog e0bc919d4945bb5f13f971f74643a0b714cf45da modules/*/module.json, modules/nats/Dockerfile mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock -mesh-sdk 90b20f5a3bda658be5fa3c868f0d9de1876521a0 conformance/events/module-event.json +mesh-sdk d4deecdb380b69358aae2fdaedcfcc06e1571d42 conformance/events/module-event.json To move them, from this repository's root, with the two repositories checked out beside it: diff --git a/vendor/modules.txt b/vendor/modules.txt index cb946391..c0114ffc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021132116-90b20f5a3bda +# git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021144048-d4deecdb380b ## explicit; go 1.26.0 git.novox.be/novox/mesh-sdk/go/asks git.novox.be/novox/mesh-sdk/go/catalogue