Major enhancements #6
7
costants.go
Normal file
7
costants.go
Normal file
@ -0,0 +1,7 @@
|
||||
package kubeprobes
|
||||
|
||||
const (
|
||||
defaultLivenessPath string = "/live"
|
||||
defaultReadinessPath string = "/ready"
|
||||
verboseOutputFlag string = "v"
|
||||
)
|
67
options.go
Normal file
67
options.go
Normal file
@ -0,0 +1,67 @@
|
||||
package kubeprobes
|
||||
|
||||
type option struct {
|
||||
fn func(*kubeprobes)
|
||||
}
|
||||
|
||||
func (o *option) apply(kp *kubeprobes) {
|
||||
o.fn(kp)
|
||||
}
|
||||
|
||||
// WithLivenessProbes adds given probe functions to the set of liveness probes.
|
||||
func WithLivenessProbes(probes ...ProbeFunction) Option {
|
||||
return &option{
|
||||
fn: func(kp *kubeprobes) {
|
||||
kp.livenessProbes = append(kp.livenessProbes, probes...)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithLivenessStatefulProbes adds given [StatefulProbe]s to the set of liveness probes.
|
||||
func WithLivenessStatefulProbes(probes ...*StatefulProbe) Option {
|
||||
return &option{
|
||||
fn: func(kp *kubeprobes) {
|
||||
for _, p := range probes {
|
||||
kp.livenessProbes = append(kp.livenessProbes, p.GetProbeFunction())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithLivenessPath sets custom path for liveness probe (default is "/live").
|
||||
func WithLivenessPath(path string) Option {
|
||||
return &option{
|
||||
fn: func(kp *kubeprobes) {
|
||||
kp.pathLive = path
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithReadinessProbes adds given probe functions to the set of readiness probes.
|
||||
func WithReadinessProbes(probes ...ProbeFunction) Option {
|
||||
return &option{
|
||||
fn: func(kp *kubeprobes) {
|
||||
kp.readinessProbes = append(kp.readinessProbes, probes...)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithReadinessProbes adds given [StatefulProbe]s to the set of readiness probes.
|
||||
func WithReadinessStatefulProbes(probes ...*StatefulProbe) Option {
|
||||
return &option{
|
||||
fn: func(kp *kubeprobes) {
|
||||
for _, p := range probes {
|
||||
kp.readinessProbes = append(kp.readinessProbes, p.GetProbeFunction())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithReadinessPath sets custom path for readiness probe (default is "/ready").
|
||||
func WithReadinessPath(path string) Option {
|
||||
return &option{
|
||||
fn: func(kp *kubeprobes) {
|
||||
kp.pathReady = path
|
||||
},
|
||||
}
|
||||
}
|
114
probes.go
114
probes.go
@ -1,6 +1,8 @@
|
||||
package kubeprobes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@ -8,57 +10,97 @@ import (
|
||||
type kubeprobes struct {
|
||||
livenessProbes []ProbeFunction
|
||||
readinessProbes []ProbeFunction
|
||||
|
||||
pathLive string
|
||||
pathReady string
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler interface
|
||||
func (kp *kubeprobes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
subs := strings.Split(strings.TrimSuffix(r.URL.Path, "/"), "/")
|
||||
switch subs[len(subs)-1] {
|
||||
case "live":
|
||||
// New returns a new instance of a Kubernetes probes with given options.
|
||||
func New(options ...Option) (Kubeprobes, error) {
|
||||
kp := &kubeprobes{
|
||||
livenessProbes: []ProbeFunction{},
|
||||
readinessProbes: []ProbeFunction{},
|
||||
pathLive: "/live",
|
||||
pathReady: "/ready",
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option.apply(kp)
|
||||
}
|
||||
|
||||
if err := kp.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kp, nil
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) validate() error {
|
||||
errs := []error{}
|
||||
|
||||
if kp.pathLive == "" {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("liveness probe path must not be empty"),
|
||||
)
|
||||
}
|
||||
|
||||
if kp.pathReady == "" {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("readiness probe path must not be empty"),
|
||||
)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(kp.pathLive, "/") {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("liveness probe path must start with slash (current: %q)", kp.pathLive),
|
||||
)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(kp.pathReady, "/") {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("readiness probe path must start with slash (current: %q)", kp.pathReady),
|
||||
)
|
||||
}
|
||||
|
||||
if kp.pathLive == kp.pathReady {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("liveness and readiness probes have the same values (both %q)", kp.pathLive),
|
||||
)
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) handleLiveness(w http.ResponseWriter, _ *http.Request) {
|
||||
sq := newStatusQuery(kp.livenessProbes)
|
||||
if sq.isAllGreen() {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
case "ready":
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) handleReadiness(w http.ResponseWriter, _ *http.Request) {
|
||||
sq := newStatusQuery(append(kp.livenessProbes, kp.readinessProbes...))
|
||||
if sq.isAllGreen() {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implements Kubeprobes.
|
||||
func (kp *kubeprobes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case kp.pathLive:
|
||||
kp.handleLiveness(w, r)
|
||||
case kp.pathReady:
|
||||
kp.handleReadiness(w, r)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
type option func(*kubeprobes)
|
||||
|
||||
// New returns a new instance of a Kubernetes probes
|
||||
func New(options ...option) *kubeprobes {
|
||||
kp := &kubeprobes{
|
||||
livenessProbes: []ProbeFunction{},
|
||||
readinessProbes: []ProbeFunction{},
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(kp)
|
||||
}
|
||||
|
||||
return kp
|
||||
}
|
||||
|
||||
// WithLivenessProbes adds given liveness probes to the set of probes
|
||||
func WithLivenessProbes(probes ...ProbeFunction) option {
|
||||
return func(kp *kubeprobes) {
|
||||
kp.livenessProbes = append(kp.livenessProbes, probes...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithReadinessProbes adds given readiness probes to the set of probes
|
||||
func WithReadinessProbes(probes ...ProbeFunction) option {
|
||||
return func(kp *kubeprobes) {
|
||||
kp.readinessProbes = append(kp.readinessProbes, probes...)
|
||||
}
|
||||
}
|
||||
|
@ -50,9 +50,9 @@ func TestKubeprobes(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
kp := New(
|
||||
WithLivenessProbes(live.GetProbeFunction()),
|
||||
WithReadinessProbes(ready.GetProbeFunction()),
|
||||
kp, _ := New(
|
||||
WithLivenessStatefulProbes(live),
|
||||
WithReadinessStatefulProbes(ready),
|
||||
)
|
||||
|
||||
srv := httptest.NewServer(kp)
|
||||
@ -65,8 +65,8 @@ func TestKubeprobes(t *testing.T) {
|
||||
test.livenessProbeTransformation(t, live)
|
||||
test.readinessProbeTransformation(t, ready)
|
||||
|
||||
liveStatus := getStatusFromEndpoint(t, client, srv.URL+"/live")
|
||||
readyStatus := getStatusFromEndpoint(t, client, srv.URL+"/ready")
|
||||
liveStatus := getStatusFromEndpoint(t, client, srv.URL+defaultLivenessPath)
|
||||
readyStatus := getStatusFromEndpoint(t, client, srv.URL+defaultReadinessPath)
|
||||
otherStatus := getStatusFromEndpoint(t, client, srv.URL+"/something")
|
||||
|
||||
if liveStatus != test.expectedLiveStatus {
|
||||
|
@ -2,11 +2,6 @@ package kubeprobes
|
||||
|
||||
import "sync"
|
||||
|
||||
// ProbeFunction is a function that determines whether
|
||||
// the given metric may be marked as correctly functioning.
|
||||
// It not, the error should be returned.
|
||||
type ProbeFunction func() error
|
||||
|
||||
type statusQuery struct {
|
||||
allGreen bool
|
||||
mux sync.Mutex
|
@ -5,13 +5,13 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var errProbeDown = errors.New("DOWN")
|
||||
var errProbeDown = errors.New("probe is down")
|
||||
|
||||
// StatefulProbe represents the simple probe that can be either
|
||||
// marked as "up" (healthy) or "down" (unhealthy).
|
||||
type StatefulProbe struct {
|
||||
status bool
|
||||
mux sync.Mutex
|
||||
status bool
|
||||
}
|
||||
|
||||
// NewStatefulProbe returns a new instance of a stateful probe
|
||||
@ -19,19 +19,19 @@ type StatefulProbe struct {
|
||||
// The probe is initially marked as "down".
|
||||
func NewStatefulProbe() *StatefulProbe {
|
||||
return &StatefulProbe{
|
||||
status: false,
|
||||
mux: sync.Mutex{},
|
||||
status: false,
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsUp marks the probe as healthy
|
||||
// MarkAsUp marks the probe as healthy.
|
||||
func (sp *StatefulProbe) MarkAsUp() {
|
||||
sp.mux.Lock()
|
||||
defer sp.mux.Unlock()
|
||||
sp.status = true
|
||||
}
|
||||
|
||||
// MarkAsDown marks the probe as unhealthy
|
||||
// MarkAsDown marks the probe as unhealthy.
|
||||
func (sp *StatefulProbe) MarkAsDown() {
|
||||
sp.mux.Lock()
|
||||
defer sp.mux.Unlock()
|
||||
|
18
types.go
Normal file
18
types.go
Normal file
@ -0,0 +1,18 @@
|
||||
package kubeprobes
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Kubeprobes represents liveness & readiness probes handler.
|
||||
type Kubeprobes interface {
|
||||
http.Handler
|
||||
}
|
||||
|
||||
// Option represents a [Kubeprobes] constructor option.
|
||||
type Option interface {
|
||||
apply(kp *kubeprobes)
|
||||
}
|
||||
|
||||
// ProbeFunction is a function that determines whether
|
||||
// the given metric may be marked as correctly functioning.
|
||||
// It not, the error should be returned.
|
||||
type ProbeFunction func() error
|
Loading…
Reference in New Issue
Block a user