github.com/uber/kraken@v0.1.4/lib/dockerregistry/transfer/ro_transferer_test.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package transfer
    15  
    16  import (
    17  	"bytes"
    18  	"io"
    19  	"io/ioutil"
    20  	"os"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/uber/kraken/build-index/tagclient"
    26  	"github.com/uber/kraken/core"
    27  	"github.com/uber/kraken/lib/store"
    28  	"github.com/uber/kraken/mocks/build-index/tagclient"
    29  	"github.com/uber/kraken/mocks/lib/torrent/scheduler"
    30  	"github.com/uber/kraken/utils/testutil"
    31  
    32  	"github.com/golang/mock/gomock"
    33  	"github.com/stretchr/testify/require"
    34  	"github.com/uber-go/tally"
    35  )
    36  
    37  type agentTransfererMocks struct {
    38  	cads  *store.CADownloadStore
    39  	tags  *mocktagclient.MockClient
    40  	sched *mockscheduler.MockScheduler
    41  }
    42  
    43  func newReadOnlyTransfererMocks(t *testing.T) (*agentTransfererMocks, func()) {
    44  	var cleanup testutil.Cleanup
    45  
    46  	cads, c := store.CADownloadStoreFixture()
    47  	cleanup.Add(c)
    48  
    49  	ctrl := gomock.NewController(t)
    50  	cleanup.Add(ctrl.Finish)
    51  
    52  	tags := mocktagclient.NewMockClient(ctrl)
    53  
    54  	sched := mockscheduler.NewMockScheduler(ctrl)
    55  
    56  	return &agentTransfererMocks{cads, tags, sched}, cleanup.Run
    57  }
    58  
    59  func (m *agentTransfererMocks) new() *ReadOnlyTransferer {
    60  	return NewReadOnlyTransferer(tally.NoopScope, m.cads, m.tags, m.sched)
    61  }
    62  
    63  func TestReadOnlyTransfererDownloadCachesBlob(t *testing.T) {
    64  	require := require.New(t)
    65  
    66  	mocks, cleanup := newReadOnlyTransfererMocks(t)
    67  	defer cleanup()
    68  
    69  	transferer := mocks.new()
    70  
    71  	namespace := "docker/repo-bar:latest"
    72  	blob := core.NewBlobFixture()
    73  
    74  	mocks.sched.EXPECT().Download(
    75  		namespace, blob.Digest).DoAndReturn(func(namespace string, d core.Digest) error {
    76  
    77  		return store.RunDownload(mocks.cads, d, blob.Content)
    78  	})
    79  
    80  	// Downloading multiple times should only call scheduler download once.
    81  	for i := 0; i < 10; i++ {
    82  		result, err := transferer.Download(namespace, blob.Digest)
    83  		require.NoError(err)
    84  		b, err := ioutil.ReadAll(result)
    85  		require.NoError(err)
    86  		require.Equal(blob.Content, b)
    87  	}
    88  }
    89  
    90  func TestReadOnlyTransfererStat(t *testing.T) {
    91  	require := require.New(t)
    92  
    93  	mocks, cleanup := newReadOnlyTransfererMocks(t)
    94  	defer cleanup()
    95  
    96  	transferer := mocks.new()
    97  
    98  	namespace := "docker/repo-bar:latest"
    99  	blob := core.NewBlobFixture()
   100  
   101  	mocks.sched.EXPECT().Download(
   102  		namespace, blob.Digest).DoAndReturn(func(namespace string, d core.Digest) error {
   103  
   104  		return store.RunDownload(mocks.cads, d, blob.Content)
   105  	})
   106  
   107  	// Stat-ing multiple times should only call scheduler download once.
   108  	for i := 0; i < 10; i++ {
   109  		bi, err := transferer.Stat(namespace, blob.Digest)
   110  		require.NoError(err)
   111  		require.Equal(blob.Info(), bi)
   112  	}
   113  }
   114  
   115  func TestReadOnlyTransfererGetTag(t *testing.T) {
   116  	require := require.New(t)
   117  
   118  	mocks, cleanup := newReadOnlyTransfererMocks(t)
   119  	defer cleanup()
   120  
   121  	transferer := mocks.new()
   122  
   123  	tag := "docker/some-tag"
   124  	manifest := core.DigestFixture()
   125  
   126  	mocks.tags.EXPECT().Get(tag).Return(manifest, nil)
   127  
   128  	d, err := transferer.GetTag(tag)
   129  	require.NoError(err)
   130  	require.Equal(manifest, d)
   131  }
   132  
   133  func TestReadOnlyTransfererGetTagNotFound(t *testing.T) {
   134  	require := require.New(t)
   135  
   136  	mocks, cleanup := newReadOnlyTransfererMocks(t)
   137  	defer cleanup()
   138  
   139  	transferer := mocks.new()
   140  
   141  	tag := "docker/some-tag"
   142  
   143  	mocks.tags.EXPECT().Get(tag).Return(core.Digest{}, tagclient.ErrTagNotFound)
   144  
   145  	_, err := transferer.GetTag(tag)
   146  	require.Error(err)
   147  	require.Equal(ErrTagNotFound, err)
   148  }
   149  
   150  // TODO(codyg): This is a particularly ugly test that is a symptom of the lack
   151  // of abstraction surrounding scheduler / file store operations.
   152  func TestReadOnlyTransfererMultipleDownloadsOfSameBlob(t *testing.T) {
   153  	require := require.New(t)
   154  
   155  	mocks, cleanup := newReadOnlyTransfererMocks(t)
   156  	defer cleanup()
   157  
   158  	transferer := mocks.new()
   159  
   160  	namespace := "docker/repo-bar:latest"
   161  	blob := core.NewBlobFixture()
   162  
   163  	require.NoError(mocks.cads.CreateDownloadFile(blob.Digest.Hex(), blob.Length()))
   164  	w, err := mocks.cads.GetDownloadFileReadWriter(blob.Digest.Hex())
   165  	require.NoError(err)
   166  	_, err = io.Copy(w, bytes.NewReader(blob.Content))
   167  	require.NoError(err)
   168  
   169  	commit := make(chan struct{})
   170  
   171  	mocks.sched.EXPECT().Download(
   172  		namespace, blob.Digest).DoAndReturn(func(namespace string, d core.Digest) error {
   173  
   174  		<-commit
   175  
   176  		if err := mocks.cads.MoveDownloadFileToCache(d.Hex()); !os.IsExist(err) {
   177  			return err
   178  		}
   179  		return nil
   180  	}).Times(10)
   181  
   182  	// Multiple clients trying to download the same file which is already in
   183  	// the download state should queue up until the file has been committed to
   184  	// the cache.
   185  	var wg sync.WaitGroup
   186  	for i := 0; i < 10; i++ {
   187  		wg.Add(1)
   188  		go func() {
   189  			defer wg.Done()
   190  			result, err := transferer.Download(namespace, blob.Digest)
   191  			require.NoError(err)
   192  			b, err := ioutil.ReadAll(result)
   193  			require.NoError(err)
   194  			require.Equal(blob.Content, b)
   195  		}()
   196  	}
   197  
   198  	time.Sleep(250 * time.Millisecond)
   199  
   200  	close(commit)
   201  
   202  	wg.Wait()
   203  }