github.com/argoproj/argo-cd@v1.8.7/controller/metrics/metrics_test.go (about)

     1  package metrics
     2  
     3  import (
     4  	"context"
     5  	"log"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/argoproj/gitops-engine/pkg/sync/common"
    14  	"github.com/ghodss/yaml"
    15  	"github.com/stretchr/testify/assert"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/client-go/tools/cache"
    19  
    20  	argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    21  	appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
    22  	appinformer "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
    23  	applister "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
    24  )
    25  
    26  const fakeApp = `
    27  apiVersion: argoproj.io/v1alpha1
    28  kind: Application
    29  metadata:
    30    name: my-app
    31    namespace: argocd
    32  spec:
    33    destination:
    34      namespace: dummy-namespace
    35      server: https://localhost:6443
    36    project: important-project
    37    source:
    38      path: some/path
    39      repoURL: https://github.com/argoproj/argocd-example-apps.git
    40  status:
    41    sync:
    42      status: Synced
    43    health:
    44      status: Healthy
    45  `
    46  
    47  const fakeApp2 = `
    48  apiVersion: argoproj.io/v1alpha1
    49  kind: Application
    50  metadata:
    51    name: my-app-2
    52    namespace: argocd
    53  spec:
    54    destination:
    55      namespace: dummy-namespace
    56      server: https://localhost:6443
    57    project: important-project
    58    source:
    59      path: some/path
    60      repoURL: https://github.com/argoproj/argocd-example-apps.git
    61  status:
    62    sync:
    63      status: Synced
    64    health:
    65      status: Healthy
    66  operation:
    67    sync:
    68      revision: 041eab7439ece92c99b043f0e171788185b8fc1d
    69      syncStrategy:
    70        hook: {}
    71  `
    72  
    73  const fakeApp3 = `
    74  apiVersion: argoproj.io/v1alpha1
    75  kind: Application
    76  metadata:
    77    name: my-app-3
    78    namespace: argocd
    79    deletionTimestamp: "2020-03-16T09:17:45Z"
    80  spec:
    81    destination:
    82      namespace: dummy-namespace
    83      server: https://localhost:6443
    84    project: important-project
    85    source:
    86      path: some/path
    87      repoURL: https://github.com/argoproj/argocd-example-apps.git
    88  status:
    89    sync:
    90      status: OutOfSync
    91    health:
    92      status: Degraded
    93  `
    94  
    95  const fakeDefaultApp = `
    96  apiVersion: argoproj.io/v1alpha1
    97  kind: Application
    98  metadata:
    99    name: my-app
   100    namespace: argocd
   101  spec:
   102    destination:
   103      namespace: dummy-namespace
   104      server: https://localhost:6443
   105    source:
   106      path: some/path
   107      repoURL: https://github.com/argoproj/argocd-example-apps.git
   108  status:
   109    sync:
   110      status: Synced
   111    health:
   112      status: Healthy
   113  `
   114  
   115  var noOpHealthCheck = func(r *http.Request) error {
   116  	return nil
   117  }
   118  
   119  var appFilter = func(obj interface{}) bool {
   120  	return true
   121  }
   122  
   123  func newFakeApp(fakeAppYAML string) *argoappv1.Application {
   124  	var app argoappv1.Application
   125  	err := yaml.Unmarshal([]byte(fakeAppYAML), &app)
   126  	if err != nil {
   127  		panic(err)
   128  	}
   129  	return &app
   130  }
   131  
   132  func newFakeLister(fakeAppYAMLs ...string) (context.CancelFunc, applister.ApplicationLister) {
   133  	ctx, cancel := context.WithCancel(context.Background())
   134  	defer cancel()
   135  	var fakeApps []runtime.Object
   136  	for _, appYAML := range fakeAppYAMLs {
   137  		a := newFakeApp(appYAML)
   138  		fakeApps = append(fakeApps, a)
   139  	}
   140  	appClientset := appclientset.NewSimpleClientset(fakeApps...)
   141  	factory := appinformer.NewFilteredSharedInformerFactory(appClientset, 0, "argocd", func(options *metav1.ListOptions) {})
   142  	appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
   143  	go appInformer.Run(ctx.Done())
   144  	if !cache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) {
   145  		log.Fatal("Timed out waiting for caches to sync")
   146  	}
   147  	return cancel, factory.Argoproj().V1alpha1().Applications().Lister()
   148  }
   149  
   150  func testApp(t *testing.T, fakeAppYAMLs []string, expectedResponse string) {
   151  	cancel, appLister := newFakeLister(fakeAppYAMLs...)
   152  	defer cancel()
   153  	metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck)
   154  	assert.NoError(t, err)
   155  	req, err := http.NewRequest("GET", "/metrics", nil)
   156  	assert.NoError(t, err)
   157  	rr := httptest.NewRecorder()
   158  	metricsServ.Handler.ServeHTTP(rr, req)
   159  	assert.Equal(t, rr.Code, http.StatusOK)
   160  	body := rr.Body.String()
   161  	log.Println(body)
   162  	assertMetricsPrinted(t, expectedResponse, body)
   163  }
   164  
   165  type testCombination struct {
   166  	applications     []string
   167  	expectedResponse string
   168  }
   169  
   170  func TestMetrics(t *testing.T) {
   171  	combinations := []testCombination{
   172  		{
   173  			applications: []string{fakeApp, fakeApp2, fakeApp3},
   174  			expectedResponse: `
   175  # HELP argocd_app_info Information about application.
   176  # TYPE argocd_app_info gauge
   177  argocd_app_info{dest_namespace="dummy-namespace",dest_server="https://localhost:6443",health_status="Degraded",name="my-app-3",namespace="argocd",operation="delete",project="important-project",repo="https://github.com/argoproj/argocd-example-apps",sync_status="OutOfSync"} 1
   178  argocd_app_info{dest_namespace="dummy-namespace",dest_server="https://localhost:6443",health_status="Healthy",name="my-app",namespace="argocd",operation="",project="important-project",repo="https://github.com/argoproj/argocd-example-apps",sync_status="Synced"} 1
   179  argocd_app_info{dest_namespace="dummy-namespace",dest_server="https://localhost:6443",health_status="Healthy",name="my-app-2",namespace="argocd",operation="sync",project="important-project",repo="https://github.com/argoproj/argocd-example-apps",sync_status="Synced"} 1
   180  `,
   181  		},
   182  		{
   183  			applications: []string{fakeDefaultApp},
   184  			expectedResponse: `
   185  # HELP argocd_app_info Information about application.
   186  # TYPE argocd_app_info gauge
   187  argocd_app_info{dest_namespace="dummy-namespace",dest_server="https://localhost:6443",health_status="Healthy",name="my-app",namespace="argocd",operation="",project="default",repo="https://github.com/argoproj/argocd-example-apps",sync_status="Synced"} 1
   188  `,
   189  		},
   190  	}
   191  
   192  	for _, combination := range combinations {
   193  		testApp(t, combination.applications, combination.expectedResponse)
   194  	}
   195  }
   196  
   197  func TestLegacyMetrics(t *testing.T) {
   198  	os.Setenv(EnvVarLegacyControllerMetrics, "true")
   199  	defer os.Unsetenv(EnvVarLegacyControllerMetrics)
   200  
   201  	expectedResponse := `
   202  # HELP argocd_app_created_time Creation time in unix timestamp for an application.
   203  # TYPE argocd_app_created_time gauge
   204  argocd_app_created_time{name="my-app",namespace="argocd",project="important-project"} -6.21355968e+10
   205  # HELP argocd_app_health_status The application current health status.
   206  # TYPE argocd_app_health_status gauge
   207  argocd_app_health_status{health_status="Degraded",name="my-app",namespace="argocd",project="important-project"} 0
   208  argocd_app_health_status{health_status="Healthy",name="my-app",namespace="argocd",project="important-project"} 1
   209  argocd_app_health_status{health_status="Missing",name="my-app",namespace="argocd",project="important-project"} 0
   210  argocd_app_health_status{health_status="Progressing",name="my-app",namespace="argocd",project="important-project"} 0
   211  argocd_app_health_status{health_status="Suspended",name="my-app",namespace="argocd",project="important-project"} 0
   212  argocd_app_health_status{health_status="Unknown",name="my-app",namespace="argocd",project="important-project"} 0
   213  # HELP argocd_app_sync_status The application current sync status.
   214  # TYPE argocd_app_sync_status gauge
   215  argocd_app_sync_status{name="my-app",namespace="argocd",project="important-project",sync_status="OutOfSync"} 0
   216  argocd_app_sync_status{name="my-app",namespace="argocd",project="important-project",sync_status="Synced"} 1
   217  argocd_app_sync_status{name="my-app",namespace="argocd",project="important-project",sync_status="Unknown"} 0
   218  `
   219  	testApp(t, []string{fakeApp}, expectedResponse)
   220  }
   221  
   222  func TestMetricsSyncCounter(t *testing.T) {
   223  	cancel, appLister := newFakeLister()
   224  	defer cancel()
   225  	metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck)
   226  	assert.NoError(t, err)
   227  
   228  	appSyncTotal := `
   229  # HELP argocd_app_sync_total Number of application syncs.
   230  # TYPE argocd_app_sync_total counter
   231  argocd_app_sync_total{dest_server="https://localhost:6443",name="my-app",namespace="argocd",phase="Error",project="important-project"} 1
   232  argocd_app_sync_total{dest_server="https://localhost:6443",name="my-app",namespace="argocd",phase="Failed",project="important-project"} 1
   233  argocd_app_sync_total{dest_server="https://localhost:6443",name="my-app",namespace="argocd",phase="Succeeded",project="important-project"} 2
   234  `
   235  
   236  	fakeApp := newFakeApp(fakeApp)
   237  	metricsServ.IncSync(fakeApp, &argoappv1.OperationState{Phase: common.OperationRunning})
   238  	metricsServ.IncSync(fakeApp, &argoappv1.OperationState{Phase: common.OperationFailed})
   239  	metricsServ.IncSync(fakeApp, &argoappv1.OperationState{Phase: common.OperationError})
   240  	metricsServ.IncSync(fakeApp, &argoappv1.OperationState{Phase: common.OperationSucceeded})
   241  	metricsServ.IncSync(fakeApp, &argoappv1.OperationState{Phase: common.OperationSucceeded})
   242  
   243  	req, err := http.NewRequest("GET", "/metrics", nil)
   244  	assert.NoError(t, err)
   245  	rr := httptest.NewRecorder()
   246  	metricsServ.Handler.ServeHTTP(rr, req)
   247  	assert.Equal(t, rr.Code, http.StatusOK)
   248  	body := rr.Body.String()
   249  	log.Println(body)
   250  	assertMetricsPrinted(t, appSyncTotal, body)
   251  }
   252  
   253  // assertMetricsPrinted asserts every line in the expected lines appears in the body
   254  func assertMetricsPrinted(t *testing.T, expectedLines, body string) {
   255  	for _, line := range strings.Split(expectedLines, "\n") {
   256  		if line == "" {
   257  			continue
   258  		}
   259  		assert.Contains(t, body, line)
   260  	}
   261  }
   262  
   263  func TestReconcileMetrics(t *testing.T) {
   264  	cancel, appLister := newFakeLister()
   265  	defer cancel()
   266  	metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck)
   267  	assert.NoError(t, err)
   268  
   269  	appReconcileMetrics := `
   270  # HELP argocd_app_reconcile Application reconciliation performance.
   271  # TYPE argocd_app_reconcile histogram
   272  argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="0.25"} 0
   273  argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="0.5"} 0
   274  argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="1"} 0
   275  argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="2"} 0
   276  argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="4"} 0
   277  argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="8"} 1
   278  argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="16"} 1
   279  argocd_app_reconcile_bucket{dest_server="https://localhost:6443",namespace="argocd",le="+Inf"} 1
   280  argocd_app_reconcile_sum{dest_server="https://localhost:6443",namespace="argocd"} 5
   281  argocd_app_reconcile_count{dest_server="https://localhost:6443",namespace="argocd"} 1
   282  `
   283  	fakeApp := newFakeApp(fakeApp)
   284  	metricsServ.IncReconcile(fakeApp, 5*time.Second)
   285  
   286  	req, err := http.NewRequest("GET", "/metrics", nil)
   287  	assert.NoError(t, err)
   288  	rr := httptest.NewRecorder()
   289  	metricsServ.Handler.ServeHTTP(rr, req)
   290  	assert.Equal(t, rr.Code, http.StatusOK)
   291  	body := rr.Body.String()
   292  	log.Println(body)
   293  	assertMetricsPrinted(t, appReconcileMetrics, body)
   294  }