Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(CNV-43603): add disk-uploader functional tests #549

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion automation/e2e-deploy-resources.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEV

kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml"

kubectl patch kubevirt kubevirt -n kubevirt --type merge -p '{"spec":{"configuration":{"developerConfiguration":{"featureGates": ["DataVolumes"]}}}}'
kubectl patch kubevirt kubevirt -n kubevirt --type merge -p '{"spec":{"configuration":{"developerConfiguration":{"featureGates": ["DataVolumes", "VMExport"]}}}}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DataVolumes FG can be dropped I guess.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep just want to get confirmation from @ksimon1 about it, so this PR won't do any other changes except adding new tests.


# Deploy Storage
kubectl apply -f "https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-operator.yaml"
Expand Down
3 changes: 3 additions & 0 deletions automation/e2e-source.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
export DEV_MODE="${DEV_MODE:-false}"
export STORAGE_CLASS="${STORAGE_CLASS:-}"
export NUM_NODES=${NUM_NODES:-2}
export REGISTRY_IMAGE_DESTINATION="quay.io/kubevirt/e2e-tests-example-vm"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there absolutely no way to use an ephemeral registry? Why do we have to store test artifacts on a live registry? (quay.io)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replied here earlier. The issue is that there is no built-in registry in Kubernetes cluster (only on OpenShift).

So we have 3 options:

1). Deploy a new pod that will use registry image to push there
2). Use OCP's built-in container registry
3). Use an external container registry

@0xFelix @ksimon1 What do you prefer?

export REGISTRY_ACCESS_KEY_ID=""
export REGISTRY_SECRET_KEY=""
12 changes: 9 additions & 3 deletions modules/disk-uploader/pkg/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"log"
"os"
"strconv"
"time"

"github.com/google/go-containerregistry/pkg/authn"
Expand Down Expand Up @@ -31,15 +32,20 @@ func Build(diskPath string) (v1.Image, error) {
return image, nil
}

func Push(image v1.Image, imageDestination string, pushTimeout int) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(pushTimeout))
func Push(image v1.Image, imageDestination string, pushTimeout string) error {
timeout, err := strconv.Atoi(pushTimeout)
if err != nil {
log.Fatalf("Invalid push timeout value: %v", err)
}

ctx, cancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(timeout))
defer cancel()

auth := &authn.Basic{
Username: os.Getenv("ACCESS_KEY_ID"),
Password: os.Getenv("SECRET_KEY"),
}
err := crane.Push(image, imageDestination, crane.WithAuth(auth), crane.WithContext(ctx))
err = crane.Push(image, imageDestination, crane.WithAuth(auth), crane.WithContext(ctx))
if err != nil {
log.Fatalf("Error pushing image: %v", err)
return err
Expand Down
8 changes: 4 additions & 4 deletions modules/disk-uploader/pkg/parse/clioptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

const (
defaultPushTimeout = 120
defaultPushTimeout = "120"
)

type CLIOptions struct {
Expand All @@ -18,7 +18,7 @@ type CLIOptions struct {
ExportSourceName string `arg:"--export-source-name" help:"Name of the export source"`
VolumeName string `arg:"--volumename" help:"Name of the volume (if source kind is 'pvc', then volume name is equal to source name)"`
ImageDestination string `arg:"--imagedestination" help:"Destination of the image in container registry"`
PushTimeout int `arg:"--pushtimeout" help:"Push timeout of container disk to registry"`
PushTimeout string `arg:"--pushtimeout" help:"Push timeout of container disk to registry"`
Debug bool `arg:"--debug" help:"Sets DEBUG log level"`
}

Expand All @@ -42,7 +42,7 @@ func (c *CLIOptions) GetImageDestination() string {
return c.ImageDestination
}

func (c *CLIOptions) GetPushTimeout() int {
func (c *CLIOptions) GetPushTimeout() string {
return c.PushTimeout
}

Expand Down Expand Up @@ -91,7 +91,7 @@ func (c *CLIOptions) setValues() error {
c.ExportSourceNamespace = namespace
}

if c.PushTimeout == 0 {
if c.PushTimeout == "" || c.PushTimeout == "0" {
c.PushTimeout = defaultPushTimeout
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions modules/disk-uploader/pkg/parse/clioptions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var _ = Describe("CLIOptions", func() {
ExportSourceName: expectedExportSourceName,
VolumeName: expectedVolumeName,
ImageDestination: expectedImageDestination,
PushTimeout: 60,
PushTimeout: "60",
Debug: true,
}
Expect(options.Init()).To(Succeed())
Expand All @@ -52,7 +52,7 @@ var _ = Describe("CLIOptions", func() {
ExportSourceName: " " + expectedExportSourceName + " ",
VolumeName: " " + expectedVolumeName + " ",
ImageDestination: " " + expectedImageDestination + " ",
PushTimeout: 60,
PushTimeout: "60",
Debug: true,
}

Expand Down
112 changes: 112 additions & 0 deletions test/disk_uploader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package test

import (
"context"
"os"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeclientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"

v1 "kubevirt.io/api/core/v1"
kubevirtcliv1 "kubevirt.io/client-go/kubecli"
cdiv1beta1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
"kubevirt.io/kubevirt/pkg/libvmi"
"kubevirt.io/kubevirt/tests/libdv"

"github.com/kubevirt/kubevirt-tekton-tasks/test/constants"
"github.com/kubevirt/kubevirt-tekton-tasks/test/framework"
"github.com/kubevirt/kubevirt-tekton-tasks/test/runner"
"github.com/kubevirt/kubevirt-tekton-tasks/test/testconfigs"
"github.com/kubevirt/kubevirt-tekton-tasks/test/vm"
)

var _ = Describe("Run disk-uploader", func() {
f := framework.NewFramework()

BeforeEach(func() {
if f.TestOptions.SkipDiskUploaderTests {
Skip("skipDiskUploaderTests is set to true, skipping tests")
}
})

It("Extracts disk from VM and upload to container registry", func() {
imageDestination := os.Getenv("REGISTRY_IMAGE_DESTINATION")
Expect(imageDestination).ToNot(BeEmpty())

registryCredentials, err := createRegistryCredentials(f.CoreV1Client, f.DeployNamespace)
Expect(err).ToNot(HaveOccurred())

cirrosVm, err := createCirrosVM(f.KubevirtClient, f.DeployNamespace)
Expect(err).ToNot(HaveOccurred())

_, err = vm.WaitForVM(f.KubevirtClient, f.DeployNamespace, cirrosVm.Name, "", constants.Timeouts.WaitForVMStart.Duration, false)
Expect(err).ToNot(HaveOccurred())
Comment on lines +38 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to BeforeEach?


config := &testconfigs.DiskUploaderTestConfig{
TaskRunTestConfig: testconfigs.TaskRunTestConfig{},
TaskData: testconfigs.DiskUploaderTaskData{
ExportSourceKind: "vm",
ExportSourceName: cirrosVm.Name,
VolumeName: cirrosVm.Spec.Template.Spec.Volumes[0].DataVolume.Name,
ImageDestination: imageDestination,
SecretName: registryCredentials.Name,
},
}
f.TestSetup(config)

runner.NewTaskRunRunner(f, config.GetTaskRun()).
CreateTaskRun().
ExpectSuccess().
ExpectLogs(config.GetAllExpectedLogs()...).
ExpectResults(nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query the repository after to verify disk was uploaded?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep good idea, need to check it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you going to add a check?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We may want to delete the repository after completing running the tests. If we won't delete it, and re-run the functional tests, it may be false check.

})
})

func createRegistryCredentials(client kubeclientcorev1.CoreV1Interface, namespace string) (*corev1.Secret, error) {
registryKeyId := os.Getenv("REGISTRY_ACCESS_KEY_ID")
Expect(registryKeyId).ToNot(BeEmpty())

registryKey := os.Getenv("REGISTRY_SECRET_KEY")
Expect(registryKey).ToNot(BeEmpty())

v1Secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: constants.E2ETestsRandomName("disk-uploader-credentials"),
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"accessKeyId": []byte(registryKeyId),
"secretKey": []byte(registryKey),
},
}

return client.Secrets(namespace).Create(context.Background(), v1Secret, metav1.CreateOptions{})
}

func createCirrosVM(client kubevirtcliv1.KubevirtClient, namespace string) (*v1.VirtualMachine, error) {
dataVolume := libdv.NewDataVolume(
libdv.WithRegistryURLSource("docker://quay.io/kubevirt/cirros-container-disk-demo"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not use libdv. It lives under tests/libdv which might be subject to change. Only libvmi can be considered stable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xFelix Don't you think that this is an issue that such important libs for external projects tests living under kubevirt/kubevirt, especially under tests/libdv?

libdv.WithForceBindAnnotation(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be required, if the VM was started at least once.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VM can't be exported if started and it's running. It should be stopped.

Copy link
Member Author

@codingben codingben Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VM is created as stopped by default.

)
dataVolume.Spec.Storage = &cdiv1beta1.StorageSpec{
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"storage": resource.MustParse("512Mi"),
},
},
}
v1VirtualMachine := libvmi.NewVirtualMachine(
libvmi.New(
libvmi.WithDataVolume("datavolumedisk", dataVolume.Name),
libvmi.WithResourceMemory("256M"),
),
libvmi.WithDataVolumeTemplate(dataVolume),
)

return client.VirtualMachine(namespace).Create(context.Background(), v1VirtualMachine, metav1.CreateOptions{})
}
4 changes: 4 additions & 0 deletions test/framework/testoptions/test-options.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var isOKD string
var skipCreateVMFromManifestTests string
var skipExecuteInVMTests string
var skipGenerateSSHKeysTests string
var skipDiskUploaderTests string

type TestOptions struct {
DeployNamespace string
Expand All @@ -29,6 +30,7 @@ type TestOptions struct {
SkipCreateVMFromManifestTests bool
SkipExecuteInVMTests bool
SkipGenerateSSHKeysTests bool
SkipDiskUploaderTests bool

CommonTemplatesVersion string
}
Expand All @@ -42,6 +44,7 @@ func init() {
flag.StringVar(&skipCreateVMFromManifestTests, "skip-create-vm-from-manifests-tests", "", "Skip create vm from manifests test suite. One of: true|false")
flag.StringVar(&skipExecuteInVMTests, "skip-execute-in-vm-tests", "", "Skip execute in vm test suite. One of: true|false")
flag.StringVar(&skipGenerateSSHKeysTests, "skip-generate-ssh-keys-tests", "", "Skip generate ssh keys suite. One of: true|false")
flag.StringVar(&skipDiskUploaderTests, "skip-disk-uploader-tests", "", "Skip disk uploader suite. One of: true|false")
}

func InitTestOptions(testOptions *TestOptions) error {
Expand Down Expand Up @@ -72,6 +75,7 @@ func InitTestOptions(testOptions *TestOptions) error {
testOptions.SkipCreateVMFromManifestTests = strings.ToLower(skipCreateVMFromManifestTests) == "true"
testOptions.SkipExecuteInVMTests = strings.ToLower(skipExecuteInVMTests) == "true"
testOptions.SkipGenerateSSHKeysTests = strings.ToLower(skipGenerateSSHKeysTests) == "true"
testOptions.SkipDiskUploaderTests = strings.ToLower(skipDiskUploaderTests) == "true"

return nil
}
Expand Down
91 changes: 91 additions & 0 deletions test/testconfigs/disk-uploader-config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package testconfigs

import (
pipev1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/kubevirt/kubevirt-tekton-tasks/test/constants"
"github.com/kubevirt/kubevirt-tekton-tasks/test/framework/testoptions"
)

type DiskUploaderTaskData struct {
ExportSourceKind string
ExportSourceName string
VolumeName string
ImageDestination string
PushTimeout string
SecretName string
}

type DiskUploaderTestConfig struct {
TaskRunTestConfig
TaskData DiskUploaderTaskData

deploymentNamespace string
}

func (c *DiskUploaderTestConfig) Init(options *testoptions.TestOptions) {
c.deploymentNamespace = options.DeployNamespace
}

func (c *DiskUploaderTestConfig) GetTaskRun() *pipev1.TaskRun {
params := []pipev1.Param{
{
Name: "EXPORT_SOURCE_KIND",
Value: pipev1.ParamValue{
Type: pipev1.ParamTypeString,
StringVal: c.TaskData.ExportSourceKind,
},
},
{
Name: "EXPORT_SOURCE_NAME",
Value: pipev1.ParamValue{
Type: pipev1.ParamTypeString,
StringVal: c.TaskData.ExportSourceName,
},
},
{
Name: "VOLUME_NAME",
Value: pipev1.ParamValue{
Type: pipev1.ParamTypeString,
StringVal: c.TaskData.VolumeName,
},
},
{
Name: "IMAGE_DESTINATION",
Value: pipev1.ParamValue{
Type: pipev1.ParamTypeString,
StringVal: c.TaskData.ImageDestination,
},
},
{
Name: "PUSH_TIMEOUT",
Value: pipev1.ParamValue{
Type: pipev1.ParamTypeString,
StringVal: c.TaskData.PushTimeout,
},
},
{
Name: "SECRET_NAME",
Value: pipev1.ParamValue{
Type: pipev1.ParamTypeString,
StringVal: c.TaskData.SecretName,
},
},
}

return &pipev1.TaskRun{
ObjectMeta: metav1.ObjectMeta{
Name: constants.E2ETestsRandomName("taskrun-disk-uploader"),
Namespace: c.deploymentNamespace,
},
Spec: pipev1.TaskRunSpec{
TaskRef: &pipev1.TaskRef{
Name: "disk-uploader",
Kind: pipev1.NamespacedTaskKind,
},
Timeout: &metav1.Duration{Duration: c.GetTaskRunTimeout()},
Params: params,
},
}
}
28 changes: 28 additions & 0 deletions vendor/kubevirt.io/kubevirt/pkg/libvmi/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions vendor/kubevirt.io/kubevirt/pkg/libvmi/OWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading