* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
302 lines
8.3 KiB
Go
302 lines
8.3 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package k8sapplication
|
|
|
|
import (
|
|
"strings"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
batchv1 "k8s.io/api/batch/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
policyv1beta1 "k8s.io/api/policy/v1beta1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
)
|
|
|
|
// Constants defining labels
|
|
const (
|
|
StatusReady = "Ready"
|
|
StatusInProgress = "InProgress"
|
|
StatusUnknown = "Unknown"
|
|
StatusDisabled = "Disabled"
|
|
)
|
|
|
|
func status(u *unstructured.Unstructured) (string, error) {
|
|
gk := u.GroupVersionKind().GroupKind()
|
|
switch gk.String() {
|
|
case "StatefulSet.apps":
|
|
return stsStatus(u)
|
|
case "Deployment.apps":
|
|
return deploymentStatus(u)
|
|
case "ReplicaSet.apps":
|
|
return replicasetStatus(u)
|
|
case "DaemonSet.apps":
|
|
return daemonsetStatus(u)
|
|
case "PersistentVolumeClaim":
|
|
return pvcStatus(u)
|
|
case "Service":
|
|
return serviceStatus(u)
|
|
case "Pod":
|
|
return podStatus(u)
|
|
case "PodDisruptionBudget.policy":
|
|
return pdbStatus(u)
|
|
case "ReplicationController":
|
|
return replicationControllerStatus(u)
|
|
case "Job.batch":
|
|
return jobStatus(u)
|
|
default:
|
|
return statusFromStandardConditions(u)
|
|
}
|
|
}
|
|
|
|
// Status from standard conditions
|
|
func statusFromStandardConditions(u *unstructured.Unstructured) (string, error) {
|
|
condition := StatusReady
|
|
|
|
// Check Ready condition
|
|
_, cs, found, err := getConditionOfType(u, StatusReady)
|
|
if err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
if found && cs == corev1.ConditionFalse {
|
|
condition = StatusInProgress
|
|
}
|
|
|
|
// Check InProgress condition
|
|
_, cs, found, err = getConditionOfType(u, StatusInProgress)
|
|
if err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
if found && cs == corev1.ConditionTrue {
|
|
condition = StatusInProgress
|
|
}
|
|
|
|
return condition, nil
|
|
}
|
|
|
|
// Statefulset
|
|
func stsStatus(u *unstructured.Unstructured) (string, error) {
|
|
sts := &appsv1.StatefulSet{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, sts); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
if sts.Status.ObservedGeneration == sts.Generation &&
|
|
sts.Status.Replicas == *sts.Spec.Replicas &&
|
|
sts.Status.ReadyReplicas == *sts.Spec.Replicas &&
|
|
sts.Status.CurrentReplicas == *sts.Spec.Replicas {
|
|
return StatusReady, nil
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
// Deployment
|
|
func deploymentStatus(u *unstructured.Unstructured) (string, error) {
|
|
deployment := &appsv1.Deployment{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, deployment); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
replicaFailure := false
|
|
progressing := false
|
|
available := false
|
|
|
|
for _, condition := range deployment.Status.Conditions {
|
|
switch condition.Type {
|
|
case appsv1.DeploymentProgressing:
|
|
if condition.Status == corev1.ConditionTrue && condition.Reason == "NewReplicaSetAvailable" {
|
|
progressing = true
|
|
}
|
|
case appsv1.DeploymentAvailable:
|
|
if condition.Status == corev1.ConditionTrue {
|
|
available = true
|
|
}
|
|
case appsv1.DeploymentReplicaFailure:
|
|
if condition.Status == corev1.ConditionTrue {
|
|
replicaFailure = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if deployment.Status.ObservedGeneration == deployment.Generation &&
|
|
deployment.Status.Replicas == *deployment.Spec.Replicas &&
|
|
deployment.Status.ReadyReplicas == *deployment.Spec.Replicas &&
|
|
deployment.Status.AvailableReplicas == *deployment.Spec.Replicas &&
|
|
deployment.Status.Conditions != nil && len(deployment.Status.Conditions) > 0 &&
|
|
(progressing || available) && !replicaFailure {
|
|
return StatusReady, nil
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
// Replicaset
|
|
func replicasetStatus(u *unstructured.Unstructured) (string, error) {
|
|
rs := &appsv1.ReplicaSet{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, rs); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
replicaFailure := false
|
|
for _, condition := range rs.Status.Conditions {
|
|
switch condition.Type {
|
|
case appsv1.ReplicaSetReplicaFailure:
|
|
if condition.Status == corev1.ConditionTrue {
|
|
replicaFailure = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if rs.Status.ObservedGeneration == rs.Generation &&
|
|
rs.Status.Replicas == *rs.Spec.Replicas &&
|
|
rs.Status.ReadyReplicas == *rs.Spec.Replicas &&
|
|
rs.Status.AvailableReplicas == *rs.Spec.Replicas && !replicaFailure {
|
|
return StatusReady, nil
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
// Daemonset
|
|
func daemonsetStatus(u *unstructured.Unstructured) (string, error) {
|
|
ds := &appsv1.DaemonSet{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, ds); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
if ds.Status.ObservedGeneration == ds.Generation &&
|
|
ds.Status.DesiredNumberScheduled == ds.Status.NumberAvailable &&
|
|
ds.Status.DesiredNumberScheduled == ds.Status.NumberReady {
|
|
return StatusReady, nil
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
// PVC
|
|
func pvcStatus(u *unstructured.Unstructured) (string, error) {
|
|
pvc := &corev1.PersistentVolumeClaim{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, pvc); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
if pvc.Status.Phase == corev1.ClaimBound {
|
|
return StatusReady, nil
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
// Service
|
|
func serviceStatus(u *unstructured.Unstructured) (string, error) {
|
|
service := &corev1.Service{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, service); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
stype := service.Spec.Type
|
|
|
|
if stype == corev1.ServiceTypeClusterIP || stype == corev1.ServiceTypeNodePort || stype == corev1.ServiceTypeExternalName ||
|
|
stype == corev1.ServiceTypeLoadBalancer && isEmpty(service.Spec.ClusterIP) &&
|
|
len(service.Status.LoadBalancer.Ingress) > 0 && !hasEmptyIngressIP(service.Status.LoadBalancer.Ingress) {
|
|
return StatusReady, nil
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
// Pod
|
|
func podStatus(u *unstructured.Unstructured) (string, error) {
|
|
pod := &corev1.Pod{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, pod); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
for _, condition := range pod.Status.Conditions {
|
|
if condition.Type == corev1.PodReady && (condition.Reason == "PodCompleted" || condition.Status == corev1.ConditionTrue) {
|
|
return StatusReady, nil
|
|
}
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
// PodDisruptionBudget
|
|
func pdbStatus(u *unstructured.Unstructured) (string, error) {
|
|
pdb := &policyv1beta1.PodDisruptionBudget{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, pdb); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
if pdb.Status.ObservedGeneration == pdb.Generation &&
|
|
pdb.Status.CurrentHealthy >= pdb.Status.DesiredHealthy {
|
|
return StatusReady, nil
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
func replicationControllerStatus(u *unstructured.Unstructured) (string, error) {
|
|
rc := &corev1.ReplicationController{}
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, rc); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
if rc.Status.ObservedGeneration == rc.Generation &&
|
|
rc.Status.Replicas == *rc.Spec.Replicas &&
|
|
rc.Status.ReadyReplicas == *rc.Spec.Replicas &&
|
|
rc.Status.AvailableReplicas == *rc.Spec.Replicas {
|
|
return StatusReady, nil
|
|
}
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
func jobStatus(u *unstructured.Unstructured) (string, error) {
|
|
job := &batchv1.Job{}
|
|
|
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, job); err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
if job.Status.StartTime == nil {
|
|
return StatusInProgress, nil
|
|
}
|
|
|
|
return StatusReady, nil
|
|
}
|
|
|
|
func hasEmptyIngressIP(ingress []corev1.LoadBalancerIngress) bool {
|
|
for _, i := range ingress {
|
|
if isEmpty(i.IP) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isEmpty(s string) bool {
|
|
return len(strings.TrimSpace(s)) == 0
|
|
}
|
|
|
|
func getConditionOfType(u *unstructured.Unstructured, conditionType string) (string, corev1.ConditionStatus, bool, error) {
|
|
conditions, found, err := unstructured.NestedSlice(u.Object, "status", "conditions")
|
|
if err != nil || !found {
|
|
return "", corev1.ConditionFalse, false, err
|
|
}
|
|
|
|
for _, c := range conditions {
|
|
condition, ok := c.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
t, found := condition["type"]
|
|
if !found {
|
|
continue
|
|
}
|
|
condType, ok := t.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if condType == conditionType {
|
|
reason := condition["reason"].(string)
|
|
conditionStatus := condition["status"].(string)
|
|
return reason, corev1.ConditionStatus(conditionStatus), true, nil
|
|
}
|
|
}
|
|
return "", corev1.ConditionFalse, false, nil
|
|
}
|