Skip to content

Commit 9a979fc

Browse files
authored
feat: add webhook configuration for cluster resource placement eviction (#70)
1 parent df0a971 commit 9a979fc

File tree

7 files changed

+163
-70
lines changed

7 files changed

+163
-70
lines changed

pkg/controllers/clusterresourceplacementeviction/controller_intergration_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,55 @@ var _ = Describe("Test ClusterResourcePlacementEviction Controller", func() {
481481
checkEvictionCompleteMetric(customRegistry, "true", "true")
482482
})
483483
})
484+
485+
It("Invalid Eviction Blocked - PickFixed CRP, invalid eviction denied - No PDB specified", func() {
486+
crpName := fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())
487+
488+
// Create the CRP.
489+
By("Create ClusterResourcePlacement", func() {
490+
crp := placementv1beta1.ClusterResourcePlacement{
491+
ObjectMeta: metav1.ObjectMeta{
492+
Name: crpName,
493+
},
494+
Spec: placementv1beta1.ClusterResourcePlacementSpec{
495+
Policy: &placementv1beta1.PlacementPolicy{
496+
PlacementType: placementv1beta1.PickFixedPlacementType,
497+
ClusterNames: []string{"test-cluster-1"},
498+
},
499+
ResourceSelectors: []placementv1beta1.ClusterResourceSelector{
500+
{
501+
Group: "",
502+
Kind: "Namespace",
503+
Version: "v1",
504+
Name: "test-ns",
505+
},
506+
},
507+
},
508+
}
509+
Expect(k8sClient.Create(ctx, &crp)).Should(Succeed())
510+
// ensure CRP exists.
511+
Eventually(func() error {
512+
return k8sClient.Get(ctx, types.NamespacedName{Name: crp.Name}, &crp)
513+
}, eventuallyDuration, eventuallyInterval).Should(Succeed())
514+
})
515+
516+
By("Create ClusterResourcePlacementEviction", func() {
517+
eviction := buildTestEviction(evictionName, crpName, "test-cluster")
518+
Expect(k8sClient.Create(ctx, eviction)).Should(Succeed())
519+
})
520+
521+
By("Check eviction status", func() {
522+
evictionStatusUpdatedActual := testutilseviction.StatusUpdatedActual(
523+
ctx, k8sClient, evictionName,
524+
&testutilseviction.IsValidEviction{IsValid: false, Msg: condition.EvictionInvalidPickFixedCRPMessage},
525+
nil)
526+
Eventually(evictionStatusUpdatedActual, eventuallyDuration, eventuallyInterval).Should(Succeed())
527+
})
528+
529+
By("Ensure eviction complete metric was emitted", func() {
530+
checkEvictionCompleteMetric(customRegistry, "false", "true")
531+
})
532+
})
484533
})
485534

486535
func buildTestPickNCRP(crpName string, clusterCount int32) placementv1beta1.ClusterResourcePlacement {

pkg/webhook/add_handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package webhook
33
import (
44
"github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceoverride"
55
"github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceplacement"
6+
"github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceplacementeviction"
67
"github.com/kubefleet-dev/kubefleet/pkg/webhook/fleetresourcehandler"
78
"github.com/kubefleet-dev/kubefleet/pkg/webhook/membercluster"
89
"github.com/kubefleet-dev/kubefleet/pkg/webhook/pod"
@@ -21,4 +22,5 @@ func init() {
2122
AddToManagerFuncs = append(AddToManagerFuncs, membercluster.Add)
2223
AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceoverride.Add)
2324
AddToManagerFuncs = append(AddToManagerFuncs, resourceoverride.Add)
25+
AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceplacementeviction.Add)
2426
}

pkg/webhook/clusterresourceplacementeviction/clusterresourceplacementeviction_validating_webhook.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func Add(mgr manager.Manager) error {
5353
return nil
5454
}
5555

56-
// Handle clusterResourcePlacementEvictionValidator checks to see if resource override is valid.
56+
// Handle clusterResourcePlacementEvictionValidator checks to see if the eviction is valid.
5757
func (v *clusterResourcePlacementEvictionValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
5858
var crpe fleetv1beta1.ClusterResourcePlacementEviction
5959
klog.V(2).InfoS("Validating webhook handling cluster resource placement eviction", "operation", req.Operation, "clusterResourcePlacementEviction", req.Name)

pkg/webhook/webhook.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import (
5959
"github.com/kubefleet-dev/kubefleet/cmd/hubagent/options"
6060
"github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceoverride"
6161
"github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceplacement"
62+
"github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceplacementeviction"
6263
"github.com/kubefleet-dev/kubefleet/pkg/webhook/fleetresourcehandler"
6364
"github.com/kubefleet-dev/kubefleet/pkg/webhook/membercluster"
6465
"github.com/kubefleet-dev/kubefleet/pkg/webhook/pod"
@@ -113,6 +114,7 @@ const (
113114
podResourceName = "pods"
114115
clusterResourceOverrideName = "clusterresourceoverrides"
115116
resourceOverrideName = "resourceoverrides"
117+
evictionName = "clusterresourceplacementevictions"
116118
)
117119

118120
var (
@@ -362,6 +364,22 @@ func (w *Config) buildFleetValidatingWebhooks() []admv1.ValidatingWebhook {
362364
},
363365
TimeoutSeconds: longWebhookTimeout,
364366
},
367+
{
368+
Name: "fleet.clusterresourceplacementeviction.validating",
369+
ClientConfig: w.createClientConfig(clusterresourceplacementeviction.ValidationPath),
370+
FailurePolicy: &failFailurePolicy,
371+
SideEffects: &sideEffortsNone,
372+
AdmissionReviewVersions: admissionReviewVersions,
373+
Rules: []admv1.RuleWithOperations{
374+
{
375+
Operations: []admv1.OperationType{
376+
admv1.Create,
377+
},
378+
Rule: createRule([]string{placementv1beta1.GroupVersion.Group}, []string{placementv1beta1.GroupVersion.Version}, []string{evictionName}, &clusterScope),
379+
},
380+
},
381+
TimeoutSeconds: longWebhookTimeout,
382+
},
365383
}
366384

367385
return webHooks

pkg/webhook/webhook_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestBuildFleetValidatingWebhooks(t *testing.T) {
2525
serviceURL: "test-url",
2626
clientConnectionType: &url,
2727
},
28-
wantLength: 7,
28+
wantLength: 8,
2929
},
3030
}
3131

test/e2e/placement_eviction_test.go

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -31,74 +31,6 @@ import (
3131
testutilseviction "github.com/kubefleet-dev/kubefleet/test/utils/eviction"
3232
)
3333

34-
var _ = Describe("ClusterResourcePlacement eviction of bound binding - PickFixed CRP, invalid eviction denied - No PDB specified", Ordered, func() {
35-
crpName := fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())
36-
crpEvictionName := fmt.Sprintf(crpEvictionNameTemplate, GinkgoParallelProcess())
37-
38-
BeforeAll(func() {
39-
By("creating work resources")
40-
createWorkResources()
41-
42-
// Create the CRP.
43-
crp := &placementv1beta1.ClusterResourcePlacement{
44-
ObjectMeta: metav1.ObjectMeta{
45-
Name: crpName,
46-
// Add a custom finalizer; this would allow us to better observe
47-
// the behavior of the controllers.
48-
Finalizers: []string{customDeletionBlockerFinalizer},
49-
},
50-
Spec: placementv1beta1.ClusterResourcePlacementSpec{
51-
Policy: &placementv1beta1.PlacementPolicy{
52-
PlacementType: placementv1beta1.PickFixedPlacementType,
53-
ClusterNames: allMemberClusterNames,
54-
},
55-
ResourceSelectors: workResourceSelector(),
56-
},
57-
}
58-
Expect(hubClient.Create(ctx, crp)).To(Succeed(), "Failed to create CRP %s", crpName)
59-
})
60-
61-
AfterAll(func() {
62-
ensureCRPEvictionDeleted(crpEvictionName)
63-
ensureCRPAndRelatedResourcesDeleted(crpName, allMemberClusters)
64-
})
65-
66-
It("should update cluster resource placement status as expected", func() {
67-
crpStatusUpdatedActual := crpStatusUpdatedActual(workResourceIdentifiers(), allMemberClusterNames, nil, "0")
68-
Eventually(crpStatusUpdatedActual, eventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update cluster resource placement status as expected")
69-
})
70-
71-
It("should place resources on the all available member clusters", checkIfPlacedWorkResourcesOnAllMemberClusters)
72-
73-
It("create cluster resource placement eviction targeting member cluster 1", func() {
74-
crpe := &placementv1beta1.ClusterResourcePlacementEviction{
75-
ObjectMeta: metav1.ObjectMeta{
76-
Name: crpEvictionName,
77-
},
78-
Spec: placementv1beta1.PlacementEvictionSpec{
79-
PlacementName: crpName,
80-
ClusterName: memberCluster1EastProdName,
81-
},
82-
}
83-
Expect(hubClient.Create(ctx, crpe)).To(Succeed(), "Failed to create CRP eviction %s", crpe.Name)
84-
})
85-
86-
It("should update cluster resource placement eviction status as expected", func() {
87-
crpEvictionStatusUpdatedActual := testutilseviction.StatusUpdatedActual(
88-
ctx, hubClient, crpEvictionName,
89-
&testutilseviction.IsValidEviction{IsValid: false, Msg: condition.EvictionInvalidPickFixedCRPMessage},
90-
nil)
91-
Eventually(crpEvictionStatusUpdatedActual, eventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update cluster resource placement eviction status as expected")
92-
})
93-
94-
It("should ensure cluster resource placement status is unchanged", func() {
95-
crpStatusUpdatedActual := crpStatusUpdatedActual(workResourceIdentifiers(), allMemberClusterNames, nil, "0")
96-
Eventually(crpStatusUpdatedActual, eventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update cluster resource placement status as expected")
97-
})
98-
99-
It("should still place resources on the all available member clusters", checkIfPlacedWorkResourcesOnAllMemberClusters)
100-
})
101-
10234
var _ = Describe("ClusterResourcePlacement eviction of bound binding, taint cluster before eviction - No PDB specified", Ordered, Serial, func() {
10335
crpName := fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())
10436
crpEvictionName := fmt.Sprintf(crpEvictionNameTemplate, GinkgoParallelProcess())

test/e2e/webhook_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,3 +1291,95 @@ var _ = Describe("webhook tests for ResourceOverride UPDATE operations", Ordered
12911291
}, testutils.PollTimeout, testutils.PollInterval).Should(Succeed())
12921292
})
12931293
})
1294+
1295+
var _ = Describe("webhook tests for ClusterResourcePlacementEviction CREATE operations", Ordered, func() {
1296+
crpName := fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())
1297+
crpeName := fmt.Sprintf(crpEvictionNameTemplate, GinkgoParallelProcess())
1298+
1299+
AfterEach(func() {
1300+
By("deleting CRP")
1301+
cleanupCRP(crpName)
1302+
})
1303+
1304+
It("should deny create on CRPE with missing placement name", func() {
1305+
// Create the CRPE.
1306+
crpe := &placementv1beta1.ClusterResourcePlacementEviction{
1307+
ObjectMeta: metav1.ObjectMeta{
1308+
Name: crpeName,
1309+
},
1310+
Spec: placementv1beta1.PlacementEvictionSpec{
1311+
PlacementName: crpName,
1312+
},
1313+
}
1314+
By(fmt.Sprintf("expecting denial of CREATE eviction %s", crpeName))
1315+
err := hubClient.Create(ctx, crpe)
1316+
var statusErr *k8sErrors.StatusError
1317+
Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create CRPE call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{})))
1318+
Expect(statusErr.Status().Message).Should(ContainSubstring(fmt.Sprintf("ClusterResourcePlacement.placement.kubernetes-fleet.io \"%s\" not found", crpName)))
1319+
})
1320+
1321+
It("should deny create on CRPE with deleting crp", func() {
1322+
// Create the CRP with deletion timestamp.
1323+
crp := &placementv1beta1.ClusterResourcePlacement{
1324+
ObjectMeta: metav1.ObjectMeta{
1325+
Name: crpName,
1326+
Finalizers: []string{"example.com/finalizer"},
1327+
},
1328+
Spec: placementv1beta1.ClusterResourcePlacementSpec{
1329+
ResourceSelectors: workResourceSelector(),
1330+
Policy: &placementv1beta1.PlacementPolicy{
1331+
PlacementType: placementv1beta1.PickAllPlacementType,
1332+
},
1333+
},
1334+
}
1335+
Expect(hubClient.Create(ctx, crp)).Should(Succeed(), "Failed to create CRP %s", crpName)
1336+
// Delete the CRP to add deletion timestamp for check
1337+
Expect(hubClient.Delete(ctx, crp)).Should(Succeed(), "Failed to delete CRP %s", crpName)
1338+
1339+
// Create the CRPE.
1340+
crpe := &placementv1beta1.ClusterResourcePlacementEviction{
1341+
ObjectMeta: metav1.ObjectMeta{
1342+
Name: crpeName,
1343+
},
1344+
Spec: placementv1beta1.PlacementEvictionSpec{
1345+
PlacementName: crpName,
1346+
},
1347+
}
1348+
By(fmt.Sprintf("expecting denial of CREATE eviction %s", crpeName))
1349+
err := hubClient.Create(ctx, crpe)
1350+
var statusErr *k8sErrors.StatusError
1351+
Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create CRPE call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{})))
1352+
Expect(statusErr.Status().Message).Should(MatchRegexp(fmt.Sprintf("cluster resource placement %s is being deleted", crpName)))
1353+
})
1354+
1355+
It("should deny create on CRPE with PickFixed crp", func() {
1356+
crp := &placementv1beta1.ClusterResourcePlacement{
1357+
ObjectMeta: metav1.ObjectMeta{
1358+
Name: crpName,
1359+
},
1360+
Spec: placementv1beta1.ClusterResourcePlacementSpec{
1361+
ResourceSelectors: workResourceSelector(),
1362+
Policy: &placementv1beta1.PlacementPolicy{
1363+
PlacementType: placementv1beta1.PickFixedPlacementType,
1364+
ClusterNames: []string{"cluster1", "cluster2"},
1365+
},
1366+
},
1367+
}
1368+
Expect(hubClient.Create(ctx, crp)).Should(Succeed(), "Failed to create CRP %s", crpName)
1369+
1370+
// Create the CRPE.
1371+
crpe := &placementv1beta1.ClusterResourcePlacementEviction{
1372+
ObjectMeta: metav1.ObjectMeta{
1373+
Name: crpeName,
1374+
},
1375+
Spec: placementv1beta1.PlacementEvictionSpec{
1376+
PlacementName: crpName,
1377+
},
1378+
}
1379+
By(fmt.Sprintf("expecting denial of CREATE eviction %s", crpName))
1380+
err := hubClient.Create(ctx, crpe)
1381+
var statusErr *k8sErrors.StatusError
1382+
Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create CRPE call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8sErrors.StatusError{})))
1383+
Expect(statusErr.Status().Message).Should(MatchRegexp("cluster resource placement policy type PickFixed is not supported"))
1384+
})
1385+
})

0 commit comments

Comments
 (0)