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 }