@@ -14,6 +14,7 @@ import (
1414 "sigs.k8s.io/controller-runtime/pkg/client"
1515 "sigs.k8s.io/yaml"
1616
17+ "github.com/crunchydata/postgres-operator/internal/controller/runtime"
1718 "github.com/crunchydata/postgres-operator/internal/testing/cmp"
1819 "github.com/crunchydata/postgres-operator/internal/testing/require"
1920 v1 "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1"
@@ -292,3 +293,207 @@ func TestAdditionalVolumes(t *testing.T) {
292293 assert .NilError (t , dryrun .Create (ctx , tmp .DeepCopy ()))
293294 })
294295}
296+
297+ func TestPostgresClusterInstrumentation (t * testing.T ) {
298+ ctx := context .Background ()
299+ cc := require .Kubernetes (t )
300+ t .Parallel ()
301+
302+ namespace := require .Namespace (t , cc )
303+ base := v1beta1 .NewPostgresCluster ()
304+ base .Namespace = namespace .Name
305+ base .Name = "postgres-instrumentation"
306+ require .UnmarshalInto (t , & base .Spec , `{
307+ postgresVersion: 16,
308+ instances: [{
309+ dataVolumeClaimSpec: {
310+ accessModes: [ReadWriteOnce],
311+ resources: { requests: { storage: 1Mi } },
312+ },
313+ }],
314+ }` )
315+
316+ assert .NilError (t , cc .Create (ctx , base .DeepCopy (), client .DryRunAll ),
317+ "expected this base to be valid" )
318+
319+ t .Run ("LogsBatches" , func (t * testing.T ) {
320+ t .Run ("Disable" , func (t * testing.T ) {
321+ for _ , tt := range []struct {
322+ batches string
323+ valid bool
324+ }{
325+ {valid : true , batches : `` }, // both null
326+ {valid : true , batches : `minRecords: 1` }, // one null
327+ {valid : true , batches : `maxDelay: 1s` }, // other null
328+
329+ {valid : false , batches : `minRecords: 0` }, // one zero
330+ {valid : false , batches : `maxDelay: 0m` }, // other zero
331+
332+ {valid : true , batches : `minRecords: 0, maxDelay: 0m` }, // both zero
333+ {valid : true , batches : `minRecords: 1, maxDelay: 1s` }, // both non-zero
334+ } {
335+ cluster := base .DeepCopy ()
336+ require .UnmarshalInto (t , & cluster .Spec .Instrumentation , `{
337+ logs: { batches: { ` + tt .batches + ` } }
338+ }` )
339+
340+ err := cc .Create (ctx , cluster , client .DryRunAll )
341+ if tt .valid {
342+ assert .NilError (t , err )
343+ } else {
344+ assert .Assert (t , apierrors .IsInvalid (err ))
345+ assert .ErrorContains (t , err , "disable" )
346+ assert .ErrorContains (t , err , "minRecords" )
347+ assert .ErrorContains (t , err , "maxDelay" )
348+
349+ details := require .StatusErrorDetails (t , err )
350+ assert .Assert (t , cmp .Len (details .Causes , 1 ))
351+
352+ for _ , cause := range details .Causes {
353+ assert .Equal (t , cause .Field , "spec.instrumentation.logs.batches" )
354+ assert .Assert (t , cmp .Contains (cause .Message , "disable batching" ))
355+ assert .Assert (t , cmp .Contains (cause .Message , "minRecords and maxDelay must be zero" ))
356+ }
357+ }
358+ }
359+ })
360+
361+ t .Run ("MaxDelay" , func (t * testing.T ) {
362+ cluster := base .DeepCopy ()
363+ require .UnmarshalInto (t , & cluster .Spec .Instrumentation , `{
364+ logs: {
365+ batches: { maxDelay: 100min },
366+ },
367+ }` )
368+
369+ err := cc .Create (ctx , cluster , client .DryRunAll )
370+ assert .Assert (t , apierrors .IsInvalid (err ))
371+ assert .ErrorContains (t , err , "maxDelay" )
372+ assert .ErrorContains (t , err , "5m" )
373+
374+ details := require .StatusErrorDetails (t , err )
375+ assert .Assert (t , cmp .Len (details .Causes , 1 ))
376+
377+ for _ , cause := range details .Causes {
378+ assert .Equal (t , cause .Field , "spec.instrumentation.logs.batches.maxDelay" )
379+ }
380+ })
381+
382+ t .Run ("MinMaxRecords" , func (t * testing.T ) {
383+ cluster := base .DeepCopy ()
384+ require .UnmarshalInto (t , & cluster .Spec .Instrumentation , `{
385+ logs: {
386+ batches: { minRecords: -11, maxRecords: 0 },
387+ },
388+ }` )
389+
390+ err := cc .Create (ctx , cluster , client .DryRunAll )
391+ assert .Assert (t , apierrors .IsInvalid (err ))
392+ assert .ErrorContains (t , err , "minRecords" )
393+ assert .ErrorContains (t , err , "greater than or equal to 0" )
394+ assert .ErrorContains (t , err , "maxRecords" )
395+ assert .ErrorContains (t , err , "greater than or equal to 1" )
396+
397+ details := require .StatusErrorDetails (t , err )
398+ assert .Assert (t , cmp .Len (details .Causes , 2 ))
399+
400+ for _ , cause := range details .Causes {
401+ switch cause .Field {
402+ case "spec.instrumentation.logs.batches.maxRecords" :
403+ assert .Assert (t , cmp .Contains (cause .Message , "0" ))
404+ assert .Assert (t , cmp .Contains (cause .Message , "greater than or equal to 1" ))
405+
406+ case "spec.instrumentation.logs.batches.minRecords" :
407+ assert .Assert (t , cmp .Contains (cause .Message , "-11" ))
408+ assert .Assert (t , cmp .Contains (cause .Message , "greater than or equal to 0" ))
409+ }
410+ }
411+
412+ t .Run ("Reversed" , func (t * testing.T ) {
413+ for _ , batches := range []string {
414+ `maxRecords: 99` , // default minRecords
415+ `minRecords: 99, maxRecords: 21` , //
416+ } {
417+ cluster := base .DeepCopy ()
418+ require .UnmarshalInto (t , & cluster .Spec .Instrumentation , `{
419+ logs: {
420+ batches: { ` + batches + ` },
421+ },
422+ }` )
423+
424+ err := cc .Create (ctx , cluster , client .DryRunAll )
425+ assert .Assert (t , apierrors .IsInvalid (err ))
426+ assert .ErrorContains (t , err , "minRecords" )
427+ assert .ErrorContains (t , err , "maxRecords" )
428+
429+ details := require .StatusErrorDetails (t , err )
430+ assert .Assert (t , cmp .Len (details .Causes , 1 ))
431+
432+ for _ , cause := range details .Causes {
433+ assert .Equal (t , cause .Field , "spec.instrumentation.logs.batches" )
434+ assert .Assert (t , cmp .Contains (cause .Message , "minRecords cannot be larger than maxRecords" ))
435+ }
436+ }
437+ })
438+ })
439+ })
440+
441+ t .Run ("LogsRetentionPeriod" , func (t * testing.T ) {
442+ cluster := base .DeepCopy ()
443+ require .UnmarshalInto (t , & cluster .Spec , `{
444+ instrumentation: {
445+ logs: { retentionPeriod: 5m },
446+ },
447+ }` )
448+
449+ err := cc .Create (ctx , cluster , client .DryRunAll )
450+ assert .Assert (t , apierrors .IsInvalid (err ))
451+ assert .ErrorContains (t , err , "retentionPeriod" )
452+ assert .ErrorContains (t , err , "hour|day|week" )
453+ assert .ErrorContains (t , err , "one hour" )
454+
455+ details := require .StatusErrorDetails (t , err )
456+ assert .Assert (t , cmp .Len (details .Causes , 2 ))
457+
458+ for _ , cause := range details .Causes {
459+ assert .Equal (t , cause .Field , "spec.instrumentation.logs.retentionPeriod" )
460+ }
461+
462+ t .Run ("Valid" , func (t * testing.T ) {
463+ for _ , tt := range []string {
464+ "28 weeks" ,
465+ "90 DAY" ,
466+ "1 hr" ,
467+ "PT1D2H" ,
468+ "1 week 2 days" ,
469+ } {
470+ u , err := runtime .ToUnstructuredObject (cluster )
471+ assert .NilError (t , err )
472+ assert .NilError (t , unstructured .SetNestedField (u .Object ,
473+ tt , "spec" , "instrumentation" , "logs" , "retentionPeriod" ))
474+
475+ assert .NilError (t , cc .Create (ctx , u , client .DryRunAll ), tt )
476+ }
477+ })
478+
479+ t .Run ("Invalid" , func (t * testing.T ) {
480+ for _ , tt := range []string {
481+ // Amount too small
482+ "0 days" ,
483+ "0" ,
484+
485+ // Text too long
486+ "2 weeks 3 days 4 hours" ,
487+ } {
488+ u , err := runtime .ToUnstructuredObject (cluster )
489+ assert .NilError (t , err )
490+ assert .NilError (t , unstructured .SetNestedField (u .Object ,
491+ tt , "spec" , "instrumentation" , "logs" , "retentionPeriod" ))
492+
493+ err = cc .Create (ctx , u , client .DryRunAll )
494+ assert .Assert (t , apierrors .IsInvalid (err ), tt )
495+ assert .ErrorContains (t , err , "retentionPeriod" )
496+ }
497+ })
498+ })
499+ }
0 commit comments