github.com/rumpl/bof@v23.0.0-rc.2+incompatible/daemon/logger/loggertest/logreader.go (about)

     1  package loggertest // import "github.com/docker/docker/daemon/logger/loggertest"
     2  
     3  import (
     4  	"runtime"
     5  	"strings"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/google/go-cmp/cmp/cmpopts"
    12  	"gotest.tools/v3/assert"
    13  
    14  	"github.com/docker/docker/api/types/backend"
    15  	"github.com/docker/docker/daemon/logger"
    16  )
    17  
    18  // Reader tests that a logger.LogReader implementation behaves as it should.
    19  type Reader struct {
    20  	// Factory returns a function which constructs loggers for the container
    21  	// specified in info. Each call to the returned function must yield a
    22  	// distinct logger instance which can read back logs written by earlier
    23  	// instances.
    24  	Factory func(*testing.T, logger.Info) func(*testing.T) logger.Logger
    25  }
    26  
    27  var compareLog cmp.Options = []cmp.Option{
    28  	// The json-log driver does not round-trip PLogMetaData and API users do
    29  	// not expect it.
    30  	cmpopts.IgnoreFields(logger.Message{}, "PLogMetaData"),
    31  	cmp.Transformer("string", func(b []byte) string { return string(b) }),
    32  }
    33  
    34  // TestTail tests the behavior of the LogReader's tail implementation.
    35  func (tr Reader) TestTail(t *testing.T) {
    36  	t.Run("Live", func(t *testing.T) { tr.testTail(t, true) })
    37  	t.Run("LiveEmpty", func(t *testing.T) { tr.testTailEmptyLogs(t, true) })
    38  	t.Run("Stopped", func(t *testing.T) { tr.testTail(t, false) })
    39  	t.Run("StoppedEmpty", func(t *testing.T) { tr.testTailEmptyLogs(t, false) })
    40  }
    41  
    42  func makeTestMessages() []*logger.Message {
    43  	return []*logger.Message{
    44  		{Source: "stdout", Timestamp: time.Now().Add(-1 * 30 * time.Minute), Line: []byte("a message")},
    45  		{Source: "stdout", Timestamp: time.Now().Add(-1 * 20 * time.Minute), Line: []byte("another message"), PLogMetaData: &backend.PartialLogMetaData{ID: "aaaaaaaa", Ordinal: 1, Last: true}},
    46  		{Source: "stderr", Timestamp: time.Now().Add(-1 * 15 * time.Minute), Line: []byte("to be..."), PLogMetaData: &backend.PartialLogMetaData{ID: "bbbbbbbb", Ordinal: 1}},
    47  		{Source: "stderr", Timestamp: time.Now().Add(-1 * 15 * time.Minute), Line: []byte("continued"), PLogMetaData: &backend.PartialLogMetaData{ID: "bbbbbbbb", Ordinal: 2, Last: true}},
    48  		{Source: "stderr", Timestamp: time.Now().Add(-1 * 10 * time.Minute), Line: []byte("a really long message " + strings.Repeat("a", 4096))},
    49  		{Source: "stderr", Timestamp: time.Now().Add(-1 * 10 * time.Minute), Line: []byte("just one more message")},
    50  	}
    51  }
    52  
    53  func (tr Reader) testTail(t *testing.T, live bool) {
    54  	t.Parallel()
    55  	factory := tr.Factory(t, logger.Info{
    56  		ContainerID:   "tailtest0000",
    57  		ContainerName: "logtail",
    58  	})
    59  	l := factory(t)
    60  	if live {
    61  		defer func() { assert.NilError(t, l.Close()) }()
    62  	}
    63  
    64  	mm := makeTestMessages()
    65  	expected := logMessages(t, l, mm)
    66  
    67  	if !live {
    68  		// Simulate reading from a stopped container.
    69  		assert.NilError(t, l.Close())
    70  		l = factory(t)
    71  		defer func() { assert.NilError(t, l.Close()) }()
    72  	}
    73  	lr := l.(logger.LogReader)
    74  
    75  	t.Run("Exact", func(t *testing.T) {
    76  		t.Parallel()
    77  		lw := lr.ReadLogs(logger.ReadConfig{Tail: len(mm)})
    78  		defer lw.ConsumerGone()
    79  		assert.DeepEqual(t, readAll(t, lw), expected, compareLog)
    80  	})
    81  
    82  	t.Run("LessThanAvailable", func(t *testing.T) {
    83  		t.Parallel()
    84  		lw := lr.ReadLogs(logger.ReadConfig{Tail: 2})
    85  		defer lw.ConsumerGone()
    86  		assert.DeepEqual(t, readAll(t, lw), expected[len(mm)-2:], compareLog)
    87  	})
    88  
    89  	t.Run("MoreThanAvailable", func(t *testing.T) {
    90  		t.Parallel()
    91  		lw := lr.ReadLogs(logger.ReadConfig{Tail: 100})
    92  		defer lw.ConsumerGone()
    93  		assert.DeepEqual(t, readAll(t, lw), expected, compareLog)
    94  	})
    95  
    96  	t.Run("All", func(t *testing.T) {
    97  		t.Parallel()
    98  		lw := lr.ReadLogs(logger.ReadConfig{Tail: -1})
    99  		defer lw.ConsumerGone()
   100  		assert.DeepEqual(t, readAll(t, lw), expected, compareLog)
   101  	})
   102  
   103  	t.Run("Since", func(t *testing.T) {
   104  		t.Parallel()
   105  		lw := lr.ReadLogs(logger.ReadConfig{Tail: -1, Since: mm[1].Timestamp.Truncate(time.Millisecond)})
   106  		defer lw.ConsumerGone()
   107  		assert.DeepEqual(t, readAll(t, lw), expected[1:], compareLog)
   108  	})
   109  
   110  	t.Run("MoreThanSince", func(t *testing.T) {
   111  		t.Parallel()
   112  		lw := lr.ReadLogs(logger.ReadConfig{Tail: len(mm), Since: mm[1].Timestamp.Truncate(time.Millisecond)})
   113  		defer lw.ConsumerGone()
   114  		assert.DeepEqual(t, readAll(t, lw), expected[1:], compareLog)
   115  	})
   116  
   117  	t.Run("LessThanSince", func(t *testing.T) {
   118  		t.Parallel()
   119  		lw := lr.ReadLogs(logger.ReadConfig{Tail: len(mm) - 2, Since: mm[1].Timestamp.Truncate(time.Millisecond)})
   120  		defer lw.ConsumerGone()
   121  		assert.DeepEqual(t, readAll(t, lw), expected[2:], compareLog)
   122  	})
   123  
   124  	t.Run("Until", func(t *testing.T) {
   125  		t.Parallel()
   126  		lw := lr.ReadLogs(logger.ReadConfig{Tail: -1, Until: mm[2].Timestamp.Add(-time.Millisecond)})
   127  		defer lw.ConsumerGone()
   128  		assert.DeepEqual(t, readAll(t, lw), expected[:2], compareLog)
   129  	})
   130  
   131  	t.Run("SinceAndUntil", func(t *testing.T) {
   132  		t.Parallel()
   133  		lw := lr.ReadLogs(logger.ReadConfig{Tail: -1, Since: mm[1].Timestamp.Truncate(time.Millisecond), Until: mm[1].Timestamp.Add(time.Millisecond)})
   134  		defer lw.ConsumerGone()
   135  		assert.DeepEqual(t, readAll(t, lw), expected[1:2], compareLog)
   136  	})
   137  }
   138  
   139  func (tr Reader) testTailEmptyLogs(t *testing.T, live bool) {
   140  	t.Parallel()
   141  	factory := tr.Factory(t, logger.Info{
   142  		ContainerID:   "tailemptytest",
   143  		ContainerName: "logtail",
   144  	})
   145  	l := factory(t)
   146  	if !live {
   147  		assert.NilError(t, l.Close())
   148  		l = factory(t)
   149  	}
   150  	defer func() { assert.NilError(t, l.Close()) }()
   151  
   152  	for _, tt := range []struct {
   153  		name string
   154  		cfg  logger.ReadConfig
   155  	}{
   156  		{name: "Zero", cfg: logger.ReadConfig{}},
   157  		{name: "All", cfg: logger.ReadConfig{Tail: -1}},
   158  		{name: "Tail", cfg: logger.ReadConfig{Tail: 42}},
   159  		{name: "Since", cfg: logger.ReadConfig{Since: time.Unix(1, 0)}},
   160  		{name: "Until", cfg: logger.ReadConfig{Until: time.Date(2100, time.January, 1, 1, 1, 1, 0, time.UTC)}},
   161  		{name: "SinceAndUntil", cfg: logger.ReadConfig{Since: time.Unix(1, 0), Until: time.Date(2100, time.January, 1, 1, 1, 1, 0, time.UTC)}},
   162  	} {
   163  		tt := tt
   164  		t.Run(tt.name, func(t *testing.T) {
   165  			t.Parallel()
   166  			lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{})
   167  			defer lw.ConsumerGone()
   168  			assert.DeepEqual(t, readAll(t, lw), ([]*logger.Message)(nil), cmpopts.EquateEmpty())
   169  		})
   170  	}
   171  }
   172  
   173  // TestFollow tests the LogReader's follow implementation.
   174  //
   175  // The LogReader is expected to be able to follow an arbitrary number of
   176  // messages at a high rate with no dropped messages.
   177  func (tr Reader) TestFollow(t *testing.T) {
   178  	// Reader sends all logs and closes after logger is closed
   179  	// - Starting from empty log (like run)
   180  	t.Run("FromEmptyLog", func(t *testing.T) {
   181  		t.Parallel()
   182  		l := tr.Factory(t, logger.Info{
   183  			ContainerID:   "followstart0",
   184  			ContainerName: "logloglog",
   185  		})(t)
   186  		lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true})
   187  		defer lw.ConsumerGone()
   188  
   189  		doneReading := make(chan struct{})
   190  		var logs []*logger.Message
   191  		go func() {
   192  			defer close(doneReading)
   193  			logs = readAll(t, lw)
   194  		}()
   195  
   196  		mm := makeTestMessages()
   197  		expected := logMessages(t, l, mm)
   198  		assert.NilError(t, l.Close())
   199  		<-doneReading
   200  		assert.DeepEqual(t, logs, expected, compareLog)
   201  	})
   202  
   203  	t.Run("AttachMidStream", func(t *testing.T) {
   204  		t.Parallel()
   205  		l := tr.Factory(t, logger.Info{
   206  			ContainerID:   "followmiddle",
   207  			ContainerName: "logloglog",
   208  		})(t)
   209  
   210  		mm := makeTestMessages()
   211  		expected := logMessages(t, l, mm[0:1])
   212  
   213  		lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true})
   214  		defer lw.ConsumerGone()
   215  
   216  		doneReading := make(chan struct{})
   217  		var logs []*logger.Message
   218  		go func() {
   219  			defer close(doneReading)
   220  			logs = readAll(t, lw)
   221  		}()
   222  
   223  		expected = append(expected, logMessages(t, l, mm[1:])...)
   224  		assert.NilError(t, l.Close())
   225  		<-doneReading
   226  		assert.DeepEqual(t, logs, expected, compareLog)
   227  	})
   228  
   229  	t.Run("Since", func(t *testing.T) {
   230  		t.Parallel()
   231  		l := tr.Factory(t, logger.Info{
   232  			ContainerID:   "followsince0",
   233  			ContainerName: "logloglog",
   234  		})(t)
   235  
   236  		mm := makeTestMessages()
   237  
   238  		lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true, Since: mm[2].Timestamp.Truncate(time.Millisecond)})
   239  		defer lw.ConsumerGone()
   240  
   241  		doneReading := make(chan struct{})
   242  		var logs []*logger.Message
   243  		go func() {
   244  			defer close(doneReading)
   245  			logs = readAll(t, lw)
   246  		}()
   247  
   248  		expected := logMessages(t, l, mm)[2:]
   249  		assert.NilError(t, l.Close())
   250  		<-doneReading
   251  		assert.DeepEqual(t, logs, expected, compareLog)
   252  	})
   253  
   254  	t.Run("Until", func(t *testing.T) {
   255  		t.Parallel()
   256  		l := tr.Factory(t, logger.Info{
   257  			ContainerID:   "followuntil0",
   258  			ContainerName: "logloglog",
   259  		})(t)
   260  
   261  		mm := makeTestMessages()
   262  
   263  		lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true, Until: mm[2].Timestamp.Add(-time.Millisecond)})
   264  		defer lw.ConsumerGone()
   265  
   266  		doneReading := make(chan struct{})
   267  		var logs []*logger.Message
   268  		go func() {
   269  			defer close(doneReading)
   270  			logs = readAll(t, lw)
   271  		}()
   272  
   273  		expected := logMessages(t, l, mm)[:2]
   274  		defer assert.NilError(t, l.Close()) // Reading should end before the logger is closed.
   275  		<-doneReading
   276  		assert.DeepEqual(t, logs, expected, compareLog)
   277  	})
   278  
   279  	t.Run("SinceAndUntil", func(t *testing.T) {
   280  		t.Parallel()
   281  		l := tr.Factory(t, logger.Info{
   282  			ContainerID:   "followbounded",
   283  			ContainerName: "logloglog",
   284  		})(t)
   285  
   286  		mm := makeTestMessages()
   287  
   288  		lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true, Since: mm[1].Timestamp.Add(-time.Millisecond), Until: mm[2].Timestamp.Add(-time.Millisecond)})
   289  		defer lw.ConsumerGone()
   290  
   291  		doneReading := make(chan struct{})
   292  		var logs []*logger.Message
   293  		go func() {
   294  			defer close(doneReading)
   295  			logs = readAll(t, lw)
   296  		}()
   297  
   298  		expected := logMessages(t, l, mm)[1:2]
   299  		defer assert.NilError(t, l.Close()) // Reading should end before the logger is closed.
   300  		<-doneReading
   301  		assert.DeepEqual(t, logs, expected, compareLog)
   302  	})
   303  
   304  	t.Run("Tail=0", func(t *testing.T) {
   305  		t.Parallel()
   306  		l := tr.Factory(t, logger.Info{
   307  			ContainerID:   "followtail00",
   308  			ContainerName: "logloglog",
   309  		})(t)
   310  
   311  		mm := makeTestMessages()
   312  		logMessages(t, l, mm[0:2])
   313  
   314  		lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: 0, Follow: true})
   315  		defer lw.ConsumerGone()
   316  
   317  		doneReading := make(chan struct{})
   318  		var logs []*logger.Message
   319  		go func() {
   320  			defer close(doneReading)
   321  			logs = readAll(t, lw)
   322  		}()
   323  
   324  		expected := logMessages(t, l, mm[2:])
   325  		assert.NilError(t, l.Close())
   326  		<-doneReading
   327  		assert.DeepEqual(t, logs, expected, compareLog)
   328  	})
   329  
   330  	t.Run("Tail>0", func(t *testing.T) {
   331  		t.Parallel()
   332  		l := tr.Factory(t, logger.Info{
   333  			ContainerID:   "followtail00",
   334  			ContainerName: "logloglog",
   335  		})(t)
   336  
   337  		mm := makeTestMessages()
   338  		expected := logMessages(t, l, mm[0:2])[1:]
   339  
   340  		lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: 1, Follow: true})
   341  		defer lw.ConsumerGone()
   342  
   343  		doneReading := make(chan struct{})
   344  		var logs []*logger.Message
   345  		go func() {
   346  			defer close(doneReading)
   347  			logs = readAll(t, lw)
   348  		}()
   349  
   350  		expected = append(expected, logMessages(t, l, mm[2:])...)
   351  		assert.NilError(t, l.Close())
   352  		<-doneReading
   353  		assert.DeepEqual(t, logs, expected, compareLog)
   354  	})
   355  
   356  	t.Run("MultipleStarts", func(t *testing.T) {
   357  		t.Parallel()
   358  		factory := tr.Factory(t, logger.Info{
   359  			ContainerID:   "startrestart",
   360  			ContainerName: "startmeup",
   361  		})
   362  
   363  		mm := makeTestMessages()
   364  		l := factory(t)
   365  		expected := logMessages(t, l, mm[:3])
   366  		assert.NilError(t, l.Close())
   367  
   368  		l = factory(t)
   369  		lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true})
   370  		defer lw.ConsumerGone()
   371  
   372  		doneReading := make(chan struct{})
   373  		var logs []*logger.Message
   374  		go func() {
   375  			defer close(doneReading)
   376  			logs = readAll(t, lw)
   377  		}()
   378  
   379  		expected = append(expected, logMessages(t, l, mm[3:])...)
   380  		assert.NilError(t, l.Close())
   381  		<-doneReading
   382  		assert.DeepEqual(t, logs, expected, compareLog)
   383  	})
   384  
   385  	t.Run("Concurrent", tr.TestConcurrent)
   386  }
   387  
   388  // TestConcurrent tests the Logger and its LogReader implementation for
   389  // race conditions when logging from multiple goroutines concurrently.
   390  func (tr Reader) TestConcurrent(t *testing.T) {
   391  	t.Parallel()
   392  	l := tr.Factory(t, logger.Info{
   393  		ContainerID:   "logconcurrent0",
   394  		ContainerName: "logconcurrent123",
   395  	})(t)
   396  
   397  	// Split test messages
   398  	stderrMessages := []*logger.Message{}
   399  	stdoutMessages := []*logger.Message{}
   400  	for _, m := range makeTestMessages() {
   401  		if m.Source == "stdout" {
   402  			stdoutMessages = append(stdoutMessages, m)
   403  		} else if m.Source == "stderr" {
   404  			stderrMessages = append(stderrMessages, m)
   405  		}
   406  	}
   407  
   408  	// Follow all logs
   409  	lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Follow: true, Tail: -1})
   410  	defer lw.ConsumerGone()
   411  
   412  	// Log concurrently from two sources and close log
   413  	wg := &sync.WaitGroup{}
   414  	logAll := func(msgs []*logger.Message) {
   415  		defer wg.Done()
   416  		for _, m := range msgs {
   417  			l.Log(copyLogMessage(m))
   418  		}
   419  	}
   420  
   421  	closed := make(chan struct{})
   422  	wg.Add(2)
   423  	go logAll(stdoutMessages)
   424  	go logAll(stderrMessages)
   425  	go func() {
   426  		defer close(closed)
   427  		defer l.Close()
   428  		wg.Wait()
   429  	}()
   430  
   431  	// Check if the message count, order and content is equal to what was logged
   432  	for {
   433  		l := readMessage(t, lw)
   434  		if l == nil {
   435  			break
   436  		}
   437  
   438  		var messages *[]*logger.Message
   439  		if l.Source == "stdout" {
   440  			messages = &stdoutMessages
   441  		} else if l.Source == "stderr" {
   442  			messages = &stderrMessages
   443  		} else {
   444  			t.Fatalf("Corrupted message.Source = %q", l.Source)
   445  		}
   446  
   447  		expectedMsg := transformToExpected((*messages)[0])
   448  
   449  		assert.DeepEqual(t, *expectedMsg, *l, compareLog)
   450  		*messages = (*messages)[1:]
   451  	}
   452  
   453  	assert.Equal(t, len(stdoutMessages), 0)
   454  	assert.Equal(t, len(stderrMessages), 0)
   455  
   456  	// Make sure log gets closed before we return
   457  	// so the temporary dir can be deleted
   458  	<-closed
   459  }
   460  
   461  // logMessages logs messages to l and returns a slice of messages as would be
   462  // expected to be read back. The message values are not modified and the
   463  // returned slice of messages are deep-copied.
   464  func logMessages(t *testing.T, l logger.Logger, messages []*logger.Message) []*logger.Message {
   465  	t.Helper()
   466  	var expected []*logger.Message
   467  	for _, m := range messages {
   468  		// Copy the log message because the underlying log writer resets
   469  		// the log message and returns it to a buffer pool.
   470  		assert.NilError(t, l.Log(copyLogMessage(m)))
   471  		runtime.Gosched()
   472  
   473  		expect := transformToExpected(m)
   474  		expected = append(expected, expect)
   475  	}
   476  	return expected
   477  }
   478  
   479  // Existing API consumers expect a newline to be appended to
   480  // messages other than nonterminal partials as that matches the
   481  // existing behavior of the json-file log driver.
   482  func transformToExpected(m *logger.Message) *logger.Message {
   483  	// Copy the log message again so as not to mutate the input.
   484  	copy := copyLogMessage(m)
   485  	if m.PLogMetaData == nil || m.PLogMetaData.Last {
   486  		copy.Line = append(copy.Line, '\n')
   487  	}
   488  
   489  	return copy
   490  }
   491  
   492  func copyLogMessage(src *logger.Message) *logger.Message {
   493  	dst := logger.NewMessage()
   494  	dst.Source = src.Source
   495  	dst.Timestamp = src.Timestamp
   496  	dst.Attrs = src.Attrs
   497  	dst.Err = src.Err
   498  	dst.Line = append(dst.Line, src.Line...)
   499  	if src.PLogMetaData != nil {
   500  		lmd := *src.PLogMetaData
   501  		dst.PLogMetaData = &lmd
   502  	}
   503  	return dst
   504  }
   505  func readMessage(t *testing.T, lw *logger.LogWatcher) *logger.Message {
   506  	t.Helper()
   507  	timeout := time.NewTimer(5 * time.Second)
   508  	defer timeout.Stop()
   509  	select {
   510  	case <-timeout.C:
   511  		t.Error("timed out waiting for message")
   512  		return nil
   513  	case err, open := <-lw.Err:
   514  		t.Errorf("unexpected receive on lw.Err: err=%v, open=%v", err, open)
   515  		return nil
   516  	case msg, open := <-lw.Msg:
   517  		if !open {
   518  			select {
   519  			case err, open := <-lw.Err:
   520  				t.Errorf("unexpected receive on lw.Err with closed lw.Msg: err=%v, open=%v", err, open)
   521  				return nil
   522  			default:
   523  			}
   524  		}
   525  		if msg != nil {
   526  			t.Logf("loggertest: ReadMessage [%v %v] %s", msg.Source, msg.Timestamp, msg.Line)
   527  		}
   528  		return msg
   529  	}
   530  }
   531  
   532  func readAll(t *testing.T, lw *logger.LogWatcher) []*logger.Message {
   533  	t.Helper()
   534  	var msgs []*logger.Message
   535  	for {
   536  		m := readMessage(t, lw)
   537  		if m == nil {
   538  			return msgs
   539  		}
   540  		msgs = append(msgs, m)
   541  	}
   542  }