Feat: HEALTH_SERVER_ADDRESS

This commit is contained in:
Quentin McGaw (desktop)
2021-07-22 20:45:17 +00:00
parent 6f58f84151
commit c33402ce66
9 changed files with 95 additions and 30 deletions

View File

@@ -113,6 +113,7 @@ ENV VPNSP=pia \
# Health # Health
HEALTH_OPENVPN_DURATION_INITIAL=6s \ HEALTH_OPENVPN_DURATION_INITIAL=6s \
HEALTH_OPENVPN_DURATION_ADDITION=5s \ HEALTH_OPENVPN_DURATION_ADDITION=5s \
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
# DNS over TLS # DNS over TLS
DOT=on \ DOT=on \
DOT_PROVIDERS=cloudflare \ DOT_PROVIDERS=cloudflare \

View File

@@ -71,10 +71,11 @@ func main() {
osUser := user.New() osUser := user.New()
unix := unix.New() unix := unix.New()
cli := cli.New() cli := cli.New()
env := params.NewEnv()
errorCh := make(chan error) errorCh := make(chan error)
go func() { go func() {
errorCh <- _main(ctx, buildInfo, args, logger, os, osUser, unix, cli) errorCh <- _main(ctx, buildInfo, args, logger, env, os, osUser, unix, cli)
}() }()
select { select {
@@ -112,12 +113,12 @@ var (
//nolint:gocognit,gocyclo //nolint:gocognit,gocyclo
func _main(ctx context.Context, buildInfo models.BuildInformation, func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger logging.ParentLogger, os os.OS, args []string, logger logging.ParentLogger, env params.Env, os os.OS,
osUser user.OSUser, unix unix.Unix, cli cli.CLI) error { osUser user.OSUser, unix unix.Unix, cli cli.CLI) error {
if len(args) > 1 { // cli operation if len(args) > 1 { // cli operation
switch args[1] { switch args[1] {
case "healthcheck": case "healthcheck":
return cli.HealthCheck(ctx) return cli.HealthCheck(ctx, env, os, logger)
case "clientkey": case "clientkey":
return cli.ClientKey(args[2:], os.OpenFile) return cli.ClientKey(args[2:], os.OpenFile)
case "openvpnconfig": case "openvpnconfig":
@@ -159,7 +160,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
} }
var allSettings configuration.Settings var allSettings configuration.Settings
err := allSettings.Read(params.NewEnv(), os, err := allSettings.Read(env, os,
logger.NewChild(logging.Settings{Prefix: "configuration: "})) logger.NewChild(logging.Settings{Prefix: "configuration: "}))
if err != nil { if err != nil {
return err return err
@@ -365,8 +366,7 @@ 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(constants.HealthcheckAddress, healthcheckServer := healthcheck.NewServer(allSettings.Health, 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)

View File

@@ -6,11 +6,12 @@ import (
"github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os" "github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/params"
) )
type CLI interface { type CLI interface {
ClientKey(args []string, openFile os.OpenFileFunc) error ClientKey(args []string, openFile os.OpenFileFunc) error
HealthCheck(ctx context.Context) error HealthCheck(ctx context.Context, env params.Env, os os.OS, logger logging.Logger) error
OpenvpnConfig(os os.OS, logger logging.Logger) error OpenvpnConfig(os os.OS, logger logging.Logger) error
Update(ctx context.Context, args []string, os os.OS, logger logging.Logger) error Update(ctx context.Context, args []string, os os.OS, logger logging.Logger) error
} }

View File

@@ -2,19 +2,36 @@ package cli
import ( import (
"context" "context"
"net"
"net/http" "net/http"
"time" "time"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/healthcheck" "github.com/qdm12/gluetun/internal/healthcheck"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/params"
) )
func (c *cli) HealthCheck(ctx context.Context) error { func (c *cli) HealthCheck(ctx context.Context, env params.Env,
os os.OS, logger logging.Logger) error {
// Extract the health server port from the configuration.
config := configuration.Health{}
err := config.Read(env, os, logger)
if err != nil {
return err
}
_, port, err := net.SplitHostPort(config.ServerAddress)
if err != nil {
return err
}
const timeout = 10 * time.Second const timeout = 10 * time.Second
httpClient := &http.Client{Timeout: timeout} httpClient := &http.Client{Timeout: timeout}
healthchecker := healthcheck.NewChecker(httpClient) healthchecker := healthcheck.NewChecker(httpClient)
ctx, cancel := context.WithTimeout(ctx, timeout) ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel() defer cancel()
const url = "http://" + constants.HealthcheckAddress
url := "http://127.0.0.1:" + port
return healthchecker.Check(ctx, url) return healthchecker.Check(ctx, url)
} }

View File

@@ -3,11 +3,14 @@ package configuration
import ( import (
"strings" "strings"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/params" "github.com/qdm12/golibs/params"
) )
// Health contains settings for the healthcheck and health server. // Health contains settings for the healthcheck and health server.
type Health struct { type Health struct {
ServerAddress string
OpenVPN HealthyWait OpenVPN HealthyWait
} }
@@ -18,6 +21,8 @@ func (settings *Health) String() string {
func (settings *Health) lines() (lines []string) { func (settings *Health) lines() (lines []string) {
lines = append(lines, lastIndent+"Health:") lines = append(lines, lastIndent+"Health:")
lines = append(lines, indent+lastIndent+"Server address: "+settings.ServerAddress)
lines = append(lines, indent+lastIndent+"OpenVPN:") lines = append(lines, indent+lastIndent+"OpenVPN:")
for _, line := range settings.OpenVPN.lines() { for _, line := range settings.OpenVPN.lines() {
lines = append(lines, indent+indent+line) lines = append(lines, indent+indent+line)
@@ -26,7 +31,23 @@ func (settings *Health) lines() (lines []string) {
return lines return lines
} }
// Read is to be used for the healthcheck query mode.
func (settings *Health) Read(env params.Env, os os.OS, logger logging.Logger) (err error) {
reader := newReader(env, os, logger)
return settings.read(reader)
}
func (settings *Health) read(r reader) (err error) { func (settings *Health) read(r reader) (err error) {
var warning string
settings.ServerAddress, warning, err = r.env.ListeningAddress(
"HEALTH_SERVER_ADDRESS", params.Default("127.0.0.1:9999"))
if warning != "" {
r.logger.Warn("health server address: " + warning)
}
if err != nil {
return err
}
settings.OpenVPN.Initial, err = r.env.Duration("HEALTH_OPENVPN_DURATION_INITIAL", params.Default("6s")) settings.OpenVPN.Initial, err = r.env.Duration("HEALTH_OPENVPN_DURATION_INITIAL", params.Default("6s"))
if err != nil { if err != nil {
return err return err

View File

@@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/qdm12/golibs/logging/mock_logging"
"github.com/qdm12/golibs/params/mock_params" "github.com/qdm12/golibs/params/mock_params"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -15,7 +16,7 @@ func Test_Health_String(t *testing.T) {
t.Parallel() t.Parallel()
var health Health var health Health
const expected = "|--Health:\n |--OpenVPN:\n |--Initial duration: 0s" const expected = "|--Health:\n |--Server address: \n |--OpenVPN:\n |--Initial duration: 0s"
s := health.String() s := health.String()
@@ -32,12 +33,14 @@ func Test_Health_lines(t *testing.T) {
"empty": { "empty": {
lines: []string{ lines: []string{
"|--Health:", "|--Health:",
" |--Server address: ",
" |--OpenVPN:", " |--OpenVPN:",
" |--Initial duration: 0s", " |--Initial duration: 0s",
}, },
}, },
"filled settings": { "filled settings": {
settings: Health{ settings: Health{
ServerAddress: "address:9999",
OpenVPN: HealthyWait{ OpenVPN: HealthyWait{
Initial: time.Second, Initial: time.Second,
Addition: time.Minute, Addition: time.Minute,
@@ -45,6 +48,7 @@ func Test_Health_lines(t *testing.T) {
}, },
lines: []string{ lines: []string{
"|--Health:", "|--Health:",
" |--Server address: address:9999",
" |--OpenVPN:", " |--OpenVPN:",
" |--Initial duration: 1s", " |--Initial duration: 1s",
" |--Addition duration: 1m0s", " |--Addition duration: 1m0s",
@@ -74,19 +78,35 @@ func Test_Health_read(t *testing.T) {
openvpnInitialErr error openvpnInitialErr error
openvpnAdditionDuration time.Duration openvpnAdditionDuration time.Duration
openvpnAdditionErr error openvpnAdditionErr error
serverAddress string
serverAddressWarning string
serverAddressErr error
expected Health expected Health
err error err error
}{ }{
"success": { "success": {
openvpnInitialDuration: time.Second, openvpnInitialDuration: time.Second,
openvpnAdditionDuration: time.Minute, openvpnAdditionDuration: time.Minute,
serverAddress: "127.0.0.1:9999",
expected: Health{ expected: Health{
ServerAddress: "127.0.0.1:9999",
OpenVPN: HealthyWait{ OpenVPN: HealthyWait{
Initial: time.Second, Initial: time.Second,
Addition: time.Minute, Addition: time.Minute,
}, },
}, },
}, },
"listening address error": {
openvpnInitialDuration: time.Second,
openvpnAdditionDuration: time.Minute,
serverAddress: "127.0.0.1:9999",
serverAddressWarning: "warning",
serverAddressErr: errDummy,
expected: Health{
ServerAddress: "127.0.0.1:9999",
},
err: errDummy,
},
"initial error": { "initial error": {
openvpnInitialDuration: time.Second, openvpnInitialDuration: time.Second,
openvpnInitialErr: errDummy, openvpnInitialErr: errDummy,
@@ -120,6 +140,16 @@ func Test_Health_read(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
env := mock_params.NewMockEnv(ctrl) env := mock_params.NewMockEnv(ctrl)
logger := mock_logging.NewMockLogger(ctrl)
env.EXPECT().ListeningAddress("HEALTH_SERVER_ADDRESS", gomock.Any()).
Return(testCase.serverAddress, testCase.serverAddressWarning,
testCase.serverAddressErr)
if testCase.serverAddressWarning != "" {
logger.EXPECT().Warn("health server address: " + testCase.serverAddressWarning)
}
if testCase.serverAddressErr == nil {
env.EXPECT(). env.EXPECT().
Duration("HEALTH_OPENVPN_DURATION_INITIAL", gomock.Any()). Duration("HEALTH_OPENVPN_DURATION_INITIAL", gomock.Any()).
Return(testCase.openvpnInitialDuration, testCase.openvpnInitialErr) Return(testCase.openvpnInitialDuration, testCase.openvpnInitialErr)
@@ -128,9 +158,11 @@ func Test_Health_read(t *testing.T) {
Duration("HEALTH_OPENVPN_DURATION_ADDITION", gomock.Any()). Duration("HEALTH_OPENVPN_DURATION_ADDITION", gomock.Any()).
Return(testCase.openvpnAdditionDuration, testCase.openvpnAdditionErr) Return(testCase.openvpnAdditionDuration, testCase.openvpnAdditionErr)
} }
}
r := reader{ r := reader{
env: env, env: env,
logger: logger,
} }
var health Health var health Health

View File

@@ -38,6 +38,7 @@ func Test_Settings_lines(t *testing.T) {
" |--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:", "|--Health:",
" |--Server address: ",
" |--OpenVPN:", " |--OpenVPN:",
" |--Initial duration: 0s", " |--Initial duration: 0s",
"|--HTTP control server:", "|--HTTP control server:",

View File

@@ -1,6 +0,0 @@
package constants
const (
// HealthcheckAddress is the default listening address for the healthcheck server.
HealthcheckAddress = "127.0.0.1:9999"
)

View File

@@ -17,7 +17,6 @@ type Server interface {
} }
type server struct { type server struct {
address string
logger logging.Logger logger logging.Logger
handler *handler handler *handler
resolver *net.Resolver resolver *net.Resolver
@@ -31,10 +30,9 @@ type openvpnHealth struct {
healthyTimer *time.Timer healthyTimer *time.Timer
} }
func NewServer(address string, config configuration.Health, func NewServer(config configuration.Health,
logger logging.Logger, openvpnLooper openvpn.Looper) Server { logger logging.Logger, openvpnLooper openvpn.Looper) Server {
return &server{ return &server{
address: address,
logger: logger, logger: logger,
handler: newHandler(logger), handler: newHandler(logger),
resolver: net.DefaultResolver, resolver: net.DefaultResolver,
@@ -53,7 +51,7 @@ func (s *server) Run(ctx context.Context, done chan<- struct{}) {
go s.runHealthcheckLoop(ctx, loopDone) go s.runHealthcheckLoop(ctx, loopDone)
server := http.Server{ server := http.Server{
Addr: s.address, Addr: s.config.ServerAddress,
Handler: s.handler, Handler: s.handler,
} }
serverDone := make(chan struct{}) serverDone := make(chan struct{})
@@ -68,7 +66,7 @@ func (s *server) Run(ctx context.Context, done chan<- struct{}) {
} }
}() }()
s.logger.Info("listening on %s", s.address) s.logger.Info("listening on " + s.config.ServerAddress)
err := server.ListenAndServe() err := server.ListenAndServe()
if err != nil && !errors.Is(ctx.Err(), context.Canceled) { if err != nil && !errors.Is(ctx.Err(), context.Canceled) {
s.logger.Error(err) s.logger.Error(err)