refactor: split main.go into packages (internal/api, internal/fission, internal/runtime, cmd/server)
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package fission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"fission-console/internal/model"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
// EnsureEnvironment создаёт Environment CRD для языка lang в namespace ns если не существует.
|
||||
// Возвращает имя environment (например "console-python-env").
|
||||
//
|
||||
// Lazy creation: environments создаются только когда пользователь создаёт первую функцию
|
||||
// на конкретном языке. Это экономит ресурсы — Fission pool pods поднимаются только
|
||||
// под те языки которые реально используются, а не все сразу при создании namespace.
|
||||
func EnsureEnvironment(ctx context.Context, dyn dynamic.Interface, ns, lang string) (string, error) {
|
||||
langDef, ok := model.LangEnvMap[lang]
|
||||
if !ok {
|
||||
// Неизвестный язык — клиентская ошибка, возвращаем 400-совместимое сообщение
|
||||
return "", fmt.Errorf("unsupported language: %q", lang)
|
||||
}
|
||||
envName := "console-" + lang + "-env"
|
||||
|
||||
// Проверяем существование — Get быстрее чем Create+IsAlreadyExists
|
||||
_, getErr := dyn.Resource(EnvironmentGVR).Namespace(ns).Get(ctx, envName, metav1.GetOptions{})
|
||||
if getErr == nil {
|
||||
return envName, nil // уже существует — быстрый путь
|
||||
}
|
||||
if !apierrors.IsNotFound(getErr) {
|
||||
return "", fmt.Errorf("check environment %q: %w", envName, getErr)
|
||||
}
|
||||
|
||||
// Строим и создаём Environment CRD
|
||||
env := buildLangEnvironment(envName, ns, langDef)
|
||||
if _, createErr := dyn.Resource(EnvironmentGVR).Namespace(ns).Create(ctx, env, metav1.CreateOptions{}); createErr != nil {
|
||||
if apierrors.IsAlreadyExists(createErr) {
|
||||
// Race condition: другой goroutine создал между Get и Create — это нормально
|
||||
return envName, nil
|
||||
}
|
||||
return "", fmt.Errorf("create environment %q: %w", envName, createErr)
|
||||
}
|
||||
log.Printf("ensureEnvironment: created %s/%s", ns, envName)
|
||||
return envName, nil
|
||||
}
|
||||
|
||||
// CleanupEnvironmentIfUnused удаляет Environment CRD если ни одна функция в namespace
|
||||
// его не использует. Вызывается после удаления функции (handleDeleteFunction)
|
||||
// и при срабатывании reaper-а (runExpiryReap).
|
||||
//
|
||||
// Не блокирующий: ошибки логируются, не возвращаются вызывающему — это best-effort cleanup.
|
||||
// Fission увидит удаление Environment CRD и убьёт pool Deployment → поды умирают.
|
||||
func CleanupEnvironmentIfUnused(ctx context.Context, dyn dynamic.Interface, ns, envName string) {
|
||||
functions, err := dyn.Resource(FunctionGVR).Namespace(ns).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Printf("cleanupEnvironmentIfUnused: list functions in %s: %v", ns, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Если хотя бы одна функция ссылается на этот environment — оставляем его
|
||||
for _, fn := range functions.Items {
|
||||
fnEnv, _, _ := unstructured.NestedString(fn.Object, "spec", "environment", "name")
|
||||
if fnEnv == envName {
|
||||
return // ещё используется
|
||||
}
|
||||
}
|
||||
|
||||
// Ни одна функция не ссылается — удаляем
|
||||
if delErr := dyn.Resource(EnvironmentGVR).Namespace(ns).Delete(ctx, envName, metav1.DeleteOptions{}); delErr != nil && !apierrors.IsNotFound(delErr) {
|
||||
log.Printf("cleanupEnvironmentIfUnused: delete env %s/%s: %v", ns, envName, delErr)
|
||||
return
|
||||
}
|
||||
log.Printf("cleanupEnvironmentIfUnused: deleted unused env %s/%s", ns, envName)
|
||||
}
|
||||
|
||||
// buildLangEnvironment строит unstructured.Unstructured объект Environment CRD.
|
||||
// Вся Fission-специфичная схема изолирована здесь — при обновлении Fission меняем только тут.
|
||||
func buildLangEnvironment(name, ns string, def model.LangEnvDef) *unstructured.Unstructured {
|
||||
// Version по умолчанию 3 (V2 protocol с async entrypoint).
|
||||
// Исключение: perl-env поддерживает только V1 protocol → version=1.
|
||||
envVersion := int64(3)
|
||||
if def.Version != 0 {
|
||||
envVersion = int64(def.Version)
|
||||
}
|
||||
|
||||
spec := map[string]any{
|
||||
"version": envVersion,
|
||||
"runtime": map[string]any{
|
||||
"image": def.Image,
|
||||
},
|
||||
"poolsize": int64(1),
|
||||
}
|
||||
|
||||
// BuilderImage задан только для Go — остальные языки интерпретируемые,
|
||||
// им builder не нужен: код передаётся напрямую в deployment.literal.
|
||||
if def.BuilderImage != "" {
|
||||
spec["builder"] = map[string]any{
|
||||
"image": def.BuilderImage,
|
||||
"command": "build",
|
||||
}
|
||||
}
|
||||
|
||||
return &unstructured.Unstructured{Object: map[string]any{
|
||||
"apiVersion": "fission.io/v1",
|
||||
"kind": "Environment",
|
||||
"metadata": map[string]any{
|
||||
"name": name,
|
||||
"namespace": ns,
|
||||
},
|
||||
"spec": spec,
|
||||
}}
|
||||
}
|
||||
Reference in New Issue
Block a user