diff --git a/weed/admin/dash/plugin_api.go b/weed/admin/dash/plugin_api.go index e3b64fa02..44d72ac63 100644 --- a/weed/admin/dash/plugin_api.go +++ b/weed/admin/dash/plugin_api.go @@ -235,6 +235,9 @@ func (s *AdminServer) GetPluginJobTypeConfigAPI(w http.ResponseWriter, r *http.R AdminRuntime: &plugin_pb.AdminRuntimeConfig{}, } } + if descriptor, err := s.LoadPluginJobTypeDescriptor(jobType); err == nil && descriptor != nil { + applyDescriptorDefaultsToPersistedConfig(config, descriptor) + } renderProtoJSON(w, http.StatusOK, config) } @@ -737,6 +740,90 @@ func buildJobSpecFromProposal(jobType string, proposal *plugin_pb.JobProposal, i return jobSpec } +func applyDescriptorDefaultsToPersistedConfig( + config *plugin_pb.PersistedJobTypeConfig, + descriptor *plugin_pb.JobTypeDescriptor, +) { + if config == nil || descriptor == nil { + return + } + + if config.AdminConfigValues == nil { + config.AdminConfigValues = map[string]*plugin_pb.ConfigValue{} + } + if config.WorkerConfigValues == nil { + config.WorkerConfigValues = map[string]*plugin_pb.ConfigValue{} + } + if config.AdminRuntime == nil { + config.AdminRuntime = &plugin_pb.AdminRuntimeConfig{} + } + + if descriptor.AdminConfigForm != nil { + for key, value := range descriptor.AdminConfigForm.DefaultValues { + if value == nil { + continue + } + current := config.AdminConfigValues[key] + if current == nil { + config.AdminConfigValues[key] = proto.Clone(value).(*plugin_pb.ConfigValue) + continue + } + if strings.EqualFold(descriptor.JobType, "admin_script") && + key == "script" && + isBlankStringConfigValue(current) { + config.AdminConfigValues[key] = proto.Clone(value).(*plugin_pb.ConfigValue) + } + } + } + if descriptor.WorkerConfigForm != nil { + for key, value := range descriptor.WorkerConfigForm.DefaultValues { + if value == nil { + continue + } + if config.WorkerConfigValues[key] != nil { + continue + } + config.WorkerConfigValues[key] = proto.Clone(value).(*plugin_pb.ConfigValue) + } + } + if descriptor.AdminRuntimeDefaults != nil { + runtime := config.AdminRuntime + defaults := descriptor.AdminRuntimeDefaults + if runtime.DetectionIntervalSeconds <= 0 { + runtime.DetectionIntervalSeconds = defaults.DetectionIntervalSeconds + } + if runtime.DetectionTimeoutSeconds <= 0 { + runtime.DetectionTimeoutSeconds = defaults.DetectionTimeoutSeconds + } + if runtime.MaxJobsPerDetection <= 0 { + runtime.MaxJobsPerDetection = defaults.MaxJobsPerDetection + } + if runtime.GlobalExecutionConcurrency <= 0 { + runtime.GlobalExecutionConcurrency = defaults.GlobalExecutionConcurrency + } + if runtime.PerWorkerExecutionConcurrency <= 0 { + runtime.PerWorkerExecutionConcurrency = defaults.PerWorkerExecutionConcurrency + } + if runtime.RetryBackoffSeconds <= 0 { + runtime.RetryBackoffSeconds = defaults.RetryBackoffSeconds + } + if runtime.RetryLimit < 0 { + runtime.RetryLimit = defaults.RetryLimit + } + } +} + +func isBlankStringConfigValue(value *plugin_pb.ConfigValue) bool { + if value == nil { + return true + } + kind, ok := value.Kind.(*plugin_pb.ConfigValue_StringValue) + if !ok { + return false + } + return strings.TrimSpace(kind.StringValue) == "" +} + func parsePositiveInt(raw string, defaultValue int) int { value, err := strconv.Atoi(strings.TrimSpace(raw)) if err != nil || value <= 0 { diff --git a/weed/admin/dash/plugin_api_test.go b/weed/admin/dash/plugin_api_test.go index c4f1b74e9..200003bf5 100644 --- a/weed/admin/dash/plugin_api_test.go +++ b/weed/admin/dash/plugin_api_test.go @@ -31,3 +31,83 @@ func TestBuildJobSpecFromProposalDoesNotReuseProposalID(t *testing.T) { t.Fatalf("dedupe key must be preserved: got=%s want=%s", jobA.DedupeKey, proposal.DedupeKey) } } + +func TestApplyDescriptorDefaultsToPersistedConfigBackfillsAdminDefaults(t *testing.T) { + t.Parallel() + + config := &plugin_pb.PersistedJobTypeConfig{ + JobType: "admin_script", + AdminConfigValues: map[string]*plugin_pb.ConfigValue{}, + WorkerConfigValues: map[string]*plugin_pb.ConfigValue{}, + AdminRuntime: &plugin_pb.AdminRuntimeConfig{}, + } + descriptor := &plugin_pb.JobTypeDescriptor{ + JobType: "admin_script", + AdminConfigForm: &plugin_pb.ConfigForm{ + DefaultValues: map[string]*plugin_pb.ConfigValue{ + "script": { + Kind: &plugin_pb.ConfigValue_StringValue{StringValue: "volume.balance -apply"}, + }, + "run_interval_minutes": { + Kind: &plugin_pb.ConfigValue_Int64Value{Int64Value: 17}, + }, + }, + }, + AdminRuntimeDefaults: &plugin_pb.AdminRuntimeDefaults{ + DetectionIntervalSeconds: 60, + DetectionTimeoutSeconds: 300, + }, + } + + applyDescriptorDefaultsToPersistedConfig(config, descriptor) + + script := config.AdminConfigValues["script"] + if script == nil { + t.Fatalf("expected script default to be backfilled") + } + scriptKind, ok := script.Kind.(*plugin_pb.ConfigValue_StringValue) + if !ok || scriptKind.StringValue == "" { + t.Fatalf("expected non-empty script default, got=%+v", script) + } + if config.AdminRuntime.DetectionIntervalSeconds != 60 { + t.Fatalf("expected runtime detection interval default to be backfilled") + } +} + +func TestApplyDescriptorDefaultsToPersistedConfigReplacesBlankAdminScript(t *testing.T) { + t.Parallel() + + config := &plugin_pb.PersistedJobTypeConfig{ + JobType: "admin_script", + AdminConfigValues: map[string]*plugin_pb.ConfigValue{ + "script": { + Kind: &plugin_pb.ConfigValue_StringValue{StringValue: " "}, + }, + }, + AdminRuntime: &plugin_pb.AdminRuntimeConfig{}, + } + descriptor := &plugin_pb.JobTypeDescriptor{ + JobType: "admin_script", + AdminConfigForm: &plugin_pb.ConfigForm{ + DefaultValues: map[string]*plugin_pb.ConfigValue{ + "script": { + Kind: &plugin_pb.ConfigValue_StringValue{StringValue: "volume.fix.replication -apply"}, + }, + }, + }, + } + + applyDescriptorDefaultsToPersistedConfig(config, descriptor) + + script := config.AdminConfigValues["script"] + if script == nil { + t.Fatalf("expected script config value") + } + scriptKind, ok := script.Kind.(*plugin_pb.ConfigValue_StringValue) + if !ok { + t.Fatalf("expected string script config value, got=%T", script.Kind) + } + if scriptKind.StringValue != "volume.fix.replication -apply" { + t.Fatalf("expected blank script to be replaced by default, got=%q", scriptKind.StringValue) + } +}