139 lines
5.3 KiB
Go
139 lines
5.3 KiB
Go
// 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=<name>)
|
||
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)
|
||
}
|