Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a rate limiter to config auto-reload #12490

Merged
merged 108 commits into from
Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from 104 commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
63d0a12
add config watcher to the config package
dhiaayachi Feb 1, 2022
9a40118
add logging to watcher
dhiaayachi Feb 1, 2022
d8d34a8
add test and refactor to add WatcherEvent.
dhiaayachi Feb 2, 2022
14675c1
add all API calls and fix a bug with recreated files
dhiaayachi Feb 2, 2022
fa069e7
add tests for watcher
dhiaayachi Feb 2, 2022
2a4efc4
remove the unnecessary use of context
dhiaayachi Feb 2, 2022
5d6f328
Add debug log and a test for file rename
dhiaayachi Feb 4, 2022
5fbf587
use inode to detect if the file is recreated/replaced and only listen…
dhiaayachi Feb 7, 2022
060937a
tidy ups (#1535)
thisisnotashwin Feb 8, 2022
d219ccb
fix linux vs windows syscall
dhiaayachi Feb 8, 2022
35135f7
fix linux vs windows syscall
dhiaayachi Feb 8, 2022
0598fc5
fix windows compile error
dhiaayachi Feb 8, 2022
e619140
increase timeout
dhiaayachi Feb 8, 2022
6617bff
use ctime ID
dhiaayachi Feb 8, 2022
834f173
remove remove/creation test as it's a use case that fail in linux
dhiaayachi Feb 8, 2022
c29dcfa
fix linux/windows to use Ino/CreationTime
dhiaayachi Feb 8, 2022
32cf0d6
fix the watcher to only overwrite current file id
dhiaayachi Feb 8, 2022
8bee625
fix linter error
dhiaayachi Feb 8, 2022
c7d4afa
fix remove/create test
dhiaayachi Feb 9, 2022
4471980
set reconcile loop to 200 Milliseconds
dhiaayachi Feb 9, 2022
f2d710a
fix watcher to not trigger event on remove, add more tests
dhiaayachi Feb 9, 2022
5919390
on a remove event try to add the file back to the watcher and trigger…
dhiaayachi Feb 9, 2022
3414acd
fix race condition
dhiaayachi Feb 9, 2022
ed7f308
fix flaky test
dhiaayachi Feb 9, 2022
66611a6
fix race conditions
dhiaayachi Feb 9, 2022
b7fce5b
set level to info
dhiaayachi Feb 9, 2022
64e0a7d
fix when file is removed and get an event for it after
dhiaayachi Feb 9, 2022
d8c8444
fix to trigger handler when we get a remove but re-add fail
dhiaayachi Feb 9, 2022
5e61b7a
fix error message
dhiaayachi Feb 9, 2022
0aa983f
add tests for directory watch and fixes
dhiaayachi Feb 9, 2022
0d5b76f
detect if a file is a symlink and return an error on Add
dhiaayachi Feb 10, 2022
1d71a2a
rename Watcher to FileWatcher and remove symlink deref
dhiaayachi Feb 10, 2022
c5be5b7
add fsnotify@v1.5.1
dhiaayachi Feb 10, 2022
a8328ff
fix go mod
dhiaayachi Feb 10, 2022
eca2ca1
do not reset timer on errors, rename OS specific files
dhiaayachi Feb 10, 2022
100deea
rename New func
dhiaayachi Feb 11, 2022
1e55e9b
events trigger on write and rename
dhiaayachi Feb 11, 2022
9ce515e
add missing test
dhiaayachi Feb 14, 2022
dd46992
fix flaking tests
dhiaayachi Feb 14, 2022
fb6766f
fix flaky test
dhiaayachi Feb 14, 2022
e1c834a
check reconcile when removed
dhiaayachi Feb 14, 2022
02163d1
delete invalid file
dhiaayachi Feb 14, 2022
4dae9dd
fix test to create files with different mod time.
dhiaayachi Feb 16, 2022
119230e
back date file instead of sleeping
dhiaayachi Feb 18, 2022
314b836
add watching file in agent command.
dhiaayachi Feb 11, 2022
be0b869
fix watcher call to use new API
dhiaayachi Feb 14, 2022
79fa73f
add configuration and stop watcher when server stop
dhiaayachi Feb 14, 2022
66367a6
add certs as watched files
dhiaayachi Feb 17, 2022
66cfe48
move FileWatcher to the agent start instead of the command code
dhiaayachi Feb 17, 2022
08fd14a
stop watcher before replacing it
dhiaayachi Feb 18, 2022
d520599
save watched files in agent
dhiaayachi Feb 21, 2022
4a77ca2
add add and remove interfaces to the file watcher
dhiaayachi Feb 21, 2022
74933e3
fix remove to not return an error
dhiaayachi Feb 21, 2022
ae1370a
use `Add` and `Remove` to update certs files
dhiaayachi Feb 21, 2022
32d0e0e
fix tests
dhiaayachi Feb 21, 2022
c209208
close events channel on the file watcher even when the context is done
dhiaayachi Feb 21, 2022
3958ce1
extract `NotAutoReloadableRuntimeConfig` is a separate struct
dhiaayachi Feb 21, 2022
e402092
fix linter errors
dhiaayachi Feb 22, 2022
bc82888
add Ca configs and outgoing verify to the not auto reloadable config
dhiaayachi Feb 23, 2022
115303b
add some logs and fix to use background context
dhiaayachi Feb 23, 2022
131ad9c
add tests to auto-config reload
dhiaayachi Feb 24, 2022
07c5bcd
remove stale test
dhiaayachi Feb 24, 2022
b13a26c
add tests to changes to config files
dhiaayachi Feb 24, 2022
3b67b41
add check to see if old cert files still trigger updates
dhiaayachi Feb 24, 2022
4168e04
rename `NotAutoReloadableRuntimeConfig` to `StaticRuntimeConfig`
dhiaayachi Feb 24, 2022
3954202
fix to re add both key and cert file. Add test to cover this case.
dhiaayachi Feb 25, 2022
6b8c441
review suggestion
dhiaayachi Feb 25, 2022
519f1d9
add check to static runtime config changes
dhiaayachi Feb 25, 2022
2a983da
fix test
dhiaayachi Feb 28, 2022
bf670aa
add changelog file
dhiaayachi Mar 2, 2022
4e0f411
fix review comments
dhiaayachi Mar 7, 2022
dbb2ccb
Apply suggestions from code review
dhiaayachi Mar 7, 2022
41f7e93
update flag description
dhiaayachi Mar 14, 2022
bb16fca
WIP: merge origin/main
boxofrad Mar 22, 2022
d72ffbd
fix compilation error
dhiaayachi Mar 22, 2022
81f3797
add static runtime config support
dhiaayachi Mar 22, 2022
fb852d0
fix test
dhiaayachi Mar 22, 2022
4cf4a9d
fix review comments
dhiaayachi Mar 22, 2022
fae31af
fix log test
dhiaayachi Mar 22, 2022
398cb8b
Update .changelog/12329.txt
dhiaayachi Mar 22, 2022
b9b633b
transfer tests to runtime_test.go
dhiaayachi Mar 22, 2022
b52d4b9
fix filewatcher Replace to not deadlock.
dhiaayachi Mar 31, 2022
3c56e4f
avoid having lingering locks
dhiaayachi Mar 31, 2022
91223d9
split ReloadConfig func
dhiaayachi Mar 31, 2022
3d1f3ea
fix warning message
dhiaayachi Mar 31, 2022
d97c0e8
convert `FileWatcher` into an interface
dhiaayachi Mar 31, 2022
9068722
fix compilation errors
dhiaayachi Mar 31, 2022
7d75ea2
fix tests
dhiaayachi Mar 31, 2022
7de933e
Merge branch 'main' into config-autoreload/autreload
dhiaayachi Mar 31, 2022
1cb8fc3
extract func for adding and removing files
dhiaayachi Mar 31, 2022
268a5cb
add a coalesceTimer with a very small timer
dhiaayachi Mar 2, 2022
c891fbc
extract coaelsce Timer and add a shim for testing
dhiaayachi Mar 2, 2022
bfca688
add tests to coalesceTimer fix to send remaining events
dhiaayachi Mar 3, 2022
e8eac12
set `coalesceTimer` to 1 Second
dhiaayachi Mar 11, 2022
a5c3d01
support symlink, fix a nil deref.
dhiaayachi Mar 17, 2022
b7b75f4
fix compile error
dhiaayachi Mar 31, 2022
291a995
Merge branch 'main' into config-autoreload/rate-limiter
dhiaayachi Mar 31, 2022
2367994
fix compile error
dhiaayachi Mar 31, 2022
4ca7e7c
refactor file watcher rate limiting to be a Watcher implementation
dhiaayachi Apr 1, 2022
a8cd4f1
fix linter issue
dhiaayachi Apr 1, 2022
1e98f22
fix runtime config
dhiaayachi Apr 1, 2022
6109786
fix runtime test
dhiaayachi Apr 1, 2022
af6a416
fix flaky tests
dhiaayachi Apr 1, 2022
477c5b1
fix compile error
dhiaayachi Apr 1, 2022
cb7b1da
Apply suggestions from code review
dhiaayachi Apr 2, 2022
a9f4f61
fix agent New to return an error if File watcher New return an error
dhiaayachi Apr 2, 2022
9df854f
quit timer loop if ctx is canceled
dhiaayachi Apr 4, 2022
d5d68c4
Apply suggestions from code review
dhiaayachi Apr 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 25 additions & 23 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,9 @@ type Agent struct {
// run by the Agent
routineManager *routine.Manager

// FileWatcher is the watcher responsible to report events when a config file
// configFileWatcher is the watcher responsible to report events when a config file
// changed
FileWatcher config.Watcher
configFileWatcher config.Watcher

// xdsServer serves the XDS protocol for configuring Envoy proxies.
xdsServer *xds.Server
Expand Down Expand Up @@ -462,6 +462,13 @@ func New(bd BaseDeps) (*Agent, error) {
a.baseDeps.WatchedFiles = append(a.baseDeps.WatchedFiles, f.Cfg.CertFile)
}
}
if a.baseDeps.RuntimeConfig.AutoReloadConfig && len(a.baseDeps.WatchedFiles) > 0 {
w, err := config.NewRateLimitedFileWatcher(a.baseDeps.WatchedFiles, a.baseDeps.Logger, a.baseDeps.RuntimeConfig.AutoReloadConfigCoalesceInterval)
if err != nil {
a.baseDeps.Logger.Error("error loading config", "error", err)
dhiaayachi marked this conversation as resolved.
Show resolved Hide resolved
}
a.configFileWatcher = w
}

return &a, nil
}
Expand Down Expand Up @@ -713,25 +720,20 @@ func (a *Agent) Start(ctx context.Context) error {
})

// start a go routine to reload config based on file watcher events
if a.baseDeps.RuntimeConfig.AutoReloadConfig && len(a.baseDeps.WatchedFiles) > 0 {
w, err := config.NewFileWatcher(a.baseDeps.WatchedFiles, a.baseDeps.Logger)
if err != nil {
a.baseDeps.Logger.Error("error loading config", "error", err)
} else {
a.FileWatcher = w
a.baseDeps.Logger.Debug("starting file watcher")
a.FileWatcher.Start(context.Background())
go func() {
for event := range a.FileWatcher.EventsCh() {
a.baseDeps.Logger.Debug("auto-reload config triggered", "event-file", event.Filename)
err := a.AutoReloadConfig()
if err != nil {
a.baseDeps.Logger.Error("error loading config", "error", err)
}
if a.configFileWatcher != nil {
a.baseDeps.Logger.Debug("starting file watcher")
a.configFileWatcher.Start(context.Background())
go func() {
for event := range a.configFileWatcher.EventsCh() {
a.baseDeps.Logger.Debug("auto-reload config triggered", "num-events", len(event.Filenames))
err := a.AutoReloadConfig()
if err != nil {
a.baseDeps.Logger.Error("error loading config", "error", err)
}
}()
}
}
}()
}

return nil
}

Expand Down Expand Up @@ -1413,8 +1415,8 @@ func (a *Agent) ShutdownAgent() error {
a.stopAllWatches()

// Stop config file watcher
if a.FileWatcher != nil {
a.FileWatcher.Stop()
if a.configFileWatcher != nil {
a.configFileWatcher.Stop()
}

a.stopLicenseManager()
Expand Down Expand Up @@ -3772,13 +3774,13 @@ func (a *Agent) reloadConfig(autoReload bool) error {
{a.config.TLS.HTTPS, newCfg.TLS.HTTPS},
} {
if f.oldCfg.KeyFile != f.newCfg.KeyFile {
a.FileWatcher.Replace(f.oldCfg.KeyFile, f.newCfg.KeyFile)
a.configFileWatcher.Replace(f.oldCfg.KeyFile, f.newCfg.KeyFile)
if err != nil {
return err
}
}
if f.oldCfg.CertFile != f.newCfg.CertFile {
a.FileWatcher.Replace(f.oldCfg.CertFile, f.newCfg.CertFile)
a.configFileWatcher.Replace(f.oldCfg.CertFile, f.newCfg.CertFile)
if err != nil {
return err
}
Expand Down
155 changes: 140 additions & 15 deletions agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5545,7 +5545,8 @@ func TestAgent_AutoReloadDoReload_WhenCertThenKeyUpdated(t *testing.T) {

testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))

cert1 := srv.tlsConfigurator.Cert()
cert1Pub := srv.tlsConfigurator.Cert().Certificate
cert1Key := srv.tlsConfigurator.Cert().PrivateKey

certNew, privateKeyNew, err := tlsutil.GenerateCert(tlsutil.CertOpts{
Signer: signer,
Expand Down Expand Up @@ -5575,17 +5576,19 @@ func TestAgent_AutoReloadDoReload_WhenCertThenKeyUpdated(t *testing.T) {
// cert should not change as we did not update the associated key
time.Sleep(1 * time.Second)
retry.Run(t, func(r *retry.R) {
require.Equal(r, cert1.Certificate, srv.tlsConfigurator.Cert().Certificate)
require.Equal(r, cert1.PrivateKey, srv.tlsConfigurator.Cert().PrivateKey)
cert := srv.tlsConfigurator.Cert()
require.NotNil(r, cert)
require.Equal(r, cert1Pub, cert.Certificate)
require.Equal(r, cert1Key, cert.PrivateKey)
})

require.NoError(t, ioutil.WriteFile(keyFile, []byte(privateKeyNew), 0600))

// cert should change as we did not update the associated key
time.Sleep(1 * time.Second)
retry.Run(t, func(r *retry.R) {
require.NotEqual(r, cert1.Certificate, srv.tlsConfigurator.Cert().Certificate)
require.NotEqual(r, cert1.PrivateKey, srv.tlsConfigurator.Cert().PrivateKey)
require.NotEqual(r, cert1Pub, srv.tlsConfigurator.Cert().Certificate)
require.NotEqual(r, cert1Key, srv.tlsConfigurator.Cert().PrivateKey)
})
}

Expand Down Expand Up @@ -5647,11 +5650,13 @@ func TestAgent_AutoReloadDoReload_WhenKeyThenCertUpdated(t *testing.T) {
`), 0600))

srv := StartTestAgent(t, TestAgent{Name: "TestAgent-Server", HCL: hclConfig, configFiles: []string{configFile}})

defer srv.Shutdown()

testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))

cert1 := srv.tlsConfigurator.Cert()
cert1Pub := srv.tlsConfigurator.Cert().Certificate
cert1Key := srv.tlsConfigurator.Cert().PrivateKey

certNew, privateKeyNew, err := tlsutil.GenerateCert(tlsutil.CertOpts{
Signer: signer,
Expand All @@ -5667,8 +5672,10 @@ func TestAgent_AutoReloadDoReload_WhenKeyThenCertUpdated(t *testing.T) {
// cert should not change as we did not update the associated key
time.Sleep(1 * time.Second)
retry.Run(t, func(r *retry.R) {
require.Equal(r, cert1.Certificate, srv.tlsConfigurator.Cert().Certificate)
require.Equal(r, cert1.PrivateKey, srv.tlsConfigurator.Cert().PrivateKey)
cert := srv.tlsConfigurator.Cert()
require.NotNil(r, cert)
require.Equal(r, cert1Pub, cert.Certificate)
require.Equal(r, cert1Key, cert.PrivateKey)
})

require.NoError(t, ioutil.WriteFile(certFileNew, []byte(certNew), 0600))
Expand All @@ -5689,10 +5696,13 @@ func TestAgent_AutoReloadDoReload_WhenKeyThenCertUpdated(t *testing.T) {
// cert should change as we did not update the associated key
time.Sleep(1 * time.Second)
retry.Run(t, func(r *retry.R) {
require.NotEqual(r, cert1.Certificate, srv.tlsConfigurator.Cert().Certificate)
require.NotEqual(r, cert1.PrivateKey, srv.tlsConfigurator.Cert().PrivateKey)
cert := srv.tlsConfigurator.Cert()
require.NotNil(r, cert)
require.NotEqual(r, cert1Key, cert.Certificate)
require.NotEqual(r, cert1Key, cert.PrivateKey)
})
cert2 := srv.tlsConfigurator.Cert()
cert2Pub := srv.tlsConfigurator.Cert().Certificate
cert2Key := srv.tlsConfigurator.Cert().PrivateKey

certNew2, privateKeyNew2, err := tlsutil.GenerateCert(tlsutil.CertOpts{
Signer: signer,
Expand All @@ -5707,16 +5717,131 @@ func TestAgent_AutoReloadDoReload_WhenKeyThenCertUpdated(t *testing.T) {
// cert should not change as we did not update the associated cert
time.Sleep(1 * time.Second)
retry.Run(t, func(r *retry.R) {
require.Equal(r, cert2.Certificate, srv.tlsConfigurator.Cert().Certificate)
require.Equal(r, cert2.PrivateKey, srv.tlsConfigurator.Cert().PrivateKey)
cert := srv.tlsConfigurator.Cert()
require.NotNil(r, cert)
require.Equal(r, cert2Pub, cert.Certificate)
require.Equal(r, cert2Key, cert.PrivateKey)
})

require.NoError(t, ioutil.WriteFile(certFileNew, []byte(certNew2), 0600))

// cert should change as we did update the associated key
time.Sleep(1 * time.Second)
retry.Run(t, func(r *retry.R) {
require.NotEqual(r, cert2.Certificate, srv.tlsConfigurator.Cert().Certificate)
require.NotEqual(r, cert2.PrivateKey, srv.tlsConfigurator.Cert().PrivateKey)
cert := srv.tlsConfigurator.Cert()
require.NotNil(r, cert)
require.NotEqual(r, cert2Pub, cert.Certificate)
require.NotEqual(r, cert2Key, cert.PrivateKey)
})
}

func Test_coalesceTimerTwoPeriods(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test to make sure that the rate limiting logic work correctly with the agent


certsDir := testutil.TempDir(t, "auto-config")

// write some test TLS certificates out to the cfg dir
serverName := "server.dc1.consul"
signer, _, err := tlsutil.GeneratePrivateKey()
require.NoError(t, err)

ca, _, err := tlsutil.GenerateCA(tlsutil.CAOpts{Signer: signer})
require.NoError(t, err)

cert, privateKey, err := tlsutil.GenerateCert(tlsutil.CertOpts{
Signer: signer,
CA: ca,
Name: "Test Cert Name",
Days: 365,
DNSNames: []string{serverName},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
})
require.NoError(t, err)

certFile := filepath.Join(certsDir, "cert.pem")
caFile := filepath.Join(certsDir, "cacert.pem")
keyFile := filepath.Join(certsDir, "key.pem")

require.NoError(t, ioutil.WriteFile(certFile, []byte(cert), 0600))
require.NoError(t, ioutil.WriteFile(caFile, []byte(ca), 0600))
require.NoError(t, ioutil.WriteFile(keyFile, []byte(privateKey), 0600))

// generate a gossip key
gossipKey := make([]byte, 32)
n, err := rand.Read(gossipKey)
require.NoError(t, err)
require.Equal(t, 32, n)
gossipKeyEncoded := base64.StdEncoding.EncodeToString(gossipKey)

hclConfig := TestACLConfigWithParams(nil)

configFile := testutil.TempDir(t, "config") + "/config.hcl"
require.NoError(t, ioutil.WriteFile(configFile, []byte(`
encrypt = "`+gossipKeyEncoded+`"
encrypt_verify_incoming = true
encrypt_verify_outgoing = true
verify_incoming = true
verify_outgoing = true
verify_server_hostname = true
ca_file = "`+caFile+`"
cert_file = "`+certFile+`"
key_file = "`+keyFile+`"
connect { enabled = true }
auto_reload_config = true
`), 0600))

coalesceInterval := 100 * time.Millisecond
testAgent := TestAgent{Name: "TestAgent-Server", HCL: hclConfig, configFiles: []string{configFile}, Config: &config.RuntimeConfig{
AutoReloadConfigCoalesceInterval: coalesceInterval,
}}
srv := StartTestAgent(t, testAgent)
defer srv.Shutdown()

testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))

cert1Pub := srv.tlsConfigurator.Cert().Certificate
cert1Key := srv.tlsConfigurator.Cert().PrivateKey

certNew, privateKeyNew, err := tlsutil.GenerateCert(tlsutil.CertOpts{
Signer: signer,
CA: ca,
Name: "Test Cert Name",
Days: 365,
DNSNames: []string{serverName},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
})
require.NoError(t, err)
certFileNew := filepath.Join(certsDir, "cert_new.pem")
require.NoError(t, ioutil.WriteFile(certFileNew, []byte(certNew), 0600))
require.NoError(t, ioutil.WriteFile(configFile, []byte(`
encrypt = "`+gossipKeyEncoded+`"
encrypt_verify_incoming = true
encrypt_verify_outgoing = true
verify_incoming = true
verify_outgoing = true
verify_server_hostname = true
ca_file = "`+caFile+`"
cert_file = "`+certFileNew+`"
key_file = "`+keyFile+`"
connect { enabled = true }
auto_reload_config = true
`), 0600))

// cert should not change as we did not update the associated key
time.Sleep(coalesceInterval * 2)
retry.Run(t, func(r *retry.R) {
cert := srv.tlsConfigurator.Cert()
require.NotNil(r, cert)
require.Equal(r, cert1Pub, cert.Certificate)
require.Equal(r, cert1Key, cert.PrivateKey)
})

require.NoError(t, ioutil.WriteFile(keyFile, []byte(privateKeyNew), 0600))

// cert should change as we did not update the associated key
time.Sleep(coalesceInterval * 2)
retry.Run(t, func(r *retry.R) {
require.NotEqual(r, cert1Pub, srv.tlsConfigurator.Cert().Certificate)
require.NotEqual(r, cert1Key, srv.tlsConfigurator.Cert().PrivateKey)
})

}
Loading