Skip to content

Commit

Permalink
Move validation web hook to a subpackage
Browse files Browse the repository at this point in the history
To add validation logic based on manifest rendering,
we need to decouple the validation web hook from the api package
to avoid loop dependencies

Signed-off-by: amaslennikov <[email protected]>
  • Loading branch information
almaslennikov committed Dec 6, 2023
1 parent f1840c5 commit ff0cf99
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 197 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1
package validator

import (
"context"
"errors"
"regexp"

apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -25,50 +27,66 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"github.com/Mellanox/network-operator/api/v1alpha1"
)

// log is for logging in this package.
var hostDeviceNetworkLog = logf.Log.WithName("hostdevicenetwork-resource")

func (w *HostDeviceNetwork) SetupWebhookWithManager(mgr ctrl.Manager) error {
type hostDeviceNetworkValidator struct {
v1alpha1.HostDeviceNetwork
}

func SetupHostDeviceNetworkWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(w).
For(&v1alpha1.HostDeviceNetwork{}).
WithValidator(&hostDeviceNetworkValidator{}).
Complete()
}

//nolint:lll
//+kubebuilder:webhook:path=/validate-mellanox-com-v1alpha1-hostdevicenetwork,mutating=false,failurePolicy=fail,sideEffects=None,groups=mellanox.com,resources=hostdevicenetworks,verbs=create;update,versions=v1alpha1,name=vhostdevicenetwork.kb.io,admissionReviewVersions=v1

var _ webhook.Validator = &HostDeviceNetwork{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (w *HostDeviceNetwork) ValidateCreate() (admission.Warnings, error) {
func (w *hostDeviceNetworkValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
if skipValidations {
nicClusterPolicyLog.Info("skipping CR validation")
return nil, nil
}

hostDeviceNetwork, ok := obj.(*v1alpha1.HostDeviceNetwork)
if !ok {
return nil, errors.New("failed to unmarshal HostDeviceNetwork object to validate")
}
w.HostDeviceNetwork = *hostDeviceNetwork
hostDeviceNetworkLog.Info("validate create", "name", w.Name)

return nil, w.validateHostDeviceNetwork()
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (w *HostDeviceNetwork) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) {
func (w *hostDeviceNetworkValidator) ValidateUpdate(
_ context.Context, _, newObj runtime.Object) (admission.Warnings, error) {
if skipValidations {
nicClusterPolicyLog.Info("skipping CR validation")
return nil, nil
}

hostDeviceNetwork, ok := newObj.(*v1alpha1.HostDeviceNetwork)
if !ok {
return nil, errors.New("failed to unmarshal HostDeviceNetwork object to validate")
}
w.HostDeviceNetwork = *hostDeviceNetwork
hostDeviceNetworkLog.Info("validate update", "name", w.Name)

return nil, w.validateHostDeviceNetwork()
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (w *HostDeviceNetwork) ValidateDelete() (admission.Warnings, error) {
func (w *hostDeviceNetworkValidator) ValidateDelete(
_ context.Context, _ runtime.Object) (admission.Warnings, error) {
if skipValidations {
nicClusterPolicyLog.Info("skipping CR validation")
return nil, nil
Expand All @@ -85,7 +103,7 @@ We are validating here HostDeviceNetwork:
- ResourceName must be valid for k8s
*/

func (w *HostDeviceNetwork) validateHostDeviceNetwork() error {
func (w *hostDeviceNetworkValidator) validateHostDeviceNetwork() error {
resourceName := w.Spec.ResourceName
if !isValidHostDeviceNetworkResourceName(resourceName) {
var allErrs field.ErrorList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,41 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1 //nolint:dupl
package validator //nolint:dupl

import (
"context"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/Mellanox/network-operator/api/v1alpha1"
)

//nolint:dupl
var _ = Describe("Validate", func() {
Context("HostDeviceNetwork tests", func() {
It("Valid ResourceName", func() {
hostDeviceNetwork := HostDeviceNetwork{
hostDeviceNetwork := &v1alpha1.HostDeviceNetwork{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: HostDeviceNetworkSpec{
Spec: v1alpha1.HostDeviceNetworkSpec{
ResourceName: "hostdev",
},
}
_, err := hostDeviceNetwork.ValidateCreate()
validator := hostDeviceNetworkValidator{}
_, err := validator.ValidateCreate(context.TODO(), hostDeviceNetwork)
Expect(err).NotTo(HaveOccurred())
})
It("Invalid ResourceName", func() {
hostDeviceNetwork := HostDeviceNetwork{
hostDeviceNetwork := &v1alpha1.HostDeviceNetwork{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: HostDeviceNetworkSpec{
Spec: v1alpha1.HostDeviceNetworkSpec{
ResourceName: "hostdev!!",
},
}
_, err := hostDeviceNetwork.ValidateCreate()
validator := hostDeviceNetworkValidator{}
_, err := validator.ValidateCreate(context.TODO(), hostDeviceNetwork)
Expect(err.Error()).To(ContainSubstring("Invalid Resource name"))
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1
package validator

import (
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
Expand All @@ -35,8 +37,9 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"github.com/Mellanox/network-operator/api/v1alpha1"
)

const (
Expand All @@ -52,43 +55,69 @@ var schemaValidators *schemaValidator

var skipValidations = false

func (w *NicClusterPolicy) SetupWebhookWithManager(mgr ctrl.Manager) error {
type nicClusterPolicyValidator struct {
v1alpha1.NicClusterPolicy
}

type devicePluginSpecWrapper struct {
v1alpha1.DevicePluginSpec
}

type ibKubernetesSpecWrapper struct {
v1alpha1.IBKubernetesSpec
}

type ofedDriverSpecWrapper struct {
v1alpha1.OFEDDriverSpec
}

func SetupNicClusterPolicyWebhookWithManager(mgr ctrl.Manager) error {
nicClusterPolicyLog.Info("Nic cluster policy webhook admission controller")
InitSchemaValidator("./webhook-schemas")
return ctrl.NewWebhookManagedBy(mgr).
For(w).
For(&v1alpha1.NicClusterPolicy{}).
WithValidator(&nicClusterPolicyValidator{}).
Complete()
}

//nolint:lll
//+kubebuilder:webhook:path=/validate-mellanox-com-v1alpha1-nicclusterpolicy,mutating=false,failurePolicy=fail,sideEffects=None,groups=mellanox.com,resources=nicclusterpolicies,verbs=create;update,versions=v1alpha1,name=vnicclusterpolicy.kb.io,admissionReviewVersions=v1

var _ webhook.Validator = &NicClusterPolicy{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (w *NicClusterPolicy) ValidateCreate() (admission.Warnings, error) {
func (w *nicClusterPolicyValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
if skipValidations {
nicClusterPolicyLog.Info("skipping CR validation")
return nil, nil
}

nicClusterPolicy, ok := obj.(*v1alpha1.NicClusterPolicy)
if !ok {
return nil, errors.New("failed to unmarshal NicClusterPolicy object to validate")
}
w.NicClusterPolicy = *nicClusterPolicy
nicClusterPolicyLog.Info("validate create", "name", w.Name)
return nil, w.validateNicClusterPolicy()
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (w *NicClusterPolicy) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) {
func (w *nicClusterPolicyValidator) ValidateUpdate(
_ context.Context, _, newObj runtime.Object) (admission.Warnings, error) {
if skipValidations {
nicClusterPolicyLog.Info("skipping CR validation")
return nil, nil
}

nicClusterPolicy, ok := newObj.(*v1alpha1.NicClusterPolicy)
if !ok {
return nil, errors.New("failed to unmarshal NicClusterPolicy object to validate")
}
w.NicClusterPolicy = *nicClusterPolicy
nicClusterPolicyLog.Info("validate update", "name", w.Name)
return nil, w.validateNicClusterPolicy()
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (w *NicClusterPolicy) ValidateDelete() (admission.Warnings, error) {
func (w *nicClusterPolicyValidator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
if skipValidations {
nicClusterPolicyLog.Info("skipping CR validation")
return nil, nil
Expand Down Expand Up @@ -117,34 +146,38 @@ We are validating here NicClusterPolicy:
4.3. At least one of the supported selectors exists.
4.4. All selectors are strings.
*/
func (w *NicClusterPolicy) validateNicClusterPolicy() error {
func (w *nicClusterPolicyValidator) validateNicClusterPolicy() error {
var allErrs field.ErrorList
// Validate Repository
allErrs = w.validateRepositories(allErrs)
allErrs = w.validateContainerResources(allErrs)
// Validate IBKubernetes
ibKubernetes := w.Spec.IBKubernetes
if ibKubernetes != nil {
allErrs = append(allErrs, ibKubernetes.validate(field.NewPath("spec").Child("ibKubernetes"))...)
wrapper := ibKubernetesSpecWrapper{IBKubernetesSpec: *w.Spec.IBKubernetes}
allErrs = append(allErrs, wrapper.validate(field.NewPath("spec").Child("ibKubernetes"))...)
}
// Validate OFEDDriverSpec
ofedDriver := w.Spec.OFEDDriver
if ofedDriver != nil {
wrapper := ofedDriverSpecWrapper{OFEDDriverSpec: *w.Spec.OFEDDriver}
ofedDriverFieldPath := field.NewPath("spec").Child("ofedDriver")
allErrs = append(append(allErrs,
ofedDriver.validateVersion(ofedDriverFieldPath)...),
ofedDriver.validateSafeLoad(ofedDriverFieldPath)...)
wrapper.validateVersion(ofedDriverFieldPath)...),
wrapper.validateSafeLoad(ofedDriverFieldPath)...)
}
// Validate RdmaSharedDevicePlugin
rdmaSharedDevicePlugin := w.Spec.RdmaSharedDevicePlugin
if rdmaSharedDevicePlugin != nil {
allErrs = append(allErrs, w.Spec.RdmaSharedDevicePlugin.validateRdmaSharedDevicePlugin(
wrapper := devicePluginSpecWrapper{DevicePluginSpec: *w.Spec.RdmaSharedDevicePlugin}
allErrs = append(allErrs, wrapper.validateRdmaSharedDevicePlugin(
field.NewPath("spec").Child("rdmaSharedDevicePlugin"))...)
}
// Validate SriovDevicePlugin
sriovNetworkDevicePlugin := w.Spec.SriovDevicePlugin
if sriovNetworkDevicePlugin != nil {
allErrs = append(allErrs, w.Spec.SriovDevicePlugin.validateSriovNetworkDevicePlugin(
wrapper := devicePluginSpecWrapper{DevicePluginSpec: *w.Spec.SriovDevicePlugin}
allErrs = append(allErrs, wrapper.validateSriovNetworkDevicePlugin(
field.NewPath("spec").Child("sriovNetworkDevicePlugin"))...)
}

Expand All @@ -155,7 +188,7 @@ func (w *NicClusterPolicy) validateNicClusterPolicy() error {
schema.GroupKind{Group: "mellanox.com", Kind: "NicClusterPolicy"},
w.Name, allErrs)
}
func (dp *DevicePluginSpec) validateSriovNetworkDevicePlugin(fldPath *field.Path) field.ErrorList {
func (dp *devicePluginSpecWrapper) validateSriovNetworkDevicePlugin(fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
var sriovNetworkDevicePluginConfigJSON map[string]interface{}
sriovNetworkDevicePluginConfig := *dp.Config
Expand Down Expand Up @@ -245,7 +278,7 @@ func (dp *DevicePluginSpec) validateSriovNetworkDevicePlugin(fldPath *field.Path
}

func validateResourceNamePrefix(resource map[string]interface{},
allErrs field.ErrorList, fldPath *field.Path, dp *DevicePluginSpec) (bool, field.ErrorList) {
allErrs field.ErrorList, fldPath *field.Path, dp *devicePluginSpecWrapper) (bool, field.ErrorList) {
resourceName := resource["resourceName"].(string)
if !isValidSriovNetworkDevicePluginResourceName(resourceName) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("Config"), dp.Config,
Expand All @@ -266,7 +299,7 @@ func validateResourceNamePrefix(resource map[string]interface{},
return true, allErrs
}

func (dp *DevicePluginSpec) validateRdmaSharedDevicePlugin(fldPath *field.Path) field.ErrorList {
func (dp *devicePluginSpecWrapper) validateRdmaSharedDevicePlugin(fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
var rdmaSharedDevicePluginConfigJSON map[string]interface{}
rdmaSharedDevicePluginConfig := *dp.Config
Expand Down Expand Up @@ -321,7 +354,7 @@ func (dp *DevicePluginSpec) validateRdmaSharedDevicePlugin(fldPath *field.Path)
}

// validate is a helper function to perform validation for IBKubernetesSpec.
func (ibk *IBKubernetesSpec) validate(fldPath *field.Path) field.ErrorList {
func (ibk *ibKubernetesSpecWrapper) validate(fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList

if !isValidPKeyGUID(ibk.PKeyGUIDPoolRangeStart) || !isValidPKeyGUID(ibk.PKeyGUIDPoolRangeEnd) {
Expand Down Expand Up @@ -362,7 +395,7 @@ func isValidPKeyRange(startGUID, endGUID string) bool {
return endGUIDIntValue.Cmp(startGUIDIntValue) > 0
}

func (ofedSpec *OFEDDriverSpec) validateVersion(fldPath *field.Path) field.ErrorList {
func (ofedSpec *ofedDriverSpecWrapper) validateVersion(fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

// Perform version validation logic here
Expand All @@ -373,7 +406,7 @@ func (ofedSpec *OFEDDriverSpec) validateVersion(fldPath *field.Path) field.Error
return allErrs
}

func (ofedSpec *OFEDDriverSpec) validateSafeLoad(fldPath *field.Path) field.ErrorList {
func (ofedSpec *ofedDriverSpecWrapper) validateSafeLoad(fldPath *field.Path) field.ErrorList {
upgradePolicy := ofedSpec.OfedUpgradePolicy
if upgradePolicy == nil {
return nil
Expand All @@ -391,7 +424,7 @@ func (ofedSpec *OFEDDriverSpec) validateSafeLoad(fldPath *field.Path) field.Erro
return allErrs
}

func (w *NicClusterPolicy) validateRepositories(allErrs field.ErrorList) field.ErrorList {
func (w *nicClusterPolicyValidator) validateRepositories(allErrs field.ErrorList) field.ErrorList {
fp := field.NewPath("spec")
if w.Spec.OFEDDriver != nil {
allErrs = validateRepository(w.Spec.OFEDDriver.ImageSpec.Repository, allErrs, fp, "nicFeatureDiscovery")
Expand Down Expand Up @@ -439,7 +472,7 @@ func validateRepository(repo string, allErrs field.ErrorList, fp *field.Path, ch
return allErrs
}

func (w *NicClusterPolicy) validateContainerResources(allErrs field.ErrorList) field.ErrorList {
func (w *nicClusterPolicyValidator) validateContainerResources(allErrs field.ErrorList) field.ErrorList {
fp := field.NewPath("spec")

states := map[string]interface{}{
Expand Down Expand Up @@ -484,15 +517,15 @@ func validateContainerResourcesIfNotNil(resource interface{}, allErrs field.Erro
containerResources := imageSpec.FieldByName("ContainerResources")
if containerResources.IsValid() && !containerResources.IsNil() {
return validateResourceRequirements(
containerResources.Interface().([]ResourceRequirements), allErrs, fp, fieldName)
containerResources.Interface().([]v1alpha1.ResourceRequirements), allErrs, fp, fieldName)
}
}
}

return allErrs
}

func validateResourceRequirements(resources []ResourceRequirements, allErrs field.ErrorList,
func validateResourceRequirements(resources []v1alpha1.ResourceRequirements, allErrs field.ErrorList,
fp *field.Path, child string) field.ErrorList {
if len(resources) == 0 {
allErrs = append(allErrs, field.Invalid(fp.Child(child).Child("containerResources"),
Expand Down
Loading

0 comments on commit ff0cf99

Please sign in to comment.