github.com/thanos-io/thanos@v0.32.5/pkg/block/indexheader/lazy_binary_reader_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package indexheader 5 6 import ( 7 "context" 8 "os" 9 "path/filepath" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/go-kit/log" 15 "github.com/oklog/ulid" 16 promtestutil "github.com/prometheus/client_golang/prometheus/testutil" 17 "github.com/prometheus/prometheus/model/labels" 18 "github.com/thanos-io/objstore/providers/filesystem" 19 20 "github.com/efficientgo/core/testutil" 21 "github.com/thanos-io/thanos/pkg/block" 22 "github.com/thanos-io/thanos/pkg/block/metadata" 23 "github.com/thanos-io/thanos/pkg/testutil/e2eutil" 24 ) 25 26 func TestNewLazyBinaryReader_ShouldFailIfUnableToBuildIndexHeader(t *testing.T) { 27 ctx := context.Background() 28 29 tmpDir := t.TempDir() 30 31 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 32 testutil.Ok(t, err) 33 defer func() { testutil.Ok(t, bkt.Close()) }() 34 35 _, err = NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, ulid.MustNew(0, nil), 3, NewLazyBinaryReaderMetrics(nil), nil) 36 testutil.NotOk(t, err) 37 } 38 39 func TestNewLazyBinaryReader_ShouldBuildIndexHeaderFromBucket(t *testing.T) { 40 ctx := context.Background() 41 42 tmpDir := t.TempDir() 43 44 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 45 testutil.Ok(t, err) 46 defer func() { testutil.Ok(t, bkt.Close()) }() 47 48 // Create block. 49 blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{ 50 {{Name: "a", Value: "1"}}, 51 {{Name: "a", Value: "2"}}, 52 }, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc) 53 testutil.Ok(t, err) 54 testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc)) 55 56 m := NewLazyBinaryReaderMetrics(nil) 57 r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil) 58 testutil.Ok(t, err) 59 testutil.Assert(t, r.reader == nil) 60 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadCount)) 61 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount)) 62 63 // Should lazy load the index upon first usage. 64 v, err := r.IndexVersion() 65 testutil.Ok(t, err) 66 testutil.Equals(t, 2, v) 67 testutil.Assert(t, r.reader != nil) 68 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount)) 69 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount)) 70 71 labelNames, err := r.LabelNames() 72 testutil.Ok(t, err) 73 testutil.Equals(t, []string{"a"}, labelNames) 74 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount)) 75 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount)) 76 } 77 78 func TestNewLazyBinaryReader_ShouldRebuildCorruptedIndexHeader(t *testing.T) { 79 ctx := context.Background() 80 81 tmpDir := t.TempDir() 82 83 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 84 testutil.Ok(t, err) 85 defer func() { testutil.Ok(t, bkt.Close()) }() 86 87 // Create block. 88 blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{ 89 {{Name: "a", Value: "1"}}, 90 {{Name: "a", Value: "2"}}, 91 }, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc) 92 testutil.Ok(t, err) 93 testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc)) 94 95 // Write a corrupted index-header for the block. 96 headerFilename := filepath.Join(tmpDir, blockID.String(), block.IndexHeaderFilename) 97 testutil.Ok(t, os.WriteFile(headerFilename, []byte("xxx"), os.ModePerm)) 98 99 m := NewLazyBinaryReaderMetrics(nil) 100 r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil) 101 testutil.Ok(t, err) 102 testutil.Assert(t, r.reader == nil) 103 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadCount)) 104 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount)) 105 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount)) 106 107 // Ensure it can read data. 108 labelNames, err := r.LabelNames() 109 testutil.Ok(t, err) 110 testutil.Equals(t, []string{"a"}, labelNames) 111 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount)) 112 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount)) 113 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount)) 114 } 115 116 func TestLazyBinaryReader_ShouldReopenOnUsageAfterClose(t *testing.T) { 117 ctx := context.Background() 118 119 tmpDir := t.TempDir() 120 121 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 122 testutil.Ok(t, err) 123 defer func() { testutil.Ok(t, bkt.Close()) }() 124 125 // Create block. 126 blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{ 127 {{Name: "a", Value: "1"}}, 128 {{Name: "a", Value: "2"}}, 129 }, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc) 130 testutil.Ok(t, err) 131 testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc)) 132 133 m := NewLazyBinaryReaderMetrics(nil) 134 r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil) 135 testutil.Ok(t, err) 136 testutil.Assert(t, r.reader == nil) 137 138 // Should lazy load the index upon first usage. 139 labelNames, err := r.LabelNames() 140 testutil.Ok(t, err) 141 testutil.Equals(t, []string{"a"}, labelNames) 142 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount)) 143 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount)) 144 145 // Close it. 146 testutil.Ok(t, r.Close()) 147 testutil.Assert(t, r.reader == nil) 148 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.unloadCount)) 149 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount)) 150 151 // Should lazy load again upon next usage. 152 labelNames, err = r.LabelNames() 153 testutil.Ok(t, err) 154 testutil.Equals(t, []string{"a"}, labelNames) 155 testutil.Equals(t, float64(2), promtestutil.ToFloat64(m.loadCount)) 156 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount)) 157 158 // Closing an already closed lazy reader should be a no-op. 159 for i := 0; i < 2; i++ { 160 testutil.Ok(t, r.Close()) 161 testutil.Equals(t, float64(2), promtestutil.ToFloat64(m.unloadCount)) 162 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount)) 163 } 164 } 165 166 func TestLazyBinaryReader_unload_ShouldReturnErrorIfNotIdle(t *testing.T) { 167 ctx := context.Background() 168 169 tmpDir := t.TempDir() 170 171 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 172 testutil.Ok(t, err) 173 defer func() { testutil.Ok(t, bkt.Close()) }() 174 175 // Create block. 176 blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{ 177 {{Name: "a", Value: "1"}}, 178 {{Name: "a", Value: "2"}}, 179 }, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc) 180 testutil.Ok(t, err) 181 testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc)) 182 183 m := NewLazyBinaryReaderMetrics(nil) 184 r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil) 185 testutil.Ok(t, err) 186 testutil.Assert(t, r.reader == nil) 187 188 // Should lazy load the index upon first usage. 189 labelNames, err := r.LabelNames() 190 testutil.Ok(t, err) 191 testutil.Equals(t, []string{"a"}, labelNames) 192 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount)) 193 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount)) 194 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount)) 195 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount)) 196 197 // Try to unload but not idle since enough time. 198 testutil.Equals(t, errNotIdle, r.unloadIfIdleSince(time.Now().Add(-time.Minute).UnixNano())) 199 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount)) 200 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount)) 201 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount)) 202 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount)) 203 204 // Try to unload and idle since enough time. 205 testutil.Ok(t, r.unloadIfIdleSince(time.Now().UnixNano())) 206 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount)) 207 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount)) 208 testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.unloadCount)) 209 testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount)) 210 } 211 212 func TestLazyBinaryReader_LoadUnloadRaceCondition(t *testing.T) { 213 // Run the test for a fixed amount of time. 214 const runDuration = 5 * time.Second 215 216 ctx := context.Background() 217 218 tmpDir := t.TempDir() 219 220 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 221 testutil.Ok(t, err) 222 defer func() { testutil.Ok(t, bkt.Close()) }() 223 224 // Create block. 225 blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{ 226 {{Name: "a", Value: "1"}}, 227 {{Name: "a", Value: "2"}}, 228 }, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc) 229 testutil.Ok(t, err) 230 testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc)) 231 232 m := NewLazyBinaryReaderMetrics(nil) 233 r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil) 234 testutil.Ok(t, err) 235 testutil.Assert(t, r.reader == nil) 236 t.Cleanup(func() { 237 testutil.Ok(t, r.Close()) 238 }) 239 240 done := make(chan struct{}) 241 time.AfterFunc(runDuration, func() { close(done) }) 242 wg := sync.WaitGroup{} 243 wg.Add(2) 244 245 // Start a goroutine which continuously try to unload the reader. 246 go func() { 247 defer wg.Done() 248 249 for { 250 select { 251 case <-done: 252 return 253 default: 254 testutil.Ok(t, r.unloadIfIdleSince(0)) 255 } 256 } 257 }() 258 259 // Try to read multiple times, while the other goroutine continuously try to unload it. 260 go func() { 261 defer wg.Done() 262 263 for { 264 select { 265 case <-done: 266 return 267 default: 268 _, err := r.PostingsOffset("a", "1") 269 testutil.Assert(t, err == nil || err == errUnloadedWhileLoading) 270 } 271 } 272 }() 273 274 // Wait until both goroutines have done. 275 wg.Wait() 276 }