github.com/uber/kraken@v0.1.4/lib/torrent/scheduler/scheduler_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 scheduler 15 16 import ( 17 "os" 18 "sync" 19 "testing" 20 "time" 21 22 "github.com/uber/kraken/core" 23 "github.com/uber/kraken/lib/hashring" 24 "github.com/uber/kraken/lib/hostlist" 25 "github.com/uber/kraken/lib/torrent/networkevent" 26 "github.com/uber/kraken/lib/torrent/scheduler/announcequeue" 27 "github.com/uber/kraken/lib/torrent/storage/piecereader" 28 "github.com/uber/kraken/tracker/announceclient" 29 "github.com/uber/kraken/utils/bitsetutil" 30 31 "github.com/andres-erbsen/clock" 32 "github.com/stretchr/testify/require" 33 ) 34 35 func TestDownloadTorrentWithSeederAndLeecher(t *testing.T) { 36 require := require.New(t) 37 38 mocks, cleanup := newTestMocks(t) 39 defer cleanup() 40 41 config := configFixture() 42 43 seeder := mocks.newPeer(config) 44 leecher := mocks.newPeer(config) 45 46 blob := core.NewBlobFixture() 47 namespace := core.TagFixture() 48 49 mocks.metaInfoClient.EXPECT().Download( 50 namespace, blob.Digest).Return(blob.MetaInfo, nil).Times(2) 51 52 seeder.writeTorrent(namespace, blob) 53 require.NoError(seeder.scheduler.Download(namespace, blob.Digest)) 54 55 require.NoError(leecher.scheduler.Download(namespace, blob.Digest)) 56 leecher.checkTorrent(t, namespace, blob) 57 } 58 59 func TestDownloadManyTorrentsWithSeederAndLeecher(t *testing.T) { 60 require := require.New(t) 61 62 mocks, cleanup := newTestMocks(t) 63 defer cleanup() 64 65 config := configFixture() 66 namespace := core.TagFixture() 67 68 seeder := mocks.newPeer(config) 69 leecher := mocks.newPeer(config) 70 71 var wg sync.WaitGroup 72 for i := 0; i < 5; i++ { 73 blob := core.NewBlobFixture() 74 75 mocks.metaInfoClient.EXPECT().Download( 76 namespace, blob.Digest).Return(blob.MetaInfo, nil).Times(2) 77 78 wg.Add(1) 79 go func() { 80 defer wg.Done() 81 82 seeder.writeTorrent(namespace, blob) 83 require.NoError(seeder.scheduler.Download(namespace, blob.Digest)) 84 85 require.NoError(leecher.scheduler.Download(namespace, blob.Digest)) 86 leecher.checkTorrent(t, namespace, blob) 87 }() 88 } 89 wg.Wait() 90 } 91 92 func TestDownloadManyTorrentsWithSeederAndManyLeechers(t *testing.T) { 93 require := require.New(t) 94 95 mocks, cleanup := newTestMocks(t) 96 defer cleanup() 97 98 config := configFixture() 99 namespace := core.TagFixture() 100 101 seeder := mocks.newPeer(config) 102 leechers := mocks.newPeers(5, config) 103 104 // Start seeding each torrent. 105 blobs := make([]*core.BlobFixture, 5) 106 for i := range blobs { 107 blob := core.NewBlobFixture() 108 blobs[i] = blob 109 110 mocks.metaInfoClient.EXPECT().Download( 111 namespace, blob.Digest).Return(blob.MetaInfo, nil).Times(6) 112 113 seeder.writeTorrent(namespace, blob) 114 require.NoError(seeder.scheduler.Download(namespace, blob.Digest)) 115 } 116 117 var wg sync.WaitGroup 118 for _, blob := range blobs { 119 blob := blob 120 for _, p := range leechers { 121 p := p 122 wg.Add(1) 123 go func() { 124 defer wg.Done() 125 require.NoError(p.scheduler.Download(namespace, blob.Digest)) 126 p.checkTorrent(t, namespace, blob) 127 }() 128 } 129 } 130 wg.Wait() 131 } 132 133 func TestDownloadTorrentWhenPeersAllHaveDifferentPiece(t *testing.T) { 134 require := require.New(t) 135 136 mocks, cleanup := newTestMocks(t) 137 defer cleanup() 138 139 config := configFixture() 140 namespace := core.TagFixture() 141 142 peers := mocks.newPeers(10, config) 143 144 pieceLength := 256 145 blob := core.SizedBlobFixture(uint64(len(peers)*pieceLength), uint64(pieceLength)) 146 147 mocks.metaInfoClient.EXPECT().Download( 148 namespace, blob.Digest).Return(blob.MetaInfo, nil).Times(len(peers)) 149 150 var wg sync.WaitGroup 151 for i, p := range peers { 152 tor, err := p.torrentArchive.CreateTorrent(namespace, blob.Digest) 153 require.NoError(err) 154 155 piece := make([]byte, pieceLength) 156 start := i * pieceLength 157 stop := (i + 1) * pieceLength 158 copy(piece, blob.Content[start:stop]) 159 require.NoError(tor.WritePiece(piecereader.NewBuffer(piece), i)) 160 161 p := p 162 wg.Add(1) 163 go func() { 164 defer wg.Done() 165 require.NoError(p.scheduler.Download(namespace, blob.Digest)) 166 p.checkTorrent(t, namespace, blob) 167 }() 168 } 169 wg.Wait() 170 } 171 172 func TestSeederTTI(t *testing.T) { 173 require := require.New(t) 174 175 mocks, cleanup := newTestMocks(t) 176 defer cleanup() 177 178 config := configFixture() 179 180 blob := core.NewBlobFixture() 181 namespace := core.TagFixture() 182 183 mocks.metaInfoClient.EXPECT().Download( 184 namespace, blob.Digest).Return(blob.MetaInfo, nil).Times(2) 185 186 clk := clock.NewMock() 187 w := newEventWatcher() 188 189 seeder := mocks.newPeer(config, withEventLoop(w), withClock(clk)) 190 seeder.writeTorrent(namespace, blob) 191 require.NoError(seeder.scheduler.Download(namespace, blob.Digest)) 192 193 leecher := mocks.newPeer(config, withClock(clk)) 194 195 errc := make(chan error) 196 go func() { errc <- leecher.scheduler.Download(namespace, blob.Digest) }() 197 198 require.NoError(<-errc) 199 leecher.checkTorrent(t, namespace, blob) 200 201 // Conns expire... 202 clk.Add(config.ConnTTI) 203 204 clk.Add(config.PreemptionInterval) 205 w.waitFor(t, preemptionTickEvent{}) 206 207 // Then seeding torrents expire. 208 clk.Add(config.SeederTTI) 209 210 waitForTorrentRemoved(t, seeder.scheduler, blob.MetaInfo.InfoHash()) 211 waitForTorrentRemoved(t, leecher.scheduler, blob.MetaInfo.InfoHash()) 212 213 require.False(hasConn(seeder.scheduler, leecher.pctx.PeerID, blob.MetaInfo.InfoHash())) 214 require.False(hasConn(leecher.scheduler, seeder.pctx.PeerID, blob.MetaInfo.InfoHash())) 215 216 // Idle seeder should keep around the torrent file so it can still serve content. 217 _, err := seeder.torrentArchive.Stat(namespace, blob.Digest) 218 require.NoError(err) 219 } 220 221 func TestLeecherTTI(t *testing.T) { 222 t.Skip() 223 224 require := require.New(t) 225 226 mocks, cleanup := newTestMocks(t) 227 defer cleanup() 228 229 config := configFixture() 230 clk := clock.NewMock() 231 w := newEventWatcher() 232 233 blob := core.NewBlobFixture() 234 namespace := core.TagFixture() 235 236 mocks.metaInfoClient.EXPECT().Download(namespace, blob.Digest).Return(blob.MetaInfo, nil) 237 238 p := mocks.newPeer(config, withEventLoop(w), withClock(clk)) 239 errc := make(chan error) 240 go func() { errc <- p.scheduler.Download(namespace, blob.Digest) }() 241 242 waitForTorrentAdded(t, p.scheduler, blob.MetaInfo.InfoHash()) 243 244 clk.Add(config.LeecherTTI) 245 246 w.waitFor(t, preemptionTickEvent{}) 247 248 require.Equal(ErrTorrentTimeout, <-errc) 249 250 // Idle leecher should delete torrent file to prevent it from being revived. 251 _, err := p.torrentArchive.Stat(namespace, blob.Digest) 252 require.True(os.IsNotExist(err)) 253 } 254 255 func TestMultipleDownloadsForSameTorrentSucceed(t *testing.T) { 256 require := require.New(t) 257 258 mocks, cleanup := newTestMocks(t) 259 defer cleanup() 260 261 blob := core.NewBlobFixture() 262 namespace := core.TagFixture() 263 264 // Allow any number of downloads due to concurrency below. 265 mocks.metaInfoClient.EXPECT().Download( 266 namespace, blob.Digest).Return(blob.MetaInfo, nil).AnyTimes() 267 268 config := configFixture() 269 270 seeder := mocks.newPeer(config) 271 seeder.writeTorrent(namespace, blob) 272 require.NoError(seeder.scheduler.Download(namespace, blob.Digest)) 273 274 leecher := mocks.newPeer(config) 275 276 var wg sync.WaitGroup 277 for i := 0; i < 10; i++ { 278 wg.Add(1) 279 go func() { 280 defer wg.Done() 281 // Multiple goroutines should be able to wait on the same torrent. 282 require.NoError(leecher.scheduler.Download(namespace, blob.Digest)) 283 }() 284 } 285 wg.Wait() 286 287 leecher.checkTorrent(t, namespace, blob) 288 289 // After the torrent is complete, further calls to Download should succeed immediately. 290 require.NoError(leecher.scheduler.Download(namespace, blob.Digest)) 291 } 292 293 func TestEmitStatsEventTriggers(t *testing.T) { 294 mocks, cleanup := newTestMocks(t) 295 defer cleanup() 296 297 config := configFixture() 298 clk := clock.NewMock() 299 w := newEventWatcher() 300 301 mocks.newPeer(config, withEventLoop(w), withClock(clk)) 302 303 clk.Add(config.EmitStatsInterval) 304 w.waitFor(t, emitStatsEvent{}) 305 } 306 307 func TestNetworkEvents(t *testing.T) { 308 require := require.New(t) 309 310 mocks, cleanup := newTestMocks(t) 311 defer cleanup() 312 313 config := configFixture() 314 config.ConnTTI = 2 * time.Second 315 config.ConnState.BlacklistDuration = 30 * time.Second 316 317 seeder := mocks.newPeer(config) 318 leecher := mocks.newPeer(config) 319 320 // Torrent with 1 piece. 321 blob := core.SizedBlobFixture(1, 1) 322 namespace := core.TagFixture() 323 324 mocks.metaInfoClient.EXPECT().Download( 325 namespace, blob.Digest).Return(blob.MetaInfo, nil).Times(2) 326 327 seeder.writeTorrent(namespace, blob) 328 require.NoError(seeder.scheduler.Download(namespace, blob.Digest)) 329 330 require.NoError(leecher.scheduler.Download(namespace, blob.Digest)) 331 leecher.checkTorrent(t, namespace, blob) 332 333 sid := seeder.pctx.PeerID 334 lid := leecher.pctx.PeerID 335 h := blob.MetaInfo.InfoHash() 336 337 waitForConnRemoved(t, seeder.scheduler, lid, h) 338 waitForConnRemoved(t, leecher.scheduler, sid, h) 339 340 seederExpected := []*networkevent.Event{ 341 networkevent.AddTorrentEvent(h, sid, bitsetutil.FromBools(true), config.ConnState.MaxOpenConnectionsPerTorrent), 342 networkevent.TorrentCompleteEvent(h, sid), 343 networkevent.AddActiveConnEvent(h, sid, lid), 344 networkevent.DropActiveConnEvent(h, sid, lid), 345 networkevent.BlacklistConnEvent(h, sid, lid, config.ConnState.BlacklistDuration), 346 } 347 348 leecherExpected := []*networkevent.Event{ 349 networkevent.AddTorrentEvent(h, lid, bitsetutil.FromBools(false), config.ConnState.MaxOpenConnectionsPerTorrent), 350 networkevent.AddActiveConnEvent(h, lid, sid), 351 networkevent.RequestPieceEvent(h, lid, sid, 0), 352 networkevent.ReceivePieceEvent(h, lid, sid, 0), 353 networkevent.TorrentCompleteEvent(h, lid), 354 networkevent.DropActiveConnEvent(h, lid, sid), 355 networkevent.BlacklistConnEvent(h, lid, sid, config.ConnState.BlacklistDuration), 356 } 357 358 require.Equal( 359 networkevent.StripTimestamps(seederExpected), 360 networkevent.StripTimestamps(seeder.testProducer.Events())) 361 362 require.Equal( 363 networkevent.StripTimestamps(leecherExpected), 364 networkevent.StripTimestamps(leecher.testProducer.Events())) 365 } 366 367 func TestPullInactiveTorrent(t *testing.T) { 368 require := require.New(t) 369 370 mocks, cleanup := newTestMocks(t) 371 defer cleanup() 372 373 config := configFixture() 374 375 blob := core.NewBlobFixture() 376 namespace := core.TagFixture() 377 378 mocks.metaInfoClient.EXPECT().Download( 379 namespace, blob.Digest).Return(blob.MetaInfo, nil).Times(2) 380 381 seeder := mocks.newPeer(config) 382 383 // Write torrent to disk, but don't add it the scheduler. 384 seeder.writeTorrent(namespace, blob) 385 386 // Force announce the scheduler for this torrent to simulate a peer which 387 // is registered in tracker but does not have the torrent in memory. 388 ac := announceclient.New(seeder.pctx, hashring.NoopPassiveRing(hostlist.Fixture(mocks.trackerAddr)), nil) 389 ac.Announce(blob.Digest, blob.MetaInfo.InfoHash(), false, announceclient.V1) 390 391 leecher := mocks.newPeer(config) 392 393 require.NoError(leecher.scheduler.Download(namespace, blob.Digest)) 394 leecher.checkTorrent(t, namespace, blob) 395 } 396 397 func TestSchedulerReload(t *testing.T) { 398 require := require.New(t) 399 400 mocks, cleanup := newTestMocks(t) 401 defer cleanup() 402 403 config := configFixture() 404 namespace := core.TagFixture() 405 406 seeder := mocks.newPeer(config) 407 leecher := mocks.newPeer(config) 408 409 download := func() { 410 blob := core.NewBlobFixture() 411 412 mocks.metaInfoClient.EXPECT().Download( 413 namespace, blob.Digest).Return(blob.MetaInfo, nil).Times(2) 414 415 seeder.writeTorrent(namespace, blob) 416 require.NoError(seeder.scheduler.Download(namespace, blob.Digest)) 417 418 require.NoError(leecher.scheduler.Download(namespace, blob.Digest)) 419 leecher.checkTorrent(t, namespace, blob) 420 } 421 422 download() 423 424 rs := makeReloadable(leecher.scheduler, func() announcequeue.Queue { return announcequeue.New() }) 425 config.ConnTTL += 5 * time.Minute 426 rs.Reload(config) 427 leecher.scheduler = rs.scheduler 428 429 download() 430 } 431 432 func TestSchedulerRemoveTorrent(t *testing.T) { 433 require := require.New(t) 434 435 mocks, cleanup := newTestMocks(t) 436 defer cleanup() 437 438 w := newEventWatcher() 439 440 p := mocks.newPeer(configFixture(), withEventLoop(w)) 441 442 blob := core.NewBlobFixture() 443 namespace := core.TagFixture() 444 445 mocks.metaInfoClient.EXPECT().Download( 446 namespace, blob.Digest).Return(blob.MetaInfo, nil) 447 448 errc := make(chan error) 449 go func() { errc <- p.scheduler.Download(namespace, blob.Digest) }() 450 451 w.waitFor(t, newTorrentEvent{}) 452 453 require.NoError(p.scheduler.RemoveTorrent(blob.Digest)) 454 455 require.Equal(ErrTorrentRemoved, <-errc) 456 457 _, err := p.torrentArchive.Stat(namespace, blob.Digest) 458 require.True(os.IsNotExist(err)) 459 } 460 461 func TestSchedulerProbe(t *testing.T) { 462 require := require.New(t) 463 464 mocks, cleanup := newTestMocks(t) 465 defer cleanup() 466 467 p := mocks.newPeer(configFixture()) 468 469 require.NoError(p.scheduler.Probe()) 470 471 p.scheduler.Stop() 472 473 require.Equal(ErrSchedulerStopped, p.scheduler.Probe()) 474 } 475 476 type deadlockEvent struct { 477 release chan struct{} 478 } 479 480 func (e deadlockEvent) apply(*state) { 481 <-e.release 482 } 483 484 func TestSchedulerProbeTimeoutsIfDeadlocked(t *testing.T) { 485 require := require.New(t) 486 487 mocks, cleanup := newTestMocks(t) 488 defer cleanup() 489 490 config := configFixture() 491 config.ProbeTimeout = 250 * time.Millisecond 492 493 p := mocks.newPeer(config) 494 495 require.NoError(p.scheduler.Probe()) 496 497 // Must release deadlock so Scheduler can shut down properly (only matters 498 // for testing). 499 release := make(chan struct{}) 500 p.scheduler.eventLoop.send(deadlockEvent{release}) 501 502 require.Equal(ErrSendEventTimedOut, p.scheduler.Probe()) 503 504 close(release) 505 }