From ec8972fef3db4736128ab4ee83b2a9c463f826b1 Mon Sep 17 00:00:00 2001 From: Sargun Dhillon Date: Fri, 17 May 2019 16:18:18 -0700 Subject: [PATCH] Use Running Pods Endpoint for testing * Use the VKUBELET API for fetching the pods during testing, which makes it easier to run testing without wiring up an entire cluster * Runningpods does not require each provider to implement the optional stats interface for testing. --- hack/skaffold/virtual-kubelet/pod.yml | 7 + test/e2e/basic_test.go | 316 ++++++++++++++------------ test/e2e/framework/env.go | 20 +- test/e2e/framework/pod.go | 56 ++++- test/e2e/main_test.go | 2 +- 5 files changed, 237 insertions(+), 164 deletions(-) diff --git a/hack/skaffold/virtual-kubelet/pod.yml b/hack/skaffold/virtual-kubelet/pod.yml index 5aaba2342..48e2c66a5 100644 --- a/hack/skaffold/virtual-kubelet/pod.yml +++ b/hack/skaffold/virtual-kubelet/pod.yml @@ -23,6 +23,13 @@ spec: - --klog.logtostderr - --log-level - debug + env: + - name: KUBELET_PORT + value: "10250" + - name: VKUBELET_POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP ports: - name: metrics containerPort: 10255 diff --git a/test/e2e/basic_test.go b/test/e2e/basic_test.go index 3428bb012..6e77b816b 100644 --- a/test/e2e/basic_test.go +++ b/test/e2e/basic_test.go @@ -8,33 +8,35 @@ import ( "time" "github.com/virtual-kubelet/virtual-kubelet/vkubelet" - v1 "k8s.io/api/core/v1" + "gotest.tools/assert" + "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" ) const ( - // deleteGracePeriodForProvider is the amount of time we allow for the provider to react to deletion of a pod before proceeding to assert that the pod has been deleted. - deleteGracePeriodForProvider = 100 * time.Millisecond + // deleteGracePeriodForProvider is the maximum amount of time we allow for the provider to react to deletion of a pod + // before proceeding to assert that the pod has been deleted. + deleteGracePeriodForProvider = 1 * time.Second ) // TestGetStatsSummary creates a pod having two containers and queries the /stats/summary endpoint of the virtual-kubelet. // It expects this endpoint to return stats for the current node, as well as for the aforementioned pod and each of its two containers. func TestGetStatsSummary(t *testing.T) { - // Create a pod with prefix "nginx-0-" having three containers. - pod, err := f.CreatePod(f.CreateDummyPodObjectWithPrefix("nginx-0-", "foo", "bar", "baz")) + // Create a pod with prefix "nginx-" having three containers. + pod, err := f.CreatePod(f.CreateDummyPodObjectWithPrefix(t.Name(), "nginx-", "foo", "bar", "baz")) if err != nil { t.Fatal(err) } // Delete the "nginx-0-X" pod after the test finishes. defer func() { - if err := f.DeletePod(pod.Namespace, pod.Name); err != nil && !apierrors.IsNotFound(err) { + if err := f.DeletePodImmediately(pod.Namespace, pod.Name); err != nil && !apierrors.IsNotFound(err) { t.Error(err) } }() - // Wait for the "nginx-0-X" pod to be reported as running and ready. - if err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil { + // Wait for the "nginx-" pod to be reported as running and ready. + if _, err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil { t.Fatal(err) } @@ -49,13 +51,13 @@ func TestGetStatsSummary(t *testing.T) { t.Fatalf("expected stats for node %s, got stats for node %s", f.NodeName, stats.Node.NodeName) } - // Make sure the "nginx-0-X" pod exists in the slice of PodStats. + // Make sure the "nginx-" pod exists in the slice of PodStats. idx, err := findPodInPodStats(stats, pod) if err != nil { t.Fatal(err) } - // Make sure that we've got stats for all the containers in the "nginx-0-X" pod. + // Make sure that we've got stats for all the containers in the "nginx-" pod. desiredContainerStatsCount := len(pod.Spec.Containers) currentContainerStatsCount := len(stats.Pods[idx].Containers) if currentContainerStatsCount != desiredContainerStatsCount { @@ -63,151 +65,174 @@ func TestGetStatsSummary(t *testing.T) { } } -// TestPodLifecycle creates two pods and verifies that the provider has been asked to create them. -// Then, it deletes one of the pods and verifies that the provider has been asked to delete it. +// TestPodLifecycleGracefulDelete creates a pod and verifies that the provider has been asked to create it. +// Then, it deletes the pods and verifies that the provider has been asked to delete it. // These verifications are made using the /stats/summary endpoint of the virtual-kubelet, by checking for the presence or absence of the pods. // Hence, the provider being tested must implement the PodMetricsProvider interface. -func TestPodLifecycle(t *testing.T) { - // Create a pod with prefix "nginx-0-" having a single container. - pod0, err := f.CreatePod(f.CreateDummyPodObjectWithPrefix("nginx-0-", "foo")) +func TestPodLifecycleGracefulDelete(t *testing.T) { + // Create a pod with prefix "nginx-" having a single container. + podSpec := f.CreateDummyPodObjectWithPrefix(t.Name(), "nginx-", "foo") + podSpec.Spec.NodeName = nodeName + + pod, err := f.CreatePod(podSpec) if err != nil { t.Fatal(err) } - // Delete the "nginx-0-X" pod after the test finishes. + // Delete the pod after the test finishes. defer func() { - if err := f.DeletePod(pod0.Namespace, pod0.Name); err != nil && !apierrors.IsNotFound(err) { + if err := f.DeletePodImmediately(pod.Namespace, pod.Name); err != nil && !apierrors.IsNotFound(err) { t.Error(err) } }() + t.Logf("Created pod: %s", pod.Name) - // Create a pod with prefix "nginx-1-" having a single container. - pod1, err := f.CreatePod(f.CreateDummyPodObjectWithPrefix("nginx-1-", "bar")) - if err != nil { + // Wait for the "nginx-" pod to be reported as running and ready. + if _, err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil { t.Fatal(err) } - // Delete the "nginx-1-Y" pod after the test finishes. - defer func() { - if err := f.DeletePod(pod1.Namespace, pod1.Name); err != nil && !apierrors.IsNotFound(err) { - t.Error(err) - } - }() + t.Logf("Pod %s ready", pod.Name) - // Wait for the "nginx-0-X" pod to be reported as running and ready. - if err := f.WaitUntilPodReady(pod0.Namespace, pod0.Name); err != nil { - t.Fatal(err) - } - // Wait for the "nginx-1-Y" pod to be reported as running and ready. - if err := f.WaitUntilPodReady(pod1.Namespace, pod1.Name); err != nil { - t.Fatal(err) - } + // Grab the pods from the provider. + pods, err := f.GetRunningPods() + assert.NilError(t, err) - // Grab the stats from the provider. - stats, err := f.GetStatsSummary() - if err != nil { - t.Fatal(err) - } + // Check if the pod exists in the slice of PodStats. + assert.NilError(t, findPodInPods(pods, pod)) - // Make sure the "nginx-0-X" pod exists in the slice of PodStats. - if _, err := findPodInPodStats(stats, pod0); err != nil { - t.Fatal(err) - } - - // Make sure the "nginx-1-Y" pod exists in the slice of PodStats. - if _, err := findPodInPodStats(stats, pod1); err != nil { - t.Fatal(err) - } - - // Wait for the "nginx-1-Y" pod to be deleted in a separate goroutine. - // This ensures that we don't possibly miss the MODIFIED/DELETED events due to establishing the watch too late in the process. - pod1Ch := make(chan error) + podCh := make(chan error) + var podLast *v1.Pod go func() { - // Wait for the "nginx-1-Y" pod to be reported as having been marked for deletion. - if err := f.WaitUntilPodDeleted(pod1.Namespace, pod1.Name); err != nil { + // Close the podCh channel, signaling we've observed deletion of the pod. + defer close(podCh) + + var err error + podLast, err = f.WaitUntilPodDeleted(pod.Namespace, pod.Name) + if err != nil { // Propagate the error to the outside so we can fail the test. - pod1Ch <- err - } else { - // Close the pod0Ch channel, signaling we've observed deletion of the pod. - close(pod1Ch) + podCh <- err } }() - // Delete the "nginx-1" pod. - if err := f.DeletePod(pod1.Namespace, pod1.Name); err != nil { + // Gracefully delete the "nginx-" pod. + if err := f.DeletePod(pod.Namespace, pod.Name); err != nil { t.Fatal(err) } + t.Logf("Deleted pod: %s", pod.Name) + // Wait for the delete event to be ACKed. - if err := <-pod1Ch; err != nil { + if err := <-podCh; err != nil { + t.Fatal(err) + } + + time.Sleep(deleteGracePeriodForProvider) + // Give the provider some time to react to the MODIFIED/DELETED events before proceeding. + // Grab the pods from the provider. + pods, err = f.GetRunningPods() + assert.NilError(t, err) + + // Make sure the pod DOES NOT exist in the provider's set of running pods + assert.Assert(t, findPodInPods(pods, pod) != nil) + + // Make sure we saw the delete event, and the delete event was graceful + assert.Assert(t, podLast != nil) + assert.Assert(t, podLast.ObjectMeta.GetDeletionGracePeriodSeconds() != nil) + assert.Assert(t, *podLast.ObjectMeta.GetDeletionGracePeriodSeconds() > 0) +} + +// TestPodLifecycleNonGracefulDelete creates one podsand verifies that the provider has created them +// and put them in the running lifecycle. It then does a force delete on the pod, and verifies the provider +// has deleted it. +func TestPodLifecycleForceDelete(t *testing.T) { + podSpec := f.CreateDummyPodObjectWithPrefix(t.Name(), "nginx-", "foo") + // Create a pod with prefix having a single container. + pod, err := f.CreatePod(podSpec) + if err != nil { + t.Fatal(err) + } + // Delete the pod after the test finishes. + defer func() { + if err := f.DeletePodImmediately(pod.Namespace, pod.Name); err != nil && !apierrors.IsNotFound(err) { + t.Error(err) + } + }() + t.Logf("Created pod: %s", pod.Name) + + // Wait for the "nginx-" pod to be reported as running and ready. + if _, err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil { + t.Fatal(err) + } + t.Logf("Pod %s ready", pod.Name) + + // Grab the pods from the provider. + pods, err := f.GetRunningPods() + assert.NilError(t, err) + + // Check if the pod exists in the slice of Pods. + assert.NilError(t, findPodInPods(pods, pod)) + + // Wait for the pod to be deleted in a separate goroutine. + // This ensures that we don't possibly miss the MODIFIED/DELETED events due to establishing the watch too late in the process. + // It also makes sure that in light of soft deletes, we properly handle non-graceful pod deletion + podCh := make(chan error) + var podLast *v1.Pod + go func() { + // Close the podCh channel, signaling we've observed deletion of the pod. + defer close(podCh) + + var err error + // Wait for the pod to be reported as having been deleted. + podLast, err = f.WaitUntilPodDeleted(pod.Namespace, pod.Name) + if err != nil { + // Propagate the error to the outside so we can fail the test. + podCh <- err + } + }() + + time.Sleep(deleteGracePeriodForProvider) + // Forcibly delete the pod. + if err := f.DeletePodImmediately(pod.Namespace, pod.Name); err != nil { + t.Logf("Last saw pod in state: %+v", podLast) + t.Fatal(err) + } + t.Log("Force deleted pod: ", pod.Name) + + // Wait for the delete event to be ACKed. + if err := <-podCh; err != nil { + t.Logf("Last saw pod in state: %+v", podLast) t.Fatal(err) } // Give the provider some time to react to the MODIFIED/DELETED events before proceeding. time.Sleep(deleteGracePeriodForProvider) - // Grab the stats from the provider. - stats, err = f.GetStatsSummary() - if err != nil { - t.Fatal(err) - } + // Grab the pods from the provider. + pods, err = f.GetRunningPods() + assert.NilError(t, err) - // Make sure the "nginx-1-Y" pod DOES NOT exist in the slice of PodStats anymore. - if _, err := findPodInPodStats(stats, pod1); err == nil { - t.Fatalf("expected to NOT find pod \"%s/%s\" in the slice of pod stats", pod1.Namespace, pod1.Name) - } + // Make sure the "nginx-" pod DOES NOT exist in the slice of Pods anymore. + assert.Assert(t, findPodInPods(pods, pod) != nil) - // Wait for the "nginx-0-X" pod to be deleted in a separate goroutine. - // This ensures that we don't possibly miss the MODIFIED/DELETED events due to establishing the watch too late in the process. - pod0Ch := make(chan error) - go func() { - // Wait for the "nginx-0-X" pod to be reported as having been deleted. - if err := f.WaitUntilPodDeleted(pod0.Namespace, pod0.Name); err != nil { - // Propagate the error to the outside so we can fail the test. - pod0Ch <- err - } else { - // Close the pod0Ch channel, signaling we've observed deletion of the pod. - close(pod0Ch) - } - }() + t.Logf("Pod ended as phase: %+v", podLast.Status.Phase) - // Forcibly delete the "nginx-0" pod. - if err := f.DeletePodImmediately(pod0.Namespace, pod0.Name); err != nil { - t.Fatal(err) - } - // Wait for the delete event to be ACKed. - if err := <-pod0Ch; err != nil { - t.Fatal(err) - } - // Give the provider some time to react to the MODIFIED/DELETED events before proceeding. - time.Sleep(deleteGracePeriodForProvider) - - // Grab the stats from the provider. - stats, err = f.GetStatsSummary() - if err != nil { - t.Fatal(err) - } - - // Make sure the "nginx-0-X" pod DOES NOT exist in the slice of PodStats anymore. - if _, err := findPodInPodStats(stats, pod0); err == nil { - t.Fatalf("expected to NOT find pod \"%s/%s\" in the slice of pod stats", pod0.Namespace, pod0.Name) - } } // TestCreatePodWithOptionalInexistentSecrets tries to create a pod referencing optional, inexistent secrets. // It then verifies that the pod is created successfully. func TestCreatePodWithOptionalInexistentSecrets(t *testing.T) { // Create a pod with a single container referencing optional, inexistent secrets. - pod, err := f.CreatePod(f.CreatePodObjectWithOptionalSecretKey()) + pod, err := f.CreatePod(f.CreatePodObjectWithOptionalSecretKey(t.Name())) if err != nil { t.Fatal(err) } // Delete the pod after the test finishes. defer func() { - if err := f.DeletePod(pod.Namespace, pod.Name); err != nil && !apierrors.IsNotFound(err) { + if err := f.DeletePodImmediately(pod.Namespace, pod.Name); err != nil && !apierrors.IsNotFound(err) { t.Error(err) } }() // Wait for the pod to be reported as running and ready. - if err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil { + if _, err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil { t.Fatal(err) } @@ -216,21 +241,19 @@ func TestCreatePodWithOptionalInexistentSecrets(t *testing.T) { t.Fatal(err) } - // Check that the pod is known to the provider. - stats, err := f.GetStatsSummary() - if err != nil { - t.Fatal(err) - } - if _, err := findPodInPodStats(stats, pod); err != nil { - t.Fatal(err) - } + // Grab the pods from the provider. + pods, err := f.GetRunningPods() + assert.NilError(t, err) + + // Check if the pod exists in the slice of Pods. + assert.NilError(t, findPodInPods(pods, pod)) } // TestCreatePodWithMandatoryInexistentSecrets tries to create a pod referencing inexistent secrets. // It then verifies that the pod is not created. func TestCreatePodWithMandatoryInexistentSecrets(t *testing.T) { // Create a pod with a single container referencing inexistent secrets. - pod, err := f.CreatePod(f.CreatePodObjectWithMandatorySecretKey()) + pod, err := f.CreatePod(f.CreatePodObjectWithMandatorySecretKey(t.Name())) if err != nil { t.Fatal(err) } @@ -247,34 +270,32 @@ func TestCreatePodWithMandatoryInexistentSecrets(t *testing.T) { t.Fatal(err) } - // Check that the pod is NOT known to the provider. - stats, err := f.GetStatsSummary() - if err != nil { - t.Fatal(err) - } - if _, err := findPodInPodStats(stats, pod); err == nil { - t.Fatalf("Expecting to NOT find pod \"%s/%s\" having mandatory, inexistent secrets.", pod.Namespace, pod.Name) - } + // Grab the pods from the provider. + pods, err := f.GetRunningPods() + assert.NilError(t, err) + + // Check if the pod exists in the slice of PodStats. + assert.Assert(t, findPodInPods(pods, pod) != nil) } // TestCreatePodWithOptionalInexistentConfigMap tries to create a pod referencing optional, inexistent config map. // It then verifies that the pod is created successfully. func TestCreatePodWithOptionalInexistentConfigMap(t *testing.T) { // Create a pod with a single container referencing optional, inexistent config map. - pod, err := f.CreatePod(f.CreatePodObjectWithOptionalConfigMapKey()) + pod, err := f.CreatePod(f.CreatePodObjectWithOptionalConfigMapKey(t.Name())) if err != nil { t.Fatal(err) } // Delete the pod after the test finishes. defer func() { - if err := f.DeletePod(pod.Namespace, pod.Name); err != nil && !apierrors.IsNotFound(err) { + if err := f.DeletePodImmediately(pod.Namespace, pod.Name); err != nil && !apierrors.IsNotFound(err) { t.Error(err) } }() // Wait for the pod to be reported as running and ready. - if err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil { + if _, err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil { t.Fatal(err) } @@ -283,21 +304,19 @@ func TestCreatePodWithOptionalInexistentConfigMap(t *testing.T) { t.Fatal(err) } - // Check that the pod is known to the provider. - stats, err := f.GetStatsSummary() - if err != nil { - t.Fatal(err) - } - if _, err := findPodInPodStats(stats, pod); err != nil { - t.Fatal(err) - } + // Grab the pods from the provider. + pods, err := f.GetRunningPods() + assert.NilError(t, err) + + // Check if the pod exists in the slice of PodStats. + assert.NilError(t, findPodInPods(pods, pod)) } // TestCreatePodWithMandatoryInexistentConfigMap tries to create a pod referencing inexistent secrets. // It then verifies that the pod is not created. func TestCreatePodWithMandatoryInexistentConfigMap(t *testing.T) { // Create a pod with a single container referencing inexistent config map. - pod, err := f.CreatePod(f.CreatePodObjectWithMandatoryConfigMapKey()) + pod, err := f.CreatePod(f.CreatePodObjectWithMandatoryConfigMapKey(t.Name())) if err != nil { t.Fatal(err) } @@ -314,14 +333,12 @@ func TestCreatePodWithMandatoryInexistentConfigMap(t *testing.T) { t.Fatal(err) } - // Check that the pod is NOT known to the provider. - stats, err := f.GetStatsSummary() - if err != nil { - t.Fatal(err) - } - if _, err := findPodInPodStats(stats, pod); err == nil { - t.Fatalf("Expecting to NOT find pod \"%s/%s\" having mandatory, inexistent config map.", pod.Namespace, pod.Name) - } + // Grab the pods from the provider. + pods, err := f.GetRunningPods() + assert.NilError(t, err) + + // Check if the pod exists in the slice of PodStats. + assert.Assert(t, findPodInPods(pods, pod) != nil) } // findPodInPodStats returns the index of the specified pod in the .pods field of the specified Summary object. @@ -334,3 +351,14 @@ func findPodInPodStats(summary *v1alpha1.Summary, pod *v1.Pod) (int, error) { } return -1, fmt.Errorf("failed to find pod \"%s/%s\" in the slice of pod stats", pod.Namespace, pod.Name) } + +// findPodInPodStats returns the index of the specified pod in the .pods field of the specified PodList object. +// It returns error if the pod doesn't exist in the podlist +func findPodInPods(pods *v1.PodList, pod *v1.Pod) error { + for _, p := range pods.Items { + if p.Namespace == pod.Namespace && p.Name == pod.Name && string(p.UID) == string(pod.UID) { + return nil + } + } + return fmt.Errorf("failed to find pod \"%s/%s\" in the slice of pod list", pod.Namespace, pod.Name) +} diff --git a/test/e2e/framework/env.go b/test/e2e/framework/env.go index 16a632950..94ab25995 100644 --- a/test/e2e/framework/env.go +++ b/test/e2e/framework/env.go @@ -10,8 +10,8 @@ var ( ) // CreatePodObjectWithMandatoryConfigMapKey creates a pod object that references the "key_0" key from the "config-map-0" config map as mandatory. -func (f *Framework) CreatePodObjectWithMandatoryConfigMapKey() *corev1.Pod { - return f.CreatePodObjectWithEnv([]corev1.EnvVar{ +func (f *Framework) CreatePodObjectWithMandatoryConfigMapKey(testName string) *corev1.Pod { + return f.CreatePodObjectWithEnv(testName, []corev1.EnvVar{ { Name: "CONFIG_MAP_0_KEY_0", ValueFrom: &corev1.EnvVarSource{ @@ -26,8 +26,8 @@ func (f *Framework) CreatePodObjectWithMandatoryConfigMapKey() *corev1.Pod { } // CreatePodObjectWithOptionalConfigMapKey creates a pod object that references the "key_0" key from the "config-map-0" config map as optional. -func (f *Framework) CreatePodObjectWithOptionalConfigMapKey() *corev1.Pod { - return f.CreatePodObjectWithEnv([]corev1.EnvVar{ +func (f *Framework) CreatePodObjectWithOptionalConfigMapKey(testName string) *corev1.Pod { + return f.CreatePodObjectWithEnv(testName, []corev1.EnvVar{ { Name: "CONFIG_MAP_0_KEY_0", ValueFrom: &corev1.EnvVarSource{ @@ -42,8 +42,8 @@ func (f *Framework) CreatePodObjectWithOptionalConfigMapKey() *corev1.Pod { } // CreatePodObjectWithMandatorySecretKey creates a pod object that references the "key_0" key from the "secret-0" config map as mandatory. -func (f *Framework) CreatePodObjectWithMandatorySecretKey() *corev1.Pod { - return f.CreatePodObjectWithEnv([]corev1.EnvVar{ +func (f *Framework) CreatePodObjectWithMandatorySecretKey(testName string) *corev1.Pod { + return f.CreatePodObjectWithEnv(testName, []corev1.EnvVar{ { Name: "SECRET_0_KEY_0", ValueFrom: &corev1.EnvVarSource{ @@ -58,8 +58,8 @@ func (f *Framework) CreatePodObjectWithMandatorySecretKey() *corev1.Pod { } // CreatePodObjectWithOptionalSecretKey creates a pod object that references the "key_0" key from the "secret-0" config map as optional. -func (f *Framework) CreatePodObjectWithOptionalSecretKey() *corev1.Pod { - return f.CreatePodObjectWithEnv([]corev1.EnvVar{ +func (f *Framework) CreatePodObjectWithOptionalSecretKey(testName string) *corev1.Pod { + return f.CreatePodObjectWithEnv(testName, []corev1.EnvVar{ { Name: "SECRET_0_KEY_0", ValueFrom: &corev1.EnvVarSource{ @@ -74,8 +74,8 @@ func (f *Framework) CreatePodObjectWithOptionalSecretKey() *corev1.Pod { } // CreatePodObjectWithEnv creates a pod object whose name starts with "env-test-" and that uses the specified environment configuration for its first container. -func (f *Framework) CreatePodObjectWithEnv(env []corev1.EnvVar) *corev1.Pod { - pod := f.CreateDummyPodObjectWithPrefix("env-test-", "foo") +func (f *Framework) CreatePodObjectWithEnv(testName string, env []corev1.EnvVar) *corev1.Pod { + pod := f.CreateDummyPodObjectWithPrefix(testName, "env-test-", "foo") pod.Spec.Containers[0].Env = env return pod } diff --git a/test/e2e/framework/pod.go b/test/e2e/framework/pod.go index 59bdcc53c..2699761ca 100644 --- a/test/e2e/framework/pod.go +++ b/test/e2e/framework/pod.go @@ -3,6 +3,7 @@ package framework import ( "context" "fmt" + "strings" "time" corev1 "k8s.io/api/core/v1" @@ -21,7 +22,13 @@ const defaultWatchTimeout = 2 * time.Minute // A variable number of strings can be provided. // For each one of these strings, a container that uses the string as its image will be appended to the pod. // This method DOES NOT create the pod in the Kubernetes API. -func (f *Framework) CreateDummyPodObjectWithPrefix(prefix string, images ...string) *corev1.Pod { +func (f *Framework) CreateDummyPodObjectWithPrefix(testName string, prefix string, images ...string) *corev1.Pod { + // Safe the test name + if testName != "" { + testName = strings.Replace(testName, "/", "-", -1) + testName = strings.ToLower(testName) + prefix = prefix + "-" + testName + "-" + } enableServiceLink := false pod := &corev1.Pod{ @@ -66,7 +73,7 @@ func (f *Framework) DeletePodImmediately(namespace, name string) error { // WaitUntilPodCondition establishes a watch on the pod with the specified name and namespace. // Then, it waits for the specified condition function to be verified. -func (f *Framework) WaitUntilPodCondition(namespace, name string, fn watch.ConditionFunc) error { +func (f *Framework) WaitUntilPodCondition(namespace, name string, fn watch.ConditionFunc) (*corev1.Pod, error) { // Create a field selector that matches the specified Pod resource. fs := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.namespace==%s,metadata.name==%s", namespace, name)) // Create a ListWatch so we can receive events for the matched Pod resource. @@ -85,27 +92,41 @@ func (f *Framework) WaitUntilPodCondition(namespace, name string, fn watch.Condi defer cfn() last, err := watch.UntilWithSync(ctx, lw, &corev1.Pod{}, nil, fn) if err != nil { - return err + return nil, err } if last == nil { - return fmt.Errorf("no events received for pod %q", name) + return nil, fmt.Errorf("no events received for pod %q", name) } - return nil + pod := last.Object.(*corev1.Pod) + return pod, nil } // WaitUntilPodReady blocks until the pod with the specified name and namespace is reported to be running and ready. -func (f *Framework) WaitUntilPodReady(namespace, name string) error { +func (f *Framework) WaitUntilPodReady(namespace, name string) (*corev1.Pod, error) { return f.WaitUntilPodCondition(namespace, name, func(event watchapi.Event) (bool, error) { pod := event.Object.(*corev1.Pod) return pod.Status.Phase == corev1.PodRunning && podutil.IsPodReady(pod) && pod.Status.PodIP != "", nil }) } -// WaitUntilPodDeleted blocks until the pod with the specified name and namespace is marked for deletion (or, alternatively, effectively deleted). -func (f *Framework) WaitUntilPodDeleted(namespace, name string) error { +// WaitUntilPodDeleted blocks until the pod with the specified name and namespace is deleted from apiserver. +func (f *Framework) WaitUntilPodDeleted(namespace, name string) (*corev1.Pod, error) { return f.WaitUntilPodCondition(namespace, name, func(event watchapi.Event) (bool, error) { pod := event.Object.(*corev1.Pod) - return event.Type == watchapi.Deleted || pod.DeletionTimestamp != nil, nil + return event.Type == watchapi.Deleted || pod.ObjectMeta.DeletionTimestamp != nil, nil + }) +} + +// WaitUntilPodInPhase blocks until the pod with the specified name and namespace is in one of the specified phases +func (f *Framework) WaitUntilPodInPhase(namespace, name string, phases ...corev1.PodPhase) (*corev1.Pod, error) { + return f.WaitUntilPodCondition(namespace, name, func(event watchapi.Event) (bool, error) { + pod := event.Object.(*corev1.Pod) + for _, p := range phases { + if pod.Status.Phase == p { + return true, nil + } + } + return false, nil }) } @@ -146,3 +167,20 @@ func (f *Framework) WaitUntilPodEventWithReason(pod *corev1.Pod, reason string) } return nil } + +// GetRunningPods gets the running pods from the provider of the virtual kubelet +func (f *Framework) GetRunningPods() (*corev1.PodList, error) { + result := &corev1.PodList{} + + err := f.KubeClient.CoreV1(). + RESTClient(). + Get(). + Resource("nodes"). + Name(f.NodeName). + SubResource("proxy"). + Suffix("runningpods/"). + Do(). + Into(result) + + return result, err +} diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index eacacab51..577497573 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -42,7 +42,7 @@ func TestMain(m *testing.M) { // Create a new instance of the test framework targeting the specified node. f = framework.NewTestingFramework(kubeconfig, namespace, nodeName) // Wait for the virtual-kubelet pod to be ready. - if err := f.WaitUntilPodReady(namespace, nodeName); err != nil { + if _, err := f.WaitUntilPodReady(namespace, nodeName); err != nil { panic(err) } // Run the test suite.