ingress: Update IngressClass feature and admission controller for v1

Signed-off-by: Christopher M. Luciano <cmluciano@us.ibm.com>
This commit is contained in:
Christopher M. Luciano 2020-06-09 15:06:11 -04:00
parent 868efab6f5
commit 92506a98fc
No known key found for this signature in database
GPG Key ID: 5148DBB31F2843F1
4 changed files with 29 additions and 50 deletions

View File

@ -568,6 +568,7 @@ const (
// owner: @robscott // owner: @robscott
// beta: v1.18 // beta: v1.18
// ga: v1.19
// //
// Enables DefaultIngressClass admission controller. // Enables DefaultIngressClass admission controller.
DefaultIngressClass featuregate.Feature = "DefaultIngressClass" DefaultIngressClass featuregate.Feature = "DefaultIngressClass"
@ -678,7 +679,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha}, ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
ServiceAppProtocol: {Default: true, PreRelease: featuregate.Beta}, ServiceAppProtocol: {Default: true, PreRelease: featuregate.Beta},
ImmutableEphemeralVolumes: {Default: true, PreRelease: featuregate.Beta}, ImmutableEphemeralVolumes: {Default: true, PreRelease: featuregate.Beta},
DefaultIngressClass: {Default: true, PreRelease: featuregate.Beta}, DefaultIngressClass: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
HugePageStorageMediumSize: {Default: false, PreRelease: featuregate.Alpha}, HugePageStorageMediumSize: {Default: false, PreRelease: featuregate.Alpha},
ExternalPolicyForExternalIP: {Default: true, PreRelease: featuregate.GA}, // remove in 1.20 ExternalPolicyForExternalIP: {Default: true, PreRelease: featuregate.GA}, // remove in 1.20
AnyVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha}, AnyVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha},

View File

@ -7,15 +7,14 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//pkg/apis/networking:go_default_library", "//pkg/apis/networking:go_default_library",
"//pkg/features:go_default_library", "//staging/src/k8s.io/api/networking/v1:go_default_library",
"//staging/src/k8s.io/api/networking/v1beta1:go_default_library", "//staging/src/k8s.io/api/networking/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/listers/networking/v1beta1:go_default_library", "//staging/src/k8s.io/client-go/listers/networking/v1:go_default_library",
"//staging/src/k8s.io/component-base/featuregate:go_default_library",
"//vendor/k8s.io/klog/v2:go_default_library", "//vendor/k8s.io/klog/v2:go_default_library",
], ],
) )
@ -28,6 +27,7 @@ go_test(
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/networking:go_default_library", "//pkg/apis/networking:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//staging/src/k8s.io/api/networking/v1:go_default_library",
"//staging/src/k8s.io/api/networking/v1beta1:go_default_library", "//staging/src/k8s.io/api/networking/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -21,17 +21,16 @@ import (
"fmt" "fmt"
"io" "io"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1" networkingv1beta1 "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
networkingv1beta1listers "k8s.io/client-go/listers/networking/v1beta1" networkingv1listers "k8s.io/client-go/listers/networking/v1"
"k8s.io/component-base/featuregate"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/networking"
"k8s.io/kubernetes/pkg/features"
) )
const ( const (
@ -50,10 +49,7 @@ func Register(plugins *admission.Plugins) {
// classDefaulterPlugin holds state for and implements the admission plugin. // classDefaulterPlugin holds state for and implements the admission plugin.
type classDefaulterPlugin struct { type classDefaulterPlugin struct {
*admission.Handler *admission.Handler
lister networkingv1beta1listers.IngressClassLister lister networkingv1listers.IngressClassLister
inspectedFeatures bool
defaultIngressClassEnabled bool
} }
var _ admission.Interface = &classDefaulterPlugin{} var _ admission.Interface = &classDefaulterPlugin{}
@ -67,31 +63,16 @@ func newPlugin() *classDefaulterPlugin {
} }
} }
// InspectFeatureGates allows setting bools without taking a dep on a global variable
func (a *classDefaulterPlugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
a.defaultIngressClassEnabled = featureGates.Enabled(features.DefaultIngressClass)
a.inspectedFeatures = true
}
// SetExternalKubeInformerFactory sets a lister and readyFunc for this // SetExternalKubeInformerFactory sets a lister and readyFunc for this
// classDefaulterPlugin using the provided SharedInformerFactory. // classDefaulterPlugin using the provided SharedInformerFactory.
func (a *classDefaulterPlugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { func (a *classDefaulterPlugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
if !a.defaultIngressClassEnabled { informer := f.Networking().V1().IngressClasses()
return
}
informer := f.Networking().V1beta1().IngressClasses()
a.lister = informer.Lister() a.lister = informer.Lister()
a.SetReadyFunc(informer.Informer().HasSynced) a.SetReadyFunc(informer.Informer().HasSynced)
} }
// ValidateInitialization ensures lister is set. // ValidateInitialization ensures lister is set.
func (a *classDefaulterPlugin) ValidateInitialization() error { func (a *classDefaulterPlugin) ValidateInitialization() error {
if !a.inspectedFeatures {
return fmt.Errorf("InspectFeatureGates was not called")
}
if !a.defaultIngressClassEnabled {
return nil
}
if a.lister == nil { if a.lister == nil {
return fmt.Errorf("missing lister") return fmt.Errorf("missing lister")
} }
@ -101,10 +82,7 @@ func (a *classDefaulterPlugin) ValidateInitialization() error {
// Admit sets the default value of a Ingress's class if the user did not specify // Admit sets the default value of a Ingress's class if the user did not specify
// a class. // a class.
func (a *classDefaulterPlugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error { func (a *classDefaulterPlugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
if !a.defaultIngressClassEnabled { if attr.GetResource().GroupResource() != networkingv1.Resource("ingresses") {
return nil
}
if attr.GetResource().GroupResource() != networkingv1beta1.Resource("ingresses") {
return nil return nil
} }
@ -147,13 +125,13 @@ func (a *classDefaulterPlugin) Admit(ctx context.Context, attr admission.Attribu
} }
// getDefaultClass returns the default IngressClass from the store, or nil. // getDefaultClass returns the default IngressClass from the store, or nil.
func getDefaultClass(lister networkingv1beta1listers.IngressClassLister) (*networkingv1beta1.IngressClass, error) { func getDefaultClass(lister networkingv1listers.IngressClassLister) (*networkingv1.IngressClass, error) {
list, err := lister.List(labels.Everything()) list, err := lister.List(labels.Everything())
if err != nil { if err != nil {
return nil, err return nil, err
} }
defaultClasses := []*networkingv1beta1.IngressClass{} defaultClasses := []*networkingv1.IngressClass{}
for _, class := range list { for _, class := range list {
if class.Annotations[networkingv1beta1.AnnotationIsDefaultIngressClass] == "true" { if class.Annotations[networkingv1beta1.AnnotationIsDefaultIngressClass] == "true" {
defaultClasses = append(defaultClasses, class) defaultClasses = append(defaultClasses, class)

View File

@ -22,6 +22,7 @@ import (
"reflect" "reflect"
"testing" "testing"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1" networkingv1beta1 "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -35,7 +36,7 @@ import (
) )
func TestAdmission(t *testing.T) { func TestAdmission(t *testing.T) {
defaultClass1 := &networkingv1beta1.IngressClass{ defaultClass1 := &networkingv1.IngressClass{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: "IngressClass", Kind: "IngressClass",
}, },
@ -46,7 +47,7 @@ func TestAdmission(t *testing.T) {
}, },
}, },
} }
defaultClass2 := &networkingv1beta1.IngressClass{ defaultClass2 := &networkingv1.IngressClass{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "default2", Name: "default2",
Annotations: map[string]string{ Annotations: map[string]string{
@ -55,7 +56,7 @@ func TestAdmission(t *testing.T) {
}, },
} }
// Class that has explicit default = false // Class that has explicit default = false
classWithFalseDefault := &networkingv1beta1.IngressClass{ classWithFalseDefault := &networkingv1.IngressClass{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: "IngressClass", Kind: "IngressClass",
}, },
@ -67,7 +68,7 @@ func TestAdmission(t *testing.T) {
}, },
} }
// Class with missing default annotation (=non-default) // Class with missing default annotation (=non-default)
classWithNoDefault := &networkingv1beta1.IngressClass{ classWithNoDefault := &networkingv1.IngressClass{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: "IngressClass", Kind: "IngressClass",
}, },
@ -76,7 +77,7 @@ func TestAdmission(t *testing.T) {
}, },
} }
// Class with empty default annotation (=non-default) // Class with empty default annotation (=non-default)
classWithEmptyDefault := &networkingv1beta1.IngressClass{ classWithEmptyDefault := &networkingv1.IngressClass{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: "IngressClass", Kind: "IngressClass",
}, },
@ -90,7 +91,7 @@ func TestAdmission(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
classes []*networkingv1beta1.IngressClass classes []*networkingv1.IngressClass
classField *string classField *string
classAnnotation *string classAnnotation *string
expectedClass *string expectedClass *string
@ -98,7 +99,7 @@ func TestAdmission(t *testing.T) {
}{ }{
{ {
name: "no default, no modification of Ingress", name: "no default, no modification of Ingress",
classes: []*networkingv1beta1.IngressClass{classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, classes: []*networkingv1.IngressClass{classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
classField: nil, classField: nil,
classAnnotation: nil, classAnnotation: nil,
expectedClass: nil, expectedClass: nil,
@ -106,7 +107,7 @@ func TestAdmission(t *testing.T) {
}, },
{ {
name: "one default, modify Ingress with class=nil", name: "one default, modify Ingress with class=nil",
classes: []*networkingv1beta1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
classField: nil, classField: nil,
classAnnotation: nil, classAnnotation: nil,
expectedClass: utilpointer.StringPtr(defaultClass1.Name), expectedClass: utilpointer.StringPtr(defaultClass1.Name),
@ -114,7 +115,7 @@ func TestAdmission(t *testing.T) {
}, },
{ {
name: "one default, no modification of Ingress with class field=''", name: "one default, no modification of Ingress with class field=''",
classes: []*networkingv1beta1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
classField: utilpointer.StringPtr(""), classField: utilpointer.StringPtr(""),
classAnnotation: nil, classAnnotation: nil,
expectedClass: utilpointer.StringPtr(""), expectedClass: utilpointer.StringPtr(""),
@ -122,7 +123,7 @@ func TestAdmission(t *testing.T) {
}, },
{ {
name: "one default, no modification of Ingress with class field='foo'", name: "one default, no modification of Ingress with class field='foo'",
classes: []*networkingv1beta1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
classField: utilpointer.StringPtr("foo"), classField: utilpointer.StringPtr("foo"),
classAnnotation: nil, classAnnotation: nil,
expectedClass: utilpointer.StringPtr("foo"), expectedClass: utilpointer.StringPtr("foo"),
@ -130,7 +131,7 @@ func TestAdmission(t *testing.T) {
}, },
{ {
name: "one default, no modification of Ingress with class annotation='foo'", name: "one default, no modification of Ingress with class annotation='foo'",
classes: []*networkingv1beta1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
classField: nil, classField: nil,
classAnnotation: utilpointer.StringPtr("foo"), classAnnotation: utilpointer.StringPtr("foo"),
expectedClass: nil, expectedClass: nil,
@ -138,15 +139,15 @@ func TestAdmission(t *testing.T) {
}, },
{ {
name: "two defaults, error with Ingress with class field=nil", name: "two defaults, error with Ingress with class field=nil",
classes: []*networkingv1beta1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, classes: []*networkingv1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
classField: nil, classField: nil,
classAnnotation: nil, classAnnotation: nil,
expectedClass: nil, expectedClass: nil,
expectedError: errors.NewForbidden(networkingv1beta1.Resource("ingresses"), "testing", errors.NewInternalError(fmt.Errorf("2 default IngressClasses were found, only 1 allowed"))), expectedError: errors.NewForbidden(networkingv1.Resource("ingresses"), "testing", errors.NewInternalError(fmt.Errorf("2 default IngressClasses were found, only 1 allowed"))),
}, },
{ {
name: "two defaults, no modification with Ingress with class field=''", name: "two defaults, no modification with Ingress with class field=''",
classes: []*networkingv1beta1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, classes: []*networkingv1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
classField: utilpointer.StringPtr(""), classField: utilpointer.StringPtr(""),
classAnnotation: nil, classAnnotation: nil,
expectedClass: utilpointer.StringPtr(""), expectedClass: utilpointer.StringPtr(""),
@ -157,11 +158,10 @@ func TestAdmission(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
ctrl := newPlugin() ctrl := newPlugin()
ctrl.defaultIngressClassEnabled = true
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
ctrl.SetExternalKubeInformerFactory(informerFactory) ctrl.SetExternalKubeInformerFactory(informerFactory)
for _, c := range testCase.classes { for _, c := range testCase.classes {
informerFactory.Networking().V1beta1().IngressClasses().Informer().GetStore().Add(c) informerFactory.Networking().V1().IngressClasses().Informer().GetStore().Add(c)
} }
ingress := &networking.Ingress{ObjectMeta: metav1.ObjectMeta{Name: "testing", Namespace: "testing"}} ingress := &networking.Ingress{ObjectMeta: metav1.ObjectMeta{Name: "testing", Namespace: "testing"}}
@ -178,7 +178,7 @@ func TestAdmission(t *testing.T) {
api.Kind("Ingress").WithVersion("version"), api.Kind("Ingress").WithVersion("version"),
ingress.Namespace, ingress.Namespace,
ingress.Name, ingress.Name,
networkingv1beta1.Resource("ingresses").WithVersion("version"), networkingv1.Resource("ingresses").WithVersion("version"),
"", // subresource "", // subresource
admission.Create, admission.Create,
&metav1.CreateOptions{}, &metav1.CreateOptions{},