@@ -17,8 +17,11 @@ limitations under the License.
17
17
package e2e
18
18
19
19
import (
20
+ "encoding/json"
20
21
"fmt"
22
+ "os"
21
23
"os/exec"
24
+ "path/filepath"
22
25
"time"
23
26
24
27
. "github.com/onsi/ginkgo/v2"
@@ -27,34 +30,44 @@ import (
27
30
"tutorial.kubebuilder.io/project/test/utils"
28
31
)
29
32
33
+ // namespace where the project is deployed in
30
34
const namespace = "project-system"
31
35
32
- // Define a set of end-to-end (e2e) tests to validate the behavior of the controller.
33
- var _ = Describe ("controller" , Ordered , func () {
36
+ // serviceAccountName created for the project
37
+ const serviceAccountName = "project-controller-manager"
38
+
39
+ // metricsServiceName is the name of the metrics service of the project
40
+ const metricsServiceName = "project-controller-manager-metrics-service"
41
+
42
+ // metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data
43
+ const metricsRoleBindingName = "project-metrics-binding"
44
+
45
+ var _ = Describe ("Manager" , Ordered , func () {
34
46
// Before running the tests, set up the environment by creating the namespace,
35
47
// installing CRDs, and deploying the controller.
36
48
BeforeAll (func () {
37
49
By ("creating manager namespace" )
38
50
cmd := exec .Command ("kubectl" , "create" , "ns" , namespace )
39
- _ , err := utils .Run (cmd )
40
- ExpectWithOffset (1 , err ).NotTo (HaveOccurred (), "Failed to create namespace" )
51
+ Expect (utils .Run (cmd )).Error ().NotTo (HaveOccurred (), "Failed to create namespace" )
41
52
42
53
By ("installing CRDs" )
43
54
cmd = exec .Command ("make" , "install" )
44
- _ , err = utils .Run (cmd )
45
- ExpectWithOffset (1 , err ).NotTo (HaveOccurred (), "Failed to install CRDs" )
55
+ Expect (utils .Run (cmd )).Error ().NotTo (HaveOccurred (), "Failed to install CRDs" )
46
56
47
57
By ("deploying the controller-manager" )
48
58
cmd = exec .Command ("make" , "deploy" , fmt .Sprintf ("IMG=%s" , projectImage ))
49
- _ , err = utils .Run (cmd )
50
- ExpectWithOffset (1 , err ).NotTo (HaveOccurred (), "Failed to deploy the controller-manager" )
59
+ Expect (utils .Run (cmd )).Error ().NotTo (HaveOccurred (), "Failed to deploy the controller-manager" )
51
60
})
52
61
53
62
// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,
54
63
// and deleting the namespace.
55
64
AfterAll (func () {
65
+ By ("cleaning up the curl pod for metrics" )
66
+ cmd := exec .Command ("kubectl" , "delete" , "pod" , "curl-metrics" , "-n" , namespace )
67
+ _ , _ = utils .Run (cmd )
68
+
56
69
By ("undeploying the controller-manager" )
57
- cmd : = exec .Command ("make" , "undeploy" )
70
+ cmd = exec .Command ("make" , "undeploy" )
58
71
_ , _ = utils .Run (cmd )
59
72
60
73
By ("uninstalling CRDs" )
@@ -66,13 +79,15 @@ var _ = Describe("controller", Ordered, func() {
66
79
_ , _ = utils .Run (cmd )
67
80
})
68
81
69
- // The Context block contains the actual tests that validate the operator's behavior.
70
- Context ("Operator" , func () {
71
- It ("should run successfully" , func () {
72
- var controllerPodName string
82
+ SetDefaultEventuallyTimeout (2 * time .Minute )
83
+ SetDefaultEventuallyPollingInterval (time .Second )
73
84
85
+ // The Context block contains the actual tests that validate the manager's behavior.
86
+ Context ("Manager" , func () {
87
+ var controllerPodName string
88
+ It ("should run successfully" , func () {
74
89
By ("validating that the controller-manager pod is running as expected" )
75
- verifyControllerUp := func () error {
90
+ verifyControllerUp := func (g Gomega ) {
76
91
// Get the name of the controller-manager pod
77
92
cmd := exec .Command ("kubectl" , "get" ,
78
93
"pods" , "-l" , "control-plane=controller-manager" ,
@@ -84,31 +99,161 @@ var _ = Describe("controller", Ordered, func() {
84
99
)
85
100
86
101
podOutput , err := utils .Run (cmd )
87
- ExpectWithOffset ( 2 , err ).NotTo (HaveOccurred (), "Failed to retrieve controller-manager pod information" )
102
+ g . Expect ( err ).NotTo (HaveOccurred (), "Failed to retrieve controller-manager pod information" )
88
103
podNames := utils .GetNonEmptyLines (string (podOutput ))
89
- if len (podNames ) != 1 {
90
- return fmt .Errorf ("expected 1 controller pod running, but got %d" , len (podNames ))
91
- }
104
+ g .Expect (podNames ).To (HaveLen (1 ), "expected 1 controller pod running" )
92
105
controllerPodName = podNames [0 ]
93
- ExpectWithOffset ( 2 , controllerPodName ).Should (ContainSubstring ("controller-manager" ))
106
+ g . Expect ( controllerPodName ).To (ContainSubstring ("controller-manager" ))
94
107
95
108
// Validate the pod's status
96
109
cmd = exec .Command ("kubectl" , "get" ,
97
110
"pods" , controllerPodName , "-o" , "jsonpath={.status.phase}" ,
98
111
"-n" , namespace ,
99
112
)
100
- status , err := utils .Run (cmd )
101
- ExpectWithOffset (2 , err ).NotTo (HaveOccurred (), "Failed to retrieve controller-manager pod status" )
102
- if string (status ) != "Running" {
103
- return fmt .Errorf ("controller pod in %s status" , status )
104
- }
105
- return nil
113
+ g .Expect (utils .Run (cmd )).To (BeEquivalentTo ("Running" ), "Incorrect controller-manager pod status" )
106
114
}
107
115
// Repeatedly check if the controller-manager pod is running until it succeeds or times out.
108
- EventuallyWithOffset (1 , verifyControllerUp , time .Minute , time .Second ).Should (Succeed ())
116
+ Eventually (verifyControllerUp ).Should (Succeed ())
117
+ })
118
+
119
+ It ("should ensure the metrics endpoint is serving metrics" , func () {
120
+ By ("creating a ClusterRoleBinding for the service account to allow access to metrics" )
121
+ cmd := exec .Command ("kubectl" , "create" , "clusterrolebinding" , metricsRoleBindingName ,
122
+ "--clusterrole=project-metrics-reader" ,
123
+ fmt .Sprintf ("--serviceaccount=%s:%s" , namespace , serviceAccountName ),
124
+ )
125
+ _ , err := utils .Run (cmd )
126
+ Expect (err ).NotTo (HaveOccurred (), "Failed to create ClusterRoleBinding" )
127
+
128
+ By ("validating that the metrics service is available" )
129
+ cmd = exec .Command ("kubectl" , "get" , "service" , metricsServiceName , "-n" , namespace )
130
+ _ , err = utils .Run (cmd )
131
+ Expect (err ).NotTo (HaveOccurred (), "Metrics service should exist" )
132
+
133
+ By ("validating that the ServiceMonitor for Prometheus is applied in the namespace" )
134
+ cmd = exec .Command ("kubectl" , "get" , "ServiceMonitor" , "-n" , namespace )
135
+ _ , err = utils .Run (cmd )
136
+ Expect (err ).NotTo (HaveOccurred (), "ServiceMonitor should exist" )
137
+
138
+ By ("getting the service account token" )
139
+ token , err := serviceAccountToken ()
140
+ Expect (err ).NotTo (HaveOccurred ())
141
+ Expect (token ).NotTo (BeEmpty ())
142
+
143
+ By ("waiting for the metrics endpoint to be ready" )
144
+ verifyMetricsEndpointReady := func (g Gomega ) {
145
+ cmd := exec .Command ("kubectl" , "get" , "endpoints" , metricsServiceName , "-n" , namespace )
146
+ output , err := utils .Run (cmd )
147
+ g .Expect (err ).NotTo (HaveOccurred (), "Failed to retrieve endpoints information" )
148
+ g .Expect (string (output )).To (ContainSubstring ("8443" ), "Metrics endpoint is not ready" )
149
+ }
150
+ Eventually (verifyMetricsEndpointReady ).Should (Succeed ())
151
+
152
+ By ("verifying that the controller manager is serving the metrics server" )
153
+ verifyMetricsServerStarted := func (g Gomega ) {
154
+ cmd := exec .Command ("kubectl" , "logs" , controllerPodName , "-n" , namespace )
155
+ logs , err := utils .Run (cmd )
156
+ g .Expect (err ).NotTo (HaveOccurred (), "Failed to retrieve controller manager logs" )
157
+ g .Expect (string (logs )).To (ContainSubstring ("controller-runtime.metrics\t Serving metrics server" ),
158
+ "Metrics server not yet started" )
159
+ }
160
+ Eventually (verifyMetricsServerStarted ).Should (Succeed ())
161
+
162
+ By ("creating the curl-metrics pod to access the metrics endpoint" )
163
+ cmd = exec .Command ("kubectl" , "run" , "curl-metrics" , "--restart=Never" ,
164
+ "--namespace" , namespace ,
165
+ "--image=curlimages/curl:7.78.0" ,
166
+ "--" , "/bin/sh" , "-c" , fmt .Sprintf (
167
+ "curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics" ,
168
+ token , metricsServiceName , namespace ))
169
+ Expect (utils .Run (cmd )).Error ().NotTo (HaveOccurred (), "Failed to create curl-metrics pod" )
170
+
171
+ By ("waiting for the curl-metrics pod to complete." )
172
+ verifyCurlUp := func (g Gomega ) {
173
+ cmd := exec .Command ("kubectl" , "get" , "pods" , "curl-metrics" ,
174
+ "-o" , "jsonpath={.status.phase}" ,
175
+ "-n" , namespace )
176
+ g .Expect (utils .Run (cmd )).To (BeEquivalentTo ("Succeeded" ), "curl pod in wrong status" )
177
+ }
178
+ Eventually (verifyCurlUp , 5 * time .Minute ).Should (Succeed ())
179
+
180
+ By ("getting the metrics by checking curl-metrics logs" )
181
+ metricsOutput := getMetricsOutput ()
182
+ Expect (metricsOutput ).To (ContainSubstring (
183
+ "controller_runtime_reconcile_total" ,
184
+ ))
109
185
})
110
186
111
- // TODO(user): Customize the e2e test suite to include
112
- // additional scenarios specific to your project.
187
+ // TODO: Customize the e2e test suite with scenarios specific to your project.
188
+ // Consider applying sample/CR(s) and check their status and/or verifying
189
+ // the reconciliation by using the metrics, i.e.:
190
+ // metricsOutput := getMetricsOutput()
191
+ // Expect(metricsOutput).To(ContainSubstring(
192
+ // fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`,
193
+ // strings.ToLower(<Kind>),
194
+ // ))
113
195
})
114
196
})
197
+
198
+ // serviceAccountToken returns a token for the specified service account in the given namespace.
199
+ // It uses the Kubernetes TokenRequest API to generate a token by directly sending a request
200
+ // and parsing the resulting token from the API response.
201
+ func serviceAccountToken () (string , error ) {
202
+ const tokenRequestRawString = `{
203
+ "apiVersion": "authentication.k8s.io/v1",
204
+ "kind": "TokenRequest"
205
+ }`
206
+
207
+ // Temporary file to store the token request
208
+ secretName := fmt .Sprintf ("%s-token-request" , serviceAccountName )
209
+ tokenRequestFile := filepath .Join ("/tmp" , secretName )
210
+ err := os .WriteFile (tokenRequestFile , []byte (tokenRequestRawString ), os .FileMode (0o644 ))
211
+ if err != nil {
212
+ return "" , err
213
+ }
214
+
215
+ var out string
216
+ var rawJson string
217
+ verifyTokenCreation := func (g Gomega ) {
218
+ // Execute kubectl command to create the token
219
+ cmd := exec .Command ("kubectl" , "create" , "--raw" , fmt .Sprintf (
220
+ "/api/v1/namespaces/%s/serviceaccounts/%s/token" ,
221
+ namespace ,
222
+ serviceAccountName ,
223
+ ), "-f" , tokenRequestFile )
224
+
225
+ output , err := cmd .CombinedOutput ()
226
+ g .Expect (err ).NotTo (HaveOccurred ())
227
+
228
+ rawJson = string (output )
229
+
230
+ // Parse the JSON output to extract the token
231
+ var token tokenRequest
232
+ err = json .Unmarshal ([]byte (rawJson ), & token )
233
+ g .Expect (err ).NotTo (HaveOccurred ())
234
+
235
+ out = token .Status .Token
236
+ }
237
+ Eventually (verifyTokenCreation ).Should (Succeed ())
238
+
239
+ return out , err
240
+ }
241
+
242
+ // getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.
243
+ func getMetricsOutput () string {
244
+ By ("getting the curl-metrics logs" )
245
+ cmd := exec .Command ("kubectl" , "logs" , "curl-metrics" , "-n" , namespace )
246
+ metricsOutput , err := utils .Run (cmd )
247
+ Expect (err ).NotTo (HaveOccurred (), "Failed to retrieve logs from curl pod" )
248
+ metricsOutputStr := string (metricsOutput )
249
+ Expect (metricsOutputStr ).To (ContainSubstring ("< HTTP/1.1 200 OK" ))
250
+ return metricsOutputStr
251
+ }
252
+
253
+ // tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,
254
+ // containing only the token field that we need to extract.
255
+ type tokenRequest struct {
256
+ Status struct {
257
+ Token string `json:"token"`
258
+ } `json:"status"`
259
+ }
0 commit comments