From 282b71e12aafe5a50ef0fbb9380769bb5c1fc856 Mon Sep 17 00:00:00 2001 From: Andrey Velichkevich Date: Wed, 12 Aug 2020 13:03:45 +0100 Subject: [PATCH] Support volume settings in Katib config (#1291) * Support volume settings in config * Set default path --- pkg/controller.v1beta1/consts/const.go | 37 ++- .../suggestion/composer/composer.go | 50 ++-- .../suggestion/composer/composer_test.go | 222 +++++++++++++++--- pkg/util/v1beta1/katibconfig/config.go | 83 ++++++- 4 files changed, 303 insertions(+), 89 deletions(-) diff --git a/pkg/controller.v1beta1/consts/const.go b/pkg/controller.v1beta1/consts/const.go index 050858cfb1c..b4fe1a3cea2 100644 --- a/pkg/controller.v1beta1/consts/const.go +++ b/pkg/controller.v1beta1/consts/const.go @@ -32,6 +32,8 @@ const ( // ContainerSuggestion is the container name in Suggestion. ContainerSuggestion = "suggestion" + // ContainerSuggestionVolumeName is the volume name that mounted on suggestion container + ContainerSuggestionVolumeName = "suggestion-volume" // DefaultSuggestionPort is the default port of suggestion service. DefaultSuggestionPort = 6789 @@ -79,6 +81,22 @@ const ( // DefaultDiskRequest is the default value for disk request. DefaultDiskRequest = "500Mi" + // DefaultContainerSuggestionVolumeMountPath is the default mount path in suggestion container + DefaultContainerSuggestionVolumeMountPath = "/opt/katib/data" + + // DefaultSuggestionStorageClassName is the default value for suggestion's volume storage class name + DefaultSuggestionStorageClassName = "katib-suggestion" + + // DefaultSuggestionVolumeStorage is the default value for suggestion's volume storage + DefaultSuggestionVolumeStorage = "1Gi" + + // DefaultSuggestionVolumeAccessMode is the default value for suggestion's volume access mode + DefaultSuggestionVolumeAccessMode = corev1.ReadWriteOnce + + // DefaultSuggestionVolumeLocalPathPrefix is the default cluster local path prefix for suggestion volume + // Full default local path = /tmp/katib/suggestions/-- + DefaultSuggestionVolumeLocalPathPrefix = "/tmp/katib/suggestions/" + // ReconcileErrorReason is the reason when there is a reconcile error. ReconcileErrorReason = "ReconcileError" @@ -127,25 +145,6 @@ const ( // UnavailableMetricValue is the value when metric was not reported or metric value can't be converted to float64 UnavailableMetricValue = "unavailable" - - // DefaultSuggestionVolumeLocalPathPrefix is the default cluster local path prefix for suggestion volume - // Full local path = /tmp/katib/suggestions/- - DefaultSuggestionVolumeLocalPathPrefix = "/tmp/katib/suggestions/" - - // DefaultSuggestionStorageClassName is the default value for suggestion's volume storage class name - DefaultSuggestionStorageClassName = "katib-suggestion" - - // DefaultSuggestionVolumeAccessMode is the default value for suggestion's volume access mode - DefaultSuggestionVolumeAccessMode = corev1.ReadWriteOnce - - // DefaultSuggestionVolumeStorage is the default value for suggestion's volume storage - DefaultSuggestionVolumeStorage = "1Gi" - - // ContainerSuggestionVolumeName is the volume name that mounted on suggestion container - ContainerSuggestionVolumeName = "suggestion-volume" - - // DefaultContainerSuggestionVolumeMountPath is the default mount path in suggestion container - DefaultContainerSuggestionVolumeMountPath = "/opt/katib/data" ) var ( diff --git a/pkg/controller.v1beta1/suggestion/composer/composer.go b/pkg/controller.v1beta1/suggestion/composer/composer.go index bdc663f5363..75695af5531 100644 --- a/pkg/controller.v1beta1/suggestion/composer/composer.go +++ b/pkg/controller.v1beta1/suggestion/composer/composer.go @@ -6,7 +6,6 @@ import ( "github.com/spf13/viper" 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" "sigs.k8s.io/controller-runtime/pkg/client" @@ -193,7 +192,7 @@ func (g *General) desiredContainer(s *suggestionsv1beta1.Suggestion, suggestionC c.VolumeMounts = []corev1.VolumeMount{ { Name: consts.ContainerSuggestionVolumeName, - MountPath: consts.DefaultContainerSuggestionVolumeMountPath, + MountPath: suggestionConfigData.VolumeMountPath, }, } } @@ -203,31 +202,20 @@ func (g *General) desiredContainer(s *suggestionsv1beta1.Suggestion, suggestionC // DesiredVolume returns desired PVC and PV for suggestion. // If StorageClassName != DefaultSuggestionStorageClassName returns only PVC. func (g *General) DesiredVolume(s *suggestionsv1beta1.Suggestion) (*corev1.PersistentVolumeClaim, *corev1.PersistentVolume, error) { - persistentVolumeName := util.GetAlgorithmPersistentVolumeName(s) - // TODO (andreyvelich): Enable to specify these values from Katib config - storageClassName := consts.DefaultSuggestionStorageClassName - persistentVolumePath := consts.DefaultSuggestionVolumeLocalPathPrefix + persistentVolumeName - volumeAccessModes := consts.DefaultSuggestionVolumeAccessMode + suggestionConfigData, err := katibconfig.GetSuggestionConfigData(s.Spec.AlgorithmName, g.Client) + if err != nil { + return nil, nil, err + } - volumeStorage, _ := resource.ParseQuantity(consts.DefaultSuggestionVolumeStorage) + persistentVolumeName := util.GetAlgorithmPersistentVolumeName(s) pvc := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: util.GetAlgorithmPersistentVolumeClaimName(s), Namespace: s.Namespace, }, - Spec: corev1.PersistentVolumeClaimSpec{ - StorageClassName: &storageClassName, - AccessModes: []corev1.PersistentVolumeAccessMode{ - volumeAccessModes, - }, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: volumeStorage, - }, - }, - }, + Spec: suggestionConfigData.PersistentVolumeClaimSpec, } // Add owner reference to the pvc so that it could be GC after the suggestion is deleted @@ -237,7 +225,7 @@ func (g *General) DesiredVolume(s *suggestionsv1beta1.Suggestion) (*corev1.Persi var pv *corev1.PersistentVolume // Create PV with local hostPath by default - if storageClassName == consts.DefaultSuggestionStorageClassName { + if *pvc.Spec.StorageClassName == consts.DefaultSuggestionStorageClassName { localLabel := map[string]string{"type": "local"} pv = &corev1.PersistentVolume{ @@ -245,20 +233,14 @@ func (g *General) DesiredVolume(s *suggestionsv1beta1.Suggestion) (*corev1.Persi Name: persistentVolumeName, Labels: localLabel, }, - Spec: corev1.PersistentVolumeSpec{ - StorageClassName: consts.DefaultSuggestionStorageClassName, - AccessModes: []corev1.PersistentVolumeAccessMode{ - volumeAccessModes, - }, - PersistentVolumeSource: corev1.PersistentVolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: persistentVolumePath, - }, - }, - Capacity: corev1.ResourceList{ - corev1.ResourceStorage: volumeStorage, - }, - }, + Spec: suggestionConfigData.PersistentVolumeSpec, + } + + // If default host path is specified attach pv name to the path. + // Full default local path = DefaultSuggestionVolumeLocalPathPrefix-- + if pv.Spec.PersistentVolumeSource.HostPath != nil && + pv.Spec.PersistentVolumeSource.HostPath.Path == consts.DefaultSuggestionVolumeLocalPathPrefix { + pv.Spec.PersistentVolumeSource.HostPath.Path = pv.Spec.PersistentVolumeSource.HostPath.Path + persistentVolumeName } // Add owner reference to the pv so that it could be GC after the suggestion is deleted diff --git a/pkg/controller.v1beta1/suggestion/composer/composer_test.go b/pkg/controller.v1beta1/suggestion/composer/composer_test.go index dec82699b15..30d395acf55 100644 --- a/pkg/controller.v1beta1/suggestion/composer/composer_test.go +++ b/pkg/controller.v1beta1/suggestion/composer/composer_test.go @@ -33,6 +33,7 @@ import ( experimentsv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/experiments/v1beta1" suggestionsv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/suggestions/v1beta1" "github.com/kubeflow/katib/pkg/controller.v1beta1/consts" + "github.com/kubeflow/katib/pkg/util/v1beta1/katibconfig" ) var ( @@ -130,13 +131,13 @@ func TestDesiredDeployment(t *testing.T) { }{ { suggestion: newFakeSuggestion(), - configMap: newFakeKatibConfig(), + configMap: newFakeKatibConfig(newFakeSuggestionConfig()), err: true, testDescription: "Set controller reference error", }, { suggestion: newFakeSuggestion(), - configMap: newFakeKatibConfig(), + configMap: newFakeKatibConfig(newFakeSuggestionConfig()), expectedDeployment: newFakeDeployment(), err: false, testDescription: "Desired Deployment valid run", @@ -144,7 +145,7 @@ func TestDesiredDeployment(t *testing.T) { { suggestion: newFakeSuggestion(), configMap: func() *corev1.ConfigMap { - cm := newFakeKatibConfig() + cm := newFakeKatibConfig(newFakeSuggestionConfig()) cm.Data["suggestion"] = strings.ReplaceAll(cm.Data["suggestion"], string(imagePullPolicy), "invalid") return cm }(), @@ -159,12 +160,28 @@ func TestDesiredDeployment(t *testing.T) { { suggestion: newFakeSuggestion(), configMap: func() *corev1.ConfigMap { - cm := newFakeKatibConfig() + cm := newFakeKatibConfig(newFakeSuggestionConfig()) cm.Data["suggestion"] = strings.ReplaceAll(cm.Data["suggestion"], cpu, "invalid") return cm }(), err: true, - testDescription: "Invalid CPU limit", + testDescription: "Get suggestion config error, invalid CPU limit", + }, + { + suggestion: newFakeSuggestion(), + configMap: func() *corev1.ConfigMap { + sc := newFakeSuggestionConfig() + sc.VolumeMountPath = "/custom/container/path" + cm := newFakeKatibConfig(sc) + return cm + }(), + expectedDeployment: func() *appsv1.Deployment { + deploy := newFakeDeployment() + deploy.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath = "/custom/container/path" + return deploy + }(), + err: false, + testDescription: "Suggestion container with custom volume mount path", }, } @@ -304,10 +321,19 @@ func TestDesiredVolume(t *testing.T) { mgr, err := manager.New(cfg, manager.Options{}) g.Expect(err).NotTo(gomega.HaveOccurred()) + stopMgr, mgrStopped := StartTestManager(mgr, g) + defer func() { + close(stopMgr) + mgrStopped.Wait() + }() + + c := mgr.GetClient() + composer := New(mgr) tcs := []struct { suggestion *suggestionsv1beta1.Suggestion + configMap *corev1.ConfigMap expectedPVC *corev1.PersistentVolumeClaim expectedPV *corev1.PersistentVolume err bool @@ -315,20 +341,133 @@ func TestDesiredVolume(t *testing.T) { }{ { suggestion: newFakeSuggestion(), + configMap: newFakeKatibConfig(newFakeSuggestionConfig()), err: true, testDescription: "Set controller reference error", }, { suggestion: newFakeSuggestion(), + err: true, + testDescription: "Get suggestion config error, not found Katib config", + }, + { + suggestion: newFakeSuggestion(), + configMap: newFakeKatibConfig(newFakeSuggestionConfig()), expectedPVC: newFakePVC(), expectedPV: newFakePV(), err: false, - testDescription: "Desired Volume valid run", + testDescription: "Desired Volume valid run with default pvc and pv", + }, + { + suggestion: newFakeSuggestion(), + configMap: func() *corev1.ConfigMap { + sc := newFakeSuggestionConfig() + storageClass := "custom-storage-class" + volumeStorage, _ := resource.ParseQuantity("5Gi") + + sc.PersistentVolumeClaimSpec = corev1.PersistentVolumeClaimSpec{ + StorageClassName: &storageClass, + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + corev1.ReadOnlyMany, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: volumeStorage, + }, + }, + } + cm := newFakeKatibConfig(sc) + return cm + }(), + expectedPVC: func() *corev1.PersistentVolumeClaim { + pvc := newFakePVC() + storageClass := "custom-storage-class" + volumeStorage, _ := resource.ParseQuantity("5Gi") + + pvc.Spec.StorageClassName = &storageClass + pvc.Spec.AccessModes = append(pvc.Spec.AccessModes, corev1.ReadOnlyMany) + pvc.Spec.Resources.Requests[corev1.ResourceStorage] = volumeStorage + return pvc + }(), + expectedPV: nil, + err: false, + testDescription: "Custom PVC with not default storage class", + }, + { + suggestion: newFakeSuggestion(), + configMap: func() *corev1.ConfigMap { + sc := newFakeSuggestionConfig() + mode := corev1.PersistentVolumeFilesystem + accessModes := []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + corev1.ReadOnlyMany, + } + volumeStorage, _ := resource.ParseQuantity("10Gi") + + sc.PersistentVolumeClaimSpec = corev1.PersistentVolumeClaimSpec{ + VolumeMode: &mode, + AccessModes: accessModes, + } + + sc.PersistentVolumeSpec = corev1.PersistentVolumeSpec{ + VolumeMode: &mode, + AccessModes: accessModes, + PersistentVolumeSource: corev1.PersistentVolumeSource{ + GCEPersistentDisk: &corev1.GCEPersistentDiskVolumeSource{ + PDName: "pd-name", + FSType: "fs-type", + }, + }, + Capacity: corev1.ResourceList{ + corev1.ResourceStorage: volumeStorage, + }, + } + cm := newFakeKatibConfig(sc) + return cm + }(), + expectedPVC: func() *corev1.PersistentVolumeClaim { + pvc := newFakePVC() + mode := corev1.PersistentVolumeFilesystem + + pvc.Spec.VolumeMode = &mode + pvc.Spec.AccessModes = append(pvc.Spec.AccessModes, corev1.ReadOnlyMany) + return pvc + }(), + expectedPV: func() *corev1.PersistentVolume { + pv := newFakePV() + mode := corev1.PersistentVolumeFilesystem + volumeStorage, _ := resource.ParseQuantity("10Gi") + + pv.Spec.VolumeMode = &mode + pv.Spec.AccessModes = append(pv.Spec.AccessModes, corev1.ReadOnlyMany) + pv.Spec.PersistentVolumeSource = corev1.PersistentVolumeSource{ + GCEPersistentDisk: &corev1.GCEPersistentDiskVolumeSource{ + PDName: "pd-name", + FSType: "fs-type", + }, + } + pv.Spec.Capacity = corev1.ResourceList{ + corev1.ResourceStorage: volumeStorage, + } + return pv + }(), + err: false, + testDescription: "Custom PVC and PV with default storage class", }, } for idx, tc := range tcs { + if tc.configMap != nil { + // Expect that ConfigMap is created + g.Eventually(func() error { + // Create ConfigMap with Katib config + c.Create(context.TODO(), tc.configMap) + return c.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: configMap}, &corev1.ConfigMap{}) + }, timeout).ShouldNot(gomega.HaveOccurred()) + } + // Get PVC and PV var actualPVC *corev1.PersistentVolumeClaim var actualPV *corev1.PersistentVolume @@ -346,15 +485,35 @@ func TestDesiredVolume(t *testing.T) { if !tc.err && err != nil { t.Errorf("Case: %v failed. Expected nil, got %v", tc.testDescription, err) + } else if tc.err && err == nil { t.Errorf("Case: %v failed. Expected err, got nil", tc.testDescription) - } else if !tc.err && (!metaEqual(tc.expectedPVC.ObjectMeta, actualPVC.ObjectMeta) || !metaEqual(tc.expectedPV.ObjectMeta, actualPV.ObjectMeta)) { + + } else if !tc.err && ((tc.expectedPV == nil && actualPV != nil) || (tc.expectedPV != nil && actualPV == nil)) { + t.Errorf("Case: %v failed. \nExpected PV: %v\n Got %v", tc.testDescription, tc.expectedPV, actualPV) + + } else if !tc.err && (!metaEqual(tc.expectedPVC.ObjectMeta, actualPVC.ObjectMeta) || + (tc.expectedPV != nil && !metaEqual(tc.expectedPV.ObjectMeta, actualPV.ObjectMeta))) { t.Errorf("Case: %v failed. \nExpected PVC metadata %v\n Got %v.\nExpected PV metadata %v\n Got %v", tc.testDescription, tc.expectedPVC.ObjectMeta, actualPVC.ObjectMeta, tc.expectedPV.ObjectMeta, actualPV.ObjectMeta) - } else if !tc.err && (!equality.Semantic.DeepEqual(tc.expectedPVC.Spec, actualPVC.Spec) || !equality.Semantic.DeepEqual(tc.expectedPV.Spec, actualPV.Spec)) { + + } else if !tc.err && (!equality.Semantic.DeepEqual(tc.expectedPVC.Spec, actualPVC.Spec) || + (tc.expectedPV != nil && !equality.Semantic.DeepEqual(tc.expectedPV.Spec, actualPV.Spec))) { t.Errorf("Case: %v failed. \nExpected PVC spec %v\n Got %v.\nExpected PV spec %v\n Got %v", - tc.testDescription, tc.expectedPVC.Spec, actualPVC.Spec, tc.expectedPV, actualPVC) + tc.testDescription, tc.expectedPVC.Spec, actualPVC.Spec, tc.expectedPV, actualPV) + + } + + if tc.configMap != nil { + // Expect that ConfigMap is deleted + g.Eventually(func() bool { + // Delete ConfigMap with Katib config + c.Delete(context.TODO(), tc.configMap) + return errors.IsNotFound( + c.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: configMap}, &corev1.ConfigMap{})) + }, timeout).Should(gomega.BeTrue()) } + } } @@ -371,35 +530,34 @@ func metaEqual(expected, actual metav1.ObjectMeta) bool { *expected.OwnerReferences[0].BlockOwnerDeletion == *actual.OwnerReferences[0].BlockOwnerDeletion } -func newFakeKatibConfig() *corev1.ConfigMap { +func newFakeSuggestionConfig() katibconfig.SuggestionConfig { cpuQ, _ := resource.ParseQuantity(cpu) memoryQ, _ := resource.ParseQuantity(memory) diskQ, _ := resource.ParseQuantity(disk) - type suggestionConfigJSON struct { - Image string `json:"image"` - ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy"` - Resource corev1.ResourceRequirements `json:"resources"` - ServiceAccountName string `json:"serviceAccountName"` - } - jsonConfig := map[string]suggestionConfigJSON{ - "random": { - Image: image, - ImagePullPolicy: imagePullPolicy, - Resource: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: cpuQ, - corev1.ResourceMemory: memoryQ, - corev1.ResourceEphemeralStorage: diskQ, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: cpuQ, - corev1.ResourceMemory: memoryQ, - corev1.ResourceEphemeralStorage: diskQ, - }, + return katibconfig.SuggestionConfig{ + Image: image, + ImagePullPolicy: imagePullPolicy, + Resource: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: cpuQ, + corev1.ResourceMemory: memoryQ, + corev1.ResourceEphemeralStorage: diskQ, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: cpuQ, + corev1.ResourceMemory: memoryQ, + corev1.ResourceEphemeralStorage: diskQ, }, - ServiceAccountName: serviceAccount, }, + ServiceAccountName: serviceAccount, + } +} + +func newFakeKatibConfig(suggestionConfig katibconfig.SuggestionConfig) *corev1.ConfigMap { + + jsonConfig := map[string]katibconfig.SuggestionConfig{ + "random": suggestionConfig, } b, _ := json.Marshal(jsonConfig) diff --git a/pkg/util/v1beta1/katibconfig/config.go b/pkg/util/v1beta1/katibconfig/config.go index 4867ca12ecd..97830cff0d3 100644 --- a/pkg/util/v1beta1/katibconfig/config.go +++ b/pkg/util/v1beta1/katibconfig/config.go @@ -17,10 +17,13 @@ import ( // SuggestionConfig is the JSON suggestion structure in Katib config type SuggestionConfig struct { - Image string `json:"image"` - ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy"` - Resource corev1.ResourceRequirements `json:"resources"` - ServiceAccountName string `json:"serviceAccountName"` + Image string `json:"image"` + ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy"` + Resource corev1.ResourceRequirements `json:"resources"` + ServiceAccountName string `json:"serviceAccountName"` + VolumeMountPath string `json:"volumeMountPath"` + PersistentVolumeClaimSpec corev1.PersistentVolumeClaimSpec `json:"persistentVolumeClaimSpec"` + PersistentVolumeSpec corev1.PersistentVolumeSpec `json:"persistentVolumeSpec"` } // MetricsCollectorConfig is the JSON metrics collector structure in Katib config @@ -75,6 +78,78 @@ func GetSuggestionConfigData(algorithmName string, client client.Client) (Sugges // Set resource requirements for suggestion suggestionConfigData.Resource = setResourceRequirements(suggestionConfigData.Resource) + // Set default suggestion container volume mount path + if suggestionConfigData.VolumeMountPath == "" { + suggestionConfigData.VolumeMountPath = consts.DefaultContainerSuggestionVolumeMountPath + } + + // Get persistent volume claim spec from config + pvcSpec := suggestionConfigData.PersistentVolumeClaimSpec + + // Set default storage class + defaultStorageClassName := consts.DefaultSuggestionStorageClassName + if pvcSpec.StorageClassName == nil { + pvcSpec.StorageClassName = &defaultStorageClassName + } + + // Set default access modes + if len(pvcSpec.AccessModes) == 0 { + pvcSpec.AccessModes = []corev1.PersistentVolumeAccessMode{ + consts.DefaultSuggestionVolumeAccessMode, + } + } + + // Set default resources + defaultVolumeStorage, _ := resource.ParseQuantity(consts.DefaultSuggestionVolumeStorage) + if len(pvcSpec.Resources.Requests) == 0 { + + pvcSpec.Resources.Requests = make(map[corev1.ResourceName]resource.Quantity) + pvcSpec.Resources.Requests[corev1.ResourceStorage] = defaultVolumeStorage + } + + // Set pvc back for suggestion config + suggestionConfigData.PersistentVolumeClaimSpec = pvcSpec + + // Get pv from config only if pvc storage class name = DefaultSuggestionStorageClassName + if *pvcSpec.StorageClassName == consts.DefaultSuggestionStorageClassName { + pvSpec := suggestionConfigData.PersistentVolumeSpec + + // Set default storage class + pvSpec.StorageClassName = defaultStorageClassName + + // Set default access modes + if len(pvSpec.AccessModes) == 0 { + pvSpec.AccessModes = []corev1.PersistentVolumeAccessMode{ + consts.DefaultSuggestionVolumeAccessMode, + } + } + + // Set default pv source. + // In composer we add name, algorithm and namespace to host path. + if pvSpec.PersistentVolumeSource == (corev1.PersistentVolumeSource{}) { + pvSpec.PersistentVolumeSource = corev1.PersistentVolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: consts.DefaultSuggestionVolumeLocalPathPrefix, + }, + } + } + + // Set default local path if it is empty + if pvSpec.PersistentVolumeSource.HostPath != nil && pvSpec.PersistentVolumeSource.HostPath.Path == "" { + pvSpec.PersistentVolumeSource.HostPath.Path = consts.DefaultSuggestionVolumeLocalPathPrefix + } + + // Set default capacity + if len(pvSpec.Capacity) == 0 { + pvSpec.Capacity = make(map[corev1.ResourceName]resource.Quantity) + pvSpec.Capacity[corev1.ResourceStorage] = defaultVolumeStorage + } + + // Set pv back for suggestion config + suggestionConfigData.PersistentVolumeSpec = pvSpec + + } + return suggestionConfigData, nil }