/
admission.go
263 lines (219 loc) · 8.97 KB
/
admission.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/*
Copyright 2019 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 runtimeclass contains an admission controller for modifying and validating new Pods to
// take RuntimeClass into account. For RuntimeClass definitions which describe an overhead associated
// with running a pod, this admission controller will set the pod.Spec.Overhead field accordingly. This
// field should only be set through this controller, so vaidation will be carried out to ensure the pod's
// value matches what is defined in the coresponding RuntimeClass.
package runtimeclass
import (
"context"
"fmt"
"io"
v1beta1 "k8s.io/api/node/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apiserver/pkg/admission"
genericadmissioninitailizer "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/client-go/informers"
nodev1beta1listers "k8s.io/client-go/listers/node/v1beta1"
"k8s.io/component-base/featuregate"
api "k8s.io/kubernetes/pkg/apis/core"
node "k8s.io/kubernetes/pkg/apis/node"
nodev1beta1 "k8s.io/kubernetes/pkg/apis/node/v1beta1"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/util/tolerations"
)
// PluginName indicates name of admission plugin.
const PluginName = "RuntimeClass"
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
return NewRuntimeClass(), nil
})
}
// RuntimeClass is an implementation of admission.Interface.
// It looks at all new pods and sets pod.Spec.Overhead if a RuntimeClass is specified which
// defines an Overhead. If pod.Spec.Overhead is set but a RuntimeClass with matching overhead is
// not specified, the pod is rejected.
type RuntimeClass struct {
*admission.Handler
runtimeClassLister nodev1beta1listers.RuntimeClassLister
inspectedFeatures bool
runtimeClassEnabled bool
podOverheadEnabled bool
}
var _ admission.MutationInterface = &RuntimeClass{}
var _ admission.ValidationInterface = &RuntimeClass{}
var _ genericadmissioninitailizer.WantsExternalKubeInformerFactory = &RuntimeClass{}
// InspectFeatureGates allows setting bools without taking a dep on a global variable
func (r *RuntimeClass) InspectFeatureGates(featureGates featuregate.FeatureGate) {
r.runtimeClassEnabled = featureGates.Enabled(features.RuntimeClass)
r.podOverheadEnabled = featureGates.Enabled(features.PodOverhead)
r.inspectedFeatures = true
}
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (r *RuntimeClass) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
if !r.runtimeClassEnabled {
return
}
runtimeClassInformer := f.Node().V1beta1().RuntimeClasses()
r.SetReadyFunc(runtimeClassInformer.Informer().HasSynced)
r.runtimeClassLister = runtimeClassInformer.Lister()
}
// ValidateInitialization implements the WantsExternalKubeInformerFactory interface.
func (r *RuntimeClass) ValidateInitialization() error {
if !r.inspectedFeatures {
return fmt.Errorf("InspectFeatureGates was not called")
}
if !r.runtimeClassEnabled {
return nil
}
if r.runtimeClassLister == nil {
return fmt.Errorf("missing RuntimeClass lister")
}
return nil
}
// Admit makes an admission decision based on the request attributes
func (r *RuntimeClass) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) error {
if !r.runtimeClassEnabled {
return nil
}
// Ignore all calls to subresources or resources other than pods.
if shouldIgnore(attributes) {
return nil
}
pod, runtimeClass, err := r.prepareObjects(attributes)
if err != nil {
return err
}
if r.podOverheadEnabled {
if err := setOverhead(attributes, pod, runtimeClass); err != nil {
return err
}
}
if err := setScheduling(attributes, pod, runtimeClass); err != nil {
return err
}
return nil
}
// Validate makes sure that pod adhere's to RuntimeClass's definition
func (r *RuntimeClass) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) error {
if !r.runtimeClassEnabled {
return nil
}
// Ignore all calls to subresources or resources other than pods.
if shouldIgnore(attributes) {
return nil
}
pod, runtimeClass, err := r.prepareObjects(attributes)
if err != nil {
return err
}
if r.podOverheadEnabled {
if err := validateOverhead(attributes, pod, runtimeClass); err != nil {
return err
}
}
return nil
}
// NewRuntimeClass creates a new RuntimeClass admission control handler
func NewRuntimeClass() *RuntimeClass {
return &RuntimeClass{
Handler: admission.NewHandler(admission.Create),
}
}
// prepareObjects returns pod and runtimeClass types from the given admission attributes
func (r *RuntimeClass) prepareObjects(attributes admission.Attributes) (pod *api.Pod, runtimeClass *v1beta1.RuntimeClass, err error) {
pod, ok := attributes.GetObject().(*api.Pod)
if !ok {
return nil, nil, apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
}
if pod.Spec.RuntimeClassName == nil {
return pod, nil, nil
}
// get RuntimeClass object
runtimeClass, err = r.runtimeClassLister.Get(*pod.Spec.RuntimeClassName)
if apierrors.IsNotFound(err) {
return pod, nil, admission.NewForbidden(attributes, fmt.Errorf("pod rejected: RuntimeClass %q not found", *pod.Spec.RuntimeClassName))
}
// return the pod and runtimeClass.
return pod, runtimeClass, err
}
func setOverhead(a admission.Attributes, pod *api.Pod, runtimeClass *v1beta1.RuntimeClass) (err error) {
if runtimeClass == nil || runtimeClass.Overhead == nil {
return nil
}
// convert to internal type and assign to pod's Overhead
nodeOverhead := &node.Overhead{}
if err = nodev1beta1.Convert_v1beta1_Overhead_To_node_Overhead(runtimeClass.Overhead, nodeOverhead, nil); err != nil {
return err
}
// reject pod if Overhead is already set that differs from what is defined in RuntimeClass
if pod.Spec.Overhead != nil && !apiequality.Semantic.DeepEqual(nodeOverhead.PodFixed, pod.Spec.Overhead) {
return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod's Overhead doesn't match RuntimeClass's defined Overhead"))
}
pod.Spec.Overhead = nodeOverhead.PodFixed
return nil
}
func setScheduling(a admission.Attributes, pod *api.Pod, runtimeClass *v1beta1.RuntimeClass) (err error) {
if runtimeClass == nil || runtimeClass.Scheduling == nil {
return nil
}
// convert to internal type and assign to pod's Scheduling
nodeScheduling := &node.Scheduling{}
if err = nodev1beta1.Convert_v1beta1_Scheduling_To_node_Scheduling(runtimeClass.Scheduling, nodeScheduling, nil); err != nil {
return err
}
runtimeNodeSelector := nodeScheduling.NodeSelector
newNodeSelector := pod.Spec.NodeSelector
if newNodeSelector == nil {
newNodeSelector = runtimeNodeSelector
} else {
for key, runtimeClassValue := range runtimeNodeSelector {
if podValue, ok := newNodeSelector[key]; ok && podValue != runtimeClassValue {
return admission.NewForbidden(a, fmt.Errorf("conflict: runtimeClass.scheduling.nodeSelector[%s] = %s; pod.spec.nodeSelector[%s] = %s", key, runtimeClassValue, key, podValue))
}
newNodeSelector[key] = runtimeClassValue
}
}
newTolerations := tolerations.MergeTolerations(pod.Spec.Tolerations, nodeScheduling.Tolerations)
pod.Spec.NodeSelector = newNodeSelector
pod.Spec.Tolerations = newTolerations
return nil
}
func validateOverhead(a admission.Attributes, pod *api.Pod, runtimeClass *v1beta1.RuntimeClass) (err error) {
if runtimeClass != nil && runtimeClass.Overhead != nil {
// If the Overhead set doesn't match what is provided in the RuntimeClass definition, reject the pod
nodeOverhead := &node.Overhead{}
if err := nodev1beta1.Convert_v1beta1_Overhead_To_node_Overhead(runtimeClass.Overhead, nodeOverhead, nil); err != nil {
return err
}
if !apiequality.Semantic.DeepEqual(nodeOverhead.PodFixed, pod.Spec.Overhead) {
return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod's Overhead doesn't match RuntimeClass's defined Overhead"))
}
} else {
// If RuntimeClass with Overhead is not defined but an Overhead is set for pod, reject the pod
if pod.Spec.Overhead != nil {
return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod Overhead set without corresponding RuntimeClass defined Overhead"))
}
}
return nil
}
func shouldIgnore(attributes admission.Attributes) bool {
// Ignore all calls to subresources or resources other than pods.
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
return true
}
return false
}