1
16
17 package priority
18
19 import (
20 "context"
21 "fmt"
22 "io"
23
24 apiv1 "k8s.io/api/core/v1"
25 schedulingv1 "k8s.io/api/scheduling/v1"
26 "k8s.io/apimachinery/pkg/api/errors"
27 "k8s.io/apimachinery/pkg/labels"
28 "k8s.io/apiserver/pkg/admission"
29 genericadmissioninitializers "k8s.io/apiserver/pkg/admission/initializer"
30 "k8s.io/client-go/informers"
31 "k8s.io/client-go/kubernetes"
32 schedulingv1listers "k8s.io/client-go/listers/scheduling/v1"
33 "k8s.io/kubernetes/pkg/apis/core"
34 "k8s.io/kubernetes/pkg/apis/scheduling"
35 )
36
37 const (
38
39 PluginName = "Priority"
40 )
41
42
43 func Register(plugins *admission.Plugins) {
44 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
45 return NewPlugin(), nil
46 })
47 }
48
49
50 type Plugin struct {
51 *admission.Handler
52 client kubernetes.Interface
53 lister schedulingv1listers.PriorityClassLister
54 }
55
56 var _ admission.MutationInterface = &Plugin{}
57 var _ admission.ValidationInterface = &Plugin{}
58 var _ = genericadmissioninitializers.WantsExternalKubeInformerFactory(&Plugin{})
59 var _ = genericadmissioninitializers.WantsExternalKubeClientSet(&Plugin{})
60
61
62 func NewPlugin() *Plugin {
63 return &Plugin{
64 Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
65 }
66 }
67
68
69 func (p *Plugin) ValidateInitialization() error {
70 if p.client == nil {
71 return fmt.Errorf("%s requires a client", PluginName)
72 }
73 if p.lister == nil {
74 return fmt.Errorf("%s requires a lister", PluginName)
75 }
76 return nil
77 }
78
79
80 func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
81 p.client = client
82 }
83
84
85 func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
86 priorityInformer := f.Scheduling().V1().PriorityClasses()
87 p.lister = priorityInformer.Lister()
88 p.SetReadyFunc(priorityInformer.Informer().HasSynced)
89 }
90
91 var (
92 podResource = core.Resource("pods")
93 priorityClassResource = scheduling.Resource("priorityclasses")
94 )
95
96
97
98 func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
99 operation := a.GetOperation()
100
101 if len(a.GetSubresource()) != 0 {
102 return nil
103 }
104 switch a.GetResource().GroupResource() {
105 case podResource:
106 if operation == admission.Create || operation == admission.Update {
107 return p.admitPod(a)
108 }
109 return nil
110
111 default:
112 return nil
113 }
114 }
115
116
117 func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
118 operation := a.GetOperation()
119
120 if len(a.GetSubresource()) != 0 {
121 return nil
122 }
123
124 switch a.GetResource().GroupResource() {
125 case priorityClassResource:
126 if operation == admission.Create || operation == admission.Update {
127 return p.validatePriorityClass(a)
128 }
129 return nil
130
131 default:
132 return nil
133 }
134 }
135
136
137 func (p *Plugin) admitPod(a admission.Attributes) error {
138 operation := a.GetOperation()
139 pod, ok := a.GetObject().(*core.Pod)
140 if !ok {
141 return errors.NewBadRequest("resource was marked with kind Pod but was unable to be converted")
142 }
143
144 if operation == admission.Update {
145 oldPod, ok := a.GetOldObject().(*core.Pod)
146 if !ok {
147 return errors.NewBadRequest("resource was marked with kind Pod but was unable to be converted")
148 }
149
150
151
152
153 if pod.Spec.Priority == nil && oldPod.Spec.Priority != nil {
154 pod.Spec.Priority = oldPod.Spec.Priority
155 }
156 if pod.Spec.PreemptionPolicy == nil && oldPod.Spec.PreemptionPolicy != nil {
157 pod.Spec.PreemptionPolicy = oldPod.Spec.PreemptionPolicy
158 }
159 return nil
160 }
161
162 if operation == admission.Create {
163 var priority int32
164 var preemptionPolicy *apiv1.PreemptionPolicy
165 if len(pod.Spec.PriorityClassName) == 0 {
166 var err error
167 var pcName string
168 pcName, priority, preemptionPolicy, err = p.getDefaultPriority()
169 if err != nil {
170 return fmt.Errorf("failed to get default priority class: %v", err)
171 }
172 pod.Spec.PriorityClassName = pcName
173 } else {
174
175 pc, err := p.lister.Get(pod.Spec.PriorityClassName)
176 if err != nil {
177 if errors.IsNotFound(err) {
178 return admission.NewForbidden(a, fmt.Errorf("no PriorityClass with name %v was found", pod.Spec.PriorityClassName))
179 }
180
181 return fmt.Errorf("failed to get PriorityClass with name %s: %v", pod.Spec.PriorityClassName, err)
182 }
183
184 priority = pc.Value
185 preemptionPolicy = pc.PreemptionPolicy
186 }
187
188 if pod.Spec.Priority != nil && *pod.Spec.Priority != priority {
189 return admission.NewForbidden(a, fmt.Errorf("the integer value of priority (%d) must not be provided in pod spec; priority admission controller computed %d from the given PriorityClass name", *pod.Spec.Priority, priority))
190 }
191 pod.Spec.Priority = &priority
192
193 var corePolicy core.PreemptionPolicy
194 if preemptionPolicy != nil {
195 corePolicy = core.PreemptionPolicy(*preemptionPolicy)
196 if pod.Spec.PreemptionPolicy != nil && *pod.Spec.PreemptionPolicy != corePolicy {
197 return admission.NewForbidden(a, fmt.Errorf("the string value of PreemptionPolicy (%s) must not be provided in pod spec; priority admission controller computed %s from the given PriorityClass name", *pod.Spec.PreemptionPolicy, corePolicy))
198 }
199 pod.Spec.PreemptionPolicy = &corePolicy
200 }
201 }
202 return nil
203 }
204
205
206 func (p *Plugin) validatePriorityClass(a admission.Attributes) error {
207 operation := a.GetOperation()
208 pc, ok := a.GetObject().(*scheduling.PriorityClass)
209 if !ok {
210 return errors.NewBadRequest("resource was marked with kind PriorityClass but was unable to be converted")
211 }
212
213 if pc.GlobalDefault {
214 dpc, err := p.getDefaultPriorityClass()
215 if err != nil {
216 return fmt.Errorf("failed to get default priority class: %v", err)
217 }
218 if dpc != nil {
219
220 if operation == admission.Create || (operation == admission.Update && dpc.GetName() != pc.GetName()) {
221 return admission.NewForbidden(a, fmt.Errorf("PriorityClass %v is already marked as default. Only one default can exist", dpc.GetName()))
222 }
223 }
224 }
225 return nil
226 }
227
228 func (p *Plugin) getDefaultPriorityClass() (*schedulingv1.PriorityClass, error) {
229 list, err := p.lister.List(labels.Everything())
230 if err != nil {
231 return nil, err
232 }
233
234
235 var defaultPC *schedulingv1.PriorityClass
236 for _, pci := range list {
237 if pci.GlobalDefault {
238 if defaultPC == nil || defaultPC.Value > pci.Value {
239 defaultPC = pci
240 }
241 }
242 }
243 return defaultPC, nil
244 }
245
246 func (p *Plugin) getDefaultPriority() (string, int32, *apiv1.PreemptionPolicy, error) {
247 dpc, err := p.getDefaultPriorityClass()
248 if err != nil {
249 return "", 0, nil, err
250 }
251 if dpc != nil {
252 return dpc.Name, dpc.Value, dpc.PreemptionPolicy, nil
253 }
254 preemptLowerPriority := apiv1.PreemptLowerPriority
255 return "", int32(scheduling.DefaultPriorityWhenNoDefaultClassExists), &preemptLowerPriority, nil
256 }
257
View as plain text