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