diff --git a/docs/user-guide/deployments.md b/docs/user-guide/deployments.md index 45bf9f45c7..75e55a2761 100644 --- a/docs/user-guide/deployments.md +++ b/docs/user-guide/deployments.md @@ -402,7 +402,6 @@ at any time during the update is at most 130% of desired pods. minimum number of seconds for which a newly created pod should be ready without any of its containers crashing, for it to be considered available. This defaults to 0 (the pod will be considered available as soon as it is ready). -__Note: This is not implemented yet__. ## Alternative to Deployments diff --git a/pkg/controller/deployment/deployment_controller.go b/pkg/controller/deployment/deployment_controller.go index ed0fc96225..6a99afcb74 100644 --- a/pkg/controller/deployment/deployment_controller.go +++ b/pkg/controller/deployment/deployment_controller.go @@ -208,8 +208,9 @@ func (d *DeploymentController) reconcileOldRCs(allRCs []*api.ReplicationControll } // Check if we can scale down. minAvailable := deployment.Spec.Replicas - maxUnavailable + minReadySeconds := deployment.Spec.Strategy.RollingUpdate.MinReadySeconds // Find the number of ready pods. - readyPodCount, err := deploymentutil.GetAvailablePodsForRCs(d.client, allRCs) + readyPodCount, err := deploymentutil.GetAvailablePodsForRCs(d.client, allRCs, minReadySeconds) if err != nil { return false, fmt.Errorf("could not find available pods: %v", err) } diff --git a/pkg/util/deployment/deployment.go b/pkg/util/deployment/deployment.go index bcc0057f44..6d2e38d351 100644 --- a/pkg/util/deployment/deployment.go +++ b/pkg/util/deployment/deployment.go @@ -19,6 +19,7 @@ package deployment import ( "fmt" "hash/adler32" + "time" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" @@ -132,19 +133,36 @@ func GetReplicaCountForRCs(replicationControllers []*api.ReplicationController) } // Returns the number of available pods corresponding to the given RCs. -func GetAvailablePodsForRCs(c client.Interface, rcs []*api.ReplicationController) (int, error) { - // TODO: Use MinReadySeconds once https://github.com/kubernetes/kubernetes/pull/12894 is merged. +func GetAvailablePodsForRCs(c client.Interface, rcs []*api.ReplicationController, minReadySeconds int) (int, error) { allPods, err := getPodsForRCs(c, rcs) if err != nil { return 0, err } + return getReadyPodsCount(allPods, minReadySeconds), nil +} + +func getReadyPodsCount(pods []api.Pod, minReadySeconds int) int { readyPodCount := 0 - for _, pod := range allPods { + for _, pod := range pods { if api.IsPodReady(&pod) { - readyPodCount++ + // Check if we've passed minReadySeconds since LastTransitionTime + // If so, this pod is ready + for _, c := range pod.Status.Conditions { + // we only care about pod ready conditions + if c.Type == api.PodReady { + // 2 cases that this ready condition is valid (passed minReadySeconds, i.e. the pod is ready): + // 1. minReadySeconds <= 0 + // 2. LastTransitionTime (is set) + minReadySeconds (>0) < current time + minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second + if minReadySeconds <= 0 || !c.LastTransitionTime.IsZero() && c.LastTransitionTime.Add(minReadySecondsDuration).Before(time.Now()) { + readyPodCount++ + break + } + } + } } } - return readyPodCount, nil + return readyPodCount } func getPodsForRCs(c client.Interface, replicationControllers []*api.ReplicationController) ([]api.Pod, error) { diff --git a/pkg/util/deployment/deployment_test.go b/pkg/util/deployment/deployment_test.go new file mode 100644 index 0000000000..54e044cd31 --- /dev/null +++ b/pkg/util/deployment/deployment_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package deployment + +import ( + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func newPod(now time.Time, ready bool, beforeSec int) api.Pod { + conditionStatus := api.ConditionFalse + if ready { + conditionStatus = api.ConditionTrue + } + return api.Pod{ + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + LastTransitionTime: unversioned.NewTime(now.Add(-1 * time.Duration(beforeSec) * time.Second)), + Status: conditionStatus, + }, + }, + }, + } +} + +func TestGetReadyPodsCount(t *testing.T) { + now := time.Now() + tests := []struct { + pods []api.Pod + minReadySeconds int + expected int + }{ + { + []api.Pod{ + newPod(now, true, 0), + newPod(now, true, 2), + newPod(now, false, 1), + }, + 1, + 1, + }, + { + []api.Pod{ + newPod(now, true, 2), + newPod(now, true, 11), + newPod(now, true, 5), + }, + 10, + 1, + }, + } + + for _, test := range tests { + if count := getReadyPodsCount(test.pods, test.minReadySeconds); count != test.expected { + t.Errorf("Pods = %#v, minReadySeconds = %d, expected %d, got %d", test.pods, test.minReadySeconds, test.expected, count) + } + } +} diff --git a/test/e2e/deployment.go b/test/e2e/deployment.go index 8591504043..18a69a64c3 100644 --- a/test/e2e/deployment.go +++ b/test/e2e/deployment.go @@ -170,7 +170,7 @@ func testRollingUpdateDeployment(f *Framework) { Expect(c.Deployments(ns).Delete(deploymentName, nil)).NotTo(HaveOccurred()) }() - err = waitForDeploymentStatus(c, ns, deploymentName, 3, 2, 4) + err = waitForDeploymentStatus(c, ns, deploymentName, 3, 2, 4, 0) Expect(err).NotTo(HaveOccurred()) } @@ -251,7 +251,7 @@ func testRollingUpdateDeploymentEvents(f *Framework) { Expect(c.Deployments(ns).Delete(deploymentName, nil)).NotTo(HaveOccurred()) }() - err = waitForDeploymentStatus(c, ns, deploymentName, 1, 0, 2) + err = waitForDeploymentStatus(c, ns, deploymentName, 1, 0, 2, 0) Expect(err).NotTo(HaveOccurred()) // Verify that the pods were scaled up and down as expected. We use events to verify that. deployment, err := c.Deployments(ns).Get(deploymentName) diff --git a/test/e2e/util.go b/test/e2e/util.go index 7a0e6a7eed..1f45aebd03 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -1843,7 +1843,7 @@ func waitForRCPodsGone(c *client.Client, rc *api.ReplicationController) error { // Waits for the deployment to reach desired state. // Returns an error if minAvailable or maxCreated is broken at any times. -func waitForDeploymentStatus(c *client.Client, ns, deploymentName string, desiredUpdatedReplicas, minAvailable, maxCreated int) error { +func waitForDeploymentStatus(c *client.Client, ns, deploymentName string, desiredUpdatedReplicas, minAvailable, maxCreated, minReadySeconds int) error { return wait.Poll(poll, 5*time.Minute, func() (bool, error) { deployment, err := c.Deployments(ns).Get(deploymentName) @@ -1864,7 +1864,7 @@ func waitForDeploymentStatus(c *client.Client, ns, deploymentName string, desire } allRCs := append(oldRCs, newRC) totalCreated := deploymentutil.GetReplicaCountForRCs(allRCs) - totalAvailable, err := deploymentutil.GetAvailablePodsForRCs(c, allRCs) + totalAvailable, err := deploymentutil.GetAvailablePodsForRCs(c, allRCs, minReadySeconds) if err != nil { return false, err }