github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/daemon/logger/loggerutils/logfile_test.go (about)

     1  package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils"
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"sync/atomic"
    13  	"testing"
    14  	"text/tabwriter"
    15  	"time"
    16  
    17  	"github.com/docker/docker/daemon/logger"
    18  	"github.com/docker/docker/pkg/pubsub"
    19  	"github.com/docker/docker/pkg/tailfile"
    20  	"gotest.tools/v3/assert"
    21  	"gotest.tools/v3/poll"
    22  )
    23  
    24  type testDecoder struct {
    25  	rdr        io.Reader
    26  	scanner    *bufio.Scanner
    27  	resetCount int
    28  }
    29  
    30  func (d *testDecoder) Decode() (*logger.Message, error) {
    31  	if d.scanner == nil {
    32  		d.scanner = bufio.NewScanner(d.rdr)
    33  	}
    34  	if !d.scanner.Scan() {
    35  		return nil, d.scanner.Err()
    36  	}
    37  	// some comment
    38  	return &logger.Message{Line: d.scanner.Bytes(), Timestamp: time.Now()}, nil
    39  }
    40  
    41  func (d *testDecoder) Reset(rdr io.Reader) {
    42  	d.rdr = rdr
    43  	d.scanner = bufio.NewScanner(rdr)
    44  	d.resetCount++
    45  }
    46  
    47  func (d *testDecoder) Close() {
    48  	d.rdr = nil
    49  	d.scanner = nil
    50  }
    51  
    52  func TestTailFiles(t *testing.T) {
    53  	s1 := strings.NewReader("Hello.\nMy name is Inigo Montoya.\n")
    54  	s2 := strings.NewReader("I'm serious.\nDon't call me Shirley!\n")
    55  	s3 := strings.NewReader("Roads?\nWhere we're going we don't need roads.\n")
    56  
    57  	files := []SizeReaderAt{s1, s2, s3}
    58  	watcher := logger.NewLogWatcher()
    59  	defer watcher.ConsumerGone()
    60  
    61  	tailReader := func(ctx context.Context, r SizeReaderAt, lines int) (io.Reader, int, error) {
    62  		return tailfile.NewTailReader(ctx, r, lines)
    63  	}
    64  	dec := &testDecoder{}
    65  
    66  	for desc, config := range map[string]logger.ReadConfig{} {
    67  		t.Run(desc, func(t *testing.T) {
    68  			started := make(chan struct{})
    69  			go func() {
    70  				close(started)
    71  				tailFiles(files, watcher, dec, tailReader, config, make(chan interface{}))
    72  			}()
    73  			<-started
    74  		})
    75  	}
    76  
    77  	config := logger.ReadConfig{Tail: 2}
    78  	started := make(chan struct{})
    79  	go func() {
    80  		close(started)
    81  		tailFiles(files, watcher, dec, tailReader, config, make(chan interface{}))
    82  	}()
    83  	<-started
    84  
    85  	select {
    86  	case <-time.After(60 * time.Second):
    87  		t.Fatal("timeout waiting for tail line")
    88  	case err := <-watcher.Err:
    89  		assert.NilError(t, err)
    90  	case msg := <-watcher.Msg:
    91  		assert.Assert(t, msg != nil)
    92  		assert.Assert(t, string(msg.Line) == "Roads?", string(msg.Line))
    93  	}
    94  
    95  	select {
    96  	case <-time.After(60 * time.Second):
    97  		t.Fatal("timeout waiting for tail line")
    98  	case err := <-watcher.Err:
    99  		assert.NilError(t, err)
   100  	case msg := <-watcher.Msg:
   101  		assert.Assert(t, msg != nil)
   102  		assert.Assert(t, string(msg.Line) == "Where we're going we don't need roads.", string(msg.Line))
   103  	}
   104  }
   105  
   106  type dummyDecoder struct{}
   107  
   108  func (dummyDecoder) Decode() (*logger.Message, error) {
   109  	return &logger.Message{}, nil
   110  }
   111  
   112  func (dummyDecoder) Close()          {}
   113  func (dummyDecoder) Reset(io.Reader) {}
   114  
   115  func TestFollowLogsConsumerGone(t *testing.T) {
   116  	lw := logger.NewLogWatcher()
   117  
   118  	f, err := os.CreateTemp("", t.Name())
   119  	assert.NilError(t, err)
   120  	defer func() {
   121  		f.Close()
   122  		os.Remove(f.Name())
   123  	}()
   124  
   125  	dec := dummyDecoder{}
   126  
   127  	followLogsDone := make(chan struct{})
   128  	var since, until time.Time
   129  	go func() {
   130  		followLogs(f, lw, make(chan interface{}), make(chan interface{}), dec, since, until)
   131  		close(followLogsDone)
   132  	}()
   133  
   134  	select {
   135  	case <-lw.Msg:
   136  	case err := <-lw.Err:
   137  		assert.NilError(t, err)
   138  	case <-followLogsDone:
   139  		t.Fatal("follow logs finished unexpectedly")
   140  	case <-time.After(10 * time.Second):
   141  		t.Fatal("timeout waiting for log message")
   142  	}
   143  
   144  	lw.ConsumerGone()
   145  	select {
   146  	case <-followLogsDone:
   147  	case <-time.After(20 * time.Second):
   148  		t.Fatal("timeout waiting for followLogs() to finish")
   149  	}
   150  }
   151  
   152  type dummyWrapper struct {
   153  	dummyDecoder
   154  	fn func() error
   155  }
   156  
   157  func (d *dummyWrapper) Decode() (*logger.Message, error) {
   158  	if err := d.fn(); err != nil {
   159  		return nil, err
   160  	}
   161  	return d.dummyDecoder.Decode()
   162  }
   163  
   164  func TestFollowLogsProducerGone(t *testing.T) {
   165  	lw := logger.NewLogWatcher()
   166  	defer lw.ConsumerGone()
   167  
   168  	f, err := os.CreateTemp("", t.Name())
   169  	assert.NilError(t, err)
   170  	defer os.Remove(f.Name())
   171  
   172  	var sent, received, closed int32
   173  	dec := &dummyWrapper{fn: func() error {
   174  		switch atomic.LoadInt32(&closed) {
   175  		case 0:
   176  			atomic.AddInt32(&sent, 1)
   177  			return nil
   178  		case 1:
   179  			atomic.AddInt32(&closed, 1)
   180  			t.Logf("logDecode() closed after sending %d messages\n", sent)
   181  			return io.EOF
   182  		default:
   183  			t.Fatal("logDecode() called after closing!")
   184  			return io.EOF
   185  		}
   186  	}}
   187  	var since, until time.Time
   188  
   189  	followLogsDone := make(chan struct{})
   190  	go func() {
   191  		followLogs(f, lw, make(chan interface{}), make(chan interface{}), dec, since, until)
   192  		close(followLogsDone)
   193  	}()
   194  
   195  	// read 1 message
   196  	select {
   197  	case <-lw.Msg:
   198  		received++
   199  	case err := <-lw.Err:
   200  		assert.NilError(t, err)
   201  	case <-followLogsDone:
   202  		t.Fatal("followLogs() finished unexpectedly")
   203  	case <-time.After(10 * time.Second):
   204  		t.Fatal("timeout waiting for log message")
   205  	}
   206  
   207  	// "stop" the "container"
   208  	atomic.StoreInt32(&closed, 1)
   209  	lw.ProducerGone()
   210  
   211  	// should receive all the messages sent
   212  	readDone := make(chan struct{})
   213  	go func() {
   214  		defer close(readDone)
   215  		for {
   216  			select {
   217  			case <-lw.Msg:
   218  				received++
   219  				if received == atomic.LoadInt32(&sent) {
   220  					return
   221  				}
   222  			case err := <-lw.Err:
   223  				assert.NilError(t, err)
   224  			}
   225  		}
   226  	}()
   227  	select {
   228  	case <-readDone:
   229  	case <-time.After(30 * time.Second):
   230  		t.Fatalf("timeout waiting for log messages to be read (sent: %d, received: %d", sent, received)
   231  	}
   232  
   233  	t.Logf("messages sent: %d, received: %d", atomic.LoadInt32(&sent), received)
   234  
   235  	// followLogs() should be done by now
   236  	select {
   237  	case <-followLogsDone:
   238  	case <-time.After(30 * time.Second):
   239  		t.Fatal("timeout waiting for followLogs() to finish")
   240  	}
   241  
   242  	select {
   243  	case <-lw.WatchConsumerGone():
   244  		t.Fatal("consumer should not have exited")
   245  	default:
   246  	}
   247  }
   248  
   249  func TestCheckCapacityAndRotate(t *testing.T) {
   250  	dir, err := os.MkdirTemp("", t.Name())
   251  	assert.NilError(t, err)
   252  	defer os.RemoveAll(dir)
   253  
   254  	f, err := os.CreateTemp(dir, "log")
   255  	assert.NilError(t, err)
   256  
   257  	l := &LogFile{
   258  		f:               f,
   259  		capacity:        5,
   260  		maxFiles:        3,
   261  		compress:        true,
   262  		notifyReaders:   pubsub.NewPublisher(0, 1),
   263  		perms:           0600,
   264  		filesRefCounter: refCounter{counter: make(map[string]int)},
   265  		getTailReader: func(ctx context.Context, r SizeReaderAt, lines int) (io.Reader, int, error) {
   266  			return tailfile.NewTailReader(ctx, r, lines)
   267  		},
   268  		createDecoder: func(io.Reader) Decoder {
   269  			return dummyDecoder{}
   270  		},
   271  		marshal: func(msg *logger.Message) ([]byte, error) {
   272  			return msg.Line, nil
   273  		},
   274  	}
   275  	defer l.Close()
   276  
   277  	ls := dirStringer{dir}
   278  
   279  	assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world!")}))
   280  	_, err = os.Stat(f.Name() + ".1")
   281  	assert.Assert(t, os.IsNotExist(err), ls)
   282  
   283  	assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world!")}))
   284  	poll.WaitOn(t, checkFileExists(f.Name()+".1.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second))
   285  
   286  	assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world!")}))
   287  	poll.WaitOn(t, checkFileExists(f.Name()+".1.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second))
   288  	poll.WaitOn(t, checkFileExists(f.Name()+".2.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second))
   289  
   290  	t.Run("closed log file", func(t *testing.T) {
   291  		// Now let's simulate a failed rotation where the file was able to be closed but something else happened elsewhere
   292  		// down the line.
   293  		// We want to make sure that we can recover in the case that `l.f` was closed while attempting a rotation.
   294  		l.f.Close()
   295  		assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world!")}))
   296  		assert.NilError(t, os.Remove(f.Name()+".2.gz"))
   297  	})
   298  
   299  	t.Run("with log reader", func(t *testing.T) {
   300  		// Make sure rotate works with an active reader
   301  		lw := logger.NewLogWatcher()
   302  		defer lw.ConsumerGone()
   303  		go l.ReadLogs(logger.ReadConfig{Follow: true, Tail: 1000}, lw)
   304  
   305  		assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world 0!")}), ls)
   306  		// make sure the log reader is primed
   307  		waitForMsg(t, lw, 30*time.Second)
   308  
   309  		assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world 1!")}), ls)
   310  		assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world 2!")}), ls)
   311  		assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world 3!")}), ls)
   312  		assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world 4!")}), ls)
   313  		poll.WaitOn(t, checkFileExists(f.Name()+".2.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second))
   314  	})
   315  }
   316  
   317  func waitForMsg(t *testing.T, lw *logger.LogWatcher, timeout time.Duration) {
   318  	t.Helper()
   319  
   320  	timer := time.NewTimer(timeout)
   321  	defer timer.Stop()
   322  
   323  	select {
   324  	case <-lw.Msg:
   325  	case <-lw.WatchProducerGone():
   326  		t.Fatal("log producer gone before log message arrived")
   327  	case err := <-lw.Err:
   328  		assert.NilError(t, err)
   329  	case <-timer.C:
   330  		t.Fatal("timeout waiting for log message")
   331  	}
   332  }
   333  
   334  type dirStringer struct {
   335  	d string
   336  }
   337  
   338  func (d dirStringer) String() string {
   339  	ls, err := os.ReadDir(d.d)
   340  	if err != nil {
   341  		return ""
   342  	}
   343  	buf := bytes.NewBuffer(nil)
   344  	tw := tabwriter.NewWriter(buf, 1, 8, 1, '\t', 0)
   345  	buf.WriteString("\n")
   346  
   347  	btw := bufio.NewWriter(tw)
   348  
   349  	for _, entry := range ls {
   350  		fi, err := entry.Info()
   351  		if err != nil {
   352  			return ""
   353  		}
   354  
   355  		btw.WriteString(fmt.Sprintf("%s\t%s\t%dB\t%s\n", fi.Name(), fi.Mode(), fi.Size(), fi.ModTime()))
   356  	}
   357  	btw.Flush()
   358  	tw.Flush()
   359  	return buf.String()
   360  }
   361  
   362  func checkFileExists(name string) poll.Check {
   363  	return func(t poll.LogT) poll.Result {
   364  		_, err := os.Stat(name)
   365  		switch {
   366  		case err == nil:
   367  			return poll.Success()
   368  		case os.IsNotExist(err):
   369  			return poll.Continue("waiting for %s to exist", name)
   370  		default:
   371  			t.Logf("waiting for %s: %v: %s", name, err, dirStringer{filepath.Dir(name)})
   372  			return poll.Error(err)
   373  		}
   374  	}
   375  }