Skip to content

Commit 512d41f

Browse files
committed
Add FS option support
1 parent ab65e2a commit 512d41f

File tree

6 files changed

+123
-66
lines changed

6 files changed

+123
-66
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,22 @@ chown -1:-1
99
1010
```
1111

12+
### Sparse
13+
14+
|FS | Sparse | Regular |
15+
| --- | ------------- | ---------- |
16+
|xfs | 0%/1% | 100%/100% |
17+
|ext4 | 0%/3% | 100%/3% |
18+
19+
1220
## Development
1321
Go 1.11
1422
```bash
1523
go mod vendor
1624
go mod tidy
17-
```
25+
```
26+
27+
## Test
28+
ext4/xfs - sparse/regular
29+
on ext4/xfs vs ext3
30+
not enough space

driver/driver.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"strconv"
7+
"strings"
78
"sync"
89
"time"
910

@@ -90,6 +91,17 @@ func (d Driver) Create(req *v.CreateRequest) error {
9091
}
9192
}
9293

94+
var fs string
95+
fsInput, fsPresent := req.Options["fs"]
96+
if fsPresent && len(fsInput) > 0 {
97+
fs = strings.ToLower(strings.TrimSpace(fsInput))
98+
} else {
99+
fs = "xfs"
100+
logger.Debug().
101+
Str("default", fs).
102+
Msg("no fs opt found, using default")
103+
}
104+
93105
uid := -1
94106
uidStr, uidPresent := req.Options["uid"]
95107
if uidPresent && len(uidStr) > 0 {
@@ -151,7 +163,7 @@ func (d Driver) Create(req *v.CreateRequest) error {
151163

152164
logger.Debug().Msg("starting creation")
153165

154-
err = d.manager.Create(req.Name, sizeInBytes, sparse, uid, gid, mode)
166+
err = d.manager.Create(req.Name, sizeInBytes, sparse, fs, uid, gid, mode)
155167
if err != nil {
156168
return err
157169
}
@@ -214,8 +226,9 @@ func (d Driver) Get(req *v.GetRequest) (*v.GetResponse, error) {
214226
CreatedAt: fmt.Sprintf(vol.CreatedAt.Format(time.RFC3339)),
215227
Mountpoint: vol.MountPointPath,
216228
Status: map[string]interface{}{
217-
"size-max": strconv.FormatUint(vol.MaxSizeInBytes, 10),
218-
"size-current": strconv.FormatUint(vol.CurrentSizeInBytes, 10),
229+
"fs": vol.Fs,
230+
"size-max": strconv.FormatUint(vol.MaxSizeInBytes, 10),
231+
"size-allocated": strconv.FormatUint(vol.AllocatedSizeInBytes, 10),
219232
},
220233
}
221234

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ var (
2929
StateDir: "/run/docker-volume-loopback",
3030
DataDir: "/var/lib/docker-volume-loopback",
3131
MountDir: "/mnt",
32-
DefaultSize: "1G",
32+
DefaultSize: "1GiB",
3333
Debug: false,
3434
}
3535
)

manager/manager.go

Lines changed: 83 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -29,47 +29,6 @@ type Config struct {
2929
MountDir string
3030
}
3131

32-
func (m Manager) getVolume(name string) (vol Volume, err error) {
33-
volumeDataFilePath := filepath.Join(m.dataDir, name)
34-
35-
volumeDataFileInfo, err := os.Stat(volumeDataFilePath)
36-
37-
if err != nil {
38-
if os.IsNotExist(err) {
39-
err = errors.Errorf("Volume '%s' does not exist", name)
40-
}
41-
return
42-
}
43-
44-
if !volumeDataFileInfo.Mode().IsRegular() {
45-
err = errors.Errorf(
46-
"Volume data path expected to point toa file but it appears to be something else: '%s'",
47-
volumeDataFilePath)
48-
return
49-
}
50-
51-
details, ok := volumeDataFileInfo.Sys().(*syscall.Stat_t)
52-
if !ok {
53-
err = errors.Errorf(
54-
"An issue occurred while retrieving details about volume '%s' - cannot stat '%s'",
55-
name, volumeDataFilePath)
56-
}
57-
58-
mountPointPath := filepath.Join(m.mountDir, name)
59-
60-
vol = Volume{
61-
Name: name,
62-
CurrentSizeInBytes: uint64(details.Blocks * 512),
63-
MaxSizeInBytes: uint64(details.Size),
64-
StateDir: filepath.Join(m.stateDir, name),
65-
DataFilePath: volumeDataFilePath,
66-
MountPointPath: mountPointPath,
67-
CreatedAt: volumeDataFileInfo.ModTime(),
68-
}
69-
70-
return
71-
}
72-
7332
func New(cfg Config) (manager Manager, err error) {
7433
// state dir
7534
if cfg.StateDir == "" {
@@ -127,7 +86,8 @@ func (m Manager) List() ([]Volume, error) {
12786

12887
for _, file := range files {
12988
if file.Mode().IsRegular() {
130-
vol, err := m.getVolume(file.Name())
89+
name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
90+
vol, err := m.getVolume(name)
13191
if err != nil {
13292
return nil, err
13393
}
@@ -151,7 +111,7 @@ func (m Manager) Get(name string) (vol Volume, err error) {
151111
return
152112
}
153113

154-
func (m Manager) Create(name string, sizeInBytes int64, sparse bool, uid, gid int, mode uint32) error {
114+
func (m Manager) Create(name string, sizeInBytes int64, sparse bool, fs string, uid, gid int, mode uint32) error {
155115
err := validateName(name)
156116
if err != nil {
157117
return errors.Wrapf(err,
@@ -165,15 +125,27 @@ func (m Manager) Create(name string, sizeInBytes int64, sparse bool, uid, gid in
165125
name, sizeInBytes)
166126
}
167127

128+
// We perform fs validation and construct mkfs flags array on the way
129+
var mkfsFlags []string
130+
if fs == "xfs" {
131+
mkfsFlags = []string{}
132+
} else if fs == "ext4" {
133+
mkfsFlags = []string{"-F"}
134+
} else {
135+
return errors.Errorf(
136+
"Error creating volume '%s' - only xfs and ext4 filesystems are supported, '%s' requested",
137+
name, fs)
138+
}
139+
168140
err = os.MkdirAll(m.dataDir, 0755)
169141
if err != nil {
170142
return errors.Wrapf(err,
171-
"Error creating volume '%s' - data dir does not exist: '%s'",
143+
"Error creating volume '%s' - cannot create data dir: '%s'",
172144
name, m.dataDir)
173145
}
174146

175147
// create data file
176-
dataFilePath := filepath.Join(m.dataDir, name)
148+
dataFilePath := filepath.Join(m.dataDir, name+"."+fs)
177149
dataFileInfo, err := os.Create(dataFilePath)
178150
if err != nil {
179151
_ = os.Remove(dataFilePath) // attempt to cleanup
@@ -207,7 +179,7 @@ func (m Manager) Create(name string, sizeInBytes int64, sparse bool, uid, gid in
207179
}
208180

209181
// Here we assume that FS is unsupported and will fall back to 'dd' which is slow but should work everywhere
210-
of := fmt.Sprintf("of=%s", dataFilePath)
182+
of := "of=" + dataFilePath
211183
bs := int64(1000000)
212184
count := sizeInBytes / bs // we lose some precision here but it's likely to be negligible
213185
errBytes, err = exec.Command(
@@ -226,12 +198,12 @@ func (m Manager) Create(name string, sizeInBytes int64, sparse bool, uid, gid in
226198
}
227199

228200
// format data file
229-
_, err = exec.Command("mkfs.ext4", "-F", dataFilePath).Output()
201+
_, err = exec.Command(fmt.Sprintf("mkfs.%s", fs), append(mkfsFlags, dataFilePath)...).Output()
230202
if err != nil {
231203
_ = os.Remove(dataFilePath) // attempt to cleanup
232204
return errors.Wrapf(err,
233-
"Error creating volume '%s' - cannot format datafile as ext4 filesystem",
234-
name, sizeInBytes)
205+
"Error creating volume '%s' - cannot format datafile as %s filesystem",
206+
name, fs)
235207
}
236208

237209
// At this point we're done - last step is to adjust ownership if required.
@@ -243,7 +215,7 @@ func (m Manager) Create(name string, sizeInBytes int64, sparse bool, uid, gid in
243215
_ = os.Remove(dataFilePath) // attempt to cleanup
244216
return errors.Wrapf(err,
245217
"Error creating volume '%s' - cannot mount volume to adjust its root owner/permissions",
246-
name, sizeInBytes)
218+
name)
247219
}
248220

249221
if mode > 0 {
@@ -253,7 +225,7 @@ func (m Manager) Create(name string, sizeInBytes int64, sparse bool, uid, gid in
253225
_ = os.Remove(dataFilePath) // attempt to cleanup
254226
return errors.Wrapf(err,
255227
"Error creating volume '%s' - cannot adjust volume root permissions",
256-
name, sizeInBytes)
228+
name)
257229
}
258230
}
259231
err = os.Chown(mountPath, uid, gid)
@@ -262,15 +234,15 @@ func (m Manager) Create(name string, sizeInBytes int64, sparse bool, uid, gid in
262234
_ = os.Remove(dataFilePath) // attempt to cleanup
263235
return errors.Wrapf(err,
264236
"Error creating volume '%s' - cannot adjust volume root owner",
265-
name, sizeInBytes)
237+
name)
266238
}
267239

268240
err = m.UnMount(name, lease)
269241
if err != nil {
270242
_ = os.Remove(dataFilePath) // attempt to cleanup
271243
return errors.Wrapf(err,
272244
"Error creating volume '%s' - cannot unmount volume after adjusting its root owner/permissions",
273-
name, sizeInBytes)
245+
name)
274246
}
275247
}
276248

@@ -450,3 +422,61 @@ func validateName(name string) error {
450422
}
451423
return nil
452424
}
425+
426+
func (m Manager) getVolume(name string) (vol Volume, err error) {
427+
prefix := filepath.Join(m.dataDir, name) + ".*"
428+
matches, err := filepath.Glob(prefix)
429+
if err != nil {
430+
err = errors.Wrapf(err,
431+
"An issue occurred while retrieving details about volume '%s' - cannot glob data dir", name)
432+
return
433+
}
434+
if len(matches) > 1 {
435+
err = errors.Errorf("More than 1 data file found for volume '%s'", name)
436+
return
437+
} else if len(matches) == 0 {
438+
err = errors.Errorf("Volume '%s' does not exist", name)
439+
return
440+
}
441+
442+
volumeDataFilePath := matches[0]
443+
fs := strings.TrimLeft(filepath.Ext(volumeDataFilePath), ".")
444+
445+
volumeDataFileInfo, err := os.Stat(volumeDataFilePath)
446+
447+
if err != nil {
448+
if os.IsNotExist(err) { // this should not happen but...
449+
err = errors.Errorf("Volume '%s' disappeared just a moment ago", name)
450+
}
451+
return
452+
}
453+
454+
if !volumeDataFileInfo.Mode().IsRegular() {
455+
err = errors.Errorf(
456+
"Volume data path expected to point to a file but it appears to be something else: '%s'",
457+
volumeDataFilePath)
458+
return
459+
}
460+
461+
details, ok := volumeDataFileInfo.Sys().(*syscall.Stat_t)
462+
if !ok {
463+
err = errors.Errorf(
464+
"An issue occurred while retrieving details about volume '%s' - cannot stat '%s'",
465+
name, volumeDataFilePath)
466+
}
467+
468+
mountPointPath := filepath.Join(m.mountDir, name)
469+
470+
vol = Volume{
471+
Name: name,
472+
Fs: fs,
473+
AllocatedSizeInBytes: uint64(details.Blocks * 512),
474+
MaxSizeInBytes: uint64(details.Size),
475+
StateDir: filepath.Join(m.stateDir, name),
476+
DataFilePath: volumeDataFilePath,
477+
MountPointPath: mountPointPath,
478+
CreatedAt: volumeDataFileInfo.ModTime(),
479+
}
480+
481+
return
482+
}

manager/volume.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import (
88
)
99

1010
type Volume struct {
11-
Name string
12-
CurrentSizeInBytes uint64
13-
MaxSizeInBytes uint64
14-
StateDir string
15-
DataFilePath string
16-
MountPointPath string
17-
CreatedAt time.Time
11+
Name string
12+
AllocatedSizeInBytes uint64
13+
Fs string
14+
MaxSizeInBytes uint64
15+
StateDir string
16+
DataFilePath string
17+
MountPointPath string
18+
CreatedAt time.Time
1819
}
1920

2021
func (v Volume) IsMounted() (mounted bool, err error) {

plugin/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"Description": "Default size to apply to volumes when no value is specified",
2828
"Name": "DEFAULT_SIZE",
2929
"Settable": ["value"],
30-
"Value": "1G"
30+
"Value": "1GiB"
3131
}
3232
],
3333
"Interface": {

0 commit comments

Comments
 (0)