// When a pod is deleted, enqueue the job that manages the pod and update its expectations. // obj could be an *api.Pod, or a DeletionFinalStateUnknown marker item. func (jm *JobController) deletePod(obj interface{}) { pod, ok := obj.(*api.Pod) // When a delete is dropped, the relist will notice a pod in the store not // in the list, leading to the insertion of a tombstone object which contains // the deleted key/value. Note that this value might be stale. If the pod // changed labels the new job will not be woken up till the periodic resync. if !ok { tombstone, ok := obj.(cache.DeletedFinalStateUnknown) if !ok { glog.Errorf("Couldn't get object from tombstone %+v, could take up to %v before a job recreates a pod", obj, controller.ExpectationsTimeout) return } pod, ok = tombstone.Obj.(*api.Pod) if !ok { glog.Errorf("Tombstone contained object that is not a pod %+v, could take up to %v before job recreates a pod", obj, controller.ExpectationsTimeout) return } } if job := jm.getPodJob(pod); job != nil { jobKey, err := controller.KeyFunc(job) if err != nil { glog.Errorf("Couldn't get key for job %#v: %v", job, err) return } jm.expectations.DeletionObserved(jobKey) jm.enqueueController(job) } }
func (dsc *DaemonSetsController) deletePod(obj interface{}) { pod, ok := obj.(*api.Pod) glog.V(4).Infof("Pod %s deleted.", pod.Name) // When a delete is dropped, the relist will notice a pod in the store not // in the list, leading to the insertion of a tombstone object which contains // the deleted key/value. Note that this value might be stale. If the pod // changed labels the new rc will not be woken up till the periodic resync. if !ok { tombstone, ok := obj.(cache.DeletedFinalStateUnknown) if !ok { glog.Errorf("Couldn't get object from tombstone %+v", obj) return } pod, ok = tombstone.Obj.(*api.Pod) if !ok { glog.Errorf("Tombstone contained object that is not a pod %+v", obj) return } } if ds := dsc.getPodDaemonSet(pod); ds != nil { dsKey, err := controller.KeyFunc(ds) if err != nil { glog.Errorf("Couldn't get key for object %+v: %v", ds, err) return } dsc.expectations.DeletionObserved(dsKey) dsc.enqueueDaemonSet(ds) } }
// When a pod is deleted, enqueue the controller that manages the pod and update its expectations. // obj could be an *api.Pod, or a DeletionFinalStateUnknown marker item. func (rm *ReplicationManager) deletePod(obj interface{}) { pod, ok := obj.(*api.Pod) // When a delete is dropped, the relist will notice a pod in the store not // in the list, leading to the insertion of a tombstone object which contains // the deleted key/value. Note that this value might be stale. If the pod // changed labels the new rc will not be woken up till the periodic resync. if !ok { tombstone, ok := obj.(cache.DeletedFinalStateUnknown) if !ok { glog.Errorf("Couldn't get object from tombstone %+v, could take up to %v before a controller recreates a replica", obj, controller.ExpectationsTimeout) return } pod, ok = tombstone.Obj.(*api.Pod) if !ok { glog.Errorf("Tombstone contained object that is not a pod %+v, could take up to %v before controller recreates a replica", obj, controller.ExpectationsTimeout) return } } if rc := rm.getPodController(pod); rc != nil { rcKey, err := controller.KeyFunc(rc) if err != nil { glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err) return } rm.expectations.DeletionObserved(rcKey) rm.enqueueController(rc) } }
func getKey(ds *extensions.DaemonSet, t *testing.T) string { if key, err := controller.KeyFunc(ds); err != nil { t.Errorf("Unexpected error getting key for ds %v: %v", ds.Name, err) return "" } else { return key } }
func getKey(rc *api.ReplicationController, t *testing.T) string { if key, err := controller.KeyFunc(rc); err != nil { t.Errorf("Unexpected error getting key for rc %v: %v", rc.Name, err) return "" } else { return key } }
func getKey(job *extensions.Job, t *testing.T) string { if key, err := controller.KeyFunc(job); err != nil { t.Errorf("Unexpected error getting key for job %v: %v", job.Name, err) return "" } else { return key } }
func syncAndValidateDaemonSets(t *testing.T, manager *DaemonSetsController, ds *extensions.DaemonSet, podControl *controller.FakePodControl, expectedCreates, expectedDeletes int) { key, err := controller.KeyFunc(ds) if err != nil { t.Errorf("Could not get key for daemon.") } manager.syncHandler(key) validateSyncDaemonSets(t, podControl, expectedCreates, expectedDeletes) }
// syncReplicationController will sync the rc with the given key if it has had its expectations fulfilled, meaning // it did not expect to see any more of its pods created or deleted. This function is not meant to be invoked // concurrently with the same key. func (rm *ReplicationManager) syncReplicationController(key string) error { startTime := time.Now() defer func() { glog.V(4).Infof("Finished syncing controller %q (%v)", key, time.Now().Sub(startTime)) }() obj, exists, err := rm.rcStore.Store.GetByKey(key) if !exists { glog.Infof("Replication Controller has been deleted %v", key) rm.expectations.DeleteExpectations(key) return nil } if err != nil { glog.Infof("Unable to retrieve rc %v from store: %v", key, err) rm.queue.Add(key) return err } rc := *obj.(*api.ReplicationController) if !rm.podStoreSynced() { // Sleep so we give the pod reflector goroutine a chance to run. time.Sleep(PodStoreSyncedPollPeriod) glog.Infof("Waiting for pods controller to sync, requeuing rc %v", rc.Name) rm.enqueueController(&rc) return nil } // Check the expectations of the rc before counting active pods, otherwise a new pod can sneak in // and update the expectations after we've retrieved active pods from the store. If a new pod enters // the store after we've checked the expectation, the rc sync is just deferred till the next relist. rcKey, err := controller.KeyFunc(&rc) if err != nil { glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err) return err } rcNeedsSync := rm.expectations.SatisfiedExpectations(rcKey) podList, err := rm.podStore.Pods(rc.Namespace).List(labels.Set(rc.Spec.Selector).AsSelector()) if err != nil { glog.Errorf("Error getting pods for rc %q: %v", key, err) rm.queue.Add(key) return err } // TODO: Do this in a single pass, or use an index. filteredPods := controller.FilterActivePods(podList.Items) if rcNeedsSync { rm.manageReplicas(filteredPods, &rc) } // Always updates status as pods come up or die. if err := updateReplicaCount(rm.kubeClient.ReplicationControllers(rc.Namespace), rc, len(filteredPods)); err != nil { // Multiple things could lead to this update failing. Requeuing the controller ensures // we retry with some fairness. glog.V(2).Infof("Failed to update replica count for controller %v, requeuing", rc.Name) rm.enqueueController(&rc) } return nil }
// manageReplicas checks and updates replicas for the given replication controller. func (rm *ReplicationManager) manageReplicas(filteredPods []*api.Pod, rc *api.ReplicationController) { diff := len(filteredPods) - rc.Spec.Replicas rcKey, err := controller.KeyFunc(rc) if err != nil { glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err) return } if diff < 0 { diff *= -1 if diff > rm.burstReplicas { diff = rm.burstReplicas } rm.expectations.ExpectCreations(rcKey, diff) wait := sync.WaitGroup{} wait.Add(diff) glog.V(2).Infof("Too few %q/%q replicas, need %d, creating %d", rc.Namespace, rc.Name, rc.Spec.Replicas, diff) for i := 0; i < diff; i++ { go func() { defer wait.Done() if err := rm.podControl.CreatePods(rc.Namespace, rc.Spec.Template, rc); err != nil { // Decrement the expected number of creates because the informer won't observe this pod glog.V(2).Infof("Failed creation, decrementing expectations for controller %q/%q", rc.Namespace, rc.Name) rm.expectations.CreationObserved(rcKey) util.HandleError(err) } }() } wait.Wait() } else if diff > 0 { if diff > rm.burstReplicas { diff = rm.burstReplicas } rm.expectations.ExpectDeletions(rcKey, diff) glog.V(2).Infof("Too many %q/%q replicas, need %d, deleting %d", rc.Namespace, rc.Name, rc.Spec.Replicas, diff) // No need to sort pods if we are about to delete all of them if rc.Spec.Replicas != 0 { // Sort the pods in the order such that not-ready < ready, unscheduled // < scheduled, and pending < running. This ensures that we delete pods // in the earlier stages whenever possible. sort.Sort(controller.ActivePods(filteredPods)) } wait := sync.WaitGroup{} wait.Add(diff) for i := 0; i < diff; i++ { go func(ix int) { defer wait.Done() if err := rm.podControl.DeletePod(rc.Namespace, filteredPods[ix].Name); err != nil { // Decrement the expected number of deletes because the informer won't observe this deletion glog.V(2).Infof("Failed deletion, decrementing expectations for controller %q/%q", rc.Namespace, rc.Name) rm.expectations.DeletionObserved(rcKey) } }(i) } wait.Wait() } }
func (dsc *DaemonSetsController) enqueueDaemonSet(obj interface{}) { key, err := controller.KeyFunc(obj) if err != nil { glog.Errorf("Couldn't get key for object %+v: %v", obj, err) return } // TODO: Handle overlapping controllers better. See comment in ReplicationManager. dsc.queue.Add(key) }
func TestSyncReplicationControllerDormancy(t *testing.T) { // Setup a test server so we can lie about the current state of pods fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: "", } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Default.Version()}) fakePodControl := controller.FakePodControl{} manager := NewReplicationManager(client, controller.NoResyncPeriodFunc, BurstReplicas) manager.podStoreSynced = alwaysReady manager.podControl = &fakePodControl controllerSpec := newReplicationController(2) manager.rcStore.Store.Add(controllerSpec) newPodList(manager.podStore.Store, 1, api.PodRunning, controllerSpec) // Creates a replica and sets expectations controllerSpec.Status.Replicas = 1 manager.syncReplicationController(getKey(controllerSpec, t)) validateSyncReplication(t, &fakePodControl, 1, 0) // Expectations prevents replicas but not an update on status controllerSpec.Status.Replicas = 0 fakePodControl.Clear() manager.syncReplicationController(getKey(controllerSpec, t)) validateSyncReplication(t, &fakePodControl, 0, 0) // Get the key for the controller rcKey, err := controller.KeyFunc(controllerSpec) if err != nil { t.Errorf("Couldn't get key for object %+v: %v", controllerSpec, err) } // Lowering expectations should lead to a sync that creates a replica, however the // fakePodControl error will prevent this, leaving expectations at 0, 0 manager.expectations.CreationObserved(rcKey) controllerSpec.Status.Replicas = 1 fakePodControl.Clear() fakePodControl.Err = fmt.Errorf("Fake Error") manager.syncReplicationController(getKey(controllerSpec, t)) validateSyncReplication(t, &fakePodControl, 0, 0) // This replica should not need a Lowering of expectations, since the previous create failed fakePodControl.Err = nil manager.syncReplicationController(getKey(controllerSpec, t)) validateSyncReplication(t, &fakePodControl, 1, 0) // 1 PUT for the rc status during dormancy window. // Note that the pod creates go through pod control so they're not recorded. fakeHandler.ValidateRequestCount(t, 1) }
func (dsc *DaemonSetsController) addPod(obj interface{}) { pod := obj.(*api.Pod) glog.V(4).Infof("Pod %s added.", pod.Name) if ds := dsc.getPodDaemonSet(pod); ds != nil { dsKey, err := controller.KeyFunc(ds) if err != nil { glog.Errorf("Couldn't get key for object %+v: %v", ds, err) return } dsc.expectations.CreationObserved(dsKey) dsc.enqueueDaemonSet(ds) } }
// obj could be an *extensions.Job, or a DeletionFinalStateUnknown marker item. func (jm *JobController) enqueueController(obj interface{}) { key, err := controller.KeyFunc(obj) if err != nil { glog.Errorf("Couldn't get key for object %+v: %v", obj, err) return } // TODO: Handle overlapping controllers better. Either disallow them at admission time or // deterministically avoid syncing controllers that fight over pods. Currently, we only // ensure that the same controller is synced for a given pod. When we periodically relist // all controllers there will still be some replica instability. One way to handle this is // by querying the store for all controllers that this rc overlaps, as well as all // controllers that overlap this rc, and sorting them. jm.queue.Add(key) }
// When a pod is created, enqueue the controller that manages it and update it's expectations. func (jm *JobController) addPod(obj interface{}) { pod := obj.(*api.Pod) if pod.DeletionTimestamp != nil { // on a restart of the controller controller, it's possible a new pod shows up in a state that // is already pending deletion. Prevent the pod from being a creation observation. jm.deletePod(pod) return } if job := jm.getPodJob(pod); job != nil { jobKey, err := controller.KeyFunc(job) if err != nil { glog.Errorf("Couldn't get key for job %#v: %v", job, err) return } jm.expectations.CreationObserved(jobKey) jm.enqueueController(job) } }
// When a pod is created, enqueue the controller that manages it and update it's expectations. func (rm *ReplicationManager) addPod(obj interface{}) { pod := obj.(*api.Pod) if pod.DeletionTimestamp != nil { // on a restart of the controller manager, it's possible a new pod shows up in a state that // is already pending deletion. Prevent the pod from being a creation observation. rm.deletePod(pod) return } if rc := rm.getPodController(pod); rc != nil { rcKey, err := controller.KeyFunc(rc) if err != nil { glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err) return } rm.expectations.CreationObserved(rcKey) rm.enqueueController(rc) } }
func (dsc *DaemonSetsController) syncDaemonSet(key string) error { startTime := time.Now() defer func() { glog.V(4).Infof("Finished syncing daemon set %q (%v)", key, time.Now().Sub(startTime)) }() obj, exists, err := dsc.dsStore.Store.GetByKey(key) if err != nil { glog.Infof("Unable to retrieve ds %v from store: %v", key, err) dsc.queue.Add(key) return err } if !exists { glog.V(3).Infof("daemon set has been deleted %v", key) dsc.expectations.DeleteExpectations(key) return nil } ds := obj.(*extensions.DaemonSet) if !dsc.podStoreSynced() { // Sleep so we give the pod reflector goroutine a chance to run. time.Sleep(PodStoreSyncedPollPeriod) glog.Infof("Waiting for pods controller to sync, requeuing ds %v", ds.Name) dsc.enqueueDaemonSet(ds) return nil } // Don't process a daemon set until all its creations and deletions have been processed. // For example if daemon set foo asked for 3 new daemon pods in the previous call to manage, // then we do not want to call manage on foo until the daemon pods have been created. dsKey, err := controller.KeyFunc(ds) if err != nil { glog.Errorf("Couldn't get key for object %+v: %v", ds, err) return err } dsNeedsSync := dsc.expectations.SatisfiedExpectations(dsKey) if dsNeedsSync { dsc.manage(ds) } dsc.updateDaemonSetStatus(ds) return nil }
func TestDeleteControllerAndExpectations(t *testing.T) { client := client.NewOrDie(&client.Config{Host: "", Version: testapi.Default.Version()}) manager := NewReplicationManager(client, controller.NoResyncPeriodFunc, 10) manager.podStoreSynced = alwaysReady rc := newReplicationController(1) manager.rcStore.Store.Add(rc) fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl // This should set expectations for the rc manager.syncReplicationController(getKey(rc, t)) validateSyncReplication(t, &fakePodControl, 1, 0) fakePodControl.Clear() // Get the RC key rcKey, err := controller.KeyFunc(rc) if err != nil { t.Errorf("Couldn't get key for object %+v: %v", rc, err) } // This is to simulate a concurrent addPod, that has a handle on the expectations // as the controller deletes it. podExp, exists, err := manager.expectations.GetExpectations(rcKey) if !exists || err != nil { t.Errorf("No expectations found for rc") } manager.rcStore.Delete(rc) manager.syncReplicationController(getKey(rc, t)) if _, exists, err = manager.expectations.GetExpectations(rcKey); exists { t.Errorf("Found expectaions, expected none since the rc has been deleted.") } // This should have no effect, since we've deleted the rc. podExp.Seen(1, 0) manager.podStore.Store.Replace(make([]interface{}, 0), "0") manager.syncReplicationController(getKey(rc, t)) validateSyncReplication(t, &fakePodControl, 0, 0) }
func (dsc *DaemonSetsController) manage(ds *extensions.DaemonSet) { // Find out which nodes are running the daemon pods selected by ds. nodeToDaemonPods, err := dsc.getNodesToDaemonPods(ds) if err != nil { glog.Errorf("Error getting node to daemon pod mapping for daemon set %+v: %v", ds, err) } // For each node, if the node is running the daemon pod but isn't supposed to, kill the daemon // pod. If the node is supposed to run the daemon pod, but isn't, create the daemon pod on the node. nodeList, err := dsc.nodeStore.List() if err != nil { glog.Errorf("Couldn't get list of nodes when syncing daemon set %+v: %v", ds, err) } var nodesNeedingDaemonPods, podsToDelete []string for i, node := range nodeList.Items { // Check if the node satisfies the daemon set's node selector. nodeSelector := labels.Set(ds.Spec.Template.Spec.NodeSelector).AsSelector() shouldRun := nodeSelector.Matches(labels.Set(nodeList.Items[i].Labels)) // If the daemon set specifies a node name, check that it matches with nodeName. nodeName := nodeList.Items[i].Name shouldRun = shouldRun && (ds.Spec.Template.Spec.NodeName == "" || ds.Spec.Template.Spec.NodeName == nodeName) // If the node is not ready, don't run on it. // TODO(mikedanese): remove this once daemonpods forgive nodes shouldRun = shouldRun && api.IsNodeReady(&node) daemonPods, isRunning := nodeToDaemonPods[nodeName] if shouldRun && !isRunning { // If daemon pod is supposed to be running on node, but isn't, create daemon pod. nodesNeedingDaemonPods = append(nodesNeedingDaemonPods, nodeName) } else if shouldRun && len(daemonPods) > 1 { // If daemon pod is supposed to be running on node, but more than 1 daemon pod is running, delete the excess daemon pods. // TODO: sort the daemon pods by creation time, so the the oldest is preserved. for i := 1; i < len(daemonPods); i++ { podsToDelete = append(podsToDelete, daemonPods[i].Name) } } else if !shouldRun && isRunning { // If daemon pod isn't supposed to run on node, but it is, delete all daemon pods on node. for i := range daemonPods { podsToDelete = append(podsToDelete, daemonPods[i].Name) } } } // We need to set expectations before creating/deleting pods to avoid race conditions. dsKey, err := controller.KeyFunc(ds) if err != nil { glog.Errorf("Couldn't get key for object %+v: %v", ds, err) return } dsc.expectations.SetExpectations(dsKey, len(nodesNeedingDaemonPods), len(podsToDelete)) glog.V(4).Infof("Nodes needing daemon pods for daemon set %s: %+v", ds.Name, nodesNeedingDaemonPods) for i := range nodesNeedingDaemonPods { if err := dsc.podControl.CreatePodsOnNode(nodesNeedingDaemonPods[i], ds.Namespace, ds.Spec.Template, ds); err != nil { glog.V(2).Infof("Failed creation, decrementing expectations for set %q/%q", ds.Namespace, ds.Name) dsc.expectations.CreationObserved(dsKey) util.HandleError(err) } } glog.V(4).Infof("Pods to delete for daemon set %s: %+v", ds.Name, podsToDelete) for i := range podsToDelete { if err := dsc.podControl.DeletePod(ds.Namespace, podsToDelete[i]); err != nil { glog.V(2).Infof("Failed deletion, decrementing expectations for set %q/%q", ds.Namespace, ds.Name) dsc.expectations.DeletionObserved(dsKey) util.HandleError(err) } } }
func (jm *JobController) manageJob(activePods []*api.Pod, succeeded int, job *extensions.Job) int { var activeLock sync.Mutex active := len(activePods) parallelism := *job.Spec.Parallelism jobKey, err := controller.KeyFunc(job) if err != nil { glog.Errorf("Couldn't get key for job %#v: %v", job, err) return 0 } if active > parallelism { diff := active - parallelism jm.expectations.ExpectDeletions(jobKey, diff) glog.V(4).Infof("Too many pods running job %q, need %d, deleting %d", jobKey, parallelism, diff) // Sort the pods in the order such that not-ready < ready, unscheduled // < scheduled, and pending < running. This ensures that we delete pods // in the earlier stages whenever possible. sort.Sort(controller.ActivePods(activePods)) active -= diff wait := sync.WaitGroup{} wait.Add(diff) for i := 0; i < diff; i++ { go func(ix int) { defer wait.Done() if err := jm.podControl.DeletePod(job.Namespace, activePods[ix].Name); err != nil { defer util.HandleError(err) // Decrement the expected number of deletes because the informer won't observe this deletion jm.expectations.DeletionObserved(jobKey) activeLock.Lock() active++ activeLock.Unlock() } }(i) } wait.Wait() } else if active < parallelism { // how many executions are left to run diff := *job.Spec.Completions - succeeded // limit to parallelism and count active pods as well if diff > parallelism { diff = parallelism } diff -= active jm.expectations.ExpectCreations(jobKey, diff) glog.V(4).Infof("Too few pods running job %q, need %d, creating %d", jobKey, parallelism, diff) active += diff wait := sync.WaitGroup{} wait.Add(diff) for i := 0; i < diff; i++ { go func() { defer wait.Done() if err := jm.podControl.CreatePods(job.Namespace, &job.Spec.Template, job); err != nil { defer util.HandleError(err) // Decrement the expected number of creates because the informer won't observe this pod jm.expectations.CreationObserved(jobKey) activeLock.Lock() active-- activeLock.Unlock() } }() } wait.Wait() } return active }
// syncJob will sync the job with the given key if it has had its expectations fulfilled, meaning // it did not expect to see any more of its pods created or deleted. This function is not meant to be invoked // concurrently with the same key. func (jm *JobController) syncJob(key string) error { startTime := time.Now() defer func() { glog.V(4).Infof("Finished syncing job %q (%v)", key, time.Now().Sub(startTime)) }() obj, exists, err := jm.jobStore.Store.GetByKey(key) if !exists { glog.V(4).Infof("Job has been deleted: %v", key) jm.expectations.DeleteExpectations(key) return nil } if err != nil { glog.Errorf("Unable to retrieve job %v from store: %v", key, err) jm.queue.Add(key) return err } job := *obj.(*extensions.Job) if !jm.podStoreSynced() { // Sleep so we give the pod reflector goroutine a chance to run. time.Sleep(replicationcontroller.PodStoreSyncedPollPeriod) glog.V(4).Infof("Waiting for pods controller to sync, requeuing job %v", job.Name) jm.enqueueController(&job) return nil } // Check the expectations of the job before counting active pods, otherwise a new pod can sneak in // and update the expectations after we've retrieved active pods from the store. If a new pod enters // the store after we've checked the expectation, the job sync is just deferred till the next relist. jobKey, err := controller.KeyFunc(&job) if err != nil { glog.Errorf("Couldn't get key for job %#v: %v", job, err) return err } jobNeedsSync := jm.expectations.SatisfiedExpectations(jobKey) selector, _ := extensions.PodSelectorAsSelector(job.Spec.Selector) podList, err := jm.podStore.Pods(job.Namespace).List(selector) if err != nil { glog.Errorf("Error getting pods for job %q: %v", key, err) jm.queue.Add(key) return err } activePods := controller.FilterActivePods(podList.Items) active := len(activePods) succeeded, failed := getStatus(podList.Items) if jobNeedsSync { active = jm.manageJob(activePods, succeeded, &job) } completions := succeeded if completions == *job.Spec.Completions { job.Status.Conditions = append(job.Status.Conditions, newCondition()) } // no need to update the job if the status hasn't changed since last time if job.Status.Active != active || job.Status.Succeeded != succeeded || job.Status.Failed != failed { job.Status.Active = active job.Status.Succeeded = succeeded job.Status.Failed = failed if err := jm.updateHandler(&job); err != nil { glog.Errorf("Failed to update job %v, requeuing. Error: %v", job.Name, err) jm.enqueueController(&job) } } return nil }
func doTestControllerBurstReplicas(t *testing.T, burstReplicas, numReplicas int) { client := client.NewOrDie(&client.Config{Host: "", Version: testapi.Default.Version()}) fakePodControl := controller.FakePodControl{} manager := NewReplicationManager(client, controller.NoResyncPeriodFunc, burstReplicas) manager.podStoreSynced = alwaysReady manager.podControl = &fakePodControl controllerSpec := newReplicationController(numReplicas) manager.rcStore.Store.Add(controllerSpec) expectedPods := 0 pods := newPodList(nil, numReplicas, api.PodPending, controllerSpec) rcKey, err := controller.KeyFunc(controllerSpec) if err != nil { t.Errorf("Couldn't get key for object %+v: %v", controllerSpec, err) } // Size up the controller, then size it down, and confirm the expected create/delete pattern for _, replicas := range []int{numReplicas, 0} { controllerSpec.Spec.Replicas = replicas manager.rcStore.Store.Add(controllerSpec) for i := 0; i < numReplicas; i += burstReplicas { manager.syncReplicationController(getKey(controllerSpec, t)) // The store accrues active pods. It's also used by the rc to determine how many // replicas to create. activePods := len(manager.podStore.Store.List()) if replicas != 0 { // This is the number of pods currently "in flight". They were created by the rc manager above, // which then puts the rc to sleep till all of them have been observed. expectedPods = replicas - activePods if expectedPods > burstReplicas { expectedPods = burstReplicas } // This validates the rc manager sync actually created pods validateSyncReplication(t, &fakePodControl, expectedPods, 0) // This simulates the watch events for all but 1 of the expected pods. // None of these should wake the controller because it has expectations==BurstReplicas. for i := 0; i < expectedPods-1; i++ { manager.podStore.Store.Add(&pods.Items[i]) manager.addPod(&pods.Items[i]) } podExp, exists, err := manager.expectations.GetExpectations(rcKey) if !exists || err != nil { t.Fatalf("Did not find expectations for rc.") } if add, _ := podExp.GetExpectations(); add != 1 { t.Fatalf("Expectations are wrong %v", podExp) } } else { expectedPods = (replicas - activePods) * -1 if expectedPods > burstReplicas { expectedPods = burstReplicas } validateSyncReplication(t, &fakePodControl, 0, expectedPods) for i := 0; i < expectedPods-1; i++ { manager.podStore.Store.Delete(&pods.Items[i]) manager.deletePod(&pods.Items[i]) } podExp, exists, err := manager.expectations.GetExpectations(rcKey) if !exists || err != nil { t.Fatalf("Did not find expectations for rc.") } if _, del := podExp.GetExpectations(); del != 1 { t.Fatalf("Expectations are wrong %v", podExp) } } // Check that the rc didn't take any action for all the above pods fakePodControl.Clear() manager.syncReplicationController(getKey(controllerSpec, t)) validateSyncReplication(t, &fakePodControl, 0, 0) // Create/Delete the last pod // The last add pod will decrease the expectation of the rc to 0, // which will cause it to create/delete the remaining replicas up to burstReplicas. if replicas != 0 { manager.podStore.Store.Add(&pods.Items[expectedPods-1]) manager.addPod(&pods.Items[expectedPods-1]) } else { manager.podStore.Store.Delete(&pods.Items[expectedPods-1]) manager.deletePod(&pods.Items[expectedPods-1]) } pods.Items = pods.Items[expectedPods:] } // Confirm that we've created the right number of replicas activePods := len(manager.podStore.Store.List()) if activePods != controllerSpec.Spec.Replicas { t.Fatalf("Unexpected number of active pods, expected %d, got %d", controllerSpec.Spec.Replicas, activePods) } // Replenish the pod list, since we cut it down sizing up pods = newPodList(nil, replicas, api.PodRunning, controllerSpec) } }