github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/targets/file/filetarget_test.go (about) 1 package file 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sort" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "go.uber.org/atomic" 16 "gopkg.in/fsnotify.v1" 17 18 "github.com/go-kit/log" 19 20 "github.com/grafana/loki/clients/pkg/promtail/client/fake" 21 "github.com/grafana/loki/clients/pkg/promtail/positions" 22 ) 23 24 func TestFileTargetSync(t *testing.T) { 25 w := log.NewSyncWriter(os.Stderr) 26 logger := log.NewLogfmtLogger(w) 27 28 dirName := newTestLogDirectories(t) 29 positionsFileName := filepath.Join(dirName, "positions.yml") 30 logDir1 := filepath.Join(dirName, "log1") 31 logDir1File1 := filepath.Join(logDir1, "test1.log") 32 logDir1File2 := filepath.Join(logDir1, "test2.log") 33 34 // Set the sync period to a really long value, to guarantee the sync timer never runs, this way we know 35 // everything saved was done through channel notifications when target.stop() was called. 36 ps, err := positions.New(logger, positions.Config{ 37 SyncPeriod: 10 * time.Minute, 38 PositionsFile: positionsFileName, 39 }) 40 if err != nil { 41 t.Fatal(err) 42 } 43 44 client := fake.New(func() {}) 45 defer client.Stop() 46 47 metrics := NewMetrics(nil) 48 49 ctx, cancel := context.WithCancel(context.Background()) 50 defer cancel() 51 fakeHandler := make(chan fileTargetEvent) 52 receivedStartWatch := atomic.NewInt32(0) 53 receivedStopWatch := atomic.NewInt32(0) 54 go func() { 55 for { 56 select { 57 case event := <-fakeHandler: 58 switch event.eventType { 59 case fileTargetEventWatchStart: 60 receivedStartWatch.Add(1) 61 case fileTargetEventWatchStop: 62 receivedStopWatch.Add(1) 63 } 64 case <-ctx.Done(): 65 return 66 } 67 } 68 }() 69 path := logDir1 + "/*.log" 70 target, err := NewFileTarget(metrics, logger, client, ps, path, "", nil, nil, &Config{ 71 SyncPeriod: 1 * time.Minute, // assure the sync is not called by the ticker 72 }, nil, fakeHandler, "") 73 assert.NoError(t, err) 74 75 // Start with nothing watched. 76 if len(target.watches) != 0 { 77 t.Fatal("Expected watches to be 0 at this point in the test...") 78 } 79 if len(target.tails) != 0 { 80 t.Fatal("Expected tails to be 0 at this point in the test...") 81 } 82 83 // Create the base dir, still nothing watched. 84 err = os.MkdirAll(logDir1, 0750) 85 assert.NoError(t, err) 86 87 err = target.sync() 88 assert.NoError(t, err) 89 90 if len(target.watches) != 0 { 91 t.Fatal("Expected watches to be 0 at this point in the test...") 92 } 93 if len(target.tails) != 0 { 94 t.Fatal("Expected tails to be 0 at this point in the test...") 95 } 96 97 // Add a file, which should create a watcher and a tailer. 98 _, err = os.Create(logDir1File1) 99 assert.NoError(t, err) 100 101 // Delay sync() call to make sure the filesystem watch event does not fire during sync() 102 time.Sleep(10 * time.Millisecond) 103 err = target.sync() 104 assert.NoError(t, err) 105 106 assert.Equal(t, 1, len(target.watches), 107 "Expected watches to be 1 at this point in the test...", 108 ) 109 assert.Equal(t, 1, len(target.tails), 110 "Expected tails to be 1 at this point in the test...", 111 ) 112 require.Eventually(t, func() bool { 113 return receivedStartWatch.Load() == 1 114 }, time.Second*10, time.Millisecond*1, "Expected received starting watch event to be 1 at this point in the test...") 115 116 // Add another file, should get another tailer. 117 _, err = os.Create(logDir1File2) 118 assert.NoError(t, err) 119 120 err = target.sync() 121 assert.NoError(t, err) 122 123 assert.Equal(t, 1, len(target.watches), 124 "Expected watches to be 1 at this point in the test...", 125 ) 126 assert.Equal(t, 2, len(target.tails), 127 "Expected tails to be 2 at this point in the test...", 128 ) 129 130 // Remove one of the files, tailer should stop. 131 err = os.Remove(logDir1File1) 132 assert.NoError(t, err) 133 134 err = target.sync() 135 assert.NoError(t, err) 136 137 assert.Equal(t, 1, len(target.watches), 138 "Expected watches to be 1 at this point in the test...", 139 ) 140 assert.Equal(t, 1, len(target.tails), 141 "Expected tails to be 1 at this point in the test...", 142 ) 143 144 // Remove the entire directory, other tailer should stop and watcher should go away. 145 err = os.RemoveAll(logDir1) 146 assert.NoError(t, err) 147 148 err = target.sync() 149 assert.NoError(t, err) 150 151 assert.Equal(t, 0, len(target.watches), 152 "Expected watches to be 0 at this point in the test...", 153 ) 154 assert.Equal(t, 0, len(target.tails), 155 "Expected tails to be 0 at this point in the test...", 156 ) 157 require.Eventually(t, func() bool { 158 return receivedStartWatch.Load() == 1 159 }, time.Second*10, time.Millisecond*1, "Expected received starting watch event to be 1 at this point in the test...") 160 require.Eventually(t, func() bool { 161 return receivedStartWatch.Load() == 1 162 }, time.Second*10, time.Millisecond*1, "Expected received stopping watch event to be 1 at this point in the test...") 163 164 target.Stop() 165 ps.Stop() 166 } 167 168 func TestFileTargetPathExclusion(t *testing.T) { 169 w := log.NewSyncWriter(os.Stderr) 170 logger := log.NewLogfmtLogger(w) 171 172 dirName := newTestLogDirectories(t) 173 positionsFileName := filepath.Join(dirName, "positions.yml") 174 logDir1 := filepath.Join(dirName, "log1") 175 logDir2 := filepath.Join(dirName, "log2") 176 logDir3 := filepath.Join(dirName, "log3") 177 logFiles := []string{ 178 filepath.Join(logDir1, "test1.log"), 179 filepath.Join(logDir1, "test2.log"), 180 filepath.Join(logDir2, "test1.log"), 181 filepath.Join(logDir3, "test1.log"), 182 filepath.Join(logDir3, "test2.log"), 183 } 184 185 // Set the sync period to a really long value, to guarantee the sync timer never runs, this way we know 186 // everything saved was done through channel notifications when target.stop() was called. 187 ps, err := positions.New(logger, positions.Config{ 188 SyncPeriod: 10 * time.Minute, 189 PositionsFile: positionsFileName, 190 }) 191 if err != nil { 192 t.Fatal(err) 193 } 194 195 client := fake.New(func() {}) 196 defer client.Stop() 197 198 metrics := NewMetrics(nil) 199 200 ctx, cancel := context.WithCancel(context.Background()) 201 defer cancel() 202 fakeHandler := make(chan fileTargetEvent) 203 receivedStartWatch := atomic.NewInt32(0) 204 receivedStopWatch := atomic.NewInt32(0) 205 go func() { 206 for { 207 select { 208 case event := <-fakeHandler: 209 switch event.eventType { 210 case fileTargetEventWatchStart: 211 receivedStartWatch.Add(1) 212 case fileTargetEventWatchStop: 213 receivedStopWatch.Add(1) 214 } 215 case <-ctx.Done(): 216 return 217 } 218 } 219 }() 220 path := filepath.Join(dirName, "**", "*.log") 221 pathExclude := filepath.Join(dirName, "log3", "*.log") 222 target, err := NewFileTarget(metrics, logger, client, ps, path, pathExclude, nil, nil, &Config{ 223 SyncPeriod: 1 * time.Minute, // assure the sync is not called by the ticker 224 }, nil, fakeHandler, "") 225 assert.NoError(t, err) 226 227 // Start with nothing watched. 228 if len(target.watches) != 0 { 229 t.Fatal("Expected watches to be 0 at this point in the test...") 230 } 231 if len(target.tails) != 0 { 232 t.Fatal("Expected tails to be 0 at this point in the test...") 233 } 234 235 // Create the base directories, still nothing watched. 236 err = os.MkdirAll(logDir1, 0750) 237 assert.NoError(t, err) 238 err = os.MkdirAll(logDir2, 0750) 239 assert.NoError(t, err) 240 err = os.MkdirAll(logDir3, 0750) 241 assert.NoError(t, err) 242 243 err = target.sync() 244 assert.NoError(t, err) 245 246 if len(target.watches) != 0 { 247 t.Fatal("Expected watches to be 0 at this point in the test...") 248 } 249 if len(target.tails) != 0 { 250 t.Fatal("Expected tails to be 0 at this point in the test...") 251 } 252 253 // Create all the files, which should create two directory watchers and three file tailers. 254 for _, f := range logFiles { 255 _, err = os.Create(f) 256 assert.NoError(t, err) 257 } 258 259 // Delay sync() call to make sure the filesystem watch event does not fire during sync() 260 time.Sleep(10 * time.Millisecond) 261 err = target.sync() 262 assert.NoError(t, err) 263 264 assert.Equal(t, 2, len(target.watches), 265 "Expected watches to be 2 at this point in the test...", 266 ) 267 assert.Equal(t, 3, len(target.tails), 268 "Expected tails to be 3 at this point in the test...", 269 ) 270 require.Eventually(t, func() bool { 271 return receivedStartWatch.Load() == 2 272 }, time.Second*10, time.Millisecond*1, "Expected received starting watch event to be 2 at this point in the test...") 273 require.Eventually(t, func() bool { 274 return receivedStopWatch.Load() == 0 275 }, time.Second*10, time.Millisecond*1, "Expected received stopping watch event to be 0 at this point in the test...") 276 277 // Remove the first directory, other tailer should stop and its watchers should go away. 278 // Only the non-excluded `logDir2` should be watched. 279 err = os.RemoveAll(logDir1) 280 assert.NoError(t, err) 281 282 err = target.sync() 283 assert.NoError(t, err) 284 285 assert.Equal(t, 1, len(target.watches), 286 "Expected watches to be 1 at this point in the test...", 287 ) 288 assert.Equal(t, 1, len(target.tails), 289 "Expected tails to be 1 at this point in the test...", 290 ) 291 require.Eventually(t, func() bool { 292 return receivedStartWatch.Load() == 2 293 }, time.Second*10, time.Millisecond*1, "Expected received starting watch event to still be 2 at this point in the test...") 294 require.Eventually(t, func() bool { 295 return receivedStopWatch.Load() == 1 296 }, time.Second*10, time.Millisecond*1, "Expected received stopping watch event to be 1 at this point in the test...") 297 298 target.Stop() 299 ps.Stop() 300 } 301 302 func TestHandleFileCreationEvent(t *testing.T) { 303 w := log.NewSyncWriter(os.Stderr) 304 logger := log.NewLogfmtLogger(w) 305 306 dirName := newTestLogDirectories(t) 307 positionsFileName := filepath.Join(dirName, "positions.yml") 308 logDir := filepath.Join(dirName, "log") 309 logFile := filepath.Join(logDir, "test1.log") 310 311 if err := os.MkdirAll(logDir, 0750); err != nil { 312 t.Fatal(err) 313 } 314 315 // Set the sync period to a really long value, to guarantee the sync timer never runs, this way we know 316 // everything saved was done through channel notifications when target.stop() was called. 317 ps, err := positions.New(logger, positions.Config{ 318 SyncPeriod: 10 * time.Minute, 319 PositionsFile: positionsFileName, 320 }) 321 if err != nil { 322 t.Fatal(err) 323 } 324 325 client := fake.New(func() {}) 326 defer client.Stop() 327 328 metrics := NewMetrics(nil) 329 330 ctx, cancel := context.WithCancel(context.Background()) 331 defer cancel() 332 fakeFileHandler := make(chan fsnotify.Event) 333 fakeTargetHandler := make(chan fileTargetEvent) 334 path := logDir + "/*.log" 335 go func() { 336 for { 337 select { 338 case <-fakeTargetHandler: 339 continue 340 case <-ctx.Done(): 341 return 342 } 343 } 344 }() 345 346 target, err := NewFileTarget(metrics, logger, client, ps, path, "", nil, nil, &Config{ 347 // To handle file creation event from channel, set enough long time as sync period 348 SyncPeriod: 10 * time.Minute, 349 }, fakeFileHandler, fakeTargetHandler, "") 350 if err != nil { 351 t.Fatal(err) 352 } 353 354 _, err = os.Create(logFile) 355 if err != nil { 356 t.Fatal(err) 357 } 358 fakeFileHandler <- fsnotify.Event{ 359 Name: logFile, 360 Op: fsnotify.Create, 361 } 362 require.Eventually(t, func() bool { 363 return len(target.tails) == 1 364 }, time.Second*10, time.Millisecond*1, "Expected tails to be 1 at this point in the test...") 365 } 366 367 func TestToStopTailing(t *testing.T) { 368 nt := []string{"file1", "file2", "file3", "file4", "file5", "file6", "file7", "file11", "file12", "file15"} 369 et := make(map[string]*tailer, 15) 370 for i := 1; i <= 15; i++ { 371 et[fmt.Sprintf("file%d", i)] = nil 372 } 373 st := toStopTailing(nt, et) 374 sort.Strings(st) 375 expected := []string{"file10", "file13", "file14", "file8", "file9"} 376 if len(st) != len(expected) { 377 t.Error("Expected 5 tailers to be stopped, got", len(st)) 378 } 379 for i := range expected { 380 if st[i] != expected[i] { 381 t.Error("Results mismatch, expected", expected[i], "got", st[i]) 382 } 383 } 384 385 } 386 387 func BenchmarkToStopTailing(b *testing.B) { 388 nt := []string{"file1", "file2", "file3", "file4", "file5", "file6", "file7", "file11", "file12", "file15"} 389 et := make(map[string]*tailer, 15) 390 for i := 1; i <= 15; i++ { 391 et[fmt.Sprintf("file%d", i)] = nil 392 } 393 for n := 0; n < b.N; n++ { 394 toStopTailing(nt, et) 395 } 396 } 397 398 func TestMissing(t *testing.T) { 399 a := map[string]struct{}{} 400 b := map[string]struct{}{} 401 402 c := missing(a, b) 403 if len(c) != 0 { 404 t.Error("Expected no results with empty sets") 405 } 406 407 a["str1"] = struct{}{} 408 a["str2"] = struct{}{} 409 a["str3"] = struct{}{} 410 c = missing(a, b) 411 if len(c) != 0 { 412 t.Error("Expected no results with empty b set") 413 } 414 c = missing(b, a) 415 if len(c) != 3 { 416 t.Error("Expected three results") 417 } 418 if _, ok := c["str1"]; !ok { 419 t.Error("Expected the set to contain str1 but it did not") 420 } 421 if _, ok := c["str2"]; !ok { 422 t.Error("Expected the set to contain str2 but it did not") 423 } 424 if _, ok := c["str3"]; !ok { 425 t.Error("Expected the set to contain str3 but it did not") 426 } 427 428 b["str1"] = struct{}{} 429 b["str4"] = struct{}{} 430 c = missing(a, b) 431 if len(c) != 1 { 432 t.Error("Expected one result") 433 } 434 if _, ok := c["str4"]; !ok { 435 t.Error("Expected the set to contain str4 but it did not") 436 } 437 438 c = missing(b, a) 439 if len(c) != 2 { 440 t.Error("Expected two results") 441 } 442 if _, ok := c["str2"]; !ok { 443 t.Error("Expected the set to contain str2 but it did not") 444 } 445 if _, ok := c["str3"]; !ok { 446 t.Error("Expected the set to contain str3 but it did not") 447 } 448 449 }