1
16
17
18
19
20
21
22 package runtimeclass
23
24 import (
25 "context"
26 "fmt"
27 "io"
28
29 nodev1 "k8s.io/api/node/v1"
30 apiequality "k8s.io/apimachinery/pkg/api/equality"
31 apierrors "k8s.io/apimachinery/pkg/api/errors"
32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33 "k8s.io/apiserver/pkg/admission"
34 genericadmissioninitailizer "k8s.io/apiserver/pkg/admission/initializer"
35 "k8s.io/client-go/informers"
36 "k8s.io/client-go/kubernetes"
37 nodev1client "k8s.io/client-go/kubernetes/typed/node/v1"
38 nodev1listers "k8s.io/client-go/listers/node/v1"
39 api "k8s.io/kubernetes/pkg/apis/core"
40 node "k8s.io/kubernetes/pkg/apis/node"
41 apinodev1 "k8s.io/kubernetes/pkg/apis/node/v1"
42 "k8s.io/kubernetes/pkg/util/tolerations"
43 )
44
45
46 const PluginName = "RuntimeClass"
47
48
49 func Register(plugins *admission.Plugins) {
50 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
51 return NewRuntimeClass(), nil
52 })
53 }
54
55
56
57
58
59 type RuntimeClass struct {
60 *admission.Handler
61 runtimeClassLister nodev1listers.RuntimeClassLister
62 runtimeClassClient nodev1client.RuntimeClassInterface
63 }
64
65 var _ admission.MutationInterface = &RuntimeClass{}
66 var _ admission.ValidationInterface = &RuntimeClass{}
67
68 var _ genericadmissioninitailizer.WantsExternalKubeInformerFactory = &RuntimeClass{}
69 var _ genericadmissioninitailizer.WantsExternalKubeClientSet = &RuntimeClass{}
70
71
72 func (r *RuntimeClass) SetExternalKubeClientSet(client kubernetes.Interface) {
73 r.runtimeClassClient = client.NodeV1().RuntimeClasses()
74 }
75
76
77 func (r *RuntimeClass) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
78 runtimeClassInformer := f.Node().V1().RuntimeClasses()
79 r.SetReadyFunc(runtimeClassInformer.Informer().HasSynced)
80 r.runtimeClassLister = runtimeClassInformer.Lister()
81 }
82
83
84 func (r *RuntimeClass) ValidateInitialization() error {
85 if r.runtimeClassLister == nil {
86 return fmt.Errorf("missing RuntimeClass lister")
87 }
88 if r.runtimeClassClient == nil {
89 return fmt.Errorf("missing RuntimeClass client")
90 }
91 return nil
92 }
93
94
95 func (r *RuntimeClass) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) error {
96
97 if shouldIgnore(attributes) {
98 return nil
99 }
100
101 pod, runtimeClass, err := r.prepareObjects(ctx, attributes)
102 if err != nil {
103 return err
104 }
105 if err := setOverhead(attributes, pod, runtimeClass); err != nil {
106 return err
107 }
108
109 if err := setScheduling(attributes, pod, runtimeClass); err != nil {
110 return err
111 }
112
113 return nil
114 }
115
116
117 func (r *RuntimeClass) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) error {
118
119 if shouldIgnore(attributes) {
120 return nil
121 }
122
123 pod, runtimeClass, err := r.prepareObjects(ctx, attributes)
124 if err != nil {
125 return err
126 }
127 if err := validateOverhead(attributes, pod, runtimeClass); err != nil {
128 return err
129 }
130
131 return nil
132 }
133
134
135 func NewRuntimeClass() *RuntimeClass {
136 return &RuntimeClass{
137 Handler: admission.NewHandler(admission.Create),
138 }
139 }
140
141
142 func (r *RuntimeClass) prepareObjects(ctx context.Context, attributes admission.Attributes) (pod *api.Pod, runtimeClass *nodev1.RuntimeClass, err error) {
143 pod, ok := attributes.GetObject().(*api.Pod)
144 if !ok {
145 return nil, nil, apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
146 }
147
148 if pod.Spec.RuntimeClassName == nil {
149 return pod, nil, nil
150 }
151
152
153 runtimeClass, err = r.runtimeClassLister.Get(*pod.Spec.RuntimeClassName)
154 if apierrors.IsNotFound(err) {
155
156 runtimeClass, err = r.runtimeClassClient.Get(ctx, *pod.Spec.RuntimeClassName, metav1.GetOptions{})
157 if apierrors.IsNotFound(err) {
158 return pod, nil, admission.NewForbidden(attributes, fmt.Errorf("pod rejected: RuntimeClass %q not found", *pod.Spec.RuntimeClassName))
159 }
160 }
161
162
163 return pod, runtimeClass, err
164 }
165
166 func setOverhead(a admission.Attributes, pod *api.Pod, runtimeClass *nodev1.RuntimeClass) (err error) {
167 if runtimeClass == nil || runtimeClass.Overhead == nil {
168 return nil
169 }
170
171
172 nodeOverhead := &node.Overhead{}
173 if err = apinodev1.Convert_v1_Overhead_To_node_Overhead(runtimeClass.Overhead, nodeOverhead, nil); err != nil {
174 return err
175 }
176
177
178 if pod.Spec.Overhead != nil && !apiequality.Semantic.DeepEqual(nodeOverhead.PodFixed, pod.Spec.Overhead) {
179 return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod's Overhead doesn't match RuntimeClass's defined Overhead"))
180 }
181
182 pod.Spec.Overhead = nodeOverhead.PodFixed
183
184 return nil
185 }
186
187 func setScheduling(a admission.Attributes, pod *api.Pod, runtimeClass *nodev1.RuntimeClass) (err error) {
188 if runtimeClass == nil || runtimeClass.Scheduling == nil {
189 return nil
190 }
191
192
193 nodeScheduling := &node.Scheduling{}
194 if err = apinodev1.Convert_v1_Scheduling_To_node_Scheduling(runtimeClass.Scheduling, nodeScheduling, nil); err != nil {
195 return err
196 }
197
198 runtimeNodeSelector := nodeScheduling.NodeSelector
199 newNodeSelector := pod.Spec.NodeSelector
200 if newNodeSelector == nil {
201 newNodeSelector = runtimeNodeSelector
202 } else {
203 for key, runtimeClassValue := range runtimeNodeSelector {
204 if podValue, ok := newNodeSelector[key]; ok && podValue != runtimeClassValue {
205 return admission.NewForbidden(a, fmt.Errorf("conflict: runtimeClass.scheduling.nodeSelector[%s] = %s; pod.spec.nodeSelector[%s] = %s", key, runtimeClassValue, key, podValue))
206 }
207 newNodeSelector[key] = runtimeClassValue
208 }
209 }
210
211 newTolerations := tolerations.MergeTolerations(pod.Spec.Tolerations, nodeScheduling.Tolerations)
212
213 pod.Spec.NodeSelector = newNodeSelector
214 pod.Spec.Tolerations = newTolerations
215
216 return nil
217 }
218
219 func validateOverhead(a admission.Attributes, pod *api.Pod, runtimeClass *nodev1.RuntimeClass) (err error) {
220 if runtimeClass != nil && runtimeClass.Overhead != nil {
221
222 nodeOverhead := &node.Overhead{}
223 if err := apinodev1.Convert_v1_Overhead_To_node_Overhead(runtimeClass.Overhead, nodeOverhead, nil); err != nil {
224 return err
225 }
226 if !apiequality.Semantic.DeepEqual(nodeOverhead.PodFixed, pod.Spec.Overhead) {
227 return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod's Overhead doesn't match RuntimeClass's defined Overhead"))
228 }
229 } else {
230
231 if pod.Spec.Overhead != nil {
232 return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod Overhead set without corresponding RuntimeClass defined Overhead"))
233 }
234 }
235
236 return nil
237 }
238
239 func shouldIgnore(attributes admission.Attributes) bool {
240
241 if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
242 return true
243 }
244
245 return false
246 }
247
View as plain text