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  }