diff --git a/test/extended/cli/idle.go b/test/extended/cli/idle.go index d0be5686cb65..ddcd64670f70 100644 --- a/test/extended/cli/idle.go +++ b/test/extended/cli/idle.go @@ -9,6 +9,7 @@ import ( g "github.com/onsi/ginkgo/v2" o "github.com/onsi/gomega" + discoveryv1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" @@ -20,9 +21,25 @@ import ( const ( idledAnnotation = "idling.alpha.openshift.io/idled-at" prevScaleAnnotation = "idling.alpha.openshift.io/previous-scale" - scaledReplicaCount = "2" + scaledReplicaCount = 2 ) +func readyPodEndpointCount(endpointSlices []discoveryv1.EndpointSlice) int { + readyPods := map[string]struct{}{} + for _, endpointSlice := range endpointSlices { + for _, endpoint := range endpointSlice.Endpoints { + if endpoint.TargetRef == nil || endpoint.TargetRef.Kind != "Pod" { + continue + } + if endpoint.Conditions.Ready == nil || *endpoint.Conditions.Ready { + key := fmt.Sprintf("%s/%s/%s", endpoint.TargetRef.Namespace, endpoint.TargetRef.Name, endpoint.TargetRef.UID) + readyPods[key] = struct{}{} + } + } + } + return len(readyPods) +} + var _ = g.Describe("[sig-cli] oc idle [apigroup:apps.openshift.io][apigroup:route.openshift.io][apigroup:project.openshift.io][apigroup:image.openshift.io]", func() { defer g.GinkgoRecover() @@ -51,24 +68,13 @@ var _ = g.Describe("[sig-cli] oc idle [apigroup:apps.openshift.io][apigroup:rout o.Expect(dcList.Items).Should(o.HaveLen(1)) deploymentConfigName = dcList.Items[0].Name - expectedOutput = fmt.Sprintf("The service will unidle DeploymentConfig \"%s/%s\" to %s replicas once it receives traffic", projectName, deploymentConfigName, scaledReplicaCount) + expectedOutput = fmt.Sprintf("The service will unidle DeploymentConfig \"%s/%s\" to %d replicas once it receives traffic", projectName, deploymentConfigName, scaledReplicaCount) err = oc.Run("describe").Args("deploymentconfigs", deploymentConfigName).Execute() o.Expect(err).NotTo(o.HaveOccurred()) ctx := context.Background() - g.By("wait until idling-echo endpoint is ready") - err = wait.PollUntilContextTimeout(ctx, time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) { - err = oc.Run("describe").Args("endpointslices", "-l", "kubernetes.io/service-name=idling-echo").Execute() - if err != nil { - return false, nil - } - - return true, nil - }) - o.Expect(err).NotTo(o.HaveOccurred()) - - g.By("wait until replicationcontroller is ready") + g.By("wait until replicationcontroller exists") err = wait.PollUntilContextTimeout(ctx, time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) { err = oc.Run("get").Args("replicationcontroller", fmt.Sprintf("%s-1", deploymentConfigName)).Execute() if err != nil { @@ -79,18 +85,18 @@ var _ = g.Describe("[sig-cli] oc idle [apigroup:apps.openshift.io][apigroup:rout }) o.Expect(err).NotTo(o.HaveOccurred()) - g.By(fmt.Sprintf("scale deploymentconfig to %s replicas", scaledReplicaCount)) - err = oc.Run("scale").Args("replicationcontroller", fmt.Sprintf("%s-1", deploymentConfigName), fmt.Sprintf("--replicas=%s", scaledReplicaCount)).Execute() + g.By(fmt.Sprintf("scale deploymentconfig to %d replicas", scaledReplicaCount)) + err = oc.Run("scale").Args("replicationcontroller", fmt.Sprintf("%s-1", deploymentConfigName), fmt.Sprintf("--replicas=%d", scaledReplicaCount)).Execute() o.Expect(err).NotTo(o.HaveOccurred()) - g.By(fmt.Sprintf("wait until pod is scaled to %s", scaledReplicaCount)) + g.By(fmt.Sprintf("wait until pod is scaled to %d", scaledReplicaCount)) err = wait.PollUntilContextTimeout(ctx, time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) { out, err := oc.Run("get").Args("pods", "-l", "app=idling-echo", "--template={{ len .items }}", "--output=go-template").Output() if err != nil { return false, err } - if out != scaledReplicaCount { + if out != fmt.Sprint(scaledReplicaCount) { return false, nil } @@ -98,14 +104,21 @@ var _ = g.Describe("[sig-cli] oc idle [apigroup:apps.openshift.io][apigroup:rout }) o.Expect(err).NotTo(o.HaveOccurred()) - g.By(fmt.Sprintf("wait until endpoint addresses are scaled to %s", scaledReplicaCount)) + g.By(fmt.Sprintf("wait until %d ready pod-backed endpoints are available", scaledReplicaCount)) err = wait.PollUntilContextTimeout(ctx, time.Second, 5*time.Minute, true, func(ctx context.Context) (done bool, err error) { - out, err := oc.Run("get").Args("endpointslices", "-l", "kubernetes.io/service-name=idling-echo", "--template={{ len (index .items 0).endpoints }}", "--output=go-template").Output() - if err != nil || out != scaledReplicaCount { + endpointSlices, err := oc.KubeClient().DiscoveryV1().EndpointSlices(projectName).List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, "idling-echo")}) + if err != nil { + klog.Info("Failed to list EndpointSlices for the idling test; retrying...") return false, nil } - return true, nil + rc, err := oc.KubeClient().CoreV1().ReplicationControllers(projectName).Get(ctx, fmt.Sprintf("%s-1", deploymentConfigName), metav1.GetOptions{}) + if err != nil { + klog.Info("Failed to get the ReplicationController for the idling test; retrying...") + return false, nil + } + + return rc.Status.ReadyReplicas == int32(scaledReplicaCount) && readyPodEndpointCount(endpointSlices.Items) == scaledReplicaCount, nil }) o.Expect(err).NotTo(o.HaveOccurred()) }) @@ -154,7 +167,7 @@ var _ = g.Describe("[sig-cli] oc idle [apigroup:apps.openshift.io][apigroup:rout dcObj, err := oc.AdminAppsClient().AppsV1().DeploymentConfigs(projectName).Get(context.TODO(), deploymentConfigName, metav1.GetOptions{}) o.Expect(err).NotTo(o.HaveOccurred()) out = dcObj.Annotations[prevScaleAnnotation] - o.Expect(out).To(o.Equal(scaledReplicaCount)) + o.Expect(out).To(o.Equal(fmt.Sprint(scaledReplicaCount))) }) }) @@ -186,24 +199,13 @@ var _ = g.Describe("[sig-cli] oc idle Deployments [apigroup:route.openshift.io][ o.Expect(dcList.Items).Should(o.HaveLen(1)) deploymentName = dcList.Items[0].Name - expectedOutput = fmt.Sprintf("The service will unidle Deployment \"%s/%s\" to %s replicas once it receives traffic", projectName, deploymentName, scaledReplicaCount) + expectedOutput = fmt.Sprintf("The service will unidle Deployment \"%s/%s\" to %d replicas once it receives traffic", projectName, deploymentName, scaledReplicaCount) err = oc.Run("describe").Args("deployments", deploymentName).Execute() o.Expect(err).NotTo(o.HaveOccurred()) ctx := context.Background() - g.By("wait until idling-echo endpoint is ready") - err = wait.PollUntilContextTimeout(ctx, time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) { - err = oc.Run("describe").Args("endpointslices", "-l", "kubernetes.io/service-name=idling-echo").Execute() - if err != nil { - return false, nil - } - - return true, nil - }) - o.Expect(err).NotTo(o.HaveOccurred()) - - g.By("wait until replicaset is ready") + g.By("wait until replicaset exists") var rsName string err = wait.PollUntilContextTimeout(ctx, time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) { rsList, err := oc.AdminKubeClient().AppsV1().ReplicaSets(projectName).List(context.TODO(), metav1.ListOptions{LabelSelector: "app=idling-echo,deployment=idling-echo"}) @@ -222,18 +224,18 @@ var _ = g.Describe("[sig-cli] oc idle Deployments [apigroup:route.openshift.io][ }) o.Expect(err).NotTo(o.HaveOccurred()) - g.By(fmt.Sprintf("scale deployment to %s replicas", scaledReplicaCount)) - err = oc.Run("scale").Args("replicaset", rsName, fmt.Sprintf("--replicas=%s", scaledReplicaCount)).Execute() + g.By(fmt.Sprintf("scale deployment to %d replicas", scaledReplicaCount)) + err = oc.Run("scale").Args("replicaset", rsName, fmt.Sprintf("--replicas=%d", scaledReplicaCount)).Execute() o.Expect(err).NotTo(o.HaveOccurred()) - g.By(fmt.Sprintf("wait until pod is scaled to %s", scaledReplicaCount)) + g.By(fmt.Sprintf("wait until pod is scaled to %d", scaledReplicaCount)) err = wait.PollUntilContextTimeout(ctx, time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) { out, err := oc.Run("get").Args("pods", "-l", "app=idling-echo", "--template={{ len .items }}", "--output=go-template").Output() if err != nil { return false, err } - if out != scaledReplicaCount { + if out != fmt.Sprint(scaledReplicaCount) { return false, nil } @@ -241,14 +243,21 @@ var _ = g.Describe("[sig-cli] oc idle Deployments [apigroup:route.openshift.io][ }) o.Expect(err).NotTo(o.HaveOccurred()) - g.By(fmt.Sprintf("wait until endpoint addresses are scaled to %s", scaledReplicaCount)) + g.By(fmt.Sprintf("wait until %d ready pod-backed endpoints are available", scaledReplicaCount)) err = wait.PollUntilContextTimeout(ctx, time.Second, 5*time.Minute, true, func(ctx context.Context) (done bool, err error) { - out, err := oc.Run("get").Args("endpointslices", "-l", "kubernetes.io/service-name=idling-echo", "--template={{ len (index .items 0).endpoints }}", "--output=go-template").Output() - if err != nil || out != scaledReplicaCount { + endpointSlices, err := oc.KubeClient().DiscoveryV1().EndpointSlices(projectName).List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, "idling-echo")}) + if err != nil { + klog.Info("Failed to list EndpointSlices for the idling test; retrying...") return false, nil } - return true, nil + rs, err := oc.KubeClient().AppsV1().ReplicaSets(projectName).Get(ctx, rsName, metav1.GetOptions{}) + if err != nil { + klog.Info("Failed to get the ReplicaSet for the idling test; retrying...") + return false, nil + } + + return rs.Status.ReadyReplicas == int32(scaledReplicaCount) && readyPodEndpointCount(endpointSlices.Items) == scaledReplicaCount, nil }) o.Expect(err).NotTo(o.HaveOccurred()) }) diff --git a/test/extended/cli/idle_test.go b/test/extended/cli/idle_test.go new file mode 100644 index 000000000000..8916e136f219 --- /dev/null +++ b/test/extended/cli/idle_test.go @@ -0,0 +1,79 @@ +package cli + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + "k8s.io/utils/ptr" +) + +func TestReadyPodEndpointCount(t *testing.T) { + tests := []struct { + name string + endpointSlices []discoveryv1.EndpointSlice + expected int + }{ + { + name: "entries for unready pods do not count", + endpointSlices: []discoveryv1.EndpointSlice{{ + Endpoints: []discoveryv1.Endpoint{ + {TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "pod-1"}, Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(false)}}, + {TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "pod-2"}, Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(false)}}, + }, + }}, + expected: 0, + }, + { + name: "ready and unset-ready pod endpoints count", + endpointSlices: []discoveryv1.EndpointSlice{ + { + Endpoints: []discoveryv1.Endpoint{ + {TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "pod-1"}, Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)}}, + {TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "pod-2"}}, + }, + }, + { + Endpoints: []discoveryv1.Endpoint{ + {TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "pod-3"}, Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(false)}}, + }, + }, + }, + expected: 2, + }, + { + name: "the same ready pod in multiple slices counts once", + endpointSlices: []discoveryv1.EndpointSlice{ + { + Endpoints: []discoveryv1.Endpoint{ + {TargetRef: &corev1.ObjectReference{Kind: "Pod", Namespace: "test", Name: "pod-1", UID: "pod-uid"}, Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)}}, + }, + }, + { + Endpoints: []discoveryv1.Endpoint{ + {TargetRef: &corev1.ObjectReference{Kind: "Pod", Namespace: "test", Name: "pod-1", UID: "pod-uid"}, Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)}}, + }, + }, + }, + expected: 1, + }, + { + name: "ready endpoints without pod references do not count", + endpointSlices: []discoveryv1.EndpointSlice{{ + Endpoints: []discoveryv1.Endpoint{ + {Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)}}, + {TargetRef: &corev1.ObjectReference{Kind: "Node", Name: "node-1"}, Conditions: discoveryv1.EndpointConditions{Ready: ptr.To(true)}}, + }, + }}, + expected: 0, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if actual := readyPodEndpointCount(test.endpointSlices); actual != test.expected { + t.Fatalf("readyPodEndpointCount() = %d, want %d", actual, test.expected) + } + }) + } +}