Skip to content

Commit 1ac3460

Browse files
committed
cmd: initial health check code
1 parent 6700cac commit 1ac3460

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed

fly/cmd/healthcheck/main.go

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/hex"
7+
"flag"
8+
"fmt"
9+
"io"
10+
"net/http"
11+
"os"
12+
"strings"
13+
"time"
14+
15+
"github.com/certusone/wormhole/node/pkg/common"
16+
"github.com/certusone/wormhole/node/pkg/p2p"
17+
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
18+
19+
ipfslog "github.com/ipfs/go-log/v2"
20+
pubsub "github.com/libp2p/go-libp2p-pubsub"
21+
"go.uber.org/zap"
22+
"google.golang.org/protobuf/proto"
23+
)
24+
25+
var (
26+
pubKey string
27+
url string
28+
timeout time.Duration
29+
p2pNetworkID string
30+
p2pBootstrap string
31+
p2pPort uint
32+
nodeKeyPath string
33+
logLevel string
34+
)
35+
36+
func main() {
37+
38+
flag.StringVar(&pubKey, "pubKey", "", "A guardian public key")
39+
flag.StringVar(&url, "url", "", "The public web url of a guardian")
40+
flag.DurationVar(&timeout, "timeout", 15*time.Second, "The duration to wait for a heartbeat and observations")
41+
flag.StringVar(&p2pNetworkID, "network", "/wormhole/mainnet/2", "P2P network identifier")
42+
flag.StringVar(&p2pBootstrap, "bootstrap", "/dns4/wormhole-v2-mainnet-bootstrap.xlabs.xyz/udp/8999/quic/p2p/12D3KooWNQ9tVrcb64tw6bNs2CaNrUGPM7yRrKvBBheQ5yCyPHKC,/dns4/wormhole.mcf.rocks/udp/8999/quic/p2p/12D3KooWDZVv7BhZ8yFLkarNdaSWaB43D6UbQwExJ8nnGAEmfHcU,/dns4/wormhole-v2-mainnet-bootstrap.staking.fund/udp/8999/quic/p2p/12D3KooWG8obDX9DNi1KUwZNu9xkGwfKqTp2GFwuuHpWZ3nQruS1", "The list of bootstrap peers (comma-separate) to connect to for gossip network tests. This can be useful to test a particular bootstrap peer.")
43+
flag.UintVar(&p2pPort, "port", p2p.DefaultPort, "P2P UDP listener port")
44+
flag.StringVar(&nodeKeyPath, "nodeKeyPath", "/tmp/health_check.key", "A libp2p node key. Will be created if it does not exist.")
45+
flag.StringVar(&logLevel, "logLevel", "error", "The logging level. Valid values are error, warn, info, and debug.")
46+
flag.Parse()
47+
48+
lvl, err := ipfslog.LevelFromString(logLevel)
49+
if err != nil {
50+
fmt.Println("Invalid log level")
51+
os.Exit(1)
52+
}
53+
logger := ipfslog.Logger("health-check").Desugar()
54+
ipfslog.SetAllLoggers(lvl)
55+
rootCtx, rootCtxCancel := context.WithCancel(context.Background())
56+
defer rootCtxCancel()
57+
58+
if pubKey != "" {
59+
priv, err := common.GetOrCreateNodeKey(logger, nodeKeyPath)
60+
if err != nil {
61+
logger.Fatal("Failed to load node key", zap.Error(err))
62+
}
63+
guardianPubKey, err := hex.DecodeString(strings.TrimPrefix(pubKey, "0x"))
64+
if err != nil {
65+
logger.Fatal("Failed to decode guardian public key", zap.Error(err))
66+
}
67+
logger.Info("Connecting to bootstrap peer(s)", zap.String("p2pBootstrap", p2pBootstrap))
68+
localContext, localCancel := context.WithCancel(rootCtx)
69+
defer localCancel()
70+
hbReceived := false
71+
observationsReceived := 0
72+
components := p2p.DefaultComponents()
73+
components.Port = p2pPort
74+
host, err := p2p.NewHost(logger, localContext, p2pNetworkID, p2pBootstrap, components, priv)
75+
if err != nil {
76+
logger.Fatal("failed to create host", zap.String("p2pBootstrap", p2pBootstrap), zap.Error(err))
77+
}
78+
79+
ps, err := pubsub.NewGossipSub(localContext, host)
80+
if err != nil {
81+
logger.Fatal("failed to create subscription", zap.String("p2pBootstrap", p2pBootstrap), zap.Error(err))
82+
}
83+
84+
topic := fmt.Sprintf("%s/%s", p2pNetworkID, "broadcast")
85+
topicHandle, err := ps.Join(topic)
86+
if err != nil {
87+
logger.Fatal("failed to join topic", zap.String("p2pBootstrap", p2pBootstrap), zap.Error(err))
88+
}
89+
sub, err := topicHandle.Subscribe()
90+
if err != nil {
91+
logger.Fatal("failed to subscribe to topic", zap.String("p2pBootstrap", p2pBootstrap), zap.Error(err))
92+
}
93+
go func() {
94+
for {
95+
envelope, err := sub.Next(localContext)
96+
if err != nil {
97+
logger.Info("failed to receive pubsub message", zap.Error(err))
98+
break
99+
}
100+
var msg gossipv1.GossipMessage
101+
err = proto.Unmarshal(envelope.Data, &msg)
102+
if err != nil {
103+
logger.Info("received invalid message",
104+
zap.Binary("data", envelope.Data),
105+
zap.String("from", envelope.GetFrom().String()))
106+
continue
107+
}
108+
switch m := msg.Message.(type) {
109+
case *gossipv1.GossipMessage_SignedHeartbeat:
110+
logger.Debug("received heartbeat")
111+
if !hbReceived && bytes.Equal(m.SignedHeartbeat.GuardianAddr, guardianPubKey) {
112+
hbReceived = true
113+
}
114+
case *gossipv1.GossipMessage_SignedObservation:
115+
logger.Debug("received observation")
116+
if bytes.Equal(m.SignedObservation.Addr, guardianPubKey) {
117+
observationsReceived++
118+
}
119+
}
120+
}
121+
// Start shutdown
122+
logger.Debug("Shutting down...")
123+
sub.Cancel()
124+
if err := topicHandle.Close(); err != nil {
125+
logger.Info("Error closing the broadcast topic", zap.Error(err))
126+
}
127+
if err := host.Close(); err != nil {
128+
logger.Info("Error closing the host", zap.Error(err))
129+
}
130+
// End shutdown
131+
}()
132+
time.Sleep(timeout)
133+
// Cancel local context to break out of sub.Next()
134+
localCancel()
135+
logger.Info("local context cancelled")
136+
137+
if hbReceived {
138+
fmt.Println("✅ guardian heartbeat received")
139+
} else {
140+
fmt.Println("❌ NO HEARTBEAT RECEIVED")
141+
}
142+
if observationsReceived > 0 {
143+
fmt.Printf("✅ %d observations received\n", observationsReceived)
144+
} else {
145+
fmt.Println("❌ NO OBSERVATIONS RECEIVED")
146+
}
147+
} else {
148+
fmt.Println("ℹ️ --pubKey not defined, skipping gossip checks")
149+
}
150+
151+
if url != "" {
152+
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
153+
logger.Fatal("url must start with http:// or https://")
154+
}
155+
hbSuccess := false
156+
url = strings.TrimSuffix(url, "/")
157+
logger.Info("Testing http services")
158+
res, err := http.Get(fmt.Sprintf("%s/v1/heartbeats", url))
159+
if err != nil {
160+
logger.Info("error fetching heartbeats", zap.Error(err))
161+
} else {
162+
if res.StatusCode == 200 {
163+
body, err := io.ReadAll(res.Body)
164+
if err != nil {
165+
logger.Info("error reading body", zap.Error(err))
166+
} else {
167+
if string(body[:10]) == "{\"entries\"" {
168+
hbSuccess = true
169+
}
170+
}
171+
} else {
172+
logger.Info("bad status fetching heartbeats", zap.Int("status", res.StatusCode))
173+
}
174+
}
175+
if hbSuccess {
176+
fmt.Println("✅ /v1/heartbeats")
177+
} else {
178+
fmt.Println("❌ /v1/heartbeats")
179+
}
180+
} else {
181+
fmt.Println("ℹ️ --url not defined, skipping web checks")
182+
}
183+
184+
rootCtxCancel()
185+
logger.Info("root context cancelled, exiting...")
186+
}

0 commit comments

Comments
 (0)