2014-01-21 02:44:23 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
2016-11-03 20:17:30 +00:00
|
|
|
"crypto/tls"
|
2014-01-30 21:39:02 +00:00
|
|
|
"fmt"
|
2016-04-14 21:28:07 +00:00
|
|
|
"io"
|
2014-01-21 02:44:23 +00:00
|
|
|
"log"
|
2015-07-23 11:45:08 +00:00
|
|
|
"net"
|
2015-01-09 22:43:24 +00:00
|
|
|
"net/http"
|
2015-10-22 22:29:13 +00:00
|
|
|
"os"
|
2014-01-21 02:44:23 +00:00
|
|
|
"os/exec"
|
|
|
|
"sync"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
2015-01-13 20:18:18 +00:00
|
|
|
|
|
|
|
"github.com/armon/circbuf"
|
2015-10-22 22:29:13 +00:00
|
|
|
docker "github.com/fsouza/go-dockerclient"
|
pkg refactor
command/agent/* -> agent/*
command/consul/* -> agent/consul/*
command/agent/command{,_test}.go -> command/agent{,_test}.go
command/base/command.go -> command/base.go
command/base/* -> command/*
commands.go -> command/commands.go
The script which did the refactor is:
(
cd $GOPATH/src/github.com/hashicorp/consul
git mv command/agent/command.go command/agent.go
git mv command/agent/command_test.go command/agent_test.go
git mv command/agent/flag_slice_value{,_test}.go command/
git mv command/agent .
git mv command/base/command.go command/base.go
git mv command/base/config_util{,_test}.go command/
git mv commands.go command/
git mv consul agent
rmdir command/base/
gsed -i -e 's|package agent|package command|' command/agent{,_test}.go
gsed -i -e 's|package agent|package command|' command/flag_slice_value{,_test}.go
gsed -i -e 's|package base|package command|' command/base.go command/config_util{,_test}.go
gsed -i -e 's|package main|package command|' command/commands.go
gsed -i -e 's|base.Command|BaseCommand|' command/commands.go
gsed -i -e 's|agent.Command|AgentCommand|' command/commands.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/commands.go
gsed -i -e 's|base\.||' command/commands.go
gsed -i -e 's|command\.||' command/commands.go
gsed -i -e 's|command|c|' main.go
gsed -i -e 's|range Commands|range command.Commands|' main.go
gsed -i -e 's|Commands: Commands|Commands: command.Commands|' main.go
gsed -i -e 's|base\.BoolValue|BoolValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.DurationValue|DurationValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.StringValue|StringValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.UintValue|UintValue|' command/operator_autopilot_set.go
gsed -i -e 's|\bCommand\b|BaseCommand|' command/base.go
gsed -i -e 's|BaseCommand Options|Command Options|' command/base.go
gsed -i -e 's|base.Command|BaseCommand|' command/*.go
gsed -i -e 's|c\.Command|c.BaseCommand|g' command/*.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/*_test.go
gsed -i -e 's|base\.||' command/*_test.go
gsed -i -e 's|\bCommand\b|AgentCommand|' command/agent{,_test}.go
gsed -i -e 's|cmd.AgentCommand|cmd.BaseCommand|' command/agent.go
gsed -i -e 's|cli.AgentCommand = new(Command)|cli.Command = new(AgentCommand)|' command/agent_test.go
gsed -i -e 's|exec.AgentCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|exec.BaseCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|NewTestAgent|agent.NewTestAgent|' command/agent_test.go
gsed -i -e 's|= TestConfig|= agent.TestConfig|' command/agent_test.go
gsed -i -e 's|: RetryJoin|: agent.RetryJoin|' command/agent_test.go
gsed -i -e 's|\.\./\.\./|../|' command/config_util_test.go
gsed -i -e 's|\bverifyUniqueListeners|VerifyUniqueListeners|' agent/config{,_test}.go command/agent.go
gsed -i -e 's|\bserfLANKeyring\b|SerfLANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bserfWANKeyring\b|SerfWANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bNewAgent\b|agent.New|g' command/agent{,_test}.go
gsed -i -e 's|\bNewAgent|New|' agent/{acl_test,agent,testagent}.go
gsed -i -e 's|\bAgent\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bBool\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDefaultConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDevConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bMergeConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bReadConfigPaths\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bParseMetaPair\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfLANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfWANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|circonus\.agent|circonus|g' command/agent{,_test}.go
gsed -i -e 's|logger\.agent|logger|g' command/agent{,_test}.go
gsed -i -e 's|metrics\.agent|metrics|g' command/agent{,_test}.go
gsed -i -e 's|// agent.Agent|// agent|' command/agent{,_test}.go
gsed -i -e 's|a\.agent\.Config|a.Config|' command/agent{,_test}.go
gsed -i -e 's|agent\.AppendSliceValue|AppendSliceValue|' command/{configtest,validate}.go
gsed -i -e 's|consul/consul|agent/consul|' GNUmakefile
gsed -i -e 's|\.\./test|../../test|' agent/consul/server_test.go
# fix imports
f=$(grep -rl 'github.com/hashicorp/consul/command/agent' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/command/agent|github.com/hashicorp/consul/agent|' $f
goimports -w $f
f=$(grep -rl 'github.com/hashicorp/consul/consul' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/consul|github.com/hashicorp/consul/agent/consul|' $f
goimports -w $f
goimports -w command/*.go main.go
)
2017-06-09 22:28:28 +00:00
|
|
|
"github.com/hashicorp/consul/agent/consul/structs"
|
2017-04-19 23:00:11 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
2016-01-29 19:42:34 +00:00
|
|
|
"github.com/hashicorp/consul/lib"
|
2016-06-06 20:19:31 +00:00
|
|
|
"github.com/hashicorp/consul/types"
|
2015-10-24 00:14:35 +00:00
|
|
|
"github.com/hashicorp/go-cleanhttp"
|
2014-01-21 02:44:23 +00:00
|
|
|
)
|
|
|
|
|
2014-04-21 21:42:42 +00:00
|
|
|
const (
|
2017-04-21 03:14:10 +00:00
|
|
|
// MinInterval is the minimal interval between
|
|
|
|
// two checks. Do not allow for a interval below this value.
|
2014-04-21 21:42:42 +00:00
|
|
|
// Otherwise we risk fork bombing a system.
|
|
|
|
MinInterval = time.Second
|
2014-04-29 22:28:56 +00:00
|
|
|
|
2017-04-21 03:14:10 +00:00
|
|
|
// CheckBufSize is the maximum size of the captured
|
|
|
|
// check output. Prevents an enormous buffer
|
2014-04-29 22:28:56 +00:00
|
|
|
// from being captured
|
|
|
|
CheckBufSize = 4 * 1024 // 4KB
|
2015-05-18 17:12:10 +00:00
|
|
|
|
2017-04-21 03:14:10 +00:00
|
|
|
// UserAgent is the value of the User-Agent header
|
|
|
|
// for HTTP health checks.
|
2017-04-21 00:02:42 +00:00
|
|
|
UserAgent = "Consul Health Check"
|
2014-04-21 21:42:42 +00:00
|
|
|
)
|
|
|
|
|
2014-01-21 02:44:23 +00:00
|
|
|
// CheckNotifier interface is used by the CheckMonitor
|
|
|
|
// to notify when a check has a status update. The update
|
|
|
|
// should take care to be idempotent.
|
|
|
|
type CheckNotifier interface {
|
2016-06-06 20:19:31 +00:00
|
|
|
UpdateCheck(checkID types.CheckID, status, output string)
|
2014-01-21 02:44:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CheckMonitor is used to periodically invoke a script to
|
|
|
|
// determine the health of a given check. It is compatible with
|
|
|
|
// nagios plugins and expects the output in the same format.
|
|
|
|
type CheckMonitor struct {
|
|
|
|
Notify CheckNotifier
|
2016-06-06 20:19:31 +00:00
|
|
|
CheckID types.CheckID
|
2014-01-21 02:44:23 +00:00
|
|
|
Script string
|
|
|
|
Interval time.Duration
|
2016-02-26 03:18:20 +00:00
|
|
|
Timeout time.Duration
|
2014-01-21 02:44:23 +00:00
|
|
|
Logger *log.Logger
|
|
|
|
|
|
|
|
stop bool
|
|
|
|
stopCh chan struct{}
|
|
|
|
stopLock sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start is used to start a check monitor.
|
|
|
|
// Monitor runs until stop is called
|
|
|
|
func (c *CheckMonitor) Start() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
c.stop = false
|
|
|
|
c.stopCh = make(chan struct{})
|
|
|
|
go c.run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop is used to stop a check monitor.
|
|
|
|
func (c *CheckMonitor) Stop() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
if !c.stop {
|
|
|
|
c.stop = true
|
|
|
|
close(c.stopCh)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run is invoked by a goroutine to run until Stop() is called
|
|
|
|
func (c *CheckMonitor) run() {
|
2014-12-18 02:44:12 +00:00
|
|
|
// Get the randomized initial pause time
|
2016-01-29 19:42:34 +00:00
|
|
|
initialPauseTime := lib.RandomStagger(c.Interval)
|
2014-12-18 14:00:51 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: pausing %v before first invocation of %s", initialPauseTime, c.Script)
|
2014-12-18 02:44:12 +00:00
|
|
|
next := time.After(initialPauseTime)
|
2014-01-21 02:46:01 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-next:
|
|
|
|
c.check()
|
|
|
|
next = time.After(c.Interval)
|
|
|
|
case <-c.stopCh:
|
|
|
|
return
|
|
|
|
}
|
2014-01-21 02:44:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check is invoked periodically to perform the script check
|
|
|
|
func (c *CheckMonitor) check() {
|
|
|
|
// Create the command
|
2014-08-21 21:28:16 +00:00
|
|
|
cmd, err := ExecScript(c.Script)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Printf("[ERR] agent: failed to setup invoke '%s': %s", c.Script, err)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, err.Error())
|
2014-08-21 21:28:16 +00:00
|
|
|
return
|
|
|
|
}
|
2014-01-21 02:44:23 +00:00
|
|
|
|
|
|
|
// Collect the output
|
2014-04-29 22:28:56 +00:00
|
|
|
output, _ := circbuf.NewBuffer(CheckBufSize)
|
|
|
|
cmd.Stdout = output
|
|
|
|
cmd.Stderr = output
|
2014-01-21 02:44:23 +00:00
|
|
|
|
|
|
|
// Start the check
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
c.Logger.Printf("[ERR] agent: failed to invoke '%s': %s", c.Script, err)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, err.Error())
|
2014-01-21 02:44:23 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the check to complete
|
2014-01-30 21:39:02 +00:00
|
|
|
errCh := make(chan error, 2)
|
|
|
|
go func() {
|
|
|
|
errCh <- cmd.Wait()
|
|
|
|
}()
|
|
|
|
go func() {
|
2016-02-26 03:18:20 +00:00
|
|
|
if c.Timeout > 0 {
|
|
|
|
time.Sleep(c.Timeout)
|
|
|
|
} else {
|
|
|
|
time.Sleep(30 * time.Second)
|
|
|
|
}
|
2014-01-30 21:39:02 +00:00
|
|
|
errCh <- fmt.Errorf("Timed out running check '%s'", c.Script)
|
|
|
|
}()
|
2014-08-21 21:28:16 +00:00
|
|
|
err = <-errCh
|
2014-01-30 21:39:02 +00:00
|
|
|
|
2014-04-29 22:28:56 +00:00
|
|
|
// Get the output, add a message about truncation
|
2014-04-21 23:20:22 +00:00
|
|
|
outputStr := string(output.Bytes())
|
2014-04-29 22:28:56 +00:00
|
|
|
if output.TotalWritten() > output.Size() {
|
|
|
|
outputStr = fmt.Sprintf("Captured %d of %d bytes\n...\n%s",
|
|
|
|
output.Size(), output.TotalWritten(), outputStr)
|
|
|
|
}
|
|
|
|
|
2017-01-05 08:28:25 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: Check '%s' script '%s' output: %s",
|
2014-04-21 23:20:22 +00:00
|
|
|
c.CheckID, c.Script, outputStr)
|
2014-01-21 02:44:23 +00:00
|
|
|
|
|
|
|
// Check if the check passed
|
|
|
|
if err == nil {
|
2015-01-13 20:18:18 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: Check '%v' is passing", c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthPassing, outputStr)
|
2014-01-21 02:44:23 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the exit code is 1, set check as warning
|
|
|
|
exitErr, ok := err.(*exec.ExitError)
|
|
|
|
if ok {
|
|
|
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
|
|
code := status.ExitStatus()
|
|
|
|
if code == 1 {
|
2015-01-13 20:18:18 +00:00
|
|
|
c.Logger.Printf("[WARN] agent: Check '%v' is now warning", c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthWarning, outputStr)
|
2014-01-21 02:44:23 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the health as critical
|
2015-01-13 20:18:18 +00:00
|
|
|
c.Logger.Printf("[WARN] agent: Check '%v' is now critical", c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, outputStr)
|
2014-01-21 02:44:23 +00:00
|
|
|
}
|
2014-01-21 03:12:40 +00:00
|
|
|
|
|
|
|
// CheckTTL is used to apply a TTL to check status,
|
|
|
|
// and enables clients to set the status of a check
|
|
|
|
// but upon the TTL expiring, the check status is
|
|
|
|
// automatically set to critical.
|
|
|
|
type CheckTTL struct {
|
|
|
|
Notify CheckNotifier
|
2016-06-06 20:19:31 +00:00
|
|
|
CheckID types.CheckID
|
2014-01-21 03:12:40 +00:00
|
|
|
TTL time.Duration
|
|
|
|
Logger *log.Logger
|
|
|
|
|
|
|
|
timer *time.Timer
|
|
|
|
|
2016-03-03 01:58:01 +00:00
|
|
|
lastOutput string
|
|
|
|
lastOutputLock sync.RWMutex
|
|
|
|
|
2014-01-21 03:12:40 +00:00
|
|
|
stop bool
|
|
|
|
stopCh chan struct{}
|
|
|
|
stopLock sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start is used to start a check ttl, runs until Stop()
|
|
|
|
func (c *CheckTTL) Start() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
c.stop = false
|
|
|
|
c.stopCh = make(chan struct{})
|
|
|
|
c.timer = time.NewTimer(c.TTL)
|
|
|
|
go c.run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop is used to stop a check ttl.
|
|
|
|
func (c *CheckTTL) Stop() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
if !c.stop {
|
|
|
|
c.timer.Stop()
|
|
|
|
c.stop = true
|
|
|
|
close(c.stopCh)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run is used to handle TTL expiration and to update the check status
|
|
|
|
func (c *CheckTTL) run() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-c.timer.C:
|
2015-01-13 20:18:18 +00:00
|
|
|
c.Logger.Printf("[WARN] agent: Check '%v' missed TTL, is now critical",
|
2014-01-21 03:12:40 +00:00
|
|
|
c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, c.getExpiredOutput())
|
2014-01-21 03:12:40 +00:00
|
|
|
|
|
|
|
case <-c.stopCh:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-03 01:58:01 +00:00
|
|
|
// getExpiredOutput formats the output for the case when the TTL is expired.
|
|
|
|
func (c *CheckTTL) getExpiredOutput() string {
|
|
|
|
c.lastOutputLock.RLock()
|
|
|
|
defer c.lastOutputLock.RUnlock()
|
|
|
|
|
|
|
|
const prefix = "TTL expired"
|
|
|
|
if c.lastOutput == "" {
|
2016-03-03 03:47:00 +00:00
|
|
|
return prefix
|
2016-03-03 01:58:01 +00:00
|
|
|
}
|
|
|
|
|
2016-03-03 03:47:00 +00:00
|
|
|
return fmt.Sprintf("%s (last output before timeout follows): %s", prefix, c.lastOutput)
|
2016-03-03 01:58:01 +00:00
|
|
|
}
|
|
|
|
|
2014-01-21 03:12:40 +00:00
|
|
|
// SetStatus is used to update the status of the check,
|
|
|
|
// and to renew the TTL. If expired, TTL is restarted.
|
2014-04-21 23:20:22 +00:00
|
|
|
func (c *CheckTTL) SetStatus(status, output string) {
|
2015-01-13 20:18:18 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: Check '%v' status is now %v",
|
2014-01-21 03:12:40 +00:00
|
|
|
c.CheckID, status)
|
2014-04-21 23:20:22 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, status, output)
|
2016-03-03 01:58:01 +00:00
|
|
|
|
|
|
|
// Store the last output so we can retain it if the TTL expires.
|
|
|
|
c.lastOutputLock.Lock()
|
|
|
|
c.lastOutput = output
|
|
|
|
c.lastOutputLock.Unlock()
|
|
|
|
|
2014-01-21 03:12:40 +00:00
|
|
|
c.timer.Reset(c.TTL)
|
|
|
|
}
|
2014-11-29 20:25:01 +00:00
|
|
|
|
|
|
|
// persistedCheck is used to serialize a check and write it to disk
|
|
|
|
// so that it may be restored later on.
|
|
|
|
type persistedCheck struct {
|
|
|
|
Check *structs.HealthCheck
|
2017-06-15 16:46:06 +00:00
|
|
|
ChkType *structs.CheckType
|
2015-04-28 02:01:02 +00:00
|
|
|
Token string
|
2014-11-29 20:25:01 +00:00
|
|
|
}
|
2015-01-09 22:43:24 +00:00
|
|
|
|
2015-06-05 23:17:07 +00:00
|
|
|
// persistedCheckState is used to persist the current state of a given
|
|
|
|
// check. This is different from the check definition, and includes an
|
|
|
|
// expiration timestamp which is used to determine staleness on later
|
|
|
|
// agent restarts.
|
|
|
|
type persistedCheckState struct {
|
2016-06-06 20:19:31 +00:00
|
|
|
CheckID types.CheckID
|
2015-06-05 23:17:07 +00:00
|
|
|
Output string
|
|
|
|
Status string
|
|
|
|
Expires int64
|
|
|
|
}
|
|
|
|
|
2015-01-09 22:43:24 +00:00
|
|
|
// CheckHTTP is used to periodically make an HTTP request to
|
|
|
|
// determine the health of a given check.
|
2015-01-13 20:18:18 +00:00
|
|
|
// The check is passing if the response code is 2XX.
|
|
|
|
// The check is warning if the response code is 429.
|
2015-01-09 22:43:24 +00:00
|
|
|
// The check is critical if the response code is anything else
|
|
|
|
// or if the request returns an error
|
|
|
|
type CheckHTTP struct {
|
2016-11-03 20:17:30 +00:00
|
|
|
Notify CheckNotifier
|
|
|
|
CheckID types.CheckID
|
|
|
|
HTTP string
|
2017-06-06 23:11:56 +00:00
|
|
|
Header map[string][]string
|
|
|
|
Method string
|
2016-11-03 20:17:30 +00:00
|
|
|
Interval time.Duration
|
|
|
|
Timeout time.Duration
|
|
|
|
Logger *log.Logger
|
|
|
|
TLSSkipVerify bool
|
2015-01-09 22:43:24 +00:00
|
|
|
|
|
|
|
httpClient *http.Client
|
|
|
|
stop bool
|
|
|
|
stopCh chan struct{}
|
|
|
|
stopLock sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start is used to start an HTTP check.
|
|
|
|
// The check runs until stop is called
|
|
|
|
func (c *CheckHTTP) Start() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
2015-01-12 23:58:25 +00:00
|
|
|
|
|
|
|
if c.httpClient == nil {
|
2015-03-15 20:30:50 +00:00
|
|
|
// Create the transport. We disable HTTP Keep-Alive's to prevent
|
|
|
|
// failing checks due to the keepalive interval.
|
2015-10-22 14:47:50 +00:00
|
|
|
trans := cleanhttp.DefaultTransport()
|
2015-03-15 20:30:50 +00:00
|
|
|
trans.DisableKeepAlives = true
|
|
|
|
|
2016-11-03 20:17:30 +00:00
|
|
|
// Skip SSL certificate verification if TLSSkipVerify is true
|
|
|
|
if trans.TLSClientConfig == nil {
|
|
|
|
trans.TLSClientConfig = &tls.Config{
|
|
|
|
InsecureSkipVerify: c.TLSSkipVerify,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
trans.TLSClientConfig.InsecureSkipVerify = c.TLSSkipVerify
|
|
|
|
}
|
|
|
|
|
2015-03-15 20:30:50 +00:00
|
|
|
// Create the HTTP client.
|
|
|
|
c.httpClient = &http.Client{
|
|
|
|
Timeout: 10 * time.Second,
|
2015-10-22 14:47:50 +00:00
|
|
|
Transport: trans,
|
2015-03-15 20:30:50 +00:00
|
|
|
}
|
|
|
|
|
2015-01-12 23:58:25 +00:00
|
|
|
// For long (>10s) interval checks the http timeout is 10s, otherwise the
|
|
|
|
// timeout is the interval. This means that a check *should* return
|
|
|
|
// before the next check begins.
|
2015-01-29 06:37:48 +00:00
|
|
|
if c.Timeout > 0 && c.Timeout < c.Interval {
|
2015-03-15 20:30:50 +00:00
|
|
|
c.httpClient.Timeout = c.Timeout
|
2015-01-29 06:37:48 +00:00
|
|
|
} else if c.Interval < 10*time.Second {
|
2015-03-15 20:30:50 +00:00
|
|
|
c.httpClient.Timeout = c.Interval
|
2015-01-12 23:58:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-09 22:43:24 +00:00
|
|
|
c.stop = false
|
|
|
|
c.stopCh = make(chan struct{})
|
|
|
|
go c.run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop is used to stop an HTTP check.
|
|
|
|
func (c *CheckHTTP) Stop() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
if !c.stop {
|
|
|
|
c.stop = true
|
|
|
|
close(c.stopCh)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run is invoked by a goroutine to run until Stop() is called
|
|
|
|
func (c *CheckHTTP) run() {
|
|
|
|
// Get the randomized initial pause time
|
2016-01-29 19:42:34 +00:00
|
|
|
initialPauseTime := lib.RandomStagger(c.Interval)
|
2015-01-09 22:43:24 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: pausing %v before first HTTP request of %s", initialPauseTime, c.HTTP)
|
|
|
|
next := time.After(initialPauseTime)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-next:
|
|
|
|
c.check()
|
|
|
|
next = time.After(c.Interval)
|
|
|
|
case <-c.stopCh:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check is invoked periodically to perform the HTTP check
|
|
|
|
func (c *CheckHTTP) check() {
|
2017-06-06 23:11:56 +00:00
|
|
|
method := c.Method
|
|
|
|
if method == "" {
|
|
|
|
method = "GET"
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest(method, c.HTTP, nil)
|
2015-05-18 17:12:10 +00:00
|
|
|
if err != nil {
|
|
|
|
c.Logger.Printf("[WARN] agent: http request failed '%s': %s", c.HTTP, err)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, err.Error())
|
2015-05-18 17:12:10 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-06 23:11:56 +00:00
|
|
|
req.Header = http.Header(c.Header)
|
|
|
|
|
|
|
|
// this happens during testing but not in prod
|
|
|
|
if req.Header == nil {
|
|
|
|
req.Header = make(http.Header)
|
|
|
|
}
|
|
|
|
|
2017-06-29 23:26:08 +00:00
|
|
|
if host := req.Header.Get("Host"); host != "" {
|
|
|
|
req.Host = host
|
|
|
|
}
|
|
|
|
|
2017-06-06 23:11:56 +00:00
|
|
|
if req.Header.Get("User-Agent") == "" {
|
|
|
|
req.Header.Set("User-Agent", UserAgent)
|
|
|
|
}
|
|
|
|
if req.Header.Get("Accept") == "" {
|
|
|
|
req.Header.Set("Accept", "text/plain, text/*, */*")
|
|
|
|
}
|
2015-05-18 17:12:10 +00:00
|
|
|
|
|
|
|
resp, err := c.httpClient.Do(req)
|
2015-01-09 22:43:24 +00:00
|
|
|
if err != nil {
|
|
|
|
c.Logger.Printf("[WARN] agent: http request failed '%s': %s", c.HTTP, err)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, err.Error())
|
2015-01-09 22:43:24 +00:00
|
|
|
return
|
|
|
|
}
|
2015-01-12 22:35:28 +00:00
|
|
|
defer resp.Body.Close()
|
2015-01-09 22:43:24 +00:00
|
|
|
|
2016-04-14 21:28:07 +00:00
|
|
|
// Read the response into a circular buffer to limit the size
|
|
|
|
output, _ := circbuf.NewBuffer(CheckBufSize)
|
|
|
|
if _, err := io.Copy(output, resp.Body); err != nil {
|
2017-01-05 08:28:25 +00:00
|
|
|
c.Logger.Printf("[WARN] agent: Check '%v': Get error while reading body: %s", c.CheckID, err)
|
2015-01-13 20:18:18 +00:00
|
|
|
}
|
2016-04-14 21:28:07 +00:00
|
|
|
|
|
|
|
// Format the response body
|
|
|
|
result := fmt.Sprintf("HTTP GET %s: %s Output: %s", c.HTTP, resp.Status, output.String())
|
2015-01-13 20:18:18 +00:00
|
|
|
|
2015-01-12 21:58:57 +00:00
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
|
|
|
|
// PASSING (2xx)
|
2017-01-05 08:28:25 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: Check '%v' is passing", c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthPassing, result)
|
2015-01-09 22:43:24 +00:00
|
|
|
|
2015-01-12 21:58:57 +00:00
|
|
|
} else if resp.StatusCode == 429 {
|
|
|
|
// WARNING
|
|
|
|
// 429 Too Many Requests (RFC 6585)
|
|
|
|
// The user has sent too many requests in a given amount of time.
|
2017-01-05 08:28:25 +00:00
|
|
|
c.Logger.Printf("[WARN] agent: Check '%v' is now warning", c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthWarning, result)
|
2015-01-09 22:43:24 +00:00
|
|
|
|
2015-01-12 21:58:57 +00:00
|
|
|
} else {
|
|
|
|
// CRITICAL
|
2017-01-05 08:28:25 +00:00
|
|
|
c.Logger.Printf("[WARN] agent: Check '%v' is now critical", c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, result)
|
2015-01-09 22:43:24 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-23 11:45:08 +00:00
|
|
|
|
|
|
|
// CheckTCP is used to periodically make an TCP/UDP connection to
|
|
|
|
// determine the health of a given check.
|
|
|
|
// The check is passing if the connection succeeds
|
|
|
|
// The check is critical if the connection returns an error
|
|
|
|
type CheckTCP struct {
|
|
|
|
Notify CheckNotifier
|
2016-06-06 20:19:31 +00:00
|
|
|
CheckID types.CheckID
|
2015-07-23 11:45:08 +00:00
|
|
|
TCP string
|
|
|
|
Interval time.Duration
|
|
|
|
Timeout time.Duration
|
|
|
|
Logger *log.Logger
|
|
|
|
|
|
|
|
dialer *net.Dialer
|
|
|
|
stop bool
|
|
|
|
stopCh chan struct{}
|
|
|
|
stopLock sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start is used to start a TCP check.
|
|
|
|
// The check runs until stop is called
|
|
|
|
func (c *CheckTCP) Start() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
|
|
|
|
if c.dialer == nil {
|
|
|
|
// Create the socket dialer
|
|
|
|
c.dialer = &net.Dialer{DualStack: true}
|
|
|
|
|
|
|
|
// For long (>10s) interval checks the socket timeout is 10s, otherwise
|
|
|
|
// the timeout is the interval. This means that a check *should* return
|
|
|
|
// before the next check begins.
|
|
|
|
if c.Timeout > 0 && c.Timeout < c.Interval {
|
|
|
|
c.dialer.Timeout = c.Timeout
|
|
|
|
} else if c.Interval < 10*time.Second {
|
|
|
|
c.dialer.Timeout = c.Interval
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.stop = false
|
|
|
|
c.stopCh = make(chan struct{})
|
|
|
|
go c.run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop is used to stop a TCP check.
|
|
|
|
func (c *CheckTCP) Stop() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
if !c.stop {
|
|
|
|
c.stop = true
|
|
|
|
close(c.stopCh)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run is invoked by a goroutine to run until Stop() is called
|
|
|
|
func (c *CheckTCP) run() {
|
|
|
|
// Get the randomized initial pause time
|
2016-01-29 19:42:34 +00:00
|
|
|
initialPauseTime := lib.RandomStagger(c.Interval)
|
2015-07-23 11:45:08 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: pausing %v before first socket connection of %s", initialPauseTime, c.TCP)
|
|
|
|
next := time.After(initialPauseTime)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-next:
|
|
|
|
c.check()
|
|
|
|
next = time.After(c.Interval)
|
|
|
|
case <-c.stopCh:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check is invoked periodically to perform the TCP check
|
|
|
|
func (c *CheckTCP) check() {
|
|
|
|
conn, err := c.dialer.Dial(`tcp`, c.TCP)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Printf("[WARN] agent: socket connection failed '%s': %s", c.TCP, err)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, err.Error())
|
2015-07-23 11:45:08 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
conn.Close()
|
2017-01-05 08:28:25 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: Check '%v' is passing", c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("TCP connect %s: Success", c.TCP))
|
2015-07-23 11:45:08 +00:00
|
|
|
}
|
2015-10-22 22:29:13 +00:00
|
|
|
|
2017-04-21 03:14:10 +00:00
|
|
|
// DockerClient defines an interface for a docker client
|
|
|
|
// which is used for injecting a fake client during tests.
|
2015-10-26 19:59:40 +00:00
|
|
|
type DockerClient interface {
|
|
|
|
CreateExec(docker.CreateExecOptions) (*docker.Exec, error)
|
|
|
|
StartExec(string, docker.StartExecOptions) error
|
|
|
|
InspectExec(string) (*docker.ExecInspect, error)
|
|
|
|
}
|
|
|
|
|
2015-10-22 22:29:13 +00:00
|
|
|
// CheckDocker is used to periodically invoke a script to
|
|
|
|
// determine the health of an application running inside a
|
|
|
|
// Docker Container. We assume that the script is compatible
|
|
|
|
// with nagios plugins and expects the output in the same format.
|
|
|
|
type CheckDocker struct {
|
|
|
|
Notify CheckNotifier
|
2016-06-06 20:19:31 +00:00
|
|
|
CheckID types.CheckID
|
2015-10-22 22:29:13 +00:00
|
|
|
Script string
|
2015-11-18 15:40:02 +00:00
|
|
|
DockerContainerID string
|
2015-10-22 22:29:13 +00:00
|
|
|
Shell string
|
|
|
|
Interval time.Duration
|
|
|
|
Logger *log.Logger
|
|
|
|
|
2015-10-26 19:59:40 +00:00
|
|
|
dockerClient DockerClient
|
2015-10-26 16:44:59 +00:00
|
|
|
cmd []string
|
|
|
|
stop bool
|
|
|
|
stopCh chan struct{}
|
|
|
|
stopLock sync.Mutex
|
2015-10-22 22:29:13 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 03:14:10 +00:00
|
|
|
// Init initializes the Docker Client
|
2015-10-26 23:45:12 +00:00
|
|
|
func (c *CheckDocker) Init() error {
|
|
|
|
var err error
|
|
|
|
c.dockerClient, err = docker.NewClientFromEnv()
|
|
|
|
if err != nil {
|
2015-10-29 19:45:48 +00:00
|
|
|
c.Logger.Printf("[DEBUG] Error creating the Docker client: %s", err.Error())
|
2015-10-26 23:45:12 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-10-22 22:29:13 +00:00
|
|
|
// Start is used to start checks.
|
|
|
|
// Docker Checks runs until stop is called
|
|
|
|
func (c *CheckDocker) Start() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
|
|
|
|
//figure out the shell
|
|
|
|
if c.Shell == "" {
|
2015-10-26 22:00:34 +00:00
|
|
|
c.Shell = shell()
|
2015-10-22 22:29:13 +00:00
|
|
|
}
|
|
|
|
|
2015-10-26 16:44:59 +00:00
|
|
|
c.cmd = []string{c.Shell, "-c", c.Script}
|
2015-10-22 22:29:13 +00:00
|
|
|
|
|
|
|
c.stop = false
|
|
|
|
c.stopCh = make(chan struct{})
|
|
|
|
go c.run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop is used to stop a docker check.
|
|
|
|
func (c *CheckDocker) Stop() {
|
|
|
|
c.stopLock.Lock()
|
|
|
|
defer c.stopLock.Unlock()
|
|
|
|
if !c.stop {
|
|
|
|
c.stop = true
|
|
|
|
close(c.stopCh)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run is invoked by a goroutine to run until Stop() is called
|
|
|
|
func (c *CheckDocker) run() {
|
|
|
|
// Get the randomized initial pause time
|
2016-01-29 19:42:34 +00:00
|
|
|
initialPauseTime := lib.RandomStagger(c.Interval)
|
2015-11-18 15:40:02 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: pausing %v before first invocation of %s -c %s in container %s", initialPauseTime, c.Shell, c.Script, c.DockerContainerID)
|
2015-10-22 22:29:13 +00:00
|
|
|
next := time.After(initialPauseTime)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-next:
|
|
|
|
c.check()
|
|
|
|
next = time.After(c.Interval)
|
|
|
|
case <-c.stopCh:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *CheckDocker) check() {
|
2015-10-26 16:44:59 +00:00
|
|
|
//Set up the Exec since
|
|
|
|
execOpts := docker.CreateExecOptions{
|
|
|
|
AttachStdin: false,
|
|
|
|
AttachStdout: true,
|
|
|
|
AttachStderr: true,
|
|
|
|
Tty: false,
|
|
|
|
Cmd: c.cmd,
|
2015-11-18 15:40:02 +00:00
|
|
|
Container: c.DockerContainerID,
|
2015-10-26 16:44:59 +00:00
|
|
|
}
|
|
|
|
var (
|
|
|
|
exec *docker.Exec
|
|
|
|
err error
|
|
|
|
)
|
2015-10-26 22:00:34 +00:00
|
|
|
if exec, err = c.dockerClient.CreateExec(execOpts); err != nil {
|
2015-10-26 16:44:59 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: Error while creating Exec: %s", err.Error())
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, fmt.Sprintf("Unable to create Exec, error: %s", err.Error()))
|
2015-10-26 18:16:11 +00:00
|
|
|
return
|
2015-10-26 16:44:59 +00:00
|
|
|
}
|
|
|
|
|
2015-10-26 22:19:35 +00:00
|
|
|
// Collect the output
|
|
|
|
output, _ := circbuf.NewBuffer(CheckBufSize)
|
|
|
|
|
|
|
|
err = c.dockerClient.StartExec(exec.ID, docker.StartExecOptions{Detach: false, Tty: false, OutputStream: output, ErrorStream: output})
|
2015-10-22 22:29:13 +00:00
|
|
|
if err != nil {
|
|
|
|
c.Logger.Printf("[DEBUG] Error in executing health checks: %s", err.Error())
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, fmt.Sprintf("Unable to start Exec: %s", err.Error()))
|
2015-10-22 22:29:13 +00:00
|
|
|
return
|
|
|
|
}
|
2015-10-26 16:44:59 +00:00
|
|
|
|
2015-10-26 22:19:35 +00:00
|
|
|
// Get the output, add a message about truncation
|
|
|
|
outputStr := string(output.Bytes())
|
|
|
|
if output.TotalWritten() > output.Size() {
|
|
|
|
outputStr = fmt.Sprintf("Captured %d of %d bytes\n...\n%s",
|
|
|
|
output.Size(), output.TotalWritten(), outputStr)
|
|
|
|
}
|
|
|
|
|
2017-01-05 08:28:25 +00:00
|
|
|
c.Logger.Printf("[DEBUG] agent: Check '%s' script '%s' output: %s",
|
2015-10-26 22:19:35 +00:00
|
|
|
c.CheckID, c.Script, outputStr)
|
|
|
|
|
2015-10-26 16:44:59 +00:00
|
|
|
execInfo, err := c.dockerClient.InspectExec(exec.ID)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Printf("[DEBUG] Error in inspecting check result : %s", err.Error())
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, fmt.Sprintf("Unable to inspect Exec: %s", err.Error()))
|
2015-10-26 16:44:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-10-26 22:19:35 +00:00
|
|
|
// Sets the status of the check to healthy if exit code is 0
|
2015-10-26 16:44:59 +00:00
|
|
|
if execInfo.ExitCode == 0 {
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthPassing, outputStr)
|
2015-10-26 22:19:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the status of the check to Warning if exit code is 1
|
|
|
|
if execInfo.ExitCode == 1 {
|
2015-10-26 17:35:51 +00:00
|
|
|
c.Logger.Printf("[DEBUG] Check failed with exit code: %d", execInfo.ExitCode)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthWarning, outputStr)
|
2015-10-26 22:19:35 +00:00
|
|
|
return
|
2015-10-26 16:44:59 +00:00
|
|
|
}
|
|
|
|
|
2015-10-26 22:19:35 +00:00
|
|
|
// Set the health as critical
|
|
|
|
c.Logger.Printf("[WARN] agent: Check '%v' is now critical", c.CheckID)
|
2017-04-19 23:00:11 +00:00
|
|
|
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, outputStr)
|
2015-10-22 22:29:13 +00:00
|
|
|
}
|
2015-10-26 22:00:34 +00:00
|
|
|
|
|
|
|
func shell() string {
|
2017-04-21 01:59:42 +00:00
|
|
|
if sh := os.Getenv("SHELL"); sh != "" {
|
|
|
|
return sh
|
2015-10-26 22:00:34 +00:00
|
|
|
}
|
2017-04-21 01:59:42 +00:00
|
|
|
return "/bin/sh"
|
2015-10-26 22:00:34 +00:00
|
|
|
}
|