From 7626663823a75dbf49f1aefe3bab70514b32fe3f Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 9 Sep 2024 03:01:17 +0300 Subject: [PATCH] chore: add node config generator --- pkg/constants/constants.go | 1 + pkg/fetchers/controller.go | 2 + pkg/fetchers/node_config_fetcher.go | 62 +++++++ .../node_config_fetcher_test.go} | 102 +++-------- pkg/generators/node_config_generator.go | 58 +++++++ pkg/generators/node_config_generator_test.go | 163 ++++++++++++++++++ pkg/node_handler.go | 4 +- pkg/queriers/node_config/querier.go | 88 ---------- 8 files changed, 311 insertions(+), 169 deletions(-) create mode 100644 pkg/fetchers/node_config_fetcher.go rename pkg/{queriers/node_config/querier_test.go => fetchers/node_config_fetcher_test.go} (53%) create mode 100644 pkg/generators/node_config_generator.go create mode 100644 pkg/generators/node_config_generator_test.go delete mode 100644 pkg/queriers/node_config/querier.go diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index b3a0521..4e1488a 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -30,6 +30,7 @@ const ( FetcherNameNodeStatus FetcherName = "node_status" FetcherNameCosmovisorVersion FetcherName = "cosmovisor_version" + FetcherNameNodeConfig FetcherName = "node_config" ) var ( diff --git a/pkg/fetchers/controller.go b/pkg/fetchers/controller.go index 16312cc..1b2414b 100644 --- a/pkg/fetchers/controller.go +++ b/pkg/fetchers/controller.go @@ -106,7 +106,9 @@ func (c *Controller) Fetch(ctx context.Context) ( c.Logger.Trace().Str("name", string(fetcher.Name())).Msg("Processing fetcher...") + mutex.Lock() fetchersStatus[fetcher.Name()] = FetcherProcessStatusProcessing + mutex.Unlock() fetcherData, fetcherQueries := fetcher.Get(ctx) diff --git a/pkg/fetchers/node_config_fetcher.go b/pkg/fetchers/node_config_fetcher.go new file mode 100644 index 0000000..ef82843 --- /dev/null +++ b/pkg/fetchers/node_config_fetcher.go @@ -0,0 +1,62 @@ +package fetchers + +import ( + "context" + grpcPkg "main/pkg/clients/grpc" + "main/pkg/constants" + "main/pkg/query_info" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + + "github.com/rs/zerolog" +) + +type NodeConfigFetcher struct { + gRPC *grpcPkg.Client + Logger zerolog.Logger + Tracer trace.Tracer +} + +func NewNodeConfigFetcher(logger zerolog.Logger, grpc *grpcPkg.Client, tracer trace.Tracer) *NodeConfigFetcher { + return &NodeConfigFetcher{ + Logger: logger.With().Str("component", "node_config_fetcher").Logger(), + gRPC: grpc, + Tracer: tracer, + } +} + +func (n *NodeConfigFetcher) Enabled() bool { + return n.gRPC != nil +} + +func (n *NodeConfigFetcher) Name() constants.FetcherName { + return constants.FetcherNameNodeConfig +} + +func (n *NodeConfigFetcher) Dependencies() []constants.FetcherName { + return []constants.FetcherName{} +} + +func (n *NodeConfigFetcher) Get(ctx context.Context) (interface{}, []query_info.QueryInfo) { + childCtx, span := n.Tracer.Start( + ctx, + "NodeConfigFetcher "+string(n.Name()), + trace.WithAttributes(attribute.String("node", string(n.Name()))), + ) + defer span.End() + + config, queryInfo, err := n.gRPC.GetNodeConfig(childCtx) + if err != nil { + n.Logger.Error().Err(err).Msg("Could not fetch node config") + return nil, []query_info.QueryInfo{queryInfo} + } + + if config == nil { + n.Logger.Debug(). + Msg("Node config is nil, probably chain does not implement the node config endpoint.") + return nil, []query_info.QueryInfo{queryInfo} + } + + return config, []query_info.QueryInfo{queryInfo} +} diff --git a/pkg/queriers/node_config/querier_test.go b/pkg/fetchers/node_config_fetcher_test.go similarity index 53% rename from pkg/queriers/node_config/querier_test.go rename to pkg/fetchers/node_config_fetcher_test.go index 10bf712..1e54055 100644 --- a/pkg/queriers/node_config/querier_test.go +++ b/pkg/fetchers/node_config_fetcher_test.go @@ -1,9 +1,10 @@ -package node_stats +package fetchers import ( "context" grpcPkg "main/pkg/clients/grpc" configPkg "main/pkg/config" + "main/pkg/constants" loggerPkg "main/pkg/logger" "main/pkg/tracing" "testing" @@ -19,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNodeConfigQuerierBase(t *testing.T) { +func TestNodeConfigFetcherBase(t *testing.T) { t.Parallel() config := configPkg.GrpcConfig{ @@ -28,11 +29,12 @@ func TestNodeConfigQuerierBase(t *testing.T) { logger := loggerPkg.GetNopLogger() tracer := tracing.InitNoopTracer() client := grpcPkg.NewClient(config, *logger, tracer) - querier := NewQuerier(*logger, client, tracer) - assert.True(t, querier.Enabled()) - assert.Equal(t, "node-config-querier", querier.Name()) + fetcher := NewNodeConfigFetcher(*logger, client, tracer) + assert.True(t, fetcher.Enabled()) + assert.Equal(t, constants.FetcherNameNodeConfig, fetcher.Name()) + assert.Empty(t, fetcher.Dependencies()) } -func TestNodeConfigQuerierFail(t *testing.T) { +func TestNodeConfigFetcherFail(t *testing.T) { t.Parallel() _, d := grpcmock.MockServerWithBufConn()(t) @@ -51,14 +53,14 @@ func TestNodeConfigQuerierFail(t *testing.T) { Tracer: tracer, } - querier := NewQuerier(*logger, client, tracer) - metrics, queryInfos := querier.Get(context.Background()) + fetcher := NewNodeConfigFetcher(*logger, client, tracer) + data, queryInfos := fetcher.Get(context.Background()) assert.Len(t, queryInfos, 1) assert.False(t, queryInfos[0].Success) - assert.Empty(t, metrics) + assert.Nil(t, data) } -func TestNodeConfigQuerierError(t *testing.T) { +func TestNodeConfigFetcherError(t *testing.T) { t.Parallel() _, d := grpcmock.MockServerWithBufConn()(t) @@ -77,14 +79,14 @@ func TestNodeConfigQuerierError(t *testing.T) { Tracer: tracer, } - querier := NewQuerier(*logger, client, tracer) - metrics, queryInfos := querier.Get(context.Background()) + fetcher := NewNodeConfigFetcher(*logger, client, tracer) + data, queryInfos := fetcher.Get(context.Background()) assert.Len(t, queryInfos, 1) assert.False(t, queryInfos[0].Success) - assert.Empty(t, metrics) + assert.Nil(t, data) } -func TestNodeConfigQuerierNotImplemented(t *testing.T) { +func TestNodeConfigFetcherNotImplemented(t *testing.T) { t.Parallel() _, d := grpcmock.MockServerWithBufConn()(t) @@ -103,52 +105,14 @@ func TestNodeConfigQuerierNotImplemented(t *testing.T) { Tracer: tracer, } - querier := NewQuerier(*logger, client, tracer) - metrics, queryInfos := querier.Get(context.Background()) + fetcher := NewNodeConfigFetcher(*logger, client, tracer) + data, queryInfos := fetcher.Get(context.Background()) assert.Len(t, queryInfos, 1) assert.True(t, queryInfos[0].Success) - assert.Empty(t, metrics) + assert.Nil(t, data) } -func TestNodeConfigQuerierInvalidResponse(t *testing.T) { - t.Parallel() - - _, d := grpcmock.MockServerWithBufConn( - grpcmock.RegisterServiceFromMethods(service.Method{ - ServiceName: "cosmos.base.node.v1beta1.Service", - MethodName: "Config", - MethodType: service.TypeUnary, - Input: &nodeTypes.ConfigRequest{}, - Output: &nodeTypes.ConfigResponse{}, - }), - func(s *grpcmock.Server) { - s.ExpectUnary("cosmos.base.node.v1beta1.Service/Config"). - Return(&nodeTypes.ConfigResponse{MinimumGasPrice: "test"}) - }, - )(t) - - logger := loggerPkg.GetNopLogger() - tracer := tracing.InitNoopTracer() - grpcConn, err := grpc.NewClient( - "localhost:9090", - grpc.WithContextDialer(d), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - require.NoError(t, err) - client := &grpcPkg.Client{ - Logger: *logger, - Client: grpcConn, - Tracer: tracer, - } - - querier := NewQuerier(*logger, client, tracer) - metrics, queryInfos := querier.Get(context.Background()) - assert.Len(t, queryInfos, 1) - assert.True(t, queryInfos[0].Success) - assert.Empty(t, metrics) -} - -func TestNodeConfigQuerierOk(t *testing.T) { +func TestNodeConfigFetcherOk(t *testing.T) { t.Parallel() _, d := grpcmock.MockServerWithBufConn( @@ -179,29 +143,9 @@ func TestNodeConfigQuerierOk(t *testing.T) { Tracer: tracer, } - querier := NewQuerier(*logger, client, tracer) - metrics, queryInfos := querier.Get(context.Background()) + fetcher := NewNodeConfigFetcher(*logger, client, tracer) + data, queryInfos := fetcher.Get(context.Background()) assert.Len(t, queryInfos, 1) assert.True(t, queryInfos[0].Success) - assert.Len(t, metrics, 4) - - pricesCount := metrics[0] - assert.Empty(t, pricesCount.Labels) - assert.InDelta(t, 2, pricesCount.Value, 0.01) - - firstPrice := metrics[1] - assert.Equal(t, map[string]string{ - "denom": "uatom", - }, firstPrice.Labels) - assert.InDelta(t, 0.1, firstPrice.Value, 0.01) - - secondPrice := metrics[2] - assert.Equal(t, map[string]string{ - "denom": "ustake", - }, secondPrice.Labels) - assert.InDelta(t, 0.2, secondPrice.Value, 0.01) - - haltHeight := metrics[3] - assert.Equal(t, map[string]string{}, haltHeight.Labels) - assert.InDelta(t, 123, haltHeight.Value, 0.01) + assert.NotNil(t, data) } diff --git a/pkg/generators/node_config_generator.go b/pkg/generators/node_config_generator.go new file mode 100644 index 0000000..98c2f63 --- /dev/null +++ b/pkg/generators/node_config_generator.go @@ -0,0 +1,58 @@ +package generators + +import ( + "main/pkg/constants" + "main/pkg/fetchers" + metricsPkg "main/pkg/metrics" + + "github.com/cosmos/cosmos-sdk/client/grpc/node" + cosmosTypes "github.com/cosmos/cosmos-sdk/types" +) + +type NodeConfigGenerator struct{} + +func NewNodeConfigGenerator() *NodeConfigGenerator { + return &NodeConfigGenerator{} +} + +func (g *NodeConfigGenerator) Get(state fetchers.State) []metricsPkg.MetricInfo { + statusRaw, ok := state[constants.FetcherNameNodeConfig] + if !ok || statusRaw == nil { + return []metricsPkg.MetricInfo{} + } + + config, ok := statusRaw.(*node.ConfigResponse) + if !ok { + panic("expected the state entry to be *node.ConfigResponse") + } + + coinsParsed, err := cosmosTypes.ParseDecCoins(config.MinimumGasPrice) + if err != nil { + panic(err) + } + + metrics := []metricsPkg.MetricInfo{} + metrics = append(metrics, metricsPkg.MetricInfo{ + MetricName: metricsPkg.MetricNameMinimumGasPricesCount, + Labels: map[string]string{}, + Value: float64(len(coinsParsed)), + }) + + for _, amount := range coinsParsed { + metrics = append(metrics, metricsPkg.MetricInfo{ + MetricName: metricsPkg.MetricNameMinimumGasPrice, + Labels: map[string]string{"denom": amount.Denom}, + Value: amount.Amount.MustFloat64(), + }) + } + + if config.HaltHeight > 0 { + metrics = append(metrics, metricsPkg.MetricInfo{ + MetricName: metricsPkg.MetricNameHaltHeight, + Labels: map[string]string{}, + Value: float64(config.HaltHeight), + }) + } + + return metrics +} diff --git a/pkg/generators/node_config_generator_test.go b/pkg/generators/node_config_generator_test.go new file mode 100644 index 0000000..a146095 --- /dev/null +++ b/pkg/generators/node_config_generator_test.go @@ -0,0 +1,163 @@ +package generators + +import ( + "context" + grpcPkg "main/pkg/clients/grpc" + "main/pkg/constants" + "main/pkg/fetchers" + loggerPkg "main/pkg/logger" + "main/pkg/tracing" + "testing" + + nodeTypes "github.com/cosmos/cosmos-sdk/client/grpc/node" + "go.nhat.io/grpcmock" + "go.nhat.io/grpcmock/service" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" +) + +func TestNodeConfigGeneratorEmpty(t *testing.T) { + t.Parallel() + + state := fetchers.State{} + + generator := NewNodeConfigGenerator() + + metrics := generator.Get(state) + assert.Empty(t, metrics) +} + +func TestNodeConfigGeneratorInvalid(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + require.Fail(t, "Expected to have a panic here!") + } + }() + + state := fetchers.State{ + constants.FetcherNameNodeConfig: 3, + } + + generator := NewNodeConfigGenerator() + generator.Get(state) +} + +func TestNodeConfigGeneratorInvalidPrices(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + require.Fail(t, "Expected to have a panic here!") + } + }() + + _, d := grpcmock.MockServerWithBufConn( + grpcmock.RegisterServiceFromMethods(service.Method{ + ServiceName: "cosmos.base.node.v1beta1.Service", + MethodName: "Config", + MethodType: service.TypeUnary, + Input: &nodeTypes.ConfigRequest{}, + Output: &nodeTypes.ConfigResponse{}, + }), + func(s *grpcmock.Server) { + s.ExpectUnary("cosmos.base.node.v1beta1.Service/Config"). + Return(&nodeTypes.ConfigResponse{MinimumGasPrice: "test"}) + }, + )(t) + + logger := loggerPkg.GetNopLogger() + tracer := tracing.InitNoopTracer() + grpcConn, err := grpc.NewClient( + "localhost:9090", + grpc.WithContextDialer(d), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + client := &grpcPkg.Client{ + Logger: *logger, + Client: grpcConn, + Tracer: tracer, + } + + fetcher := fetchers.NewNodeConfigFetcher(*logger, client, tracer) + data, _ := fetcher.Get(context.Background()) + assert.NotNil(t, data) + + state := fetchers.State{ + constants.FetcherNameNodeConfig: data, + } + + generator := NewNodeConfigGenerator() + generator.Get(state) +} + +func TestNodeConfigGeneratorOk(t *testing.T) { + t.Parallel() + + _, d := grpcmock.MockServerWithBufConn( + grpcmock.RegisterServiceFromMethods(service.Method{ + ServiceName: "cosmos.base.node.v1beta1.Service", + MethodName: "Config", + MethodType: service.TypeUnary, + Input: &nodeTypes.ConfigRequest{}, + Output: &nodeTypes.ConfigResponse{}, + }), + func(s *grpcmock.Server) { + s.ExpectUnary("cosmos.base.node.v1beta1.Service/Config"). + Return(&nodeTypes.ConfigResponse{MinimumGasPrice: "0.1uatom,0.2ustake", HaltHeight: 123}) + }, + )(t) + + logger := loggerPkg.GetNopLogger() + tracer := tracing.InitNoopTracer() + grpcConn, err := grpc.NewClient( + "localhost:9090", + grpc.WithContextDialer(d), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + client := &grpcPkg.Client{ + Logger: *logger, + Client: grpcConn, + Tracer: tracer, + } + + fetcher := fetchers.NewNodeConfigFetcher(*logger, client, tracer) + data, _ := fetcher.Get(context.Background()) + assert.NotNil(t, data) + + state := fetchers.State{ + constants.FetcherNameNodeConfig: data, + } + + generator := NewNodeConfigGenerator() + + metrics := generator.Get(state) + assert.Len(t, metrics, 4) + + pricesCount := metrics[0] + assert.Empty(t, pricesCount.Labels) + assert.InDelta(t, 2, pricesCount.Value, 0.01) + + firstPrice := metrics[1] + assert.Equal(t, map[string]string{ + "denom": "uatom", + }, firstPrice.Labels) + assert.InDelta(t, 0.1, firstPrice.Value, 0.01) + + secondPrice := metrics[2] + assert.Equal(t, map[string]string{ + "denom": "ustake", + }, secondPrice.Labels) + assert.InDelta(t, 0.2, secondPrice.Value, 0.01) + + haltHeight := metrics[3] + assert.Equal(t, map[string]string{}, haltHeight.Labels) + assert.InDelta(t, 123, haltHeight.Value, 0.01) +} diff --git a/pkg/node_handler.go b/pkg/node_handler.go index 91e1dae..b503217 100644 --- a/pkg/node_handler.go +++ b/pkg/node_handler.go @@ -10,7 +10,6 @@ import ( fetchersPkg "main/pkg/fetchers" generatorsPkg "main/pkg/generators" metricsPkg "main/pkg/metrics" - nodeConfig "main/pkg/queriers/node_config" nodeInfo "main/pkg/queriers/node_info" "main/pkg/queriers/upgrades" "main/pkg/queriers/versions" @@ -65,18 +64,19 @@ func NewNodeHandler( queriers := []types.Querier{ versions.NewQuerier(appLogger, gitClient, cosmovisor, tracer), upgrades.NewQuerier(config.TendermintConfig.QueryUpgrades.Bool, appLogger, cosmovisor, tendermintRPC, tracer), - nodeConfig.NewQuerier(appLogger, grpc, tracer), nodeInfo.NewQuerier(appLogger, grpc, tracer), } fetchers := fetchersPkg.Fetchers{ fetchersPkg.NewNodeStatusFetcher(appLogger, tendermintRPC, tracer), fetchersPkg.NewCosmovisorVersionFetcher(appLogger, cosmovisor, tracer), + fetchersPkg.NewNodeConfigFetcher(appLogger, grpc, tracer), } generators := []generatorsPkg.Generator{ generatorsPkg.NewNodeStatusGenerator(), generatorsPkg.NewCosmovisorVersionGenerator(), + generatorsPkg.NewNodeConfigGenerator(), } controller := fetchersPkg.NewController(fetchers, appLogger, config.Name) diff --git a/pkg/queriers/node_config/querier.go b/pkg/queriers/node_config/querier.go deleted file mode 100644 index aac0c02..0000000 --- a/pkg/queriers/node_config/querier.go +++ /dev/null @@ -1,88 +0,0 @@ -package node_stats - -import ( - "context" - grpcPkg "main/pkg/clients/grpc" - "main/pkg/metrics" - "main/pkg/query_info" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - - cosmosTypes "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" -) - -type Querier struct { - gRPC *grpcPkg.Client - Logger zerolog.Logger - Tracer trace.Tracer -} - -func NewQuerier(logger zerolog.Logger, grpc *grpcPkg.Client, tracer trace.Tracer) *Querier { - return &Querier{ - Logger: logger.With().Str("component", "node-config-querier").Logger(), - gRPC: grpc, - Tracer: tracer, - } -} - -func (n *Querier) Enabled() bool { - return n.gRPC != nil -} - -func (n *Querier) Name() string { - return "node-config-querier" -} - -func (n *Querier) Get(ctx context.Context) ([]metrics.MetricInfo, []query_info.QueryInfo) { - childCtx, span := n.Tracer.Start( - ctx, - "Querier "+n.Name(), - trace.WithAttributes(attribute.String("node", n.Name())), - ) - defer span.End() - - config, queryInfo, err := n.gRPC.GetNodeConfig(childCtx) - if err != nil { - n.Logger.Error().Err(err).Msg("Could not fetch node config") - return []metrics.MetricInfo{}, []query_info.QueryInfo{queryInfo} - } - - if config == nil { - n.Logger.Debug(). - Msg("Node config is nil, probably chain does not implement the node config endpoint.") - return []metrics.MetricInfo{}, []query_info.QueryInfo{queryInfo} - } - - coinsParsed, err := cosmosTypes.ParseDecCoins(config.MinimumGasPrice) - if err != nil { - n.Logger.Error().Err(err).Msg("Error decoding minimum gas prices") - return []metrics.MetricInfo{}, []query_info.QueryInfo{queryInfo} - } - - querierMetrics := []metrics.MetricInfo{} - querierMetrics = append(querierMetrics, metrics.MetricInfo{ - MetricName: metrics.MetricNameMinimumGasPricesCount, - Labels: map[string]string{}, - Value: float64(len(coinsParsed)), - }) - - for _, amount := range coinsParsed { - querierMetrics = append(querierMetrics, metrics.MetricInfo{ - MetricName: metrics.MetricNameMinimumGasPrice, - Labels: map[string]string{"denom": amount.Denom}, - Value: amount.Amount.MustFloat64(), - }) - } - - if config.HaltHeight > 0 { - querierMetrics = append(querierMetrics, metrics.MetricInfo{ - MetricName: metrics.MetricNameHaltHeight, - Labels: map[string]string{}, - Value: float64(config.HaltHeight), - }) - } - - return querierMetrics, []query_info.QueryInfo{queryInfo} -}