reload_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. package config
  2. import (
  3. "errors"
  4. "log/slog"
  5. "testing"
  6. "github.com/slackhq/nebula/test"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestReloadConfigString(t *testing.T) {
  11. c := NewC(test.NewLogger())
  12. require.NoError(t, c.LoadString("a: 1"))
  13. fail := false
  14. ran := 0
  15. c.RegisterReloadCallback(func(c *C, r *Reload) {
  16. ran++
  17. if fail {
  18. r.Failed()
  19. }
  20. })
  21. assert.Equal(t, ReloadResult{}, c.ReloadConfigString("a: 2"))
  22. fail = true
  23. assert.Equal(t, ReloadResult{Failed: true}, c.ReloadConfigString("a: 3"))
  24. assert.Equal(t, 2, ran)
  25. // A config that fails to load runs no callbacks and returns the error
  26. res := c.ReloadConfigString("a: [")
  27. require.Error(t, res.Err)
  28. assert.False(t, res.Failed)
  29. assert.Equal(t, 2, ran)
  30. assert.Equal(t, 3, c.GetInt("a", 0))
  31. }
  32. func TestReloadRestartRequiredLatches(t *testing.T) {
  33. c := NewC(test.NewLogger())
  34. require.NoError(t, c.LoadString("a: 1\nb: 1"))
  35. c.RegisterReloadCallback(func(c *C, r *Reload) { r.RestartRequiredIfChanged("a", "b") })
  36. // Nothing that needs a restart changed
  37. assert.Equal(t, ReloadResult{}, c.ReloadConfigString("a: 1\nb: 1\nc: 1"))
  38. assert.Equal(t, ReloadResult{RestartRequired: true, RestartKeys: []string{"a"}}, c.ReloadConfigString("a: 2\nb: 1\nc: 1"))
  39. // The restart sticks through a reload with no changes, reverting the key and a config that fails to load. Only the
  40. // reload that changed the key lists it
  41. assert.Equal(t, ReloadResult{RestartRequired: true}, c.ReloadConfigString("a: 2\nb: 1\nc: 1"))
  42. assert.Equal(t, ReloadResult{RestartRequired: true, RestartKeys: []string{"a"}}, c.ReloadConfigString("a: 1\nb: 1\nc: 1"))
  43. res := c.ReloadConfigString("a: [")
  44. require.Error(t, res.Err)
  45. assert.True(t, res.RestartRequired)
  46. }
  47. // A key reported by more than one callback is listed once
  48. func TestReloadRestartKeysDeduped(t *testing.T) {
  49. c := NewC(test.NewLogger())
  50. require.NoError(t, c.LoadString("a: 1\nb: 1"))
  51. c.RegisterReloadCallback(func(c *C, r *Reload) { r.RestartRequiredIfChanged("a") })
  52. c.RegisterReloadCallback(func(c *C, r *Reload) { r.RestartRequiredIfChanged("a", "b") })
  53. assert.Equal(t, []string{"a", "b"}, c.ReloadConfigString("a: 2\nb: 2").RestartKeys)
  54. }
  55. // A callback can require a restart without a key changing, a file the config points at may have
  56. func TestReloadRestartRequiredWithoutAChange(t *testing.T) {
  57. c := NewC(test.NewLogger())
  58. require.NoError(t, c.LoadString("a: 1"))
  59. c.RegisterReloadCallback(func(c *C, r *Reload) { r.RestartRequired() })
  60. assert.Equal(t, ReloadResult{RestartRequired: true}, c.ReloadConfigString("a: 1"))
  61. }
  62. func TestReloadResultSummary(t *testing.T) {
  63. cases := []struct {
  64. res ReloadResult
  65. level slog.Level
  66. msg string
  67. }{
  68. {ReloadResult{}, slog.LevelInfo, "Config reload complete"},
  69. {ReloadResult{RestartRequired: true}, slog.LevelWarn, "Config reload was completed but requires a restart to fully apply"},
  70. {ReloadResult{Failed: true}, slog.LevelError, "Config reload completed with errors, review the logs"},
  71. {ReloadResult{Failed: true, RestartRequired: true}, slog.LevelError, "Config reload completed with errors and requires a restart to fully apply, review the logs"},
  72. {ReloadResult{Err: errors.New("bad yaml")}, slog.LevelError, "Config reload failed, the new config did not load"},
  73. {ReloadResult{Err: errors.New("bad yaml"), RestartRequired: true}, slog.LevelError, "Config reload failed, the new config did not load and a restart is still required"},
  74. }
  75. for _, tc := range cases {
  76. level, msg := tc.res.Summary()
  77. assert.Equal(t, tc.level, level, tc.msg)
  78. assert.Equal(t, tc.msg, msg)
  79. }
  80. res := ReloadResult{RestartRequired: true, RestartKeys: []string{"a", "b"}}
  81. assert.Equal(t, "Config reload was completed but requires a restart to fully apply, keys [a b]", res.String())
  82. // String includes the error
  83. assert.Equal(t, "Config reload failed, the new config did not load: bad yaml", ReloadResult{Err: errors.New("bad yaml")}.String())
  84. }
  85. // The initial load passes a nil Reload
  86. func TestReloadNilIsSafe(t *testing.T) {
  87. var r *Reload
  88. r.Failed()
  89. r.RestartRequired()
  90. r.RestartRequiredIfChanged("a")
  91. }