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