apiserver/pkg/admission/configuration/mutating_webhook_manager_te...

284 lines
9.3 KiB
Go

/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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 configuration
import (
"context"
"reflect"
"testing"
"time"
"k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
)
func TestGetMutatingWebhookConfig(t *testing.T) {
// Build a test client that the admission plugin can use to look up the MutatingWebhookConfiguration
client := fake.NewSimpleClientset()
informerFactory := informers.NewSharedInformerFactory(client, 0)
stop := make(chan struct{})
defer close(stop)
manager := NewMutatingWebhookConfigurationManager(informerFactory).(*mutatingWebhookConfigurationManager)
informerFactory.Start(stop)
informerFactory.WaitForCacheSync(stop)
// no configurations
if configurations := manager.Webhooks(); len(configurations) != 0 {
t.Errorf("expected empty webhooks, but got %v", configurations)
}
webhookConfiguration := &v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook1"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook1.1"}},
}
client.
AdmissionregistrationV1().
MutatingWebhookConfigurations().
Create(context.TODO(), webhookConfiguration, metav1.CreateOptions{})
// Wait up to 10s for the notification to be delivered.
// (on my system this takes < 2ms)
startTime := time.Now()
configurations := manager.Webhooks()
for len(configurations) == 0 {
if time.Since(startTime) > 10*time.Second {
break
}
time.Sleep(time.Millisecond)
configurations = manager.Webhooks()
}
// verify presence
if len(configurations) == 0 {
t.Errorf("expected non empty webhooks")
}
for i := range configurations {
h, ok := configurations[i].GetMutatingWebhook()
if !ok {
t.Errorf("Expected mutating webhook")
continue
}
if !reflect.DeepEqual(h, &webhookConfiguration.Webhooks[i]) {
t.Errorf("Expected\n%#v\ngot\n%#v", &webhookConfiguration.Webhooks[i], h)
}
}
}
// mockCreateMutatingWebhookAccessor is a struct used to compute how many times
// the function webhook.NewMutatingWebhookAccessor is being called when refreshing
// webhookAccessors.
//
// NOTE: Maybe there some testing help that we can import and reuse instead.
type mockCreateMutatingWebhookAccessor struct {
numberOfCalls int
}
func (mock *mockCreateMutatingWebhookAccessor) calledNTimes() int { return mock.numberOfCalls }
func (mock *mockCreateMutatingWebhookAccessor) resetCounter() { mock.numberOfCalls = 0 }
func (mock *mockCreateMutatingWebhookAccessor) incrementCounter() { mock.numberOfCalls++ }
func (mock *mockCreateMutatingWebhookAccessor) fn(uid string, configurationName string, h *v1.MutatingWebhook) webhook.WebhookAccessor {
mock.incrementCounter()
return webhook.NewMutatingWebhookAccessor(uid, configurationName, h)
}
func mutatingConfigurationTotalWebhooks(configurations []*v1.MutatingWebhookConfiguration) int {
total := 0
for _, configuration := range configurations {
total += len(configuration.Webhooks)
}
return total
}
func TestGetMutatingWebhookConfigSmartReload(t *testing.T) {
type args struct {
createWebhookConfigurations []*v1.MutatingWebhookConfiguration
updateWebhookConfigurations []*v1.MutatingWebhookConfiguration
}
tests := []struct {
name string
args args
numberOfCreations int
// number of refreshes are number of times we pulled a webhook configuration
// from the cache without having to create new ones from scratch.
numberOfRefreshes int
finalNumberOfWebhookAccessors int
}{
{
name: "no creations and no updates",
args: args{
nil,
nil,
},
numberOfCreations: 0,
numberOfRefreshes: 0,
finalNumberOfWebhookAccessors: 0,
},
{
name: "create configurations and no updates",
args: args{
[]*v1.MutatingWebhookConfiguration{
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook1"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook1.1"}, {Name: "webhook1.2"}},
},
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook2"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook2.1"}, {Name: "webhook2.2"}},
},
},
nil,
},
numberOfCreations: 4,
numberOfRefreshes: 0,
finalNumberOfWebhookAccessors: 4,
},
{
name: "create configurations and update some of them",
args: args{
[]*v1.MutatingWebhookConfiguration{
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook3"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook3.1"}},
},
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook4"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook4.1"}},
},
},
[]*v1.MutatingWebhookConfiguration{
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook4"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook4.1-updated"}, {Name: "webhook4.2"}},
},
},
},
numberOfCreations: 2,
numberOfRefreshes: 2,
finalNumberOfWebhookAccessors: 3,
},
{
name: "create configuration and update moar of them",
args: args{
[]*v1.MutatingWebhookConfiguration{
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook5"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook5.1"}, {Name: "webhook5.2"}},
},
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook6"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook6.1"}},
},
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook7"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook7.1"}},
},
},
[]*v1.MutatingWebhookConfiguration{
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook6"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook6.1-updated"}},
},
&v1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook7"},
Webhooks: []v1.MutatingWebhook{{Name: "webhook7.1-updated"}, {Name: "webhook7.2"}},
},
},
},
numberOfCreations: 4,
numberOfRefreshes: 3,
finalNumberOfWebhookAccessors: 5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := fake.NewSimpleClientset()
informerFactory := informers.NewSharedInformerFactory(client, 0)
stop := make(chan struct{})
defer close(stop)
manager := NewMutatingWebhookConfigurationManager(informerFactory)
managerStructPtr := manager.(*mutatingWebhookConfigurationManager)
fakeWebhookAccessorCreator := &mockCreateMutatingWebhookAccessor{}
managerStructPtr.createMutatingWebhookAccessor = fakeWebhookAccessorCreator.fn
informerFactory.Start(stop)
informerFactory.WaitForCacheSync(stop)
// Create webhooks
for _, configurations := range tt.args.createWebhookConfigurations {
client.
AdmissionregistrationV1().
MutatingWebhookConfigurations().
Create(context.TODO(), configurations, metav1.CreateOptions{})
}
// TODO use channels to wait for manager.createMutatingWebhookAccessor
// to be called instead of using time.Sleep
time.Sleep(1 * time.Second)
webhooks := manager.Webhooks()
if mutatingConfigurationTotalWebhooks(tt.args.createWebhookConfigurations) != len(webhooks) {
t.Errorf("Expected number of webhooks %d received %d",
mutatingConfigurationTotalWebhooks(tt.args.createWebhookConfigurations),
len(webhooks),
)
}
// assert creations
if tt.numberOfCreations != fakeWebhookAccessorCreator.calledNTimes() {
t.Errorf(
"Expected number of creations %d received %d",
tt.numberOfCreations, fakeWebhookAccessorCreator.calledNTimes(),
)
}
// reset mock counter
fakeWebhookAccessorCreator.resetCounter()
// Update webhooks
for _, configurations := range tt.args.updateWebhookConfigurations {
client.
AdmissionregistrationV1().
MutatingWebhookConfigurations().
Update(context.TODO(), configurations, metav1.UpdateOptions{})
}
// TODO use channels to wait for manager.createMutatingWebhookAccessor
// to be called instead of using time.Sleep
time.Sleep(1 * time.Second)
webhooks = manager.Webhooks()
if tt.finalNumberOfWebhookAccessors != len(webhooks) {
t.Errorf("Expected final number of webhooks %d received %d",
tt.finalNumberOfWebhookAccessors,
len(webhooks),
)
}
// assert updates
if tt.numberOfRefreshes != fakeWebhookAccessorCreator.calledNTimes() {
t.Errorf(
"Expected number of refreshes %d received %d",
tt.numberOfRefreshes, fakeWebhookAccessorCreator.calledNTimes(),
)
}
// reset mock counter for the next test cases
fakeWebhookAccessorCreator.resetCounter()
})
}
}