mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
530 lines
16 KiB
Go
530 lines
16 KiB
Go
package storage
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/google/go-cmp/cmp"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
ktesting "k8s.io/client-go/testing"
|
|
|
|
"github.com/redhat-developer/odo/pkg/kclient"
|
|
odolabels "github.com/redhat-developer/odo/pkg/labels"
|
|
"github.com/redhat-developer/odo/pkg/testingutil"
|
|
"github.com/redhat-developer/odo/pkg/util"
|
|
)
|
|
|
|
func Test_kubernetesClient_List(t *testing.T) {
|
|
type fields struct {
|
|
generic generic
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
returnedDeployments *appsv1.DeploymentList
|
|
returnedPVCs *corev1.PersistentVolumeClaimList
|
|
want StorageList
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "case 1: should error out for multiple pods returned",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
*testingutil.CreateFakeDeployment("nodejs", true),
|
|
*testingutil.CreateFakeDeployment("nodejs", true),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "case 2: pod not found",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{},
|
|
},
|
|
want: StorageList{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "case 3: no volume mounts on pod",
|
|
fields: fields{
|
|
generic: generic{
|
|
componentName: "nodejs",
|
|
appName: "app",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
*testingutil.CreateFakeDeployment("nodejs", true),
|
|
},
|
|
},
|
|
want: StorageList{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "case 4: two volumes mounted on a single container",
|
|
fields: fields{
|
|
generic: generic{
|
|
componentName: "nodejs",
|
|
appName: "app",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
|
{Name: "volume-0-vol", MountPath: "/data"},
|
|
{Name: "volume-1-vol", MountPath: "/path"},
|
|
}),
|
|
}, []corev1.Container{}, true),
|
|
},
|
|
},
|
|
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
|
Items: []corev1.PersistentVolumeClaim{
|
|
*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
|
|
*testingutil.FakePVC("volume-1", "10Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
|
|
},
|
|
},
|
|
want: StorageList{
|
|
Items: []Storage{
|
|
generateStorage(NewStorage("volume-0", "5Gi", "/data", nil), "", "container-0"),
|
|
generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-0"),
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "case 5: one volume is mounted on a single container and another on both",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
|
{Name: "volume-0-vol", MountPath: "/data"},
|
|
{Name: "volume-1-vol", MountPath: "/path"},
|
|
}),
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-1", []corev1.VolumeMount{
|
|
{Name: "volume-1-vol", MountPath: "/path"},
|
|
}),
|
|
}, []corev1.Container{}, true),
|
|
},
|
|
},
|
|
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
|
Items: []corev1.PersistentVolumeClaim{
|
|
*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
|
|
*testingutil.FakePVC("volume-1", "10Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
|
|
},
|
|
},
|
|
want: StorageList{
|
|
Items: []Storage{
|
|
generateStorage(NewStorage("volume-0", "5Gi", "/data", nil), "", "container-0"),
|
|
generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-0"),
|
|
generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-1"),
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "case 6: pvc for volumeMount not found",
|
|
fields: fields{
|
|
generic: generic{
|
|
componentName: "nodejs",
|
|
appName: "app",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
|
{Name: "volume-0-vol", MountPath: "/data"},
|
|
}),
|
|
testingutil.CreateFakeContainer("container-1"),
|
|
}, []corev1.Container{}, true),
|
|
},
|
|
},
|
|
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
|
Items: []corev1.PersistentVolumeClaim{
|
|
*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
|
|
*testingutil.FakePVC("volume-1", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "case 7: the storage label should be used as the name of the storage",
|
|
fields: fields{
|
|
generic: generic{
|
|
componentName: "nodejs",
|
|
appName: "app",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
|
{Name: "volume-0-nodejs-vol", MountPath: "/data"},
|
|
}),
|
|
}, []corev1.Container{}, true),
|
|
},
|
|
},
|
|
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
|
Items: []corev1.PersistentVolumeClaim{
|
|
*testingutil.FakePVC("volume-0-nodejs", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
|
|
},
|
|
},
|
|
want: StorageList{
|
|
Items: []Storage{
|
|
generateStorage(NewStorage("volume-0", "5Gi", "/data", nil), "", "container-0"),
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "case 8: no pvc found for mount path",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
|
{Name: "volume-0-vol", MountPath: "/data"},
|
|
{Name: "volume-1-vol", MountPath: "/path"},
|
|
}),
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-1", []corev1.VolumeMount{
|
|
{Name: "volume-1-vol", MountPath: "/path"},
|
|
{Name: "volume-vol", MountPath: "/path1"},
|
|
}),
|
|
}, []corev1.Container{}, true),
|
|
},
|
|
},
|
|
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
|
Items: []corev1.PersistentVolumeClaim{
|
|
*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
|
|
*testingutil.FakePVC("volume-1", "10Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
|
|
},
|
|
},
|
|
want: StorageList{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "case 9: avoid the source volume's mount",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
|
{Name: "volume-0-vol", MountPath: "/data"},
|
|
{Name: "volume-1-vol", MountPath: "/path"},
|
|
}),
|
|
testingutil.CreateFakeContainerWithVolumeMounts("container-1", []corev1.VolumeMount{
|
|
{Name: "volume-1-vol", MountPath: "/path"},
|
|
{Name: OdoSourceVolume, MountPath: "/path1"},
|
|
}),
|
|
}, []corev1.Container{}, true),
|
|
},
|
|
},
|
|
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
|
Items: []corev1.PersistentVolumeClaim{
|
|
*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
|
|
*testingutil.FakePVC("volume-1", "10Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
|
|
},
|
|
},
|
|
want: StorageList{
|
|
Items: []Storage{
|
|
generateStorage(NewStorage("volume-0", "5Gi", "/data", nil), "", "container-0"),
|
|
generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-0"),
|
|
generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-1"),
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "case 10: avoid the mandatory volume mounts used by odo",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
returnedDeployments: &appsv1.DeploymentList{
|
|
Items: []appsv1.Deployment{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "nodejs",
|
|
Labels: map[string]string{
|
|
"component": "nodejs",
|
|
},
|
|
},
|
|
Spec: appsv1.DeploymentSpec{
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
InitContainers: []corev1.Container{
|
|
{
|
|
Name: "my-container-with-shared-project",
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "odo-shared-project",
|
|
MountPath: "/opt/",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
|
Items: []corev1.PersistentVolumeClaim{},
|
|
},
|
|
want: StorageList{},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
fakeClient, fakeClientSet := kclient.FakeNew()
|
|
|
|
fakeClientSet.Kubernetes.PrependReactor("list", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) {
|
|
return true, tt.returnedPVCs, nil
|
|
})
|
|
|
|
fakeClientSet.Kubernetes.PrependReactor("list", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) {
|
|
return true, tt.returnedDeployments, nil
|
|
})
|
|
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
k := kubernetesClient{
|
|
generic: tt.fields.generic,
|
|
client: fakeClient,
|
|
}
|
|
got, err := k.List()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("List() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
t.Errorf("kubernetesClient.List() mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_kubernetesClient_Create(t *testing.T) {
|
|
type fields struct {
|
|
generic generic
|
|
}
|
|
type args struct {
|
|
storage Storage
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "case 1: valid storage",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
args: args{
|
|
storage: NewStorageWithContainer("storage-0", "5Gi", "/data", "runtime", util.GetBoolPtr(false)),
|
|
},
|
|
},
|
|
{
|
|
name: "case 2: invalid storage size",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
args: args{
|
|
storage: NewStorageWithContainer("storage-0", "example", "/data", "runtime", util.GetBoolPtr(false)),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "case 3: valid odo project related storage",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
args: args{
|
|
storage: NewStorageWithContainer("odo-projects-vol", "5Gi", "/data", "runtime", util.GetBoolPtr(false)),
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
fkclient, fkclientset := kclient.FakeNew()
|
|
|
|
k := kubernetesClient{
|
|
generic: tt.fields.generic,
|
|
client: fkclient,
|
|
}
|
|
if err := k.Create(tt.args.storage); (err != nil) != tt.wantErr {
|
|
t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if tt.wantErr == true {
|
|
return
|
|
}
|
|
|
|
// Check for validating actions performed
|
|
if (len(fkclientset.Kubernetes.Actions()) != 1) && (tt.wantErr != true) {
|
|
t.Errorf("expected 1 action in CreatePVC got: %v", fkclientset.Kubernetes.Actions())
|
|
return
|
|
}
|
|
|
|
createdPVC := fkclientset.Kubernetes.Actions()[0].(ktesting.CreateAction).GetObject().(*corev1.PersistentVolumeClaim)
|
|
quantity, err := resource.ParseQuantity(tt.args.storage.Spec.Size)
|
|
if err != nil {
|
|
t.Errorf("failed to create quantity by calling resource.ParseQuantity(%v)", tt.args.storage.Spec.Size)
|
|
}
|
|
|
|
wantLabels := odolabels.GetLabels(tt.fields.generic.componentName, tt.fields.generic.appName, "", odolabels.ComponentDevMode, false)
|
|
odolabels.AddStorageInfo(wantLabels, tt.args.storage.Name, strings.Contains(tt.args.storage.Name, OdoSourceVolume))
|
|
|
|
// created PVC should be labeled with labels passed to CreatePVC
|
|
if diff := cmp.Diff(wantLabels, createdPVC.Labels); diff != "" {
|
|
t.Errorf("kubernetesClient.Create() wantLabels mismatch (-want +got):\n%s", diff)
|
|
}
|
|
// name, size of createdPVC should be matching to size, name passed to CreatePVC
|
|
if diff := cmp.Diff(quantity, createdPVC.Spec.Resources.Requests["storage"]); diff != "" {
|
|
t.Errorf("kubernetesClient.Create() quantity mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
wantedPVCName, err := generatePVCName(tt.args.storage.Name, tt.fields.generic.componentName, tt.fields.generic.appName)
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
if diff := cmp.Diff(wantedPVCName, createdPVC.Name); diff != "" {
|
|
t.Errorf("kubernetesClient.Create() wantedPVCName mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_kubernetesClient_Delete(t *testing.T) {
|
|
pvcName := "pvc-0"
|
|
returnedPVCs := corev1.PersistentVolumeClaimList{
|
|
Items: []corev1.PersistentVolumeClaim{
|
|
*testingutil.FakePVC(pvcName, "5Gi", getStorageLabels("storage-0", "nodejs", "app")),
|
|
},
|
|
}
|
|
|
|
type fields struct {
|
|
generic generic
|
|
}
|
|
type args struct {
|
|
name string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "case 1: delete successful",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
args: args{
|
|
"storage-0",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "case 2: pvc not found",
|
|
fields: fields{
|
|
generic: generic{
|
|
appName: "app",
|
|
componentName: "nodejs",
|
|
},
|
|
},
|
|
args: args{
|
|
"storage-example",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
fkclient, fkclientset := kclient.FakeNew()
|
|
|
|
fkclientset.Kubernetes.PrependReactor("list", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) {
|
|
return true, &returnedPVCs, nil
|
|
})
|
|
|
|
fkclientset.Kubernetes.PrependReactor("delete", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) {
|
|
if action.(ktesting.DeleteAction).GetName() != pvcName {
|
|
t.Errorf("delete called with = %v, want %v", action.(ktesting.DeleteAction).GetName(), pvcName)
|
|
}
|
|
return true, nil, nil
|
|
})
|
|
|
|
k := kubernetesClient{
|
|
generic: tt.fields.generic,
|
|
client: fkclient,
|
|
}
|
|
if err := k.Delete(tt.args.name); (err != nil) != tt.wantErr {
|
|
t.Errorf("Delete() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if tt.wantErr {
|
|
return
|
|
}
|
|
|
|
if len(fkclientset.Kubernetes.Actions()) != 2 {
|
|
t.Errorf("expected 2 action, got %v", len(fkclientset.Kubernetes.Actions()))
|
|
}
|
|
})
|
|
}
|
|
}
|