Files
podinfo/pkg/api/http/fault_injection.go

105 lines
3.4 KiB
Go

package http
import (
"net/http"
"strings"
"sync/atomic"
)
// faultInjection toggles a process-wide fault-injection mode in which
// the server responds with HTTP 500 for application endpoints. It is
// intended for testing client-side circuit breakers / outlier detection
// against a single "sick" replica.
var faultInjection int32
// faultInjectionExcluded returns true if the given request path should
// bypass fault injection. Kubernetes probes, metrics, pprof and the
// control endpoints themselves stay functional so the pod is not torn
// down by the platform while a circuit breaker detects the fault.
func faultInjectionExcluded(p string) bool {
switch {
case strings.HasPrefix(p, "/fault_injection"):
return true
case p == "/healthz", p == "/readyz":
return true
case p == "/metrics":
return true
case strings.HasPrefix(p, "/debug/"):
return true
}
return false
}
func faultInjectionMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if atomic.LoadInt32(&faultInjection) == 1 && !faultInjectionExcluded(r.URL.Path) {
http.Error(w, `{"status":"fault injection enabled"}`, http.StatusInternalServerError)
return
}
next.ServeHTTP(w, r)
})
}
// faultInjectionMiddleware is a method form that strips the configured
// path prefix before consulting the exclusion list, so the same set of
// excluded routes works regardless of whether --prefix is set.
func (s *Server) faultInjectionMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := r.URL.Path
if prefix := s.config.Prefix; prefix != "" && prefix != "/" {
if trimmed := strings.TrimPrefix(p, prefix); trimmed != p {
if trimmed == "" {
trimmed = "/"
}
p = trimmed
}
}
if atomic.LoadInt32(&faultInjection) == 1 && !faultInjectionExcluded(p) {
http.Error(w, `{"status":"fault injection enabled"}`, http.StatusInternalServerError)
return
}
next.ServeHTTP(w, r)
})
}
// EnableFaultInjection godoc
// @Summary Enable fault injection
// @Description makes the server respond with HTTP 500 for all non-probe endpoints
// @Tags Fault Injection
// @Accept json
// @Produce json
// @Router /fault_injection/enable [post]
// @Success 202 {string} string "OK"
func (s *Server) enableFaultInjectionHandler(w http.ResponseWriter, r *http.Request) {
atomic.StoreInt32(&faultInjection, 1)
s.JSONResponseCode(w, r, map[string]string{"fault_injection": "enabled"}, http.StatusAccepted)
}
// DisableFaultInjection godoc
// @Summary Disable fault injection
// @Description restores normal responses
// @Tags Fault Injection
// @Accept json
// @Produce json
// @Router /fault_injection/disable [post]
// @Success 202 {string} string "OK"
func (s *Server) disableFaultInjectionHandler(w http.ResponseWriter, r *http.Request) {
atomic.StoreInt32(&faultInjection, 0)
s.JSONResponseCode(w, r, map[string]string{"fault_injection": "disabled"}, http.StatusAccepted)
}
// FaultInjectionStatus godoc
// @Summary Get fault injection status
// @Tags Fault Injection
// @Accept json
// @Produce json
// @Router /fault_injection/status [get]
// @Success 200 {object} map[string]string
func (s *Server) faultInjectionStatusHandler(w http.ResponseWriter, r *http.Request) {
state := "disabled"
if atomic.LoadInt32(&faultInjection) == 1 {
state = "enabled"
}
s.JSONResponse(w, r, map[string]string{"fault_injection": state})
}