-
Notifications
You must be signed in to change notification settings - Fork 177
/
engine.go
127 lines (111 loc) · 3.73 KB
/
engine.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package ping
import (
"time"
"github.com/rs/zerolog"
"github.com/onflow/flow-go/engine"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/model/flow/filter"
"github.com/onflow/flow-go/module"
"github.com/onflow/flow-go/network/p2p"
"github.com/onflow/flow-go/state/protocol"
)
type Engine struct {
unit *engine.Unit
log zerolog.Logger
state protocol.State
me module.Local
metrics module.PingMetrics
pingEnabled bool
pingInterval time.Duration
middleware *p2p.Middleware
nodeInfo map[flow.Identifier]string // additional details about a node such as operator name
}
func New(
log zerolog.Logger,
state protocol.State,
me module.Local,
metrics module.PingMetrics,
pingEnabled bool,
mw *p2p.Middleware,
nodeInfoFile string,
) (*Engine, error) {
eng := &Engine{
unit: engine.NewUnit(),
log: log.With().Str("engine", "ping").Logger(),
state: state,
me: me,
metrics: metrics,
pingEnabled: pingEnabled,
pingInterval: time.Minute,
middleware: mw,
}
// if a node info file is provided, it is read and the additional node information is reported as part of the ping metric
if nodeInfoFile != "" {
nodeInfo, err := readExtraNodeInfoJSON(nodeInfoFile)
if err != nil {
log.Error().Err(err).Str("node_info_file", nodeInfoFile).Msg("failed to read node info file")
} else {
eng.nodeInfo = nodeInfo
log.Debug().Str("node_info_file", nodeInfoFile).Msg("using node info file")
}
} else {
// initialize nodeInfo with an empty map
eng.nodeInfo = make(map[flow.Identifier]string)
// the node info file is not mandatory and should not stop the Ping engine from running
log.Trace().Msg("no node info file specified")
}
return eng, nil
}
// Ready returns a ready channel that is closed once the engine has fully
// started. For the ingestion engine, we consider the engine up and running
// upon initialization.
func (e *Engine) Ready() <-chan struct{} {
// only launch when ping is enabled
if e.pingEnabled {
e.unit.Launch(e.startPing)
}
e.log.Info().Bool("ping enabled", e.pingEnabled).Msg("ping enabled")
return e.unit.Ready()
}
// Done returns a done channel that is closed once the engine has fully stopped.
// For the ingestion engine, it only waits for all submit goroutines to end.
func (e *Engine) Done() <-chan struct{} {
return e.unit.Done()
}
func (e *Engine) startPing() {
peers, err := e.state.Final().Identities(filter.Not(filter.HasNodeID(e.me.NodeID())))
if err != nil {
e.log.Err(err).Msg("could not get identity list")
return
}
pingInterval := time.Second * 60
// for each peer, send a ping every ping interval
for i, peer := range peers {
func(peer *flow.Identity, delay time.Duration) {
e.log.Info().Str("peer", peer.String()).Dur("interval", pingInterval).Msg("periodically ping node")
e.unit.LaunchPeriodically(func() {
e.pingNode(peer)
}, pingInterval, delay)
}(peer, time.Duration(i)*time.Second)
}
}
// pingNode pings the given peer and updates the metrics with the result and the additional node information
func (e *Engine) pingNode(peer *flow.Identity) {
id := peer.ID()
reachable := e.pingAddress(id)
info := e.nodeInfo[id]
e.metrics.NodeReachable(peer, info, reachable)
}
// pingAddress sends a ping request to the given address, and block until either receive
// a ping respond then return true, or hitting a timeout and return false.
// if there is other unknown error, return false
func (e *Engine) pingAddress(target flow.Identifier) bool {
// ignore the ping duration for now
// ping will timeout in libp2p.PingTimeoutSecs seconds
_, err := e.middleware.Ping(target)
if err != nil {
e.log.Debug().Err(err).Str("target", target.String()).Msg("failed to ping")
return false
}
return true
}