Skip to content

Commit febc641

Browse files
author
Andy Goldstein
committed
Fix DaemonSet controller cache mutation
Add dsStoreSynced so we also wait on this cache when starting the DaemonSetController. Switch to using a fake clientset in the unit tests. Fix TestNumberReadyStatus so it doesn't expect the cache to be mutated.
1 parent def8022 commit febc641

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)