diff --git a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go index 7bd28b50..489d5d28 100644 --- a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go +++ b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go @@ -120,7 +120,7 @@ type Client struct { // Now dates the reports; nil is time.Now. Now func() time.Time // SocketUp wakes the client when the node-engine's write socket is made again (watchSocket); nil never does. - // A module whose write failed for want of the socket is filled again then (issue 1165), and nothing else is. + // A module whose write failed is filled again then (issue 1165, issue 1873), and nothing else is. SocketUp <-chan struct{} // watchBound watches a provider's bucket of entries for this machine's keys, once per run (consumers.go); set by @@ -150,7 +150,8 @@ type state struct { // of the consumers left out of one of its files. providing map[string]bool leftOut map[string]string - // unplaced is a module whose last fill failed for want of the node-engine's write socket (issue 1165). + // unplaced is a module whose last fill failed, for want of the node-engine's write socket (issue 1165) or by the + // engine's own refusal (issue 1873): filled again when the socket is made again. unplaced map[string]bool // bound is each provider's bucket of entries this client watches, by key: this machine's consumers' entries alone; // boundReady is a bucket whose current entries are all read, and boundUsers the modules filled from each. @@ -705,9 +706,10 @@ func (c *Client) reconcile(ctx context.Context, kvs map[string]jetstream.KeyValu err = errFill(a) } if err != nil { - if socketAbsent(err) { - s.unplaced[module] = true - } + // Filled again when a node-engine makes its socket again: one gone (issue 1165), or one that refused for a + // reason of its own, which a newer engine may write (issue 1873: "is a directory", what a container + // runtime left at the path, cleared by the engine that followed). + s.unplaced[module] = true failed = append(failed, fmt.Sprintf("%s: %v", f.Path, err)) continue } diff --git a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/socket_integration_test.go b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/socket_integration_test.go index 094a7e29..049b0be7 100644 --- a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/socket_integration_test.go +++ b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/socket_integration_test.go @@ -106,3 +106,38 @@ func TestWatchSocketWakesWhenTheSocketIsMadeAndWhenItsDirectoryReturns(t *testin defer l.Close() heard("the directory and socket back") } + +// Issue 1873: a fill the node-engine refused for a reason of its own (on ace, "read …/superuser.secret: is a +// directory", what a container runtime had left there) is tried again when a node-engine makes its socket again, as a +// newer engine may write what the older could not. Before, only a fill that failed for want of the socket was, so +// ace's postgres stayed unplaced after the engine that cures it was delivered, and its old failure was said every 30 s. +func TestNatsAFillTheEngineFailedIsTriedWhenTheSocketReturns(t *testing.T) { + b := startBus(t) + heard := b.listen(t) + e := newEngine(t, map[string]string{dunstrc: "owned"}) + if err := os.MkdirAll(e.at(dunstrc), 0o755); err != nil { + t.Fatal(err) + } + stop := e.listen(t) + b.config(t, "dunst", "font-size", "13") + b.assign(t, "dunst", dunst(t)) + aClient(t, b, e, t.TempDir()) + + eventually(t, "the engine's failure is said", func() bool { + r, _ := heard.last("dunst") + return r.Status == StatusFailed && strings.Contains(r.Reason, "is a directory") + }) + // The node-engine replaces itself with one that clears what the runtime left. + stop() + if err := os.Remove(e.at(dunstrc)); err != nil { + t.Fatal(err) + } + e.listen(t) + eventually(t, "dunstrc is written by the new engine", func() bool { + return strings.Contains(e.read(t, dunstrc), "Monospace 13") + }) + eventually(t, "and the report says applied", func() bool { + r, _ := heard.last("dunst") + return r.Status == StatusApplied + }) +}