Read volume sizes only when asked and remove in bounded batches, so docker_volumes_remove_anonymous answers on a machine with over a thousand volumes (issue 1796)
mesh/merge-gate building on shanks since 00:21
mesh/delivery superseded: a newer head of the same pull request

This commit is contained in:
2026-10-22 00:20:32 +02:00
parent a3794ea156
commit bfc42e4f4f
4 changed files with 98 additions and 24 deletions
+26 -2
View File
@@ -445,7 +445,7 @@ func TestRemoveAnonymousVolumesTakesOnlyTheUnmountedAnonymousOnes(t *testing.T)
on("docker volume rm old", Ran{Stdout: "old\n"})
c := client(f, 1000)
before := time.Date(2026, 10, 3, 0, 0, 0, 0, time.UTC)
got, err := c.RemoveAnonymousVolumes(context.Background(), RemoveVolumesAsk{DryRun: true, CreatedBefore: &before})
got, err := c.RemoveAnonymousVolumes(context.Background(), RemoveVolumesAsk{DryRun: true, Sizes: true, CreatedBefore: &before})
if err != nil {
t.Fatal(err)
}
@@ -463,7 +463,7 @@ func TestRemoveAnonymousVolumesTakesOnlyTheUnmountedAnonymousOnes(t *testing.T)
if len(why) != 3 || !strings.Contains(why["webdata"], "mesh holds") || why["young"] == "" || why["named"] == "" {
t.Fatalf("skipped: %v", skipped)
}
got, err = c.RemoveAnonymousVolumes(context.Background(), RemoveVolumesAsk{Why: "leftovers", CreatedBefore: &before})
got, err = c.RemoveAnonymousVolumes(context.Background(), RemoveVolumesAsk{Why: "leftovers", Sizes: true, CreatedBefore: &before})
if err != nil {
t.Fatal(err)
}
@@ -501,3 +501,27 @@ func TestRemoveAnonymousVolumesRefusesANamedVolumeAndKeepsWhatTheMeshHolds(t *te
}
t.Fatal("tool not served")
}
// 1796/AC3: on a machine with a very large set of volumes docker's size listing does not answer in
// time; the tool still answers, and removal never depends on it.
func TestRemoveAnonymousVolumesAnswersWhenTheSizeListingDoesNot(t *testing.T) {
f := (&fake{}).
on("docker ps --all --quiet --no-trunc", Ran{}).
on("docker volume ls --quiet", Ran{Stdout: "a1\na2\na3\n"}).
on("docker volume inspect", Ran{Stdout: `[{"Name":"a1","Labels":{"com.docker.volume.anonymous":""}},{"Name":"a2","Labels":{"com.docker.volume.anonymous":""}},{"Name":"a3","Labels":{"com.docker.volume.anonymous":""}}]`}).
on("docker system df", Ran{Status: 124, Err: "no answer within 20 s"}).
on("docker volume rm", Ran{Stdout: "ok\n"})
c := client(f, 1000)
got, err := c.RemoveAnonymousVolumes(context.Background(), RemoveVolumesAsk{DryRun: true})
if err != nil || f.ran("docker system df") || !reflect.DeepEqual(got["would_remove"], []string{"a1", "a2", "a3"}) {
t.Fatalf("default dry run: %v %v %+v", got, err, f.calls)
}
got, err = c.RemoveAnonymousVolumes(context.Background(), RemoveVolumesAsk{DryRun: true, Sizes: true})
if err != nil || !reflect.DeepEqual(got["would_remove"], []string{"a1", "a2", "a3"}) || got["sizes_unknown"] == nil {
t.Fatalf("sizes asked, df silent: %v %v", got, err)
}
got, err = c.RemoveAnonymousVolumes(context.Background(), RemoveVolumesAsk{Why: "w", Sizes: true, Limit: 2})
if err != nil || got["count"] != 2 || got["remaining"] != 1 || !f.ran("docker volume rm a1 a2") {
t.Fatalf("removal: %v %v %+v", got, err, f.calls)
}
}