github.com/uber/kraken@v0.1.4/lib/torrent/storage/agentstorage/torrent_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 agentstorage
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"io/ioutil"
    20  	"math"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/uber/kraken/core"
    26  	"github.com/uber/kraken/lib/store"
    27  	"github.com/uber/kraken/lib/store/metadata"
    28  	"github.com/uber/kraken/lib/torrent/storage"
    29  	"github.com/uber/kraken/lib/torrent/storage/piecereader"
    30  	"github.com/uber/kraken/mocks/lib/store"
    31  	"github.com/uber/kraken/utils/bitsetutil"
    32  
    33  	"github.com/golang/mock/gomock"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  func prepareStore(cads *store.CADownloadStore, mi *core.MetaInfo) {
    38  	if err := cads.CreateDownloadFile(mi.Digest().Hex(), mi.Length()); err != nil {
    39  		panic(err)
    40  	}
    41  	if _, err := cads.Download().SetMetadata(mi.Digest().Hex(), metadata.NewTorrentMeta(mi)); err != nil {
    42  		panic(err)
    43  	}
    44  }
    45  
    46  func TestTorrentCreate(t *testing.T) {
    47  	require := require.New(t)
    48  
    49  	cads, cleanup := store.CADownloadStoreFixture()
    50  	defer cleanup()
    51  
    52  	mi := core.SizedBlobFixture(7, 2).MetaInfo
    53  
    54  	prepareStore(cads, mi)
    55  
    56  	tor, err := NewTorrent(cads, mi)
    57  	require.NoError(err)
    58  
    59  	// New torrent
    60  	require.Equal(mi.Digest(), tor.Digest())
    61  	require.Equal(4, tor.NumPieces())
    62  	require.Equal(int64(7), tor.Length())
    63  	require.Equal(int64(2), tor.PieceLength(0))
    64  	require.Equal(int64(1), tor.PieceLength(3))
    65  	require.Equal(mi.InfoHash(), tor.InfoHash())
    66  	require.False(tor.Complete())
    67  	require.Equal(int64(0), tor.BytesDownloaded())
    68  	require.Equal(bitsetutil.FromBools(false, false, false, false), tor.Bitfield())
    69  	require.False(tor.HasPiece(0))
    70  	require.Equal([]int{0, 1, 2, 3}, tor.MissingPieces())
    71  }
    72  
    73  func TestTorrentWriteUpdatesBytesDownloadedAndBitfield(t *testing.T) {
    74  	require := require.New(t)
    75  
    76  	cads, cleanup := store.CADownloadStoreFixture()
    77  	defer cleanup()
    78  
    79  	blob := core.SizedBlobFixture(2, 1)
    80  
    81  	prepareStore(cads, blob.MetaInfo)
    82  
    83  	tor, err := NewTorrent(cads, blob.MetaInfo)
    84  	require.NoError(err)
    85  
    86  	require.NoError(tor.WritePiece(piecereader.NewBuffer(blob.Content[:1]), 0))
    87  	require.False(tor.Complete())
    88  	require.Equal(int64(1), tor.BytesDownloaded())
    89  	require.Equal(bitsetutil.FromBools(true, false), tor.Bitfield())
    90  }
    91  
    92  func TestTorrentWriteComplete(t *testing.T) {
    93  	require := require.New(t)
    94  
    95  	cads, cleanup := store.CADownloadStoreFixture()
    96  	defer cleanup()
    97  
    98  	blob := core.SizedBlobFixture(1, 1)
    99  
   100  	prepareStore(cads, blob.MetaInfo)
   101  
   102  	tor, err := NewTorrent(cads, blob.MetaInfo)
   103  	require.NoError(err)
   104  
   105  	require.NoError(tor.WritePiece(piecereader.NewBuffer(blob.Content), 0))
   106  
   107  	r, err := tor.GetPieceReader(0)
   108  	require.NoError(err)
   109  	defer r.Close()
   110  	result, err := ioutil.ReadAll(r)
   111  	require.NoError(err)
   112  	require.Equal(blob.Content, result)
   113  
   114  	require.True(tor.Complete())
   115  	require.Equal(int64(1), tor.BytesDownloaded())
   116  
   117  	// Duplicate write should detect piece is complete.
   118  	require.Equal(storage.ErrPieceComplete, tor.WritePiece(piecereader.NewBuffer(blob.Content[:1]), 0))
   119  }
   120  
   121  func TestTorrentWriteMultiplePieceConcurrent(t *testing.T) {
   122  	require := require.New(t)
   123  
   124  	cads, cleanup := store.CADownloadStoreFixture()
   125  	defer cleanup()
   126  
   127  	blob := core.SizedBlobFixture(7, 2)
   128  
   129  	prepareStore(cads, blob.MetaInfo)
   130  
   131  	tor, err := NewTorrent(cads, blob.MetaInfo)
   132  	require.NoError(err)
   133  
   134  	wg := sync.WaitGroup{}
   135  	wg.Add(tor.NumPieces())
   136  	for i := 0; i < tor.NumPieces(); i++ {
   137  		go func(i int) {
   138  			defer wg.Done()
   139  			start := i * int(blob.MetaInfo.PieceLength())
   140  			end := start + int(tor.PieceLength(i))
   141  			require.NoError(tor.WritePiece(piecereader.NewBuffer(blob.Content[start:end]), i))
   142  		}(i)
   143  	}
   144  
   145  	wg.Wait()
   146  
   147  	// Complete
   148  	require.True(tor.Complete())
   149  	require.Equal(int64(7), tor.BytesDownloaded())
   150  	require.Nil(tor.MissingPieces())
   151  
   152  	// Check content
   153  	reader, err := cads.Cache().GetFileReader(blob.MetaInfo.Digest().Hex())
   154  	require.NoError(err)
   155  	torrentBytes, err := ioutil.ReadAll(reader)
   156  	require.NoError(err)
   157  	require.Equal(blob.Content, torrentBytes)
   158  }
   159  
   160  func TestTorrentWriteSamePieceConcurrent(t *testing.T) {
   161  	require := require.New(t)
   162  
   163  	cads, cleanup := store.CADownloadStoreFixture()
   164  	defer cleanup()
   165  
   166  	blob := core.SizedBlobFixture(16, 1)
   167  
   168  	prepareStore(cads, blob.MetaInfo)
   169  
   170  	tor, err := NewTorrent(cads, blob.MetaInfo)
   171  	require.NoError(err)
   172  
   173  	var wg sync.WaitGroup
   174  	for i := 0; i < 32; i++ {
   175  		wg.Add(1)
   176  		go func(i int) {
   177  			defer wg.Done()
   178  
   179  			pi := int(math.Mod(float64(i), float64(len(blob.Content))))
   180  
   181  			// If another goroutine is currently writing, we should get errWritePieceConflict.
   182  			// If another goroutine has finished writing, we should get storage.ErrPieceComplete.
   183  			err := tor.WritePiece(piecereader.NewBuffer([]byte{blob.Content[pi]}), pi)
   184  			if err != nil {
   185  				require.Contains([]error{errWritePieceConflict, storage.ErrPieceComplete}, err)
   186  			}
   187  
   188  			start := time.Now()
   189  			timeout := time.Duration(100 * time.Millisecond)
   190  			for {
   191  				time.Sleep(5 * time.Millisecond)
   192  				if time.Since(start) > timeout {
   193  					require.FailNow(fmt.Sprintf("failed to get piece reader %v after writing", timeout))
   194  				}
   195  
   196  				// If another goroutine was writing when we tried to, we will get errPieceNotComplete
   197  				// until they finish.
   198  				r, err := tor.GetPieceReader(pi)
   199  				if err != nil {
   200  					require.Equal(errPieceNotComplete, err)
   201  					continue
   202  				}
   203  				defer r.Close()
   204  
   205  				result, err := ioutil.ReadAll(r)
   206  				require.NoError(err)
   207  				require.Equal(1, len(result))
   208  				require.Equal(1, len(result))
   209  				require.Equal(blob.Content[pi], result[0])
   210  
   211  				return
   212  			}
   213  		}(i)
   214  	}
   215  	wg.Wait()
   216  
   217  	reader, err := cads.Cache().GetFileReader(blob.MetaInfo.Digest().Hex())
   218  	require.NoError(err)
   219  	torrentBytes, err := ioutil.ReadAll(reader)
   220  	require.NoError(err)
   221  	require.Equal(blob.Content, torrentBytes)
   222  }
   223  
   224  // mockGetDownloadFileReadWriterStore wraps an internal CADownloadStore but
   225  // overrides the GetDownloadFileReadWriter method to return f.
   226  type mockGetDownloadFileReadWriterStore struct {
   227  	*store.CADownloadStore
   228  	f store.FileReadWriter
   229  }
   230  
   231  func (s *mockGetDownloadFileReadWriterStore) GetDownloadFileReadWriter(
   232  	name string) (store.FileReadWriter, error) {
   233  
   234  	return s.f, nil
   235  }
   236  
   237  // coordinatedWriter allows blocking WriteAt calls to simulate race conditions.
   238  type coordinatedWriter struct {
   239  	store.FileReadWriter
   240  	startWriting chan bool
   241  	stopWriting  chan bool
   242  }
   243  
   244  func newCoordinatedWriter(f store.FileReadWriter) *coordinatedWriter {
   245  	return &coordinatedWriter{f, make(chan bool), make(chan bool)}
   246  }
   247  
   248  func (w *coordinatedWriter) Write(b []byte) (int, error) {
   249  	w.startWriting <- true
   250  	<-w.stopWriting
   251  	return len(b), nil
   252  }
   253  
   254  func TestTorrentWritePieceConflictsDoNotBlock(t *testing.T) {
   255  	require := require.New(t)
   256  
   257  	blob := core.SizedBlobFixture(1, 1)
   258  
   259  	f, cleanup := store.NewMockFileReadWriter([]byte{})
   260  	defer cleanup()
   261  
   262  	w := newCoordinatedWriter(f)
   263  
   264  	cads, cleanup := store.CADownloadStoreFixture()
   265  	defer cleanup()
   266  
   267  	prepareStore(cads, blob.MetaInfo)
   268  
   269  	mockCADS := &mockGetDownloadFileReadWriterStore{cads, w}
   270  
   271  	tor, err := NewTorrent(mockCADS, blob.MetaInfo)
   272  	require.NoError(err)
   273  
   274  	done := make(chan struct{})
   275  	go func() {
   276  		defer close(done)
   277  		require.NoError(tor.WritePiece(piecereader.NewBuffer(blob.Content), 0))
   278  	}()
   279  
   280  	// Writing while another goroutine is mid-write should not block.
   281  	<-w.startWriting
   282  	require.Equal(errWritePieceConflict, tor.WritePiece(piecereader.NewBuffer(blob.Content), 0))
   283  	w.stopWriting <- true
   284  
   285  	<-done
   286  
   287  	// Duplicate write should detect piece is complete.
   288  	require.Equal(storage.ErrPieceComplete, tor.WritePiece(piecereader.NewBuffer(blob.Content), 0))
   289  }
   290  
   291  func TestTorrentWritePieceFailuresRemoveDirtyStatus(t *testing.T) {
   292  	require := require.New(t)
   293  
   294  	ctrl := gomock.NewController(t)
   295  	defer ctrl.Finish()
   296  
   297  	w := mockstore.NewMockFileReadWriter(ctrl)
   298  
   299  	cads, cleanup := store.CADownloadStoreFixture()
   300  	defer cleanup()
   301  
   302  	blob := core.SizedBlobFixture(1, 1)
   303  
   304  	prepareStore(cads, blob.MetaInfo)
   305  
   306  	mockCADS := &mockGetDownloadFileReadWriterStore{cads, w}
   307  
   308  	gomock.InOrder(
   309  		// First write fails.
   310  		w.EXPECT().Seek(int64(0), 0).Return(int64(0), nil),
   311  		w.EXPECT().Write(blob.Content).Return(0, errors.New("first write error")),
   312  		w.EXPECT().Close().Return(nil),
   313  
   314  		// Second write succeeds.
   315  		w.EXPECT().Seek(int64(0), 0).Return(int64(0), nil),
   316  		w.EXPECT().Write(blob.Content).Return(len(blob.Content), nil),
   317  		w.EXPECT().Close().Return(nil),
   318  	)
   319  
   320  	tor, err := NewTorrent(mockCADS, blob.MetaInfo)
   321  	require.NoError(err)
   322  
   323  	// After the first write fails, the dirty bit should be flipped to empty,
   324  	// allowing future writes to succeed.
   325  	require.Error(tor.WritePiece(piecereader.NewBuffer(blob.Content), 0))
   326  	require.NoError(tor.WritePiece(piecereader.NewBuffer(blob.Content), 0))
   327  }
   328  
   329  func TestTorrentRestoreCompletedTorrent(t *testing.T) {
   330  	require := require.New(t)
   331  
   332  	cads, cleanup := store.CADownloadStoreFixture()
   333  	defer cleanup()
   334  
   335  	blob := core.SizedBlobFixture(8, 1)
   336  
   337  	prepareStore(cads, blob.MetaInfo)
   338  
   339  	tor, err := NewTorrent(cads, blob.MetaInfo)
   340  	require.NoError(err)
   341  
   342  	for i, b := range blob.Content {
   343  		require.NoError(tor.WritePiece(piecereader.NewBuffer([]byte{b}), i))
   344  	}
   345  
   346  	require.True(tor.Complete())
   347  
   348  	tor, err = NewTorrent(cads, blob.MetaInfo)
   349  	require.NoError(err)
   350  
   351  	require.True(tor.Complete())
   352  }
   353  
   354  func TestTorrentRestoreInProgressTorrent(t *testing.T) {
   355  	require := require.New(t)
   356  
   357  	cads, cleanup := store.CADownloadStoreFixture()
   358  	defer cleanup()
   359  
   360  	blob := core.SizedBlobFixture(8, 1)
   361  
   362  	prepareStore(cads, blob.MetaInfo)
   363  
   364  	tor, err := NewTorrent(cads, blob.MetaInfo)
   365  	require.NoError(err)
   366  
   367  	pi := 4
   368  
   369  	require.NoError(tor.WritePiece(piecereader.NewBuffer([]byte{blob.Content[pi]}), pi))
   370  	require.Equal(int64(1), tor.BytesDownloaded())
   371  
   372  	tor, err = NewTorrent(cads, blob.MetaInfo)
   373  	require.NoError(err)
   374  
   375  	require.Equal(int64(1), tor.BytesDownloaded())
   376  	require.Equal(
   377  		storage.ErrPieceComplete,
   378  		tor.WritePiece(piecereader.NewBuffer([]byte{blob.Content[pi]}), pi))
   379  }