github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/distribution/xfer/download.go (about) 1 package xfer 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "time" 8 9 "github.com/Sirupsen/logrus" 10 "github.com/docker/docker/image" 11 "github.com/docker/docker/layer" 12 "github.com/docker/docker/pkg/archive" 13 "github.com/docker/docker/pkg/ioutils" 14 "github.com/docker/docker/pkg/progress" 15 "golang.org/x/net/context" 16 ) 17 18 const maxDownloadAttempts = 5 19 20 // LayerDownloadManager figures out which layers need to be downloaded, then 21 // registers and downloads those, taking into account dependencies between 22 // layers. 23 type LayerDownloadManager struct { 24 layerStore layer.Store 25 tm TransferManager 26 } 27 28 // NewLayerDownloadManager returns a new LayerDownloadManager. 29 func NewLayerDownloadManager(layerStore layer.Store, concurrencyLimit int) *LayerDownloadManager { 30 return &LayerDownloadManager{ 31 layerStore: layerStore, 32 tm: NewTransferManager(concurrencyLimit), 33 } 34 } 35 36 type downloadTransfer struct { 37 Transfer 38 39 layerStore layer.Store 40 layer layer.Layer 41 err error 42 } 43 44 // result returns the layer resulting from the download, if the download 45 // and registration were successful. 46 func (d *downloadTransfer) result() (layer.Layer, error) { 47 return d.layer, d.err 48 } 49 50 // A DownloadDescriptor references a layer that may need to be downloaded. 51 type DownloadDescriptor interface { 52 // Key returns the key used to deduplicate downloads. 53 Key() string 54 // ID returns the ID for display purposes. 55 ID() string 56 // DiffID should return the DiffID for this layer, or an error 57 // if it is unknown (for example, if it has not been downloaded 58 // before). 59 DiffID() (layer.DiffID, error) 60 // Download is called to perform the download. 61 Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) 62 } 63 64 // DownloadDescriptorWithRegistered is a DownloadDescriptor that has an 65 // additional Registered method which gets called after a downloaded layer is 66 // registered. This allows the user of the download manager to know the DiffID 67 // of each registered layer. This method is called if a cast to 68 // DownloadDescriptorWithRegistered is successful. 69 type DownloadDescriptorWithRegistered interface { 70 DownloadDescriptor 71 Registered(diffID layer.DiffID) 72 } 73 74 // Download is a blocking function which ensures the requested layers are 75 // present in the layer store. It uses the string returned by the Key method to 76 // deduplicate downloads. If a given layer is not already known to present in 77 // the layer store, and the key is not used by an in-progress download, the 78 // Download method is called to get the layer tar data. Layers are then 79 // registered in the appropriate order. The caller must call the returned 80 // release function once it is is done with the returned RootFS object. 81 func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) { 82 var ( 83 topLayer layer.Layer 84 topDownload *downloadTransfer 85 watcher *Watcher 86 missingLayer bool 87 transferKey = "" 88 downloadsByKey = make(map[string]*downloadTransfer) 89 ) 90 91 rootFS := initialRootFS 92 for _, descriptor := range layers { 93 key := descriptor.Key() 94 transferKey += key 95 96 if !missingLayer { 97 missingLayer = true 98 diffID, err := descriptor.DiffID() 99 if err == nil { 100 getRootFS := rootFS 101 getRootFS.Append(diffID) 102 l, err := ldm.layerStore.Get(getRootFS.ChainID()) 103 if err == nil { 104 // Layer already exists. 105 logrus.Debugf("Layer already exists: %s", descriptor.ID()) 106 progress.Update(progressOutput, descriptor.ID(), "Already exists") 107 if topLayer != nil { 108 layer.ReleaseAndLog(ldm.layerStore, topLayer) 109 } 110 topLayer = l 111 missingLayer = false 112 rootFS.Append(diffID) 113 continue 114 } 115 } 116 } 117 118 // Does this layer have the same data as a previous layer in 119 // the stack? If so, avoid downloading it more than once. 120 var topDownloadUncasted Transfer 121 if existingDownload, ok := downloadsByKey[key]; ok { 122 xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload) 123 defer topDownload.Transfer.Release(watcher) 124 topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput) 125 topDownload = topDownloadUncasted.(*downloadTransfer) 126 continue 127 } 128 129 // Layer is not known to exist - download and register it. 130 progress.Update(progressOutput, descriptor.ID(), "Pulling fs layer") 131 132 var xferFunc DoFunc 133 if topDownload != nil { 134 xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload) 135 defer topDownload.Transfer.Release(watcher) 136 } else { 137 xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil) 138 } 139 topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput) 140 topDownload = topDownloadUncasted.(*downloadTransfer) 141 downloadsByKey[key] = topDownload 142 } 143 144 if topDownload == nil { 145 return rootFS, func() { layer.ReleaseAndLog(ldm.layerStore, topLayer) }, nil 146 } 147 148 // Won't be using the list built up so far - will generate it 149 // from downloaded layers instead. 150 rootFS.DiffIDs = []layer.DiffID{} 151 152 defer func() { 153 if topLayer != nil { 154 layer.ReleaseAndLog(ldm.layerStore, topLayer) 155 } 156 }() 157 158 select { 159 case <-ctx.Done(): 160 topDownload.Transfer.Release(watcher) 161 return rootFS, func() {}, ctx.Err() 162 case <-topDownload.Done(): 163 break 164 } 165 166 l, err := topDownload.result() 167 if err != nil { 168 topDownload.Transfer.Release(watcher) 169 return rootFS, func() {}, err 170 } 171 172 // Must do this exactly len(layers) times, so we don't include the 173 // base layer on Windows. 174 for range layers { 175 if l == nil { 176 topDownload.Transfer.Release(watcher) 177 return rootFS, func() {}, errors.New("internal error: too few parent layers") 178 } 179 rootFS.DiffIDs = append([]layer.DiffID{l.DiffID()}, rootFS.DiffIDs...) 180 l = l.Parent() 181 } 182 return rootFS, func() { topDownload.Transfer.Release(watcher) }, err 183 } 184 185 // makeDownloadFunc returns a function that performs the layer download and 186 // registration. If parentDownload is non-nil, it waits for that download to 187 // complete before the registration step, and registers the downloaded data 188 // on top of parentDownload's resulting layer. Otherwise, it registers the 189 // layer on top of the ChainID given by parentLayer. 190 func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer) DoFunc { 191 return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer { 192 d := &downloadTransfer{ 193 Transfer: NewTransfer(), 194 layerStore: ldm.layerStore, 195 } 196 197 go func() { 198 defer func() { 199 close(progressChan) 200 }() 201 202 progressOutput := progress.ChanOutput(progressChan) 203 204 select { 205 case <-start: 206 default: 207 progress.Update(progressOutput, descriptor.ID(), "Waiting") 208 <-start 209 } 210 211 if parentDownload != nil { 212 // Did the parent download already fail or get 213 // cancelled? 214 select { 215 case <-parentDownload.Done(): 216 _, err := parentDownload.result() 217 if err != nil { 218 d.err = err 219 return 220 } 221 default: 222 } 223 } 224 225 var ( 226 downloadReader io.ReadCloser 227 size int64 228 err error 229 retries int 230 ) 231 232 for { 233 downloadReader, size, err = descriptor.Download(d.Transfer.Context(), progressOutput) 234 if err == nil { 235 break 236 } 237 238 // If an error was returned because the context 239 // was cancelled, we shouldn't retry. 240 select { 241 case <-d.Transfer.Context().Done(): 242 d.err = err 243 return 244 default: 245 } 246 247 retries++ 248 if _, isDNR := err.(DoNotRetry); isDNR || retries == maxDownloadAttempts { 249 logrus.Errorf("Download failed: %v", err) 250 d.err = err 251 return 252 } 253 254 logrus.Errorf("Download failed, retrying: %v", err) 255 delay := retries * 5 256 ticker := time.NewTicker(time.Second) 257 258 selectLoop: 259 for { 260 progress.Updatef(progressOutput, descriptor.ID(), "Retrying in %d seconds", delay) 261 select { 262 case <-ticker.C: 263 delay-- 264 if delay == 0 { 265 ticker.Stop() 266 break selectLoop 267 } 268 case <-d.Transfer.Context().Done(): 269 ticker.Stop() 270 d.err = errors.New("download cancelled during retry delay") 271 return 272 } 273 274 } 275 } 276 277 close(inactive) 278 279 if parentDownload != nil { 280 select { 281 case <-d.Transfer.Context().Done(): 282 d.err = errors.New("layer registration cancelled") 283 downloadReader.Close() 284 return 285 case <-parentDownload.Done(): 286 } 287 288 l, err := parentDownload.result() 289 if err != nil { 290 d.err = err 291 downloadReader.Close() 292 return 293 } 294 parentLayer = l.ChainID() 295 } 296 297 reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(d.Transfer.Context(), downloadReader), progressOutput, size, descriptor.ID(), "Extracting") 298 defer reader.Close() 299 300 inflatedLayerData, err := archive.DecompressStream(reader) 301 if err != nil { 302 d.err = fmt.Errorf("could not get decompression stream: %v", err) 303 return 304 } 305 306 d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer) 307 if err != nil { 308 select { 309 case <-d.Transfer.Context().Done(): 310 d.err = errors.New("layer registration cancelled") 311 default: 312 d.err = fmt.Errorf("failed to register layer: %v", err) 313 } 314 return 315 } 316 317 progress.Update(progressOutput, descriptor.ID(), "Pull complete") 318 withRegistered, hasRegistered := descriptor.(DownloadDescriptorWithRegistered) 319 if hasRegistered { 320 withRegistered.Registered(d.layer.DiffID()) 321 } 322 323 // Doesn't actually need to be its own goroutine, but 324 // done like this so we can defer close(c). 325 go func() { 326 <-d.Transfer.Released() 327 if d.layer != nil { 328 layer.ReleaseAndLog(d.layerStore, d.layer) 329 } 330 }() 331 }() 332 333 return d 334 } 335 } 336 337 // makeDownloadFuncFromDownload returns a function that performs the layer 338 // registration when the layer data is coming from an existing download. It 339 // waits for sourceDownload and parentDownload to complete, and then 340 // reregisters the data from sourceDownload's top layer on top of 341 // parentDownload. This function does not log progress output because it would 342 // interfere with the progress reporting for sourceDownload, which has the same 343 // Key. 344 func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer) DoFunc { 345 return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer { 346 d := &downloadTransfer{ 347 Transfer: NewTransfer(), 348 layerStore: ldm.layerStore, 349 } 350 351 go func() { 352 defer func() { 353 close(progressChan) 354 }() 355 356 <-start 357 358 close(inactive) 359 360 select { 361 case <-d.Transfer.Context().Done(): 362 d.err = errors.New("layer registration cancelled") 363 return 364 case <-parentDownload.Done(): 365 } 366 367 l, err := parentDownload.result() 368 if err != nil { 369 d.err = err 370 return 371 } 372 parentLayer := l.ChainID() 373 374 // sourceDownload should have already finished if 375 // parentDownload finished, but wait for it explicitly 376 // to be sure. 377 select { 378 case <-d.Transfer.Context().Done(): 379 d.err = errors.New("layer registration cancelled") 380 return 381 case <-sourceDownload.Done(): 382 } 383 384 l, err = sourceDownload.result() 385 if err != nil { 386 d.err = err 387 return 388 } 389 390 layerReader, err := l.TarStream() 391 if err != nil { 392 d.err = err 393 return 394 } 395 defer layerReader.Close() 396 397 d.layer, err = d.layerStore.Register(layerReader, parentLayer) 398 if err != nil { 399 d.err = fmt.Errorf("failed to register layer: %v", err) 400 return 401 } 402 403 withRegistered, hasRegistered := descriptor.(DownloadDescriptorWithRegistered) 404 if hasRegistered { 405 withRegistered.Registered(d.layer.DiffID()) 406 } 407 408 // Doesn't actually need to be its own goroutine, but 409 // done like this so we can defer close(c). 410 go func() { 411 <-d.Transfer.Released() 412 if d.layer != nil { 413 layer.ReleaseAndLog(d.layerStore, d.layer) 414 } 415 }() 416 }() 417 418 return d 419 } 420 }