-
Notifications
You must be signed in to change notification settings - Fork 0
/
healthcheck.go
168 lines (151 loc) · 4.91 KB
/
healthcheck.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package composable
import (
"log"
"net/http"
"sync/atomic"
"time"
)
// HealthcheckStatus represents states of a Healthcheck.
type HealthcheckStatus string
const (
HealthcheckPending HealthcheckStatus = "Pending"
HealthcheckPassing HealthcheckStatus = "Passing"
HealthcheckFailing HealthcheckStatus = "Failing"
HealthcheckStopped HealthcheckStatus = "Stopped"
)
// HealthcheckMethod provides Healthcheck API with an abstraction for performing different types of health checks.
type HealthcheckMethod interface {
// Perform is called at each Healthcheck.Interval with a sending bool chan to report result.
Perform(result chan<- bool)
// Destroy will be called when a Healthcheck is finished.
// Destroy implementations should close and clean up open resources.
Destroy()
}
// Healthcheck manages a time.Ticker and HealthcheckMethod to perform health checks.
// The frequency of health checking is controlled by Healthcheck.Interval.
// Use Healthcheck.Delay to initially pause health checking for a system under test to complete initialization.
type Healthcheck struct {
Delay time.Duration
Interval time.Duration
Status HealthcheckStatus
StatusC <-chan HealthcheckStatus `json:"-"`
method HealthcheckMethod
running atomic.Bool
statusC chan<- HealthcheckStatus
ticker *time.Ticker
}
// NewHealthcheck creates a new Healthcheck for a given HealthcheckMethod to run at an interval specified by time.Duration.
// An optional time.Duration for Healthcheck.Delay allows a system under test to be given sufficient startup time.
func NewHealthcheck(method HealthcheckMethod, delay time.Duration, interval time.Duration) *Healthcheck {
statusC := make(chan HealthcheckStatus)
return &Healthcheck{
Delay: delay,
Interval: interval,
Status: HealthcheckPending,
StatusC: statusC,
method: method,
statusC: statusC,
}
}
// Start creates a time.Ticker to trigger health checks and continues calling HealthcheckMethod.Perform until Healthcheck.Stop is called.
func (hc *Healthcheck) Start() {
hc.running.Swap(true)
if hc.Delay > 0 {
log.Println("[DEBUG] Healthcheck.Start delay", hc.Delay)
<-time.NewTimer(hc.Delay).C
}
log.Println("[DEBUG] Healthcheck.Start interval", hc.Interval)
hc.ticker = time.NewTicker(hc.Interval)
hcResultC := make(chan bool)
for {
if !hc.running.Load() {
return
}
select {
case <-hc.ticker.C:
log.Println("[DEBUG] Healthcheck.Start tick")
go hc.method.Perform(hcResultC)
case passing := <-hcResultC:
if hc.running.Load() {
if passing {
hc.updateStatus(HealthcheckPassing)
} else {
hc.updateStatus(HealthcheckFailing)
}
}
}
}
}
// Stop stops the time.Ticker that triggers health checks and delegates clean up to HealthcheckMethod.Destroy.
func (hc *Healthcheck) Stop() {
log.Println("[DEBUG] Healthcheck.stop")
hc.running.Swap(false)
hc.ticker.Stop()
hc.method.Destroy()
hc.updateStatus(HealthcheckStopped)
}
// updateStatus pushes the new HealthcheckStatus to Healthcheck.StatusC readers.
func (hc *Healthcheck) updateStatus(status HealthcheckStatus) {
log.Println("[DEBUG] Healthcheck.updateStatus", hc.Status, "to", status)
hc.Status = status
select {
case hc.statusC <- status:
default:
}
}
// NewExecHealthcheck creates a Healthcheck from an ExecDescription using a ExecHealthcheckMethod.
func NewExecHealthcheck(exec *ExecDescription, delay time.Duration, interval time.Duration) *Healthcheck {
p := exec.Process()
m := &ExecHealthcheckMethod{p}
return NewHealthcheck(m, delay, interval)
}
// ExecHealthcheckMethod is a HealthcheckMethod that runs a Process at every Healthcheck.Interval.
type ExecHealthcheckMethod struct {
process *Process
}
func (hm *ExecHealthcheckMethod) Perform(hcResultC chan<- bool) {
hm.process.Restart()
for {
c := hm.process.StatusC()
switch <-c {
case Stopped:
if hm.process.Command.ProcessState.ExitCode() > 0 {
hcResultC <- false
} else {
hcResultC <- true
}
break
case Error:
hcResultC <- false
break
}
}
}
func (hm *ExecHealthcheckMethod) Destroy() {
hm.process.Stop()
}
// NewHttpHealthcheck creates a Healthcheck from an HttpGetDescription.
func NewHttpHealthcheck(get *HttpGetDescription, delay time.Duration, interval time.Duration) *Healthcheck {
url := get.Url()
m := &HttpHealthcheckMethod{url}
return NewHealthcheck(m, delay, interval)
}
// HttpHealthcheckMethod is a HealthcheckMethod that performs an HTTP GET request at every Healthcheck.Interval.
type HttpHealthcheckMethod struct {
url string
}
func (hm *HttpHealthcheckMethod) Perform(hcResultC chan<- bool) {
res, err := http.Get(hm.url)
if err != nil {
log.Println("[ERROR] HttpHealthcheckMethod.Perform", err)
hcResultC <- false
} else if res.StatusCode >= 300 {
log.Println("[WARN] HttpHealthcheckMethod.Perform", res.StatusCode)
hcResultC <- false
} else {
log.Println("[DEBUG] HttpHealthcheckMethod.Perform", res.StatusCode)
hcResultC <- true
}
}
func (hm *HttpHealthcheckMethod) Destroy() {
}