Skip to content

Commit c098f5d

Browse files
authored
Merge pull request kubernetes#2864 from tweks/admission-controller
Add ability to add handlers for new resources to AdmissionController via RegisterHandler()
2 parents f21b047 + 3bf0f19 commit c098f5d

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)