... | ... |
@@ -45,14 +45,28 @@ images.` |
45 | 45 |
%[1]s %[2]s --keep-tag-revisions=3 --keep-younger-than=60m |
46 | 46 |
|
47 | 47 |
# To actually perform the prune operation, the confirm flag must be appended |
48 |
- %[1]s %[2]s --keep-tag-revisions=3 --keep-younger-than=60m --confirm` |
|
48 |
+ %[1]s %[2]s --keep-tag-revisions=3 --keep-younger-than=60m --confirm |
|
49 |
+ |
|
50 |
+ # See, what the prune command would delete if we're interested in removing images |
|
51 |
+ # exceeding currently set LimitRanges ('openshift.io/Image') |
|
52 |
+ %[1]s %[2]s --prune-over-size-limit |
|
53 |
+ |
|
54 |
+ # To actually perform the prune operation, the confirm flag must be appended |
|
55 |
+ %[1]s %[2]s --prune-over-size-limit --confirm` |
|
56 |
+) |
|
57 |
+ |
|
58 |
+var ( |
|
59 |
+ defaultKeepYoungerThan = 60 * time.Minute |
|
60 |
+ defaultKeepTagRevisions = 3 |
|
61 |
+ defaultPruneImageOverSizeLimit = false |
|
49 | 62 |
) |
50 | 63 |
|
51 | 64 |
// PruneImagesOptions holds all the required options for pruning images. |
52 | 65 |
type PruneImagesOptions struct { |
53 | 66 |
Confirm bool |
54 |
- KeepYoungerThan time.Duration |
|
55 |
- KeepTagRevisions int |
|
67 |
+ KeepYoungerThan *time.Duration |
|
68 |
+ KeepTagRevisions *int |
|
69 |
+ PruneOverSizeLimit *bool |
|
56 | 70 |
CABundle string |
57 | 71 |
RegistryUrlOverride string |
58 | 72 |
|
... | ... |
@@ -64,9 +78,10 @@ type PruneImagesOptions struct { |
64 | 64 |
// NewCmdPruneImages implements the OpenShift cli prune images command. |
65 | 65 |
func NewCmdPruneImages(f *clientcmd.Factory, parentName, name string, out io.Writer) *cobra.Command { |
66 | 66 |
opts := &PruneImagesOptions{ |
67 |
- Confirm: false, |
|
68 |
- KeepYoungerThan: 60 * time.Minute, |
|
69 |
- KeepTagRevisions: 3, |
|
67 |
+ Confirm: false, |
|
68 |
+ KeepYoungerThan: &defaultKeepYoungerThan, |
|
69 |
+ KeepTagRevisions: &defaultKeepTagRevisions, |
|
70 |
+ PruneOverSizeLimit: &defaultPruneImageOverSizeLimit, |
|
70 | 71 |
} |
71 | 72 |
|
72 | 73 |
cmd := &cobra.Command{ |
... | ... |
@@ -84,8 +99,9 @@ func NewCmdPruneImages(f *clientcmd.Factory, parentName, name string, out io.Wri |
84 | 84 |
} |
85 | 85 |
|
86 | 86 |
cmd.Flags().BoolVar(&opts.Confirm, "confirm", opts.Confirm, "Specify that image pruning should proceed. Defaults to false, displaying what would be deleted but not actually deleting anything.") |
87 |
- cmd.Flags().DurationVar(&opts.KeepYoungerThan, "keep-younger-than", opts.KeepYoungerThan, "Specify the minimum age of an image for it to be considered a candidate for pruning.") |
|
88 |
- cmd.Flags().IntVar(&opts.KeepTagRevisions, "keep-tag-revisions", opts.KeepTagRevisions, "Specify the number of image revisions for a tag in an image stream that will be preserved.") |
|
87 |
+ cmd.Flags().DurationVar(opts.KeepYoungerThan, "keep-younger-than", *opts.KeepYoungerThan, "Specify the minimum age of an image for it to be considered a candidate for pruning.") |
|
88 |
+ cmd.Flags().IntVar(opts.KeepTagRevisions, "keep-tag-revisions", *opts.KeepTagRevisions, "Specify the number of image revisions for a tag in an image stream that will be preserved.") |
|
89 |
+ cmd.Flags().BoolVar(opts.PruneOverSizeLimit, "prune-over-size-limit", *opts.PruneOverSizeLimit, "Specify if images which are exceeding LimitRanges (see 'openshift.io/Image'), specified in the same namespace, should be considered for pruning. This flag cannot be combined with --keep-younger-than nor --keep-tag-revisions.") |
|
89 | 90 |
cmd.Flags().StringVar(&opts.CABundle, "certificate-authority", opts.CABundle, "The path to a certificate authority bundle to use when communicating with the managed Docker registries. Defaults to the certificate authority data from the current user's config file.") |
90 | 91 |
cmd.Flags().StringVar(&opts.RegistryUrlOverride, "registry-url", opts.RegistryUrlOverride, "The address to use when contacting the registry, instead of using the default value. This is useful if you can't resolve or reach the registry (e.g.; the default is a cluster-internal URL) but you do have an alternative route that works.") |
91 | 92 |
|
... | ... |
@@ -99,6 +115,16 @@ func (o *PruneImagesOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, |
99 | 99 |
return kcmdutil.UsageError(cmd, "no arguments are allowed to this command") |
100 | 100 |
} |
101 | 101 |
|
102 |
+ if !cmd.Flags().Lookup("keep-younger-than").Changed { |
|
103 |
+ o.KeepYoungerThan = nil |
|
104 |
+ } |
|
105 |
+ if !cmd.Flags().Lookup("keep-tag-revisions").Changed { |
|
106 |
+ o.KeepTagRevisions = nil |
|
107 |
+ } |
|
108 |
+ if !cmd.Flags().Lookup("prune-over-size-limit").Changed { |
|
109 |
+ o.PruneOverSizeLimit = nil |
|
110 |
+ } |
|
111 |
+ |
|
102 | 112 |
o.Out = out |
103 | 113 |
|
104 | 114 |
osClient, kClient, registryClient, err := getClients(f, o.CABundle) |
... | ... |
@@ -146,19 +172,36 @@ func (o *PruneImagesOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, |
146 | 146 |
return err |
147 | 147 |
} |
148 | 148 |
|
149 |
+ limitRangesList, err := kClient.LimitRanges(kapi.NamespaceAll).List(kapi.ListOptions{}) |
|
150 |
+ if err != nil { |
|
151 |
+ return err |
|
152 |
+ } |
|
153 |
+ limitRangesMap := make(map[string][]*kapi.LimitRange) |
|
154 |
+ for i := range limitRangesList.Items { |
|
155 |
+ limit := limitRangesList.Items[i] |
|
156 |
+ limits, ok := limitRangesMap[limit.Namespace] |
|
157 |
+ if !ok { |
|
158 |
+ limits = []*kapi.LimitRange{} |
|
159 |
+ } |
|
160 |
+ limits = append(limits, &limit) |
|
161 |
+ limitRangesMap[limit.Namespace] = limits |
|
162 |
+ } |
|
163 |
+ |
|
149 | 164 |
options := prune.PrunerOptions{ |
150 |
- KeepYoungerThan: o.KeepYoungerThan, |
|
151 |
- KeepTagRevisions: o.KeepTagRevisions, |
|
152 |
- Images: allImages, |
|
153 |
- Streams: allStreams, |
|
154 |
- Pods: allPods, |
|
155 |
- RCs: allRCs, |
|
156 |
- BCs: allBCs, |
|
157 |
- Builds: allBuilds, |
|
158 |
- DCs: allDCs, |
|
159 |
- DryRun: o.Confirm == false, |
|
160 |
- RegistryClient: registryClient, |
|
161 |
- RegistryURL: o.RegistryUrlOverride, |
|
165 |
+ KeepYoungerThan: o.KeepYoungerThan, |
|
166 |
+ KeepTagRevisions: o.KeepTagRevisions, |
|
167 |
+ PruneOverSizeLimit: o.PruneOverSizeLimit, |
|
168 |
+ Images: allImages, |
|
169 |
+ Streams: allStreams, |
|
170 |
+ Pods: allPods, |
|
171 |
+ RCs: allRCs, |
|
172 |
+ BCs: allBCs, |
|
173 |
+ Builds: allBuilds, |
|
174 |
+ DCs: allDCs, |
|
175 |
+ LimitRanges: limitRangesMap, |
|
176 |
+ DryRun: o.Confirm == false, |
|
177 |
+ RegistryClient: registryClient, |
|
178 |
+ RegistryURL: o.RegistryUrlOverride, |
|
162 | 179 |
} |
163 | 180 |
|
164 | 181 |
o.Pruner = prune.NewPruner(options) |
... | ... |
@@ -168,10 +211,13 @@ func (o *PruneImagesOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, |
168 | 168 |
|
169 | 169 |
// Validate ensures that a PruneImagesOptions is valid and can be used to execute pruning. |
170 | 170 |
func (o PruneImagesOptions) Validate() error { |
171 |
- if o.KeepYoungerThan < 0 { |
|
171 |
+ if o.PruneOverSizeLimit != nil && (o.KeepYoungerThan != nil || o.KeepTagRevisions != nil) { |
|
172 |
+ return fmt.Errorf("--prune-over-size-limit cannot be specified with --keep-tag-revisions nor --keep-younger-than") |
|
173 |
+ } |
|
174 |
+ if o.KeepYoungerThan != nil && *o.KeepYoungerThan < 0 { |
|
172 | 175 |
return fmt.Errorf("--keep-younger-than must be greater than or equal to 0") |
173 | 176 |
} |
174 |
- if o.KeepTagRevisions < 0 { |
|
177 |
+ if o.KeepTagRevisions != nil && *o.KeepTagRevisions < 0 { |
|
175 | 178 |
return fmt.Errorf("--keep-tag-revisions must be greater than or equal to 0") |
176 | 179 |
} |
177 | 180 |
if _, err := url.Parse(o.RegistryUrlOverride); err != nil { |
... | ... |
@@ -422,6 +422,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole { |
422 | 422 |
}, |
423 | 423 |
Rules: []authorizationapi.PolicyRule{ |
424 | 424 |
authorizationapi.NewRule("get", "list").Groups(kapiGroup).Resources("pods", "replicationcontrollers").RuleOrDie(), |
425 |
+ authorizationapi.NewRule("list").Groups(kapiGroup).Resources("limitranges").RuleOrDie(), |
|
425 | 426 |
authorizationapi.NewRule("get", "list").Groups(buildGroup).Resources("buildconfigs", "builds").RuleOrDie(), |
426 | 427 |
authorizationapi.NewRule("get", "list").Groups(deployGroup).Resources("deploymentconfigs").RuleOrDie(), |
427 | 428 |
|
... | ... |
@@ -11,6 +11,7 @@ import ( |
11 | 11 |
gonum "github.com/gonum/graph" |
12 | 12 |
|
13 | 13 |
kapi "k8s.io/kubernetes/pkg/api" |
14 |
+ "k8s.io/kubernetes/pkg/api/resource" |
|
14 | 15 |
"k8s.io/kubernetes/pkg/api/unversioned" |
15 | 16 |
kerrors "k8s.io/kubernetes/pkg/util/errors" |
16 | 17 |
utilruntime "k8s.io/kubernetes/pkg/util/runtime" |
... | ... |
@@ -47,8 +48,9 @@ const ( |
47 | 47 |
// pruneAlgorithm contains the various settings to use when evaluating images |
48 | 48 |
// and layers for pruning. |
49 | 49 |
type pruneAlgorithm struct { |
50 |
- keepYoungerThan time.Duration |
|
51 |
- keepTagRevisions int |
|
50 |
+ keepYoungerThan time.Duration |
|
51 |
+ keepTagRevisions int |
|
52 |
+ pruneOverSizeLimit bool |
|
52 | 53 |
} |
53 | 54 |
|
54 | 55 |
// ImageDeleter knows how to remove images from OpenShift. |
... | ... |
@@ -90,10 +92,13 @@ type ManifestDeleter interface { |
90 | 90 |
type PrunerOptions struct { |
91 | 91 |
// KeepYoungerThan indicates the minimum age an Image must be to be a |
92 | 92 |
// candidate for pruning. |
93 |
- KeepYoungerThan time.Duration |
|
93 |
+ KeepYoungerThan *time.Duration |
|
94 | 94 |
// KeepTagRevisions is the minimum number of tag revisions to preserve; |
95 | 95 |
// revisions older than this value are candidates for pruning. |
96 |
- KeepTagRevisions int |
|
96 |
+ KeepTagRevisions *int |
|
97 |
+ // PruneOverSizeLimit indicates that images exceeding defined limits (openshift.io/Image) |
|
98 |
+ // will be considered as candidates for pruning. |
|
99 |
+ PruneOverSizeLimit *bool |
|
97 | 100 |
// Images is the entire list of images in OpenShift. An image must be in this |
98 | 101 |
// list to be a candidate for pruning. |
99 | 102 |
Images *imageapi.ImageList |
... | ... |
@@ -113,6 +118,8 @@ type PrunerOptions struct { |
113 | 113 |
// DCs is the entire list of deployment configs across all namespaces in the |
114 | 114 |
// cluster. |
115 | 115 |
DCs *deployapi.DeploymentConfigList |
116 |
+ // LimitRanges is a map of LimitRanges across namespaces, being keys in this map. |
|
117 |
+ LimitRanges map[string][]*kapi.LimitRange |
|
116 | 118 |
// DryRun indicates that no changes will be made to the cluster and nothing |
117 | 119 |
// will be removed. |
118 | 120 |
DryRun bool |
... | ... |
@@ -203,6 +210,10 @@ func (*dryRunRegistryPinger) ping(registry string) error { |
203 | 203 |
// status.tags that are preserved and ineligible for pruning. Any revision older |
204 | 204 |
// than keepTagRevisions is eligible for pruning. |
205 | 205 |
// |
206 |
+// pruneOverSizeLimit is a boolean flag speyfing that all images exceeding limits |
|
207 |
+// defined in their namespace will be considered for pruning. Important to note is |
|
208 |
+// the fact that this flag does not work in any combination with the keep* flags. |
|
209 |
+// |
|
206 | 210 |
// images, streams, pods, rcs, bcs, builds, and dcs are the resources used to run |
207 | 211 |
// the pruning algorithm. These should be the full list for each type from the |
208 | 212 |
// cluster; otherwise, the pruning algorithm might result in incorrect |
... | ... |
@@ -230,15 +241,22 @@ func (*dryRunRegistryPinger) ping(registry string) error { |
230 | 230 |
func NewPruner(options PrunerOptions) Pruner { |
231 | 231 |
g := graph.New() |
232 | 232 |
|
233 |
- glog.V(1).Infof("Creating image pruner with keepYoungerThan=%v, keepTagRevisions=%d", options.KeepYoungerThan, options.KeepTagRevisions) |
|
233 |
+ glog.V(1).Infof("Creating image pruner with keepYoungerThan=%v, keepTagRevisions=%v, pruneOverSizeLimit=%v", |
|
234 |
+ options.KeepYoungerThan, options.KeepTagRevisions, options.PruneOverSizeLimit) |
|
234 | 235 |
|
235 |
- algorithm := pruneAlgorithm{ |
|
236 |
- keepYoungerThan: options.KeepYoungerThan, |
|
237 |
- keepTagRevisions: options.KeepTagRevisions, |
|
236 |
+ algorithm := pruneAlgorithm{} |
|
237 |
+ if options.KeepYoungerThan != nil { |
|
238 |
+ algorithm.keepYoungerThan = *options.KeepYoungerThan |
|
239 |
+ } |
|
240 |
+ if options.KeepTagRevisions != nil { |
|
241 |
+ algorithm.keepTagRevisions = *options.KeepTagRevisions |
|
242 |
+ } |
|
243 |
+ if options.PruneOverSizeLimit != nil { |
|
244 |
+ algorithm.pruneOverSizeLimit = *options.PruneOverSizeLimit |
|
238 | 245 |
} |
239 | 246 |
|
240 | 247 |
addImagesToGraph(g, options.Images, algorithm) |
241 |
- addImageStreamsToGraph(g, options.Streams, algorithm) |
|
248 |
+ addImageStreamsToGraph(g, options.Streams, options.LimitRanges, algorithm) |
|
242 | 249 |
addPodsToGraph(g, options.Pods, algorithm) |
243 | 250 |
addReplicationControllersToGraph(g, options.RCs) |
244 | 251 |
addBuildConfigsToGraph(g, options.BCs) |
... | ... |
@@ -281,7 +299,7 @@ func addImagesToGraph(g graph.Graph, images *imageapi.ImageList, algorithm prune |
281 | 281 |
} |
282 | 282 |
|
283 | 283 |
age := unversioned.Now().Sub(image.CreationTimestamp.Time) |
284 |
- if age < algorithm.keepYoungerThan { |
|
284 |
+ if !algorithm.pruneOverSizeLimit && age < algorithm.keepYoungerThan { |
|
285 | 285 |
glog.V(4).Infof("Image %q is younger than minimum pruning age, skipping (age=%v)", image.Name, age) |
286 | 286 |
continue |
287 | 287 |
} |
... | ... |
@@ -306,13 +324,17 @@ func addImagesToGraph(g graph.Graph, images *imageapi.ImageList, algorithm prune |
306 | 306 |
// addImageStreamsToGraph adds all the streams to the graph. The most recent n |
307 | 307 |
// image revisions for a tag will be preserved, where n is specified by the |
308 | 308 |
// algorithm's keepTagRevisions. Image revisions older than n are candidates |
309 |
-// for pruning. if the image stream's age is at least as old as the minimum |
|
309 |
+// for pruning if the image stream's age is at least as old as the minimum |
|
310 | 310 |
// threshold in algorithm. Otherwise, if the image stream is younger than the |
311 | 311 |
// threshold, all image revisions for that stream are ineligible for pruning. |
312 |
+// If pruneOverSizeLimit flag is set to true, above does not matter, instead |
|
313 |
+// all images size is checked against LimitRanges defined in that same namespace, |
|
314 |
+// and whenever its size exceeds the smallest limit in that namespace, it will be |
|
315 |
+// considered a candidate for pruning. |
|
312 | 316 |
// |
313 | 317 |
// addImageStreamsToGraph also adds references from each stream to all the |
314 | 318 |
// layers it references (via each image a stream references). |
315 |
-func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, algorithm pruneAlgorithm) { |
|
319 |
+func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, limits map[string][]*kapi.LimitRange, algorithm pruneAlgorithm) { |
|
316 | 320 |
for i := range streams.Items { |
317 | 321 |
stream := &streams.Items[i] |
318 | 322 |
|
... | ... |
@@ -322,9 +344,8 @@ func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, al |
322 | 322 |
oldImageRevisionReferenceKind := WeakReferencedImageEdgeKind |
323 | 323 |
|
324 | 324 |
age := unversioned.Now().Sub(stream.CreationTimestamp.Time) |
325 |
- if age < algorithm.keepYoungerThan { |
|
325 |
+ if !algorithm.pruneOverSizeLimit && age < algorithm.keepYoungerThan { |
|
326 | 326 |
// stream's age is below threshold - use a strong reference for old image revisions instead |
327 |
- glog.V(4).Infof("Stream %s/%s is below age threshold - none of its images are eligible for pruning", stream.Namespace, stream.Name) |
|
328 | 327 |
oldImageRevisionReferenceKind = ReferencedImageEdgeKind |
329 | 328 |
} |
330 | 329 |
|
... | ... |
@@ -341,12 +362,17 @@ func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, al |
341 | 341 |
} |
342 | 342 |
imageNode := n.(*imagegraph.ImageNode) |
343 | 343 |
|
344 |
- var kind string |
|
345 |
- switch { |
|
346 |
- case i < algorithm.keepTagRevisions: |
|
347 |
- kind = ReferencedImageEdgeKind |
|
348 |
- default: |
|
349 |
- kind = oldImageRevisionReferenceKind |
|
344 |
+ kind := oldImageRevisionReferenceKind |
|
345 |
+ if algorithm.pruneOverSizeLimit { |
|
346 |
+ if exceedsLimits(stream, imageNode.Image, limits) { |
|
347 |
+ kind = WeakReferencedImageEdgeKind |
|
348 |
+ } else { |
|
349 |
+ kind = ReferencedImageEdgeKind |
|
350 |
+ } |
|
351 |
+ } else { |
|
352 |
+ if i < algorithm.keepTagRevisions { |
|
353 |
+ kind = ReferencedImageEdgeKind |
|
354 |
+ } |
|
350 | 355 |
} |
351 | 356 |
|
352 | 357 |
glog.V(4).Infof("Checking for existing strong reference from stream %s/%s to image %s", stream.Namespace, stream.Name, imageNode.Image.Name) |
... | ... |
@@ -372,6 +398,41 @@ func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, al |
372 | 372 |
} |
373 | 373 |
} |
374 | 374 |
|
375 |
+// exceedsLimits checks if given image exceeds LimitRanges defined in ImageStream's namespace. |
|
376 |
+func exceedsLimits(is *imageapi.ImageStream, image *imageapi.Image, limits map[string][]*kapi.LimitRange) bool { |
|
377 |
+ limitRanges, ok := limits[is.Namespace] |
|
378 |
+ if !ok { |
|
379 |
+ return false |
|
380 |
+ } |
|
381 |
+ if len(limitRanges) == 0 { |
|
382 |
+ return false |
|
383 |
+ } |
|
384 |
+ |
|
385 |
+ imageSize := resource.NewQuantity(image.DockerImageMetadata.Size, resource.BinarySI) |
|
386 |
+ for _, limitRange := range limitRanges { |
|
387 |
+ if limitRange == nil { |
|
388 |
+ continue |
|
389 |
+ } |
|
390 |
+ for _, limit := range limitRange.Spec.Limits { |
|
391 |
+ if limit.Type != imageapi.LimitTypeImage { |
|
392 |
+ continue |
|
393 |
+ } |
|
394 |
+ |
|
395 |
+ limitQuantity, ok := limit.Max[kapi.ResourceStorage] |
|
396 |
+ if !ok { |
|
397 |
+ continue |
|
398 |
+ } |
|
399 |
+ if limitQuantity.Cmp(*imageSize) < 0 { |
|
400 |
+ // image size is larger than the permitted limit range max size |
|
401 |
+ glog.V(4).Infof("Image %s in stream %s/%s exceeds limit %s: %v vs %v", |
|
402 |
+ image.Name, is.Namespace, is.Name, limitRange.Name, *imageSize, limitQuantity) |
|
403 |
+ return true |
|
404 |
+ } |
|
405 |
+ } |
|
406 |
+ } |
|
407 |
+ return false |
|
408 |
+} |
|
409 |
+ |
|
375 | 410 |
// addPodsToGraph adds pods to the graph. |
376 | 411 |
// |
377 | 412 |
// A pod is only *excluded* from being added to the graph if its phase is not |
... | ... |
@@ -13,6 +13,7 @@ import ( |
13 | 13 |
"time" |
14 | 14 |
|
15 | 15 |
kapi "k8s.io/kubernetes/pkg/api" |
16 |
+ "k8s.io/kubernetes/pkg/api/resource" |
|
16 | 17 |
"k8s.io/kubernetes/pkg/api/unversioned" |
17 | 18 |
"k8s.io/kubernetes/pkg/client/unversioned/fake" |
18 | 19 |
ktc "k8s.io/kubernetes/pkg/client/unversioned/testclient" |
... | ... |
@@ -57,6 +58,21 @@ func agedImage(id, ref string, ageInMinutes int64) imageapi.Image { |
57 | 57 |
return image |
58 | 58 |
} |
59 | 59 |
|
60 |
+func sizedImage(id, ref string, size int64) imageapi.Image { |
|
61 |
+ image := imageWithLayers(id, ref, |
|
62 |
+ "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", |
|
63 |
+ "tarsum.dev+sha256:b194de3772ebbcdc8f244f663669799ac1cb141834b7cb8b69100285d357a2b0", |
|
64 |
+ "tarsum.dev+sha256:c937c4bb1c1a21cc6d94340812262c6472092028972ae69b551b1a70d4276171", |
|
65 |
+ "tarsum.dev+sha256:2aaacc362ac6be2b9e9ae8c6029f6f616bb50aec63746521858e47841b90fabd", |
|
66 |
+ "tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", |
|
67 |
+ ) |
|
68 |
+ |
|
69 |
+ image.CreationTimestamp = unversioned.NewTime(unversioned.Now().Add(time.Duration(-1) * time.Minute)) |
|
70 |
+ image.DockerImageMetadata.Size = size |
|
71 |
+ |
|
72 |
+ return image |
|
73 |
+} |
|
74 |
+ |
|
60 | 75 |
func image(id, ref string) imageapi.Image { |
61 | 76 |
return agedImage(id, ref, -1) |
62 | 77 |
} |
... | ... |
@@ -288,6 +304,26 @@ func build(namespace, name, strategyType, fromKind, fromNamespace, fromName stri |
288 | 288 |
} |
289 | 289 |
} |
290 | 290 |
|
291 |
+func limitList(limits ...int64) []*kapi.LimitRange { |
|
292 |
+ list := make([]*kapi.LimitRange, len(limits)) |
|
293 |
+ for _, limit := range limits { |
|
294 |
+ quantity := resource.NewQuantity(limit, resource.BinarySI) |
|
295 |
+ list = append(list, &kapi.LimitRange{ |
|
296 |
+ Spec: kapi.LimitRangeSpec{ |
|
297 |
+ Limits: []kapi.LimitRangeItem{ |
|
298 |
+ { |
|
299 |
+ Type: imageapi.LimitTypeImage, |
|
300 |
+ Max: kapi.ResourceList{ |
|
301 |
+ kapi.ResourceStorage: *quantity, |
|
302 |
+ }, |
|
303 |
+ }, |
|
304 |
+ }, |
|
305 |
+ }, |
|
306 |
+ }) |
|
307 |
+ } |
|
308 |
+ return list |
|
309 |
+} |
|
310 |
+ |
|
291 | 311 |
func commonSpec(strategyType, fromKind, fromNamespace, fromName string) buildapi.CommonSpec { |
292 | 312 |
spec := buildapi.CommonSpec{ |
293 | 313 |
Strategy: buildapi.BuildStrategy{}, |
... | ... |
@@ -390,6 +426,7 @@ func TestImagePruning(t *testing.T) { |
390 | 390 |
registryURL := "registry" |
391 | 391 |
|
392 | 392 |
tests := map[string]struct { |
393 |
+ pruneOverSizeLimit *bool |
|
393 | 394 |
registryURLs []string |
394 | 395 |
images imageapi.ImageList |
395 | 396 |
pods kapi.PodList |
... | ... |
@@ -398,6 +435,7 @@ func TestImagePruning(t *testing.T) { |
398 | 398 |
bcs buildapi.BuildConfigList |
399 | 399 |
builds buildapi.BuildList |
400 | 400 |
dcs deployapi.DeploymentConfigList |
401 |
+ limits map[string][]*kapi.LimitRange |
|
401 | 402 |
expectedDeletions []string |
402 | 403 |
expectedUpdatedStreams []string |
403 | 404 |
}{ |
... | ... |
@@ -663,6 +701,79 @@ func TestImagePruning(t *testing.T) { |
663 | 663 |
expectedDeletions: []string{"id"}, |
664 | 664 |
expectedUpdatedStreams: []string{}, |
665 | 665 |
}, |
666 |
+ "image exceeding limits": { |
|
667 |
+ pruneOverSizeLimit: newBool(true), |
|
668 |
+ images: imageList( |
|
669 |
+ unmanagedImage("id", "otherregistry/foo/bar@id", false, "", ""), |
|
670 |
+ sizedImage("id2", registryURL+"/foo/bar@id2", 100), |
|
671 |
+ sizedImage("id3", registryURL+"/foo/bar@id3", 200), |
|
672 |
+ ), |
|
673 |
+ streams: streamList( |
|
674 |
+ stream(registryURL, "foo", "bar", tags( |
|
675 |
+ tag("latest", |
|
676 |
+ tagEvent("id", "otherregistry/foo/bar@id"), |
|
677 |
+ tagEvent("id2", registryURL+"/foo/bar@id2"), |
|
678 |
+ tagEvent("id3", registryURL+"/foo/bar@id3"), |
|
679 |
+ ), |
|
680 |
+ )), |
|
681 |
+ ), |
|
682 |
+ limits: map[string][]*kapi.LimitRange{ |
|
683 |
+ "foo": limitList(100, 200), |
|
684 |
+ }, |
|
685 |
+ expectedDeletions: []string{"id3"}, |
|
686 |
+ expectedUpdatedStreams: []string{"foo/bar|id3"}, |
|
687 |
+ }, |
|
688 |
+ "multiple images in different namespaces exceeding different limits": { |
|
689 |
+ pruneOverSizeLimit: newBool(true), |
|
690 |
+ images: imageList( |
|
691 |
+ sizedImage("id1", registryURL+"/foo/bar@id1", 100), |
|
692 |
+ sizedImage("id2", registryURL+"/foo/bar@id2", 200), |
|
693 |
+ sizedImage("id3", registryURL+"/bar/foo@id3", 500), |
|
694 |
+ sizedImage("id4", registryURL+"/bar/foo@id4", 600), |
|
695 |
+ ), |
|
696 |
+ streams: streamList( |
|
697 |
+ stream(registryURL, "foo", "bar", tags( |
|
698 |
+ tag("latest", |
|
699 |
+ tagEvent("id1", registryURL+"/foo/bar@id1"), |
|
700 |
+ tagEvent("id2", registryURL+"/foo/bar@id2"), |
|
701 |
+ ), |
|
702 |
+ )), |
|
703 |
+ stream(registryURL, "bar", "foo", tags( |
|
704 |
+ tag("latest", |
|
705 |
+ tagEvent("id3", registryURL+"/bar/foo@id3"), |
|
706 |
+ tagEvent("id4", registryURL+"/bar/foo@id4"), |
|
707 |
+ ), |
|
708 |
+ )), |
|
709 |
+ ), |
|
710 |
+ limits: map[string][]*kapi.LimitRange{ |
|
711 |
+ "foo": limitList(150), |
|
712 |
+ "bar": limitList(550), |
|
713 |
+ }, |
|
714 |
+ expectedDeletions: []string{"id2", "id4"}, |
|
715 |
+ expectedUpdatedStreams: []string{"foo/bar|id2", "bar/foo|id4"}, |
|
716 |
+ }, |
|
717 |
+ "image within allowed limits": { |
|
718 |
+ pruneOverSizeLimit: newBool(true), |
|
719 |
+ images: imageList( |
|
720 |
+ unmanagedImage("id", "otherregistry/foo/bar@id", false, "", ""), |
|
721 |
+ sizedImage("id2", registryURL+"/foo/bar@id2", 100), |
|
722 |
+ sizedImage("id3", registryURL+"/foo/bar@id3", 200), |
|
723 |
+ ), |
|
724 |
+ streams: streamList( |
|
725 |
+ stream(registryURL, "foo", "bar", tags( |
|
726 |
+ tag("latest", |
|
727 |
+ tagEvent("id", "otherregistry/foo/bar@id"), |
|
728 |
+ tagEvent("id2", registryURL+"/foo/bar@id2"), |
|
729 |
+ tagEvent("id3", registryURL+"/foo/bar@id3"), |
|
730 |
+ ), |
|
731 |
+ )), |
|
732 |
+ ), |
|
733 |
+ limits: map[string][]*kapi.LimitRange{ |
|
734 |
+ "foo": limitList(300), |
|
735 |
+ }, |
|
736 |
+ expectedDeletions: []string{}, |
|
737 |
+ expectedUpdatedStreams: []string{}, |
|
738 |
+ }, |
|
666 | 739 |
} |
667 | 740 |
|
668 | 741 |
for name, test := range tests { |
... | ... |
@@ -672,15 +783,22 @@ func TestImagePruning(t *testing.T) { |
672 | 672 |
} |
673 | 673 |
|
674 | 674 |
options := PrunerOptions{ |
675 |
- KeepYoungerThan: 60 * time.Minute, |
|
676 |
- KeepTagRevisions: 3, |
|
677 |
- Images: &test.images, |
|
678 |
- Streams: &test.streams, |
|
679 |
- Pods: &test.pods, |
|
680 |
- RCs: &test.rcs, |
|
681 |
- BCs: &test.bcs, |
|
682 |
- Builds: &test.builds, |
|
683 |
- DCs: &test.dcs, |
|
675 |
+ Images: &test.images, |
|
676 |
+ Streams: &test.streams, |
|
677 |
+ Pods: &test.pods, |
|
678 |
+ RCs: &test.rcs, |
|
679 |
+ BCs: &test.bcs, |
|
680 |
+ Builds: &test.builds, |
|
681 |
+ DCs: &test.dcs, |
|
682 |
+ LimitRanges: test.limits, |
|
683 |
+ } |
|
684 |
+ if test.pruneOverSizeLimit != nil { |
|
685 |
+ options.PruneOverSizeLimit = test.pruneOverSizeLimit |
|
686 |
+ } else { |
|
687 |
+ keepYoungerThan := 60 * time.Minute |
|
688 |
+ keepTagRevisions := 3 |
|
689 |
+ options.KeepYoungerThan = &keepYoungerThan |
|
690 |
+ options.KeepTagRevisions = &keepTagRevisions |
|
684 | 691 |
} |
685 | 692 |
p := NewPruner(options) |
686 | 693 |
p.(*pruner).registryPinger = &fakeRegistryPinger{} |
... | ... |
@@ -880,9 +998,11 @@ func TestRegistryPruning(t *testing.T) { |
880 | 880 |
|
881 | 881 |
t.Logf("Running test case %s", name) |
882 | 882 |
|
883 |
+ keepYoungerThan := 60 * time.Minute |
|
884 |
+ keepTagRevisions := 1 |
|
883 | 885 |
options := PrunerOptions{ |
884 |
- KeepYoungerThan: 60 * time.Minute, |
|
885 |
- KeepTagRevisions: 1, |
|
886 |
+ KeepYoungerThan: &keepYoungerThan, |
|
887 |
+ KeepTagRevisions: &keepTagRevisions, |
|
886 | 888 |
Images: &test.images, |
887 | 889 |
Streams: &test.streams, |
888 | 890 |
Pods: &kapi.PodList{}, |
... | ... |
@@ -913,3 +1033,9 @@ func TestRegistryPruning(t *testing.T) { |
913 | 913 |
} |
914 | 914 |
} |
915 | 915 |
} |
916 |
+ |
|
917 |
+func newBool(a bool) *bool { |
|
918 |
+ r := new(bool) |
|
919 |
+ *r = a |
|
920 |
+ return r |
|
921 |
+} |
... | ... |
@@ -1394,6 +1394,13 @@ items: |
1394 | 1394 |
- "" |
1395 | 1395 |
attributeRestrictions: null |
1396 | 1396 |
resources: |
1397 |
+ - limitranges |
|
1398 |
+ verbs: |
|
1399 |
+ - list |
|
1400 |
+ - apiGroups: |
|
1401 |
+ - "" |
|
1402 |
+ attributeRestrictions: null |
|
1403 |
+ resources: |
|
1397 | 1404 |
- buildconfigs |
1398 | 1405 |
- builds |
1399 | 1406 |
verbs: |