Skip to content

Commit ebad7c5

Browse files
committed
update metrics e2e to curl from a random namespace
This change proves that the NetworkPolicy for catalogd and operator-controller allows scraping metrics from outside the namespace in which catalogd and operator-controller are running. Signed-off-by: Joe Lanford <[email protected]>
1 parent 7831977 commit ebad7c5

File tree

1 file changed

+93
-62
lines changed

1 file changed

+93
-62
lines changed

test/e2e/metrics_test.go

+93-62
Original file line numberDiff line numberDiff line change
@@ -19,50 +19,57 @@ import (
1919
"fmt"
2020
"io"
2121
"os/exec"
22-
"strings"
2322
"testing"
2423
"time"
2524

2625
"github.com/stretchr/testify/require"
26+
"k8s.io/apimachinery/pkg/util/rand"
2727

2828
"github.com/operator-framework/operator-controller/test/utils"
2929
)
3030

3131
// TestOperatorControllerMetricsExportedEndpoint verifies that the metrics endpoint for the operator controller
3232
func TestOperatorControllerMetricsExportedEndpoint(t *testing.T) {
3333
client := utils.FindK8sClient(t)
34+
curlNamespace := createRandomNamespace(t, client)
35+
componentNamespace := getComponentNamespace(t, client, "control-plane=operator-controller-controller-manager")
36+
metricsURL := fmt.Sprintf("https://operator-controller-service.%s.svc.cluster.local:8443/metrics", componentNamespace)
37+
3438
config := NewMetricsTestConfig(
35-
t, client,
36-
"control-plane=operator-controller-controller-manager",
39+
client,
40+
curlNamespace,
3741
"operator-controller-metrics-reader",
3842
"operator-controller-metrics-binding",
39-
"operator-controller-controller-manager",
43+
"operator-controller-metrics-reader",
4044
"oper-curl-metrics",
41-
"https://operator-controller-service.NAMESPACE.svc.cluster.local:8443/metrics",
45+
metricsURL,
4246
)
4347

44-
config.run()
48+
config.run(t)
4549
}
4650

4751
// TestCatalogdMetricsExportedEndpoint verifies that the metrics endpoint for catalogd
4852
func TestCatalogdMetricsExportedEndpoint(t *testing.T) {
4953
client := utils.FindK8sClient(t)
54+
curlNamespace := createRandomNamespace(t, client)
55+
componentNamespace := getComponentNamespace(t, client, "control-plane=catalogd-controller-manager")
56+
metricsURL := fmt.Sprintf("https://catalogd-service.%s.svc.cluster.local:7443/metrics", componentNamespace)
57+
5058
config := NewMetricsTestConfig(
51-
t, client,
52-
"control-plane=catalogd-controller-manager",
59+
client,
60+
curlNamespace,
5361
"catalogd-metrics-reader",
5462
"catalogd-metrics-binding",
55-
"catalogd-controller-manager",
63+
"catalogd-metrics-reader",
5664
"catalogd-curl-metrics",
57-
"https://catalogd-service.NAMESPACE.svc.cluster.local:7443/metrics",
65+
metricsURL,
5866
)
5967

60-
config.run()
68+
config.run(t)
6169
}
6270

6371
// MetricsTestConfig holds the necessary configurations for testing metrics endpoints.
6472
type MetricsTestConfig struct {
65-
t *testing.T
6673
client string
6774
namespace string
6875
clusterRole string
@@ -73,12 +80,8 @@ type MetricsTestConfig struct {
7380
}
7481

7582
// NewMetricsTestConfig initializes a new MetricsTestConfig.
76-
func NewMetricsTestConfig(t *testing.T, client, selector, clusterRole, clusterBinding, serviceAccount, curlPodName, metricsURL string) *MetricsTestConfig {
77-
namespace := getComponentNamespace(t, client, selector)
78-
metricsURL = strings.ReplaceAll(metricsURL, "NAMESPACE", namespace)
79-
83+
func NewMetricsTestConfig(client, namespace, clusterRole, clusterBinding, serviceAccount, curlPodName, metricsURL string) *MetricsTestConfig {
8084
return &MetricsTestConfig{
81-
t: t,
8285
client: client,
8386
namespace: namespace,
8487
clusterRole: clusterRole,
@@ -90,38 +93,44 @@ func NewMetricsTestConfig(t *testing.T, client, selector, clusterRole, clusterBi
9093
}
9194

9295
// run will execute all steps of those tests
93-
func (c *MetricsTestConfig) run() {
94-
c.createMetricsClusterRoleBinding()
95-
token := c.getServiceAccountToken()
96-
c.createCurlMetricsPod()
97-
c.validate(token)
98-
defer c.cleanup()
96+
func (c *MetricsTestConfig) run(t *testing.T) {
97+
defer c.cleanup(t)
98+
99+
c.createMetricsClusterRoleBinding(t)
100+
token := c.getServiceAccountToken(t)
101+
c.createCurlMetricsPod(t)
102+
c.validate(t, token)
99103
}
100104

101105
// createMetricsClusterRoleBinding to binding and expose the metrics
102-
func (c *MetricsTestConfig) createMetricsClusterRoleBinding() {
103-
c.t.Logf("Creating ClusterRoleBinding %s in namespace %s", c.clusterBinding, c.namespace)
106+
func (c *MetricsTestConfig) createMetricsClusterRoleBinding(t *testing.T) {
107+
t.Logf("Creating ClusterRoleBinding %s for %s in namespace %s", c.clusterBinding, c.serviceAccount, c.namespace)
104108
cmd := exec.Command(c.client, "create", "clusterrolebinding", c.clusterBinding,
105109
"--clusterrole="+c.clusterRole,
106110
"--serviceaccount="+c.namespace+":"+c.serviceAccount)
107111
output, err := cmd.CombinedOutput()
108-
require.NoError(c.t, err, "Error creating ClusterRoleBinding: %s", string(output))
112+
require.NoError(t, err, "Error creating ClusterRoleBinding: %s", string(output))
109113
}
110114

111115
// getServiceAccountToken return the token requires to have access to the metrics
112-
func (c *MetricsTestConfig) getServiceAccountToken() string {
113-
c.t.Logf("Generating ServiceAccount token at namespace %s", c.namespace)
114-
cmd := exec.Command(c.client, "create", "token", c.serviceAccount, "-n", c.namespace)
116+
func (c *MetricsTestConfig) getServiceAccountToken(t *testing.T) string {
117+
t.Logf("Creating ServiceAccount %q in namespace %q", c.serviceAccount, c.namespace)
118+
output, err := exec.Command(c.client, "create", "serviceaccount", c.serviceAccount, "--namespace="+c.namespace).CombinedOutput()
119+
require.NoError(t, err, "Error creating service account: %v", string(output))
120+
121+
t.Logf("Generating ServiceAccount token for %q in namespace %q", c.serviceAccount, c.namespace)
122+
cmd := exec.Command(c.client, "create", "token", c.serviceAccount, "--namespace", c.namespace)
115123
tokenOutput, tokenCombinedOutput, err := stdoutAndCombined(cmd)
116-
require.NoError(c.t, err, "Error creating token: %s", string(tokenCombinedOutput))
124+
require.NoError(t, err, "Error creating token: %s", string(tokenCombinedOutput))
117125
return string(bytes.TrimSpace(tokenOutput))
118126
}
119127

120128
// createCurlMetricsPod creates the Pod with curl image to allow check if the metrics are working
121-
func (c *MetricsTestConfig) createCurlMetricsPod() {
122-
c.t.Logf("Creating curl pod (%s/%s) to validate the metrics endpoint", c.namespace, c.curlPodName)
129+
func (c *MetricsTestConfig) createCurlMetricsPod(t *testing.T) {
130+
t.Logf("Creating curl pod (%s/%s) to validate the metrics endpoint", c.namespace, c.curlPodName)
123131
cmd := exec.Command(c.client, "run", c.curlPodName,
124-
"--image=curlimages/curl", "-n", c.namespace,
132+
"--image=curlimages/curl",
133+
"--namespace", c.namespace,
125134
"--restart=Never",
126135
"--overrides", `{
127136
"spec": {
@@ -142,55 +151,66 @@ func (c *MetricsTestConfig) createCurlMetricsPod() {
142151
}
143152
}`)
144153
output, err := cmd.CombinedOutput()
145-
require.NoError(c.t, err, "Error creating curl pod: %s", string(output))
154+
require.NoError(t, err, "Error creating curl pod: %s", string(output))
146155
}
147156

148157
// validate verifies if is possible to access the metrics
149-
func (c *MetricsTestConfig) validate(token string) {
150-
c.t.Log("Waiting for the curl pod to be ready")
151-
waitCmd := exec.Command(c.client, "wait", "--for=condition=Ready", "pod", c.curlPodName, "-n", c.namespace, "--timeout=60s")
158+
func (c *MetricsTestConfig) validate(t *testing.T, token string) {
159+
t.Log("Waiting for the curl pod to be ready")
160+
waitCmd := exec.Command(c.client, "wait", "--for=condition=Ready", "pod", c.curlPodName, "--namespace", c.namespace, "--timeout=60s")
152161
waitOutput, waitErr := waitCmd.CombinedOutput()
153-
require.NoError(c.t, waitErr, "Error waiting for curl pod to be ready: %s", string(waitOutput))
162+
require.NoError(t, waitErr, "Error waiting for curl pod to be ready: %s", string(waitOutput))
154163

155-
c.t.Log("Validating the metrics endpoint")
156-
curlCmd := exec.Command(c.client, "exec", c.curlPodName, "-n", c.namespace, "--",
164+
t.Log("Validating the metrics endpoint")
165+
curlCmd := exec.Command(c.client, "exec", c.curlPodName, "--namespace", c.namespace, "--",
157166
"curl", "-v", "-k", "-H", "Authorization: Bearer "+token, c.metricsURL)
158167
output, err := curlCmd.CombinedOutput()
159-
require.NoError(c.t, err, "Error calling metrics endpoint: %s", string(output))
160-
require.Contains(c.t, string(output), "200 OK", "Metrics endpoint did not return 200 OK")
168+
require.NoError(t, err, "Error calling metrics endpoint: %s", string(output))
169+
require.Contains(t, string(output), "200 OK", "Metrics endpoint did not return 200 OK")
161170
}
162171

163172
// cleanup removes the created resources. Uses a context with timeout to prevent hangs.
164-
func (c *MetricsTestConfig) cleanup() {
165-
c.t.Log("Cleaning up resources")
166-
_ = exec.Command(c.client, "delete", "clusterrolebinding", c.clusterBinding, "--ignore-not-found=true", "--force").Run()
167-
_ = exec.Command(c.client, "delete", "pod", c.curlPodName, "-n", c.namespace, "--ignore-not-found=true", "--force").Run()
173+
func (c *MetricsTestConfig) cleanup(t *testing.T) {
174+
type objDesc struct {
175+
resourceName string
176+
name string
177+
namespace string
178+
}
179+
objects := []objDesc{
180+
{"clusterrolebinding", c.clusterBinding, ""},
181+
{"pod", c.curlPodName, c.namespace},
182+
{"serviceaccount", c.serviceAccount, c.namespace},
183+
{"namespace", c.namespace, ""},
184+
}
185+
186+
t.Log("Cleaning up resources")
187+
for _, obj := range objects {
188+
args := []string{"delete", obj.resourceName, obj.name, "--ignore-not-found=true", "--force"}
189+
if obj.namespace != "" {
190+
args = append(args, "--namespace", obj.namespace)
191+
}
192+
output, err := exec.Command(c.client, args...).CombinedOutput()
193+
require.NoError(t, err, "Error deleting %q %q in namespace %q: %v", obj.resourceName, obj.name, obj.namespace, string(output))
194+
}
168195

169196
// Create a context with a 60-second timeout.
170197
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
171198
defer cancel()
172199

173-
// Wait for the ClusterRoleBinding to be deleted.
174-
if err := waitForDeletion(ctx, c.client, "clusterrolebinding", c.clusterBinding); err != nil {
175-
c.t.Logf("Error waiting for clusterrolebinding deletion: %v", err)
176-
} else {
177-
c.t.Log("ClusterRoleBinding deleted")
178-
}
179-
180-
// Wait for the Pod to be deleted.
181-
if err := waitForDeletion(ctx, c.client, "pod", c.curlPodName, "-n", c.namespace); err != nil {
182-
c.t.Logf("Error waiting for pod deletion: %v", err)
183-
} else {
184-
c.t.Log("Pod deleted")
200+
for _, obj := range objects {
201+
err := waitForDeletion(ctx, c.client, obj.resourceName, obj.name, obj.namespace)
202+
require.NoError(t, err, "Error deleting %q %q in namespace %q", obj.resourceName, obj.name, obj.namespace)
203+
t.Logf("Successfully deleted %q %q in namespace %q", obj.resourceName, obj.name, obj.namespace)
185204
}
186205
}
187206

188207
// waitForDeletion uses "kubectl wait" to block until the specified resource is deleted
189208
// or until the 60-second timeout is reached.
190-
func waitForDeletion(ctx context.Context, client, resourceType, resourceName string, extraArgs ...string) error {
191-
args := []string{"wait", "--for=delete", resourceType, resourceName}
192-
args = append(args, extraArgs...)
193-
args = append(args, "--timeout=60s")
209+
func waitForDeletion(ctx context.Context, client, resourceType, resourceName, resourceNamespace string) error {
210+
args := []string{"wait", "--for=delete", "--timeout=60s", resourceType, resourceName}
211+
if resourceNamespace != "" {
212+
args = append(args, "--namespace", resourceNamespace)
213+
}
194214
cmd := exec.CommandContext(ctx, client, args...)
195215
output, err := cmd.CombinedOutput()
196216
if err != nil {
@@ -199,6 +219,17 @@ func waitForDeletion(ctx context.Context, client, resourceType, resourceName str
199219
return nil
200220
}
201221

222+
// createRandomNamespace creates a random namespace
223+
func createRandomNamespace(t *testing.T, client string) string {
224+
nsName := fmt.Sprintf("testns-%s", rand.String(8))
225+
226+
cmd := exec.Command(client, "create", "namespace", nsName)
227+
output, err := cmd.CombinedOutput()
228+
require.NoError(t, err, "Error creating namespace: %s", string(output))
229+
230+
return nsName
231+
}
232+
202233
// getComponentNamespace returns the namespace where operator-controller or catalogd is running
203234
func getComponentNamespace(t *testing.T, client, selector string) string {
204235
cmd := exec.Command(client, "get", "pods", "--all-namespaces", "--selector="+selector, "--output=jsonpath={.items[0].metadata.namespace}")

0 commit comments

Comments
 (0)