Skip to content

Commit aa0afaf

Browse files
authored
Merge pull request #6686 from XiShanYongYe-Chang/consider-apienablement-complete
When controller calls IsAPIEnabled, it also considers whether the judgment result is trustworthy
2 parents ff7e097 + 0876615 commit aa0afaf

File tree

11 files changed

+256
-139
lines changed

11 files changed

+256
-139
lines changed

pkg/apis/cluster/v1alpha1/cluster_helper.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,52 @@ limitations under the License.
1616

1717
package v1alpha1
1818

19+
import (
20+
"k8s.io/apimachinery/pkg/api/meta"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/apimachinery/pkg/runtime/schema"
23+
)
24+
1925
// String returns a well-formatted string for the Cluster object.
2026
func (c *Cluster) String() string {
2127
return c.Name
2228
}
29+
30+
// APIEnablementStatus is the status of the specific API on the cluster.
31+
type APIEnablementStatus string
32+
33+
const (
34+
// APIEnabled means the cluster supports the specified API.
35+
APIEnabled APIEnablementStatus = "APIEnabled"
36+
// APIDisabled means the cluster does not support the specified API.
37+
APIDisabled APIEnablementStatus = "APIDisabled"
38+
// APIUnknown means it is unknown whether the cluster supports the specified API.
39+
APIUnknown APIEnablementStatus = "APIUnknown"
40+
)
41+
42+
// APIEnablement checks if the target API (or CRD) referenced by gvk has been installed in the cluster.
43+
// The check takes the CompleteAPIEnablements condition into account. If the CompleteAPIEnablements condition indicates
44+
// the current APIEnablements is Partial, it returns APIEnabled if the gvk is found in the list; otherwise, the status is considered APIUnknown.
45+
// This means that when the APIEnablements is Partial and the gvk is not present, we cannot definitively say the API is disabled.
46+
func (c *Cluster) APIEnablement(gvk schema.GroupVersionKind) APIEnablementStatus {
47+
targetGroupVersion := gvk.GroupVersion().String()
48+
for _, apiEnablement := range c.Status.APIEnablements {
49+
if apiEnablement.GroupVersion != targetGroupVersion {
50+
continue
51+
}
52+
for _, resource := range apiEnablement.Resources {
53+
if resource.Kind != gvk.Kind {
54+
continue
55+
}
56+
return APIEnabled
57+
}
58+
}
59+
60+
// If we have the complete APIEnablements list for the cluster,
61+
// we can confidently determine that the API is disabled if it was not found above.
62+
if meta.IsStatusConditionPresentAndEqual(c.Status.Conditions, ClusterConditionCompleteAPIEnablements, metav1.ConditionTrue) {
63+
return APIDisabled
64+
}
65+
66+
return APIUnknown
67+
}

pkg/apis/cluster/v1alpha1/cluster_helper_test.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"testing"
2222

2323
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/runtime/schema"
2425
)
2526

2627
func TestString(t *testing.T) {
@@ -64,3 +65,196 @@ func TestString(t *testing.T) {
6465
})
6566
}
6667
}
68+
69+
func TestAPIEnablement(t *testing.T) {
70+
tests := []struct {
71+
name string
72+
cluster *Cluster
73+
gvk schema.GroupVersionKind
74+
expected APIEnablementStatus
75+
}{
76+
{
77+
name: "API enabled - exact match found",
78+
cluster: &Cluster{
79+
Status: ClusterStatus{
80+
APIEnablements: []APIEnablement{
81+
{
82+
GroupVersion: "apps/v1",
83+
Resources: []APIResource{
84+
{Name: "deployments", Kind: "Deployment"},
85+
{Name: "replicasets", Kind: "ReplicaSet"},
86+
},
87+
},
88+
},
89+
Conditions: []metav1.Condition{
90+
{
91+
Type: ClusterConditionCompleteAPIEnablements,
92+
Status: metav1.ConditionTrue,
93+
},
94+
},
95+
},
96+
},
97+
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
98+
expected: APIEnabled,
99+
},
100+
{
101+
name: "API disabled - not found in complete list",
102+
cluster: &Cluster{
103+
Status: ClusterStatus{
104+
APIEnablements: []APIEnablement{
105+
{
106+
GroupVersion: "apps/v1",
107+
Resources: []APIResource{
108+
{Name: "deployments", Kind: "Deployment"},
109+
},
110+
},
111+
},
112+
Conditions: []metav1.Condition{
113+
{
114+
Type: ClusterConditionCompleteAPIEnablements,
115+
Status: metav1.ConditionTrue,
116+
},
117+
},
118+
},
119+
},
120+
gvk: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"},
121+
expected: APIDisabled,
122+
},
123+
{
124+
name: "API unknown - not found in partial list",
125+
cluster: &Cluster{
126+
Status: ClusterStatus{
127+
APIEnablements: []APIEnablement{
128+
{
129+
GroupVersion: "apps/v1",
130+
Resources: []APIResource{
131+
{Name: "deployments", Kind: "Deployment"},
132+
},
133+
},
134+
},
135+
Conditions: []metav1.Condition{
136+
{
137+
Type: ClusterConditionCompleteAPIEnablements,
138+
Status: metav1.ConditionFalse,
139+
},
140+
},
141+
},
142+
},
143+
gvk: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"},
144+
expected: APIUnknown,
145+
},
146+
{
147+
name: "API unknown - no CompleteAPIEnablements condition",
148+
cluster: &Cluster{
149+
Status: ClusterStatus{
150+
APIEnablements: []APIEnablement{
151+
{
152+
GroupVersion: "apps/v1",
153+
Resources: []APIResource{
154+
{Name: "deployments", Kind: "Deployment"},
155+
},
156+
},
157+
},
158+
Conditions: []metav1.Condition{},
159+
},
160+
},
161+
gvk: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"},
162+
expected: APIUnknown,
163+
},
164+
{
165+
name: "API enabled - found in core group",
166+
cluster: &Cluster{
167+
Status: ClusterStatus{
168+
APIEnablements: []APIEnablement{
169+
{
170+
GroupVersion: "v1",
171+
Resources: []APIResource{
172+
{Name: "pods", Kind: "Pod"},
173+
{Name: "services", Kind: "Service"},
174+
},
175+
},
176+
},
177+
Conditions: []metav1.Condition{
178+
{
179+
Type: ClusterConditionCompleteAPIEnablements,
180+
Status: metav1.ConditionTrue,
181+
},
182+
},
183+
},
184+
},
185+
gvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"},
186+
expected: APIEnabled,
187+
},
188+
{
189+
name: "API disabled - wrong kind in same group version",
190+
cluster: &Cluster{
191+
Status: ClusterStatus{
192+
APIEnablements: []APIEnablement{
193+
{
194+
GroupVersion: "apps/v1",
195+
Resources: []APIResource{
196+
{Name: "deployments", Kind: "Deployment"},
197+
},
198+
},
199+
},
200+
Conditions: []metav1.Condition{
201+
{
202+
Type: ClusterConditionCompleteAPIEnablements,
203+
Status: metav1.ConditionTrue,
204+
},
205+
},
206+
},
207+
},
208+
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"},
209+
expected: APIDisabled,
210+
},
211+
{
212+
name: "API disabled - empty APIEnablements with complete condition",
213+
cluster: &Cluster{
214+
Status: ClusterStatus{
215+
APIEnablements: []APIEnablement{},
216+
Conditions: []metav1.Condition{
217+
{
218+
Type: ClusterConditionCompleteAPIEnablements,
219+
Status: metav1.ConditionTrue,
220+
},
221+
},
222+
},
223+
},
224+
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
225+
expected: APIDisabled,
226+
},
227+
{
228+
name: "API enabled - custom resource found",
229+
cluster: &Cluster{
230+
Status: ClusterStatus{
231+
APIEnablements: []APIEnablement{
232+
{
233+
GroupVersion: "example.com/v1alpha1",
234+
Resources: []APIResource{
235+
{Name: "customresources", Kind: "CustomResource"},
236+
},
237+
},
238+
},
239+
Conditions: []metav1.Condition{
240+
{
241+
Type: ClusterConditionCompleteAPIEnablements,
242+
Status: metav1.ConditionTrue,
243+
},
244+
},
245+
},
246+
},
247+
gvk: schema.GroupVersionKind{Group: "example.com", Version: "v1alpha1", Kind: "CustomResource"},
248+
expected: APIEnabled,
249+
},
250+
}
251+
252+
for _, tt := range tests {
253+
t.Run(tt.name, func(t *testing.T) {
254+
result := tt.cluster.APIEnablement(tt.gvk)
255+
if result != tt.expected {
256+
t.Errorf("APIEnablement() = %v, want %v", result, tt.expected)
257+
}
258+
})
259+
}
260+
}

pkg/controllers/multiclusterservice/endpointslice_dispatch_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,8 @@ func (c *EndpointsliceDispatchController) dispatchEndpointSlice(ctx context.Cont
338338
"Consumer cluster %s is not ready, skip to propagate EndpointSlice", clusterName)
339339
continue
340340
}
341-
if !helper.IsAPIEnabled(clusterObj.Status.APIEnablements, util.EndpointSliceGVK.GroupVersion().String(), util.EndpointSliceGVK.Kind) {
341+
342+
if clusterObj.APIEnablement(util.EndpointSliceGVK) == clusterv1alpha1.APIDisabled {
342343
c.EventRecorder.Eventf(mcs, corev1.EventTypeWarning, events.EventReasonAPIIncompatible, "Consumer cluster %s does not support EndpointSlice", clusterName)
343344
continue
344345
}

pkg/controllers/multiclusterservice/mcs_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@ func (c *MCSController) propagateMultiClusterService(ctx context.Context, mcs *n
288288
"Provider cluster %s is not ready, skip to propagate MultiClusterService", clusterName)
289289
continue
290290
}
291-
if !helper.IsAPIEnabled(clusterObj.Status.APIEnablements, util.EndpointSliceGVK.GroupVersion().String(), util.EndpointSliceGVK.Kind) {
291+
292+
if clusterObj.APIEnablement(util.EndpointSliceGVK) == clusterv1alpha1.APIDisabled {
292293
c.EventRecorder.Eventf(mcs, corev1.EventTypeWarning, events.EventReasonAPIIncompatible, "Provider cluster %s does not support EndpointSlice", clusterName)
293294
continue
294295
}

pkg/controllers/multiclusterservice/mcs_controller_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ func TestPropagateMultiClusterService(t *testing.T) {
284284
Status: clusterv1alpha1.ClusterStatus{
285285
Conditions: []metav1.Condition{
286286
{Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue},
287+
{Type: clusterv1alpha1.ClusterConditionCompleteAPIEnablements, Status: metav1.ConditionTrue},
287288
},
288289
APIEnablements: []clusterv1alpha1.APIEnablement{
289290
{
@@ -340,6 +341,7 @@ func TestPropagateMultiClusterService(t *testing.T) {
340341
Status: clusterv1alpha1.ClusterStatus{
341342
Conditions: []metav1.Condition{
342343
{Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue},
344+
{Type: clusterv1alpha1.ClusterConditionCompleteAPIEnablements, Status: metav1.ConditionTrue},
343345
},
344346
APIEnablements: []clusterv1alpha1.APIEnablement{
345347
{
@@ -376,6 +378,7 @@ func TestPropagateMultiClusterService(t *testing.T) {
376378
Status: clusterv1alpha1.ClusterStatus{
377379
Conditions: []metav1.Condition{
378380
{Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue},
381+
{Type: clusterv1alpha1.ClusterConditionCompleteAPIEnablements, Status: metav1.ConditionTrue},
379382
},
380383
APIEnablements: []clusterv1alpha1.APIEnablement{
381384
{
@@ -392,6 +395,7 @@ func TestPropagateMultiClusterService(t *testing.T) {
392395
Status: clusterv1alpha1.ClusterStatus{
393396
Conditions: []metav1.Condition{
394397
{Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue},
398+
{Type: clusterv1alpha1.ClusterConditionCompleteAPIEnablements, Status: metav1.ConditionTrue},
395399
},
396400
APIEnablements: []clusterv1alpha1.APIEnablement{
397401
{
@@ -428,6 +432,7 @@ func TestPropagateMultiClusterService(t *testing.T) {
428432
Status: clusterv1alpha1.ClusterStatus{
429433
Conditions: []metav1.Condition{
430434
{Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue},
435+
{Type: clusterv1alpha1.ClusterConditionCompleteAPIEnablements, Status: metav1.ConditionTrue},
431436
},
432437
APIEnablements: []clusterv1alpha1.APIEnablement{
433438
{
@@ -452,6 +457,7 @@ func TestPropagateMultiClusterService(t *testing.T) {
452457
Status: clusterv1alpha1.ClusterStatus{
453458
Conditions: []metav1.Condition{
454459
{Type: clusterv1alpha1.ClusterConditionReady, Status: metav1.ConditionTrue},
460+
{Type: clusterv1alpha1.ClusterConditionCompleteAPIEnablements, Status: metav1.ConditionTrue},
455461
},
456462
APIEnablements: []clusterv1alpha1.APIEnablement{
457463
{

pkg/scheduler/framework/plugins/apienablement/api_enablement.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ package apienablement
1919
import (
2020
"context"
2121

22+
"k8s.io/apimachinery/pkg/runtime/schema"
2223
"k8s.io/klog/v2"
2324

2425
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
2526
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
2627
"github.com/karmada-io/karmada/pkg/scheduler/framework"
27-
"github.com/karmada-io/karmada/pkg/util/helper"
2828
)
2929

3030
const (
@@ -68,7 +68,7 @@ func (p *APIEnablement) Filter(
6868
// we enforce strict checks to maintain consistency. This may occasionally
6969
// exclude clusters prematurely. Users requiring a specific number of target
7070
// clusters should use SpreadConstraints(in PropagationPolicy) to meet their requirements.
71-
if helper.IsAPIEnabled(cluster.Status.APIEnablements, bindingSpec.Resource.APIVersion, bindingSpec.Resource.Kind) {
71+
if cluster.APIEnablement(schema.FromAPIVersionAndKind(bindingSpec.Resource.APIVersion, bindingSpec.Resource.Kind)) == clusterv1alpha1.APIEnabled {
7272
return framework.NewResult(framework.Success)
7373
}
7474

pkg/search/controller.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import (
4848
"github.com/karmada-io/karmada/pkg/util/fedinformer"
4949
"github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager"
5050
"github.com/karmada-io/karmada/pkg/util/gclient"
51-
"github.com/karmada-io/karmada/pkg/util/helper"
5251
"github.com/karmada-io/karmada/pkg/util/restmapper"
5352
)
5453

@@ -412,7 +411,7 @@ func (c *Controller) doCacheCluster(cluster string) error {
412411
klog.Errorf("Failed to get gvk: %v", err)
413412
continue
414413
}
415-
if !helper.IsAPIEnabled(cls.Status.APIEnablements, gvk.GroupVersion().String(), gvk.Kind) {
414+
if cls.APIEnablement(gvk) == clusterv1alpha1.APIDisabled {
416415
klog.Warningf("Resource %s is not enabled for cluster %s", gvr.String(), cluster)
417416
continue
418417
}

pkg/search/proxy/controller.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import (
5050
pluginruntime "github.com/karmada-io/karmada/pkg/search/proxy/framework/runtime"
5151
"github.com/karmada-io/karmada/pkg/search/proxy/store"
5252
"github.com/karmada-io/karmada/pkg/util"
53-
"github.com/karmada-io/karmada/pkg/util/helper"
5453
"github.com/karmada-io/karmada/pkg/util/lifted"
5554
"github.com/karmada-io/karmada/pkg/util/names"
5655
"github.com/karmada-io/karmada/pkg/util/restmapper"
@@ -246,8 +245,8 @@ func (ctl *Controller) mergeResourcesByClusters(resourcesByClusters map[string]m
246245
klog.Errorf("Failed to get gvk: %v", err)
247246
continue
248247
}
249-
if !helper.IsAPIEnabled(cluster.Status.APIEnablements, gvk.GroupVersion().String(), gvk.Kind) {
250-
klog.Warningf("Resource %s is not enabled for cluster %s", resource.String(), cluster)
248+
if cluster.APIEnablement(gvk) == clusterv1alpha1.APIDisabled {
249+
klog.Warningf("Resource %s is not enabled for cluster %s", resource.String(), cluster.Name)
251250
continue
252251
}
253252
if ns, exist := resourcesByClusters[cluster.Name][resource]; !exist {

0 commit comments

Comments
 (0)