Skip to content

Commit 934e247

Browse files
committed
Remove kubernetes.io/hostname label copying, skip overriding, and support direct spec.nodeName changes.
1 parent 6ddabb6 commit 934e247

File tree

5 files changed

+199
-129
lines changed

5 files changed

+199
-129
lines changed

pkg/registry/core/pod/storage/storage.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,10 @@ func (r *BindingREST) PreserveRequestObjectMetaSystemFieldsOnSubresourceCreate()
205205
return true
206206
}
207207

208-
// setPodHostAndAnnotations sets the given pod's host to 'machine' if and only if
209-
// the pod is unassigned and merges the provided annotations with those of the pod.
208+
// setPodNodeAndMetadata sets the given pod's nodeName to 'machine' if and only if
209+
// the pod is unassigned, and merges the provided annotations and labels with those of the pod.
210210
// Returns the current state of the pod, or an error.
211-
func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podUID types.UID, podResourceVersion, podID, machine string, annotations, labels map[string]string, dryRun bool) (finalPod *api.Pod, err error) {
211+
func (r *BindingREST) setPodNodeAndMetadata(ctx context.Context, podUID types.UID, podResourceVersion, podID, machine string, annotations, labels map[string]string, dryRun bool) (finalPod *api.Pod, err error) {
212212
podKey, err := r.store.KeyFunc(ctx, podID)
213213
if err != nil {
214214
return nil, err
@@ -284,7 +284,7 @@ func copyLabelsWithoutOverwriting(pod *api.Pod, labels map[string]string) {
284284

285285
// assignPod assigns the given pod to the given machine.
286286
func (r *BindingREST) assignPod(ctx context.Context, podUID types.UID, podResourceVersion, podID string, machine string, annotations, labels map[string]string, dryRun bool) (err error) {
287-
if _, err = r.setPodHostAndAnnotations(ctx, podUID, podResourceVersion, podID, machine, annotations, labels, dryRun); err != nil {
287+
if _, err = r.setPodNodeAndMetadata(ctx, podUID, podResourceVersion, podID, machine, annotations, labels, dryRun); err != nil {
288288
err = storeerr.InterpretGetError(err, api.Resource("pods"), podID)
289289
err = storeerr.InterpretUpdateError(err, api.Resource("pods"), podID)
290290
if _, ok := err.(*errors.StatusError); !ok {

pkg/registry/core/pod/storage/storage_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"k8s.io/apimachinery/pkg/labels"
3535
"k8s.io/apimachinery/pkg/runtime"
3636
"k8s.io/apimachinery/pkg/types"
37-
"k8s.io/apimachinery/pkg/util/version"
3837
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
3938
"k8s.io/apiserver/pkg/registry/generic"
4039
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
@@ -680,7 +679,6 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) {
680679
t.Fatalf("unexpected error: %v", err)
681680
}
682681

683-
featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.33"))
684682
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.PodTopologyLabelsAdmission, true)
685683
// Suddenly, a wild scheduler appears:
686684
_, err = bindingStorage.Create(ctx, "foo", &api.Binding{

plugin/pkg/admission/podtopologylabels/admission.go

Lines changed: 94 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"context"
2121
"fmt"
2222
"io"
23-
"strings"
2423

2524
"k8s.io/klog/v2"
2625

@@ -39,7 +38,7 @@ import (
3938
const PluginName = "PodTopologyLabels"
4039

4140
// 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.
4342
// It is not exported to avoid any chance of accidentally mutating the variable.
4443
var defaultConfig = Config{
4544
Labels: []string{"topology.k8s.io/zone", "topology.k8s.io/region", "kubernetes.io/hostname"},
@@ -60,27 +59,13 @@ type Config struct {
6059
// Labels is set of explicit label keys to be copied from the Node object onto
6160
// pod Binding objects during admission.
6261
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
7562
}
7663

7764
// NewPodTopologyPlugin initializes a Plugin
7865
func NewPodTopologyPlugin(c Config) *Plugin {
7966
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...),
8469
}
8570
}
8671

@@ -89,9 +74,8 @@ type Plugin struct {
8974

9075
nodeLister corev1listers.NodeLister
9176

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]
9579

9680
enabled, inspectedFeatureGates bool
9781
}
@@ -126,105 +110,137 @@ func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.
126110
if !p.enabled {
127111
return nil
128112
}
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
131118
}
132119
// we need to wait for our caches to warm
133120
if !p.WaitForReady() {
134121
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
135122
}
123+
// run type specific admission
124+
return doAdmit(ctx, a, o)
125+
}
136126

127+
func (p *Plugin) admitBinding(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
137128
binding := a.GetObject().(*api.Binding)
138129
// other fields are not set by the default scheduler for the binding target, so only check the Kind.
139130
if binding.Target.Kind != "Node" {
140131
klog.V(6).Info("Skipping Pod being bound to non-Node object type", "target", binding.Target.GroupVersionKind())
141132
return nil
142133
}
143134

144-
node, err := p.nodeLister.Get(binding.Target.Name)
135+
labelsToCopy, err := p.topologyLabelsForNodeName(binding.Target.Name)
145136
if err != nil {
146-
// Ignore NotFound errors to avoid risking breaking compatibility/behaviour.
147-
if apierrors.IsNotFound(err) {
148-
return nil
149-
}
150137
return err
151138
}
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
155141
return nil
156142
}
157143

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
164159
}
165160

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+
}
166166
if len(labelsToCopy) == 0 {
167167
// fast-path/short circuit if the node has no topology labels
168168
return nil
169169
}
170170

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
175190
}
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) {
180194
continue
181195
}
182-
binding.Labels[k] = v
196+
labels[k] = v
183197
}
184198

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+
}
186210
}
187211

188212
func (p *Plugin) isTopologyLabel(key string) bool {
189213
// First check explicit label keys.
190214
if p.labels.Has(key) {
191215
return true
192216
}
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-
}
209217
return false
210218
}
211219

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)
221221

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
227227
}
228228

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+
}
230246
}

0 commit comments

Comments
 (0)