Skip to content

Commit

Permalink
UPSTREAM: <carry>: admission: validate minimumKubeletVersion
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Hunt <[email protected]>
  • Loading branch information
haircommander committed Dec 2, 2024
1 parent c415839 commit f9dd6fa
Show file tree
Hide file tree
Showing 2 changed files with 312 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@ package node

import (
"context"
"errors"
"fmt"
"io"

configv1 "github.com/openshift/api/config/v1"
nodelib "github.com/openshift/library-go/pkg/apiserver/node"

openshiftfeatures "github.com/openshift/api/features"
"k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"

configv1 "github.com/openshift/api/config/v1"
"k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/component-base/featuregate"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation"
)

Expand All @@ -25,18 +34,28 @@ var rejectionScenarios = []struct {
{fromProfile: configv1.LowUpdateSlowReaction, toProfile: configv1.DefaultUpdateDefaultReaction},
}

const PluginName = "config.openshift.io/RestrictExtremeWorkerLatencyProfile"
const PluginName = "config.openshift.io/ValidateConfigNodeV1"

// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
return customresourcevalidation.NewValidator(
ret := &validateCustomResourceWithNodeLister{}
delegate, err := customresourcevalidation.NewValidator(
map[schema.GroupResource]bool{
configv1.Resource("nodes"): true,
},
map[schema.GroupVersionKind]customresourcevalidation.ObjectValidator{
configv1.GroupVersion.WithKind("Node"): configNodeV1{},
configv1.GroupVersion.WithKind("Node"): &configNodeV1{
nodeLister: ret.getNodeLister,
waitForReady: ret.getWaitForReady,
minimumKubeletVersionEnabled: feature.DefaultMutableFeatureGate.Enabled(featuregate.Feature(openshiftfeatures.FeatureGateMinimumKubeletVersion)),
},
})
if err != nil {
return nil, err
}
ret.ValidationInterface = delegate
return ret, nil
})
}

Expand All @@ -57,7 +76,13 @@ func toConfigNodeV1(uncastObj runtime.Object) (*configv1.Node, field.ErrorList)
return obj, nil
}

type configNodeV1 struct{}
type configNodeV1 struct {
admission.ValidationInterface

nodeLister func() corev1listers.NodeLister
waitForReady func() func() bool
minimumKubeletVersionEnabled bool
}

func validateConfigNodeForExtremeLatencyProfile(obj, oldObj *configv1.Node) *field.Error {
fromProfile := oldObj.Spec.WorkerLatencyProfile
Expand All @@ -78,18 +103,21 @@ func validateConfigNodeForExtremeLatencyProfile(obj, oldObj *configv1.Node) *fie
return nil
}

func (configNodeV1) ValidateCreate(_ context.Context, uncastObj runtime.Object) field.ErrorList {
func (c *configNodeV1) ValidateCreate(_ context.Context, uncastObj runtime.Object) field.ErrorList {
obj, allErrs := toConfigNodeV1(uncastObj)
if len(allErrs) > 0 {
return allErrs
}

allErrs = append(allErrs, validation.ValidateObjectMeta(&obj.ObjectMeta, false, customresourcevalidation.RequireNameCluster, field.NewPath("metadata"))...)
if err := c.validateMinimumKubeletVersion(obj); err != nil {
allErrs = append(allErrs, err)
}

return allErrs
}

func (configNodeV1) ValidateUpdate(_ context.Context, uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList {
func (c *configNodeV1) ValidateUpdate(_ context.Context, uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList {
obj, allErrs := toConfigNodeV1(uncastObj)
if len(allErrs) > 0 {
return allErrs
Expand All @@ -103,11 +131,36 @@ func (configNodeV1) ValidateUpdate(_ context.Context, uncastObj runtime.Object,
if err := validateConfigNodeForExtremeLatencyProfile(obj, oldObj); err != nil {
allErrs = append(allErrs, err)
}
if err := c.validateMinimumKubeletVersion(obj); err != nil {
allErrs = append(allErrs, err)
}

return allErrs
}
func (c *configNodeV1) validateMinimumKubeletVersion(obj *configv1.Node) *field.Error {
if !c.minimumKubeletVersionEnabled {
return nil
}
fieldPath := field.NewPath("spec", "minimumKubeletVersion")
if !c.waitForReady()() {
return field.InternalError(fieldPath, fmt.Errorf("caches not synchronized, cannot validate minimumKubeletVersion"))
}

func (configNodeV1) ValidateStatusUpdate(_ context.Context, uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList {
nodes, err := c.nodeLister().List(labels.Everything())
if err != nil {
return field.Forbidden(fieldPath, fmt.Sprintf("Getting nodes to compare minimum version %v", err.Error()))
}

if err := nodelib.ValidateMinimumKubeletVersion(nodes, obj.Spec.MinimumKubeletVersion); err != nil {
if errors.Is(err, nodelib.ErrKubeletOutdated) {
return field.Forbidden(fieldPath, err.Error())
}
return field.Invalid(fieldPath, obj.Spec.MinimumKubeletVersion, err.Error())
}
return nil
}

func (*configNodeV1) ValidateStatusUpdate(_ context.Context, uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList {
obj, errs := toConfigNodeV1(uncastObj)
if len(errs) > 0 {
return errs
Expand All @@ -122,3 +175,34 @@ func (configNodeV1) ValidateStatusUpdate(_ context.Context, uncastObj runtime.Ob

return errs
}

type validateCustomResourceWithNodeLister struct {
nodeLister corev1listers.NodeLister

handler admission.Handler
admission.ValidationInterface
}

var _ = initializer.WantsExternalKubeInformerFactory(&validateCustomResourceWithNodeLister{})

func (c *validateCustomResourceWithNodeLister) SetExternalKubeInformerFactory(kubeInformers informers.SharedInformerFactory) {
nodeInformer := kubeInformers.Core().V1().Nodes()
c.nodeLister = nodeInformer.Lister()
c.handler.SetReadyFunc(nodeInformer.Informer().HasSynced)
}

func (c *validateCustomResourceWithNodeLister) ValidateInitialization() error {
if c.nodeLister == nil {
return fmt.Errorf("%s needs a nodes", PluginName)
}

return nil
}

func (c *validateCustomResourceWithNodeLister) getNodeLister() corev1listers.NodeLister {
return c.nodeLister
}

func (c *validateCustomResourceWithNodeLister) getWaitForReady() func() bool {
return c.handler.WaitForReady
}
Loading

0 comments on commit f9dd6fa

Please sign in to comment.