You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							361 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							361 lines
						
					
					
						
							11 KiB
						
					
					
				| package config | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"os" | |
| 	"strconv" | |
| 	"strings" | |
| 	"time" | |
| 
 | |
| 	"gopkg.in/yaml.v3" | |
| ) | |
| 
 | |
| // Config represents the complete load test configuration | |
| type Config struct { | |
| 	TestMode string        `yaml:"test_mode"` | |
| 	Duration time.Duration `yaml:"duration"` | |
| 
 | |
| 	Kafka          KafkaConfig          `yaml:"kafka"` | |
| 	SchemaRegistry SchemaRegistryConfig `yaml:"schema_registry"` | |
| 	Producers      ProducersConfig      `yaml:"producers"` | |
| 	Consumers      ConsumersConfig      `yaml:"consumers"` | |
| 	Topics         TopicsConfig         `yaml:"topics"` | |
| 	Schemas        SchemasConfig        `yaml:"schemas"` | |
| 	Metrics        MetricsConfig        `yaml:"metrics"` | |
| 	Scenarios      ScenariosConfig      `yaml:"scenarios"` | |
| 	Chaos          ChaosConfig          `yaml:"chaos"` | |
| 	Output         OutputConfig         `yaml:"output"` | |
| 	Logging        LoggingConfig        `yaml:"logging"` | |
| } | |
| 
 | |
| type KafkaConfig struct { | |
| 	BootstrapServers []string `yaml:"bootstrap_servers"` | |
| 	SecurityProtocol string   `yaml:"security_protocol"` | |
| 	SASLMechanism    string   `yaml:"sasl_mechanism"` | |
| 	SASLUsername     string   `yaml:"sasl_username"` | |
| 	SASLPassword     string   `yaml:"sasl_password"` | |
| } | |
| 
 | |
| type SchemaRegistryConfig struct { | |
| 	URL  string `yaml:"url"` | |
| 	Auth struct { | |
| 		Username string `yaml:"username"` | |
| 		Password string `yaml:"password"` | |
| 	} `yaml:"auth"` | |
| } | |
| 
 | |
| type ProducersConfig struct { | |
| 	Count             int    `yaml:"count"` | |
| 	MessageRate       int    `yaml:"message_rate"` | |
| 	MessageSize       int    `yaml:"message_size"` | |
| 	BatchSize         int    `yaml:"batch_size"` | |
| 	LingerMs          int    `yaml:"linger_ms"` | |
| 	CompressionType   string `yaml:"compression_type"` | |
| 	Acks              string `yaml:"acks"` | |
| 	Retries           int    `yaml:"retries"` | |
| 	RetryBackoffMs    int    `yaml:"retry_backoff_ms"` | |
| 	RequestTimeoutMs  int    `yaml:"request_timeout_ms"` | |
| 	DeliveryTimeoutMs int    `yaml:"delivery_timeout_ms"` | |
| 	KeyDistribution   string `yaml:"key_distribution"` | |
| 	ValueType         string `yaml:"value_type"`    // json, avro, protobuf, binary | |
| 	SchemaFormat      string `yaml:"schema_format"` // AVRO, JSON, PROTOBUF (schema registry format) | |
| 	IncludeTimestamp  bool   `yaml:"include_timestamp"` | |
| 	IncludeHeaders    bool   `yaml:"include_headers"` | |
| } | |
| 
 | |
| type ConsumersConfig struct { | |
| 	Count                int    `yaml:"count"` | |
| 	GroupPrefix          string `yaml:"group_prefix"` | |
| 	AutoOffsetReset      string `yaml:"auto_offset_reset"` | |
| 	EnableAutoCommit     bool   `yaml:"enable_auto_commit"` | |
| 	AutoCommitIntervalMs int    `yaml:"auto_commit_interval_ms"` | |
| 	SessionTimeoutMs     int    `yaml:"session_timeout_ms"` | |
| 	HeartbeatIntervalMs  int    `yaml:"heartbeat_interval_ms"` | |
| 	MaxPollRecords       int    `yaml:"max_poll_records"` | |
| 	MaxPollIntervalMs    int    `yaml:"max_poll_interval_ms"` | |
| 	FetchMinBytes        int    `yaml:"fetch_min_bytes"` | |
| 	FetchMaxBytes        int    `yaml:"fetch_max_bytes"` | |
| 	FetchMaxWaitMs       int    `yaml:"fetch_max_wait_ms"` | |
| } | |
| 
 | |
| type TopicsConfig struct { | |
| 	Count             int    `yaml:"count"` | |
| 	Prefix            string `yaml:"prefix"` | |
| 	Partitions        int    `yaml:"partitions"` | |
| 	ReplicationFactor int    `yaml:"replication_factor"` | |
| 	CleanupPolicy     string `yaml:"cleanup_policy"` | |
| 	RetentionMs       int64  `yaml:"retention_ms"` | |
| 	SegmentMs         int64  `yaml:"segment_ms"` | |
| } | |
| 
 | |
| type SchemaConfig struct { | |
| 	Type   string `yaml:"type"` | |
| 	Schema string `yaml:"schema"` | |
| } | |
| 
 | |
| type SchemasConfig struct { | |
| 	Enabled           bool         `yaml:"enabled"` | |
| 	RegistryTimeoutMs int          `yaml:"registry_timeout_ms"` | |
| 	UserEvent         SchemaConfig `yaml:"user_event"` | |
| 	Transaction       SchemaConfig `yaml:"transaction"` | |
| } | |
| 
 | |
| type MetricsConfig struct { | |
| 	Enabled            bool          `yaml:"enabled"` | |
| 	CollectionInterval time.Duration `yaml:"collection_interval"` | |
| 	PrometheusPort     int           `yaml:"prometheus_port"` | |
| 	TrackLatency       bool          `yaml:"track_latency"` | |
| 	TrackThroughput    bool          `yaml:"track_throughput"` | |
| 	TrackErrors        bool          `yaml:"track_errors"` | |
| 	TrackConsumerLag   bool          `yaml:"track_consumer_lag"` | |
| 	LatencyPercentiles []float64     `yaml:"latency_percentiles"` | |
| } | |
| 
 | |
| type ScenarioConfig struct { | |
| 	ProducerRate   int           `yaml:"producer_rate"` | |
| 	RampUpTime     time.Duration `yaml:"ramp_up_time"` | |
| 	SteadyDuration time.Duration `yaml:"steady_duration"` | |
| 	RampDownTime   time.Duration `yaml:"ramp_down_time"` | |
| 	BaseRate       int           `yaml:"base_rate"` | |
| 	BurstRate      int           `yaml:"burst_rate"` | |
| 	BurstDuration  time.Duration `yaml:"burst_duration"` | |
| 	BurstInterval  time.Duration `yaml:"burst_interval"` | |
| 	StartRate      int           `yaml:"start_rate"` | |
| 	EndRate        int           `yaml:"end_rate"` | |
| 	RampDuration   time.Duration `yaml:"ramp_duration"` | |
| 	StepDuration   time.Duration `yaml:"step_duration"` | |
| } | |
| 
 | |
| type ScenariosConfig struct { | |
| 	SteadyLoad ScenarioConfig `yaml:"steady_load"` | |
| 	BurstLoad  ScenarioConfig `yaml:"burst_load"` | |
| 	RampTest   ScenarioConfig `yaml:"ramp_test"` | |
| } | |
| 
 | |
| type ChaosConfig struct { | |
| 	Enabled                     bool          `yaml:"enabled"` | |
| 	ProducerFailureRate         float64       `yaml:"producer_failure_rate"` | |
| 	ConsumerFailureRate         float64       `yaml:"consumer_failure_rate"` | |
| 	NetworkPartitionProbability float64       `yaml:"network_partition_probability"` | |
| 	BrokerRestartInterval       time.Duration `yaml:"broker_restart_interval"` | |
| } | |
| 
 | |
| type OutputConfig struct { | |
| 	ResultsDir       string        `yaml:"results_dir"` | |
| 	ExportPrometheus bool          `yaml:"export_prometheus"` | |
| 	ExportCSV        bool          `yaml:"export_csv"` | |
| 	ExportJSON       bool          `yaml:"export_json"` | |
| 	RealTimeStats    bool          `yaml:"real_time_stats"` | |
| 	StatsInterval    time.Duration `yaml:"stats_interval"` | |
| } | |
| 
 | |
| type LoggingConfig struct { | |
| 	Level           string `yaml:"level"` | |
| 	Format          string `yaml:"format"` | |
| 	EnableKafkaLogs bool   `yaml:"enable_kafka_logs"` | |
| } | |
| 
 | |
| // Load reads and parses the configuration file | |
| func Load(configFile string) (*Config, error) { | |
| 	data, err := os.ReadFile(configFile) | |
| 	if err != nil { | |
| 		return nil, fmt.Errorf("failed to read config file %s: %w", configFile, err) | |
| 	} | |
| 
 | |
| 	var cfg Config | |
| 	if err := yaml.Unmarshal(data, &cfg); err != nil { | |
| 		return nil, fmt.Errorf("failed to parse config file %s: %w", configFile, err) | |
| 	} | |
| 
 | |
| 	// Apply default values | |
| 	cfg.setDefaults() | |
| 
 | |
| 	// Apply environment variable overrides | |
| 	cfg.applyEnvOverrides() | |
| 
 | |
| 	return &cfg, nil | |
| } | |
| 
 | |
| // ApplyOverrides applies command-line flag overrides | |
| func (c *Config) ApplyOverrides(testMode string, duration time.Duration) { | |
| 	if testMode != "" { | |
| 		c.TestMode = testMode | |
| 	} | |
| 	if duration > 0 { | |
| 		c.Duration = duration | |
| 	} | |
| } | |
| 
 | |
| // setDefaults sets default values for optional fields | |
| func (c *Config) setDefaults() { | |
| 	if c.TestMode == "" { | |
| 		c.TestMode = "comprehensive" | |
| 	} | |
| 
 | |
| 	if len(c.Kafka.BootstrapServers) == 0 { | |
| 		c.Kafka.BootstrapServers = []string{"kafka-gateway:9093"} | |
| 	} | |
| 
 | |
| 	if c.SchemaRegistry.URL == "" { | |
| 		c.SchemaRegistry.URL = "http://schema-registry:8081" | |
| 	} | |
| 
 | |
| 	// Schema support is always enabled since Kafka Gateway now enforces schema-first behavior | |
| 	c.Schemas.Enabled = true | |
| 
 | |
| 	if c.Producers.Count == 0 { | |
| 		c.Producers.Count = 10 | |
| 	} | |
| 
 | |
| 	if c.Consumers.Count == 0 { | |
| 		c.Consumers.Count = 5 | |
| 	} | |
| 
 | |
| 	if c.Topics.Count == 0 { | |
| 		c.Topics.Count = 5 | |
| 	} | |
| 
 | |
| 	if c.Topics.Prefix == "" { | |
| 		c.Topics.Prefix = "loadtest-topic" | |
| 	} | |
| 
 | |
| 	if c.Topics.Partitions == 0 { | |
| 		c.Topics.Partitions = 4 // Default to 4 partitions | |
| 	} | |
| 
 | |
| 	if c.Topics.ReplicationFactor == 0 { | |
| 		c.Topics.ReplicationFactor = 1 // Default to 1 replica | |
| 	} | |
| 
 | |
| 	if c.Consumers.GroupPrefix == "" { | |
| 		c.Consumers.GroupPrefix = "loadtest-group" | |
| 	} | |
| 
 | |
| 	if c.Output.ResultsDir == "" { | |
| 		c.Output.ResultsDir = "/test-results" | |
| 	} | |
| 
 | |
| 	if c.Metrics.CollectionInterval == 0 { | |
| 		c.Metrics.CollectionInterval = 10 * time.Second | |
| 	} | |
| 
 | |
| 	if c.Output.StatsInterval == 0 { | |
| 		c.Output.StatsInterval = 30 * time.Second | |
| 	} | |
| } | |
| 
 | |
| // applyEnvOverrides applies environment variable overrides | |
| func (c *Config) applyEnvOverrides() { | |
| 	if servers := os.Getenv("KAFKA_BOOTSTRAP_SERVERS"); servers != "" { | |
| 		c.Kafka.BootstrapServers = strings.Split(servers, ",") | |
| 	} | |
| 
 | |
| 	if url := os.Getenv("SCHEMA_REGISTRY_URL"); url != "" { | |
| 		c.SchemaRegistry.URL = url | |
| 	} | |
| 
 | |
| 	if mode := os.Getenv("TEST_MODE"); mode != "" { | |
| 		c.TestMode = mode | |
| 	} | |
| 
 | |
| 	if duration := os.Getenv("TEST_DURATION"); duration != "" { | |
| 		if d, err := time.ParseDuration(duration); err == nil { | |
| 			c.Duration = d | |
| 		} | |
| 	} | |
| 
 | |
| 	if count := os.Getenv("PRODUCER_COUNT"); count != "" { | |
| 		if i, err := strconv.Atoi(count); err == nil { | |
| 			c.Producers.Count = i | |
| 		} | |
| 	} | |
| 
 | |
| 	if count := os.Getenv("CONSUMER_COUNT"); count != "" { | |
| 		if i, err := strconv.Atoi(count); err == nil { | |
| 			c.Consumers.Count = i | |
| 		} | |
| 	} | |
| 
 | |
| 	if rate := os.Getenv("MESSAGE_RATE"); rate != "" { | |
| 		if i, err := strconv.Atoi(rate); err == nil { | |
| 			c.Producers.MessageRate = i | |
| 		} | |
| 	} | |
| 
 | |
| 	if size := os.Getenv("MESSAGE_SIZE"); size != "" { | |
| 		if i, err := strconv.Atoi(size); err == nil { | |
| 			c.Producers.MessageSize = i | |
| 		} | |
| 	} | |
| 
 | |
| 	if count := os.Getenv("TOPIC_COUNT"); count != "" { | |
| 		if i, err := strconv.Atoi(count); err == nil { | |
| 			c.Topics.Count = i | |
| 		} | |
| 	} | |
| 
 | |
| 	if partitions := os.Getenv("PARTITIONS_PER_TOPIC"); partitions != "" { | |
| 		if i, err := strconv.Atoi(partitions); err == nil { | |
| 			c.Topics.Partitions = i | |
| 		} | |
| 	} | |
| 
 | |
| 	if valueType := os.Getenv("VALUE_TYPE"); valueType != "" { | |
| 		c.Producers.ValueType = valueType | |
| 	} | |
| 
 | |
| 	if schemaFormat := os.Getenv("SCHEMA_FORMAT"); schemaFormat != "" { | |
| 		c.Producers.SchemaFormat = schemaFormat | |
| 	} | |
| 
 | |
| 	if enabled := os.Getenv("SCHEMAS_ENABLED"); enabled != "" { | |
| 		c.Schemas.Enabled = enabled == "true" | |
| 	} | |
| } | |
| 
 | |
| // GetTopicNames returns the list of topic names to use for testing | |
| func (c *Config) GetTopicNames() []string { | |
| 	topics := make([]string, c.Topics.Count) | |
| 	for i := 0; i < c.Topics.Count; i++ { | |
| 		topics[i] = fmt.Sprintf("%s-%d", c.Topics.Prefix, i) | |
| 	} | |
| 	return topics | |
| } | |
| 
 | |
| // GetConsumerGroupNames returns the list of consumer group names | |
| func (c *Config) GetConsumerGroupNames() []string { | |
| 	groups := make([]string, c.Consumers.Count) | |
| 	for i := 0; i < c.Consumers.Count; i++ { | |
| 		groups[i] = fmt.Sprintf("%s-%d", c.Consumers.GroupPrefix, i) | |
| 	} | |
| 	return groups | |
| } | |
| 
 | |
| // Validate validates the configuration | |
| func (c *Config) Validate() error { | |
| 	if c.TestMode != "producer" && c.TestMode != "consumer" && c.TestMode != "comprehensive" { | |
| 		return fmt.Errorf("invalid test mode: %s", c.TestMode) | |
| 	} | |
| 
 | |
| 	if len(c.Kafka.BootstrapServers) == 0 { | |
| 		return fmt.Errorf("kafka bootstrap servers not specified") | |
| 	} | |
| 
 | |
| 	if c.Producers.Count <= 0 && (c.TestMode == "producer" || c.TestMode == "comprehensive") { | |
| 		return fmt.Errorf("producer count must be greater than 0 for producer or comprehensive tests") | |
| 	} | |
| 
 | |
| 	if c.Consumers.Count <= 0 && (c.TestMode == "consumer" || c.TestMode == "comprehensive") { | |
| 		return fmt.Errorf("consumer count must be greater than 0 for consumer or comprehensive tests") | |
| 	} | |
| 
 | |
| 	if c.Topics.Count <= 0 { | |
| 		return fmt.Errorf("topic count must be greater than 0") | |
| 	} | |
| 
 | |
| 	if c.Topics.Partitions <= 0 { | |
| 		return fmt.Errorf("partitions per topic must be greater than 0") | |
| 	} | |
| 
 | |
| 	return nil | |
| }
 |