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

* Support registry import using node docker cache The new CRI (container runtime interface) importer pod is created with three containers and a shared emptyDir volume: -Init container: copies static http server binary to empty dir -Server container: container image container configured to run the http binary and serve up the image file in /data -Client container: import.sh uses cdi-import to import from server container, and writes "done" file on emptydir -Server container sees "done" file and exits Thanks mhenriks for the PoC! Done: -added ImportMethod to DataVolumeSourceRegistry (DataVolume.Spec.Source.Registry, DataImportCron.Spec.Source.Registry). Import method can be "skopeo" (default), or "cri" for container runtime interface based import -added cdi-containerimage-server & import.sh to the cdi-importer container ToDo: -utests and func tests -doc Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * Add tests, fix CR comments Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * CR fixes Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * Use deployment docker prefix and tag in func tests Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * Add OpenShift ImageStreams import support Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * Add importer pod lookup annotation for image streams Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * Add pullMethod and imageStream doc Signed-off-by: Arnon Gilboa <agilboa@redhat.com>
614 lines
23 KiB
Go
614 lines
23 KiB
Go
package utils
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/onsi/gomega"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
k8sv1 "k8s.io/api/core/v1"
|
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
|
|
cdiv1 "kubevirt.io/containerized-data-importer/pkg/apis/core/v1beta1"
|
|
cdiclientset "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned"
|
|
)
|
|
|
|
const (
|
|
dataVolumePollInterval = 3 * time.Second
|
|
dataVolumeCreateTime = 270 * time.Second
|
|
dataVolumeDeleteTime = 270 * time.Second
|
|
dataVolumePhaseTime = 270 * time.Second
|
|
)
|
|
|
|
const (
|
|
// TinyCoreIsoURL provides a test url for the tineyCore iso image
|
|
TinyCoreIsoURL = "http://cdi-file-host.%s/tinyCore.iso"
|
|
// TinyCoreQcow2URL provides a test url for the tineyCore qcow2 image
|
|
TinyCoreQcow2URL = "http://cdi-file-host.%s/tinyCore.qcow2"
|
|
//TinyCoreIsoRegistryURL provides a test url for the tinycore.qcow2 image wrapped in docker container
|
|
TinyCoreIsoRegistryURL = "docker://cdi-docker-registry-host.%s/tinycoreqcow2"
|
|
//TinyCoreIsoRegistryProxyURL provides a test url for the tinycore.qcow2 image wrapped in docker container available through rate-limiting proxy
|
|
TinyCoreIsoRegistryProxyURL = "docker://cdi-file-host.%s:83/tinycoreqcow2"
|
|
// HTTPSTinyCoreIsoURL provides a test (https) url for the tineyCore iso image
|
|
HTTPSTinyCoreIsoURL = "https://cdi-file-host.%s/tinyCore.iso"
|
|
// HTTPSTinyCoreQcow2URL provides a test (https) url for the tineyCore qcow2 image
|
|
HTTPSTinyCoreQcow2URL = "https://cdi-file-host.%s/tinyCore.qcow2"
|
|
// TinyCoreQcow2URLRateLimit provides a test url for the tineyCore qcow2 image via rate-limiting proxy
|
|
TinyCoreQcow2URLRateLimit = "http://cdi-file-host.%s:82/tinyCore.qcow2"
|
|
// TinyCoreQcow2GzURLRateLimit provides a test url for the tineyCore qcow2.gz image via rate-limiting proxy
|
|
TinyCoreQcow2GzURLRateLimit = "http://cdi-file-host.%s:82/tinyCore.qcow2.gz"
|
|
// HTTPSTinyCoreVmdkURL provides a test url for the tineyCore qcow2 image
|
|
HTTPSTinyCoreVmdkURL = "https://cdi-file-host.%s/tinyCore.vmdk"
|
|
// HTTPSTinyCoreVdiURL provides a test url for the tineyCore qcow2 image
|
|
HTTPSTinyCoreVdiURL = "https://cdi-file-host.%s/tinyCore.vdi"
|
|
// HTTPSTinyCoreVhdURL provides a test url for the tineyCore qcow2 image
|
|
HTTPSTinyCoreVhdURL = "https://cdi-file-host.%s/tinyCore.vhd"
|
|
// HTTPSTinyCoreVhdxURL provides a test url for the tineyCore qcow2 image
|
|
HTTPSTinyCoreVhdxURL = "https://cdi-file-host.%s/tinyCore.vhdx"
|
|
// InvalidQcowImagesURL provides a test url for invalid qcow images
|
|
InvalidQcowImagesURL = "http://cdi-file-host.%s/invalid_qcow_images/"
|
|
// LargeVirtualDiskQcow provides a test url for a cirros image with a large virtual size, in qcow2 format
|
|
LargeVirtualDiskQcow = "http://cdi-file-host.%s/cirros-large-virtual-size.qcow2"
|
|
// LargeVirtualDiskXz provides a test url for a cirros image with a large virtual size, in RAW format, XZ-compressed
|
|
LargeVirtualDiskXz = "http://cdi-file-host.%s/cirros-large-virtual-size.raw.xz"
|
|
// LargePhysicalDiskQcow provides a test url for a cirros image with a large physical size, in qcow2 format
|
|
LargePhysicalDiskQcow = "http://cdi-file-host.%s/cirros-large-physical-size.qcow2"
|
|
// LargePhysicalDiskXz provides a test url for a cirros image with a large physical size, in RAW format, XZ-compressed
|
|
LargePhysicalDiskXz = "http://cdi-file-host.%s/cirros-large-physical-size.raw.xz"
|
|
// TarArchiveURL provides a test url for a tar achive file
|
|
TarArchiveURL = "http://cdi-file-host.%s/archive.tar"
|
|
// CirrosURL provides the standard cirros image qcow image
|
|
CirrosURL = "http://cdi-file-host.%s/cirros-qcow2.img"
|
|
// ImageioURL provides URL of oVirt engine hosting imageio
|
|
ImageioURL = "https://imageio.%s:12346/ovirt-engine/api"
|
|
// ImageioRootURL provides the base path to fakeovirt, for inventory modifications
|
|
ImageioRootURL = "https://imageio.%s:12346"
|
|
// ImageioImageURL provides the base path to imageiotest, for test images
|
|
ImageioImageURL = "https://imageio.%s:12345"
|
|
// VcenterURL provides URL of vCenter/ESX simulator
|
|
VcenterURL = "https://vcenter.%s:8989/sdk"
|
|
// TrustedRegistryURL provides the base path to trusted registry test url for the tinycore.iso image wrapped in docker container
|
|
TrustedRegistryURL = "docker://%s/cdi-func-test-tinycore:%s"
|
|
// TrustedRegistryIS provides the base path to trusted registry test fake imagestream for the tinycore.iso image wrapped in docker container
|
|
TrustedRegistryIS = "%s/cdi-func-test-tinycore:%s"
|
|
|
|
// TinyCoreMD5 is the MD5 hash of first 100k bytes of tinyCore image
|
|
TinyCoreMD5 = "3710416a680523c7d07538cb1026c60c"
|
|
// TinyCoreTarMD5 is the MD5 hash of first 100k bytes of tinyCore tar image
|
|
TinyCoreTarMD5 = "aec1a39d753b4b7cc81ee02bc625a342"
|
|
// ImageioMD5 is the MD5 hash of first 100k bytes of imageio image
|
|
ImageioMD5 = "91150be031835ccfac458744da57d4f6"
|
|
// VcenterMD5 is the MD5 hash of first 100k bytes of Vcenter image
|
|
VcenterMD5 = "91150be031835ccfac458744da57d4f6"
|
|
// BlankMD5 is the MD5 hash of first 100k bytes of blank image
|
|
BlankMD5 = "0019d23bef56a136a1891211d7007f6f"
|
|
)
|
|
|
|
// CreateDataVolumeFromDefinition is used by tests to create a testable Data Volume
|
|
func CreateDataVolumeFromDefinition(clientSet *cdiclientset.Clientset, namespace string, def *cdiv1.DataVolume) (*cdiv1.DataVolume, error) {
|
|
var dataVolume *cdiv1.DataVolume
|
|
err := wait.PollImmediate(dataVolumePollInterval, dataVolumeCreateTime, func() (bool, error) {
|
|
var err error
|
|
dataVolume, err = clientSet.CdiV1beta1().DataVolumes(namespace).Create(context.TODO(), def, metav1.CreateOptions{})
|
|
if err == nil || apierrs.IsAlreadyExists(err) {
|
|
return true, nil
|
|
}
|
|
return false, err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return dataVolume, nil
|
|
}
|
|
|
|
// DeleteDataVolume deletes the DataVolume with the given name
|
|
func DeleteDataVolume(clientSet *cdiclientset.Clientset, namespace, name string) error {
|
|
return wait.PollImmediate(dataVolumePollInterval, dataVolumeDeleteTime, func() (bool, error) {
|
|
err := clientSet.CdiV1beta1().DataVolumes(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
|
|
if err == nil || apierrs.IsNotFound(err) {
|
|
return true, nil
|
|
}
|
|
return false, err
|
|
})
|
|
}
|
|
|
|
// NewCloningDataVolume initializes a DataVolume struct with PVC annotations
|
|
func NewCloningDataVolume(dataVolumeName, size string, sourcePvc *k8sv1.PersistentVolumeClaim) *cdiv1.DataVolume {
|
|
return NewDataVolumeForImageCloning(dataVolumeName, size, sourcePvc.Namespace, sourcePvc.Name, sourcePvc.Spec.StorageClassName, sourcePvc.Spec.VolumeMode)
|
|
}
|
|
|
|
// NewDataVolumeWithSourceRef initializes a DataVolume struct with DataSource SourceRef
|
|
func NewDataVolumeWithSourceRef(dataVolumeName string, size, sourceRefNamespace, sourceRefName string) *cdiv1.DataVolume {
|
|
claimSpec := &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceStorage: resource.MustParse(size),
|
|
},
|
|
},
|
|
}
|
|
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
SourceRef: &cdiv1.DataVolumeSourceRef{
|
|
Kind: cdiv1.DataVolumeDataSource,
|
|
Namespace: &sourceRefNamespace,
|
|
Name: sourceRefName,
|
|
},
|
|
PVC: claimSpec,
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataSource initializes a DataSource struct with PVC source
|
|
func NewDataSource(dataSourceName, dataSourceNamespace, pvcName, pvcNamespace string) *cdiv1.DataSource {
|
|
return &cdiv1.DataSource{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataSourceName,
|
|
Namespace: dataSourceNamespace,
|
|
},
|
|
Spec: cdiv1.DataSourceSpec{
|
|
Source: cdiv1.DataSourceSource{
|
|
PVC: &cdiv1.DataVolumeSourcePVC{
|
|
Name: pvcName,
|
|
Namespace: pvcNamespace,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataVolumeWithHTTPImport initializes a DataVolume struct with HTTP annotations
|
|
func NewDataVolumeWithHTTPImport(dataVolumeName string, size string, httpURL string) *cdiv1.DataVolume {
|
|
claimSpec := &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceStorage: resource.MustParse(size),
|
|
},
|
|
},
|
|
}
|
|
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
HTTP: &cdiv1.DataVolumeSourceHTTP{
|
|
URL: httpURL,
|
|
},
|
|
},
|
|
PVC: claimSpec,
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataVolumeWithHTTPImportAndStorageSpec initializes a DataVolume struct with HTTP annotations
|
|
func NewDataVolumeWithHTTPImportAndStorageSpec(dataVolumeName string, size string, httpURL string) *cdiv1.DataVolume {
|
|
storageSpec := &cdiv1.StorageSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceStorage: resource.MustParse(size),
|
|
},
|
|
},
|
|
}
|
|
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
HTTP: &cdiv1.DataVolumeSourceHTTP{
|
|
URL: httpURL,
|
|
},
|
|
},
|
|
Storage: storageSpec,
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataVolumeWithImageioImport initializes a DataVolume struct with Imageio annotations
|
|
func NewDataVolumeWithImageioImport(dataVolumeName string, size string, httpURL string, secret string, configMap string, diskID string) *cdiv1.DataVolume {
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
Imageio: &cdiv1.DataVolumeSourceImageIO{
|
|
URL: httpURL,
|
|
SecretRef: secret,
|
|
CertConfigMap: configMap,
|
|
DiskID: diskID,
|
|
},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataVolumeWithImageioWarmImport initializes a DataVolume struct for a multi-stage import from oVirt snapshots
|
|
func NewDataVolumeWithImageioWarmImport(dataVolumeName string, size string, httpURL string, secret string, configMap string, diskID string, checkpoints []cdiv1.DataVolumeCheckpoint, finalCheckpoint bool) *cdiv1.DataVolume {
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
Imageio: &cdiv1.DataVolumeSourceImageIO{
|
|
URL: httpURL,
|
|
SecretRef: secret,
|
|
CertConfigMap: configMap,
|
|
DiskID: diskID,
|
|
},
|
|
},
|
|
FinalCheckpoint: finalCheckpoint,
|
|
Checkpoints: checkpoints,
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataVolumeWithHTTPImportToBlockPV initializes a DataVolume struct with HTTP annotations to import to block PV
|
|
func NewDataVolumeWithHTTPImportToBlockPV(dataVolumeName string, size string, httpURL, storageClassName string) *cdiv1.DataVolume {
|
|
volumeMode := corev1.PersistentVolumeMode(corev1.PersistentVolumeBlock)
|
|
dataVolume := &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
HTTP: &cdiv1.DataVolumeSourceHTTP{
|
|
URL: httpURL,
|
|
},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
VolumeMode: &volumeMode,
|
|
StorageClassName: &storageClassName,
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return dataVolume
|
|
}
|
|
|
|
// NewDataVolumeCloneToBlockPV initializes a DataVolume for block cloning
|
|
func NewDataVolumeCloneToBlockPV(dataVolumeName string, size string, srcNamespace, srcName, storageClassName string) *cdiv1.DataVolume {
|
|
volumeMode := corev1.PersistentVolumeMode(corev1.PersistentVolumeBlock)
|
|
dataVolume := &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
PVC: &cdiv1.DataVolumeSourcePVC{
|
|
Name: srcName,
|
|
Namespace: srcNamespace,
|
|
},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
VolumeMode: &volumeMode,
|
|
StorageClassName: &storageClassName,
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return dataVolume
|
|
}
|
|
|
|
// NewDataVolumeForUpload initializes a DataVolume struct with Upload annotations
|
|
func NewDataVolumeForUpload(dataVolumeName string, size string) *cdiv1.DataVolume {
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
Upload: &cdiv1.DataVolumeSourceUpload{},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataVolumeForBlankRawImage initializes a DataVolume struct for creating blank raw image
|
|
func NewDataVolumeForBlankRawImage(dataVolumeName, size string) *cdiv1.DataVolume {
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
Blank: &cdiv1.DataVolumeBlankImage{},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataVolumeForBlankRawImageBlock initializes a DataVolume struct for creating blank raw image for a block device
|
|
func NewDataVolumeForBlankRawImageBlock(dataVolumeName, size string, storageClassName string) *cdiv1.DataVolume {
|
|
volumeMode := corev1.PersistentVolumeMode(corev1.PersistentVolumeBlock)
|
|
dataVolume := &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
Blank: &cdiv1.DataVolumeBlankImage{},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
VolumeMode: &volumeMode,
|
|
StorageClassName: &storageClassName,
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return dataVolume
|
|
}
|
|
|
|
// NewDataVolumeForImageCloning initializes a DataVolume struct for cloning disk image
|
|
func NewDataVolumeForImageCloning(dataVolumeName, size, namespace, pvcName string, storageClassName *string, volumeMode *k8sv1.PersistentVolumeMode) *cdiv1.DataVolume {
|
|
dv := &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
PVC: &cdiv1.DataVolumeSourcePVC{
|
|
Namespace: namespace,
|
|
Name: pvcName,
|
|
},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if volumeMode != nil {
|
|
dv.Spec.PVC.VolumeMode = volumeMode
|
|
}
|
|
if storageClassName != nil {
|
|
dv.Spec.PVC.StorageClassName = storageClassName
|
|
}
|
|
return dv
|
|
}
|
|
|
|
// NewDataVolumeWithRegistryImport initializes a DataVolume struct with registry annotations
|
|
func NewDataVolumeWithRegistryImport(dataVolumeName string, size string, registryURL string) *cdiv1.DataVolume {
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
Registry: &cdiv1.DataVolumeSourceRegistry{
|
|
URL: ®istryURL,
|
|
},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// ModifyDataVolumeWithImportToBlockPV modifies a DataVolume struct for import to a block PV
|
|
func ModifyDataVolumeWithImportToBlockPV(dataVolume *cdiv1.DataVolume, storageClassName string) *cdiv1.DataVolume {
|
|
volumeMode := corev1.PersistentVolumeMode(corev1.PersistentVolumeBlock)
|
|
dataVolume.Spec.PVC.VolumeMode = &volumeMode
|
|
dataVolume.Spec.PVC.StorageClassName = &storageClassName
|
|
return dataVolume
|
|
}
|
|
|
|
// NewDataVolumeWithVddkImport initializes a DataVolume struct for importing disks from vCenter/ESX
|
|
func NewDataVolumeWithVddkImport(dataVolumeName string, size string, backingFile string, secretRef string, thumbprint string, httpURL string, uuid string) *cdiv1.DataVolume {
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
VDDK: &cdiv1.DataVolumeSourceVDDK{
|
|
BackingFile: backingFile,
|
|
SecretRef: secretRef,
|
|
Thumbprint: thumbprint,
|
|
URL: httpURL,
|
|
UUID: uuid,
|
|
},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDataVolumeWithVddkWarmImport initializes a DataVolume struct for a multi-stage import from vCenter/ESX snapshots
|
|
func NewDataVolumeWithVddkWarmImport(dataVolumeName string, size string, backingFile string, secretRef string, thumbprint string, httpURL string, uuid string, currentCheckpoint string, previousCheckpoint string, finalCheckpoint bool) *cdiv1.DataVolume {
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
VDDK: &cdiv1.DataVolumeSourceVDDK{
|
|
BackingFile: backingFile,
|
|
SecretRef: secretRef,
|
|
Thumbprint: thumbprint,
|
|
URL: httpURL,
|
|
UUID: uuid,
|
|
},
|
|
},
|
|
FinalCheckpoint: finalCheckpoint,
|
|
Checkpoints: []cdiv1.DataVolumeCheckpoint{
|
|
{Current: previousCheckpoint, Previous: ""},
|
|
{Current: currentCheckpoint, Previous: previousCheckpoint},
|
|
},
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// WaitForDataVolumePhase waits for DV's phase to be in a particular phase (Pending, Bound, or Lost)
|
|
func WaitForDataVolumePhase(clientSet *cdiclientset.Clientset, namespace string, phase cdiv1.DataVolumePhase, dataVolumeName string) error {
|
|
return WaitForDataVolumePhaseWithTimeout(clientSet, namespace, phase, dataVolumeName, dataVolumePhaseTime)
|
|
}
|
|
|
|
// WaitForDataVolumePhaseWithTimeout waits for DV's phase to be in a particular phase (Pending, Bound, or Lost) with a specified timeout
|
|
func WaitForDataVolumePhaseWithTimeout(clientSet *cdiclientset.Clientset, namespace string, phase cdiv1.DataVolumePhase, dataVolumeName string, timeout time.Duration) error {
|
|
err := wait.PollImmediate(dataVolumePollInterval, timeout, func() (bool, error) {
|
|
dataVolume, err := clientSet.CdiV1beta1().DataVolumes(namespace).Get(context.TODO(), dataVolumeName, metav1.GetOptions{})
|
|
if err != nil || dataVolume.Status.Phase != phase {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("DataVolume %s not in phase %s within %v", dataVolumeName, phase, timeout)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewDataVolumeWithArchiveContent initializes a DataVolume struct with 'archive' ContentType
|
|
func NewDataVolumeWithArchiveContent(dataVolumeName string, size string, httpURL string) *cdiv1.DataVolume {
|
|
return &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: dataVolumeName,
|
|
},
|
|
Spec: cdiv1.DataVolumeSpec{
|
|
Source: &cdiv1.DataVolumeSource{
|
|
HTTP: &cdiv1.DataVolumeSourceHTTP{
|
|
URL: httpURL,
|
|
},
|
|
},
|
|
ContentType: "archive",
|
|
PVC: &k8sv1.PersistentVolumeClaimSpec{
|
|
AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce},
|
|
Resources: k8sv1.ResourceRequirements{
|
|
Requests: k8sv1.ResourceList{
|
|
k8sv1.ResourceName(k8sv1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// PersistentVolumeClaimFromDataVolume creates a PersistentVolumeClaim definition so we can use PersistentVolumeClaim for various operations.
|
|
func PersistentVolumeClaimFromDataVolume(datavolume *cdiv1.DataVolume) *corev1.PersistentVolumeClaim {
|
|
return &corev1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: datavolume.Name,
|
|
Namespace: datavolume.Namespace,
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
*metav1.NewControllerRef(datavolume, schema.GroupVersionKind{
|
|
Group: cdiv1.SchemeGroupVersion.Group,
|
|
Version: cdiv1.SchemeGroupVersion.Version,
|
|
Kind: "DataVolume",
|
|
}),
|
|
},
|
|
},
|
|
Spec: *datavolume.Spec.PVC,
|
|
}
|
|
}
|
|
|
|
// GetCloneType returnc the CDI clone type
|
|
func GetCloneType(clientSet *cdiclientset.Clientset, dataVolume *cdiv1.DataVolume) string {
|
|
var cloneType string
|
|
gomega.Eventually(func() bool {
|
|
dv, err := clientSet.CdiV1beta1().DataVolumes(dataVolume.Namespace).Get(context.TODO(), dataVolume.Name, metav1.GetOptions{})
|
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
|
val, ok := dv.Annotations["cdi.kubevirt.io/cloneType"]
|
|
if ok {
|
|
cloneType = val
|
|
}
|
|
return ok
|
|
}, 90*time.Second, 2*time.Second).Should(gomega.BeTrue())
|
|
return cloneType
|
|
}
|