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 }