github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/distribution/xfer/download_test.go (about) 1 package xfer 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "io/ioutil" 8 "sync/atomic" 9 "testing" 10 "time" 11 12 "github.com/docker/distribution/digest" 13 "github.com/docker/docker/image" 14 "github.com/docker/docker/layer" 15 "github.com/docker/docker/pkg/archive" 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, err := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0]))) 70 if err != nil { 71 // Digest calculation is not expected to throw an error, 72 // any error at this point is a program error 73 panic(err) 74 } 75 return createChainIDFromParent(layer.ChainID(dgst), dgsts[1:]...) 76 } 77 78 func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID) (layer.Layer, error) { 79 var ( 80 parent layer.Layer 81 err error 82 ) 83 84 if parentID != "" { 85 parent, err = ls.Get(parentID) 86 if err != nil { 87 return nil, err 88 } 89 } 90 91 l := &mockLayer{parent: parent} 92 _, err = l.layerData.ReadFrom(reader) 93 if err != nil { 94 return nil, err 95 } 96 diffID, err := digest.FromBytes(l.layerData.Bytes()) 97 if err != nil { 98 return nil, err 99 } 100 l.diffID = layer.DiffID(diffID) 101 l.chainID = createChainIDFromParent(parentID, l.diffID) 102 103 ls.layers[l.chainID] = l 104 return l, nil 105 } 106 107 func (ls *mockLayerStore) Get(chainID layer.ChainID) (layer.Layer, error) { 108 l, ok := ls.layers[chainID] 109 if !ok { 110 return nil, layer.ErrLayerDoesNotExist 111 } 112 return l, nil 113 } 114 115 func (ls *mockLayerStore) Release(l layer.Layer) ([]layer.Metadata, error) { 116 return []layer.Metadata{}, nil 117 } 118 119 func (ls *mockLayerStore) Mount(id string, parent layer.ChainID, label string, init layer.MountInit) (layer.RWLayer, error) { 120 return nil, errors.New("not implemented") 121 } 122 123 func (ls *mockLayerStore) Unmount(id string) error { 124 return errors.New("not implemented") 125 } 126 127 func (ls *mockLayerStore) DeleteMount(id string) ([]layer.Metadata, error) { 128 return nil, errors.New("not implemented") 129 } 130 131 func (ls *mockLayerStore) Changes(id string) ([]archive.Change, error) { 132 return nil, errors.New("not implemented") 133 } 134 135 func (ls *mockLayerStore) Metadata(id string) (map[string]string, error) { 136 return nil, errors.New("not implemented") 137 } 138 139 type mockDownloadDescriptor struct { 140 currentDownloads *int32 141 id string 142 diffID layer.DiffID 143 registeredDiffID layer.DiffID 144 expectedDiffID layer.DiffID 145 simulateRetries int 146 } 147 148 // Key returns the key used to deduplicate downloads. 149 func (d *mockDownloadDescriptor) Key() string { 150 return d.id 151 } 152 153 // ID returns the ID for display purposes. 154 func (d *mockDownloadDescriptor) ID() string { 155 return d.id 156 } 157 158 // DiffID should return the DiffID for this layer, or an error 159 // if it is unknown (for example, if it has not been downloaded 160 // before). 161 func (d *mockDownloadDescriptor) DiffID() (layer.DiffID, error) { 162 if d.diffID != "" { 163 return d.diffID, nil 164 } 165 return "", errors.New("no diffID available") 166 } 167 168 func (d *mockDownloadDescriptor) Registered(diffID layer.DiffID) { 169 d.registeredDiffID = diffID 170 } 171 172 func (d *mockDownloadDescriptor) mockTarStream() io.ReadCloser { 173 // The mock implementation returns the ID repeated 5 times as a tar 174 // stream instead of actual tar data. The data is ignored except for 175 // computing IDs. 176 return ioutil.NopCloser(bytes.NewBuffer([]byte(d.id + d.id + d.id + d.id + d.id))) 177 } 178 179 // Download is called to perform the download. 180 func (d *mockDownloadDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) { 181 if d.currentDownloads != nil { 182 defer atomic.AddInt32(d.currentDownloads, -1) 183 184 if atomic.AddInt32(d.currentDownloads, 1) > maxDownloadConcurrency { 185 return nil, 0, errors.New("concurrency limit exceeded") 186 } 187 } 188 189 // Sleep a bit to simulate a time-consuming download. 190 for i := int64(0); i <= 10; i++ { 191 select { 192 case <-ctx.Done(): 193 return nil, 0, ctx.Err() 194 case <-time.After(10 * time.Millisecond): 195 progressOutput.WriteProgress(progress.Progress{ID: d.ID(), Action: "Downloading", Current: i, Total: 10}) 196 } 197 } 198 199 if d.simulateRetries != 0 { 200 d.simulateRetries-- 201 return nil, 0, errors.New("simulating retry") 202 } 203 204 return d.mockTarStream(), 0, nil 205 } 206 207 func downloadDescriptors(currentDownloads *int32) []DownloadDescriptor { 208 return []DownloadDescriptor{ 209 &mockDownloadDescriptor{ 210 currentDownloads: currentDownloads, 211 id: "id1", 212 expectedDiffID: layer.DiffID("sha256:68e2c75dc5c78ea9240689c60d7599766c213ae210434c53af18470ae8c53ec1"), 213 }, 214 &mockDownloadDescriptor{ 215 currentDownloads: currentDownloads, 216 id: "id2", 217 expectedDiffID: layer.DiffID("sha256:64a636223116aa837973a5d9c2bdd17d9b204e4f95ac423e20e65dfbb3655473"), 218 }, 219 &mockDownloadDescriptor{ 220 currentDownloads: currentDownloads, 221 id: "id3", 222 expectedDiffID: layer.DiffID("sha256:58745a8bbd669c25213e9de578c4da5c8ee1c836b3581432c2b50e38a6753300"), 223 }, 224 &mockDownloadDescriptor{ 225 currentDownloads: currentDownloads, 226 id: "id2", 227 expectedDiffID: layer.DiffID("sha256:64a636223116aa837973a5d9c2bdd17d9b204e4f95ac423e20e65dfbb3655473"), 228 }, 229 &mockDownloadDescriptor{ 230 currentDownloads: currentDownloads, 231 id: "id4", 232 expectedDiffID: layer.DiffID("sha256:0dfb5b9577716cc173e95af7c10289322c29a6453a1718addc00c0c5b1330936"), 233 simulateRetries: 1, 234 }, 235 &mockDownloadDescriptor{ 236 currentDownloads: currentDownloads, 237 id: "id5", 238 expectedDiffID: layer.DiffID("sha256:0a5f25fa1acbc647f6112a6276735d0fa01e4ee2aa7ec33015e337350e1ea23d"), 239 }, 240 } 241 } 242 243 func TestSuccessfulDownload(t *testing.T) { 244 layerStore := &mockLayerStore{make(map[layer.ChainID]*mockLayer)} 245 ldm := NewLayerDownloadManager(layerStore, maxDownloadConcurrency) 246 247 progressChan := make(chan progress.Progress) 248 progressDone := make(chan struct{}) 249 receivedProgress := make(map[string]int64) 250 251 go func() { 252 for p := range progressChan { 253 if p.Action == "Downloading" { 254 receivedProgress[p.ID] = p.Current 255 } else if p.Action == "Already exists" { 256 receivedProgress[p.ID] = -1 257 } 258 } 259 close(progressDone) 260 }() 261 262 var currentDownloads int32 263 descriptors := downloadDescriptors(¤tDownloads) 264 265 firstDescriptor := descriptors[0].(*mockDownloadDescriptor) 266 267 // Pre-register the first layer to simulate an already-existing layer 268 l, err := layerStore.Register(firstDescriptor.mockTarStream(), "") 269 if err != nil { 270 t.Fatal(err) 271 } 272 firstDescriptor.diffID = l.DiffID() 273 274 rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), descriptors, progress.ChanOutput(progressChan)) 275 if err != nil { 276 t.Fatalf("download error: %v", err) 277 } 278 279 releaseFunc() 280 281 close(progressChan) 282 <-progressDone 283 284 if len(rootFS.DiffIDs) != len(descriptors) { 285 t.Fatal("got wrong number of diffIDs in rootfs") 286 } 287 288 for i, d := range descriptors { 289 descriptor := d.(*mockDownloadDescriptor) 290 291 if descriptor.diffID != "" { 292 if receivedProgress[d.ID()] != -1 { 293 t.Fatalf("did not get 'already exists' message for %v", d.ID()) 294 } 295 } else if receivedProgress[d.ID()] != 10 { 296 t.Fatalf("missing or wrong progress output for %v (got: %d)", d.ID(), receivedProgress[d.ID()]) 297 } 298 299 if rootFS.DiffIDs[i] != descriptor.expectedDiffID { 300 t.Fatalf("rootFS item %d has the wrong diffID (expected: %v got: %v)", i, descriptor.expectedDiffID, rootFS.DiffIDs[i]) 301 } 302 303 if descriptor.diffID == "" && descriptor.registeredDiffID != rootFS.DiffIDs[i] { 304 t.Fatal("diffID mismatch between rootFS and Registered callback") 305 } 306 } 307 } 308 309 func TestCancelledDownload(t *testing.T) { 310 ldm := NewLayerDownloadManager(&mockLayerStore{make(map[layer.ChainID]*mockLayer)}, maxDownloadConcurrency) 311 312 progressChan := make(chan progress.Progress) 313 progressDone := make(chan struct{}) 314 315 go func() { 316 for range progressChan { 317 } 318 close(progressDone) 319 }() 320 321 ctx, cancel := context.WithCancel(context.Background()) 322 323 go func() { 324 <-time.After(time.Millisecond) 325 cancel() 326 }() 327 328 descriptors := downloadDescriptors(nil) 329 _, _, err := ldm.Download(ctx, *image.NewRootFS(), descriptors, progress.ChanOutput(progressChan)) 330 if err != context.Canceled { 331 t.Fatal("expected download to be cancelled") 332 } 333 334 close(progressChan) 335 <-progressDone 336 }