Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more validation #393

Merged
merged 2 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/v1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ var (

// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme

YtsaurusGVK = GroupVersion.WithKind("Ytsaurus")
)
163 changes: 101 additions & 62 deletions api/v1/ytsaurus_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/validation"
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/utils/ptr"
Expand All @@ -42,13 +43,19 @@ import (
// log is for logging in this package.
var ytsauruslog = logf.Log.WithName("ytsaurus-resource")

type ytsaurusValidator struct {
type baseValidator struct {
Client client.Client
}

type ytsaurusValidator struct {
baseValidator
}

func (r *Ytsaurus) SetupWebhookWithManager(mgr ctrl.Manager) error {
validator := &ytsaurusValidator{
Client: mgr.GetClient(),
baseValidator: baseValidator{
Client: mgr.GetClient(),
},
}
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Expand All @@ -69,70 +76,82 @@ func (r *ytsaurusValidator) validateDiscovery(newYtsaurus *Ytsaurus) field.Error
return allErrors
}

func (r *ytsaurusValidator) validatePrimaryMasters(newYtsaurus, oldYtsaurus *Ytsaurus) field.ErrorList {
func (r *ytsaurusValidator) validateMasterSpec(newYtsaurus *Ytsaurus, mastersSpec, oldMastersSpec *MastersSpec, path *field.Path) field.ErrorList {
var allErrors field.ErrorList

path := field.NewPath("spec").Child("primaryMasters")
allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.PrimaryMasters.InstanceSpec, path)...)
allErrors = append(allErrors, r.validateHostAddresses(newYtsaurus, path)...)
allErrors = append(allErrors, r.validateInstanceSpec(mastersSpec.InstanceSpec, path)...)
allErrors = append(allErrors, r.validateHostAddresses(newYtsaurus, mastersSpec, path)...)

if FindFirstLocation(newYtsaurus.Spec.PrimaryMasters.Locations, LocationTypeMasterChangelogs) == nil {
if FindFirstLocation(mastersSpec.Locations, LocationTypeMasterChangelogs) == nil {
allErrors = append(allErrors, field.NotFound(path.Child("locations"), LocationTypeMasterChangelogs))
}

if FindFirstLocation(newYtsaurus.Spec.PrimaryMasters.Locations, LocationTypeMasterSnapshots) == nil {
if FindFirstLocation(mastersSpec.Locations, LocationTypeMasterSnapshots) == nil {
allErrors = append(allErrors, field.NotFound(path.Child("locations"), LocationTypeMasterSnapshots))
}

if oldYtsaurus != nil && oldYtsaurus.Spec.PrimaryMasters.CellTag != newYtsaurus.Spec.PrimaryMasters.CellTag {
allErrors = append(allErrors, field.Invalid(path.Child("cellTag"), newYtsaurus.Spec.PrimaryMasters.CellTag, "Could not be changed"))
if oldMastersSpec != nil && oldMastersSpec.CellTag != mastersSpec.CellTag {
allErrors = append(allErrors, field.Invalid(path.Child("cellTag"), mastersSpec.CellTag, "Could not be changed"))
}

if newYtsaurus.Spec.PrimaryMasters.InstanceCount > 1 && !newYtsaurus.Spec.EphemeralCluster {
if mastersSpec.InstanceCount > 1 && !newYtsaurus.Spec.EphemeralCluster {
affinity := newYtsaurus.Spec.PrimaryMasters.Affinity
if affinity == nil || affinity.PodAntiAffinity == nil {
allErrors = append(allErrors, field.Required(path.Child("affinity").Child("podAntiAffinity"), "Masters should be placed on different nodes"))
allErrors = append(allErrors, field.Required(path.Child("affinity").Child("podAntiAffinity"),
"Masters should be placed on different nodes"))
}
}

allErrors = append(allErrors, r.validateSidecars(mastersSpec.Sidecars, path.Child("sidecars"))...)

return allErrors
}

func (r *ytsaurusValidator) validateSecondaryMasters(newYtsaurus *Ytsaurus) field.ErrorList {
func (r *ytsaurusValidator) validatePrimaryMasters(newYtsaurus, oldYtsaurus *Ytsaurus) field.ErrorList {
var allErrors field.ErrorList
for i, sm := range newYtsaurus.Spec.SecondaryMasters {

mastersSpec := &newYtsaurus.Spec.PrimaryMasters
path := field.NewPath("spec").Child("primaryMasters")

var oldMastersSpec *MastersSpec
if oldYtsaurus != nil {
oldMastersSpec = &oldYtsaurus.Spec.PrimaryMasters
}

allErrors = append(allErrors, r.validateMasterSpec(newYtsaurus, mastersSpec, oldMastersSpec, path)...)

return allErrors
}

func (r *ytsaurusValidator) validateSecondaryMasters(newYtsaurus, oldYtsaurus *Ytsaurus) field.ErrorList {
var allErrors field.ErrorList

for i := range newYtsaurus.Spec.SecondaryMasters {
path := field.NewPath("spec").Child("secondaryMasters").Index(i)
allErrors = append(allErrors, r.validateInstanceSpec(sm.InstanceSpec, path)...)
allErrors = append(allErrors, r.validateHostAddresses(newYtsaurus, path)...)
mastersSpec := &newYtsaurus.Spec.SecondaryMasters[i]
var oldMastersSpec *MastersSpec
if oldYtsaurus != nil && len(oldYtsaurus.Spec.SecondaryMasters) > i {
oldMastersSpec = &oldYtsaurus.Spec.SecondaryMasters[i]
}
allErrors = append(allErrors, r.validateMasterSpec(newYtsaurus, mastersSpec, oldMastersSpec, path)...)
}

return allErrors
}

func (r *ytsaurusValidator) validateHostAddresses(newYtsaurus *Ytsaurus, fieldPath *field.Path) field.ErrorList {
func (r *ytsaurusValidator) validateHostAddresses(newYtsaurus *Ytsaurus, mastersSpec *MastersSpec, fieldPath *field.Path) field.ErrorList {
var allErrors field.ErrorList

hostAddressesFieldPath := fieldPath.Child("hostAddresses")
if !ptr.Deref(newYtsaurus.Spec.PrimaryMasters.HostNetwork, newYtsaurus.Spec.HostNetwork) && len(newYtsaurus.Spec.PrimaryMasters.HostAddresses) != 0 {
allErrors = append(
allErrors,
field.Required(
field.NewPath("spec").Child("hostNetwork"),
fmt.Sprintf("%s doesn't make sense without hostNetwork=true", hostAddressesFieldPath.String()),
),
)
if !ptr.Deref(mastersSpec.HostNetwork, newYtsaurus.Spec.HostNetwork) && len(mastersSpec.HostAddresses) != 0 {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("hostNetwork"),
fmt.Sprintf("%s doesn't make sense without hostNetwork=true", hostAddressesFieldPath.String())))
}

if len(newYtsaurus.Spec.PrimaryMasters.HostAddresses) != 0 && len(newYtsaurus.Spec.PrimaryMasters.HostAddresses) != int(newYtsaurus.Spec.PrimaryMasters.InstanceCount) {
if len(mastersSpec.HostAddresses) != 0 && len(mastersSpec.HostAddresses) != int(mastersSpec.InstanceCount) {
instanceCountFieldPath := fieldPath.Child("instanceCount")
allErrors = append(
allErrors,
field.Invalid(
hostAddressesFieldPath,
newYtsaurus.Spec.PrimaryMasters.HostAddresses,
fmt.Sprintf("%s list length shoud be equal to %s", hostAddressesFieldPath.String(), instanceCountFieldPath.String()),
),
)
allErrors = append(allErrors, field.Invalid(hostAddressesFieldPath, newYtsaurus.Spec.PrimaryMasters.HostAddresses,
fmt.Sprintf("%s list length shoud be equal to %s", hostAddressesFieldPath.String(), instanceCountFieldPath.String())))
}

return allErrors
Expand Down Expand Up @@ -221,7 +240,7 @@ func (r *ytsaurusValidator) validateDataNodes(newYtsaurus *Ytsaurus) field.Error
return allErrors
}

func validateSidecars(sidecars []string, path *field.Path) field.ErrorList {
func (r *baseValidator) validateSidecars(sidecars []string, path *field.Path) field.ErrorList {
var allErrors field.ErrorList

names := make(map[string]bool)
Expand Down Expand Up @@ -262,16 +281,17 @@ func (r *ytsaurusValidator) validateExecNodes(newYtsaurus *Ytsaurus) field.Error
}

if en.InitContainers != nil {
allErrors = append(allErrors, validateSidecars(en.InitContainers, path.Child("initContainers"))...)
allErrors = append(allErrors, r.validateSidecars(en.InitContainers, path.Child("initContainers"))...)
}
if en.Sidecars != nil {
allErrors = append(allErrors, validateSidecars(en.Sidecars, path.Child("sidecars"))...)
allErrors = append(allErrors, r.validateSidecars(en.Sidecars, path.Child("sidecars"))...)
}
}

if len(newYtsaurus.Spec.ExecNodes) > 0 {
if newYtsaurus.Spec.Schedulers == nil {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"), "execNodes doesn't make sense without schedulers"))
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"),
"execNodes doesn't make sense without schedulers"))
}
}

Expand All @@ -286,7 +306,8 @@ func (r *ytsaurusValidator) validateSchedulers(newYtsaurus *Ytsaurus) field.Erro
allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.Schedulers.InstanceSpec, path)...)

if newYtsaurus.Spec.ControllerAgents == nil {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("controllerAgents"), "schedulers doesn't make sense without controllerAgents"))
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("controllerAgents"),
"schedulers doesn't make sense without controllerAgents"))
}
}

Expand All @@ -301,7 +322,8 @@ func (r *ytsaurusValidator) validateControllerAgents(newYtsaurus *Ytsaurus) fiel
allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.ControllerAgents.InstanceSpec, path)...)

if newYtsaurus.Spec.Schedulers == nil {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"), "controllerAgents doesn't make sense without schedulers"))
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"),
"controllerAgents doesn't make sense without schedulers"))
}
}

Expand Down Expand Up @@ -337,7 +359,8 @@ func (r *ytsaurusValidator) validateStrawberry(newYtsaurus *Ytsaurus) field.Erro

if newYtsaurus.Spec.StrawberryController != nil {
if newYtsaurus.Spec.Schedulers == nil {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"), "schedulers are required for strawberry"))
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"),
"schedulers are required for strawberry"))
}
}

Expand All @@ -352,11 +375,13 @@ func (r *ytsaurusValidator) validateQueryTrackers(newYtsaurus *Ytsaurus) field.E
allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.QueryTrackers.InstanceSpec, path)...)

if len(newYtsaurus.Spec.TabletNodes) == 0 {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("tabletNodes"), "tabletNodes are required for queryTrackers"))
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("tabletNodes"),
"tabletNodes are required for queryTrackers"))
}

if newYtsaurus.Spec.Schedulers == nil {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"), "schedulers are required for queryTrackers"))
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("schedulers"),
"schedulers are required for queryTrackers"))
}
}

Expand All @@ -371,7 +396,8 @@ func (r *ytsaurusValidator) validateQueueAgents(newYtsaurus *Ytsaurus) field.Err
allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.QueueAgents.InstanceSpec, path)...)

if len(newYtsaurus.Spec.TabletNodes) == 0 {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("tabletNodes"), "tabletNodes are required for queueAgents"))
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("tabletNodes"),
"tabletNodes are required for queueAgents"))
}
}

Expand All @@ -383,7 +409,8 @@ func (r *ytsaurusValidator) validateSpyt(newYtsaurus *Ytsaurus) field.ErrorList
path := field.NewPath("spec").Child("spyt")

if newYtsaurus.Spec.Spyt != nil {
allErrors = append(allErrors, field.Invalid(path, newYtsaurus.Spec.Spyt, "spyt is deprecated here, use Spyt resource instead"))
allErrors = append(allErrors, field.Invalid(path, newYtsaurus.Spec.Spyt,
"spyt is deprecated here, use Spyt resource instead"))
}

return allErrors
Expand All @@ -397,7 +424,8 @@ func (r *ytsaurusValidator) validateYQLAgents(newYtsaurus *Ytsaurus) field.Error
allErrors = append(allErrors, r.validateInstanceSpec(newYtsaurus.Spec.YQLAgents.InstanceSpec, path)...)

if newYtsaurus.Spec.QueryTrackers == nil {
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("queryTrackers"), "yqlAgents doesn't make sense without queryTrackers"))
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("queryTrackers"),
"yqlAgents doesn't make sense without queryTrackers"))
}
}

Expand All @@ -424,13 +452,15 @@ func (r *ytsaurusValidator) validateUi(newYtsaurus *Ytsaurus) field.ErrorList {
return allErrors
}

//////////////////////////////////////////////////

func (r *ytsaurusValidator) validateInstanceSpec(instanceSpec InstanceSpec, path *field.Path) field.ErrorList {
func (r *baseValidator) validateInstanceSpec(instanceSpec InstanceSpec, path *field.Path) field.ErrorList {
var allErrors field.ErrorList

allErrors = append(allErrors, v1validation.ValidateLabels(instanceSpec.PodLabels, path.Child("podLabels"))...)
allErrors = append(allErrors, validation.ValidateAnnotations(instanceSpec.PodAnnotations, path.Child("podAnnotations"))...)

if instanceSpec.EnableAntiAffinity != nil {
allErrors = append(allErrors, field.Invalid(path.Child("EnableAntiAffinity"), instanceSpec.EnableAntiAffinity, "EnableAntiAffinity is deprecated, use Affinity instead"))
allErrors = append(allErrors, field.Invalid(path.Child("EnableAntiAffinity"), instanceSpec.EnableAntiAffinity,
"EnableAntiAffinity is deprecated, use Affinity instead"))
}

if instanceSpec.Locations != nil {
Expand All @@ -444,7 +474,8 @@ func (r *ytsaurusValidator) validateInstanceSpec(instanceSpec InstanceSpec, path
}

if !inVolumeMount {
allErrors = append(allErrors, field.Invalid(path.Child("locations").Index(locationIdx), location, "location path is not in any volume mount"))
allErrors = append(allErrors, field.Invalid(path.Child("locations").Index(locationIdx), location,
"location path is not in any volume mount"))
}
}
}
Expand All @@ -471,18 +502,29 @@ func (r *ytsaurusValidator) validateExistsYtsaurus(ctx context.Context, newYtsau
// it's the creation operation for the first ytsaurus object
return allErrors
} else {
allErrors = append(allErrors, field.Forbidden(field.NewPath("metadata").Child("namespace"), fmt.Sprintf("A Ytsaurus object already exists in the given namespace %s", newYtsaurus.Namespace)))
allErrors = append(allErrors, field.Forbidden(field.NewPath("metadata").Child("namespace"),
fmt.Sprintf("A Ytsaurus object already exists in the given namespace %s", newYtsaurus.Namespace)))
}

return allErrors
}

func (r *baseValidator) validateCommonSpec(spec *CommonSpec) field.ErrorList {
var allErrors field.ErrorList
path := field.NewPath("spec")

allErrors = append(allErrors, validation.ValidateAnnotations(spec.ExtraPodAnnotations, path.Child("extraPodAnnotations"))...)

return allErrors
}

func (r *ytsaurusValidator) validateYtsaurus(ctx context.Context, newYtsaurus, oldYtsaurus *Ytsaurus) field.ErrorList {
var allErrors field.ErrorList

allErrors = append(allErrors, r.validateCommonSpec(&newYtsaurus.Spec.CommonSpec)...)
allErrors = append(allErrors, r.validateDiscovery(newYtsaurus)...)
allErrors = append(allErrors, r.validatePrimaryMasters(newYtsaurus, oldYtsaurus)...)
allErrors = append(allErrors, r.validateSecondaryMasters(newYtsaurus)...)
allErrors = append(allErrors, r.validateSecondaryMasters(newYtsaurus, oldYtsaurus)...)
allErrors = append(allErrors, r.validateHTTPProxies(newYtsaurus)...)
allErrors = append(allErrors, r.validateRPCProxies(newYtsaurus)...)
allErrors = append(allErrors, r.validateTCPProxies(newYtsaurus)...)
Expand All @@ -503,16 +545,13 @@ func (r *ytsaurusValidator) validateYtsaurus(ctx context.Context, newYtsaurus, o
return allErrors
}

func (r *ytsaurusValidator) evaluateYtsaurusValidation(ctx context.Context, newYtsaurus, oldYtsaurus *Ytsaurus) error {
func (r *ytsaurusValidator) evaluateYtsaurusValidation(ctx context.Context, newYtsaurus, oldYtsaurus *Ytsaurus) (admission.Warnings, error) {
allErrors := r.validateYtsaurus(ctx, newYtsaurus, oldYtsaurus)
if len(allErrors) == 0 {
return nil
return nil, nil
}

return apierrors.NewInvalid(
schema.GroupKind{Group: "cluster.ytsaurus.tech", Kind: "Ytsaurus"},
newYtsaurus.Name,
allErrors)
return nil, apierrors.NewInvalid(YtsaurusGVK.GroupKind(), newYtsaurus.Name, allErrors)
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
Expand All @@ -522,7 +561,7 @@ func (r *ytsaurusValidator) ValidateCreate(ctx context.Context, obj runtime.Obje
if !ok {
return nil, fmt.Errorf("expected a Ytsaurus but got a %T", obj)
}
return nil, r.evaluateYtsaurusValidation(ctx, newYtsaurus, nil)
return r.evaluateYtsaurusValidation(ctx, newYtsaurus, nil)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
Expand All @@ -536,7 +575,7 @@ func (r *ytsaurusValidator) ValidateUpdate(ctx context.Context, oldObj, newObj r
if !ok {
return nil, fmt.Errorf("expected a Ytsaurus but got a %T", newYtsaurus)
}
return nil, r.evaluateYtsaurusValidation(ctx, newYtsaurus, oldYtsaurus)
return r.evaluateYtsaurusValidation(ctx, newYtsaurus, oldYtsaurus)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
Expand Down
Loading