2016-11-26 19:48:49 +01:00
package healthcheck
import (
2016-11-30 22:49:57 +01:00
"context"
2016-11-26 19:48:49 +01:00
"net/http"
"net/url"
"sync"
"time"
2017-01-31 22:55:02 +01:00
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/vulcand/oxy/roundrobin"
2016-11-26 19:48:49 +01:00
)
var singleton * HealthCheck
var once sync . Once
// GetHealthCheck Get HealtchCheck Singleton
func GetHealthCheck ( ) * HealthCheck {
once . Do ( func ( ) {
singleton = newHealthCheck ( )
} )
return singleton
}
// BackendHealthCheck HealthCheck configuration for a backend
type BackendHealthCheck struct {
2017-04-11 17:10:46 +02:00
Path string
Interval time . Duration
DisabledURLs [ ] * url . URL
2017-03-09 16:27:31 +01:00
requestTimeout time . Duration
2017-04-11 17:10:46 +02:00
lb loadBalancer
2016-11-26 19:48:49 +01:00
}
var launch = false
//HealthCheck struct
type HealthCheck struct {
Backends map [ string ] * BackendHealthCheck
2016-11-30 22:49:57 +01:00
cancel context . CancelFunc
2016-11-26 19:48:49 +01:00
}
type loadBalancer interface {
RemoveServer ( u * url . URL ) error
UpsertServer ( u * url . URL , options ... roundrobin . ServerOption ) error
Servers ( ) [ ] * url . URL
}
func newHealthCheck ( ) * HealthCheck {
2017-03-09 16:27:31 +01:00
return & HealthCheck {
Backends : make ( map [ string ] * BackendHealthCheck ) ,
}
2016-11-26 19:48:49 +01:00
}
// NewBackendHealthCheck Instantiate a new BackendHealthCheck
2017-04-11 17:10:46 +02:00
func NewBackendHealthCheck ( Path string , interval time . Duration , lb loadBalancer ) * BackendHealthCheck {
2017-03-09 16:27:31 +01:00
return & BackendHealthCheck {
2017-04-11 17:10:46 +02:00
Path : Path ,
2017-03-09 16:27:31 +01:00
Interval : interval ,
requestTimeout : 5 * time . Second ,
lb : lb ,
}
2016-11-26 19:48:49 +01:00
}
2016-11-29 19:30:51 +01:00
//SetBackendsConfiguration set backends configuration
2017-02-06 09:31:20 +01:00
func ( hc * HealthCheck ) SetBackendsConfiguration ( parentCtx context . Context , backends map [ string ] * BackendHealthCheck ) {
2016-11-26 19:48:49 +01:00
hc . Backends = backends
2016-11-30 22:49:57 +01:00
if hc . cancel != nil {
hc . cancel ( )
}
2017-01-31 22:55:02 +01:00
ctx , cancel := context . WithCancel ( parentCtx )
2016-11-30 22:49:57 +01:00
hc . cancel = cancel
2016-11-26 19:48:49 +01:00
2016-11-30 22:49:57 +01:00
for backendID , backend := range hc . Backends {
2017-01-31 22:55:02 +01:00
currentBackendID := backendID
2017-03-09 16:27:31 +01:00
currentBackend := backend
2017-01-31 22:55:02 +01:00
safe . Go ( func ( ) {
2017-03-09 16:27:31 +01:00
hc . execute ( ctx , currentBackendID , currentBackend )
2017-01-31 22:55:02 +01:00
} )
2016-11-30 22:49:57 +01:00
}
2016-11-26 19:48:49 +01:00
}
2017-03-09 16:27:31 +01:00
func ( hc * HealthCheck ) execute ( ctx context . Context , backendID string , backend * BackendHealthCheck ) {
log . Debugf ( "Initial healthcheck for currentBackend %s " , backendID )
checkBackend ( backend )
ticker := time . NewTicker ( backend . Interval )
defer ticker . Stop ( )
2017-04-11 17:10:46 +02:00
for {
select {
case <- ctx . Done ( ) :
log . Debugf ( "Stopping all current Healthcheck goroutines" )
return
case <- ticker . C :
2017-03-09 16:27:31 +01:00
log . Debugf ( "Refreshing healthcheck for currentBackend %s " , backendID )
checkBackend ( backend )
}
}
}
func checkBackend ( currentBackend * BackendHealthCheck ) {
2017-04-11 17:10:46 +02:00
enabledURLs := currentBackend . lb . Servers ( )
var newDisabledURLs [ ] * url . URL
for _ , url := range currentBackend . DisabledURLs {
2017-03-09 16:27:31 +01:00
if checkHealth ( url , currentBackend ) {
2017-04-11 17:10:46 +02:00
log . Debugf ( "HealthCheck is up [%s]: Upsert in server list" , url . String ( ) )
currentBackend . lb . UpsertServer ( url , roundrobin . Weight ( 1 ) )
} else {
2017-03-09 16:27:31 +01:00
log . Warnf ( "HealthCheck is still failing [%s]" , url . String ( ) )
2017-04-11 17:10:46 +02:00
newDisabledURLs = append ( newDisabledURLs , url )
}
}
currentBackend . DisabledURLs = newDisabledURLs
2017-03-09 16:27:31 +01:00
2017-04-11 17:10:46 +02:00
for _ , url := range enabledURLs {
2017-03-09 16:27:31 +01:00
if ! checkHealth ( url , currentBackend ) {
log . Warnf ( "HealthCheck has failed [%s]: Remove from server list" , url . String ( ) )
2017-04-11 17:10:46 +02:00
currentBackend . lb . RemoveServer ( url )
currentBackend . DisabledURLs = append ( currentBackend . DisabledURLs , url )
}
}
2017-03-09 16:27:31 +01:00
}
func checkHealth ( serverURL * url . URL , backend * BackendHealthCheck ) bool {
2016-11-30 22:48:09 +01:00
client := http . Client {
2017-03-09 16:27:31 +01:00
Timeout : backend . requestTimeout ,
2016-11-30 22:48:09 +01:00
}
2017-03-14 01:22:08 +01:00
resp , err := client . Get ( serverURL . String ( ) + backend . Path )
2017-03-09 16:27:31 +01:00
if err == nil {
defer resp . Body . Close ( )
2016-11-26 19:48:49 +01:00
}
2017-03-09 16:27:31 +01:00
return err == nil && resp . StatusCode == 200
2016-11-26 19:48:49 +01:00
}