Skip to content

Commit d5b34d6

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

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed

test/e2e/reconcile_objects_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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-config",
50+
Namespace: f.UserWorkloadMonitoringNs,
51+
},
52+
{
53+
Name: "thanos-ruler-user-workload-web-config",
54+
Namespace: f.UserWorkloadMonitoringNs,
55+
},
56+
}
57+
58+
// Restore all unsynced secrets to their original state.
59+
cleanup := func() {
60+
for _, secret := range unsyncedSecrets {
61+
gotSecret, err := f.KubeClient.CoreV1().Secrets(secret.Namespace).Get(context.Background(), secret.Name, metav1.GetOptions{})
62+
if errors.IsNotFound(err) {
63+
continue
64+
}
65+
require.NoError(t, err)
66+
data := gotSecret.Data
67+
for k, v := range data {
68+
data[k] = []byte(strings.TrimPrefix(string(v), t.Name()))
69+
}
70+
_, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Update(context.Background(), gotSecret, metav1.UpdateOptions{})
71+
require.NoError(t, err)
72+
}
73+
}
74+
defer cleanup()
75+
76+
var syncedSecrets []types.NamespacedName
77+
secretsNS, err := f.KubeClient.CoreV1().Secrets(f.Ns).List(context.Background(), metav1.ListOptions{
78+
// Intentionally commented out as we want to fetch all secrets.
79+
// LabelSelector: "app.kubernetes.io/managed-by=cluster-monitoring-operator",
80+
})
81+
require.NoError(t, err)
82+
83+
secretsUWMNS, err := f.KubeClient.CoreV1().Secrets(f.UserWorkloadMonitoringNs).List(context.Background(), metav1.ListOptions{
84+
// Intentionally commented out as we want to fetch all secrets.
85+
// LabelSelector: "app.kubernetes.io/managed-by=cluster-monitoring-operator",
86+
})
87+
require.NoError(t, err)
88+
89+
for _, secret := range append(secretsNS.Items, secretsUWMNS.Items...) {
90+
secretNamespacedName := types.NamespacedName{
91+
Name: secret.Name,
92+
Namespace: secret.Namespace,
93+
}
94+
if slices.Contains(unsyncedSecrets, secretNamespacedName) {
95+
continue
96+
}
97+
syncedSecrets = append(syncedSecrets, secretNamespacedName)
98+
}
99+
require.NotEmpty(t, syncedSecrets)
100+
101+
secrets := append(syncedSecrets, unsyncedSecrets...)
102+
103+
var filteredSecrets []types.NamespacedName
104+
for _, secret := range secrets {
105+
if strings.Contains(secret.Name, "tls") {
106+
continue
107+
}
108+
filteredSecrets = append(filteredSecrets, secret)
109+
}
110+
111+
// Update the aforementioned secrets' data.
112+
for _, secret := range filteredSecrets {
113+
var gotSecret *v1.Secret
114+
gotSecret, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Get(context.Background(), secret.Name, metav1.GetOptions{})
115+
require.NoError(t, err)
116+
data := gotSecret.Data
117+
for k, v := range data {
118+
if isSecretDataJSONEncoded(gotSecret) {
119+
var jsonData map[string]interface{}
120+
err = json.Unmarshal(v, &jsonData)
121+
require.NoError(t, err)
122+
jsonData[t.Name()] = t.Name()
123+
v, err = json.Marshal(jsonData)
124+
require.NoError(t, err)
125+
data[k] = v
126+
} else {
127+
data[k] = []byte(t.Name() + string(v))
128+
}
129+
break
130+
}
131+
132+
_, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Update(context.Background(), gotSecret, metav1.UpdateOptions{})
133+
require.NoError(t, err)
134+
}
135+
136+
// Check for reconciliation of secrets.
137+
for _, secret := range filteredSecrets {
138+
// Check if the secrets were reconciled as expected.
139+
if slices.Contains(syncedSecrets, secret) {
140+
err = framework.Poll(10*time.Second, 5*time.Minute, func() error {
141+
var updatedSecret *v1.Secret
142+
updatedSecret, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Get(context.Background(), secret.Name, metav1.GetOptions{})
143+
if err != nil {
144+
return err
145+
}
146+
147+
data := updatedSecret.Data
148+
for _, v := range data {
149+
if !isSecretDataJSONEncoded(updatedSecret) {
150+
if strings.HasPrefix(string(v), t.Name()) {
151+
return fmt.Errorf("secret %s has unexpected data: %v", secret.String(), string(v))
152+
}
153+
return nil
154+
}
155+
156+
var jsonData map[string]interface{}
157+
err = json.Unmarshal(v, &jsonData)
158+
if err != nil {
159+
return fmt.Errorf("failed to unmarshal JSON data in secret %s: %v", secret.String(), err)
160+
}
161+
if _, ok := jsonData[t.Name()]; ok {
162+
return fmt.Errorf("secret %s does contains unexpected key %s", secret.String(), t.Name())
163+
}
164+
}
165+
166+
return nil
167+
})
168+
169+
require.NoError(t, err)
170+
}
171+
172+
// Check if the secrets were reconciled unexpectedly.
173+
if slices.Contains(unsyncedSecrets, secret) {
174+
var updatedSecret *v1.Secret
175+
updatedSecret, err = f.KubeClient.CoreV1().Secrets(secret.Namespace).Get(context.Background(), secret.Name, metav1.GetOptions{})
176+
require.NoError(t, err)
177+
data := updatedSecret.Data
178+
for _, v := range data {
179+
require.False(t, strings.HasPrefix(string(v), t.Name()), fmt.Sprintf("secret %s was unexpectedly reconciled", secret.String()))
180+
}
181+
}
182+
}
183+
}
184+
185+
func isSecretDataJSONEncoded(secret *v1.Secret) bool {
186+
return secret.Type == v1.SecretTypeDockercfg || secret.Type == v1.SecretTypeDockerConfigJson
187+
}

0 commit comments

Comments
 (0)