mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-08-23 22:26:49 +00:00
enhance webhook to set the value of taint timeAdded (#186)
Signed-off-by: Yang Le <yangle@redhat.com>
This commit is contained in:
@@ -97,6 +97,7 @@ spec:
|
||||
description: TimeAdded represents the time at which the taint was added.
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
value:
|
||||
description: Value is the taint value corresponding to the taint key.
|
||||
type: string
|
||||
|
||||
@@ -295,7 +295,7 @@ spec:
|
||||
integer:
|
||||
description: Integer is the integer value when type is integer.
|
||||
type: integer
|
||||
format: int32
|
||||
format: int64
|
||||
string:
|
||||
description: String is the string value when when type is string.
|
||||
type: string
|
||||
|
||||
@@ -19,7 +19,7 @@ require (
|
||||
k8s.io/klog/v2 v2.30.0
|
||||
k8s.io/kube-aggregator v0.23.0
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b
|
||||
open-cluster-management.io/api v0.5.1-0.20220104024812-246f7e36d0d2
|
||||
open-cluster-management.io/api v0.5.1-0.20220107041310-9303b90b6ade
|
||||
sigs.k8s.io/controller-runtime v0.11.0
|
||||
)
|
||||
|
||||
|
||||
@@ -1282,8 +1282,8 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||
open-cluster-management.io/api v0.5.1-0.20220104024812-246f7e36d0d2 h1:r+2z448obb2/SfLCtjKN4ge7ZTcIj2qNVWU3qp6CuMw=
|
||||
open-cluster-management.io/api v0.5.1-0.20220104024812-246f7e36d0d2/go.mod h1:0IUTh8J+p4pv1THh1r9oO0luX9Z1FLDEAmvzW09qC0o=
|
||||
open-cluster-management.io/api v0.5.1-0.20220107041310-9303b90b6ade h1:oPuNeeOub6iZFavW/iULi+ctyJsOjQE75Plmuyk9D9Y=
|
||||
open-cluster-management.io/api v0.5.1-0.20220107041310-9303b90b6ade/go.mod h1:0IUTh8J+p4pv1THh1r9oO0luX9Z1FLDEAmvzW09qC0o=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@@ -321,3 +321,17 @@ func ManagedClusterAssetFn(fs embed.FS, managedClusterName string) resourceapply
|
||||
return assets.MustCreateAssetFromTemplate(name, template, config).Data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// FindTaintByKey returns a taint if the managed cluster has a taint with the given key.
|
||||
func FindTaintByKey(managedCluster *clusterv1.ManagedCluster, key string) *clusterv1.Taint {
|
||||
if managedCluster == nil {
|
||||
return nil
|
||||
}
|
||||
for _, taint := range managedCluster.Spec.Taints {
|
||||
if key != taint.Key {
|
||||
continue
|
||||
}
|
||||
return &taint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -451,6 +451,60 @@ func TestCleanUpGroupFromRoleBindings(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTaintByKey(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
cluster *clusterv1.ManagedCluster
|
||||
key string
|
||||
expected *clusterv1.Taint
|
||||
}{
|
||||
{
|
||||
name: "nil of managed cluster",
|
||||
key: "taint1",
|
||||
},
|
||||
{
|
||||
name: "taint found",
|
||||
cluster: &clusterv1.ManagedCluster{
|
||||
Spec: clusterv1.ManagedClusterSpec{
|
||||
Taints: []clusterv1.Taint{
|
||||
{
|
||||
Key: "taint1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
key: "taint1",
|
||||
expected: &clusterv1.Taint{
|
||||
Key: "taint1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "taint not found",
|
||||
cluster: &clusterv1.ManagedCluster{
|
||||
Spec: clusterv1.ManagedClusterSpec{
|
||||
Taints: []clusterv1.Taint{
|
||||
{
|
||||
Key: "taint1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
key: "taint2",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := FindTaintByKey(c.cluster, c.key)
|
||||
if !reflect.DeepEqual(actual, c.expected) {
|
||||
t.Errorf("expected %v but got %v", c.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getApplyFileNames(applyFiles map[string]runtime.Object) []string {
|
||||
keys := []string{}
|
||||
for key := range applyFiles {
|
||||
|
||||
@@ -2,9 +2,13 @@ package cluster
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
clusterv1 "open-cluster-management.io/api/cluster/v1"
|
||||
"open-cluster-management.io/registration/pkg/helpers"
|
||||
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -13,7 +17,13 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const defaultLeaseDurationSecondsPatch = `[{"op": "replace", "path": "/spec/leaseDurationSeconds", "value": 60}]`
|
||||
var nowFunc = time.Now
|
||||
|
||||
type jsonPatchOperation struct {
|
||||
Operation string `json:"op"`
|
||||
Path string `json:"path"`
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// ManagedClusterMutatingAdmissionHook will mutate the creating/updating managedcluster request.
|
||||
type ManagedClusterMutatingAdmissionHook struct{}
|
||||
@@ -58,18 +68,109 @@ func (a *ManagedClusterMutatingAdmissionHook) Admit(req *admissionv1beta1.Admiss
|
||||
return status
|
||||
}
|
||||
|
||||
// If LeaseDurationSeconds value is zero, update it to 60 by default
|
||||
if managedCluster.Spec.LeaseDurationSeconds == 0 {
|
||||
status.Patch = []byte(defaultLeaseDurationSecondsPatch)
|
||||
pt := admissionv1beta1.PatchTypeJSONPatch
|
||||
status.PatchType = &pt
|
||||
var jsonPatches []jsonPatchOperation
|
||||
|
||||
// set timeAdded of taint if it is nil and reset it if it is modified
|
||||
taintJsonPatches, status := a.processTaints(managedCluster, req.OldObject.Raw)
|
||||
if !status.Allowed {
|
||||
return status
|
||||
}
|
||||
jsonPatches = append(jsonPatches, taintJsonPatches...)
|
||||
|
||||
if len(jsonPatches) == 0 {
|
||||
return status
|
||||
}
|
||||
|
||||
patch, err := json.Marshal(jsonPatches)
|
||||
if err != nil {
|
||||
status.Allowed = false
|
||||
status.Result = &metav1.Status{
|
||||
Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
status.Patch = patch
|
||||
pt := admissionv1beta1.PatchTypeJSONPatch
|
||||
status.PatchType = &pt
|
||||
return status
|
||||
}
|
||||
|
||||
// processTaints generates json patched for cluster taints
|
||||
func (a *ManagedClusterMutatingAdmissionHook) processTaints(managedCluster *clusterv1.ManagedCluster, oldManagedClusterRaw []byte) ([]jsonPatchOperation, *admissionv1beta1.AdmissionResponse) {
|
||||
status := &admissionv1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
}
|
||||
|
||||
if len(managedCluster.Spec.Taints) == 0 {
|
||||
return nil, status
|
||||
}
|
||||
|
||||
var oldManagedCluster *clusterv1.ManagedCluster
|
||||
if len(oldManagedClusterRaw) > 0 {
|
||||
cluster := &clusterv1.ManagedCluster{}
|
||||
if err := json.Unmarshal(oldManagedClusterRaw, cluster); err != nil {
|
||||
status.Allowed = false
|
||||
status.Result = &metav1.Status{
|
||||
Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
return nil, status
|
||||
}
|
||||
oldManagedCluster = cluster
|
||||
}
|
||||
|
||||
var invalidTaints []string
|
||||
var jsonPatches []jsonPatchOperation
|
||||
now := metav1.NewTime(nowFunc())
|
||||
for index, taint := range managedCluster.Spec.Taints {
|
||||
originalTaint := helpers.FindTaintByKey(oldManagedCluster, taint.Key)
|
||||
switch {
|
||||
case originalTaint == nil:
|
||||
// new taint
|
||||
if !taint.TimeAdded.IsZero() {
|
||||
invalidTaints = append(invalidTaints, taint.Key)
|
||||
continue
|
||||
}
|
||||
jsonPatches = append(jsonPatches, newTaintTimeAddedJsonPatch(index, now.Time))
|
||||
case originalTaint.Value == taint.Value && originalTaint.Effect == taint.Effect:
|
||||
// no change
|
||||
if !originalTaint.TimeAdded.Equal(&taint.TimeAdded) {
|
||||
invalidTaints = append(invalidTaints, taint.Key)
|
||||
}
|
||||
default:
|
||||
// taint's value/effect has changed
|
||||
if !taint.TimeAdded.IsZero() {
|
||||
invalidTaints = append(invalidTaints, taint.Key)
|
||||
continue
|
||||
}
|
||||
jsonPatches = append(jsonPatches, newTaintTimeAddedJsonPatch(index, now.Time))
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidTaints) == 0 {
|
||||
return jsonPatches, status
|
||||
}
|
||||
|
||||
status.Allowed = false
|
||||
status.Result = &metav1.Status{
|
||||
Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest,
|
||||
Message: fmt.Sprintf("It is not allowed to set TimeAdded of Taint %q.", strings.Join(invalidTaints, ",")),
|
||||
}
|
||||
return nil, status
|
||||
}
|
||||
|
||||
// Initialize is called by generic-admission-server on startup to setup initialization that managedclusters webhook needs.
|
||||
func (a *ManagedClusterMutatingAdmissionHook) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error {
|
||||
// do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTaintTimeAddedJsonPatch(index int, timeAdded time.Time) jsonPatchOperation {
|
||||
return jsonPatchOperation{
|
||||
Operation: "replace",
|
||||
Path: fmt.Sprintf("/spec/taints/%d/timeAdded", index),
|
||||
Value: timeAdded.UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@ 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"
|
||||
@@ -12,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func TestManagedClusterMutate(t *testing.T) {
|
||||
pt := admissionv1beta1.PatchTypeJSONPatch
|
||||
now := time.Now()
|
||||
cases := []struct {
|
||||
name string
|
||||
request *admissionv1beta1.AdmissionRequest
|
||||
@@ -28,9 +32,7 @@ func TestManagedClusterMutate(t *testing.T) {
|
||||
Resource: "tests",
|
||||
},
|
||||
},
|
||||
expectedResponse: &admissionv1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
},
|
||||
expectedResponse: newAdmissionResponse(true).build(),
|
||||
},
|
||||
{
|
||||
name: "mutate deleting operation",
|
||||
@@ -38,54 +40,193 @@ func TestManagedClusterMutate(t *testing.T) {
|
||||
Resource: managedclustersSchema,
|
||||
Operation: admissionv1beta1.Delete,
|
||||
},
|
||||
expectedResponse: &admissionv1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
},
|
||||
expectedResponse: newAdmissionResponse(true).build(),
|
||||
},
|
||||
{
|
||||
name: "mutate a ManagedCluster without LeaseDurationSeconds setting",
|
||||
name: "new taints",
|
||||
request: &admissionv1beta1.AdmissionRequest{
|
||||
Resource: managedclustersSchema,
|
||||
Operation: admissionv1beta1.Create,
|
||||
Object: newManagedClusterObj(),
|
||||
},
|
||||
expectedResponse: &admissionv1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
Patch: []byte(`[{"op": "replace", "path": "/spec/leaseDurationSeconds", "value": 60}]`),
|
||||
PatchType: &pt,
|
||||
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: "mutate a ManagedCluster with LeaseDurationSeconds setting",
|
||||
name: "new taint request denied",
|
||||
request: &admissionv1beta1.AdmissionRequest{
|
||||
Resource: managedclustersSchema,
|
||||
Operation: admissionv1beta1.Create,
|
||||
Object: newManagedClusterObjWithLeaseDurationSeconds(60),
|
||||
},
|
||||
expectedResponse: &admissionv1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
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 %#v but got: %#v", c.expectedResponse.Result, actualResponse.Result)
|
||||
t.Errorf("expected \n%#v but got: \n%#v", c.expectedResponse, actualResponse)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newManagedClusterObjWithLeaseDurationSeconds(leaseDurationSeconds int32) runtime.RawExtension {
|
||||
managedCluster := testinghelpers.NewManagedCluster()
|
||||
managedCluster.Spec.LeaseDurationSeconds = leaseDurationSeconds
|
||||
clusterObj, _ := json.Marshal(managedCluster)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -82,6 +82,64 @@ var _ = ginkgo.Describe("Admission webhook", func() {
|
||||
gomega.Expect(deleteManageClusterAndRelatedNamespace(clusterName)).ToNot(gomega.HaveOccurred())
|
||||
})
|
||||
|
||||
ginkgo.It("Should have the timeAdded for taints", func() {
|
||||
clusterName := fmt.Sprintf("webhook-spoke-%s", rand.String(6))
|
||||
ginkgo.By(fmt.Sprintf("create a managed cluster %q with taint", clusterName))
|
||||
|
||||
cluster := newManagedCluster(clusterName, false, validURL)
|
||||
cluster.Spec.Taints = []clusterv1.Taint{
|
||||
{
|
||||
Key: "a",
|
||||
Value: "b",
|
||||
Effect: clusterv1.TaintEffectNoSelect,
|
||||
},
|
||||
}
|
||||
_, err := clusterClient.ClusterV1().ManagedClusters().Create(context.TODO(), cluster, metav1.CreateOptions{})
|
||||
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||
|
||||
ginkgo.By("check if timeAdded of the taint is set automatically")
|
||||
managedCluster, err := clusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||
taint := findTaint(managedCluster.Spec.Taints, "a", "b", clusterv1.TaintEffectNoSelect)
|
||||
gomega.Expect(taint).ShouldNot(gomega.BeNil())
|
||||
gomega.Expect(taint.TimeAdded.IsZero()).To(gomega.BeFalse())
|
||||
|
||||
ginkgo.By("chang the effect of the taint")
|
||||
// sleep and make sure the update is performed 1 second later than the creation
|
||||
time.Sleep(1 * time.Second)
|
||||
gomega.Eventually(func() error {
|
||||
managedCluster, err := clusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for index, taint := range managedCluster.Spec.Taints {
|
||||
if taint.Key != "a" {
|
||||
continue
|
||||
}
|
||||
if taint.Value != "b" {
|
||||
continue
|
||||
}
|
||||
managedCluster.Spec.Taints[index] = clusterv1.Taint{
|
||||
Key: taint.Key,
|
||||
Value: taint.Value,
|
||||
Effect: clusterv1.TaintEffectNoSelectIfNew,
|
||||
}
|
||||
}
|
||||
_, err = clusterClient.ClusterV1().ManagedClusters().Update(context.TODO(), managedCluster, metav1.UpdateOptions{})
|
||||
return err
|
||||
}, 60*time.Second, 1*time.Second).Should(gomega.Succeed())
|
||||
|
||||
ginkgo.By("check if timeAdded of the taint is reset")
|
||||
managedCluster, err = clusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||
updatedTaint := findTaint(managedCluster.Spec.Taints, "a", "b", clusterv1.TaintEffectNoSelectIfNew)
|
||||
gomega.Expect(updatedTaint).ShouldNot(gomega.BeNil())
|
||||
gomega.Expect(taint.TimeAdded.Equal(&updatedTaint.TimeAdded)).To(gomega.BeFalse(),
|
||||
"timeAdded of taint should be updated (before=%s, after=%s)", taint.TimeAdded.Time.String(), updatedTaint.TimeAdded.Time.String())
|
||||
|
||||
gomega.Expect(deleteManageClusterAndRelatedNamespace(clusterName)).ToNot(gomega.HaveOccurred())
|
||||
})
|
||||
|
||||
ginkgo.It("Should respond bad request when creating a managed cluster with invalid external server URLs", func() {
|
||||
clusterName := fmt.Sprintf("webhook-spoke-%s", rand.String(6))
|
||||
ginkgo.By(fmt.Sprintf("create a managed cluster %q with an invalid external server URL %q", clusterName, invalidURL))
|
||||
@@ -772,3 +830,23 @@ func cleanupClusterClient(saNamespace, saName string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findTaint returns the first matched taint in the given taint array. Return nil if no taint matched.
|
||||
func findTaint(taints []clusterv1.Taint, key, value string, effect clusterv1.TaintEffect) *clusterv1.Taint {
|
||||
for _, taint := range taints {
|
||||
if len(key) != 0 && taint.Key != key {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(value) != 0 && taint.Value != value {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(effect) != 0 && taint.Effect != effect {
|
||||
continue
|
||||
}
|
||||
|
||||
return &taint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1180,7 +1180,7 @@ k8s.io/utils/net
|
||||
k8s.io/utils/path
|
||||
k8s.io/utils/pointer
|
||||
k8s.io/utils/trace
|
||||
# open-cluster-management.io/api v0.5.1-0.20220104024812-246f7e36d0d2
|
||||
# open-cluster-management.io/api v0.5.1-0.20220107041310-9303b90b6ade
|
||||
## explicit; go 1.17
|
||||
open-cluster-management.io/api/addon/v1alpha1
|
||||
open-cluster-management.io/api/client/addon/clientset/versioned
|
||||
|
||||
Generated
Vendored
+1
@@ -97,6 +97,7 @@ spec:
|
||||
description: TimeAdded represents the time at which the taint was added.
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
value:
|
||||
description: Value is the taint value corresponding to the taint key.
|
||||
type: string
|
||||
|
||||
+1
@@ -109,6 +109,7 @@ type Taint struct {
|
||||
// +required
|
||||
Effect TaintEffect `json:"effect"`
|
||||
// TimeAdded represents the time at which the taint was added.
|
||||
// +nullable
|
||||
// +required
|
||||
TimeAdded metav1.Time `json:"timeAdded"`
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -295,7 +295,7 @@ spec:
|
||||
integer:
|
||||
description: Integer is the integer value when type is integer.
|
||||
type: integer
|
||||
format: int32
|
||||
format: int64
|
||||
string:
|
||||
description: String is the string value when when type is string.
|
||||
type: string
|
||||
|
||||
+3
-3
@@ -327,15 +327,15 @@ type FieldValue struct {
|
||||
|
||||
// Integer is the integer value when type is integer.
|
||||
// +optional
|
||||
Integer int32 `json:"integer,omitempty"`
|
||||
Integer *int64 `json:"integer,omitempty"`
|
||||
|
||||
// String is the string value when when type is string.
|
||||
// +optional
|
||||
String string `json:"string,omitempty"`
|
||||
String *string `json:"string,omitempty"`
|
||||
|
||||
// Boolean is bool value when type is boolean.
|
||||
// +optional
|
||||
Boolean bool `json:"boolean,omitempty"`
|
||||
Boolean *bool `json:"boolean,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=Integer;String;Boolean
|
||||
|
||||
+19
-2
@@ -170,7 +170,7 @@ func (in *FeedbackRule) DeepCopy() *FeedbackRule {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FeedbackValue) DeepCopyInto(out *FeedbackValue) {
|
||||
*out = *in
|
||||
out.Value = in.Value
|
||||
in.Value.DeepCopyInto(&out.Value)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -187,6 +187,21 @@ func (in *FeedbackValue) DeepCopy() *FeedbackValue {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FieldValue) DeepCopyInto(out *FieldValue) {
|
||||
*out = *in
|
||||
if in.Integer != nil {
|
||||
in, out := &in.Integer, &out.Integer
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.String != nil {
|
||||
in, out := &in.String, &out.String
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Boolean != nil {
|
||||
in, out := &in.Boolean, &out.Boolean
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -517,7 +532,9 @@ func (in *StatusFeedbackResult) DeepCopyInto(out *StatusFeedbackResult) {
|
||||
if in.Values != nil {
|
||||
in, out := &in.Values, &out.Values
|
||||
*out = make([]FeedbackValue, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user