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  }