vertical-pod-autoscaler/pkg/admission-controller/logic/server.go (133 lines of code) (raw):
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logic
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod/patch"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/limitrange"
metrics_admission "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/admission"
"k8s.io/klog/v2"
)
// AdmissionServer is an admission webhook server that modifies pod resources request based on VPA recommendation
type AdmissionServer struct {
limitsChecker limitrange.LimitRangeCalculator
resourceHandlers map[metav1.GroupResource]resource.Handler
}
// NewAdmissionServer constructs new AdmissionServer
func NewAdmissionServer(podPreProcessor pod.PreProcessor,
vpaPreProcessor vpa.PreProcessor,
limitsChecker limitrange.LimitRangeCalculator,
vpaMatcher vpa.Matcher,
patchCalculators []patch.Calculator) *AdmissionServer {
as := &AdmissionServer{limitsChecker, map[metav1.GroupResource]resource.Handler{}}
as.RegisterResourceHandler(pod.NewResourceHandler(podPreProcessor, vpaMatcher, patchCalculators))
as.RegisterResourceHandler(vpa.NewResourceHandler(vpaPreProcessor))
return as
}
// RegisterResourceHandler allows to register a custom logic for handling given types of resources.
func (s *AdmissionServer) RegisterResourceHandler(resourceHandler resource.Handler) {
s.resourceHandlers[resourceHandler.GroupResource()] = resourceHandler
}
func (s *AdmissionServer) admit(ctx context.Context, data []byte) (*admissionv1.AdmissionResponse, metrics_admission.AdmissionStatus, metrics_admission.AdmissionResource) {
// we don't block the admission by default, even on unparsable JSON
response := admissionv1.AdmissionResponse{}
response.Allowed = true
ar := admissionv1.AdmissionReview{}
if err := json.Unmarshal(data, &ar); err != nil {
klog.Error(err)
return &response, metrics_admission.Error, metrics_admission.Unknown
}
response.UID = ar.Request.UID
var patches []resource.PatchRecord
var err error
resource := metrics_admission.Unknown
admittedGroupResource := metav1.GroupResource{
Group: ar.Request.Resource.Group,
Resource: ar.Request.Resource.Resource,
}
handler, ok := s.resourceHandlers[admittedGroupResource]
if ok {
patches, err = handler.GetPatches(ctx, ar.Request)
resource = handler.AdmissionResource()
if handler.DisallowIncorrectObjects() && err != nil {
// we don't let in problematic objects - late validation
status := metav1.Status{}
status.Status = "Failure"
status.Message = err.Error()
response.Result = &status
response.Allowed = false
}
} else {
patches, err = nil, fmt.Errorf("not supported resource type: %v", admittedGroupResource)
}
if err != nil {
klog.Error(err)
return &response, metrics_admission.Error, resource
}
if len(patches) > 0 {
patch, err := json.Marshal(patches)
if err != nil {
klog.Errorf("Cannot marshal the patch %v: %v", patches, err)
return &response, metrics_admission.Error, resource
}
patchType := admissionv1.PatchTypeJSONPatch
response.PatchType = &patchType
response.Patch = patch
klog.V(4).InfoS("Sending patches", "patches", patches)
}
var status metrics_admission.AdmissionStatus
if len(patches) > 0 {
status = metrics_admission.Applied
} else {
status = metrics_admission.Skipped
}
if resource == metrics_admission.Pod {
metrics_admission.OnAdmittedPod(status == metrics_admission.Applied)
}
return &response, status, resource
}
// Serve is a handler function of AdmissionServer
func (s *AdmissionServer) Serve(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
executionTimer := metrics_admission.NewExecutionTimer()
defer executionTimer.ObserveTotal()
admissionLatency := metrics_admission.NewAdmissionLatency()
var body []byte
if r.Body != nil {
if data, err := io.ReadAll(r.Body); err == nil {
body = data
}
}
// verify the content type is accurate
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
klog.Errorf("contentType=%s, expect application/json", contentType)
admissionLatency.Observe(metrics_admission.Error, metrics_admission.Unknown)
return
}
executionTimer.ObserveStep("read_request")
reviewResponse, status, resource := s.admit(ctx, body)
ar := admissionv1.AdmissionReview{
Response: reviewResponse,
TypeMeta: metav1.TypeMeta{
Kind: "AdmissionReview",
APIVersion: "admission.k8s.io/v1",
},
}
executionTimer.ObserveStep("admit")
resp, err := json.Marshal(ar)
if err != nil {
klog.Error(err)
admissionLatency.Observe(metrics_admission.Error, resource)
return
}
executionTimer.ObserveStep("build_response")
_, err = w.Write(resp)
if err != nil {
klog.Error(err)
admissionLatency.Observe(metrics_admission.Error, resource)
return
}
executionTimer.ObserveStep("write_response")
admissionLatency.Observe(status, resource)
}