storj.io/uplink@v1.13.0/private/storage/streams/segmenttracker/tracker_test.go (about) 1 // Copyright (C) 2023 Storj Labs, Inc. 2 // See LICENSE for copying information. 3 4 package segmenttracker 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "testing" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "storj.io/uplink/private/metaclient" 16 ) 17 18 func TestTracker(t *testing.T) { 19 setup := func(eTag string) (*Tracker, *fakeBatchScheduler) { 20 eTagCh := make(chan []byte, 1) 21 eTagCh <- []byte(eTag) 22 scheduler := new(fakeBatchScheduler) 23 return New(scheduler, eTagCh), scheduler 24 } 25 26 segment := func(index int32) Segment { 27 return &fakeSegment{index: index} 28 } 29 30 badSegment := func(index int32) Segment { 31 return &fakeSegment{index: index, err: errors.New("oh no")} 32 } 33 34 makeInlineSegmentWithEncryptedTag := func(index int32, encryptedTag string) metaclient.BatchItem { 35 return &metaclient.MakeInlineSegmentParams{PlainSize: int64(index), EncryptedTag: []byte(encryptedTag)} 36 } 37 38 makeInlineSegment := func(index int32) metaclient.BatchItem { 39 return makeInlineSegmentWithEncryptedTag(index, "") 40 } 41 42 commitSegmentWithEncryptedTag := func(index int32, encryptedTag string) metaclient.BatchItem { 43 return &metaclient.CommitSegmentParams{PlainSize: int64(index), EncryptedTag: []byte(encryptedTag)} 44 } 45 46 commitSegment := func(index int32) metaclient.BatchItem { 47 return commitSegmentWithEncryptedTag(index, "") 48 } 49 50 t.Run("SegmentDone holds back highest seen segment", func(t *testing.T) { 51 tracker, scheduler := setup("") 52 53 // 2 will should be held back 54 tracker.SegmentDone(segment(2), makeInlineSegment(2)) 55 scheduler.AssertScheduledAndReset(t) 56 57 // 3 done will allow 1 to be scheduled 58 tracker.SegmentDone(segment(3), makeInlineSegment(3)) 59 scheduler.AssertScheduledAndReset(t, makeInlineSegment(2)) 60 61 // 1 will be scheduled immediate since 3 is known and higher 62 tracker.SegmentDone(segment(1), makeInlineSegment(1)) 63 scheduler.AssertScheduledAndReset(t, makeInlineSegment(1)) 64 65 // 4 will still be held back (and 3 scheduled) even though 5 is 66 // known to be the last segment. 67 tracker.SegmentsScheduled(segment(5)) 68 tracker.SegmentDone(segment(4), makeInlineSegment(4)) 69 scheduler.AssertScheduledAndReset(t, makeInlineSegment(3)) 70 }) 71 72 t.Run("SegmentDone immediately schedules when etag is not a concern", func(t *testing.T) { 73 scheduler := new(fakeBatchScheduler) 74 tracker := New(scheduler, nil) 75 76 tracker.SegmentDone(segment(1), makeInlineSegment(1)) 77 scheduler.AssertScheduledAndReset(t, makeInlineSegment(1)) 78 }) 79 80 t.Run("Flush flushes the last segment", func(t *testing.T) { 81 t.Run("MakeInlineSegment", func(t *testing.T) { 82 tracker, scheduler := setup("etag") 83 tracker.SegmentDone(segment(1), makeInlineSegment(1)) 84 tracker.SegmentsScheduled(segment(1)) 85 86 scheduler.AssertScheduledAndReset(t) 87 err := tracker.Flush(context.Background()) 88 require.NoError(t, err) 89 scheduler.AssertScheduledAndReset(t, makeInlineSegmentWithEncryptedTag(1, "etag-1")) 90 }) 91 t.Run("CommitSegment", func(t *testing.T) { 92 tracker, scheduler := setup("etag") 93 tracker.SegmentDone(segment(1), commitSegment(1)) 94 tracker.SegmentsScheduled(segment(1)) 95 96 scheduler.AssertScheduledAndReset(t) 97 err := tracker.Flush(context.Background()) 98 require.NoError(t, err) 99 scheduler.AssertScheduledAndReset(t, commitSegmentWithEncryptedTag(1, "etag-1")) 100 }) 101 }) 102 103 t.Run("Flush responds to context cancellation waiting for etag", func(t *testing.T) { 104 scheduler := new(fakeBatchScheduler) 105 106 tracker := New(scheduler, make(chan []byte)) 107 tracker.SegmentDone(segment(1), makeInlineSegment(1)) 108 tracker.SegmentsScheduled(segment(1)) 109 110 ctx, cancel := context.WithCancel(context.Background()) 111 cancel() 112 113 err := tracker.Flush(ctx) 114 require.Error(t, err) 115 require.True(t, errors.Is(err, context.Canceled)) 116 }) 117 118 t.Run("Flush does not encrypt an empty etag", func(t *testing.T) { 119 tracker, scheduler := setup("") 120 tracker.SegmentDone(segment(1), makeInlineSegment(1)) 121 tracker.SegmentsScheduled(segment(1)) 122 123 err := tracker.Flush(context.Background()) 124 require.NoError(t, err) 125 scheduler.AssertScheduledAndReset(t, makeInlineSegment(1)) 126 }) 127 128 t.Run("Flush fails if last segment was never done", func(t *testing.T) { 129 tracker, _ := setup("etag") 130 tracker.SegmentDone(segment(1), makeInlineSegment(1)) 131 tracker.SegmentsScheduled(segment(2)) 132 err := tracker.Flush(context.Background()) 133 require.EqualError(t, err, "programmer error: expected held back segment with index 1 to have last segment index 2") 134 }) 135 136 t.Run("Flush fails if last segment batch item is unhandled", func(t *testing.T) { 137 tracker, _ := setup("etag") 138 tracker.SegmentDone(segment(1), &metaclient.BeginSegmentParams{}) 139 tracker.SegmentsScheduled(segment(1)) 140 err := tracker.Flush(context.Background()) 141 require.EqualError(t, err, "unhandled segment batch item type: *metaclient.BeginSegmentParams") 142 }) 143 144 t.Run("Flush before SegmentDone is an error", func(t *testing.T) { 145 tracker, _ := setup("") 146 err := tracker.Flush(context.Background()) 147 require.EqualError(t, err, "programmer error: no segment has been held back") 148 }) 149 150 t.Run("Flush before SegmentsScheduled is an error", func(t *testing.T) { 151 tracker, _ := setup("") 152 tracker.SegmentDone(segment(1), makeInlineSegment(1)) 153 err := tracker.Flush(context.Background()) 154 require.EqualError(t, err, "programmer error: cannot flush before last segment known") 155 }) 156 157 t.Run("Flush fails if eTag cannot be encrypted", func(t *testing.T) { 158 tracker, _ := setup("etag") 159 tracker.SegmentDone(badSegment(1), makeInlineSegment(1)) 160 tracker.SegmentsScheduled(segment(1)) 161 err := tracker.Flush(context.Background()) 162 require.EqualError(t, err, "failed to encrypt eTag: oh no") 163 }) 164 165 t.Run("Flush is no-op when etag is not a concern", func(t *testing.T) { 166 tracker := New(new(fakeBatchScheduler), nil) 167 err := tracker.Flush(context.Background()) 168 require.NoError(t, err) 169 }) 170 } 171 172 type fakeBatchScheduler struct { 173 scheduled []metaclient.BatchItem 174 } 175 176 func (s *fakeBatchScheduler) Schedule(batchItem metaclient.BatchItem) { 177 s.scheduled = append(s.scheduled, batchItem) 178 } 179 180 func (s *fakeBatchScheduler) AssertScheduledAndReset(t *testing.T, expected ...metaclient.BatchItem) { 181 assert.Equal(t, expected, s.scheduled) 182 s.scheduled = nil 183 } 184 185 type fakeSegment struct { 186 index int32 187 err error 188 } 189 190 func (s *fakeSegment) Position() metaclient.SegmentPosition { 191 return metaclient.SegmentPosition{Index: s.index} 192 } 193 194 func (s *fakeSegment) EncryptETag(eTag []byte) ([]byte, error) { 195 if s.err != nil { 196 return nil, s.err 197 } 198 return []byte(fmt.Sprintf("%s-%d", string(eTag), s.index)), nil 199 }