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.
This commit is contained in:
@@ -23,6 +23,13 @@ spec:
|
|||||||
- --klog.logtostderr
|
- --klog.logtostderr
|
||||||
- --log-level
|
- --log-level
|
||||||
- debug
|
- debug
|
||||||
|
env:
|
||||||
|
- name: KUBELET_PORT
|
||||||
|
value: "10250"
|
||||||
|
- name: VKUBELET_POD_IP
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: status.podIP
|
||||||
ports:
|
ports:
|
||||||
- name: metrics
|
- name: metrics
|
||||||
containerPort: 10255
|
containerPort: 10255
|
||||||
|
|||||||
@@ -8,33 +8,35 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/virtual-kubelet/virtual-kubelet/vkubelet"
|
"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"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
"k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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 is the maximum amount of time we allow for the provider to react to deletion of a pod
|
||||||
deleteGracePeriodForProvider = 100 * time.Millisecond
|
// 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.
|
// 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.
|
// 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) {
|
func TestGetStatsSummary(t *testing.T) {
|
||||||
// Create a pod with prefix "nginx-0-" having three containers.
|
// Create a pod with prefix "nginx-" having three containers.
|
||||||
pod, err := f.CreatePod(f.CreateDummyPodObjectWithPrefix("nginx-0-", "foo", "bar", "baz"))
|
pod, err := f.CreatePod(f.CreateDummyPodObjectWithPrefix(t.Name(), "nginx-", "foo", "bar", "baz"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Delete the "nginx-0-X" pod after the test finishes.
|
// Delete the "nginx-0-X" pod after the test finishes.
|
||||||
defer func() {
|
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)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for the "nginx-0-X" pod to be reported as running and ready.
|
// Wait for the "nginx-" 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)
|
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)
|
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)
|
idx, err := findPodInPodStats(stats, pod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
desiredContainerStatsCount := len(pod.Spec.Containers)
|
||||||
currentContainerStatsCount := len(stats.Pods[idx].Containers)
|
currentContainerStatsCount := len(stats.Pods[idx].Containers)
|
||||||
if currentContainerStatsCount != desiredContainerStatsCount {
|
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.
|
// TestPodLifecycleGracefulDelete creates a pod and verifies that the provider has been asked to create it.
|
||||||
// Then, it deletes one of the pods and verifies that the provider has been asked to delete 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.
|
// 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.
|
// Hence, the provider being tested must implement the PodMetricsProvider interface.
|
||||||
func TestPodLifecycle(t *testing.T) {
|
func TestPodLifecycleGracefulDelete(t *testing.T) {
|
||||||
// Create a pod with prefix "nginx-0-" having a single container.
|
// Create a pod with prefix "nginx-" having a single container.
|
||||||
pod0, err := f.CreatePod(f.CreateDummyPodObjectWithPrefix("nginx-0-", "foo"))
|
podSpec := f.CreateDummyPodObjectWithPrefix(t.Name(), "nginx-", "foo")
|
||||||
|
podSpec.Spec.NodeName = nodeName
|
||||||
|
|
||||||
|
pod, err := f.CreatePod(podSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Delete the "nginx-0-X" pod after the test finishes.
|
// Delete the pod after the test finishes.
|
||||||
defer func() {
|
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.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
t.Logf("Created pod: %s", pod.Name)
|
||||||
|
|
||||||
// Create a pod with prefix "nginx-1-" having a single container.
|
// Wait for the "nginx-" pod to be reported as running and ready.
|
||||||
pod1, err := f.CreatePod(f.CreateDummyPodObjectWithPrefix("nginx-1-", "bar"))
|
if _, err := f.WaitUntilPodReady(pod.Namespace, pod.Name); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Delete the "nginx-1-Y" pod after the test finishes.
|
t.Logf("Pod %s ready", pod.Name)
|
||||||
defer func() {
|
|
||||||
if err := f.DeletePod(pod1.Namespace, pod1.Name); err != nil && !apierrors.IsNotFound(err) {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for the "nginx-0-X" pod to be reported as running and ready.
|
// Grab the pods from the provider.
|
||||||
if err := f.WaitUntilPodReady(pod0.Namespace, pod0.Name); err != nil {
|
pods, err := f.GetRunningPods()
|
||||||
t.Fatal(err)
|
assert.NilError(t, 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 stats from the provider.
|
// Check if the pod exists in the slice of PodStats.
|
||||||
stats, err := f.GetStatsSummary()
|
assert.NilError(t, findPodInPods(pods, pod))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the "nginx-0-X" pod exists in the slice of PodStats.
|
podCh := make(chan error)
|
||||||
if _, err := findPodInPodStats(stats, pod0); err != nil {
|
var podLast *v1.Pod
|
||||||
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)
|
|
||||||
go func() {
|
go func() {
|
||||||
// Wait for the "nginx-1-Y" pod to be reported as having been marked for deletion.
|
// Close the podCh channel, signaling we've observed deletion of the pod.
|
||||||
if err := f.WaitUntilPodDeleted(pod1.Namespace, pod1.Name); err != nil {
|
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.
|
// Propagate the error to the outside so we can fail the test.
|
||||||
pod1Ch <- err
|
podCh <- err
|
||||||
} else {
|
|
||||||
// Close the pod0Ch channel, signaling we've observed deletion of the pod.
|
|
||||||
close(pod1Ch)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Delete the "nginx-1" pod.
|
// Gracefully delete the "nginx-" pod.
|
||||||
if err := f.DeletePod(pod1.Namespace, pod1.Name); err != nil {
|
if err := f.DeletePod(pod.Namespace, pod.Name); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
t.Logf("Deleted pod: %s", pod.Name)
|
||||||
|
|
||||||
// Wait for the delete event to be ACKed.
|
// 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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Give the provider some time to react to the MODIFIED/DELETED events before proceeding.
|
// Give the provider some time to react to the MODIFIED/DELETED events before proceeding.
|
||||||
time.Sleep(deleteGracePeriodForProvider)
|
time.Sleep(deleteGracePeriodForProvider)
|
||||||
|
|
||||||
// Grab the stats from the provider.
|
// Grab the pods from the provider.
|
||||||
stats, err = f.GetStatsSummary()
|
pods, err = f.GetRunningPods()
|
||||||
if err != nil {
|
assert.NilError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the "nginx-1-Y" pod DOES NOT exist in the slice of PodStats anymore.
|
// Make sure the "nginx-" pod DOES NOT exist in the slice of Pods anymore.
|
||||||
if _, err := findPodInPodStats(stats, pod1); err == nil {
|
assert.Assert(t, findPodInPods(pods, pod) != nil)
|
||||||
t.Fatalf("expected to NOT find pod \"%s/%s\" in the slice of pod stats", pod1.Namespace, pod1.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the "nginx-0-X" pod to be deleted in a separate goroutine.
|
t.Logf("Pod ended as phase: %+v", podLast.Status.Phase)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 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.
|
// TestCreatePodWithOptionalInexistentSecrets tries to create a pod referencing optional, inexistent secrets.
|
||||||
// It then verifies that the pod is created successfully.
|
// It then verifies that the pod is created successfully.
|
||||||
func TestCreatePodWithOptionalInexistentSecrets(t *testing.T) {
|
func TestCreatePodWithOptionalInexistentSecrets(t *testing.T) {
|
||||||
// Create a pod with a single container referencing optional, inexistent secrets.
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the pod after the test finishes.
|
// Delete the pod after the test finishes.
|
||||||
defer func() {
|
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)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for the pod to be reported as running and ready.
|
// 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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,21 +241,19 @@ func TestCreatePodWithOptionalInexistentSecrets(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the pod is known to the provider.
|
// Grab the pods from the provider.
|
||||||
stats, err := f.GetStatsSummary()
|
pods, err := f.GetRunningPods()
|
||||||
if err != nil {
|
assert.NilError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
// Check if the pod exists in the slice of Pods.
|
||||||
if _, err := findPodInPodStats(stats, pod); err != nil {
|
assert.NilError(t, findPodInPods(pods, pod))
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCreatePodWithMandatoryInexistentSecrets tries to create a pod referencing inexistent secrets.
|
// TestCreatePodWithMandatoryInexistentSecrets tries to create a pod referencing inexistent secrets.
|
||||||
// It then verifies that the pod is not created.
|
// It then verifies that the pod is not created.
|
||||||
func TestCreatePodWithMandatoryInexistentSecrets(t *testing.T) {
|
func TestCreatePodWithMandatoryInexistentSecrets(t *testing.T) {
|
||||||
// Create a pod with a single container referencing inexistent secrets.
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -247,34 +270,32 @@ func TestCreatePodWithMandatoryInexistentSecrets(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the pod is NOT known to the provider.
|
// Grab the pods from the provider.
|
||||||
stats, err := f.GetStatsSummary()
|
pods, err := f.GetRunningPods()
|
||||||
if err != nil {
|
assert.NilError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
// Check if the pod exists in the slice of PodStats.
|
||||||
if _, err := findPodInPodStats(stats, pod); err == nil {
|
assert.Assert(t, findPodInPods(pods, pod) != nil)
|
||||||
t.Fatalf("Expecting to NOT find pod \"%s/%s\" having mandatory, inexistent secrets.", pod.Namespace, pod.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCreatePodWithOptionalInexistentConfigMap tries to create a pod referencing optional, inexistent config map.
|
// TestCreatePodWithOptionalInexistentConfigMap tries to create a pod referencing optional, inexistent config map.
|
||||||
// It then verifies that the pod is created successfully.
|
// It then verifies that the pod is created successfully.
|
||||||
func TestCreatePodWithOptionalInexistentConfigMap(t *testing.T) {
|
func TestCreatePodWithOptionalInexistentConfigMap(t *testing.T) {
|
||||||
// Create a pod with a single container referencing optional, inexistent config map.
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the pod after the test finishes.
|
// Delete the pod after the test finishes.
|
||||||
defer func() {
|
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)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for the pod to be reported as running and ready.
|
// 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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,21 +304,19 @@ func TestCreatePodWithOptionalInexistentConfigMap(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the pod is known to the provider.
|
// Grab the pods from the provider.
|
||||||
stats, err := f.GetStatsSummary()
|
pods, err := f.GetRunningPods()
|
||||||
if err != nil {
|
assert.NilError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
// Check if the pod exists in the slice of PodStats.
|
||||||
if _, err := findPodInPodStats(stats, pod); err != nil {
|
assert.NilError(t, findPodInPods(pods, pod))
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCreatePodWithMandatoryInexistentConfigMap tries to create a pod referencing inexistent secrets.
|
// TestCreatePodWithMandatoryInexistentConfigMap tries to create a pod referencing inexistent secrets.
|
||||||
// It then verifies that the pod is not created.
|
// It then verifies that the pod is not created.
|
||||||
func TestCreatePodWithMandatoryInexistentConfigMap(t *testing.T) {
|
func TestCreatePodWithMandatoryInexistentConfigMap(t *testing.T) {
|
||||||
// Create a pod with a single container referencing inexistent config map.
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -314,14 +333,12 @@ func TestCreatePodWithMandatoryInexistentConfigMap(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the pod is NOT known to the provider.
|
// Grab the pods from the provider.
|
||||||
stats, err := f.GetStatsSummary()
|
pods, err := f.GetRunningPods()
|
||||||
if err != nil {
|
assert.NilError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
// Check if the pod exists in the slice of PodStats.
|
||||||
if _, err := findPodInPodStats(stats, pod); err == nil {
|
assert.Assert(t, findPodInPods(pods, pod) != nil)
|
||||||
t.Fatalf("Expecting to NOT find pod \"%s/%s\" having mandatory, inexistent config map.", pod.Namespace, pod.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findPodInPodStats returns the index of the specified pod in the .pods field of the specified Summary object.
|
// 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)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
// 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 {
|
func (f *Framework) CreatePodObjectWithMandatoryConfigMapKey(testName string) *corev1.Pod {
|
||||||
return f.CreatePodObjectWithEnv([]corev1.EnvVar{
|
return f.CreatePodObjectWithEnv(testName, []corev1.EnvVar{
|
||||||
{
|
{
|
||||||
Name: "CONFIG_MAP_0_KEY_0",
|
Name: "CONFIG_MAP_0_KEY_0",
|
||||||
ValueFrom: &corev1.EnvVarSource{
|
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.
|
// 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 {
|
func (f *Framework) CreatePodObjectWithOptionalConfigMapKey(testName string) *corev1.Pod {
|
||||||
return f.CreatePodObjectWithEnv([]corev1.EnvVar{
|
return f.CreatePodObjectWithEnv(testName, []corev1.EnvVar{
|
||||||
{
|
{
|
||||||
Name: "CONFIG_MAP_0_KEY_0",
|
Name: "CONFIG_MAP_0_KEY_0",
|
||||||
ValueFrom: &corev1.EnvVarSource{
|
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.
|
// 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 {
|
func (f *Framework) CreatePodObjectWithMandatorySecretKey(testName string) *corev1.Pod {
|
||||||
return f.CreatePodObjectWithEnv([]corev1.EnvVar{
|
return f.CreatePodObjectWithEnv(testName, []corev1.EnvVar{
|
||||||
{
|
{
|
||||||
Name: "SECRET_0_KEY_0",
|
Name: "SECRET_0_KEY_0",
|
||||||
ValueFrom: &corev1.EnvVarSource{
|
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.
|
// 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 {
|
func (f *Framework) CreatePodObjectWithOptionalSecretKey(testName string) *corev1.Pod {
|
||||||
return f.CreatePodObjectWithEnv([]corev1.EnvVar{
|
return f.CreatePodObjectWithEnv(testName, []corev1.EnvVar{
|
||||||
{
|
{
|
||||||
Name: "SECRET_0_KEY_0",
|
Name: "SECRET_0_KEY_0",
|
||||||
ValueFrom: &corev1.EnvVarSource{
|
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.
|
// 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 {
|
func (f *Framework) CreatePodObjectWithEnv(testName string, env []corev1.EnvVar) *corev1.Pod {
|
||||||
pod := f.CreateDummyPodObjectWithPrefix("env-test-", "foo")
|
pod := f.CreateDummyPodObjectWithPrefix(testName, "env-test-", "foo")
|
||||||
pod.Spec.Containers[0].Env = env
|
pod.Spec.Containers[0].Env = env
|
||||||
return pod
|
return pod
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package framework
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -21,7 +22,13 @@ const defaultWatchTimeout = 2 * time.Minute
|
|||||||
// A variable number of strings can be provided.
|
// 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.
|
// 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.
|
// 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
|
enableServiceLink := false
|
||||||
|
|
||||||
pod := &corev1.Pod{
|
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.
|
// WaitUntilPodCondition establishes a watch on the pod with the specified name and namespace.
|
||||||
// Then, it waits for the specified condition function to be verified.
|
// 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.
|
// Create a field selector that matches the specified Pod resource.
|
||||||
fs := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.namespace==%s,metadata.name==%s", namespace, name))
|
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.
|
// 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()
|
defer cfn()
|
||||||
last, err := watch.UntilWithSync(ctx, lw, &corev1.Pod{}, nil, fn)
|
last, err := watch.UntilWithSync(ctx, lw, &corev1.Pod{}, nil, fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if last == nil {
|
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.
|
// 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) {
|
return f.WaitUntilPodCondition(namespace, name, func(event watchapi.Event) (bool, error) {
|
||||||
pod := event.Object.(*corev1.Pod)
|
pod := event.Object.(*corev1.Pod)
|
||||||
return pod.Status.Phase == corev1.PodRunning && podutil.IsPodReady(pod) && pod.Status.PodIP != "", nil
|
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).
|
// WaitUntilPodDeleted blocks until the pod with the specified name and namespace is deleted from apiserver.
|
||||||
func (f *Framework) WaitUntilPodDeleted(namespace, name string) error {
|
func (f *Framework) WaitUntilPodDeleted(namespace, name string) (*corev1.Pod, error) {
|
||||||
return f.WaitUntilPodCondition(namespace, name, func(event watchapi.Event) (bool, error) {
|
return f.WaitUntilPodCondition(namespace, name, func(event watchapi.Event) (bool, error) {
|
||||||
pod := event.Object.(*corev1.Pod)
|
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
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func TestMain(m *testing.M) {
|
|||||||
// Create a new instance of the test framework targeting the specified node.
|
// Create a new instance of the test framework targeting the specified node.
|
||||||
f = framework.NewTestingFramework(kubeconfig, namespace, nodeName)
|
f = framework.NewTestingFramework(kubeconfig, namespace, nodeName)
|
||||||
// Wait for the virtual-kubelet pod to be ready.
|
// 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)
|
panic(err)
|
||||||
}
|
}
|
||||||
// Run the test suite.
|
// Run the test suite.
|
||||||
|
|||||||
Reference in New Issue
Block a user