// Package api — определение статуса функции. // // Этот файл содержит функции для определения статуса функции на основе: // - buildStatus пакета (Package CRD) // - состояния pod-а (kubernetes Pod) // // Возможные статусы: // - "Ready" (зелёный) — Pod в состоянии Running // - "Cold" (синий) — функция готова, но нет активного Pod // - "Building" (жёлтый) — Package ещё собирается (buildStatus != "succeeded") // - "Error" (красный) — Pod в состоянии ошибки (CrashLoopBackOff, ImagePullBackOff и т.д.) или build failed package api import ( "context" "fmt" "net/http" "time" "fission-console/internal/fission" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) // FunctionStatus — расширенный объект Function с полем status. type FunctionStatus struct { // Встраиваем исходный объект и добавляем status Object map[string]any `json:"object,inline"` Status string `json:"status"` // "Ready", "Cold", "Building", "Error" } // getFunctionStatus определяет статус функции на основе pod-ов и Package. func (s *Server) getFunctionStatus(ctx context.Context, ns string, fn *unstructured.Unstructured) (string, error) { fnName := fn.GetName() // Шаг 1: проверяем buildStatus пакета (если есть) packageName, _, _ := unstructured.NestedString(fn.Object, "spec", "package", "packageref", "name") if packageName != "" { pkg, pkgErr := s.dyn.Resource(fission.PackageGVR).Namespace(ns).Get(ctx, packageName, metav1.GetOptions{}) if pkgErr == nil { buildStatus, _, _ := unstructured.NestedString(pkg.Object, "status", "buildstatus") // "failed" → Error // "none", "succeeded" → OK, Package готов // "pending", "building" и т.д. → Building if buildStatus == "failed" { return "Error", nil } if buildStatus != "" && buildStatus != "succeeded" && buildStatus != "none" { // Building, Pending и т.д. return "Building", nil } } } // Шаг 2: ищем Pod-ы для этой функции (по лейблу functionName=) pods, err := s.kube.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("functionName=%s", fnName), }) if err != nil && !apierrors.IsNotFound(err) { // Логируем ошибку, но не падаем — возвращаем "Cold" (функция есть, но pod-а нет) return "Cold", nil } // Шаг 3: анализируем состояние pod-ов if pods != nil && len(pods.Items) > 0 { for _, pod := range pods.Items { // Проверяем фазу pod-а phase := pod.Status.Phase if phase == "Running" { // Дополнительная проверка: все контейнеры готовы? allReady := true for _, status := range pod.Status.ContainerStatuses { if !status.Ready { allReady = false break } } if allReady { return "Ready", nil } } // Проверяем состояние контейнеров (CrashLoopBackOff, ImagePullBackOff и т.д.) for _, status := range pod.Status.ContainerStatuses { if status.State.Waiting != nil && status.State.Waiting.Reason == "CrashLoopBackOff" { return "Error", nil } if status.State.Waiting != nil && status.State.Waiting.Reason == "ImagePullBackOff" { return "Error", nil } if status.LastTerminationState.Terminated != nil && status.LastTerminationState.Terminated.ExitCode != 0 { return "Error", nil } } } // Pod есть, но не в Running или Error состоянии → Building/Deploying return "Building", nil } // Нет pod-ов → Cold (функция холодная, но готова) return "Cold", nil } // handleListFunctionsWithStatus возвращает список функций с полем "status". // Добавляет status в поле .metadata.annotations["fission-console/status"] каждой функции. func (s *Server) handleListFunctionsWithStatus(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) // 30 сек для определения статуса всех функций defer cancel() ns := s.userNS(r) // Получаем список функций list, err := s.dyn.Resource(fission.FunctionGVR).Namespace(ns).List(ctx, metav1.ListOptions{}) if err != nil { writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("list functions: %v", err)) return } // Обогащаем каждую функцию статусом (добавляем в .metadata.annotations["fission-console/status"]) for i := range list.Items { status, _ := s.getFunctionStatus(ctx, ns, &list.Items[i]) anns := list.Items[i].GetAnnotations() if anns == nil { anns = make(map[string]string) } anns["fission-console/status"] = status list.Items[i].SetAnnotations(anns) } writeJSON(w, http.StatusOK, list.Items) }