Skip to content

Commit

Permalink
tests: Add e2e test cases for lido-exporter service integration
Browse files Browse the repository at this point in the history
* tests: Add e2e test cases for lido-exporter in sedge

* tests: Update tests

* tests: Update assertions

* tests: Update tests

* tests: Update assertions

* tests: Add checks

* tests: Update lido service test case

* style: Update comments

* style: Adjust comments
  • Loading branch information
khalifaa55 authored Oct 15, 2024
1 parent 1f69582 commit cb6e42e
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 14 deletions.
28 changes: 23 additions & 5 deletions cli/monitoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cli
import (
"errors"
"fmt"
"math/big"
"time"

log "github.com/sirupsen/logrus"
Expand All @@ -26,7 +27,7 @@ import (
"github.com/NethermindEth/sedge/internal/common"
"github.com/NethermindEth/sedge/internal/monitoring"
lidoExporter "github.com/NethermindEth/sedge/internal/monitoring/services/lido_exporter"
"github.com/NethermindEth/sedge/internal/ui"
"github.com/NethermindEth/sedge/internal/utils"
)

func MonitoringCmd(mgr MonitoringManager) *cobra.Command {
Expand All @@ -46,6 +47,11 @@ func InitSubCmd(mgr MonitoringManager) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize the monitoring stack",
Long: `This command initializes the monitoring stack (Grafana, Prometheus, etc.) for Lido CSM or general node monitoring.
The monitoring stack includes:
- Grafana dashboards for real-time monitoring of Lido CSM node metrics.
- Prometheus for collecting and displaying key metrics about your node operations.`,
}
cmd.AddCommand(DefaultSubCmd(mgr, additionalServices))
cmd.AddCommand(LidoSubCmd(mgr, additionalServices))
Expand All @@ -68,14 +74,26 @@ func LidoSubCmd(mgr MonitoringManager, additionalServices []monitoring.ServiceAP
cmd := &cobra.Command{
Use: "lido",
Short: "Configure Lido CSM Node monitoring",
Long: "Configure Lido CSM Node monitoring using Prometheus, Grafana, Node Exporter, and Lido Exporter",
Long: "Configure Lido CSM node monitoring (Prometheus, Grafana, Node Exporter,Lido Exporter)",
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
if lido.NodeOperatorID == "" && lido.RewardAddress == "" {
return errors.New("Node Operator ID or Reward Address is required")
}
if err := ui.EthAddressValidator(rewardAddress, false); err != nil && len(args) != 0 {
return err
if lido.NodeOperatorID != "" {
var nodeOperatorIDBigInt *big.Int
var ok bool
nodeOperatorIDBigInt, ok = new(big.Int).SetString(lido.NodeOperatorID, 10)
if !ok {
return errors.New("Failed to convert Node Operator ID to big.Int")
}
if nodeOperatorIDBigInt.Sign() < 0 {
return errors.New("Node Operator ID cannot be negative")
}
} else {
if !utils.IsAddress(rewardAddress) {
return errors.New("Invalid reward address")
}
}
additionalServices = append(additionalServices, lidoExporter.NewLidoExporter(*lido))
return nil
Expand All @@ -100,7 +118,7 @@ func DefaultSubCmd(mgr MonitoringManager, additionalServices []monitoring.Servic
cmd := &cobra.Command{
Use: "default",
Short: "Default monitoring configuration",
Long: "Default monitoring configuration using Prometheus, Grafana, and Node Exporter",
Long: "Default monitoring configuration (Prometheus, Grafana, Node Exporter)",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return InitMonitoring(true, true, mgr, nil)
Expand Down
41 changes: 39 additions & 2 deletions e2e/sedge/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func checkMonitoringStackNotInstalled(t *testing.T) {
// checkMonitoringStackContainers checks that the monitoring stack containers are running
func checkMonitoringStackContainers(t *testing.T, containerNames ...string) {
t.Logf("Checking monitoring stack containers")
containerNames = append(containerNames, "sedge_grafana", "sedge_prometheus", "sedge_node_exporter")
containerNames = append(containerNames, "sedge_grafana", "sedge_prometheus", "sedge_node_exporter", "sedge_alertmanager")
checkContainerRunning(t, containerNames...)
}

Expand Down Expand Up @@ -168,7 +168,7 @@ func checkGrafanaHealth(t *testing.T) {
// checkMonitoringStackContainersNotRunning checks that the monitoring stack containers are not running
func checkMonitoringStackContainersNotRunning(t *testing.T, containerNames ...string) {
t.Logf("Checking monitoring stack containers are not running")
containerNames = append(containerNames, "sedge_grafana", "sedge_prometheus", "sedge_node_exporter")
containerNames = append(containerNames, "sedge_grafana", "sedge_prometheus", "sedge_node_exporter", "sedge_alertmanager")
checkContainerNotExisting(t, containerNames...)
}

Expand All @@ -191,3 +191,40 @@ func checkContainerNotExisting(t *testing.T, containerNames ...string) {
assert.Error(t, err)
}
}

// checkMonitoringStackDir checks that the monitoring stack directory exists and contains the docker-compose file
func checkPrometheusDir(t *testing.T) {
t.Logf("Checking prometheus directory")
// Check monitoring folder exists
dataDir, err := dataDirPath()
if err != nil {
t.Fatal(err)
}
prometheusDir := filepath.Join(dataDir, "monitoring", "prometheus")
assert.DirExists(t, prometheusDir)

assert.DirExists(t, filepath.Join(prometheusDir, "rules"))
// Check monitoring docker-compose file exists
assert.FileExists(t, filepath.Join(prometheusDir, "alertmanager", "alertmanager.yml"))
}

// checkContainerRunning checks that the given containers are running
func checkContainerNotRunning(t *testing.T, containerNames ...string) {
cli, err := client.NewClientWithOpts(
client.FromEnv,
client.WithAPIVersionNegotiation(),
)
if err != nil {
t.Fatalf("Failed to create Docker client: %v", err)
}
defer cli.Close()

dockerServiceManager := services.NewDockerServiceManager(cli)

for _, containerName := range containerNames {
t.Logf("Checking %s container is not running", containerName)
isRunning, err := dockerServiceManager.IsRunning(containerName)
require.NoError(t, err)
assert.False(t, isRunning, "%s container should not be running", containerName)
}
}
135 changes: 130 additions & 5 deletions e2e/sedge/monitoring_stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func skipIfNotAMD64(t *testing.T) {
}
}

var grafanaOnCallContainers = []string{"engine", "celery", "redis", "oncall_setup"}

// TestMonitoringStack_Init tests that the monitoring stack is not initialized if the user does not run the init-monitoring command
func TestE2E_MonitoringStack_NotInitialized(t *testing.T) {
skipIfNotAMD64(t)
Expand All @@ -50,7 +52,7 @@ func TestE2E_MonitoringStack_NotInitialized(t *testing.T) {
assert.NoError(t, runErr)

checkMonitoringStackNotInstalled(t)
checkMonitoringStackContainersNotRunning(t)
checkMonitoringStackContainersNotRunning(t, grafanaOnCallContainers...)
},
)
// Run test case
Expand All @@ -77,7 +79,8 @@ func TestE2E_MonitoringStack_Init(t *testing.T) {
func(t *testing.T, dataDirPath string) {
assert.NoError(t, runErr)
checkMonitoringStackDir(t)
checkMonitoringStackContainers(t)
checkPrometheusDir(t)
checkMonitoringStackContainers(t, grafanaOnCallContainers...)
checkPrometheusTargetsUp(t, "sedge_node_exporter:9100")
checkGrafanaHealth(t)
},
Expand Down Expand Up @@ -124,6 +127,7 @@ func TestE2E_MonitoringStack_NotReinstalled(t *testing.T) {
assert.NoError(t, runErr)

checkMonitoringStackDir(t)
checkPrometheusDir(t)
checkMonitoringStackContainers(t)
checkGrafanaHealth(t)
newGrafanaContainerID, err := getContainerIDByName("sedge_grafana")
Expand Down Expand Up @@ -166,7 +170,7 @@ func TestE2E_MonitoringStack_Clean(t *testing.T) {
assert.NoDirExists(t, dataDirPath)

// Check that monitoring stack containers are removed
checkMonitoringStackContainersNotRunning(t)
checkMonitoringStackContainersNotRunning(t, grafanaOnCallContainers...)
},
)
// Run test case
Expand Down Expand Up @@ -196,14 +200,14 @@ func TestE2E_MonitoringStack_CleanNonExistent(t *testing.T) {
assert.NoDirExists(t, dataDirPath)

// Check that monitoring stack containers don't exist
checkMonitoringStackContainersNotRunning(t)
checkMonitoringStackContainersNotRunning(t, grafanaOnCallContainers...)
},
)
// Run test case
e2eTest.run()
}

func TestE2E_MonitoringStack_InitLido(t *testing.T) {
func TestE2E_MonitoringStack_InitLido_ValidID(t *testing.T) {
skipIfNotAMD64(t)
// Test context
var (
Expand All @@ -222,6 +226,7 @@ func TestE2E_MonitoringStack_InitLido(t *testing.T) {
func(t *testing.T, dataDirPath string) {
assert.NoError(t, runErr)
checkMonitoringStackDir(t)
checkPrometheusDir(t)
checkMonitoringStackContainers(t, "sedge_lido_exporter")
checkPrometheusTargetsUp(t, "sedge_lido_exporter:8080", "sedge_node_exporter:9100")
checkGrafanaHealth(t)
Expand Down Expand Up @@ -262,3 +267,123 @@ func TestE2E_MonitoringStack_CleanLido(t *testing.T) {
// Run test case
e2eTest.run()
}

func TestE2E_MonitoringStack_InitLido_InvalidAddress(t *testing.T) {
skipIfNotAMD64(t)
// Test context
var (
runErr error
)
// Build test case
e2eTest := newE2ESedgeTestCase(
t,
// Arrange
func(t *testing.T, sedgePath string) error {
return base.RunCommand(t, sedgePath, "sedge", "monitoring", "clean")
},
// Act
func(t *testing.T, binaryPath string, dataDirPath string) {
runErr = base.RunCommand(t, binaryPath, "sedge", "monitoring", "init", "lido", "--reward-address", "lol_what_a_reward_address")
},
// Assert
func(t *testing.T, dataDirPath string) {
assert.Error(t, runErr)

checkMonitoringStackNotInstalled(t)
checkMonitoringStackContainersNotRunning(t, grafanaOnCallContainers...)
},
)
// Run test case
e2eTest.run()
}

func TestE2E_MonitoringStack_InitLido_OccupiedPort(t *testing.T) {
skipIfNotAMD64(t)
// Test context
var (
runErr error
)
// Build test case
e2eTest := newE2ESedgeTestCase(
t,
// Arrange
nil,
// Act
func(t *testing.T, binaryPath string, dataDirPath string) {
runErr = base.RunCommand(t, binaryPath, "sedge", "monitoring", "init", "lido", "--node-operator-id", "10", "--port", "9090")
},
// Assert
func(t *testing.T, dataDirPath string) {
assert.Error(t, runErr)
checkContainerNotRunning(t, "sedge_lido_exporter")
},
)
// Run test case
e2eTest.run()
}

func TestE2E_MonitoringStack_InitLido(t *testing.T) {
skipIfNotAMD64(t)
// Test context
var (
runErr error
)
// Build test case
e2eTest := newE2ESedgeTestCase(
t,
// Arrange
nil,
// Act
func(t *testing.T, binaryPath string, dataDirPath string) {
runErr = base.RunCommand(t, binaryPath, "sedge", "monitoring", "init", "lido",
"--rpc-endpoints", "https://endpoints.omniatech.io/v1/eth/holesky/public,https://ethereum-holesky-rpc.publicnode.com",
"--ws-endpoints", "https://ethereum-holesky-rpc.publicnode.com,wss://ethereum-holesky-rpc.publicnode.com",
"--port", "9989",
"--scrape-time", "30s",
"--network", "holesky",
"--node-operator-id", "250",
"--reward-address", "0x22bA5CaFB5E26E6Fe51f330294209034013A5A4c",
)
},
// Assert
func(t *testing.T, dataDirPath string) {
assert.NoError(t, runErr)
checkMonitoringStackDir(t)
checkPrometheusDir(t)
checkMonitoringStackContainers(t, "sedge_lido_exporter")
checkPrometheusTargetsUp(t, "sedge_lido_exporter:9989", "sedge_node_exporter:9100")
checkGrafanaHealth(t)
},
)
// Run test case
e2eTest.run()
}

func TestE2E_MonitoringStack_InitLido_InvalidNodeID(t *testing.T) {
skipIfNotAMD64(t)
// Test context
var (
runErr error
)
// Build test case
e2eTest := newE2ESedgeTestCase(
t,
// Arrange
func(t *testing.T, sedgePath string) error {
return base.RunCommand(t, sedgePath, "sedge", "monitoring", "clean")
},
// Act
func(t *testing.T, binaryPath string, dataDirPath string) {
runErr = base.RunCommand(t, binaryPath, "sedge", "monitoring", "init", "lido", "--node-operator-id", "-1")
},
// Assert
func(t *testing.T, dataDirPath string) {
assert.Error(t, runErr)

checkMonitoringStackNotInstalled(t)
checkMonitoringStackContainersNotRunning(t, grafanaOnCallContainers...)
},
)
// Run test case
e2eTest.run()
}
11 changes: 10 additions & 1 deletion internal/monitoring/monitoring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -1974,7 +1975,15 @@ func TestUpdateEnvFile(t *testing.T) {
assert.NoError(t, err)
content, err := afero.ReadFile(fs, filepath.Join(manager.stack.Path(), ".env"))
assert.NoError(t, err)
assert.Equal(t, tt.expectedEnv, string(content))

// Normalize and sort the lines for comparison
expectedLines := strings.Split(strings.TrimSpace(tt.expectedEnv), "\n")
actualLines := strings.Split(strings.TrimSpace(string(content)), "\n")

sort.Strings(expectedLines)
sort.Strings(actualLines)

assert.Equal(t, expectedLines, actualLines)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion internal/monitoring/services/lido_exporter/dotenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package lido_exporter

var dotEnv map[string]string = map[string]string{
"LIDO_EXPORTER_IMAGE": "nethermindeth/lido-exporter:v1.0.1",
"LIDO_EXPORTER_PORT": "8080",
"LIDO_EXPORTER_PORT": "",
"LIDO_EXPORTER_NODE_OPERATOR_ID": "",
"LIDO_EXPORTER_REWARD_ADDRESS": "",
"LIDO_EXPORTER_NETWORK": "",
Expand Down
1 change: 1 addition & 0 deletions internal/monitoring/services/lido_exporter/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func NewLidoExporter(params LidoExporterParams) *LidoExporterService {
dotEnv["LIDO_EXPORTER_WS_ENDPOINTS"] = strings.Join(params.WSEndpoints, ",")
dotEnv["LIDO_EXPORTER_SCRAPE_TIME"] = params.ScrapeTime.String()
dotEnv["LIDO_EXPORTER_LOG_LEVEL"] = params.LogLevel
dotEnv["LIDO_EXPORTER_PORT"] = strconv.Itoa(int(params.Port))

return &LidoExporterService{
params: params,
Expand Down

0 comments on commit cb6e42e

Please sign in to comment.