From 1514b4c0e506249bba4615560218503d1fd45966 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 31 Jul 2025 13:26:22 +0200 Subject: [PATCH 1/3] chare: remove unused optional argument support again --- .../alter_limit_connection_creation_rate.go | 2 +- extkafka/alter_max_message_bytes.go | 2 +- extkafka/alter_num_io_threads.go | 9 +--- extkafka/alter_num_network_threads.go | 9 +--- extkafka/common.go | 50 +++++++------------ 5 files changed, 22 insertions(+), 50 deletions(-) diff --git a/extkafka/alter_limit_connection_creation_rate.go b/extkafka/alter_limit_connection_creation_rate.go index 63a9e92..0357c35 100644 --- a/extkafka/alter_limit_connection_creation_rate.go +++ b/extkafka/alter_limit_connection_creation_rate.go @@ -75,7 +75,7 @@ func (k *AlterLimitConnectionCreateRateAttack) Prepare(ctx context.Context, stat } func (k *AlterLimitConnectionCreateRateAttack) Start(ctx context.Context, state *AlterState) (*action_kit_api.StartResult, error) { - if err := alterConfigInt(ctx, state.BrokerHosts, LimitConnectionRate, &state.TargetBrokerConfigValue, state.BrokerID); err != nil { + if err := alterConfigInt(ctx, state.BrokerHosts, LimitConnectionRate, state.TargetBrokerConfigValue, state.BrokerID); err != nil { return nil, err } return &action_kit_api.StartResult{ diff --git a/extkafka/alter_max_message_bytes.go b/extkafka/alter_max_message_bytes.go index a3ba5bb..acc3bc7 100644 --- a/extkafka/alter_max_message_bytes.go +++ b/extkafka/alter_max_message_bytes.go @@ -75,7 +75,7 @@ func (k *AlterMessageMaxBytesAttack) Prepare(ctx context.Context, state *AlterSt } func (k *AlterMessageMaxBytesAttack) Start(ctx context.Context, state *AlterState) (*action_kit_api.StartResult, error) { - if err := alterConfigInt(ctx, state.BrokerHosts, MessageMaxBytes, &state.TargetBrokerConfigValue, state.BrokerID); err != nil { + if err := alterConfigInt(ctx, state.BrokerHosts, MessageMaxBytes, state.TargetBrokerConfigValue, state.BrokerID); err != nil { return nil, err } return &action_kit_api.StartResult{ diff --git a/extkafka/alter_num_io_threads.go b/extkafka/alter_num_io_threads.go index 3346a16..4764238 100644 --- a/extkafka/alter_num_io_threads.go +++ b/extkafka/alter_num_io_threads.go @@ -87,16 +87,9 @@ func (k *AlterNumberIOThreadsAttack) Start(ctx context.Context, state *AlterStat } func (k *AlterNumberIOThreadsAttack) Stop(ctx context.Context, state *AlterState) (*action_kit_api.StopResult, error) { - var err error - if state.InitialBrokerConfigValue == nil { - err = alterConfigInt(ctx, state.BrokerHosts, NumberIOThreads, nil, state.BrokerID) - } else { - err = adjustThreads(ctx, state.BrokerHosts, NumberIOThreads, *state.InitialBrokerConfigValue, state.BrokerID) - } - if err != nil { + if err := adjustThreads(ctx, state.BrokerHosts, NumberIOThreads, state.InitialBrokerConfigValue, state.BrokerID); err != nil { return nil, err } - return &action_kit_api.StopResult{ Messages: &[]action_kit_api.Message{{ Level: extutil.Ptr(action_kit_api.Info), diff --git a/extkafka/alter_num_network_threads.go b/extkafka/alter_num_network_threads.go index a9ad0d3..5636395 100644 --- a/extkafka/alter_num_network_threads.go +++ b/extkafka/alter_num_network_threads.go @@ -87,16 +87,9 @@ func (k *AlterNumberNetworkThreadsAttack) Start(ctx context.Context, state *Alte } func (k *AlterNumberNetworkThreadsAttack) Stop(ctx context.Context, state *AlterState) (*action_kit_api.StopResult, error) { - var err error - if state.InitialBrokerConfigValue == nil { - err = alterConfigInt(ctx, state.BrokerHosts, NumberNetworkThreads, nil, state.BrokerID) - } else { - err = adjustThreads(ctx, state.BrokerHosts, NumberNetworkThreads, *state.InitialBrokerConfigValue, state.BrokerID) - } - if err != nil { + if err := adjustThreads(ctx, state.BrokerHosts, NumberNetworkThreads, state.InitialBrokerConfigValue, state.BrokerID); err != nil { return nil, err } - return &action_kit_api.StopResult{ Messages: &[]action_kit_api.Message{{ Level: extutil.Ptr(action_kit_api.Info), diff --git a/extkafka/common.go b/extkafka/common.go index f6b75cb..60f4f4e 100644 --- a/extkafka/common.go +++ b/extkafka/common.go @@ -57,7 +57,7 @@ type KafkaBrokerAttackState struct { type AlterState struct { BrokerHosts []string BrokerID int32 - InitialBrokerConfigValue *int + InitialBrokerConfigValue int TargetBrokerConfigValue int } @@ -188,19 +188,12 @@ func createNewAdminClient(brokers []string) (*kadm.Client, error) { return kadm.NewClient(client), nil } -func describeConfigInt(ctx context.Context, brokers []string, configName string, brokerID int32) (*int, error) { +func describeConfigInt(ctx context.Context, brokers []string, configName string, brokerID int32) (int, error) { value, err := describeConfigStr(ctx, brokers, configName, brokerID) if err != nil { - return nil, err - } - if value == "" { - return nil, nil - } - i, err := strconv.Atoi(value) - if err != nil { - return nil, err + return -1, err } - return extutil.Ptr(i), nil + return strconv.Atoi(value) } func describeConfigStr(ctx context.Context, brokers []string, configName string, brokerID int32) (string, error) { @@ -211,7 +204,7 @@ func describeConfigStr(ctx context.Context, brokers []string, configName string, return describeConfigOf(ctx, adminClient, configName, brokerID) } -func describeConfigOf(ctx context.Context, adminClient *kadm.Client, configName string, brokerID int32) (initialValue string, err error) { +func describeConfigOf(ctx context.Context, adminClient *kadm.Client, configName string, brokerID int32) (configValue string, err error) { configs, err := adminClient.DescribeBrokerConfigs(ctx, brokerID) if err != nil { return "", err @@ -220,7 +213,7 @@ func describeConfigOf(ctx context.Context, adminClient *kadm.Client, configName _, err = configs.On(strconv.FormatInt(int64(brokerID), 10), func(resourceConfig *kadm.ResourceConfig) error { for i := range resourceConfig.Configs { if resourceConfig.Configs[i].Key == configName { - initialValue = resourceConfig.Configs[i].MaybeValue() + configValue = resourceConfig.Configs[i].MaybeValue() return nil } } @@ -237,19 +230,17 @@ func describeConfigOf(ctx context.Context, adminClient *kadm.Client, configName return "", err } - if initialValue == "" { + if configValue == "" { log.Warn().Msgf("No value found for configuration key: %s, for broker node-id: %d", configName, brokerID) + } else { + log.Debug().Msgf("Configuration value for key %s: %s, for broker node-id: %d", configName, configValue, brokerID) } - return initialValue, nil + return configValue, nil } -func alterConfigInt(ctx context.Context, brokers []string, configName string, configValue *int, brokerID int32) error { - var value = "" - if configValue != nil { - value = strconv.Itoa(*configValue) - } - return alterConfigStr(ctx, brokers, configName, value, brokerID) +func alterConfigInt(ctx context.Context, brokers []string, configName string, configValue int, brokerID int32) error { + return alterConfigStr(ctx, brokers, configName, strconv.Itoa(configValue), brokerID) } func alterConfigStr(ctx context.Context, brokers []string, configName string, configValue string, brokerID int32) error { @@ -259,11 +250,11 @@ func alterConfigStr(ctx context.Context, brokers []string, configName string, co } defer adminClient.Close() - var value *string - if configValue != "" { - value = extutil.Ptr(configValue) + op := kadm.SetConfig + if configValue == "" { + op = kadm.DeleteConfig } - responses, err := adminClient.AlterBrokerConfigs(ctx, []kadm.AlterConfig{{Name: configName, Value: value}}, brokerID) + responses, err := adminClient.AlterBrokerConfigs(ctx, []kadm.AlterConfig{{Name: configName, Value: extutil.Ptr(configValue), Op: op}}, brokerID) if err != nil { return err } @@ -296,21 +287,16 @@ func alterConfigStr(ctx context.Context, brokers []string, configName string, co } func adjustThreads(ctx context.Context, hosts []string, configName string, targetValue int, brokerId int32) error { - initialValue, err := describeConfigInt(ctx, hosts, configName, brokerId) + currentValue, err := describeConfigInt(ctx, hosts, configName, brokerId) if err != nil { return err } - currentValue := targetValue - if initialValue != nil { - currentValue = *initialValue - } - // As kafka does not allow to more than double or halve the number of threads, we use an iterative approach to get to that value for currentValue != targetValue { nextValue := max(min(targetValue, currentValue*2), currentValue/2) - if err := alterConfigInt(ctx, hosts, configName, &nextValue, brokerId); err != nil { + if err := alterConfigInt(ctx, hosts, configName, nextValue, brokerId); err != nil { return err } else { currentValue = nextValue From b128e254a7782b1419081dd68e1ff5fa68b01312 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 31 Jul 2025 17:20:11 +0200 Subject: [PATCH 2/3] chore: cleanup unit tests --- extkafka/check_brokers_test.go | 82 +++++------ extkafka/check_consumer_group_test.go | 136 ++++-------------- extkafka/check_topic_lag_for_consumer_test.go | 135 ++++------------- extkafka/consumergroup_discovery_test.go | 112 ++++----------- extkafka/produceFixAmount_test.go | 45 +++--- extkafka/producePeriodically_test.go | 46 +++--- extkafka/produce_test.go | 7 +- extkafka/topic_discovery_test.go | 118 +++++---------- 8 files changed, 206 insertions(+), 475 deletions(-) diff --git a/extkafka/check_brokers_test.go b/extkafka/check_brokers_test.go index 9127101..816fe0e 100644 --- a/extkafka/check_brokers_test.go +++ b/extkafka/check_brokers_test.go @@ -11,6 +11,7 @@ import ( "github.com/steadybit/extension-kafka/config" "github.com/steadybit/extension-kit/extutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kfake" "github.com/twmb/franz-go/pkg/kgo" "strings" @@ -19,30 +20,17 @@ import ( ) func TestCheckBrokers_Describe(t *testing.T) { - tests := []struct { - name string - requestBody action_kit_api.PrepareActionRequestBody - wantedError error - wantedState *CheckBrokersState - }{ - { - name: "Should return description", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - //Given - action := CheckBrokersAction{} - //When - response := action.Describe() + //Given + action := CheckBrokersAction{} - //Then - assert.Equal(t, "Check activity of brokers.", response.Description) - assert.Equal(t, "Check Brokers", response.Label) - assert.Equal(t, fmt.Sprintf("%s.check", kafkaBrokerTargetId), response.Id) - assert.Equal(t, extutil.Ptr("Kafka"), response.Technology) - }) - } + //When + response := action.Describe() + + //Then + assert.Equal(t, "Check activity of brokers.", response.Description) + assert.Equal(t, "Check Brokers", response.Label) + assert.Equal(t, fmt.Sprintf("%s.check", kafkaBrokerTargetId), response.Id) + assert.Equal(t, extutil.Ptr("Kafka"), response.Technology) } func TestCheckBrokers_Prepare(t *testing.T) { @@ -50,9 +38,7 @@ func TestCheckBrokers_Prepare(t *testing.T) { kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(3), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer c.Close() seeds := c.ListenAddrs() @@ -69,7 +55,7 @@ func TestCheckBrokers_Prepare(t *testing.T) { requestBody: extutil.JsonMangle(action_kit_api.PrepareActionRequestBody{ Config: map[string]interface{}{ "expectedChanges": []string{"test"}, - "stateCheckMode": "test", + "changeCheckMode": "allTheTime", "duration": 10000, }, ExecutionId: uuid.New(), @@ -77,7 +63,7 @@ func TestCheckBrokers_Prepare(t *testing.T) { wantedState: &CheckBrokersState{ ExpectedChanges: []string{"test"}, - StateCheckMode: "test", + StateCheckMode: "allTheTime", StateCheckSuccess: false, }, }, @@ -97,8 +83,8 @@ func TestCheckBrokers_Prepare(t *testing.T) { } if tt.wantedState != nil { assert.NoError(t, err) - assert.Equal(t, "test", tt.wantedState.StateCheckMode) - assert.Equal(t, []string{"test"}, state.ExpectedChanges) + assert.Equal(t, tt.wantedState.ExpectedChanges, state.ExpectedChanges) + assert.Equal(t, tt.wantedState.StateCheckMode, state.StateCheckMode) assert.Equal(t, tt.wantedState.StateCheckSuccess, state.StateCheckSuccess) assert.NotNil(t, state.End) } @@ -111,9 +97,7 @@ func TestCheckBrokers_Status(t *testing.T) { kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(3), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer c.Close() seeds := c.ListenAddrs() @@ -125,19 +109,19 @@ func TestCheckBrokers_Status(t *testing.T) { kgo.ConsumerGroup("steadybit"), kgo.ConsumeTopics("steadybit"), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer cl.Close() tests := []struct { name string + killNode *int requestBody action_kit_api.PrepareActionRequestBody wantedError error wantedState *CheckBrokersState }{ { - name: "Should return status ok", + name: "Should return status ok", + killNode: extutil.Ptr(1), requestBody: extutil.JsonMangle(action_kit_api.PrepareActionRequestBody{ Target: &action_kit_api.Target{ Attributes: map[string][]string{ @@ -158,7 +142,8 @@ func TestCheckBrokers_Status(t *testing.T) { }, }, { - name: "Should return status ok with all the time check mode", + name: "Should return status ok with all the time check mode", + killNode: extutil.Ptr(2), requestBody: extutil.JsonMangle(action_kit_api.PrepareActionRequestBody{ Target: &action_kit_api.Target{ Attributes: map[string][]string{ @@ -188,31 +173,32 @@ func TestCheckBrokers_Status(t *testing.T) { state := CheckBrokersState{} request := tt.requestBody //When - _, errPrepare := action.Prepare(context.TODO(), &state, request) - statusResult, errStatus := action.Status(context.TODO(), &state) - time.Sleep(6 * time.Second) - err := c.RemoveNode(1) - if err != nil { - return - } + _, errPrepare := action.Prepare(t.Context(), &state, request) + statusResult, errStatus := action.Status(t.Context(), &state) //Then if tt.wantedState != nil { assert.NoError(t, errPrepare) assert.NoError(t, errStatus) assert.Equal(t, tt.wantedState.StateCheckMode, state.StateCheckMode) - assert.Equal(t, false, statusResult.Completed) + assert.False(t, statusResult.Completed) assert.NotNil(t, state.End) } + if tt.wantedError != nil { + err := c.RemoveNode(int32(*tt.killNode)) + require.NoError(t, err) + } + time.Sleep(6 * time.Second) + // Completed - statusResult, errStatus = action.Status(context.TODO(), &state) + statusResult, errStatus = action.Status(t.Context(), &state) //Then if tt.wantedState != nil { assert.NoError(t, errPrepare) assert.NoError(t, errStatus) assert.Equal(t, tt.wantedState.StateCheckMode, state.StateCheckMode) - assert.Equal(t, true, statusResult.Completed) + assert.True(t, statusResult.Completed) assert.NotNil(t, state.End) } }) diff --git a/extkafka/check_consumer_group_test.go b/extkafka/check_consumer_group_test.go index e70c841..889c78d 100644 --- a/extkafka/check_consumer_group_test.go +++ b/extkafka/check_consumer_group_test.go @@ -4,7 +4,6 @@ package extkafka import ( - "context" "fmt" "github.com/google/uuid" "github.com/steadybit/action-kit/go/action_kit_api/v2" @@ -12,6 +11,7 @@ import ( extension_kit "github.com/steadybit/extension-kit" "github.com/steadybit/extension-kit/extutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kfake" "github.com/twmb/franz-go/pkg/kgo" "strings" @@ -20,31 +20,18 @@ import ( ) func TestCheckConsumerGroup_Describe(t *testing.T) { - tests := []struct { - name string - requestBody action_kit_api.PrepareActionRequestBody - wantedError error - wantedState *ConsumerGroupCheckState - }{ - { - name: "Should return description", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - //Given - action := ConsumerGroupCheckAction{} - //When - response := action.Describe() - - //Then - assert.Equal(t, "Check the consumer state", response.Description) - assert.Equal(t, "Check Consumer State", response.Label) - assert.Equal(t, kafkaConsumerTargetId, response.TargetSelection.TargetType) - assert.Equal(t, fmt.Sprintf("%s.check", kafkaConsumerTargetId), response.Id) - assert.Equal(t, extutil.Ptr("Kafka"), response.Technology) - }) - } + //Given + action := ConsumerGroupCheckAction{} + + //When + response := action.Describe() + + //Then + assert.Equal(t, "Check the consumer state", response.Description) + assert.Equal(t, "Check Consumer State", response.Label) + assert.Equal(t, kafkaConsumerTargetId, response.TargetSelection.TargetType) + assert.Equal(t, fmt.Sprintf("%s.check", kafkaConsumerTargetId), response.Id) + assert.Equal(t, extutil.Ptr("Kafka"), response.Technology) } func TestCheckConsumerGroup_Prepare(t *testing.T) { @@ -101,8 +88,9 @@ func TestCheckConsumerGroup_Prepare(t *testing.T) { action := ConsumerGroupCheckAction{} state := ConsumerGroupCheckState{} request := tt.requestBody + //When - _, err := action.Prepare(context.TODO(), &state, request) + _, err := action.Prepare(t.Context(), &state, request) //Then if tt.wantedError != nil { @@ -110,10 +98,10 @@ func TestCheckConsumerGroup_Prepare(t *testing.T) { } if tt.wantedState != nil { assert.NoError(t, err) - assert.Equal(t, "test", tt.wantedState.StateCheckMode) - assert.Equal(t, "steadybit", state.ConsumerGroupName) - assert.Equal(t, []string{"test"}, state.ExpectedState) - assert.Equal(t, false, state.StateCheckSuccess) + assert.Equal(t, tt.wantedState.StateCheckMode, state.StateCheckMode) + assert.Equal(t, tt.wantedState.ConsumerGroupName, state.ConsumerGroupName) + assert.Equal(t, tt.wantedState.ExpectedState, state.ExpectedState) + assert.False(t, state.StateCheckSuccess) assert.NotNil(t, state.End) } }) @@ -125,9 +113,7 @@ func TestCheckConsumerGroup_Status(t *testing.T) { kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(3), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer c.Close() seeds := c.ListenAddrs() @@ -139,9 +125,7 @@ func TestCheckConsumerGroup_Status(t *testing.T) { kgo.ConsumerGroup("steadybit"), kgo.ConsumeTopics("steadybit"), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer cl.Close() tests := []struct { @@ -165,7 +149,6 @@ func TestCheckConsumerGroup_Status(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedState: &ConsumerGroupCheckState{ ConsumerGroupName: "steadybit", ExpectedState: []string{"Dead"}, @@ -189,7 +172,6 @@ func TestCheckConsumerGroup_Status(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedState: &ConsumerGroupCheckState{ ConsumerGroupName: "steadybit", ExpectedState: []string{"Dead"}, @@ -205,10 +187,10 @@ func TestCheckConsumerGroup_Status(t *testing.T) { action := ConsumerGroupCheckAction{} state := ConsumerGroupCheckState{} request := tt.requestBody + //When - _, errPrepare := action.Prepare(context.TODO(), &state, request) - statusResult, errStatus := action.Status(context.TODO(), &state) - time.Sleep(6 * time.Second) + _, errPrepare := action.Prepare(t.Context(), &state, request) + statusResult, errStatus := action.Status(t.Context(), &state) //Then if tt.wantedState != nil { @@ -216,82 +198,24 @@ func TestCheckConsumerGroup_Status(t *testing.T) { assert.NoError(t, errStatus) assert.Equal(t, tt.wantedState.StateCheckMode, state.StateCheckMode) assert.Equal(t, tt.wantedState.ConsumerGroupName, state.ConsumerGroupName) - assert.Equal(t, false, statusResult.Completed) + assert.False(t, statusResult.Completed) assert.NotNil(t, state.End) } + time.Sleep(6 * time.Second) + // Completed - statusResult, errStatus = action.Status(context.TODO(), &state) + statusResult, errStatus = action.Status(t.Context(), &state) + //Then if tt.wantedState != nil { assert.NoError(t, errPrepare) assert.NoError(t, errStatus) assert.Equal(t, tt.wantedState.StateCheckMode, state.StateCheckMode) assert.Equal(t, tt.wantedState.ConsumerGroupName, state.ConsumerGroupName) - assert.Equal(t, true, statusResult.Completed) + assert.True(t, statusResult.Completed) assert.NotNil(t, state.End) } }) } } - -//func TestAction_Stop(t *testing.T) { -// -// tests := []struct { -// name string -// requestBody action_kit_api.StopActionRequestBody -// state *KafkaBrokerAttackState -// executionRunData *ExecutionRunData -// wantedError error -// }{ -// { -// name: "Should successfully stop the action", -// requestBody: extutil.JsonMangle(action_kit_api.StopActionRequestBody{}), -// state: &KafkaBrokerAttackState{ -// ExecutionID: uuid.New(), -// SuccessRate: 40, -// }, -// executionRunData: getExecutionRunData(5, 10), -// wantedError: nil, -// }, { -// name: "Should fail because of low success rate", -// requestBody: extutil.JsonMangle(action_kit_api.StopActionRequestBody{}), -// state: &KafkaBrokerAttackState{ -// ExecutionID: uuid.New(), -// SuccessRate: 100, -// }, -// executionRunData: getExecutionRunData(4, 11), -// wantedError: extension_kit.ToError("Success Rate (36.36%) was below 100%", nil), -// }, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// //Given -// saveExecutionRunData(tt.state.ExecutionID, tt.executionRunData) -// //When -// result, err := stop(tt.state) -// -// //Then -// if tt.wantedError != nil && result.Error == nil { -// assert.EqualError(t, err, tt.wantedError.Error()) -// } else if tt.wantedError != nil && result.Error != nil { -// assert.Equal(t, tt.wantedError.Error(), result.Error.Title) -// } else if tt.wantedError == nil && result.Error != nil { -// assert.Fail(t, "Should not have error", result.Error.Title) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -//} -// -//func getExecutionRunData(successCounter uint64, counter uint64) *ExecutionRunData { -// data := &ExecutionRunData{ -// requestSuccessCounter: atomic.Uint64{}, -// requestCounter: atomic.Uint64{}, -// } -// data.requestCounter.Store(counter) -// data.requestSuccessCounter.Store(successCounter) -// return data -// -//} diff --git a/extkafka/check_topic_lag_for_consumer_test.go b/extkafka/check_topic_lag_for_consumer_test.go index af3780b..38450c8 100644 --- a/extkafka/check_topic_lag_for_consumer_test.go +++ b/extkafka/check_topic_lag_for_consumer_test.go @@ -12,6 +12,7 @@ import ( extension_kit "github.com/steadybit/extension-kit" "github.com/steadybit/extension-kit/extutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kfake" "github.com/twmb/franz-go/pkg/kgo" "strings" @@ -20,31 +21,18 @@ import ( ) func TestCheckTopicLag_Describe(t *testing.T) { - tests := []struct { - name string - requestBody action_kit_api.PrepareActionRequestBody - wantedError error - wantedState *ConsumerGroupLagCheckState - }{ - { - name: "Should return description", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - //Given - action := ConsumerGroupLagCheckAction{} - //When - response := action.Describe() - - //Then - assert.Equal(t, "Check the consumer lag for a given topic (lag is calculated by the difference between topic offset and consumer offset)", response.Description) - assert.Equal(t, "Check Topic Lag", response.Label) - assert.Equal(t, kafkaConsumerTargetId, response.TargetSelection.TargetType) - assert.Equal(t, fmt.Sprintf("%s.check-lag", kafkaConsumerTargetId), response.Id) - assert.Equal(t, extutil.Ptr("Kafka"), response.Technology) - }) - } + //Given + action := ConsumerGroupLagCheckAction{} + + //When + response := action.Describe() + + //Then + assert.Equal(t, "Check the consumer lag for a given topic (lag is calculated by the difference between topic offset and consumer offset)", response.Description) + assert.Equal(t, "Check Topic Lag", response.Label) + assert.Equal(t, kafkaConsumerTargetId, response.TargetSelection.TargetType) + assert.Equal(t, fmt.Sprintf("%s.check-lag", kafkaConsumerTargetId), response.Id) + assert.Equal(t, extutil.Ptr("Kafka"), response.Technology) } func TestCheckConsumerGroupLag_Prepare(t *testing.T) { @@ -69,7 +57,6 @@ func TestCheckConsumerGroupLag_Prepare(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedState: &ConsumerGroupLagCheckState{ ConsumerGroupName: "steadybit", StateCheckSuccess: true, @@ -90,7 +77,6 @@ func TestCheckConsumerGroupLag_Prepare(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedError: extension_kit.ToError("the target is missing the kafka.consumer-group.name attribute", nil), }, } @@ -100,8 +86,9 @@ func TestCheckConsumerGroupLag_Prepare(t *testing.T) { action := ConsumerGroupLagCheckAction{} state := ConsumerGroupLagCheckState{} request := tt.requestBody + //When - _, err := action.Prepare(context.TODO(), &state, request) + _, err := action.Prepare(t.Context(), &state, request) //Then if tt.wantedError != nil { @@ -109,10 +96,10 @@ func TestCheckConsumerGroupLag_Prepare(t *testing.T) { } if tt.wantedState != nil { assert.NoError(t, err) - assert.Equal(t, int64(1), tt.wantedState.AcceptableLag) - assert.Equal(t, "steadybit", state.ConsumerGroupName) - assert.Equal(t, "steadybit", state.Topic) - assert.Equal(t, false, state.StateCheckSuccess) + assert.Equal(t, tt.wantedState.AcceptableLag, state.AcceptableLag) + assert.Equal(t, state.ConsumerGroupName, state.ConsumerGroupName) + assert.Equal(t, state.Topic, state.Topic) + assert.False(t, state.StateCheckSuccess) assert.NotNil(t, state.End) } }) @@ -124,9 +111,7 @@ func TestCheckConsumerGroupLag_Status(t *testing.T) { kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(3), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer c.Close() seeds := c.ListenAddrs() @@ -139,9 +124,7 @@ func TestCheckConsumerGroupLag_Status(t *testing.T) { kgo.DefaultProduceTopic("steadybit"), kgo.ConsumeTopics("steadybit"), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer cl.Close() // produce messages for lags @@ -170,7 +153,6 @@ func TestCheckConsumerGroupLag_Status(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedState: &ConsumerGroupLagCheckState{ ConsumerGroupName: "steadybit", AcceptableLag: int64(15), @@ -193,7 +175,6 @@ func TestCheckConsumerGroupLag_Status(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedState: &ConsumerGroupLagCheckState{ ConsumerGroupName: "steadybit", AcceptableLag: int64(1), @@ -208,10 +189,10 @@ func TestCheckConsumerGroupLag_Status(t *testing.T) { action := ConsumerGroupLagCheckAction{} state := ConsumerGroupLagCheckState{} request := tt.requestBody + //When - _, errPrepare := action.Prepare(context.TODO(), &state, request) - statusResult, errStatus := action.Status(context.TODO(), &state) - time.Sleep(6 * time.Second) + _, errPrepare := action.Prepare(t.Context(), &state, request) + statusResult, errStatus := action.Status(t.Context(), &state) //Then if tt.wantedState != nil { @@ -220,12 +201,15 @@ func TestCheckConsumerGroupLag_Status(t *testing.T) { assert.Equal(t, tt.wantedState.AcceptableLag, state.AcceptableLag) assert.Equal(t, tt.wantedState.Topic, state.Topic) assert.Equal(t, tt.wantedState.ConsumerGroupName, state.ConsumerGroupName) - assert.Equal(t, false, statusResult.Completed) + assert.False(t, statusResult.Completed) assert.NotNil(t, state.End) } + time.Sleep(6 * time.Second) + // Completed - _, errStatus = action.Status(context.TODO(), &state) + _, errStatus = action.Status(t.Context(), &state) + //Then if tt.wantedState != nil { assert.NoError(t, errPrepare) @@ -238,64 +222,3 @@ func TestCheckConsumerGroupLag_Status(t *testing.T) { }) } } - -//func TestAction_Stop(t *testing.T) { -// -// tests := []struct { -// name string -// requestBody action_kit_api.StopActionRequestBody -// state *KafkaBrokerAttackState -// executionRunData *ExecutionRunData -// wantedError error -// }{ -// { -// name: "Should successfully stop the action", -// requestBody: extutil.JsonMangle(action_kit_api.StopActionRequestBody{}), -// state: &KafkaBrokerAttackState{ -// ExecutionID: uuid.New(), -// SuccessRate: 40, -// }, -// executionRunData: getExecutionRunData(5, 10), -// wantedError: nil, -// }, { -// name: "Should fail because of low success rate", -// requestBody: extutil.JsonMangle(action_kit_api.StopActionRequestBody{}), -// state: &KafkaBrokerAttackState{ -// ExecutionID: uuid.New(), -// SuccessRate: 100, -// }, -// executionRunData: getExecutionRunData(4, 11), -// wantedError: extension_kit.ToError("Success Rate (36.36%) was below 100%", nil), -// }, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// //Given -// saveExecutionRunData(tt.state.ExecutionID, tt.executionRunData) -// //When -// result, err := stop(tt.state) -// -// //Then -// if tt.wantedError != nil && result.Error == nil { -// assert.EqualError(t, err, tt.wantedError.Error()) -// } else if tt.wantedError != nil && result.Error != nil { -// assert.Equal(t, tt.wantedError.Error(), result.Error.Title) -// } else if tt.wantedError == nil && result.Error != nil { -// assert.Fail(t, "Should not have error", result.Error.Title) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -//} -// -//func getExecutionRunData(successCounter uint64, counter uint64) *ExecutionRunData { -// data := &ExecutionRunData{ -// requestSuccessCounter: atomic.Uint64{}, -// requestCounter: atomic.Uint64{}, -// } -// data.requestCounter.Store(counter) -// data.requestSuccessCounter.Store(successCounter) -// return data -// -//} diff --git a/extkafka/consumergroup_discovery_test.go b/extkafka/consumergroup_discovery_test.go index e4c4ee9..a3a0a9b 100644 --- a/extkafka/consumergroup_discovery_test.go +++ b/extkafka/consumergroup_discovery_test.go @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT -// SPDX-FileCopyrightText: 2024 Steadybit GmbH +// SPDX-FileCopyrightText: 2025 Steadybit GmbH package extkafka import ( "fmt" - "reflect" + "github.com/steadybit/discovery-kit/go/discovery_kit_api" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" "github.com/twmb/franz-go/pkg/kadm" @@ -16,46 +18,25 @@ import ( "strings" ) -// Test Describe() func TestDescribe(t *testing.T) { - d := &kafkaTopicDiscovery{} - desc := d.Describe() - if desc.Id != kafkaTopicTargetId { - t.Errorf("Describe().Id = %q; want %q", desc.Id, kafkaTopicTargetId) - } - if desc.Discover.CallInterval == nil { - t.Error("Describe().Discover.CallInterval = nil; want non-nil") - } + desc := (&kafkaTopicDiscovery{}).Describe() + assert.Equal(t, kafkaTopicTargetId, desc.Id) + assert.NotNil(t, desc.Discover.CallInterval) } -// Test DescribeTarget() func TestDescribeTarget(t *testing.T) { d := &kafkaTopicDiscovery{} td := d.DescribeTarget() - - if td.Id != kafkaTopicTargetId { - t.Errorf("DescribeTarget().Id = %q; want %q", td.Id, kafkaTopicTargetId) - } - if td.Label.One != "Kafka topic" || td.Label.Other != "Kafka topics" { - t.Errorf("DescribeTarget().Label = %+v; want One=\"Kafka topic\", Other=\"Kafka topics\"", td.Label) - } - if td.Category == nil || *td.Category != "kafka" { - t.Errorf("DescribeTarget().Category = %v; want pointer to \"kafka\"", td.Category) - } - if len(td.Table.Columns) != 6 { - t.Errorf("DescribeTarget().Table.Columns length = %d; want 6", len(td.Table.Columns)) - } - if len(td.Table.OrderBy) != 1 { - t.Errorf("DescribeTarget().Table.OrderBy length = %d; want 1", len(td.Table.OrderBy)) - } else { - ob := td.Table.OrderBy[0] - if ob.Attribute != "steadybit.label" || ob.Direction != "ASC" { - t.Errorf("DescribeTarget().OrderBy[0] = %+v; want Attribute=\"steadybit.label\", Direction=\"ASC\"", ob) - } - } + assert.Equal(t, kafkaTopicTargetId, td.Id) + assert.Equal(t, "Kafka topic", td.Label.One) + assert.Equal(t, "Kafka topics", td.Label.Other) + assert.Equal(t, "kafka", *td.Category) + assert.Len(t, td.Table.Columns, 6) + require.Len(t, td.Table.OrderBy, 1) + assert.Equal(t, "steadybit.label", td.Table.OrderBy[0].Attribute) + assert.Equal(t, discovery_kit_api.OrderByDirection("ASC"), td.Table.OrderBy[0].Direction) } -// Test DescribeAttributes() func TestDescribeAttributes(t *testing.T) { d := &kafkaTopicDiscovery{} attrs := d.DescribeAttributes() @@ -67,11 +48,7 @@ func TestDescribeAttributes(t *testing.T) { "kafka.topic.partitions-isr", "kafka.topic.replication-factor", } - - if len(attrs) != len(expected) { - t.Fatalf("DescribeAttributes() len = %d; want %d", len(attrs), len(expected)) - } - + require.Len(t, attrs, len(expected)) for _, want := range expected { found := false for _, a := range attrs { @@ -80,13 +57,10 @@ func TestDescribeAttributes(t *testing.T) { break } } - if !found { - t.Errorf("DescribeAttributes() missing %q", want) - } + assert.Truef(t, found, "DescribeAttributes() missing %q", want) } } -// Test toTopicTarget() func TestToTopicTarget(t *testing.T) { td := kadm.TopicDetail{ Topic: "my-topic", @@ -96,32 +70,19 @@ func TestToTopicTarget(t *testing.T) { }, } cluster := "cluster-42" - tgt := toTopicTarget(td, cluster) // Basic fields - if want := "my-topic-cluster-42"; tgt.Id != want { - t.Errorf("Id = %q; want %q", tgt.Id, want) - } - if tgt.Label != "my-topic" { - t.Errorf("Label = %q; want %q", tgt.Label, "my-topic") - } - if tgt.TargetType != kafkaTopicTargetId { - t.Errorf("TargetType = %q; want %q", tgt.TargetType, kafkaTopicTargetId) - } + assert.Equal(t, "my-topic-cluster-42", tgt.Id) + assert.Equal(t, "my-topic", tgt.Label) + assert.Equal(t, kafkaTopicTargetId, tgt.TargetType) // Attributes attr := tgt.Attributes - check := func(key string, want []string) { v, ok := attr[key] - if !ok { - t.Errorf("missing attribute %q", key) - return - } - if !reflect.DeepEqual(v, want) { - t.Errorf("%s = %v; want %v", key, v, want) - } + assert.True(t, ok, "missing attribute %q", key) + assert.Equal(t, want, v) } check("kafka.cluster.name", []string{cluster}) @@ -154,9 +115,7 @@ func TestDiscoverTargetsClusterName(t *testing.T) { kfake.NumBrokers(1), kfake.ClusterID("test"), ) - if err != nil { - t.Fatalf("failed to create fake cluster: %v", err) - } + require.NoError(t, err) defer c.Close() // Configure seed brokers for discovery @@ -169,34 +128,21 @@ func TestDiscoverTargetsClusterName(t *testing.T) { // Discover targets ctx := context.Background() targets, err := getAllTopics(ctx) - if err != nil { - t.Fatalf("getAllTopics error: %v", err) - } - if len(targets) == 0 { - t.Fatal("expected at least one discovered topic") - } + require.NoError(t, err) + require.NotEmpty(t, targets) // Retrieve expected cluster name from metadata client, err := createNewAdminClient(strings.Split(config.Config.SeedBrokers, ",")) - if err != nil { - t.Fatalf("createNewAdminClient error: %v", err) - } + require.NoError(t, err) defer client.Close() meta, err := client.BrokerMetadata(ctx) - if err != nil { - t.Fatalf("BrokerMetadata error: %v", err) - } + require.NoError(t, err) expected := meta.Cluster // Assert each discovered target has the correct cluster name attribute for _, tgt := range targets { values, ok := tgt.Attributes["kafka.cluster.name"] - if !ok { - t.Errorf("missing kafka.cluster.name for target %s", tgt.Id) - continue - } - if len(values) != 1 || values[0] != expected { - t.Errorf("kafka.cluster.name = %v; want [%s]", values, expected) - } + require.True(t, ok, "missing kafka.cluster.name for target %s", tgt.Id) + require.Equal(t, []string{expected}, values) } } diff --git a/extkafka/produceFixAmount_test.go b/extkafka/produceFixAmount_test.go index 1ba128c..8510b3a 100644 --- a/extkafka/produceFixAmount_test.go +++ b/extkafka/produceFixAmount_test.go @@ -4,12 +4,12 @@ package extkafka import ( - "context" "github.com/google/uuid" "github.com/steadybit/action-kit/go/action_kit_api/v2" "github.com/steadybit/extension-kafka/config" "github.com/steadybit/extension-kit/extutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kfake" "strings" "testing" @@ -18,7 +18,6 @@ import ( func TestNewHTTPCheckActionFixedAmount_Prepare(t *testing.T) { action := produceMessageActionFixedAmount{} - tests := []struct { name string requestBody action_kit_api.PrepareActionRequestBody @@ -45,7 +44,6 @@ func TestNewHTTPCheckActionFixedAmount_Prepare(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedState: &KafkaBrokerAttackState{ ConsumerGroup: "", Topic: "steadybit", @@ -62,8 +60,9 @@ func TestNewHTTPCheckActionFixedAmount_Prepare(t *testing.T) { //Given state := action.NewEmptyState() request := tt.requestBody + //When - _, err := action.Prepare(context.Background(), &state, request) + _, err := action.Prepare(t.Context(), &state, request) //Then if tt.wantedError != nil { @@ -88,14 +87,12 @@ func TestNewHTTPCheckActionFixedAmount_All_Success(t *testing.T) { kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(1), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer c.Close() seeds := c.ListenAddrs() config.Config.SeedBrokers = strings.Join(seeds, ",") - //prepare the action + action := produceMessageActionFixedAmount{} state := action.NewEmptyState() prepareActionRequestBody := extutil.JsonMangle(action_kit_api.PrepareActionRequestBody{ @@ -119,7 +116,7 @@ func TestNewHTTPCheckActionFixedAmount_All_Success(t *testing.T) { }) // Prepare - prepareResult, err := action.Prepare(context.Background(), &state, prepareActionRequestBody) + prepareResult, err := action.Prepare(t.Context(), &state, prepareActionRequestBody) assert.NoError(t, err) assert.Nil(t, prepareResult) assert.Greater(t, state.DelayBetweenRequestsInMS, extutil.ToInt64(0)) @@ -127,24 +124,28 @@ func TestNewHTTPCheckActionFixedAmount_All_Success(t *testing.T) { executionRunData, err := action.getExecutionRunData(state.ExecutionID) assert.NoError(t, err) assert.NotNil(t, executionRunData) + // Start - startResult, err := action.Start(context.Background(), &state) + startResult, err := action.Start(t.Context(), &state) assert.NoError(t, err) assert.Nil(t, startResult) // Status - statusResult, err := action.Status(context.Background(), &state) + statusResult, err := action.Status(t.Context(), &state) assert.NoError(t, err) assert.NotNil(t, statusResult.Metrics) + time.Sleep(10 * time.Second) + // Status completed - statusResult, err = action.Status(context.Background(), &state) + statusResult, err = action.Status(t.Context(), &state) assert.NoError(t, err) assert.Equal(t, true, statusResult.Completed) assert.Equal(t, uint64(10), executionRunData.requestCounter.Load()) + // Stop - stopResult, err := action.Stop(context.Background(), &state) + stopResult, err := action.Stop(t.Context(), &state) assert.NoError(t, err) assert.NotNil(t, stopResult.Metrics) assert.Nil(t, stopResult.Error) @@ -156,14 +157,12 @@ func TestNewHTTPCheckActionFixedAmount_All_Failure(t *testing.T) { kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(1), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer c.Close() seeds := c.ListenAddrs() config.Config.SeedBrokers = strings.Join(seeds, ",") - //prepare the action + action := produceMessageActionFixedAmount{} state := action.NewEmptyState() prepareActionRequestBody := extutil.JsonMangle(action_kit_api.PrepareActionRequestBody{ @@ -187,23 +186,25 @@ func TestNewHTTPCheckActionFixedAmount_All_Failure(t *testing.T) { }) // Prepare - prepareResult, err := action.Prepare(context.Background(), &state, prepareActionRequestBody) + prepareResult, err := action.Prepare(t.Context(), &state, prepareActionRequestBody) assert.NoError(t, err) assert.Nil(t, prepareResult) assert.Greater(t, state.DelayBetweenRequestsInMS, extutil.ToInt64(0)) // Start - startResult, err := action.Start(context.Background(), &state) + startResult, err := action.Start(t.Context(), &state) assert.NoError(t, err) assert.Nil(t, startResult) // Status - statusResult, err := action.Status(context.Background(), &state) + statusResult, err := action.Status(t.Context(), &state) assert.NoError(t, err) assert.NotNil(t, statusResult.Metrics) + time.Sleep(5 * time.Second) + // Status completed - statusResult, err = action.Status(context.Background(), &state) + statusResult, err = action.Status(t.Context(), &state) assert.NoError(t, err) assert.Equal(t, true, statusResult.Completed) @@ -211,7 +212,7 @@ func TestNewHTTPCheckActionFixedAmount_All_Failure(t *testing.T) { assert.NoError(t, err) assert.Greater(t, executionRunData.requestCounter.Load(), uint64(0)) // Stop - stopResult, err := action.Stop(context.Background(), &state) + stopResult, err := action.Stop(t.Context(), &state) assert.NoError(t, err) assert.NotNil(t, stopResult.Metrics) assert.NotNil(t, stopResult.Error) diff --git a/extkafka/producePeriodically_test.go b/extkafka/producePeriodically_test.go index 889188a..60b909b 100644 --- a/extkafka/producePeriodically_test.go +++ b/extkafka/producePeriodically_test.go @@ -4,13 +4,13 @@ package extkafka import ( - "context" "github.com/google/uuid" "github.com/steadybit/action-kit/go/action_kit_api/v2" "github.com/steadybit/extension-kafka/config" extension_kit "github.com/steadybit/extension-kit" "github.com/steadybit/extension-kit/extutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kfake" "strings" "testing" @@ -56,7 +56,6 @@ func TestNewProduceMessageActionPeriodically_Prepare(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedState: &KafkaBrokerAttackState{ ConsumerGroup: "", Topic: "steadybit", @@ -85,7 +84,6 @@ func TestNewProduceMessageActionPeriodically_Prepare(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedError: extension_kit.ToError("the target is missing the kafka.topic.name attribute", nil), }, } @@ -94,8 +92,9 @@ func TestNewProduceMessageActionPeriodically_Prepare(t *testing.T) { //Given state := action.NewEmptyState() request := tt.requestBody + //When - _, err := action.Prepare(context.Background(), &state, request) + _, err := action.Prepare(t.Context(), &state, request) //Then if tt.wantedError != nil { @@ -120,14 +119,12 @@ func TestNewHTTPCheckActionPeriodically_All_Success(t *testing.T) { kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(1), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer c.Close() seeds := c.ListenAddrs() config.Config.SeedBrokers = strings.Join(seeds, ",") - //prepare the action + action := produceMessageActionPeriodically{} state := action.NewEmptyState() prepareActionRequestBody := extutil.JsonMangle(action_kit_api.PrepareActionRequestBody{ @@ -150,7 +147,7 @@ func TestNewHTTPCheckActionPeriodically_All_Success(t *testing.T) { }) // Prepare - prepareResult, err := action.Prepare(context.Background(), &state, prepareActionRequestBody) + prepareResult, err := action.Prepare(t.Context(), &state, prepareActionRequestBody) assert.NoError(t, err) assert.Nil(t, prepareResult) assert.Greater(t, state.DelayBetweenRequestsInMS, extutil.ToInt64(0)) @@ -158,22 +155,26 @@ func TestNewHTTPCheckActionPeriodically_All_Success(t *testing.T) { executionRunData, err := action.getExecutionRunData(state.ExecutionID) assert.NoError(t, err) assert.NotNil(t, executionRunData) + // Start - startResult, err := action.Start(context.Background(), &state) + startResult, err := action.Start(t.Context(), &state) assert.NoError(t, err) assert.Nil(t, startResult) // Status - statusResult, err := action.Status(context.Background(), &state) + statusResult, err := action.Status(t.Context(), &state) assert.NoError(t, err) assert.NotNil(t, statusResult.Metrics) + time.Sleep(10 * time.Second) + // Status completed - statusResult, err = action.Status(context.Background(), &state) + statusResult, err = action.Status(t.Context(), &state) assert.NoError(t, err) assert.Equal(t, false, statusResult.Completed) + // Stop - stopResult, err := action.Stop(context.Background(), &state) + stopResult, err := action.Stop(t.Context(), &state) assert.NoError(t, err) assert.NotNil(t, stopResult.Metrics) assert.Nil(t, stopResult.Error) @@ -185,14 +186,12 @@ func TestNewHTTPCheckActionPeriodically_All_Failure(t *testing.T) { kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(1), ) - if err != nil { - panic(err) - } + require.NoError(t, err) defer c.Close() seeds := c.ListenAddrs() config.Config.SeedBrokers = strings.Join(seeds, ",") - //prepare the action + action := produceMessageActionPeriodically{} state := action.NewEmptyState() prepareActionRequestBody := extutil.JsonMangle(action_kit_api.PrepareActionRequestBody{ @@ -217,31 +216,34 @@ func TestNewHTTPCheckActionPeriodically_All_Failure(t *testing.T) { }) // Prepare - prepareResult, err := action.Prepare(context.Background(), &state, prepareActionRequestBody) + prepareResult, err := action.Prepare(t.Context(), &state, prepareActionRequestBody) assert.NoError(t, err) assert.Nil(t, prepareResult) assert.Greater(t, state.DelayBetweenRequestsInMS, extutil.ToInt64(0)) // Start - startResult, err := action.Start(context.Background(), &state) + startResult, err := action.Start(t.Context(), &state) assert.NoError(t, err) assert.Nil(t, startResult) // Status - statusResult, err := action.Status(context.Background(), &state) + statusResult, err := action.Status(t.Context(), &state) assert.NoError(t, err) assert.NotNil(t, statusResult.Metrics) + time.Sleep(5 * time.Second) + // Status completed - statusResult, err = action.Status(context.Background(), &state) + statusResult, err = action.Status(t.Context(), &state) assert.NoError(t, err) assert.Equal(t, statusResult.Completed, false) executionRunData, err := action.getExecutionRunData(state.ExecutionID) assert.NoError(t, err) assert.Greater(t, executionRunData.requestCounter.Load(), uint64(0)) + // Stop - stopResult, err := action.Stop(context.Background(), &state) + stopResult, err := action.Stop(t.Context(), &state) assert.NoError(t, err) assert.NotNil(t, stopResult.Metrics) assert.NotNil(t, stopResult.Error) diff --git a/extkafka/produce_test.go b/extkafka/produce_test.go index 32f48ae..f72188a 100644 --- a/extkafka/produce_test.go +++ b/extkafka/produce_test.go @@ -14,7 +14,6 @@ import ( ) func TestAction_Prepare(t *testing.T) { - tests := []struct { name string requestBody action_kit_api.PrepareActionRequestBody @@ -42,7 +41,6 @@ func TestAction_Prepare(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedState: &KafkaBrokerAttackState{ ConsumerGroup: "", Topic: "steadybit", @@ -68,7 +66,6 @@ func TestAction_Prepare(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedError: extension_kit.ToError("failed to interpret config value for recordHeaders as a key/value array", nil), }, { @@ -85,7 +82,6 @@ func TestAction_Prepare(t *testing.T) { }, ExecutionId: uuid.New(), }), - wantedError: extension_kit.ToError("max concurrent can't be zero", nil), }, } @@ -94,6 +90,7 @@ func TestAction_Prepare(t *testing.T) { //Given state := KafkaBrokerAttackState{} request := tt.requestBody + //When _, err := prepare(request, &state, func(executionRunData *ExecutionRunData, state *KafkaBrokerAttackState) bool { return false }) @@ -115,7 +112,6 @@ func TestAction_Prepare(t *testing.T) { } func TestAction_Stop(t *testing.T) { - tests := []struct { name string requestBody action_kit_api.StopActionRequestBody @@ -147,6 +143,7 @@ func TestAction_Stop(t *testing.T) { t.Run(tt.name, func(t *testing.T) { //Given saveExecutionRunData(tt.state.ExecutionID, tt.executionRunData) + //When result, err := stop(tt.state) diff --git a/extkafka/topic_discovery_test.go b/extkafka/topic_discovery_test.go index 9fa0037..328edc9 100644 --- a/extkafka/topic_discovery_test.go +++ b/extkafka/topic_discovery_test.go @@ -1,13 +1,16 @@ // SPDX-License-Identifier: MIT -// SPDX-FileCopyrightText: 2024 Steadybit GmbH +// SPDX-FileCopyrightText: 2025 Steadybit GmbH package extkafka import ( "fmt" + "github.com/steadybit/discovery-kit/go/discovery_kit_api" + "github.com/stretchr/testify/assert" "reflect" "testing" + "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kadm" "context" @@ -16,49 +19,31 @@ import ( "strings" ) -// Test Describe() func TestDescribeTopic(t *testing.T) { - d := &kafkaTopicDiscovery{} - desc := d.Describe() - if desc.Id != kafkaTopicTargetId { - t.Errorf("Describe().Id = %q; want %q", desc.Id, kafkaTopicTargetId) - } - if desc.Discover.CallInterval == nil { - t.Error("Describe().Discover.CallInterval = nil; want non-nil") - } + desc := (&kafkaTopicDiscovery{}).Describe() + require.Equal(t, kafkaTopicTargetId, desc.Id) + require.NotNil(t, desc.Discover.CallInterval) } -// Test DescribeTarget() func TestDescribeTargetTopic(t *testing.T) { d := &kafkaTopicDiscovery{} td := d.DescribeTarget() - if td.Id != kafkaTopicTargetId { - t.Errorf("DescribeTarget().Id = %q; want %q", td.Id, kafkaTopicTargetId) - } - if td.Label.One != "Kafka topic" || td.Label.Other != "Kafka topics" { - t.Errorf("DescribeTarget().Label = %+v; want One=\"Kafka topic\", Other=\"Kafka topics\"", td.Label) - } - if td.Category == nil || *td.Category != "kafka" { - t.Errorf("DescribeTarget().Category = %v; want pointer to \"kafka\"", td.Category) - } - if len(td.Table.Columns) != 6 { - t.Errorf("DescribeTarget().Table.Columns length = %d; want 6", len(td.Table.Columns)) - } - if len(td.Table.OrderBy) != 1 { - t.Errorf("DescribeTarget().Table.OrderBy length = %d; want 1", len(td.Table.OrderBy)) - } else { - ob := td.Table.OrderBy[0] - if ob.Attribute != "steadybit.label" || ob.Direction != "ASC" { - t.Errorf("DescribeTarget().OrderBy[0] = %+v; want Attribute=\"steadybit.label\", Direction=\"ASC\"", ob) - } - } + require.Equal(t, kafkaTopicTargetId, td.Id) + require.Equal(t, "Kafka topic", td.Label.One) + require.Equal(t, "Kafka topics", td.Label.Other) + require.NotNil(t, td.Category) + require.Equal(t, "kafka", *td.Category) + require.Len(t, td.Table.Columns, 6) + require.Len(t, td.Table.OrderBy, 1) + + ob := td.Table.OrderBy[0] + require.Equal(t, "steadybit.label", ob.Attribute) + require.Equal(t, discovery_kit_api.OrderByDirection("ASC"), ob.Direction) } -// Test DescribeAttributes() func TestDescribeAttributesTopic(t *testing.T) { - d := &kafkaTopicDiscovery{} - attrs := d.DescribeAttributes() + attrs := (&kafkaTopicDiscovery{}).DescribeAttributes() expected := []string{ "kafka.topic.name", "kafka.topic.partitions", @@ -68,10 +53,7 @@ func TestDescribeAttributesTopic(t *testing.T) { "kafka.topic.replication-factor", } - if len(attrs) != len(expected) { - t.Fatalf("DescribeAttributes() len = %d; want %d", len(attrs), len(expected)) - } - + require.Len(t, attrs, len(expected)) for _, want := range expected { found := false for _, a := range attrs { @@ -80,13 +62,10 @@ func TestDescribeAttributesTopic(t *testing.T) { break } } - if !found { - t.Errorf("DescribeAttributes() missing %q", want) - } + assert.Truef(t, found, "DescribeAttributes() missing %q", want) } } -// Test toTopicTarget() func TestToTopicTargetTopic(t *testing.T) { td := kadm.TopicDetail{ Topic: "my-topic", @@ -96,32 +75,19 @@ func TestToTopicTargetTopic(t *testing.T) { }, } cluster := "cluster-42" - tgt := toTopicTarget(td, cluster) // Basic fields - if want := "my-topic-cluster-42"; tgt.Id != want { - t.Errorf("Id = %q; want %q", tgt.Id, want) - } - if tgt.Label != "my-topic" { - t.Errorf("Label = %q; want %q", tgt.Label, "my-topic") - } - if tgt.TargetType != kafkaTopicTargetId { - t.Errorf("TargetType = %q; want %q", tgt.TargetType, kafkaTopicTargetId) - } + assert.Equal(t, "my-topic-cluster-42", tgt.Id) + assert.Equal(t, "my-topic", tgt.Label) + assert.Equal(t, kafkaTopicTargetId, tgt.TargetType) // Attributes attr := tgt.Attributes - check := func(key string, want []string) { v, ok := attr[key] - if !ok { - t.Errorf("missing attribute %q", key) - return - } - if !reflect.DeepEqual(v, want) { - t.Errorf("%s = %v; want %v", key, v, want) - } + assert.True(t, ok, "missing attribute %q", key) + assert.True(t, reflect.DeepEqual(v, want), "%s = %v; want %v", key, v, want) } check("kafka.cluster.name", []string{cluster}) @@ -148,15 +114,12 @@ func TestToTopicTargetTopic(t *testing.T) { // TestDiscoverTargetsClusterName verifies that the kafka.cluster.name attribute // is correctly set when discovering topics against a fake Kafka cluster. func TestDiscoverTopicTargetsClusterName(t *testing.T) { - // Set up fake Kafka cluster with a topic "steadybit" c, err := kfake.NewCluster( kfake.SeedTopics(-1, "steadybit"), kfake.NumBrokers(1), kfake.ClusterID("test"), ) - if err != nil { - t.Fatalf("failed to create fake cluster: %v", err) - } + require.NoError(t, err) defer c.Close() // Configure seed brokers for discovery @@ -169,34 +132,23 @@ func TestDiscoverTopicTargetsClusterName(t *testing.T) { // Discover targets ctx := context.Background() targets, err := getAllTopics(ctx) - if err != nil { - t.Fatalf("getAllTopics error: %v", err) - } - if len(targets) == 0 { - t.Fatal("expected at least one discovered topic") - } + require.NoError(t, err) + require.NotEmpty(t, targets) // Retrieve expected cluster name from metadata client, err := createNewAdminClient(strings.Split(config.Config.SeedBrokers, ",")) - if err != nil { - t.Fatalf("createNewAdminClient error: %v", err) - } + require.NoError(t, err) defer client.Close() + meta, err := client.BrokerMetadata(ctx) - if err != nil { - t.Fatalf("BrokerMetadata error: %v", err) - } + require.NoError(t, err) expected := meta.Cluster // Assert each discovered target has the correct cluster name attribute for _, tgt := range targets { values, ok := tgt.Attributes["kafka.cluster.name"] - if !ok { - t.Errorf("missing kafka.cluster.name for target %s", tgt.Id) - continue - } - if len(values) != 1 || values[0] != expected { - t.Errorf("kafka.cluster.name = %v; want [%s]", values, expected) - } + require.True(t, ok, "missing kafka.cluster.name for target %s", tgt.Id) + require.Len(t, values, 1) + require.Equal(t, expected, values[0]) } } From 75c410200289695d31c0bbf15c1f38c1a259acfb Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Fri, 1 Aug 2025 13:14:06 +0200 Subject: [PATCH 3/3] test: extend integration tests --- .github/workflows/ci.yml | 1 + Makefile | 6 ++ e2e/integration_test.go | 209 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 210 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcffcf1..e3e093e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: go_version: '1.24' runs_on: steadybit_runner_ubuntu_latest_4cores_16GB build_linux_packages: true + run_make_prepare_audit: true VERSION_BUMPER_APPID: ${{ vars.GH_APP_STEADYBIT_APP_ID }} secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/Makefile b/Makefile index 27cf7e3..d9bad2a 100755 --- a/Makefile +++ b/Makefile @@ -32,6 +32,12 @@ tidy: go fmt ./... go mod tidy -v +## prepare_audit: install required kafkactl command for e2e tests, only intended for the CI runner +.PHONY: prepare_audit +prepare_audit: + wget https://github.com/deviceinsight/kafkactl/releases/download/v5.11.1/kafkactl_5.11.1_linux_amd64.deb + sudo dpkg -i kafkactl_5.11.1_linux_amd64.deb + ## audit: run quality control checks .PHONY: audit audit: diff --git a/e2e/integration_test.go b/e2e/integration_test.go index f3c3248..c924105 100644 --- a/e2e/integration_test.go +++ b/e2e/integration_test.go @@ -6,7 +6,9 @@ package e2e import ( "context" "fmt" + "github.com/rs/zerolog/log" "github.com/steadybit/action-kit/go/action_kit_api/v2" + "github.com/steadybit/action-kit/go/action_kit_test/client" "github.com/steadybit/action-kit/go/action_kit_test/e2e" actValidate "github.com/steadybit/action-kit/go/action_kit_test/validate" "github.com/steadybit/discovery-kit/go/discovery_kit_api" @@ -14,19 +16,29 @@ import ( "github.com/steadybit/extension-kit/extlogging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "io" + "os" "os/exec" + "path/filepath" + "strconv" + "strings" "testing" "time" ) +var kafkactl func(ctx context.Context, commands ...string) (string, error) +var kafkactlStop func() + func TestWithMinikube(t *testing.T) { extlogging.InitZeroLog() extFactory := e2e.HelmExtensionFactory{ Name: "extension-kafka", Port: 8083, + ExtraArgs: func(m *e2e.Minikube) []string { return []string{ + "--set", "logging.lever=debug", "--set", "kafka.seedBrokers='my-kafka.default.svc.cluster.local:9092'", "--set", "kafka.auth.saslMechanism=PLAIN", "--set", "kafka.auth.saslUser=user1", @@ -35,7 +47,13 @@ func TestWithMinikube(t *testing.T) { }, } - e2e.WithMinikube(t, e2e.DefaultMinikubeOpts().AfterStart(helmInstallLocalStack), &extFactory, []e2e.WithMinikubeTestCase{ + defer func() { + if kafkactlStop != nil { + kafkactlStop() + } + }() + + e2e.WithMinikube(t, e2e.DefaultMinikubeOpts().AfterStart(helmInstallLocalStack).AfterStart(setupKafkactl), &extFactory, []e2e.WithMinikubeTestCase{ { Name: "validate discovery", Test: validateDiscovery, @@ -56,6 +74,14 @@ func TestWithMinikube(t *testing.T) { Name: "alter num network threads", Test: testAlterNumNetworkThreads, }, + { + Name: "alter limit connection creation rate", + Test: testAlterLimitConnectionCreationRate, + }, + { + Name: "alter max message bytes", + Test: testAlterMaxMessageBytes, + }, }) } @@ -93,7 +119,7 @@ func testAlterNumIoThreads(t *testing.T, _ *e2e.Minikube, e *e2e.Extension) { Duration int `json:"duration"` IoThreads float32 `json:"io_threads"` }{ - Duration: 5000, + Duration: 20000, IoThreads: 1.0, } @@ -102,6 +128,12 @@ func testAlterNumIoThreads(t *testing.T, _ *e2e.Minikube, e *e2e.Extension) { require.NoError(t, err) defer func() { _ = increaseThreadsAction.Cancel() }() + require.EventuallyWithT(t, func(c *assert.CollectT) { + brokerConfig, err := kafkactl(t.Context(), "describe", "broker", "0") + assert.NoError(c, err, "Failed to describe broker config") + assert.Regexp(c, `num\.io\.threads\s+1`, brokerConfig, "property not found") + }, 20*time.Second, 1*time.Second, "num.io.threads should be set to 1") + require.NoError(t, increaseThreadsAction.Wait()) require.NotEmpty(t, t, increaseThreadsAction.Messages()) require.NotEmpty(t, t, increaseThreadsAction.Metrics()) @@ -112,6 +144,12 @@ func testAlterNumIoThreads(t *testing.T, _ *e2e.Minikube, e *e2e.Extension) { require.NoError(t, err) defer func() { _ = decreaseThreadsAction.Cancel() }() + require.EventuallyWithT(t, func(c *assert.CollectT) { + brokerConfig, err := kafkactl(t.Context(), "describe", "broker", "0") + assert.NoError(c, err, "Failed to describe broker config") + assert.Regexp(c, `num\.io\.threads\s+1`, brokerConfig, "property not found") + }, 20*time.Second, 1*time.Second, "num.io.threads should be set to 100") + require.NoError(t, increaseThreadsAction.Wait()) require.NotEmpty(t, t, decreaseThreadsAction.Messages()) require.NotEmpty(t, t, decreaseThreadsAction.Metrics()) @@ -126,11 +164,11 @@ func testAlterNumNetworkThreads(t *testing.T, _ *e2e.Minikube, e *e2e.Extension) } config := struct { - Duration int `json:"duration"` - NetworkThreads float32 `json:"network_threads"` + Duration int `json:"duration"` + NetworkThreads int `json:"network_threads"` }{ - Duration: 5000, - NetworkThreads: 1.0, + Duration: 20000, + NetworkThreads: 1, } // Reduce @@ -138,6 +176,12 @@ func testAlterNumNetworkThreads(t *testing.T, _ *e2e.Minikube, e *e2e.Extension) require.NoError(t, err) defer func() { _ = increaseThreadsAction.Cancel() }() + require.EventuallyWithT(t, func(c *assert.CollectT) { + brokerConfig, err := kafkactl(t.Context(), "describe", "broker", "0") + assert.NoError(c, err, "Failed to describe broker config") + assert.Regexp(c, `num\.network\.threads\s+1`, brokerConfig, "property not found") + }, 20*time.Second, 1*time.Second, "num.network.threads should be set to 1") + require.NoError(t, increaseThreadsAction.Wait()) require.NotEmpty(t, t, increaseThreadsAction.Messages()) require.NotEmpty(t, t, increaseThreadsAction.Metrics()) @@ -148,11 +192,89 @@ func testAlterNumNetworkThreads(t *testing.T, _ *e2e.Minikube, e *e2e.Extension) require.NoError(t, err) defer func() { _ = decreaseThreadsAction.Cancel() }() + require.EventuallyWithT(t, func(c *assert.CollectT) { + brokerConfig, err := kafkactl(t.Context(), "describe", "broker", "0") + assert.NoError(c, err, "Failed to describe broker config") + assert.Regexp(c, `num\.network\.threads\s+1`, brokerConfig, "property not found") + }, 20*time.Second, 1*time.Second, "num.network.threads should be set to 1") + require.NoError(t, increaseThreadsAction.Wait()) require.NotEmpty(t, t, decreaseThreadsAction.Messages()) require.NotEmpty(t, t, decreaseThreadsAction.Metrics()) } +func testAlterLimitConnectionCreationRate(t *testing.T, _ *e2e.Minikube, e *e2e.Extension) { + target := &action_kit_api.Target{ + Name: "test_broker", + Attributes: map[string][]string{ + "kafka.broker.node-id": {"0"}, + }, + } + + config := struct { + Duration int `json:"duration"` + ConnectionRate int `json:"connection_rate"` + }{ + Duration: 20000, + ConnectionRate: 1, + } + + action, err := e.RunAction("com.steadybit.extension_kafka.broker.limit-connection-creation", target, config, &action_kit_api.ExecutionContext{}) + require.NoError(t, err) + defer func() { _ = action.Cancel() }() + + // Testing this setting by opening up too many connections does not work with the current setup, + // as executing "kubectl" commands is too slow, and it does not support parallel invocations. + require.EventuallyWithT(t, func(c *assert.CollectT) { + brokerConfig, err := kafkactl(t.Context(), "describe", "broker", "0") + assert.NoError(c, err, "Failed to describe broker config") + assert.Regexp(c, `max\.connection\.creation\.rate\s+1`, brokerConfig, "property not found") + }, 20*time.Second, 1*time.Second, "max.connection.creation.rate should be set to 1") + + require.NoError(t, action.Wait()) + require.NotEmpty(t, t, action.Messages()) + require.NotEmpty(t, t, action.Metrics()) +} + +func testAlterMaxMessageBytes(t *testing.T, _ *e2e.Minikube, e *e2e.Extension) { + message := fmt.Sprintf("{\"a\": \"%s\"}", strings.Repeat("x", 1000)) + out, err := kafkactl(t.Context(), "produce", "foo", "-v", message) + require.NoError(t, err, out) + + config := struct { + Duration int `json:"duration"` + MaxBytes int `json:"max_bytes"` + }{ + Duration: 20000, + MaxBytes: 100, + } + + // Change message size setting on all nodes + var action client.ActionExecution + for i := 0; i < 3; i++ { + target := &action_kit_api.Target{ + Name: "test_broker", + Attributes: map[string][]string{ + "kafka.broker.node-id": {strconv.Itoa(i)}, + }, + } + action, err = e.RunAction("com.steadybit.extension_kafka.broker.reduce-message-max-bytes", target, config, &action_kit_api.ExecutionContext{}) + require.NoError(t, err) + //goland:noinspection ALL + defer func(a client.ActionExecution) { _ = a.Cancel() }(action) + } + + require.EventuallyWithT(t, func(c *assert.CollectT) { + out, err = kafkactl(t.Context(), "produce", "foo", "-v", message) + require.Error(t, err, out) + }, 20*time.Second, 1*time.Second, "long messages should be rejected") + + //goland:noinspection GoDfaNilDereference + require.NoError(t, action.Wait()) + require.NotEmpty(t, t, action.Messages()) + require.NotEmpty(t, t, action.Metrics()) +} + func helmInstallLocalStack(minikube *e2e.Minikube) error { out, err := exec.Command("helm", "repo", "add", "bitnami", "https://charts.bitnami.com/bitnami").CombinedOutput() if err != nil { @@ -162,6 +284,8 @@ func helmInstallLocalStack(minikube *e2e.Minikube) error { "upgrade", "--install", "--kube-context", minikube.Profile, "--set", "sasl.client.passwords=steadybit", + "--set", "provisioning.enabled=true", + "--set", "provisioning.topics[0].name=foo", "--namespace=default", "--timeout=15m0s", "my-kafka", "bitnami/kafka ", "--wait").CombinedOutput() @@ -170,3 +294,76 @@ func helmInstallLocalStack(minikube *e2e.Minikube) error { } return nil } + +func setupKafkactl(m *e2e.Minikube) error { + configPath := filepath.Join(os.Getenv("HOME"), ".config", "kafkactl", "config.yml") + if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + backupPath := configPath + ".backup" + if _, err := os.Stat(configPath); err == nil { + if err := copyFile(configPath, backupPath); err != nil { + return fmt.Errorf("failed to backup config: %w", err) + } + } + + stop := func() { + if err := os.Rename(backupPath, configPath); err != nil { + log.Error().Err(err).Msg("Failed to restore original config") + } + } + + configContent := fmt.Sprintf(`contexts: + e2e: + brokers: + - my-kafka.default.svc.cluster.local:9092 + tls: + enabled: false + sasl: + enabled: true + username: user1 + password: steadybit + kubernetes: + enabled: true + kubecontext: %s + namespace: default +`, m.Profile) + + if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { + stop() + return fmt.Errorf("failed to write temporary config: %w", err) + } + + kafkactl = func(ctx context.Context, commands ...string) (string, error) { + cmd := exec.CommandContext(ctx, "kafkactl", append(commands, "--context", "e2e")...) + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("kafkactl command failed: %w, output: %s", err, string(output)) + } + return string(output), nil + } + kafkactlStop = stop + return nil +} + +func copyFile(src, dst string) error { + sourceFile, err := os.Open(src) + if err != nil { + return err + } + defer func(sourceFile *os.File) { + _ = sourceFile.Close() + }(sourceFile) + + destFile, err := os.Create(dst) + if err != nil { + return err + } + defer func(destFile *os.File) { + _ = destFile.Close() + }(destFile) + + _, err = io.Copy(destFile, sourceFile) + return err +}