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