mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Adds list command of storage for devfile v2 (#3788)
* Adds list command of storage for devfile v2 * Fixes import names in cli/storage/list.go * Fixes display of componentName in list output. It also converts some functions to private and adds project flag while creating components in the storage script. * Uses default storage size from the adapter * Changes default storage size in integration test
This commit is contained in:
@@ -61,7 +61,7 @@ const (
|
||||
BinBash = "/bin/sh"
|
||||
|
||||
// Default volume size for volumes defined in a devfile
|
||||
volumeSize = "5Gi"
|
||||
DefaultVolumeSize = "1Gi"
|
||||
|
||||
// EnvProjectsRoot is the env defined for /projects where component mountSources=true
|
||||
EnvProjectsRoot = "PROJECTS_ROOT"
|
||||
@@ -180,7 +180,7 @@ func GetVolumes(devfileObj devfileParser.DevfileObj) map[string][]DevfileVolume
|
||||
containerNameToVolumes := make(map[string][]DevfileVolume)
|
||||
for _, containerComp := range containerComponents {
|
||||
for _, volumeMount := range containerComp.Container.VolumeMounts {
|
||||
size := volumeSize
|
||||
size := DefaultVolumeSize
|
||||
|
||||
// check if there is a volume component name against the container component volume mount name
|
||||
if volumeComp, ok := volumeNameToVolumeComponent[volumeMount.Name]; ok {
|
||||
|
||||
@@ -186,7 +186,7 @@ func TestGetVolumes(t *testing.T) {
|
||||
"comp1": {
|
||||
{
|
||||
Name: "myvolume1",
|
||||
Size: "5Gi",
|
||||
Size: "1Gi",
|
||||
ContainerPath: "/my/volume/mount/path1",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ package storage
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/openshift/odo/pkg/devfile"
|
||||
adapterCommon "github.com/openshift/odo/pkg/devfile/adapters/common"
|
||||
"github.com/openshift/odo/pkg/devfile/parser/data/common"
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
"github.com/openshift/odo/pkg/machineoutput"
|
||||
@@ -27,8 +28,6 @@ var (
|
||||
`)
|
||||
)
|
||||
|
||||
const defaultStorageSize = "1Gi"
|
||||
|
||||
type StorageCreateOptions struct {
|
||||
storageName string
|
||||
storageSize string
|
||||
@@ -55,7 +54,7 @@ func (o *StorageCreateOptions) Complete(name string, cmd *cobra.Command, args []
|
||||
|
||||
o.componentName = o.EnvSpecificInfo.GetName()
|
||||
if o.storageSize == "" {
|
||||
o.storageSize = defaultStorageSize
|
||||
o.storageSize = adapterCommon.DefaultVolumeSize
|
||||
}
|
||||
} else {
|
||||
o.Context = genericclioptions.NewContext(cmd)
|
||||
|
||||
@@ -2,7 +2,13 @@ package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/openshift/odo/pkg/devfile"
|
||||
"github.com/openshift/odo/pkg/devfile/parser"
|
||||
"github.com/openshift/odo/pkg/devfile/parser/data/common"
|
||||
"github.com/openshift/odo/pkg/odo/cli/component"
|
||||
odoutil "github.com/openshift/odo/pkg/util"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
@@ -31,6 +37,9 @@ var (
|
||||
type StorageListOptions struct {
|
||||
componentContext string
|
||||
*genericclioptions.Context
|
||||
|
||||
isDevfile bool
|
||||
parser.DevfileObj
|
||||
}
|
||||
|
||||
// NewStorageListOptions creates a new StorageListOptions instance
|
||||
@@ -40,8 +49,19 @@ func NewStorageListOptions() *StorageListOptions {
|
||||
|
||||
// Complete completes StorageListOptions after they've been created
|
||||
func (o *StorageListOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) {
|
||||
// this also initializes the context as well
|
||||
o.Context = genericclioptions.NewContext(cmd)
|
||||
devFilePath := filepath.Join(o.componentContext, component.DevfilePath)
|
||||
o.isDevfile = odoutil.CheckPathExists(devFilePath)
|
||||
if o.isDevfile {
|
||||
o.Context = genericclioptions.NewDevfileContext(cmd)
|
||||
|
||||
o.DevfileObj, err = devfile.ParseAndValidate(devFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// this also initializes the context as well
|
||||
o.Context = genericclioptions.NewContext(cmd)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,32 +71,53 @@ func (o *StorageListOptions) Validate() (err error) {
|
||||
}
|
||||
|
||||
func (o *StorageListOptions) Run() (err error) {
|
||||
|
||||
storageList, err := storage.ListStorageWithState(o.Client, o.LocalConfigInfo, o.Component(), o.Application)
|
||||
if err != nil {
|
||||
return err
|
||||
var storageList storage.StorageList
|
||||
var componentName string
|
||||
if o.isDevfile {
|
||||
componentName = o.EnvSpecificInfo.GetName()
|
||||
storageList, err = storage.DevfileList(o.KClient, o.DevfileObj.Data, o.EnvSpecificInfo.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
componentName = o.LocalConfigInfo.GetName()
|
||||
storageList, err = storage.ListStorageWithState(o.Client, o.LocalConfigInfo, o.Component(), o.Application)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if log.IsJSON() {
|
||||
machineoutput.OutputSuccess(storageList)
|
||||
} else {
|
||||
printStorage(storageList, o.LocalConfigInfo.GetName())
|
||||
if o.isDevfile && isContainerDisplay(storageList, o.DevfileObj.Data.GetComponents()) {
|
||||
printStorageWithContainer(storageList, componentName)
|
||||
} else {
|
||||
printStorage(storageList, componentName)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// printStorage prints the given storageList
|
||||
func printStorage(storageList storage.StorageList, compName string) {
|
||||
|
||||
if len(storageList.Items) > 0 {
|
||||
|
||||
tabWriterMounted := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent)
|
||||
|
||||
storageMap := make(map[string]bool)
|
||||
|
||||
// create headers of mounted storage table
|
||||
fmt.Fprintln(tabWriterMounted, "NAME", "\t", "SIZE", "\t", "PATH", "\t", "STATE")
|
||||
// iterating over all mounted storage and put in the mount storage table
|
||||
for _, mStorage := range storageList.Items {
|
||||
fmt.Fprintln(tabWriterMounted, mStorage.Name, "\t", mStorage.Spec.Size, "\t", mStorage.Spec.Path, "\t", mStorage.Status)
|
||||
_, ok := storageMap[mStorage.Name]
|
||||
if !ok {
|
||||
storageMap[mStorage.Name] = true
|
||||
fmt.Fprintln(tabWriterMounted, mStorage.Name, "\t", mStorage.Spec.Size, "\t", mStorage.Spec.Path, "\t", mStorage.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// print all mounted storage of the given component
|
||||
@@ -89,6 +130,83 @@ func printStorage(storageList storage.StorageList, compName string) {
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
// printStorageWithContainer prints the given storageList with the corresponding container name
|
||||
func printStorageWithContainer(storageList storage.StorageList, compName string) {
|
||||
|
||||
if len(storageList.Items) > 0 {
|
||||
|
||||
tabWriterMounted := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent)
|
||||
|
||||
// create headers of mounted storage table
|
||||
fmt.Fprintln(tabWriterMounted, "NAME", "\t", "SIZE", "\t", "PATH", "\t", "CONTAINER", "\t", "STATE")
|
||||
// iterating over all mounted storage and put in the mount storage table
|
||||
for _, mStorage := range storageList.Items {
|
||||
fmt.Fprintln(tabWriterMounted, mStorage.Name, "\t", mStorage.Spec.Size, "\t", mStorage.Spec.Path, "\t", mStorage.Spec.ContainerName, "\t", mStorage.Status)
|
||||
}
|
||||
|
||||
// print all mounted storage of the given component
|
||||
log.Infof("The component '%v' has the following storage attached:", compName)
|
||||
tabWriterMounted.Flush()
|
||||
} else {
|
||||
log.Infof("The component '%v' has no storage attached", compName)
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
// isContainerDisplay checks whether the container name should be included in the output
|
||||
func isContainerDisplay(storageList storage.StorageList, components []common.DevfileComponent) bool {
|
||||
|
||||
// get all the container names
|
||||
componentsMap := make(map[string]bool)
|
||||
for _, comp := range components {
|
||||
if comp.Container != nil {
|
||||
componentsMap[comp.Container.Name] = true
|
||||
}
|
||||
}
|
||||
|
||||
storageCompMap := make(map[string][]string)
|
||||
pathMap := make(map[string]string)
|
||||
storageMap := make(map[string]storage.StorageStatus)
|
||||
|
||||
for _, storageItem := range storageList.Items {
|
||||
if pathMap[storageItem.Name] == "" {
|
||||
pathMap[storageItem.Name] = storageItem.Spec.Path
|
||||
}
|
||||
if storageMap[storageItem.Name] == "" {
|
||||
storageMap[storageItem.Name] = storageItem.Status
|
||||
}
|
||||
|
||||
// check if the storage is mounted on the same path in all the containers
|
||||
if pathMap[storageItem.Name] != storageItem.Spec.Path {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if the storage is in the same state for all the containers
|
||||
if storageMap[storageItem.Name] != storageItem.Status {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if the storage is mounted on a valid devfile container
|
||||
// this situation can arrive when a container is removed from the devfile
|
||||
// but the state is not pushed thus it exists on the cluster
|
||||
_, ok := componentsMap[storageItem.Spec.ContainerName]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
storageCompMap[storageItem.Name] = append(storageCompMap[storageItem.Name], storageItem.Spec.ContainerName)
|
||||
}
|
||||
|
||||
for _, containerNames := range storageCompMap {
|
||||
// check if the storage is mounted on all the devfile containers
|
||||
if len(containerNames) != len(componentsMap) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// NewCmdStorageList implements the odo storage list command.
|
||||
func NewCmdStorageList(name, fullName string) *cobra.Command {
|
||||
o := NewStorageListOptions()
|
||||
|
||||
112
pkg/odo/cli/storage/list_test.go
Normal file
112
pkg/odo/cli/storage/list_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/openshift/odo/pkg/devfile/parser/data/common"
|
||||
"github.com/openshift/odo/pkg/storage"
|
||||
"github.com/openshift/odo/pkg/testingutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_isContainerDisplay(t *testing.T) {
|
||||
generateStorage := func(storage storage.Storage, status storage.StorageStatus, containerName string) storage.Storage {
|
||||
storage.Status = status
|
||||
storage.Spec.ContainerName = containerName
|
||||
return storage
|
||||
}
|
||||
|
||||
type args struct {
|
||||
storageList storage.StorageList
|
||||
obj []common.DevfileComponent
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "case 1: storage is mounted on all the containers on the same path",
|
||||
args: args{
|
||||
storageList: storage.StorageList{
|
||||
Items: []storage.Storage{
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/data"), storage.StateTypePushed, "container-0"),
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/data"), storage.StateTypePushed, "container-1"),
|
||||
},
|
||||
},
|
||||
obj: []common.DevfileComponent{
|
||||
testingutil.GetFakeContainerComponent("container-0"),
|
||||
testingutil.GetFakeContainerComponent("container-1"),
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "case 2: storage is mounted on different paths",
|
||||
args: args{
|
||||
storageList: storage.StorageList{
|
||||
Items: []storage.Storage{
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/data"), storage.StateTypePushed, "container-0"),
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/path"), storage.StateTypePushed, "container-1"),
|
||||
},
|
||||
},
|
||||
obj: []common.DevfileComponent{
|
||||
testingutil.GetFakeContainerComponent("container-0"),
|
||||
testingutil.GetFakeContainerComponent("container-1"),
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "case 3: storage is mounted to the same path on all the containers but states are different",
|
||||
args: args{
|
||||
storageList: storage.StorageList{
|
||||
Items: []storage.Storage{
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/data"), storage.StateTypePushed, "container-0"),
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/data"), storage.StateTypeNotPushed, "container-1"),
|
||||
},
|
||||
},
|
||||
obj: []common.DevfileComponent{
|
||||
testingutil.GetFakeContainerComponent("container-0"),
|
||||
testingutil.GetFakeContainerComponent("container-1"),
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "case 4: storage is not mounted on all the containers",
|
||||
args: args{
|
||||
storageList: storage.StorageList{
|
||||
Items: []storage.Storage{
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/data"), storage.StateTypePushed, "container-0"),
|
||||
},
|
||||
},
|
||||
obj: []common.DevfileComponent{
|
||||
testingutil.GetFakeContainerComponent("container-0"),
|
||||
testingutil.GetFakeContainerComponent("container-1"),
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "case 5: storage is mounted on a container deleted locally from the devfile",
|
||||
args: args{
|
||||
storageList: storage.StorageList{
|
||||
Items: []storage.Storage{
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/data"), storage.StateTypePushed, "container-0"),
|
||||
generateStorage(storage.GetMachineReadableFormat("pvc-1", "1Gi", "/data"), storage.StateTypePushed, "container-1"),
|
||||
},
|
||||
},
|
||||
obj: []common.DevfileComponent{
|
||||
testingutil.GetFakeContainerComponent("container-0"),
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isContainerDisplay(tt.args.storageList, tt.args.obj); got != tt.want {
|
||||
t.Errorf("isContainerDisplay() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,23 @@ package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/openshift/odo/pkg/machineoutput"
|
||||
"github.com/openshift/odo/pkg/devfile/adapters/common"
|
||||
"reflect"
|
||||
|
||||
"github.com/openshift/odo/pkg/config"
|
||||
"github.com/openshift/odo/pkg/devfile/parser/data"
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
"github.com/openshift/odo/pkg/machineoutput"
|
||||
|
||||
applabels "github.com/openshift/odo/pkg/application/labels"
|
||||
componentlabels "github.com/openshift/odo/pkg/component/labels"
|
||||
"github.com/openshift/odo/pkg/kclient"
|
||||
"github.com/openshift/odo/pkg/occlient"
|
||||
storagelabels "github.com/openshift/odo/pkg/storage/labels"
|
||||
"github.com/openshift/odo/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
@@ -465,6 +468,21 @@ func GetMachineReadableFormat(storageName, storageSize, storagePath string) Stor
|
||||
}
|
||||
}
|
||||
|
||||
// GetMachineFormatWithContainer gives machine readable Storage definition
|
||||
// storagePath indicates the path to which the storage is mounted to, "" if not mounted
|
||||
func GetMachineFormatWithContainer(storageName, storageSize, storagePath string, container string) Storage {
|
||||
storage := Storage{
|
||||
TypeMeta: metav1.TypeMeta{Kind: "storage", APIVersion: apiVersion},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: storageName},
|
||||
Spec: StorageSpec{
|
||||
Size: storageSize,
|
||||
Path: storagePath,
|
||||
},
|
||||
}
|
||||
storage.Spec.ContainerName = container
|
||||
return storage
|
||||
}
|
||||
|
||||
func ListStorageWithState(client *occlient.Client, localConfig *config.LocalConfigInfo, componentName string, applicationName string) (StorageList, error) {
|
||||
|
||||
storageConfig, err := localConfig.StorageList()
|
||||
@@ -550,3 +568,127 @@ func MachineReadableSuccessOutput(storageName string, message string) {
|
||||
|
||||
machineoutput.OutputSuccess(machineOutput)
|
||||
}
|
||||
|
||||
// devfileListMounted lists the storage which are mounted on a container
|
||||
func devfileListMounted(kClient *kclient.Client, componentName string) (StorageList, error) {
|
||||
pod, err := kClient.GetPodUsingComponentName(componentName)
|
||||
if err != nil {
|
||||
if _, ok := err.(*kclient.PodNotFoundError); ok {
|
||||
return StorageList{}, nil
|
||||
}
|
||||
return StorageList{}, err
|
||||
}
|
||||
|
||||
var storage []Storage
|
||||
var volumeMounts []Storage
|
||||
for _, container := range pod.Spec.Containers {
|
||||
for _, volumeMount := range container.VolumeMounts {
|
||||
volumeMounts = append(volumeMounts, Storage{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: volumeMount.Name},
|
||||
Spec: StorageSpec{
|
||||
Path: volumeMount.MountPath,
|
||||
ContainerName: container.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(volumeMounts) <= 0 {
|
||||
return StorageList{}, nil
|
||||
}
|
||||
|
||||
label := fmt.Sprintf("component=%s", componentName)
|
||||
pvcs, err := kClient.GetPVCsFromSelector(label)
|
||||
if err != nil {
|
||||
return StorageList{}, errors.Wrapf(err, "unable to get PVC using selector %v", storagelabels.StorageLabel)
|
||||
}
|
||||
|
||||
for _, pvc := range pvcs {
|
||||
found := false
|
||||
for _, volumeMount := range volumeMounts {
|
||||
if volumeMount.Name == pvc.Name+"-vol" {
|
||||
found = true
|
||||
size := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
|
||||
storage = append(storage, GetMachineFormatWithContainer(pvc.Labels[storagelabels.DevfileStorageLabel], size.String(), volumeMount.Spec.Path, volumeMount.Spec.ContainerName))
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return StorageList{}, fmt.Errorf("mount path for pvc %s not found", pvc.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return StorageList{Items: storage}, nil
|
||||
}
|
||||
|
||||
// getLocalDevfileStorage lists the storage from the devfile
|
||||
func getLocalDevfileStorage(devfileData data.DevfileData) StorageList {
|
||||
volumeSizeMap := make(map[string]string)
|
||||
for _, component := range devfileData.GetComponents() {
|
||||
if component.Volume == nil {
|
||||
continue
|
||||
}
|
||||
if component.Volume.Size == "" {
|
||||
component.Volume.Size = common.DefaultVolumeSize
|
||||
}
|
||||
volumeSizeMap[component.Volume.Name] = component.Volume.Size
|
||||
}
|
||||
|
||||
components := devfileData.GetComponents()
|
||||
var storage []Storage
|
||||
for _, component := range components {
|
||||
if component.Container == nil {
|
||||
continue
|
||||
}
|
||||
for _, volumeMount := range component.Container.VolumeMounts {
|
||||
size, ok := volumeSizeMap[volumeMount.Name]
|
||||
if ok {
|
||||
storage = append(storage, GetMachineFormatWithContainer(volumeMount.Name, size, volumeMount.Path, component.Container.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return StorageList{Items: storage}
|
||||
}
|
||||
|
||||
// DevfileList lists the storage from the local devfile and cluster with their respective state
|
||||
func DevfileList(kClient *kclient.Client, devfileData data.DevfileData, componentName string) (StorageList, error) {
|
||||
localStorage := getLocalDevfileStorage(devfileData)
|
||||
|
||||
clusterStorage, err := devfileListMounted(kClient, componentName)
|
||||
if err != nil {
|
||||
return StorageList{}, err
|
||||
}
|
||||
|
||||
var storageList []Storage
|
||||
|
||||
// find the local storage which are in a pushed and not pushed state
|
||||
for _, localStore := range localStorage.Items {
|
||||
found := false
|
||||
for _, clusterStore := range clusterStorage.Items {
|
||||
if reflect.DeepEqual(localStore, clusterStore) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if found {
|
||||
localStore.Status = StateTypePushed
|
||||
} else {
|
||||
localStore.Status = StateTypeNotPushed
|
||||
}
|
||||
storageList = append(storageList, localStore)
|
||||
}
|
||||
|
||||
// find the cluster storage which have been deleted locally
|
||||
for _, clusterStore := range clusterStorage.Items {
|
||||
found := false
|
||||
for _, localStore := range localStorage.Items {
|
||||
if reflect.DeepEqual(localStore, clusterStore) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
clusterStore.Status = StateTypeLocallyDeleted
|
||||
storageList = append(storageList, clusterStore)
|
||||
}
|
||||
}
|
||||
return GetMachineReadableFormatForList(storageList), nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/openshift/odo/pkg/devfile/parser/data"
|
||||
"github.com/openshift/odo/pkg/devfile/parser/data/common"
|
||||
"github.com/openshift/odo/pkg/kclient"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -937,3 +941,612 @@ func TestListStorageWithState(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateStorage(storage Storage, status StorageStatus, containerName string) Storage {
|
||||
storage.Status = status
|
||||
storage.Spec.ContainerName = containerName
|
||||
return storage
|
||||
}
|
||||
|
||||
func TestGetLocalDevfileStorage(t *testing.T) {
|
||||
type args struct {
|
||||
devfileData data.DevfileData
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want StorageList
|
||||
}{
|
||||
{
|
||||
name: "case 1: list all the volumes in the devfile along with their respective size and containers",
|
||||
args: args{
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-0",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-0",
|
||||
Path: "/path",
|
||||
},
|
||||
{
|
||||
Name: "volume-1",
|
||||
Path: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-1",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-1",
|
||||
Path: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
testingutil.GetFakeVolumeComponent("volume-0", "5Gi"),
|
||||
testingutil.GetFakeVolumeComponent("volume-1", "10Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: StorageList{
|
||||
Items: []Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/path"), "", "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/data"), "", "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/data"), "", "container-1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "case 2: list all the volumes in the devfile with the default size when no size is mentioned",
|
||||
args: args{
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-0",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-0",
|
||||
Path: "/path",
|
||||
},
|
||||
{
|
||||
Name: "volume-1",
|
||||
Path: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
testingutil.GetFakeVolumeComponent("volume-0", ""),
|
||||
testingutil.GetFakeVolumeComponent("volume-1", "10Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: StorageList{
|
||||
Items: []Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "1Gi", "/path"), "", "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/data"), "", "container-0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "case 3: return empty when no volumes is mounted",
|
||||
args: args{
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-0",
|
||||
},
|
||||
},
|
||||
testingutil.GetFakeVolumeComponent("volume-0", ""),
|
||||
testingutil.GetFakeVolumeComponent("volume-1", "10Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: StorageList{
|
||||
Items: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getLocalDevfileStorage(tt.args.devfileData); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getLocalDevfileStorage() difference between got and want : %v", pretty.Compare(got, tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevfileListMounted(t *testing.T) {
|
||||
type args struct {
|
||||
componentName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
returnedPods *corev1.PodList
|
||||
returnedPVCs *corev1.PersistentVolumeClaimList
|
||||
want StorageList
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "case 1: should error out for multiple pods returned",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePod("nodejs", "pod-0"),
|
||||
*testingutil.CreateFakePod("nodejs", "pod-1"),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "case 2: pod not found",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{},
|
||||
},
|
||||
want: StorageList{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 3: no volume mounts on pod",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePod("nodejs", "pod-0"),
|
||||
},
|
||||
},
|
||||
want: StorageList{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 4: two volumes mounted on a single container",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []corev1.Container{
|
||||
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
||||
{Name: "volume-0-vol", MountPath: "/data"},
|
||||
{Name: "volume-1-vol", MountPath: "/path"},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
*testingutil.FakePVC("volume-0", "5Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-0"}),
|
||||
*testingutil.FakePVC("volume-1", "10Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-1"}),
|
||||
},
|
||||
},
|
||||
want: StorageList{
|
||||
Items: []Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/data"), "", "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/path"), "", "container-0"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 5: one volume is mounted on a single container and another on both",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []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"},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
*testingutil.FakePVC("volume-0", "5Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-0"}),
|
||||
*testingutil.FakePVC("volume-1", "10Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-1"}),
|
||||
},
|
||||
},
|
||||
want: StorageList{
|
||||
Items: []Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/data"), "", "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/path"), "", "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/path"), "", "container-1"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 6: pvc for volumeMount not found",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []corev1.Container{
|
||||
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
||||
{Name: "volume-0-vol", MountPath: "/data"},
|
||||
}),
|
||||
testingutil.CreateFakeContainer("container-1"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
*testingutil.FakePVC("volume-0", "5Gi", map[string]string{"component": "nodejs"}),
|
||||
*testingutil.FakePVC("volume-1", "5Gi", map[string]string{"component": "nodejs"}),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "case 7: the storage label should be used as the name of the storage",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []corev1.Container{
|
||||
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
||||
{Name: "volume-0-nodejs-vol", MountPath: "/data"},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
*testingutil.FakePVC("volume-0-nodejs", "5Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-0"}),
|
||||
},
|
||||
},
|
||||
want: StorageList{
|
||||
Items: []Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/data"), "", "container-0"),
|
||||
},
|
||||
},
|
||||
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", "pods", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, tt.returnedPods, nil
|
||||
})
|
||||
|
||||
got, err := devfileListMounted(fakeClient, tt.args.componentName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("devfileListMounted() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("devfileListMounted() result is different: %v", pretty.Compare(got, tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevfileList(t *testing.T) {
|
||||
type args struct {
|
||||
devfileData data.DevfileData
|
||||
componentName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
returnedPods *corev1.PodList
|
||||
returnedPVCs *corev1.PersistentVolumeClaimList
|
||||
want StorageList
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "case 1: no volume on devfile and no pod on cluster",
|
||||
args: args{
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
testingutil.GetFakeContainerComponent("runtime"),
|
||||
},
|
||||
},
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{},
|
||||
},
|
||||
want: GetMachineReadableFormatForList(nil),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 2: no volume on devfile and pod",
|
||||
args: args{
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
testingutil.GetFakeContainerComponent("runtime"),
|
||||
},
|
||||
},
|
||||
componentName: "nodejs",
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []corev1.Container{testingutil.CreateFakeContainer("container-0")}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{},
|
||||
},
|
||||
want: GetMachineReadableFormatForList(nil),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 3: same two volumes on cluster and devFile",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-0",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-0",
|
||||
Path: "/data",
|
||||
},
|
||||
{
|
||||
Name: "volume-1",
|
||||
Path: "/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
testingutil.GetFakeVolumeComponent("volume-0", "5Gi"),
|
||||
testingutil.GetFakeVolumeComponent("volume-1", "10Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []corev1.Container{
|
||||
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
||||
{Name: "volume-0-vol", MountPath: "/data"},
|
||||
{Name: "volume-1-vol", MountPath: "/path"},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
*testingutil.FakePVC("volume-0", "5Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-0"}),
|
||||
*testingutil.FakePVC("volume-1", "10Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-1"}),
|
||||
},
|
||||
},
|
||||
want: GetMachineReadableFormatForList([]Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/data"), StateTypePushed, "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/path"), StateTypePushed, "container-0"),
|
||||
}),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 4: both volumes, present on the cluster and devFile, are different",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-0",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-0",
|
||||
Path: "/data",
|
||||
},
|
||||
{
|
||||
Name: "volume-1",
|
||||
Path: "/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
testingutil.GetFakeVolumeComponent("volume-0", "5Gi"),
|
||||
testingutil.GetFakeVolumeComponent("volume-1", "10Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []corev1.Container{
|
||||
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
||||
{Name: "volume-00-vol", MountPath: "/data"},
|
||||
{Name: "volume-11-vol", MountPath: "/path"},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
*testingutil.FakePVC("volume-00", "5Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-00"}),
|
||||
*testingutil.FakePVC("volume-11", "10Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-11"}),
|
||||
},
|
||||
},
|
||||
want: GetMachineReadableFormatForList([]Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/data"), StateTypeNotPushed, "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/path"), StateTypeNotPushed, "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-00", "5Gi", "/data"), StateTypeLocallyDeleted, "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-11", "10Gi", "/path"), StateTypeLocallyDeleted, "container-0"),
|
||||
}),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 5: two containers with different volumes but one container is not pushed",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-0",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-0",
|
||||
Path: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-1",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-1",
|
||||
Path: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
testingutil.GetFakeVolumeComponent("volume-0", "5Gi"),
|
||||
testingutil.GetFakeVolumeComponent("volume-1", "10Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []corev1.Container{
|
||||
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
||||
{Name: "volume-0-vol", MountPath: "/data"},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
*testingutil.FakePVC("volume-0", "5Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-0"}),
|
||||
},
|
||||
},
|
||||
want: GetMachineReadableFormatForList([]Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/data"), StateTypePushed, "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-1", "10Gi", "/data"), StateTypeNotPushed, "container-1"),
|
||||
}),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 6: two containers with different volumes on the cluster but one container is deleted locally",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-0",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-0",
|
||||
Path: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
testingutil.GetFakeVolumeComponent("volume-0", "5Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePodWithContainers("nodejs", "pod-0", []corev1.Container{
|
||||
testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
|
||||
{Name: "volume-0-vol", MountPath: "/data"},
|
||||
}),
|
||||
testingutil.CreateFakeContainerWithVolumeMounts("container-1", []corev1.VolumeMount{
|
||||
{Name: "volume-0-vol", MountPath: "/data"}},
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
*testingutil.FakePVC("volume-0", "5Gi", map[string]string{"component": "nodejs", storageLabels.DevfileStorageLabel: "volume-0"}),
|
||||
},
|
||||
},
|
||||
want: GetMachineReadableFormatForList([]Storage{
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/data"), StateTypePushed, "container-0"),
|
||||
generateStorage(GetMachineReadableFormat("volume-0", "5Gi", "/data"), StateTypeLocallyDeleted, "container-1"),
|
||||
}),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "case 7: multiple pods are present on the cluster",
|
||||
args: args{
|
||||
componentName: "nodejs",
|
||||
devfileData: &testingutil.TestDevfileData{
|
||||
Components: []common.DevfileComponent{
|
||||
{
|
||||
Container: &common.Container{
|
||||
Name: "container-0",
|
||||
VolumeMounts: []common.VolumeMount{
|
||||
{
|
||||
Name: "volume-0",
|
||||
Path: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
testingutil.GetFakeVolumeComponent("volume-0", "5Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
returnedPods: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
*testingutil.CreateFakePod("nodejs", "pod-0"),
|
||||
*testingutil.CreateFakePod("nodejs", "pod-1"),
|
||||
},
|
||||
},
|
||||
returnedPVCs: &corev1.PersistentVolumeClaimList{},
|
||||
want: StorageList{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
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", "pods", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, tt.returnedPods, nil
|
||||
})
|
||||
|
||||
got, err := DevfileList(fakeClient, tt.args.devfileData, tt.args.componentName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DevfileList() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("DevfileList() result is different: %v", pretty.Compare(tt.want, got))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ type StorageSpec struct {
|
||||
Size string `json:"size,omitempty"`
|
||||
// if path is empty, it indicates that the storage is not mounted in any component
|
||||
Path string `json:"path,omitempty"`
|
||||
|
||||
ContainerName string `json:"containerName,omitempty"`
|
||||
}
|
||||
|
||||
// StorageList is a list of storages
|
||||
|
||||
17
pkg/testingutil/containers.go
Normal file
17
pkg/testingutil/containers.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package testingutil
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
// CreateFakeContainer creates a container with the given containerName
|
||||
func CreateFakeContainer(containerName string) corev1.Container {
|
||||
return corev1.Container{
|
||||
Name: containerName,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateFakeContainerWithVolumeMounts creates a container with the given containerName and volumeMounts
|
||||
func CreateFakeContainerWithVolumeMounts(containerName string, volumeMounts []corev1.VolumeMount) corev1.Container {
|
||||
container := CreateFakeContainer(containerName)
|
||||
container.VolumeMounts = volumeMounts
|
||||
return container
|
||||
}
|
||||
@@ -143,7 +143,9 @@ func (d TestDevfileData) AddProjects(projects []common.DevfileProject) error { r
|
||||
|
||||
func (d TestDevfileData) UpdateProject(project common.DevfileProject) {}
|
||||
|
||||
func (d TestDevfileData) AddStarterProjects(projects []common.DevfileStarterProject) error { return nil }
|
||||
func (d TestDevfileData) AddStarterProjects(projects []common.DevfileStarterProject) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d TestDevfileData) UpdateStarterProject(project common.DevfileStarterProject) {}
|
||||
|
||||
|
||||
@@ -20,3 +20,10 @@ func CreateFakePod(componentName, podName string) *corev1.Pod {
|
||||
}
|
||||
return fakePod
|
||||
}
|
||||
|
||||
// CreateFakePodWithContainers creates a fake pod with the given pod name, container name and containers
|
||||
func CreateFakePodWithContainers(componentName, podName string, containers []corev1.Container) *corev1.Pod {
|
||||
fakePod := CreateFakePod(componentName, podName)
|
||||
fakePod.Spec.Containers = containers
|
||||
return fakePod
|
||||
}
|
||||
|
||||
@@ -643,7 +643,7 @@ var _ = Describe("odo devfile push command tests", func() {
|
||||
// Verify the pvc size for firstvol
|
||||
storageSize := cliRunner.GetPVCSize(cmpName, "firstvol", namespace)
|
||||
// should be the default size
|
||||
Expect(storageSize).To(ContainSubstring("5Gi"))
|
||||
Expect(storageSize).To(ContainSubstring("1Gi"))
|
||||
|
||||
// Verify the pvc size for secondvol
|
||||
storageSize = cliRunner.GetPVCSize(cmpName, "secondvol", namespace)
|
||||
|
||||
@@ -49,7 +49,7 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
Context("When devfile storage create command is executed", func() {
|
||||
|
||||
It("should create the storage and mount it on the container", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
@@ -90,7 +90,7 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
})
|
||||
|
||||
It("should create a storage with default size when --size is not provided", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
@@ -109,7 +109,7 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
})
|
||||
|
||||
It("should create a storage when storage is not provided", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
@@ -126,7 +126,7 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
})
|
||||
|
||||
It("should create and output in json format", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
@@ -140,7 +140,7 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
|
||||
Context("When devfile storage delete command is executed", func() {
|
||||
It("should delete the storage and unmount it on the container", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
@@ -184,7 +184,7 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
})
|
||||
|
||||
It("should delete the storage and output in json format", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
@@ -198,9 +198,80 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Context("When devfile storage list command is executed", func() {
|
||||
It("should list the storage with the proper states", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml"))
|
||||
|
||||
storageNames := []string{helper.RandString(5), helper.RandString(5)}
|
||||
pathNames := []string{"/data", "/data-1"}
|
||||
sizes := []string{"5Gi", "1Gi"}
|
||||
|
||||
helper.CmdShouldPass("odo", "storage", "create", storageNames[0], "--path", pathNames[0], "--size", sizes[0], "--context", context)
|
||||
stdOut := helper.CmdShouldPass("odo", "storage", "list", "--context", context)
|
||||
helper.MatchAllInOutput(stdOut, []string{storageNames[0], pathNames[0], sizes[0], "Not Pushed", cmpName})
|
||||
helper.DontMatchAllInOutput(stdOut, []string{"CONTAINER", "runtime"})
|
||||
|
||||
helper.CmdShouldPass("odo", "push", "--context", context)
|
||||
stdOut = helper.CmdShouldPass("odo", "storage", "list", "--context", context)
|
||||
helper.MatchAllInOutput(stdOut, []string{storageNames[0], pathNames[0], sizes[0], "Pushed"})
|
||||
helper.DontMatchAllInOutput(stdOut, []string{"CONTAINER", "runtime"})
|
||||
|
||||
helper.CmdShouldPass("odo", "storage", "create", storageNames[1], "--path", pathNames[1], "--size", sizes[1], "--context", context)
|
||||
|
||||
stdOut = helper.CmdShouldPass("odo", "storage", "list", "--context", context)
|
||||
helper.MatchAllInOutput(stdOut, []string{storageNames[0], pathNames[0], sizes[0], "Pushed"})
|
||||
helper.MatchAllInOutput(stdOut, []string{storageNames[1], pathNames[1], sizes[1], "Not Pushed"})
|
||||
helper.DontMatchAllInOutput(stdOut, []string{"CONTAINER", "runtime"})
|
||||
|
||||
helper.CmdShouldPass("odo", "storage", "delete", storageNames[0], "-f", "--context", context)
|
||||
stdOut = helper.CmdShouldPass("odo", "storage", "list", "--context", context)
|
||||
helper.MatchAllInOutput(stdOut, []string{storageNames[0], pathNames[0], sizes[0], "Locally Deleted"})
|
||||
helper.DontMatchAllInOutput(stdOut, []string{"CONTAINER", "runtime"})
|
||||
})
|
||||
|
||||
It("should list the storage with the proper states and container names", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-volume-components.yaml"), filepath.Join(context, "devfile.yaml"))
|
||||
|
||||
stdOut := helper.CmdShouldPass("odo", "storage", "list", "--context", context)
|
||||
helper.MatchAllInOutput(stdOut, []string{"firstvol", "secondvol", "Not Pushed", "CONTAINER", "runtime", "runtime2"})
|
||||
|
||||
helper.CmdShouldPass("odo", "push", "--context", context)
|
||||
|
||||
stdOut = helper.CmdShouldPass("odo", "storage", "list", "--context", context)
|
||||
helper.MatchAllInOutput(stdOut, []string{"firstvol", "secondvol", "Pushed", "CONTAINER", "runtime", "runtime2"})
|
||||
|
||||
helper.CmdShouldPass("odo", "storage", "delete", "firstvol", "-f", "--context", context)
|
||||
|
||||
stdOut = helper.CmdShouldPass("odo", "storage", "list", "--context", context)
|
||||
helper.MatchAllInOutput(stdOut, []string{"firstvol", "secondvol", "Pushed", "Locally Deleted", "CONTAINER", "runtime", "runtime2"})
|
||||
})
|
||||
|
||||
It("should list output in json format", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context, "devfile.yaml"))
|
||||
|
||||
helper.CmdShouldPass("odo", "storage", "create", "mystorage", "--path=/opt/app-root/src/storage/", "--size=1Gi", "--context", context)
|
||||
|
||||
actualStorageList := helper.CmdShouldPass("odo", "storage", "list", "--context", context, "-o", "json")
|
||||
desiredStorageList := `{"kind":"List","apiVersion":"odo.dev/v1alpha1","metadata":{},"items":[{"kind":"storage","apiVersion":"odo.dev/v1alpha1","metadata":{"name":"mystorage","creationTimestamp":null},"spec":{"size":"1Gi","path":"/opt/app-root/src/storage/","containerName":"runtime"},"status":"Not Pushed"}]}`
|
||||
Expect(desiredStorageList).Should(MatchJSON(actualStorageList))
|
||||
})
|
||||
})
|
||||
|
||||
Context("When devfile storage commands are invalid", func() {
|
||||
It("should error if same storage name is provided again", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
@@ -215,7 +286,7 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
})
|
||||
|
||||
It("should error if same path is provided again", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
@@ -230,7 +301,7 @@ var _ = Describe("odo devfile storage command tests", func() {
|
||||
})
|
||||
|
||||
It("should throw error if no storage is present", func() {
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context}
|
||||
args := []string{"create", "nodejs", cmpName, "--context", context, "--project", namespace}
|
||||
helper.CmdShouldPass("odo", args...)
|
||||
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context)
|
||||
|
||||
Reference in New Issue
Block a user