Files
ayatori/internal/execution/state.go
panxiao81 4846ff2aba
Verify / lint (pull_request) Failing after 5m37s
Verify / test (pull_request) Successful in 5m38s
feat: scaffold job execution API
2026-09-17 18:58:11 +00:00

95 lines
3.1 KiB
Go

package execution
import (
"fmt"
executionv1alpha1 "git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type JobState string
const (
JobStateResolving JobState = "Resolving"
JobStateScheduling JobState = "Scheduling"
JobStateStarting JobState = "Starting"
JobStateRunning JobState = "Running"
JobStateCancelling JobState = "Cancelling"
JobStateResultUnknown JobState = "ResultUnknown"
JobStateSucceeded JobState = "Succeeded"
JobStateFailed JobState = "Failed"
JobStateCancelled JobState = "Cancelled"
JobStateDeleting JobState = "Deleting"
)
// StateOf derives a presentation state from durable API facts. The derived
// state is deliberately not persisted as a second source of truth.
func StateOf(job *executionv1alpha1.Job) JobState {
if !job.DeletionTimestamp.IsZero() {
return JobStateDeleting
}
succeeded := condition(job.Status.Conditions, executionv1alpha1.JobConditionSucceeded)
if succeeded != nil {
switch succeeded.Status {
case metav1.ConditionTrue:
return JobStateSucceeded
case metav1.ConditionFalse:
if succeeded.Reason == "Cancelled" {
return JobStateCancelled
}
return JobStateFailed
}
}
if job.Spec.DesiredState == executionv1alpha1.JobDesiredStateCancelled {
return JobStateCancelling
}
if succeeded != nil && succeeded.Status == metav1.ConditionUnknown && succeeded.Reason == "ResultUnknown" {
return JobStateResultUnknown
}
if job.Status.StartTime != nil {
return JobStateRunning
}
if conditionTrue(job.Status.Conditions, executionv1alpha1.JobConditionScheduled) {
return JobStateStarting
}
if conditionTrue(job.Status.Conditions, executionv1alpha1.JobConditionAccepted) {
return JobStateScheduling
}
return JobStateResolving
}
// ValidateStatus checks invariants that every adapter must preserve.
func ValidateStatus(job *executionv1alpha1.Job) error {
succeeded := condition(job.Status.Conditions, executionv1alpha1.JobConditionSucceeded)
terminal := succeeded != nil && (succeeded.Status == metav1.ConditionTrue || succeeded.Status == metav1.ConditionFalse)
if terminal && job.Status.CompletionTime == nil {
return fmt.Errorf("terminal job must have completionTime")
}
if job.Status.CompletionTime != nil && !terminal {
return fmt.Errorf("completionTime requires a terminal Succeeded condition")
}
if job.Status.StartTime != nil && !conditionTrue(job.Status.Conditions, executionv1alpha1.JobConditionScheduled) {
return fmt.Errorf("startTime requires Scheduled=True")
}
if job.Status.StartTime != nil && job.Status.CompletionTime != nil && job.Status.CompletionTime.Before(job.Status.StartTime) {
return fmt.Errorf("completionTime must not precede startTime")
}
return nil
}
func condition(conditions []metav1.Condition, conditionType string) *metav1.Condition {
for i := range conditions {
if conditions[i].Type == conditionType {
return &conditions[i]
}
}
return nil
}
func conditionTrue(conditions []metav1.Condition, conditionType string) bool {
current := condition(conditions, conditionType)
return current != nil && current.Status == metav1.ConditionTrue
}