Browse code

Honor context cancellation when pruning

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>

Kenfe-Mickael Laventure authored on 2017/04/12 04:52:33
Showing 13 changed files
... ...
@@ -65,7 +65,7 @@ type attachBackend interface {
65 65
 
66 66
 // systemBackend includes functions to implement to provide system wide containers functionality
67 67
 type systemBackend interface {
68
-	ContainersPrune(pruneFilters filters.Args) (*types.ContainersPruneReport, error)
68
+	ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error)
69 69
 }
70 70
 
71 71
 // Backend is all the methods that need to be implemented to provide container specific functionality.
... ...
@@ -68,7 +68,7 @@ func (r *containerRouter) initRoutes() {
68 68
 		router.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
69 69
 		router.NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
70 70
 		router.NewPostRoute("/containers/{name:.*}/update", r.postContainerUpdate),
71
-		router.NewPostRoute("/containers/prune", r.postContainersPrune),
71
+		router.NewPostRoute("/containers/prune", r.postContainersPrune, router.WithCancel),
72 72
 		// PUT
73 73
 		router.NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
74 74
 		// DELETE
... ...
@@ -565,7 +565,7 @@ func (s *containerRouter) postContainersPrune(ctx context.Context, w http.Respon
565 565
 		return err
566 566
 	}
567 567
 
568
-	pruneReport, err := s.backend.ContainersPrune(pruneFilters)
568
+	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
569 569
 	if err != nil {
570 570
 		return err
571 571
 	}
... ...
@@ -30,7 +30,7 @@ type imageBackend interface {
30 30
 	Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error)
31 31
 	LookupImage(name string) (*types.ImageInspect, error)
32 32
 	TagImage(imageName, repository, tag string) error
33
-	ImagesPrune(pruneFilters filters.Args) (*types.ImagesPruneReport, error)
33
+	ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error)
34 34
 }
35 35
 
36 36
 type importExportBackend interface {
... ...
@@ -43,7 +43,7 @@ func (r *imageRouter) initRoutes() {
43 43
 		router.NewPostRoute("/images/create", r.postImagesCreate, router.WithCancel),
44 44
 		router.NewPostRoute("/images/{name:.*}/push", r.postImagesPush, router.WithCancel),
45 45
 		router.NewPostRoute("/images/{name:.*}/tag", r.postImagesTag),
46
-		router.NewPostRoute("/images/prune", r.postImagesPrune),
46
+		router.NewPostRoute("/images/prune", r.postImagesPrune, router.WithCancel),
47 47
 		// DELETE
48 48
 		router.NewDeleteRoute("/images/{name:.*}", r.deleteImages),
49 49
 	}
... ...
@@ -336,7 +336,7 @@ func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter
336 336
 		return err
337 337
 	}
338 338
 
339
-	pruneReport, err := s.backend.ImagesPrune(pruneFilters)
339
+	pruneReport, err := s.backend.ImagesPrune(ctx, pruneFilters)
340 340
 	if err != nil {
341 341
 		return err
342 342
 	}
... ...
@@ -1,6 +1,8 @@
1 1
 package network
2 2
 
3 3
 import (
4
+	"golang.org/x/net/context"
5
+
4 6
 	"github.com/docker/docker/api/types"
5 7
 	"github.com/docker/docker/api/types/filters"
6 8
 	"github.com/docker/docker/api/types/network"
... ...
@@ -16,5 +18,5 @@ type Backend interface {
16 16
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
17 17
 	DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
18 18
 	DeleteNetwork(name string) error
19
-	NetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error)
19
+	NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error)
20 20
 }
... ...
@@ -37,7 +37,7 @@ func (r *networkRouter) initRoutes() {
37 37
 		router.NewPostRoute("/networks/create", r.postNetworkCreate),
38 38
 		router.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
39 39
 		router.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
40
-		router.NewPostRoute("/networks/prune", r.postNetworksPrune),
40
+		router.NewPostRoute("/networks/prune", r.postNetworksPrune, router.WithCancel),
41 41
 		// DELETE
42 42
 		router.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
43 43
 	}
... ...
@@ -455,7 +455,7 @@ func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWr
455 455
 		return err
456 456
 	}
457 457
 
458
-	pruneReport, err := n.backend.NetworksPrune(pruneFilters)
458
+	pruneReport, err := n.backend.NetworksPrune(ctx, pruneFilters)
459 459
 	if err != nil {
460 460
 		return err
461 461
 	}
... ...
@@ -1,6 +1,8 @@
1 1
 package volume
2 2
 
3 3
 import (
4
+	"golang.org/x/net/context"
5
+
4 6
 	// TODO return types need to be refactored into pkg
5 7
 	"github.com/docker/docker/api/types"
6 8
 	"github.com/docker/docker/api/types/filters"
... ...
@@ -13,5 +15,5 @@ type Backend interface {
13 13
 	VolumeInspect(name string) (*types.Volume, error)
14 14
 	VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error)
15 15
 	VolumeRm(name string, force bool) error
16
-	VolumesPrune(pruneFilters filters.Args) (*types.VolumesPruneReport, error)
16
+	VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error)
17 17
 }
... ...
@@ -29,7 +29,7 @@ func (r *volumeRouter) initRoutes() {
29 29
 		router.NewGetRoute("/volumes/{name:.*}", r.getVolumeByName),
30 30
 		// POST
31 31
 		router.NewPostRoute("/volumes/create", r.postVolumesCreate),
32
-		router.NewPostRoute("/volumes/prune", r.postVolumesPrune),
32
+		router.NewPostRoute("/volumes/prune", r.postVolumesPrune, router.WithCancel),
33 33
 		// DELETE
34 34
 		router.NewDeleteRoute("/volumes/{name:.*}", r.deleteVolumes),
35 35
 	}
... ...
@@ -77,7 +77,7 @@ func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWrit
77 77
 		return err
78 78
 	}
79 79
 
80
-	pruneReport, err := v.backend.VolumesPrune(pruneFilters)
80
+	pruneReport, err := v.backend.VolumesPrune(ctx, pruneFilters)
81 81
 	if err != nil {
82 82
 		return err
83 83
 	}
... ...
@@ -5,6 +5,8 @@ import (
5 5
 	"regexp"
6 6
 	"time"
7 7
 
8
+	"golang.org/x/net/context"
9
+
8 10
 	"github.com/Sirupsen/logrus"
9 11
 	"github.com/docker/distribution/reference"
10 12
 	"github.com/docker/docker/api/types"
... ...
@@ -20,7 +22,7 @@ import (
20 20
 )
21 21
 
22 22
 // ContainersPrune removes unused containers
23
-func (daemon *Daemon) ContainersPrune(pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
23
+func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
24 24
 	rep := &types.ContainersPruneReport{}
25 25
 
26 26
 	until, err := getUntilFromPruneFilters(pruneFilters)
... ...
@@ -30,6 +32,13 @@ func (daemon *Daemon) ContainersPrune(pruneFilters filters.Args) (*types.Contain
30 30
 
31 31
 	allContainers := daemon.List()
32 32
 	for _, c := range allContainers {
33
+		select {
34
+		case <-ctx.Done():
35
+			logrus.Warnf("ContainersPrune operation cancelled: %#v", *rep)
36
+			return rep, ctx.Err()
37
+		default:
38
+		}
39
+
33 40
 		if !c.IsRunning() {
34 41
 			if !until.IsZero() && c.Created.After(until) {
35 42
 				continue
... ...
@@ -55,10 +64,17 @@ func (daemon *Daemon) ContainersPrune(pruneFilters filters.Args) (*types.Contain
55 55
 }
56 56
 
57 57
 // VolumesPrune removes unused local volumes
58
-func (daemon *Daemon) VolumesPrune(pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
58
+func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
59 59
 	rep := &types.VolumesPruneReport{}
60 60
 
61 61
 	pruneVols := func(v volume.Volume) error {
62
+		select {
63
+		case <-ctx.Done():
64
+			logrus.Warnf("VolumesPrune operation cancelled: %#v", *rep)
65
+			return ctx.Err()
66
+		default:
67
+		}
68
+
62 69
 		name := v.Name()
63 70
 		refs := daemon.volumes.Refs(v)
64 71
 
... ...
@@ -91,7 +107,7 @@ func (daemon *Daemon) VolumesPrune(pruneFilters filters.Args) (*types.VolumesPru
91 91
 }
92 92
 
93 93
 // ImagesPrune removes unused images
94
-func (daemon *Daemon) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
94
+func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
95 95
 	rep := &types.ImagesPruneReport{}
96 96
 
97 97
 	danglingOnly := true
... ...
@@ -117,27 +133,47 @@ func (daemon *Daemon) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPrune
117 117
 	allContainers := daemon.List()
118 118
 	imageRefs := map[string]bool{}
119 119
 	for _, c := range allContainers {
120
-		imageRefs[c.ID] = true
120
+		select {
121
+		case <-ctx.Done():
122
+			return nil, ctx.Err()
123
+		default:
124
+			imageRefs[c.ID] = true
125
+		}
121 126
 	}
122 127
 
123 128
 	// Filter intermediary images and get their unique size
124 129
 	allLayers := daemon.layerStore.Map()
125 130
 	topImages := map[image.ID]*image.Image{}
126 131
 	for id, img := range allImages {
127
-		dgst := digest.Digest(id)
128
-		if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
129
-			continue
130
-		}
131
-		if !until.IsZero() && img.Created.After(until) {
132
-			continue
133
-		}
134
-		if !matchLabels(pruneFilters, img.Config.Labels) {
135
-			continue
132
+		select {
133
+		case <-ctx.Done():
134
+			return nil, ctx.Err()
135
+		default:
136
+			dgst := digest.Digest(id)
137
+			if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
138
+				continue
139
+			}
140
+			if !until.IsZero() && img.Created.After(until) {
141
+				continue
142
+			}
143
+			if !matchLabels(pruneFilters, img.Config.Labels) {
144
+				continue
145
+			}
146
+			topImages[id] = img
136 147
 		}
137
-		topImages[id] = img
138 148
 	}
139 149
 
150
+	canceled := false
151
+deleteImagesLoop:
140 152
 	for id := range topImages {
153
+		select {
154
+		case <-ctx.Done():
155
+			// we still want to calculate freed size and return the data
156
+			canceled = true
157
+			break deleteImagesLoop
158
+		default:
159
+		}
160
+
141 161
 		dgst := digest.Digest(id)
142 162
 		hex := dgst.Hex()
143 163
 		if _, ok := imageRefs[hex]; ok {
... ...
@@ -198,17 +234,27 @@ func (daemon *Daemon) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPrune
198 198
 		}
199 199
 	}
200 200
 
201
+	if canceled {
202
+		logrus.Warnf("ImagesPrune operation cancelled: %#v", *rep)
203
+		return nil, ctx.Err()
204
+	}
205
+
201 206
 	return rep, nil
202 207
 }
203 208
 
204 209
 // localNetworksPrune removes unused local networks
205
-func (daemon *Daemon) localNetworksPrune(pruneFilters filters.Args) *types.NetworksPruneReport {
210
+func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
206 211
 	rep := &types.NetworksPruneReport{}
207 212
 
208 213
 	until, _ := getUntilFromPruneFilters(pruneFilters)
209 214
 
210 215
 	// When the function returns true, the walk will stop.
211 216
 	l := func(nw libnetwork.Network) bool {
217
+		select {
218
+		case <-ctx.Done():
219
+			return true
220
+		default:
221
+		}
212 222
 		if !until.IsZero() && nw.Info().Created().After(until) {
213 223
 			return false
214 224
 		}
... ...
@@ -234,7 +280,7 @@ func (daemon *Daemon) localNetworksPrune(pruneFilters filters.Args) *types.Netwo
234 234
 }
235 235
 
236 236
 // clusterNetworksPrune removes unused cluster networks
237
-func (daemon *Daemon) clusterNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
237
+func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
238 238
 	rep := &types.NetworksPruneReport{}
239 239
 
240 240
 	until, _ := getUntilFromPruneFilters(pruneFilters)
... ...
@@ -251,46 +297,59 @@ func (daemon *Daemon) clusterNetworksPrune(pruneFilters filters.Args) (*types.Ne
251 251
 	}
252 252
 	networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
253 253
 	for _, nw := range networks {
254
-		if nw.Ingress {
255
-			// Routing-mesh network removal has to be explicitly invoked by user
256
-			continue
257
-		}
258
-		if !until.IsZero() && nw.Created.After(until) {
259
-			continue
260
-		}
261
-		if !matchLabels(pruneFilters, nw.Labels) {
262
-			continue
263
-		}
264
-		// https://github.com/docker/docker/issues/24186
265
-		// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
266
-		// So we try to remove it anyway and check the error
267
-		err = cluster.RemoveNetwork(nw.ID)
268
-		if err != nil {
269
-			// we can safely ignore the "network .. is in use" error
270
-			match := networkIsInUse.FindStringSubmatch(err.Error())
271
-			if len(match) != 2 || match[1] != nw.ID {
272
-				logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err)
254
+		select {
255
+		case <-ctx.Done():
256
+			return rep, ctx.Err()
257
+		default:
258
+			if nw.Ingress {
259
+				// Routing-mesh network removal has to be explicitly invoked by user
260
+				continue
273 261
 			}
274
-			continue
262
+			if !until.IsZero() && nw.Created.After(until) {
263
+				continue
264
+			}
265
+			if !matchLabels(pruneFilters, nw.Labels) {
266
+				continue
267
+			}
268
+			// https://github.com/docker/docker/issues/24186
269
+			// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
270
+			// So we try to remove it anyway and check the error
271
+			err = cluster.RemoveNetwork(nw.ID)
272
+			if err != nil {
273
+				// we can safely ignore the "network .. is in use" error
274
+				match := networkIsInUse.FindStringSubmatch(err.Error())
275
+				if len(match) != 2 || match[1] != nw.ID {
276
+					logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err)
277
+				}
278
+				continue
279
+			}
280
+			rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
275 281
 		}
276
-		rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
277 282
 	}
278 283
 	return rep, nil
279 284
 }
280 285
 
281 286
 // NetworksPrune removes unused networks
282
-func (daemon *Daemon) NetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
287
+func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
283 288
 	if _, err := getUntilFromPruneFilters(pruneFilters); err != nil {
284 289
 		return nil, err
285 290
 	}
286 291
 
287 292
 	rep := &types.NetworksPruneReport{}
288
-	if clusterRep, err := daemon.clusterNetworksPrune(pruneFilters); err == nil {
293
+	if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil {
289 294
 		rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
290 295
 	}
291 296
 
292
-	localRep := daemon.localNetworksPrune(pruneFilters)
297
+	localRep := daemon.localNetworksPrune(ctx, pruneFilters)
293 298
 	rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)
299
+
300
+	select {
301
+	case <-ctx.Done():
302
+		logrus.Warnf("NetworksPrune operation cancelled: %#v", *rep)
303
+		return nil, ctx.Err()
304
+	default:
305
+	}
306
+
294 307
 	return rep, nil
295 308
 }
296 309