Skip to content

Commit 0adeed2

Browse files
committed
add distributed-provisioning
Signed-off-by: laik <laik.lj@me.com>
1 parent f1014f8 commit 0adeed2

File tree

12 files changed

+700
-71
lines changed

12 files changed

+700
-71
lines changed

buildscripts/provisioner-localpv/Dockerfile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ RUN apk add --no-cache \
2222
mii-tool \
2323
procps \
2424
libc6-compat \
25-
ca-certificates
25+
ca-certificates \
26+
util-linux \
27+
e2fsprogs \
28+
xfsprogs \
29+
xfsprogs-extra \
30+
blkid \
31+
findmnt \
32+
quota-tools
2633

2734
COPY provisioner-localpv /
2835

cmd/provisioner-localpv/app/config.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package app
22

33
import (
44
"context"
5-
"gopkg.in/yaml.v3"
65
"strconv"
76
"strings"
87

8+
"gopkg.in/yaml.v3"
9+
910
mconfig "github.com/openebs/maya/pkg/apis/openebs.io/v1alpha1"
1011
hostpath "github.com/openebs/maya/pkg/hostpath/v1alpha1"
1112
"github.com/openebs/maya/pkg/util"
@@ -146,6 +147,9 @@ func (p *Provisioner) GetVolumeConfig(ctx context.Context, pvName string, pvc *c
146147

147148
//Fetch the SC
148149
scName := GetStorageClassName(pvc)
150+
if scName == nil || *scName == "" {
151+
return nil, errors.Errorf("failed to get storageclass: storageClassName is not set in PVC %s/%s", pvc.Namespace, pvc.Name)
152+
}
149153
sc, err := p.kubeClient.StorageV1().StorageClasses().Get(ctx, *scName, metav1.GetOptions{})
150154
if err != nil {
151155
return nil, errors.Wrapf(err, "failed to get storageclass: missing sc name {%v}", scName)

cmd/provisioner-localpv/app/env.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,13 @@ func getOpenEBSServiceAccountName() string {
5959
func getOpenEBSImagePullSecrets() string {
6060
return menv.Get(ProvisionerImagePullSecrets)
6161
}
62+
63+
// getNodeName returns the current node name from NODE_NAME environment variable
64+
func getNodeName() string {
65+
return menv.Get(menv.ENVKey("NODE_NAME"))
66+
}
67+
68+
// GetEnv gets an environment variable value
69+
func GetEnv(key string) string {
70+
return menv.Get(menv.ENVKey(key))
71+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package app
2+
3+
import (
4+
"context"
5+
6+
"k8s.io/klog/v2"
7+
)
8+
9+
// createVolumeLocally performs volume creation directly on the local node
10+
func (p *Provisioner) createVolumeLocally(ctx context.Context, pOpts *HelperPodOptions, enableQuota bool) error {
11+
klog.Infof("Creating volume %s locally", pOpts.name)
12+
13+
// Create a temporary volume manager to perform local operations
14+
vm := NewLocalVolumeManager()
15+
16+
req := &VolumeRequest{
17+
Name: pOpts.name,
18+
Path: pOpts.path,
19+
FsMode: "0777", // Default file permissions
20+
SoftLimitGrace: pOpts.softLimitGrace,
21+
HardLimitGrace: pOpts.hardLimitGrace,
22+
PVCStorage: pOpts.pvcStorage,
23+
}
24+
25+
return vm.CreateVolume(ctx, req, enableQuota)
26+
}
27+
28+
// deleteVolumeLocally deletes volume directly on the local node
29+
func (p *Provisioner) deleteVolumeLocally(ctx context.Context, pOpts *HelperPodOptions) error {
30+
klog.Infof("Deleting volume %s locally", pOpts.name)
31+
32+
// Create a temporary volume manager to perform local operations
33+
vm := NewLocalVolumeManager()
34+
35+
req := &VolumeRequest{
36+
Name: pOpts.name,
37+
Path: pOpts.path,
38+
}
39+
40+
return vm.DeleteVolume(ctx, req)
41+
}
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
package app
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math"
7+
"os/exec"
8+
"path/filepath"
9+
"regexp"
10+
"strconv"
11+
"strings"
12+
"sync"
13+
"syscall"
14+
"time"
15+
16+
hostpath "github.com/openebs/maya/pkg/hostpath/v1alpha1"
17+
"k8s.io/klog/v2"
18+
)
19+
20+
// VolumeRequest represents a request for volume operations
21+
type VolumeRequest struct {
22+
Name string `json:"name"`
23+
Path string `json:"path"`
24+
FsMode string `json:"fsMode,omitempty"`
25+
SoftLimitGrace string `json:"softLimitGrace,omitempty"`
26+
HardLimitGrace string `json:"hardLimitGrace,omitempty"`
27+
PVCStorage int64 `json:"pvcStorage,omitempty"`
28+
}
29+
30+
// LocalVolumeManager handles volume operations on the local node
31+
type LocalVolumeManager struct {
32+
// Add any necessary fields for volume management
33+
34+
// race condition protection
35+
// mutex for thread-safe operations
36+
mu *sync.Mutex
37+
}
38+
39+
// NewLocalVolumeManager creates a new LocalVolumeManager instance
40+
func NewLocalVolumeManager() *LocalVolumeManager {
41+
return &LocalVolumeManager{
42+
mu: &sync.Mutex{},
43+
}
44+
}
45+
46+
// CreateVolume creates a new volume directory on the local node
47+
func (vm *LocalVolumeManager) CreateVolume(ctx context.Context, req *VolumeRequest, enableQuota bool) error {
48+
vm.mu.Lock()
49+
defer vm.mu.Unlock()
50+
klog.Infof("Creating volume %s at path %s", req.Name, req.Path)
51+
52+
// Extract the base path and the volume unique path
53+
parentDir, volumeDir, err := vm.extractPaths(req.Path)
54+
if err != nil {
55+
return fmt.Errorf("failed to extract paths: %v", err)
56+
}
57+
58+
// Set default file permissions if not specified
59+
fsMode := req.FsMode
60+
if fsMode == "" {
61+
fsMode = "0777"
62+
}
63+
64+
// Create the directory with specified permissions
65+
fullPath := filepath.Join(parentDir, volumeDir)
66+
if err := vm.executeCommand(ctx, "mkdir", "-m", fsMode, "-p", fullPath); err != nil {
67+
return fmt.Errorf("failed to create directory: %v", err)
68+
}
69+
70+
if enableQuota {
71+
// Apply quota if enabled
72+
if err := vm.ApplyQuota(ctx, req); err != nil {
73+
return fmt.Errorf("failed to apply quota: %v", err)
74+
}
75+
}
76+
77+
klog.Infof("Successfully created volume %s at path %s", req.Name, fullPath)
78+
return nil
79+
}
80+
81+
// DeleteVolume removes a volume directory from the local node
82+
func (vm *LocalVolumeManager) DeleteVolume(ctx context.Context, req *VolumeRequest) error {
83+
vm.mu.Lock()
84+
defer vm.mu.Unlock()
85+
klog.Infof("Deleting volume %s at path %s", req.Name, req.Path)
86+
87+
// Extract the base path and the volume unique path
88+
parentDir, volumeDir, err := vm.extractPaths(req.Path)
89+
if err != nil {
90+
return fmt.Errorf("failed to extract paths: %v", err)
91+
}
92+
93+
// Remove the directory
94+
fullPath := filepath.Join(parentDir, volumeDir)
95+
96+
// check if path is xfs quota enabled and remove quota projid
97+
cleanupCmdsForPath := fmt.Sprintf(`
98+
d="%s"
99+
base="%s"
100+
# check fs type first
101+
fs=$(stat -f -c %%T $base 2>/dev/null)
102+
if [ "$fs" = "xfs" ]; then
103+
id=$(xfs_io -c stat $d 2>/dev/null | awk '/projid/{print $3}' | head -1)
104+
echo "projid=$id"
105+
if [ -n "$id" ] && [ "$id" != "0" ]; then
106+
# remove projid binding
107+
xfs_io -c "chproj -R 0" "$d" 2>/dev/null || true
108+
# remove quota limit
109+
xfs_quota -x -c "limit -p bsoft=0 bhard=0 $id" $base 2>/dev/null || true
110+
fi
111+
fi
112+
rm -rf $d
113+
`, fullPath, parentDir)
114+
115+
if err := vm.executeCommand(ctx, "sh", "-c", cleanupCmdsForPath); err != nil {
116+
return fmt.Errorf("failed to delete directory: %v", err)
117+
}
118+
119+
klog.Infof("Successfully deleted volume %s at path %s", req.Name, fullPath)
120+
return nil
121+
}
122+
123+
// ApplyQuota applies filesystem quota to a volume
124+
func (vm *LocalVolumeManager) ApplyQuota(ctx context.Context, req *VolumeRequest) error {
125+
klog.Infof("Applying quota for volume %s at path %s", req.Name, req.Path)
126+
127+
// Extract the base path and the volume unique path
128+
parentDir, volumeDir, err := vm.extractPaths(req.Path)
129+
if err != nil {
130+
return fmt.Errorf("failed to extract paths: %v", err)
131+
}
132+
133+
// Convert limits to kilobytes
134+
softLimitGrace, err := vm.convertToK(req.SoftLimitGrace, req.PVCStorage)
135+
if err != nil {
136+
return fmt.Errorf("failed to convert soft limit: %v", err)
137+
}
138+
139+
hardLimitGrace, err := vm.convertToK(req.HardLimitGrace, req.PVCStorage)
140+
if err != nil {
141+
return fmt.Errorf("failed to convert hard limit: %v", err)
142+
}
143+
144+
// Validate limits
145+
if err := vm.validateLimits(softLimitGrace, hardLimitGrace, req.PVCStorage); err != nil {
146+
return fmt.Errorf("invalid limits: %v", err)
147+
}
148+
149+
// Apply quota based on filesystem type
150+
if err := vm.applyQuotaByFilesystem(ctx, parentDir, volumeDir, softLimitGrace, hardLimitGrace); err != nil {
151+
return fmt.Errorf("failed to apply quota: %v", err)
152+
}
153+
154+
klog.Infof("Successfully applied quota for volume %s", req.Name)
155+
return nil
156+
}
157+
158+
// extractPaths extracts parent directory and volume directory from the full path
159+
func (vm *LocalVolumeManager) extractPaths(fullPath string) (parentDir, volumeDir string, err error) {
160+
// Use hostpath builder to validate and extract paths
161+
return hostpath.NewBuilder().WithPath(fullPath).
162+
WithCheckf(hostpath.IsNonRoot(), "volume directory {%v} should not be under root directory", fullPath).
163+
ExtractSubPath()
164+
}
165+
166+
// executeCommand executes a system command with timeout
167+
func (vm *LocalVolumeManager) executeCommand(ctx context.Context, name string, args ...string) error {
168+
// Create command context with timeout
169+
cmdCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
170+
defer cancel()
171+
172+
cmd := exec.CommandContext(cmdCtx, name, args...)
173+
174+
// Run the command
175+
output, err := cmd.CombinedOutput()
176+
if err != nil {
177+
outputStr := string(output)
178+
if exitError, ok := err.(*exec.ExitError); ok {
179+
if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
180+
// Check for specific error conditions
181+
switch status.ExitStatus() {
182+
case 127: // Command not found
183+
return fmt.Errorf("command not found: %s - please ensure required quota tools are installed", name)
184+
case 1: // General error
185+
if strings.Contains(outputStr, "Unsupported filesystem type") {
186+
return fmt.Errorf("unsupported filesystem type - please ensure the filesystem supports quotas and is properly mounted with quota options")
187+
}
188+
return fmt.Errorf("command failed with exit code %d: %s", status.ExitStatus(), outputStr)
189+
default:
190+
return fmt.Errorf("command failed with exit code %d: %s", status.ExitStatus(), outputStr)
191+
}
192+
}
193+
}
194+
return fmt.Errorf("command failed: %v, output: %s", err, outputStr)
195+
}
196+
197+
return nil
198+
}
199+
200+
// convertToK converts the limits to kilobytes
201+
func (vm *LocalVolumeManager) convertToK(limit string, pvcStorage int64) (string, error) {
202+
if len(limit) == 0 {
203+
return "0k", nil
204+
}
205+
206+
valueRegex := regexp.MustCompile(`[\d]*[\.]?[\d]*`)
207+
valueString := valueRegex.FindString(limit)
208+
209+
if limit != valueString+"%" {
210+
return "", fmt.Errorf("invalid format for limit grace")
211+
}
212+
213+
value, err := strconv.ParseFloat(valueString, 64)
214+
if err != nil {
215+
return "", fmt.Errorf("invalid format, cannot parse")
216+
}
217+
218+
if value > 100 {
219+
value = 100
220+
}
221+
222+
value *= float64(pvcStorage)
223+
value /= 100
224+
value += float64(pvcStorage)
225+
value /= 1024
226+
227+
value = math.Ceil(value)
228+
valueString = strconv.FormatFloat(value, 'f', -1, 64)
229+
valueString += "k"
230+
return valueString, nil
231+
}
232+
233+
// validateLimits validates quota limits
234+
func (vm *LocalVolumeManager) validateLimits(softLimitGrace, hardLimitGrace string, pvcStorage int64) error {
235+
if softLimitGrace == "0k" && hardLimitGrace == "0k" {
236+
// Use PVC storage as both limits
237+
pvcStorageInK := math.Ceil(float64(pvcStorage) / 1024)
238+
pvcStorageInKString := strconv.FormatFloat(pvcStorageInK, 'f', -1, 64) + "k"
239+
softLimitGrace = pvcStorageInKString
240+
hardLimitGrace = pvcStorageInKString
241+
return nil
242+
}
243+
244+
if softLimitGrace == "0k" || hardLimitGrace == "0k" {
245+
return nil
246+
}
247+
248+
if len(softLimitGrace) > len(hardLimitGrace) ||
249+
(len(softLimitGrace) == len(hardLimitGrace) && softLimitGrace > hardLimitGrace) {
250+
return fmt.Errorf("hard limit cannot be smaller than soft limit")
251+
}
252+
253+
return nil
254+
}
255+
256+
// applyQuotaByFilesystem applies quota based on the filesystem type
257+
func (vm *LocalVolumeManager) applyQuotaByFilesystem(ctx context.Context, parentDir, volumeDir, softLimitGrace, hardLimitGrace string) error {
258+
// Create a shell script to detect filesystem and apply quota
259+
// We need to find the actual XFS mount point since parentDir might be a bind mount
260+
script := fmt.Sprintf(`
261+
set -e
262+
263+
# Find the actual mount point for the path using host's mount info
264+
# The host's proc is mounted at /host/proc for container environments
265+
if [ -f /host/proc/1/mountinfo ]; then
266+
# Use host's mountinfo to find the real mount point
267+
MOUNT_POINT=$(findmnt -n -o TARGET --target %s --mountinfo /host/proc/1/mountinfo 2>/dev/null || findmnt -n -o TARGET --target %s 2>/dev/null || echo %s)
268+
else
269+
MOUNT_POINT=$(findmnt -n -o TARGET --target %s 2>/dev/null || echo %s)
270+
fi
271+
272+
# Get filesystem type
273+
FS=$(stat -f -c %%T %s)
274+
275+
if [[ "$FS" == "xfs" ]]; then
276+
# Get the next available project ID
277+
PID=$(xfs_quota -x -c 'report -h' "$MOUNT_POINT" 2>/dev/null | tail -2 | awk 'NR==1{print substr ($1,2)}+0' || echo "0")
278+
PID=$((PID + 1))
279+
# Set up project for the volume directory
280+
xfs_quota -x -c "project -s -p %s $PID" "$MOUNT_POINT"
281+
# Apply quota limits
282+
xfs_quota -x -c "limit -p bsoft=%s bhard=%s $PID" "$MOUNT_POINT"
283+
elif [[ "$FS" == "ext2/ext3" ]]; then
284+
PID=$(repquota -P "$MOUNT_POINT" 2>/dev/null | tail -3 | awk 'NR==1{print substr ($1,2)}+0' || echo "0")
285+
PID=$((PID + 1))
286+
chattr +P -p $PID %s
287+
setquota -P $PID %s %s 0 0 "$MOUNT_POINT"
288+
else
289+
echo "Unsupported filesystem type: $FS"
290+
exit 1
291+
fi`,
292+
parentDir, parentDir, parentDir, // findmnt with host mountinfo, fallback, and default
293+
parentDir, parentDir, // findmnt without host mountinfo
294+
parentDir, // stat filesystem
295+
filepath.Join(parentDir, volumeDir), // project path
296+
softLimitGrace, hardLimitGrace, // xfs limits
297+
filepath.Join(parentDir, volumeDir), // chattr path
298+
strings.ToUpper(softLimitGrace), strings.ToUpper(hardLimitGrace), // ext quota limits
299+
)
300+
301+
// Execute the quota script
302+
return vm.executeCommand(ctx, "sh", "-c", script)
303+
}

0 commit comments

Comments
 (0)