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