restructure: console→client-console, add admin-console skeleton, move docs to doc/
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"fission-console/internal/fission"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var requiredBootstrapServiceAccounts = []string{"fission-fetcher", "fission-builder"}
|
||||
|
||||
var requiredBootstrapRoleBindings = []string{
|
||||
"fission-executor-user-ns",
|
||||
"fission-router-user-ns",
|
||||
"fission-buildermgr-user-ns",
|
||||
"fission-kubewatcher-user-ns",
|
||||
"fission-timer-user-ns",
|
||||
"fission-fetcher-system-user-ns",
|
||||
"fission-builder-system-user-ns",
|
||||
"fission-fetcher-local-user-ns",
|
||||
"fission-builder-local-user-ns",
|
||||
}
|
||||
|
||||
type namespacePreparationStatus struct {
|
||||
ManagedByConsole bool `json:"managedByConsole"`
|
||||
WatcherLabel bool `json:"watcherLabel"`
|
||||
ServiceAccounts map[string]bool `json:"serviceAccounts"`
|
||||
RoleBindings map[string]bool `json:"roleBindings"`
|
||||
}
|
||||
|
||||
func (s namespacePreparationStatus) Ready() bool {
|
||||
if !s.ManagedByConsole || !s.WatcherLabel {
|
||||
return false
|
||||
}
|
||||
for _, ok := range s.ServiceAccounts {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, ok := range s.RoleBindings {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type controlPlaneServiceStatus struct {
|
||||
Ready bool `json:"ready"`
|
||||
PodCount int `json:"podCount"`
|
||||
ReadyPods []string `json:"readyPods"`
|
||||
}
|
||||
|
||||
type controlPlaneStatus struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Ready bool `json:"ready"`
|
||||
Executor controlPlaneServiceStatus `json:"executor"`
|
||||
Router controlPlaneServiceStatus `json:"router"`
|
||||
}
|
||||
|
||||
// handleNSStatus возвращает статус инициализации пользовательского namespace.
|
||||
// Используется UI для отображения прогресса при первом входе нового пользователя.
|
||||
//
|
||||
// Три стадии:
|
||||
// 1. Namespace существует и Active
|
||||
// 2. Namespace подготовлен для Layer 1 NSWatcher: label + bootstrap RBAC/SA
|
||||
// 3. Fission control-plane готов: executor и router Running + Ready
|
||||
func (s *Server) handleNSStatus(w http.ResponseWriter, r *http.Request) {
|
||||
ns := s.userNS(r)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
type stageInfo struct {
|
||||
Name string `json:"name"`
|
||||
Done bool `json:"done"`
|
||||
}
|
||||
stages := []stageInfo{
|
||||
{Name: "Создание пространства имён"},
|
||||
{Name: "Подготовка namespace для Fission"},
|
||||
{Name: "Готовность control-plane"},
|
||||
}
|
||||
|
||||
nsObj, err := s.dyn.Resource(fission.NamespaceGVR).Get(ctx, ns, metav1.GetOptions{})
|
||||
var preparation namespacePreparationStatus
|
||||
control := controlPlaneStatus{Namespace: fissionSystemNamespace()}
|
||||
if err == nil {
|
||||
phase, _, _ := unstructured.NestedString(nsObj.Object, "status", "phase")
|
||||
stages[0].Done = phase == "Active"
|
||||
}
|
||||
|
||||
if stages[0].Done {
|
||||
preparation = s.namespacePreparationStatus(ctx, ns, nsObj)
|
||||
stages[1].Done = preparation.Ready()
|
||||
}
|
||||
|
||||
if stages[1].Done {
|
||||
control = s.controlPlaneStatus(ctx)
|
||||
stages[2].Done = control.Ready
|
||||
}
|
||||
|
||||
ready := stages[0].Done && stages[1].Done && stages[2].Done
|
||||
writeAnyJSON(w, http.StatusOK, map[string]any{
|
||||
"ready": ready,
|
||||
"stages": stages,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleNSDebug(w http.ResponseWriter, r *http.Request) {
|
||||
ns := s.userNS(r)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nsObj, err := s.dyn.Resource(fission.NamespaceGVR).Get(ctx, ns, metav1.GetOptions{})
|
||||
labels := map[string]string{}
|
||||
phase := ""
|
||||
exists := err == nil
|
||||
active := false
|
||||
preparation := namespacePreparationStatus{}
|
||||
if exists {
|
||||
labels, _, _ = unstructured.NestedStringMap(nsObj.Object, "metadata", "labels")
|
||||
phase, _, _ = unstructured.NestedString(nsObj.Object, "status", "phase")
|
||||
active = phase == "Active"
|
||||
preparation = s.namespacePreparationStatus(ctx, ns, nsObj)
|
||||
}
|
||||
control := s.controlPlaneStatus(ctx)
|
||||
|
||||
writeAnyJSON(w, http.StatusOK, map[string]any{
|
||||
"namespace": ns,
|
||||
"exists": exists,
|
||||
"active": active,
|
||||
"phase": phase,
|
||||
"labels": labels,
|
||||
"prepared": preparation.Ready(),
|
||||
"preparation": preparation,
|
||||
"controlPlane": control,
|
||||
"namespaceReady": active && preparation.Ready() && control.Ready,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) namespacePreparationStatus(ctx context.Context, ns string, nsObj *unstructured.Unstructured) namespacePreparationStatus {
|
||||
status := namespacePreparationStatus{
|
||||
ServiceAccounts: make(map[string]bool, len(requiredBootstrapServiceAccounts)),
|
||||
RoleBindings: make(map[string]bool, len(requiredBootstrapRoleBindings)),
|
||||
}
|
||||
if nsObj != nil {
|
||||
labels, _, _ := unstructured.NestedStringMap(nsObj.Object, "metadata", "labels")
|
||||
status.ManagedByConsole = labels["managed-by"] == "fission-console"
|
||||
status.WatcherLabel = labels["fission.io/managed"] == "true"
|
||||
}
|
||||
|
||||
saGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "serviceaccounts"}
|
||||
for _, name := range requiredBootstrapServiceAccounts {
|
||||
_, err := s.dyn.Resource(saGVR).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
status.ServiceAccounts[name] = err == nil
|
||||
}
|
||||
|
||||
rbGVR := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "rolebindings"}
|
||||
for _, name := range requiredBootstrapRoleBindings {
|
||||
_, err := s.dyn.Resource(rbGVR).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
status.RoleBindings[name] = err == nil
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (s *Server) controlPlaneStatus(ctx context.Context) controlPlaneStatus {
|
||||
status := controlPlaneStatus{Namespace: fissionSystemNamespace()}
|
||||
status.Executor = s.serviceStatus(ctx, status.Namespace, "executor")
|
||||
status.Router = s.serviceStatus(ctx, status.Namespace, "router")
|
||||
status.Ready = status.Executor.Ready && status.Router.Ready
|
||||
return status
|
||||
}
|
||||
|
||||
func (s *Server) serviceStatus(ctx context.Context, namespace, service string) controlPlaneServiceStatus {
|
||||
podGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
podList, err := s.dyn.Resource(podGVR).Namespace(namespace).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: "svc=" + service,
|
||||
})
|
||||
if err != nil {
|
||||
return controlPlaneServiceStatus{}
|
||||
}
|
||||
|
||||
status := controlPlaneServiceStatus{PodCount: len(podList.Items)}
|
||||
for _, pod := range podList.Items {
|
||||
if podReady(&pod) {
|
||||
status.Ready = true
|
||||
status.ReadyPods = append(status.ReadyPods, pod.GetName())
|
||||
}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func fissionSystemNamespace() string {
|
||||
ns := os.Getenv("FISSION_SYSTEM_NAMESPACE")
|
||||
if ns == "" {
|
||||
return "fission"
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func podReady(pod *unstructured.Unstructured) bool {
|
||||
if pod == nil {
|
||||
return false
|
||||
}
|
||||
phase, _, _ := unstructured.NestedString(pod.Object, "status", "phase")
|
||||
if phase != "Running" {
|
||||
return false
|
||||
}
|
||||
conditions, _, _ := unstructured.NestedSlice(pod.Object, "status", "conditions")
|
||||
for _, c := range conditions {
|
||||
cond, ok := c.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if cond["type"] == "Ready" && cond["status"] == "True" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user