Skip to content

Commit daaad09

Browse files
committed
Make sure etsts work regardless of how plugin is run
1 parent b6bd336 commit daaad09

File tree

9 files changed

+114
-48
lines changed

9 files changed

+114
-48
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ RUN make build && \
99

1010
# package
1111
FROM alpine
12-
RUN apk --no-cache add e2fsprogs xfsprogs
12+
RUN apk --no-cache add e2fsprogs xfsprogs util-linux && rm -rf /usr/share/terminfo && rm -rf /etc/terminfo
1313
COPY --from=builder /docker-volume-loopback /
1414
CMD [ "/docker-volume-loopback" ]

README.md

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
# Performance
2-
https://serverfault.com/questions/166748/performance-of-loopback-filesystems
3-
https://kernelnewbies.org/Linux_4.4#Faster_and_leaner_loop_device_with_Direct_I.2FO_and_Asynchronous_I.2FO_support
4-
5-
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=bc07c10a3603a5ab3ef01ba42b3d41f9ac63d1b6
1+
# Docker Volume Loopback
62

3+
## Overview
74

85
### Sparse
96

@@ -12,17 +9,85 @@ https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=bc
129
|xfs | 0%/1% | 100%/100% |
1310
|ext4 | 0%/3% | 100%/3% |
1411

12+
## Installation
13+
14+
### Automatic
15+
16+
### Manual
17+
18+
## Usage
19+
20+
## Known Issues and Limitations
21+
22+
### Compatibility
23+
24+
When dealing with volumes based on `XFS` filesystem the driver depends on `mkfs.xfs` from `xfsprogs` package.
25+
Starting from `v3.2.3` (released on 2015-06-10) of the package newly created `XfS` filesystems default to use of a newer
26+
metadata format that is only supported by Linux kernel `v3.16` and above.
27+
28+
This manifests as a failure during an attempt to mount a volume - the filesystem will be initialized properly upon
29+
volume creation but kernel will not be able to mount it.
30+
31+
This is unlikely to be an issue if _manual_ installation mode is used as the version of `xfsprogs` available via Linux
32+
distribution-specific package manager is likely to be compatible with the version of Linux kernel required.
33+
When _automatic_ installation is used though the plugin is \[effectively\] distributed as a container image based on
34+
Alpine Linux which \[at the moment of writing\] uses at least `v4.19.0` of `xfsprogs` package. This means that in case
35+
the plugin will be installed on a system based on older version of kernel it won't be able to use `XFS` filesystem.
36+
37+
There is a workaround available that can be used to circumvent the issue: use an extra `-m crc=0` parameter when calling
38+
`mkfs.xfs` which will force use of older version of metadata compatible with older versions of kernel. Unfortunately this
39+
cannot be a default behavior as this flag was added only to `xfsprogs` starting from `v3.2.0` and therefore is unlikely
40+
to be available on similar systems based on outdated versions of kernel \[in case _manual_ installation mode is used\].
41+
42+
43+
While the workaround is trivial in essence it is trickier to implement as would require fir amount of code to run
44+
dynamic checks for versions of kernel and `mkfs.xfs`. Hence a conscious decision to not implement this behavior initially.
45+
In case there will be interest if support for older systems it can be easily added and should be requested via a GitHub
46+
issue.
47+
48+
Below is the list of distributions known to be affected by this:
49+
50+
| Distribution | Version | Release Date | End of Life Date |
51+
| ------------ | --------------------- | ------------ | ---------------- |
52+
| Ubuntu | 14.04 LTS Trusty Tahr | 2014-04-17 | 2019-04-01 |
53+
54+
55+
Entries are going to be removed from the table upon reaching EOL.
56+
May the list above be exhausted this notice will be dropped altogether.
57+
58+
### Performance
59+
60+
[Loop devices are notorious for their "bad" performance]. While it's not arguable that they incur and some overhead both
61+
in terms of CPU and memory it, it always should be evaluated in a context of a concrete use case. Generally speaking,
62+
if one does not constantly `fsync` after each write to a filesystem based on a loopback device \[and just let kernel do its job\]
63+
the performance seems to be comparable to regular block devices. Although, it must be noted that in such cases write
64+
operations are susceptible to the "double caching" issue where data are cached first while being written to the loopback
65+
device backed filesystem and then data cached again while changes are bing finally committed to the backing block device.
66+
This means that there may be a delay (on average, up to a 2 x 30 seconds = 1 minute) before writes will become durable
67+
by being committed to the backing block device. Also, cache buffers are usually freed and committed more often when
68+
system runs low on memory which means that usage of loopback devices is unlikely to degrade performance of the system as
69+
a whole but system running low on memory is likely to have loopback devices performing worse. For the record, cache memory
70+
is not being counted against `memory.max_usage_in_bytes` cgroup controller and therefore is ignored by Docker.
71+
72+
Last but not least, the release of Linux kernel `v4.4` includes "[Faster and leaner loop device with Direct I/O and Asynchronous I/O support]"
73+
which [circumvents the "double buffering" issue].
74+
75+
In terms of CPU, while no comprehensive benchmarks have been done during development of the plugin, the overhead seem to
76+
be negligible.
77+
1578

1679
## Development
17-
Go 1.11
80+
Go 1.11 modules
81+
1882
```bash
1983
go mod vendor
2084
go mod tidy
2185
```
2286

23-
# Extra
24-
validate options
87+
## License
88+
89+
This is free and unencumbered software released into the public domain. See [LICENSE](./LICENSE)
2590

26-
if _, err := exec.LookPath("mkfs.xfs"); err != nil {
27-
logrus.Fatal("mkfs.xfs is not available, please install xfsprogs to continue")
28-
}
91+
[Loop devices are notorious for their "bad" performance]: https://serverfault.com/questions/166748/performance-of-loopback-filesystems
92+
[Faster and leaner loop device with Direct I/O and Asynchronous I/O support]: https://kernelnewbies.org/Linux_4.4#Faster_and_leaner_loop_device_with_Direct_I.2FO_and_Asynchronous_I.2FO_support
93+
[circumvents the "double buffering" issue]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=bc07c10a3603a5ab3ef01ba42b3d41f9ac63d1b6

Vagrantfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Vagrant.configure(2) do |config|
2-
config.vm.box = "ubuntu/trusty64"
2+
config.vm.box = "ubuntu/bionic64"
33
config.vm.provision :docker
4-
config.vm.provision "shell", inline: "apt-get update && apt-get install -y jq xfsprogs"
4+
config.vm.provision "shell", inline: "apt-get update && apt-get install -y binutils jq xfsprogs"
55
end

manager/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var (
1818

1919
MkFsOptions = map[string][]string{
2020
"ext4": {"-F"},
21-
"xfs": {},
21+
"xfs": {"-f"},
2222
}
2323

2424
MountOptions = map[string][]string{

plugin/config.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
"Description": "Path to the plugin UNIX socket",
3434
"Name": "SOCKET",
3535
"Settable": [],
36-
"Value": "/run/docker/plugins/docker-volume-loop.sock"
36+
"Value": "/run/docker/plugins/loop.sock"
3737
}
3838
],
3939
"Interface": {
40-
"Socket": "docker-volume-loop.sock",
40+
"Socket": "loop.sock",
4141
"Types": ["docker.volumedriver/1.0"]
4242
},
4343
"Linux": {
@@ -61,7 +61,7 @@
6161
},
6262
{
6363
"Destination": "/srv",
64-
"Options": ["bind"],
64+
"Options": ["rbind"],
6565
"Source": "/",
6666
"Type": "bind"
6767
}

tests/test.sh

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,30 @@
44

55
IMAGE="alpine"
66
DRIVER="docker-volume-loopback"
7-
DATA_DIR="/var/lib/${DRIVER}"
7+
eval $(strings -a /proc/$(pidof docker-volume-loopback)/environ | grep DATA_DIR)
8+
DATA_DIR=${DATA_DIR:-"/var/lib/${DRIVER}"} # a default fall-back
9+
10+
run() {
11+
nsenter -t $(pidof "${DRIVER}") -a "${@}"
12+
}
813

914
oneTimeSetUp() {
1015
docker volume rm $(docker volume create -d "${DRIVER}" -o size=100MiB) &> /dev/null
1116
}
1217

1318
setUp() {
1419
HANDLE=$(mktemp -u)
15-
truncate -s "${BASE_SIZE:-2G}" "${HANDLE}"
20+
run truncate -s "${BASE_SIZE:-2G}" "${HANDLE}"
1621

1722

1823
case "${BASE_FS:-xfs}" in
1924
xfs)
20-
mkfs.xfs "${HANDLE}" &> /dev/null
21-
mount -o nouuid "${HANDLE}" "${DATA_DIR}"
25+
run mkfs.xfs -f "${HANDLE}" &> /dev/null
26+
run mount -o nouuid "${HANDLE}" "${DATA_DIR}"
2227
;;
2328
ext*)
24-
mkfs.${BASE_FS} -F "${HANDLE}" &> /dev/null
25-
mount "${HANDLE}" "${DATA_DIR}"
29+
run mkfs.${BASE_FS} -F "${HANDLE}" &> /dev/null
30+
run mount "${HANDLE}" "${DATA_DIR}"
2631
;;
2732
*)
2833
echo "Unsupported BASE fs"
@@ -32,8 +37,8 @@ setUp() {
3237
}
3338

3439
tearDown() {
35-
umount -ld "${DATA_DIR}"
36-
rm -f "${HANDLE}"
40+
run umount -ld "${DATA_DIR}"
41+
run rm -f "${HANDLE}"
3742
}
3843

3944
. ./shunit2

tests/test_fs_ext4.sh

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ testRegularVolumeChecksDiskSpaceBeforeFormatting() {
1111
result=$?
1212

1313
## because we shadow real data dir with our test volume we're sure there shouldn't be any volumes
14-
count=$(ls -1 "/var/lib/${DRIVER}/" | wc -l)
14+
count=$(run ls -1 "${DATA_DIR}/" | wc -l)
1515

1616
# checks
1717
assertEquals "1" "${result}"
@@ -23,9 +23,8 @@ testRegularVolumeDoesNotReserveDiskSpace() {
2323
# setup
2424
volume=$(docker volume create -d "${DRIVER}" -o fs=${FS} -o sparse=false)
2525

26-
info=$(ls --block-size=M -ls "/var/lib/${DRIVER}/${volume}.${FS}")
27-
allocated_size=$(echo ${info} | awk '{print $1}' | tr -dc '0-9')
28-
apparent_size=$(echo ${info} | awk '{print $6}' | tr -dc '0-9')
26+
allocated_size="$(($(run stat -c '%b %B' ${DATA_DIR}/${volume}.${FS} | tr ' ' '*')))" # allocated space in bytes
27+
apparent_size="$(run stat -c '%s' ${DATA_DIR}/${volume}.${FS})" # apparent space in bytes
2928

3029
# checks
3130
assertTrue "Regular ${FS} volume of ${apparent_size} MiB should take less space: ${allocated_size} MiB" "[ ${allocated_size} -lt ${apparent_size} ]"
@@ -42,12 +41,11 @@ testSparseVolumeDoesNotCheckAvailableDiskSpace() {
4241
volume=$(docker volume create -d "${DRIVER}" -o fs=${FS} -o sparse=true -o size=10GiB)
4342
result=$?
4443

45-
info=$(ls --block-size=M -ls "/var/lib/${DRIVER}/${volume}.${FS}")
46-
apparent_size=$(echo ${info} | awk '{print $6}' | tr -dc '0-9')
44+
apparent_size="$(run stat -c '%s' ${DATA_DIR}/${volume}.${FS})" # apparent space in bytes
4745

4846
# checks
4947
assertEquals "0" "${result}"
50-
assertEquals "10240" "${apparent_size}"
48+
assertEquals "$((10*1024*1024*1024))" "${apparent_size}"
5149

5250
# cleanup
5351
docker volume rm "${volume}" > /dev/null
@@ -58,9 +56,8 @@ testSparseVolumeDoesNotReserveDiskSpace() {
5856
# setup
5957
volume=$(docker volume create -d "${DRIVER}" -o fs=${FS} -o sparse=true)
6058

61-
info=$(ls --block-size=M -ls "/var/lib/${DRIVER}/${volume}.${FS}")
62-
allocated_size=$(echo ${info} | awk '{print $1}' | tr -dc '0-9')
63-
apparent_size=$(echo ${info} | awk '{print $6}' | tr -dc '0-9')
59+
allocated_size="$(($(run stat -c '%b %B' ${DATA_DIR}/${volume}.${FS} | tr ' ' '*')))" # allocated space in bytes
60+
apparent_size="$(run stat -c '%s' ${DATA_DIR}/${volume}.${FS})" # apparent space in bytes
6461

6562
# checks
6663
assertTrue "Sparse ${FS} volume of ${apparent_size} MiB should take less space: ${allocated_size} MiB" "[ ${allocated_size} -lt ${apparent_size} ]"

tests/test_fs_xfs.sh

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ testRegularVolumeChecksDiskSpaceBeforeFormatting() {
1111
result=$?
1212

1313
## because we shadow real data dir with our test volume we're sure there shouldn't be any volumes
14-
count=$(ls -1 "/var/lib/${DRIVER}/" | wc -l)
14+
count=$(run ls -1 "${DATA_DIR}/" | wc -l)
1515

1616
# checks
1717
assertEquals "1" "${result}"
@@ -22,9 +22,9 @@ testRegularVolumeReservesDiskSpace() {
2222
local volume info allocated_size apparent_size
2323
# setup
2424
local volume=$(docker volume create -d "${DRIVER}" -o fs=${FS} -o sparse=false)
25-
local info=$(ls --block-size=M -ls "/var/lib/${DRIVER}/${volume}.${FS}")
26-
local allocated_size=$(echo ${info} | awk '{print $1}' | tr -dc '0-9')
27-
local apparent_size=$(echo ${info} | awk '{print $6}' | tr -dc '0-9')
25+
26+
allocated_size="$(($(run stat -c '%b %B' ${DATA_DIR}/${volume}.${FS} | tr ' ' '*')))" # allocated space in bytes
27+
apparent_size="$(run stat -c '%s' ${DATA_DIR}/${volume}.${FS})" # apparent space in bytes
2828

2929
# checks
3030
assertTrue "Regular ${FS} volume of ${apparent_size} MiB should take at least same space: ${allocated_size} MiB" "[ ${allocated_size} -ge ${apparent_size} ]"
@@ -41,12 +41,11 @@ testSparseVolumeDoesNotCheckAvailableDiskSpace() {
4141
volume=$(docker volume create -d "${DRIVER}" -o fs=${FS} -o sparse=true -o size=10GiB)
4242
result=$?
4343

44-
info=$(ls --block-size=M -ls "/var/lib/${DRIVER}/${volume}.${FS}")
45-
apparent_size=$(echo ${info} | awk '{print $6}' | tr -dc '0-9')
44+
apparent_size="$(run stat -c '%s' ${DATA_DIR}/${volume}.${FS})" # apparent space in bytes
4645

4746
# checks
4847
assertEquals "0" "${result}"
49-
assertEquals "10240" "${apparent_size}"
48+
assertEquals "$((10*1024*1024*1024))" "${apparent_size}"
5049

5150
# cleanup
5251
docker volume rm "${volume}" > /dev/null
@@ -56,9 +55,9 @@ testSparseVolumeDoesNotTakeDiskSpace() {
5655
local volume info allocated_size apparent_size
5756
# setup
5857
local volume=$(docker volume create -d "${DRIVER}" -o fs=${FS} -o sparse=true)
59-
local info=$(ls --block-size=M -ls "/var/lib/${DRIVER}/${volume}.${FS}")
60-
local allocated_size=$(echo ${info} | awk '{print $1}' | tr -dc '0-9')
61-
local apparent_size=$(echo ${info} | awk '{print $6}' | tr -dc '0-9')
58+
59+
allocated_size="$(($(run stat -c '%b %B' ${DATA_DIR}/${volume}.${FS} | tr ' ' '*')))" # allocated space in bytes
60+
apparent_size="$(run stat -c '%s' ${DATA_DIR}/${volume}.${FS})" # apparent space in bytes
6261

6362
# checks
6463
assertTrue "Sparse ${FS} volume of ${apparent_size} MiB should take less space: ${allocated_size} MiB" "[ ${allocated_size} -lt ${apparent_size} ]"

tests/test_volume_create_backing_fs.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ testFallbackToDdFromFallocateOnUnsupportedFs() {
1212
result=$?
1313

1414
## because we shadow real data dir with our test volume we're sure there shouldn't be any volumes
15-
count=$(ls -1 "/var/lib/${DRIVER}/" | grep -v "lost+found" | wc -l)
15+
count=$(run ls -1 "${DATA_DIR}/" | grep -v "lost+found" | wc -l)
1616

1717
assertEquals "Volume creation should succeed" "0" "${result}"
1818
assertEquals "There should be 1 volume" "1" "${count}"
@@ -30,7 +30,7 @@ testDdFailureScenarioWhenThereIsNotEnoughDiskSpace() {
3030
result=$?
3131

3232
## because we shadow real data dir with our test volume we're sure there shouldn't be any volumes
33-
count=$(ls -1 "/var/lib/${DRIVER}/" | grep -v "lost+found" | wc -l)
33+
count=$(run ls -1 "${DATA_DIR}/" | grep -v "lost+found" | wc -l)
3434

3535
assertEquals "Volume creation should fail" "1" "${result}"
3636
assertEquals "There should be no volumes" "0" "${count}"

0 commit comments

Comments
 (0)