Ctrl K
ComunidadGuías…

pkg/executioninsight/jobset.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Package executioninsight projects controller-owned execution facts into a
// shared lifecycle with typed controller detail. It does not infer children.
package executioninsight

import (
	"strings"
	"unicode/utf8"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"

	"github.com/skyhook-io/radar/pkg/jobset"
	"github.com/skyhook-io/radar/pkg/resourcecontext"
)

const maxStateMessageBytes = 256

// ForResource returns an execution summary only for an exact supported GVK.
// The mapping follows JobSet v0.12.0; the served API version does not identify
// the controller release. Basic tier omits condition messages/times; diagnostic
// tier retains them with bounded messages.
func ForResource(obj runtime.Object, tier resourcecontext.ContextTier) *resourcecontext.ExecutionSummary {
	u, ok := obj.(*unstructured.Unstructured)
	if !ok || u.GroupVersionKind() != jobset.GVK {
		return nil
	}

	detail := jobSetCounts(u)
	nativeState, _, _ := unstructured.NestedString(u.Object, "status", "terminalState")
	suspendRequested, _, _ := unstructured.NestedBool(u.Object, "spec", "suspend")
	phase, outcome, primary := jobSetPhase(jobset.Lifecycle(u), jobSetConditions(u), tier)
	detail.Restarts = jobSetRestarts(u)
	return &resourcecontext.ExecutionSummary{
		Controller:        resourcecontext.ExecutionControllerJobSet,
		SubjectGeneration: u.GetGeneration(),
		Phase:             phase,
		Outcome:           outcome,
		PrimaryCondition:  primary,
		NativeState:       nativeState,
		SuspendRequested:  &suspendRequested,
		JobSet:            &detail,
	}
}

func jobSetCounts(u *unstructured.Unstructured) resourcecontext.JobSetExecution {
	detail := resourcecontext.JobSetExecution{}
	detail.DeclaredRoles, detail.DeclaredJobs = jobset.Declared(u)
	observed := jobset.ObservedChildren(u)
	if observed == nil {
		return detail
	}
	detail.ObservedRoles = &observed.Roles
	detail.Jobs = &resourcecontext.ChildJobCounts{
		Ready:     observed.Ready,
		Active:    observed.Active,
		Succeeded: observed.Succeeded,
		Failed:    observed.Failed,
		Suspended: observed.Suspended,
	}
	return detail
}

func int64Field(object map[string]any, field string) int64 {
	value, _, _ := unstructured.NestedInt64(object, field)
	return value
}

func jobSetRestarts(u *unstructured.Unstructured) *resourcecontext.JobSetRestartCounts {
	restarts := &resourcecontext.JobSetRestartCounts{}
	if value, found, _ := unstructured.NestedInt64(u.Object, "status", "restarts"); found {
		restarts.Global = &value
	}
	// JobSet omits the counted field when its observed value is zero.
	if value, found, _ := unstructured.NestedInt64(u.Object, "status", "restartsCountTowardsMax"); found || restarts.Global != nil {
		restarts.GlobalCountTowardsMax = &value
	}

	statuses, observed, _ := unstructured.NestedSlice(u.Object, "status", "replicatedJobsStatus")
	var individual, individualCounted int64
	for _, item := range statuses {
		status, ok := item.(map[string]any)
		if !ok {
			continue
		}
		if values, found, _ := unstructured.NestedSlice(status, "jobRestarts"); found {
			individual += sumInt64Slice(values)
		}
		if values, found, _ := unstructured.NestedSlice(status, "jobRestartsCountTowardsMax"); found {
			individualCounted += sumInt64Slice(values)
		}
	}
	// Unmaterialized arrays are zero for reported roles in JobSet.
	if observed {
		restarts.Individual = &individual
		restarts.IndividualCountTowardsMax = &individualCounted
	}

	if restarts.Global == nil && restarts.GlobalCountTowardsMax == nil &&
		restarts.Individual == nil && restarts.IndividualCountTowardsMax == nil {
		return nil
	}
	return restarts
}

func sumInt64Slice(values []any) int64 {
	var total int64
	for _, value := range values {
		if count, ok := value.(int64); ok {
			total += count
		}
	}
	return total
}

func jobSetConditions(u *unstructured.Unstructured) map[string]resourcecontext.ConditionSummary {
	raw, ok, _ := unstructured.NestedSlice(u.Object, "status", "conditions")
	if !ok {
		return nil
	}
	conditions := make(map[string]resourcecontext.ConditionSummary, len(raw))
	for _, item := range raw {
		condition, ok := item.(map[string]any)
		if !ok {
			continue
		}
		conditionType, _ := condition["type"].(string)
		if conditionType == "" {
			continue
		}
		conditions[conditionType] = resourcecontext.ConditionSummary{
			Type:               conditionType,
			Status:             stringField(condition, "status"),
			Reason:             stringField(condition, "reason"),
			Message:            stringField(condition, "message"),
			LastTransitionTime: stringField(condition, "lastTransitionTime"),
			ObservedGeneration: int64Field(condition, "observedGeneration"),
		}
	}
	return conditions
}

func stringField(object map[string]any, field string) string {
	value, _ := object[field].(string)
	return value
}

// jobSetPhase maps the shared lifecycle onto the execution vocabulary and
// attaches the deciding condition at the requested tier.
func jobSetPhase(
	state jobset.State,
	conditions map[string]resourcecontext.ConditionSummary,
	tier resourcecontext.ContextTier,
) (resourcecontext.ExecutionPhase, resourcecontext.ExecutionOutcome, *resourcecontext.ConditionSummary) {
	var primary *resourcecontext.ConditionSummary
	if state.Condition != "" {
		primary = conditionForExecution(conditions, state.Condition, tier)
	}
	return executionPhases[state.Phase], executionOutcomes[state.Outcome], primary
}

var executionPhases = map[jobset.Phase]resourcecontext.ExecutionPhase{
	jobset.PhasePending:   resourcecontext.ExecutionPending,
	jobset.PhaseActive:    resourcecontext.ExecutionActive,
	jobset.PhaseSuspended: resourcecontext.ExecutionSuspended,
	jobset.PhaseFinished:  resourcecontext.ExecutionFinished,
	jobset.PhaseUnknown:   resourcecontext.ExecutionUnknown,
}

var executionOutcomes = map[jobset.Outcome]resourcecontext.ExecutionOutcome{
	jobset.OutcomeSucceeded: resourcecontext.ExecutionSucceeded,
	jobset.OutcomeFailed:    resourcecontext.ExecutionFailed,
}

func conditionForExecution(conditions map[string]resourcecontext.ConditionSummary, conditionType string, tier resourcecontext.ContextTier) *resourcecontext.ConditionSummary {
	condition, ok := conditions[conditionType]
	if !ok || condition.Status != "True" {
		return nil
	}
	if tier == resourcecontext.TierDiagnostic {
		condition.Message = truncateMessage(condition.Message)
	} else {
		condition.Message = ""
		condition.LastTransitionTime = ""
	}
	return &condition
}

func truncateMessage(message string) string {
	message = strings.TrimSpace(message)
	if len(message) <= maxStateMessageBytes {
		return message
	}
	const suffix = "…"
	cut := maxStateMessageBytes - len(suffix)
	for cut > 0 && !utf8.RuneStart(message[cut]) {
		cut--
	}
	return strings.TrimSpace(message[:cut]) + suffix
}