Skip to content

Commit 7da93cf

Browse files
committed
Tests for ssl_groups and ssl_ecdh_curve config parameters.
1 parent d463f12 commit 7da93cf

File tree

3 files changed

+429
-1
lines changed

3 files changed

+429
-1
lines changed
Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
// Copyright 2021 - 2025 Crunchy Data Solutions, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package validation
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"testing"
11+
12+
"gotest.tools/v3/assert"
13+
apierrors "k8s.io/apimachinery/pkg/api/errors"
14+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/yaml"
17+
18+
"github.com/crunchydata/postgres-operator/internal/testing/cmp"
19+
"github.com/crunchydata/postgres-operator/internal/testing/require"
20+
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
21+
)
22+
23+
func TestPostgresConfigParametersV1beta1(t *testing.T) {
24+
ctx := context.Background()
25+
cc := require.Kubernetes(t)
26+
t.Parallel()
27+
28+
namespace := require.Namespace(t, cc)
29+
base := v1beta1.NewPostgresCluster()
30+
31+
// required fields
32+
require.UnmarshalInto(t, &base.Spec, `{
33+
postgresVersion: 16,
34+
instances: [{
35+
dataVolumeClaimSpec: {
36+
accessModes: [ReadWriteOnce],
37+
resources: { requests: { storage: 1Mi } },
38+
},
39+
}],
40+
}`)
41+
42+
base.Spec.Backups = v1beta1.Backups{
43+
PGBackRest: v1beta1.PGBackRestArchive{
44+
Repos: []v1beta1.PGBackRestRepo{{Name: "repo1"}},
45+
},
46+
}
47+
base.Namespace = namespace.Name
48+
base.Name = "postgres-config-parameters"
49+
50+
assert.NilError(t, cc.Create(ctx, base.DeepCopy(), client.DryRunAll),
51+
"expected this base cluster to be valid")
52+
53+
var u unstructured.Unstructured
54+
require.UnmarshalInto(t, &u, require.Value(yaml.Marshal(base)))
55+
assert.Equal(t, u.GetAPIVersion(), "postgres-operator.crunchydata.com/v1beta1")
56+
57+
testPostgresConfigParametersCommon(t, cc, u)
58+
59+
t.Run("Logging", func(t *testing.T) {
60+
t.Run("Allowed", func(t *testing.T) {
61+
for _, tt := range []struct {
62+
key string
63+
value any
64+
}{
65+
{key: "log_directory", value: "anything"},
66+
} {
67+
t.Run(tt.key, func(t *testing.T) {
68+
cluster := u.DeepCopy()
69+
require.UnmarshalIntoField(t, cluster,
70+
require.Value(yaml.Marshal(tt.value)),
71+
"spec", "config", "parameters", tt.key)
72+
73+
assert.NilError(t, cc.Create(ctx, cluster, client.DryRunAll))
74+
})
75+
}
76+
})
77+
})
78+
79+
t.Run("ssl_groups and ssl_ecdh_curve", func(t *testing.T) {
80+
t.Run("ssl_groups not allowed for pg17", func(t *testing.T) {
81+
for _, tt := range []struct {
82+
key string
83+
value any
84+
}{
85+
{key: "ssl_groups", value: "anything"},
86+
} {
87+
t.Run(tt.key, func(t *testing.T) {
88+
cluster := u.DeepCopy()
89+
require.UnmarshalIntoField(t, cluster,
90+
require.Value(yaml.Marshal(17)),
91+
"spec", "postgresVersion")
92+
require.UnmarshalIntoField(t, cluster,
93+
require.Value(yaml.Marshal(tt.value)),
94+
"spec", "config", "parameters", tt.key)
95+
96+
err := cc.Create(ctx, cluster, client.DryRunAll)
97+
assert.Assert(t, apierrors.IsInvalid(err))
98+
99+
details := require.StatusErrorDetails(t, err)
100+
assert.Assert(t, cmp.Len(details.Causes, 1))
101+
})
102+
}
103+
})
104+
105+
t.Run("ssl_groups allowed for pg18", func(t *testing.T) {
106+
for _, tt := range []struct {
107+
key string
108+
value any
109+
}{
110+
{key: "ssl_groups", value: "anything"},
111+
} {
112+
t.Run(tt.key, func(t *testing.T) {
113+
cluster := u.DeepCopy()
114+
require.UnmarshalIntoField(t, cluster,
115+
require.Value(yaml.Marshal(18)),
116+
"spec", "postgresVersion")
117+
require.UnmarshalIntoField(t, cluster,
118+
require.Value(yaml.Marshal(tt.value)),
119+
"spec", "config", "parameters", tt.key)
120+
121+
assert.NilError(t, cc.Create(ctx, cluster, client.DryRunAll))
122+
})
123+
}
124+
})
125+
126+
t.Run("ssl_ecdh_curve allowed for both", func(t *testing.T) {
127+
for _, tt := range []struct {
128+
key string
129+
value any
130+
}{
131+
{key: "ssl_ecdh_curve", value: "anything"},
132+
} {
133+
t.Run(tt.key, func(t *testing.T) {
134+
cluster := u.DeepCopy()
135+
require.UnmarshalIntoField(t, cluster,
136+
require.Value(yaml.Marshal(17)),
137+
"spec", "postgresVersion")
138+
require.UnmarshalIntoField(t, cluster,
139+
require.Value(yaml.Marshal(tt.value)),
140+
"spec", "config", "parameters", tt.key)
141+
142+
assert.NilError(t, cc.Create(ctx, cluster, client.DryRunAll))
143+
144+
cluster2 := u.DeepCopy()
145+
require.UnmarshalIntoField(t, cluster2,
146+
require.Value(yaml.Marshal(18)),
147+
"spec", "postgresVersion")
148+
require.UnmarshalIntoField(t, cluster2,
149+
require.Value(yaml.Marshal(tt.value)),
150+
"spec", "config", "parameters", tt.key)
151+
152+
assert.NilError(t, cc.Create(ctx, cluster2, client.DryRunAll))
153+
})
154+
}
155+
})
156+
157+
t.Run("other ssl_* parameters not allowed for any pg version", func(t *testing.T) {
158+
for _, tt := range []struct {
159+
key string
160+
value any
161+
}{
162+
{key: "ssl_anything", value: "anything"},
163+
} {
164+
t.Run(tt.key, func(t *testing.T) {
165+
cluster := u.DeepCopy()
166+
require.UnmarshalIntoField(t, cluster,
167+
require.Value(yaml.Marshal(17)),
168+
"spec", "postgresVersion")
169+
require.UnmarshalIntoField(t, cluster,
170+
require.Value(yaml.Marshal(tt.value)),
171+
"spec", "config", "parameters", tt.key)
172+
173+
err := cc.Create(ctx, cluster, client.DryRunAll)
174+
assert.Assert(t, apierrors.IsInvalid(err))
175+
176+
details := require.StatusErrorDetails(t, err)
177+
assert.Assert(t, cmp.Len(details.Causes, 1))
178+
179+
cluster1 := u.DeepCopy()
180+
require.UnmarshalIntoField(t, cluster1,
181+
require.Value(yaml.Marshal(18)),
182+
"spec", "postgresVersion")
183+
require.UnmarshalIntoField(t, cluster1,
184+
require.Value(yaml.Marshal(tt.value)),
185+
"spec", "config", "parameters", tt.key)
186+
187+
err = cc.Create(ctx, cluster1, client.DryRunAll)
188+
assert.Assert(t, apierrors.IsInvalid(err))
189+
190+
details = require.StatusErrorDetails(t, err)
191+
assert.Assert(t, cmp.Len(details.Causes, 1))
192+
})
193+
}
194+
})
195+
})
196+
}
197+
198+
func testPostgresConfigParametersCommon(t *testing.T, cc client.Client, base unstructured.Unstructured) {
199+
ctx := context.Background()
200+
201+
t.Run("Allowed", func(t *testing.T) {
202+
for _, tt := range []struct {
203+
key string
204+
value any
205+
}{
206+
{"archive_timeout", 100},
207+
{"archive_timeout", "20s"},
208+
} {
209+
t.Run(tt.key, func(t *testing.T) {
210+
cluster := base.DeepCopy()
211+
require.UnmarshalIntoField(t, cluster,
212+
require.Value(yaml.Marshal(tt.value)),
213+
"spec", "config", "parameters", tt.key)
214+
215+
assert.NilError(t, cc.Create(ctx, cluster, client.DryRunAll))
216+
})
217+
}
218+
})
219+
220+
t.Run("Disallowed", func(t *testing.T) {
221+
for _, tt := range []struct {
222+
key string
223+
value any
224+
}{
225+
{key: "cluster_name", value: "asdf"},
226+
{key: "config_file", value: "asdf"},
227+
{key: "data_directory", value: ""},
228+
{key: "external_pid_file", value: ""},
229+
{key: "hba_file", value: "one"},
230+
{key: "hot_standby", value: "off"},
231+
{key: "ident_file", value: "two"},
232+
{key: "listen_addresses", value: ""},
233+
{key: "port", value: 5},
234+
{key: "wal_log_hints", value: "off"},
235+
} {
236+
t.Run(tt.key, func(t *testing.T) {
237+
cluster := base.DeepCopy()
238+
require.UnmarshalIntoField(t, cluster,
239+
require.Value(yaml.Marshal(tt.value)),
240+
"spec", "config", "parameters", tt.key)
241+
242+
err := cc.Create(ctx, cluster, client.DryRunAll)
243+
assert.Assert(t, apierrors.IsInvalid(err))
244+
245+
details := require.StatusErrorDetails(t, err)
246+
assert.Assert(t, cmp.Len(details.Causes, 1))
247+
248+
// TODO(k8s-1.30) TODO(validation): Move the parameter name from the message to the field path.
249+
assert.Equal(t, details.Causes[0].Field, "spec.config.parameters")
250+
assert.Assert(t, cmp.Contains(details.Causes[0].Message, tt.key))
251+
})
252+
}
253+
})
254+
255+
t.Run("Logging", func(t *testing.T) {
256+
for _, tt := range []struct {
257+
valid bool
258+
key string
259+
value any
260+
message string
261+
}{
262+
{valid: false, key: "log_file_mode", value: "", message: "cannot be changed"},
263+
{valid: false, key: "log_file_mode", value: "any", message: "cannot be changed"},
264+
{valid: false, key: "logging_collector", value: "", message: "unsafe"},
265+
{valid: false, key: "logging_collector", value: "off", message: "unsafe"},
266+
{valid: false, key: "logging_collector", value: "on", message: "unsafe"},
267+
268+
{valid: true, key: "log_destination", value: "anything"},
269+
{valid: true, key: "log_filename", value: "anything"},
270+
{valid: true, key: "log_filename", value: "percent-%s-too"},
271+
{valid: true, key: "log_rotation_age", value: "7d"},
272+
{valid: true, key: "log_rotation_age", value: 5},
273+
{valid: true, key: "log_rotation_size", value: "100MB"},
274+
{valid: true, key: "log_rotation_size", value: 13},
275+
{valid: true, key: "log_timezone", value: ""},
276+
{valid: true, key: "log_timezone", value: "nonsense"},
277+
} {
278+
t.Run(fmt.Sprint(tt), func(t *testing.T) {
279+
cluster := base.DeepCopy()
280+
require.UnmarshalIntoField(t, cluster,
281+
require.Value(yaml.Marshal(tt.value)),
282+
"spec", "config", "parameters", tt.key)
283+
284+
err := cc.Create(ctx, cluster, client.DryRunAll)
285+
286+
if tt.valid {
287+
assert.NilError(t, err)
288+
assert.Equal(t, "", tt.message, "BUG IN TEST: no message expected when valid")
289+
} else {
290+
assert.Assert(t, apierrors.IsInvalid(err))
291+
292+
details := require.StatusErrorDetails(t, err)
293+
assert.Assert(t, cmp.Len(details.Causes, 1))
294+
295+
// TODO(k8s-1.30) TODO(validation): Move the parameter name from the message to the field path.
296+
assert.Equal(t, details.Causes[0].Field, "spec.config.parameters")
297+
assert.Assert(t, cmp.Contains(details.Causes[0].Message, tt.key))
298+
assert.Assert(t, cmp.Contains(details.Causes[0].Message, tt.message))
299+
}
300+
})
301+
}
302+
})
303+
304+
t.Run("NoConnections", func(t *testing.T) {
305+
for _, tt := range []struct {
306+
key string
307+
value any
308+
}{
309+
{key: "ssl", value: "off"},
310+
{key: "ssl_ca_file", value: ""},
311+
{key: "unix_socket_directories", value: "one"},
312+
{key: "unix_socket_group", value: "two"},
313+
} {
314+
t.Run(tt.key, func(t *testing.T) {
315+
cluster := base.DeepCopy()
316+
require.UnmarshalIntoField(t, cluster,
317+
require.Value(yaml.Marshal(tt.value)),
318+
"spec", "config", "parameters", tt.key)
319+
320+
err := cc.Create(ctx, cluster, client.DryRunAll)
321+
assert.Assert(t, apierrors.IsInvalid(err))
322+
})
323+
}
324+
})
325+
326+
t.Run("NoWriteAheadLog", func(t *testing.T) {
327+
for _, tt := range []struct {
328+
key string
329+
value any
330+
}{
331+
{key: "archive_mode", value: "off"},
332+
{key: "archive_command", value: "true"},
333+
{key: "restore_command", value: "true"},
334+
{key: "recovery_target", value: "immediate"},
335+
{key: "recovery_target_name", value: "doot"},
336+
} {
337+
t.Run(tt.key, func(t *testing.T) {
338+
cluster := base.DeepCopy()
339+
require.UnmarshalIntoField(t, cluster,
340+
require.Value(yaml.Marshal(tt.value)),
341+
"spec", "config", "parameters", tt.key)
342+
343+
err := cc.Create(ctx, cluster, client.DryRunAll)
344+
assert.Assert(t, apierrors.IsInvalid(err))
345+
})
346+
}
347+
})
348+
349+
t.Run("wal_level", func(t *testing.T) {
350+
t.Run("Valid", func(t *testing.T) {
351+
cluster := base.DeepCopy()
352+
require.UnmarshalIntoField(t, cluster,
353+
`logical`, "spec", "config", "parameters", "wal_level")
354+
355+
assert.NilError(t, cc.Create(ctx, cluster, client.DryRunAll))
356+
})
357+
358+
t.Run("Invalid", func(t *testing.T) {
359+
cluster := base.DeepCopy()
360+
require.UnmarshalIntoField(t, cluster,
361+
`minimal`, "spec", "config", "parameters", "wal_level")
362+
363+
err := cc.Create(ctx, cluster, client.DryRunAll)
364+
assert.Assert(t, apierrors.IsInvalid(err))
365+
assert.ErrorContains(t, err, `"replica" or higher`)
366+
367+
details := require.StatusErrorDetails(t, err)
368+
assert.Assert(t, cmp.Len(details.Causes, 1))
369+
assert.Equal(t, details.Causes[0].Field, "spec.config.parameters")
370+
assert.Assert(t, cmp.Contains(details.Causes[0].Message, "wal_level"))
371+
})
372+
})
373+
374+
t.Run("NoReplication", func(t *testing.T) {
375+
for _, tt := range []struct {
376+
key string
377+
value any
378+
}{
379+
{key: "synchronous_standby_names", value: ""},
380+
{key: "primary_conninfo", value: ""},
381+
{key: "primary_slot_name", value: ""},
382+
{key: "recovery_min_apply_delay", value: ""},
383+
} {
384+
t.Run(tt.key, func(t *testing.T) {
385+
cluster := base.DeepCopy()
386+
require.UnmarshalIntoField(t, cluster,
387+
require.Value(yaml.Marshal(tt.value)),
388+
"spec", "config", "parameters", tt.key)
389+
390+
err := cc.Create(ctx, cluster, client.DryRunAll)
391+
assert.Assert(t, apierrors.IsInvalid(err))
392+
})
393+
}
394+
})
395+
}

0 commit comments

Comments
 (0)