Skip to content

Commit 2e60a6e

Browse files
authored
Merge pull request #5494 from mohamedawnallah/unitTestClusterPropagationPolicy
pkg/webhook: unit test cluster propagation policy
2 parents c3d582b + 8795864 commit 2e60a6e

File tree

2 files changed

+402
-0
lines changed

2 files changed

+402
-0
lines changed
+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
Copyright 2024 The Karmada Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package clusterpropagationpolicy
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"errors"
23+
"net/http"
24+
"reflect"
25+
"testing"
26+
27+
admissionv1 "k8s.io/api/admission/v1"
28+
corev1 "k8s.io/api/core/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
"k8s.io/utils/ptr"
32+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
33+
mcsv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
34+
35+
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
36+
"github.com/karmada-io/karmada/pkg/util"
37+
"github.com/karmada-io/karmada/pkg/util/helper"
38+
)
39+
40+
var (
41+
notReadyTolerationSeconds int64 = 300
42+
unreachableTolerationSeconds int64 = 300
43+
failOverGracePeriodSeconds int32 = 600
44+
)
45+
46+
type fakeMutationDecoder struct {
47+
err error
48+
obj runtime.Object
49+
}
50+
51+
// Decode mocks the Decode method of admission.Decoder.
52+
func (f *fakeMutationDecoder) Decode(_ admission.Request, obj runtime.Object) error {
53+
if f.err != nil {
54+
return f.err
55+
}
56+
if f.obj != nil {
57+
reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(f.obj).Elem())
58+
}
59+
return nil
60+
}
61+
62+
// DecodeRaw mocks the DecodeRaw method of admission.Decoder.
63+
func (f *fakeMutationDecoder) DecodeRaw(_ runtime.RawExtension, obj runtime.Object) error {
64+
if f.err != nil {
65+
return f.err
66+
}
67+
if f.obj != nil {
68+
reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(f.obj).Elem())
69+
}
70+
return nil
71+
}
72+
73+
func TestMutatingAdmission_Handle(t *testing.T) {
74+
tests := []struct {
75+
name string
76+
decoder admission.Decoder
77+
req admission.Request
78+
want admission.Response
79+
}{
80+
{
81+
name: "Handle_DecodeError_DeniesAdmission",
82+
decoder: &fakeMutationDecoder{
83+
err: errors.New("decode error"),
84+
},
85+
req: admission.Request{},
86+
want: admission.Errored(http.StatusBadRequest, errors.New("decode error")),
87+
},
88+
}
89+
90+
for _, tt := range tests {
91+
t.Run(tt.name, func(t *testing.T) {
92+
v := NewMutatingHandler(
93+
notReadyTolerationSeconds, unreachableTolerationSeconds, tt.decoder,
94+
)
95+
got := v.Handle(context.Background(), tt.req)
96+
if !reflect.DeepEqual(got, tt.want) {
97+
t.Errorf("Handle() = %v, want %v", got, tt.want)
98+
}
99+
})
100+
}
101+
}
102+
103+
func TestMutatingAdmission_Handle_FullCoverage(t *testing.T) {
104+
// Define the cp policy name to be used in the test.
105+
policyName := "test-cp-policy"
106+
107+
// Mock admission request with no specific namespace.
108+
req := admission.Request{
109+
AdmissionRequest: admissionv1.AdmissionRequest{
110+
Operation: admissionv1.Create,
111+
},
112+
}
113+
114+
// Create the initial cp policy with default values for testing.
115+
cpPolicy := &policyv1alpha1.ClusterPropagationPolicy{
116+
ObjectMeta: metav1.ObjectMeta{
117+
Name: policyName,
118+
},
119+
Spec: policyv1alpha1.PropagationSpec{
120+
Placement: policyv1alpha1.Placement{
121+
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
122+
{SpreadByLabel: "", SpreadByField: "", MinGroups: 0},
123+
},
124+
ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{
125+
ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDivided,
126+
},
127+
},
128+
PropagateDeps: false,
129+
ResourceSelectors: []policyv1alpha1.ResourceSelector{
130+
{
131+
Kind: util.ServiceImportKind,
132+
APIVersion: mcsv1alpha1.GroupVersion.String(),
133+
},
134+
},
135+
Failover: &policyv1alpha1.FailoverBehavior{
136+
Application: &policyv1alpha1.ApplicationFailoverBehavior{
137+
PurgeMode: policyv1alpha1.Graciously,
138+
GracePeriodSeconds: nil,
139+
},
140+
},
141+
},
142+
}
143+
144+
// Define the expected cp policy after mutations.
145+
wantCPPolicy := &policyv1alpha1.ClusterPropagationPolicy{
146+
ObjectMeta: metav1.ObjectMeta{
147+
Name: policyName,
148+
Labels: map[string]string{
149+
policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "some-unique-uuid",
150+
},
151+
Finalizers: []string{util.ClusterPropagationPolicyControllerFinalizer},
152+
},
153+
Spec: policyv1alpha1.PropagationSpec{
154+
Placement: policyv1alpha1.Placement{
155+
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
156+
{
157+
SpreadByField: policyv1alpha1.SpreadByFieldCluster,
158+
MinGroups: 1,
159+
},
160+
},
161+
ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{
162+
ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDivided,
163+
ReplicaDivisionPreference: policyv1alpha1.ReplicaDivisionPreferenceWeighted,
164+
},
165+
ClusterTolerations: []corev1.Toleration{
166+
*helper.NewNotReadyToleration(notReadyTolerationSeconds),
167+
*helper.NewUnreachableToleration(unreachableTolerationSeconds),
168+
},
169+
},
170+
PropagateDeps: true,
171+
ResourceSelectors: []policyv1alpha1.ResourceSelector{
172+
{
173+
Kind: util.ServiceImportKind,
174+
APIVersion: mcsv1alpha1.GroupVersion.String(),
175+
},
176+
},
177+
Failover: &policyv1alpha1.FailoverBehavior{
178+
Application: &policyv1alpha1.ApplicationFailoverBehavior{
179+
PurgeMode: policyv1alpha1.Graciously,
180+
GracePeriodSeconds: ptr.To[int32](failOverGracePeriodSeconds),
181+
},
182+
},
183+
},
184+
}
185+
186+
// Mock decoder that decodes the request into the cp policy object.
187+
decoder := &fakeMutationDecoder{
188+
obj: cpPolicy,
189+
}
190+
191+
// Marshal the expected cp policy to simulate the final mutated object.
192+
wantBytes, err := json.Marshal(wantCPPolicy)
193+
if err != nil {
194+
t.Fatalf("Failed to marshal expected cp policy: %v", err)
195+
}
196+
req.Object.Raw = wantBytes
197+
198+
// Instantiate the mutating handler.
199+
mutatingHandler := NewMutatingHandler(
200+
notReadyTolerationSeconds, unreachableTolerationSeconds, decoder,
201+
)
202+
203+
// Call the Handle function.
204+
got := mutatingHandler.Handle(context.Background(), req)
205+
206+
// Check if exactly one patch is applied.
207+
if len(got.Patches) != 1 {
208+
t.Errorf("Handle() returned an unexpected number of patches. Expected one patch, received: %v", got.Patches)
209+
}
210+
211+
// Verify that the only patch applied is for the UUID label.
212+
// If any other patches are present, it indicates that the cp policy was not handled as expected.
213+
firstPatch := got.Patches[0]
214+
if firstPatch.Operation != "replace" || firstPatch.Path != "/metadata/labels/clusterpropagationpolicy.karmada.io~1permanent-id" {
215+
t.Errorf("Handle() returned unexpected patches. Only the UUID patch was expected. Received patches: %v", got.Patches)
216+
}
217+
218+
// Check if the admission request was allowed.
219+
if !got.Allowed {
220+
t.Errorf("Handle() got.Allowed = false, want true")
221+
}
222+
}

0 commit comments

Comments
 (0)