1
1
package server
2
2
3
3
import (
4
+ "fmt"
5
+ "sort"
6
+ "time"
7
+
4
8
"github.com/docker/distribution"
5
9
"github.com/docker/distribution/context"
6
10
"github.com/docker/distribution/digest"
7
11
"github.com/docker/distribution/registry/middleware/registry"
8
12
"github.com/docker/distribution/registry/storage"
13
+
14
+ kerrors "k8s.io/kubernetes/pkg/api/errors"
15
+
16
+ imageapi "github.com/openshift/origin/pkg/image/api"
9
17
)
10
18
19
+ // ByGeneration allows for sorting tag events from latest to oldest.
20
+ type ByGeneration []* imageapi.TagEvent
21
+
22
+ func (b ByGeneration ) Less (i , j int ) bool { return b [i ].Generation > b [j ].Generation }
23
+ func (b ByGeneration ) Len () int { return len (b ) }
24
+ func (b ByGeneration ) Swap (i , j int ) { b [i ], b [j ] = b [j ], b [i ] }
25
+
11
26
func init () {
12
27
middleware .RegisterOptions (storage .BlobDescriptorServiceFactory (& blobDescriptorServiceFactory {}))
13
28
}
@@ -25,6 +40,168 @@ type blobDescriptorService struct {
25
40
distribution.BlobDescriptorService
26
41
}
27
42
43
+ // Stat returns a a blob descriptor if the given blob is either linked in repository or is referenced in
44
+ // corresponding image stream. This method is invoked from inside of upstream's linkedBlobStore. It expects
45
+ // a proper repository object to be set on given context by upper openshift middleware wrappers.
28
46
func (bs * blobDescriptorService ) Stat (ctx context.Context , dgst digest.Digest ) (distribution.Descriptor , error ) {
29
- return dockerRegistry .BlobStatter ().Stat (ctx , dgst )
47
+ repo , found := RepositoryFrom (ctx )
48
+ if ! found || repo == nil {
49
+ err := fmt .Errorf ("failed to retrieve repository from context" )
50
+ context .GetLogger (ctx ).Error (err )
51
+ return distribution.Descriptor {}, err
52
+ }
53
+
54
+ // if there is a repo layer link, return its descriptor
55
+ desc , err := bs .BlobDescriptorService .Stat (ctx , dgst )
56
+ if err == nil {
57
+ // and remember the association
58
+ repo .cachedLayers .RememberDigest (dgst , repo .blobrepositorycachettl , imageapi.DockerImageReference {
59
+ Namespace : repo .namespace ,
60
+ Name : repo .name ,
61
+ }.Exact ())
62
+ return desc , nil
63
+ }
64
+
65
+ context .GetLogger (ctx ).Debugf ("could not stat layer link %q in repository %q: %v" , dgst .String (), repo .Named ().Name (), err )
66
+
67
+ // verify the blob is stored locally
68
+ desc , err = dockerRegistry .BlobStatter ().Stat (ctx , dgst )
69
+ if err != nil {
70
+ return desc , err
71
+ }
72
+
73
+ // ensure it's referenced inside of corresponding image stream
74
+ if imageStreamHasBlob (repo , dgst ) {
75
+ return desc , nil
76
+ }
77
+
78
+ return distribution.Descriptor {}, distribution .ErrBlobUnknown
79
+ }
80
+
81
+ func (bs * blobDescriptorService ) Clear (ctx context.Context , dgst digest.Digest ) error {
82
+ repo , found := RepositoryFrom (ctx )
83
+ if ! found || repo == nil {
84
+ err := fmt .Errorf ("failed to retrieve repository from context" )
85
+ context .GetLogger (ctx ).Error (err )
86
+ return err
87
+ }
88
+
89
+ repo .cachedLayers .ForgetDigest (dgst , imageapi.DockerImageReference {
90
+ Namespace : repo .namespace ,
91
+ Name : repo .name ,
92
+ }.Exact ())
93
+ return bs .BlobDescriptorService .Clear (ctx , dgst )
94
+ }
95
+
96
+ // imageStreamHasBlob returns true if the given blob digest is referenced in image stream corresponding to
97
+ // given repository. If not found locally, image stream's images will be iterated and fetched from newest to
98
+ // oldest until found. Each processed image will update local cache of blobs.
99
+ func imageStreamHasBlob (r * repository , dgst digest.Digest ) bool {
100
+ repoCacheName := imageapi.DockerImageReference {Namespace : r .namespace , Name : r .name }.Exact ()
101
+ if r .cachedLayers .RepositoryHasBlob (repoCacheName , dgst ) {
102
+ context .GetLogger (r .ctx ).Debugf ("found cached blob %q in repository %s" , dgst .String (), r .Named ().Name ())
103
+ return true
104
+ }
105
+
106
+ context .GetLogger (r .ctx ).Debugf ("verifying presence of blob %q in image stream %s/%s" , dgst .String (), r .namespace , r .name )
107
+ started := time .Now ()
108
+ logFound := func (found bool ) bool {
109
+ elapsed := time .Now ().Sub (started )
110
+ if found {
111
+ context .GetLogger (r .ctx ).Debugf ("verified presence of blob %q in image stream %s/%s after %s" , dgst .String (), r .namespace , r .name , elapsed .String ())
112
+ } else {
113
+ context .GetLogger (r .ctx ).Debugf ("detected absence of blob %q in image stream %s/%s after %s" , dgst .String (), r .namespace , r .name , elapsed .String ())
114
+ }
115
+ return found
116
+ }
117
+
118
+ // verify directly with etcd
119
+ is , err := r .getImageStream ()
120
+ if err != nil {
121
+ context .GetLogger (r .ctx ).Errorf ("failed to get image stream: %v" , err )
122
+ return logFound (false )
123
+ }
124
+
125
+ tagEvents := []* imageapi.TagEvent {}
126
+ event2Name := make (map [* imageapi.TagEvent ]string )
127
+ for name , eventList := range is .Status .Tags {
128
+ for i := range eventList .Items {
129
+ event := & eventList .Items [i ]
130
+ tagEvents = append (tagEvents , event )
131
+ event2Name [event ] = name
132
+ }
133
+ }
134
+ // search from youngest to oldest
135
+ sort .Sort (ByGeneration (tagEvents ))
136
+
137
+ processedImages := map [string ]struct {}{}
138
+
139
+ for _ , tagEvent := range tagEvents {
140
+ if _ , processed := processedImages [tagEvent .Image ]; processed {
141
+ continue
142
+ }
143
+ if imageHasBlob (r , repoCacheName , tagEvent .Image , dgst .String (), ! r .pullthrough ) {
144
+ tagName := event2Name [tagEvent ]
145
+ context .GetLogger (r .ctx ).Debugf ("blob found under istag %s/%s:%s in image %s" , r .namespace , r .name , tagName , tagEvent .Image )
146
+ return logFound (true )
147
+ }
148
+ processedImages [tagEvent .Image ] = struct {}{}
149
+ }
150
+
151
+ context .GetLogger (r .ctx ).Warnf ("blob %q exists locally but is not referenced in repository %s/%s" , dgst .String (), r .namespace , r .name )
152
+
153
+ return logFound (false )
154
+ }
155
+
156
+ // imageHasBlob returns true if the image identified by imageName refers to the given blob. The image is
157
+ // fetched. If requireManaged is true and the image is not managed (it refers to remote registry), the image
158
+ // will not be processed. Fetched image will update local cache of blobs -> repositories with (blobDigest,
159
+ // cacheName) pairs.
160
+ func imageHasBlob (
161
+ r * repository ,
162
+ cacheName ,
163
+ imageName ,
164
+ blobDigest string ,
165
+ requireManaged bool ,
166
+ ) bool {
167
+ context .GetLogger (r .ctx ).Debugf ("getting image %s" , imageName )
168
+ image , err := r .getImage (digest .Digest (imageName ))
169
+ if err != nil {
170
+ if kerrors .IsNotFound (err ) {
171
+ context .GetLogger (r .ctx ).Debugf ("image %q not found: imageName" )
172
+ } else {
173
+ context .GetLogger (r .ctx ).Errorf ("failed to get image: %v" , err )
174
+ }
175
+ return false
176
+ }
177
+
178
+ // in case of pullthrough disabled, client won't be able to download a blob belonging to not managed image
179
+ // (image stored in external registry), thus don't consider them as candidates
180
+ if managed := image .Annotations [imageapi .ManagedByOpenShiftAnnotation ]; requireManaged && managed != "true" {
181
+ context .GetLogger (r .ctx ).Debugf ("skipping not managed image" )
182
+ return false
183
+ }
184
+
185
+ if len (image .DockerImageLayers ) == 0 {
186
+ if len (image .DockerImageManifestMediaType ) > 0 {
187
+ // If the media type is set, we can safely assume that the best effort to fill the image layers
188
+ // has already been done. There are none.
189
+ return false
190
+ }
191
+ err = imageapi .ImageWithMetadata (image )
192
+ if err != nil {
193
+ context .GetLogger (r .ctx ).Errorf ("failed to get metadata for image %s: %v" , imageName , err )
194
+ return false
195
+ }
196
+ }
197
+
198
+ for _ , layer := range image .DockerImageLayers {
199
+ if layer .Name == blobDigest {
200
+ // remember all the layers of matching image
201
+ r .rememberLayersOfImage (image , cacheName )
202
+ return true
203
+ }
204
+ }
205
+
206
+ return false
30
207
}
0 commit comments