Skip to content

Commit 71b967a

Browse files
authored
feat: add validator functions for placement disruption budget to use in webhook (#68)
1 parent 4142f9c commit 71b967a

File tree

8 files changed

+448
-25
lines changed

8 files changed

+448
-25
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ local-unit-test: $(ENVTEST) ## Run tests.
141141
export CGO_ENABLED=1 && \
142142
export KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" && \
143143
go test ./pkg/controllers/workv1alpha1 -race -coverprofile=ut-coverage.xml -covermode=atomic -v && \
144-
go test `go list ./pkg/... ./cmd/... | grep -v pkg/controllers/workv1alpha1` -race -coverprofile=ut-coverage.xml -covermode=atomic -v
144+
go test `go list ./pkg/... ./cmd/... | grep -v pkg/controllers/workv1alpha1` -race -coverpkg=./... -coverprofile=ut-coverage.xml -covermode=atomic -v
145145

146146
.PHONY: integration-test
147147
integration-test: $(ENVTEST) ## Run tests.

pkg/utils/common.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ var (
171171
Kind: placementv1beta1.ClusterResourcePlacementKind,
172172
}
173173

174+
ClusterResourcePlacementDisruptionBudgetMetaGVK = metav1.GroupVersionKind{
175+
Group: placementv1beta1.GroupVersion.Group,
176+
Version: placementv1beta1.GroupVersion.Version,
177+
Kind: placementv1beta1.ClusterResourcePlacementDisruptionBudgetKind,
178+
}
179+
174180
ClusterResourcePlacementEvictionMetaGVK = metav1.GroupVersionKind{
175181
Group: placementv1beta1.GroupVersion.Group,
176182
Version: placementv1beta1.GroupVersion.Version,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
// Package validator provides utils to validate ClusterResourcePlacementDisruptionBudget resources.
15+
package validator
16+
17+
import (
18+
"fmt"
19+
20+
"k8s.io/apimachinery/pkg/util/errors"
21+
"k8s.io/apimachinery/pkg/util/intstr"
22+
23+
fleetv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
24+
)
25+
26+
// ValidateClusterResourcePlacementDisruptionBudget validates cluster resource placement disruption budget fields based on crp placement type and returns error.
27+
func ValidateClusterResourcePlacementDisruptionBudget(db *fleetv1beta1.ClusterResourcePlacementDisruptionBudget, crp *fleetv1beta1.ClusterResourcePlacement) error {
28+
allErr := make([]error, 0)
29+
30+
// Check ClusterResourcePlacementDisruptionBudget fields if CRP is PickAll placement type
31+
if crp.Spec.Policy == nil || crp.Spec.Policy.PlacementType == fleetv1beta1.PickAllPlacementType {
32+
if db.Spec.MaxUnavailable != nil {
33+
allErr = append(allErr, fmt.Errorf("cluster resource placement policy type PickAll is not supported with any specified max unavailable %v", db.Spec.MaxUnavailable))
34+
}
35+
if db.Spec.MinAvailable != nil && db.Spec.MinAvailable.Type == intstr.String {
36+
allErr = append(allErr, fmt.Errorf("cluster resource placement policy type PickAll is not supported with min available as a percentage %v", db.Spec.MinAvailable))
37+
}
38+
}
39+
40+
return errors.NewAggregate(allErr)
41+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
// Package clusterresourceplacementdisruptionbudget provides a validating webhook for the clusterresourceplacementdisruptionbudget custom resource in the KubeFleet API group.
15+
package clusterresourceplacementdisruptionbudget
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"net/http"
21+
22+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
23+
"k8s.io/apimachinery/pkg/types"
24+
"k8s.io/klog/v2"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
"sigs.k8s.io/controller-runtime/pkg/manager"
27+
"sigs.k8s.io/controller-runtime/pkg/webhook"
28+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
29+
30+
fleetv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
31+
"github.com/kubefleet-dev/kubefleet/pkg/utils"
32+
"github.com/kubefleet-dev/kubefleet/pkg/utils/validator"
33+
)
34+
35+
var (
36+
// ValidationPath is the webhook service path which admission requests are routed to for validating clusterresourceplacementdisruptionbudget resources.
37+
ValidationPath = fmt.Sprintf(utils.ValidationPathFmt, fleetv1beta1.GroupVersion.Group, fleetv1beta1.GroupVersion.Version, "clusterresourceplacementdisruptionbudget")
38+
)
39+
40+
type clusterResourcePlacementDisruptionBudgetValidator struct {
41+
client client.Client
42+
decoder webhook.AdmissionDecoder
43+
}
44+
45+
// Add registers the webhook for K8s bulit-in object types.
46+
func Add(mgr manager.Manager) error {
47+
hookServer := mgr.GetWebhookServer()
48+
hookServer.Register(ValidationPath, &webhook.Admission{Handler: &clusterResourcePlacementDisruptionBudgetValidator{mgr.GetClient(), admission.NewDecoder(mgr.GetScheme())}})
49+
return nil
50+
}
51+
52+
// Handle clusterResourcePlacementDisruptionBudgetValidator checks to see if resource override is valid.
53+
func (v *clusterResourcePlacementDisruptionBudgetValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
54+
var db fleetv1beta1.ClusterResourcePlacementDisruptionBudget
55+
klog.V(2).InfoS("Validating webhook handling cluster resource placement disruption budget", "operation", req.Operation, "clusterResourcePlacementDisruptionBudget", req.Name)
56+
if err := v.decoder.Decode(req, &db); err != nil {
57+
klog.ErrorS(err, "Failed to decode cluster resource placement disruption budget object for validating fields", "userName", req.UserInfo.Username, "groups", req.UserInfo.Groups, "clusterResourcePlacementDisruptionBudget", req.Name)
58+
return admission.Errored(http.StatusBadRequest, err)
59+
}
60+
61+
// Get the corresponding ClusterResourcePlacement object
62+
var crp fleetv1beta1.ClusterResourcePlacement
63+
if err := v.client.Get(ctx, types.NamespacedName{Name: db.Name}, &crp); err != nil {
64+
if k8serrors.IsNotFound(err) {
65+
klog.V(2).InfoS("The corresponding ClusterResourcePlacement object does not exist", "clusterResourcePlacementDisruptionBudget", db.Name, "clusterResourcePlacement", db.Name)
66+
return admission.Allowed("Associated clusterResourcePlacement object for clusterResourcePlacementDisruptionBudget is not found")
67+
}
68+
return admission.Errored(http.StatusBadRequest, fmt.Errorf("failed to get clusterResourcePlacement %s for clusterResourcePlacementDisruptionBudget %s: %w", db.Name, db.Name, err))
69+
}
70+
71+
if err := validator.ValidateClusterResourcePlacementDisruptionBudget(&db, &crp); err != nil {
72+
klog.V(2).ErrorS(err, "ClusterResourcePlacementDisruptionBudget has invalid fields, request is denied", "operation", req.Operation, "clusterResourcePlacementDisruptionBudget", db.Name)
73+
return admission.Denied(err.Error())
74+
}
75+
76+
klog.V(2).InfoS("ClusterResourcePlacementDisruptionBudget has valid fields", "clusterResourcePlacementDisruptionBudget", db.Name)
77+
return admission.Allowed("clusterResourcePlacementDisruptionBudget has valid fields")
78+
}

0 commit comments

Comments
 (0)