mirror of https://github.com/kubevela/kubevela.git
774 lines
26 KiB
Go
774 lines
26 KiB
Go
/*
|
|
Copyright 2021 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package query
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
v12 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
types2 "k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/duration"
|
|
"k8s.io/kubectl/pkg/util/podutils"
|
|
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
velatypes "github.com/oam-dev/kubevela/apis/types"
|
|
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
|
"github.com/oam-dev/kubevela/pkg/multicluster"
|
|
"github.com/oam-dev/kubevela/pkg/oam"
|
|
"github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
|
|
|
|
helmreleaseapi "github.com/fluxcd/helm-controller/api/v2beta1"
|
|
helmrepoapi "github.com/fluxcd/source-controller/api/v1beta2"
|
|
)
|
|
|
|
// relationshipKey is the configmap key of relationShip rule
|
|
var relationshipKey = "rules"
|
|
|
|
// set the iterator max depth is 5
|
|
var maxDepth = 5
|
|
|
|
// globalRule define the whole relationShip rule
|
|
var globalRule map[GroupResourceType]ChildrenResourcesRule
|
|
|
|
func init() {
|
|
globalRule = make(map[GroupResourceType]ChildrenResourcesRule)
|
|
globalRule[GroupResourceType{Group: "apps", Kind: "Deployment"}] = ChildrenResourcesRule{
|
|
CareResource: map[ResourceType]genListOptionFunc{
|
|
{APIVersion: "apps/v1", Kind: "ReplicaSet"}: deploy2RsLabelListOption,
|
|
},
|
|
}
|
|
globalRule[GroupResourceType{Group: "apps", Kind: "ReplicaSet"}] = ChildrenResourcesRule{
|
|
CareResource: map[ResourceType]genListOptionFunc{
|
|
{APIVersion: "v1", Kind: "Pod"}: rs2PodLabelListOption,
|
|
},
|
|
}
|
|
globalRule[GroupResourceType{Group: "apps", Kind: "StatefulSet"}] = ChildrenResourcesRule{
|
|
CareResource: map[ResourceType]genListOptionFunc{
|
|
{APIVersion: "v1", Kind: "Pod"}: statefulSet2PodListOption,
|
|
},
|
|
}
|
|
globalRule[GroupResourceType{Group: "", Kind: "Service"}] = ChildrenResourcesRule{
|
|
CareResource: map[ResourceType]genListOptionFunc{
|
|
{APIVersion: "discovery.k8s.io/v1beta1", Kind: "EndpointSlice"}: nil,
|
|
{APIVersion: "v1", Kind: "Endpoints"}: service2EndpointListOption,
|
|
},
|
|
}
|
|
globalRule[GroupResourceType{Group: "helm.toolkit.fluxcd.io", Kind: "HelmRelease"}] = ChildrenResourcesRule{
|
|
CareResource: map[ResourceType]genListOptionFunc{
|
|
{APIVersion: "apps/v1", Kind: "Deployment"}: nil,
|
|
{APIVersion: "apps/v1", Kind: "StatefulSet"}: nil,
|
|
{APIVersion: "v1", Kind: "ConfigMap"}: nil,
|
|
{APIVersion: "v1", Kind: "Secret"}: nil,
|
|
{APIVersion: "v1", Kind: "Service"}: nil,
|
|
{APIVersion: "v1", Kind: "PersistentVolumeClaim"}: nil,
|
|
{APIVersion: "networking.k8s.io/v1", Kind: "Ingress"}: nil,
|
|
{APIVersion: "v1", Kind: "ServiceAccount"}: nil,
|
|
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"}: nil,
|
|
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "RoleBinding"}: nil,
|
|
},
|
|
DefaultGenListOptionFunc: helmRelease2AnyListOption,
|
|
DisableFilterByOwnerReference: true,
|
|
}
|
|
}
|
|
|
|
// GroupResourceType define the parent resource type
|
|
type GroupResourceType struct {
|
|
Group string `json:"group"`
|
|
Kind string `json:"kind"`
|
|
}
|
|
|
|
// ResourceType define the children resource type
|
|
type ResourceType struct {
|
|
APIVersion string `json:"apiVersion,omitempty"`
|
|
Kind string `json:"kind,omitempty"`
|
|
}
|
|
|
|
// customRule define the customize rule created by user
|
|
type customRule struct {
|
|
ParentResourceType *GroupResourceType `json:"parentResourceType,omitempty"`
|
|
ChildrenResourceType []ResourceType `json:"childrenResourceType,omitempty"`
|
|
}
|
|
|
|
// ChildrenResourcesRule define the relationShip between parentObject and children resource
|
|
type ChildrenResourcesRule struct {
|
|
// every subResourceType can have a specified genListOptionFunc.
|
|
CareResource map[ResourceType]genListOptionFunc
|
|
// if specified genListOptionFunc is nil will use use default genListOptionFunc to generate listOption.
|
|
DefaultGenListOptionFunc genListOptionFunc
|
|
// DisableFilterByOwnerReference means don't use parent resource's UID filter the result.
|
|
DisableFilterByOwnerReference bool
|
|
}
|
|
|
|
type genListOptionFunc func(unstructured.Unstructured) (client.ListOptions, error)
|
|
|
|
var deploy2RsLabelListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
|
|
deploy := appsv1.Deployment{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deploy)
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
deploySelector, err := v1.LabelSelectorAsSelector(deploy.Spec.Selector)
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
return client.ListOptions{Namespace: deploy.Namespace, LabelSelector: deploySelector}, nil
|
|
}
|
|
|
|
var rs2PodLabelListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
|
|
rs := appsv1.ReplicaSet{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &rs)
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
rsSelector, err := v1.LabelSelectorAsSelector(rs.Spec.Selector)
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
return client.ListOptions{Namespace: rs.Namespace, LabelSelector: rsSelector}, nil
|
|
}
|
|
|
|
var statefulSet2PodListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
|
|
sts := appsv1.StatefulSet{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &sts)
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
stsSelector, err := v1.LabelSelectorAsSelector(sts.Spec.Selector)
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
return client.ListOptions{Namespace: sts.Namespace, LabelSelector: stsSelector}, nil
|
|
}
|
|
|
|
var service2EndpointListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
|
|
svc := v12.Service{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &svc)
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
stsSelector, err := v1.LabelSelectorAsSelector(&v1.LabelSelector{MatchLabels: svc.Labels})
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
return client.ListOptions{Namespace: svc.Namespace, LabelSelector: stsSelector}, nil
|
|
}
|
|
|
|
var helmRelease2AnyListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
|
|
hrSelector, err := v1.LabelSelectorAsSelector(&v1.LabelSelector{MatchLabels: map[string]string{
|
|
"helm.toolkit.fluxcd.io/name": obj.GetName(),
|
|
"helm.toolkit.fluxcd.io/namespace": obj.GetNamespace(),
|
|
}})
|
|
if err != nil {
|
|
return client.ListOptions{}, err
|
|
}
|
|
return client.ListOptions{LabelSelector: hrSelector}, nil
|
|
}
|
|
|
|
type healthyCheckFunc func(obj unstructured.Unstructured) (*types.HealthStatus, error)
|
|
|
|
var checkPodStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
|
var pod v12.Pod
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pod)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert unstructured Pod to typed: %w", err)
|
|
}
|
|
|
|
getFailMessage := func(ctr *v12.ContainerStatus) string {
|
|
if ctr.State.Terminated != nil {
|
|
if ctr.State.Terminated.Message != "" {
|
|
return ctr.State.Terminated.Message
|
|
}
|
|
if ctr.State.Terminated.Reason == "OOMKilled" {
|
|
return ctr.State.Terminated.Reason
|
|
}
|
|
if ctr.State.Terminated.ExitCode != 0 {
|
|
return fmt.Sprintf("container %q failed with exit code %d", ctr.Name, ctr.State.Terminated.ExitCode)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
switch pod.Status.Phase {
|
|
case v12.PodSucceeded:
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusHealthy,
|
|
Reason: pod.Status.Reason,
|
|
Message: pod.Status.Message,
|
|
}, nil
|
|
case v12.PodRunning:
|
|
switch pod.Spec.RestartPolicy {
|
|
case v12.RestartPolicyAlways:
|
|
// if pod is ready, it is automatically healthy
|
|
if podutils.IsPodReady(&pod) {
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusHealthy,
|
|
Reason: "all containers are ready",
|
|
}, nil
|
|
}
|
|
// if it's not ready, check to see if any container terminated, if so, it's unhealthy
|
|
for _, ctrStatus := range pod.Status.ContainerStatuses {
|
|
if ctrStatus.LastTerminationState.Terminated != nil {
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusUnHealthy,
|
|
Reason: pod.Status.Reason,
|
|
Message: pod.Status.Message,
|
|
}, nil
|
|
}
|
|
}
|
|
// otherwise we are progressing towards a ready state
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusProgressing,
|
|
Reason: pod.Status.Reason,
|
|
Message: pod.Status.Message,
|
|
}, nil
|
|
case v12.RestartPolicyOnFailure, v12.RestartPolicyNever:
|
|
// pods set with a restart policy of OnFailure or Never, have a finite life.
|
|
// These pods are typically resource hooks. Thus, we consider these as Progressing
|
|
// instead of healthy.
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusProgressing,
|
|
Reason: pod.Status.Reason,
|
|
Message: pod.Status.Message,
|
|
}, nil
|
|
}
|
|
case v12.PodPending:
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusProgressing,
|
|
Message: pod.Status.Message,
|
|
}, nil
|
|
case v12.PodFailed:
|
|
if pod.Status.Message != "" {
|
|
// Pod has a nice error message. Use that.
|
|
return &types.HealthStatus{Status: types.HealthStatusUnHealthy, Message: pod.Status.Message}, nil
|
|
}
|
|
for _, ctr := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) {
|
|
if msg := getFailMessage(ctr.DeepCopy()); msg != "" {
|
|
return &types.HealthStatus{Status: types.HealthStatusUnHealthy, Message: msg}, nil
|
|
}
|
|
}
|
|
return &types.HealthStatus{Status: types.HealthStatusUnHealthy, Message: ""}, nil
|
|
default:
|
|
}
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusUnKnown,
|
|
Reason: string(pod.Status.Phase),
|
|
Message: pod.Status.Message,
|
|
}, nil
|
|
}
|
|
|
|
var checkHelmReleaseStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
|
helmRelease := &helmreleaseapi.HelmRelease{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &helmRelease)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert unstructured helmRelease to typed: %w", err)
|
|
}
|
|
if len(helmRelease.Status.Conditions) != 0 {
|
|
for _, condition := range helmRelease.Status.Conditions {
|
|
if condition.Type == "Ready" {
|
|
if condition.Status == v1.ConditionTrue {
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusHealthy,
|
|
}, nil
|
|
}
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusUnHealthy,
|
|
Message: condition.Message,
|
|
}, nil
|
|
}
|
|
}
|
|
}
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusUnKnown,
|
|
}, nil
|
|
}
|
|
|
|
var checkHelmRepoStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
|
helmRepo := helmrepoapi.HelmRepository{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &helmRepo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert unstructured helmRelease to typed: %w", err)
|
|
}
|
|
if len(helmRepo.Status.Conditions) != 0 {
|
|
for _, condition := range helmRepo.Status.Conditions {
|
|
if condition.Type == "Ready" {
|
|
if condition.Status == v1.ConditionTrue {
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusHealthy,
|
|
Message: condition.Message,
|
|
}, nil
|
|
}
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusUnHealthy,
|
|
Message: condition.Message,
|
|
}, nil
|
|
}
|
|
}
|
|
}
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusUnKnown,
|
|
}, nil
|
|
}
|
|
|
|
var checkReplicaSetStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
|
replicaSet := appsv1.ReplicaSet{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &replicaSet)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert unstructured ReplicaSet to typed: %w", err)
|
|
}
|
|
if replicaSet.Generation <= replicaSet.Status.ObservedGeneration {
|
|
cond := getAppsv1ReplicaSetCondition(replicaSet.Status, appsv1.ReplicaSetReplicaFailure)
|
|
if cond != nil && cond.Status == v12.ConditionTrue {
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusUnHealthy,
|
|
Reason: cond.Reason,
|
|
Message: cond.Message,
|
|
}, nil
|
|
} else if replicaSet.Spec.Replicas != nil && replicaSet.Status.AvailableReplicas < *replicaSet.Spec.Replicas {
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusProgressing,
|
|
Message: fmt.Sprintf("Waiting for rollout to finish: %d out of %d new replicas are available...", replicaSet.Status.AvailableReplicas, *replicaSet.Spec.Replicas),
|
|
}, nil
|
|
}
|
|
} else {
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusProgressing,
|
|
Message: "Waiting for rollout to finish: observed replica set generation less then desired generation",
|
|
}, nil
|
|
}
|
|
|
|
return &types.HealthStatus{
|
|
Status: types.HealthStatusHealthy,
|
|
}, nil
|
|
}
|
|
|
|
func getAppsv1ReplicaSetCondition(status appsv1.ReplicaSetStatus, condType appsv1.ReplicaSetConditionType) *appsv1.ReplicaSetCondition {
|
|
for i := range status.Conditions {
|
|
c := status.Conditions[i]
|
|
if c.Type == condType {
|
|
return &c
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var checkPVCHealthStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
|
pvc := v12.PersistentVolumeClaim{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pvc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert unstructured PVC to typed: %w", err)
|
|
}
|
|
var status types.HealthStatusCode
|
|
switch pvc.Status.Phase {
|
|
case v12.ClaimLost:
|
|
status = types.HealthStatusUnHealthy
|
|
case v12.ClaimPending:
|
|
status = types.HealthStatusProgressing
|
|
case v12.ClaimBound:
|
|
status = types.HealthStatusHealthy
|
|
default:
|
|
status = types.HealthStatusUnKnown
|
|
}
|
|
return &types.HealthStatus{Status: status}, nil
|
|
}
|
|
|
|
var checkServiceStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
|
svc := v12.Service{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &svc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert unstructured service to typed: %w", err)
|
|
}
|
|
health := types.HealthStatus{Status: types.HealthStatusHealthy}
|
|
if svc.Spec.Type == v12.ServiceTypeLoadBalancer {
|
|
if len(svc.Status.LoadBalancer.Ingress) > 0 {
|
|
health.Status = types.HealthStatusHealthy
|
|
} else {
|
|
health.Status = types.HealthStatusProgressing
|
|
}
|
|
}
|
|
return &health, nil
|
|
}
|
|
|
|
func checkResourceStatus(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
|
group := obj.GroupVersionKind().Group
|
|
kind := obj.GroupVersionKind().Kind
|
|
var checkFunc healthyCheckFunc
|
|
switch group {
|
|
case "":
|
|
switch kind {
|
|
case "Pod":
|
|
checkFunc = checkPodStatus
|
|
case "Service":
|
|
checkFunc = checkServiceStatus
|
|
case "PersistentVolumeClaim":
|
|
checkFunc = checkPVCHealthStatus
|
|
}
|
|
case "apps":
|
|
switch kind {
|
|
case "ReplicaSet":
|
|
checkFunc = checkReplicaSetStatus
|
|
default:
|
|
}
|
|
case "helm.toolkit.fluxcd.io":
|
|
switch kind {
|
|
case "HelmRelease":
|
|
checkFunc = checkHelmReleaseStatus
|
|
default:
|
|
}
|
|
case "source.toolkit.fluxcd.io":
|
|
switch kind {
|
|
case "HelmRepository":
|
|
checkFunc = checkHelmRepoStatus
|
|
default:
|
|
}
|
|
default:
|
|
}
|
|
if checkFunc != nil {
|
|
return checkFunc(obj)
|
|
}
|
|
return &types.HealthStatus{Status: types.HealthStatusHealthy}, nil
|
|
}
|
|
|
|
type additionalInfoFunc func(obj unstructured.Unstructured) (map[string]interface{}, error)
|
|
|
|
func additionalInfo(obj unstructured.Unstructured) (map[string]interface{}, error) {
|
|
group := obj.GroupVersionKind().Group
|
|
kind := obj.GroupVersionKind().Kind
|
|
var infoFunc additionalInfoFunc
|
|
switch group {
|
|
case "":
|
|
switch kind {
|
|
case "Pod":
|
|
infoFunc = podAdditionalInfo
|
|
case "Service":
|
|
infoFunc = svcAdditionalInfo
|
|
}
|
|
default:
|
|
}
|
|
if infoFunc != nil {
|
|
return infoFunc(obj)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func svcAdditionalInfo(obj unstructured.Unstructured) (map[string]interface{}, error) {
|
|
svc := v12.Service{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &svc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert unstructured svc to typed: %w", err)
|
|
}
|
|
if svc.Spec.Type == v12.ServiceTypeLoadBalancer {
|
|
var eip string
|
|
for _, ingress := range svc.Status.LoadBalancer.Ingress {
|
|
if len(ingress.IP) != 0 {
|
|
eip = ingress.IP
|
|
}
|
|
}
|
|
if len(eip) == 0 {
|
|
eip = "pending"
|
|
}
|
|
return map[string]interface{}{
|
|
"EIP": eip,
|
|
}, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// the logic of this func totaly copy from the source-code of kubernetes tableConvertor
|
|
// https://github.com/kubernetes/kubernetes/blob/ea0764452222146c47ec826977f49d7001b0ea8c/pkg/printers/internalversion/printers.go#L740
|
|
// The result is same with the output of kubectl.
|
|
//nolint
|
|
func podAdditionalInfo(obj unstructured.Unstructured) (map[string]interface{}, error) {
|
|
pod := v12.Pod{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pod)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert unstructured Pod to typed: %w", err)
|
|
}
|
|
|
|
hasPodReadyCondition := func(conditions []v12.PodCondition) bool {
|
|
for _, condition := range conditions {
|
|
if condition.Type == v12.PodReady && condition.Status == v12.ConditionTrue {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
translateTimestampSince := func(timestamp v1.Time) string {
|
|
if timestamp.IsZero() {
|
|
return "<unknown>"
|
|
}
|
|
|
|
return duration.HumanDuration(time.Since(timestamp.Time))
|
|
}
|
|
|
|
restarts := 0
|
|
totalContainers := len(pod.Spec.Containers)
|
|
readyContainers := 0
|
|
|
|
reason := string(pod.Status.Phase)
|
|
if pod.Status.Reason != "" {
|
|
reason = pod.Status.Reason
|
|
}
|
|
|
|
initializing := false
|
|
for i := range pod.Status.InitContainerStatuses {
|
|
container := pod.Status.InitContainerStatuses[i]
|
|
restarts += int(container.RestartCount)
|
|
switch {
|
|
case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0:
|
|
continue
|
|
case container.State.Terminated != nil:
|
|
// initialization is failed
|
|
if len(container.State.Terminated.Reason) == 0 {
|
|
if container.State.Terminated.Signal != 0 {
|
|
reason = fmt.Sprintf("Init:Signal:%d", container.State.Terminated.Signal)
|
|
} else {
|
|
reason = fmt.Sprintf("Init:ExitCode:%d", container.State.Terminated.ExitCode)
|
|
}
|
|
} else {
|
|
reason = "Init:" + container.State.Terminated.Reason
|
|
}
|
|
initializing = true
|
|
case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
|
|
reason = "Init:" + container.State.Waiting.Reason
|
|
initializing = true
|
|
default:
|
|
reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers))
|
|
initializing = true
|
|
}
|
|
break
|
|
}
|
|
if !initializing {
|
|
restarts = 0
|
|
hasRunning := false
|
|
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
|
|
container := pod.Status.ContainerStatuses[i]
|
|
|
|
restarts += int(container.RestartCount)
|
|
if container.State.Waiting != nil && container.State.Waiting.Reason != "" {
|
|
reason = container.State.Waiting.Reason
|
|
} else if container.State.Terminated != nil && container.State.Terminated.Reason != "" {
|
|
reason = container.State.Terminated.Reason
|
|
} else if container.State.Terminated != nil && container.State.Terminated.Reason == "" {
|
|
if container.State.Terminated.Signal != 0 {
|
|
reason = fmt.Sprintf("Signal:%d", container.State.Terminated.Signal)
|
|
} else {
|
|
reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode)
|
|
}
|
|
} else if container.Ready && container.State.Running != nil {
|
|
hasRunning = true
|
|
readyContainers++
|
|
}
|
|
}
|
|
|
|
// change pod status back to "Running" if there is at least one container still reporting as "Running" status
|
|
if reason == "Completed" && hasRunning {
|
|
if hasPodReadyCondition(pod.Status.Conditions) {
|
|
reason = "Running"
|
|
} else {
|
|
reason = "NotReady"
|
|
}
|
|
}
|
|
}
|
|
|
|
if pod.DeletionTimestamp != nil && pod.Status.Reason == "NodeLost" {
|
|
reason = "Unknown"
|
|
} else if pod.DeletionTimestamp != nil {
|
|
reason = "Terminating"
|
|
}
|
|
return map[string]interface{}{
|
|
"Ready": fmt.Sprintf("%d/%d", readyContainers, totalContainers),
|
|
"Status": reason,
|
|
"Restarts": restarts,
|
|
"Age": translateTimestampSince(pod.CreationTimestamp),
|
|
}, nil
|
|
}
|
|
|
|
func fetchObjectWithResourceTreeNode(ctx context.Context, cluster string, k8sClient client.Client, resource types.ResourceTreeNode) (*unstructured.Unstructured, error) {
|
|
o := unstructured.Unstructured{}
|
|
o.SetAPIVersion(resource.APIVersion)
|
|
o.SetKind(resource.Kind)
|
|
o.SetNamespace(resource.Namespace)
|
|
o.SetName(resource.Name)
|
|
err := k8sClient.Get(multicluster.ContextWithClusterName(ctx, cluster), types2.NamespacedName{Namespace: resource.Namespace, Name: resource.Name}, &o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &o, nil
|
|
}
|
|
|
|
func listItemByRule(clusterCTX context.Context, k8sClient client.Client, resource ResourceType,
|
|
parentObject unstructured.Unstructured, specifiedFunc genListOptionFunc, defaultFunc genListOptionFunc, disableFilterByOwner bool) ([]unstructured.Unstructured, error) {
|
|
|
|
itemList := unstructured.UnstructuredList{}
|
|
itemList.SetAPIVersion(resource.APIVersion)
|
|
itemList.SetKind(fmt.Sprintf("%sList", resource.Kind))
|
|
var err error
|
|
if specifiedFunc == nil && defaultFunc == nil {
|
|
// if the relationShip between parent and child hasn't defined by any genListOption, list all subResource and filter by ownerReference UID
|
|
err = k8sClient.List(clusterCTX, &itemList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var res []unstructured.Unstructured
|
|
for _, item := range itemList.Items {
|
|
for _, reference := range item.GetOwnerReferences() {
|
|
if reference.UID == parentObject.GetUID() {
|
|
res = append(res, item)
|
|
}
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
var listOptions client.ListOptions
|
|
if specifiedFunc != nil {
|
|
// specified func will override the default func
|
|
listOptions, err = specifiedFunc(parentObject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
listOptions, err = defaultFunc(parentObject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
err = k8sClient.List(clusterCTX, &itemList, &listOptions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !disableFilterByOwner {
|
|
var res []unstructured.Unstructured
|
|
for _, item := range itemList.Items {
|
|
if len(item.GetOwnerReferences()) == 0 {
|
|
res = append(res, item)
|
|
}
|
|
for _, reference := range item.GetOwnerReferences() {
|
|
if reference.UID == parentObject.GetUID() {
|
|
res = append(res, item)
|
|
}
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
return itemList.Items, nil
|
|
}
|
|
|
|
func iteratorChildResources(ctx context.Context, cluster string, k8sClient client.Client, parentResource types.ResourceTreeNode, depth int) ([]*types.ResourceTreeNode, error) {
|
|
if depth > maxDepth {
|
|
log.Logger.Warnf("listing application resource tree has reached the max-depth %d parentObject is %v", depth, parentResource)
|
|
return nil, nil
|
|
}
|
|
parentObject, err := fetchObjectWithResourceTreeNode(ctx, cluster, k8sClient, parentResource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
group := parentObject.GetObjectKind().GroupVersionKind().Group
|
|
kind := parentObject.GetObjectKind().GroupVersionKind().Kind
|
|
|
|
if rules, ok := globalRule[GroupResourceType{Group: group, Kind: kind}]; ok {
|
|
var resList []*types.ResourceTreeNode
|
|
for resource, specifiedFunc := range rules.CareResource {
|
|
clusterCTX := multicluster.ContextWithClusterName(ctx, cluster)
|
|
items, err := listItemByRule(clusterCTX, k8sClient, resource, *parentObject, specifiedFunc, rules.DefaultGenListOptionFunc, rules.DisableFilterByOwnerReference)
|
|
if err != nil {
|
|
if meta.IsNoMatchError(err) || runtime.IsNotRegisteredError(err) {
|
|
log.Logger.Errorf("error to list subresources: %s err: %v", resource.Kind, err)
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
for _, item := range items {
|
|
rtn := types.ResourceTreeNode{
|
|
APIVersion: item.GetAPIVersion(),
|
|
Kind: item.GroupVersionKind().Kind,
|
|
Namespace: item.GetNamespace(),
|
|
Name: item.GetName(),
|
|
UID: item.GetUID(),
|
|
Cluster: cluster,
|
|
}
|
|
if _, ok := globalRule[GroupResourceType{Group: item.GetObjectKind().GroupVersionKind().Group, Kind: item.GetObjectKind().GroupVersionKind().Kind}]; ok {
|
|
childrenRes, err := iteratorChildResources(ctx, cluster, k8sClient, rtn, depth+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rtn.LeafNodes = childrenRes
|
|
}
|
|
healthStatus, err := checkResourceStatus(item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rtn.HealthStatus = *healthStatus
|
|
addInfo, err := additionalInfo(item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rtn.CreationTimestamp = item.GetCreationTimestamp().Time
|
|
if !item.GetDeletionTimestamp().IsZero() {
|
|
rtn.DeletionTimestamp = item.GetDeletionTimestamp().Time
|
|
}
|
|
rtn.AdditionalInfo = addInfo
|
|
resList = append(resList, &rtn)
|
|
}
|
|
}
|
|
return resList, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// mergeCustomRules merge the customize
|
|
func mergeCustomRules(ctx context.Context, k8sClient client.Client) error {
|
|
rulesList := v12.ConfigMapList{}
|
|
if err := k8sClient.List(ctx, &rulesList, client.InNamespace(velatypes.DefaultKubeVelaNS), client.HasLabels{oam.LabelResourceRules}); err != nil {
|
|
return err
|
|
}
|
|
for _, item := range rulesList.Items {
|
|
ruleStr := item.Data[relationshipKey]
|
|
var customRules []*customRule
|
|
err := yaml.Unmarshal([]byte(ruleStr), &customRules)
|
|
if err != nil {
|
|
// don't let one miss-config configmap brake whole process
|
|
log.Logger.Errorf("relationship rule configamp %s miss config %v", item.Name, err)
|
|
}
|
|
for _, rule := range customRules {
|
|
if cResource, ok := globalRule[*rule.ParentResourceType]; ok {
|
|
for _, resourceType := range rule.ChildrenResourceType {
|
|
if _, ok := cResource.CareResource[resourceType]; !ok {
|
|
cResource.CareResource[resourceType] = nil
|
|
}
|
|
}
|
|
} else {
|
|
caredResources := map[ResourceType]genListOptionFunc{}
|
|
for _, resourceType := range rule.ChildrenResourceType {
|
|
caredResources[resourceType] = nil
|
|
}
|
|
globalRule[*rule.ParentResourceType] = ChildrenResourcesRule{DefaultGenListOptionFunc: nil, CareResource: caredResources}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|