add admin script worker (#8491)
* admin: add plugin lock coordination * shell: allow bypassing lock checks * plugin worker: add admin script handler * mini: include admin_script in plugin defaults * admin script UI: drop name and enlarge text * admin script: add default script * admin_script: make run interval configurable * plugin: gate other jobs during admin_script runs * plugin: use last completed admin_script run * admin: backfill plugin config defaults * templ Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * comparable to default version Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * default to run Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * format Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * shell: respect pre-set noLock for fix.replication * shell: add force no-lock mode for admin scripts * volume balance worker already exists Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * admin: expose scheduler status JSON * shell: add sleep command * shell: restrict sleep syntax * Revert "shell: respect pre-set noLock for fix.replication" This reverts commit 2b14e8b82602a740d3a473c085e3b3a14f1ddbb3. * templ Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * fix import Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * less logs Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * Reduce master client logs on canceled contexts * Update mini default job type count --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -214,6 +214,27 @@ func (s *AdminServer) GetPluginSchedulerStatesAPI(w http.ResponseWriter, r *http
|
||||
writeJSON(w, http.StatusOK, states)
|
||||
}
|
||||
|
||||
// GetPluginSchedulerStatusAPI returns scheduler status including in-process jobs and lock state.
|
||||
func (s *AdminServer) GetPluginSchedulerStatusAPI(w http.ResponseWriter, r *http.Request) {
|
||||
pluginSvc := s.GetPlugin()
|
||||
if pluginSvc == nil {
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"enabled": false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"enabled": true,
|
||||
"scheduler": pluginSvc.GetSchedulerStatus(),
|
||||
}
|
||||
if s.pluginLock != nil {
|
||||
response["lock"] = s.pluginLock.Status()
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// RequestPluginJobTypeSchemaAPI asks a worker for one job type schema.
|
||||
func (s *AdminServer) RequestPluginJobTypeSchemaAPI(w http.ResponseWriter, r *http.Request) {
|
||||
jobType := strings.TrimSpace(mux.Vars(r)["jobType"])
|
||||
@@ -277,6 +298,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)
|
||||
}
|
||||
@@ -455,6 +479,14 @@ func (s *AdminServer) RunPluginJobTypeAPI(w http.ResponseWriter, r *http.Request
|
||||
writeJSONError(w, http.StatusBadRequest, "jobType is required")
|
||||
return
|
||||
}
|
||||
releaseLock, err := s.acquirePluginLock(fmt.Sprintf("plugin detect+execute %s", jobType))
|
||||
if err != nil {
|
||||
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if releaseLock != nil {
|
||||
defer releaseLock()
|
||||
}
|
||||
|
||||
var req struct {
|
||||
ClusterContext json.RawMessage `json:"cluster_context"`
|
||||
@@ -771,6 +803,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 {
|
||||
|
||||
Reference in New Issue
Block a user