1+ // Package e2e contains end-to-end tests to verify that the metrics endpoints
2+ // for both components. Metrics are exported and accessible by authorized users through
3+ // RBAC and ServiceAccount tokens.
4+ //
5+ // These tests perform the following steps:
6+ // 1. Create a ClusterRoleBinding to grant necessary permissions for accessing metrics.
7+ // 2. Generate a ServiceAccount token for authentication.
8+ // 3. Deploy a curl pod to interact with the metrics endpoint.
9+ // 4. Wait for the curl pod to become ready.
10+ // 5. Execute a curl command from the pod to validate the metrics endpoint.
11+ // 6. Clean up all resources created during the test, such as the ClusterRoleBinding and curl pod.
12+ //
13+ //nolint:gosec
114package e2e
215
316import (
417 "bytes"
518 "io"
619 "os/exec"
20+ "strings"
721 "testing"
822
923 "github.com/stretchr/testify/require"
24+
25+ "github.com/operator-framework/operator-controller/test/utils"
1026)
1127
12- // nolint:gosec
1328// TestOperatorControllerMetricsExportedEndpoint verifies that the metrics endpoint for the operator controller
14- // is exported correctly and accessible by authorized users through RBAC and a ServiceAccount token.
15- // The test performs the following steps:
16- // 1. Creates a ClusterRoleBinding to grant necessary permissions for accessing metrics.
17- // 2. Generates a ServiceAccount token for authentication.
18- // 3. Deploys a curl pod to interact with the metrics endpoint.
19- // 4. Waits for the curl pod to become ready.
20- // 5. Executes a curl command from the pod to validate the metrics endpoint.
21- // 6. Cleans up all resources created during the test, such as the ClusterRoleBinding and curl pod.
2229func TestOperatorControllerMetricsExportedEndpoint (t * testing.T ) {
23- var (
24- token string
25- curlPod = "curl-metrics"
26- client = ""
27- clients = []string {"kubectl" , "oc" }
30+ client := utils .FindK8sClient (t )
31+ config := NewMetricsTestConfig (
32+ t , client ,
33+ "control-plane=operator-controller-controller-manager" ,
34+ "operator-controller-metrics-reader" ,
35+ "operator-controller-metrics-binding" ,
36+ "operator-controller-controller-manager" ,
37+ "oper-curl-metrics" ,
38+ "https://operator-controller-service.NAMESPACE.svc.cluster.local:8443/metrics" ,
2839 )
2940
30- t .Log ("Looking for k8s client" )
31- for _ , c := range clients {
32- // Would prefer to use `command -v`, but even that may not be installed!
33- err := exec .Command (c , "version" , "--client" ).Run ()
34- if err == nil {
35- client = c
36- break
37- }
38- }
39- if client == "" {
40- t .Fatal ("k8s client not found" )
41+ config .createMetricsClusterRoleBinding ()
42+ token := config .getServiceAccountToken ()
43+ config .createCurlMetricsPod ()
44+ config .validate (token )
45+ defer config .cleanup ()
46+ }
47+
48+ // TestCatalogdMetricsExportedEndpoint verifies that the metrics endpoint for catalogd
49+ func TestCatalogdMetricsExportedEndpoint (t * testing.T ) {
50+ client := utils .FindK8sClient (t )
51+ config := NewMetricsTestConfig (
52+ t , client ,
53+ "control-plane=catalogd-controller-manager" ,
54+ "catalogd-metrics-reader" ,
55+ "catalogd-metrics-binding" ,
56+ "catalogd-controller-manager" ,
57+ "catalogd-curl-metrics" ,
58+ "https://catalogd-service.NAMESPACE.svc.cluster.local:7443/metrics" ,
59+ )
60+
61+ config .createMetricsClusterRoleBinding ()
62+ token := config .getServiceAccountToken ()
63+ config .createCurlMetricsPod ()
64+ config .validate (token )
65+ defer config .cleanup ()
66+ }
67+
68+ // MetricsTestConfig holds the necessary configurations for testing metrics endpoints.
69+ type MetricsTestConfig struct {
70+ t * testing.T
71+ client string
72+ namespace string
73+ clusterRole string
74+ clusterBinding string
75+ serviceAccount string
76+ curlPodName string
77+ metricsURL string
78+ }
79+
80+ // NewMetricsTestConfig initializes a new MetricsTestConfig.
81+ func NewMetricsTestConfig (t * testing.T , client , selector , clusterRole , clusterBinding , serviceAccount , curlPodName , metricsURL string ) * MetricsTestConfig {
82+ namespace := getComponentNamespace (t , client , selector )
83+ metricsURL = strings .ReplaceAll (metricsURL , "NAMESPACE" , namespace )
84+
85+ return & MetricsTestConfig {
86+ t : t ,
87+ client : client ,
88+ namespace : namespace ,
89+ clusterRole : clusterRole ,
90+ clusterBinding : clusterBinding ,
91+ serviceAccount : serviceAccount ,
92+ curlPodName : curlPodName ,
93+ metricsURL : metricsURL ,
4194 }
42- t . Logf ( "Using %q as k8s client" , client )
95+ }
4396
44- t .Log ("Determining operator-controller namespace" )
45- cmd := exec .Command (client , "get" , "pods" , "--all-namespaces" , "--selector=control-plane=operator-controller-controller-manager" , "--output=jsonpath={.items[0].metadata.namespace}" )
97+ func (c * MetricsTestConfig ) createMetricsClusterRoleBinding () {
98+ c .t .Logf ("Creating ClusterRoleBinding %s in namespace %s" , c .clusterBinding , c .namespace )
99+ cmd := exec .Command (c .client , "create" , "clusterrolebinding" , c .clusterBinding ,
100+ "--clusterrole=" + c .clusterRole ,
101+ "--serviceaccount=" + c .namespace + ":" + c .serviceAccount )
46102 output , err := cmd .CombinedOutput ()
47- require .NoError (t , err , "Error creating determining operator-controller namespace: %s" , string (output ))
48- namespace := string (output )
49- if namespace == "" {
50- t .Fatal ("No operator-controller namespace found" )
51- }
52- t .Logf ("Using %q as operator-controller namespace" , namespace )
53-
54- t .Log ("Creating ClusterRoleBinding for operator controller metrics" )
55- cmd = exec .Command (client , "create" , "clusterrolebinding" , "operator-controller-metrics-binding" ,
56- "--clusterrole=operator-controller-metrics-reader" ,
57- "--serviceaccount=" + namespace + ":operator-controller-controller-manager" )
58- output , err = cmd .CombinedOutput ()
59- require .NoError (t , err , "Error creating ClusterRoleBinding: %s" , string (output ))
60-
61- defer func () {
62- t .Log ("Cleaning up ClusterRoleBinding" )
63- _ = exec .Command (client , "delete" , "clusterrolebinding" , "operator-controller-metrics-binding" , "--ignore-not-found=true" ).Run ()
64- }()
65-
66- t .Log ("Generating ServiceAccount token" )
67- tokenCmd := exec .Command (client , "create" , "token" , "operator-controller-controller-manager" , "-n" , namespace )
68- tokenOutput , tokenCombinedOutput , err := stdoutAndCombined (tokenCmd )
69- require .NoError (t , err , "Error creating token: %s" , string (tokenCombinedOutput ))
70- token = string (bytes .TrimSpace (tokenOutput ))
71-
72- t .Log ("Creating curl pod to validate the metrics endpoint" )
73- cmd = exec .Command (client , "run" , curlPod ,
74- "--image=curlimages/curl:7.87.0" , "-n" , namespace ,
103+ require .NoError (c .t , err , "Error creating ClusterRoleBinding: %s" , string (output ))
104+ }
105+
106+ func (c * MetricsTestConfig ) getServiceAccountToken () string {
107+ c .t .Logf ("Generating ServiceAccount token at namespace %s" , c .namespace )
108+ cmd := exec .Command (c .client , "create" , "token" , c .serviceAccount , "-n" , c .namespace )
109+ tokenOutput , tokenCombinedOutput , err := stdoutAndCombined (cmd )
110+ require .NoError (c .t , err , "Error creating token: %s" , string (tokenCombinedOutput ))
111+ return string (bytes .TrimSpace (tokenOutput ))
112+ }
113+
114+ func (c * MetricsTestConfig ) createCurlMetricsPod () {
115+ c .t .Logf ("Creating curl pod (%s/%s) to validate the metrics endpoint" , c .namespace , c .curlPodName )
116+ cmd := exec .Command (c .client , "run" , c .curlPodName ,
117+ "--image=curlimages/curl:7.87.0" , "-n" , c .namespace ,
75118 "--restart=Never" ,
76119 "--overrides" , `{
77120 "spec": {
@@ -81,154 +124,57 @@ func TestOperatorControllerMetricsExportedEndpoint(t *testing.T) {
81124 "command": ["sh", "-c", "sleep 3600"],
82125 "securityContext": {
83126 "allowPrivilegeEscalation": false,
84- "capabilities": {
85- "drop": ["ALL"]
86- },
127+ "capabilities": {"drop": ["ALL"]},
87128 "runAsNonRoot": true,
88129 "runAsUser": 1000,
89- "seccompProfile": {
90- "type": "RuntimeDefault"
91- }
130+ "seccompProfile": {"type": "RuntimeDefault"}
92131 }
93132 }],
94- "serviceAccountName": "operator-controller-controller-manager "
133+ "serviceAccountName": "` + c . serviceAccount + ` "
95134 }
96135 }` )
97- output , err = cmd .CombinedOutput ()
98- require .NoError (t , err , "Error creating curl pod: %s" , string (output ))
99-
100- defer func () {
101- t .Log ("Cleaning up curl pod" )
102- _ = exec .Command (client , "delete" , "pod" , curlPod , "-n" , namespace , "--ignore-not-found=true" ).Run ()
103- }()
136+ output , err := cmd .CombinedOutput ()
137+ require .NoError (c .t , err , "Error creating curl pod: %s" , string (output ))
138+ }
104139
105- t .Log ("Waiting for the curl pod to be ready" )
106- waitCmd := exec .Command (client , "wait" , "--for=condition=Ready" , "pod" , curlPod , "-n" , namespace , "--timeout=60s" )
140+ func (c * MetricsTestConfig ) validate (token string ) {
141+ c .t .Log ("Waiting for the curl pod to be ready" )
142+ waitCmd := exec .Command (c .client , "wait" , "--for=condition=Ready" , "pod" , c .curlPodName , "-n" , c .namespace , "--timeout=60s" )
107143 waitOutput , waitErr := waitCmd .CombinedOutput ()
108- require .NoError (t , waitErr , "Error waiting for curl pod to be ready: %s" , string (waitOutput ))
109-
110- t .Log ("Validating the metrics endpoint" )
111- metricsURL := "https://operator-controller-service." + namespace + ".svc.cluster.local:8443/metrics"
112- curlCmd := exec .Command (client , "exec" , curlPod , "-n" , namespace , "--" ,
113- "curl" , "-v" , "-k" , "-H" , "Authorization: Bearer " + token , metricsURL )
114- output , err = curlCmd .CombinedOutput ()
115- require .NoError (t , err , "Error calling metrics endpoint: %s" , string (output ))
116- require .Contains (t , string (output ), "200 OK" , "Metrics endpoint did not return 200 OK" )
144+ require .NoError (c .t , waitErr , "Error waiting for curl pod to be ready: %s" , string (waitOutput ))
145+
146+ c .t .Log ("Validating the metrics endpoint" )
147+ curlCmd := exec .Command (c .client , "exec" , c .curlPodName , "-n" , c .namespace , "--" ,
148+ "curl" , "-v" , "-k" , "-H" , "Authorization: Bearer " + token , c .metricsURL )
149+ output , err := curlCmd .CombinedOutput ()
150+ require .NoError (c .t , err , "Error calling metrics endpoint: %s" , string (output ))
151+ require .Contains (c .t , string (output ), "200 OK" , "Metrics endpoint did not return 200 OK" )
117152}
118153
119- // nolint:gosec
120- // TestCatalogdMetricsExportedEndpoint verifies that the metrics endpoint for the catalogd
121- // is exported correctly and accessible by authorized users through RBAC and a ServiceAccount token.
122- // The test performs the following steps:
123- // 1. Creates a ClusterRoleBinding to grant necessary permissions for accessing metrics.
124- // 2. Generates a ServiceAccount token for authentication.
125- // 3. Deploys a curl pod to interact with the metrics endpoint.
126- // 4. Waits for the curl pod to become ready.
127- // 5. Executes a curl command from the pod to validate the metrics endpoint.
128- // 6. Cleans up all resources created during the test, such as the ClusterRoleBinding and curl pod.
129- func TestCatalogdMetricsExportedEndpoint (t * testing.T ) {
130- var (
131- token string
132- curlPod = "curl-metrics"
133- client = ""
134- clients = []string {"kubectl" , "oc" }
135- )
136-
137- t .Log ("Looking for k8s client" )
138- for _ , c := range clients {
139- // Would prefer to use `command -v`, but even that may not be installed!
140- err := exec .Command (c , "version" , "--client" ).Run ()
141- if err == nil {
142- client = c
143- break
144- }
145- }
146- if client == "" {
147- t .Fatal ("k8s client not found" )
148- }
149- t .Logf ("Using %q as k8s client" , client )
154+ func (c * MetricsTestConfig ) cleanup () {
155+ c .t .Log ("Cleaning up resources" )
156+ _ = exec .Command (c .client , "delete" , "clusterrolebinding" , c .clusterBinding , "--ignore-not-found=true" ).Run ()
157+ _ = exec .Command (c .client , "delete" , "pod" , c .curlPodName , "-n" , c .namespace , "--ignore-not-found=true" ).Run ()
158+ }
150159
151- t .Log ("Determining catalogd namespace" )
152- cmd := exec .Command (client , "get" , "pods" , "--all-namespaces" , "--selector=control-plane=catalogd-controller-manager" , "--output=jsonpath={.items[0].metadata.namespace}" )
160+ // getComponentNamespace returns the namespace where operator-controller or catalogd is running
161+ func getComponentNamespace (t * testing.T , client , selector string ) string {
162+ cmd := exec .Command (client , "get" , "pods" , "--all-namespaces" , "--selector=" + selector , "--output=jsonpath={.items[0].metadata.namespace}" )
153163 output , err := cmd .CombinedOutput ()
154- require .NoError (t , err , "Error creating determining catalogd namespace: %s" , string (output ))
155- namespace := string (output )
164+ require .NoError (t , err , "Error determining namespace: %s" , string (output ))
165+
166+ namespace := string (bytes .TrimSpace (output ))
156167 if namespace == "" {
157- t .Fatal ("No catalogd namespace found" )
168+ t .Fatal ("No namespace found for selector " + selector )
158169 }
159- t .Logf ("Using %q as catalogd namespace" , namespace )
160-
161- t .Log ("Creating ClusterRoleBinding for metrics access" )
162- cmd = exec .Command (client , "create" , "clusterrolebinding" , "catalogd-metrics-binding" ,
163- "--clusterrole=catalogd-metrics-reader" ,
164- "--serviceaccount=" + namespace + ":catalogd-controller-manager" )
165- output , err = cmd .CombinedOutput ()
166- require .NoError (t , err , "Error creating ClusterRoleBinding: %s" , string (output ))
167-
168- defer func () {
169- t .Log ("Cleaning up ClusterRoleBinding" )
170- _ = exec .Command (client , "delete" , "clusterrolebinding" , "catalogd-metrics-binding" , "--ignore-not-found=true" ).Run ()
171- }()
172-
173- t .Log ("Creating service account token for authentication" )
174- tokenCmd := exec .Command (client , "create" , "token" , "catalogd-controller-manager" , "-n" , namespace )
175- tokenOutput , tokenCombinedOutput , err := stdoutAndCombined (tokenCmd )
176- require .NoError (t , err , "Error creating token: %s" , string (tokenCombinedOutput ))
177- token = string (bytes .TrimSpace (tokenOutput ))
178-
179- t .Log ("Creating a pod to run curl commands" )
180- cmd = exec .Command (client , "run" , curlPod ,
181- "--image=curlimages/curl:7.87.0" , "-n" , namespace ,
182- "--restart=Never" ,
183- "--overrides" , `{
184- "spec": {
185- "containers": [{
186- "name": "curl",
187- "image": "curlimages/curl:7.87.0",
188- "command": ["sh", "-c", "sleep 3600"],
189- "securityContext": {
190- "allowPrivilegeEscalation": false,
191- "capabilities": {
192- "drop": ["ALL"]
193- },
194- "runAsNonRoot": true,
195- "runAsUser": 1000,
196- "seccompProfile": {
197- "type": "RuntimeDefault"
198- }
199- }
200- }],
201- "serviceAccountName": "catalogd-controller-manager"
202- }
203- }` )
204- output , err = cmd .CombinedOutput ()
205- require .NoError (t , err , "Error creating curl pod: %s" , string (output ))
206-
207- defer func () {
208- t .Log ("Cleaning up curl pod" )
209- _ = exec .Command (client , "delete" , "pod" , curlPod , "-n" , namespace , "--ignore-not-found=true" ).Run ()
210- }()
211-
212- t .Log ("Waiting for the curl pod to become ready" )
213- waitCmd := exec .Command (client , "wait" , "--for=condition=Ready" , "pod" , curlPod , "-n" , namespace , "--timeout=60s" )
214- waitOutput , waitErr := waitCmd .CombinedOutput ()
215- require .NoError (t , waitErr , "Error waiting for curl pod to be ready: %s" , string (waitOutput ))
216-
217- t .Log ("Validating the metrics endpoint" )
218- metricsURL := "https://catalogd-service." + namespace + ".svc.cluster.local:7443/metrics"
219- curlCmd := exec .Command (client , "exec" , curlPod , "-n" , namespace , "--" ,
220- "curl" , "-v" , "-k" , "-H" , "Authorization: Bearer " + token , metricsURL )
221- output , err = curlCmd .CombinedOutput ()
222- require .NoError (t , err , "Error calling metrics endpoint: %s" , string (output ))
223- require .Contains (t , string (output ), "200 OK" , "Metrics endpoint did not return 200 OK" )
170+ return namespace
224171}
225172
226173func stdoutAndCombined (cmd * exec.Cmd ) ([]byte , []byte , error ) {
227- var outOnly bytes.Buffer
228- var outAndErr bytes.Buffer
174+ var outOnly , outAndErr bytes.Buffer
229175 allWriter := io .MultiWriter (& outOnly , & outAndErr )
230- cmd .Stderr = & outAndErr
231176 cmd .Stdout = allWriter
177+ cmd .Stderr = & outAndErr
232178 err := cmd .Run ()
233179 return outOnly .Bytes (), outAndErr .Bytes (), err
234180}
0 commit comments