package handler import ( "context" "sort" "time" "github.com/goodrain/rainbond/pkg/component/k8s" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" ) type WorkloadDetailSummary struct { Name string `json:"name"` Kind string `json:"kind"` Namespace string `json:"namespace"` Status string `json:"status"` Replicas int64 `json:"replicas,omitempty"` ReadyReplicas int64 `json:"ready_replicas,omitempty"` CreatedAt string `json:"created_at"` Selector map[string]string `json:"selector,omitempty"` } type WorkloadDetail struct { Summary WorkloadDetailSummary `json:"summary"` Workload *unstructured.Unstructured `json:"workload"` Pods []corev1.Pod `json:"pods"` Services []corev1.Service `json:"services"` Ingresses []networkingv1.Ingress `json:"ingresses"` } type PodContainerInfo struct { Name string `json:"name"` Image string `json:"image,omitempty"` Ready bool `json:"ready"` RestartCount int32 `json:"restart_count,omitempty"` } type PodDetailSummary struct { Name string `json:"name"` Namespace string `json:"namespace"` Phase string `json:"phase,omitempty"` NodeName string `json:"node_name,omitempty"` PodIP string `json:"pod_ip,omitempty"` CreatedAt string `json:"created_at"` } type PodResourceDetail struct { Summary PodDetailSummary `json:"summary"` Pod *corev1.Pod `json:"pod"` Detail interface{} `json:"detail,omitempty"` Containers []PodContainerInfo `json:"containers"` Services []corev1.Service `json:"services"` Ingresses []networkingv1.Ingress `json:"ingresses"` } type ResourceEventInfo struct { Type string `json:"type"` Reason string `json:"reason"` Message string `json:"message"` Count int32 `json:"count"` LastTimestamp string `json:"last_timestamp"` } type ResourceCenterHandler struct{} func formatResourceTimestamp(value time.Time) string { if value.IsZero() { return "" } return value.UTC().Format(time.RFC3339) } func (h *ResourceCenterHandler) GetWorkloadDetail(tenantName, group, version, resource, name string) (*WorkloadDetail, error) { if err := validateGVRParams(group, version, resource); err != nil { return nil, err } ns, err := GetNsResourceHandler().getTenantNamespace(tenantName) if err != nil { return nil, err } gvr := schema.GroupVersionResource{Group: group, Version: version, Resource: resource} workload, err := k8s.Default().DynamicClient.Resource(gvr).Namespace(ns).Get(context.Background(), name, metav1.GetOptions{}) if err != nil { return nil, err } selector := extractWorkloadSelector(workload) pods, err := h.listWorkloadPods(ns, selector) if err != nil { return nil, err } services, err := h.listRelatedServices(ns, selector) if err != nil { return nil, err } ingresses, err := h.listRelatedIngresses(ns, services) if err != nil { return nil, err } summary := WorkloadDetailSummary{ Name: workload.GetName(), Kind: workload.GetKind(), Namespace: ns, Status: computeNsResourceStatus(*workload), CreatedAt: formatResourceTimestamp(workload.GetCreationTimestamp().Time), Selector: selector, Replicas: extractWorkloadReplicas(workload), ReadyReplicas: extractWorkloadReadyReplicas(workload), } return &WorkloadDetail{ Summary: summary, Workload: workload, Pods: pods, Services: services, Ingresses: ingresses, }, nil } func (h *ResourceCenterHandler) GetPodDetail(tenantName, podName string) (*PodResourceDetail, error) { ns, err := GetNsResourceHandler().getTenantNamespace(tenantName) if err != nil { return nil, err } pod, err := k8s.Default().Clientset.CoreV1().Pods(ns).Get(context.Background(), podName, metav1.GetOptions{}) if err != nil { return nil, err } detail, err := GetPodHandler().PodDetail(ns, podName) if err != nil { return nil, err } services, err := h.listRelatedServices(ns, pod.Labels) if err != nil { return nil, err } ingresses, err := h.listRelatedIngresses(ns, services) if err != nil { return nil, err } containers := make([]PodContainerInfo, 0, len(pod.Spec.Containers)) statusByName := make(map[string]corev1.ContainerStatus, len(pod.Status.ContainerStatuses)) for _, status := range pod.Status.ContainerStatuses { statusByName[status.Name] = status } for _, container := range pod.Spec.Containers { info := PodContainerInfo{ Name: container.Name, Image: container.Image, } if status, ok := statusByName[container.Name]; ok { info.Ready = status.Ready info.RestartCount = status.RestartCount } containers = append(containers, info) } return &PodResourceDetail{ Summary: PodDetailSummary{ Name: pod.Name, Namespace: pod.Namespace, Phase: string(pod.Status.Phase), NodeName: pod.Spec.NodeName, PodIP: pod.Status.PodIP, CreatedAt: formatResourceTimestamp(pod.CreationTimestamp.Time), }, Pod: pod, Detail: detail, Containers: containers, Services: services, Ingresses: ingresses, }, nil } func (h *ResourceCenterHandler) ListEvents(tenantName, namespace, kind, name string) ([]ResourceEventInfo, error) { ns := namespace if ns == "" { var err error ns, err = GetNsResourceHandler().getTenantNamespace(tenantName) if err != nil { return nil, err } } selector := fields.Set{ "involvedObject.kind": kind, "involvedObject.name": name, }.String() list, err := k8s.Default().Clientset.CoreV1().Events(ns).List(context.Background(), metav1.ListOptions{ FieldSelector: selector, }) if err != nil { return nil, err } items := make([]ResourceEventInfo, 0, len(list.Items)) for _, event := range list.Items { items = append(items, toResourceEventInfo(event)) } sort.Slice(items, func(i, j int) bool { return items[i].LastTimestamp > items[j].LastTimestamp }) return items, nil } func (h *ResourceCenterHandler) listWorkloadPods(namespace string, selector map[string]string) ([]corev1.Pod, error) { if len(selector) == 0 { return []corev1.Pod{}, nil } list, err := k8s.Default().Clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ LabelSelector: labels.Set(selector).String(), }) if err != nil { return nil, err } return list.Items, nil } func (h *ResourceCenterHandler) listRelatedServices(namespace string, selector map[string]string) ([]corev1.Service, error) { list, err := k8s.Default().Clientset.CoreV1().Services(namespace).List(context.Background(), metav1.ListOptions{}) if err != nil { return nil, err } var matched []corev1.Service for _, svc := range list.Items { if labelsMatchSelector(svc.Spec.Selector, selector) { matched = append(matched, svc) } } return matched, nil } func (h *ResourceCenterHandler) listRelatedIngresses(namespace string, services []corev1.Service) ([]networkingv1.Ingress, error) { if len(services) == 0 { return []networkingv1.Ingress{}, nil } serviceNames := make(map[string]struct{}, len(services)) for _, svc := range services { serviceNames[svc.Name] = struct{}{} } list, err := k8s.Default().Clientset.NetworkingV1().Ingresses(namespace).List(context.Background(), metav1.ListOptions{}) if err != nil { return nil, err } var matched []networkingv1.Ingress for _, ing := range list.Items { for _, serviceName := range collectIngressServiceNames(ing) { if _, ok := serviceNames[serviceName]; ok { matched = append(matched, ing) break } } } return matched, nil } func extractWorkloadSelector(workload *unstructured.Unstructured) map[string]string { if selector, ok, _ := unstructured.NestedStringMap(workload.Object, "spec", "selector", "matchLabels"); ok && len(selector) > 0 { return selector } if selector, ok, _ := unstructured.NestedStringMap(workload.Object, "spec", "jobTemplate", "spec", "template", "metadata", "labels"); ok && len(selector) > 0 { return selector } if selector, ok, _ := unstructured.NestedStringMap(workload.Object, "spec", "template", "metadata", "labels"); ok && len(selector) > 0 { return selector } return nil } func extractWorkloadReplicas(workload *unstructured.Unstructured) int64 { switch workload.GetKind() { case "DaemonSet": value, _, _ := unstructured.NestedInt64(workload.Object, "status", "desiredNumberScheduled") return value case "CronJob": active, _, _ := unstructured.NestedSlice(workload.Object, "status", "active") return int64(len(active)) default: value, _, _ := unstructured.NestedInt64(workload.Object, "spec", "replicas") return value } } func extractWorkloadReadyReplicas(workload *unstructured.Unstructured) int64 { switch workload.GetKind() { case "DaemonSet": value, _, _ := unstructured.NestedInt64(workload.Object, "status", "numberReady") return value case "CronJob": active, _, _ := unstructured.NestedSlice(workload.Object, "status", "active") return int64(len(active)) default: value, _, _ := unstructured.NestedInt64(workload.Object, "status", "readyReplicas") return value } } func labelsMatchSelector(selector map[string]string, resourceLabels map[string]string) bool { if len(selector) == 0 || len(resourceLabels) == 0 { return false } for key, value := range selector { if resourceLabels[key] != value { return false } } return true } func collectIngressServiceNames(ingress networkingv1.Ingress) []string { names := make([]string, 0, 4) if ingress.Spec.DefaultBackend != nil && ingress.Spec.DefaultBackend.Service != nil { names = append(names, ingress.Spec.DefaultBackend.Service.Name) } for _, rule := range ingress.Spec.Rules { if rule.HTTP == nil { continue } for _, path := range rule.HTTP.Paths { if path.Backend.Service != nil { names = append(names, path.Backend.Service.Name) } } } return uniqueStrings(names) } func uniqueStrings(values []string) []string { seen := make(map[string]struct{}, len(values)) result := make([]string, 0, len(values)) for _, value := range values { if value == "" { continue } if _, ok := seen[value]; ok { continue } seen[value] = struct{}{} result = append(result, value) } return result } func toResourceEventInfo(event corev1.Event) ResourceEventInfo { lastTimestamp := formatResourceTimestamp(event.LastTimestamp.Time) if lastTimestamp == "" { lastTimestamp = formatResourceTimestamp(event.EventTime.Time) } if lastTimestamp == "" { lastTimestamp = formatResourceTimestamp(event.FirstTimestamp.Time) } return ResourceEventInfo{ Type: event.Type, Reason: event.Reason, Message: event.Message, Count: event.Count, LastTimestamp: lastTimestamp, } } var resourceCenterHandler *ResourceCenterHandler func GetResourceCenterHandler() *ResourceCenterHandler { if resourceCenterHandler == nil { resourceCenterHandler = &ResourceCenterHandler{} } return resourceCenterHandler }