diff --git a/test/e2e/disruption.go b/test/e2e/disruption.go index 276ab8b3348..7bad1dee8fa 100644 --- a/test/e2e/disruption.go +++ b/test/e2e/disruption.go @@ -23,14 +23,24 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" release_1_4 "k8s.io/client-go/1.4/kubernetes" + apiapi "k8s.io/client-go/1.4/pkg/api" "k8s.io/client-go/1.4/pkg/api/unversioned" api "k8s.io/client-go/1.4/pkg/api/v1" + extensions "k8s.io/client-go/1.4/pkg/apis/extensions/v1beta1" policy "k8s.io/client-go/1.4/pkg/apis/policy/v1alpha1" "k8s.io/client-go/1.4/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/test/e2e/framework" ) +// timeout is used for most polling/waiting activities +const timeout = 60 * time.Second + +// schedulingTimeout is longer specifically because sometimes we need to wait +// awhile to guarantee that we've been patient waiting for something ordinary +// to happen: a pod to get scheduled and move into Ready +const schedulingTimeout = 5 * time.Minute + var _ = framework.KubeDescribe("DisruptionController", func() { f := framework.NewDefaultFramework("disruption") var ns string @@ -55,7 +65,7 @@ var _ = framework.KubeDescribe("DisruptionController", func() { // Since disruptionAllowed starts out false, if we see it ever become true, // that means the controller is working. - err := wait.PollImmediate(framework.Poll, 60*time.Second, func() (bool, error) { + err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) { pdb, err := cs.Policy().PodDisruptionBudgets(ns).Get("foo") if err != nil { return false, err @@ -66,73 +76,107 @@ var _ = framework.KubeDescribe("DisruptionController", func() { }) - It("should allow an eviction when there is no PDB", func() { - createPodsOrDie(cs, ns, 1) - - pod, err := cs.Pods(ns).Get("pod-0") - Expect(err).NotTo(HaveOccurred()) - - e := &policy.Eviction{ - ObjectMeta: api.ObjectMeta{ - Name: pod.Name, - Namespace: ns, - }, + evictionCases := []struct { + description string + minAvailable intstr.IntOrString + podCount int + replicaSetSize int32 + shouldDeny bool + exclusive bool + }{ + { + description: "no PDB", + minAvailable: intstr.FromString(""), + podCount: 1, + shouldDeny: false, + }, { + description: "too few pods, absolute", + minAvailable: intstr.FromInt(2), + podCount: 1, + shouldDeny: true, + }, { + description: "enough pods, absolute", + minAvailable: intstr.FromInt(2), + podCount: 2, + shouldDeny: false, + }, { + description: "enough pods, replicaSet, percentage", + minAvailable: intstr.FromString("100%"), + replicaSetSize: 10, + exclusive: false, + shouldDeny: false, + }, { + description: "too few pods, replicaSet, percentage", + minAvailable: intstr.FromString("100%"), + replicaSetSize: 10, + exclusive: true, + shouldDeny: true, + }, + } + for _, c := range evictionCases { + expectation := "should allow an eviction" + if c.shouldDeny { + expectation = "should not allow an eviction" } + It(fmt.Sprintf("evictions: %s => %s", c.description, expectation), func() { + createPodsOrDie(cs, ns, c.podCount) + if c.replicaSetSize > 0 { + createReplicaSetOrDie(cs, ns, c.replicaSetSize, c.exclusive) + } - err = cs.Pods(ns).Evict(e) - Expect(err).NotTo(HaveOccurred()) - }) + if c.minAvailable.String() != "" { + createPodDisruptionBudgetOrDie(cs, ns, c.minAvailable) + } - It("should not allow an eviction when too few pods", func() { - createPodDisruptionBudgetOrDie(cs, ns, intstr.FromInt(2)) + // Locate a running pod. + var pod api.Pod + err := wait.PollImmediate(framework.Poll, schedulingTimeout, func() (bool, error) { + podList, err := cs.Pods(ns).List(apiapi.ListOptions{}) + if err != nil { + return false, err + } - createPodsOrDie(cs, ns, 1) + for i := range podList.Items { + if podList.Items[i].Status.Phase == api.PodRunning { + pod = podList.Items[i] + return true, nil + } + } - pod, err := cs.Pods(ns).Get("pod-0") - Expect(err).NotTo(HaveOccurred()) - - e := &policy.Eviction{ - ObjectMeta: api.ObjectMeta{ - Name: pod.Name, - Namespace: ns, - }, - } - - // Since disruptionAllowed starts out false, wait at least 60s hoping that - // this gives the controller enough time to have truly set the status. - time.Sleep(60 * time.Second) - - err = cs.Pods(ns).Evict(e) - Expect(err).Should(MatchError("Cannot evict pod as it would violate the pod's disruption budget.")) - }) - - It("should allow an eviction when enough pods", func() { - createPodDisruptionBudgetOrDie(cs, ns, intstr.FromInt(2)) - - createPodsOrDie(cs, ns, 2) - - pod, err := cs.Pods(ns).Get("pod-0") - Expect(err).NotTo(HaveOccurred()) - - e := &policy.Eviction{ - ObjectMeta: api.ObjectMeta{ - Name: pod.Name, - Namespace: ns, - }, - } - - // Since disruptionAllowed starts out false, if an eviction is ever allowed, - // that means the controller is working. - err = wait.PollImmediate(framework.Poll, 60*time.Second, func() (bool, error) { - err = cs.Pods(ns).Evict(e) - if err != nil { return false, nil + }) + Expect(err).NotTo(HaveOccurred()) + + e := &policy.Eviction{ + ObjectMeta: api.ObjectMeta{ + Name: pod.Name, + Namespace: ns, + }, + } + + if c.shouldDeny { + // Since disruptionAllowed starts out false, wait at least 60s hoping that + // this gives the controller enough time to have truly set the status. + time.Sleep(timeout) + + err = cs.Pods(ns).Evict(e) + Expect(err).Should(MatchError("Cannot evict pod as it would violate the pod's disruption budget.")) } else { - return true, nil + // Since disruptionAllowed starts out false, if an eviction is ever allowed, + // that means the controller is working. + err = wait.PollImmediate(framework.Poll, timeout, func() (bool, error) { + err = cs.Pods(ns).Evict(e) + if err != nil { + return false, nil + } else { + return true, nil + } + }) + Expect(err).NotTo(HaveOccurred()) } }) - Expect(err).NotTo(HaveOccurred()) - }) + } + }) func createPodDisruptionBudgetOrDie(cs *release_1_4.Clientset, ns string, minAvailable intstr.IntOrString) { @@ -173,3 +217,39 @@ func createPodsOrDie(cs *release_1_4.Clientset, ns string, n int) { framework.ExpectNoError(err, "Creating pod %q in namespace %q", pod.Name, ns) } } + +func createReplicaSetOrDie(cs *release_1_4.Clientset, ns string, size int32, exclusive bool) { + container := api.Container{ + Name: "busybox", + Image: "gcr.io/google_containers/echoserver:1.4", + } + if exclusive { + container.Ports = []api.ContainerPort{ + {HostPort: 5555, ContainerPort: 5555}, + } + } + + rs := &extensions.ReplicaSet{ + ObjectMeta: api.ObjectMeta{ + Name: "rs", + Namespace: ns, + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: &size, + Selector: &extensions.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{container}, + }, + }, + }, + } + + _, err := cs.Extensions().ReplicaSets(ns).Create(rs) + framework.ExpectNoError(err, "Creating replica set %q in namespace %q", rs.Name, ns) +}