@@ -21,7 +21,10 @@ package app
2121
2222import (
2323 "context"
24+ "math"
2425 "path/filepath"
26+ "regexp"
27+ "strconv"
2528 "time"
2629
2730 errors "github.com/pkg/errors"
@@ -69,12 +72,21 @@ type HelperPodOptions struct {
6972 //path is the volume hostpath directory
7073 path string
7174
72- // serviceAccountName is the service account with which the pod should be launched
75+ //serviceAccountName is the service account with which the pod should be launched
7376 serviceAccountName string
7477
7578 selectedNodeTaints []corev1.Taint
7679
7780 imagePullSecrets []corev1.LocalObjectReference
81+
82+ //softLimitGrace is the soft limit of quota on the project
83+ softLimitGrace string
84+
85+ //hardLimitGrace is the hard limit of quota on the project
86+ hardLimitGrace string
87+
88+ //pvcStorage is the storage requested for pv
89+ pvcStorage int64
7890}
7991
8092// validate checks that the required fields to launch
@@ -92,6 +104,64 @@ func (pOpts *HelperPodOptions) validate() error {
92104 return nil
93105}
94106
107+ //validateLimits check that the limits to setup qouta are valid
108+ func (pOpts * HelperPodOptions ) validateLimits () error {
109+ if pOpts .softLimitGrace == "0k" &&
110+ pOpts .hardLimitGrace == "0k" {
111+ return errors .Errorf ("both limits cannot be 0" )
112+ }
113+
114+ if pOpts .softLimitGrace == "0k" ||
115+ pOpts .hardLimitGrace == "0k" {
116+ return nil
117+ }
118+
119+ if len (pOpts .softLimitGrace ) > len (pOpts .hardLimitGrace ) ||
120+ (len (pOpts .softLimitGrace ) == len (pOpts .hardLimitGrace ) &&
121+ pOpts .softLimitGrace > pOpts .hardLimitGrace ) {
122+ return errors .Errorf ("hard limit cannot be smaller than soft limit" )
123+ }
124+
125+ return nil
126+ }
127+
128+ //converToK converts the limits to kilobytes
129+ func convertToK (limit string , pvcStorage int64 ) (string , error ) {
130+
131+ if len (limit ) == 0 {
132+ return "0k" , nil
133+ }
134+
135+ valueRegex := regexp .MustCompile (`[\d]*[\.]?[\d]*` )
136+ valueString := valueRegex .FindString (limit )
137+
138+ if limit != valueString + "%" {
139+ return "" , errors .Errorf ("invalid format for limit grace" )
140+ }
141+
142+ value , err := strconv .ParseFloat (valueString , 64 )
143+
144+ if err != nil {
145+ return "" , errors .Errorf ("invalid format, cannot parse" )
146+ }
147+ if value > 100 {
148+ value = 100
149+ }
150+
151+ value *= float64 (pvcStorage )
152+ value /= 100
153+ value += float64 (pvcStorage )
154+ value /= 1000
155+
156+ if value != math .Trunc (value ) {
157+ value ++
158+ }
159+ value = math .Trunc (value )
160+ valueString = strconv .FormatFloat (value , 'f' , - 1 , 64 )
161+ valueString += "k"
162+ return valueString , nil
163+ }
164+
95165// createInitPod launches a helper(busybox) pod, to create the host path.
96166// The local pv expect the hostpath to be already present before mounting
97167// into pod. Validate that the local pv host path is not created under root.
@@ -117,6 +187,8 @@ func (p *Provisioner) createInitPod(ctx context.Context, pOpts *HelperPodOptions
117187 //Pass on the taints, to create tolerations.
118188 config .taints = pOpts .selectedNodeTaints
119189
190+ config .pOpts .cmdsForPath = append (config .pOpts .cmdsForPath , filepath .Join ("/data/" , config .volumeDir ))
191+
120192 iPod , err := p .launchPod (ctx , config )
121193 if err != nil {
122194 return err
@@ -142,7 +214,6 @@ func (p *Provisioner) createCleanupPod(ctx context.Context, pOpts *HelperPodOpti
142214 return err
143215 }
144216
145- config .taints = pOpts .selectedNodeTaints
146217 // Initialize HostPath builder and validate that
147218 // volume directory is not directly under root.
148219 // Extract the base path and the volume unique path.
@@ -154,6 +225,10 @@ func (p *Provisioner) createCleanupPod(ctx context.Context, pOpts *HelperPodOpti
154225 return vErr
155226 }
156227
228+ config .taints = pOpts .selectedNodeTaints
229+
230+ config .pOpts .cmdsForPath = append (config .pOpts .cmdsForPath , filepath .Join ("/data/" , config .volumeDir ))
231+
157232 cPod , err := p .launchPod (ctx , config )
158233 if err != nil {
159234 return err
@@ -165,6 +240,72 @@ func (p *Provisioner) createCleanupPod(ctx context.Context, pOpts *HelperPodOpti
165240 return nil
166241}
167242
243+ // createQuotaPod launches a helper(busybox) pod, to apply the quota.
244+ // The local pv expect the hostpath to be already present before mounting
245+ // into pod. Validate that the local pv host path is not created under root.
246+ func (p * Provisioner ) createQuotaPod (ctx context.Context , pOpts * HelperPodOptions ) error {
247+ var config podConfig
248+ config .pOpts , config .podName = pOpts , "quota"
249+ //err := pOpts.validate()
250+ if err := pOpts .validate (); err != nil {
251+ return err
252+ }
253+
254+ // Initialize HostPath builder and validate that
255+ // volume directory is not directly under root.
256+ // Extract the base path and the volume unique path.
257+ var vErr error
258+ config .parentDir , config .volumeDir , vErr = hostpath .NewBuilder ().WithPath (pOpts .path ).
259+ WithCheckf (hostpath .IsNonRoot (), "volume directory {%v} should not be under root directory" , pOpts .path ).
260+ ExtractSubPath ()
261+ if vErr != nil {
262+ return vErr
263+ }
264+
265+ //Pass on the taints, to create tolerations.
266+ config .taints = pOpts .selectedNodeTaints
267+
268+ var lErr error
269+ config .pOpts .softLimitGrace , lErr = convertToK (config .pOpts .softLimitGrace , config .pOpts .pvcStorage )
270+ if lErr != nil {
271+ return lErr
272+ }
273+ config .pOpts .hardLimitGrace , lErr = convertToK (config .pOpts .hardLimitGrace , config .pOpts .pvcStorage )
274+ if lErr != nil {
275+ return lErr
276+ }
277+
278+ if err := pOpts .validateLimits (); err != nil {
279+ return err
280+ }
281+
282+ //fs stores the file system of mount
283+ fs := "FS=`stat -f -c %T /data` ; "
284+ //check if fs is xfs
285+ checkXfs := "if [[ \" $FS\" != \" xfs\" ]]; then rm -rf " + filepath .Join ("/data/" , config .volumeDir ) + " ;exit 1 ; else "
286+ //lastPid finds last project Id in the directory
287+ lastPid := "PID=`xfs_quota -x -c 'report -h' /data | tail -2 | awk 'NR==1{print substr ($1,2)}+0'` ;"
288+ //newPid increments last project Id by 1
289+ newPid := "PID=`expr $PID + 1` ;"
290+ //initializeProject initializes project with newpid
291+ initializeProject := "xfs_quota -x -c 'project -s -p " + filepath .Join ("/data/" , config .volumeDir ) + " '$PID /data ;"
292+ //setQuota sets the quota according to limits defined
293+ setQuota := "xfs_quota -x -c 'limit -p bsoft=" + config .pOpts .softLimitGrace + " bhard=" + config .pOpts .hardLimitGrace + " '$PID /data ; fi"
294+
295+ config .pOpts .cmdsForPath = []string {"sh" , "-c" , fs + checkXfs + lastPid + newPid + initializeProject + setQuota }
296+
297+ qPod , err := p .launchPod (ctx , config )
298+ if err != nil {
299+ return err
300+ }
301+
302+ if err := p .exitPod (ctx , qPod ); err != nil {
303+ return err
304+ }
305+
306+ return nil
307+ }
308+
168309func (p * Provisioner ) launchPod (ctx context.Context , config podConfig ) (* corev1.Pod , error ) {
169310 // the helper pod need to be launched in privileged mode. This is because in CoreOS
170311 // nodes, pods without privileged access cannot write to the host directory.
@@ -182,7 +323,7 @@ func (p *Provisioner) launchPod(ctx context.Context, config podConfig) (*corev1.
182323 container .NewBuilder ().
183324 WithName ("local-path-" + config .podName ).
184325 WithImage (p .helperImage ).
185- WithCommandNew (append ( config .pOpts .cmdsForPath , filepath . Join ( "/data/" , config . volumeDir )) ).
326+ WithCommandNew (config .pOpts .cmdsForPath ).
186327 WithVolumeMountsNew ([]corev1.VolumeMount {
187328 {
188329 Name : "data" ,
0 commit comments