Skip to content

Commit e17db27

Browse files
niladrihKiran Mova
authored andcommitted
refact(XFS-Quota): get XFS Quota parameters from StorageClass 'cas.openebs.io/config' annotation (#107)
* Use XFS Quota parameters from cas-config * Add integration test changes * Empty softLimitGrace and hardLimitGrace default to 0% and 0% respectively * Extend timeout on integration test checks to account for delays Signed-off-by: Niladri Halder <[email protected]>
1 parent 030ba6f commit e17db27

File tree

11 files changed

+436
-39
lines changed

11 files changed

+436
-39
lines changed

cmd/provisioner-localpv/app/config.go

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package app
1919

2020
import (
2121
"context"
22+
"strconv"
2223
"strings"
2324

2425
mconfig "github.com/openebs/maya/pkg/apis/openebs.io/v1alpha1"
@@ -113,6 +114,17 @@ const (
113114
// is specified with PVC and is useful for granting shared access
114115
// to underlying hostpaths across multiple pods.
115116
//KeyPVAbsolutePath = "AbsolutePath"
117+
118+
//KeyXFSQuota enables/sets parameters for XFS Quota.
119+
// Example StorageClass snippet:
120+
// - name: XFSQuota
121+
// enabled: true
122+
// data:
123+
// softLimitGrace: "80%"
124+
// hardLimitGrace: "85%"
125+
KeyXFSQuota = "XFSQuota"
126+
KeyXfsQuotaSoftLimit = "softLimitGrace"
127+
KeyXfsQuotaHardLimit = "hardLimitGrace"
116128
)
117129

118130
const (
@@ -168,11 +180,17 @@ func (p *Provisioner) GetVolumeConfig(ctx context.Context, pvName string, pvc *c
168180
return nil, errors.Wrapf(err, "unable to read volume config: pvc {%v}", pvc.ObjectMeta.Name)
169181
}
170182

183+
dataPvConfigMap, err := dataConfigToMap(pvConfig)
184+
if err != nil {
185+
return nil, errors.Wrapf(err, "unable to read volume config: pvc {%v}", pvc.ObjectMeta.Name)
186+
}
187+
171188
c := &VolumeConfig{
172-
pvName: pvName,
173-
pvcName: pvc.ObjectMeta.Name,
174-
scName: *scName,
175-
options: pvConfigMap,
189+
pvName: pvName,
190+
pvcName: pvc.ObjectMeta.Name,
191+
scName: *scName,
192+
options: pvConfigMap,
193+
configData: dataPvConfigMap,
176194
}
177195
return c, nil
178196
}
@@ -262,6 +280,23 @@ func (c *VolumeConfig) GetPath() (string, error) {
262280
ValidateAndBuild()
263281
}
264282

283+
func (c *VolumeConfig) IsXfsQuotaEnabled() bool {
284+
xfsQuotaEnabled := c.getEnabled(KeyXFSQuota)
285+
xfsQuotaEnabled = strings.TrimSpace(xfsQuotaEnabled)
286+
287+
enableXfsQuotaBool, err := strconv.ParseBool(xfsQuotaEnabled)
288+
//Default case
289+
// this means that we have hit either of the two cases below:
290+
// i. The value was something other than a straightforward
291+
// true or false
292+
// ii. The value was empty
293+
if err != nil {
294+
return false
295+
}
296+
297+
return enableXfsQuotaBool
298+
}
299+
265300
//getValue is a utility function to extract the value
266301
// of the `key` from the ConfigMap object - which is
267302
// map[string]interface{map[string][string]}
@@ -283,6 +318,30 @@ func (c *VolumeConfig) getValue(key string) string {
283318
return ""
284319
}
285320

321+
//Similar to getValue() above. Returns value of the
322+
// 'Enabled' parameter.
323+
func (c *VolumeConfig) getEnabled(key string) string {
324+
if configObj, ok := util.GetNestedField(c.options, key).(map[string]string); ok {
325+
if val, p := configObj[string(mconfig.EnabledPTP)]; p {
326+
return val
327+
}
328+
}
329+
return ""
330+
}
331+
332+
//This is similar to getValue() and getEnabled().
333+
// This gets the value for a specific
334+
// 'Data' parameter key-value pair.
335+
func (c *VolumeConfig) getData(key string, dataKey string) string {
336+
if configData, ok := util.GetNestedField(c.configData, key).(map[string]string); ok {
337+
if val, p := configData[dataKey]; p {
338+
return val
339+
}
340+
}
341+
//Default case
342+
return ""
343+
}
344+
286345
// GetStorageClassName extracts the StorageClass name from PVC
287346
func GetStorageClassName(pvc *corev1.PersistentVolumeClaim) *string {
288347
// Use beta annotation first
@@ -347,3 +406,25 @@ func GetImagePullSecrets(s string) []corev1.LocalObjectReference {
347406
}
348407
return list
349408
}
409+
410+
func dataConfigToMap(pvConfig []mconfig.Config) (map[string]interface{}, error) {
411+
m := map[string]interface{}{}
412+
413+
for _, configObj := range pvConfig {
414+
//No Data Parameter
415+
if configObj.Data == nil {
416+
continue
417+
}
418+
419+
configName := strings.TrimSpace(configObj.Name)
420+
confHierarchy := map[string]interface{}{
421+
configName: configObj.Data,
422+
}
423+
isMerged := util.MergeMapOfObjects(m, confHierarchy)
424+
if !isMerged {
425+
return nil, errors.Errorf("failed to transform cas config 'Data' for configName '%s' to map: failed to merge: %s", configName, configObj)
426+
}
427+
}
428+
429+
return m, nil
430+
}

cmd/provisioner-localpv/app/config_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"reflect"
2222
"testing"
2323

24+
mconfig "github.com/openebs/maya/pkg/apis/openebs.io/v1alpha1"
25+
2426
corev1 "k8s.io/api/core/v1"
2527
)
2628

@@ -64,3 +66,42 @@ func TestGetImagePullSecrets(t *testing.T) {
6466
})
6567
}
6668
}
69+
70+
func TestDataConfigToMap(t *testing.T) {
71+
hostpathConfig := mconfig.Config{Name: "StorageType", Value: "hostpath"}
72+
xfsQuotaConfig := mconfig.Config{Name: "XFSQuota", Enabled: "true",
73+
Data: map[string]string{
74+
"SoftLimitGrace": "20%",
75+
"HardLimitGrace": "80%",
76+
},
77+
}
78+
79+
testCases := map[string]struct {
80+
config []mconfig.Config
81+
expectedValue map[string]interface{}
82+
}{
83+
"nil 'Data' map": {
84+
config: []mconfig.Config{hostpathConfig, xfsQuotaConfig},
85+
expectedValue: map[string]interface{}{
86+
"XFSQuota": map[string]string{
87+
"SoftLimitGrace": "20%",
88+
"HardLimitGrace": "80%",
89+
},
90+
},
91+
},
92+
}
93+
94+
for k, v := range testCases {
95+
v := v
96+
k := k
97+
t.Run(k, func(t *testing.T) {
98+
actualValue, err := dataConfigToMap(v.config)
99+
if err != nil {
100+
t.Errorf("expected error to be nil, but got %v", err)
101+
}
102+
if !reflect.DeepEqual(actualValue, v.expectedValue) {
103+
t.Errorf("expected %v, but got %v", v.expectedValue, actualValue)
104+
}
105+
})
106+
}
107+
}

cmd/provisioner-localpv/app/helper_hostpath.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,12 @@ func (pOpts *HelperPodOptions) validate() error {
109109
func (pOpts *HelperPodOptions) validateLimits() error {
110110
if pOpts.softLimitGrace == "0k" &&
111111
pOpts.hardLimitGrace == "0k" {
112-
return errors.Errorf("both limits cannot be 0")
112+
// Hack: using convertToK() style converstion
113+
// TODO: Refactor this section of the code
114+
pvcStorageInK := math.Ceil(float64(pOpts.pvcStorage) / 1000)
115+
pvcStorageInKString := strconv.FormatFloat(pvcStorageInK, 'f', -1, 64) + "k"
116+
pOpts.softLimitGrace, pOpts.hardLimitGrace = pvcStorageInKString, pvcStorageInKString
117+
return nil
113118
}
114119

115120
if pOpts.softLimitGrace == "0k" ||
@@ -154,10 +159,7 @@ func convertToK(limit string, pvcStorage int64) (string, error) {
154159
value += float64(pvcStorage)
155160
value /= 1000
156161

157-
if value != math.Trunc(value) {
158-
value++
159-
}
160-
value = math.Trunc(value)
162+
value = math.Ceil(value)
161163
valueString = strconv.FormatFloat(value, 'f', -1, 64)
162164
valueString += "k"
163165
return valueString, nil

cmd/provisioner-localpv/app/provisioner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func NewProvisioner(kubeClient *clientset.Clientset) (*Provisioner, error) {
8080

8181
// SupportsBlock will be used by controller to determine if block mode is
8282
// supported by the host path provisioner.
83-
func (p *Provisioner) SupportsBlock(ctx context.Context) bool {
83+
func (p *Provisioner) SupportsBlock(_ context.Context) bool {
8484
return true
8585
}
8686

cmd/provisioner-localpv/app/provisioner_hostpath.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,9 @@ func (p *Provisioner) ProvisionHostPath(ctx context.Context, opts pvController.P
9393
return nil, pvController.ProvisioningFinished, iErr
9494
}
9595

96-
enableXfsQuota := opts.StorageClass.Parameters[EnableXfsQuota]
97-
98-
if enableXfsQuota == "true" {
99-
softLimitGrace := opts.StorageClass.Parameters[SoftLimitGrace]
100-
hardLimitGrace := opts.StorageClass.Parameters[HardLimitGrace]
96+
if volumeConfig.IsXfsQuotaEnabled() {
97+
softLimitGrace := volumeConfig.getData(KeyXFSQuota, KeyXfsQuotaSoftLimit)
98+
hardLimitGrace := volumeConfig.getData(KeyXFSQuota, KeyXfsQuotaHardLimit)
10199
pvcStorage := opts.PVC.Spec.Resources.Requests.Storage().Value()
102100

103101
podOpts := &HelperPodOptions{

cmd/provisioner-localpv/app/types.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ type Provisioner struct {
6060
// },
6161
// }
6262
type VolumeConfig struct {
63-
pvName string
64-
pvcName string
65-
scName string
66-
options map[string]interface{}
63+
pvName string
64+
pvcName string
65+
scName string
66+
options map[string]interface{}
67+
configData map[string]interface{}
6768
}
6869

6970
// GetVolumeConfigFn allows to plugin a custom function

pkg/kubernetes/api/storage/v1/storageclass/build.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,17 @@ import (
2727
const (
2828
localPVcasTypeValue = "local"
2929

30-
// Provisioner Name
30+
//Provisioner Name
3131
localPVprovisionerName = "openebs.io/local"
3232

33-
// The following are imported from mconfig at the moment
33+
//The following are imported from mconfig at the moment
3434
// CASConfigKey = "cas.openebs.io/config"
3535
// CASTypeKey = "openebs.io/cas-type"
36+
37+
//These are from 'app' package
38+
// cmd/provisioner-localpv/app/config.go
39+
KeyXfsQuotaSoftLimit = "softLimitGrace"
40+
KeyXfsQuotaHardLimit = "hardLimitGrace"
3641
)
3742

3843
type StorageClassOption func(*storagev1.StorageClass) error
@@ -186,6 +191,43 @@ func WithDevice() StorageClassOption {
186191
}
187192
}
188193

194+
func WithXfsQuota(softLimit, hardLimit string) StorageClassOption {
195+
return func(s *storagev1.StorageClass) error {
196+
if !isCompatibleWithXfsQuota(s) {
197+
return errors.New("Failed to set XFSQuota parameters. " +
198+
"Invalid existing '" + string(mconfig.CASConfigKey) + "' annotation" +
199+
" parameters or Provisioner name.")
200+
}
201+
202+
// TODO: Refactor this code
203+
204+
config := "- name: XFSQuota\n" +
205+
" enabled: \"true\"\n"
206+
207+
if len(softLimit) > 0 || len(hardLimit) > 0 {
208+
if !isValidXfsQuotaData(map[string]string{
209+
KeyXfsQuotaSoftLimit: softLimit,
210+
KeyXfsQuotaHardLimit: hardLimit,
211+
}) {
212+
return errors.New("Failed to set XFSQuota parameters. " +
213+
"Invalid " + KeyXfsQuotaSoftLimit + " and " +
214+
KeyXfsQuotaHardLimit + " values")
215+
}
216+
217+
config = config +
218+
" data:\n" +
219+
" " + KeyXfsQuotaSoftLimit + ": \"" + softLimit + "\"\n" +
220+
" " + KeyXfsQuotaHardLimit + ": \"" + hardLimit + "\"\n"
221+
}
222+
223+
ok := writeOrAppendCASConfig(s, config)
224+
if !ok {
225+
return errors.New("Failed to set XFSQuota parameters")
226+
}
227+
return nil
228+
}
229+
}
230+
189231
func WithVolumeBindingMode(volBindingMode storagev1.VolumeBindingMode) StorageClassOption {
190232
return func(s *storagev1.StorageClass) error {
191233
if len(volBindingMode) == 0 {

0 commit comments

Comments
 (0)