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.
		
		
		
		
		
			
		
			
				
					
					
						
							389 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							389 lines
						
					
					
						
							11 KiB
						
					
					
				
								package handlers
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"net/url"
							 | 
						|
									"testing"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/admin/config"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/worker/tasks"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/worker/tasks/balance"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/worker/tasks/base"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/worker/tasks/erasure_coding"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/worker/tasks/vacuum"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								func TestParseTaskConfigFromForm_WithEmbeddedStruct(t *testing.T) {
							 | 
						|
									// Create a maintenance handlers instance for testing
							 | 
						|
									h := &MaintenanceHandlers{}
							 | 
						|
								
							 | 
						|
									// Test with balance config
							 | 
						|
									t.Run("Balance Config", func(t *testing.T) {
							 | 
						|
										// Simulate form data
							 | 
						|
										formData := url.Values{
							 | 
						|
											"enabled":                     {"on"},      // checkbox field
							 | 
						|
											"scan_interval_seconds_value": {"30"},      // interval field
							 | 
						|
											"scan_interval_seconds_unit":  {"minutes"}, // interval unit
							 | 
						|
											"max_concurrent":              {"2"},       // number field
							 | 
						|
											"imbalance_threshold":         {"0.15"},    // float field
							 | 
						|
											"min_server_count":            {"3"},       // number field
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Get schema
							 | 
						|
										schema := tasks.GetTaskConfigSchema("balance")
							 | 
						|
										if schema == nil {
							 | 
						|
											t.Fatal("Failed to get balance schema")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Create config instance
							 | 
						|
										config := &balance.Config{}
							 | 
						|
								
							 | 
						|
										// Parse form data
							 | 
						|
										err := h.parseTaskConfigFromForm(formData, schema, config)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to parse form data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Verify embedded struct fields were set correctly
							 | 
						|
										if !config.Enabled {
							 | 
						|
											t.Errorf("Expected Enabled=true, got %v", config.Enabled)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.ScanIntervalSeconds != 1800 { // 30 minutes * 60
							 | 
						|
											t.Errorf("Expected ScanIntervalSeconds=1800, got %v", config.ScanIntervalSeconds)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.MaxConcurrent != 2 {
							 | 
						|
											t.Errorf("Expected MaxConcurrent=2, got %v", config.MaxConcurrent)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Verify balance-specific fields were set correctly
							 | 
						|
										if config.ImbalanceThreshold != 0.15 {
							 | 
						|
											t.Errorf("Expected ImbalanceThreshold=0.15, got %v", config.ImbalanceThreshold)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.MinServerCount != 3 {
							 | 
						|
											t.Errorf("Expected MinServerCount=3, got %v", config.MinServerCount)
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									// Test with vacuum config
							 | 
						|
									t.Run("Vacuum Config", func(t *testing.T) {
							 | 
						|
										// Simulate form data
							 | 
						|
										formData := url.Values{
							 | 
						|
											// "enabled" field omitted to simulate unchecked checkbox
							 | 
						|
											"scan_interval_seconds_value":  {"4"},     // interval field
							 | 
						|
											"scan_interval_seconds_unit":   {"hours"}, // interval unit
							 | 
						|
											"max_concurrent":               {"3"},     // number field
							 | 
						|
											"garbage_threshold":            {"0.4"},   // float field
							 | 
						|
											"min_volume_age_seconds_value": {"2"},     // interval field
							 | 
						|
											"min_volume_age_seconds_unit":  {"days"},  // interval unit
							 | 
						|
											"min_interval_seconds_value":   {"1"},     // interval field
							 | 
						|
											"min_interval_seconds_unit":    {"days"},  // interval unit
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Get schema
							 | 
						|
										schema := tasks.GetTaskConfigSchema("vacuum")
							 | 
						|
										if schema == nil {
							 | 
						|
											t.Fatal("Failed to get vacuum schema")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Create config instance
							 | 
						|
										config := &vacuum.Config{}
							 | 
						|
								
							 | 
						|
										// Parse form data
							 | 
						|
										err := h.parseTaskConfigFromForm(formData, schema, config)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to parse form data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Verify embedded struct fields were set correctly
							 | 
						|
										if config.Enabled {
							 | 
						|
											t.Errorf("Expected Enabled=false, got %v", config.Enabled)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.ScanIntervalSeconds != 14400 { // 4 hours * 3600
							 | 
						|
											t.Errorf("Expected ScanIntervalSeconds=14400, got %v", config.ScanIntervalSeconds)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.MaxConcurrent != 3 {
							 | 
						|
											t.Errorf("Expected MaxConcurrent=3, got %v", config.MaxConcurrent)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Verify vacuum-specific fields were set correctly
							 | 
						|
										if config.GarbageThreshold != 0.4 {
							 | 
						|
											t.Errorf("Expected GarbageThreshold=0.4, got %v", config.GarbageThreshold)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.MinVolumeAgeSeconds != 172800 { // 2 days * 86400
							 | 
						|
											t.Errorf("Expected MinVolumeAgeSeconds=172800, got %v", config.MinVolumeAgeSeconds)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.MinIntervalSeconds != 86400 { // 1 day * 86400
							 | 
						|
											t.Errorf("Expected MinIntervalSeconds=86400, got %v", config.MinIntervalSeconds)
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									// Test with erasure coding config
							 | 
						|
									t.Run("Erasure Coding Config", func(t *testing.T) {
							 | 
						|
										// Simulate form data
							 | 
						|
										formData := url.Values{
							 | 
						|
											"enabled":                     {"on"},              // checkbox field
							 | 
						|
											"scan_interval_seconds_value": {"2"},               // interval field
							 | 
						|
											"scan_interval_seconds_unit":  {"hours"},           // interval unit
							 | 
						|
											"max_concurrent":              {"1"},               // number field
							 | 
						|
											"quiet_for_seconds_value":     {"10"},              // interval field
							 | 
						|
											"quiet_for_seconds_unit":      {"minutes"},         // interval unit
							 | 
						|
											"fullness_ratio":              {"0.85"},            // float field
							 | 
						|
											"collection_filter":           {"test_collection"}, // string field
							 | 
						|
											"min_size_mb":                 {"50"},              // number field
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Get schema
							 | 
						|
										schema := tasks.GetTaskConfigSchema("erasure_coding")
							 | 
						|
										if schema == nil {
							 | 
						|
											t.Fatal("Failed to get erasure_coding schema")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Create config instance
							 | 
						|
										config := &erasure_coding.Config{}
							 | 
						|
								
							 | 
						|
										// Parse form data
							 | 
						|
										err := h.parseTaskConfigFromForm(formData, schema, config)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to parse form data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Verify embedded struct fields were set correctly
							 | 
						|
										if !config.Enabled {
							 | 
						|
											t.Errorf("Expected Enabled=true, got %v", config.Enabled)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.ScanIntervalSeconds != 7200 { // 2 hours * 3600
							 | 
						|
											t.Errorf("Expected ScanIntervalSeconds=7200, got %v", config.ScanIntervalSeconds)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.MaxConcurrent != 1 {
							 | 
						|
											t.Errorf("Expected MaxConcurrent=1, got %v", config.MaxConcurrent)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Verify erasure coding-specific fields were set correctly
							 | 
						|
										if config.QuietForSeconds != 600 { // 10 minutes * 60
							 | 
						|
											t.Errorf("Expected QuietForSeconds=600, got %v", config.QuietForSeconds)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.FullnessRatio != 0.85 {
							 | 
						|
											t.Errorf("Expected FullnessRatio=0.85, got %v", config.FullnessRatio)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.CollectionFilter != "test_collection" {
							 | 
						|
											t.Errorf("Expected CollectionFilter='test_collection', got %v", config.CollectionFilter)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if config.MinSizeMB != 50 {
							 | 
						|
											t.Errorf("Expected MinSizeMB=50, got %v", config.MinSizeMB)
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func TestConfigurationValidation(t *testing.T) {
							 | 
						|
									// Test that config structs can be validated and converted to protobuf format
							 | 
						|
									taskTypes := []struct {
							 | 
						|
										name   string
							 | 
						|
										config interface{}
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											"balance",
							 | 
						|
											&balance.Config{
							 | 
						|
												BaseConfig: base.BaseConfig{
							 | 
						|
													Enabled:             true,
							 | 
						|
													ScanIntervalSeconds: 2400,
							 | 
						|
													MaxConcurrent:       3,
							 | 
						|
												},
							 | 
						|
												ImbalanceThreshold: 0.18,
							 | 
						|
												MinServerCount:     4,
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											"vacuum",
							 | 
						|
											&vacuum.Config{
							 | 
						|
												BaseConfig: base.BaseConfig{
							 | 
						|
													Enabled:             false,
							 | 
						|
													ScanIntervalSeconds: 7200,
							 | 
						|
													MaxConcurrent:       2,
							 | 
						|
												},
							 | 
						|
												GarbageThreshold:    0.35,
							 | 
						|
												MinVolumeAgeSeconds: 86400,
							 | 
						|
												MinIntervalSeconds:  604800,
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											"erasure_coding",
							 | 
						|
											&erasure_coding.Config{
							 | 
						|
												BaseConfig: base.BaseConfig{
							 | 
						|
													Enabled:             true,
							 | 
						|
													ScanIntervalSeconds: 3600,
							 | 
						|
													MaxConcurrent:       1,
							 | 
						|
												},
							 | 
						|
												QuietForSeconds:  900,
							 | 
						|
												FullnessRatio:    0.9,
							 | 
						|
												CollectionFilter: "important",
							 | 
						|
												MinSizeMB:        100,
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, test := range taskTypes {
							 | 
						|
										t.Run(test.name, func(t *testing.T) {
							 | 
						|
											// Test that configs can be converted to protobuf TaskPolicy
							 | 
						|
											switch cfg := test.config.(type) {
							 | 
						|
											case *balance.Config:
							 | 
						|
												policy := cfg.ToTaskPolicy()
							 | 
						|
												if policy == nil {
							 | 
						|
													t.Fatal("ToTaskPolicy returned nil")
							 | 
						|
												}
							 | 
						|
												if policy.Enabled != cfg.Enabled {
							 | 
						|
													t.Errorf("Expected Enabled=%v, got %v", cfg.Enabled, policy.Enabled)
							 | 
						|
												}
							 | 
						|
												if policy.MaxConcurrent != int32(cfg.MaxConcurrent) {
							 | 
						|
													t.Errorf("Expected MaxConcurrent=%v, got %v", cfg.MaxConcurrent, policy.MaxConcurrent)
							 | 
						|
												}
							 | 
						|
											case *vacuum.Config:
							 | 
						|
												policy := cfg.ToTaskPolicy()
							 | 
						|
												if policy == nil {
							 | 
						|
													t.Fatal("ToTaskPolicy returned nil")
							 | 
						|
												}
							 | 
						|
												if policy.Enabled != cfg.Enabled {
							 | 
						|
													t.Errorf("Expected Enabled=%v, got %v", cfg.Enabled, policy.Enabled)
							 | 
						|
												}
							 | 
						|
												if policy.MaxConcurrent != int32(cfg.MaxConcurrent) {
							 | 
						|
													t.Errorf("Expected MaxConcurrent=%v, got %v", cfg.MaxConcurrent, policy.MaxConcurrent)
							 | 
						|
												}
							 | 
						|
											case *erasure_coding.Config:
							 | 
						|
												policy := cfg.ToTaskPolicy()
							 | 
						|
												if policy == nil {
							 | 
						|
													t.Fatal("ToTaskPolicy returned nil")
							 | 
						|
												}
							 | 
						|
												if policy.Enabled != cfg.Enabled {
							 | 
						|
													t.Errorf("Expected Enabled=%v, got %v", cfg.Enabled, policy.Enabled)
							 | 
						|
												}
							 | 
						|
												if policy.MaxConcurrent != int32(cfg.MaxConcurrent) {
							 | 
						|
													t.Errorf("Expected MaxConcurrent=%v, got %v", cfg.MaxConcurrent, policy.MaxConcurrent)
							 | 
						|
												}
							 | 
						|
											default:
							 | 
						|
												t.Fatalf("Unknown config type: %T", test.config)
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											// Test that configs can be validated
							 | 
						|
											switch cfg := test.config.(type) {
							 | 
						|
											case *balance.Config:
							 | 
						|
												if err := cfg.Validate(); err != nil {
							 | 
						|
													t.Errorf("Validation failed: %v", err)
							 | 
						|
												}
							 | 
						|
											case *vacuum.Config:
							 | 
						|
												if err := cfg.Validate(); err != nil {
							 | 
						|
													t.Errorf("Validation failed: %v", err)
							 | 
						|
												}
							 | 
						|
											case *erasure_coding.Config:
							 | 
						|
												if err := cfg.Validate(); err != nil {
							 | 
						|
													t.Errorf("Validation failed: %v", err)
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func TestParseFieldFromForm_EdgeCases(t *testing.T) {
							 | 
						|
									h := &MaintenanceHandlers{}
							 | 
						|
								
							 | 
						|
									// Test checkbox parsing (boolean fields)
							 | 
						|
									t.Run("Checkbox Fields", func(t *testing.T) {
							 | 
						|
										tests := []struct {
							 | 
						|
											name          string
							 | 
						|
											formData      url.Values
							 | 
						|
											expectedValue bool
							 | 
						|
										}{
							 | 
						|
											{"Checked checkbox", url.Values{"test_field": {"on"}}, true},
							 | 
						|
											{"Unchecked checkbox", url.Values{}, false},
							 | 
						|
											{"Empty value checkbox", url.Values{"test_field": {""}}, true}, // Present but empty means checked
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										for _, test := range tests {
							 | 
						|
											t.Run(test.name, func(t *testing.T) {
							 | 
						|
												schema := &tasks.TaskConfigSchema{
							 | 
						|
													Schema: config.Schema{
							 | 
						|
														Fields: []*config.Field{
							 | 
						|
															{
							 | 
						|
																JSONName:  "test_field",
							 | 
						|
																Type:      config.FieldTypeBool,
							 | 
						|
																InputType: "checkbox",
							 | 
						|
															},
							 | 
						|
														},
							 | 
						|
													},
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												type TestConfig struct {
							 | 
						|
													TestField bool `json:"test_field"`
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												config := &TestConfig{}
							 | 
						|
												err := h.parseTaskConfigFromForm(test.formData, schema, config)
							 | 
						|
												if err != nil {
							 | 
						|
													t.Fatalf("parseTaskConfigFromForm failed: %v", err)
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												if config.TestField != test.expectedValue {
							 | 
						|
													t.Errorf("Expected %v, got %v", test.expectedValue, config.TestField)
							 | 
						|
												}
							 | 
						|
											})
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									// Test interval parsing
							 | 
						|
									t.Run("Interval Fields", func(t *testing.T) {
							 | 
						|
										tests := []struct {
							 | 
						|
											name         string
							 | 
						|
											value        string
							 | 
						|
											unit         string
							 | 
						|
											expectedSecs int
							 | 
						|
										}{
							 | 
						|
											{"Minutes", "30", "minutes", 1800},
							 | 
						|
											{"Hours", "2", "hours", 7200},
							 | 
						|
											{"Days", "1", "days", 86400},
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										for _, test := range tests {
							 | 
						|
											t.Run(test.name, func(t *testing.T) {
							 | 
						|
												formData := url.Values{
							 | 
						|
													"test_field_value": {test.value},
							 | 
						|
													"test_field_unit":  {test.unit},
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												schema := &tasks.TaskConfigSchema{
							 | 
						|
													Schema: config.Schema{
							 | 
						|
														Fields: []*config.Field{
							 | 
						|
															{
							 | 
						|
																JSONName:  "test_field",
							 | 
						|
																Type:      config.FieldTypeInterval,
							 | 
						|
																InputType: "interval",
							 | 
						|
															},
							 | 
						|
														},
							 | 
						|
													},
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												type TestConfig struct {
							 | 
						|
													TestField int `json:"test_field"`
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												config := &TestConfig{}
							 | 
						|
												err := h.parseTaskConfigFromForm(formData, schema, config)
							 | 
						|
												if err != nil {
							 | 
						|
													t.Fatalf("parseTaskConfigFromForm failed: %v", err)
							 | 
						|
												}
							 | 
						|
								
							 | 
						|
												if config.TestField != test.expectedSecs {
							 | 
						|
													t.Errorf("Expected %d seconds, got %d", test.expectedSecs, config.TestField)
							 | 
						|
												}
							 | 
						|
											})
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 |