Skip to content

Commit 6e6f3c6

Browse files
birdayzRafalKorepta
authored andcommitted
operator v1: normalize cluster config before drift detection comparison
cluster controller normalizes configs before sending them to admin-api, when writing. this patch makes use of the normalization when comparing for drift detection, to avoid unexpected drifts, for example if the CRD sets spec.additionalProperties.key to "null" (string).
1 parent b457ca1 commit 6e6f3c6

File tree

3 files changed

+93
-3
lines changed

3 files changed

+93
-3
lines changed

operator/internal/controller/vectorized/cluster_controller_configuration_drift.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,17 @@ func (r *ClusterConfigurationDriftReconciler) Reconcile(
181181
func hasDrift(log logr.Logger, desired, actual map[string]any, schema map[string]rpadmin.ConfigPropertyMetadata) (configuration.CentralConfigurationPatch, bool) {
182182
// Make copy of desired, actual, so callers not surprised that items are removed by this function.
183183
copiedDesired := make(map[string]any)
184+
185+
for k, v := range desired {
186+
s := schema[k]
187+
v := v
188+
189+
// Before sending cluster properties to admin-api, cluster controller "sanitizes" them.
190+
// Do the same for drift detection.
191+
copiedDesired[k] = configuration.ParseConfigValueBeforeUpsert(log, v, &s)
192+
}
193+
184194
copiedActual := make(map[string]any)
185-
maps.Copy(copiedDesired, desired)
186195
maps.Copy(copiedActual, actual)
187196

188197
for k, v := range schema { //nolint:gocritic // ignore rangeValCopy - this is the type returned by Admin API client.

operator/internal/controller/vectorized/cluster_controller_configuration_drift_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,93 @@
1010
package vectorized
1111

1212
import (
13+
"context"
14+
"fmt"
1315
"testing"
1416

1517
"github.com/go-logr/logr"
18+
"github.com/go-logr/logr/testr"
1619
"github.com/redpanda-data/common-go/rpadmin"
1720
"github.com/stretchr/testify/require"
21+
"github.com/testcontainers/testcontainers-go"
22+
"github.com/testcontainers/testcontainers-go/modules/redpanda"
23+
"sigs.k8s.io/controller-runtime/pkg/log"
1824
)
1925

26+
func TestDiffIntegration(t *testing.T) {
27+
const user = "syncer"
28+
const password = "password"
29+
const saslMechanism = "SCRAM-SHA-256"
30+
31+
ctx := context.Background()
32+
logger := testr.New(t)
33+
ctx = log.IntoContext(ctx, logger)
34+
35+
// No auth is easy, only test on a cluster with auth on admin API.
36+
container, err := redpanda.Run(
37+
ctx,
38+
"docker.redpanda.com/redpandadata/redpanda:v24.2.4",
39+
// TODO: Upgrade to testcontainers 0.33.0 so we get
40+
// WithBootstrapConfig. For whatever reason, it seems to not get along
41+
// with CI.
42+
// redpanda.WithBootstrapConfig("admin_api_require_auth", true),
43+
redpanda.WithSuperusers("syncer"),
44+
testcontainers.WithEnv(map[string]string{
45+
"RP_BOOTSTRAP_USER": fmt.Sprintf("%s:%s:%s", user, password, saslMechanism),
46+
}),
47+
)
48+
require.NoError(t, err)
49+
defer func() {
50+
_ = container.Stop(ctx, nil)
51+
}()
52+
53+
// Configure the same environment as we'll be executed in within the helm
54+
// chart:
55+
// https://github.com/redpanda-data/helm-charts/commit/081c08b6b83ba196994ec3312a7c6011e4ef0a22#diff-84c6555620e4e5f79262384a9fa3e8f4876b36bb3a64748cbd8fbdcb66e8c1b9R966
56+
t.Setenv("RPK_USER", user)
57+
t.Setenv("RPK_PASS", password)
58+
t.Setenv("RPK_SASL_MECHANISM", saslMechanism)
59+
60+
adminAPIAddr, err := container.AdminAPIAddress(ctx)
61+
require.NoError(t, err)
62+
63+
adminAPIClient, err := rpadmin.NewAdminAPI([]string{adminAPIAddr}, &rpadmin.BasicAuth{
64+
Username: user,
65+
Password: password,
66+
}, nil)
67+
require.NoError(t, err)
68+
69+
_, err = adminAPIClient.PatchClusterConfig(ctx, map[string]any{
70+
"kafka_rpc_server_tcp_send_buf": 102400,
71+
}, []string{})
72+
require.NoError(t, err)
73+
74+
schema, err := adminAPIClient.ClusterConfigSchema(ctx)
75+
require.NoError(t, err)
76+
77+
config, err := adminAPIClient.Config(ctx, true)
78+
require.NoError(t, err)
79+
require.Equal(t, config["kafka_rpc_server_tcp_send_buf"].(float64), float64(102400))
80+
81+
_, drift := hasDrift(logr.Discard(), map[string]any{
82+
"kafka_rpc_server_tcp_send_buf": "null",
83+
}, config, schema)
84+
require.True(t, drift, `expecting to see a drift between integer in redpanda and "null" in desired configuration`)
85+
86+
_, err = adminAPIClient.PatchClusterConfig(ctx, map[string]any{
87+
"kafka_rpc_server_tcp_send_buf": nil,
88+
}, []string{})
89+
require.NoError(t, err)
90+
91+
config, err = adminAPIClient.Config(ctx, true)
92+
require.NoError(t, err)
93+
94+
_, drift = hasDrift(logr.Discard(), map[string]any{
95+
"kafka_rpc_server_tcp_send_buf": "null",
96+
}, config, schema)
97+
require.False(t, drift, `shall have no drift if we compare null against "null"`)
98+
}
99+
20100
func TestDiffWithNull(t *testing.T) {
21101
assert := require.New(t)
22102

operator/pkg/resources/configuration/patch.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func ThreeWayMerge(
8080
for k, v := range apply {
8181
if oldValue, ok := current[k]; !ok || !PropertiesEqual(log, v, oldValue, schema[k]) {
8282
metadata := schema[k]
83-
patch.Upsert[k] = parseConfigValueBeforeUpsert(log, v, &metadata)
83+
patch.Upsert[k] = ParseConfigValueBeforeUpsert(log, v, &metadata)
8484
}
8585
}
8686
invalidSet := make(map[string]bool, len(invalidProperties))
@@ -99,7 +99,7 @@ func ThreeWayMerge(
9999
return patch
100100
}
101101

102-
func parseConfigValueBeforeUpsert(log logr.Logger, value interface{}, metadata *rpadmin.ConfigPropertyMetadata) interface{} {
102+
func ParseConfigValueBeforeUpsert(log logr.Logger, value interface{}, metadata *rpadmin.ConfigPropertyMetadata) interface{} {
103103
tempValue := fmt.Sprintf("%v", value)
104104

105105
//nolint:gocritic // no need to be a switch case
@@ -145,6 +145,7 @@ func PropertiesEqual(
145145
l logr.Logger, v1, v2 interface{}, metadata rpadmin.ConfigPropertyMetadata,
146146
) bool {
147147
log := l.WithName("PropertiesEqual")
148+
148149
switch metadata.Type {
149150
case "number":
150151
if f1, f2, ok := bothFloat64(v1, v2); ok {

0 commit comments

Comments
 (0)