@@ -22,6 +22,7 @@ import (
22
22
kerrors "k8s.io/apimachinery/pkg/util/errors"
23
23
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
24
24
"k8s.io/apimachinery/pkg/util/sets"
25
+ "k8s.io/apimachinery/pkg/watch"
25
26
"k8s.io/client-go/util/retry"
26
27
kapi "k8s.io/kubernetes/pkg/apis/core"
27
28
kapisext "k8s.io/kubernetes/pkg/apis/extensions"
@@ -60,7 +61,7 @@ const (
60
61
// ImageNode to an ImageComponentNode.
61
62
ReferencedImageManifestEdgeKind = "ReferencedImageManifest"
62
63
63
- pruneImageWorkerCount = 5
64
+ defaultPruneImageWorkerCount = 5
64
65
)
65
66
66
67
// RegistryClientFactoryFunc is a factory function returning a registry client for use in a worker.
@@ -149,6 +150,8 @@ type PrunerOptions struct {
149
150
// Streams is the entire list of image streams across all namespaces in the
150
151
// cluster.
151
152
Streams * imageapi.ImageStreamList
153
+ // StreamsWatcher watches for stream changes.
154
+ StreamsWatcher watch.Interface
152
155
// Pods is the entire list of pods across all namespaces in the cluster.
153
156
Pods * kapi.PodList
154
157
// RCs is the entire list of replication controllers across all namespaces in
@@ -176,6 +179,9 @@ type PrunerOptions struct {
176
179
RegistryClientFactory RegistryClientFactoryFunc
177
180
// RegistryURL is the URL of the integrated Docker registry.
178
181
RegistryURL * url.URL
182
+ // NumWorkers is a desired number of workers concurrently handling image prune jobs. If less than 1, the
183
+ // default number of workers will be spawned.
184
+ NumWorkers int
179
185
}
180
186
181
187
// Pruner knows how to prune istags, images, manifest, layers, image configs and blobs.
@@ -199,10 +205,13 @@ type pruner struct {
199
205
algorithm pruneAlgorithm
200
206
registryClientFactory RegistryClientFactoryFunc
201
207
registryURL * url.URL
208
+ imageStreamWatcher watch.Interface
209
+ imageStreamLimits map [string ][]* kapi.LimitRange
202
210
// sorted queue of images to prune
203
211
queue []* imagegraph.ImageNode
204
212
// contains prunable images removed from queue that are currently being processed
205
213
processedImages map [* imagegraph.ImageNode ]* Job
214
+ numWorkers int
206
215
}
207
216
208
217
var _ Pruner = & pruner {}
@@ -283,6 +292,13 @@ func NewPruner(options PrunerOptions) (Pruner, kerrors.Aggregate) {
283
292
registryClientFactory : options .RegistryClientFactory ,
284
293
registryURL : options .RegistryURL ,
285
294
processedImages : make (map [* imagegraph.ImageNode ]* Job ),
295
+ imageStreamWatcher : options .StreamsWatcher ,
296
+ imageStreamLimits : options .LimitRanges ,
297
+ numWorkers : options .NumWorkers ,
298
+ }
299
+
300
+ if p .numWorkers < 1 {
301
+ p .numWorkers = defaultPruneImageWorkerCount
286
302
}
287
303
288
304
if err := p .buildGraph (options ); err != nil {
@@ -776,6 +792,50 @@ func (p *pruner) addBuildStrategyImageReferencesToGraph(referrer *kapi.ObjectRef
776
792
return nil
777
793
}
778
794
795
+ func (p * pruner ) handleImageStreamEvent (event watch.Event ) {
796
+ getIsNode := func () (* imageapi.ImageStream , * imagegraph.ImageStreamNode ) {
797
+ is , ok := event .Object .(* imageapi.ImageStream )
798
+ if ! ok {
799
+ utilruntime .HandleError (fmt .Errorf ("internal error: expected ImageStream object in %s event, not %T" , event .Type , event .Object ))
800
+ return nil , nil
801
+ }
802
+ n := p .g .Find (imagegraph .ImageStreamNodeName (is ))
803
+ if isNode , ok := n .(* imagegraph.ImageStreamNode ); ok {
804
+ return is , isNode
805
+ }
806
+ return is , nil
807
+ }
808
+
809
+ switch event .Type {
810
+ case watch .Added :
811
+ is , isNode := getIsNode ()
812
+ if is == nil {
813
+ return
814
+ }
815
+ if isNode != nil {
816
+ glog .V (4 ).Infof ("Ignoring added ImageStream %s that is already present in the graph" , getName (is ))
817
+ return
818
+ }
819
+ glog .V (4 ).Infof ("Adding ImageStream %s to the graph" , getName (is ))
820
+ p .addImageStreamsToGraph (& imageapi.ImageStreamList {Items : []imageapi.ImageStream {* is }}, p .imageStreamLimits )
821
+
822
+ case watch .Modified :
823
+ is , isNode := getIsNode ()
824
+ if is == nil {
825
+ return
826
+ }
827
+
828
+ if isNode != nil {
829
+ glog .V (4 ).Infof ("Removing updated ImageStream %s from the graph" , getName (is ))
830
+ // first remove the current node if present
831
+ p .g .RemoveNode (imagegraph .EnsureImageStreamNode (p .g , is ))
832
+ }
833
+
834
+ glog .V (4 ).Infof ("Adding updated ImageStream %s back to the graph" , getName (is ))
835
+ p .addImageStreamsToGraph (& imageapi.ImageStreamList {Items : []imageapi.ImageStream {* is }}, p .imageStreamLimits )
836
+ }
837
+ }
838
+
779
839
// getImageNodes returns only nodes of type ImageNode.
780
840
func getImageNodes (nodes []gonum.Node ) map [string ]* imagegraph.ImageNode {
781
841
ret := make (map [string ]* imagegraph.ImageNode )
@@ -1112,7 +1172,7 @@ func (p *pruner) Prune(
1112
1172
1113
1173
defer close (jobChan )
1114
1174
1115
- for i := 0 ; i < pruneImageWorkerCount ; i ++ {
1175
+ for i := 0 ; i < p . numWorkers ; i ++ {
1116
1176
worker , err := NewWorker (
1117
1177
p .algorithm ,
1118
1178
p .registryClientFactory ,
@@ -1145,9 +1205,10 @@ func (p *pruner) runLoop(
1145
1205
jobChan chan <- * Job ,
1146
1206
resultChan <- chan JobResult ,
1147
1207
) (deletions []Deletion , failures []Failure ) {
1208
+ isUpdateChan := p .imageStreamWatcher .ResultChan ()
1148
1209
for {
1149
1210
// make workers busy
1150
- for len (p .processedImages ) < pruneImageWorkerCount {
1211
+ for len (p .processedImages ) < p . numWorkers {
1151
1212
job , blocked := p .getNextJob ()
1152
1213
if blocked {
1153
1214
break
@@ -1172,7 +1233,8 @@ func (p *pruner) runLoop(
1172
1233
failures = append (failures , failure )
1173
1234
}
1174
1235
delete (p .processedImages , res .Job .Image )
1175
- // TODO: handle image stream updates
1236
+ case event := <- isUpdateChan :
1237
+ p .handleImageStreamEvent (event )
1176
1238
// TODO: handle new images - do not add them to the queue though
1177
1239
}
1178
1240
}
@@ -1220,13 +1282,8 @@ func (p *pruner) getNextJob() (job *Job, blocked bool) {
1220
1282
blocked = job == nil
1221
1283
1222
1284
// remove no longer prunable images from the queue
1223
- for i , n := range toRemove {
1224
- if i < len (toRemove )- 1 {
1225
- p .queue = append (p .queue [:n - i ], p .queue [n + 1 - i :toRemove [i + 1 ]- i ]... )
1226
- } else {
1227
- p .queue = append (p .queue [:n - i ], p .queue [n + 1 - i :]... )
1228
- }
1229
- }
1285
+ // TODO: highly ineffective for a long queue O(N*M), consider the use of doubly linked list
1286
+ p .queue = removeImageNodesAtIndexes (p .queue , toRemove ... )
1230
1287
1231
1288
return
1232
1289
}
0 commit comments