mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 16:59:26 +00:00
Add testing
This commit is contained in:
parent
3ded1c14c3
commit
cc3cd2f775
8 changed files with 790 additions and 34 deletions
|
|
@ -2,16 +2,13 @@ package health
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
)
|
||||
|
||||
func checkBlockNumber(ec *ethclient.Client, blockNumber *big.Int) error {
|
||||
func checkBlockNumber(ec ethClient, blockNumber *big.Int) error {
|
||||
_, err := ec.BlockByNumber(context.TODO(), blockNumber)
|
||||
if err != nil {
|
||||
return fmt.Errorf("no known block with number %v (%x hex)", blockNumber.Int64(), blockNumber.Int64())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotEnoughPeers = errors.New("not enough peers")
|
||||
)
|
||||
|
||||
func checkMinPeers(ec *ethclient.Client, minPeerCount uint) error {
|
||||
func checkMinPeers(ec ethClient, minPeerCount uint) error {
|
||||
peerCount, err := ec.PeerCount(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
|
|
@ -12,7 +11,7 @@ var (
|
|||
errNotSynced = errors.New("not synced")
|
||||
)
|
||||
|
||||
func checkSynced(ec *ethclient.Client, r *http.Request) error {
|
||||
func checkSynced(ec ethClient, r *http.Request) error {
|
||||
i, err := ec.SyncProgress(r.Context())
|
||||
if err != nil {
|
||||
log.Root().Warn("Unable to check sync status for healthcheck", "err", err.Error())
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -14,7 +12,7 @@ var (
|
|||
)
|
||||
|
||||
func checkTime(
|
||||
ec *ethclient.Client,
|
||||
ec ethClient,
|
||||
r *http.Request,
|
||||
seconds int,
|
||||
) error {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
const (
|
||||
healthHeader = "X-GETH-HEALTHCHECK"
|
||||
query = "query"
|
||||
synced = "synced"
|
||||
minPeerCount = "min_peer_count"
|
||||
checkBlock = "check_block"
|
||||
|
|
@ -23,8 +24,8 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
errCheckDisabled = errors.New("error check disabled")
|
||||
errBadHeaderValue = errors.New("bad header value")
|
||||
errCheckDisabled = errors.New("error check disabled")
|
||||
errInvalidValue = errors.New("invalid value provided")
|
||||
)
|
||||
|
||||
type requestBody struct {
|
||||
|
|
@ -34,7 +35,7 @@ type requestBody struct {
|
|||
MaxSecondsBehind *int `json:"max_seconds_behind"`
|
||||
}
|
||||
|
||||
func (h *handler) processFromHeaders(headers []string, w http.ResponseWriter, r *http.Request) {
|
||||
func processFromHeaders(ec ethClient, headers []string, w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
errCheckSynced = errCheckDisabled
|
||||
errCheckPeer = errCheckDisabled
|
||||
|
|
@ -45,7 +46,7 @@ func (h *handler) processFromHeaders(headers []string, w http.ResponseWriter, r
|
|||
for _, header := range headers {
|
||||
lHeader := strings.ToLower(header)
|
||||
if lHeader == synced {
|
||||
errCheckSynced = checkSynced(h.ec, r)
|
||||
errCheckSynced = checkSynced(ec, r)
|
||||
}
|
||||
if strings.HasPrefix(lHeader, minPeerCount) {
|
||||
peers, err := strconv.Atoi(strings.TrimPrefix(lHeader, minPeerCount))
|
||||
|
|
@ -53,7 +54,7 @@ func (h *handler) processFromHeaders(headers []string, w http.ResponseWriter, r
|
|||
errCheckPeer = err
|
||||
break
|
||||
}
|
||||
errCheckPeer = checkMinPeers(h.ec, uint(peers))
|
||||
errCheckPeer = checkMinPeers(ec, uint(peers))
|
||||
}
|
||||
if strings.HasPrefix(lHeader, checkBlock) {
|
||||
block, err := strconv.Atoi(strings.TrimPrefix(lHeader, checkBlock))
|
||||
|
|
@ -61,7 +62,7 @@ func (h *handler) processFromHeaders(headers []string, w http.ResponseWriter, r
|
|||
errCheckBlock = err
|
||||
break
|
||||
}
|
||||
errCheckBlock = checkBlockNumber(h.ec, big.NewInt(int64(block)))
|
||||
errCheckBlock = checkBlockNumber(ec, big.NewInt(int64(block)))
|
||||
}
|
||||
if strings.HasPrefix(lHeader, maxSecondsBehind) {
|
||||
seconds, err := strconv.Atoi(strings.TrimPrefix(lHeader, maxSecondsBehind))
|
||||
|
|
@ -70,18 +71,18 @@ func (h *handler) processFromHeaders(headers []string, w http.ResponseWriter, r
|
|||
break
|
||||
}
|
||||
if seconds < 0 {
|
||||
errCheckSeconds = errBadHeaderValue
|
||||
errCheckSeconds = errInvalidValue
|
||||
break
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
errCheckSeconds = checkTime(h.ec, r, int(now)-seconds)
|
||||
errCheckSeconds = checkTime(ec, r, int(now)-seconds)
|
||||
}
|
||||
}
|
||||
|
||||
reportHealth(errCheckSynced, errCheckPeer, errCheckBlock, errCheckSeconds, w)
|
||||
reportHealth(nil, errCheckSynced, errCheckPeer, errCheckBlock, errCheckSeconds, w)
|
||||
}
|
||||
|
||||
func (h *handler) processFromBody(w http.ResponseWriter, r *http.Request) {
|
||||
func processFromBody(ec ethClient, w http.ResponseWriter, r *http.Request) {
|
||||
body, errParse := parseHealthCheckBody(r.Body)
|
||||
defer r.Body.Close()
|
||||
|
||||
|
|
@ -96,37 +97,43 @@ func (h *handler) processFromBody(w http.ResponseWriter, r *http.Request) {
|
|||
log.Root().Warn("Unable to process healthcheck request", "err", errParse)
|
||||
} else {
|
||||
if body.Synced != nil {
|
||||
errCheckSynced = checkSynced(h.ec, r)
|
||||
errCheckSynced = checkSynced(ec, r)
|
||||
}
|
||||
|
||||
if body.MinPeerCount != nil {
|
||||
errCheckPeer = checkMinPeers(h.ec, *body.MinPeerCount)
|
||||
errCheckPeer = checkMinPeers(ec, *body.MinPeerCount)
|
||||
}
|
||||
|
||||
if body.CheckBlock != nil {
|
||||
errCheckBlock = checkBlockNumber(h.ec, big.NewInt(int64(*body.CheckBlock)))
|
||||
errCheckBlock = checkBlockNumber(ec, big.NewInt(int64(*body.CheckBlock)))
|
||||
}
|
||||
|
||||
if body.MaxSecondsBehind != nil {
|
||||
seconds := *body.MaxSecondsBehind
|
||||
if seconds < 0 {
|
||||
errCheckSeconds = errBadHeaderValue
|
||||
errCheckSeconds = errInvalidValue
|
||||
} else {
|
||||
now := time.Now().Unix()
|
||||
errCheckSeconds = checkTime(ec, r, int(now)-seconds)
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
errCheckSeconds = checkTime(h.ec, r, int(now)-seconds)
|
||||
}
|
||||
}
|
||||
|
||||
err := reportHealth(errCheckSynced, errCheckPeer, errCheckBlock, errCheckSeconds, w)
|
||||
err := reportHealth(errParse, errCheckSynced, errCheckPeer, errCheckBlock, errCheckSeconds, w)
|
||||
if err != nil {
|
||||
log.Root().Warn("Unable to process healthcheck request", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func reportHealth(errCheckSynced, errCheckPeer, errCheckBlock, errCheckSeconds error, w http.ResponseWriter) error {
|
||||
func reportHealth(errParse, errCheckSynced, errCheckPeer, errCheckBlock, errCheckSeconds error, w http.ResponseWriter) error {
|
||||
statusCode := http.StatusOK
|
||||
errs := make(map[string]string)
|
||||
|
||||
if shouldChangeStatusCode(errParse) {
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
errs[query] = errorStringOrOK(errParse)
|
||||
|
||||
if shouldChangeStatusCode(errCheckSynced) {
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
|
|
@ -188,7 +195,7 @@ func shouldChangeStatusCode(err error) bool {
|
|||
|
||||
func errorStringOrOK(err error) string {
|
||||
if err == nil {
|
||||
return "HEALTHY"
|
||||
return "OK"
|
||||
}
|
||||
|
||||
if errors.Is(err, errCheckDisabled) {
|
||||
|
|
|
|||
742
health/health_test.go
Normal file
742
health/health_test.go
Normal file
|
|
@ -0,0 +1,742 @@
|
|||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
type ethClientStub struct {
|
||||
peersResult uint64
|
||||
peersError error
|
||||
blockResult *types.Block
|
||||
blockError error
|
||||
syncingResult *ethereum.SyncProgress
|
||||
syncingError error
|
||||
}
|
||||
|
||||
func (e *ethClientStub) PeerCount(_ context.Context) (uint64, error) {
|
||||
return e.peersResult, e.peersError
|
||||
}
|
||||
|
||||
func (e *ethClientStub) BlockByNumber(_ context.Context, _ *big.Int) (*types.Block, error) {
|
||||
return e.blockResult, e.blockError
|
||||
}
|
||||
|
||||
func (e *ethClientStub) SyncProgress(_ context.Context) (*ethereum.SyncProgress, error) {
|
||||
return e.syncingResult, e.syncingError
|
||||
}
|
||||
|
||||
func TestProcessFromHeaders(t *testing.T) {
|
||||
cases := []struct {
|
||||
headers []string
|
||||
clientPeerResult uint64
|
||||
clientPeerError error
|
||||
clientBlockResult *types.Block
|
||||
clientBlockError error
|
||||
clientSyncingResult *ethereum.SyncProgress
|
||||
clientSyncingError error
|
||||
expectedStatusCode int
|
||||
expectedBody map[string]string
|
||||
}{
|
||||
// 0 - sync check enabled - syncing
|
||||
{
|
||||
headers: []string{"synced"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: &types.Block{},
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
synced: "OK",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 1 - sync check enabled - not syncing
|
||||
{
|
||||
headers: []string{"synced"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: ðereum.SyncProgress{},
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "ERROR: not synced",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 2 - sync check enabled - error checking sync
|
||||
{
|
||||
headers: []string{"synced"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: ðereum.SyncProgress{},
|
||||
clientSyncingError: errors.New("problem checking sync"),
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "ERROR: problem checking sync",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 3 - peer count enabled - good request
|
||||
{
|
||||
headers: []string{"min_peer_count1"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "OK",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 4 - peer count enabled - not enough peers
|
||||
{
|
||||
headers: []string{"min_peer_count10"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "ERROR: not enough peers: 1 (minimum 10)",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 5 - peer count enabled - error checking peers
|
||||
{
|
||||
headers: []string{"min_peer_count10"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: errors.New("problem checking peers"),
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "ERROR: problem checking peers",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 6 - peer count enabled - badly formed request
|
||||
{
|
||||
headers: []string{"min_peer_countABC"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "ERROR: strconv.Atoi: parsing \"abc\": invalid syntax",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 7 - block check - all ok
|
||||
{
|
||||
headers: []string{"check_block10"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: &types.Block{},
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "OK",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 8 - block check - no block found
|
||||
{
|
||||
headers: []string{"check_block10"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: errors.New("not found"),
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "ERROR: not found",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 9 - block check - error checking block
|
||||
{
|
||||
headers: []string{"check_block10"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: &types.Block{},
|
||||
clientBlockError: errors.New("problem checking block"),
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "ERROR: problem checking block",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 10 - block check - badly formed request
|
||||
{
|
||||
headers: []string{"check_blockABC"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "ERROR: strconv.Atoi: parsing \"abc\": invalid syntax",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 11 - seconds check - all ok
|
||||
{
|
||||
headers: []string{"max_seconds_behind60"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: types.NewBlockWithHeader(&types.Header{
|
||||
Time: uint64(time.Now().Add(-10 * time.Second).Unix()),
|
||||
}),
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "OK",
|
||||
},
|
||||
},
|
||||
// 12 - seconds check - too old
|
||||
{
|
||||
headers: []string{"max_seconds_behind60"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: types.NewBlockWithHeader(&types.Header{
|
||||
Time: uint64(time.Now().Add(-1 * time.Hour).Unix()),
|
||||
}),
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "ERROR: timestamp too old: got ts:",
|
||||
},
|
||||
},
|
||||
// 13 - seconds check - less than 0 seconds
|
||||
{
|
||||
headers: []string{"max_seconds_behind-1"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: types.NewBlockWithHeader(&types.Header{
|
||||
Time: uint64(time.Now().Add(1 * time.Hour).Unix()),
|
||||
}),
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "ERROR: invalid value provided",
|
||||
},
|
||||
},
|
||||
// 14 - seconds check - badly formed request
|
||||
{
|
||||
headers: []string{"max_seconds_behindABC"},
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: &types.Block{},
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "ERROR: strconv.Atoi: parsing \"abc\": invalid syntax",
|
||||
},
|
||||
},
|
||||
// 15 - all checks - report ok
|
||||
{
|
||||
headers: []string{"synced", "check_block10", "min_peer_count1", "max_seconds_behind60"},
|
||||
clientPeerResult: uint64(10),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: types.NewBlockWithHeader(&types.Header{
|
||||
Time: uint64(time.Now().Add(1 * time.Second).Unix()),
|
||||
}),
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
synced: "OK",
|
||||
minPeerCount: "OK",
|
||||
checkBlock: "OK",
|
||||
maxSecondsBehind: "OK",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range cases {
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/health", nil)
|
||||
if err != nil {
|
||||
t.Errorf("%v: creating request: %v", idx, err)
|
||||
}
|
||||
|
||||
for _, header := range c.headers {
|
||||
r.Header.Add("X-GETH-HEALTHCHECK", header)
|
||||
}
|
||||
|
||||
ethClient := ðClientStub{
|
||||
peersResult: c.clientPeerResult,
|
||||
peersError: c.clientPeerError,
|
||||
blockResult: c.clientBlockResult,
|
||||
blockError: c.clientBlockError,
|
||||
syncingResult: c.clientSyncingResult,
|
||||
syncingError: c.clientSyncingError,
|
||||
}
|
||||
|
||||
processFromHeaders(ethClient, r.Header.Values(healthHeader), w, r)
|
||||
|
||||
result := w.Result()
|
||||
if result.StatusCode != c.expectedStatusCode {
|
||||
t.Errorf("%v: expected status code: %v, but got: %v", idx, c.expectedStatusCode, result.StatusCode)
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
t.Errorf("%v: reading response body: %s", idx, err)
|
||||
}
|
||||
|
||||
var body map[string]string
|
||||
err = json.Unmarshal(bodyBytes, &body)
|
||||
if err != nil {
|
||||
t.Errorf("%v: unmarshalling the response body: %s", idx, err)
|
||||
}
|
||||
result.Body.Close()
|
||||
|
||||
for k, v := range c.expectedBody {
|
||||
val, found := body[k]
|
||||
if !found {
|
||||
t.Errorf("%v: expected the key: %s to be in the response body but it wasn't there", idx, k)
|
||||
}
|
||||
if !strings.Contains(val, v) {
|
||||
t.Errorf("%v: expected the response body key: %s to contain: %s, but it contained: %s", idx, k, v, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessHealthcheckIfNeeded_RequestBody(t *testing.T) {
|
||||
cases := []struct {
|
||||
body string
|
||||
clientPeerResult uint64
|
||||
clientPeerError error
|
||||
clientBlockResult *types.Block
|
||||
clientBlockError error
|
||||
clientSyncingResult *ethereum.SyncProgress
|
||||
clientSyncingError error
|
||||
expectedStatusCode int
|
||||
expectedBody map[string]string
|
||||
}{
|
||||
// 0 - sync check enabled - syncing
|
||||
{
|
||||
body: "{\"synced\": true}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "OK",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 1 - sync check enabled - not syncing
|
||||
{
|
||||
body: "{\"synced\": true}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: ðereum.SyncProgress{},
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "ERROR: not synced",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 2 - sync check enabled - error checking sync
|
||||
{
|
||||
body: "{\"synced\": true}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: ðereum.SyncProgress{},
|
||||
clientSyncingError: errors.New("problem checking sync"),
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "ERROR: problem checking sync",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 3 - peer count enabled - good request
|
||||
{
|
||||
body: "{\"min_peer_count\": 1}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "OK",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 4 - peer count enabled - not enough peers
|
||||
{
|
||||
body: "{\"min_peer_count\": 10}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "ERROR: not enough peers: 1 (minimum 10)",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 5 - peer count enabled - error checking peers
|
||||
{
|
||||
body: "{\"min_peer_count\": 10}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: errors.New("problem checking peers"),
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "ERROR: problem checking peers",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 6 - peer count enabled - badly formed request
|
||||
{
|
||||
body: "{\"min_peer_count\": \"ABC\"}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "ERROR: json: cannot unmarshal string into Go struct field requestBody.min_peer_count of type uint",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 7 - block check - all ok
|
||||
{
|
||||
body: "{\"check_block\": 10}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: &types.Block{},
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "OK",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 8 - block check - no block found
|
||||
{
|
||||
body: "{\"check_block\": 10}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: errors.New("not found"),
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "ERROR: not found",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 9 - block check - error checking block
|
||||
{
|
||||
body: "{\"check_block\": 10}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: &types.Block{},
|
||||
clientBlockError: errors.New("problem checking block"),
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "ERROR: problem checking block",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 10 - block check - badly formed request
|
||||
{
|
||||
body: "{\"check_block\": \"ABC\"}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: nil,
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "ERROR: json: cannot unmarshal string into Go struct field requestBody.check_block of type uint64",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 11 - seconds check - all ok
|
||||
{
|
||||
body: "{\"max_seconds_behind\": 60}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: types.NewBlockWithHeader(&types.Header{
|
||||
Time: uint64(time.Now().Add(-10 * time.Second).Unix()),
|
||||
}),
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "OK",
|
||||
},
|
||||
},
|
||||
// 12 - seconds check - too old
|
||||
{
|
||||
body: "{\"max_seconds_behind\": 60}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: types.NewBlockWithHeader(&types.Header{
|
||||
Time: uint64(time.Now().Add(-1 * time.Hour).Unix()),
|
||||
}),
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "ERROR: timestamp too old: got ts:",
|
||||
},
|
||||
},
|
||||
// 13 - seconds check - less than 0 seconds
|
||||
{
|
||||
body: "{\"max_seconds_behind\": -1}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: types.NewBlockWithHeader(&types.Header{
|
||||
Time: uint64(time.Now().Add(1 * time.Hour).Unix()),
|
||||
}),
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "ERROR: invalid value provided",
|
||||
},
|
||||
},
|
||||
// 14 - seconds check - badly formed request
|
||||
{
|
||||
body: "{\"max_seconds_behind\": \"ABC\"}",
|
||||
clientPeerResult: uint64(1),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: &types.Block{},
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedBody: map[string]string{
|
||||
query: "ERROR: json: cannot unmarshal string into Go struct field requestBody.max_seconds_behind of type int",
|
||||
synced: "DISABLED",
|
||||
minPeerCount: "DISABLED",
|
||||
checkBlock: "DISABLED",
|
||||
maxSecondsBehind: "DISABLED",
|
||||
},
|
||||
},
|
||||
// 15 - all checks - report ok
|
||||
{
|
||||
body: "{\"synced\": true, \"min_peer_count\": 1, \"check_block\": 10, \"max_seconds_behind\": 60}",
|
||||
clientPeerResult: uint64(10),
|
||||
clientPeerError: nil,
|
||||
clientBlockResult: types.NewBlockWithHeader(&types.Header{
|
||||
Time: uint64(time.Now().Add(1 * time.Second).Unix()),
|
||||
}),
|
||||
clientBlockError: nil,
|
||||
clientSyncingResult: nil,
|
||||
clientSyncingError: nil,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedBody: map[string]string{
|
||||
query: "OK",
|
||||
synced: "OK",
|
||||
minPeerCount: "OK",
|
||||
checkBlock: "OK",
|
||||
maxSecondsBehind: "OK",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range cases {
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/health", nil)
|
||||
if err != nil {
|
||||
t.Errorf("%v: creating request: %v", idx, err)
|
||||
}
|
||||
|
||||
r.Body = io.NopCloser(strings.NewReader(c.body))
|
||||
|
||||
ethClient := ðClientStub{
|
||||
peersResult: c.clientPeerResult,
|
||||
peersError: c.clientPeerError,
|
||||
blockResult: c.clientBlockResult,
|
||||
blockError: c.clientBlockError,
|
||||
syncingResult: c.clientSyncingResult,
|
||||
syncingError: c.clientSyncingError,
|
||||
}
|
||||
|
||||
processFromBody(ethClient, w, r)
|
||||
|
||||
result := w.Result()
|
||||
if result.StatusCode != c.expectedStatusCode {
|
||||
t.Errorf("%v: expected status code: %v, but got: %v", idx, c.expectedStatusCode, result.StatusCode)
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
t.Errorf("%v: reading response body: %s", idx, err)
|
||||
}
|
||||
|
||||
var body map[string]string
|
||||
err = json.Unmarshal(bodyBytes, &body)
|
||||
if err != nil {
|
||||
t.Errorf("%v: unmarshalling the response body: %s", idx, err)
|
||||
}
|
||||
result.Body.Close()
|
||||
|
||||
for k, v := range c.expectedBody {
|
||||
val, found := body[k]
|
||||
if !found {
|
||||
t.Errorf("%v: expected the key: %s to be in the response body but it wasn't there", idx, k)
|
||||
}
|
||||
if !strings.Contains(val, v) {
|
||||
t.Errorf("%v: expected the response body key: %s to contain: %s, but it contained: %s", idx, k, v, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
health/interface.go
Normal file
15
health/interface.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
type ethClient interface {
|
||||
PeerCount(context.Context) (uint64, error)
|
||||
BlockByNumber(context.Context, *big.Int) (*types.Block, error)
|
||||
SyncProgress(context.Context) (*ethereum.SyncProgress, error)
|
||||
}
|
||||
|
|
@ -14,9 +14,9 @@ type handler struct {
|
|||
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
headers := r.Header.Values(healthHeader)
|
||||
if len(headers) != 0 {
|
||||
h.processFromHeaders(headers, w, r)
|
||||
processFromHeaders(h.ec, headers, w, r)
|
||||
} else {
|
||||
h.processFromBody(w, r)
|
||||
processFromBody(h.ec, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue