Feat: Env variables to set health timeouts
- HEALTH_OPENVPN_DURATION_INITIAL - HEALTH_OPENVPN_DURATION_ADDITION
This commit is contained in:
@@ -110,6 +110,9 @@ ENV VPNSP=pia \
|
|||||||
# Openvpn
|
# Openvpn
|
||||||
OPENVPN_CIPHER= \
|
OPENVPN_CIPHER= \
|
||||||
OPENVPN_AUTH= \
|
OPENVPN_AUTH= \
|
||||||
|
# Health
|
||||||
|
HEALTH_OPENVPN_DURATION_INITIAL=6s \
|
||||||
|
HEALTH_OPENVPN_DURATION_ADDITION=5s \
|
||||||
# DNS over TLS
|
# DNS over TLS
|
||||||
DOT=on \
|
DOT=on \
|
||||||
DOT_PROVIDERS=cloudflare \
|
DOT_PROVIDERS=cloudflare \
|
||||||
|
|||||||
@@ -365,8 +365,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
controlGroupHandler.Add(httpServerHandler)
|
controlGroupHandler.Add(httpServerHandler)
|
||||||
|
|
||||||
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
||||||
healthcheckServer := healthcheck.NewServer(
|
healthcheckServer := healthcheck.NewServer(constants.HealthcheckAddress,
|
||||||
constants.HealthcheckAddress, healthLogger, openvpnLooper)
|
allSettings.Health, healthLogger, openvpnLooper)
|
||||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
"HTTP health server", defaultGoRoutineSettings)
|
"HTTP health server", defaultGoRoutineSettings)
|
||||||
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
||||||
|
|||||||
41
internal/configuration/health.go
Normal file
41
internal/configuration/health.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Health contains settings for the healthcheck and health server.
|
||||||
|
type Health struct {
|
||||||
|
OpenVPN HealthyWait
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *Health) String() string {
|
||||||
|
return strings.Join(settings.lines(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *Health) lines() (lines []string) {
|
||||||
|
lines = append(lines, lastIndent+"Health:")
|
||||||
|
|
||||||
|
lines = append(lines, indent+lastIndent+"OpenVPN:")
|
||||||
|
for _, line := range settings.OpenVPN.lines() {
|
||||||
|
lines = append(lines, indent+indent+line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *Health) read(r reader) (err error) {
|
||||||
|
settings.OpenVPN.Initial, err = r.env.Duration("HEALTH_OPENVPN_DURATION_INITIAL", params.Default("6s"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.OpenVPN.Addition, err = r.env.Duration("HEALTH_OPENVPN_DURATION_ADDITION", params.Default("5s"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
150
internal/configuration/health_test.go
Normal file
150
internal/configuration/health_test.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/qdm12/golibs/params/mock_params"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Health_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var health Health
|
||||||
|
const expected = "|--Health:\n |--OpenVPN:\n |--Initial duration: 0s"
|
||||||
|
|
||||||
|
s := health.String()
|
||||||
|
|
||||||
|
assert.Equal(t, expected, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Health_lines(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
settings Health
|
||||||
|
lines []string
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
lines: []string{
|
||||||
|
"|--Health:",
|
||||||
|
" |--OpenVPN:",
|
||||||
|
" |--Initial duration: 0s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"filled settings": {
|
||||||
|
settings: Health{
|
||||||
|
OpenVPN: HealthyWait{
|
||||||
|
Initial: time.Second,
|
||||||
|
Addition: time.Minute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lines: []string{
|
||||||
|
"|--Health:",
|
||||||
|
" |--OpenVPN:",
|
||||||
|
" |--Initial duration: 1s",
|
||||||
|
" |--Addition duration: 1m0s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
lines := testCase.settings.lines()
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.lines, lines)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Health_read(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
errDummy := errors.New("dummy")
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
openvpnInitialDuration time.Duration
|
||||||
|
openvpnInitialErr error
|
||||||
|
openvpnAdditionDuration time.Duration
|
||||||
|
openvpnAdditionErr error
|
||||||
|
expected Health
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
openvpnInitialDuration: time.Second,
|
||||||
|
openvpnAdditionDuration: time.Minute,
|
||||||
|
expected: Health{
|
||||||
|
OpenVPN: HealthyWait{
|
||||||
|
Initial: time.Second,
|
||||||
|
Addition: time.Minute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"initial error": {
|
||||||
|
openvpnInitialDuration: time.Second,
|
||||||
|
openvpnInitialErr: errDummy,
|
||||||
|
openvpnAdditionDuration: time.Minute,
|
||||||
|
expected: Health{
|
||||||
|
OpenVPN: HealthyWait{
|
||||||
|
Initial: time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"addition error": {
|
||||||
|
openvpnInitialDuration: time.Second,
|
||||||
|
openvpnAdditionDuration: time.Minute,
|
||||||
|
openvpnAdditionErr: errDummy,
|
||||||
|
expected: Health{
|
||||||
|
OpenVPN: HealthyWait{
|
||||||
|
Initial: time.Second,
|
||||||
|
Addition: time.Minute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
env := mock_params.NewMockEnv(ctrl)
|
||||||
|
env.EXPECT().
|
||||||
|
Duration("HEALTH_OPENVPN_DURATION_INITIAL", gomock.Any()).
|
||||||
|
Return(testCase.openvpnInitialDuration, testCase.openvpnInitialErr)
|
||||||
|
if testCase.openvpnInitialErr == nil {
|
||||||
|
env.EXPECT().
|
||||||
|
Duration("HEALTH_OPENVPN_DURATION_ADDITION", gomock.Any()).
|
||||||
|
Return(testCase.openvpnAdditionDuration, testCase.openvpnAdditionErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reader{
|
||||||
|
env: env,
|
||||||
|
}
|
||||||
|
|
||||||
|
var health Health
|
||||||
|
|
||||||
|
err := health.read(r)
|
||||||
|
|
||||||
|
if testCase.err != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.expected, health)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
55
internal/configuration/healthwait_test.go
Normal file
55
internal/configuration/healthwait_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_HealthyWait_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var healthyWait HealthyWait
|
||||||
|
const expected = "|--Initial duration: 0s"
|
||||||
|
|
||||||
|
s := healthyWait.String()
|
||||||
|
|
||||||
|
assert.Equal(t, expected, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HealthyWait_lines(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
settings HealthyWait
|
||||||
|
lines []string
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
lines: []string{
|
||||||
|
"|--Initial duration: 0s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"filled settings": {
|
||||||
|
settings: HealthyWait{
|
||||||
|
Initial: time.Second,
|
||||||
|
Addition: time.Minute,
|
||||||
|
},
|
||||||
|
lines: []string{
|
||||||
|
"|--Initial duration: 1s",
|
||||||
|
"|--Addition duration: 1m0s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
lines := testCase.settings.lines()
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.lines, lines)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
30
internal/configuration/healthywait.go
Normal file
30
internal/configuration/healthywait.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthyWait struct {
|
||||||
|
// Initial is the initial duration to wait for the program
|
||||||
|
// to be healthy before taking action.
|
||||||
|
Initial time.Duration
|
||||||
|
// Addition is the duration to add to the Initial duration
|
||||||
|
// after Initial has expired to wait longer for the program
|
||||||
|
// to be healthy.
|
||||||
|
Addition time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *HealthyWait) String() string {
|
||||||
|
return strings.Join(settings.lines(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *HealthyWait) lines() (lines []string) {
|
||||||
|
lines = append(lines, lastIndent+"Initial duration: "+settings.Initial.String())
|
||||||
|
|
||||||
|
if settings.Addition > 0 {
|
||||||
|
lines = append(lines, lastIndent+"Addition duration: "+settings.Addition.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ type Settings struct {
|
|||||||
PublicIP PublicIP
|
PublicIP PublicIP
|
||||||
VersionInformation bool
|
VersionInformation bool
|
||||||
ControlServer ControlServer
|
ControlServer ControlServer
|
||||||
|
Health Health
|
||||||
}
|
}
|
||||||
|
|
||||||
func (settings *Settings) String() string {
|
func (settings *Settings) String() string {
|
||||||
@@ -36,6 +37,7 @@ func (settings *Settings) lines() (lines []string) {
|
|||||||
lines = append(lines, settings.System.lines()...)
|
lines = append(lines, settings.System.lines()...)
|
||||||
lines = append(lines, settings.HTTPProxy.lines()...)
|
lines = append(lines, settings.HTTPProxy.lines()...)
|
||||||
lines = append(lines, settings.ShadowSocks.lines()...)
|
lines = append(lines, settings.ShadowSocks.lines()...)
|
||||||
|
lines = append(lines, settings.Health.lines()...)
|
||||||
lines = append(lines, settings.ControlServer.lines()...)
|
lines = append(lines, settings.ControlServer.lines()...)
|
||||||
lines = append(lines, settings.Updater.lines()...)
|
lines = append(lines, settings.Updater.lines()...)
|
||||||
lines = append(lines, settings.PublicIP.lines()...)
|
lines = append(lines, settings.PublicIP.lines()...)
|
||||||
@@ -55,6 +57,7 @@ var (
|
|||||||
ErrControlServer = errors.New("cannot read control server settings")
|
ErrControlServer = errors.New("cannot read control server settings")
|
||||||
ErrUpdater = errors.New("cannot read Updater settings")
|
ErrUpdater = errors.New("cannot read Updater settings")
|
||||||
ErrPublicIP = errors.New("cannot read Public IP getter settings")
|
ErrPublicIP = errors.New("cannot read Public IP getter settings")
|
||||||
|
ErrHealth = errors.New("cannot read health settings")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Read obtains all configuration options for the program and returns an error as soon
|
// Read obtains all configuration options for the program and returns an error as soon
|
||||||
@@ -107,5 +110,9 @@ func (settings *Settings) Read(env params.Env, os os.OS, logger logging.Logger)
|
|||||||
return fmt.Errorf("%w: %s", ErrPublicIP, err)
|
return fmt.Errorf("%w: %s", ErrPublicIP, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := settings.Health.read(r); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrHealth, err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ func Test_Settings_lines(t *testing.T) {
|
|||||||
" |--Process user ID: 0",
|
" |--Process user ID: 0",
|
||||||
" |--Process group ID: 0",
|
" |--Process group ID: 0",
|
||||||
" |--Timezone: NOT SET ⚠️ - it can cause time related issues",
|
" |--Timezone: NOT SET ⚠️ - it can cause time related issues",
|
||||||
|
"|--Health:",
|
||||||
|
" |--OpenVPN:",
|
||||||
|
" |--Initial duration: 0s",
|
||||||
"|--HTTP control server:",
|
"|--HTTP control server:",
|
||||||
" |--Listening port: 0",
|
" |--Listening port: 0",
|
||||||
"|--Public IP getter: disabled",
|
"|--Public IP getter: disabled",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
func (s *server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
func (s *server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
|
|
||||||
s.openvpn.healthyTimer = time.NewTimer(defaultOpenvpnHealthyWaitTime)
|
s.openvpn.healthyTimer = time.NewTimer(s.openvpn.currentHealthyWait)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
previousErr := s.handler.getErr()
|
previousErr := s.handler.getErr()
|
||||||
@@ -23,10 +23,10 @@ func (s *server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
|||||||
if previousErr != nil && err == nil {
|
if previousErr != nil && err == nil {
|
||||||
s.logger.Info("healthy!")
|
s.logger.Info("healthy!")
|
||||||
s.openvpn.healthyTimer.Stop()
|
s.openvpn.healthyTimer.Stop()
|
||||||
s.openvpn.healthyWaitTime = defaultOpenvpnHealthyWaitTime
|
s.openvpn.currentHealthyWait = s.openvpn.healthyWaitConfig.Initial
|
||||||
} else if previousErr == nil && err != nil {
|
} else if previousErr == nil && err != nil {
|
||||||
s.logger.Info("unhealthy: " + err.Error())
|
s.logger.Info("unhealthy: " + err.Error())
|
||||||
s.openvpn.healthyTimer = time.NewTimer(s.openvpn.healthyWaitTime)
|
s.openvpn.healthyTimer = time.NewTimer(s.openvpn.currentHealthyWait)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil { // try again after 1 second
|
if err != nil { // try again after 1 second
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
|
|
||||||
func (s *server) onUnhealthyOpenvpn(ctx context.Context) {
|
func (s *server) onUnhealthyOpenvpn(ctx context.Context) {
|
||||||
s.logger.Info("program has been unhealthy for " +
|
s.logger.Info("program has been unhealthy for " +
|
||||||
s.openvpn.healthyWaitTime.String() + ": restarting OpenVPN")
|
s.openvpn.currentHealthyWait.String() + ": restarting OpenVPN")
|
||||||
_, _ = s.openvpn.looper.ApplyStatus(ctx, constants.Stopped)
|
_, _ = s.openvpn.looper.ApplyStatus(ctx, constants.Stopped)
|
||||||
_, _ = s.openvpn.looper.ApplyStatus(ctx, constants.Running)
|
_, _ = s.openvpn.looper.ApplyStatus(ctx, constants.Running)
|
||||||
s.openvpn.healthyWaitTime += openvpnHealthyWaitTimeAdd
|
s.openvpn.currentHealthyWait += s.openvpn.healthyWaitConfig.Addition
|
||||||
s.openvpn.healthyTimer = time.NewTimer(s.openvpn.healthyWaitTime)
|
s.openvpn.healthyTimer = time.NewTimer(s.openvpn.currentHealthyWait)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
@@ -24,26 +25,23 @@ type server struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type openvpnHealth struct {
|
type openvpnHealth struct {
|
||||||
looper openvpn.Looper
|
looper openvpn.Looper
|
||||||
healthyWaitTime time.Duration
|
healthyWaitConfig configuration.HealthyWait
|
||||||
healthyTimer *time.Timer
|
currentHealthyWait time.Duration
|
||||||
|
healthyTimer *time.Timer
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
func NewServer(address string, settings configuration.Health,
|
||||||
defaultOpenvpnHealthyWaitTime = 6 * time.Second
|
logger logging.Logger, openvpnLooper openvpn.Looper) Server {
|
||||||
openvpnHealthyWaitTimeAdd = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewServer(address string, logger logging.Logger,
|
|
||||||
openvpnLooper openvpn.Looper) Server {
|
|
||||||
return &server{
|
return &server{
|
||||||
address: address,
|
address: address,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
handler: newHandler(logger),
|
handler: newHandler(logger),
|
||||||
resolver: net.DefaultResolver,
|
resolver: net.DefaultResolver,
|
||||||
openvpn: openvpnHealth{
|
openvpn: openvpnHealth{
|
||||||
looper: openvpnLooper,
|
looper: openvpnLooper,
|
||||||
healthyWaitTime: defaultOpenvpnHealthyWaitTime,
|
currentHealthyWait: settings.OpenVPN.Initial,
|
||||||
|
healthyWaitConfig: settings.OpenVPN,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user