Skip to content

Commit ad47a18

Browse files
author
Kubernetes Submit Queue
authored
Merge pull request kubernetes#38986 from ncdc/fix-daemonset-controller-cache-mutation
Automatic merge from submit-queue Fix DaemonSet cache mutation **What this PR does / why we need it**: stops the DaemonSetController from mutating the DaemonSet shared informer cache **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes kubernetes#38985 cc @deads2k @mikedanese @lavalamp @smarterclayton
2 parents 60a34fd + febc641 commit ad47a18

File tree

3 files changed

+70
-47
lines changed

3 files changed

+70
-47
lines changed

pkg/controller/daemon/BUILD

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ go_library(
1616
],
1717
tags = ["automanaged"],
1818
deps = [
19+
"//pkg/api:go_default_library",
1920
"//pkg/api/v1:go_default_library",
2021
"//pkg/apis/extensions/v1beta1:go_default_library",
2122
"//pkg/apis/meta/v1:go_default_library",
@@ -51,11 +52,11 @@ go_test(
5152
"//pkg/apis/extensions/v1beta1:go_default_library",
5253
"//pkg/apis/meta/v1:go_default_library",
5354
"//pkg/client/cache:go_default_library",
54-
"//pkg/client/clientset_generated/clientset:go_default_library",
55-
"//pkg/client/restclient:go_default_library",
55+
"//pkg/client/clientset_generated/clientset/fake:go_default_library",
56+
"//pkg/client/testing/core:go_default_library",
5657
"//pkg/controller:go_default_library",
5758
"//pkg/controller/informers:go_default_library",
59+
"//pkg/runtime:go_default_library",
5860
"//pkg/securitycontext:go_default_library",
59-
"//pkg/util/wait:go_default_library",
6061
],
6162
)

pkg/controller/daemon/daemoncontroller.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"sync"
2424
"time"
2525

26+
"k8s.io/kubernetes/pkg/api"
2627
"k8s.io/kubernetes/pkg/api/v1"
2728
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
2829
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
@@ -78,6 +79,9 @@ type DaemonSetsController struct {
7879
podStore *cache.StoreToPodLister
7980
// A store of nodes
8081
nodeStore *cache.StoreToNodeLister
82+
// dsStoreSynced returns true if the daemonset store has been synced at least once.
83+
// Added as a member to the struct to allow injection for testing.
84+
dsStoreSynced cache.InformerSynced
8185
// podStoreSynced returns true if the pod store has been synced at least once.
8286
// Added as a member to the struct to allow injection for testing.
8387
podStoreSynced cache.InformerSynced
@@ -142,6 +146,7 @@ func NewDaemonSetsController(daemonSetInformer informers.DaemonSetInformer, podI
142146
DeleteFunc: dsc.deleteDaemonset,
143147
})
144148
dsc.dsStore = daemonSetInformer.Lister()
149+
dsc.dsStoreSynced = daemonSetInformer.Informer().HasSynced
145150

146151
// Watch for creation/deletion of pods. The reason we watch is that we don't want a daemon set to create/delete
147152
// more pods until all the effects (expectations) of a daemon set's create/delete have been observed.
@@ -191,7 +196,7 @@ func (dsc *DaemonSetsController) Run(workers int, stopCh <-chan struct{}) {
191196

192197
glog.Infof("Starting Daemon Sets controller manager")
193198

194-
if !cache.WaitForCacheSync(stopCh, dsc.podStoreSynced, dsc.nodeStoreSynced) {
199+
if !cache.WaitForCacheSync(stopCh, dsc.podStoreSynced, dsc.nodeStoreSynced, dsc.dsStoreSynced) {
195200
return
196201
}
197202

@@ -539,19 +544,26 @@ func storeDaemonSetStatus(dsClient unversionedextensions.DaemonSetInterface, ds
539544
return nil
540545
}
541546

547+
clone, err := api.Scheme.DeepCopy(ds)
548+
if err != nil {
549+
return err
550+
}
551+
552+
toUpdate := clone.(*extensions.DaemonSet)
553+
542554
var updateErr, getErr error
543555
for i := 0; i < StatusUpdateRetries; i++ {
544-
ds.Status.DesiredNumberScheduled = int32(desiredNumberScheduled)
545-
ds.Status.CurrentNumberScheduled = int32(currentNumberScheduled)
546-
ds.Status.NumberMisscheduled = int32(numberMisscheduled)
547-
ds.Status.NumberReady = int32(numberReady)
556+
toUpdate.Status.DesiredNumberScheduled = int32(desiredNumberScheduled)
557+
toUpdate.Status.CurrentNumberScheduled = int32(currentNumberScheduled)
558+
toUpdate.Status.NumberMisscheduled = int32(numberMisscheduled)
559+
toUpdate.Status.NumberReady = int32(numberReady)
548560

549-
if _, updateErr = dsClient.UpdateStatus(ds); updateErr == nil {
561+
if _, updateErr = dsClient.UpdateStatus(toUpdate); updateErr == nil {
550562
return nil
551563
}
552564

553565
// Update the set with the latest resource version for the next poll
554-
if ds, getErr = dsClient.Get(ds.Name, metav1.GetOptions{}); getErr != nil {
566+
if toUpdate, getErr = dsClient.Get(ds.Name, metav1.GetOptions{}); getErr != nil {
555567
// If the GET fails we can't trust status.Replicas anymore. This error
556568
// is bound to be more interesting than the update failure.
557569
return getErr

pkg/controller/daemon/daemoncontroller_test.go

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ import (
2727
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
2828
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
2929
"k8s.io/kubernetes/pkg/client/cache"
30-
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
31-
"k8s.io/kubernetes/pkg/client/restclient"
30+
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
31+
"k8s.io/kubernetes/pkg/client/testing/core"
3232
"k8s.io/kubernetes/pkg/controller"
3333
"k8s.io/kubernetes/pkg/controller/informers"
34+
"k8s.io/kubernetes/pkg/runtime"
3435
"k8s.io/kubernetes/pkg/securitycontext"
35-
"k8s.io/kubernetes/pkg/util/wait"
3636
)
3737

3838
var (
@@ -137,18 +137,18 @@ func addPods(podStore cache.Store, nodeName string, label map[string]string, num
137137
}
138138
}
139139

140-
func newTestController() (*DaemonSetsController, *controller.FakePodControl) {
141-
clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &registered.GroupOrDie(v1.GroupName).GroupVersion}})
140+
func newTestController(initialObjects ...runtime.Object) (*DaemonSetsController, *controller.FakePodControl, *fake.Clientset) {
141+
clientset := fake.NewSimpleClientset(initialObjects...)
142142
informerFactory := informers.NewSharedInformerFactory(clientset, nil, controller.NoResyncPeriodFunc())
143143

144144
manager := NewDaemonSetsController(informerFactory.DaemonSets(), informerFactory.Pods(), informerFactory.Nodes(), clientset, 0)
145-
informerFactory.Start(wait.NeverStop)
146145

147146
manager.podStoreSynced = alwaysReady
148147
manager.nodeStoreSynced = alwaysReady
148+
manager.dsStoreSynced = alwaysReady
149149
podControl := &controller.FakePodControl{}
150150
manager.podControl = podControl
151-
return manager, podControl
151+
return manager, podControl, clientset
152152
}
153153

154154
func validateSyncDaemonSets(t *testing.T, fakePodControl *controller.FakePodControl, expectedCreates, expectedDeletes int) {
@@ -170,7 +170,7 @@ func syncAndValidateDaemonSets(t *testing.T, manager *DaemonSetsController, ds *
170170
}
171171

172172
func TestDeleteFinalStateUnknown(t *testing.T) {
173-
manager, _ := newTestController()
173+
manager, _, _ := newTestController()
174174
addNodes(manager.nodeStore.Store, 0, 1, nil)
175175
ds := newDaemonSet("foo")
176176
// DeletedFinalStateUnknown should queue the embedded DS if found.
@@ -183,7 +183,7 @@ func TestDeleteFinalStateUnknown(t *testing.T) {
183183

184184
// DaemonSets without node selectors should launch pods on every node.
185185
func TestSimpleDaemonSetLaunchesPods(t *testing.T) {
186-
manager, podControl := newTestController()
186+
manager, podControl, _ := newTestController()
187187
addNodes(manager.nodeStore.Store, 0, 5, nil)
188188
ds := newDaemonSet("foo")
189189
manager.dsStore.Add(ds)
@@ -192,7 +192,7 @@ func TestSimpleDaemonSetLaunchesPods(t *testing.T) {
192192

193193
// DaemonSets should do nothing if there aren't any nodes
194194
func TestNoNodesDoesNothing(t *testing.T) {
195-
manager, podControl := newTestController()
195+
manager, podControl, _ := newTestController()
196196
ds := newDaemonSet("foo")
197197
manager.dsStore.Add(ds)
198198
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0)
@@ -201,7 +201,7 @@ func TestNoNodesDoesNothing(t *testing.T) {
201201
// DaemonSets without node selectors should launch on a single node in a
202202
// single node cluster.
203203
func TestOneNodeDaemonLaunchesPod(t *testing.T) {
204-
manager, podControl := newTestController()
204+
manager, podControl, _ := newTestController()
205205
manager.nodeStore.Add(newNode("only-node", nil))
206206
ds := newDaemonSet("foo")
207207
manager.dsStore.Add(ds)
@@ -210,7 +210,7 @@ func TestOneNodeDaemonLaunchesPod(t *testing.T) {
210210

211211
// DaemonSets should place onto NotReady nodes
212212
func TestNotReadNodeDaemonDoesNotLaunchPod(t *testing.T) {
213-
manager, podControl := newTestController()
213+
manager, podControl, _ := newTestController()
214214
node := newNode("not-ready", nil)
215215
node.Status.Conditions = []v1.NodeCondition{
216216
{Type: v1.NodeReady, Status: v1.ConditionFalse},
@@ -223,7 +223,7 @@ func TestNotReadNodeDaemonDoesNotLaunchPod(t *testing.T) {
223223

224224
// DaemonSets should not place onto OutOfDisk nodes
225225
func TestOutOfDiskNodeDaemonDoesNotLaunchPod(t *testing.T) {
226-
manager, podControl := newTestController()
226+
manager, podControl, _ := newTestController()
227227
node := newNode("not-enough-disk", nil)
228228
node.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeOutOfDisk, Status: v1.ConditionTrue}}
229229
manager.nodeStore.Add(node)
@@ -254,7 +254,7 @@ func allocatableResources(memory, cpu string) v1.ResourceList {
254254
// DaemonSets should not place onto nodes with insufficient free resource
255255
func TestInsufficentCapacityNodeDaemonDoesNotLaunchPod(t *testing.T) {
256256
podSpec := resourcePodSpec("too-much-mem", "75M", "75m")
257-
manager, podControl := newTestController()
257+
manager, podControl, _ := newTestController()
258258
node := newNode("too-much-mem", nil)
259259
node.Status.Allocatable = allocatableResources("100M", "200m")
260260
manager.nodeStore.Add(node)
@@ -269,7 +269,7 @@ func TestInsufficentCapacityNodeDaemonDoesNotLaunchPod(t *testing.T) {
269269

270270
func TestSufficentCapacityWithTerminatedPodsDaemonLaunchesPod(t *testing.T) {
271271
podSpec := resourcePodSpec("too-much-mem", "75M", "75m")
272-
manager, podControl := newTestController()
272+
manager, podControl, _ := newTestController()
273273
node := newNode("too-much-mem", nil)
274274
node.Status.Allocatable = allocatableResources("100M", "200m")
275275
manager.nodeStore.Add(node)
@@ -286,7 +286,7 @@ func TestSufficentCapacityWithTerminatedPodsDaemonLaunchesPod(t *testing.T) {
286286
// DaemonSets should place onto nodes with sufficient free resource
287287
func TestSufficentCapacityNodeDaemonLaunchesPod(t *testing.T) {
288288
podSpec := resourcePodSpec("not-too-much-mem", "75M", "75m")
289-
manager, podControl := newTestController()
289+
manager, podControl, _ := newTestController()
290290
node := newNode("not-too-much-mem", nil)
291291
node.Status.Allocatable = allocatableResources("200M", "200m")
292292
manager.nodeStore.Add(node)
@@ -302,7 +302,7 @@ func TestSufficentCapacityNodeDaemonLaunchesPod(t *testing.T) {
302302
// DaemonSets not take any actions when being deleted
303303
func TestDontDoAnythingIfBeingDeleted(t *testing.T) {
304304
podSpec := resourcePodSpec("not-too-much-mem", "75M", "75m")
305-
manager, podControl := newTestController()
305+
manager, podControl, _ := newTestController()
306306
node := newNode("not-too-much-mem", nil)
307307
node.Status.Allocatable = allocatableResources("200M", "200m")
308308
manager.nodeStore.Add(node)
@@ -327,7 +327,7 @@ func TestPortConflictNodeDaemonDoesNotLaunchPod(t *testing.T) {
327327
}},
328328
}},
329329
}
330-
manager, podControl := newTestController()
330+
manager, podControl, _ := newTestController()
331331
node := newNode("port-conflict", nil)
332332
manager.nodeStore.Add(node)
333333
manager.podStore.Indexer.Add(&v1.Pod{
@@ -353,7 +353,7 @@ func TestPortConflictWithSameDaemonPodDoesNotDeletePod(t *testing.T) {
353353
}},
354354
}},
355355
}
356-
manager, podControl := newTestController()
356+
manager, podControl, _ := newTestController()
357357
node := newNode("port-conflict", nil)
358358
manager.nodeStore.Add(node)
359359
manager.podStore.Indexer.Add(&v1.Pod{
@@ -387,7 +387,7 @@ func TestNoPortConflictNodeDaemonLaunchesPod(t *testing.T) {
387387
}},
388388
}},
389389
}
390-
manager, podControl := newTestController()
390+
manager, podControl, _ := newTestController()
391391
node := newNode("no-port-conflict", nil)
392392
manager.nodeStore.Add(node)
393393
manager.podStore.Indexer.Add(&v1.Pod{
@@ -403,7 +403,7 @@ func TestNoPortConflictNodeDaemonLaunchesPod(t *testing.T) {
403403
//
404404
// issue https://github.com/kubernetes/kubernetes/pull/23223
405405
func TestPodIsNotDeletedByDaemonsetWithEmptyLabelSelector(t *testing.T) {
406-
manager, podControl := newTestController()
406+
manager, podControl, _ := newTestController()
407407
manager.nodeStore.Store.Add(newNode("node1", nil))
408408
// Create pod not controlled by a daemonset.
409409
manager.podStore.Indexer.Add(&v1.Pod{
@@ -436,7 +436,7 @@ func TestPodIsNotDeletedByDaemonsetWithEmptyLabelSelector(t *testing.T) {
436436

437437
// Controller should not create pods on nodes which have daemon pods, and should remove excess pods from nodes that have extra pods.
438438
func TestDealsWithExistingPods(t *testing.T) {
439-
manager, podControl := newTestController()
439+
manager, podControl, _ := newTestController()
440440
addNodes(manager.nodeStore.Store, 0, 5, nil)
441441
addPods(manager.podStore.Indexer, "node-1", simpleDaemonSetLabel, 1)
442442
addPods(manager.podStore.Indexer, "node-2", simpleDaemonSetLabel, 2)
@@ -449,7 +449,7 @@ func TestDealsWithExistingPods(t *testing.T) {
449449

450450
// Daemon with node selector should launch pods on nodes matching selector.
451451
func TestSelectorDaemonLaunchesPods(t *testing.T) {
452-
manager, podControl := newTestController()
452+
manager, podControl, _ := newTestController()
453453
addNodes(manager.nodeStore.Store, 0, 4, nil)
454454
addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel)
455455
daemon := newDaemonSet("foo")
@@ -460,7 +460,7 @@ func TestSelectorDaemonLaunchesPods(t *testing.T) {
460460

461461
// Daemon with node selector should delete pods from nodes that do not satisfy selector.
462462
func TestSelectorDaemonDeletesUnselectedPods(t *testing.T) {
463-
manager, podControl := newTestController()
463+
manager, podControl, _ := newTestController()
464464
addNodes(manager.nodeStore.Store, 0, 5, nil)
465465
addNodes(manager.nodeStore.Store, 5, 5, simpleNodeLabel)
466466
addPods(manager.podStore.Indexer, "node-0", simpleDaemonSetLabel2, 2)
@@ -475,7 +475,7 @@ func TestSelectorDaemonDeletesUnselectedPods(t *testing.T) {
475475

476476
// DaemonSet with node selector should launch pods on nodes matching selector, but also deal with existing pods on nodes.
477477
func TestSelectorDaemonDealsWithExistingPods(t *testing.T) {
478-
manager, podControl := newTestController()
478+
manager, podControl, _ := newTestController()
479479
addNodes(manager.nodeStore.Store, 0, 5, nil)
480480
addNodes(manager.nodeStore.Store, 5, 5, simpleNodeLabel)
481481
addPods(manager.podStore.Indexer, "node-0", simpleDaemonSetLabel, 1)
@@ -494,7 +494,7 @@ func TestSelectorDaemonDealsWithExistingPods(t *testing.T) {
494494

495495
// DaemonSet with node selector which does not match any node labels should not launch pods.
496496
func TestBadSelectorDaemonDoesNothing(t *testing.T) {
497-
manager, podControl := newTestController()
497+
manager, podControl, _ := newTestController()
498498
addNodes(manager.nodeStore.Store, 0, 4, nil)
499499
addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel)
500500
ds := newDaemonSet("foo")
@@ -505,7 +505,7 @@ func TestBadSelectorDaemonDoesNothing(t *testing.T) {
505505

506506
// DaemonSet with node name should launch pod on node with corresponding name.
507507
func TestNameDaemonSetLaunchesPods(t *testing.T) {
508-
manager, podControl := newTestController()
508+
manager, podControl, _ := newTestController()
509509
addNodes(manager.nodeStore.Store, 0, 5, nil)
510510
ds := newDaemonSet("foo")
511511
ds.Spec.Template.Spec.NodeName = "node-0"
@@ -515,7 +515,7 @@ func TestNameDaemonSetLaunchesPods(t *testing.T) {
515515

516516
// DaemonSet with node name that does not exist should not launch pods.
517517
func TestBadNameDaemonSetDoesNothing(t *testing.T) {
518-
manager, podControl := newTestController()
518+
manager, podControl, _ := newTestController()
519519
addNodes(manager.nodeStore.Store, 0, 5, nil)
520520
ds := newDaemonSet("foo")
521521
ds.Spec.Template.Spec.NodeName = "node-10"
@@ -525,7 +525,7 @@ func TestBadNameDaemonSetDoesNothing(t *testing.T) {
525525

526526
// DaemonSet with node selector, and node name, matching a node, should launch a pod on the node.
527527
func TestNameAndSelectorDaemonSetLaunchesPods(t *testing.T) {
528-
manager, podControl := newTestController()
528+
manager, podControl, _ := newTestController()
529529
addNodes(manager.nodeStore.Store, 0, 4, nil)
530530
addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel)
531531
ds := newDaemonSet("foo")
@@ -537,7 +537,7 @@ func TestNameAndSelectorDaemonSetLaunchesPods(t *testing.T) {
537537

538538
// DaemonSet with node selector that matches some nodes, and node name that matches a different node, should do nothing.
539539
func TestInconsistentNameSelectorDaemonSetDoesNothing(t *testing.T) {
540-
manager, podControl := newTestController()
540+
manager, podControl, _ := newTestController()
541541
addNodes(manager.nodeStore.Store, 0, 4, nil)
542542
addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel)
543543
ds := newDaemonSet("foo")
@@ -549,7 +549,7 @@ func TestInconsistentNameSelectorDaemonSetDoesNothing(t *testing.T) {
549549

550550
// Daemon with node affinity should launch pods on nodes matching affinity.
551551
func TestNodeAffinityDaemonLaunchesPods(t *testing.T) {
552-
manager, podControl := newTestController()
552+
manager, podControl, _ := newTestController()
553553
addNodes(manager.nodeStore.Store, 0, 4, nil)
554554
addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel)
555555
daemon := newDaemonSet("foo")
@@ -575,16 +575,26 @@ func TestNodeAffinityDaemonLaunchesPods(t *testing.T) {
575575
}
576576

577577
func TestNumberReadyStatus(t *testing.T) {
578-
manager, podControl := newTestController()
578+
daemon := newDaemonSet("foo")
579+
manager, podControl, clientset := newTestController()
580+
var updated *extensions.DaemonSet
581+
clientset.PrependReactor("update", "daemonsets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
582+
if action.GetSubresource() != "status" {
583+
return false, nil, nil
584+
}
585+
if u, ok := action.(core.UpdateAction); ok {
586+
updated = u.GetObject().(*extensions.DaemonSet)
587+
}
588+
return false, nil, nil
589+
})
579590
addNodes(manager.nodeStore.Store, 0, 2, simpleNodeLabel)
580591
addPods(manager.podStore.Indexer, "node-0", simpleDaemonSetLabel, 1)
581592
addPods(manager.podStore.Indexer, "node-1", simpleDaemonSetLabel, 1)
582-
daemon := newDaemonSet("foo")
583593
manager.dsStore.Add(daemon)
584594

585595
syncAndValidateDaemonSets(t, manager, daemon, podControl, 0, 0)
586-
if daemon.Status.NumberReady != 0 {
587-
t.Errorf("Wrong daemon %s status: %v", daemon.Name, daemon.Status)
596+
if updated.Status.NumberReady != 0 {
597+
t.Errorf("Wrong daemon %s status: %v", updated.Name, updated.Status)
588598
}
589599

590600
selector, _ := metav1.LabelSelectorAsSelector(daemon.Spec.Selector)
@@ -595,7 +605,7 @@ func TestNumberReadyStatus(t *testing.T) {
595605
}
596606

597607
syncAndValidateDaemonSets(t, manager, daemon, podControl, 0, 0)
598-
if daemon.Status.NumberReady != 2 {
599-
t.Errorf("Wrong daemon %s status: %v", daemon.Name, daemon.Status)
608+
if updated.Status.NumberReady != 2 {
609+
t.Errorf("Wrong daemon %s status: %v", updated.Name, updated.Status)
600610
}
601611
}

0 commit comments

Comments
 (0)