diff --git a/e2e/deployment.go b/e2e/deployment.go new file mode 100644 index 000000000..32a9fad20 --- /dev/null +++ b/e2e/deployment.go @@ -0,0 +1,126 @@ +/* +Copyright 2021 The Ceph-CSI Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "context" + "fmt" + "time" + + appsv1 "k8s.io/api/apps/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" + "k8s.io/kubernetes/test/e2e/framework" + e2elog "k8s.io/kubernetes/test/e2e/framework/log" +) + +// execCommandInPodWithName run command in pod using podName. +func execCommandInPodWithName( + f *framework.Framework, + cmdString, + podName, + containerName, + nameSpace string) (string, string, error) { + cmd := []string{"/bin/sh", "-c", cmdString} + podOpt := framework.ExecOptions{ + Command: cmd, + PodName: podName, + Namespace: nameSpace, + ContainerName: containerName, + Stdin: nil, + CaptureStdout: true, + CaptureStderr: true, + PreserveWhitespace: true, + } + + return f.ExecWithOptions(podOpt) +} + +// loadAppDeployment loads the deployment app config and return deployment +// object. +func loadAppDeployment(path string) (*appsv1.Deployment, error) { + deploy := appsv1.Deployment{} + if err := unmarshal(path, &deploy); err != nil { + return nil, err + } + + return &deploy, nil +} + +// createDeploymentApp creates the deployment object and waits for it to be in +// Available state. +func createDeploymentApp(clientSet kubernetes.Interface, app *appsv1.Deployment, deployTimeout int) error { + _, err := clientSet.AppsV1().Deployments(app.Namespace).Create(context.TODO(), app, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create deploy: %w", err) + } + + return waitForDeploymentInAvailableState(clientSet, app.Name, app.Namespace, deployTimeout) +} + +// deleteDeploymentApp deletes the deployment object. +func deleteDeploymentApp(clientSet kubernetes.Interface, name, ns string, deployTimeout int) error { + timeout := time.Duration(deployTimeout) * time.Minute + err := clientSet.AppsV1().Deployments(ns).Delete(context.TODO(), name, metav1.DeleteOptions{}) + if err != nil { + return fmt.Errorf("failed to delete deployment: %w", err) + } + start := time.Now() + e2elog.Logf("Waiting for deployment %q to be deleted", name) + + return wait.PollImmediate(poll, timeout, func() (bool, error) { + _, err := clientSet.AppsV1().Deployments(ns).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + if isRetryableAPIError(err) { + return false, nil + } + if apierrs.IsNotFound(err) { + return true, nil + } + e2elog.Logf("%q deployment to be deleted (%d seconds elapsed)", name, int(time.Since(start).Seconds())) + + return false, fmt.Errorf("failed to get deployment: %w", err) + } + + return false, nil + }) +} + +// waitForDeploymentInAvailableState wait for deployment to be in Available state. +func waitForDeploymentInAvailableState(clientSet kubernetes.Interface, name, ns string, deployTimeout int) error { + timeout := time.Duration(deployTimeout) * time.Minute + start := time.Now() + e2elog.Logf("Waiting up to %q to be in Available state", name) + + return wait.PollImmediate(poll, timeout, func() (bool, error) { + d, err := clientSet.AppsV1().Deployments(ns).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + if isRetryableAPIError(err) { + return false, nil + } + e2elog.Logf("%q deployment to be Available (%d seconds elapsed)", name, int(time.Since(start).Seconds())) + + return false, err + } + cond := deploymentutil.GetDeploymentCondition(d.Status, appsv1.DeploymentAvailable) + + return cond != nil, nil + }) +} diff --git a/e2e/utils.go b/e2e/utils.go index 58a13853e..9af1a42a6 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -15,6 +15,7 @@ import ( "time" snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" + appsv1 "k8s.io/api/apps/v1" batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" scv1 "k8s.io/api/storage/v1" @@ -177,6 +178,50 @@ func createPVCAndApp( return err } +// createPVCAndDeploymentApp creates pvc and deployment, if name is not empty +// same will be set as pvc and app name. +func createPVCAndDeploymentApp( + f *framework.Framework, + name string, + pvc *v1.PersistentVolumeClaim, + app *appsv1.Deployment, + pvcTimeout int) error { + if name != "" { + pvc.Name = name + app.Name = name + app.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = name + } + err := createPVCAndvalidatePV(f.ClientSet, pvc, pvcTimeout) + if err != nil { + return err + } + err = createDeploymentApp(f.ClientSet, app, deployTimeout) + + return err +} + +// DeletePVCAndDeploymentApp deletes pvc and deployment, if name is not empty +// same will be set as pvc and app name. +func deletePVCAndDeploymentApp( + f *framework.Framework, + name string, + pvc *v1.PersistentVolumeClaim, + app *appsv1.Deployment) error { + if name != "" { + pvc.Name = name + app.Name = name + app.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = name + } + + err := deleteDeploymentApp(f.ClientSet, app.Name, app.Namespace, deployTimeout) + if err != nil { + return err + } + err = deletePVCAndValidatePV(f.ClientSet, pvc, deployTimeout) + + return err +} + // deletePVCAndApp delete pvc and pod // if name is not empty same will be set as pvc and app name. func deletePVCAndApp(name string, f *framework.Framework, pvc *v1.PersistentVolumeClaim, app *v1.Pod) error {