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