aboutsummaryrefslogtreecommitdiff
path: root/weed/worker/tasks/erasure_coding/ui.go
diff options
context:
space:
mode:
authorChris Lu <chrislusf@users.noreply.github.com>2025-07-06 13:57:02 -0700
committerGitHub <noreply@github.com>2025-07-06 13:57:02 -0700
commitaa668523047c273dc4065dc0f40852efcdf9e9f0 (patch)
tree87f7f145d699cf1824c8251ae71435462bfd3318 /weed/worker/tasks/erasure_coding/ui.go
parent302e62d4805c60f3fdb6620b01e85859d68078ed (diff)
downloadseaweedfs-aa668523047c273dc4065dc0f40852efcdf9e9f0.tar.xz
seaweedfs-aa668523047c273dc4065dc0f40852efcdf9e9f0.zip
Admin UI add maintenance menu (#6944)
* add ui for maintenance * valid config loading. fix workers page. * refactor * grpc between admin and workers * add a long-running bidirectional grpc call between admin and worker * use the grpc call to heartbeat * use the grpc call to communicate * worker can remove the http client * admin uses http port + 10000 as its default grpc port * one task one package * handles connection failures gracefully with exponential backoff * grpc with insecure tls * grpc with optional tls * fix detecting tls * change time config from nano seconds to seconds * add tasks with 3 interfaces * compiles reducing hard coded * remove a couple of tasks * remove hard coded references * reduce hard coded values * remove hard coded values * remove hard coded from templ * refactor maintenance package * fix import cycle * simplify * simplify * auto register * auto register factory * auto register task types * self register types * refactor * simplify * remove one task * register ui * lazy init executor factories * use registered task types * DefaultWorkerConfig remove hard coded task types * remove more hard coded * implement get maintenance task * dynamic task configuration * "System Settings" should only have system level settings * adjust menu for tasks * ensure menu not collapsed * render job configuration well * use templ for ui of task configuration * fix ordering * fix bugs * saving duration in seconds * use value and unit for duration * Delete WORKER_REFACTORING_PLAN.md * Delete maintenance.json * Delete custom_worker_example.go * remove address from workers * remove old code from ec task * remove creating collection button * reconnect with exponential backoff * worker use security.toml * start admin server with tls info from security.toml * fix "weed admin" cli description
Diffstat (limited to 'weed/worker/tasks/erasure_coding/ui.go')
-rw-r--r--weed/worker/tasks/erasure_coding/ui.go309
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")
+}