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

Changes and flow: * datavolume-controller: - When clone pvc is requested and smart-clone is applicable -> Create a new VolumeSnapshot. * Introduced smart-clone-controller: - Listens to VolumeSnapshot changes and create a new PVC based on the snapshot. - Upon PVC successful creation, deletes the snapshot for cleaning up. Change-Id: I369fc92e72edb8a2cf584c8cb916795415f9d6e0 Signed-off-by: Daniel Erez <derez@redhat.com> Co-authored-by: Fred Rolland <frolland@redhat.com>
2108 lines
58 KiB
Go
2108 lines
58 KiB
Go
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
snapshotv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1alpha1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
v1 "k8s.io/api/core/v1"
|
|
storagev1 "k8s.io/api/storage/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
kubeinformers "k8s.io/client-go/informers"
|
|
"k8s.io/client-go/kubernetes"
|
|
k8sfake "k8s.io/client-go/kubernetes/fake"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/client-go/util/workqueue"
|
|
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
|
|
|
|
crdv1 "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1alpha1"
|
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
|
cdiv1 "kubevirt.io/containerized-data-importer/pkg/apis/core/v1alpha1"
|
|
cdifake "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned/fake"
|
|
"kubevirt.io/containerized-data-importer/pkg/common"
|
|
. "kubevirt.io/containerized-data-importer/pkg/common"
|
|
"kubevirt.io/containerized-data-importer/pkg/keys"
|
|
)
|
|
|
|
func TestController_pvcFromKey(t *testing.T) {
|
|
//create staging pvc and pod
|
|
pvcWithEndPointAnno := createPvc("testPvcWithEndPointAnno", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
|
|
podWithCdiAnno := createPod(pvcWithEndPointAnno, DataVolName, nil)
|
|
|
|
//run the informers
|
|
c, pvc, pod, err := createImportController(pvcWithEndPointAnno, podWithCdiAnno, "default")
|
|
if err != nil {
|
|
t.Errorf("Controller.pvcFromKey() failed to initialize fake controller error = %v", err)
|
|
return
|
|
}
|
|
|
|
type args struct {
|
|
key interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *v1.PersistentVolumeClaim
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "expect to get pvc object from key",
|
|
args: args{fmt.Sprintf("%s/%s", pod.Namespace, pvc.Name)},
|
|
want: pvc,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expect to not get pvc object from key",
|
|
args: args{fmt.Sprintf("%s/%s", "myns", pvc.Name)},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, exists, err := c.pvcFromKey(tt.args.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Controller.pvcFromKey() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("Controller.pvcFromKey() = %v, want %v", got, tt.want)
|
|
}
|
|
if tt.want == nil && exists {
|
|
t.Errorf("Controller.pvcFromKey() expected key not to exist")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCloneController_pvcFromKey(t *testing.T) {
|
|
|
|
//create staging pvc and pods
|
|
pvcWithCloneRequestAnno := createPvc("target-pvc", "target-ns", map[string]string{AnnCloneRequest: "source-ns/golden-pvc"}, nil)
|
|
id := string(pvcWithCloneRequestAnno.GetUID())
|
|
sourcePod := createSourcePod(pvcWithCloneRequestAnno, id)
|
|
targetPod := createTargetPod(pvcWithCloneRequestAnno, id, sourcePod.Namespace)
|
|
|
|
//run the informers
|
|
c, pvc, sourcePod, targetPod, err := createCloneController(pvcWithCloneRequestAnno, sourcePod, targetPod, "target-ns", "sourceNs")
|
|
if err != nil {
|
|
t.Errorf("Controller.pvcFromKey() failed to initialize fake clone controller error = %v", err)
|
|
return
|
|
}
|
|
|
|
type args struct {
|
|
key interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *v1.PersistentVolumeClaim
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "expect to get pvc object from key",
|
|
args: args{fmt.Sprintf("%s/%s", sourcePod.GetAnnotations()[AnnTargetPodNamespace], pvc.Name)},
|
|
want: pvc,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expect to not get pvc object from key",
|
|
args: args{fmt.Sprintf("%s/%s", "myns", pvc.Name)},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expect to get pvc object from key",
|
|
args: args{fmt.Sprintf("%s/%s", targetPod.Namespace, pvc.Name)},
|
|
want: pvc,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, exists, err := c.pvcFromKey(tt.args.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Controller.pvcFromKey() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("Controller.pvcFromKey() = %v, want %v", got, tt.want)
|
|
}
|
|
if tt.want == nil && exists {
|
|
t.Errorf("Controller.pvcFromKey() expected key not to exist")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestController_objFromKey(t *testing.T) {
|
|
//create staging pvc and pod
|
|
pvcWithEndPointAnno := createPvc("testPvcWithEndPointAnno", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
|
|
podWithCdiAnno := createPod(pvcWithEndPointAnno, DataVolName, nil)
|
|
|
|
//run the informers
|
|
c, pvc, pod, err := createImportController(pvcWithEndPointAnno, podWithCdiAnno, "default")
|
|
if err != nil {
|
|
t.Errorf("Controller.objFromKey() failed to initialize fake controller error = %v", err)
|
|
return
|
|
}
|
|
|
|
type args struct {
|
|
informer cache.SharedIndexInformer
|
|
key interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want interface{}
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "expect to get object key",
|
|
args: args{c.pvcInformer, fmt.Sprintf("%s/%s", pod.Namespace, pvc.Name)},
|
|
want: pvc,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expect to not get object key",
|
|
args: args{c.pvcInformer, fmt.Sprintf("%s/%s", "myns", pvc.Name)},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, exists, err := c.objFromKey(tt.args.informer, tt.args.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Controller.objFromKey() error = %v, wantErr %v myKey = %v", err, tt.wantErr, tt.args.key)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("Controller.objFromKey() = %v, want %v", got, tt.want)
|
|
}
|
|
if tt.want == nil && exists {
|
|
t.Errorf("Controller.pvcFromKey() expected key not to exist")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCloneController_objFromKey(t *testing.T) {
|
|
//create staging pvc and pods
|
|
pvcWithCloneRequestAnno := createPvc("testPvcWithCloneRequestAnno", "target-ns", map[string]string{AnnCloneRequest: "source-ns/golden-pvc"}, nil)
|
|
id := string(pvcWithCloneRequestAnno.GetUID())
|
|
sourcePod := createSourcePod(pvcWithCloneRequestAnno, id)
|
|
targetPod := createTargetPod(pvcWithCloneRequestAnno, id, sourcePod.Namespace)
|
|
|
|
//run the informers
|
|
c, pvc, sourcePod, targetPod, err := createCloneController(pvcWithCloneRequestAnno, sourcePod, targetPod, "target-ns", "source-ns")
|
|
if err != nil {
|
|
t.Errorf("Controller.objFromKey() failed to initialize fake clone controller error = %v", err)
|
|
return
|
|
}
|
|
|
|
type args struct {
|
|
informer cache.SharedIndexInformer
|
|
key interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want interface{}
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "expect to get object key",
|
|
args: args{c.pvcInformer, fmt.Sprintf("%s/%s", sourcePod.GetAnnotations()[AnnTargetPodNamespace], pvc.Name)},
|
|
want: pvc,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expect to not get object key",
|
|
args: args{c.pvcInformer, fmt.Sprintf("%s/%s", "myns", pvc.Name)},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expect to get object key",
|
|
args: args{c.pvcInformer, fmt.Sprintf("%s/%s", targetPod.Namespace, pvc.Name)},
|
|
want: pvc,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, exists, err := c.objFromKey(tt.args.informer, tt.args.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Controller.objFromKey() error = %v, wantErr %v myKey = %v", err, tt.wantErr, tt.args.key)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("Controller.objFromKey() = %v, want %v", got, tt.want)
|
|
}
|
|
if tt.want == nil && exists {
|
|
t.Errorf("Controller.pvcFromKey() expected key not to exist")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_checkPVC(t *testing.T) {
|
|
//Create base pvcs and secrets
|
|
pvcNoAnno := createPvc("testPvcNoAnno", "default", nil, nil)
|
|
pvcWithEndPointAnno := createPvc("testPvcWithEndPointAnno", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
wantOk bool
|
|
pvc *v1.PersistentVolumeClaim
|
|
}{
|
|
{
|
|
name: "pvc does not have endpoint annotation or pod annotation",
|
|
wantOk: false,
|
|
pvc: pvcNoAnno,
|
|
},
|
|
{
|
|
name: "pvc does have endpoint annotation and does not have a pod annotation",
|
|
wantOk: true,
|
|
pvc: pvcWithEndPointAnno,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotOk := checkPVC(tt.pvc, AnnEndpoint)
|
|
if gotOk != tt.wantOk {
|
|
t.Errorf("checkPVC() gotOk = %v, want %v", gotOk, tt.wantOk)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func Test_checkClonePVC(t *testing.T) {
|
|
//Create base pvcs and secrets
|
|
pvcNoAnno := createPvc("testPvcNoAnno", "default", nil, nil)
|
|
pvcWithCloneRequestAnno := createPvc("testPvcWithCloneRequestAnno", "default", map[string]string{AnnCloneRequest: "source-ns/golden-pvc"}, nil)
|
|
tests := []struct {
|
|
name string
|
|
wantOk bool
|
|
pvc *v1.PersistentVolumeClaim
|
|
}{
|
|
{
|
|
name: "pvc does not have cloneRequest annotation or pod annotation",
|
|
wantOk: false,
|
|
pvc: pvcNoAnno,
|
|
},
|
|
{
|
|
name: "pvc does have CloneRequest annotation and does not have a pod annotation",
|
|
wantOk: true,
|
|
pvc: pvcWithCloneRequestAnno,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotOk := checkPVC(tt.pvc, AnnCloneRequest)
|
|
if gotOk != tt.wantOk {
|
|
t.Errorf("checkPVC() gotOk = %v, want %v", gotOk, tt.wantOk)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func Test_getEndpoint(t *testing.T) {
|
|
type args struct {
|
|
pvc *v1.PersistentVolumeClaim
|
|
}
|
|
pvcNoAnno := createPvc("testPVCNoAnno", "default", nil, nil)
|
|
pvcWithAnno := createPvc("testPVCWithAnno", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
|
|
pvcNoValue := createPvc("testPVCNoValue", "default", map[string]string{AnnEndpoint: ""}, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "expected to find endpoint annotation",
|
|
args: args{pvcWithAnno},
|
|
want: "http://test",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expected to not find endpoint annotation",
|
|
args: args{pvcNoAnno},
|
|
want: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing endpoint value",
|
|
args: args{pvcNoValue},
|
|
want: "",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, _ := getEndpoint(tt.args.pvc)
|
|
if got != tt.want {
|
|
t.Errorf("getEndpoint() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getSource(t *testing.T) {
|
|
type args struct {
|
|
pvc *v1.PersistentVolumeClaim
|
|
}
|
|
|
|
pvcNoAnno := createPvc("testPVCNoAnno", "default", nil, nil)
|
|
pvcNoneAnno := createPvc("testPVCNoneAnno", "default", map[string]string{AnnSource: SourceNone}, nil)
|
|
pvcGlanceAnno := createPvc("testPVCNoneAnno", "default", map[string]string{AnnSource: SourceGlance}, nil)
|
|
pvcInvalidValue := createPvc("testPVCInvalidValue", "default", map[string]string{AnnSource: "iaminvalid"}, nil)
|
|
pvcRegistryAnno := createPvc("testPVCRegistryAnno", "default", map[string]string{AnnSource: SourceRegistry}, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
}{
|
|
{
|
|
name: "expected to find none anno",
|
|
args: args{pvcNoneAnno},
|
|
want: SourceNone,
|
|
},
|
|
{
|
|
name: "expected to find http with invalid value",
|
|
args: args{pvcInvalidValue},
|
|
want: SourceHTTP,
|
|
},
|
|
{
|
|
name: "expected to find http with no anno",
|
|
args: args{pvcNoAnno},
|
|
want: SourceHTTP,
|
|
},
|
|
{
|
|
name: "expected to find glance with glance anno",
|
|
args: args{pvcGlanceAnno},
|
|
want: SourceGlance,
|
|
},
|
|
{
|
|
name: "expected to find registry with registry anno",
|
|
args: args{pvcRegistryAnno},
|
|
want: SourceRegistry,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := getSource(tt.args.pvc)
|
|
if got != tt.want {
|
|
t.Errorf("getSource() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getVolumeMode(t *testing.T) {
|
|
type args struct {
|
|
pvc *v1.PersistentVolumeClaim
|
|
}
|
|
|
|
pvcVolumeModeBlock := createBlockPvc("testPVCVolumeModeBlock", "default", map[string]string{AnnSource: SourceHTTP}, nil)
|
|
pvcVolumeModeFilesystem := createPvc("testPVCVolumeModeFS", "default", map[string]string{AnnSource: SourceHTTP}, nil)
|
|
pvcVolumeModeFilesystemDefault := createPvc("testPVCVolumeModeFS", "default", map[string]string{AnnSource: SourceHTTP}, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want corev1.PersistentVolumeMode
|
|
}{
|
|
{
|
|
name: "expected volumeMode to be Block",
|
|
args: args{pvcVolumeModeBlock},
|
|
want: corev1.PersistentVolumeBlock,
|
|
},
|
|
{
|
|
name: "expected volumeMode to be Filesystem",
|
|
args: args{pvcVolumeModeFilesystem},
|
|
want: corev1.PersistentVolumeFilesystem,
|
|
},
|
|
{
|
|
name: "expected volumeMode to be Filesystem",
|
|
args: args{pvcVolumeModeFilesystemDefault},
|
|
want: corev1.PersistentVolumeFilesystem,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := getVolumeMode(tt.args.pvc)
|
|
if got != tt.want {
|
|
t.Errorf("getVolumeMode() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getContentType(t *testing.T) {
|
|
type args struct {
|
|
pvc *v1.PersistentVolumeClaim
|
|
}
|
|
|
|
pvcNoAnno := createPvc("testPVCNoAnno", "default", nil, nil)
|
|
pvcArchiveAnno := createPvc("testPVCArchiveAnno", "default", map[string]string{AnnContentType: string(cdiv1.DataVolumeArchive)}, nil)
|
|
pvcKubevirtAnno := createPvc("testPVCKubevirtAnno", "default", map[string]string{AnnContentType: string(cdiv1.DataVolumeKubeVirt)}, nil)
|
|
pvcInvalidValue := createPvc("testPVCInvalidValue", "default", map[string]string{AnnContentType: "iaminvalid"}, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
}{
|
|
{
|
|
name: "expected to kubevirt content type",
|
|
args: args{pvcNoAnno},
|
|
want: string(cdiv1.DataVolumeKubeVirt),
|
|
},
|
|
{
|
|
name: "expected to find archive content type",
|
|
args: args{pvcArchiveAnno},
|
|
want: string(cdiv1.DataVolumeArchive),
|
|
},
|
|
{
|
|
name: "expected to kubevirt content type",
|
|
args: args{pvcKubevirtAnno},
|
|
want: string(cdiv1.DataVolumeKubeVirt),
|
|
},
|
|
{
|
|
name: "expected to find kubevirt with invalid anno",
|
|
args: args{pvcInvalidValue},
|
|
want: string(cdiv1.DataVolumeKubeVirt),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := getContentType(tt.args.pvc)
|
|
if got != tt.want {
|
|
t.Errorf("getSource() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getImageSize(t *testing.T) {
|
|
type args struct {
|
|
pvc *v1.PersistentVolumeClaim
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "expected to get size 1G",
|
|
args: args{createPvc("testPVC", "default", nil, nil)},
|
|
want: "1G",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expected to get error, because of missing size",
|
|
args: args{createPvcNoSize("testPVC", "default", nil, nil)},
|
|
want: "",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := getRequestedImageSize(tt.args.pvc)
|
|
if err != nil && !tt.wantErr {
|
|
t.Errorf("Error retrieving adjusted image size, when not expecting error: %s", err.Error())
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("getSource() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getSecretName(t *testing.T) {
|
|
type args struct {
|
|
client kubernetes.Interface
|
|
pvc *v1.PersistentVolumeClaim
|
|
}
|
|
//Create base pvcs and secrets
|
|
pvcNoAnno := createPvc("testPVCNoAnno", "default", nil, nil)
|
|
pvcWithAnno := createPvc("testPVCWithAnno", "default", map[string]string{AnnSecret: "mysecret"}, nil)
|
|
testSecret1 := createSecret("mysecret", "default", "mysecretkey", "mysecretstring", map[string]string{AnnSecret: "mysecret"})
|
|
testSecret2 := createSecret("mysecret2", "default", "mysecretkey2", "mysecretstring2", map[string]string{AnnSecret: "mysecret2"})
|
|
|
|
// set test env
|
|
myclient := k8sfake.NewSimpleClientset(pvcNoAnno, pvcWithAnno, testSecret1, testSecret2)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "expected to find secret",
|
|
args: args{myclient, pvcWithAnno},
|
|
want: "mysecret",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "expected to not find secret",
|
|
args: args{myclient, pvcNoAnno},
|
|
want: "",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := getSecretName(tt.args.client, tt.args.pvc)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("getSecretName() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("getSecretName() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getCloneRequestPVCAnnotation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
wantOk bool
|
|
annKey string
|
|
annValue string
|
|
annErrType string
|
|
}{
|
|
{
|
|
name: "pvc without annotation should return error",
|
|
wantOk: false,
|
|
annKey: "",
|
|
annValue: "",
|
|
annErrType: "missing",
|
|
},
|
|
{
|
|
name: "pvc with blank annotation should return error",
|
|
wantOk: false,
|
|
annKey: AnnCloneRequest,
|
|
annValue: "",
|
|
annErrType: "empty",
|
|
},
|
|
{
|
|
name: "pvc with valid clone annotation",
|
|
wantOk: true,
|
|
annKey: AnnCloneRequest,
|
|
annValue: "default/pvc-name",
|
|
annErrType: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pvc := createPvc("test", "default", map[string]string{tt.annKey: tt.annValue}, nil)
|
|
ann, err := getCloneRequestPVCAnnotation(pvc)
|
|
if !tt.wantOk && err == nil {
|
|
t.Error("Got no error when expecting one")
|
|
} else if tt.wantOk && err != nil {
|
|
t.Errorf("Got error %+v when not expecting one", err)
|
|
}
|
|
if !tt.wantOk && err != nil {
|
|
// Verify that the error contains what we are expecting.
|
|
if !strings.Contains(err.Error(), tt.annErrType) {
|
|
t.Errorf("Expecting error message to contain %s, but not found", tt.annErrType)
|
|
}
|
|
} else if tt.wantOk && err == nil {
|
|
if ann != tt.annValue {
|
|
t.Error("expected annotation did not match found annotation")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func Test_updatePVC(t *testing.T) {
|
|
type args struct {
|
|
client kubernetes.Interface
|
|
pvc *v1.PersistentVolumeClaim
|
|
anno map[string]string
|
|
label map[string]string
|
|
}
|
|
//Create base pvc
|
|
pvcNoAnno := createPvc("testPVC1", "default", nil, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *v1.PersistentVolumeClaim
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "pvc is updated with annotation and label",
|
|
args: args{k8sfake.NewSimpleClientset(pvcNoAnno), pvcNoAnno, map[string]string{AnnCreatedBy: "cdi"}, map[string]string{CDILabelKey: CDILabelValue}},
|
|
want: createPvc("testPVC1", "default", map[string]string{AnnCreatedBy: "cdi"}, map[string]string{CDILabelKey: CDILabelValue}),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "pvc is updated with annotation",
|
|
args: args{k8sfake.NewSimpleClientset(pvcNoAnno), pvcNoAnno, map[string]string{AnnCreatedBy: "cdi"}, nil},
|
|
want: createPvc("testPVC1", "default", map[string]string{AnnCreatedBy: "cdi"}, nil),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "pvc is updated with label",
|
|
args: args{k8sfake.NewSimpleClientset(pvcNoAnno), pvcNoAnno, nil, map[string]string{CDILabelKey: CDILabelValue}},
|
|
want: createPvc("testPVC1", "default", nil, map[string]string{CDILabelKey: CDILabelValue}),
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := updatePVC(tt.args.client, tt.args.pvc, tt.args.anno, tt.args.label)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("updatePVC() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("updatePVC() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_setPVCAnnotation(t *testing.T) {
|
|
type args struct {
|
|
client kubernetes.Interface
|
|
pvc *v1.PersistentVolumeClaim
|
|
key string
|
|
val string
|
|
}
|
|
|
|
//Create base pvc
|
|
pvcNoAnno := createPvc("testPVC1", "default", nil, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *v1.PersistentVolumeClaim
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "pvc is updated with new annotation",
|
|
args: args{k8sfake.NewSimpleClientset(pvcNoAnno), pvcNoAnno, AnnCreatedBy, "cdi"},
|
|
want: createPvc("testPVC1", "default", map[string]string{AnnCreatedBy: "cdi"}, nil),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "pvc is updated with new annotation - empty value",
|
|
args: args{k8sfake.NewSimpleClientset(pvcNoAnno), pvcNoAnno, AnnCreatedBy, ""},
|
|
want: createPvc("testPVC1", "default", map[string]string{AnnCreatedBy: ""}, nil),
|
|
wantErr: false,
|
|
},
|
|
// TODO: Do we want to allow an Empty Key??
|
|
{
|
|
name: "pvc is not updated with new annotation - empty key",
|
|
args: args{k8sfake.NewSimpleClientset(pvcNoAnno), pvcNoAnno, "", "cdi"},
|
|
want: createPvc("testPVC1", "default", map[string]string{"": "cdi"}, nil),
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := setPVCAnnotation(tt.args.client, tt.args.pvc, tt.args.key, tt.args.val)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("setPVCAnnotation() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("setPVCAnnotation() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_checkIfAnnoExists(t *testing.T) {
|
|
type args struct {
|
|
pvc *v1.PersistentVolumeClaim
|
|
key string
|
|
val string
|
|
}
|
|
|
|
//create PVCs
|
|
pvc := createPvc("testPVC", "default", map[string]string{AnnPodPhase: "Running"}, nil)
|
|
pvcNoAnno := createPvc("testPVC2", "default", nil, nil)
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{
|
|
name: "pvc does have expected annotation key and value",
|
|
args: args{pvc, AnnPodPhase, "Running"},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "pvc does not have expected annotation key and value",
|
|
args: args{pvc, AnnEndpoint, "http://test"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "pvc does have expected key but not value",
|
|
args: args{pvc, AnnPodPhase, "Pending"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "pvc does not have any annotations",
|
|
args: args{pvcNoAnno, AnnPodPhase, "Running"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "pvc does have expected annotations but pass in empty strings",
|
|
args: args{pvc, "", ""},
|
|
want: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := checkIfAnnoExists(tt.args.pvc, tt.args.key, tt.args.val); got != tt.want {
|
|
t.Errorf("checkIfAnnoExists() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_checkIfLabelExists(t *testing.T) {
|
|
type args struct {
|
|
pvc *v1.PersistentVolumeClaim
|
|
lbl string
|
|
val string
|
|
}
|
|
//create PVCs
|
|
pvc := createPvc("testPVC", "default", nil, map[string]string{CDILabelKey: CDILabelValue})
|
|
pvcNoLbl := createPvc("testPVC2", "default", nil, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{
|
|
name: "pvc does have expected label and expected value",
|
|
args: args{pvc, CDILabelKey, CDILabelValue},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "pvc does not have expected label",
|
|
args: args{pvc, AnnCreatedBy, "yes"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "pvc does have expected label but does not have expected value",
|
|
args: args{pvc, CDILabelKey, "something"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "pvc does not have any labels",
|
|
args: args{pvcNoLbl, CDILabelKey, CDILabelValue},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "pvc does have labels but pass in empty search strings",
|
|
args: args{pvc, "", ""},
|
|
want: false,
|
|
},
|
|
}
|
|
// checkIfLabelExists expects both label to be present and correct value to match
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := checkIfLabelExists(tt.args.pvc, tt.args.lbl, tt.args.val); got != tt.want {
|
|
t.Errorf("checkIfLabelExists() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateImporterPod(t *testing.T) {
|
|
type args struct {
|
|
client kubernetes.Interface
|
|
image string
|
|
verbose string
|
|
pullPolicy string
|
|
podEnvVar *importPodEnvVar
|
|
pvc *v1.PersistentVolumeClaim
|
|
}
|
|
|
|
// create PVC
|
|
pvc := createPvc("testPVC2", "", nil, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *v1.Pod
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "expect pod to be created for PVC with VolumeMode Filesystem",
|
|
args: args{k8sfake.NewSimpleClientset(pvc), "test/image", "-v=5", "Always", &importPodEnvVar{"", "", "", "", "1G", "", false}, pvc},
|
|
want: MakeImporterPodSpec("test/image", "-v=5", "Always", &importPodEnvVar{"", "", "", "", "1G", "", false}, pvc, nil),
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := CreateImporterPod(tt.args.client, tt.args.image, tt.args.verbose, tt.args.pullPolicy, tt.args.podEnvVar, tt.args.pvc, nil)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("CreateImporterPod() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("CreateImporterPod() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMakeImporterPodSpec(t *testing.T) {
|
|
type args struct {
|
|
image string
|
|
verbose string
|
|
pullPolicy string
|
|
podEnvVar *importPodEnvVar
|
|
pvc *v1.PersistentVolumeClaim
|
|
}
|
|
// create PVC with VolumeMode: Filesystem
|
|
pvc := createPvc("testPVC2", "default", nil, nil)
|
|
// create POD
|
|
pod := createPod(pvc, DataVolName, nil)
|
|
|
|
// create PVC with VolumeMode: Block
|
|
pvc1 := createBlockPvc("testPVC2", "default", nil, nil)
|
|
// create POD
|
|
pod1 := createPod(pvc1, DataVolName, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantPod *v1.Pod
|
|
}{
|
|
{
|
|
name: "expect pod to be created for PVC with VolumeMode: Filesystem",
|
|
args: args{"test/myimage", "5", "Always", &importPodEnvVar{"", "", SourceHTTP, string(cdiv1.DataVolumeKubeVirt), "1G", "", false}, pvc},
|
|
wantPod: pod,
|
|
},
|
|
{
|
|
name: "expect pod to be created for PVC with VolumeMode: Block",
|
|
args: args{"test/myimage", "5", "Always", &importPodEnvVar{"", "", SourceHTTP, string(cdiv1.DataVolumeKubeVirt), "1G", "", false}, pvc1},
|
|
wantPod: pod1,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := MakeImporterPodSpec(tt.args.image, tt.args.verbose, tt.args.pullPolicy, tt.args.podEnvVar, tt.args.pvc, nil)
|
|
|
|
if !reflect.DeepEqual(got, tt.wantPod) {
|
|
t.Errorf("MakeImporterPodSpec() =\n%v\n, want\n%v", got, tt.wantPod)
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_makeEnv(t *testing.T) {
|
|
const mockUID = "1111-1111-1111-1111"
|
|
|
|
type args struct {
|
|
podEnvVar *importPodEnvVar
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want []v1.EnvVar
|
|
}{
|
|
{
|
|
name: "env should match",
|
|
args: args{&importPodEnvVar{"myendpoint", "mysecret", SourceHTTP, string(cdiv1.DataVolumeKubeVirt), "1G", "", false}},
|
|
want: createEnv(&importPodEnvVar{"myendpoint", "mysecret", SourceHTTP, string(cdiv1.DataVolumeKubeVirt), "1G", "", false}, mockUID),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
{
|
|
}
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := makeEnv(tt.args.podEnvVar, mockUID); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("makeEnv() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMakeCDIConfigSpec(t *testing.T) {
|
|
type args struct {
|
|
name string
|
|
}
|
|
config := createCDIConfigWithStorageClass("testConfig", "")
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantCDIConfig *cdiv1.CDIConfig
|
|
}{
|
|
{
|
|
name: "expect CDIConfig to be created",
|
|
args: args{"testConfig"},
|
|
wantCDIConfig: config,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := MakeEmptyCDIConfigSpec(tt.args.name)
|
|
|
|
if !reflect.DeepEqual(got, tt.wantCDIConfig) {
|
|
t.Errorf("MakeEmptyCDIConfigSpec() =\n%v\n, want\n%v", got, tt.wantCDIConfig)
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_addToMap(t *testing.T) {
|
|
type args struct {
|
|
m1 map[string]string
|
|
m2 map[string]string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want map[string]string
|
|
}{
|
|
{
|
|
name: "use different key for map1 and map2 expect both maps to be returned",
|
|
args: args{map[string]string{AnnImportPod: "mypod"}, map[string]string{CDILabelKey: CDILabelValue}},
|
|
want: map[string]string{AnnImportPod: "mypod", CDILabelKey: CDILabelValue},
|
|
},
|
|
{
|
|
name: "use same key for map1 and map2 expect map2 to be returned",
|
|
args: args{map[string]string{AnnImportPod: "mypod"}, map[string]string{AnnImportPod: "map2pod"}},
|
|
want: map[string]string{AnnImportPod: "map2pod"},
|
|
},
|
|
{
|
|
name: "pass in empty map1 and map2 expect empty map",
|
|
args: args{nil, nil},
|
|
want: map[string]string{},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := addToMap(tt.args.m1, tt.args.m2); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("addToMap() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getCertConfigMap(t *testing.T) {
|
|
namespace := "default"
|
|
configMapName := "foobar"
|
|
|
|
testPVC := &corev1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
"cdi.kubevirt.io/storage.import.certConfigMap": configMapName,
|
|
},
|
|
},
|
|
}
|
|
|
|
testConfigMap := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: configMapName,
|
|
Namespace: namespace,
|
|
},
|
|
}
|
|
|
|
type args struct {
|
|
pvc *corev1.PersistentVolumeClaim
|
|
objs []runtime.Object
|
|
result string
|
|
}
|
|
|
|
for _, arg := range []args{
|
|
{&corev1.PersistentVolumeClaim{}, nil, ""},
|
|
{testPVC, []runtime.Object{testConfigMap}, configMapName},
|
|
{testPVC, nil, configMapName},
|
|
} {
|
|
client := k8sfake.NewSimpleClientset(arg.objs...)
|
|
|
|
result, err := getCertConfigMap(client, arg.pvc)
|
|
|
|
if err != nil {
|
|
t.Errorf("Enexpected error %+v", err)
|
|
}
|
|
|
|
if result != arg.result {
|
|
t.Errorf("Expected %s got %s", arg.result, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_getInsecureTLS(t *testing.T) {
|
|
namespace := "cdi"
|
|
configMapName := "cdi-insecure-registries"
|
|
host := "myregistry"
|
|
endpointNoPort := "docker://" + host
|
|
hostWithPort := host + ":5000"
|
|
endpointWithPort := "docker://" + hostWithPort
|
|
|
|
type args struct {
|
|
endpoint string
|
|
confiMapExists bool
|
|
insecureHost string
|
|
result bool
|
|
}
|
|
|
|
for _, arg := range []args{
|
|
{endpointNoPort, true, host, true},
|
|
{endpointWithPort, true, hostWithPort, true},
|
|
{endpointNoPort, true, hostWithPort, false},
|
|
{endpointWithPort, true, host, false},
|
|
{endpointNoPort, false, "", false},
|
|
{"", true, host, false},
|
|
} {
|
|
var objs []runtime.Object
|
|
|
|
pvc := &corev1.PersistentVolumeClaim{}
|
|
if arg.endpoint != "" {
|
|
pvc.Annotations = map[string]string{
|
|
"cdi.kubevirt.io/storage.import.endpoint": arg.endpoint,
|
|
}
|
|
}
|
|
|
|
if arg.confiMapExists {
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: configMapName,
|
|
Namespace: namespace,
|
|
},
|
|
}
|
|
|
|
if arg.insecureHost != "" {
|
|
cm.Data = map[string]string{
|
|
arg.insecureHost: "",
|
|
}
|
|
}
|
|
|
|
objs = append(objs, cm)
|
|
}
|
|
|
|
client := k8sfake.NewSimpleClientset(objs...)
|
|
|
|
result, err := isInsecureTLS(client, pvc)
|
|
|
|
if err != nil {
|
|
t.Errorf("Enexpected error %+v", err)
|
|
}
|
|
|
|
if result != arg.result {
|
|
t.Errorf("Expected %t got %t", arg.result, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_GetScratchPvcStorageClassDefault(t *testing.T) {
|
|
var objs []runtime.Object
|
|
objs = append(objs, createStorageClass("test1", nil))
|
|
objs = append(objs, createStorageClass("test2", nil))
|
|
objs = append(objs, createStorageClass("test3", map[string]string{
|
|
AnnDefaultStorageClass: "true",
|
|
}))
|
|
client := k8sfake.NewSimpleClientset(objs...)
|
|
|
|
storageClassName := "test3"
|
|
var cdiObjs []runtime.Object
|
|
cdiObjs = append(cdiObjs, createCDIConfigWithStorageClass(common.ConfigName, storageClassName))
|
|
cdiclient := cdifake.NewSimpleClientset(cdiObjs...)
|
|
|
|
pvc := createPvc("test", "test", nil, nil)
|
|
result := GetScratchPvcStorageClass(client, cdiclient, pvc)
|
|
|
|
if result != storageClassName {
|
|
t.Error("Storage class is not test3")
|
|
}
|
|
}
|
|
|
|
func Test_GetScratchPvcStorageClassConfig(t *testing.T) {
|
|
var objs []runtime.Object
|
|
objs = append(objs, createStorageClass("test1", nil))
|
|
objs = append(objs, createStorageClass("test2", nil))
|
|
objs = append(objs, createStorageClass("test3", map[string]string{
|
|
AnnDefaultStorageClass: "true",
|
|
}))
|
|
client := k8sfake.NewSimpleClientset(objs...)
|
|
|
|
storageClassName := "test1"
|
|
var cdiObjs []runtime.Object
|
|
config := createCDIConfigWithStorageClass(common.ConfigName, storageClassName)
|
|
config.Spec.ScratchSpaceStorageClass = &storageClassName
|
|
cdiObjs = append(cdiObjs, config)
|
|
cdiclient := cdifake.NewSimpleClientset(cdiObjs...)
|
|
|
|
pvc := createPvc("test", "test", nil, nil)
|
|
result := GetScratchPvcStorageClass(client, cdiclient, pvc)
|
|
|
|
if result != storageClassName {
|
|
t.Error("Storage class is not test1")
|
|
}
|
|
}
|
|
|
|
func Test_GetScratchPvcStorageClassPvc(t *testing.T) {
|
|
var objs []runtime.Object
|
|
client := k8sfake.NewSimpleClientset(objs...)
|
|
|
|
storageClass := "storageClass"
|
|
var cdiObjs []runtime.Object
|
|
cdiObjs = append(cdiObjs, createCDIConfigWithStorageClass(common.ConfigName, storageClass))
|
|
cdiclient := cdifake.NewSimpleClientset(cdiObjs...)
|
|
|
|
pvc := createPvcInStorageClass("test", "test", &storageClass, nil, nil)
|
|
result := GetScratchPvcStorageClass(client, cdiclient, pvc)
|
|
|
|
if result != storageClass {
|
|
t.Error("Storage class is not storageClass")
|
|
}
|
|
}
|
|
|
|
func createPod(pvc *v1.PersistentVolumeClaim, dvname string, scratchPvc *v1.PersistentVolumeClaim) *v1.Pod {
|
|
// importer pod name contains the pvc name
|
|
podName := fmt.Sprintf("%s-%s-", ImporterPodName, pvc.Name)
|
|
|
|
blockOwnerDeletion := true
|
|
isController := true
|
|
|
|
volumes := []v1.Volume{
|
|
{
|
|
Name: DataVolName,
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: pvc.Name,
|
|
ReadOnly: false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
if scratchPvc != nil {
|
|
volumes = append(volumes, v1.Volume{
|
|
Name: ScratchVolName,
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: scratchPvc.Name,
|
|
ReadOnly: false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
pod := &v1.Pod{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Pod",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
GenerateName: podName,
|
|
Annotations: map[string]string{
|
|
AnnCreatedBy: "yes",
|
|
},
|
|
Labels: map[string]string{
|
|
CDILabelKey: CDILabelValue,
|
|
CDIComponentLabel: ImporterPodName,
|
|
LabelImportPvc: pvc.Name,
|
|
PrometheusLabel: "",
|
|
},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "v1",
|
|
Kind: "PersistentVolumeClaim",
|
|
Name: pvc.Name,
|
|
UID: pvc.GetUID(),
|
|
BlockOwnerDeletion: &blockOwnerDeletion,
|
|
Controller: &isController,
|
|
},
|
|
},
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Containers: []v1.Container{
|
|
{
|
|
Name: ImporterPodName,
|
|
Image: "test/myimage",
|
|
ImagePullPolicy: v1.PullPolicy("Always"),
|
|
Args: []string{"-v=5"},
|
|
Ports: []v1.ContainerPort{
|
|
{
|
|
Name: "metrics",
|
|
ContainerPort: 8443,
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
RestartPolicy: v1.RestartPolicyOnFailure,
|
|
Volumes: volumes,
|
|
},
|
|
}
|
|
|
|
ep, _ := getEndpoint(pvc)
|
|
source := getSource(pvc)
|
|
contentType := getContentType(pvc)
|
|
imageSize, _ := getRequestedImageSize(pvc)
|
|
volumeMode := getVolumeMode(pvc)
|
|
|
|
env := []v1.EnvVar{
|
|
{
|
|
Name: ImporterSource,
|
|
Value: source,
|
|
},
|
|
{
|
|
Name: ImporterEndpoint,
|
|
Value: ep,
|
|
},
|
|
{
|
|
Name: ImporterContentType,
|
|
Value: contentType,
|
|
},
|
|
{
|
|
Name: ImporterImageSize,
|
|
Value: imageSize,
|
|
},
|
|
{
|
|
Name: OwnerUID,
|
|
Value: string(pvc.UID),
|
|
},
|
|
{
|
|
Name: InsecureTLSVar,
|
|
Value: "false",
|
|
},
|
|
}
|
|
pod.Spec.Containers[0].Env = env
|
|
if volumeMode == v1.PersistentVolumeBlock {
|
|
pod.Spec.Containers[0].VolumeDevices = addVolumeDevices()
|
|
|
|
} else {
|
|
pod.Spec.Containers[0].VolumeMounts = addVolumeMounts()
|
|
}
|
|
|
|
if scratchPvc != nil {
|
|
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
|
|
Name: ScratchVolName,
|
|
MountPath: common.ScratchDataDir,
|
|
})
|
|
}
|
|
|
|
return pod
|
|
}
|
|
|
|
func createBlockPvc(name, ns string, annotations, labels map[string]string) *v1.PersistentVolumeClaim {
|
|
pvcDef := createPvcInStorageClass(name, ns, nil, annotations, labels)
|
|
volumeMode := v1.PersistentVolumeBlock
|
|
pvcDef.Spec.VolumeMode = &volumeMode
|
|
return pvcDef
|
|
}
|
|
|
|
func createPvc(name, ns string, annotations, labels map[string]string) *v1.PersistentVolumeClaim {
|
|
return createPvcInStorageClass(name, ns, nil, annotations, labels)
|
|
}
|
|
|
|
func createPvcInStorageClass(name, ns string, storageClassName *string, annotations, labels map[string]string) *v1.PersistentVolumeClaim {
|
|
return &v1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: ns,
|
|
Annotations: annotations,
|
|
Labels: labels,
|
|
UID: "pvc-uid",
|
|
},
|
|
Spec: v1.PersistentVolumeClaimSpec{
|
|
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
|
|
Resources: v1.ResourceRequirements{
|
|
Requests: v1.ResourceList{
|
|
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
|
},
|
|
},
|
|
StorageClassName: storageClassName,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createScratchPvc(pvc *v1.PersistentVolumeClaim, pod *v1.Pod, storageClassName string) *v1.PersistentVolumeClaim {
|
|
labels := map[string]string{
|
|
"cdi-controller": pod.Name,
|
|
"app": "containerized-data-importer",
|
|
LabelImportPvc: pvc.Name,
|
|
}
|
|
|
|
return &v1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: pvc.Name + "-scratch",
|
|
Namespace: pvc.Namespace,
|
|
Labels: labels,
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "v1",
|
|
Kind: "Pod",
|
|
Name: pod.Name,
|
|
UID: pod.GetUID(),
|
|
},
|
|
},
|
|
},
|
|
Spec: v1.PersistentVolumeClaimSpec{
|
|
AccessModes: []v1.PersistentVolumeAccessMode{"ReadWriteOnce"},
|
|
Resources: pvc.Spec.Resources,
|
|
StorageClassName: &storageClassName,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createPvcNoSize(name, ns string, annotations, labels map[string]string) *v1.PersistentVolumeClaim {
|
|
return &v1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: ns,
|
|
Annotations: annotations,
|
|
Labels: labels,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createClonePvc(name, ns string, annotations, labels map[string]string) *v1.PersistentVolumeClaim {
|
|
return createClonePvcWithSize(name, ns, annotations, labels, "1G")
|
|
}
|
|
|
|
func createClonePvcWithSize(name, ns string, annotations, labels map[string]string, size string) *v1.PersistentVolumeClaim {
|
|
return &v1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: ns,
|
|
Annotations: annotations,
|
|
Labels: labels,
|
|
UID: "pvc-uid",
|
|
},
|
|
Spec: v1.PersistentVolumeClaimSpec{
|
|
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
|
|
Resources: v1.ResourceRequirements{
|
|
Requests: v1.ResourceList{
|
|
v1.ResourceName(v1.ResourceStorage): resource.MustParse(size),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func createCloneBlockPvc(name, ns string, annotations, labels map[string]string) *v1.PersistentVolumeClaim {
|
|
pvc := createClonePvc(name, ns, annotations, labels)
|
|
VolumeMode := v1.PersistentVolumeBlock
|
|
pvc.Spec.VolumeMode = &VolumeMode
|
|
return pvc
|
|
}
|
|
|
|
func createSecret(name, ns, accessKey, secretKey string, labels map[string]string) *v1.Secret {
|
|
return &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: ns,
|
|
Labels: labels,
|
|
},
|
|
Type: "Opaque",
|
|
Data: map[string][]byte{
|
|
bootstrapapi.BootstrapTokenIDKey: []byte(accessKey),
|
|
bootstrapapi.BootstrapTokenSecretKey: []byte(secretKey),
|
|
bootstrapapi.BootstrapTokenUsageSigningKey: []byte("true"),
|
|
},
|
|
}
|
|
}
|
|
|
|
func createEnv(podEnvVar *importPodEnvVar, uid string) []v1.EnvVar {
|
|
env := []v1.EnvVar{
|
|
{
|
|
Name: ImporterSource,
|
|
Value: podEnvVar.source,
|
|
},
|
|
{
|
|
Name: ImporterEndpoint,
|
|
Value: podEnvVar.ep,
|
|
},
|
|
{
|
|
Name: ImporterContentType,
|
|
Value: podEnvVar.contentType,
|
|
},
|
|
{
|
|
Name: ImporterImageSize,
|
|
Value: podEnvVar.imageSize,
|
|
},
|
|
{
|
|
Name: OwnerUID,
|
|
Value: string(uid),
|
|
},
|
|
{
|
|
Name: InsecureTLSVar,
|
|
Value: strconv.FormatBool(podEnvVar.insecureTLS),
|
|
},
|
|
}
|
|
|
|
if podEnvVar.secretName != "" {
|
|
env = append(env, v1.EnvVar{
|
|
Name: ImporterAccessKeyID,
|
|
ValueFrom: &v1.EnvVarSource{
|
|
SecretKeyRef: &v1.SecretKeySelector{
|
|
LocalObjectReference: v1.LocalObjectReference{
|
|
Name: podEnvVar.secretName,
|
|
},
|
|
Key: KeyAccess,
|
|
},
|
|
},
|
|
}, v1.EnvVar{
|
|
Name: ImporterSecretKey,
|
|
ValueFrom: &v1.EnvVarSource{
|
|
SecretKeyRef: &v1.SecretKeySelector{
|
|
LocalObjectReference: v1.LocalObjectReference{
|
|
Name: podEnvVar.secretName,
|
|
},
|
|
Key: KeySecret,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
return env
|
|
}
|
|
|
|
func createImportController(pvcSpec *v1.PersistentVolumeClaim, podSpec *v1.Pod, ns string) (*ImportController, *v1.PersistentVolumeClaim, *v1.Pod, error) {
|
|
//Set up environment
|
|
myclient := k8sfake.NewSimpleClientset()
|
|
cdiclient := cdifake.NewSimpleClientset()
|
|
|
|
//create staging pvc and pod
|
|
pvc, err := myclient.CoreV1().PersistentVolumeClaims(ns).Create(pvcSpec)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("createImportController: failed to initialize and create pvc error = %v", err)
|
|
}
|
|
|
|
pod, err := myclient.CoreV1().Pods(ns).Create(podSpec)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("createImportController: failed to initialize and create pod error = %v", err)
|
|
}
|
|
|
|
// create informers and queue
|
|
k8sI := kubeinformers.NewSharedInformerFactory(myclient, noResyncPeriodFunc())
|
|
|
|
pvcInformer := k8sI.Core().V1().PersistentVolumeClaims()
|
|
podInformer := k8sI.Core().V1().Pods()
|
|
|
|
pvcQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
|
|
pvcQueue.Add(pvc)
|
|
|
|
k8sI.Core().V1().PersistentVolumeClaims().Informer().GetIndexer().Add(pvc)
|
|
k8sI.Core().V1().Pods().Informer().GetIndexer().Add(pod)
|
|
|
|
//run the informers
|
|
stop := make(chan struct{})
|
|
go pvcInformer.Informer().Run(stop)
|
|
go podInformer.Informer().Run(stop)
|
|
cache.WaitForCacheSync(stop, podInformer.Informer().HasSynced)
|
|
cache.WaitForCacheSync(stop, pvcInformer.Informer().HasSynced)
|
|
defer close(stop)
|
|
|
|
c := NewImportController(myclient, cdiclient, pvcInformer, podInformer, "test/image", "Always", "-v=5")
|
|
return c, pvc, pod, nil
|
|
}
|
|
|
|
func createCloneController(pvcSpec *v1.PersistentVolumeClaim, sourcePodSpec *v1.Pod, targetPodSpec *v1.Pod, targetNs, sourceNs string) (*CloneController, *v1.PersistentVolumeClaim, *v1.Pod, *v1.Pod, error) {
|
|
//Set up environment
|
|
myclient := k8sfake.NewSimpleClientset()
|
|
|
|
//create staging pvc and pods
|
|
pvc, err := myclient.CoreV1().PersistentVolumeClaims(targetNs).Create(pvcSpec)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, fmt.Errorf("createImportController: failed to initialize and create pvc error = %v", err)
|
|
}
|
|
|
|
sourcePod, err := myclient.CoreV1().Pods(sourceNs).Create(sourcePodSpec)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, fmt.Errorf("createCloneController: failed to initialize and create source pod error = %v", err)
|
|
}
|
|
targetPod, err := myclient.CoreV1().Pods(targetNs).Create(targetPodSpec)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, fmt.Errorf("createCloneController: failed to initialize and create target pod error = %v", err)
|
|
}
|
|
|
|
// create informers and queue
|
|
k8sI := kubeinformers.NewSharedInformerFactory(myclient, noResyncPeriodFunc())
|
|
|
|
pvcInformer := k8sI.Core().V1().PersistentVolumeClaims()
|
|
podInformer := k8sI.Core().V1().Pods()
|
|
|
|
pvcQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
|
|
pvcQueue.Add(pvc)
|
|
|
|
k8sI.Core().V1().PersistentVolumeClaims().Informer().GetIndexer().Add(pvc)
|
|
k8sI.Core().V1().Pods().Informer().GetIndexer().Add(sourcePod)
|
|
k8sI.Core().V1().Pods().Informer().GetIndexer().Add(targetPod)
|
|
|
|
//run the informers
|
|
stop := make(chan struct{})
|
|
go pvcInformer.Informer().Run(stop)
|
|
go podInformer.Informer().Run(stop)
|
|
cache.WaitForCacheSync(stop, podInformer.Informer().HasSynced)
|
|
cache.WaitForCacheSync(stop, pvcInformer.Informer().HasSynced)
|
|
defer close(stop)
|
|
|
|
c := NewCloneController(myclient, pvcInformer, podInformer, "test/mycloneimage", "Always", "-v=5")
|
|
return c, pvc, sourcePod, targetPod, nil
|
|
}
|
|
|
|
func createSourcePod(pvc *v1.PersistentVolumeClaim, pvcUID string) *v1.Pod {
|
|
_, sourcePvcName := ParseSourcePvcAnnotation(pvc.GetAnnotations()[AnnCloneRequest], "/")
|
|
podName := fmt.Sprintf("%s-", common.ClonerSourcePodName)
|
|
blockOwnerDeletion := true
|
|
isController := true
|
|
pod := &v1.Pod{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Pod",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
GenerateName: podName,
|
|
Annotations: map[string]string{
|
|
AnnCreatedBy: "yes",
|
|
AnnTargetPodNamespace: pvc.Namespace,
|
|
},
|
|
Labels: map[string]string{
|
|
CDILabelKey: CDILabelValue, //filtered by the podInformer
|
|
CDIComponentLabel: ClonerSourcePodName,
|
|
CloningLabelKey: CloningLabelValue + "-" + pvcUID, //used by podAffity
|
|
// this label is used when searching for a pvc's cloner source pod.
|
|
CloneUniqueID: pvcUID + "-source-pod",
|
|
},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "v1",
|
|
Kind: "PersistentVolumeClaim",
|
|
Name: pvc.Name,
|
|
UID: pvc.GetUID(),
|
|
BlockOwnerDeletion: &blockOwnerDeletion,
|
|
Controller: &isController,
|
|
},
|
|
},
|
|
},
|
|
Spec: v1.PodSpec{
|
|
InitContainers: []v1.Container{
|
|
{
|
|
Name: "init",
|
|
Image: "test/mycloneimage",
|
|
VolumeMounts: []v1.VolumeMount{
|
|
{
|
|
Name: socketPathName,
|
|
MountPath: ClonerSocketPath + "/" + pvcUID,
|
|
},
|
|
},
|
|
SecurityContext: &v1.SecurityContext{
|
|
Privileged: &[]bool{true}[0],
|
|
RunAsUser: &[]int64{0}[0],
|
|
},
|
|
Command: []string{"sh", "-c", "echo setting the pod as privileged"},
|
|
},
|
|
},
|
|
Containers: []v1.Container{
|
|
{
|
|
Name: common.ClonerSourcePodName,
|
|
Image: "test/mycloneimage",
|
|
ImagePullPolicy: v1.PullPolicy("Always"),
|
|
},
|
|
},
|
|
RestartPolicy: v1.RestartPolicyNever,
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: DataVolName,
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: sourcePvcName,
|
|
ReadOnly: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: socketPathName,
|
|
VolumeSource: v1.VolumeSource{
|
|
HostPath: &v1.HostPathVolumeSource{
|
|
Path: ClonerSocketPath + "/" + pvcUID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var volumeMode v1.PersistentVolumeMode
|
|
if pvc.Spec.VolumeMode != nil {
|
|
volumeMode = *pvc.Spec.VolumeMode
|
|
} else {
|
|
volumeMode = v1.PersistentVolumeFilesystem
|
|
}
|
|
|
|
if volumeMode == v1.PersistentVolumeBlock {
|
|
pod.Spec.Containers[0].VolumeDevices = addVolumeDevices()
|
|
pod.Spec.Containers[0].VolumeMounts = addCloneVolumeMounts("Block", pvcUID)
|
|
pod.Spec.Containers[0].Args = addArgs("source", pvcUID, "block")
|
|
} else {
|
|
pod.Spec.Containers[0].VolumeMounts = addCloneVolumeMounts("FS", pvcUID)
|
|
pod.Spec.Containers[0].Args = addArgs("source", pvcUID, "FS")
|
|
}
|
|
return pod
|
|
}
|
|
|
|
func createTargetPod(pvc *v1.PersistentVolumeClaim, pvcUID, podAffinityNamespace string) *v1.Pod {
|
|
// target pod name contains the pvc name
|
|
podName := fmt.Sprintf("%s-", ClonerTargetPodName)
|
|
blockOwnerDeletion := true
|
|
isController := true
|
|
ownerUID := pvc.UID
|
|
if len(pvc.OwnerReferences) == 1 {
|
|
ownerUID = pvc.OwnerReferences[0].UID
|
|
}
|
|
|
|
pod := &v1.Pod{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Pod",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
GenerateName: podName,
|
|
Annotations: map[string]string{
|
|
AnnCreatedBy: "yes",
|
|
AnnTargetPodNamespace: pvc.Namespace,
|
|
},
|
|
Labels: map[string]string{
|
|
common.CDILabelKey: common.CDILabelValue, //filtered by the podInformer
|
|
common.CDIComponentLabel: common.ClonerTargetPodName,
|
|
// this label is used when searching for a pvc's cloner target pod.
|
|
CloneUniqueID: pvcUID + "-target-pod",
|
|
PrometheusLabel: "",
|
|
},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "v1",
|
|
Kind: "PersistentVolumeClaim",
|
|
Name: pvc.Name,
|
|
UID: pvc.GetUID(),
|
|
BlockOwnerDeletion: &blockOwnerDeletion,
|
|
Controller: &isController,
|
|
},
|
|
},
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Affinity: &v1.Affinity{
|
|
PodAffinity: &v1.PodAffinity{
|
|
RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
|
|
{
|
|
LabelSelector: &metav1.LabelSelector{
|
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
|
{
|
|
Key: common.CloningLabelKey,
|
|
Operator: metav1.LabelSelectorOpIn,
|
|
Values: []string{CloningLabelValue + "-" + pvcUID},
|
|
},
|
|
},
|
|
},
|
|
Namespaces: []string{podAffinityNamespace}, //the scheduler looks for the namespace of the source pod
|
|
TopologyKey: common.CloningTopologyKey,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
InitContainers: []v1.Container{
|
|
{
|
|
Name: "init",
|
|
Image: "test/mycloneimage",
|
|
VolumeMounts: []v1.VolumeMount{
|
|
{
|
|
Name: socketPathName,
|
|
MountPath: ClonerSocketPath + "/" + pvcUID,
|
|
},
|
|
},
|
|
SecurityContext: &v1.SecurityContext{
|
|
Privileged: &[]bool{true}[0],
|
|
RunAsUser: &[]int64{0}[0],
|
|
},
|
|
Command: []string{"sh", "-c", "echo setting the pod as privileged"},
|
|
},
|
|
},
|
|
|
|
Containers: []v1.Container{
|
|
{
|
|
Name: common.ClonerTargetPodName,
|
|
Image: "test/mycloneimage",
|
|
ImagePullPolicy: v1.PullPolicy("Always"),
|
|
Ports: []v1.ContainerPort{
|
|
{
|
|
Name: "metrics",
|
|
ContainerPort: 8443,
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
Env: []v1.EnvVar{
|
|
{
|
|
Name: common.OwnerUID,
|
|
Value: string(ownerUID),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
RestartPolicy: v1.RestartPolicyNever,
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: DataVolName,
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: pvc.Name,
|
|
ReadOnly: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: socketPathName,
|
|
VolumeSource: v1.VolumeSource{
|
|
HostPath: &v1.HostPathVolumeSource{
|
|
Path: ClonerSocketPath + "/" + pvcUID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
var volumeMode v1.PersistentVolumeMode
|
|
if pvc.Spec.VolumeMode != nil {
|
|
volumeMode = *pvc.Spec.VolumeMode
|
|
} else {
|
|
volumeMode = v1.PersistentVolumeFilesystem
|
|
}
|
|
|
|
if volumeMode == v1.PersistentVolumeBlock {
|
|
pod.Spec.Containers[0].VolumeDevices = addVolumeDevices()
|
|
pod.Spec.Containers[0].VolumeMounts = addCloneVolumeMounts("Block", pvcUID)
|
|
pod.Spec.Containers[0].Args = addArgs("target", pvcUID, "block")
|
|
} else {
|
|
pod.Spec.Containers[0].VolumeMounts = addCloneVolumeMounts("FS", pvcUID)
|
|
pod.Spec.Containers[0].Args = addArgs("target", pvcUID, "FS")
|
|
}
|
|
return pod
|
|
}
|
|
|
|
func getPvcKey(pvc *corev1.PersistentVolumeClaim, t *testing.T) string {
|
|
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(pvc)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error getting key for pvc %v: %v", pvc.Name, err)
|
|
return ""
|
|
}
|
|
return key
|
|
}
|
|
|
|
func createUploadPod(pvc *v1.PersistentVolumeClaim) *v1.Pod {
|
|
name := "cdi-upload-" + pvc.Name
|
|
secretName := name + "-server-tls"
|
|
requestImageSize, _ := getRequestedImageSize(pvc)
|
|
|
|
pod := &v1.Pod{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Pod",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: pvc.Namespace,
|
|
Annotations: map[string]string{
|
|
annCreatedByUpload: "yes",
|
|
},
|
|
Labels: map[string]string{
|
|
"app": "containerized-data-importer",
|
|
"cdi.kubevirt.io": "cdi-upload-server",
|
|
"service": name,
|
|
},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
MakePVCOwnerReference(pvc),
|
|
},
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Containers: []v1.Container{
|
|
{
|
|
Name: "cdi-upload-server",
|
|
Image: "test/myimage",
|
|
ImagePullPolicy: v1.PullPolicy("Always"),
|
|
VolumeMounts: []v1.VolumeMount{
|
|
{
|
|
Name: DataVolName,
|
|
MountPath: "/data",
|
|
},
|
|
{
|
|
Name: ScratchVolName,
|
|
MountPath: "/scratch",
|
|
},
|
|
},
|
|
Env: []v1.EnvVar{
|
|
{
|
|
Name: "TLS_KEY",
|
|
ValueFrom: &v1.EnvVarSource{
|
|
SecretKeyRef: &v1.SecretKeySelector{
|
|
LocalObjectReference: v1.LocalObjectReference{
|
|
Name: secretName,
|
|
},
|
|
Key: keys.KeyStoreTLSKeyFile,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "TLS_CERT",
|
|
ValueFrom: &v1.EnvVarSource{
|
|
SecretKeyRef: &v1.SecretKeySelector{
|
|
LocalObjectReference: v1.LocalObjectReference{
|
|
Name: secretName,
|
|
},
|
|
Key: keys.KeyStoreTLSCertFile,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "CLIENT_CERT",
|
|
ValueFrom: &v1.EnvVarSource{
|
|
SecretKeyRef: &v1.SecretKeySelector{
|
|
LocalObjectReference: v1.LocalObjectReference{
|
|
Name: secretName,
|
|
},
|
|
Key: keys.KeyStoreTLSCAFile,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: common.UploadImageSize,
|
|
Value: requestImageSize,
|
|
},
|
|
},
|
|
Args: []string{"-v=" + "5"},
|
|
ReadinessProbe: &v1.Probe{
|
|
Handler: v1.Handler{
|
|
HTTPGet: &v1.HTTPGetAction{
|
|
Path: "/healthz",
|
|
Port: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 8080,
|
|
},
|
|
},
|
|
},
|
|
InitialDelaySeconds: 2,
|
|
PeriodSeconds: 5,
|
|
},
|
|
},
|
|
},
|
|
RestartPolicy: v1.RestartPolicyOnFailure,
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: DataVolName,
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: pvc.Name,
|
|
ReadOnly: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: ScratchVolName,
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: pvc.Name + "-scratch",
|
|
ReadOnly: false,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return pod
|
|
}
|
|
|
|
func createUploadService(pvc *v1.PersistentVolumeClaim) *v1.Service {
|
|
name := "cdi-upload-" + pvc.Name
|
|
blockOwnerDeletion := true
|
|
isController := true
|
|
service := &v1.Service{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Service",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: pvc.Namespace,
|
|
Annotations: map[string]string{
|
|
annCreatedByUpload: "yes",
|
|
},
|
|
Labels: map[string]string{
|
|
"app": "containerized-data-importer",
|
|
"cdi.kubevirt.io": "cdi-upload-server",
|
|
},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "v1",
|
|
Kind: "PersistentVolumeClaim",
|
|
Name: pvc.Name,
|
|
UID: pvc.GetUID(),
|
|
BlockOwnerDeletion: &blockOwnerDeletion,
|
|
Controller: &isController,
|
|
},
|
|
},
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Protocol: "TCP",
|
|
Port: 443,
|
|
TargetPort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 8443,
|
|
},
|
|
},
|
|
},
|
|
Selector: map[string]string{
|
|
"service": name,
|
|
},
|
|
},
|
|
}
|
|
return service
|
|
}
|
|
|
|
func createCDIConfig(name string) *cdiv1.CDIConfig {
|
|
return createCDIConfigWithStorageClass(name, "")
|
|
}
|
|
|
|
func createCDIConfigWithStorageClass(name string, storageClass string) *cdiv1.CDIConfig {
|
|
return &cdiv1.CDIConfig{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Labels: map[string]string{
|
|
common.CDILabelKey: common.CDILabelValue,
|
|
common.CDIComponentLabel: "",
|
|
},
|
|
},
|
|
Status: cdiv1.CDIConfigStatus{
|
|
ScratchSpaceStorageClass: storageClass,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
|
|
return &storagev1.StorageClass{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Annotations: annotations,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createStorageClassWithProvisioner(name string, annotations map[string]string, provisioner string) *storagev1.StorageClass {
|
|
return &storagev1.StorageClass{
|
|
Provisioner: provisioner,
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Annotations: annotations,
|
|
},
|
|
}
|
|
}
|
|
func createSnapshotClass(name string, annotations map[string]string, snapshotter string) *snapshotv1.VolumeSnapshotClass {
|
|
return &snapshotv1.VolumeSnapshotClass{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "VolumeSnapshotClass",
|
|
APIVersion: snapshotv1.SchemeGroupVersion.String(),
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Annotations: annotations,
|
|
},
|
|
Snapshotter: snapshotter,
|
|
}
|
|
}
|
|
|
|
func createVolumeSnapshotContentCrd() *apiextensionsv1beta1.CustomResourceDefinition {
|
|
return &apiextensionsv1beta1.CustomResourceDefinition{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "CustomResourceDefinition",
|
|
APIVersion: apiextensionsv1beta1.SchemeGroupVersion.String(),
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: crdv1.VolumeSnapshotContentResourcePlural + "." + crdv1.GroupName,
|
|
},
|
|
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
|
|
Group: crdv1.GroupName,
|
|
Version: crdv1.SchemeGroupVersion.Version,
|
|
Scope: apiextensionsv1beta1.ClusterScoped,
|
|
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
|
|
Plural: crdv1.VolumeSnapshotContentResourcePlural,
|
|
Kind: reflect.TypeOf(crdv1.VolumeSnapshotContent{}).Name(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func createVolumeSnapshotClassCrd() *apiextensionsv1beta1.CustomResourceDefinition {
|
|
return &apiextensionsv1beta1.CustomResourceDefinition{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "CustomResourceDefinition",
|
|
APIVersion: apiextensionsv1beta1.SchemeGroupVersion.String(),
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: crdv1.VolumeSnapshotClassResourcePlural + "." + crdv1.GroupName,
|
|
},
|
|
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
|
|
Group: crdv1.GroupName,
|
|
Version: crdv1.SchemeGroupVersion.Version,
|
|
Scope: apiextensionsv1beta1.ClusterScoped,
|
|
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
|
|
Plural: crdv1.VolumeSnapshotClassResourcePlural,
|
|
Kind: reflect.TypeOf(crdv1.VolumeSnapshotClass{}).Name(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func createVolumeSnapshotCrd() *apiextensionsv1beta1.CustomResourceDefinition {
|
|
return &apiextensionsv1beta1.CustomResourceDefinition{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "CustomResourceDefinition",
|
|
APIVersion: apiextensionsv1beta1.SchemeGroupVersion.String(),
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: crdv1.VolumeSnapshotResourcePlural + "." + crdv1.GroupName,
|
|
},
|
|
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
|
|
Group: crdv1.GroupName,
|
|
Version: crdv1.SchemeGroupVersion.Version,
|
|
Scope: apiextensionsv1beta1.NamespaceScoped,
|
|
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
|
|
Plural: crdv1.VolumeSnapshotResourcePlural,
|
|
Kind: reflect.TypeOf(crdv1.VolumeSnapshot{}).Name(),
|
|
},
|
|
},
|
|
}
|
|
}
|