github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/metrics/metrics_test.go (about) 1 package metrics 2 3 import ( 4 "net/http" 5 "net/http/httptest" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/prometheus/client_golang/prometheus" 11 "github.com/prometheus/client_golang/prometheus/promhttp" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 "k8s.io/apimachinery/pkg/runtime" 15 ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" 16 "sigs.k8s.io/controller-runtime/pkg/client/fake" 17 "sigs.k8s.io/controller-runtime/pkg/metrics" 18 "sigs.k8s.io/yaml" 19 20 "github.com/argoproj/argo-cd/v3/applicationset/utils" 21 argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 22 metricsutil "github.com/argoproj/argo-cd/v3/util/metrics" 23 ) 24 25 var ( 26 applicationsetNamespaces = []string{"argocd", "test-namespace1"} 27 28 filter = func(appset *argoappv1.ApplicationSet) bool { 29 return utils.IsNamespaceAllowed(applicationsetNamespaces, appset.Namespace) 30 } 31 32 collectedLabels = []string{"included/test"} 33 ) 34 35 const fakeAppsetList = ` 36 apiVersion: argoproj.io/v1alpha1 37 kind: ApplicationSet 38 metadata: 39 name: test1 40 namespace: argocd 41 labels: 42 included/test: test 43 not-included.label/test: test 44 spec: 45 generators: 46 - git: 47 directories: 48 - path: test/* 49 repoURL: https://github.com/test/test.git 50 revision: HEAD 51 template: 52 metadata: 53 name: '{{.path.basename}}' 54 spec: 55 destination: 56 namespace: '{{.path.basename}}' 57 server: https://kubernetes.default.svc 58 project: default 59 source: 60 path: '{{.path.path}}' 61 repoURL: https://github.com/test/test.git 62 targetRevision: HEAD 63 status: 64 resources: 65 - group: argoproj.io 66 health: 67 status: Missing 68 kind: Application 69 name: test-app1 70 namespace: argocd 71 status: OutOfSync 72 version: v1alpha1 73 - group: argoproj.io 74 health: 75 status: Missing 76 kind: Application 77 name: test-app2 78 namespace: argocd 79 status: OutOfSync 80 version: v1alpha1 81 conditions: 82 - lastTransitionTime: "2024-01-01T00:00:00Z" 83 message: Successfully generated parameters for all Applications 84 reason: ApplicationSetUpToDate 85 status: "False" 86 type: ErrorOccurred 87 - lastTransitionTime: "2024-01-01T00:00:00Z" 88 message: Successfully generated parameters for all Applications 89 reason: ParametersGenerated 90 status: "True" 91 type: ParametersGenerated 92 - lastTransitionTime: "2024-01-01T00:00:00Z" 93 message: ApplicationSet up to date 94 reason: ApplicationSetUpToDate 95 status: "True" 96 type: ResourcesUpToDate 97 --- 98 apiVersion: argoproj.io/v1alpha1 99 kind: ApplicationSet 100 metadata: 101 name: test2 102 namespace: argocd 103 labels: 104 not-included.label/test: test 105 spec: 106 generators: 107 - git: 108 directories: 109 - path: test/* 110 repoURL: https://github.com/test/test.git 111 revision: HEAD 112 template: 113 metadata: 114 name: '{{.path.basename}}' 115 spec: 116 destination: 117 namespace: '{{.path.basename}}' 118 server: https://kubernetes.default.svc 119 project: default 120 source: 121 path: '{{.path.path}}' 122 repoURL: https://github.com/test/test.git 123 targetRevision: HEAD 124 --- 125 apiVersion: argoproj.io/v1alpha1 126 kind: ApplicationSet 127 metadata: 128 name: should-be-filtered-out 129 namespace: not-allowed 130 spec: 131 generators: 132 - git: 133 directories: 134 - path: test/* 135 repoURL: https://github.com/test/test.git 136 revision: HEAD 137 template: 138 metadata: 139 name: '{{.path.basename}}' 140 spec: 141 destination: 142 namespace: '{{.path.basename}}' 143 server: https://kubernetes.default.svc 144 project: default 145 source: 146 path: '{{.path.path}}' 147 repoURL: https://github.com/test/test.git 148 targetRevision: HEAD 149 ` 150 151 func newFakeAppsets(fakeAppsetYAML string) []argoappv1.ApplicationSet { 152 var results []argoappv1.ApplicationSet 153 154 appsetRawYamls := strings.Split(fakeAppsetYAML, "---") 155 156 for _, appsetRawYaml := range appsetRawYamls { 157 var appset argoappv1.ApplicationSet 158 err := yaml.Unmarshal([]byte(appsetRawYaml), &appset) 159 if err != nil { 160 panic(err) 161 } 162 163 results = append(results, appset) 164 } 165 166 return results 167 } 168 169 func TestApplicationsetCollector(t *testing.T) { 170 appsetList := newFakeAppsets(fakeAppsetList) 171 client := initializeClient(appsetList) 172 metrics.Registry = prometheus.NewRegistry() 173 174 appsetCollector := newAppsetCollector(utils.NewAppsetLister(client), collectedLabels, filter) 175 176 metrics.Registry.MustRegister(appsetCollector) 177 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 178 require.NoError(t, err) 179 rr := httptest.NewRecorder() 180 handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{}) 181 handler.ServeHTTP(rr, req) 182 183 assert.Equal(t, http.StatusOK, rr.Code) 184 // Test correct appset_info and owned applications 185 assert.Contains(t, rr.Body.String(), ` 186 argocd_appset_info{name="test1",namespace="argocd",resource_update_status="ApplicationSetUpToDate"} 1 187 `) 188 assert.Contains(t, rr.Body.String(), ` 189 argocd_appset_owned_applications{name="test1",namespace="argocd"} 2 190 `) 191 // Test labels collection - should not include labels not included in the list of collected labels and include the ones that do. 192 assert.Contains(t, rr.Body.String(), ` 193 argocd_appset_labels{label_included_test="test",name="test1",namespace="argocd"} 1 194 `) 195 assert.NotContains(t, rr.Body.String(), normalizeLabel("not-included.label/test")) 196 // If collected label is not present on the applicationset the value should be empty 197 assert.Contains(t, rr.Body.String(), ` 198 argocd_appset_labels{label_included_test="",name="test2",namespace="argocd"} 1 199 `) 200 // If ResourcesUpToDate condition is not present on the applicationset the status should be reported as 'Unknown' 201 assert.Contains(t, rr.Body.String(), ` 202 argocd_appset_info{name="test2",namespace="argocd",resource_update_status="Unknown"} 1 203 `) 204 // If there are no resources on the applicationset the owned application gague should return 0 205 assert.Contains(t, rr.Body.String(), ` 206 argocd_appset_owned_applications{name="test2",namespace="argocd"} 0 207 `) 208 // Test that filter is working 209 assert.NotContains(t, rr.Body.String(), `name="should-be-filtered-out"`) 210 } 211 212 func TestObserveReconcile(t *testing.T) { 213 appsetList := newFakeAppsets(fakeAppsetList) 214 client := initializeClient(appsetList) 215 metrics.Registry = prometheus.NewRegistry() 216 217 appsetMetrics := NewApplicationsetMetrics(utils.NewAppsetLister(client), collectedLabels, filter) 218 219 req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) 220 require.NoError(t, err) 221 rr := httptest.NewRecorder() 222 handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{}) 223 appsetMetrics.ObserveReconcile(&appsetList[0], 5*time.Second) 224 handler.ServeHTTP(rr, req) 225 assert.Contains(t, rr.Body.String(), ` 226 argocd_appset_reconcile_sum{name="test1",namespace="argocd"} 5 227 `) 228 // If there are no resources on the applicationset the owned application gague should return 0 229 assert.Contains(t, rr.Body.String(), ` 230 argocd_appset_reconcile_count{name="test1",namespace="argocd"} 1 231 `) 232 } 233 234 func initializeClient(appsets []argoappv1.ApplicationSet) ctrlclient.WithWatch { 235 scheme := runtime.NewScheme() 236 err := argoappv1.AddToScheme(scheme) 237 if err != nil { 238 panic(err) 239 } 240 241 var clientObjects []ctrlclient.Object 242 243 for _, appset := range appsets { 244 clientObjects = append(clientObjects, appset.DeepCopy()) 245 } 246 247 return fake.NewClientBuilder().WithScheme(scheme).WithObjects(clientObjects...).Build() 248 } 249 250 func normalizeLabel(label string) string { 251 return metricsutil.NormalizeLabels("label", []string{label})[0] 252 }