Skip to content

Commit 7d022ad

Browse files
Merge pull request #2271 from zimnx/scylladbcluster-webhook
Add ScyllaDBCluster webhook validation
2 parents 4a18da8 + 99a26d1 commit 7d022ad

File tree

11 files changed

+4081
-32
lines changed

11 files changed

+4081
-32
lines changed

deploy/operator.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46538,6 +46538,7 @@ webhooks:
4653846538
- nodeconfigs
4653946539
- scyllaoperatorconfigs
4654046540
- scylladbdatacenters
46541+
- scylladbclusters
4654146542

4654246543
---
4654346544
apiVersion: policy/v1

deploy/operator/10_validatingwebhook.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ webhooks:
3636
- nodeconfigs
3737
- scyllaoperatorconfigs
3838
- scylladbdatacenters
39+
- scylladbclusters

helm/scylla-operator/templates/validatingwebhook.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ webhooks:
3636
- nodeconfigs
3737
- scyllaoperatorconfigs
3838
- scylladbdatacenters
39+
- scylladbclusters

pkg/api/scylla/validation/scylladbcluster_validation.go

Lines changed: 424 additions & 0 deletions
Large diffs are not rendered by default.

pkg/api/scylla/validation/scylladbcluster_validation_test.go

Lines changed: 3096 additions & 0 deletions
Large diffs are not rendered by default.

pkg/api/scylla/validation/scylladbdatacenter_validation.go

Lines changed: 88 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
scyllav1alpha1 "github.com/scylladb/scylla-operator/pkg/api/scylla/v1alpha1"
1313
"github.com/scylladb/scylla-operator/pkg/helpers/slices"
1414
"github.com/scylladb/scylla-operator/pkg/pointer"
15+
corevalidation "github.com/scylladb/scylla-operator/pkg/thirdparty/k8s.io/kubernetes/pkg/apis/core/validation"
1516
"k8s.io/apimachinery/pkg/api/resource"
1617
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
1718
apimachinerymetav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
@@ -26,6 +27,27 @@ var (
2627
scyllav1alpha1.BroadcastAddressTypeServiceClusterIP,
2728
scyllav1alpha1.BroadcastAddressTypeServiceLoadBalancerIngress,
2829
}
30+
31+
allowedNodeServiceTypesByBroadcastAddressType = map[scyllav1alpha1.BroadcastAddressType][]scyllav1alpha1.NodeServiceType{
32+
scyllav1alpha1.BroadcastAddressTypeServiceClusterIP: {
33+
scyllav1alpha1.NodeServiceTypeClusterIP,
34+
scyllav1alpha1.NodeServiceTypeLoadBalancer,
35+
},
36+
scyllav1alpha1.BroadcastAddressTypePodIP: {
37+
scyllav1alpha1.NodeServiceTypeHeadless,
38+
scyllav1alpha1.NodeServiceTypeClusterIP,
39+
scyllav1alpha1.NodeServiceTypeLoadBalancer,
40+
},
41+
scyllav1alpha1.BroadcastAddressTypeServiceLoadBalancerIngress: {
42+
scyllav1alpha1.NodeServiceTypeLoadBalancer,
43+
},
44+
}
45+
46+
supportedNodeServiceTypes = []scyllav1alpha1.NodeServiceType{
47+
scyllav1alpha1.NodeServiceTypeHeadless,
48+
scyllav1alpha1.NodeServiceTypeClusterIP,
49+
scyllav1alpha1.NodeServiceTypeLoadBalancer,
50+
}
2951
)
3052

3153
func ValidateScyllaDBDatacenter(sdc *scyllav1alpha1.ScyllaDBDatacenter) field.ErrorList {
@@ -87,38 +109,56 @@ func ValidateScyllaDBDatacenterRackTemplate(rackTemplate *scyllav1alpha1.RackTem
87109
}
88110

89111
if rackTemplate.ScyllaDB != nil {
90-
if rackTemplate.ScyllaDB.Storage != nil {
91-
if rackTemplate.ScyllaDB.Storage.Metadata != nil {
92-
allErrs = append(allErrs, apimachinerymetav1validation.ValidateLabels(rackTemplate.ScyllaDB.Storage.Metadata.Labels, fldPath.Child("scyllaDB", "storage", "metadata", "labels"))...)
93-
allErrs = append(allErrs, apimachineryvalidation.ValidateAnnotations(rackTemplate.ScyllaDB.Storage.Metadata.Annotations, fldPath.Child("scyllaDB", "storage", "metadata", "annotations"))...)
94-
}
112+
allErrs = append(allErrs, ValidateScyllaDBDatacenterScyllaDBTemplate(rackTemplate.ScyllaDB, fldPath.Child("scyllaDB"))...)
113+
}
95114

96-
storageCapacity, err := resource.ParseQuantity(rackTemplate.ScyllaDB.Storage.Capacity)
97-
if err != nil {
98-
allErrs = append(allErrs, field.Invalid(fldPath.Child("scyllaDB", "storage", "capacity"), rackTemplate.ScyllaDB.Storage.Capacity, fmt.Sprintf("unable to parse capacity: %v", err)))
99-
} else if storageCapacity.CmpInt64(0) <= 0 {
100-
allErrs = append(allErrs, field.Invalid(fldPath.Child("scyllaDB", "storage", "capacity"), rackTemplate.ScyllaDB.Storage.Capacity, "must be greater than zero"))
101-
}
115+
if rackTemplate.ScyllaDBManagerAgent != nil {
116+
allErrs = append(allErrs, ValidateScyllaDBDatacenterScyllaDBManagerAgentTemplate(rackTemplate.ScyllaDBManagerAgent, fldPath.Child("scyllaDBManagerAgent"))...)
117+
}
102118

103-
if rackTemplate.ScyllaDB.Storage.StorageClassName != nil {
104-
for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(*rackTemplate.ScyllaDB.Storage.StorageClassName, false) {
105-
allErrs = append(allErrs, field.Invalid(fldPath.Child("scyllaDB", "storage", "storageClassName"), *rackTemplate.ScyllaDB.Storage.StorageClassName, msg))
106-
}
107-
}
119+
// TODO: Add placement validation in >=v1alpha2
120+
121+
return allErrs
122+
}
123+
124+
func ValidateScyllaDBDatacenterScyllaDBManagerAgentTemplate(scyllaDBManagerAgentTemplate *scyllav1alpha1.ScyllaDBManagerAgentTemplate, fldPath *field.Path) field.ErrorList {
125+
allErrs := field.ErrorList{}
126+
127+
if scyllaDBManagerAgentTemplate.CustomConfigSecretRef != nil {
128+
for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(*scyllaDBManagerAgentTemplate.CustomConfigSecretRef, false) {
129+
allErrs = append(allErrs, field.Invalid(fldPath.Child("customConfigSecretRef"), *scyllaDBManagerAgentTemplate.CustomConfigSecretRef, msg))
130+
}
131+
}
132+
133+
return allErrs
134+
}
135+
136+
func ValidateScyllaDBDatacenterScyllaDBTemplate(scyllaDBTemplate *scyllav1alpha1.ScyllaDBTemplate, fldPath *field.Path) field.ErrorList {
137+
allErrs := field.ErrorList{}
138+
139+
if scyllaDBTemplate.Storage != nil {
140+
if scyllaDBTemplate.Storage.Metadata != nil {
141+
allErrs = append(allErrs, apimachinerymetav1validation.ValidateLabels(scyllaDBTemplate.Storage.Metadata.Labels, fldPath.Child("storage", "metadata", "labels"))...)
142+
allErrs = append(allErrs, apimachineryvalidation.ValidateAnnotations(scyllaDBTemplate.Storage.Metadata.Annotations, fldPath.Child("storage", "metadata", "annotations"))...)
108143
}
109144

110-
if rackTemplate.ScyllaDB.CustomConfigMapRef != nil {
111-
for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(*rackTemplate.ScyllaDB.CustomConfigMapRef, false) {
112-
allErrs = append(allErrs, field.Invalid(fldPath.Child("scyllaDB", "customConfigMapRef"), *rackTemplate.ScyllaDB.CustomConfigMapRef, msg))
145+
storageCapacity, err := resource.ParseQuantity(scyllaDBTemplate.Storage.Capacity)
146+
if err != nil {
147+
allErrs = append(allErrs, field.Invalid(fldPath.Child("storage", "capacity"), scyllaDBTemplate.Storage.Capacity, fmt.Sprintf("unable to parse capacity: %v", err)))
148+
} else if storageCapacity.CmpInt64(0) <= 0 {
149+
allErrs = append(allErrs, field.Invalid(fldPath.Child("storage", "capacity"), scyllaDBTemplate.Storage.Capacity, "must be greater than zero"))
150+
}
151+
152+
if scyllaDBTemplate.Storage.StorageClassName != nil {
153+
for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(*scyllaDBTemplate.Storage.StorageClassName, false) {
154+
allErrs = append(allErrs, field.Invalid(fldPath.Child("storage", "storageClassName"), *scyllaDBTemplate.Storage.StorageClassName, msg))
113155
}
114156
}
115157
}
116158

117-
if rackTemplate.ScyllaDBManagerAgent != nil {
118-
if rackTemplate.ScyllaDBManagerAgent.CustomConfigSecretRef != nil {
119-
for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(*rackTemplate.ScyllaDBManagerAgent.CustomConfigSecretRef, false) {
120-
allErrs = append(allErrs, field.Invalid(fldPath.Child("scyllaDBManagerAgent", "customConfigSecretRef"), *rackTemplate.ScyllaDBManagerAgent.CustomConfigSecretRef, msg))
121-
}
159+
if scyllaDBTemplate.CustomConfigMapRef != nil {
160+
for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(*scyllaDBTemplate.CustomConfigMapRef, false) {
161+
allErrs = append(allErrs, field.Invalid(fldPath.Child("customConfigMapRef"), *scyllaDBTemplate.CustomConfigMapRef, msg))
122162
}
123163
}
124164

@@ -195,16 +235,10 @@ func ValidateScyllaDBDatacenterIngressOptions(options *scyllav1alpha1.ExposeOpti
195235
func ValidateScyllaDBDatacenterNodeService(options *scyllav1alpha1.ExposeOptions, fldPath *field.Path) field.ErrorList {
196236
allErrs := field.ErrorList{}
197237

198-
var supportedServiceTypes = []scyllav1alpha1.NodeServiceType{
199-
scyllav1alpha1.NodeServiceTypeHeadless,
200-
scyllav1alpha1.NodeServiceTypeClusterIP,
201-
scyllav1alpha1.NodeServiceTypeLoadBalancer,
202-
}
203-
204238
if len(options.NodeService.Type) == 0 {
205-
allErrs = append(allErrs, field.Required(fldPath.Child("nodeService", "type"), fmt.Sprintf("supported values: %s", strings.Join(slices.ConvertSlice(supportedServiceTypes, slices.ToString[scyllav1alpha1.NodeServiceType]), ", "))))
239+
allErrs = append(allErrs, field.Required(fldPath.Child("nodeService", "type"), fmt.Sprintf("supported values: %s", strings.Join(slices.ConvertSlice(supportedNodeServiceTypes, slices.ToString[scyllav1alpha1.NodeServiceType]), ", "))))
206240
} else {
207-
allErrs = append(allErrs, validateEnum(options.NodeService.Type, supportedServiceTypes, fldPath.Child("nodeService", "type"))...)
241+
allErrs = append(allErrs, validateEnum(options.NodeService.Type, supportedNodeServiceTypes, fldPath.Child("nodeService", "type"))...)
208242
}
209243

210244
if options.NodeService.LoadBalancerClass != nil && len(*options.NodeService.LoadBalancerClass) != 0 {
@@ -371,6 +405,28 @@ func ValidateScyllaDBDatacenterOperatorManagedTLSCertificateOptions(options *scy
371405
return allErrs
372406
}
373407

408+
func ValidateScyllaDBDatacenterPlacement(placement *scyllav1alpha1.Placement, fldPath *field.Path) field.ErrorList {
409+
allErrs := field.ErrorList{}
410+
411+
if placement.NodeAffinity != nil {
412+
allErrs = append(allErrs, corevalidation.ValidateNodeAffinity(placement.NodeAffinity, fldPath.Child("nodeAffinity"))...)
413+
}
414+
415+
if placement.PodAntiAffinity != nil {
416+
allErrs = append(allErrs, corevalidation.ValidatePodAntiAffinity(placement.PodAntiAffinity, false, fldPath.Child("podAntiAffinity"))...)
417+
}
418+
419+
if placement.PodAffinity != nil {
420+
allErrs = append(allErrs, corevalidation.ValidatePodAffinity(placement.PodAffinity, false, fldPath.Child("podAffinity"))...)
421+
}
422+
423+
if placement.Tolerations != nil {
424+
allErrs = append(allErrs, corevalidation.ValidateTolerations(placement.Tolerations, fldPath.Child("tolerations"))...)
425+
}
426+
427+
return allErrs
428+
}
429+
374430
func ValidateScyllaDBDatacenterUpdate(new, old *scyllav1alpha1.ScyllaDBDatacenter) field.ErrorList {
375431
allErrs := field.ErrorList{}
376432

pkg/cmd/operator/webhooks.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ var (
5353
ValidateCreateFunc: validation.ValidateScyllaDBDatacenter,
5454
ValidateUpdateFunc: validation.ValidateScyllaDBDatacenterUpdate,
5555
},
56+
scyllav1alpha1.GroupVersion.WithResource("scylladbclusters"): &GenericValidator[*scyllav1alpha1.ScyllaDBCluster]{
57+
ValidateCreateFunc: validation.ValidateScyllaDBCluster,
58+
ValidateUpdateFunc: validation.ValidateScyllaDBClusterUpdate,
59+
},
5660
}
5761
)
5862

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2025 ScyllaDB.
2+
3+
package validation
4+
5+
import (
6+
corev1 "k8s.io/api/core/v1"
7+
"k8s.io/apimachinery/pkg/util/validation/field"
8+
)
9+
10+
func ValidatePodAffinity(podAffinity *corev1.PodAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
11+
return validatePodAffinity(podAffinity, allowInvalidLabelValueInSelector, fldPath)
12+
}
13+
14+
func ValidateNodeAffinity(na *corev1.NodeAffinity, fldPath *field.Path) field.ErrorList {
15+
return validateNodeAffinity(na, fldPath)
16+
}
17+
18+
func ValidatePodAntiAffinity(podAntiAffinity *corev1.PodAntiAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
19+
return validatePodAntiAffinity(podAntiAffinity, allowInvalidLabelValueInSelector, fldPath)
20+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v1.31.5 (af64d838aacd9173317b39cf273741816bd82377)

0 commit comments

Comments
 (0)