github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/distribution/xfer/download_test.go (about)

     1  package xfer
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"runtime"
    10  	"sync/atomic"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/docker/distribution"
    15  	"github.com/docker/docker/image"
    16  	"github.com/docker/docker/layer"
    17  	"github.com/docker/docker/pkg/progress"
    18  	"github.com/opencontainers/go-digest"
    19  	"golang.org/x/net/context"
    20  )
    21  
    22  const maxDownloadConcurrency = 3
    23  
    24  type mockLayer struct {
    25  	layerData bytes.Buffer
    26  	diffID    layer.DiffID
    27  	chainID   layer.ChainID
    28  	parent    layer.Layer
    29  	os        layer.OS
    30  }
    31  
    32  func (ml *mockLayer) TarStream() (io.ReadCloser, error) {
    33  	return ioutil.NopCloser(bytes.NewBuffer(ml.layerData.Bytes())), nil
    34  }
    35  
    36  func (ml *mockLayer) TarStreamFrom(layer.ChainID) (io.ReadCloser, error) {
    37  	return nil, fmt.Errorf("not implemented")
    38  }
    39  
    40  func (ml *mockLayer) ChainID() layer.ChainID {
    41  	return ml.chainID
    42  }
    43  
    44  func (ml *mockLayer) DiffID() layer.DiffID {
    45  	return ml.diffID
    46  }
    47  
    48  func (ml *mockLayer) Parent() layer.Layer {
    49  	return ml.parent
    50  }
    51  
    52  func (ml *mockLayer) Size() (size int64, err error) {
    53  	return 0, nil
    54  }
    55  
    56  func (ml *mockLayer) DiffSize() (size int64, err error) {
    57  	return 0, nil
    58  }
    59  
    60  func (ml *mockLayer) OS() layer.OS {
    61  	return ml.os
    62  }
    63  
    64  func (ml *mockLayer) Metadata() (map[string]string, error) {
    65  	return make(map[string]string), nil
    66  }
    67  
    68  type mockLayerStore struct {
    69  	layers map[layer.ChainID]*mockLayer
    70  }
    71  
    72  func createChainIDFromParent(parent layer.ChainID, dgsts ...layer.DiffID) layer.ChainID {
    73  	if len(dgsts) == 0 {
    74  		return parent
    75  	}
    76  	if parent == "" {
    77  		return createChainIDFromParent(layer.ChainID(dgsts[0]), dgsts[1:]...)
    78  	}
    79  	// H = "H(n-1) SHA256(n)"
    80  	dgst := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0])))
    81  	return createChainIDFromParent(layer.ChainID(dgst), dgsts[1:]...)
    82  }
    83  
    84  func (ls *mockLayerStore) Map() map[layer.ChainID]layer.Layer {
    85  	layers := map[layer.ChainID]layer.Layer{}
    86  
    87  	for k, v := range ls.layers {
    88  		layers[k] = v
    89  	}
    90  
    91  	return layers
    92  }
    93  
    94  func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID, os layer.OS) (layer.Layer, error) {
    95  	return ls.RegisterWithDescriptor(reader, parentID, distribution.Descriptor{})
    96  }
    97  
    98  func (ls *mockLayerStore) RegisterWithDescriptor(reader io.Reader, parentID layer.ChainID, _ distribution.Descriptor) (layer.Layer, error) {
    99  	var (
   100  		parent layer.Layer
   101  		err    error
   102  	)
   103  
   104  	if parentID != "" {
   105  		parent, err = ls.Get(parentID)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  	}
   110  
   111  	l := &mockLayer{parent: parent}
   112  	_, err = l.layerData.ReadFrom(reader)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	l.diffID = layer.DiffID(digest.FromBytes(l.layerData.Bytes()))
   117  	l.chainID = createChainIDFromParent(parentID, l.diffID)
   118  
   119  	ls.layers[l.chainID] = l
   120  	return l, nil
   121  }
   122  
   123  func (ls *mockLayerStore) Get(chainID layer.ChainID) (layer.Layer, error) {
   124  	l, ok := ls.layers[chainID]
   125  	if !ok {
   126  		return nil, layer.ErrLayerDoesNotExist
   127  	}
   128  	return l, nil
   129  }
   130  
   131  func (ls *mockLayerStore) Release(l layer.Layer) ([]layer.Metadata, error) {
   132  	return []layer.Metadata{}, nil
   133  }
   134  func (ls *mockLayerStore) CreateRWLayer(string, layer.ChainID, *layer.CreateRWLayerOpts) (layer.RWLayer, error) {
   135  	return nil, errors.New("not implemented")
   136  }
   137  
   138  func (ls *mockLayerStore) GetRWLayer(string) (layer.RWLayer, error) {
   139  	return nil, errors.New("not implemented")
   140  }
   141  
   142  func (ls *mockLayerStore) ReleaseRWLayer(layer.RWLayer) ([]layer.Metadata, error) {
   143  	return nil, errors.New("not implemented")
   144  }
   145  func (ls *mockLayerStore) GetMountID(string) (string, error) {
   146  	return "", errors.New("not implemented")
   147  }
   148  
   149  func (ls *mockLayerStore) Cleanup() error {
   150  	return nil
   151  }
   152  
   153  func (ls *mockLayerStore) DriverStatus() [][2]string {
   154  	return [][2]string{}
   155  }
   156  
   157  func (ls *mockLayerStore) DriverName() string {
   158  	return "mock"
   159  }
   160  
   161  type mockDownloadDescriptor struct {
   162  	currentDownloads *int32
   163  	id               string
   164  	diffID           layer.DiffID
   165  	registeredDiffID layer.DiffID
   166  	expectedDiffID   layer.DiffID
   167  	simulateRetries  int
   168  }
   169  
   170  // Key returns the key used to deduplicate downloads.
   171  func (d *mockDownloadDescriptor) Key() string {
   172  	return d.id
   173  }
   174  
   175  // ID returns the ID for display purposes.
   176  func (d *mockDownloadDescriptor) ID() string {
   177  	return d.id
   178  }
   179  
   180  // DiffID should return the DiffID for this layer, or an error
   181  // if it is unknown (for example, if it has not been downloaded
   182  // before).
   183  func (d *mockDownloadDescriptor) DiffID() (layer.DiffID, error) {
   184  	if d.diffID != "" {
   185  		return d.diffID, nil
   186  	}
   187  	return "", errors.New("no diffID available")
   188  }
   189  
   190  func (d *mockDownloadDescriptor) Registered(diffID layer.DiffID) {
   191  	d.registeredDiffID = diffID
   192  }
   193  
   194  func (d *mockDownloadDescriptor) mockTarStream() io.ReadCloser {
   195  	// The mock implementation returns the ID repeated 5 times as a tar
   196  	// stream instead of actual tar data. The data is ignored except for
   197  	// computing IDs.
   198  	return ioutil.NopCloser(bytes.NewBuffer([]byte(d.id + d.id + d.id + d.id + d.id)))
   199  }
   200  
   201  // Download is called to perform the download.
   202  func (d *mockDownloadDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) {
   203  	if d.currentDownloads != nil {
   204  		defer atomic.AddInt32(d.currentDownloads, -1)
   205  
   206  		if atomic.AddInt32(d.currentDownloads, 1) > maxDownloadConcurrency {
   207  			return nil, 0, errors.New("concurrency limit exceeded")
   208  		}
   209  	}
   210  
   211  	// Sleep a bit to simulate a time-consuming download.
   212  	for i := int64(0); i <= 10; i++ {
   213  		select {
   214  		case <-ctx.Done():
   215  			return nil, 0, ctx.Err()
   216  		case <-time.After(10 * time.Millisecond):
   217  			progressOutput.WriteProgress(progress.Progress{ID: d.ID(), Action: "Downloading", Current: i, Total: 10})
   218  		}
   219  	}
   220  
   221  	if d.simulateRetries != 0 {
   222  		d.simulateRetries--
   223  		return nil, 0, errors.New("simulating retry")
   224  	}
   225  
   226  	return d.mockTarStream(), 0, nil
   227  }
   228  
   229  func (d *mockDownloadDescriptor) Close() {
   230  }
   231  
   232  func downloadDescriptors(currentDownloads *int32) []DownloadDescriptor {
   233  	return []DownloadDescriptor{
   234  		&mockDownloadDescriptor{
   235  			currentDownloads: currentDownloads,
   236  			id:               "id1",
   237  			expectedDiffID:   layer.DiffID("sha256:68e2c75dc5c78ea9240689c60d7599766c213ae210434c53af18470ae8c53ec1"),
   238  		},
   239  		&mockDownloadDescriptor{
   240  			currentDownloads: currentDownloads,
   241  			id:               "id2",
   242  			expectedDiffID:   layer.DiffID("sha256:64a636223116aa837973a5d9c2bdd17d9b204e4f95ac423e20e65dfbb3655473"),
   243  		},
   244  		&mockDownloadDescriptor{
   245  			currentDownloads: currentDownloads,
   246  			id:               "id3",
   247  			expectedDiffID:   layer.DiffID("sha256:58745a8bbd669c25213e9de578c4da5c8ee1c836b3581432c2b50e38a6753300"),
   248  		},
   249  		&mockDownloadDescriptor{
   250  			currentDownloads: currentDownloads,
   251  			id:               "id2",
   252  			expectedDiffID:   layer.DiffID("sha256:64a636223116aa837973a5d9c2bdd17d9b204e4f95ac423e20e65dfbb3655473"),
   253  		},
   254  		&mockDownloadDescriptor{
   255  			currentDownloads: currentDownloads,
   256  			id:               "id4",
   257  			expectedDiffID:   layer.DiffID("sha256:0dfb5b9577716cc173e95af7c10289322c29a6453a1718addc00c0c5b1330936"),
   258  			simulateRetries:  1,
   259  		},
   260  		&mockDownloadDescriptor{
   261  			currentDownloads: currentDownloads,
   262  			id:               "id5",
   263  			expectedDiffID:   layer.DiffID("sha256:0a5f25fa1acbc647f6112a6276735d0fa01e4ee2aa7ec33015e337350e1ea23d"),
   264  		},
   265  	}
   266  }
   267  
   268  func TestSuccessfulDownload(t *testing.T) {
   269  	// TODO Windows: Fix this unit text
   270  	if runtime.GOOS == "windows" {
   271  		t.Skip("Needs fixing on Windows")
   272  	}
   273  
   274  	layerStore := &mockLayerStore{make(map[layer.ChainID]*mockLayer)}
   275  	lsMap := make(map[string]layer.Store)
   276  	lsMap[runtime.GOOS] = layerStore
   277  	ldm := NewLayerDownloadManager(lsMap, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
   278  
   279  	progressChan := make(chan progress.Progress)
   280  	progressDone := make(chan struct{})
   281  	receivedProgress := make(map[string]progress.Progress)
   282  
   283  	go func() {
   284  		for p := range progressChan {
   285  			receivedProgress[p.ID] = p
   286  		}
   287  		close(progressDone)
   288  	}()
   289  
   290  	var currentDownloads int32
   291  	descriptors := downloadDescriptors(&currentDownloads)
   292  
   293  	firstDescriptor := descriptors[0].(*mockDownloadDescriptor)
   294  
   295  	// Pre-register the first layer to simulate an already-existing layer
   296  	l, err := layerStore.Register(firstDescriptor.mockTarStream(), "", layer.OS(runtime.GOOS))
   297  	if err != nil {
   298  		t.Fatal(err)
   299  	}
   300  	firstDescriptor.diffID = l.DiffID()
   301  
   302  	rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), layer.OS(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
   303  	if err != nil {
   304  		t.Fatalf("download error: %v", err)
   305  	}
   306  
   307  	releaseFunc()
   308  
   309  	close(progressChan)
   310  	<-progressDone
   311  
   312  	if len(rootFS.DiffIDs) != len(descriptors) {
   313  		t.Fatal("got wrong number of diffIDs in rootfs")
   314  	}
   315  
   316  	for i, d := range descriptors {
   317  		descriptor := d.(*mockDownloadDescriptor)
   318  
   319  		if descriptor.diffID != "" {
   320  			if receivedProgress[d.ID()].Action != "Already exists" {
   321  				t.Fatalf("did not get 'Already exists' message for %v", d.ID())
   322  			}
   323  		} else if receivedProgress[d.ID()].Action != "Pull complete" {
   324  			t.Fatalf("did not get 'Pull complete' message for %v", d.ID())
   325  		}
   326  
   327  		if rootFS.DiffIDs[i] != descriptor.expectedDiffID {
   328  			t.Fatalf("rootFS item %d has the wrong diffID (expected: %v got: %v)", i, descriptor.expectedDiffID, rootFS.DiffIDs[i])
   329  		}
   330  
   331  		if descriptor.diffID == "" && descriptor.registeredDiffID != rootFS.DiffIDs[i] {
   332  			t.Fatal("diffID mismatch between rootFS and Registered callback")
   333  		}
   334  	}
   335  }
   336  
   337  func TestCancelledDownload(t *testing.T) {
   338  	layerStore := &mockLayerStore{make(map[layer.ChainID]*mockLayer)}
   339  	lsMap := make(map[string]layer.Store)
   340  	lsMap[runtime.GOOS] = layerStore
   341  	ldm := NewLayerDownloadManager(lsMap, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
   342  
   343  	progressChan := make(chan progress.Progress)
   344  	progressDone := make(chan struct{})
   345  
   346  	go func() {
   347  		for range progressChan {
   348  		}
   349  		close(progressDone)
   350  	}()
   351  
   352  	ctx, cancel := context.WithCancel(context.Background())
   353  
   354  	go func() {
   355  		<-time.After(time.Millisecond)
   356  		cancel()
   357  	}()
   358  
   359  	descriptors := downloadDescriptors(nil)
   360  	_, _, err := ldm.Download(ctx, *image.NewRootFS(), layer.OS(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
   361  	if err != context.Canceled {
   362  		t.Fatal("expected download to be cancelled")
   363  	}
   364  
   365  	close(progressChan)
   366  	<-progressDone
   367  }