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 }