diff --git a/weed/admin/plugin/plugin.go b/weed/admin/plugin/plugin.go index 4a993f994..2afcffeb1 100644 --- a/weed/admin/plugin/plugin.go +++ b/weed/admin/plugin/plugin.go @@ -416,7 +416,10 @@ func (r *Plugin) RunDetectionWithReport( if err != nil { return nil, err } - lastSuccessfulRun := r.loadLastSuccessfulRun(jobType) + lastCompletedRun := r.loadLastSuccessfulRun(jobType) + if strings.EqualFold(strings.TrimSpace(jobType), adminScriptJobType) { + lastCompletedRun = r.loadLastCompletedRun(jobType) + } state := &pendingDetectionState{ complete: make(chan *plugin_pb.DetectionComplete, 1), @@ -457,7 +460,7 @@ func (r *Plugin) RunDetectionWithReport( AdminConfigValues: adminConfigValues, WorkerConfigValues: workerConfigValues, ClusterContext: clusterContext, - LastSuccessfulRun: lastSuccessfulRun, + LastSuccessfulRun: lastCompletedRun, MaxResults: maxResults, }, }, @@ -1324,6 +1327,41 @@ func (r *Plugin) loadLastSuccessfulRun(jobType string) *timestamppb.Timestamp { return timestamppb.New(latest.UTC()) } +func (r *Plugin) loadLastCompletedRun(jobType string) *timestamppb.Timestamp { + history, err := r.store.LoadRunHistory(jobType) + if err != nil { + glog.Warningf("Plugin failed to load run history for %s: %v", jobType, err) + return nil + } + if history == nil { + return nil + } + + var latest time.Time + for i := range history.SuccessfulRuns { + completedAt := history.SuccessfulRuns[i].CompletedAt + if completedAt == nil || completedAt.IsZero() { + continue + } + if latest.IsZero() || completedAt.After(latest) { + latest = *completedAt + } + } + for i := range history.ErrorRuns { + completedAt := history.ErrorRuns[i].CompletedAt + if completedAt == nil || completedAt.IsZero() { + continue + } + if latest.IsZero() || completedAt.After(latest) { + latest = *completedAt + } + } + if latest.IsZero() { + return nil + } + return timestamppb.New(latest.UTC()) +} + func CloneConfigValueMap(in map[string]*plugin_pb.ConfigValue) map[string]*plugin_pb.ConfigValue { if len(in) == 0 { return map[string]*plugin_pb.ConfigValue{} diff --git a/weed/admin/plugin/plugin_detection_test.go b/weed/admin/plugin/plugin_detection_test.go index 755ade4cd..be2aac50c 100644 --- a/weed/admin/plugin/plugin_detection_test.go +++ b/weed/admin/plugin/plugin_detection_test.go @@ -195,3 +195,64 @@ func TestRunDetectionWithReportCapturesDetectionActivities(t *testing.T) { t.Fatalf("expected requested/proposal/completed activities, got stages=%v", stages) } } + +func TestRunDetectionAdminScriptUsesLastCompletedRun(t *testing.T) { + pluginSvc, err := New(Options{}) + if err != nil { + t.Fatalf("New plugin error: %v", err) + } + defer pluginSvc.Shutdown() + + jobType := "admin_script" + pluginSvc.registry.UpsertFromHello(&plugin_pb.WorkerHello{ + WorkerId: "worker-admin-script", + Capabilities: []*plugin_pb.JobTypeCapability{ + {JobType: jobType, CanDetect: true, MaxDetectionConcurrency: 1}, + }, + }) + session := &streamSession{workerID: "worker-admin-script", outgoing: make(chan *plugin_pb.AdminToWorkerMessage, 1)} + pluginSvc.putSession(session) + + successCompleted := time.Date(2026, 2, 1, 10, 0, 0, 0, time.UTC) + errorCompleted := successCompleted.Add(45 * time.Minute) + if err := pluginSvc.store.AppendRunRecord(jobType, &JobRunRecord{ + Outcome: RunOutcomeSuccess, + CompletedAt: timeToPtr(successCompleted), + }); err != nil { + t.Fatalf("AppendRunRecord success run: %v", err) + } + if err := pluginSvc.store.AppendRunRecord(jobType, &JobRunRecord{ + Outcome: RunOutcomeError, + CompletedAt: timeToPtr(errorCompleted), + }); err != nil { + t.Fatalf("AppendRunRecord error run: %v", err) + } + + errCh := make(chan error, 1) + go func() { + _, runErr := pluginSvc.RunDetection(context.Background(), jobType, &plugin_pb.ClusterContext{}, 10) + errCh <- runErr + }() + + message := <-session.outgoing + detectRequest := message.GetRunDetectionRequest() + if detectRequest == nil { + t.Fatalf("expected run detection request message") + } + if detectRequest.LastSuccessfulRun == nil { + t.Fatalf("expected last_successful_run to be set") + } + if got := detectRequest.LastSuccessfulRun.AsTime().UTC(); !got.Equal(errorCompleted) { + t.Fatalf("unexpected last_successful_run, got=%s want=%s", got, errorCompleted) + } + + pluginSvc.handleDetectionComplete("worker-admin-script", &plugin_pb.DetectionComplete{ + RequestId: message.RequestId, + JobType: jobType, + Success: true, + }) + + if runErr := <-errCh; runErr != nil { + t.Fatalf("RunDetection error: %v", runErr) + } +}