summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rwxr-xr-xplugins/checker/tests/configs/.rr-checker-init.yaml4
-rw-r--r--plugins/checker/tests/plugin_test.go4
-rwxr-xr-xplugins/config/tests/config_test.go2
-rw-r--r--plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml5
-rw-r--r--plugins/gzip/tests/configs/.rr-http-withGzip.yaml5
-rw-r--r--plugins/gzip/tests/plugin_test.go4
-rw-r--r--plugins/headers/tests/configs/.rr-cors-headers.yaml4
-rw-r--r--plugins/headers/tests/configs/.rr-headers-init.yaml4
-rw-r--r--plugins/headers/tests/configs/.rr-req-headers.yaml4
-rw-r--r--plugins/headers/tests/configs/.rr-res-headers.yaml4
-rw-r--r--plugins/headers/tests/headers_plugin_test.go8
-rw-r--r--plugins/http/plugin.go5
-rw-r--r--plugins/http/tests/configs/.rr-broken-pipes.yaml4
-rw-r--r--plugins/http/tests/configs/.rr-echoErr.yaml4
-rw-r--r--plugins/http/tests/configs/.rr-env.yaml4
-rw-r--r--plugins/http/tests/configs/.rr-fcgi-reqUri.yaml5
-rw-r--r--plugins/http/tests/configs/.rr-fcgi.yaml5
-rw-r--r--plugins/http/tests/configs/.rr-h2c.yaml5
-rw-r--r--plugins/http/tests/configs/.rr-http.yaml3
-rw-r--r--plugins/http/tests/configs/.rr-init.yaml4
-rw-r--r--plugins/http/tests/configs/.rr-resetter.yaml4
-rw-r--r--plugins/http/tests/configs/.rr-ssl-push.yaml4
-rw-r--r--plugins/http/tests/configs/.rr-ssl-redirect.yaml4
-rw-r--r--plugins/http/tests/configs/.rr-ssl.yaml5
-rw-r--r--plugins/http/tests/http_test.go24
-rw-r--r--plugins/informer/tests/.rr-informer.yaml5
-rw-r--r--plugins/informer/tests/informer_test.go2
-rw-r--r--plugins/logger/tests/.rr.yaml3
-rw-r--r--plugins/logger/tests/logger_test.go2
-rw-r--r--plugins/metrics/tests/.rr-test.yaml5
-rw-r--r--plugins/metrics/tests/metrics_test.go6
-rw-r--r--plugins/reload/config.go58
-rw-r--r--plugins/reload/plugin.go158
-rw-r--r--plugins/reload/tests/config_test.go63
-rw-r--r--plugins/reload/tests/configs/.rr-reload-2.yaml44
-rw-r--r--plugins/reload/tests/configs/.rr-reload-3.yaml46
-rw-r--r--plugins/reload/tests/configs/.rr-reload-4.yaml46
-rw-r--r--plugins/reload/tests/configs/.rr-reload.yaml44
-rw-r--r--plugins/reload/tests/reload_plugin_test.go822
-rw-r--r--plugins/reload/watcher.go372
-rw-r--r--plugins/resetter/plugin.go33
-rw-r--r--plugins/resetter/rpc.go4
-rw-r--r--plugins/resetter/tests/.rr-resetter.yaml5
-rw-r--r--plugins/resetter/tests/resetter_test.go2
-rw-r--r--plugins/rpc/tests/.rr-rpc-disabled.yaml5
-rw-r--r--plugins/rpc/tests/.rr.yaml5
-rw-r--r--plugins/rpc/tests/rpc_test.go4
-rw-r--r--plugins/server/tests/configs/.rr-no-app-section.yaml5
-rw-r--r--plugins/server/tests/configs/.rr-sockets.yaml5
-rw-r--r--plugins/server/tests/configs/.rr-tcp.yaml5
-rw-r--r--plugins/server/tests/configs/.rr-wrong-command.yaml3
-rw-r--r--plugins/server/tests/configs/.rr-wrong-relay.yaml5
-rw-r--r--plugins/server/tests/configs/.rr.yaml5
-rw-r--r--plugins/server/tests/server_test.go14
-rw-r--r--plugins/static/tests/configs/.rr-http-static-disabled.yaml5
-rw-r--r--plugins/static/tests/configs/.rr-http-static-files-disable.yaml5
-rw-r--r--plugins/static/tests/configs/.rr-http-static-files.yaml5
-rw-r--r--plugins/static/tests/configs/.rr-http-static.yaml5
-rw-r--r--plugins/static/tests/static_plugin_test.go8
59 files changed, 1854 insertions, 78 deletions
diff --git a/plugins/checker/tests/configs/.rr-checker-init.yaml b/plugins/checker/tests/configs/.rr-checker-init.yaml
index 5943551b..0aba90c5 100755
--- a/plugins/checker/tests/configs/.rr-checker-init.yaml
+++ b/plugins/checker/tests/configs/.rr-checker-init.yaml
@@ -13,7 +13,9 @@ server:
status:
address: "127.0.0.1:34333"
-
+logs:
+ mode: development
+ level: error
http:
debug: true
address: 127.0.0.1:11933
diff --git a/plugins/checker/tests/plugin_test.go b/plugins/checker/tests/plugin_test.go
index a37fc08e..02a7f953 100644
--- a/plugins/checker/tests/plugin_test.go
+++ b/plugins/checker/tests/plugin_test.go
@@ -25,7 +25,7 @@ import (
)
func TestStatusHttp(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -109,7 +109,7 @@ func checkHTTPStatus(t *testing.T) {
}
func TestStatusRPC(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
diff --git a/plugins/config/tests/config_test.go b/plugins/config/tests/config_test.go
index 422e7eee..91ddc4ae 100755
--- a/plugins/config/tests/config_test.go
+++ b/plugins/config/tests/config_test.go
@@ -12,7 +12,7 @@ import (
)
func TestViperProvider_Init(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
diff --git a/plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml b/plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml
index 3dc5f9df..08c9b0ff 100644
--- a/plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml
+++ b/plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml
@@ -19,4 +19,7 @@ http:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
- destroyTimeout: 60s \ No newline at end of file
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/gzip/tests/configs/.rr-http-withGzip.yaml b/plugins/gzip/tests/configs/.rr-http-withGzip.yaml
index 38fdfe47..3475d5dd 100644
--- a/plugins/gzip/tests/configs/.rr-http-withGzip.yaml
+++ b/plugins/gzip/tests/configs/.rr-http-withGzip.yaml
@@ -19,4 +19,7 @@ http:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
- destroyTimeout: 60s \ No newline at end of file
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/gzip/tests/plugin_test.go b/plugins/gzip/tests/plugin_test.go
index 39979895..97291ebe 100644
--- a/plugins/gzip/tests/plugin_test.go
+++ b/plugins/gzip/tests/plugin_test.go
@@ -21,7 +21,7 @@ import (
)
func TestGzipPlugin(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -102,7 +102,7 @@ func headerCheck(t *testing.T) {
}
func TestMiddlewareNotExist(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
diff --git a/plugins/headers/tests/configs/.rr-cors-headers.yaml b/plugins/headers/tests/configs/.rr-cors-headers.yaml
index 5c1a200b..df985809 100644
--- a/plugins/headers/tests/configs/.rr-cors-headers.yaml
+++ b/plugins/headers/tests/configs/.rr-cors-headers.yaml
@@ -33,5 +33,7 @@ http:
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/headers/tests/configs/.rr-headers-init.yaml b/plugins/headers/tests/configs/.rr-headers-init.yaml
index 252fe8f3..21a4373a 100644
--- a/plugins/headers/tests/configs/.rr-headers-init.yaml
+++ b/plugins/headers/tests/configs/.rr-headers-init.yaml
@@ -33,5 +33,7 @@ http:
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/headers/tests/configs/.rr-req-headers.yaml b/plugins/headers/tests/configs/.rr-req-headers.yaml
index 9256e98d..bf305227 100644
--- a/plugins/headers/tests/configs/.rr-req-headers.yaml
+++ b/plugins/headers/tests/configs/.rr-req-headers.yaml
@@ -26,5 +26,7 @@ http:
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/headers/tests/configs/.rr-res-headers.yaml b/plugins/headers/tests/configs/.rr-res-headers.yaml
index 1bca2c3d..ae354051 100644
--- a/plugins/headers/tests/configs/.rr-res-headers.yaml
+++ b/plugins/headers/tests/configs/.rr-res-headers.yaml
@@ -26,5 +26,7 @@ http:
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/headers/tests/headers_plugin_test.go b/plugins/headers/tests/headers_plugin_test.go
index f1de8cb9..73aedb2c 100644
--- a/plugins/headers/tests/headers_plugin_test.go
+++ b/plugins/headers/tests/headers_plugin_test.go
@@ -20,7 +20,7 @@ import (
)
func TestHeadersInit(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -83,7 +83,7 @@ func TestHeadersInit(t *testing.T) {
}
func TestRequestHeaders(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -166,7 +166,7 @@ func reqHeaders(t *testing.T) {
}
func TestResponseHeaders(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -250,7 +250,7 @@ func resHeaders(t *testing.T) {
}
func TestCORSHeaders(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go
index a6399489..13299da1 100644
--- a/plugins/http/plugin.go
+++ b/plugins/http/plugin.go
@@ -296,7 +296,7 @@ func (s *Plugin) Reset() error {
s.Lock()
defer s.Unlock()
const op = errors.Op("http reset")
- s.log.Info("Resetting http plugin")
+ s.log.Info("HTTP plugin got restart request. Restarting...")
s.pool.Destroy(context.Background())
// re-read the config
@@ -317,6 +317,7 @@ func (s *Plugin) Reset() error {
return errors.E(op, err)
}
+ s.log.Info("HTTP workers Pool successfully restarted")
s.handler, err = NewHandler(
s.cfg.MaxRequestSize,
*s.cfg.Uploads,
@@ -329,7 +330,9 @@ func (s *Plugin) Reset() error {
// restore original listeners
s.pool.AddListener(s.listener)
+ s.log.Info("HTTP listeners successfully re-added")
+ s.log.Info("HTTP plugin successfully restarted")
return nil
}
diff --git a/plugins/http/tests/configs/.rr-broken-pipes.yaml b/plugins/http/tests/configs/.rr-broken-pipes.yaml
index aacc303e..e57d0b86 100644
--- a/plugins/http/tests/configs/.rr-broken-pipes.yaml
+++ b/plugins/http/tests/configs/.rr-broken-pipes.yaml
@@ -24,6 +24,8 @@ http:
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/http/tests/configs/.rr-echoErr.yaml b/plugins/http/tests/configs/.rr-echoErr.yaml
index 6ecdbb2a..24946c88 100644
--- a/plugins/http/tests/configs/.rr-echoErr.yaml
+++ b/plugins/http/tests/configs/.rr-echoErr.yaml
@@ -24,5 +24,7 @@ http:
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/http/tests/configs/.rr-env.yaml b/plugins/http/tests/configs/.rr-env.yaml
index c9fdc798..e29f66cc 100644
--- a/plugins/http/tests/configs/.rr-env.yaml
+++ b/plugins/http/tests/configs/.rr-env.yaml
@@ -27,5 +27,7 @@ http:
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/http/tests/configs/.rr-fcgi-reqUri.yaml b/plugins/http/tests/configs/.rr-fcgi-reqUri.yaml
index dbd19445..3009c30e 100644
--- a/plugins/http/tests/configs/.rr-fcgi-reqUri.yaml
+++ b/plugins/http/tests/configs/.rr-fcgi-reqUri.yaml
@@ -32,4 +32,7 @@ http:
http2:
enabled: false
h2c: false
- maxConcurrentStreams: 128 \ No newline at end of file
+ maxConcurrentStreams: 128
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/http/tests/configs/.rr-fcgi.yaml b/plugins/http/tests/configs/.rr-fcgi.yaml
index 0cbd6d02..45b6dbd0 100644
--- a/plugins/http/tests/configs/.rr-fcgi.yaml
+++ b/plugins/http/tests/configs/.rr-fcgi.yaml
@@ -32,4 +32,7 @@ http:
http2:
enabled: false
h2c: false
- maxConcurrentStreams: 128 \ No newline at end of file
+ maxConcurrentStreams: 128
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/http/tests/configs/.rr-h2c.yaml b/plugins/http/tests/configs/.rr-h2c.yaml
index d1b24338..cc42e3bf 100644
--- a/plugins/http/tests/configs/.rr-h2c.yaml
+++ b/plugins/http/tests/configs/.rr-h2c.yaml
@@ -23,4 +23,7 @@ http:
http2:
enabled: true
h2c: true
- maxConcurrentStreams: 128 \ No newline at end of file
+ maxConcurrentStreams: 128
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/http/tests/configs/.rr-http.yaml b/plugins/http/tests/configs/.rr-http.yaml
index 7b91f735..c6868f8c 100644
--- a/plugins/http/tests/configs/.rr-http.yaml
+++ b/plugins/http/tests/configs/.rr-http.yaml
@@ -37,5 +37,8 @@ http:
enabled: false
h2c: false
maxConcurrentStreams: 128
+logs:
+ mode: development
+ level: error
diff --git a/plugins/http/tests/configs/.rr-init.yaml b/plugins/http/tests/configs/.rr-init.yaml
index 50aa91ec..70b9642b 100644
--- a/plugins/http/tests/configs/.rr-init.yaml
+++ b/plugins/http/tests/configs/.rr-init.yaml
@@ -37,5 +37,7 @@ http:
enabled: false
h2c: false
maxConcurrentStreams: 128
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/http/tests/configs/.rr-resetter.yaml b/plugins/http/tests/configs/.rr-resetter.yaml
index b46b21f5..f2134812 100644
--- a/plugins/http/tests/configs/.rr-resetter.yaml
+++ b/plugins/http/tests/configs/.rr-resetter.yaml
@@ -24,5 +24,7 @@ http:
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s
-
+logs:
+ mode: development
+ level: error
diff --git a/plugins/http/tests/configs/.rr-ssl-push.yaml b/plugins/http/tests/configs/.rr-ssl-push.yaml
index 02de906a..3aea683c 100644
--- a/plugins/http/tests/configs/.rr-ssl-push.yaml
+++ b/plugins/http/tests/configs/.rr-ssl-push.yaml
@@ -26,4 +26,6 @@ http:
redirect: true
cert: fixtures/server.crt
key: fixtures/server.key
- # rootCa: root.crt \ No newline at end of file
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/http/tests/configs/.rr-ssl-redirect.yaml b/plugins/http/tests/configs/.rr-ssl-redirect.yaml
index 0ba1753e..4d889734 100644
--- a/plugins/http/tests/configs/.rr-ssl-redirect.yaml
+++ b/plugins/http/tests/configs/.rr-ssl-redirect.yaml
@@ -26,4 +26,6 @@ http:
redirect: true
cert: fixtures/server.crt
key: fixtures/server.key
- # rootCa: root.crt \ No newline at end of file
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/http/tests/configs/.rr-ssl.yaml b/plugins/http/tests/configs/.rr-ssl.yaml
index fb54d3fa..83b5a2dc 100644
--- a/plugins/http/tests/configs/.rr-ssl.yaml
+++ b/plugins/http/tests/configs/.rr-ssl.yaml
@@ -32,4 +32,7 @@ http:
http2:
enabled: false
h2c: false
- maxConcurrentStreams: 128 \ No newline at end of file
+ maxConcurrentStreams: 128
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/http/tests/http_test.go b/plugins/http/tests/http_test.go
index f68cd42c..c8dd4b38 100644
--- a/plugins/http/tests/http_test.go
+++ b/plugins/http/tests/http_test.go
@@ -41,7 +41,7 @@ var sslClient = &http.Client{
}
func TestHTTPInit(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -104,7 +104,7 @@ func TestHTTPInit(t *testing.T) {
}
func TestHTTPInformerReset(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -225,7 +225,7 @@ func informerTest(t *testing.T) {
}
func TestSSL(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -353,7 +353,7 @@ func fcgiEcho(t *testing.T) {
}
func TestSSLRedirect(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -439,7 +439,7 @@ func sslRedirect(t *testing.T) {
}
func TestSSLPushPipes(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -527,7 +527,7 @@ func sslPush(t *testing.T) {
}
func TestFastCGI_RequestUri(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -611,7 +611,7 @@ func fcgiReqURI(t *testing.T) {
}
func TestH2CUpgrade(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -700,7 +700,7 @@ func h2cUpgrade(t *testing.T) {
}
func TestH2C(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -788,7 +788,7 @@ func h2c(t *testing.T) {
}
func TestHttpMiddleware(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -887,7 +887,7 @@ func middleware(t *testing.T) {
}
func TestHttpEchoErr(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -977,7 +977,7 @@ func echoError(t *testing.T) {
}
func TestHttpEnvVariables(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -1061,7 +1061,7 @@ func envVarsTest(t *testing.T) {
}
func TestHttpBrokenPipes(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
diff --git a/plugins/informer/tests/.rr-informer.yaml b/plugins/informer/tests/.rr-informer.yaml
index 83ecd582..266933fd 100644
--- a/plugins/informer/tests/.rr-informer.yaml
+++ b/plugins/informer/tests/.rr-informer.yaml
@@ -10,4 +10,7 @@ server:
rpc:
listen: tcp://127.0.0.1:6001
- disabled: false \ No newline at end of file
+ disabled: false
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/informer/tests/informer_test.go b/plugins/informer/tests/informer_test.go
index 9e21e7ea..193e84bb 100644
--- a/plugins/informer/tests/informer_test.go
+++ b/plugins/informer/tests/informer_test.go
@@ -21,7 +21,7 @@ import (
)
func TestInformerInit(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
diff --git a/plugins/logger/tests/.rr.yaml b/plugins/logger/tests/.rr.yaml
index e69de29b..cb555ec3 100644
--- a/plugins/logger/tests/.rr.yaml
+++ b/plugins/logger/tests/.rr.yaml
@@ -0,0 +1,3 @@
+logs:
+ mode: development
+ level: debug \ No newline at end of file
diff --git a/plugins/logger/tests/logger_test.go b/plugins/logger/tests/logger_test.go
index 1df74c47..3e6faf1f 100644
--- a/plugins/logger/tests/logger_test.go
+++ b/plugins/logger/tests/logger_test.go
@@ -13,7 +13,7 @@ import (
)
func TestLogger(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
diff --git a/plugins/metrics/tests/.rr-test.yaml b/plugins/metrics/tests/.rr-test.yaml
index 79343e3c..37c50395 100644
--- a/plugins/metrics/tests/.rr-test.yaml
+++ b/plugins/metrics/tests/.rr-test.yaml
@@ -10,4 +10,7 @@ metrics:
type: histogram
help: "Custom application metric"
labels: [ "type" ]
- buckets: [ 0.1, 0.2, 0.3, 1.0 ] \ No newline at end of file
+ buckets: [ 0.1, 0.2, 0.3, 1.0 ]
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/metrics/tests/metrics_test.go b/plugins/metrics/tests/metrics_test.go
index 57b10aa4..2d3a3c27 100644
--- a/plugins/metrics/tests/metrics_test.go
+++ b/plugins/metrics/tests/metrics_test.go
@@ -45,7 +45,7 @@ func get() (string, error) {
}
func TestMetricsInit(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -107,7 +107,7 @@ func TestMetricsInit(t *testing.T) {
}
func TestMetricsGaugeCollector(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -174,7 +174,7 @@ func TestMetricsGaugeCollector(t *testing.T) {
}
func TestMetricsDifferentRPCCalls(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
diff --git a/plugins/reload/config.go b/plugins/reload/config.go
new file mode 100644
index 00000000..9ca2c0dc
--- /dev/null
+++ b/plugins/reload/config.go
@@ -0,0 +1,58 @@
+package reload
+
+import (
+ "time"
+
+ "github.com/spiral/errors"
+)
+
+// Config is a Reload configuration point.
+type Config struct {
+ // Interval is a global refresh interval
+ Interval time.Duration
+
+ // Patterns is a global file patterns to watch. It will be applied to every directory in project
+ Patterns []string
+
+ // Services is set of services which would be reloaded in case of FS changes
+ Services map[string]ServiceConfig
+}
+
+type ServiceConfig struct {
+ // Enabled indicates that service must be watched, doest not required when any other option specified
+ Enabled bool
+
+ // Recursive is options to use nested files from root folder
+ Recursive bool
+
+ // Patterns is per-service specific files to watch
+ Patterns []string
+
+ // Dirs is per-service specific dirs which will be combined with Patterns
+ Dirs []string
+
+ // Ignore is set of files which would not be watched
+ Ignore []string
+}
+
+// InitDefaults sets missing values to their default values.
+func InitDefaults(c *Config) {
+ c.Interval = time.Second
+ c.Patterns = []string{".php"}
+}
+
+// Valid validates the configuration.
+func (c *Config) Valid() error {
+ const op = errors.Op("config validation [reload plugin]")
+ if c.Interval < time.Second {
+ return errors.E(op, errors.Str("too short interval"))
+ }
+
+ if c.Services == nil {
+ return errors.E(op, errors.Str("should add at least 1 service"))
+ } else if len(c.Services) == 0 {
+ return errors.E(op, errors.Str("service initialized, however, no config added"))
+ }
+
+ return nil
+}
diff --git a/plugins/reload/plugin.go b/plugins/reload/plugin.go
new file mode 100644
index 00000000..555ddb82
--- /dev/null
+++ b/plugins/reload/plugin.go
@@ -0,0 +1,158 @@
+package reload
+
+import (
+ "os"
+ "strings"
+ "time"
+
+ "github.com/spiral/errors"
+ "github.com/spiral/roadrunner/v2/interfaces/log"
+ "github.com/spiral/roadrunner/v2/interfaces/resetter"
+ "github.com/spiral/roadrunner/v2/plugins/config"
+)
+
+// PluginName contains default plugin name.
+const PluginName string = "reload"
+const thresholdChanBuffer uint = 1000
+
+type Plugin struct {
+ cfg *Config
+ log log.Logger
+ watcher *Watcher
+ services map[string]interface{}
+ res resetter.Resetter
+ stopc chan struct{}
+}
+
+// Init controller service
+func (s *Plugin) Init(cfg config.Configurer, log log.Logger, res resetter.Resetter) error {
+ const op = errors.Op("reload plugin init")
+ s.cfg = &Config{}
+ InitDefaults(s.cfg)
+ err := cfg.UnmarshalKey(PluginName, &s.cfg)
+ if err != nil {
+ // disable plugin in case of error
+ return errors.E(op, errors.Disabled, err)
+ }
+
+ s.log = log
+ s.res = res
+ s.stopc = make(chan struct{})
+ s.services = make(map[string]interface{})
+
+ var configs []WatcherConfig
+
+ for serviceName, serviceConfig := range s.cfg.Services {
+ ignored, err := ConvertIgnored(serviceConfig.Ignore)
+ if err != nil {
+ return errors.E(op, err)
+ }
+ configs = append(configs, WatcherConfig{
+ ServiceName: serviceName,
+ Recursive: serviceConfig.Recursive,
+ Directories: serviceConfig.Dirs,
+ FilterHooks: func(filename string, patterns []string) error {
+ for i := 0; i < len(patterns); i++ {
+ if strings.Contains(filename, patterns[i]) {
+ return nil
+ }
+ }
+ return errors.E(op, errors.Skip)
+ },
+ Files: make(map[string]os.FileInfo),
+ Ignored: ignored,
+ FilePatterns: append(serviceConfig.Patterns, s.cfg.Patterns...),
+ })
+ }
+
+ s.watcher, err = NewWatcher(configs, s.log)
+ if err != nil {
+ return errors.E(op, err)
+ }
+
+ return nil
+}
+
+func (s *Plugin) Serve() chan error {
+ const op = errors.Op("reload plugin serve")
+ errCh := make(chan error, 1)
+ if s.cfg.Interval < time.Second {
+ errCh <- errors.E(op, errors.Str("reload interval is too fast"))
+ return errCh
+ }
+
+ // make a map with unique services
+ // so, if we would have a 100 events from http service
+ // in map we would see only 1 key and it's config
+ treshholdc := make(chan struct {
+ serviceConfig ServiceConfig
+ service string
+ }, thresholdChanBuffer)
+
+ // use the same interval
+ timer := time.NewTimer(s.cfg.Interval)
+
+ go func() {
+ for e := range s.watcher.Event {
+ treshholdc <- struct {
+ serviceConfig ServiceConfig
+ service string
+ }{serviceConfig: s.cfg.Services[e.service], service: e.service}
+ }
+ }()
+
+ // map with configs by services
+ updated := make(map[string]ServiceConfig, len(s.cfg.Services))
+
+ go func() {
+ for {
+ select {
+ case cfg := <-treshholdc:
+ // logic is following:
+ // restart
+ timer.Stop()
+ // replace previous value in map by more recent without adding new one
+ updated[cfg.service] = cfg.serviceConfig
+ // if we getting a lot of events, we shouldn't restart particular service on each of it (user doing batch move or very fast typing)
+ // instead, we are resetting the timer and wait for s.cfg.Interval time
+ // If there is no more events, we restart service only once
+ timer.Reset(s.cfg.Interval)
+ case <-timer.C:
+ if len(updated) > 0 {
+ for name := range updated {
+ err := s.res.ResetByName(name)
+ if err != nil {
+ errCh <- errors.E(op, err)
+ return
+ }
+ }
+ // zero map
+ updated = make(map[string]ServiceConfig, len(s.cfg.Services))
+ }
+ case <-s.stopc:
+ timer.Stop()
+ return
+ }
+ }
+ }()
+
+ go func() {
+ err := s.watcher.StartPolling(s.cfg.Interval)
+ if err != nil {
+ errCh <- errors.E(op, err)
+ return
+ }
+ }()
+
+ return errCh
+}
+
+func (s *Plugin) Stop() error {
+ s.watcher.Stop()
+ s.stopc <- struct{}{}
+ return nil
+}
+
+func (s *Plugin) Name() string {
+ return PluginName
+}
diff --git a/plugins/reload/tests/config_test.go b/plugins/reload/tests/config_test.go
new file mode 100644
index 00000000..5bb64b6b
--- /dev/null
+++ b/plugins/reload/tests/config_test.go
@@ -0,0 +1,63 @@
+package tests
+
+import (
+ "testing"
+ "time"
+
+ "github.com/spiral/roadrunner/v2/plugins/reload"
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_Config_Valid(t *testing.T) {
+ services := make(map[string]reload.ServiceConfig)
+ services["test"] = reload.ServiceConfig{
+ Recursive: false,
+ Patterns: nil,
+ Dirs: nil,
+ Ignore: nil,
+ }
+
+ cfg := &reload.Config{
+ Interval: time.Second,
+ Patterns: nil,
+ Services: services,
+ }
+ assert.NoError(t, cfg.Valid())
+}
+
+func Test_Fake_ServiceConfig(t *testing.T) {
+ services := make(map[string]reload.ServiceConfig)
+ cfg := &reload.Config{
+ Interval: time.Microsecond,
+ Patterns: nil,
+ Services: services,
+ }
+ assert.Error(t, cfg.Valid())
+}
+
+func Test_Interval(t *testing.T) {
+ services := make(map[string]reload.ServiceConfig)
+ services["test"] = reload.ServiceConfig{
+ Enabled: false,
+ Recursive: false,
+ Patterns: nil,
+ Dirs: nil,
+ Ignore: nil,
+ }
+
+ cfg := &reload.Config{
+ Interval: time.Millisecond, // should crash here
+ Patterns: nil,
+ Services: services,
+ }
+ assert.Error(t, cfg.Valid())
+}
+
+func Test_NoServiceConfig(t *testing.T) {
+ cfg := &reload.Config{
+ Interval: time.Second,
+ Patterns: nil,
+ Services: nil,
+ }
+ assert.Error(t, cfg.Valid())
+}
diff --git a/plugins/reload/tests/configs/.rr-reload-2.yaml b/plugins/reload/tests/configs/.rr-reload-2.yaml
new file mode 100644
index 00000000..5be3179b
--- /dev/null
+++ b/plugins/reload/tests/configs/.rr-reload-2.yaml
@@ -0,0 +1,44 @@
+server:
+ command: php ../../../tests/psr-worker-bench.php
+ user: ''
+ group: ''
+ env:
+ RR_HTTP: 'true'
+ relay: pipes
+ relayTimeout: 20s
+http:
+ debug: true
+ address: '127.0.0.1:27388'
+ maxRequestSize: 1024
+ middleware:
+ - ''
+ uploads:
+ forbid:
+ - .php
+ - .exe
+ - .bat
+ trustedSubnets:
+ - 10.0.0.0/8
+ - 127.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+ - '::1/128'
+ - 'fc00::/7'
+ - 'fe80::/10'
+ pool:
+ numWorkers: 2
+ maxJobs: 0
+ allocateTimeout: 60s
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error
+reload:
+ interval: 2s
+ patterns:
+ - .txt
+ services:
+ http:
+ dirs:
+ - './unit_tests'
+ recursive: true
diff --git a/plugins/reload/tests/configs/.rr-reload-3.yaml b/plugins/reload/tests/configs/.rr-reload-3.yaml
new file mode 100644
index 00000000..b97ed667
--- /dev/null
+++ b/plugins/reload/tests/configs/.rr-reload-3.yaml
@@ -0,0 +1,46 @@
+server:
+ command: php ../../../tests/psr-worker-bench.php
+ user: ''
+ group: ''
+ env:
+ RR_HTTP: 'true'
+ relay: pipes
+ relayTimeout: 20s
+http:
+ debug: true
+ address: '127.0.0.1:37388'
+ maxRequestSize: 1024
+ middleware:
+ - ''
+ uploads:
+ forbid:
+ - .php
+ - .exe
+ - .bat
+ trustedSubnets:
+ - 10.0.0.0/8
+ - 127.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+ - '::1/128'
+ - 'fc00::/7'
+ - 'fe80::/10'
+ pool:
+ numWorkers: 2
+ maxJobs: 0
+ allocateTimeout: 60s
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error
+reload:
+ interval: 2s
+ patterns:
+ - .txt
+ services:
+ http:
+ dirs:
+ - './unit_tests'
+ - './unit_tests_copied'
+ - './dir1'
+ recursive: true
diff --git a/plugins/reload/tests/configs/.rr-reload-4.yaml b/plugins/reload/tests/configs/.rr-reload-4.yaml
new file mode 100644
index 00000000..b664b836
--- /dev/null
+++ b/plugins/reload/tests/configs/.rr-reload-4.yaml
@@ -0,0 +1,46 @@
+server:
+ command: php ../../../tests/psr-worker-bench.php
+ user: ''
+ group: ''
+ env:
+ RR_HTTP: 'true'
+ relay: pipes
+ relayTimeout: 20s
+http:
+ debug: true
+ address: '127.0.0.1:22766'
+ maxRequestSize: 1024
+ middleware:
+ - ''
+ uploads:
+ forbid:
+ - .php
+ - .exe
+ - .bat
+ trustedSubnets:
+ - 10.0.0.0/8
+ - 127.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+ - '::1/128'
+ - 'fc00::/7'
+ - 'fe80::/10'
+ pool:
+ numWorkers: 2
+ maxJobs: 0
+ allocateTimeout: 60s
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error
+reload:
+ interval: 2s
+ patterns:
+ - .aaa
+ services:
+ http:
+ dirs:
+ - './unit_tests'
+ - './unit_tests_copied'
+ - './dir1'
+ recursive: false
diff --git a/plugins/reload/tests/configs/.rr-reload.yaml b/plugins/reload/tests/configs/.rr-reload.yaml
new file mode 100644
index 00000000..5e223db3
--- /dev/null
+++ b/plugins/reload/tests/configs/.rr-reload.yaml
@@ -0,0 +1,44 @@
+server:
+ command: php ../../../tests/psr-worker-bench.php
+ user: ''
+ group: ''
+ env:
+ RR_HTTP: 'true'
+ relay: pipes
+ relayTimeout: 20s
+http:
+ debug: true
+ address: '127.0.0.1:22388'
+ maxRequestSize: 1024
+ middleware:
+ - ''
+ uploads:
+ forbid:
+ - .php
+ - .exe
+ - .bat
+ trustedSubnets:
+ - 10.0.0.0/8
+ - 127.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+ - '::1/128'
+ - 'fc00::/7'
+ - 'fe80::/10'
+ pool:
+ numWorkers: 2
+ maxJobs: 0
+ allocateTimeout: 60s
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error
+reload:
+ interval: 1s
+ patterns:
+ - .txt
+ services:
+ http:
+ dirs:
+ - './unit_tests'
+ recursive: true
diff --git a/plugins/reload/tests/reload_plugin_test.go b/plugins/reload/tests/reload_plugin_test.go
new file mode 100644
index 00000000..8fb9474f
--- /dev/null
+++ b/plugins/reload/tests/reload_plugin_test.go
@@ -0,0 +1,822 @@
+package tests
+
+import (
+ "io"
+ "io/ioutil"
+ "math/rand"
+ "net/http"
+ "os"
+ "os/signal"
+ "path/filepath"
+ "strconv"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/spiral/endure"
+ "github.com/spiral/errors"
+ "github.com/spiral/roadrunner/v2/plugins/config"
+ httpPlugin "github.com/spiral/roadrunner/v2/plugins/http"
+ "github.com/spiral/roadrunner/v2/plugins/logger"
+ "github.com/spiral/roadrunner/v2/plugins/reload"
+ "github.com/spiral/roadrunner/v2/plugins/resetter"
+ "github.com/spiral/roadrunner/v2/plugins/server"
+ "github.com/stretchr/testify/assert"
+)
+
+const testDir string = "unit_tests"
+const testCopyToDir string = "unit_tests_copied"
+const hugeNumberOfFiles uint = 5000
+
+func TestReloadInit(t *testing.T) {
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
+ assert.NoError(t, err)
+
+ cfg := &config.Viper{
+ Path: "configs/.rr-reload.yaml",
+ Prefix: "rr",
+ }
+
+ // try to remove, skip error
+ assert.NoError(t, freeResources(testDir))
+ err = os.Mkdir(testDir, 0755)
+ assert.NoError(t, err)
+
+ defer func() {
+ assert.NoError(t, freeResources(testDir))
+ }()
+
+ err = cont.RegisterAll(
+ cfg,
+ &logger.ZapLogger{},
+ &server.Plugin{},
+ &httpPlugin.Plugin{},
+ &reload.Plugin{},
+ &resetter.Plugin{},
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ assert.NoError(t, err)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ tt := time.NewTimer(time.Second * 10)
+
+ go func() {
+ defer wg.Done()
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ case <-sig:
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ case <-tt.C:
+ // timeout
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ }
+ }
+ }()
+
+ t.Run("ReloadTestInit", reloadTestInit)
+ reloadHTTPLiveAfterReset(t, "22388")
+
+ wg.Wait()
+}
+
+func reloadTestInit(t *testing.T) {
+ err := ioutil.WriteFile(filepath.Join(testDir, "file.txt"), //nolint:gosec
+ []byte{}, 0755)
+ assert.NoError(t, err)
+}
+
+func TestReloadHugeNumberOfFiles(t *testing.T) {
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
+ assert.NoError(t, err)
+
+ cfg := &config.Viper{
+ Path: "configs/.rr-reload.yaml",
+ Prefix: "rr",
+ }
+
+ // try to remove, skip error
+ assert.NoError(t, freeResources(testDir))
+ assert.NoError(t, freeResources(testCopyToDir))
+ err = os.Mkdir(testDir, 0755)
+ assert.NoError(t, err)
+ err = os.Mkdir(testCopyToDir, 0755)
+ assert.NoError(t, err)
+
+ defer func() {
+ assert.NoError(t, freeResources(testDir))
+ assert.NoError(t, freeResources(testCopyToDir))
+ }()
+
+ err = cont.RegisterAll(
+ cfg,
+ &logger.ZapLogger{},
+ &server.Plugin{},
+ &httpPlugin.Plugin{},
+ &reload.Plugin{},
+ &resetter.Plugin{},
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ assert.NoError(t, err)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ tt := time.NewTimer(time.Second * 100)
+
+ go func() {
+ defer wg.Done()
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ case <-sig:
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ case <-tt.C:
+ // timeout
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ }
+ }
+ }()
+
+ t.Run("ReloadTestHugeNumberOfFiles", reloadHugeNumberOfFiles)
+ ttt := time.Now()
+ t.Run("ReloadRandomlyChangeFile", randomlyChangeFile)
+ if time.Since(ttt).Seconds() > 80 {
+ t.Fatal("spend too much time on reloading")
+ }
+ reloadHTTPLiveAfterReset(t, "22388")
+
+ wg.Wait()
+}
+
+func randomlyChangeFile(t *testing.T) {
+ // we know, that directory contains 5000 files (0-4999)
+ // let's try to randomly change it
+ for i := 0; i < 100; i++ {
+ // rand sleep
+ rSleep := rand.Int63n(1000) // nolint:gosec
+ time.Sleep(time.Millisecond * time.Duration(rSleep))
+ rNum := rand.Int63n(int64(hugeNumberOfFiles)) // nolint:gosec
+ err := ioutil.WriteFile(filepath.Join(testDir, "file_"+strconv.Itoa(int(rNum))+".txt"), []byte("Hello, Gophers!"), 0755) // nolint:gosec
+ assert.NoError(t, err)
+ }
+}
+
+func reloadHugeNumberOfFiles(t *testing.T) {
+ for i := uint(0); i < hugeNumberOfFiles; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".txt"))
+ }
+}
+
+// Should be events only about creating files with txt ext
+func TestReloadFilterFileExt(t *testing.T) {
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
+ assert.NoError(t, err)
+
+ cfg := &config.Viper{
+ Path: "configs/.rr-reload-2.yaml",
+ Prefix: "rr",
+ }
+
+ // try to remove, skip error
+ assert.NoError(t, freeResources(testDir))
+ err = os.Mkdir(testDir, 0755)
+ assert.NoError(t, err)
+
+ defer func() {
+ assert.NoError(t, freeResources(testDir))
+ }()
+
+ err = cont.RegisterAll(
+ cfg,
+ &logger.ZapLogger{},
+ &server.Plugin{},
+ &httpPlugin.Plugin{},
+ &reload.Plugin{},
+ &resetter.Plugin{},
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ assert.NoError(t, err)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ tt := time.NewTimer(time.Second * 40)
+
+ go func() {
+ defer wg.Done()
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ case <-sig:
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ case <-tt.C:
+ // timeout
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ }
+ }
+ }()
+
+ t.Run("ReloadMakeFiles", reloadMakeFiles)
+ ttt := time.Now()
+ t.Run("ReloadFilteredExt", reloadFilteredExt)
+ if time.Since(ttt).Seconds() > 20 {
+ t.Fatal("spend too much time on reloading")
+ }
+
+ reloadHTTPLiveAfterReset(t, "27388")
+
+ wg.Wait()
+}
+
+func reloadMakeFiles(t *testing.T) {
+ for i := uint(0); i < 100; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".txt"))
+ }
+ for i := uint(0); i < 100; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".abc"))
+ }
+ for i := uint(0); i < 100; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".def"))
+ }
+}
+
+func reloadFilteredExt(t *testing.T) {
+ // change files with abc extension
+ for i := 0; i < 10; i++ {
+ // rand sleep
+ rSleep := rand.Int63n(1000) // nolint:gosec
+ time.Sleep(time.Millisecond * time.Duration(rSleep))
+ rNum := rand.Int63n(int64(hugeNumberOfFiles)) // nolint:gosec
+ err := ioutil.WriteFile(filepath.Join(testDir, "file_"+strconv.Itoa(int(rNum))+".abc"), []byte("Hello, Gophers!"), 0755) // nolint:gosec
+ assert.NoError(t, err)
+ }
+
+ // change files with def extension
+ for i := 0; i < 10; i++ {
+ // rand sleep
+ rSleep := rand.Int63n(1000) // nolint:gosec
+ time.Sleep(time.Millisecond * time.Duration(rSleep))
+ rNum := rand.Int63n(int64(hugeNumberOfFiles)) // nolint:gosec
+ err := ioutil.WriteFile(filepath.Join(testDir, "file_"+strconv.Itoa(int(rNum))+".def"), []byte("Hello, Gophers!"), 0755) // nolint:gosec
+ assert.NoError(t, err)
+ }
+}
+
+// Should be events only about creating files with txt ext
+func TestReloadCopy3k(t *testing.T) {
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
+ assert.NoError(t, err)
+
+ cfg := &config.Viper{
+ Path: "configs/.rr-reload-3.yaml",
+ Prefix: "rr",
+ }
+
+ // try to remove, skip error
+ assert.NoError(t, freeResources(testDir))
+ assert.NoError(t, freeResources(testCopyToDir))
+ assert.NoError(t, freeResources("dir1"))
+ err = os.Mkdir(testDir, 0755)
+ assert.NoError(t, err)
+ err = os.Mkdir(testCopyToDir, 0755)
+ assert.NoError(t, err)
+ err = os.Mkdir("dir1", 0755)
+ assert.NoError(t, err)
+
+ defer func() {
+ assert.NoError(t, freeResources(testDir))
+ assert.NoError(t, freeResources(testCopyToDir))
+ assert.NoError(t, freeResources("dir1"))
+ }()
+
+ err = cont.RegisterAll(
+ cfg,
+ &logger.ZapLogger{},
+ &server.Plugin{},
+ &httpPlugin.Plugin{},
+ &reload.Plugin{},
+ &resetter.Plugin{},
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ assert.NoError(t, err)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ tt := time.NewTimer(time.Second * 180)
+
+ go func() {
+ defer wg.Done()
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ case <-sig:
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ case <-tt.C:
+ // timeout
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ }
+ }
+ }()
+
+ // Scenario
+ // 1
+ // Create 3k files with txt, abc, def extensions
+ // Copy files to the unit_tests_copy dir
+ // 2
+ // Delete both dirs, recreate
+ // Create 3k files with txt, abc, def extensions
+ // Move files to the unit_tests_copy dir
+ // 3
+ // Recursive
+
+ t.Run("ReloadMake3kFiles", reloadMake3kFiles)
+ ttt := time.Now()
+ t.Run("ReloadCopyFiles", reloadCopyFiles)
+ if time.Since(ttt).Seconds() > 100 {
+ t.Fatal("spend too much time on copy")
+ }
+
+ t.Run("ReloadRecursiveDirsSupport", copyFilesRecursive)
+ t.Run("RandomChangesInRecursiveDirs", randomChangesInRecursiveDirs)
+ t.Run("RemoveFilesSupport", removeFilesSupport)
+ t.Run("ReloadMoveSupport", reloadMoveSupport)
+
+ reloadHTTPLiveAfterReset(t, "37388")
+
+ wg.Wait()
+}
+
+func reloadMoveSupport(t *testing.T) {
+ t.Run("MoveSupportCopy", copyFilesRecursive)
+ // move some files
+ for i := 0; i < 50; i++ {
+ // rand sleep
+ rSleep := rand.Int63n(1000) // nolint:gosec
+ time.Sleep(time.Millisecond * time.Duration(rSleep))
+ rNum := rand.Int63n(int64(200)) // nolint:gosec
+ rDir := rand.Int63n(9) // nolint:gosec
+ rExt := rand.Int63n(3) // nolint:gosec
+
+ ext := []string{
+ ".txt",
+ ".abc",
+ ".def",
+ }
+
+ // change files with def extension
+ dirs := []string{
+ "dir1",
+ "dir1/dir2",
+ "dir1/dir2/dir3",
+ "dir1/dir2/dir3/dir4",
+ "dir1/dir2/dir3/dir4/dir5",
+ "dir1/dir2/dir3/dir4/dir5/dir6",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/dir10",
+ }
+
+ // move file
+ err := os.Rename(filepath.Join(dirs[rDir], "file_"+strconv.Itoa(int(rNum))+ext[rExt]), filepath.Join(dirs[rDir+1], "file_"+strconv.Itoa(int(rNum))+ext[rExt]))
+ assert.NoError(t, err)
+ }
+}
+
+func removeFilesSupport(t *testing.T) {
+ // remove some files
+ for i := 0; i < 50; i++ {
+ // rand sleep
+ rSleep := rand.Int63n(1000) // nolint:gosec
+ time.Sleep(time.Millisecond * time.Duration(rSleep))
+ rNum := rand.Int63n(int64(200)) // nolint:gosec
+ rDir := rand.Int63n(10) // nolint:gosec
+ rExt := rand.Int63n(3) // nolint:gosec
+
+ ext := []string{
+ ".txt",
+ ".abc",
+ ".def",
+ }
+
+ // change files with def extension
+ dirs := []string{
+ "dir1",
+ "dir1/dir2",
+ "dir1/dir2/dir3",
+ "dir1/dir2/dir3/dir4",
+ "dir1/dir2/dir3/dir4/dir5",
+ "dir1/dir2/dir3/dir4/dir5/dir6",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/dir10",
+ }
+ err := os.Remove(filepath.Join(dirs[rDir], "file_"+strconv.Itoa(int(rNum))+ext[rExt]))
+ assert.NoError(t, err)
+ }
+}
+
+func randomChangesInRecursiveDirs(t *testing.T) {
+ // change files with def extension
+ dirs := []string{
+ "dir1",
+ "dir1/dir2",
+ "dir1/dir2/dir3",
+ "dir1/dir2/dir3/dir4",
+ "dir1/dir2/dir3/dir4/dir5",
+ "dir1/dir2/dir3/dir4/dir5/dir6",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9",
+ "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/dir10",
+ }
+
+ ext := []string{
+ ".txt",
+ ".abc",
+ ".def",
+ }
+
+ filenames := []string{
+ "file_", // should be update
+ "foo_", // should be created
+ "bar_", // should be created
+ }
+ for i := 0; i < 50; i++ {
+ // rand sleep
+ rSleep := rand.Int63n(1000) // nolint:gosec
+ time.Sleep(time.Millisecond * time.Duration(rSleep))
+ rNum := rand.Int63n(int64(200)) // nolint:gosec
+ rDir := rand.Int63n(10) // nolint:gosec
+ rExt := rand.Int63n(3) // nolint:gosec
+ rName := rand.Int63n(3) // nolint:gosec
+
+ err := ioutil.WriteFile(filepath.Join(dirs[rDir], filenames[rName]+strconv.Itoa(int(rNum))+ext[rExt]), []byte("Hello, Gophers!"), 0755) // nolint:gosec
+ assert.NoError(t, err)
+ }
+}
+
+func copyFilesRecursive(t *testing.T) {
+ err := copyDir(testDir, "dir1")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2/dir3")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2/dir3/dir4")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6/dir7")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9")
+ assert.NoError(t, err)
+ err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/dir10")
+ assert.NoError(t, err)
+}
+
+func reloadCopyFiles(t *testing.T) {
+ err := copyDir(testDir, testCopyToDir)
+ assert.NoError(t, err)
+
+ assert.NoError(t, freeResources(testDir))
+ assert.NoError(t, freeResources(testCopyToDir))
+ err = os.Mkdir(testDir, 0755)
+ assert.NoError(t, err)
+ err = os.Mkdir(testCopyToDir, 0755)
+ assert.NoError(t, err)
+
+ // recreate files
+ for i := uint(0); i < 200; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".txt"))
+ }
+ for i := uint(0); i < 200; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".abc"))
+ }
+ for i := uint(0); i < 200; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".def"))
+ }
+
+ err = copyDir(testDir, testCopyToDir)
+ assert.NoError(t, err)
+}
+
+func reloadMake3kFiles(t *testing.T) {
+ for i := uint(0); i < 100; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".txt"))
+ }
+ for i := uint(0); i < 100; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".abc"))
+ }
+ for i := uint(0); i < 100; i++ {
+ assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".def"))
+ }
+}
+
+func TestReloadNoRecursion(t *testing.T) {
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
+ assert.NoError(t, err)
+
+ cfg := &config.Viper{
+ Path: "configs/.rr-reload-4.yaml",
+ Prefix: "rr",
+ }
+
+ // try to remove, skip error
+ assert.NoError(t, freeResources(testDir))
+ assert.NoError(t, freeResources(testCopyToDir))
+ assert.NoError(t, freeResources("dir1"))
+ err = os.Mkdir(testDir, 0755)
+ assert.NoError(t, err)
+
+ err = os.Mkdir("dir1", 0755)
+ assert.NoError(t, err)
+
+ err = os.Mkdir(testCopyToDir, 0755)
+ assert.NoError(t, err)
+
+ defer func() {
+ assert.NoError(t, freeResources(testDir))
+ assert.NoError(t, freeResources(testCopyToDir))
+ assert.NoError(t, freeResources("dir1"))
+ }()
+
+ err = cont.RegisterAll(
+ cfg,
+ &logger.ZapLogger{},
+ &server.Plugin{},
+ &httpPlugin.Plugin{},
+ &reload.Plugin{},
+ &resetter.Plugin{},
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ assert.NoError(t, err)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ tt := time.NewTimer(time.Second * 30)
+
+ go func() {
+ defer wg.Done()
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ case <-sig:
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ case <-tt.C:
+ // timeout
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ }
+ }
+ }()
+ t.Run("ReloadMakeFiles", reloadMakeFiles) // make files in the testDir
+ t.Run("ReloadCopyFilesRecursive", reloadCopyFiles)
+
+ reloadHTTPLiveAfterReset(t, "22766")
+
+ wg.Wait()
+}
+
+// ========================================================================
+
+func reloadHTTPLiveAfterReset(t *testing.T, port string) {
+ req, err := http.NewRequest("GET", "http://localhost:"+port, nil)
+ assert.NoError(t, err)
+
+ r, err := http.DefaultClient.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "hello world", string(b))
+
+ err = r.Body.Close()
+ assert.NoError(t, err)
+}
+
+func freeResources(path string) error {
+ return os.RemoveAll(path)
+}
+
+func makeFile(filename string) error {
+ return ioutil.WriteFile(filepath.Join(testDir, filename), []byte{}, 0755) //nolint:gosec
+}
+
+func copyDir(src string, dst string) error {
+ src = filepath.Clean(src)
+ dst = filepath.Clean(dst)
+
+ si, err := os.Stat(src)
+ if err != nil {
+ return err
+ }
+ if !si.IsDir() {
+ return errors.E(errors.Str("source is not a directory"))
+ }
+
+ _, err = os.Stat(dst)
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ err = os.MkdirAll(dst, si.Mode())
+ if err != nil {
+ return err
+ }
+
+ entries, err := ioutil.ReadDir(src)
+ if err != nil {
+ return err
+ }
+
+ for _, entry := range entries {
+ srcPath := filepath.Join(src, entry.Name())
+ dstPath := filepath.Join(dst, entry.Name())
+
+ if entry.IsDir() {
+ err = copyDir(srcPath, dstPath)
+ if err != nil {
+ return err
+ }
+ } else {
+ // Skip symlinks.
+ if entry.Mode()&os.ModeSymlink != 0 {
+ continue
+ }
+
+ err = copyFile(srcPath, dstPath)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func copyFile(src, dst string) error {
+ in, err := os.Open(src)
+ if err != nil {
+ return errors.E(err)
+ }
+ defer func() {
+ _ = in.Close()
+ }()
+
+ out, err := os.Create(dst)
+ if err != nil {
+ return errors.E(err)
+ }
+ defer func() {
+ _ = out.Close()
+ }()
+
+ _, err = io.Copy(out, in)
+ if err != nil {
+ return errors.E(err)
+ }
+
+ err = out.Sync()
+ if err != nil {
+ return errors.E(err)
+ }
+
+ si, err := os.Stat(src)
+ if err != nil {
+ return errors.E(err)
+ }
+ err = os.Chmod(dst, si.Mode())
+ if err != nil {
+ return errors.E(err)
+ }
+ return nil
+}
diff --git a/plugins/reload/watcher.go b/plugins/reload/watcher.go
new file mode 100644
index 00000000..55e1d9d5
--- /dev/null
+++ b/plugins/reload/watcher.go
@@ -0,0 +1,372 @@
+package reload
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+
+ "github.com/spiral/errors"
+ "github.com/spiral/roadrunner/v2/interfaces/log"
+)
+
+// SimpleHook is used to filter by simple criteria, CONTAINS
+type SimpleHook func(filename string, pattern []string) error
+
+// An Event describes an event that is received when files or directory
+// changes occur. It includes the os.FileInfo of the changed file or
+// directory and the type of event that's occurred and the full path of the file.
+type Event struct {
+ Path string
+ Info os.FileInfo
+
+ service string // type of service, http, grpc, etc...
+}
+
+type WatcherConfig struct {
+ // service name
+ ServiceName string
+
+ // Recursive or just add by singe directory
+ Recursive bool
+
+ // Directories used per-service
+ Directories []string
+
+ // simple hook, just CONTAINS
+ FilterHooks func(filename string, pattern []string) error
+
+ // path to file with Files
+ Files map[string]os.FileInfo
+
+ // Ignored Directories, used map for O(1) amortized get
+ Ignored map[string]struct{}
+
+ // FilePatterns to ignore
+ FilePatterns []string
+}
+
+type Watcher struct {
+ // main event channel
+ Event chan Event
+ close chan struct{}
+
+ // =============================
+ mu *sync.Mutex
+
+ // indicates is walker started or not
+ started bool
+
+ // config for each service
+ // need pointer here to assign files
+ watcherConfigs map[string]WatcherConfig
+
+ // logger
+ log log.Logger
+}
+
+// Options is used to set Watcher Options
+type Options func(*Watcher)
+
+// NewWatcher returns new instance of File Watcher
+func NewWatcher(configs []WatcherConfig, log log.Logger, options ...Options) (*Watcher, error) {
+ w := &Watcher{
+ Event: make(chan Event),
+ mu: &sync.Mutex{},
+
+ log: log,
+
+ close: make(chan struct{}),
+
+ //workingDir: workDir,
+ watcherConfigs: make(map[string]WatcherConfig),
+ }
+
+ // add watcherConfigs by service names
+ for _, v := range configs {
+ w.watcherConfigs[v.ServiceName] = v
+ }
+
+ // apply options
+ for _, option := range options {
+ option(w)
+ }
+ err := w.initFs()
+ if err != nil {
+ return nil, err
+ }
+
+ return w, nil
+}
+
+// initFs makes initial map with files
+func (w *Watcher) initFs() error {
+ for srvName, config := range w.watcherConfigs {
+ fileList, err := w.retrieveFileList(srvName, config)
+ if err != nil {
+ return err
+ }
+ // workaround. in golang you can't assign to map in struct field
+ tmp := w.watcherConfigs[srvName]
+ tmp.Files = fileList
+ w.watcherConfigs[srvName] = tmp
+ }
+ return nil
+}
+
+// ConvertIgnored is used to convert slice to map with ignored files
+func ConvertIgnored(ignored []string) (map[string]struct{}, error) {
+ if len(ignored) == 0 {
+ return nil, nil
+ }
+
+ ign := make(map[string]struct{}, len(ignored))
+ for i := 0; i < len(ignored); i++ {
+ abs, err := filepath.Abs(ignored[i])
+ if err != nil {
+ return nil, err
+ }
+ ign[abs] = struct{}{}
+ }
+
+ return ign, nil
+}
+
+// https://en.wikipedia.org/wiki/Inotify
+// SetMaxFileEvents sets max file notify events for Watcher
+// In case of file watch errors, this value can be increased system-wide
+// For linux: set --> fs.inotify.max_user_watches = 600000 (under /etc/<choose_name_here>.conf)
+// Add apply: sudo sysctl -p --system
+// func SetMaxFileEvents(events int) Options {
+// return func(watcher *Watcher) {
+// watcher.maxFileWatchEvents = events
+// }
+//
+// }
+
+// pass map from outside
+func (w *Watcher) retrieveFilesSingle(serviceName, path string) (map[string]os.FileInfo, error) {
+ const op = errors.Op("retrieve")
+ stat, err := os.Stat(path)
+ if err != nil {
+ return nil, err
+ }
+
+ filesList := make(map[string]os.FileInfo, 10)
+ filesList[path] = stat
+
+ // if it's not a dir, return
+ if !stat.IsDir() {
+ return filesList, nil
+ }
+
+ fileInfoList, err := ioutil.ReadDir(path)
+ if err != nil {
+ return nil, err
+ }
+
+ // recursive calls are slow in compare to goto
+ // so, we will add files with goto pattern
+outer:
+ for i := 0; i < len(fileInfoList); i++ {
+ // if file in ignored --> continue
+ if _, ignored := w.watcherConfigs[serviceName].Ignored[path]; ignored {
+ continue
+ }
+
+ // if filename does not contain pattern --> ignore that file
+ if w.watcherConfigs[serviceName].FilePatterns != nil && w.watcherConfigs[serviceName].FilterHooks != nil {
+ err = w.watcherConfigs[serviceName].FilterHooks(fileInfoList[i].Name(), w.watcherConfigs[serviceName].FilePatterns)
+ if errors.Is(errors.Skip, err) {
+ continue outer
+ }
+ }
+
+ filesList[fileInfoList[i].Name()] = fileInfoList[i]
+ }
+
+ return filesList, nil
+}
+
+func (w *Watcher) StartPolling(duration time.Duration) error {
+ w.mu.Lock()
+ const op = errors.Op("start polling")
+ if w.started {
+ w.mu.Unlock()
+ return errors.E(op, errors.Str("already started"))
+ }
+
+ w.started = true
+ w.mu.Unlock()
+
+ return w.waitEvent(duration)
+}
+
+// this is blocking operation
+func (w *Watcher) waitEvent(d time.Duration) error {
+ ticker := time.NewTicker(d)
+ for {
+ select {
+ case <-w.close:
+ ticker.Stop()
+ // just exit
+ // no matter for the pollEvents
+ return nil
+ case <-ticker.C:
+ // this is not very effective way
+ // because we have to wait on Lock
+ // better is to listen files in parallel, but, since that would be used in debug... TODO
+ for serviceName := range w.watcherConfigs {
+ // TODO sync approach
+ fileList, _ := w.retrieveFileList(serviceName, w.watcherConfigs[serviceName])
+ w.pollEvents(w.watcherConfigs[serviceName].ServiceName, fileList)
+ }
+ }
+ }
+}
+
+// retrieveFileList get file list for service
+func (w *Watcher) retrieveFileList(serviceName string, config WatcherConfig) (map[string]os.FileInfo, error) {
+ fileList := make(map[string]os.FileInfo)
+ if config.Recursive {
+ // walk through directories recursively
+ for i := 0; i < len(config.Directories); i++ {
+ // full path is workdir/relative_path
+ fullPath, err := filepath.Abs(config.Directories[i])
+ if err != nil {
+ return nil, err
+ }
+ list, err := w.retrieveFilesRecursive(serviceName, fullPath)
+ if err != nil {
+ return nil, err
+ }
+
+ for k := range list {
+ fileList[k] = list[k]
+ }
+ }
+ return fileList, nil
+ }
+
+ for i := 0; i < len(config.Directories); i++ {
+ // full path is workdir/relative_path
+ fullPath, err := filepath.Abs(config.Directories[i])
+ if err != nil {
+ return nil, err
+ }
+
+ // list is pathToFiles with files
+ list, err := w.retrieveFilesSingle(serviceName, fullPath)
+ if err != nil {
+ return nil, err
+ }
+
+ for pathToFile, file := range list {
+ fileList[pathToFile] = file
+ }
+ }
+
+ return fileList, nil
+}
+
+func (w *Watcher) retrieveFilesRecursive(serviceName, root string) (map[string]os.FileInfo, error) {
+ fileList := make(map[string]os.FileInfo)
+
+ return fileList, filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ // If path is ignored and it's a directory, skip the directory. If it's
+ // ignored and it's a single file, skip the file.
+ _, ignored := w.watcherConfigs[serviceName].Ignored[path]
+ if ignored {
+ if info.IsDir() {
+ // if it's dir, ignore whole
+ return filepath.SkipDir
+ }
+ return nil
+ }
+
+ // if filename does not contain pattern --> ignore that file
+ err = w.watcherConfigs[serviceName].FilterHooks(info.Name(), w.watcherConfigs[serviceName].FilePatterns)
+ if errors.Is(errors.Skip, err) {
+ return nil
+ }
+
+ // Add the path and it's info to the file list.
+ fileList[path] = info
+ return nil
+ })
+}
+
+func (w *Watcher) pollEvents(serviceName string, files map[string]os.FileInfo) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+
+ // Store create and remove events for use to check for rename events.
+ creates := make(map[string]os.FileInfo)
+ removes := make(map[string]os.FileInfo)
+
+ // Check for removed files.
+ for pth := range w.watcherConfigs[serviceName].Files {
+ if _, found := files[pth]; !found {
+ removes[pth] = w.watcherConfigs[serviceName].Files[pth]
+ w.log.Debug("file added to the list of removed files", "path", pth, "name", w.watcherConfigs[serviceName].Files[pth].Name(), "size", w.watcherConfigs[serviceName].Files[pth].Size())
+ }
+ }
+
+ // Check for created files, writes and chmods.
+ for pth := range files {
+ if files[pth].IsDir() {
+ continue
+ }
+ oldInfo, found := w.watcherConfigs[serviceName].Files[pth]
+ if !found {
+ // A file was created.
+ creates[pth] = files[pth]
+ w.log.Debug("file was created", "path", pth, "name", files[pth].Name(), "size", files[pth].Size())
+ continue
+ }
+
+ if oldInfo.ModTime() != files[pth].ModTime() || oldInfo.Mode() != files[pth].Mode() {
+ w.watcherConfigs[serviceName].Files[pth] = files[pth]
+ w.log.Debug("file was updated", "path", pth, "name", files[pth].Name(), "size", files[pth].Size())
+ w.Event <- Event{
+ Path: pth,
+ Info: files[pth],
+ service: serviceName,
+ }
+ }
+ }
+
+ // Send all the remaining create and remove events.
+ for pth := range creates {
+ // add file to the plugin watch files
+ w.watcherConfigs[serviceName].Files[pth] = creates[pth]
+ w.log.Debug("file was added to watcher", "path", pth, "name", creates[pth].Name(), "size", creates[pth].Size())
+
+ w.Event <- Event{
+ Path: pth,
+ Info: creates[pth],
+ service: serviceName,
+ }
+ }
+
+ for pth := range removes {
+ // delete path from the config
+ delete(w.watcherConfigs[serviceName].Files, pth)
+ w.log.Debug("file was removed from watcher", "path", pth, "name", removes[pth].Name(), "size", removes[pth].Size())
+
+ w.Event <- Event{
+ Path: pth,
+ Info: removes[pth],
+ service: serviceName,
+ }
+ }
+}
+
+func (w *Watcher) Stop() {
+ w.close <- struct{}{}
+}
diff --git a/plugins/resetter/plugin.go b/plugins/resetter/plugin.go
index 99e02aef..8dc5cc31 100644
--- a/plugins/resetter/plugin.go
+++ b/plugins/resetter/plugin.go
@@ -10,12 +10,39 @@ import (
const PluginName = "resetter"
type Plugin struct {
- registry map[string]resetter.Resetter
+ registry map[string]resetter.Resettable
log log.Logger
}
+func (p *Plugin) ResetAll() error {
+ const op = errors.Op("reset all")
+ for name := range p.registry {
+ err := p.registry[name].Reset()
+ if err != nil {
+ return errors.E(op, err)
+ }
+ }
+ return nil
+}
+
+func (p *Plugin) ResetByName(plugin string) error {
+ const op = errors.Op("reset by name")
+ if plugin, ok := p.registry[plugin]; ok {
+ return plugin.Reset()
+ }
+ return errors.E(op, errors.Errorf("can't find plugin: %s", plugin))
+}
+
+func (p *Plugin) GetAll() []string {
+ all := make([]string, 0, len(p.registry))
+ for name := range p.registry {
+ all = append(all, name)
+ }
+ return all
+}
+
func (p *Plugin) Init(log log.Logger) error {
- p.registry = make(map[string]resetter.Resetter)
+ p.registry = make(map[string]resetter.Resettable)
p.log = log
return nil
}
@@ -31,7 +58,7 @@ func (p *Plugin) Reset(name string) error {
}
// RegisterTarget resettable service.
-func (p *Plugin) RegisterTarget(name endure.Named, r resetter.Resetter) error {
+func (p *Plugin) RegisterTarget(name endure.Named, r resetter.Resettable) error {
p.registry[name.Name()] = r
return nil
}
diff --git a/plugins/resetter/rpc.go b/plugins/resetter/rpc.go
index ecc51bb3..344c6681 100644
--- a/plugins/resetter/rpc.go
+++ b/plugins/resetter/rpc.go
@@ -7,7 +7,7 @@ type rpc struct {
log log.Logger
}
-// List all resettable services.
+// List all resettable plugins.
func (rpc *rpc) List(_ bool, list *[]string) error {
rpc.log.Debug("started List method")
*list = make([]string, 0)
@@ -21,7 +21,7 @@ func (rpc *rpc) List(_ bool, list *[]string) error {
return nil
}
-// Reset named service.
+// Reset named plugin.
func (rpc *rpc) Reset(service string, done *bool) error {
rpc.log.Debug("started Reset method for the service", "service", service)
defer rpc.log.Debug("finished Reset method for the service", "service", service)
diff --git a/plugins/resetter/tests/.rr-resetter.yaml b/plugins/resetter/tests/.rr-resetter.yaml
index 83ecd582..266933fd 100644
--- a/plugins/resetter/tests/.rr-resetter.yaml
+++ b/plugins/resetter/tests/.rr-resetter.yaml
@@ -10,4 +10,7 @@ server:
rpc:
listen: tcp://127.0.0.1:6001
- disabled: false \ No newline at end of file
+ disabled: false
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/resetter/tests/resetter_test.go b/plugins/resetter/tests/resetter_test.go
index 3bfccf47..45de67e3 100644
--- a/plugins/resetter/tests/resetter_test.go
+++ b/plugins/resetter/tests/resetter_test.go
@@ -20,7 +20,7 @@ import (
)
func TestResetterInit(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
diff --git a/plugins/rpc/tests/.rr-rpc-disabled.yaml b/plugins/rpc/tests/.rr-rpc-disabled.yaml
index 624fb3c5..d5c185e7 100644
--- a/plugins/rpc/tests/.rr-rpc-disabled.yaml
+++ b/plugins/rpc/tests/.rr-rpc-disabled.yaml
@@ -1,3 +1,6 @@
rpc:
listen: tcp://127.0.0.1:6001
- disabled: true \ No newline at end of file
+ disabled: true
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/rpc/tests/.rr.yaml b/plugins/rpc/tests/.rr.yaml
index 76e8b440..d2cb6c70 100644
--- a/plugins/rpc/tests/.rr.yaml
+++ b/plugins/rpc/tests/.rr.yaml
@@ -1,3 +1,6 @@
rpc:
listen: tcp://127.0.0.1:6001
- disabled: false \ No newline at end of file
+ disabled: false
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/rpc/tests/rpc_test.go b/plugins/rpc/tests/rpc_test.go
index 88267dfb..0344da6b 100644
--- a/plugins/rpc/tests/rpc_test.go
+++ b/plugins/rpc/tests/rpc_test.go
@@ -17,7 +17,7 @@ import (
// graph https://bit.ly/3ensdNb
func TestRpcInit(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -96,7 +96,7 @@ func TestRpcInit(t *testing.T) {
// graph https://bit.ly/3ensdNb
func TestRpcDisabled(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
diff --git a/plugins/server/tests/configs/.rr-no-app-section.yaml b/plugins/server/tests/configs/.rr-no-app-section.yaml
index b6e3ea93..5266e83d 100644
--- a/plugins/server/tests/configs/.rr-no-app-section.yaml
+++ b/plugins/server/tests/configs/.rr-no-app-section.yaml
@@ -6,4 +6,7 @@ server:
"RR_CONFIG": "/some/place/on/the/C134"
"RR_CONFIG2": "C138"
relay: "pipes"
- relayTimeout: "20s" \ No newline at end of file
+ relayTimeout: "20s"
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/server/tests/configs/.rr-sockets.yaml b/plugins/server/tests/configs/.rr-sockets.yaml
index ab1239aa..6b5b6bf5 100644
--- a/plugins/server/tests/configs/.rr-sockets.yaml
+++ b/plugins/server/tests/configs/.rr-sockets.yaml
@@ -6,4 +6,7 @@ server:
"RR_CONFIG": "/some/place/on/the/C134"
"RR_CONFIG2": "C138"
relay: "unix://unix.sock"
- relayTimeout: "20s" \ No newline at end of file
+ relayTimeout: "20s"
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/server/tests/configs/.rr-tcp.yaml b/plugins/server/tests/configs/.rr-tcp.yaml
index f53bffcc..ee1d450a 100644
--- a/plugins/server/tests/configs/.rr-tcp.yaml
+++ b/plugins/server/tests/configs/.rr-tcp.yaml
@@ -6,4 +6,7 @@ server:
"RR_CONFIG": "/some/place/on/the/C134"
"RR_CONFIG2": "C138"
relay: "tcp://localhost:9999"
- relayTimeout: "20s" \ No newline at end of file
+ relayTimeout: "20s"
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/server/tests/configs/.rr-wrong-command.yaml b/plugins/server/tests/configs/.rr-wrong-command.yaml
index d2c087a6..e66349dd 100644
--- a/plugins/server/tests/configs/.rr-wrong-command.yaml
+++ b/plugins/server/tests/configs/.rr-wrong-command.yaml
@@ -7,3 +7,6 @@ server:
"RR_CONFIG2": "C138"
relay: "pipes"
relayTimeout: "20s"
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/server/tests/configs/.rr-wrong-relay.yaml b/plugins/server/tests/configs/.rr-wrong-relay.yaml
index 1dd73d73..98894c7a 100644
--- a/plugins/server/tests/configs/.rr-wrong-relay.yaml
+++ b/plugins/server/tests/configs/.rr-wrong-relay.yaml
@@ -6,4 +6,7 @@ server:
"RR_CONFIG": "/some/place/on/the/C134"
"RR_CONFIG2": "C138"
relay: "pupes"
- relayTimeout: "20s" \ No newline at end of file
+ relayTimeout: "20s"
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/server/tests/configs/.rr.yaml b/plugins/server/tests/configs/.rr.yaml
index b6e3ea93..5266e83d 100644
--- a/plugins/server/tests/configs/.rr.yaml
+++ b/plugins/server/tests/configs/.rr.yaml
@@ -6,4 +6,7 @@ server:
"RR_CONFIG": "/some/place/on/the/C134"
"RR_CONFIG2": "C138"
relay: "pipes"
- relayTimeout: "20s" \ No newline at end of file
+ relayTimeout: "20s"
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/server/tests/server_test.go b/plugins/server/tests/server_test.go
index bc374a9e..faf01b11 100644
--- a/plugins/server/tests/server_test.go
+++ b/plugins/server/tests/server_test.go
@@ -14,7 +14,7 @@ import (
)
func TestAppPipes(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -80,7 +80,7 @@ func TestAppPipes(t *testing.T) {
}
func TestAppSockets(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -146,7 +146,7 @@ func TestAppSockets(t *testing.T) {
}
func TestAppTCP(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -212,7 +212,7 @@ func TestAppTCP(t *testing.T) {
}
func TestAppWrongConfig(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -244,7 +244,7 @@ func TestAppWrongConfig(t *testing.T) {
}
func TestAppWrongRelay(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -280,7 +280,7 @@ func TestAppWrongRelay(t *testing.T) {
}
func TestAppWrongCommand(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -318,7 +318,7 @@ func TestAppWrongCommand(t *testing.T) {
}
func TestAppNoAppSectionInConfig(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.DebugLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
diff --git a/plugins/static/tests/configs/.rr-http-static-disabled.yaml b/plugins/static/tests/configs/.rr-http-static-disabled.yaml
index d0b9b388..e8917c06 100644
--- a/plugins/static/tests/configs/.rr-http-static-disabled.yaml
+++ b/plugins/static/tests/configs/.rr-http-static-disabled.yaml
@@ -27,4 +27,7 @@ http:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
- destroyTimeout: 60s \ No newline at end of file
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/static/tests/configs/.rr-http-static-files-disable.yaml b/plugins/static/tests/configs/.rr-http-static-files-disable.yaml
index a3d814a3..1cae9ed7 100644
--- a/plugins/static/tests/configs/.rr-http-static-files-disable.yaml
+++ b/plugins/static/tests/configs/.rr-http-static-files-disable.yaml
@@ -27,4 +27,7 @@ http:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
- destroyTimeout: 60s \ No newline at end of file
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/static/tests/configs/.rr-http-static-files.yaml b/plugins/static/tests/configs/.rr-http-static-files.yaml
index 35938b80..32d0a6c7 100644
--- a/plugins/static/tests/configs/.rr-http-static-files.yaml
+++ b/plugins/static/tests/configs/.rr-http-static-files.yaml
@@ -28,4 +28,7 @@ http:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
- destroyTimeout: 60s \ No newline at end of file
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/static/tests/configs/.rr-http-static.yaml b/plugins/static/tests/configs/.rr-http-static.yaml
index 80a1fa7f..d3bd05f5 100644
--- a/plugins/static/tests/configs/.rr-http-static.yaml
+++ b/plugins/static/tests/configs/.rr-http-static.yaml
@@ -26,4 +26,7 @@ http:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
- destroyTimeout: 60s \ No newline at end of file
+ destroyTimeout: 60s
+logs:
+ mode: development
+ level: error \ No newline at end of file
diff --git a/plugins/static/tests/static_plugin_test.go b/plugins/static/tests/static_plugin_test.go
index 528d5eea..5bad54bf 100644
--- a/plugins/static/tests/static_plugin_test.go
+++ b/plugins/static/tests/static_plugin_test.go
@@ -25,7 +25,7 @@ import (
)
func TestStaticPlugin(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -133,7 +133,7 @@ func serveStaticSample(t *testing.T) {
}
func TestStaticDisabled(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -206,7 +206,7 @@ func staticDisabled(t *testing.T) {
}
func TestStaticFilesDisabled(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{
@@ -282,7 +282,7 @@ func staticFilesDisabled(t *testing.T) {
}
func TestStaticFilesForbid(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel))
assert.NoError(t, err)
cfg := &config.Viper{