@@ -20,7 +20,6 @@ import (
20
20
"context"
21
21
"fmt"
22
22
"io"
23
- "strings"
24
23
25
24
"k8s.io/klog/v2"
26
25
@@ -39,7 +38,7 @@ import (
39
38
const PluginName = "PodTopologyLabels"
40
39
41
40
// defaultConfig is the configuration used for the default instantiation of the plugin.
42
- // This is the configured used by kube-apiserver.
41
+ // This configuration is used by kube-apiserver.
43
42
// It is not exported to avoid any chance of accidentally mutating the variable.
44
43
var defaultConfig = Config {
45
44
Labels : []string {"topology.k8s.io/zone" , "topology.k8s.io/region" , "kubernetes.io/hostname" },
@@ -60,27 +59,13 @@ type Config struct {
60
59
// Labels is set of explicit label keys to be copied from the Node object onto
61
60
// pod Binding objects during admission.
62
61
Labels []string
63
- // Domains is a set of label key prefixes used to copy across label values
64
- // for all labels with a given domain prefix.
65
- // For example, `example.com` would match all labels matching `example.com/*`.
66
- // Keys without a domain portion (i.e. those not containing a /) will never match.
67
- Domains []string
68
- // Suffixes is a set of label key domain suffixes used to copy label values for
69
- // all labels of a given subdomain.
70
- // This acts as a suffix match on the domain portion of label keys.
71
- // If a suffix does not have a leading '.', one will be prepended to avoid
72
- // programmer errors with values like `example.com` matching `notexample.com`.
73
- // Keys without a domain portion (i.e. those not containing a /) will never match.
74
- Suffixes []string
75
62
}
76
63
77
64
// NewPodTopologyPlugin initializes a Plugin
78
65
func NewPodTopologyPlugin (c Config ) * Plugin {
79
66
return & Plugin {
80
- Handler : admission .NewHandler (admission .Create ),
81
- labels : sets .New (c .Labels ... ),
82
- domains : sets .New (c .Domains ... ),
83
- suffixes : sets .New (c .Suffixes ... ),
67
+ Handler : admission .NewHandler (admission .Create ),
68
+ labels : sets .New (c .Labels ... ),
84
69
}
85
70
}
86
71
@@ -89,9 +74,8 @@ type Plugin struct {
89
74
90
75
nodeLister corev1listers.NodeLister
91
76
92
- // explicit labels, list of domains or a list of domain
93
- // suffixes to be copies to Pod objects being bound.
94
- labels , domains , suffixes sets.Set [string ]
77
+ // explicit labels to be copied to Pod objects being bound.
78
+ labels sets.Set [string ]
95
79
96
80
enabled , inspectedFeatureGates bool
97
81
}
@@ -126,105 +110,137 @@ func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.
126
110
if ! p .enabled {
127
111
return nil
128
112
}
129
- if shouldIgnore (a ) {
130
- return nil
113
+ // check whether the request is for a Binding or a Pod spec update.
114
+ shouldAdmit , doAdmit , err := p .shouldAdmit (a )
115
+ if ! shouldAdmit || err != nil {
116
+ // error is either nil and admit == false, or err is non-nil and should be returned.
117
+ return err
131
118
}
132
119
// we need to wait for our caches to warm
133
120
if ! p .WaitForReady () {
134
121
return admission .NewForbidden (a , fmt .Errorf ("not yet ready to handle request" ))
135
122
}
123
+ // run type specific admission
124
+ return doAdmit (ctx , a , o )
125
+ }
136
126
127
+ func (p * Plugin ) admitBinding (ctx context.Context , a admission.Attributes , o admission.ObjectInterfaces ) error {
137
128
binding := a .GetObject ().(* api.Binding )
138
129
// other fields are not set by the default scheduler for the binding target, so only check the Kind.
139
130
if binding .Target .Kind != "Node" {
140
131
klog .V (6 ).Info ("Skipping Pod being bound to non-Node object type" , "target" , binding .Target .GroupVersionKind ())
141
132
return nil
142
133
}
143
134
144
- node , err := p .nodeLister . Get (binding .Target .Name )
135
+ labelsToCopy , err := p .topologyLabelsForNodeName (binding .Target .Name )
145
136
if err != nil {
146
- // Ignore NotFound errors to avoid risking breaking compatibility/behaviour.
147
- if apierrors .IsNotFound (err ) {
148
- return nil
149
- }
150
137
return err
151
138
}
152
-
153
- // fast-path/short circuit if the node has no labels
154
- if node .Labels == nil {
139
+ if len (labelsToCopy ) == 0 {
140
+ // fast-path/short circuit if the node has no topology labels
155
141
return nil
156
142
}
157
143
158
- labelsToCopy := make (map [string ]string )
159
- for k , v := range node .Labels {
160
- if ! p .isTopologyLabel (k ) {
161
- continue
162
- }
163
- labelsToCopy [k ] = v
144
+ // copy the topology labels into the Binding's labels, as these are copied from the Binding
145
+ // to the Pod object being bound within the podBinding registry/store.
146
+ if binding .Labels == nil {
147
+ binding .Labels = make (map [string ]string )
148
+ }
149
+ mergeLabels (binding .Labels , labelsToCopy )
150
+
151
+ return nil
152
+ }
153
+
154
+ func (p * Plugin ) admitPod (ctx context.Context , a admission.Attributes , o admission.ObjectInterfaces ) error {
155
+ pod := a .GetObject ().(* api.Pod )
156
+ if pod .Spec .NodeName == "" {
157
+ // pod has not been scheduled yet
158
+ return nil
164
159
}
165
160
161
+ // Determine the topology labels from the assigned node to be copied.
162
+ labelsToCopy , err := p .topologyLabelsForNodeName (pod .Spec .NodeName )
163
+ if err != nil {
164
+ return err
165
+ }
166
166
if len (labelsToCopy ) == 0 {
167
167
// fast-path/short circuit if the node has no topology labels
168
168
return nil
169
169
}
170
170
171
- // copy the topology labels into the Binding's labels, as these are copied from the Binding
172
- // to the Pod object being bound within the podBinding registry/store.
173
- if binding .Labels == nil {
174
- binding .Labels = make (map [string ]string )
171
+ // copy the topology labels into the Pod's labels.
172
+ if pod .Labels == nil {
173
+ pod .Labels = make (map [string ]string )
174
+ }
175
+ // avoid overwriting any existing labels on the Pod.
176
+ mergeLabels (pod .Labels , labelsToCopy )
177
+
178
+ return nil
179
+ }
180
+
181
+ func (p * Plugin ) topologyLabelsForNodeName (nodeName string ) (map [string ]string , error ) {
182
+ labels := make (map [string ]string )
183
+ node , err := p .nodeLister .Get (nodeName )
184
+ if err != nil {
185
+ // Ignore NotFound errors to avoid risking breaking compatibility/behaviour.
186
+ if apierrors .IsNotFound (err ) {
187
+ return labels , nil
188
+ }
189
+ return nil , err
175
190
}
176
- for k , v := range labelsToCopy {
177
- if _ , exists := binding .Labels [k ]; exists {
178
- // Don't overwrite labels on Binding resources as this could lead to unexpected
179
- // behaviour if any schedulers rely on being able to explicitly set values themselves.
191
+
192
+ for k , v := range node .Labels {
193
+ if ! p .isTopologyLabel (k ) {
180
194
continue
181
195
}
182
- binding . Labels [k ] = v
196
+ labels [k ] = v
183
197
}
184
198
185
- return nil
199
+ return labels , nil
200
+ }
201
+
202
+ // mergeLabels merges new into existing, without overwriting existing keys.
203
+ func mergeLabels (existing , new map [string ]string ) {
204
+ for k , v := range new {
205
+ if _ , exists := existing [k ]; exists {
206
+ continue
207
+ }
208
+ existing [k ] = v
209
+ }
186
210
}
187
211
188
212
func (p * Plugin ) isTopologyLabel (key string ) bool {
189
213
// First check explicit label keys.
190
214
if p .labels .Has (key ) {
191
215
return true
192
216
}
193
- // Check the domain portion of the label key, if present
194
- domain , _ , hasDomain := strings .Cut (key , "/" )
195
- if ! hasDomain {
196
- // fast-path if there is no / separator
197
- return false
198
- }
199
- if p .domains .Has (domain ) {
200
- // check for explicit domains to copy
201
- return true
202
- }
203
- for _ , suffix := range p .suffixes .UnsortedList () {
204
- // check if the domain has one of the suffixes that are to be copied
205
- if strings .HasSuffix (domain , suffix ) {
206
- return true
207
- }
208
- }
209
217
return false
210
218
}
211
219
212
- func shouldIgnore (a admission.Attributes ) bool {
213
- resource := a .GetResource ().GroupResource ()
214
- if resource != api .Resource ("pods" ) {
215
- return true
216
- }
217
- if a .GetSubresource () != "binding" {
218
- // only run the checks below on the binding subresource
219
- return true
220
- }
220
+ type admitFunc func (ctx context.Context , a admission.Attributes , o admission.ObjectInterfaces ) (err error )
221
221
222
- obj := a . GetObject ()
223
- _ , ok := obj .( * api. Binding )
224
- if ! ok {
225
- klog . Errorf ( "expected Binding but got %s" , a . GetKind ().Kind )
226
- return true
222
+ // shouldAdmit inspects the provided adminssion attributes to determine whether the request
223
+ // requires admittance through this plugin.
224
+ func ( p * Plugin ) shouldAdmit ( a admission. Attributes ) ( bool , admitFunc , error ) {
225
+ if a . GetResource ().GroupResource () != api . Resource ( "pods" ) {
226
+ return false , nil , nil
227
227
}
228
228
229
- return false
229
+ switch a .GetSubresource () {
230
+ case "" : // regular Pod endpoint
231
+ _ , ok := a .GetObject ().(* api.Pod )
232
+ if ! ok {
233
+ return false , nil , fmt .Errorf ("expected Pod but got %T" , a .GetObject ())
234
+ }
235
+ return true , p .admitPod , nil
236
+ case "binding" :
237
+ _ , ok := a .GetObject ().(* api.Binding )
238
+ if ! ok {
239
+ return false , nil , fmt .Errorf ("expected Binding but got %s" , a .GetKind ().Kind )
240
+ }
241
+ return true , p .admitBinding , nil
242
+ default :
243
+ // Ignore all other sub-resources.
244
+ return false , nil , nil
245
+ }
230
246
}
0 commit comments