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_OPENVPN_DURATION_INITIAL=6s \
HEALTH_OPENVPN_DURATION_ADDITION=5s \
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
# DNS over TLS
DOT=on \
DOT_PROVIDERS=cloudflare \

View File

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

View File

@@ -6,11 +6,12 @@ import (
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/params"
)
type CLI interface {
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
Update(ctx context.Context, args []string, os os.OS, logger logging.Logger) error
}

View File

@@ -2,19 +2,36 @@ package cli
import (
"context"
"net"
"net/http"
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/configuration"
"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
httpClient := &http.Client{Timeout: timeout}
healthchecker := healthcheck.NewChecker(httpClient)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
const url = "http://" + constants.HealthcheckAddress
url := "http://127.0.0.1:" + port
return healthchecker.Check(ctx, url)
}

View File

@@ -3,11 +3,14 @@ package configuration
import (
"strings"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/params"
)
// Health contains settings for the healthcheck and health server.
type Health struct {
ServerAddress string
OpenVPN HealthyWait
}
@@ -18,6 +21,8 @@ func (settings *Health) String() string {
func (settings *Health) lines() (lines []string) {
lines = append(lines, lastIndent+"Health:")
lines = append(lines, indent+lastIndent+"Server address: "+settings.ServerAddress)
lines = append(lines, indent+lastIndent+"OpenVPN:")
for _, line := range settings.OpenVPN.lines() {
lines = append(lines, indent+indent+line)
@@ -26,7 +31,23 @@ func (settings *Health) lines() (lines []string) {
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) {
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"))
if err != nil {
return err

View File

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

View File

@@ -38,6 +38,7 @@ func Test_Settings_lines(t *testing.T) {
" |--Process group ID: 0",
" |--Timezone: NOT SET ⚠️ - it can cause time related issues",
"|--Health:",
" |--Server address: ",
" |--OpenVPN:",
" |--Initial duration: 0s",
"|--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 {
address string
logger logging.Logger
handler *handler
resolver *net.Resolver
@@ -31,10 +30,9 @@ type openvpnHealth struct {
healthyTimer *time.Timer
}
func NewServer(address string, config configuration.Health,
func NewServer(config configuration.Health,
logger logging.Logger, openvpnLooper openvpn.Looper) Server {
return &server{
address: address,
logger: logger,
handler: newHandler(logger),
resolver: net.DefaultResolver,
@@ -53,7 +51,7 @@ func (s *server) Run(ctx context.Context, done chan<- struct{}) {
go s.runHealthcheckLoop(ctx, loopDone)
server := http.Server{
Addr: s.address,
Addr: s.config.ServerAddress,
Handler: s.handler,
}
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()
if err != nil && !errors.Is(ctx.Err(), context.Canceled) {
s.logger.Error(err)