github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/targets/file/filetarget_test.go (about)

     1  package file
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"go.uber.org/atomic"
    16  	"gopkg.in/fsnotify.v1"
    17  
    18  	"github.com/go-kit/log"
    19  
    20  	"github.com/grafana/loki/clients/pkg/promtail/client/fake"
    21  	"github.com/grafana/loki/clients/pkg/promtail/positions"
    22  )
    23  
    24  func TestFileTargetSync(t *testing.T) {
    25  	w := log.NewSyncWriter(os.Stderr)
    26  	logger := log.NewLogfmtLogger(w)
    27  
    28  	dirName := newTestLogDirectories(t)
    29  	positionsFileName := filepath.Join(dirName, "positions.yml")
    30  	logDir1 := filepath.Join(dirName, "log1")
    31  	logDir1File1 := filepath.Join(logDir1, "test1.log")
    32  	logDir1File2 := filepath.Join(logDir1, "test2.log")
    33  
    34  	// Set the sync period to a really long value, to guarantee the sync timer never runs, this way we know
    35  	// everything saved was done through channel notifications when target.stop() was called.
    36  	ps, err := positions.New(logger, positions.Config{
    37  		SyncPeriod:    10 * time.Minute,
    38  		PositionsFile: positionsFileName,
    39  	})
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	client := fake.New(func() {})
    45  	defer client.Stop()
    46  
    47  	metrics := NewMetrics(nil)
    48  
    49  	ctx, cancel := context.WithCancel(context.Background())
    50  	defer cancel()
    51  	fakeHandler := make(chan fileTargetEvent)
    52  	receivedStartWatch := atomic.NewInt32(0)
    53  	receivedStopWatch := atomic.NewInt32(0)
    54  	go func() {
    55  		for {
    56  			select {
    57  			case event := <-fakeHandler:
    58  				switch event.eventType {
    59  				case fileTargetEventWatchStart:
    60  					receivedStartWatch.Add(1)
    61  				case fileTargetEventWatchStop:
    62  					receivedStopWatch.Add(1)
    63  				}
    64  			case <-ctx.Done():
    65  				return
    66  			}
    67  		}
    68  	}()
    69  	path := logDir1 + "/*.log"
    70  	target, err := NewFileTarget(metrics, logger, client, ps, path, "", nil, nil, &Config{
    71  		SyncPeriod: 1 * time.Minute, // assure the sync is not called by the ticker
    72  	}, nil, fakeHandler, "")
    73  	assert.NoError(t, err)
    74  
    75  	// Start with nothing watched.
    76  	if len(target.watches) != 0 {
    77  		t.Fatal("Expected watches to be 0 at this point in the test...")
    78  	}
    79  	if len(target.tails) != 0 {
    80  		t.Fatal("Expected tails to be 0 at this point in the test...")
    81  	}
    82  
    83  	// Create the base dir, still nothing watched.
    84  	err = os.MkdirAll(logDir1, 0750)
    85  	assert.NoError(t, err)
    86  
    87  	err = target.sync()
    88  	assert.NoError(t, err)
    89  
    90  	if len(target.watches) != 0 {
    91  		t.Fatal("Expected watches to be 0 at this point in the test...")
    92  	}
    93  	if len(target.tails) != 0 {
    94  		t.Fatal("Expected tails to be 0 at this point in the test...")
    95  	}
    96  
    97  	// Add a file, which should create a watcher and a tailer.
    98  	_, err = os.Create(logDir1File1)
    99  	assert.NoError(t, err)
   100  
   101  	// Delay sync() call to make sure the filesystem watch event does not fire during sync()
   102  	time.Sleep(10 * time.Millisecond)
   103  	err = target.sync()
   104  	assert.NoError(t, err)
   105  
   106  	assert.Equal(t, 1, len(target.watches),
   107  		"Expected watches to be 1 at this point in the test...",
   108  	)
   109  	assert.Equal(t, 1, len(target.tails),
   110  		"Expected tails to be 1 at this point in the test...",
   111  	)
   112  	require.Eventually(t, func() bool {
   113  		return receivedStartWatch.Load() == 1
   114  	}, time.Second*10, time.Millisecond*1, "Expected received starting watch event to be 1 at this point in the test...")
   115  
   116  	// Add another file, should get another tailer.
   117  	_, err = os.Create(logDir1File2)
   118  	assert.NoError(t, err)
   119  
   120  	err = target.sync()
   121  	assert.NoError(t, err)
   122  
   123  	assert.Equal(t, 1, len(target.watches),
   124  		"Expected watches to be 1 at this point in the test...",
   125  	)
   126  	assert.Equal(t, 2, len(target.tails),
   127  		"Expected tails to be 2 at this point in the test...",
   128  	)
   129  
   130  	// Remove one of the files, tailer should stop.
   131  	err = os.Remove(logDir1File1)
   132  	assert.NoError(t, err)
   133  
   134  	err = target.sync()
   135  	assert.NoError(t, err)
   136  
   137  	assert.Equal(t, 1, len(target.watches),
   138  		"Expected watches to be 1 at this point in the test...",
   139  	)
   140  	assert.Equal(t, 1, len(target.tails),
   141  		"Expected tails to be 1 at this point in the test...",
   142  	)
   143  
   144  	// Remove the entire directory, other tailer should stop and watcher should go away.
   145  	err = os.RemoveAll(logDir1)
   146  	assert.NoError(t, err)
   147  
   148  	err = target.sync()
   149  	assert.NoError(t, err)
   150  
   151  	assert.Equal(t, 0, len(target.watches),
   152  		"Expected watches to be 0 at this point in the test...",
   153  	)
   154  	assert.Equal(t, 0, len(target.tails),
   155  		"Expected tails to be 0 at this point in the test...",
   156  	)
   157  	require.Eventually(t, func() bool {
   158  		return receivedStartWatch.Load() == 1
   159  	}, time.Second*10, time.Millisecond*1, "Expected received starting watch event to be 1 at this point in the test...")
   160  	require.Eventually(t, func() bool {
   161  		return receivedStartWatch.Load() == 1
   162  	}, time.Second*10, time.Millisecond*1, "Expected received stopping watch event to be 1 at this point in the test...")
   163  
   164  	target.Stop()
   165  	ps.Stop()
   166  }
   167  
   168  func TestFileTargetPathExclusion(t *testing.T) {
   169  	w := log.NewSyncWriter(os.Stderr)
   170  	logger := log.NewLogfmtLogger(w)
   171  
   172  	dirName := newTestLogDirectories(t)
   173  	positionsFileName := filepath.Join(dirName, "positions.yml")
   174  	logDir1 := filepath.Join(dirName, "log1")
   175  	logDir2 := filepath.Join(dirName, "log2")
   176  	logDir3 := filepath.Join(dirName, "log3")
   177  	logFiles := []string{
   178  		filepath.Join(logDir1, "test1.log"),
   179  		filepath.Join(logDir1, "test2.log"),
   180  		filepath.Join(logDir2, "test1.log"),
   181  		filepath.Join(logDir3, "test1.log"),
   182  		filepath.Join(logDir3, "test2.log"),
   183  	}
   184  
   185  	// Set the sync period to a really long value, to guarantee the sync timer never runs, this way we know
   186  	// everything saved was done through channel notifications when target.stop() was called.
   187  	ps, err := positions.New(logger, positions.Config{
   188  		SyncPeriod:    10 * time.Minute,
   189  		PositionsFile: positionsFileName,
   190  	})
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  
   195  	client := fake.New(func() {})
   196  	defer client.Stop()
   197  
   198  	metrics := NewMetrics(nil)
   199  
   200  	ctx, cancel := context.WithCancel(context.Background())
   201  	defer cancel()
   202  	fakeHandler := make(chan fileTargetEvent)
   203  	receivedStartWatch := atomic.NewInt32(0)
   204  	receivedStopWatch := atomic.NewInt32(0)
   205  	go func() {
   206  		for {
   207  			select {
   208  			case event := <-fakeHandler:
   209  				switch event.eventType {
   210  				case fileTargetEventWatchStart:
   211  					receivedStartWatch.Add(1)
   212  				case fileTargetEventWatchStop:
   213  					receivedStopWatch.Add(1)
   214  				}
   215  			case <-ctx.Done():
   216  				return
   217  			}
   218  		}
   219  	}()
   220  	path := filepath.Join(dirName, "**", "*.log")
   221  	pathExclude := filepath.Join(dirName, "log3", "*.log")
   222  	target, err := NewFileTarget(metrics, logger, client, ps, path, pathExclude, nil, nil, &Config{
   223  		SyncPeriod: 1 * time.Minute, // assure the sync is not called by the ticker
   224  	}, nil, fakeHandler, "")
   225  	assert.NoError(t, err)
   226  
   227  	// Start with nothing watched.
   228  	if len(target.watches) != 0 {
   229  		t.Fatal("Expected watches to be 0 at this point in the test...")
   230  	}
   231  	if len(target.tails) != 0 {
   232  		t.Fatal("Expected tails to be 0 at this point in the test...")
   233  	}
   234  
   235  	// Create the base directories, still nothing watched.
   236  	err = os.MkdirAll(logDir1, 0750)
   237  	assert.NoError(t, err)
   238  	err = os.MkdirAll(logDir2, 0750)
   239  	assert.NoError(t, err)
   240  	err = os.MkdirAll(logDir3, 0750)
   241  	assert.NoError(t, err)
   242  
   243  	err = target.sync()
   244  	assert.NoError(t, err)
   245  
   246  	if len(target.watches) != 0 {
   247  		t.Fatal("Expected watches to be 0 at this point in the test...")
   248  	}
   249  	if len(target.tails) != 0 {
   250  		t.Fatal("Expected tails to be 0 at this point in the test...")
   251  	}
   252  
   253  	// Create all the files, which should create two directory watchers and three file tailers.
   254  	for _, f := range logFiles {
   255  		_, err = os.Create(f)
   256  		assert.NoError(t, err)
   257  	}
   258  
   259  	// Delay sync() call to make sure the filesystem watch event does not fire during sync()
   260  	time.Sleep(10 * time.Millisecond)
   261  	err = target.sync()
   262  	assert.NoError(t, err)
   263  
   264  	assert.Equal(t, 2, len(target.watches),
   265  		"Expected watches to be 2 at this point in the test...",
   266  	)
   267  	assert.Equal(t, 3, len(target.tails),
   268  		"Expected tails to be 3 at this point in the test...",
   269  	)
   270  	require.Eventually(t, func() bool {
   271  		return receivedStartWatch.Load() == 2
   272  	}, time.Second*10, time.Millisecond*1, "Expected received starting watch event to be 2 at this point in the test...")
   273  	require.Eventually(t, func() bool {
   274  		return receivedStopWatch.Load() == 0
   275  	}, time.Second*10, time.Millisecond*1, "Expected received stopping watch event to be 0 at this point in the test...")
   276  
   277  	// Remove the first directory, other tailer should stop and its watchers should go away.
   278  	// Only the non-excluded `logDir2` should be watched.
   279  	err = os.RemoveAll(logDir1)
   280  	assert.NoError(t, err)
   281  
   282  	err = target.sync()
   283  	assert.NoError(t, err)
   284  
   285  	assert.Equal(t, 1, len(target.watches),
   286  		"Expected watches to be 1 at this point in the test...",
   287  	)
   288  	assert.Equal(t, 1, len(target.tails),
   289  		"Expected tails to be 1 at this point in the test...",
   290  	)
   291  	require.Eventually(t, func() bool {
   292  		return receivedStartWatch.Load() == 2
   293  	}, time.Second*10, time.Millisecond*1, "Expected received starting watch event to still be 2 at this point in the test...")
   294  	require.Eventually(t, func() bool {
   295  		return receivedStopWatch.Load() == 1
   296  	}, time.Second*10, time.Millisecond*1, "Expected received stopping watch event to be 1 at this point in the test...")
   297  
   298  	target.Stop()
   299  	ps.Stop()
   300  }
   301  
   302  func TestHandleFileCreationEvent(t *testing.T) {
   303  	w := log.NewSyncWriter(os.Stderr)
   304  	logger := log.NewLogfmtLogger(w)
   305  
   306  	dirName := newTestLogDirectories(t)
   307  	positionsFileName := filepath.Join(dirName, "positions.yml")
   308  	logDir := filepath.Join(dirName, "log")
   309  	logFile := filepath.Join(logDir, "test1.log")
   310  
   311  	if err := os.MkdirAll(logDir, 0750); err != nil {
   312  		t.Fatal(err)
   313  	}
   314  
   315  	// Set the sync period to a really long value, to guarantee the sync timer never runs, this way we know
   316  	// everything saved was done through channel notifications when target.stop() was called.
   317  	ps, err := positions.New(logger, positions.Config{
   318  		SyncPeriod:    10 * time.Minute,
   319  		PositionsFile: positionsFileName,
   320  	})
   321  	if err != nil {
   322  		t.Fatal(err)
   323  	}
   324  
   325  	client := fake.New(func() {})
   326  	defer client.Stop()
   327  
   328  	metrics := NewMetrics(nil)
   329  
   330  	ctx, cancel := context.WithCancel(context.Background())
   331  	defer cancel()
   332  	fakeFileHandler := make(chan fsnotify.Event)
   333  	fakeTargetHandler := make(chan fileTargetEvent)
   334  	path := logDir + "/*.log"
   335  	go func() {
   336  		for {
   337  			select {
   338  			case <-fakeTargetHandler:
   339  				continue
   340  			case <-ctx.Done():
   341  				return
   342  			}
   343  		}
   344  	}()
   345  
   346  	target, err := NewFileTarget(metrics, logger, client, ps, path, "", nil, nil, &Config{
   347  		// To handle file creation event from channel, set enough long time as sync period
   348  		SyncPeriod: 10 * time.Minute,
   349  	}, fakeFileHandler, fakeTargetHandler, "")
   350  	if err != nil {
   351  		t.Fatal(err)
   352  	}
   353  
   354  	_, err = os.Create(logFile)
   355  	if err != nil {
   356  		t.Fatal(err)
   357  	}
   358  	fakeFileHandler <- fsnotify.Event{
   359  		Name: logFile,
   360  		Op:   fsnotify.Create,
   361  	}
   362  	require.Eventually(t, func() bool {
   363  		return len(target.tails) == 1
   364  	}, time.Second*10, time.Millisecond*1, "Expected tails to be 1 at this point in the test...")
   365  }
   366  
   367  func TestToStopTailing(t *testing.T) {
   368  	nt := []string{"file1", "file2", "file3", "file4", "file5", "file6", "file7", "file11", "file12", "file15"}
   369  	et := make(map[string]*tailer, 15)
   370  	for i := 1; i <= 15; i++ {
   371  		et[fmt.Sprintf("file%d", i)] = nil
   372  	}
   373  	st := toStopTailing(nt, et)
   374  	sort.Strings(st)
   375  	expected := []string{"file10", "file13", "file14", "file8", "file9"}
   376  	if len(st) != len(expected) {
   377  		t.Error("Expected 5 tailers to be stopped, got", len(st))
   378  	}
   379  	for i := range expected {
   380  		if st[i] != expected[i] {
   381  			t.Error("Results mismatch, expected", expected[i], "got", st[i])
   382  		}
   383  	}
   384  
   385  }
   386  
   387  func BenchmarkToStopTailing(b *testing.B) {
   388  	nt := []string{"file1", "file2", "file3", "file4", "file5", "file6", "file7", "file11", "file12", "file15"}
   389  	et := make(map[string]*tailer, 15)
   390  	for i := 1; i <= 15; i++ {
   391  		et[fmt.Sprintf("file%d", i)] = nil
   392  	}
   393  	for n := 0; n < b.N; n++ {
   394  		toStopTailing(nt, et)
   395  	}
   396  }
   397  
   398  func TestMissing(t *testing.T) {
   399  	a := map[string]struct{}{}
   400  	b := map[string]struct{}{}
   401  
   402  	c := missing(a, b)
   403  	if len(c) != 0 {
   404  		t.Error("Expected no results with empty sets")
   405  	}
   406  
   407  	a["str1"] = struct{}{}
   408  	a["str2"] = struct{}{}
   409  	a["str3"] = struct{}{}
   410  	c = missing(a, b)
   411  	if len(c) != 0 {
   412  		t.Error("Expected no results with empty b set")
   413  	}
   414  	c = missing(b, a)
   415  	if len(c) != 3 {
   416  		t.Error("Expected three results")
   417  	}
   418  	if _, ok := c["str1"]; !ok {
   419  		t.Error("Expected the set to contain str1 but it did not")
   420  	}
   421  	if _, ok := c["str2"]; !ok {
   422  		t.Error("Expected the set to contain str2 but it did not")
   423  	}
   424  	if _, ok := c["str3"]; !ok {
   425  		t.Error("Expected the set to contain str3 but it did not")
   426  	}
   427  
   428  	b["str1"] = struct{}{}
   429  	b["str4"] = struct{}{}
   430  	c = missing(a, b)
   431  	if len(c) != 1 {
   432  		t.Error("Expected one result")
   433  	}
   434  	if _, ok := c["str4"]; !ok {
   435  		t.Error("Expected the set to contain str4 but it did not")
   436  	}
   437  
   438  	c = missing(b, a)
   439  	if len(c) != 2 {
   440  		t.Error("Expected two results")
   441  	}
   442  	if _, ok := c["str2"]; !ok {
   443  		t.Error("Expected the set to contain str2 but it did not")
   444  	}
   445  	if _, ok := c["str3"]; !ok {
   446  		t.Error("Expected the set to contain str3 but it did not")
   447  	}
   448  
   449  }