Skip to content

Commit 06053ce

Browse files
committed
chore: add tests for secret updation behavior
1 parent 19928f2 commit 06053ce

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed

test/e2e/reconcile_objects_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"slices"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"github.com/openshift/cluster-monitoring-operator/test/e2e/framework"
13+
"github.com/stretchr/testify/require"
14+
v1 "k8s.io/api/core/v1"
15+
"k8s.io/apimachinery/pkg/api/errors"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/apimachinery/pkg/types"
18+
)
19+
20+
// TestSecretsReconciliation tests whether the secrets created by the operator are reconciled correctly. These include:
21+
// * unsynced secrets: secrets that are deployed by, but not synced by the operator, and,
22+
// * synced secrets: secrets that are deployed by, and should be synced by the operator.
23+
24+
// TODO: Exclude all secrets that are initially empty and populated by other operators.
25+
func TestSecretsReconciliation(t *testing.T) {
26+
// Create assets under both scenarios for us to work with.
27+
setupUserWorkloadAssetsWithTeardownHook(t, f)
28+
userWorkloadConfigMap := f.BuildUserWorkloadConfigMap(t, `alertmanager:
29+
enabled: true
30+
`)
31+
f.MustCreateOrUpdateConfigMap(t, userWorkloadConfigMap)
32+
defer f.MustDeleteConfigMap(t, userWorkloadConfigMap)
33+
34+
f.AssertStatefulSetExistsAndRollout("alertmanager-user-workload", f.UserWorkloadMonitoringNs)(t)
35+
f.AssertServiceExists("alertmanager-user-workload", f.UserWorkloadMonitoringNs)(t)
36+
f.AssertSecretExists("alertmanager-user-workload", f.UserWorkloadMonitoringNs)(t)
37+
38+
// List of secrets that should not be synced during operator's reconciliation.
39+
unsyncedSecrets := []types.NamespacedName{
40+
{
41+
Name: "alertmanager-main",
42+
Namespace: f.Ns,
43+
},
44+
{
45+
Name: "alertmanager-user-workload",
46+
Namespace: f.UserWorkloadMonitoringNs,
47+
},
48+
{
49+
Name: "thanos-ruler-user-workload-web-config",
50+
Namespace: f.UserWorkloadMonitoringNs,
51+
},
52+
}
53+
54+
// Restore all unsynced secrets to their original state.
55+
cleanup := func() {
56+
for _, secret := range unsyncedSecrets {
57+
gotSecret, err := f.KubeClient.CoreV1().Secrets(secret.Namespace).Get(context.Background(), secret.Name, metav1.GetOptions{})
58+
if errors.IsNotFound(err) {
59+
continue
60+
}
61+
require.NoError(t, err)
62+
data := gotSecret.Data
63+
for k, v := range data {
64+
data[k] = []byte(strings.TrimPrefix(string(v), t.Name()))
65+
}
66+
_, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Update(context.Background(), gotSecret, metav1.UpdateOptions{})
67+
require.NoError(t, err)
68+
}
69+
}
70+
defer cleanup()
71+
72+
var syncedSecrets []types.NamespacedName
73+
secretsNS, err := f.KubeClient.CoreV1().Secrets(f.Ns).List(context.Background(), metav1.ListOptions{
74+
// Intentionally commented out as we want to fetch all secrets.
75+
// LabelSelector: "app.kubernetes.io/managed-by=cluster-monitoring-operator",
76+
})
77+
require.NoError(t, err)
78+
79+
secretsUWMNS, err := f.KubeClient.CoreV1().Secrets(f.UserWorkloadMonitoringNs).List(context.Background(), metav1.ListOptions{
80+
// Intentionally commented out as we want to fetch all secrets.
81+
// LabelSelector: "app.kubernetes.io/managed-by=cluster-monitoring-operator",
82+
})
83+
require.NoError(t, err)
84+
85+
for _, secret := range append(secretsNS.Items, secretsUWMNS.Items...) {
86+
secretNamespacedName := types.NamespacedName{
87+
Name: secret.Name,
88+
Namespace: secret.Namespace,
89+
}
90+
if slices.Contains(unsyncedSecrets, secretNamespacedName) {
91+
continue
92+
}
93+
syncedSecrets = append(syncedSecrets, secretNamespacedName)
94+
}
95+
require.NotEmpty(t, syncedSecrets)
96+
97+
secrets := append(syncedSecrets, unsyncedSecrets...)
98+
99+
var filteredSecrets []types.NamespacedName
100+
for _, secret := range secrets {
101+
if strings.Contains(secret.Name, "tls") {
102+
continue
103+
}
104+
filteredSecrets = append(filteredSecrets, secret)
105+
}
106+
107+
// Update the aforementioned secrets' data.
108+
for _, secret := range filteredSecrets {
109+
var gotSecret *v1.Secret
110+
gotSecret, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Get(context.Background(), secret.Name, metav1.GetOptions{})
111+
require.NoError(t, err)
112+
data := gotSecret.Data
113+
for k, v := range data {
114+
if isSecretDataJSONEncoded(gotSecret) {
115+
var jsonData map[string]interface{}
116+
err = json.Unmarshal(v, &jsonData)
117+
require.NoError(t, err)
118+
jsonData[t.Name()] = t.Name()
119+
v, err = json.Marshal(jsonData)
120+
require.NoError(t, err)
121+
data[k] = v
122+
} else {
123+
data[k] = []byte(t.Name() + string(v))
124+
}
125+
break
126+
}
127+
128+
_, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Update(context.Background(), gotSecret, metav1.UpdateOptions{})
129+
require.NoError(t, err)
130+
}
131+
132+
// Check for reconciliation of secrets.
133+
for _, secret := range filteredSecrets {
134+
// Check if the secrets were reconciled as expected.
135+
if slices.Contains(syncedSecrets, secret) {
136+
err = framework.Poll(10*time.Second, 5*time.Minute, func() error {
137+
var updatedSecret *v1.Secret
138+
updatedSecret, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Get(context.Background(), secret.Name, metav1.GetOptions{})
139+
if err != nil {
140+
return err
141+
}
142+
143+
data := updatedSecret.Data
144+
for _, v := range data {
145+
if !isSecretDataJSONEncoded(updatedSecret) {
146+
if strings.HasPrefix(string(v), t.Name()) {
147+
return fmt.Errorf("secret %s has unexpected data: %v", secret.String(), string(v))
148+
}
149+
return nil
150+
}
151+
152+
var jsonData map[string]interface{}
153+
err = json.Unmarshal(v, &jsonData)
154+
if err != nil {
155+
return fmt.Errorf("failed to unmarshal JSON data in secret %s: %v", secret.String(), err)
156+
}
157+
if _, ok := jsonData[t.Name()]; ok {
158+
return fmt.Errorf("secret %s does contains unexpected key %s", secret.String(), t.Name())
159+
}
160+
}
161+
162+
return nil
163+
})
164+
165+
require.NoError(t, err)
166+
}
167+
168+
// Check if the secrets were reconciled unexpectedly.
169+
if slices.Contains(unsyncedSecrets, secret) {
170+
var updatedSecret *v1.Secret
171+
updatedSecret, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Get(context.Background(), secret.Name, metav1.GetOptions{})
172+
require.NoError(t, err)
173+
data := updatedSecret.Data
174+
for _, v := range data {
175+
require.False(t, strings.HasPrefix(string(v), t.Name()), fmt.Sprintf("secret %s was unexpectedly reconciled", secret.String()))
176+
}
177+
}
178+
}
179+
}
180+
181+
func isSecretDataJSONEncoded(secret *v1.Secret) bool {
182+
return secret.Type == v1.SecretTypeDockercfg || secret.Type == v1.SecretTypeDockerConfigJson
183+
}

0 commit comments

Comments
 (0)