github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/transfer_test.go (about) 1 package ingester 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "sort" 8 "testing" 9 "time" 10 11 gokitlog "github.com/go-kit/log" 12 "github.com/go-kit/log/level" 13 "github.com/grafana/dskit/kv" 14 "github.com/grafana/dskit/ring" 15 "github.com/grafana/dskit/services" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 "github.com/weaveworks/common/user" 19 "golang.org/x/net/context" 20 "google.golang.org/grpc" 21 22 "github.com/grafana/loki/pkg/chunkenc" 23 "github.com/grafana/loki/pkg/ingester/client" 24 "github.com/grafana/loki/pkg/logproto" 25 "github.com/grafana/loki/pkg/logql/log" 26 util_log "github.com/grafana/loki/pkg/util/log" 27 ) 28 29 func TestTransferOut(t *testing.T) { 30 f := newTestIngesterFactory(t) 31 32 ing := f.getIngester(time.Duration(0), t) 33 34 // Push some data into our original ingester 35 ctx := user.InjectOrgID(context.Background(), "test") 36 _, err := ing.Push(ctx, &logproto.PushRequest{ 37 Streams: []logproto.Stream{ 38 { 39 Entries: []logproto.Entry{ 40 {Line: "line 0", Timestamp: time.Unix(0, 0)}, 41 {Line: "line 1", Timestamp: time.Unix(1, 0)}, 42 }, 43 Labels: `{foo="bar",bar="baz1"}`, 44 }, 45 { 46 Entries: []logproto.Entry{ 47 {Line: "line 2", Timestamp: time.Unix(2, 0)}, 48 {Line: "line 3", Timestamp: time.Unix(3, 0)}, 49 }, 50 Labels: `{foo="bar",bar="baz2"}`, 51 }, 52 }, 53 }) 54 require.NoError(t, err) 55 56 assert.Len(t, ing.instances, 1) 57 if assert.Contains(t, ing.instances, "test") { 58 assert.Equal(t, ing.instances["test"].streams.Len(), 2) 59 } 60 61 // Create a new ingester and transfer data to it 62 ing2 := f.getIngester(time.Second*60, t) 63 defer services.StopAndAwaitTerminated(context.Background(), ing2) //nolint:errcheck 64 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing)) 65 66 assert.Len(t, ing2.instances, 1) 67 if assert.Contains(t, ing2.instances, "test") { 68 assert.Equal(t, ing2.instances["test"].streams.Len(), 2) 69 70 lines := []string{} 71 72 // Get all the lines back and make sure the blocks transferred successfully 73 _ = ing2.instances["test"].streams.ForEach(func(s *stream) (bool, error) { 74 it, err := s.Iterator( 75 context.TODO(), 76 nil, 77 time.Unix(0, 0), 78 time.Unix(10, 0), 79 logproto.FORWARD, 80 log.NewNoopPipeline().ForStream(s.labels), 81 ) 82 if !assert.NoError(t, err) { 83 return true, nil 84 } 85 86 for it.Next() { 87 entry := it.Entry() 88 lines = append(lines, entry.Line) 89 } 90 return true, nil 91 }) 92 93 sort.Strings(lines) 94 95 assert.Equal( 96 t, 97 []string{"line 0", "line 1", "line 2", "line 3"}, 98 lines, 99 ) 100 } 101 } 102 103 type testIngesterFactory struct { 104 t *testing.T 105 store kv.Client 106 n int 107 ingesters map[string]*Ingester 108 } 109 110 func newTestIngesterFactory(t *testing.T) *testIngesterFactory { 111 kvClient, err := kv.NewClient(kv.Config{Store: "inmemory"}, ring.GetCodec(), nil, gokitlog.NewNopLogger()) 112 require.NoError(t, err) 113 114 return &testIngesterFactory{ 115 t: t, 116 store: kvClient, 117 ingesters: make(map[string]*Ingester), 118 } 119 } 120 121 func (f *testIngesterFactory) getIngester(joinAfter time.Duration, t *testing.T) *Ingester { 122 f.n++ 123 124 cfg := defaultIngesterTestConfig(t) 125 cfg.MaxTransferRetries = 1 126 cfg.LifecyclerConfig.ID = fmt.Sprintf("localhost-%d", f.n) 127 cfg.LifecyclerConfig.RingConfig.KVStore.Mock = f.store 128 cfg.LifecyclerConfig.JoinAfter = joinAfter 129 cfg.LifecyclerConfig.Addr = cfg.LifecyclerConfig.ID 130 // Force a tiny chunk size and no encoding so we can guarantee multiple chunks 131 // These values are also crafted around the specific use of `line _` in the log line which is 6 bytes long 132 cfg.BlockSize = 3 // Block size needs to be less than chunk size so we can get more than one block per chunk 133 cfg.TargetChunkSize = 24 134 cfg.ChunkEncoding = chunkenc.EncNone.String() 135 136 cfg.ingesterClientFactory = func(cfg client.Config, addr string) (client.HealthAndIngesterClient, error) { 137 ingester, ok := f.ingesters[addr] 138 if !ok { 139 return nil, fmt.Errorf("no ingester %s", addr) 140 } 141 142 return client.ClosableHealthAndIngesterClient{ 143 PusherClient: nil, 144 QuerierClient: nil, 145 IngesterClient: &testIngesterClient{t: f.t, i: ingester}, 146 Closer: ioutil.NopCloser(nil), 147 }, nil 148 } 149 150 _, ing := newTestStore(f.t, cfg, nil) 151 f.ingesters[fmt.Sprintf("%s:0", cfg.LifecyclerConfig.ID)] = ing 152 153 // NB there's some kind of race condition with the in-memory KV client when 154 // we don't give the ingester a little bit of time to initialize. a 100ms 155 // wait time seems effective. 156 time.Sleep(time.Millisecond * 100) 157 return ing 158 } 159 160 type testIngesterClient struct { 161 t *testing.T 162 i *Ingester 163 } 164 165 func (c *testIngesterClient) TransferChunks(context.Context, ...grpc.CallOption) (logproto.Ingester_TransferChunksClient, error) { 166 chunkCh := make(chan *logproto.TimeSeriesChunk) 167 respCh := make(chan *logproto.TransferChunksResponse) 168 waitCh := make(chan bool) 169 170 client := &testTransferChunksClient{ch: chunkCh, resp: respCh, wait: waitCh} 171 go func() { 172 server := &testTransferChunksServer{ch: chunkCh, resp: respCh} 173 err := c.i.TransferChunks(server) 174 require.NoError(c.t, err) 175 }() 176 177 // After 50ms, we try killing the target ingester's lifecycler to verify 178 // that it obtained a lock on the shutdown process. This operation should 179 // block until the transfer completes. 180 // 181 // Then after another 50ms, we also allow data to start sending. This tests an issue 182 // where an ingester is shut down before it completes the handoff and ends up in an 183 // unhealthy state, permanently stuck in the handler for claiming tokens. 184 go func() { 185 time.Sleep(time.Millisecond * 50) 186 c.i.stopIncomingRequests() // used to be called from lifecycler, now it must be called *before* stopping lifecyler. (ingester does this on shutdown) 187 err := services.StopAndAwaitTerminated(context.Background(), c.i.lifecycler) 188 if err != nil { 189 level.Error(util_log.Logger).Log("msg", "lifecycler failed", "err", err) 190 } 191 }() 192 193 go func() { 194 time.Sleep(time.Millisecond * 100) 195 close(waitCh) 196 }() 197 198 return client, nil 199 } 200 201 type testTransferChunksClient struct { 202 wait chan bool 203 ch chan *logproto.TimeSeriesChunk 204 resp chan *logproto.TransferChunksResponse 205 206 grpc.ClientStream 207 } 208 209 func (c *testTransferChunksClient) Send(chunk *logproto.TimeSeriesChunk) error { 210 <-c.wait 211 c.ch <- chunk 212 return nil 213 } 214 215 func (c *testTransferChunksClient) CloseAndRecv() (*logproto.TransferChunksResponse, error) { 216 <-c.wait 217 close(c.ch) 218 resp := <-c.resp 219 close(c.resp) 220 return resp, nil 221 } 222 223 type testTransferChunksServer struct { 224 ch chan *logproto.TimeSeriesChunk 225 resp chan *logproto.TransferChunksResponse 226 227 grpc.ServerStream 228 } 229 230 func (s *testTransferChunksServer) Context() context.Context { 231 return context.Background() 232 } 233 234 func (s *testTransferChunksServer) SendAndClose(resp *logproto.TransferChunksResponse) error { 235 s.resp <- resp 236 return nil 237 } 238 239 func (s *testTransferChunksServer) Recv() (*logproto.TimeSeriesChunk, error) { 240 chunk, ok := <-s.ch 241 if !ok { 242 return nil, io.EOF 243 } 244 return chunk, nil 245 }