2021-09-28 09:27:57 +00:00
|
|
|
package health
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/ledgerwatch/erigon/rpc"
|
|
|
|
"github.com/ledgerwatch/log/v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
type requestBody struct {
|
|
|
|
MinPeerCount *uint `json:"min_peer_count"`
|
|
|
|
BlockNumber *rpc.BlockNumber `json:"known_block"`
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
urlPath = "/health"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errCheckDisabled = errors.New("error check disabled")
|
|
|
|
)
|
|
|
|
|
|
|
|
func ProcessHealthcheckIfNeeded(
|
|
|
|
w http.ResponseWriter,
|
|
|
|
r *http.Request,
|
|
|
|
rpcAPI []rpc.API,
|
|
|
|
) bool {
|
|
|
|
if !strings.EqualFold(r.URL.Path, urlPath) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
netAPI, ethAPI := parseAPI(rpcAPI)
|
|
|
|
|
|
|
|
var errMinPeerCount = errCheckDisabled
|
|
|
|
var errCheckBlock = errCheckDisabled
|
|
|
|
|
|
|
|
body, errParse := parseHealthCheckBody(r.Body)
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
if errParse != nil {
|
2022-03-01 15:40:51 +00:00
|
|
|
log.Root().Warn("unable to process healthcheck request", "err", errParse)
|
2021-09-28 09:27:57 +00:00
|
|
|
} else {
|
|
|
|
// 1. net_peerCount
|
|
|
|
if body.MinPeerCount != nil {
|
|
|
|
errMinPeerCount = checkMinPeers(*body.MinPeerCount, netAPI)
|
|
|
|
}
|
|
|
|
// 2. custom query (shouldn't fail)
|
|
|
|
if body.BlockNumber != nil {
|
|
|
|
errCheckBlock = checkBlockNumber(*body.BlockNumber, ethAPI)
|
|
|
|
}
|
|
|
|
// TODO add time from the last sync cycle
|
|
|
|
}
|
|
|
|
|
|
|
|
err := reportHealth(errParse, errMinPeerCount, errCheckBlock, w)
|
|
|
|
if err != nil {
|
2022-03-01 15:40:51 +00:00
|
|
|
log.Root().Warn("unable to process healthcheck request", "err", err)
|
2021-09-28 09:27:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseHealthCheckBody(reader io.Reader) (requestBody, error) {
|
|
|
|
var body requestBody
|
|
|
|
|
|
|
|
bodyBytes, err := ioutil.ReadAll(reader)
|
|
|
|
if err != nil {
|
|
|
|
return body, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(bodyBytes, &body)
|
|
|
|
if err != nil {
|
|
|
|
return body, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return body, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func reportHealth(errParse, errMinPeerCount, errCheckBlock error, w http.ResponseWriter) error {
|
|
|
|
statusCode := http.StatusOK
|
|
|
|
errors := make(map[string]string)
|
|
|
|
|
|
|
|
if shouldChangeStatusCode(errParse) {
|
|
|
|
statusCode = http.StatusInternalServerError
|
|
|
|
}
|
|
|
|
errors["healthcheck_query"] = errorStringOrOK(errParse)
|
|
|
|
|
|
|
|
if shouldChangeStatusCode(errMinPeerCount) {
|
|
|
|
statusCode = http.StatusInternalServerError
|
|
|
|
}
|
|
|
|
errors["min_peer_count"] = errorStringOrOK(errMinPeerCount)
|
|
|
|
|
|
|
|
if shouldChangeStatusCode(errCheckBlock) {
|
|
|
|
statusCode = http.StatusInternalServerError
|
|
|
|
}
|
|
|
|
errors["check_block"] = errorStringOrOK(errCheckBlock)
|
|
|
|
|
|
|
|
w.WriteHeader(statusCode)
|
|
|
|
|
|
|
|
bodyJson, err := json.Marshal(errors)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = w.Write(bodyJson)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func shouldChangeStatusCode(err error) bool {
|
|
|
|
return err != nil && !errors.Is(err, errCheckDisabled)
|
|
|
|
}
|
|
|
|
|
|
|
|
func errorStringOrOK(err error) string {
|
|
|
|
if err == nil {
|
|
|
|
return "HEALTHY"
|
|
|
|
}
|
|
|
|
|
|
|
|
if errors.Is(err, errCheckDisabled) {
|
|
|
|
return "DISABLED"
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("ERROR: %v", err)
|
|
|
|
}
|