-
Notifications
You must be signed in to change notification settings - Fork 260
/
health_hpa.go
158 lines (138 loc) · 4.81 KB
/
health_hpa.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package health
import (
"encoding/json"
"fmt"
autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
)
var (
progressingStatus = &HealthStatus{
Status: HealthStatusProgressing,
Message: "Waiting to Autoscale",
}
)
type hpaCondition struct {
Type string
Reason string
Message string
}
func getHPAHealth(obj *unstructured.Unstructured) (*HealthStatus, error) {
gvk := obj.GroupVersionKind()
failedConversionMsg := "failed to convert unstructured HPA to typed: %v"
switch gvk {
case autoscalingv1.SchemeGroupVersion.WithKind(kube.HorizontalPodAutoscalerKind):
var hpa autoscalingv1.HorizontalPodAutoscaler
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &hpa)
if err != nil {
return nil, fmt.Errorf(failedConversionMsg, err)
}
return getAutoScalingV1HPAHealth(&hpa)
case autoscalingv2beta1.SchemeGroupVersion.WithKind(kube.HorizontalPodAutoscalerKind):
var hpa autoscalingv2beta1.HorizontalPodAutoscaler
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &hpa)
if err != nil {
return nil, fmt.Errorf(failedConversionMsg, err)
}
return getAutoScalingV2beta1HPAHealth(&hpa)
case autoscalingv2beta2.SchemeGroupVersion.WithKind(kube.HorizontalPodAutoscalerKind):
var hpa autoscalingv2beta2.HorizontalPodAutoscaler
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &hpa)
if err != nil {
return nil, fmt.Errorf(failedConversionMsg, err)
}
return getAutoScalingV2beta2HPAHealth(&hpa)
default:
return nil, fmt.Errorf("unsupported HPA GVK: %s", gvk)
}
}
func getAutoScalingV2beta2HPAHealth(hpa *autoscalingv2beta2.HorizontalPodAutoscaler) (*HealthStatus, error) {
statusConditions := hpa.Status.Conditions
conditions := make([]hpaCondition, 0, len(statusConditions))
for _, statusCondition := range statusConditions {
conditions = append(conditions, hpaCondition{
Type: string(statusCondition.Type),
Reason: statusCondition.Reason,
Message: statusCondition.Message,
})
}
return checkConditions(conditions, progressingStatus)
}
func getAutoScalingV2beta1HPAHealth(hpa *autoscalingv2beta1.HorizontalPodAutoscaler) (*HealthStatus, error) {
statusConditions := hpa.Status.Conditions
conditions := make([]hpaCondition, 0, len(statusConditions))
for _, statusCondition := range statusConditions {
conditions = append(conditions, hpaCondition{
Type: string(statusCondition.Type),
Reason: statusCondition.Reason,
Message: statusCondition.Message,
})
}
return checkConditions(conditions, progressingStatus)
}
func getAutoScalingV1HPAHealth(hpa *autoscalingv1.HorizontalPodAutoscaler) (*HealthStatus, error) {
annotation, ok := hpa.GetAnnotations()["autoscaling.alpha.kubernetes.io/conditions"]
if !ok {
return progressingStatus, nil
}
var conditions []hpaCondition
err := json.Unmarshal([]byte(annotation), &conditions)
if err != nil {
failedMessage := "failed to convert conditions annotation to typed: %v"
return nil, fmt.Errorf(failedMessage, err)
}
if len(conditions) == 0 {
return progressingStatus, nil
}
return checkConditions(conditions, progressingStatus)
}
func checkConditions(conditions []hpaCondition, progressingStatus *HealthStatus) (*HealthStatus, error) {
for _, condition := range conditions {
if isDegraded(&condition) {
return &HealthStatus{
Status: HealthStatusDegraded,
Message: condition.Message,
}, nil
}
if isHealthy(&condition) {
return &HealthStatus{
Status: HealthStatusHealthy,
Message: condition.Message,
}, nil
}
}
return progressingStatus, nil
}
func isDegraded(condition *hpaCondition) bool {
degraded_states := []hpaCondition{
{Type: "AbleToScale", Reason: "FailedGetScale"},
{Type: "AbleToScale", Reason: "FailedUpdateScale"},
{Type: "ScalingActive", Reason: "FailedGetResourceMetric"},
{Type: "ScalingActive", Reason: "InvalidSelector"},
}
for _, degraded_state := range degraded_states {
if condition.Type == degraded_state.Type && condition.Reason == degraded_state.Reason {
return true
}
}
return false
}
func isHealthy(condition *hpaCondition) bool {
healthy_states := []hpaCondition{
{Type: "AbleToScale", Reason: "SucceededRescale"},
{Type: "ScalingLimited", Reason: "DesiredWithinRange"},
{Type: "ScalingLimited", Reason: "TooFewReplicas"},
{Type: "ScalingLimited", Reason: "TooManyReplicas"},
{Type: "ScalingLimited", Reason: "ScaleDownLimit"},
{Type: "ScalingActive", Reason: "ScalingDisabled"},
}
for _, healthy_state := range healthy_states {
if condition.Type == healthy_state.Type && condition.Reason == healthy_state.Reason {
return true
}
}
return false
}