mirror of
https://github.com/kubevirt/containerized-data-importer.git
synced 2025-06-03 06:30:22 +00:00

* Validate images fit on a filesystem in more cases. Background: When the backing store is a filesystem, we store the images as sparse files. So the file may eventually grow to be bigger than the available storage. This will cause unfortunate failures down the line. Prior to this commit, we validated the size: - In case the backing store implicitly did it for us (block volumes) - On async upload - When resizing (by the operation failing if the image cannot fit in the available space). The Resize phase is encountered quite commonly: Transfer->Convert->Resize TransferFile->Resize Adding validation here for the non-resize case covers almost all the cases. The only exceptions that aren't validated now are: - DataVolumeArchive via the HTTP datasource - VDDK Signed-off-by: Maya Rashish <mrashish@redhat.com> * When resizing, take into account filesystem overhead. Signed-off-by: Maya Rashish <mrashish@redhat.com> * Add testing for too large upload/import - Import/sync upload of too large physical size image (raw.xz, qcow2) - Import/sync upload of too large virtual size image (raw.xz, qcow2) - Import of a too large raw image file, if filesystem overhead is taken into account - Async upload of too large physical size qcow2. The async upload cases do not mirror the sync upload ones because if a block device is used as scratch space, it will hit a size limit before the validation pause, and fail differently. This scenario is identical to the sync upload case which was added. Signed-off-by: Maya Rashish <mrashish@redhat.com> * Refactor code in a way that requires less comments to explain. We can just validate that the requested image size will fit in the available space, and not rely on the fact we typically resize the images to the full size. Signed-off-by: Maya Rashish <mrashish@redhat.com> * When calculating usable space, round down to a multiple of 512. Our validation is indirectly: image after resize to usable space <= usable space For this to pass, we need to ensure that qemu-img's rounding up to 512 doesn't change the size. Signed-off-by: Maya Rashish <mrashish@redhat.com> * Adjust qemu-img to the ones emitted by nbdkit: - In some cases, we clearly don't rely on the qemu-img error, so don't check for it. - In one case, switch it to looking for the nbdkit equivalent error message. Signed-off-by: Maya Rashish <mrashish@redhat.com>
1196 lines
44 KiB
Go
1196 lines
44 KiB
Go
package tests_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/ginkgo/extensions/table"
|
|
. "github.com/onsi/gomega"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
cdiv1 "kubevirt.io/containerized-data-importer/pkg/apis/core/v1beta1"
|
|
"kubevirt.io/containerized-data-importer/pkg/common"
|
|
"kubevirt.io/containerized-data-importer/pkg/controller"
|
|
"kubevirt.io/containerized-data-importer/tests"
|
|
"kubevirt.io/containerized-data-importer/tests/framework"
|
|
"kubevirt.io/containerized-data-importer/tests/utils"
|
|
)
|
|
|
|
const (
|
|
syncUploadPath = "/v1beta1/upload"
|
|
asyncUploadPath = "/v1beta1/upload-async"
|
|
|
|
syncFormPath = "/v1beta1/upload-form"
|
|
asyncFormPath = "/v1beta1/upload-form-async"
|
|
|
|
alphaSyncUploadPath = "/v1alpha1/upload"
|
|
alphaAsyncUploadPath = "/v1alpha1/upload-async"
|
|
)
|
|
|
|
type uploadFunc func(string, string, int) error
|
|
|
|
type uploadFileNameRequestCreator func(string, string) (*http.Request, error)
|
|
|
|
var _ = Describe("[rfe_id:138][crit:high][vendor:cnv-qe@redhat.com][level:component]Upload tests", func() {
|
|
|
|
var (
|
|
pvc *v1.PersistentVolumeClaim
|
|
err error
|
|
|
|
uploadProxyURL string
|
|
portForwardCmd *exec.Cmd
|
|
)
|
|
|
|
f := framework.NewFramework("upload-func-test")
|
|
|
|
BeforeEach(func() {
|
|
if pvc != nil {
|
|
Eventually(func() bool {
|
|
// Make sure the pvc doesn't still exist. The after each should have called delete.
|
|
_, err := f.FindPVC(pvc.Name)
|
|
return err != nil
|
|
}, timeout, pollingInterval).Should(BeTrue())
|
|
}
|
|
By("Creating PVC with upload target annotation")
|
|
pvc, err = f.CreateBoundPVCFromDefinition(utils.UploadPVCDefinition())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
uploadProxyURL = findProxyURLCdiConfig(f)
|
|
if uploadProxyURL == "" {
|
|
By("Set up port forwarding")
|
|
uploadProxyURL, portForwardCmd, err = startUploadProxyPortForward(f)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
})
|
|
|
|
AfterEach(func() {
|
|
By("Stop port forwarding")
|
|
if portForwardCmd != nil {
|
|
err = portForwardCmd.Process.Kill()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
portForwardCmd.Wait()
|
|
portForwardCmd = nil
|
|
}
|
|
|
|
By("Delete upload PVC")
|
|
err = f.DeletePVC(pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Wait for upload pod to be deleted")
|
|
deleted, err := utils.WaitPodDeleted(f.K8sClient, utils.UploadPodName(pvc), f.Namespace.Name, time.Second*20)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(deleted).To(BeTrue())
|
|
})
|
|
|
|
DescribeTable("should", func(uploader uploadFunc, validToken bool, expectedStatus int) {
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
var token string
|
|
if validToken {
|
|
By("Get an upload token")
|
|
token, err = utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
} else {
|
|
token = "abc"
|
|
}
|
|
|
|
By("Do upload")
|
|
Eventually(func() error {
|
|
return uploader(uploadProxyURL, token, expectedStatus)
|
|
}, timeout, pollingInterval).Should(BeNil(), "Upload should eventually succeed, even if initially pod is not ready")
|
|
|
|
if validToken {
|
|
By("Verify PVC status annotation says succeeded")
|
|
found, err := utils.WaitPVCPodStatusSucceeded(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
By("Verify content")
|
|
same, err := f.VerifyTargetPVCContentMD5(f.Namespace, pvc, utils.DefaultImagePath, utils.UploadFileMD5100kbytes, 100000)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(same).To(BeTrue())
|
|
By("Verifying the image is sparse")
|
|
Expect(f.VerifySparse(f.Namespace, pvc)).To(BeTrue())
|
|
if utils.DefaultStorageCSI {
|
|
// CSI storage class, it should respect fsGroup
|
|
By("Checking that disk image group is qemu")
|
|
Expect(f.GetDiskGroup(f.Namespace, pvc, false)).To(Equal("107"))
|
|
}
|
|
By("Verifying permissions are 660")
|
|
Expect(f.VerifyPermissions(f.Namespace, pvc)).To(BeTrue(), "Permissions on disk image are not 660")
|
|
} else {
|
|
uploadPod, err := utils.FindPodByPrefix(f.K8sClient, f.Namespace.Name, utils.UploadPodName(pvc), common.CDILabelSelector)
|
|
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Unable to get uploader pod %q", f.Namespace.Name+"/"+utils.UploadPodName(pvc)))
|
|
|
|
pvc, err = f.K8sClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
delete(pvc.Annotations, controller.AnnUploadRequest)
|
|
pvc, err = f.K8sClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).Update(context.TODO(), pvc, metav1.UpdateOptions{})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Eventually(func() bool {
|
|
_, err = f.K8sClient.CoreV1().Pods(uploadPod.Namespace).Get(context.TODO(), uploadPod.Name, metav1.GetOptions{})
|
|
if k8serrors.IsNotFound(err) {
|
|
return true
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
return false
|
|
}, timeout, pollingInterval).Should(BeTrue())
|
|
|
|
By("Verify PVC empty")
|
|
_, err = framework.VerifyPVCIsEmpty(f, pvc, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
},
|
|
Entry("[test_id:1368]succeed given a valid token", uploadImage, true, http.StatusOK),
|
|
Entry("[test_id:5078]succeed given a valid token (async)", uploadImageAsync, true, http.StatusOK),
|
|
Entry("[test_id:5079]succeed given a valid token (alpha)", uploadImageAlpha, true, http.StatusOK),
|
|
Entry("[test_id:5080]succeed given a valid token (async alpha)", uploadImageAsyncAlpha, true, http.StatusOK),
|
|
Entry("[test_id:5081]succeed given a valid token (form)", uploadForm, true, http.StatusOK),
|
|
Entry("[test_id:5082]succeed given a valid token (form async)", uploadFormAsync, true, http.StatusOK),
|
|
Entry("[posneg:negative][test_id:1369]fail given an invalid token", uploadImage, false, http.StatusUnauthorized),
|
|
)
|
|
|
|
It("[test_id:4988]Verify upload to the same pvc fails", func() {
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
var token string
|
|
By("Get an upload token")
|
|
token, err = utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
Eventually(func() error {
|
|
return uploadImage(uploadProxyURL, token, http.StatusOK)
|
|
}, timeout, pollingInterval).Should(BeNil(), "Upload should eventually succeed, even if initially pod is not ready")
|
|
|
|
By("Verify PVC status annotation says succeeded")
|
|
found, err = utils.WaitPVCPodStatusSucceeded(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
By("Verifying permissions are 660")
|
|
Expect(f.VerifyPermissions(f.Namespace, pvc)).To(BeTrue(), "Permissions on disk image are not 660")
|
|
|
|
By("Try upload again")
|
|
err = uploadImage(uploadProxyURL, token, http.StatusServiceUnavailable)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
})
|
|
|
|
DescribeTable("Verify validation error message on async upload if virtual size > pvc size", func(filename string) {
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
var token string
|
|
By("Get an upload token")
|
|
token, err = utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
Eventually(func() error {
|
|
return uploadFileNameToPath(binaryRequestFunc, filename, uploadProxyURL, asyncUploadPath, token, http.StatusBadRequest)
|
|
}, timeout, pollingInterval).Should(BeNil(), "Upload should eventually succeed, even if initially pod is not ready")
|
|
},
|
|
Entry("[test_id:4989]fail given a large virtual size QCOW2 file", utils.UploadFileLargeVirtualDiskQcow),
|
|
Entry("fail given a large physical size QCOW2 file", utils.UploadFileLargePhysicalDiskQcow),
|
|
)
|
|
|
|
DescribeTable("[posneg:negative][test_id:2330]Verify failure on sync upload if virtual size > pvc size", func(filename string) {
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
var token string
|
|
By("Get an upload token")
|
|
token, err = utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
err = uploadFileNameToPath(binaryRequestFunc, filename, uploadProxyURL, syncUploadPath, token, http.StatusOK)
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
uploadPod, err := utils.FindPodByPrefix(f.K8sClient, f.Namespace.Name, utils.UploadPodName(pvc), common.CDILabelSelector)
|
|
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Unable to get uploader pod %q", f.Namespace.Name+"/"+utils.UploadPodName(pvc)))
|
|
|
|
By("Verify size error in logs")
|
|
Eventually(func() bool {
|
|
log, _ := tests.RunKubectlCommand(f, "logs", uploadPod.Name, "-n", uploadPod.Namespace)
|
|
if strings.Contains(log, "is larger than available size") {
|
|
return true
|
|
}
|
|
if strings.Contains(log, "no space left on device") {
|
|
return true
|
|
}
|
|
if strings.Contains(log, "qemu-img execution failed") {
|
|
return true
|
|
}
|
|
By("Failed to find error messages about a too large image in log:")
|
|
By(log)
|
|
return false
|
|
}, controllerSkipPVCCompleteTimeout, assertionPollInterval).Should(BeTrue())
|
|
},
|
|
Entry("fail given a large virtual size RAW XZ file", utils.UploadFileLargeVirtualDiskXz),
|
|
Entry("fail given a large virtual size QCOW2 file", utils.UploadFileLargeVirtualDiskQcow),
|
|
Entry("fail given a large physical size RAW XZ file", utils.UploadFileLargePhysicalDiskXz),
|
|
Entry("fail given a large physical size QCOW2 file", utils.UploadFileLargePhysicalDiskQcow),
|
|
)
|
|
})
|
|
|
|
var TestFakeError = errors.New("TestFakeError")
|
|
|
|
// LimitThenErrorReader returns a Reader that reads from r
|
|
// but stops with FakeError after n bytes.
|
|
// Based on io.LimitReader
|
|
func LimitThenErrorReader(r io.Reader, n int64) io.Reader { return &limitThenErrorReader{r, n} }
|
|
|
|
// A limitThenErrorReader reads from R but limits the amount of
|
|
// data returned to just N bytes. Each call to Read
|
|
// updates N to reflect the new amount remaining.
|
|
// Read returns ERR when N <= 0.
|
|
type limitThenErrorReader struct {
|
|
r io.Reader // underlying reader
|
|
n int64 // max bytes remaining
|
|
}
|
|
|
|
func (l *limitThenErrorReader) Read(p []byte) (n int, err error) {
|
|
if l.n <= 0 {
|
|
return 0, TestFakeError // EOF
|
|
}
|
|
if int64(len(p)) > l.n {
|
|
p = p[0:l.n]
|
|
}
|
|
n, err = l.r.Read(p)
|
|
l.n -= int64(n)
|
|
return
|
|
}
|
|
|
|
func startUploadProxyPortForward(f *framework.Framework) (string, *exec.Cmd, error) {
|
|
lp := "18443"
|
|
pm := lp + ":443"
|
|
url := "https://127.0.0.1:" + lp
|
|
|
|
cmd := tests.CreateKubectlCommand(f, "-n", f.CdiInstallNs, "port-forward", "svc/cdi-uploadproxy", pm)
|
|
err := cmd.Start()
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
return url, cmd, nil
|
|
}
|
|
|
|
func formRequestFunc(url, fileName string) (*http.Request, error) {
|
|
f, err := os.Open(fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pipeReader, pipeWriter := io.Pipe()
|
|
multipartWriter := multipart.NewWriter(pipeWriter)
|
|
|
|
req, err := http.NewRequest("POST", url, pipeReader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Add("Content-Type", multipartWriter.FormDataContentType())
|
|
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
defer f.Close()
|
|
defer pipeWriter.Close()
|
|
|
|
formFile, err := multipartWriter.CreateFormFile("file", utils.UploadFile)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
_, err = io.Copy(formFile, f)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = multipartWriter.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}()
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func binaryRequestFunc(url, fileName string) (*http.Request, error) {
|
|
f, err := os.Open(fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", url, f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Add("Content-Type", "application/octet-stream")
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func testBadRequestFunc(url, fileName string) (*http.Request, error) {
|
|
f, err := os.Open(fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lr := LimitThenErrorReader(f, 2048)
|
|
req, err := http.NewRequest("POST", url, lr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Add("Content-Type", "application/octet-stream")
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func uploadImage(portForwardURL, token string, expectedStatus int) error {
|
|
return uploadFileNameToPath(binaryRequestFunc, utils.UploadFile, portForwardURL, syncUploadPath, token, expectedStatus)
|
|
}
|
|
|
|
func uploadImageAsync(portForwardURL, token string, expectedStatus int) error {
|
|
return uploadFileNameToPath(binaryRequestFunc, utils.UploadFile, portForwardURL, asyncUploadPath, token, expectedStatus)
|
|
}
|
|
|
|
func uploadImageAlpha(portForwardURL, token string, expectedStatus int) error {
|
|
return uploadFileNameToPath(binaryRequestFunc, utils.UploadFile, portForwardURL, alphaSyncUploadPath, token, expectedStatus)
|
|
}
|
|
|
|
func uploadImageAsyncAlpha(portForwardURL, token string, expectedStatus int) error {
|
|
return uploadFileNameToPath(binaryRequestFunc, utils.UploadFile, portForwardURL, alphaAsyncUploadPath, token, expectedStatus)
|
|
}
|
|
|
|
func uploadForm(portForwardURL, token string, expectedStatus int) error {
|
|
return uploadFileNameToPath(formRequestFunc, utils.UploadFile, portForwardURL, syncFormPath, token, expectedStatus)
|
|
}
|
|
|
|
func uploadFormAsync(portForwardURL, token string, expectedStatus int) error {
|
|
return uploadFileNameToPath(formRequestFunc, utils.UploadFile, portForwardURL, syncFormPath, token, expectedStatus)
|
|
}
|
|
|
|
func uploadFileNameToPath(requestFunc uploadFileNameRequestCreator, fileName, portForwardURL, path, token string, expectedStatus int) error {
|
|
url := portForwardURL + path
|
|
|
|
client := &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
},
|
|
}
|
|
|
|
req, err := requestFunc(url, fileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer req.Body.Close()
|
|
|
|
req.Header.Add("Authorization", "Bearer "+token)
|
|
req.Header.Add("Origin", "foo.bar.com")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode != expectedStatus {
|
|
return fmt.Errorf("Unexpected return value %d expected %d, Response: %s", resp.StatusCode, expectedStatus, resp.Body)
|
|
}
|
|
|
|
if resp.Header.Get("Access-Control-Allow-Origin") != "*" {
|
|
return fmt.Errorf("Auth response header missing")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func findProxyURLCdiConfig(f *framework.Framework) string {
|
|
config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
if config.Status.UploadProxyURL != nil {
|
|
return fmt.Sprintf("https://%s", *config.Status.UploadProxyURL)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
var _ = Describe("Block PV upload Test", func() {
|
|
var (
|
|
pvc *v1.PersistentVolumeClaim
|
|
err error
|
|
|
|
uploadProxyURL string
|
|
portForwardCmd *exec.Cmd
|
|
)
|
|
|
|
f := framework.NewFramework(namespacePrefix)
|
|
|
|
BeforeEach(func() {
|
|
if pvc != nil {
|
|
Eventually(func() bool {
|
|
// Make sure the pvc doesn't still exist. The after each should have called delete.
|
|
_, err := f.FindPVC(pvc.Name)
|
|
return err != nil
|
|
}, timeout, pollingInterval).Should(BeTrue())
|
|
}
|
|
|
|
By("Creating PVC with upload target annotation")
|
|
pvc, err = f.CreateBoundPVCFromDefinition(utils.UploadBlockPVCDefinition(f.BlockSCName))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
uploadProxyURL = findProxyURLCdiConfig(f)
|
|
if uploadProxyURL == "" {
|
|
By("Set up port forwarding")
|
|
uploadProxyURL, portForwardCmd, err = startUploadProxyPortForward(f)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
})
|
|
|
|
AfterEach(func() {
|
|
By("Stop port forwarding")
|
|
if portForwardCmd != nil {
|
|
err = portForwardCmd.Process.Kill()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
portForwardCmd.Wait()
|
|
portForwardCmd = nil
|
|
}
|
|
|
|
By("Delete upload PVC")
|
|
err = f.DeletePVC(pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
By("Wait for upload pod to be deleted")
|
|
deleted, err := utils.WaitPodDeleted(f.K8sClient, utils.UploadPodName(pvc), f.Namespace.Name, time.Second*20)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(deleted).To(BeTrue())
|
|
})
|
|
|
|
DescribeTable("should", func(validToken bool, expectedStatus int) {
|
|
if !f.IsBlockVolumeStorageClassAvailable() {
|
|
Skip("Storage Class for block volume is not available")
|
|
}
|
|
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
var token string
|
|
if validToken {
|
|
By("Get an upload token")
|
|
token, err = utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
} else {
|
|
token = "abc"
|
|
}
|
|
|
|
By("Do upload")
|
|
Eventually(func() error {
|
|
return uploadImage(uploadProxyURL, token, expectedStatus)
|
|
}, timeout, pollingInterval).Should(BeNil(), "Upload should eventually succeed, even if initially pod is not ready")
|
|
|
|
if validToken {
|
|
By("Verify PVC status annotation says succeeded")
|
|
found, err := utils.WaitPVCPodStatusSucceeded(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
same, err := f.VerifyTargetPVCContentMD5(f.Namespace, pvc, utils.DefaultPvcMountPath, utils.UploadFileMD5, utils.UploadFileSize)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(same).To(BeTrue())
|
|
} else {
|
|
// TODO framework.VerifyPVCIsEmpty doesn't make sense for block devices
|
|
//By("Verify PVC empty")
|
|
//_, err = framework.VerifyPVCIsEmpty(f, pvc)
|
|
//Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
},
|
|
Entry("[test_id:1368]succeed given a valid token (block)", true, http.StatusOK),
|
|
Entry("[posneg:negative][test_id:1369]fail given an invalid token (block)", false, http.StatusUnauthorized),
|
|
)
|
|
})
|
|
|
|
var _ = Describe("CDIConfig manipulation upload tests", func() {
|
|
f := framework.NewFramework(namespacePrefix)
|
|
var (
|
|
origSpec *cdiv1.CDIConfigSpec
|
|
pvc *v1.PersistentVolumeClaim
|
|
portForwardCmd *exec.Cmd
|
|
uploadProxyURL string
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
By("Capturing original CDIConfig state")
|
|
config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
origSpec = config.Spec.DeepCopy()
|
|
if pvc != nil {
|
|
By("Making sure no pvc exists")
|
|
Eventually(func() bool {
|
|
// Make sure the pvc doesn't still exist. The after each should have called delete.
|
|
_, err := f.FindPVC(pvc.Name)
|
|
return err != nil
|
|
}, timeout, pollingInterval).Should(BeTrue())
|
|
}
|
|
|
|
uploadProxyURL = findProxyURLCdiConfig(f)
|
|
if uploadProxyURL == "" {
|
|
By("Set up port forwarding")
|
|
uploadProxyURL, portForwardCmd, err = startUploadProxyPortForward(f)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
})
|
|
|
|
AfterEach(func() {
|
|
By("Restoring CDIConfig to original state")
|
|
err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
|
|
origSpec.DeepCopyInto(config)
|
|
})
|
|
Eventually(func() bool {
|
|
config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
return apiequality.Semantic.DeepEqual(config.Spec, *origSpec)
|
|
}, timeout, pollingInterval).Should(BeTrue(), "CDIConfig not properly restored to original value")
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
By("Stop port forwarding")
|
|
if portForwardCmd != nil {
|
|
err = portForwardCmd.Process.Kill()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
portForwardCmd.Wait()
|
|
portForwardCmd = nil
|
|
}
|
|
|
|
By("Delete upload PVC")
|
|
err = f.DeletePVC(pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Waiting for PVC to be deleted")
|
|
Eventually(func() bool {
|
|
_, err := f.K8sClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{})
|
|
return k8serrors.IsNotFound(err)
|
|
}, timeout, pollingInterval).Should(BeTrue())
|
|
|
|
By("Wait for upload pod to be deleted")
|
|
deleted, err := utils.WaitPodDeleted(f.K8sClient, utils.UploadPodName(pvc), f.Namespace.Name, time.Second*20)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(deleted).To(BeTrue())
|
|
})
|
|
|
|
It("[test_id:4990]Should create upload pod in namespace with quota", func() {
|
|
err := f.CreateQuotaInNs(int64(1), int64(1024*1024*1024), int64(2), int64(2*1024*1024*1024))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
By("Creating PVC with upload target annotation")
|
|
pvc, err = f.CreateBoundPVCFromDefinition(utils.UploadPVCDefinition())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
})
|
|
|
|
It("[test_id:4991]Should fail to create upload pod in namespace with quota, when pods have higher requirements", func() {
|
|
err := f.UpdateCdiConfigResourceLimits(int64(2), int64(1024*1024*1024), int64(2), int64(1*1024*1024*1024))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = f.CreateQuotaInNs(int64(1), int64(1024*1024*1024), int64(2), int64(2*1024*1024*1024))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
By("Creating PVC with upload target annotation")
|
|
pvc, err = f.CreateBoundPVCFromDefinition(utils.UploadPVCDefinition())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Verify Quota was exceeded in logs")
|
|
matchString := fmt.Sprintf("pods \\\"cdi-upload-upload-test\\\" is forbidden: exceeded quota: test-quota, requested")
|
|
Eventually(func() string {
|
|
log, err := tests.RunKubectlCommand(f, "logs", f.ControllerPod.Name, "-n", f.CdiInstallNs)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return log
|
|
}, controllerSkipPVCCompleteTimeout, assertionPollInterval).Should(ContainSubstring(matchString))
|
|
})
|
|
|
|
It("[test_id:4992]Should fail to create upload pod in namespace with quota, and recover when quota fixed", func() {
|
|
err := f.UpdateCdiConfigResourceLimits(int64(0), int64(512*1024*1024), int64(2), int64(512*1024*1024))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = f.CreateQuotaInNs(int64(1), int64(256*1024*1024), int64(2), int64(256*1024*1024))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
By("Creating PVC with upload target annotation")
|
|
pvc, err = f.CreateBoundPVCFromDefinition(utils.UploadPVCDefinition())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Verify Quota was exceeded in logs")
|
|
matchString := fmt.Sprintf("pods \\\"cdi-upload-upload-test\\\" is forbidden: exceeded quota: test-quota, requested")
|
|
Eventually(func() string {
|
|
log, err := tests.RunKubectlCommand(f, "logs", f.ControllerPod.Name, "-n", f.CdiInstallNs)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return log
|
|
}, controllerSkipPVCCompleteTimeout, assertionPollInterval).Should(ContainSubstring(matchString))
|
|
By("Updating the quota to be enough")
|
|
err = f.UpdateQuotaInNs(int64(2), int64(512*1024*1024), int64(2), int64(1024*1024*1024))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
})
|
|
|
|
It("[test_id:4993]Should create upload pod in namespace with quota and pods limits are low enough", func() {
|
|
err := f.UpdateCdiConfigResourceLimits(int64(0), int64(0), int64(1), int64(512*1024*1024))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = f.CreateQuotaInNs(int64(1), int64(1024*1024*1024), int64(2), int64(2*1024*1024*1024))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
By("Creating PVC with upload target annotation")
|
|
pvc, err = f.CreateBoundPVCFromDefinition(utils.UploadPVCDefinition())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
})
|
|
|
|
DescribeTable("Async upload with filesystem overhead", func(expectedStatus int, globalOverhead, scOverhead string) {
|
|
defaultSCName := utils.DefaultStorageClass.GetName()
|
|
testedFilesystemOverhead := &cdiv1.FilesystemOverhead{}
|
|
if globalOverhead != "" {
|
|
testedFilesystemOverhead.Global = cdiv1.Percent(globalOverhead)
|
|
}
|
|
if scOverhead != "" {
|
|
testedFilesystemOverhead.StorageClass = map[string]cdiv1.Percent{defaultSCName: cdiv1.Percent(scOverhead)}
|
|
}
|
|
err := utils.UpdateCDIConfig(f.CrClient, func(config *cdiv1.CDIConfigSpec) {
|
|
config.FilesystemOverhead = testedFilesystemOverhead.DeepCopy()
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
By(fmt.Sprintf("Waiting for filsystem overhead status to be set to %v", testedFilesystemOverhead))
|
|
Eventually(func() bool {
|
|
config, err := f.CdiClient.CdiV1beta1().CDIConfigs().Get(context.TODO(), common.ConfigName, metav1.GetOptions{})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
if scOverhead != "" {
|
|
return config.Status.FilesystemOverhead.StorageClass[defaultSCName] == cdiv1.Percent(scOverhead)
|
|
}
|
|
return config.Status.FilesystemOverhead.StorageClass[defaultSCName] == cdiv1.Percent(globalOverhead)
|
|
}, timeout, pollingInterval).Should(BeTrue(), "CDIConfig filesystem overhead wasn't set")
|
|
|
|
By("Creating PVC with upload target annotation")
|
|
pvc, err = f.CreateBoundPVCFromDefinition(utils.UploadPVCDefinition())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Verify PVC annotation says ready")
|
|
found, err := utils.WaitPVCPodStatusReady(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
Eventually(func() error {
|
|
return uploadImageAsync(uploadProxyURL, token, expectedStatus)
|
|
}, timeout, pollingInterval).Should(BeNil(), "uploadImageAsync should return nil, even if not ready")
|
|
},
|
|
Entry("[test_id:4548] Succeed with low global overhead", http.StatusOK, "0.1", ""),
|
|
Entry("[test_id:4672][posneg:negative] Fail with high global overhead", http.StatusBadRequest, "0.99", ""),
|
|
Entry("[test_id:4673] Succeed with low per-storageclass overhead (despite high global overhead)", http.StatusOK, "0.99", "0.1"),
|
|
Entry("[test_id:4714][posneg:negative] Fail with high per-storageclass overhead (despite low global overhead)", http.StatusBadRequest, "0.1", "0.99"),
|
|
)
|
|
})
|
|
|
|
var _ = Describe("[rfe_id:138][crit:high][vendor:cnv-qe@redhat.com][level:component] Upload tests", func() {
|
|
f := framework.NewFramework("upload-func-test")
|
|
|
|
var (
|
|
pvc *v1.PersistentVolumeClaim
|
|
dataVolume *cdiv1.DataVolume
|
|
err error
|
|
|
|
uploadProxyURL string
|
|
portForwardCmd *exec.Cmd
|
|
errAsString = func(e error) string { return e.Error() }
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
uploadProxyURL = findProxyURLCdiConfig(f)
|
|
if uploadProxyURL == "" {
|
|
By("Set up port forwarding")
|
|
uploadProxyURL, portForwardCmd, err = startUploadProxyPortForward(f)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
})
|
|
|
|
AfterEach(func() {
|
|
By("Stop port forwarding")
|
|
if portForwardCmd != nil {
|
|
err = portForwardCmd.Process.Kill()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
portForwardCmd.Wait()
|
|
portForwardCmd = nil
|
|
}
|
|
|
|
By("Delete upload DV")
|
|
err = utils.DeleteDataVolume(f.CdiClient, f.Namespace.Name, dataVolume.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Wait for upload pod to be deleted")
|
|
deleted, err := utils.WaitPodDeleted(f.K8sClient, utils.UploadPodName(pvc), f.Namespace.Name, time.Second*20)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(deleted).To(BeTrue())
|
|
})
|
|
|
|
It("[test_id:3993] Upload image to data volume and verify retry count", func() {
|
|
dvName := "upload-dv"
|
|
By(fmt.Sprintf("Creating new datavolume %s", dvName))
|
|
dv := utils.NewDataVolumeForUpload(dvName, "100Mi")
|
|
dataVolume, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, f.Namespace.Name, dv)
|
|
pvc = utils.PersistentVolumeClaimFromDataVolume(dataVolume)
|
|
|
|
By("verifying pvc was created, force bind if needed")
|
|
pvc, err := utils.WaitForPVC(f.K8sClient, pvc.Namespace, pvc.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
f.ForceBindIfWaitForFirstConsumer(pvc)
|
|
|
|
phase := cdiv1.UploadReady
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
Eventually(func() error {
|
|
return uploadImage(uploadProxyURL, token, http.StatusOK)
|
|
}, timeout, pollingInterval).Should(BeNil(), "Upload should eventually succeed, even if initially pod is not ready")
|
|
|
|
phase = cdiv1.Succeeded
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Verify retry annotation on PVC")
|
|
Eventually(func() int {
|
|
restarts, status, err := utils.WaitForPVCAnnotation(f.K8sClient, f.Namespace.Name, pvc, controller.AnnPodRestarts)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(status).To(BeTrue())
|
|
i, err := strconv.Atoi(restarts)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
return i
|
|
}, timeout, pollingInterval).Should(BeNumerically("==", 0))
|
|
|
|
By("Verify the number of retries on the datavolume")
|
|
Eventually(func() int32 {
|
|
dv, err := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
restarts := dv.Status.RestartCount
|
|
return restarts
|
|
}, timeout, pollingInterval).Should(BeNumerically("==", 0))
|
|
|
|
})
|
|
|
|
It("[test_id:3997] Upload image to data volume - kill container and verify retry count", func() {
|
|
dvName := "upload-dv"
|
|
By(fmt.Sprintf("Creating new datavolume %s", dvName))
|
|
dv := utils.NewDataVolumeForUpload(dvName, "100Mi")
|
|
dataVolume, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, f.Namespace.Name, dv)
|
|
pvc = utils.PersistentVolumeClaimFromDataVolume(dataVolume)
|
|
|
|
By("verifying pvc was created, force bind if needed")
|
|
pvc, err := utils.WaitForPVC(f.K8sClient, pvc.Namespace, pvc.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
f.ForceBindIfWaitForFirstConsumer(pvc)
|
|
|
|
phase := cdiv1.UploadReady
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Kill upload pod to force error")
|
|
// exit code 137 = 128 + 9, it means parent process issued kill -9, in our case it is not a problem
|
|
_, _, err = f.ExecShellInPod(utils.UploadPodName(pvc), f.Namespace.Name, "kill 1")
|
|
Expect(err).To(Or(
|
|
BeNil(),
|
|
WithTransform(errAsString, ContainSubstring("137"))))
|
|
|
|
By("Verify retry annotation on PVC")
|
|
Eventually(func() int {
|
|
restarts, status, err := utils.WaitForPVCAnnotation(f.K8sClient, f.Namespace.Name, pvc, controller.AnnPodRestarts)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(status).To(BeTrue())
|
|
i, err := strconv.Atoi(restarts)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
return i
|
|
}, timeout, pollingInterval).Should(BeNumerically(">=", 1))
|
|
|
|
By("Verify the number of retries on the datavolume")
|
|
Eventually(func() int32 {
|
|
dv, err := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
restarts := dv.Status.RestartCount
|
|
return restarts
|
|
}, timeout, pollingInterval).Should(BeNumerically(">=", 1))
|
|
|
|
})
|
|
|
|
DescribeTable("Upload datavolume creates correct scratch space, pod and service names", func(dvName string) {
|
|
By(fmt.Sprintf("Creating new datavolume %s", dvName))
|
|
|
|
dv := utils.NewDataVolumeForUpload(dvName, "1Gi")
|
|
dataVolume, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, f.Namespace.Name, dv)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
pvc = utils.PersistentVolumeClaimFromDataVolume(dataVolume)
|
|
|
|
By("verifying pvc was created, force bind if needed")
|
|
pvc, err := utils.WaitForPVC(f.K8sClient, pvc.Namespace, pvc.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
f.ForceBindIfWaitForFirstConsumer(pvc)
|
|
|
|
phase := cdiv1.UploadReady
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
Eventually(func() error {
|
|
return uploadImage(uploadProxyURL, token, http.StatusOK)
|
|
}, timeout, pollingInterval).Should(BeNil(), "Upload should eventually succeed, even if initially pod is not ready")
|
|
|
|
phase = cdiv1.Succeeded
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
},
|
|
Entry("[test_id:4273] with short DataVolume name", "import-long-name-dv"),
|
|
Entry("[test_id:4274] with long DataVolume name", "import-long-name-dv-"+
|
|
"123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-"+
|
|
"123456789-123456789-123456789-1234567890"),
|
|
Entry("[test_id:4275] with long DataVolume name including special chars '.'",
|
|
"import-long-name-dv."+
|
|
"123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-"+
|
|
"123456789-123456789-123456789-1234567890"),
|
|
)
|
|
|
|
It("[test_id:1985] Upload datavolume should succeed on retry after failure", func() {
|
|
shortDvName := "upload-after-fail-1985"
|
|
By(fmt.Sprintf("Creating new datavolume %s", shortDvName))
|
|
|
|
By("Create DV")
|
|
dv := utils.NewDataVolumeForUpload(shortDvName, "1Gi")
|
|
dataVolume, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, f.Namespace.Name, dv)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
f.ForceBindPvcIfDvIsWaitForFirstConsumer(dataVolume)
|
|
|
|
phase := cdiv1.UploadReady
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Get an upload token")
|
|
pvc = utils.PersistentVolumeClaimFromDataVolume(dataVolume)
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload - expecting failure")
|
|
err = uploadFileNameToPath(testBadRequestFunc, utils.UploadFile, uploadProxyURL, syncUploadPath, token, http.StatusOK)
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
phase = cdiv1.UploadReady
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Retry Upload")
|
|
Eventually(func() error {
|
|
return uploadFileNameToPath(binaryRequestFunc, utils.UploadFile, uploadProxyURL, syncUploadPath, token, http.StatusOK)
|
|
}, timeout, pollingInterval).Should(BeNil(), "uploadFileNameToPath should return nil, even if not ready")
|
|
|
|
phase = cdiv1.Succeeded
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Verify PVC status annotation says succeeded")
|
|
found, err := utils.WaitPVCPodStatusSucceeded(f.K8sClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(found).To(BeTrue())
|
|
same, err := f.VerifyTargetPVCContentMD5(f.Namespace, pvc, utils.DefaultImagePath, utils.UploadFileMD5100kbytes, 100000)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(same).To(BeTrue(), "MD5 does not match")
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Preallocation", func() {
|
|
f := framework.NewFramework(namespacePrefix)
|
|
dvName := "upload-dv"
|
|
|
|
var (
|
|
dataVolume *cdiv1.DataVolume
|
|
err error
|
|
uploadProxyURL string
|
|
portForwardCmd *exec.Cmd
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
uploadProxyURL = findProxyURLCdiConfig(f)
|
|
if uploadProxyURL == "" {
|
|
By("Set up port forwarding")
|
|
uploadProxyURL, portForwardCmd, err = startUploadProxyPortForward(f)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
})
|
|
|
|
AfterEach(func() {
|
|
if portForwardCmd != nil {
|
|
By("Delete port forward")
|
|
err = portForwardCmd.Process.Kill()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
portForwardCmd.Wait()
|
|
portForwardCmd = nil
|
|
}
|
|
|
|
By("Delete DV")
|
|
err := utils.DeleteDataVolume(f.CdiClient, f.Namespace.Name, dataVolume.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Eventually(func() bool {
|
|
_, err := f.K8sClient.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if k8serrors.IsNotFound(err) {
|
|
return true
|
|
}
|
|
return false
|
|
}, timeout, pollingInterval).Should(BeTrue())
|
|
})
|
|
|
|
It("Uploader should add preallocation when requested", func() {
|
|
By(fmt.Sprintf("Creating new datavolume %s", dvName))
|
|
dv := utils.NewDataVolumeForUpload(dvName, "100Mi")
|
|
preallocation := true
|
|
dv.Spec.Preallocation = &preallocation
|
|
dataVolume, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, f.Namespace.Name, dv)
|
|
pvc := utils.PersistentVolumeClaimFromDataVolume(dataVolume)
|
|
|
|
By("verifying pvc was created, force bind if needed")
|
|
pvc, err := utils.WaitForPVC(f.K8sClient, pvc.Namespace, pvc.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
f.ForceBindIfWaitForFirstConsumer(pvc)
|
|
|
|
phase := cdiv1.UploadReady
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
err = uploadImage(uploadProxyURL, token, http.StatusOK)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
phase = cdiv1.Succeeded
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
pvc, err = utils.FindPVC(f.K8sClient, dataVolume.Namespace, dataVolume.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(pvc.GetAnnotations()[controller.AnnPreallocationApplied]).Should(Or(Equal("true"), Equal("skipped")))
|
|
})
|
|
|
|
It("Uploader should not add preallocation when preallocation=false", func() {
|
|
By(fmt.Sprintf("Creating new datavolume %s", dvName))
|
|
dv := utils.NewDataVolumeForUpload(dvName, "100Mi")
|
|
preallocation := false
|
|
dv.Spec.Preallocation = &preallocation
|
|
dataVolume, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, f.Namespace.Name, dv)
|
|
pvc := utils.PersistentVolumeClaimFromDataVolume(dataVolume)
|
|
|
|
By("verifying pvc was created, force bind if needed")
|
|
pvc, err := utils.WaitForPVC(f.K8sClient, pvc.Namespace, pvc.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
f.ForceBindIfWaitForFirstConsumer(pvc)
|
|
|
|
phase := cdiv1.UploadReady
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
Eventually(func() error {
|
|
return uploadImage(uploadProxyURL, token, http.StatusOK)
|
|
}, timeout, pollingInterval).Should(BeNil(), "Upload should eventually succeed, even if initially pod is not ready")
|
|
|
|
phase = cdiv1.Succeeded
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
pvc, err = utils.FindPVC(f.K8sClient, dataVolume.Namespace, dataVolume.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(pvc.GetAnnotations()[controller.AnnPreallocationApplied]).ShouldNot(Equal("true"))
|
|
})
|
|
|
|
DescribeTable("Each upload path include preallocation/conversion", func(uploader uploadFunc) {
|
|
By(fmt.Sprintf("Creating new datavolume %s", dvName))
|
|
dv := utils.NewDataVolumeForUpload(dvName, "100Mi")
|
|
preallocation := true
|
|
dv.Spec.Preallocation = &preallocation
|
|
dataVolume, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, f.Namespace.Name, dv)
|
|
pvc := utils.PersistentVolumeClaimFromDataVolume(dataVolume)
|
|
|
|
By("verifying pvc was created, force bind if needed")
|
|
pvc, err := utils.WaitForPVC(f.K8sClient, pvc.Namespace, pvc.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
f.ForceBindIfWaitForFirstConsumer(pvc)
|
|
|
|
phase := cdiv1.UploadReady
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
if err != nil {
|
|
dv, dverr := f.CdiClient.CdiV1beta1().DataVolumes(f.Namespace.Name).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
if dverr != nil {
|
|
Fail(fmt.Sprintf("datavolume %s phase %s", dv.Name, dv.Status.Phase))
|
|
}
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
By("Get an upload token")
|
|
token, err := utils.RequestUploadToken(f.CdiClient, pvc)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(token).ToNot(BeEmpty())
|
|
|
|
By("Do upload")
|
|
err = uploadImage(uploadProxyURL, token, http.StatusOK)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
phase = cdiv1.Succeeded
|
|
By(fmt.Sprintf("Waiting for datavolume to match phase %s", string(phase)))
|
|
err = utils.WaitForDataVolumePhase(f.CdiClient, f.Namespace.Name, phase, dataVolume.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
pvc, err = utils.FindPVC(f.K8sClient, dataVolume.Namespace, dataVolume.Name)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(pvc.GetAnnotations()[controller.AnnPreallocationApplied]).Should(Or(Equal("true"), Equal("skipped")))
|
|
},
|
|
Entry("sync", uploadImage),
|
|
Entry("async", uploadImageAsync),
|
|
Entry("form sync", uploadForm),
|
|
Entry("form async", uploadFormAsync),
|
|
)
|
|
})
|