diff --git a/test/e2e/apps/deployment.go b/test/e2e/apps/deployment.go index eb7726dbd0d..49949dfcc19 100644 --- a/test/e2e/apps/deployment.go +++ b/test/e2e/apps/deployment.go @@ -21,6 +21,7 @@ import ( "fmt" "math/rand" "time" + "encoding/json" "github.com/davecgh/go-spew/spew" "github.com/onsi/ginkgo" @@ -28,6 +29,10 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/tools/cache" + unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -67,6 +72,7 @@ var ( var _ = SIGDescribe("Deployment", func() { var ns string var c clientset.Interface + var dc dynamic.Interface ginkgo.AfterEach(func() { failureTrap(c, ns) @@ -77,6 +83,7 @@ var _ = SIGDescribe("Deployment", func() { ginkgo.BeforeEach(func() { c = f.ClientSet ns = f.Namespace.Name + dc = f.DynamicClient }) ginkgo.It("deployment reaping should cascade to its replica sets and pods", func() { @@ -134,6 +141,270 @@ var _ = SIGDescribe("Deployment", func() { }) // TODO: add tests that cover deployment.Spec.MinReadySeconds once we solved clock-skew issues // See https://github.com/kubernetes/kubernetes/issues/29229 + + ginkgo.It("should run the lifecycle of a Deployment", func() { + deploymentResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"} + testDeploymentName := "test-deployment" + testDeploymentInitialImage := "nginx" + testDeploymentPatchImage := "alpine" + testDeploymentUpdateImage := "httpd" + testDeploymentDefaultReplicas := int32(3) + testDeploymentMinimumReplicas := int32(1) + testDeploymentNoReplicas := int32(0) + // TODO labels + // TODO flat labels + testDeploymentLabelSelectors := metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test-deployment"}, + } + testNamespaceName := "default" + + fmt.Println("creating a Deployment") + testDeployment := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: testDeploymentName, + Labels: map[string]string{"test-deployment-static": "true"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &testDeploymentDefaultReplicas, + Selector: &testDeploymentLabelSelectors, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: testDeploymentLabelSelectors.MatchLabels, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{ + Name: testDeploymentName, + Image: testDeploymentInitialImage, + }}, + }, + }, + }, + } + _, err := f.ClientSet.AppsV1().Deployments(testNamespaceName).Create(context.TODO(), &testDeployment, metav1.CreateOptions{}) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("watching for the Deployment to be added") + dplmtWatchTimeoutSeconds := int64(180) + dplmtWatch, err := f.ClientSet.AppsV1().Deployments(testNamespaceName).Watch(context.TODO(), metav1.ListOptions{LabelSelector: "test-deployment-static=true", TimeoutSeconds: &dplmtWatchTimeoutSeconds}) + if err != nil { + fmt.Println(err, "Failed to setup watch on newly created Deployment") + return + } + + dplmtWatchChan := dplmtWatch.ResultChan() + for event := range dplmtWatchChan { + if event.Type == watch.Added { + break + } + } + defer func() { + fmt.Println("deleting the Deployment") + err = f.ClientSet.AppsV1().Deployments(testNamespaceName).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: "test-deployment-static=true"}) + if err != nil { + fmt.Println(err) + return + } + for event := range dplmtWatchChan { + deployment, ok := event.Object.(*appsv1.Deployment) + if ok != true { + fmt.Println("unable to convert event.Object type") + return + } + if event.Type == watch.Deleted && deployment.ObjectMeta.Name == testDeploymentName { + break + } + } + }() + fmt.Println("waiting for all Replicas to be Ready") + for event := range dplmtWatchChan { + deployment, ok := event.Object.(*appsv1.Deployment) + if ok != true { + fmt.Println("unable to convert event.Object type") + return + } + if deployment.Status.AvailableReplicas == testDeploymentDefaultReplicas && + deployment.Status.ReadyReplicas == testDeploymentDefaultReplicas { + break + } + } + + fmt.Println("patching the Deployment") + deploymentPatch, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{"test-deployment": "patched"}, + }, + "spec": map[string]interface{}{ + "replicas": testDeploymentMinimumReplicas, + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{{ + "name": testDeploymentName, + "image": testDeploymentPatchImage, + "command": []string{"/bin/sleep", "100000"}, + }}, + }, + }, + }, + }) + if err != nil { + fmt.Println(err, "failed to Marshal Deployment JSON patch") + return + } + _, err = f.ClientSet.AppsV1().Deployments(testNamespaceName).Patch(context.TODO(), testDeploymentName, types.StrategicMergePatchType, []byte(deploymentPatch), metav1.PatchOptions{}) + if err != nil { + fmt.Println(err, "failed to patch Deployment") + return + } + + for event := range dplmtWatchChan { + if event.Type == watch.Modified { + break + } + } + fmt.Println("waiting for Replicas to scale") + for event := range dplmtWatchChan { + deployment, ok := event.Object.(*appsv1.Deployment) + if ok != true { + fmt.Println("unable to convert event.Object type") + return + } + if deployment.Status.AvailableReplicas == testDeploymentMinimumReplicas && + deployment.Status.ReadyReplicas == testDeploymentMinimumReplicas { + break + } + } + + fmt.Println("listing Deployments") + deploymentsList, err := f.ClientSet.AppsV1().Deployments("").List(context.TODO(), metav1.ListOptions{LabelSelector: "test-deployment-static=true"}) + if err != nil { + fmt.Println(err, "failed to list Deployments") + return + } + foundDeployment := false + for _, deploymentItem := range deploymentsList.Items { + if deploymentItem.ObjectMeta.Name == testDeploymentName && + deploymentItem.ObjectMeta.Namespace == testNamespaceName && + deploymentItem.ObjectMeta.Labels["test-deployment-static"] == "true" && + *deploymentItem.Spec.Replicas == testDeploymentMinimumReplicas && + deploymentItem.Spec.Template.Spec.Containers[0].Image == testDeploymentPatchImage { + foundDeployment = true + break + } + } + if foundDeployment != true { + fmt.Println("unable to find the Deployment in list") + return + } + + fmt.Println("updating the DeploymentStatus") + testDeploymentUpdate := testDeployment + testDeploymentUpdate.ObjectMeta.Labels["test-deployment"] = "updated" + testDeploymentUpdate.Spec.Template.Spec.Containers[0].Image = testDeploymentUpdateImage + testDeploymentDefaultReplicasPointer := &testDeploymentDefaultReplicas + testDeploymentUpdate.Spec.Replicas = testDeploymentDefaultReplicasPointer + testDeploymentUpdate.Status.ReadyReplicas = testDeploymentNoReplicas + testDeploymentUpdateUnstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&testDeploymentUpdate) + if err != nil { + fmt.Println(err, "failed to convert to unstructured") + } + testDeploymentUpdateUnstructured := unstructuredv1.Unstructured{ + Object: testDeploymentUpdateUnstructuredMap, + } + // currently this hasn't been able to hit the endpoint replaceAppsV1NamespacedDeploymentStatus + _, err = dc.Resource(deploymentResource).Namespace(testNamespaceName).Update(context.TODO(), &testDeploymentUpdateUnstructured, metav1.UpdateOptions{}) //, "status") + if err != nil { + fmt.Println(err, "failed to update the DeploymentStatus") + return + } + for event := range dplmtWatchChan { + if event.Type == watch.Modified { + break + } + } + + fmt.Println("fetching the DeploymentStatus") + deploymentGetUnstructured, err := dc.Resource(deploymentResource).Namespace(testNamespaceName).Get(context.TODO(), testDeploymentName, metav1.GetOptions{}, "status") + if err != nil { + fmt.Println(err, "failed to fetch the Deployment") + return + } + deploymentGet := appsv1.Deployment{} + err = runtime.DefaultUnstructuredConverter.FromUnstructured(deploymentGetUnstructured.Object, &deploymentGet) + if err != nil { + fmt.Println(err, "failed to convert the unstructured response to a Deployment") + return + } + if !(deploymentGet.Spec.Template.Spec.Containers[0].Image == testDeploymentUpdateImage || deploymentGet.Status.ReadyReplicas == testDeploymentNoReplicas || deploymentGet.ObjectMeta.Labels["test-deployment"] == "updated") { + fmt.Println("failed to update the Deployment (did not return correct values)") + return + } + for event := range dplmtWatchChan { + if event.Type == watch.Modified { + break + } + } + for event := range dplmtWatchChan { + deployment, ok := event.Object.(*appsv1.Deployment) + if ok != true { + fmt.Println("failed to convert event Object to a Deployment") + return + } + if deployment.Status.ReadyReplicas == testDeploymentDefaultReplicas { + break + } + } + + fmt.Println("patching the DeploymentStatus") + deploymentStatusPatch, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{"test-deployment": "patched-status"}, + }, + "status": map[string]interface{}{ + "readyReplicas": testDeploymentNoReplicas, + }, + }) + if err != nil { + fmt.Println(err, "failed to Marshal Deployment JSON patch") + return + } + dc.Resource(deploymentResource).Namespace(testNamespaceName).Patch(context.TODO(), testDeploymentName, types.StrategicMergePatchType, []byte(deploymentStatusPatch), metav1.PatchOptions{}, "status") + + fmt.Println("fetching the DeploymentStatus") + deploymentGetUnstructured, err = dc.Resource(deploymentResource).Namespace(testNamespaceName).Get(context.TODO(), testDeploymentName, metav1.GetOptions{}, "status") + + if err != nil { + fmt.Println(err, "failed to fetch the DeploymentStatus") + return + } + deploymentGet = appsv1.Deployment{} + err = runtime.DefaultUnstructuredConverter.FromUnstructured(deploymentGetUnstructured.Object, &deploymentGet) + if err != nil { + fmt.Println(err, "failed to convert the unstructured response to a Deployment") + return + } + if !(deploymentGet.Spec.Template.Spec.Containers[0].Image == testDeploymentUpdateImage || deploymentGet.Status.ReadyReplicas == 0 || deploymentGet.ObjectMeta.Labels["test-deployment"] == "patched-status") { + fmt.Println("failed to update the Deployment (did not return correct values)") + return + } + for event := range dplmtWatchChan { + if event.Type == watch.Modified { + break + } + } + for event := range dplmtWatchChan { + deployment, ok := event.Object.(*appsv1.Deployment) + if ok != true { + fmt.Println("failed to convert event Object to a Deployment") + return + } + if deployment.Status.ReadyReplicas == testDeploymentDefaultReplicas { + break + } + } + }) }) func failureTrap(c clientset.Interface, ns string) { @@ -372,7 +643,6 @@ func testDeploymentCleanUpPolicy(f *framework.Framework) { w, err := c.CoreV1().Pods(ns).Watch(context.TODO(), options) framework.ExpectNoError(err) go func() { - defer ginkgo.GinkgoRecover() // There should be only one pod being created, which is the pod with the agnhost image. // The old RS shouldn't create new pod when deployment controller adding pod template hash label to its selector. numPodCreation := 1