Plugin/RELOAD - Tune usage of var global, add limit to options (#1457)
* tune usage of var global, add limit to options * update readme for minimal values * useless change to quick-off codecov * fix msgs for min values and tune the flag for end of reload usage, with a 'maybe' option * adding UT for min values, adding MD5 of corefile on the log
This commit is contained in:
parent
3fb07161b7
commit
2440024772
4 changed files with 56 additions and 16 deletions
|
@ -35,6 +35,7 @@ reload [INTERVAL] [JITTER]
|
||||||
* The plugin will check for changes every **INTERVAL**, subject to +/- the **JITTER** duration
|
* The plugin will check for changes every **INTERVAL**, subject to +/- the **JITTER** duration
|
||||||
* **INTERVAL** and **JITTER** are Golang (durations)[https://golang.org/pkg/time/#ParseDuration]
|
* **INTERVAL** and **JITTER** are Golang (durations)[https://golang.org/pkg/time/#ParseDuration]
|
||||||
* Default **INTERVAL** is 30s, default **JITTER** is 15s
|
* Default **INTERVAL** is 30s, default **JITTER** is 15s
|
||||||
|
* Minimal value for **INTERVAL** is 2s, and for **JITTER** is 1s
|
||||||
* If **JITTER** is more than half of **INTERVAL**, it will be set to half of **INTERVAL**
|
* If **JITTER** is more than half of **INTERVAL**, it will be set to half of **INTERVAL**
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
|
@ -9,12 +9,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// reload periodically checks if the Corefile has changed, and reloads if so
|
// reload periodically checks if the Corefile has changed, and reloads if so
|
||||||
|
const (
|
||||||
|
unused = 0
|
||||||
|
maybeUsed = 1
|
||||||
|
used = 2
|
||||||
|
)
|
||||||
|
|
||||||
type reload struct {
|
type reload struct {
|
||||||
instance *caddy.Instance
|
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
sum [md5.Size]byte
|
usage int
|
||||||
stopped bool
|
|
||||||
quit chan bool
|
quit chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,13 +29,14 @@ func hook(event caddy.EventName, info interface{}) error {
|
||||||
// if reload is removed from the Corefile, then the hook
|
// if reload is removed from the Corefile, then the hook
|
||||||
// is still registered but setup is never called again
|
// is still registered but setup is never called again
|
||||||
// so we need a flag to tell us not to reload
|
// so we need a flag to tell us not to reload
|
||||||
if r.stopped {
|
if r.usage == unused {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// this should be an instance. ok to panic if not
|
// this should be an instance. ok to panic if not
|
||||||
r.instance = info.(*caddy.Instance)
|
instance := info.(*caddy.Instance)
|
||||||
r.sum = md5.Sum(r.instance.Caddyfile().Body())
|
md5sum := md5.Sum(instance.Caddyfile().Body())
|
||||||
|
log.Printf("[INFO] Running configuration MD5 = %x\n", md5sum)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
tick := time.NewTicker(r.interval)
|
tick := time.NewTicker(r.interval)
|
||||||
|
@ -40,19 +44,26 @@ func hook(event caddy.EventName, info interface{}) error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-tick.C:
|
case <-tick.C:
|
||||||
corefile, err := caddy.LoadCaddyfile(r.instance.Caddyfile().ServerType())
|
corefile, err := caddy.LoadCaddyfile(instance.Caddyfile().ServerType())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s := md5.Sum(corefile.Body())
|
s := md5.Sum(corefile.Body())
|
||||||
if s != r.sum {
|
if s != md5sum {
|
||||||
_, err := r.instance.Restart(corefile)
|
// Let not try to restart with the same file, even though it is wrong.
|
||||||
|
md5sum = s
|
||||||
|
// now lets consider that plugin will not be reload, unless appear in next config file
|
||||||
|
// change status iof usage will be reset in setup if the plugin appears in config file
|
||||||
|
r.usage = maybeUsed
|
||||||
|
_, err := instance.Restart(corefile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Corefile changed but reload failed: %s\n", err)
|
log.Printf("[ERROR] Corefile changed but reload failed: %s\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// we are done, this hook gets called again with new instance
|
// we are done, if the plugin was not set used, then it is not.
|
||||||
r.stopped = true
|
if r.usage == maybeUsed {
|
||||||
|
r.usage = unused
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-r.quit:
|
case <-r.quit:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package reload
|
package reload
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -17,7 +18,11 @@ func init() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var r *reload
|
// the info reload is global to all application, whatever number of reloads.
|
||||||
|
// it is used to transmit data between Setup and start of the hook called 'onInstanceStartup'
|
||||||
|
// channel for QUIT is never changed in purpose.
|
||||||
|
// WARNING: this data may be unsync after an invalid attempt of reload Corefile.
|
||||||
|
var r = reload{interval: defaultInterval, usage: unused, quit: make(chan bool)}
|
||||||
var once sync.Once
|
var once sync.Once
|
||||||
|
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
|
@ -32,19 +37,25 @@ func setup(c *caddy.Controller) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
d, err := time.ParseDuration(args[0])
|
d, err := time.ParseDuration(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return plugin.Error("reload", err)
|
||||||
}
|
}
|
||||||
i = d
|
i = d
|
||||||
}
|
}
|
||||||
|
if i < minInterval {
|
||||||
|
return plugin.Error("reload", fmt.Errorf("interval value must be greater or equal to %v", minInterval))
|
||||||
|
}
|
||||||
|
|
||||||
j := defaultJitter
|
j := defaultJitter
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
d, err := time.ParseDuration(args[1])
|
d, err := time.ParseDuration(args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return plugin.Error("reload", err)
|
||||||
}
|
}
|
||||||
j = d
|
j = d
|
||||||
}
|
}
|
||||||
|
if j < minJitter {
|
||||||
|
return plugin.Error("reload", fmt.Errorf("jitter value must be greater or equal to %v", minJitter))
|
||||||
|
}
|
||||||
|
|
||||||
if j > i/2 {
|
if j > i/2 {
|
||||||
j = i / 2
|
j = i / 2
|
||||||
|
@ -53,20 +64,25 @@ func setup(c *caddy.Controller) error {
|
||||||
jitter := time.Duration(rand.Int63n(j.Nanoseconds()) - (j.Nanoseconds() / 2))
|
jitter := time.Duration(rand.Int63n(j.Nanoseconds()) - (j.Nanoseconds() / 2))
|
||||||
i = i + jitter
|
i = i + jitter
|
||||||
|
|
||||||
r = &reload{interval: i, quit: make(chan bool)}
|
// prepare info for next onInstanceStartup event
|
||||||
|
r.interval = i
|
||||||
|
r.usage = used
|
||||||
|
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
caddy.RegisterEventHook("reload", hook)
|
caddy.RegisterEventHook("reload", hook)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// re-register on finalShutDown as the instance most-likely will be changed
|
||||||
c.OnFinalShutdown(func() error {
|
c.OnFinalShutdown(func() error {
|
||||||
r.quit <- true
|
r.quit <- true
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
minJitter = 1 * time.Second
|
||||||
|
minInterval = 2 * time.Second
|
||||||
defaultInterval = 30 * time.Second
|
defaultInterval = 30 * time.Second
|
||||||
defaultJitter = 15 * time.Second
|
defaultJitter = 15 * time.Second
|
||||||
)
|
)
|
||||||
|
|
|
@ -36,4 +36,16 @@ func TestSetupReload(t *testing.T) {
|
||||||
if err := setup(c); err == nil {
|
if err := setup(c); err == nil {
|
||||||
t.Fatalf("Expected errors, but got: %v", err)
|
t.Fatalf("Expected errors, but got: %v", err)
|
||||||
}
|
}
|
||||||
|
c = caddy.NewTestController("dns", `reload 1s`)
|
||||||
|
if err := setup(c); err == nil {
|
||||||
|
t.Fatalf("Expected errors, but got: %v", err)
|
||||||
|
}
|
||||||
|
c = caddy.NewTestController("dns", `reload 0s`)
|
||||||
|
if err := setup(c); err == nil {
|
||||||
|
t.Fatalf("Expected errors, but got: %v", err)
|
||||||
|
}
|
||||||
|
c = caddy.NewTestController("dns", `reload 3s 0.5s`)
|
||||||
|
if err := setup(c); err == nil {
|
||||||
|
t.Fatalf("Expected errors, but got: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue