Skip to content

Commit

Permalink
Add scyllav1alpha1.ScyllaDBDatacenter validation
Browse files Browse the repository at this point in the history
  • Loading branch information
zimnx committed Sep 4, 2024
1 parent b6ffd1d commit 47d0452
Show file tree
Hide file tree
Showing 14 changed files with 1,611 additions and 173 deletions.
1 change: 1 addition & 0 deletions helm/scylla-operator/templates/validatingwebhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ webhooks:
resources:
- nodeconfigs
- scyllaoperatorconfigs
- scylladbdatacenters
251 changes: 80 additions & 171 deletions pkg/api/scylla/validation/cluster_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/robfig/cron/v3"
scyllav1 "github.com/scylladb/scylla-operator/pkg/api/scylla/v1"
scyllav1alpha1 "github.com/scylladb/scylla-operator/pkg/api/scylla/v1alpha1"
"github.com/scylladb/scylla-operator/pkg/helpers/slices"
"github.com/scylladb/scylla-operator/pkg/pointer"
"github.com/scylladb/scylla-operator/pkg/semver"
"github.com/scylladb/scylla-operator/pkg/util/duration"
Expand Down Expand Up @@ -49,80 +48,9 @@ var (
)

func ValidateScyllaCluster(c *scyllav1.ScyllaCluster) field.ErrorList {
return ValidateScyllaClusterSpec(&c.Spec, field.NewPath("spec"))
}

func ValidateUserManagedTLSCertificateOptions(opts *scyllav1.UserManagedTLSCertificateOptions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

if len(opts.SecretName) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), ""))
} else {
for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(opts.SecretName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("secretName"), opts.SecretName, msg))
}
}

return allErrs
}

func ValidateOperatorManagedTLSCertificateOptions(opts *scyllav1.OperatorManagedTLSCertificateOptions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

for _, dnsName := range opts.AdditionalDNSNames {
for _, msg := range apimachineryutilvalidation.IsDNS1123Subdomain(dnsName) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("additionalDNSNames"), opts.AdditionalDNSNames, msg))
}
}

for _, ip := range opts.AdditionalIPAddresses {
for _, msg := range apimachineryutilvalidation.IsValidIP(ip) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("additionalIPAddresses"), opts.AdditionalIPAddresses, msg))
}
}

return allErrs
}

func ValidateTLSCertificate(cert *scyllav1.TLSCertificate, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

switch cert.Type {
case scyllav1.TLSCertificateTypeOperatorManaged:
if cert.OperatorManagedOptions != nil {
allErrs = append(allErrs, ValidateOperatorManagedTLSCertificateOptions(
cert.OperatorManagedOptions,
fldPath.Child("operatorManagedOptions"),
)...)
}

case scyllav1.TLSCertificateTypeUserManaged:
if cert.UserManagedOptions != nil {
allErrs = append(allErrs, ValidateUserManagedTLSCertificateOptions(
cert.UserManagedOptions,
fldPath.Child("userManagedOptions"),
)...)
} else {
allErrs = append(allErrs, field.Required(fldPath.Child("userManagedOptions"), ""))
}

case "":
allErrs = append(allErrs, field.Required(fldPath.Child("type"), ""))

default:
allErrs = append(
allErrs,
field.NotSupported(
fldPath.Child("type"),
cert.Type,
[]scyllav1.TLSCertificateType{
scyllav1.TLSCertificateTypeOperatorManaged,
scyllav1.TLSCertificateTypeUserManaged,
},
),
)

}
allErrs = append(allErrs, ValidateScyllaClusterSpec(&c.Spec, field.NewPath("spec"))...)

return allErrs
}
Expand All @@ -131,17 +59,7 @@ func ValidateAlternatorSpec(alternator *scyllav1.AlternatorSpec, fldPath *field.
allErrs := field.ErrorList{}

if alternator.WriteIsolation != "" {
found := slices.ContainsItem(AlternatorSupportedWriteIsolation, alternator.WriteIsolation)
if !found {
allErrs = append(
allErrs,
field.NotSupported(
fldPath.Child("alternator", "writeIsolation"),
alternator.WriteIsolation,
AlternatorSupportedWriteIsolation,
),
)
}
allErrs = append(allErrs, ValidateAlternatorWriteIsolation(alternator.WriteIsolation, fldPath.Child("writeIsolation"))...)
}

if alternator.InsecureEnableHTTP != nil && *alternator.InsecureEnableHTTP {
Expand All @@ -151,7 +69,40 @@ func ValidateAlternatorSpec(alternator *scyllav1.AlternatorSpec, fldPath *field.
}

if alternator.ServingCertificate != nil {
allErrs = append(allErrs, ValidateTLSCertificate(alternator.ServingCertificate, fldPath.Child("servingCertificate"))...)
allErrs = append(allErrs,
ValidateTLSCertificate(
map[scyllav1.TLSCertificateType]func() field.ErrorList{
scyllav1.TLSCertificateTypeOperatorManaged: func() field.ErrorList {
allErrs := field.ErrorList{}

if alternator.ServingCertificate.OperatorManagedOptions != nil {
allErrs = append(allErrs, ValidateOperatorManagedTLSCertificateOptions(
alternator.ServingCertificate.OperatorManagedOptions.AdditionalDNSNames,
alternator.ServingCertificate.OperatorManagedOptions.AdditionalIPAddresses,
fldPath.Child("servingCertificate", "operatorManagedOptions"),
)...)
}

return allErrs
},
scyllav1.TLSCertificateTypeUserManaged: func() field.ErrorList {
allErrs := field.ErrorList{}

if alternator.ServingCertificate.UserManagedOptions != nil {
allErrs = append(allErrs, ValidateUserManagedTLSCertificateOptions(
alternator.ServingCertificate.UserManagedOptions.SecretName,
fldPath.Child("servingCertificate", "userManagedOptions"),
)...)
} else {
allErrs = append(allErrs, field.Required(fldPath.Child("servingCertificate", "userManagedOptions"), ""))
}

return allErrs
},
},
alternator.ServingCertificate.Type,
fldPath.Child("servingCertificate"),
)...)
}

return allErrs
Expand All @@ -160,7 +111,9 @@ func ValidateAlternatorSpec(alternator *scyllav1.AlternatorSpec, fldPath *field.
func ValidateScyllaClusterSpec(spec *scyllav1.ScyllaClusterSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

rackNames := sets.NewString()
allErrs = append(allErrs, ValidateStructSliceFieldUniqueness(spec.Datacenter.Racks, func(rackSpec scyllav1.RackSpec) string {
return rackSpec.Name
}, "name", fldPath.Child("datacenter", "racks"))...)

if spec.Alternator != nil {
allErrs = append(allErrs, ValidateAlternatorSpec(spec.Alternator, fldPath.Child("alternator"))...)
Expand All @@ -174,26 +127,22 @@ func ValidateScyllaClusterSpec(spec *scyllav1.ScyllaClusterSpec, fldPath *field.
}

for i, rack := range spec.Datacenter.Racks {
allErrs = append(allErrs, ValidateScyllaClusterRackSpec(rack, rackNames, spec.CpuSet, fldPath.Child("datacenter", "racks").Index(i))...)
allErrs = append(allErrs, ValidateScyllaClusterRackSpec(rack, spec.CpuSet, fldPath.Child("datacenter", "racks").Index(i))...)
}

managerRepairTaskNames := sets.New[string]()
for i, r := range spec.Repairs {
if managerRepairTaskNames.Has(r.Name) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("repairs").Index(i).Child("name"), r.Name))
}
managerRepairTaskNames.Insert(r.Name)
allErrs = append(allErrs, ValidateStructSliceFieldUniqueness(spec.Repairs, func(repair scyllav1.RepairTaskSpec) string {
return repair.Name
}, "name", fldPath.Child("repairs"))...)

for i, r := range spec.Repairs {
allErrs = append(allErrs, ValidateRepairTaskSpec(&r, fldPath.Child("repairs").Index(i))...)
}

managerBackupTaskNames := sets.New[string]()
for i, b := range spec.Backups {
if managerBackupTaskNames.Has(b.Name) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("backups").Index(i).Child("name"), b.Name))
}
managerBackupTaskNames.Insert(b.Name)
allErrs = append(allErrs, ValidateStructSliceFieldUniqueness(spec.Backups, func(backup scyllav1.BackupTaskSpec) string {
return backup.Name
}, "name", fldPath.Child("backups"))...)

for i, b := range spec.Backups {
allErrs = append(allErrs, ValidateBackupTaskSpec(&b, fldPath.Child("backups").Index(i))...)
}

Expand Down Expand Up @@ -299,11 +248,16 @@ func ValidateExposeOptions(options *scyllav1.ExposeOptions, fldPath *field.Path)
allErrs := field.ErrorList{}

if options.CQL != nil && options.CQL.Ingress != nil {
allErrs = append(allErrs, ValidateIngressOptions(options.CQL.Ingress, fldPath.Child("cql", "ingress"))...)
allErrs = append(allErrs, ValidateIngressOptions(options.CQL.Ingress.IngressClassName, options.CQL.Ingress.Annotations, fldPath.Child("cql", "ingress"))...)
}

if options.NodeService != nil {
allErrs = append(allErrs, ValidateNodeService(options.NodeService, fldPath.Child("nodeService"))...)
var supportedServiceTypes = []scyllav1.NodeServiceType{
scyllav1.NodeServiceTypeHeadless,
scyllav1.NodeServiceTypeClusterIP,
scyllav1.NodeServiceTypeLoadBalancer,
}
allErrs = append(allErrs, ValidateExposeOptionsNodeService(options.NodeService.Type, supportedServiceTypes, options.NodeService.LoadBalancerClass, options.NodeService.Annotations, fldPath.Child("nodeService"))...)
}

if options.BroadcastOptions != nil {
Expand All @@ -316,17 +270,9 @@ func ValidateExposeOptions(options *scyllav1.ExposeOptions, fldPath *field.Path)
func ValidateNodeBroadcastOptions(options *scyllav1.NodeBroadcastOptions, nodeService *scyllav1.NodeServiceTemplate, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

allErrs = append(allErrs, ValidateBroadcastOptions(options.Clients, nodeService, fldPath.Child("clients"))...)
allErrs = append(allErrs, ValidateBroadcastOptions(options.Nodes, nodeService, fldPath.Child("nodes"))...)

return allErrs
}

func ValidateBroadcastOptions(options scyllav1.BroadcastOptions, nodeService *scyllav1.NodeServiceTemplate, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

if !slices.ContainsItem(SupportedScyllaV1BroadcastAddressTypes, options.Type) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), options.Type, slices.ConvertSlice(SupportedScyllaV1BroadcastAddressTypes, slices.ToString[scyllav1.BroadcastAddressType])))
var nodeServiceType *scyllav1.NodeServiceType
if nodeService != nil {
nodeServiceType = pointer.Ptr(nodeService.Type)
}

var allowedNodeServiceTypesByBroadcastAddressType = map[scyllav1.BroadcastAddressType][]scyllav1.NodeServiceType{
Expand All @@ -344,71 +290,34 @@ func ValidateBroadcastOptions(options scyllav1.BroadcastOptions, nodeService *sc
},
}

nodeServiceType := scyllav1.NodeServiceTypeClusterIP
if nodeService != nil {
nodeServiceType = nodeService.Type
}

// Skipping an error when chosen option type is unsupported as it won't help anyhow users reading it.
allowedNodeServiceTypes, ok := allowedNodeServiceTypesByBroadcastAddressType[options.Type]
if ok && !slices.ContainsItem(allowedNodeServiceTypes, nodeServiceType) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), options.Type, fmt.Sprintf("can't broadcast address unavailable within the selected node service type, allowed types for chosen broadcast address type are: %v", allowedNodeServiceTypes)))
}

return allErrs
}

func ValidateNodeService(nodeService *scyllav1.NodeServiceTemplate, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

var supportedServiceTypes = []scyllav1.NodeServiceType{
scyllav1.NodeServiceTypeHeadless,
scyllav1.NodeServiceTypeClusterIP,
scyllav1.NodeServiceTypeLoadBalancer,
}

if !slices.ContainsItem(supportedServiceTypes, nodeService.Type) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), nodeService.Type, slices.ConvertSlice(supportedServiceTypes, slices.ToString[scyllav1.NodeServiceType])))
}

if nodeService.LoadBalancerClass != nil && len(*nodeService.LoadBalancerClass) != 0 {
for _, msg := range apimachineryutilvalidation.IsQualifiedName(*nodeService.LoadBalancerClass) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("loadBalancerClass"), *nodeService.LoadBalancerClass, msg))
}
}

if len(nodeService.Annotations) != 0 {
allErrs = append(allErrs, apimachineryvalidation.ValidateAnnotations(nodeService.Annotations, fldPath.Child("annotations"))...)
}

return allErrs
}

func ValidateIngressOptions(options *scyllav1.IngressOptions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

if len(options.IngressClassName) != 0 {
for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(options.IngressClassName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("ingressClassName"), options.IngressClassName, msg))
}
}

if len(options.Annotations) != 0 {
allErrs = append(allErrs, apimachineryvalidation.ValidateAnnotations(options.Annotations, fldPath.Child("annotations"))...)
}
allErrs = append(allErrs,
ValidateBroadcastOptions(
options.Clients.Type,
SupportedScyllaV1BroadcastAddressTypes,
scyllav1.NodeServiceTypeClusterIP,
nodeServiceType,
allowedNodeServiceTypesByBroadcastAddressType,
fldPath.Child("clients"),
)...,
)

allErrs = append(allErrs,
ValidateBroadcastOptions(
options.Nodes.Type,
SupportedScyllaV1BroadcastAddressTypes,
scyllav1.NodeServiceTypeClusterIP,
nodeServiceType,
allowedNodeServiceTypesByBroadcastAddressType,
fldPath.Child("nodes"),
)...,
)

return allErrs
}

func ValidateScyllaClusterRackSpec(rack scyllav1.RackSpec, rackNames sets.String, cpuSet bool, fldPath *field.Path) field.ErrorList {
func ValidateScyllaClusterRackSpec(rack scyllav1.RackSpec, cpuSet bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

// Check that no two racks have the same name
if rackNames.Has(rack.Name) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), rack.Name))
}
rackNames.Insert(rack.Name)

// Check that limits are defined
limits := rack.Resources.Limits
if limits == nil || limits.Cpu().Value() == 0 || limits.Memory().Value() == 0 {
Expand Down
Loading

0 comments on commit 47d0452

Please sign in to comment.