Skip to content

Commit 447e507

Browse files
authored
Added startucpuboost validating webhook (#16)
* Added startucpuboost validating webhook
1 parent 44c289b commit 447e507

File tree

8 files changed

+263
-8
lines changed

8 files changed

+263
-8
lines changed

PROJECT

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ resources:
1717
kind: StartupCPUBoost
1818
path: github.com/google/kube-startup-cpu-boost/api/v1alpha1
1919
version: v1alpha1
20+
webhooks:
21+
validation: true
22+
webhookVersion: v1
2023
version: "3"

api/v1alpha1/startupcpuboost_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type DurationPolicy struct {
6060
PodCondition *PodConditionDurationPolicy `json:"podCondition,omitempty"`
6161
}
6262

63-
// PercentagePolicy defines the policy used to determine the target
63+
// PercentageIncrease defines the policy used to determine the target
6464
// resources for a container
6565
type PercentageIncrease struct {
6666
// Value specifies the percentage value

cmd/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ func setupControllers(mgr ctrl.Manager, boostMgr boost.Manager, certsReady chan
122122
<-certsReady
123123
setupLog.Info("Certificate generation has completed")
124124

125+
if failedWebhook, err := boostWebhook.Setup(mgr); err != nil {
126+
setupLog.Error(err, "Unable to create webhook", "webhook", failedWebhook)
127+
os.Exit(1)
128+
}
125129
cpuBoostWebHook := boostWebhook.NewPodCPUBoostWebHook(boostMgr, scheme)
126130
mgr.GetWebhookServer().Register("/mutate-v1-pod", cpuBoostWebHook)
127131

config/webhook/manifests.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,29 @@ webhooks:
2323
resources:
2424
- pods
2525
sideEffects: None
26+
---
27+
apiVersion: admissionregistration.k8s.io/v1
28+
kind: ValidatingWebhookConfiguration
29+
metadata:
30+
name: validating-webhook-configuration
31+
webhooks:
32+
- admissionReviewVersions:
33+
- v1
34+
clientConfig:
35+
service:
36+
name: webhook-service
37+
namespace: system
38+
path: /validate-autoscaling-x-k8s-io-v1alpha1-startupcpuboost
39+
failurePolicy: Fail
40+
name: vstartupcpuboost.autoscaling.x-k8s.io
41+
rules:
42+
- apiGroups:
43+
- autoscaling.x-k8s.io
44+
apiVersions:
45+
- v1alpha1
46+
operations:
47+
- CREATE
48+
- UPDATE
49+
resources:
50+
- startupcpuboosts
51+
sideEffects: None

internal/util/cert.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ import (
2323
)
2424

2525
const (
26-
certDir = "/tmp/k8s-webhook-server/serving-certs"
27-
podBoostWebHookName = "kube-startup-cpu-boost-mutating-webhook-configuration"
28-
caName = "kube-startup-cpu-boost-ca"
29-
caOrganization = "kube-startup-cpu-boost"
30-
webhookServiceName = "kube-startup-cpu-boost-webhook-service"
31-
webhookSecretName = "kube-startup-cpu-boost-webhook-secret"
26+
certDir = "/tmp/k8s-webhook-server/serving-certs"
27+
boostMutatingWebHookName = "kube-startup-cpu-boost-mutating-webhook-configuration"
28+
boostValidatingWebHookName = "kube-startup-cpu-boost-validating-webhook-configuration"
29+
caName = "kube-startup-cpu-boost-ca"
30+
caOrganization = "kube-startup-cpu-boost"
31+
webhookServiceName = "kube-startup-cpu-boost-webhook-service"
32+
webhookSecretName = "kube-startup-cpu-boost-webhook-secret"
3233
)
3334

3435
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;update
@@ -49,7 +50,10 @@ func ManageCerts(mgr ctrl.Manager, namespace string, setupFinished chan struct{}
4950
IsReady: setupFinished,
5051
Webhooks: []cert.WebhookInfo{{
5152
Type: cert.Mutating,
52-
Name: podBoostWebHookName,
53+
Name: boostMutatingWebHookName,
54+
}, {
55+
Type: cert.Validating,
56+
Name: boostValidatingWebHookName,
5357
}},
5458
RequireLeaderElection: false,
5559
})
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2023 Google LLC
2+
//
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+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package webhook
16+
17+
import (
18+
"context"
19+
"errors"
20+
21+
"github.com/google/kube-startup-cpu-boost/api/v1alpha1"
22+
apierrors "k8s.io/apimachinery/pkg/api/errors"
23+
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/runtime/schema"
25+
"k8s.io/apimachinery/pkg/util/validation/field"
26+
"k8s.io/klog/v2"
27+
ctrl "sigs.k8s.io/controller-runtime"
28+
"sigs.k8s.io/controller-runtime/pkg/webhook"
29+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
30+
)
31+
32+
type StartupCPUBoostWebhook struct{}
33+
34+
var _ webhook.CustomValidator = &StartupCPUBoostWebhook{}
35+
36+
func setupWebhookForStartupCPUBoost(mgr ctrl.Manager) error {
37+
return ctrl.NewWebhookManagedBy(mgr).
38+
For(&v1alpha1.StartupCPUBoost{}).
39+
WithValidator(&StartupCPUBoostWebhook{}).
40+
Complete()
41+
}
42+
43+
// +kubebuilder:webhook:path=/validate-autoscaling-x-k8s-io-v1alpha1-startupcpuboost,mutating=false,failurePolicy=fail,sideEffects=None,groups=autoscaling.x-k8s.io,resources=startupcpuboosts,verbs=create;update,versions=v1alpha1,name=vstartupcpuboost.autoscaling.x-k8s.io,admissionReviewVersions=v1
44+
45+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type
46+
func (w *StartupCPUBoostWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
47+
boost := obj.(*v1alpha1.StartupCPUBoost)
48+
log := ctrl.LoggerFrom(ctx).WithName("startupcpuboost-webhook")
49+
log.V(5).Info("validating create", "startupcpuboost", klog.KObj(boost))
50+
return nil, validate(boost)
51+
}
52+
53+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type
54+
func (w *StartupCPUBoostWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
55+
boost := newObj.(*v1alpha1.StartupCPUBoost)
56+
log := ctrl.LoggerFrom(ctx).WithName("startupcpuboost-webhook")
57+
log.V(5).Info("validating update", "startupcpuboost", klog.KObj(boost))
58+
return nil, validate(boost)
59+
}
60+
61+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type
62+
func (w *StartupCPUBoostWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
63+
return nil, nil
64+
}
65+
66+
// validate verifies if Startup CPU Boost is valid. This is programmatic
67+
// validation on a top of declarative API validation
68+
func validate(boost *v1alpha1.StartupCPUBoost) error {
69+
var allErrs field.ErrorList
70+
if err := validateDurationPolicy(boost.Spec.DurationPolicy); err != nil {
71+
allErrs = append(allErrs, err)
72+
}
73+
if len(allErrs) > 0 {
74+
return apierrors.NewInvalid(
75+
schema.GroupKind{Group: "autoscaling.x-k8s.io", Kind: "StartupCPUBoost"},
76+
boost.Name, allErrs)
77+
}
78+
return nil
79+
}
80+
81+
func validateDurationPolicy(policy v1alpha1.DurationPolicy) *field.Error {
82+
var cnt int
83+
fldPath := field.NewPath("spec").Child("")
84+
if policy.Fixed != nil {
85+
cnt++
86+
}
87+
if policy.PodCondition != nil {
88+
cnt++
89+
}
90+
if cnt != 1 {
91+
err := errors.New("one type of duration policy should be defined")
92+
return field.Invalid(fldPath, policy, err.Error())
93+
}
94+
return nil
95+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2023 Google LLC
2+
//
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+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package webhook_test
16+
17+
import (
18+
"context"
19+
20+
"github.com/google/kube-startup-cpu-boost/api/v1alpha1"
21+
"github.com/google/kube-startup-cpu-boost/internal/webhook"
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
)
25+
26+
var _ = Describe("StartupCPUBoost webhook", func() {
27+
var w webhook.StartupCPUBoostWebhook
28+
BeforeEach(func() {
29+
w = webhook.StartupCPUBoostWebhook{}
30+
})
31+
32+
When("Validates StartupCPUBoost", func() {
33+
var (
34+
boost v1alpha1.StartupCPUBoost
35+
err error
36+
)
37+
When("Startup CPU Boost has no duration policy", func() {
38+
BeforeEach(func() {
39+
boost = v1alpha1.StartupCPUBoost{
40+
Spec: v1alpha1.StartupCPUBoostSpec{
41+
DurationPolicy: v1alpha1.DurationPolicy{},
42+
},
43+
}
44+
})
45+
It("errors", func() {
46+
By("validating create event")
47+
_, err = w.ValidateCreate(context.TODO(), &boost)
48+
Expect(err).To(HaveOccurred())
49+
50+
By("validating update event")
51+
_, err = w.ValidateUpdate(context.TODO(), nil, &boost)
52+
Expect(err).To(HaveOccurred())
53+
})
54+
})
55+
When("Startup CPU Boost has more than one duration policy", func() {
56+
BeforeEach(func() {
57+
boost = v1alpha1.StartupCPUBoost{
58+
Spec: v1alpha1.StartupCPUBoostSpec{
59+
DurationPolicy: v1alpha1.DurationPolicy{
60+
Fixed: &v1alpha1.FixedDurationPolicy{},
61+
PodCondition: &v1alpha1.PodConditionDurationPolicy{},
62+
},
63+
},
64+
}
65+
})
66+
It("errors", func() {
67+
By("validating create event")
68+
_, err = w.ValidateCreate(context.TODO(), &boost)
69+
Expect(err).To(HaveOccurred())
70+
71+
By("validating update event")
72+
_, err = w.ValidateUpdate(context.TODO(), nil, &boost)
73+
Expect(err).To(HaveOccurred())
74+
})
75+
})
76+
When("Startup CPU Boost has one duration policy", func() {
77+
BeforeEach(func() {
78+
boost = v1alpha1.StartupCPUBoost{
79+
Spec: v1alpha1.StartupCPUBoostSpec{
80+
DurationPolicy: v1alpha1.DurationPolicy{
81+
PodCondition: &v1alpha1.PodConditionDurationPolicy{},
82+
},
83+
},
84+
}
85+
})
86+
It("does not error", func() {
87+
By("validating create event")
88+
_, err = w.ValidateCreate(context.TODO(), &boost)
89+
Expect(err).NotTo(HaveOccurred())
90+
91+
By("validating update event")
92+
_, err = w.ValidateUpdate(context.TODO(), nil, &boost)
93+
Expect(err).NotTo(HaveOccurred())
94+
})
95+
})
96+
})
97+
})

internal/webhook/webhooks.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2023 Google LLC
2+
//
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+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package webhook
16+
17+
import ctrl "sigs.k8s.io/controller-runtime"
18+
19+
// Setup sets up the webhooks for core controllers. It returns the name of the
20+
// webhook that failed to create and an error, if any.
21+
func Setup(mgr ctrl.Manager) (string, error) {
22+
if err := setupWebhookForStartupCPUBoost(mgr); err != nil {
23+
return "StartupCPUBoost", err
24+
}
25+
return "", nil
26+
}

0 commit comments

Comments
 (0)