mirror of
https://github.com/hanzokv/go.git
synced 2026-09-24 00:31:51 +00:00
* update e2e test, change script * update script and tests * fixed bdbid parsing * disabled majority of tests, swapped event order * change the config tag * revert test order * fix typo * reenable all e2e tests * change the clonfig flag key for all e2e tests * improve logging for debug purposes of tests * longer deadline for FI in CI * increase waiting for notifications * extend tests * dont fail on flaky third client * fi new params * fix test build * more time for migrating * first wait for FI action, then assert notification * fix test build * fix tests * fix tests * change output * global print logs for tests * better output * fix error format * maybe the notification is already received * second and third client fix * print output if failed * better second and third client checks * output action data if notification is not received * stop command runner * database create / delete actions * database create / delete actions used in tests * fix import * remove example * remove unused var * use different port than the one in env * wait for action to get the response * fix output * fix create db config * fix create db config * use new database for client * fix create db config * db per scenario * less logs, correct check * Add CTRF to the scenario tests (#3545) * add some json ctrf improvements * fix -v * attempt to separate the output --------- Co-authored-by: Nedyalko Dyakov <[email protected]> --------- Co-authored-by: Nedyalko Dyakov <[email protected]> Co-authored-by: kiryazovi-redis <[email protected]> Co-authored-by: Nedyalko Dyakov <[email protected]>
246 lines
6.3 KiB
Plaintext
246 lines
6.3 KiB
Plaintext
|
|
|
|
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"github.com/redis/go-redis/v9/hitless"
|
|
)
|
|
|
|
// TestScenarioTemplate is a template for writing scenario tests
|
|
// Copy this file and rename it to scenario_your_test_name.go
|
|
func TestScenarioTemplate(t *testing.T) {
|
|
if os.Getenv("E2E_SCENARIO_TESTS") != "true" {
|
|
t.Skip("Scenario tests require E2E_SCENARIO_TESTS=true")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
|
defer cancel()
|
|
|
|
// Step 1: Create client factory from configuration
|
|
factory, err := CreateTestClientFactory("enterprise-cluster") // or "standalone0"
|
|
if err != nil {
|
|
t.Fatalf("Failed to create client factory: %v", err)
|
|
}
|
|
defer factory.DestroyAll()
|
|
|
|
// Step 2: Create fault injector
|
|
faultInjector, err := CreateTestFaultInjector()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create fault injector: %v", err)
|
|
}
|
|
|
|
// Step 3: Create Redis client with hitless upgrades
|
|
client, err := factory.Create("scenario-client", &CreateClientOptions{
|
|
Protocol: 3,
|
|
HitlessUpgradeConfig: &hitless.Config{
|
|
Mode: hitless.MaintNotificationsEnabled,
|
|
HandoffTimeout: 30000, // 30 seconds
|
|
RelaxedTimeout: 10000, // 10 seconds
|
|
MaxWorkers: 20,
|
|
},
|
|
ClientName: "scenario-test-client",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create client: %v", err)
|
|
}
|
|
|
|
// Step 4: Verify initial connectivity
|
|
err = client.Ping(ctx).Err()
|
|
if err != nil {
|
|
t.Fatalf("Failed to ping Redis: %v", err)
|
|
}
|
|
|
|
t.Log("Initial setup completed successfully")
|
|
|
|
// Step 5: Start background operations (optional)
|
|
stopCh := make(chan struct{})
|
|
defer close(stopCh)
|
|
|
|
go func() {
|
|
counter := 0
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-stopCh:
|
|
return
|
|
case <-ticker.C:
|
|
key := fmt.Sprintf("test-key-%d", counter)
|
|
value := fmt.Sprintf("test-value-%d", counter)
|
|
|
|
err := client.Set(ctx, key, value, time.Minute).Err()
|
|
if err != nil {
|
|
t.Logf("Background operation failed: %v", err)
|
|
}
|
|
|
|
counter++
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Step 6: Wait for baseline operations
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Step 7: Trigger fault injection scenario
|
|
t.Log("Triggering fault injection scenario...")
|
|
|
|
// Example: Cluster failover
|
|
// resp, err := faultInjector.TriggerClusterFailover(ctx, "node-1", false)
|
|
// if err != nil {
|
|
// t.Fatalf("Failed to trigger failover: %v", err)
|
|
// }
|
|
|
|
// Example: Network latency
|
|
// nodes := []string{"localhost:7001", "localhost:7002"}
|
|
// resp, err := faultInjector.SimulateNetworkLatency(ctx, nodes, 100*time.Millisecond, 20*time.Millisecond)
|
|
// if err != nil {
|
|
// t.Fatalf("Failed to simulate latency: %v", err)
|
|
// }
|
|
|
|
// Example: Complex sequence
|
|
// sequence := []SequenceAction{
|
|
// {
|
|
// Type: ActionNetworkLatency,
|
|
// Parameters: map[string]interface{}{
|
|
// "nodes": []string{"localhost:7001"},
|
|
// "latency": "50ms",
|
|
// },
|
|
// },
|
|
// {
|
|
// Type: ActionClusterFailover,
|
|
// Parameters: map[string]interface{}{
|
|
// "node_id": "node-1",
|
|
// "force": false,
|
|
// },
|
|
// Delay: 10 * time.Second,
|
|
// },
|
|
// }
|
|
// resp, err := faultInjector.ExecuteSequence(ctx, sequence)
|
|
// if err != nil {
|
|
// t.Fatalf("Failed to execute sequence: %v", err)
|
|
// }
|
|
|
|
// Step 8: Wait for fault injection to complete
|
|
// status, err := faultInjector.WaitForAction(ctx, resp.ActionID,
|
|
// WithMaxWaitTime(240*time.Second),
|
|
// WithPollInterval(2*time.Second))
|
|
// if err != nil {
|
|
// t.Fatalf("Fault injection failed: %v", err)
|
|
// }
|
|
// t.Logf("Fault injection completed: %s", status.Status)
|
|
|
|
// Step 9: Verify client remains operational during and after fault injection
|
|
time.Sleep(10 * time.Second)
|
|
|
|
err = client.Ping(ctx).Err()
|
|
if err != nil {
|
|
t.Errorf("Client not responsive after fault injection: %v", err)
|
|
}
|
|
|
|
// Step 10: Perform additional validation
|
|
testKey := "validation-key"
|
|
testValue := "validation-value"
|
|
|
|
err = client.Set(ctx, testKey, testValue, time.Minute).Err()
|
|
if err != nil {
|
|
t.Errorf("Failed to set validation key: %v", err)
|
|
}
|
|
|
|
retrievedValue, err := client.Get(ctx, testKey).Result()
|
|
if err != nil {
|
|
t.Errorf("Failed to get validation key: %v", err)
|
|
} else if retrievedValue != testValue {
|
|
t.Errorf("Validation failed: expected %s, got %s", testValue, retrievedValue)
|
|
}
|
|
|
|
t.Log("Scenario test completed successfully")
|
|
}
|
|
|
|
// Helper functions for common scenario patterns
|
|
|
|
func performContinuousOperations(ctx context.Context, client redis.UniversalClient, workerID int, stopCh <-chan struct{}, errorCh chan<- error) {
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
counter := 0
|
|
for {
|
|
select {
|
|
case <-stopCh:
|
|
return
|
|
case <-ticker.C:
|
|
key := fmt.Sprintf("worker_%d_key_%d", workerID, counter)
|
|
value := fmt.Sprintf("value_%d", counter)
|
|
|
|
// Perform operation with timeout
|
|
opCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
err := client.Set(opCtx, key, value, time.Minute).Err()
|
|
cancel()
|
|
|
|
if err != nil {
|
|
select {
|
|
case errorCh <- err:
|
|
default:
|
|
}
|
|
}
|
|
|
|
counter++
|
|
}
|
|
}
|
|
}
|
|
|
|
func validateClusterHealth(ctx context.Context, client redis.UniversalClient) error {
|
|
// Basic connectivity test
|
|
if err := client.Ping(ctx).Err(); err != nil {
|
|
return fmt.Errorf("ping failed: %w", err)
|
|
}
|
|
|
|
// Test basic operations
|
|
testKey := "health-check-key"
|
|
testValue := "health-check-value"
|
|
|
|
if err := client.Set(ctx, testKey, testValue, time.Minute).Err(); err != nil {
|
|
return fmt.Errorf("set operation failed: %w", err)
|
|
}
|
|
|
|
retrievedValue, err := client.Get(ctx, testKey).Result()
|
|
if err != nil {
|
|
return fmt.Errorf("get operation failed: %w", err)
|
|
}
|
|
|
|
if retrievedValue != testValue {
|
|
return fmt.Errorf("value mismatch: expected %s, got %s", testValue, retrievedValue)
|
|
}
|
|
|
|
// Clean up
|
|
client.Del(ctx, testKey)
|
|
|
|
return nil
|
|
}
|
|
|
|
func waitForStableOperations(ctx context.Context, client redis.UniversalClient, duration time.Duration) error {
|
|
deadline := time.Now().Add(duration)
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for time.Now().Before(deadline) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-ticker.C:
|
|
if err := validateClusterHealth(ctx, client); err != nil {
|
|
return fmt.Errorf("cluster health check failed: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|