From ef903df540d552fc6056d927f6fd74fb39e95f29 Mon Sep 17 00:00:00 2001 From: Ben Oukhanov Date: Tue, 12 Nov 2024 16:38:39 +0200 Subject: [PATCH] test(CNV-43603): add disk-uploader functional tests Deploy a new Cirros VM to export from, and run disk-uploader as Task to execute extraction process and upload it to the Quay container registry. Signed-off-by: Ben Oukhanov --- automation/e2e-deploy-resources.sh | 2 +- automation/e2e-source.sh | 3 + modules/disk-uploader/pkg/image/image.go | 12 +- modules/disk-uploader/pkg/parse/clioptions.go | 8 +- .../pkg/parse/clioptions_test.go | 4 +- test/disk_uploader_test.go | 112 +++++++ test/framework/testoptions/test-options.go | 4 + test/testconfigs/disk-uploader-config.go | 91 ++++++ .../kubevirt/pkg/libvmi/BUILD.bazel | 28 ++ vendor/kubevirt.io/kubevirt/pkg/libvmi/OWNERS | 3 + .../kubevirt.io/kubevirt/pkg/libvmi/README.md | 74 +++++ .../kubevirt/pkg/libvmi/cloudinit.go | 118 +++++++ .../kubevirt.io/kubevirt/pkg/libvmi/config.go | 154 +++++++++ vendor/kubevirt.io/kubevirt/pkg/libvmi/cpu.go | 77 +++++ .../kubevirt.io/kubevirt/pkg/libvmi/memory.go | 54 ++++ .../kubevirt/pkg/libvmi/network.go | 134 ++++++++ .../kubevirt/pkg/libvmi/selector.go | 103 ++++++ .../kubevirt/pkg/libvmi/storage.go | 296 ++++++++++++++++++ vendor/kubevirt.io/kubevirt/pkg/libvmi/vm.go | 171 ++++++++++ vendor/kubevirt.io/kubevirt/pkg/libvmi/vmi.go | 279 +++++++++++++++++ .../kubevirt/tests/containerdisk/BUILD.bazel | 9 + .../tests/containerdisk/containerdisk.go | 82 +++++ .../kubevirt/tests/flags/BUILD.bazel | 9 + .../kubevirt.io/kubevirt/tests/flags/flags.go | 121 +++++++ .../kubevirt/tests/libdv/BUILD.bazel | 16 + vendor/kubevirt.io/kubevirt/tests/libdv/dv.go | 213 +++++++++++++ vendor/modules.txt | 4 + 27 files changed, 2171 insertions(+), 10 deletions(-) create mode 100644 test/disk_uploader_test.go create mode 100644 test/testconfigs/disk-uploader-config.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/BUILD.bazel create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/OWNERS create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/README.md create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/cloudinit.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/config.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/cpu.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/memory.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/network.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/selector.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/storage.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/vm.go create mode 100644 vendor/kubevirt.io/kubevirt/pkg/libvmi/vmi.go create mode 100644 vendor/kubevirt.io/kubevirt/tests/containerdisk/BUILD.bazel create mode 100644 vendor/kubevirt.io/kubevirt/tests/containerdisk/containerdisk.go create mode 100644 vendor/kubevirt.io/kubevirt/tests/flags/BUILD.bazel create mode 100644 vendor/kubevirt.io/kubevirt/tests/flags/flags.go create mode 100644 vendor/kubevirt.io/kubevirt/tests/libdv/BUILD.bazel create mode 100644 vendor/kubevirt.io/kubevirt/tests/libdv/dv.go diff --git a/automation/e2e-deploy-resources.sh b/automation/e2e-deploy-resources.sh index 2b88da5fd..3d0b688ae 100755 --- a/automation/e2e-deploy-resources.sh +++ b/automation/e2e-deploy-resources.sh @@ -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"]}}}}' # Deploy Storage kubectl apply -f "https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-operator.yaml" diff --git a/automation/e2e-source.sh b/automation/e2e-source.sh index bf375386f..8e5344785 100755 --- a/automation/e2e-source.sh +++ b/automation/e2e-source.sh @@ -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" +export REGISTRY_ACCESS_KEY_ID="" +export REGISTRY_SECRET_KEY="" diff --git a/modules/disk-uploader/pkg/image/image.go b/modules/disk-uploader/pkg/image/image.go index a83ff7734..78d1d9eab 100644 --- a/modules/disk-uploader/pkg/image/image.go +++ b/modules/disk-uploader/pkg/image/image.go @@ -4,6 +4,7 @@ import ( "context" "log" "os" + "strconv" "time" "github.com/google/go-containerregistry/pkg/authn" @@ -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 diff --git a/modules/disk-uploader/pkg/parse/clioptions.go b/modules/disk-uploader/pkg/parse/clioptions.go index 8d984a0ad..7ab6796db 100644 --- a/modules/disk-uploader/pkg/parse/clioptions.go +++ b/modules/disk-uploader/pkg/parse/clioptions.go @@ -9,7 +9,7 @@ import ( ) const ( - defaultPushTimeout = 120 + defaultPushTimeout = "120" ) type CLIOptions struct { @@ -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"` } @@ -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 } @@ -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 diff --git a/modules/disk-uploader/pkg/parse/clioptions_test.go b/modules/disk-uploader/pkg/parse/clioptions_test.go index 3ddfe1f12..400151d51 100644 --- a/modules/disk-uploader/pkg/parse/clioptions_test.go +++ b/modules/disk-uploader/pkg/parse/clioptions_test.go @@ -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()) @@ -52,7 +52,7 @@ var _ = Describe("CLIOptions", func() { ExportSourceName: " " + expectedExportSourceName + " ", VolumeName: " " + expectedVolumeName + " ", ImageDestination: " " + expectedImageDestination + " ", - PushTimeout: 60, + PushTimeout: "60", Debug: true, } diff --git a/test/disk_uploader_test.go b/test/disk_uploader_test.go new file mode 100644 index 000000000..6424ed8cb --- /dev/null +++ b/test/disk_uploader_test.go @@ -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()) + + 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) + }) +}) + +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"), + libdv.WithForceBindAnnotation(), + ) + 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{}) +} diff --git a/test/framework/testoptions/test-options.go b/test/framework/testoptions/test-options.go index 3921e8104..9561ce319 100644 --- a/test/framework/testoptions/test-options.go +++ b/test/framework/testoptions/test-options.go @@ -19,6 +19,7 @@ var isOKD string var skipCreateVMFromManifestTests string var skipExecuteInVMTests string var skipGenerateSSHKeysTests string +var skipDiskUploaderTests string type TestOptions struct { DeployNamespace string @@ -29,6 +30,7 @@ type TestOptions struct { SkipCreateVMFromManifestTests bool SkipExecuteInVMTests bool SkipGenerateSSHKeysTests bool + SkipDiskUploaderTests bool CommonTemplatesVersion string } @@ -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 { @@ -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 } diff --git a/test/testconfigs/disk-uploader-config.go b/test/testconfigs/disk-uploader-config.go new file mode 100644 index 000000000..62ad5f433 --- /dev/null +++ b/test/testconfigs/disk-uploader-config.go @@ -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, + }, + } +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/BUILD.bazel b/vendor/kubevirt.io/kubevirt/pkg/libvmi/BUILD.bazel new file mode 100644 index 000000000..6d7b88a23 --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/BUILD.bazel @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "cloudinit.go", + "config.go", + "cpu.go", + "memory.go", + "network.go", + "selector.go", + "storage.go", + "vm.go", + "vmi.go", + ], + importpath = "kubevirt.io/kubevirt/pkg/libvmi", + visibility = ["//visibility:public"], + deps = [ + "//pkg/pointer:go_default_library", + "//staging/src/kubevirt.io/api/core/v1:go_default_library", + "//staging/src/kubevirt.io/api/instancetype:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", + "//vendor/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1:go_default_library", + ], +) diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/OWNERS b/vendor/kubevirt.io/kubevirt/pkg/libvmi/OWNERS new file mode 100644 index 000000000..210a84c3a --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/OWNERS @@ -0,0 +1,3 @@ +# See the OWNERS docs at https://go.k8s.io/owners +approvers: + - EdDev diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/README.md b/vendor/kubevirt.io/kubevirt/pkg/libvmi/README.md new file mode 100644 index 000000000..058d51488 --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/README.md @@ -0,0 +1,74 @@ +# libvmi + +libvmi is a VMI manifest composer. + +## Overview + +### Motivation + +While reading code, especially test code, a difficulty has been +observed to understand in an easy and clear manner, what is the content +of a VMI object. In a long chain of function call stack, different portions +of the VMI got updated, sometime overriding previously set fields. + +### Goal + - Simplify the creation of VMI objects. + - Easily understand what VMI objects contain. + +## How +libvmi is aimed to build the vmi manifest. + +It uses the builder pattern, allowing a modular construction of VMI +different sections. + +### Rules +In order to keep the package useful and easy to maintain, a few +rules are in order. + +The main goal of these rules are to keep the package simple to use and +easy to grow. While exceptions may apply, they will need a wide consensus +and should not be overused. It would be better to just ask to change the rules. + +- Use only the existing builder pattern to support editing fields. +- Place builders in a proper subject file, either one that exists or in a new + one with a good name. A common developer should be able to find the relevant + content based on the file name. + - Do **not** fill up the `vmi.go` file, unless you have a very good reason. +- Do **not** add logic in builders, unless they fell into these categories: + - It is a factory that is widely used. (e.g. amount of memory depending on + the architecture). + - It is a widely used abstraction that combines several fields that have some + dependency on each other. +- Any builder can be added if it has no logic (except for lazy creation of the + path to the relevant fields). + In practice, this implies that even builders that are used by a small amount + of callers can be added, as long as they do not possess logic. +- Do **not** add commands on objects, e.g. calls through clients. +- Building general annotations or labels which have special meaning are a fit + if they related to VM or VMI objects. + To clarify, annotation that relate to pods, are not a perfect fit here. + Annotations that relate to a specific subject (e.g. network) may fit here + or under a more dedicated library/package. + +> **Note**: A builder is considered `widely used` when it is needed from multiple +> packages. In case a single package or test file is using it, it may fit better +> under that package or test. The reason is simple, it has more context closer to +> the usage and known by the developers in a more accurate manner. + +### Structure + +- VMI: `vmi.go` contains the most basic tooling to start building VMI manifests. + It contains the most basic factory (`New`), the definition of how the builders + look like and any other helper that serves the whole libvmi package. +- Subject builders: Various files in which builders and defined. + These files should group builders with some commonality, such that they + can be easily found. With time, the grouping and naming may change + (e.g. subjects split when they grow too much). + +## Maintenance and Ownership + +Maintainers are expected to follow the above rules or ask for exceptions. +Possibly asking to change a rule with a good reasoning. + +Make sure to always keep things in focus, simple and clean. +When things get out of control, things get slower, not faster. diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/cloudinit.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/cloudinit.go new file mode 100644 index 000000000..ef05426fe --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/cloudinit.go @@ -0,0 +1,118 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2020 Red Hat, Inc. + * + */ + +package libvmi + +import ( + "encoding/base64" + + k8scorev1 "k8s.io/api/core/v1" + + v1 "kubevirt.io/api/core/v1" +) + +const cloudInitDiskName = "disk1" + +// WithCloudInitNoCloudUserData adds cloud-init no-cloud user data. +func WithCloudInitNoCloudUserData(data string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDiskVolumeWithCloudInitNoCloud(vmi, cloudInitDiskName, v1.DiskBusVirtio) + + volume := getVolume(vmi, cloudInitDiskName) + volume.CloudInitNoCloud.UserData = data + volume.CloudInitNoCloud.UserDataBase64 = "" + } +} + +// WithCloudInitNoCloudEncodedUserData adds cloud-init no-cloud base64-encoded user data +func WithCloudInitNoCloudEncodedUserData(data string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDiskVolumeWithCloudInitNoCloud(vmi, cloudInitDiskName, v1.DiskBusVirtio) + + volume := getVolume(vmi, cloudInitDiskName) + encodedData := base64.StdEncoding.EncodeToString([]byte(data)) + volume.CloudInitNoCloud.UserData = "" + volume.CloudInitNoCloud.UserDataBase64 = encodedData + } +} + +// WithCloudInitNoCloudNetworkData adds cloud-init no-cloud network data. +func WithCloudInitNoCloudNetworkData(data string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDiskVolumeWithCloudInitNoCloud(vmi, cloudInitDiskName, v1.DiskBusVirtio) + + volume := getVolume(vmi, cloudInitDiskName) + volume.CloudInitNoCloud.NetworkData = data + volume.CloudInitNoCloud.NetworkDataBase64 = "" + volume.CloudInitNoCloud.NetworkDataSecretRef = nil + } +} + +// WithCloudInitNoCloudEncodedNetworkData adds cloud-init no-cloud base64 encoded network data. +func WithCloudInitNoCloudEncodedNetworkData(networkData string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDiskVolumeWithCloudInitNoCloud(vmi, cloudInitDiskName, v1.DiskBusVirtio) + + volume := getVolume(vmi, cloudInitDiskName) + volume.CloudInitNoCloud.NetworkDataBase64 = base64.StdEncoding.EncodeToString([]byte(networkData)) + volume.CloudInitNoCloud.NetworkData = "" + volume.CloudInitNoCloud.NetworkDataSecretRef = nil + } +} + +// WithCloudInitNoCloudNetworkDataSecretName adds cloud-init no-cloud network data from secret. +func WithCloudInitNoCloudNetworkDataSecretName(secretName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDiskVolumeWithCloudInitNoCloud(vmi, cloudInitDiskName, v1.DiskBusVirtio) + + volume := getVolume(vmi, cloudInitDiskName) + volume.CloudInitNoCloud.NetworkDataSecretRef = &k8scorev1.LocalObjectReference{Name: secretName} + volume.CloudInitNoCloud.NetworkData = "" + volume.CloudInitNoCloud.NetworkDataBase64 = "" + } +} + +// WithCloudInitConfigDriveUserData adds cloud-init config-drive user data. +func WithCloudInitConfigDriveUserData(data string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDiskVolumeWithCloudInitConfigDrive(vmi, cloudInitDiskName, v1.DiskBusVirtio) + + volume := getVolume(vmi, cloudInitDiskName) + volume.CloudInitConfigDrive.UserData = data + volume.CloudInitConfigDrive.UserDataBase64 = "" + } +} + +func addDiskVolumeWithCloudInitConfigDrive(vmi *v1.VirtualMachineInstance, diskName string, bus v1.DiskBus) { + addDisk(vmi, newDisk(diskName, bus)) + v := newVolume(diskName) + v.VolumeSource = v1.VolumeSource{CloudInitConfigDrive: &v1.CloudInitConfigDriveSource{}} + addVolume(vmi, v) +} + +func addDiskVolumeWithCloudInitNoCloud(vmi *v1.VirtualMachineInstance, diskName string, bus v1.DiskBus) { + addDisk(vmi, newDisk(diskName, bus)) + v := newVolume(diskName) + setCloudInitNoCloud(&v, &v1.CloudInitNoCloudSource{}) + addVolume(vmi, v) +} + +func setCloudInitNoCloud(volume *v1.Volume, source *v1.CloudInitNoCloudSource) { + volume.VolumeSource = v1.VolumeSource{CloudInitNoCloud: source} +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/config.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/config.go new file mode 100644 index 000000000..ded50fa2e --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/config.go @@ -0,0 +1,154 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2018 Red Hat, Inc. + * + */ + +package libvmi + +import ( + k8sv1 "k8s.io/api/core/v1" + v1 "kubevirt.io/api/core/v1" +) + +func WithSecretDisk(secretName, volumeName string) Option { + return WithLabelledSecretDisk(secretName, volumeName, "") +} + +func WithLabelledSecretDisk(secretName, volumeName, label string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, newSecretVolume(secretName, volumeName, label)) + vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{ + Name: volumeName, + }) + } +} + +func WithConfigMapDisk(configMapName, volumeName string) Option { + return WithLabelledConfigMapDisk(configMapName, volumeName, "") +} + +func WithLabelledConfigMapDisk(configMapName, volumeName, label string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, newConfigMapVolume(configMapName, volumeName, label)) + vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{ + Name: volumeName, + }) + } +} + +func WithServiceAccountDisk(name string) Option { + return func(vmi *v1.VirtualMachineInstance) { + const volumeSuffix = "-disk" + vmi.Spec.Volumes = append(vmi.Spec.Volumes, newServiceAccountVolume(name, name+volumeSuffix)) + vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{ + Name: name + volumeSuffix, + }) + } +} + +func WithDownwardAPIDisk(name string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, newDownwardAPIVolume(name)) + vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{ + Name: name, + }) + } +} + +func WithConfigMapFs(configMapName, volumeName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, newConfigMapVolume(configMapName, volumeName, "")) + vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, newVirtiofsFilesystem(volumeName)) + } +} + +func WithSecretFs(secretName, volumeName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, newSecretVolume(secretName, volumeName, "")) + vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, newVirtiofsFilesystem(volumeName)) + } +} + +func WithServiceAccountFs(serviceAccountName, volumeName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, newServiceAccountVolume(serviceAccountName, volumeName)) + vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, newVirtiofsFilesystem(volumeName)) + } +} + +func WithDownwardAPIFs(name string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, newDownwardAPIVolume(name)) + vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, newVirtiofsFilesystem(name)) + } +} + +func newSecretVolume(secretName, volumeName, label string) v1.Volume { + return v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: secretName, + VolumeLabel: label, + }, + }, + } +} + +func newConfigMapVolume(configMapName, volumeName, label string) v1.Volume { + return v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{ + ConfigMap: &v1.ConfigMapVolumeSource{ + LocalObjectReference: k8sv1.LocalObjectReference{ + Name: configMapName, + }, + VolumeLabel: label, + }, + }, + } +} + +func newServiceAccountVolume(serviceAccountName, volumeName string) v1.Volume { + return v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{ + ServiceAccount: &v1.ServiceAccountVolumeSource{ + ServiceAccountName: serviceAccountName, + }, + }, + } +} + +func newDownwardAPIVolume(name string) v1.Volume { + return v1.Volume{ + Name: name, + VolumeSource: v1.VolumeSource{ + DownwardAPI: &v1.DownwardAPIVolumeSource{ + Fields: []k8sv1.DownwardAPIVolumeFile{ + { + Path: "labels", + FieldRef: &k8sv1.ObjectFieldSelector{ + FieldPath: "metadata.labels", + }, + }, + }, + VolumeLabel: "", + }, + }, + } +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/cpu.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/cpu.go new file mode 100644 index 000000000..1081901bb --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/cpu.go @@ -0,0 +1,77 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2022 Red Hat, Inc. + * + */ + +package libvmi + +import ( + v1 "kubevirt.io/api/core/v1" +) + +func WithCPUCount(cores, threads, sockets uint32) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.CPU == nil { + vmi.Spec.Domain.CPU = &v1.CPU{} + } + vmi.Spec.Domain.CPU.Cores = cores + vmi.Spec.Domain.CPU.Threads = threads + vmi.Spec.Domain.CPU.Sockets = sockets + } +} + +func WithCPUModel(model string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.CPU == nil { + vmi.Spec.Domain.CPU = &v1.CPU{} + } + vmi.Spec.Domain.CPU.Model = model + } +} + +func WithDedicatedCPUPlacement() Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.CPU == nil { + vmi.Spec.Domain.CPU = &v1.CPU{} + } + vmi.Spec.Domain.CPU.DedicatedCPUPlacement = true + } +} + +func WithRealtimeMask(realtimeMask string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.CPU == nil { + vmi.Spec.Domain.CPU = &v1.CPU{} + } + vmi.Spec.Domain.CPU.Realtime = &v1.Realtime{Mask: realtimeMask} + } +} + +func WithNUMAGuestMappingPassthrough() Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.CPU == nil { + vmi.Spec.Domain.CPU = &v1.CPU{} + } + vmi.Spec.Domain.CPU.NUMA = &v1.NUMA{GuestMappingPassthrough: &v1.NUMAGuestMappingPassthrough{}} + } +} + +func WithArchitecture(arch string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Architecture = arch + } +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/memory.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/memory.go new file mode 100644 index 000000000..0ae097e8e --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/memory.go @@ -0,0 +1,54 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2022 Red Hat, Inc. + * + */ + +package libvmi + +import ( + "k8s.io/apimachinery/pkg/api/resource" + v1 "kubevirt.io/api/core/v1" +) + +func WithHugepages(pageSize string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.Memory == nil { + vmi.Spec.Domain.Memory = &v1.Memory{} + } + vmi.Spec.Domain.Memory.Hugepages = &v1.Hugepages{PageSize: pageSize} + } +} + +func WithGuestMemory(memory string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.Memory == nil { + vmi.Spec.Domain.Memory = &v1.Memory{} + } + quantity := resource.MustParse(memory) + vmi.Spec.Domain.Memory.Guest = &quantity + } +} + +func WithMaxGuest(memory string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.Memory == nil { + vmi.Spec.Domain.Memory = &v1.Memory{} + } + quantity := resource.MustParse(memory) + vmi.Spec.Domain.Memory.MaxGuest = &quantity + } +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/network.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/network.go new file mode 100644 index 000000000..a1e090dfe --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/network.go @@ -0,0 +1,134 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2020 Red Hat, Inc. + * + */ + +package libvmi + +import ( + kvirtv1 "kubevirt.io/api/core/v1" +) + +// WithInterface adds a Domain Device Interface. +func WithInterface(iface kvirtv1.Interface) Option { + return func(vmi *kvirtv1.VirtualMachineInstance) { + vmi.Spec.Domain.Devices.Interfaces = append( + vmi.Spec.Domain.Devices.Interfaces, iface, + ) + } +} + +// WithNetwork adds a network object. +func WithNetwork(network *kvirtv1.Network) Option { + return func(vmi *kvirtv1.VirtualMachineInstance) { + vmi.Spec.Networks = append(vmi.Spec.Networks, *network) + } +} + +func WithPasstInterfaceWithPort() Option { + return WithInterface(InterfaceWithPasstBindingPlugin([]kvirtv1.Port{{Port: 1234, Protocol: "TCP"}}...)) +} + +// InterfaceDeviceWithMasqueradeBinding returns an Interface named "default" with masquerade binding. +func InterfaceDeviceWithMasqueradeBinding(ports ...kvirtv1.Port) kvirtv1.Interface { + return kvirtv1.Interface{ + Name: kvirtv1.DefaultPodNetwork().Name, + InterfaceBindingMethod: kvirtv1.InterfaceBindingMethod{ + Masquerade: &kvirtv1.InterfaceMasquerade{}, + }, + Ports: ports, + } +} + +// InterfaceDeviceWithBridgeBinding returns an Interface with bridge binding. +func InterfaceDeviceWithBridgeBinding(name string) kvirtv1.Interface { + return kvirtv1.Interface{ + Name: name, + InterfaceBindingMethod: kvirtv1.InterfaceBindingMethod{ + Bridge: &kvirtv1.InterfaceBridge{}, + }, + } +} + +// InterfaceDeviceWithSRIOVBinding returns an Interface with SRIOV binding. +func InterfaceDeviceWithSRIOVBinding(name string) kvirtv1.Interface { + return kvirtv1.Interface{ + Name: name, + InterfaceBindingMethod: kvirtv1.InterfaceBindingMethod{ + SRIOV: &kvirtv1.InterfaceSRIOV{}, + }, + } +} + +// InterfaceWithPasstBinding returns an Interface named "default" with passt binding plugin. +func InterfaceWithPasstBindingPlugin(ports ...kvirtv1.Port) kvirtv1.Interface { + const passtBindingName = "passt" + return kvirtv1.Interface{ + Name: kvirtv1.DefaultPodNetwork().Name, + Binding: &kvirtv1.PluginBinding{Name: passtBindingName}, + Ports: ports, + } +} + +// InterfaceWithMacvtapBindingPlugin returns an Interface named "default" with "macvtap" binding plugin. +func InterfaceWithMacvtapBindingPlugin(name string) *kvirtv1.Interface { + const macvtapBindingName = "macvtap" + return &kvirtv1.Interface{ + Name: name, + Binding: &kvirtv1.PluginBinding{Name: macvtapBindingName}, + } +} + +func InterfaceWithBindingPlugin(name string, binding kvirtv1.PluginBinding, ports ...kvirtv1.Port) kvirtv1.Interface { + return kvirtv1.Interface{ + Name: name, + Binding: &binding, + Ports: ports, + } +} + +// InterfaceWithMac decorates an existing Interface with a MAC address. +func InterfaceWithMac(iface *kvirtv1.Interface, macAddress string) *kvirtv1.Interface { + iface.MacAddress = macAddress + return iface +} + +// MultusNetwork returns a Network with the given name, associated to the given nad +func MultusNetwork(name, nadName string) *kvirtv1.Network { + return &kvirtv1.Network{ + Name: name, + NetworkSource: kvirtv1.NetworkSource{ + Multus: &kvirtv1.MultusNetwork{ + NetworkName: nadName, + }, + }, + } +} + +// WithHostname sets the hostname parameter. +func WithHostname(hostname string) Option { + return func(vmi *kvirtv1.VirtualMachineInstance) { + vmi.Spec.Hostname = hostname + } +} + +// WithSubdomain sets the subdomain parameter. +func WithSubdomain(subdomain string) Option { + return func(vmi *kvirtv1.VirtualMachineInstance) { + vmi.Spec.Subdomain = subdomain + } +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/selector.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/selector.go new file mode 100644 index 000000000..69b9466c4 --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/selector.go @@ -0,0 +1,103 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright the KubeVirt Authors. + * + */ + +package libvmi + +import ( + k8sv1 "k8s.io/api/core/v1" + v1 "kubevirt.io/api/core/v1" +) + +// WithNodeSelectorFor ensures that the VMI gets scheduled on the specified node +func WithNodeSelectorFor(node *k8sv1.Node) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.NodeSelector == nil { + vmi.Spec.NodeSelector = map[string]string{} + } + vmi.Spec.NodeSelector[k8sv1.LabelHostname] = node.Name + } +} + +func WithNodeAffinityFor(nodeName string) Option { + return WithNodeAffinityForLabel(k8sv1.LabelHostname, nodeName) +} + +func WithNodeAffinityForLabel(nodeLabelKey, nodeLabelValue string) Option { + return func(vmi *v1.VirtualMachineInstance) { + nodeSelectorTerm := k8sv1.NodeSelectorTerm{ + MatchExpressions: []k8sv1.NodeSelectorRequirement{ + {Key: nodeLabelKey, Operator: k8sv1.NodeSelectorOpIn, Values: []string{nodeLabelValue}}, + }, + } + + if vmi.Spec.Affinity == nil { + vmi.Spec.Affinity = &k8sv1.Affinity{} + } + + if vmi.Spec.Affinity.NodeAffinity == nil { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{} + } + + if vmi.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil { + vmi.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = &k8sv1.NodeSelector{} + } + + if vmi.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms == nil { + vmi.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = []k8sv1.NodeSelectorTerm{} + } + + vmi.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = append( + vmi.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms, + nodeSelectorTerm, + ) + } +} + +func WithPreferredPodAffinity(term k8sv1.WeightedPodAffinityTerm) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Affinity == nil { + vmi.Spec.Affinity = &k8sv1.Affinity{} + } + + if vmi.Spec.Affinity.PodAffinity == nil { + vmi.Spec.Affinity.PodAffinity = &k8sv1.PodAffinity{} + } + + vmi.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append( + vmi.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution, term, + ) + } +} + +func WithPreferredNodeAffinity(term k8sv1.PreferredSchedulingTerm) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Affinity == nil { + vmi.Spec.Affinity = &k8sv1.Affinity{} + } + + if vmi.Spec.Affinity.NodeAffinity == nil { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{} + } + + vmi.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append( + vmi.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution, + term, + ) + } +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/storage.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/storage.go new file mode 100644 index 000000000..7559a20ff --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/storage.go @@ -0,0 +1,296 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2020 Red Hat, Inc. + * + */ + +package libvmi + +import ( + "fmt" + + k8sv1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + v1 "kubevirt.io/api/core/v1" +) + +const ( + defaultDiskSize = "1Gi" +) + +// WithContainerDisk specifies the disk name and the name of the container image to be used. +func WithContainerDisk(diskName, imageName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newDisk(diskName, v1.DiskBusVirtio)) + addVolume(vmi, newContainerVolume(diskName, imageName)) + } +} + +// WithPersistentVolumeClaim specifies the name of the PersistentVolumeClaim to be used. +func WithPersistentVolumeClaim(diskName, pvcName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newDisk(diskName, v1.DiskBusVirtio)) + addVolume(vmi, newPersistentVolumeClaimVolume(diskName, pvcName)) + } +} + +// WithEphemeralPersistentVolumeClaim specifies the name of the Ephemeral.PersistentVolumeClaim to be used. +func WithEphemeralPersistentVolumeClaim(diskName, pvcName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newDisk(diskName, v1.DiskBusSATA)) + addVolume(vmi, newEphemeralPersistentVolumeClaimVolume(diskName, pvcName)) + } +} + +// WithDataVolume specifies the name of the DataVolume to be used. +func WithDataVolume(diskName, pvcName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newDisk(diskName, v1.DiskBusVirtio)) + addVolume(vmi, newDataVolume(diskName, pvcName)) + } +} + +// WithEmptyDisk specifies the name of the EmptyDisk to be used. +func WithEmptyDisk(diskName string, bus v1.DiskBus, capacity resource.Quantity) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newDisk(diskName, bus)) + addVolume(vmi, newEmptyDisk(diskName, capacity)) + } +} + +// WithCDRom specifies a CDRom drive backed by a PVC to be used. +func WithCDRom(cdRomName string, bus v1.DiskBus, claimName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newCDRom(cdRomName, bus)) + addVolume(vmi, newPersistentVolumeClaimVolume(cdRomName, claimName)) + } +} + +// WithEphemeralCDRom specifies a CDRom drive to be used. +func WithEphemeralCDRom(cdRomName string, bus v1.DiskBus, claimName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newCDRom(cdRomName, bus)) + addVolume(vmi, newContainerVolume(cdRomName, claimName)) + } +} + +// WithFilesystemPVC specifies a filesystem backed by a PVC to be used. +func WithFilesystemPVC(claimName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addFilesystem(vmi, newVirtiofsFilesystem(claimName)) + addVolume(vmi, newPersistentVolumeClaimVolume(claimName, claimName)) + } +} + +// WithFilesystemDV specifies a filesystem backed by a DV to be used. +func WithFilesystemDV(dataVolumeName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + addFilesystem(vmi, newVirtiofsFilesystem(dataVolumeName)) + addVolume(vmi, newDataVolume(dataVolumeName, dataVolumeName)) + } +} + +func WithPersistentVolumeClaimLun(diskName, pvcName string, reservation bool) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newLun(diskName, reservation)) + addVolume(vmi, newPersistentVolumeClaimVolume(diskName, pvcName)) + } +} + +func WithHostDisk(diskName, path string, diskType v1.HostDiskType) Option { + return func(vmi *v1.VirtualMachineInstance) { + addDisk(vmi, newDisk(diskName, v1.DiskBusVirtio)) + addVolume(vmi, newHostDisk(diskName, path, diskType)) + } +} + +func addDisk(vmi *v1.VirtualMachineInstance, disk v1.Disk) { + if !diskExists(vmi, disk) { + vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, disk) + } +} + +func addVolume(vmi *v1.VirtualMachineInstance, volume v1.Volume) { + if !volumeExists(vmi, volume) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, volume) + } +} + +func addFilesystem(vmi *v1.VirtualMachineInstance, filesystem v1.Filesystem) { + if filesystemExists(vmi, filesystem) { + panic(fmt.Errorf("filesystem %s already exists", filesystem.Name)) + } + + vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, filesystem) +} + +func getVolume(vmi *v1.VirtualMachineInstance, name string) *v1.Volume { + for i := range vmi.Spec.Volumes { + if vmi.Spec.Volumes[i].Name == name { + return &vmi.Spec.Volumes[i] + } + } + return nil +} + +func diskExists(vmi *v1.VirtualMachineInstance, disk v1.Disk) bool { + for _, d := range vmi.Spec.Domain.Devices.Disks { + if d.Name == disk.Name { + return true + } + } + return false +} + +func volumeExists(vmi *v1.VirtualMachineInstance, volume v1.Volume) bool { + for _, v := range vmi.Spec.Volumes { + if v.Name == volume.Name { + return true + } + } + return false +} + +func filesystemExists(vmi *v1.VirtualMachineInstance, filesystem v1.Filesystem) bool { + for _, f := range vmi.Spec.Domain.Devices.Filesystems { + if f.Name == filesystem.Name { + return true + } + } + return false +} + +func newDisk(name string, bus v1.DiskBus) v1.Disk { + return v1.Disk{ + Name: name, + DiskDevice: v1.DiskDevice{ + Disk: &v1.DiskTarget{ + Bus: bus, + }, + }, + } +} + +func newCDRom(name string, bus v1.DiskBus) v1.Disk { + return v1.Disk{ + Name: name, + DiskDevice: v1.DiskDevice{ + CDRom: &v1.CDRomTarget{ + Bus: bus, + }, + }, + } +} + +func newVirtiofsFilesystem(name string) v1.Filesystem { + return v1.Filesystem{ + Name: name, + Virtiofs: &v1.FilesystemVirtiofs{}, + } +} + +func newLun(name string, reservation bool) v1.Disk { + return v1.Disk{ + Name: name, + DiskDevice: v1.DiskDevice{ + LUN: &v1.LunTarget{ + Bus: v1.DiskBusSCSI, + Reservation: reservation, + }, + }, + } +} + +func newVolume(name string) v1.Volume { + return v1.Volume{Name: name} +} + +func newContainerVolume(name, image string) v1.Volume { + return v1.Volume{ + Name: name, + VolumeSource: v1.VolumeSource{ + ContainerDisk: &v1.ContainerDiskSource{ + Image: image, + }, + }, + } +} + +func newPersistentVolumeClaimVolume(name, claimName string) v1.Volume { + return v1.Volume{ + Name: name, + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + PersistentVolumeClaimVolumeSource: k8sv1.PersistentVolumeClaimVolumeSource{ + ClaimName: claimName, + }, + }, + }, + } +} + +func newEphemeralPersistentVolumeClaimVolume(name, claimName string) v1.Volume { + return v1.Volume{ + Name: name, + VolumeSource: v1.VolumeSource{ + Ephemeral: &v1.EphemeralVolumeSource{ + PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{ + ClaimName: claimName, + }, + }, + }, + } +} + +func newDataVolume(name, dataVolumeName string) v1.Volume { + return v1.Volume{ + Name: name, + VolumeSource: v1.VolumeSource{ + DataVolume: &v1.DataVolumeSource{ + Name: dataVolumeName, + }, + }, + } +} + +func newEmptyDisk(name string, capacity resource.Quantity) v1.Volume { + return v1.Volume{ + Name: name, + VolumeSource: v1.VolumeSource{ + EmptyDisk: &v1.EmptyDiskSource{ + Capacity: capacity, + }, + }, + } +} + +func newHostDisk(name, path string, diskType v1.HostDiskType) v1.Volume { + hostDisk := v1.HostDisk{ + Path: path, + Type: diskType, + } + if diskType == v1.HostDiskExistsOrCreate { + hostDisk.Capacity = resource.MustParse(defaultDiskSize) + } + + return v1.Volume{ + Name: name, + VolumeSource: v1.VolumeSource{ + HostDisk: &hostDisk, + }, + } +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/vm.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/vm.go new file mode 100644 index 000000000..7d798f358 --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/vm.go @@ -0,0 +1,171 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2024 Red Hat, Inc. + * + */ + +package libvmi + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "kubevirt.io/api/core/v1" + instancetypeapi "kubevirt.io/api/instancetype" + "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + + "kubevirt.io/kubevirt/pkg/pointer" +) + +type VMOption func(vm *v1.VirtualMachine) + +func NewVirtualMachine(vmi *v1.VirtualMachineInstance, opts ...VMOption) *v1.VirtualMachine { + vm := &v1.VirtualMachine{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.GroupVersion.String(), + Kind: "VirtualMachine", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vmi.Name, + Namespace: vmi.Namespace, + }, + Spec: v1.VirtualMachineSpec{ + Running: pointer.P(false), + Template: &v1.VirtualMachineInstanceTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: vmi.ObjectMeta.Annotations, + Labels: vmi.ObjectMeta.Labels, + }, + Spec: vmi.Spec, + }, + }, + } + + for _, f := range opts { + f(vm) + } + + return vm +} + +func WithRunning() VMOption { + return func(vm *v1.VirtualMachine) { + vm.Spec.Running = pointer.P(true) + } +} + +func WithDataVolumeTemplate(datavolume *v1beta1.DataVolume) VMOption { + return func(vm *v1.VirtualMachine) { + vm.Spec.DataVolumeTemplates = append(vm.Spec.DataVolumeTemplates, + v1.DataVolumeTemplateSpec{ + ObjectMeta: datavolume.ObjectMeta, + Spec: datavolume.Spec, + }, + ) + } +} + +func resourcesRemovedFromVMI(vmiSpec *v1.VirtualMachineInstanceSpec) { + vmiSpec.Domain.CPU = nil + vmiSpec.Domain.Memory = nil + vmiSpec.Domain.Resources = v1.ResourceRequirements{} +} + +func preferencesRemovedFromVMI(vmiSpec *v1.VirtualMachineInstanceSpec) { + vmiSpec.TerminationGracePeriodSeconds = nil + vmiSpec.Domain.Features = nil + vmiSpec.Domain.Machine = nil + for diskIndex := range vmiSpec.Domain.Devices.Disks { + disk := vmiSpec.Domain.Devices.Disks[diskIndex].DiskDevice.Disk + if disk != nil && disk.Bus != "" { + disk.Bus = "" + } + } +} + +func WithClusterInstancetype(name string) VMOption { + return func(vm *v1.VirtualMachine) { + resourcesRemovedFromVMI(&vm.Spec.Template.Spec) + vm.Spec.Instancetype = &v1.InstancetypeMatcher{ + Name: name, + } + } +} + +func WithClusterPreference(name string) VMOption { + return func(vm *v1.VirtualMachine) { + preferencesRemovedFromVMI(&vm.Spec.Template.Spec) + vm.Spec.Preference = &v1.PreferenceMatcher{ + Name: name, + } + } +} + +func WithInstancetype(name string) VMOption { + return func(vm *v1.VirtualMachine) { + resourcesRemovedFromVMI(&vm.Spec.Template.Spec) + vm.Spec.Instancetype = &v1.InstancetypeMatcher{ + Name: name, + Kind: instancetypeapi.SingularResourceName, + } + } +} + +func WithPreference(name string) VMOption { + return func(vm *v1.VirtualMachine) { + preferencesRemovedFromVMI(&vm.Spec.Template.Spec) + vm.Spec.Preference = &v1.PreferenceMatcher{ + Name: name, + Kind: instancetypeapi.SingularPreferenceResourceName, + } + } +} + +func WithInstancetypeInferredFromVolume(name string) VMOption { + return func(vm *v1.VirtualMachine) { + resourcesRemovedFromVMI(&vm.Spec.Template.Spec) + vm.Spec.Instancetype = &v1.InstancetypeMatcher{ + InferFromVolume: name, + } + } +} + +func WithPreferenceInferredFromVolume(name string) VMOption { + return func(vm *v1.VirtualMachine) { + preferencesRemovedFromVMI(&vm.Spec.Template.Spec) + vm.Spec.Preference = &v1.PreferenceMatcher{ + InferFromVolume: name, + } + } +} + +func WithInstancetypeRevision(revisionName string) VMOption { + return func(vm *v1.VirtualMachine) { + resourcesRemovedFromVMI(&vm.Spec.Template.Spec) + vm.Spec.Instancetype = &v1.InstancetypeMatcher{ + Name: "unused", + RevisionName: revisionName, + } + } +} + +func WithPreferenceRevision(revisionName string) VMOption { + return func(vm *v1.VirtualMachine) { + preferencesRemovedFromVMI(&vm.Spec.Template.Spec) + vm.Spec.Preference = &v1.PreferenceMatcher{ + Name: "unused", + RevisionName: revisionName, + } + } +} diff --git a/vendor/kubevirt.io/kubevirt/pkg/libvmi/vmi.go b/vendor/kubevirt.io/kubevirt/pkg/libvmi/vmi.go new file mode 100644 index 000000000..5f96ac61f --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/pkg/libvmi/vmi.go @@ -0,0 +1,279 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2020 Red Hat, Inc. + * + */ + +package libvmi + +import ( + k8sv1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + v1 "kubevirt.io/api/core/v1" + + "kubevirt.io/kubevirt/pkg/pointer" +) + +// Option represents an action that enables an option. +type Option func(vmi *v1.VirtualMachineInstance) + +// New instantiates a new VMI configuration, +// building its properties based on the specified With* options. +func New(opts ...Option) *v1.VirtualMachineInstance { + vmi := baseVmi(randName()) + + WithTerminationGracePeriod(0)(vmi) + for _, f := range opts { + f(vmi) + } + + return vmi +} + +var defaultOptions []Option + +func RegisterDefaultOption(opt Option) { + defaultOptions = append(defaultOptions, opt) +} + +// randName returns a random name for a virtual machine +func randName() string { + const randomPostfixLen = 5 + return "testvmi" + "-" + rand.String(randomPostfixLen) +} + +// WithLabel sets a label with specified value +func WithLabel(key, value string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Labels == nil { + vmi.Labels = map[string]string{} + } + vmi.Labels[key] = value + } +} + +// WithAnnotation adds an annotation with specified value +func WithAnnotation(key, value string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Annotations == nil { + vmi.Annotations = map[string]string{} + } + vmi.Annotations[key] = value + } +} + +func WithNamespace(namespace string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Namespace = namespace + } +} + +// WithTerminationGracePeriod specifies the termination grace period in seconds. +func WithTerminationGracePeriod(seconds int64) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.TerminationGracePeriodSeconds = &seconds + } +} + +// WithRng adds `rng` to the vmi devices. +func WithRng() Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Domain.Devices.Rng = &v1.Rng{} + } +} + +// WithWatchdog adds a watchdog to the vmi devices. +func WithWatchdog(action v1.WatchdogAction) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Domain.Devices.Watchdog = &v1.Watchdog{ + Name: "watchdog", + WatchdogDevice: v1.WatchdogDevice{ + I6300ESB: &v1.I6300ESBWatchdog{ + Action: action, + }, + }, + } + } +} + +// WithResourceMemory specifies the vmi memory resource. +func WithResourceMemory(value string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.Resources.Requests == nil { + vmi.Spec.Domain.Resources.Requests = k8sv1.ResourceList{} + } + vmi.Spec.Domain.Resources.Requests[k8sv1.ResourceMemory] = resource.MustParse(value) + } +} + +// WithResourceCPU specifies the vmi CPU resource. +func WithResourceCPU(value string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.Resources.Requests == nil { + vmi.Spec.Domain.Resources.Requests = k8sv1.ResourceList{} + } + vmi.Spec.Domain.Resources.Requests[k8sv1.ResourceCPU] = resource.MustParse(value) + } +} + +// WithLimitMemory specifies the VMI memory limit. +func WithLimitMemory(value string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.Resources.Limits == nil { + vmi.Spec.Domain.Resources.Limits = k8sv1.ResourceList{} + } + vmi.Spec.Domain.Resources.Limits[k8sv1.ResourceMemory] = resource.MustParse(value) + } +} + +// WithLimitCPU specifies the VMI CPU limit. +func WithLimitCPU(value string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.Resources.Limits == nil { + vmi.Spec.Domain.Resources.Limits = k8sv1.ResourceList{} + } + vmi.Spec.Domain.Resources.Limits[k8sv1.ResourceCPU] = resource.MustParse(value) + } +} + +func WithDownwardMetricsVolume(volumeName string) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Volumes = append(vmi.Spec.Volumes, v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{ + DownwardMetrics: &v1.DownwardMetricsVolumeSource{}, + }, + }) + + vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{ + Name: volumeName, + DiskDevice: v1.DiskDevice{ + Disk: &v1.DiskTarget{ + Bus: v1.DiskBusVirtio, + }, + }, + }) + } +} + +func WithDownwardMetricsChannel() Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Domain.Devices.DownwardMetrics = &v1.DownwardMetrics{} + } +} + +// WithUefi configures EFI bootloader and SecureBoot. +func WithUefi(secureBoot bool) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.Firmware == nil { + vmi.Spec.Domain.Firmware = &v1.Firmware{} + } + if vmi.Spec.Domain.Firmware.Bootloader == nil { + vmi.Spec.Domain.Firmware.Bootloader = &v1.Bootloader{} + } + if vmi.Spec.Domain.Firmware.Bootloader.EFI == nil { + vmi.Spec.Domain.Firmware.Bootloader.EFI = &v1.EFI{} + } + vmi.Spec.Domain.Firmware.Bootloader.EFI.SecureBoot = pointer.P(secureBoot) + // secureBoot Requires SMM to be enabled + if secureBoot { + if vmi.Spec.Domain.Features == nil { + vmi.Spec.Domain.Features = &v1.Features{} + } + if vmi.Spec.Domain.Features.SMM == nil { + vmi.Spec.Domain.Features.SMM = &v1.FeatureState{} + } + vmi.Spec.Domain.Features.SMM.Enabled = pointer.P(secureBoot) + } + } +} + +// WithSEV adds `launchSecurity` with `sev`. +func WithSEV(isESEnabled bool) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.Domain.LaunchSecurity = &v1.LaunchSecurity{ + SEV: &v1.SEV{ + Policy: &v1.SEVPolicy{ + EncryptedState: &isESEnabled, + }, + }, + } + } +} + +func WithSEVAttestation() Option { + return func(vmi *v1.VirtualMachineInstance) { + startStrategy := v1.StartStrategyPaused + vmi.Spec.StartStrategy = &startStrategy + if vmi.Spec.Domain.LaunchSecurity == nil { + vmi.Spec.Domain.LaunchSecurity = &v1.LaunchSecurity{} + } + if vmi.Spec.Domain.LaunchSecurity.SEV == nil { + vmi.Spec.Domain.LaunchSecurity.SEV = &v1.SEV{} + } + vmi.Spec.Domain.LaunchSecurity.SEV.Attestation = &v1.SEVAttestation{} + } +} + +func WithCPUFeature(featureName, policy string) Option { + return func(vmi *v1.VirtualMachineInstance) { + if vmi.Spec.Domain.CPU == nil { + vmi.Spec.Domain.CPU = &v1.CPU{} + } + + vmi.Spec.Domain.CPU.Features = append(vmi.Spec.Domain.CPU.Features, v1.CPUFeature{ + Name: featureName, + Policy: policy, + }) + } +} + +func WithEvictionStrategy(evictionStrategy v1.EvictionStrategy) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.EvictionStrategy = &evictionStrategy + } +} + +func WithStartStrategy(startStrategy v1.StartStrategy) Option { + return func(vmi *v1.VirtualMachineInstance) { + vmi.Spec.StartStrategy = &startStrategy + } +} + +func WithoutSerialConsole() Option { + return func(vmi *v1.VirtualMachineInstance) { + enabled := false + vmi.Spec.Domain.Devices.AutoattachSerialConsole = &enabled + } +} + +func baseVmi(name string) *v1.VirtualMachineInstance { + vmi := v1.NewVMIReferenceFromNameWithNS("", name) + vmi.Spec = v1.VirtualMachineInstanceSpec{Domain: v1.DomainSpec{}} + vmi.TypeMeta = k8smetav1.TypeMeta{ + APIVersion: v1.GroupVersion.String(), + Kind: "VirtualMachineInstance", + } + + for _, opt := range defaultOptions { + opt(vmi) + } + + return vmi +} diff --git a/vendor/kubevirt.io/kubevirt/tests/containerdisk/BUILD.bazel b/vendor/kubevirt.io/kubevirt/tests/containerdisk/BUILD.bazel new file mode 100644 index 000000000..d86483e1f --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/tests/containerdisk/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["containerdisk.go"], + importpath = "kubevirt.io/kubevirt/tests/containerdisk", + visibility = ["//visibility:public"], + deps = ["//tests/flags:go_default_library"], +) diff --git a/vendor/kubevirt.io/kubevirt/tests/containerdisk/containerdisk.go b/vendor/kubevirt.io/kubevirt/tests/containerdisk/containerdisk.go new file mode 100644 index 000000000..a35510a1c --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/tests/containerdisk/containerdisk.go @@ -0,0 +1,82 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2020 Red Hat, Inc. + * + */ + +package tests + +import ( + "fmt" + + "kubevirt.io/kubevirt/tests/flags" +) + +type ContainerDisk string + +const ( + ContainerDiskCirrosCustomLocation ContainerDisk = "cirros-custom" + ContainerDiskCirros ContainerDisk = "cirros" + ContainerDiskAlpine ContainerDisk = "alpine" + ContainerDiskAlpineTestTooling ContainerDisk = "alpine-with-test-tooling" + ContainerDiskFedoraTestTooling ContainerDisk = "fedora-with-test-tooling" + ContainerDiskVirtio ContainerDisk = "virtio-container-disk" + ContainerDiskEmpty ContainerDisk = "empty" + ContainerDiskFedoraRealtime ContainerDisk = "fedora-realtime" +) + +const ( + FedoraVolumeSize = "6Gi" + CirrosVolumeSize = "512Mi" + AlpineVolumeSize = "512Mi" + BlankVolumeSize = "16Mi" +) + +// ContainerDiskFor takes the name of an image and returns the full +// registry diks image path. +// Use the ContainerDisk* constants as input values. +func ContainerDiskFor(name ContainerDisk) string { + return ContainerDiskFromRegistryFor(flags.KubeVirtUtilityRepoPrefix, name) +} + +func DataVolumeImportUrlForContainerDisk(name ContainerDisk) string { + return DataVolumeImportUrlFromRegistryForContainerDisk(flags.KubeVirtUtilityRepoPrefix, name) +} + +func DataVolumeImportUrlFromRegistryForContainerDisk(registry string, name ContainerDisk) string { + return fmt.Sprintf("docker://%s", ContainerDiskFromRegistryFor(registry, name)) +} + +func ContainerDiskFromRegistryFor(registry string, name ContainerDisk) string { + switch name { + case ContainerDiskCirros, ContainerDiskAlpine, ContainerDiskCirrosCustomLocation: + return fmt.Sprintf("%s/%s-container-disk-demo:%s", registry, name, flags.KubeVirtUtilityVersionTag) + case ContainerDiskVirtio: + return fmt.Sprintf("%s/virtio-container-disk:%s", registry, flags.KubeVirtUtilityVersionTag) + case ContainerDiskFedoraTestTooling, ContainerDiskFedoraRealtime, ContainerDiskAlpineTestTooling: + return fmt.Sprintf("%s/%s-container-disk:%s", registry, name, flags.KubeVirtUtilityVersionTag) + } + panic(fmt.Sprintf("Unsupported registry disk %s", name)) +} + +func ContainerDiskSizeBySourceURL(url string) string { + if url == DataVolumeImportUrlForContainerDisk(ContainerDiskFedoraTestTooling) || + url == DataVolumeImportUrlForContainerDisk(ContainerDiskFedoraRealtime) { + return FedoraVolumeSize + } + + return CirrosVolumeSize +} diff --git a/vendor/kubevirt.io/kubevirt/tests/flags/BUILD.bazel b/vendor/kubevirt.io/kubevirt/tests/flags/BUILD.bazel new file mode 100644 index 000000000..29116fb07 --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/tests/flags/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["flags.go"], + importpath = "kubevirt.io/kubevirt/tests/flags", + visibility = ["//visibility:public"], + deps = ["//staging/src/kubevirt.io/client-go/kubecli:go_default_library"], +) diff --git a/vendor/kubevirt.io/kubevirt/tests/flags/flags.go b/vendor/kubevirt.io/kubevirt/tests/flags/flags.go new file mode 100644 index 000000000..bf163ac5d --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/tests/flags/flags.go @@ -0,0 +1,121 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2020 Red Hat, Inc. + * + */ + +package flags + +import ( + "flag" + "os" + + "kubevirt.io/client-go/kubecli" +) + +var KubeVirtUtilityVersionTag = "" +var KubeVirtVersionTag = "latest" +var KubeVirtVersionTagAlt = "" +var KubeVirtUtilityRepoPrefix = "" +var KubeVirtRepoPrefix = "quay.io/kubevirt" +var ImagePrefixAlt = "" +var ContainerizedDataImporterNamespace = "cdi" +var KubeVirtKubectlPath = "" +var KubeVirtOcPath = "" +var KubeVirtVirtctlPath = "" +var KubeVirtExampleGuestAgentPath = "" +var KubeVirtGoCliPath = "" +var KubeVirtInstallNamespace string +var PreviousReleaseTag = "" +var PreviousReleaseRegistry = "" +var PreviousUtilityRegistry = "" +var PreviousUtilityTag = "" +var ConfigFile = "" +var SkipShasumCheck bool +var SkipDualStackTests bool +var IPV4ConnectivityCheckAddress = "" +var IPV6ConnectivityCheckAddress = "" +var ConnectivityCheckDNS = "" +var ArtifactsDir string +var OperatorManifestPath string +var TestingManifestPath string +var ApplyDefaulte2eConfiguration bool + +var DeployTestingInfrastructureFlag = false +var PathToTestingInfrastrucureManifests = "" +var DNSServiceName = "" +var DNSServiceNamespace = "" + +var MigrationNetworkNIC = "eth1" + +var DisableCustomSELinuxPolicy bool + +func init() { + kubecli.Init() + flag.StringVar(&KubeVirtUtilityVersionTag, "utility-container-tag", "", "Set the image tag or digest to use") + flag.StringVar(&KubeVirtVersionTag, "container-tag", "latest", "Set the image tag or digest to use") + flag.StringVar(&KubeVirtVersionTagAlt, "container-tag-alt", "", "An alternate tag that can be used to test operator deployments") + flag.StringVar(&KubeVirtUtilityRepoPrefix, "utility-container-prefix", "", "Set the repository prefix for all images") + flag.StringVar(&KubeVirtRepoPrefix, "container-prefix", KubeVirtRepoPrefix, "Set the repository prefix for all images") + flag.StringVar(&ImagePrefixAlt, "image-prefix-alt", "", "Optional prefix for virt-* image names for additional imagePrefix operator test") + flag.StringVar(&ContainerizedDataImporterNamespace, "cdi-namespace", "cdi", "Set the repository prefix for CDI components") + flag.StringVar(&KubeVirtKubectlPath, "kubectl-path", "", "Set path to kubectl binary") + flag.StringVar(&KubeVirtOcPath, "oc-path", "", "Set path to oc binary") + flag.StringVar(&KubeVirtVirtctlPath, "virtctl-path", "", "Set path to virtctl binary") + flag.StringVar(&KubeVirtExampleGuestAgentPath, "example-guest-agent-path", "", "Set path to the example-guest-agent binary which is used for vsock testing") + flag.StringVar(&KubeVirtGoCliPath, "gocli-path", "", "Set path to gocli binary") + flag.StringVar(&KubeVirtInstallNamespace, "installed-namespace", "", "Set the namespace KubeVirt is installed in") + flag.BoolVar(&DeployTestingInfrastructureFlag, "deploy-testing-infra", false, "Deploy testing infrastructure if set") + flag.StringVar(&PathToTestingInfrastrucureManifests, "path-to-testing-infra-manifests", "manifests/testing", "Set path to testing infrastructure manifests") + flag.StringVar(&PreviousReleaseTag, "previous-release-tag", "", "Set tag of the release to test updating from") + flag.StringVar(&PreviousReleaseRegistry, "previous-release-registry", "quay.io/kubevirt", "Set registry of the release to test updating from") + flag.StringVar(&PreviousUtilityRegistry, "previous-utility-container-registry", "", "Set registry of the utility containers to test updating from") + flag.StringVar(&PreviousUtilityTag, "previous-utility-container-tag", "", "Set tag of the utility containers to test updating from") + flag.StringVar(&ConfigFile, "config", "tests/default-config.json", "Path to a JSON formatted file from which the test suite will load its configuration. The path may be absolute or relative; relative paths start at the current working directory.") + flag.StringVar(&ArtifactsDir, "artifacts", os.Getenv("ARTIFACTS"), "Directory for storing reporter artifacts like junit files or logs") + flag.StringVar(&OperatorManifestPath, "operator-manifest-path", "", "Set path to virt-operator manifest file") + flag.StringVar(&TestingManifestPath, "testing-manifest-path", "", "Set path to testing manifests directory") + flag.BoolVar(&SkipShasumCheck, "skip-shasums-check", false, "Skip tests with sha sums.") + flag.BoolVar(&SkipDualStackTests, "skip-dual-stack-test", false, "Skip test that actively checks for the presence of IPv6 address in the cluster pods.") + flag.StringVar(&IPV4ConnectivityCheckAddress, "conn-check-ipv4-address", "", "Address that is used for testing IPV4 connectivity to the outside world") + flag.StringVar(&IPV6ConnectivityCheckAddress, "conn-check-ipv6-address", "", "Address that is used for testing IPV6 connectivity to the outside world") + flag.StringVar(&ConnectivityCheckDNS, "conn-check-dns", "", "dns that is used for testing connectivity to the outside world") + flag.BoolVar(&ApplyDefaulte2eConfiguration, "apply-default-e2e-configuration", false, "Apply the default e2e test configuration (feature gates, selinux contexts, ...)") + flag.StringVar(&DNSServiceName, "dns-service-name", "kube-dns", "cluster DNS service name") + flag.StringVar(&DNSServiceNamespace, "dns-service-namespace", "kube-system", "cluster DNS service namespace") + flag.StringVar(&MigrationNetworkNIC, "migration-network-nic", "eth1", "NIC to use on cluster nodes to access the dedicated migration network") + flag.BoolVar(&DisableCustomSELinuxPolicy, "disable-custom-selinux-policy", false, "disables the installation and use of the custom SELinux policy for virt-launcher") +} + +func NormalizeFlags() { + // When the flags are not provided, copy the values from normal version tag and prefix + if KubeVirtUtilityVersionTag == "" { + KubeVirtUtilityVersionTag = KubeVirtVersionTag + } + + if KubeVirtUtilityRepoPrefix == "" { + KubeVirtUtilityRepoPrefix = KubeVirtRepoPrefix + } + + if PreviousUtilityRegistry == "" { + PreviousUtilityRegistry = PreviousReleaseRegistry + } + + if PreviousUtilityTag == "" { + PreviousUtilityTag = PreviousReleaseTag + } + +} diff --git a/vendor/kubevirt.io/kubevirt/tests/libdv/BUILD.bazel b/vendor/kubevirt.io/kubevirt/tests/libdv/BUILD.bazel new file mode 100644 index 000000000..5bfdd152c --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/tests/libdv/BUILD.bazel @@ -0,0 +1,16 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["dv.go"], + importpath = "kubevirt.io/kubevirt/tests/libdv", + visibility = ["//visibility:public"], + deps = [ + "//tests/containerdisk:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", + "//vendor/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1:go_default_library", + ], +) diff --git a/vendor/kubevirt.io/kubevirt/tests/libdv/dv.go b/vendor/kubevirt.io/kubevirt/tests/libdv/dv.go new file mode 100644 index 000000000..b1e2a64d2 --- /dev/null +++ b/vendor/kubevirt.io/kubevirt/tests/libdv/dv.go @@ -0,0 +1,213 @@ +/* + * This file is part of the KubeVirt project + * + * 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. + * + * Copyright 2022 Red Hat, Inc. + * + */ + +package libdv + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + + cd "kubevirt.io/kubevirt/tests/containerdisk" +) + +const ( + dvRandomNameLength = 12 +) + +// dvOption is an option type for the NewDataVolume function +type dvOption func(*v1beta1.DataVolume) + +// NewDataVolume Set up a new DataVolume with a random name, a namespace and an optional list of options +func NewDataVolume(options ...dvOption) *v1beta1.DataVolume { + name := randName() + dv := &v1beta1.DataVolume{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "cdi.kubevirt.io/v1beta1", + Kind: "DataVolume", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + + for _, option := range options { + option(dv) + } + + return dv +} + +func WithNamespace(namespace string) dvOption { + return func(dv *v1beta1.DataVolume) { + dv.Namespace = namespace + } +} + +func WithName(name string) dvOption { + return func(dv *v1beta1.DataVolume) { + dv.ObjectMeta.Name = name + } +} + +type pvcOption func(*corev1.PersistentVolumeClaimSpec) + +// WithPVC is a dvOption to add a PVCOption spec to the DataVolume +// The function receives an optional list of pvcOption, to override the defaults +// +// The default values are: +// * no storage class +// * access mode of ReadWriteOnce +// * volume size of cd.CirrosVolumeSize +// * no volume mode. kubernetes default is PersistentVolumeFilesystem +func WithPVC(options ...pvcOption) dvOption { + pvc := &corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + "storage": resource.MustParse(cd.CirrosVolumeSize), + }, + }, + } + + for _, opt := range options { + opt(pvc) + } + + return func(dv *v1beta1.DataVolume) { + dv.Spec.PVC = pvc + } +} + +// withSource is a dvOption to add a DataVolumeSource to the DataVolume +func withSource(s v1beta1.DataVolumeSource) dvOption { + return func(dv *v1beta1.DataVolume) { + dv.Spec.Source = &s + } +} + +// WithRegistryURLSource is a dvOption to add a DataVolumeSource to the DataVolume, with a registry and a URL +func WithRegistryURLSource(imageURL string) dvOption { + return withSource(v1beta1.DataVolumeSource{ + Registry: &v1beta1.DataVolumeSourceRegistry{ + URL: &imageURL, + }, + }) +} + +// WithRegistryURLSourceAndPullMethod is a dvOption to add a DataVolumeSource to the DataVolume, with a registry and URL + pull method +func WithRegistryURLSourceAndPullMethod(imageURL string, pullMethod v1beta1.RegistryPullMethod) dvOption { + return withSource(v1beta1.DataVolumeSource{ + Registry: &v1beta1.DataVolumeSourceRegistry{ + URL: &imageURL, + PullMethod: &pullMethod, + }, + }) +} + +// WithBlankImageSource is a dvOption to add a blank DataVolumeSource to the DataVolume +func WithBlankImageSource() dvOption { + return withSource(v1beta1.DataVolumeSource{ + Blank: &v1beta1.DataVolumeBlankImage{}, + }) +} + +// WithPVCSource is a dvOption to add a DataVolumeSource to the DataVolume, with a PVC source +func WithPVCSource(namespace, name string) dvOption { + return withSource(v1beta1.DataVolumeSource{ + PVC: &v1beta1.DataVolumeSourcePVC{ + Namespace: namespace, + Name: name, + }, + }) +} + +// WithForceBindAnnotation adds the "cdi.kubevirt.io/storage.bind.immediate.requested" annotation to the DV, +// with the value of "true" +func WithForceBindAnnotation() dvOption { + return func(dv *v1beta1.DataVolume) { + if dv.Annotations == nil { + dv.Annotations = make(map[string]string) + } + dv.Annotations["cdi.kubevirt.io/storage.bind.immediate.requested"] = "true" + } +} + +func randName() string { + return "test-datavolume-" + rand.String(dvRandomNameLength) +} + +// PVC Options + +// PVCWithStorageClass add the sc storage class name to the DV +func PVCWithStorageClass(sc string) pvcOption { + return func(pvc *corev1.PersistentVolumeClaimSpec) { + if pvc == nil { + return + } + + pvc.StorageClassName = &sc + } +} + +// PVCWithVolumeSize overrides the default volume size (cd.CirrosVolumeSize), with the size parameter +// The size parameter must be in parsable valid quantity string. +func PVCWithVolumeSize(size string) pvcOption { + return func(pvc *corev1.PersistentVolumeClaimSpec) { + if pvc == nil { + return + } + + pvc.Resources.Requests = corev1.ResourceList{"storage": resource.MustParse(size)} + } +} + +// PVCWithVolumeMode adds the volume mode to the DV +func PVCWithVolumeMode(volumeMode corev1.PersistentVolumeMode) pvcOption { + return func(pvc *corev1.PersistentVolumeClaimSpec) { + if pvc == nil { + return + } + + pvc.VolumeMode = &volumeMode + } +} + +// PVCWithBlockVolumeMode adds the PersistentVolumeBlock volume mode to the DV +func PVCWithBlockVolumeMode() pvcOption { + return PVCWithVolumeMode(corev1.PersistentVolumeBlock) +} + +// PVCWithAccessMode overrides the DV default access mode (ReadWriteOnce) with the accessMode parameter +func PVCWithAccessMode(accessMode corev1.PersistentVolumeAccessMode) pvcOption { + return func(pvc *corev1.PersistentVolumeClaimSpec) { + if pvc == nil { + return + } + + pvc.AccessModes = []corev1.PersistentVolumeAccessMode{accessMode} + } +} + +// PVCWithReadWriteManyAccessMode set the DV access mode to ReadWriteMany +func PVCWithReadWriteManyAccessMode() pvcOption { + return PVCWithAccessMode(corev1.ReadWriteMany) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ce6ffe452..a09a5205e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1396,6 +1396,7 @@ kubevirt.io/controller-lifecycle-operator-sdk/api kubevirt.io/kubevirt/pkg/apimachinery/patch kubevirt.io/kubevirt/pkg/controller kubevirt.io/kubevirt/pkg/instancetype +kubevirt.io/kubevirt/pkg/libvmi kubevirt.io/kubevirt/pkg/pointer kubevirt.io/kubevirt/pkg/testutils kubevirt.io/kubevirt/pkg/util @@ -1409,6 +1410,9 @@ kubevirt.io/kubevirt/pkg/virtctl/create/params kubevirt.io/kubevirt/pkg/virtctl/create/preference kubevirt.io/kubevirt/pkg/virtctl/create/vm kubevirt.io/kubevirt/pkg/virtctl/templates +kubevirt.io/kubevirt/tests/containerdisk +kubevirt.io/kubevirt/tests/flags +kubevirt.io/kubevirt/tests/libdv # kubevirt.io/qe-tools v0.1.8 ## explicit; go 1.16 kubevirt.io/qe-tools/pkg/ginkgo-reporters