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  }