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 }