Files
open-cluster-management/pkg/webhook/cluster/mutating_webhook_test.go
2022-01-12 02:54:07 +01:00

233 lines
7.3 KiB
Go

package cluster
import (
"encoding/json"
"net/http"
"reflect"
"testing"
"time"
clusterv1 "open-cluster-management.io/api/cluster/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
testinghelpers "open-cluster-management.io/registration/pkg/helpers/testing"
)
func TestManagedClusterMutate(t *testing.T) {
now := time.Now()
cases := []struct {
name string
request *admissionv1beta1.AdmissionRequest
expectedResponse *admissionv1beta1.AdmissionResponse
allowUpdateAcceptField bool
}{
{
name: "mutate non-managedclusters request",
request: &admissionv1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: "test.open-cluster-management.io",
Version: "v1",
Resource: "tests",
},
},
expectedResponse: newAdmissionResponse(true).build(),
},
{
name: "mutate deleting operation",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Delete,
},
expectedResponse: newAdmissionResponse(true).build(),
},
{
name: "new taints",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, nil)).
addTaint(newTaint("c", "d", clusterv1.TaintEffectPreferNoSelect, nil)).
build(),
},
expectedResponse: newAdmissionResponse(true).
addJsonPatch(newTaintTimeAddedJsonPatch(0, now)).
addJsonPatch(newTaintTimeAddedJsonPatch(1, now)).
build(),
},
{
name: "new taint request denied",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, 0))).
addTaint(newTaint("c", "d", clusterv1.TaintEffectPreferNoSelect, newTime(now, 0))).
build(),
},
expectedResponse: newAdmissionResponse(false).
withResult(metav1.StatusFailure, http.StatusBadRequest, metav1.StatusReasonBadRequest, "It is not allowed to set TimeAdded of Taint \"a,c\".").
build(),
},
{
name: "update taint",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
OldObject: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
build(),
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))). // no change
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelectIfNew, nil)). // effect modified
build(),
},
expectedResponse: newAdmissionResponse(true).
addJsonPatch(newTaintTimeAddedJsonPatch(1, now)).
build(),
},
{
name: "taint update request denied",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
OldObject: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
build(),
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -20*time.Second))). // timeAdded modified
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelectIfNew, newTime(now, -10*time.Second))). // effect modified with timeAdded
build(),
},
expectedResponse: newAdmissionResponse(false).
withResult(metav1.StatusFailure, http.StatusBadRequest, metav1.StatusReasonBadRequest, "It is not allowed to set TimeAdded of Taint \"a,c\".").
build(),
},
{
name: "delete taint",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
OldObject: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
build(),
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
build(),
},
expectedResponse: newAdmissionResponse(true).build(),
},
}
nowFunc = func() time.Time {
return now
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
admissionHook := &ManagedClusterMutatingAdmissionHook{}
actualResponse := admissionHook.Admit(c.request)
if !reflect.DeepEqual(actualResponse, c.expectedResponse) {
t.Errorf("expected \n%#v but got: \n%#v", c.expectedResponse, actualResponse)
}
})
}
}
type admissionResponseBuilder struct {
jsonPatchOperations []jsonPatchOperation
response admissionv1beta1.AdmissionResponse
}
func newAdmissionResponse(allowed bool) *admissionResponseBuilder {
return &admissionResponseBuilder{
response: admissionv1beta1.AdmissionResponse{
Allowed: allowed,
},
}
}
func (b *admissionResponseBuilder) addJsonPatch(jsonPatch jsonPatchOperation) *admissionResponseBuilder {
b.jsonPatchOperations = append(b.jsonPatchOperations, jsonPatch)
pt := admissionv1beta1.PatchTypeJSONPatch
b.response.PatchType = &pt
return b
}
func (b *admissionResponseBuilder) withResult(status string, code int32, reason metav1.StatusReason, message string) *admissionResponseBuilder {
b.response.Result = &metav1.Status{
Status: status,
Code: code,
Reason: reason,
Message: message,
}
return b
}
func (b *admissionResponseBuilder) build() *admissionv1beta1.AdmissionResponse {
if len(b.jsonPatchOperations) > 0 {
patch, _ := json.Marshal(b.jsonPatchOperations)
b.response.Patch = patch
}
return &b.response
}
type managedClusterBuilder struct {
cluster clusterv1.ManagedCluster
}
func newManagedCluster() *managedClusterBuilder {
return &managedClusterBuilder{
cluster: *testinghelpers.NewManagedCluster(),
}
}
func (b *managedClusterBuilder) withLeaseDurationSeconds(leaseDurationSeconds int32) *managedClusterBuilder {
b.cluster.Spec.LeaseDurationSeconds = leaseDurationSeconds
return b
}
func (b *managedClusterBuilder) addTaint(taint clusterv1.Taint) *managedClusterBuilder {
b.cluster.Spec.Taints = append(b.cluster.Spec.Taints, taint)
return b
}
func (b *managedClusterBuilder) build() runtime.RawExtension {
clusterObj, _ := json.Marshal(b.cluster)
return runtime.RawExtension{
Raw: clusterObj,
}
}
func newTaint(key, value string, effect clusterv1.TaintEffect, timeAdded *metav1.Time) clusterv1.Taint {
taint := clusterv1.Taint{
Key: key,
Value: value,
Effect: effect,
}
if timeAdded != nil {
taint.TimeAdded = *timeAdded
}
return taint
}
func newTime(time time.Time, offset time.Duration) *metav1.Time {
mt := metav1.NewTime(time.Add(offset))
return &mt
}