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