github.com/lacework-dev/go-moby@v20.10.12+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 }