diff options
Diffstat (limited to 'weed/worker/tasks/erasure_coding/ui.go')
| -rw-r--r-- | weed/worker/tasks/erasure_coding/ui.go | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/weed/worker/tasks/erasure_coding/ui.go b/weed/worker/tasks/erasure_coding/ui.go new file mode 100644 index 000000000..8a4640cf8 --- /dev/null +++ b/weed/worker/tasks/erasure_coding/ui.go @@ -0,0 +1,309 @@ +package erasure_coding + +import ( + "fmt" + "html/template" + "strconv" + "time" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/worker/types" +) + +// UIProvider provides the UI for erasure coding task configuration +type UIProvider struct { + detector *EcDetector + scheduler *Scheduler +} + +// NewUIProvider creates a new erasure coding UI provider +func NewUIProvider(detector *EcDetector, scheduler *Scheduler) *UIProvider { + return &UIProvider{ + detector: detector, + scheduler: scheduler, + } +} + +// GetTaskType returns the task type +func (ui *UIProvider) GetTaskType() types.TaskType { + return types.TaskTypeErasureCoding +} + +// GetDisplayName returns the human-readable name +func (ui *UIProvider) GetDisplayName() string { + return "Erasure Coding" +} + +// GetDescription returns a description of what this task does +func (ui *UIProvider) GetDescription() string { + return "Converts volumes to erasure coded format for improved data durability and fault tolerance" +} + +// GetIcon returns the icon CSS class for this task type +func (ui *UIProvider) GetIcon() string { + return "fas fa-shield-alt text-info" +} + +// ErasureCodingConfig represents the erasure coding configuration +type ErasureCodingConfig struct { + Enabled bool `json:"enabled"` + VolumeAgeHoursSeconds int `json:"volume_age_hours_seconds"` + FullnessRatio float64 `json:"fullness_ratio"` + ScanIntervalSeconds int `json:"scan_interval_seconds"` + MaxConcurrent int `json:"max_concurrent"` + ShardCount int `json:"shard_count"` + ParityCount int `json:"parity_count"` + CollectionFilter string `json:"collection_filter"` +} + +// Helper functions for duration conversion +func secondsToDuration(seconds int) time.Duration { + return time.Duration(seconds) * time.Second +} + +func durationToSeconds(d time.Duration) int { + return int(d.Seconds()) +} + +// formatDurationForUser formats seconds as a user-friendly duration string +func formatDurationForUser(seconds int) string { + d := secondsToDuration(seconds) + if d < time.Minute { + return fmt.Sprintf("%ds", seconds) + } + if d < time.Hour { + return fmt.Sprintf("%.0fm", d.Minutes()) + } + if d < 24*time.Hour { + return fmt.Sprintf("%.1fh", d.Hours()) + } + return fmt.Sprintf("%.1fd", d.Hours()/24) +} + +// RenderConfigForm renders the configuration form HTML +func (ui *UIProvider) RenderConfigForm(currentConfig interface{}) (template.HTML, error) { + config := ui.getCurrentECConfig() + + // Build form using the FormBuilder helper + form := types.NewFormBuilder() + + // Detection Settings + form.AddCheckboxField( + "enabled", + "Enable Erasure Coding Tasks", + "Whether erasure coding tasks should be automatically created", + config.Enabled, + ) + + form.AddNumberField( + "volume_age_hours_seconds", + "Volume Age Threshold", + "Only apply erasure coding to volumes older than this duration", + float64(config.VolumeAgeHoursSeconds), + true, + ) + + form.AddNumberField( + "scan_interval_seconds", + "Scan Interval", + "How often to scan for volumes needing erasure coding", + float64(config.ScanIntervalSeconds), + true, + ) + + // Scheduling Settings + form.AddNumberField( + "max_concurrent", + "Max Concurrent Tasks", + "Maximum number of erasure coding tasks that can run simultaneously", + float64(config.MaxConcurrent), + true, + ) + + // Erasure Coding Parameters + form.AddNumberField( + "shard_count", + "Data Shards", + "Number of data shards for erasure coding (recommended: 10)", + float64(config.ShardCount), + true, + ) + + form.AddNumberField( + "parity_count", + "Parity Shards", + "Number of parity shards for erasure coding (recommended: 4)", + float64(config.ParityCount), + true, + ) + + // Generate organized form sections using Bootstrap components + html := ` +<div class="row"> + <div class="col-12"> + <div class="card mb-4"> + <div class="card-header"> + <h5 class="mb-0"> + <i class="fas fa-shield-alt me-2"></i> + Erasure Coding Configuration + </h5> + </div> + <div class="card-body"> +` + string(form.Build()) + ` + </div> + </div> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <div class="card mb-3"> + <div class="card-header"> + <h5 class="mb-0"> + <i class="fas fa-info-circle me-2"></i> + Performance Impact + </h5> + </div> + <div class="card-body"> + <div class="alert alert-info" role="alert"> + <h6 class="alert-heading">Important Notes:</h6> + <p class="mb-2"><strong>Performance:</strong> Erasure coding is CPU and I/O intensive. Consider running during off-peak hours.</p> + <p class="mb-0"><strong>Durability:</strong> With ` + fmt.Sprintf("%d+%d", config.ShardCount, config.ParityCount) + ` configuration, can tolerate up to ` + fmt.Sprintf("%d", config.ParityCount) + ` shard failures.</p> + </div> + </div> + </div> + </div> +</div>` + + return template.HTML(html), nil +} + +// ParseConfigForm parses form data into configuration +func (ui *UIProvider) ParseConfigForm(formData map[string][]string) (interface{}, error) { + config := &ErasureCodingConfig{} + + // Parse enabled + config.Enabled = len(formData["enabled"]) > 0 + + // Parse volume age hours + if values, ok := formData["volume_age_hours_seconds"]; ok && len(values) > 0 { + hours, err := strconv.Atoi(values[0]) + if err != nil { + return nil, fmt.Errorf("invalid volume age hours: %v", err) + } + config.VolumeAgeHoursSeconds = hours + } + + // Parse scan interval + if values, ok := formData["scan_interval_seconds"]; ok && len(values) > 0 { + interval, err := strconv.Atoi(values[0]) + if err != nil { + return nil, fmt.Errorf("invalid scan interval: %v", err) + } + config.ScanIntervalSeconds = interval + } + + // Parse max concurrent + if values, ok := formData["max_concurrent"]; ok && len(values) > 0 { + maxConcurrent, err := strconv.Atoi(values[0]) + if err != nil { + return nil, fmt.Errorf("invalid max concurrent: %v", err) + } + if maxConcurrent < 1 { + return nil, fmt.Errorf("max concurrent must be at least 1") + } + config.MaxConcurrent = maxConcurrent + } + + // Parse shard count + if values, ok := formData["shard_count"]; ok && len(values) > 0 { + shardCount, err := strconv.Atoi(values[0]) + if err != nil { + return nil, fmt.Errorf("invalid shard count: %v", err) + } + if shardCount < 1 { + return nil, fmt.Errorf("shard count must be at least 1") + } + config.ShardCount = shardCount + } + + // Parse parity count + if values, ok := formData["parity_count"]; ok && len(values) > 0 { + parityCount, err := strconv.Atoi(values[0]) + if err != nil { + return nil, fmt.Errorf("invalid parity count: %v", err) + } + if parityCount < 1 { + return nil, fmt.Errorf("parity count must be at least 1") + } + config.ParityCount = parityCount + } + + return config, nil +} + +// GetCurrentConfig returns the current configuration +func (ui *UIProvider) GetCurrentConfig() interface{} { + return ui.getCurrentECConfig() +} + +// ApplyConfig applies the new configuration +func (ui *UIProvider) ApplyConfig(config interface{}) error { + ecConfig, ok := config.(ErasureCodingConfig) + if !ok { + return fmt.Errorf("invalid config type, expected ErasureCodingConfig") + } + + // Apply to detector + if ui.detector != nil { + ui.detector.SetEnabled(ecConfig.Enabled) + ui.detector.SetVolumeAgeHours(ecConfig.VolumeAgeHoursSeconds) + ui.detector.SetScanInterval(secondsToDuration(ecConfig.ScanIntervalSeconds)) + } + + // Apply to scheduler + if ui.scheduler != nil { + ui.scheduler.SetEnabled(ecConfig.Enabled) + ui.scheduler.SetMaxConcurrent(ecConfig.MaxConcurrent) + } + + glog.V(1).Infof("Applied erasure coding configuration: enabled=%v, age_threshold=%v, max_concurrent=%d, shards=%d+%d", + ecConfig.Enabled, ecConfig.VolumeAgeHoursSeconds, ecConfig.MaxConcurrent, ecConfig.ShardCount, ecConfig.ParityCount) + + return nil +} + +// getCurrentECConfig gets the current configuration from detector and scheduler +func (ui *UIProvider) getCurrentECConfig() ErasureCodingConfig { + config := ErasureCodingConfig{ + // Default values (fallback if detectors/schedulers are nil) + Enabled: true, + VolumeAgeHoursSeconds: 24 * 3600, // 24 hours in seconds + ScanIntervalSeconds: 2 * 3600, // 2 hours in seconds + MaxConcurrent: 1, + ShardCount: 10, + ParityCount: 4, + } + + // Get current values from detector + if ui.detector != nil { + config.Enabled = ui.detector.IsEnabled() + config.VolumeAgeHoursSeconds = ui.detector.GetVolumeAgeHours() + config.ScanIntervalSeconds = durationToSeconds(ui.detector.ScanInterval()) + } + + // Get current values from scheduler + if ui.scheduler != nil { + config.MaxConcurrent = ui.scheduler.GetMaxConcurrent() + } + + return config +} + +// RegisterUI registers the erasure coding UI provider with the UI registry +func RegisterUI(uiRegistry *types.UIRegistry, detector *EcDetector, scheduler *Scheduler) { + uiProvider := NewUIProvider(detector, scheduler) + uiRegistry.RegisterUI(uiProvider) + + glog.V(1).Infof("✅ Registered erasure coding task UI provider") +} |
