Skip to content

Commit 3bf0f19

Browse files
committed
Add ability to add handlers for new resources to AdmissionController via RegisterHandler().
This PR refactors AdmissionController and decouples its core logic from logic responsible for handling resources. Handlers for already supported resources are registered via the new mechanism.
1 parent 975892c commit 3bf0f19

File tree

6 files changed

+585
-393
lines changed

6 files changed

+585
-393
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package logic
18+
19+
import (
20+
"encoding/json"
21+
"fmt"
22+
"strings"
23+
24+
"k8s.io/api/admission/v1beta1"
25+
v1 "k8s.io/api/core/v1"
26+
"k8s.io/apimachinery/pkg/api/resource"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
29+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/admission"
30+
vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
31+
"k8s.io/klog"
32+
)
33+
34+
const (
35+
vpaAnnotationLabel = "vpaUpdates"
36+
)
37+
38+
type patchRecord struct {
39+
Op string `json:"op,inline"`
40+
Path string `json:"path,inline"`
41+
Value interface{} `json:"value"`
42+
}
43+
44+
// podResourceHandler builds patches for Pods.
45+
type podResourceHandler struct {
46+
podPreProcessor PodPreProcessor
47+
recommendationProvider RecommendationProvider
48+
vpaMatcher VpaMatcher
49+
}
50+
51+
// newPodResourceHandler creates new instance of podResourceHandler.
52+
func newPodResourceHandler(podPreProcessor PodPreProcessor, recommendationProvider RecommendationProvider, vpaMatcher VpaMatcher) ResourceHandler {
53+
return &podResourceHandler{
54+
podPreProcessor: podPreProcessor,
55+
recommendationProvider: recommendationProvider,
56+
vpaMatcher: vpaMatcher,
57+
}
58+
}
59+
60+
// AdmissionResource returns resource type this handler accepts.
61+
func (h *podResourceHandler) AdmissionResource() admission.AdmissionResource {
62+
return admission.Pod
63+
}
64+
65+
// GroupResource returns Group and Resource type this handler accepts.
66+
func (h *podResourceHandler) GroupResource() metav1.GroupResource {
67+
return metav1.GroupResource{Group: "", Resource: "pods"}
68+
}
69+
70+
// DisallowIncorrectObjects decides whether incorrect objects (eg. unparsable, not passing validations) should be disallowed by Admission Server.
71+
func (h *podResourceHandler) DisallowIncorrectObjects() bool {
72+
// Incorrect Pods are validated by API Server.
73+
return false
74+
}
75+
76+
// GetPatches builds patches for Pod in given admission request.
77+
func (h *podResourceHandler) GetPatches(ar *v1beta1.AdmissionRequest) ([]patchRecord, error) {
78+
if ar.Resource.Version != "v1" {
79+
return nil, fmt.Errorf("only v1 Pods are supported")
80+
}
81+
raw, namespace := ar.Object.Raw, ar.Namespace
82+
pod := v1.Pod{}
83+
if err := json.Unmarshal(raw, &pod); err != nil {
84+
return nil, err
85+
}
86+
if len(pod.Name) == 0 {
87+
pod.Name = pod.GenerateName + "%"
88+
pod.Namespace = namespace
89+
}
90+
klog.V(4).Infof("Admitting pod %v", pod.ObjectMeta)
91+
controllingVpa := h.vpaMatcher.GetMatchingVPA(&pod)
92+
if controllingVpa == nil {
93+
klog.V(4).Infof("No matching VPA found for pod %s/%s", pod.Namespace, pod.Name)
94+
return []patchRecord{}, nil
95+
}
96+
containersResources, annotationsPerContainer, err := h.recommendationProvider.GetContainersResourcesForPod(&pod, controllingVpa)
97+
if err != nil {
98+
return nil, err
99+
}
100+
pod, err = h.podPreProcessor.Process(pod)
101+
if err != nil {
102+
return nil, err
103+
}
104+
if annotationsPerContainer == nil {
105+
annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap{}
106+
}
107+
108+
patches := []patchRecord{}
109+
updatesAnnotation := []string{}
110+
for i, containerResources := range containersResources {
111+
newPatches, newUpdatesAnnotation := getContainerPatch(pod, i, annotationsPerContainer, containerResources)
112+
patches = append(patches, newPatches...)
113+
updatesAnnotation = append(updatesAnnotation, newUpdatesAnnotation)
114+
}
115+
116+
if pod.Annotations == nil {
117+
patches = append(patches, getAddEmptyAnnotationsPatch())
118+
}
119+
if len(updatesAnnotation) > 0 {
120+
vpaAnnotationValue := fmt.Sprintf("Pod resources updated by %s: %s", controllingVpa.Name, strings.Join(updatesAnnotation, "; "))
121+
patches = append(patches, getAddAnnotationPatch(vpaAnnotationLabel, vpaAnnotationValue))
122+
}
123+
vpaObservedContainersValue := annotations.GetVpaObservedContainersValue(&pod)
124+
patches = append(patches, getAddAnnotationPatch(annotations.VpaObservedContainersLabel, vpaObservedContainersValue))
125+
126+
return patches, nil
127+
}
128+
129+
func getContainerPatch(pod v1.Pod, i int, annotationsPerContainer vpa_api_util.ContainerToAnnotationsMap, containerResources vpa_api_util.ContainerResources) ([]patchRecord, string) {
130+
var patches []patchRecord
131+
// Add empty resources object if missing.
132+
if pod.Spec.Containers[i].Resources.Limits == nil &&
133+
pod.Spec.Containers[i].Resources.Requests == nil {
134+
patches = append(patches, getPatchInitializingEmptyResources(i))
135+
}
136+
137+
annotations, found := annotationsPerContainer[pod.Spec.Containers[i].Name]
138+
if !found {
139+
annotations = make([]string, 0)
140+
}
141+
142+
patches, annotations = appendPatchesAndAnnotations(patches, annotations, pod.Spec.Containers[i].Resources.Requests, i, containerResources.Requests, "requests", "request")
143+
patches, annotations = appendPatchesAndAnnotations(patches, annotations, pod.Spec.Containers[i].Resources.Limits, i, containerResources.Limits, "limits", "limit")
144+
145+
updatesAnnotation := fmt.Sprintf("container %d: ", i) + strings.Join(annotations, ", ")
146+
return patches, updatesAnnotation
147+
}
148+
149+
func getAddEmptyAnnotationsPatch() patchRecord {
150+
return patchRecord{
151+
Op: "add",
152+
Path: "/metadata/annotations",
153+
Value: map[string]string{},
154+
}
155+
}
156+
157+
func getAddAnnotationPatch(annotationName, annotationValue string) patchRecord {
158+
return patchRecord{
159+
Op: "add",
160+
Path: fmt.Sprintf("/metadata/annotations/%s", annotationName),
161+
Value: annotationValue,
162+
}
163+
}
164+
165+
func getPatchInitializingEmptyResources(i int) patchRecord {
166+
return patchRecord{
167+
Op: "add",
168+
Path: fmt.Sprintf("/spec/containers/%d/resources", i),
169+
Value: v1.ResourceRequirements{},
170+
}
171+
}
172+
173+
func getPatchInitializingEmptyResourcesSubfield(i int, kind string) patchRecord {
174+
return patchRecord{
175+
Op: "add",
176+
Path: fmt.Sprintf("/spec/containers/%d/resources/%s", i, kind),
177+
Value: v1.ResourceList{},
178+
}
179+
}
180+
181+
func getAddResourceRequirementValuePatch(i int, kind string, resource v1.ResourceName, quantity resource.Quantity) patchRecord {
182+
return patchRecord{
183+
Op: "add",
184+
Path: fmt.Sprintf("/spec/containers/%d/resources/%s/%s", i, kind, resource),
185+
Value: quantity.String()}
186+
}
187+
188+
func appendPatchesAndAnnotations(patches []patchRecord, annotations []string, current v1.ResourceList, containerIndex int, resources v1.ResourceList, fieldName, resourceName string) ([]patchRecord, []string) {
189+
// Add empty object if it's missing and we're about to fill it.
190+
if current == nil && len(resources) > 0 {
191+
patches = append(patches, getPatchInitializingEmptyResourcesSubfield(containerIndex, fieldName))
192+
}
193+
for resource, request := range resources {
194+
patches = append(patches, getAddResourceRequirementValuePatch(containerIndex, fieldName, resource, request))
195+
annotations = append(annotations, fmt.Sprintf("%s %s", resource, resourceName))
196+
}
197+
return patches, annotations
198+
}

0 commit comments

Comments
 (0)