github.com/argoproj/argo-cd/v3@v3.2.1/controller/health_test.go (about) 1 package controller 2 3 import ( 4 "fmt" 5 "os" 6 "testing" 7 "time" 8 9 "github.com/argoproj/gitops-engine/pkg/health" 10 synccommon "github.com/argoproj/gitops-engine/pkg/sync/common" 11 "github.com/argoproj/gitops-engine/pkg/utils/kube" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 16 "k8s.io/apimachinery/pkg/runtime/schema" 17 "sigs.k8s.io/yaml" 18 19 "github.com/argoproj/argo-cd/v3/pkg/apis/application" 20 appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 21 "github.com/argoproj/argo-cd/v3/util/lua" 22 ) 23 24 var ( 25 app = &appv1.Application{ 26 Status: appv1.ApplicationStatus{ 27 Health: appv1.AppHealthStatus{ 28 LastTransitionTime: &metav1.Time{Time: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)}, 29 }, 30 }, 31 } 32 testTimestamp = metav1.Time{Time: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)} 33 ) 34 35 func initStatuses(resources []managedResource) []appv1.ResourceStatus { 36 statuses := make([]appv1.ResourceStatus, len(resources)) 37 for i := range resources { 38 statuses[i] = appv1.ResourceStatus{Group: resources[i].Group, Kind: resources[i].Kind, Version: resources[i].Version} 39 } 40 return statuses 41 } 42 43 func resourceFromFile(filePath string) unstructured.Unstructured { 44 yamlBytes, err := os.ReadFile(filePath) 45 if err != nil { 46 panic(err) 47 } 48 var res unstructured.Unstructured 49 err = yaml.Unmarshal(yamlBytes, &res) 50 if err != nil { 51 panic(err) 52 } 53 return res 54 } 55 56 func TestSetApplicationHealth(t *testing.T) { 57 failedJob := resourceFromFile("./testdata/job-failed.yaml") 58 runningPod := resourceFromFile("./testdata/pod-running-restart-always.yaml") 59 60 resources := []managedResource{{ 61 Group: "", Version: "v1", Kind: "Pod", Live: &runningPod, 62 }, { 63 Group: "batch", Version: "v1", Kind: "Job", Live: &failedJob, 64 }} 65 resourceStatuses := initStatuses(resources) 66 67 healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{}, app, true) 68 require.NoError(t, err) 69 assert.Equal(t, health.HealthStatusDegraded, healthStatus) 70 assert.Equal(t, health.HealthStatusHealthy, resourceStatuses[0].Health.Status) 71 assert.Equal(t, health.HealthStatusDegraded, resourceStatuses[1].Health.Status) 72 app.Status.Health.Status = healthStatus 73 74 // now mark the job as a hook and retry. it should ignore the hook and consider the app healthy 75 failedJob.SetAnnotations(map[string]string{synccommon.AnnotationKeyHook: "PreSync"}) 76 healthStatus, err = setApplicationHealth(resources, resourceStatuses, nil, app, true) 77 require.NoError(t, err) 78 assert.Equal(t, health.HealthStatusHealthy, healthStatus) 79 app.Status.Health.Status = healthStatus 80 81 // now we set the `argocd.argoproj.io/ignore-healthcheck: "true"` annotation on the job's target. 82 // The app is considered healthy 83 failedJob.SetAnnotations(nil) 84 failedJobIgnoreHealthcheck := resourceFromFile("./testdata/job-failed-ignore-healthcheck.yaml") 85 resources[1].Live = &failedJobIgnoreHealthcheck 86 healthStatus, err = setApplicationHealth(resources, resourceStatuses, nil, app, true) 87 require.NoError(t, err) 88 assert.Equal(t, health.HealthStatusHealthy, healthStatus) 89 } 90 91 func TestSetApplicationHealth_ResourceHealthNotPersisted(t *testing.T) { 92 failedJob := resourceFromFile("./testdata/job-failed.yaml") 93 94 resources := []managedResource{{ 95 Group: "batch", Version: "v1", Kind: "Job", Live: &failedJob, 96 }} 97 resourceStatuses := initStatuses(resources) 98 99 healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{}, app, false) 100 require.NoError(t, err) 101 assert.Equal(t, health.HealthStatusDegraded, healthStatus) 102 103 assert.Nil(t, resourceStatuses[0].Health) 104 } 105 106 func TestSetApplicationHealth_MissingResource(t *testing.T) { 107 pod := resourceFromFile("./testdata/pod-running-restart-always.yaml") 108 109 resources := []managedResource{{ 110 Group: "", Version: "v1", Kind: "Pod", Target: &pod, 111 }, {}} 112 resourceStatuses := initStatuses(resources) 113 114 healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{}, app, true) 115 require.NoError(t, err) 116 assert.Equal(t, health.HealthStatusMissing, healthStatus) 117 } 118 119 func TestSetApplicationHealth_HealthImproves(t *testing.T) { 120 testCases := []struct { 121 oldStatus health.HealthStatusCode 122 newStatus health.HealthStatusCode 123 }{ 124 {health.HealthStatusUnknown, health.HealthStatusDegraded}, 125 {health.HealthStatusDegraded, health.HealthStatusProgressing}, 126 {health.HealthStatusMissing, health.HealthStatusProgressing}, 127 {health.HealthStatusProgressing, health.HealthStatusSuspended}, 128 {health.HealthStatusSuspended, health.HealthStatusHealthy}, 129 } 130 131 for _, tc := range testCases { 132 overrides := lua.ResourceHealthOverrides{ 133 lua.GetConfigMapKey(schema.FromAPIVersionAndKind("v1", "Pod")): appv1.ResourceOverride{ 134 HealthLua: fmt.Sprintf("hs = {}\nhs.status = %q\nhs.message = \"\"return hs", tc.newStatus), 135 }, 136 } 137 138 runningPod := resourceFromFile("./testdata/pod-running-restart-always.yaml") 139 resources := []managedResource{{ 140 Group: "", Version: "v1", Kind: "Pod", Live: &runningPod, 141 }} 142 resourceStatuses := initStatuses(resources) 143 144 t.Run(string(fmt.Sprintf("%s to %s", tc.oldStatus, tc.newStatus)), func(t *testing.T) { 145 healthStatus, err := setApplicationHealth(resources, resourceStatuses, overrides, app, true) 146 require.NoError(t, err) 147 assert.Equal(t, tc.newStatus, healthStatus) 148 }) 149 } 150 } 151 152 func TestSetApplicationHealth_MissingResourceNoBuiltHealthCheck(t *testing.T) { 153 cm := resourceFromFile("./testdata/configmap.yaml") 154 155 resources := []managedResource{{ 156 Group: "", Version: "v1", Kind: "ConfigMap", Target: &cm, 157 }} 158 resourceStatuses := initStatuses(resources) 159 160 t.Run("NoOverride", func(t *testing.T) { 161 healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{}, app, true) 162 require.NoError(t, err) 163 assert.Equal(t, health.HealthStatusHealthy, healthStatus) 164 assert.Equal(t, health.HealthStatusMissing, resourceStatuses[0].Health.Status) 165 }) 166 167 t.Run("HasOverride", func(t *testing.T) { 168 healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{ 169 lua.GetConfigMapKey(schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}): appv1.ResourceOverride{ 170 HealthLua: "some health check", 171 }, 172 }, app, true) 173 require.NoError(t, err) 174 assert.Equal(t, health.HealthStatusMissing, healthStatus) 175 }) 176 } 177 178 func newAppLiveObj(status health.HealthStatusCode) *unstructured.Unstructured { 179 app := appv1.Application{ 180 ObjectMeta: metav1.ObjectMeta{ 181 Name: "foo", 182 }, 183 TypeMeta: metav1.TypeMeta{ 184 APIVersion: "argoproj.io/v1alpha1", 185 Kind: application.ApplicationKind, 186 }, 187 Status: appv1.ApplicationStatus{ 188 Health: appv1.AppHealthStatus{ 189 Status: status, 190 }, 191 }, 192 } 193 194 return kube.MustToUnstructured(&app) 195 } 196 197 func TestChildAppHealth(t *testing.T) { 198 overrides := lua.ResourceHealthOverrides{ 199 lua.GetConfigMapKey(appv1.ApplicationSchemaGroupVersionKind): appv1.ResourceOverride{ 200 HealthLua: ` 201 hs = {} 202 hs.status = "Progressing" 203 hs.message = "" 204 if obj.status ~= nil then 205 if obj.status.health ~= nil then 206 hs.status = obj.status.health.status 207 if obj.status.health.message ~= nil then 208 hs.message = obj.status.health.message 209 end 210 end 211 end 212 return hs`, 213 }, 214 } 215 216 t.Run("ChildAppDegraded", func(t *testing.T) { 217 degradedApp := newAppLiveObj(health.HealthStatusDegraded) 218 resources := []managedResource{{ 219 Group: application.Group, Version: "v1alpha1", Kind: application.ApplicationKind, Live: degradedApp, 220 }, {}} 221 resourceStatuses := initStatuses(resources) 222 223 healthStatus, err := setApplicationHealth(resources, resourceStatuses, overrides, app, true) 224 require.NoError(t, err) 225 assert.Equal(t, health.HealthStatusDegraded, healthStatus) 226 }) 227 228 t.Run("ChildAppMissing", func(t *testing.T) { 229 degradedApp := newAppLiveObj(health.HealthStatusMissing) 230 resources := []managedResource{{ 231 Group: application.Group, Version: "v1alpha1", Kind: application.ApplicationKind, Live: degradedApp, 232 }, {}} 233 resourceStatuses := initStatuses(resources) 234 235 healthStatus, err := setApplicationHealth(resources, resourceStatuses, overrides, app, true) 236 require.NoError(t, err) 237 assert.Equal(t, health.HealthStatusHealthy, healthStatus) 238 }) 239 }