Skip to content

Commit 0633869

Browse files
authored
simulate atomic write file for store (#482)
1 parent c72b925 commit 0633869

File tree

1 file changed

+63
-20
lines changed

1 file changed

+63
-20
lines changed

pkg/store/store_test.go

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -402,10 +402,10 @@ func TestStore_KubernetesSecretRotation(t *testing.T) {
402402
// Create a temporary directory to simulate K8s secret mount
403403
tmpDir := t.TempDir()
404404

405-
// Create initial secret file
405+
// Create initial secret file using the atomic writer pattern (like Kubernetes)
406406
secretFile := filepath.Join(tmpDir, "tls.crt")
407407
initialContent := []byte("initial-cert-content")
408-
err := os.WriteFile(secretFile, initialContent, 0o644)
408+
err := atomicWriteFile(tmpDir, "tls.crt", initialContent)
409409
require.NoError(t, err)
410410

411411
// Create store and add the file
@@ -424,31 +424,74 @@ func TestStore_KubernetesSecretRotation(t *testing.T) {
424424
require.True(t, exists)
425425
require.Equal(t, initialContent, content)
426426

427-
// Simulate Kubernetes secret rotation:
428-
// 1. Remove the file (this sends a REMOVE event)
429-
err = os.Remove(secretFile)
430-
require.NoError(t, err)
427+
// Add a small delay to ensure the file system watcher is ready
428+
time.Sleep(100 * time.Millisecond)
431429

432-
// 2. Create new file with updated content (simulating symlink update)
430+
// Simulate Kubernetes secret rotation using atomic operations
433431
newContent := []byte("rotated-cert-content")
434-
err = os.WriteFile(secretFile, newContent, 0o644)
432+
err = atomicWriteFile(tmpDir, "tls.crt", newContent)
435433
require.NoError(t, err)
436434

437-
// Wait for rotation event
435+
// Wait for rotation event with more robust event handling
438436
var rotationEvent RotationEvent
439-
select {
440-
case rotationEvent = <-store.RotationEvents():
441-
assert.Equal(t, secretFile, rotationEvent.Path)
442-
case err := <-store.Errors():
443-
t.Fatalf("Unexpected error during rotation: %v", err)
444-
case <-time.After(2 * time.Second):
445-
t.Fatal("No rotation event received within timeout")
437+
var gotRotationEvent bool
438+
439+
// We might get multiple events, so we need to handle them properly
440+
timeout := time.After(5 * time.Second)
441+
for !gotRotationEvent {
442+
select {
443+
case rotationEvent = <-store.RotationEvents():
444+
if rotationEvent.Path == secretFile {
445+
gotRotationEvent = true
446+
}
447+
// Continue if we got an event for a different file (shouldn't happen in this test)
448+
case err := <-store.Errors():
449+
t.Fatalf("Unexpected error during rotation: %v", err)
450+
case <-timeout:
451+
t.Fatal("No rotation event received within timeout")
452+
}
446453
}
447454

448-
// Verify content was updated
449-
content, exists = store.GetContent(secretFile)
450-
require.True(t, exists)
451-
require.Equal(t, newContent, content)
455+
assert.Equal(t, secretFile, rotationEvent.Path)
456+
457+
// Verify content was updated with eventual consistency
458+
assert.Eventually(t, func() bool {
459+
content, exists := store.GetContent(secretFile)
460+
return exists && string(content) == string(newContent)
461+
}, 1*time.Second, 50*time.Millisecond, "Content should be updated after rotation")
462+
}
463+
464+
// atomicWriteFile simulates how Kubernetes writes files atomically
465+
// This is a simplified version that focuses on the filesystem events that matter for testing
466+
func atomicWriteFile(targetDir, filename string, data []byte) error {
467+
targetFile := filepath.Join(targetDir, filename)
468+
469+
// Check if target exists
470+
_, err := os.Stat(targetFile)
471+
targetExists := err == nil
472+
473+
if targetExists {
474+
// For existing files, write to a temp file first, then do atomic rename
475+
tempFile := targetFile + ".tmp"
476+
477+
// Write new content to temp file
478+
if err := os.WriteFile(tempFile, data, 0o644); err != nil {
479+
return fmt.Errorf("failed to write temp file: %w", err)
480+
}
481+
482+
// Atomic rename (this is the key operation that generates the filesystem events)
483+
if err := os.Rename(tempFile, targetFile); err != nil {
484+
os.Remove(tempFile) // cleanup on failure
485+
return fmt.Errorf("failed to rename temp file: %w", err)
486+
}
487+
} else {
488+
// For new files, just write directly
489+
if err := os.WriteFile(targetFile, data, 0o644); err != nil {
490+
return fmt.Errorf("failed to write file: %w", err)
491+
}
492+
}
493+
494+
return nil
452495
}
453496

454497
func TestStore_RealFileDeletion(t *testing.T) {

0 commit comments

Comments
 (0)