apiserver/pkg/admission/plugin/policy/generic/policy_source_test.go

245 lines
6.2 KiB
Go

/*
Copyright 2024 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 generic_test
import (
"testing"
"github.com/stretchr/testify/require"
"k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/tools/cache"
)
func makeTestDispatcher(authorizer.Authorizer, *matching.Matcher) generic.Dispatcher[generic.PolicyHook[*FakePolicy, *FakeBinding, generic.Evaluator]] {
return nil
}
func TestPolicySourceHasSyncedEmpty(t *testing.T) {
testContext, testCancel, err := generic.NewPolicyTestContext(
func(fp *FakePolicy) generic.PolicyAccessor { return fp },
func(fb *FakeBinding) generic.BindingAccessor { return fb },
func(fp *FakePolicy) generic.Evaluator { return nil },
makeTestDispatcher,
nil,
nil,
)
require.NoError(t, err)
defer testCancel()
require.NoError(t, testContext.Start())
// Should be able to wait for cache sync
require.True(t, cache.WaitForCacheSync(testContext.Done(), testContext.Source.HasSynced), "cache should sync after informer running")
}
func TestPolicySourceHasSyncedInitialList(t *testing.T) {
// Create a list of fake policies
initialObjects := []runtime.Object{
&FakePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "policy1",
},
},
&FakeBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "binding1",
},
PolicyName: "policy1",
},
}
testContext, testCancel, err := generic.NewPolicyTestContext(
func(fp *FakePolicy) generic.PolicyAccessor { return fp },
func(fb *FakeBinding) generic.BindingAccessor { return fb },
func(fp *FakePolicy) generic.Evaluator { return nil },
makeTestDispatcher,
initialObjects,
nil,
)
require.NoError(t, err)
defer testCancel()
require.NoError(t, testContext.Start())
// Should be able to wait for cache sync
require.Len(t, testContext.Source.Hooks(), 1, "should have one policy")
require.NoError(t, testContext.UpdateAndWait(
&FakePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "policy2",
},
},
&FakeBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "binding2",
},
PolicyName: "policy2",
},
))
require.Len(t, testContext.Source.Hooks(), 2, "should have two policies")
require.NoError(t, testContext.UpdateAndWait(
&FakePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "policy3",
},
},
&FakeBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "binding3",
},
PolicyName: "policy3",
},
&FakePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "policy2",
},
ParamKind: &v1.ParamKind{
APIVersion: "policy.example.com/v1",
Kind: "FakeParam",
},
},
))
require.Len(t, testContext.Source.Hooks(), 3, "should have 3 policies")
}
func TestPolicySourceBindsToPolicies(t *testing.T) {
// Create a list of fake policies
initialObjects := []runtime.Object{
&FakePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "policy1",
},
},
&FakeBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "binding1",
},
PolicyName: "policy1",
},
}
testContext, testCancel, err := generic.NewPolicyTestContext(
func(fp *FakePolicy) generic.PolicyAccessor { return fp },
func(fb *FakeBinding) generic.BindingAccessor { return fb },
func(fp *FakePolicy) generic.Evaluator { return nil },
makeTestDispatcher,
initialObjects,
nil,
)
require.NoError(t, err)
defer testCancel()
require.NoError(t, testContext.Start())
require.Len(t, testContext.Source.Hooks(), 1, "should have one policy")
require.Len(t, testContext.Source.Hooks()[0].Bindings, 1, "should have one binding")
require.Equal(t, "binding1", testContext.Source.Hooks()[0].Bindings[0].GetName(), "should have one binding")
// Change the binding to another policy (policies without bindings should
// be ignored, so it should remove the first
require.NoError(t, testContext.UpdateAndWait(
&FakePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "policy2",
},
},
&FakeBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "binding1",
},
PolicyName: "policy2",
}))
require.Len(t, testContext.Source.Hooks(), 1, "should have one policy")
require.Equal(t, "policy2", testContext.Source.Hooks()[0].Policy.GetName(), "policy name should be policy2")
require.Len(t, testContext.Source.Hooks()[0].Bindings, 1, "should have one binding")
require.Equal(t, "binding1", testContext.Source.Hooks()[0].Bindings[0].GetName(), "binding name should be binding1")
}
type FakePolicy struct {
metav1.TypeMeta
metav1.ObjectMeta
ParamKind *v1.ParamKind
}
var _ generic.PolicyAccessor = &FakePolicy{}
type FakeBinding struct {
metav1.TypeMeta
metav1.ObjectMeta
PolicyName string
}
var _ generic.BindingAccessor = &FakeBinding{}
func (fp *FakePolicy) GetName() string {
return fp.Name
}
func (fp *FakePolicy) GetNamespace() string {
return fp.Namespace
}
func (fp *FakePolicy) GetParamKind() *v1.ParamKind {
return fp.ParamKind
}
func (fb *FakePolicy) GetMatchConstraints() *v1.MatchResources {
return nil
}
func (fb *FakeBinding) GetName() string {
return fb.Name
}
func (fb *FakeBinding) GetNamespace() string {
return fb.Namespace
}
func (fb *FakeBinding) GetPolicyName() types.NamespacedName {
return types.NamespacedName{
Name: fb.PolicyName,
}
}
func (fb *FakeBinding) GetMatchResources() *v1.MatchResources {
return nil
}
func (fb *FakeBinding) GetParamRef() *v1.ParamRef {
return nil
}
func (fp *FakePolicy) DeepCopyObject() runtime.Object {
// totally fudged deepcopy
newFP := &FakePolicy{}
*newFP = *fp
return newFP
}
func (fb *FakeBinding) DeepCopyObject() runtime.Object {
// totally fudged deepcopy
newFB := &FakeBinding{}
*newFB = *fb
return newFB
}