Skip to content

Commit f92882f

Browse files
authored
Merge pull request #1020 from b0a7/topic/externalModeAlerts
external mode alerts
2 parents 4524f06 + 5a58f1e commit f92882f

File tree

4 files changed

+140
-23
lines changed

4 files changed

+140
-23
lines changed

rocketpool/node/node.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/rocket-pool/smartnode/rocketpool/node/collectors"
1818
"github.com/rocket-pool/smartnode/shared/services"
1919
"github.com/rocket-pool/smartnode/shared/services/alerting"
20+
"github.com/rocket-pool/smartnode/shared/services/connectivity"
2021
"github.com/rocket-pool/smartnode/shared/services/state"
2122
"github.com/rocket-pool/smartnode/shared/services/wallet/keystore/lighthouse"
2223
"github.com/rocket-pool/smartnode/shared/services/wallet/keystore/nimbus"
@@ -189,7 +190,8 @@ func run(c *cli.Context) error {
189190
if err != nil {
190191
return err
191192
}
192-
checkPorts, err := newCheckPortConnectivity(c, log.NewColorLogger(CheckPortConnectivityColor))
193+
var checkPorts *connectivity.CheckPortConnectivity
194+
checkPorts, err = connectivity.NewCheckPortConnectivity(c, cfg, log.NewColorLogger(CheckPortConnectivityColor))
193195
if err != nil {
194196
return err
195197
}
@@ -326,7 +328,7 @@ func run(c *cli.Context) error {
326328
time.Sleep(taskCooldown)
327329

328330
// Run the port connectivity check
329-
if err := checkPorts.run(); err != nil {
331+
if err := checkPorts.Run(); err != nil {
330332
errorLog.Println(err)
331333
}
332334
time.Sleep(taskCooldown)

shared/services/config/alertmanager-config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ func (cfg *AlertmanagerConfig) UpdateConfigurationFiles(configPath string) error
372372
if err != nil {
373373
return fmt.Errorf("error processing alerting rules template: %w", err)
374374
}
375+
375376
return nil
376377
}
377378

rocketpool/node/check-port-connectivity.go renamed to shared/services/connectivity/check-port-connectivity.go

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package node
1+
package connectivity
22

33
import (
44
"context"
@@ -13,9 +13,9 @@ import (
1313

1414
"github.com/urfave/cli"
1515

16-
"github.com/rocket-pool/smartnode/shared/services"
1716
"github.com/rocket-pool/smartnode/shared/services/alerting"
18-
"github.com/rocket-pool/smartnode/shared/services/config"
17+
cfg "github.com/rocket-pool/smartnode/shared/services/config"
18+
cfgtypes "github.com/rocket-pool/smartnode/shared/types/config"
1919
"github.com/rocket-pool/smartnode/shared/utils/log"
2020
)
2121

@@ -36,36 +36,45 @@ var publicIPResolvers = []struct {
3636
}
3737

3838
// Check port connectivity task
39-
type checkPortConnectivity struct {
39+
type CheckPortConnectivity struct {
4040
c *cli.Context
4141
log log.ColorLogger
42-
cfg *config.RocketPoolConfig
42+
cfg *cfg.RocketPoolConfig
4343

4444
// Track previous state to avoid flooding with repeated alerts
4545
wasEth1PortOpen bool
4646
wasBeaconP2POpen bool
47+
48+
// Function pointers for network and alerting actions (to facilitate testing)
49+
getPublicIP func() (string, error)
50+
isPortReachableNATReflection func(string, uint16) bool
51+
isPortReachableExternalService func(uint16) (bool, string, error)
52+
alertEth1P2PPortNotOpen func(*cfg.RocketPoolConfig, uint16) error
53+
alertBeaconP2PPortNotOpen func(*cfg.RocketPoolConfig, uint16) error
4754
}
4855

4956
// Create check port connectivity task
50-
func newCheckPortConnectivity(c *cli.Context, logger log.ColorLogger) (*checkPortConnectivity, error) {
51-
cfg, err := services.GetConfig(c)
52-
if err != nil {
53-
return nil, err
54-
}
55-
56-
return &checkPortConnectivity{
57+
func NewCheckPortConnectivity(c *cli.Context, config *cfg.RocketPoolConfig, logger log.ColorLogger) (*CheckPortConnectivity, error) {
58+
return &CheckPortConnectivity{
5759
c: c,
5860
log: logger,
59-
cfg: cfg,
61+
cfg: config,
6062
// Assume ports are open at startup to avoid spurious alerts on first cycle
6163
wasEth1PortOpen: true,
6264
wasBeaconP2POpen: true,
65+
66+
// Default implementations
67+
getPublicIP: getPublicIP,
68+
isPortReachableNATReflection: isPortReachableNATReflection,
69+
isPortReachableExternalService: isPortReachableExternalService,
70+
alertEth1P2PPortNotOpen: alerting.AlertEth1P2PPortNotOpen,
71+
alertBeaconP2PPortNotOpen: alerting.AlertBeaconP2PPortNotOpen,
6372
}, nil
6473
}
6574

6675
// Check whether the configured execution/consensus client P2P ports are
6776
// reachable from the internet. Sends an alert the first time either port is detected as closed.
68-
func (t *checkPortConnectivity) run() error {
77+
func (t *CheckPortConnectivity) Run() error {
6978
if t.cfg.Alertmanager.EnableAlerting.Value != true {
7079
return nil
7180
}
@@ -74,18 +83,27 @@ func (t *checkPortConnectivity) run() error {
7483
}
7584
t.log.Print("Checking port connectivity...")
7685

86+
// EC and CC are always both local or both external - mixed configurations are rejected during validation.
87+
// We don't check external clients because the node operator is responsible for their
88+
// connectivity, and they may be behind a different firewall or NAT entirely.
89+
isLocalMode := t.cfg.ExecutionClientMode.Value.(cfgtypes.Mode) == cfgtypes.Mode_Local
90+
if !isLocalMode {
91+
return nil
92+
}
93+
7794
ecOpen := false
7895
ccOpen := false
7996
ecP2PPort := t.cfg.ExecutionCommon.P2pPort.Value.(uint16)
8097
ccP2PPort := t.cfg.ConsensusCommon.P2pPort.Value.(uint16)
81-
publicIP, err := getPublicIP()
98+
publicIP, err := t.getPublicIP()
8299
if err == nil {
83-
ecOpen = isPortReachableNATReflection(publicIP, ecP2PPort)
84-
ccOpen = isPortReachableNATReflection(publicIP, ccP2PPort)
100+
ecOpen = t.isPortReachableNATReflection(publicIP, ecP2PPort)
101+
ccOpen = t.isPortReachableNATReflection(publicIP, ccP2PPort)
85102
}
103+
86104
if !ecOpen {
87105
// Fallback to using an external service
88-
ecOpen, _, err = isPortReachableExternalService(ecP2PPort)
106+
ecOpen, _, err = t.isPortReachableExternalService(ecP2PPort)
89107
if err != nil {
90108
return fmt.Errorf("error checking port connectivity: %w", err)
91109
}
@@ -99,14 +117,15 @@ func (t *checkPortConnectivity) run() error {
99117
if t.wasEth1PortOpen {
100118
t.log.Printlnf("WARNING: Execution client P2P port %d is not accessible from the internet.", ecP2PPort)
101119
}
102-
if err := alerting.AlertEth1P2PPortNotOpen(t.cfg, ecP2PPort); err != nil {
120+
if err := t.alertEth1P2PPortNotOpen(t.cfg, ecP2PPort); err != nil {
103121
t.log.Printlnf("WARNING: Could not send Eth1P2PPortNotOpen alert: %s", err.Error())
104122
}
105123
}
106124
t.wasEth1PortOpen = ecOpen
125+
107126
if !ccOpen {
108127
// Fallback to using an external service
109-
ccOpen, _, err = isPortReachableExternalService(ccP2PPort)
128+
ccOpen, _, err = t.isPortReachableExternalService(ccP2PPort)
110129
if err != nil {
111130
return fmt.Errorf("error checking port connectivity: %w", err)
112131
}
@@ -120,7 +139,7 @@ func (t *checkPortConnectivity) run() error {
120139
if t.wasBeaconP2POpen {
121140
t.log.Printlnf("WARNING: Consensus client P2P port %d is not accessible from the internet.", ccP2PPort)
122141
}
123-
if err := alerting.AlertBeaconP2PPortNotOpen(t.cfg, ccP2PPort); err != nil {
142+
if err := t.alertBeaconP2PPortNotOpen(t.cfg, ccP2PPort); err != nil {
124143
t.log.Printlnf("WARNING: Could not send BeaconP2PPortNotOpen alert: %s", err.Error())
125144
}
126145
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package connectivity
2+
3+
import (
4+
"testing"
5+
6+
cfg "github.com/rocket-pool/smartnode/shared/services/config"
7+
cfgtypes "github.com/rocket-pool/smartnode/shared/types/config"
8+
log "github.com/rocket-pool/smartnode/shared/utils/log"
9+
)
10+
11+
// TestCheckPortConnectivity_Run verifies that the port connectivity task correctly
12+
// decides whether to perform network checks based on the client modes and global
13+
// alerting settings.
14+
func TestCheckPortConnectivity_Run(t *testing.T) {
15+
logger := log.NewColorLogger(0)
16+
tests := []struct {
17+
name string
18+
ecMode cfgtypes.Mode
19+
ccMode cfgtypes.Mode
20+
enableAlerting bool
21+
portAlertingEnabled bool
22+
expectNetCalls bool
23+
}{
24+
{
25+
name: "local EC + local CC -> performs checks",
26+
ecMode: cfgtypes.Mode_Local,
27+
ccMode: cfgtypes.Mode_Local,
28+
enableAlerting: true,
29+
portAlertingEnabled: true,
30+
expectNetCalls: true,
31+
},
32+
{
33+
name: "external EC + external CC -> skips checks",
34+
ecMode: cfgtypes.Mode_External,
35+
ccMode: cfgtypes.Mode_External,
36+
enableAlerting: true,
37+
portAlertingEnabled: true,
38+
expectNetCalls: false,
39+
},
40+
{
41+
name: "alerting disabled -> skips checks",
42+
ecMode: cfgtypes.Mode_Local,
43+
ccMode: cfgtypes.Mode_Local,
44+
enableAlerting: false,
45+
portAlertingEnabled: true,
46+
expectNetCalls: false,
47+
},
48+
}
49+
50+
for _, tc := range tests {
51+
t.Run(tc.name, func(t *testing.T) {
52+
config := cfg.NewRocketPoolConfig("", false)
53+
config.ExecutionClientMode.Value = tc.ecMode
54+
config.ConsensusClientMode.Value = tc.ccMode
55+
config.Alertmanager.EnableAlerting.Value = tc.enableAlerting
56+
config.Alertmanager.AlertEnabled_PortConnectivityCheck.Value = tc.portAlertingEnabled
57+
58+
netCallsMade := false
59+
mockGetPublicIP := func() (string, error) {
60+
netCallsMade = true
61+
return "1.2.3.4", nil
62+
}
63+
mockIsPortReachable := func(host string, port uint16) bool {
64+
netCallsMade = true
65+
return true
66+
}
67+
mockExternalCheck := func(port uint16) (bool, string, error) {
68+
netCallsMade = true
69+
return true, "Success", nil
70+
}
71+
mockAlert := func(cfg *cfg.RocketPoolConfig, port uint16) error {
72+
return nil
73+
}
74+
75+
task := &CheckPortConnectivity{
76+
cfg: config,
77+
log: logger,
78+
getPublicIP: mockGetPublicIP,
79+
isPortReachableNATReflection: mockIsPortReachable,
80+
isPortReachableExternalService: mockExternalCheck,
81+
alertEth1P2PPortNotOpen: mockAlert,
82+
alertBeaconP2PPortNotOpen: mockAlert,
83+
}
84+
85+
err := task.Run()
86+
if err != nil {
87+
t.Fatalf("unexpected error: %v", err)
88+
}
89+
90+
if netCallsMade != tc.expectNetCalls {
91+
t.Errorf("expected network calls: %v, got: %v", tc.expectNetCalls, netCallsMade)
92+
}
93+
})
94+
}
95+
}

0 commit comments

Comments
 (0)