Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a feature to balance write to load lowest rw disk #47

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/fstab
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/data/backup1=RW:/data/backup2=RW /data/backup unionfs rw,cow,max_files=32768,allow_other,use_ino,suid,dev,nonempty,lbwrite 0 0
6 changes: 3 additions & 3 deletions mount.unionfs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/bin/bash

label=$1
export PATH=/sbin:/usr/local/bin:/bin:/usr/bin
src=$1
mpoint=$2
shift 3
dirs=${@##*,dirs=}
dirs=${dirs/,/:}
mount.fuse "unionfs#$dirs" "$mpoint" -o ${@%%,dirs=*}
mount.fuse "unionfs#$src" "$mpoint" -o ${@%%,dirs=*}

39 changes: 37 additions & 2 deletions src/findbranch.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/vfs.h>

#include "unionfs.h"
#include "opts.h"
Expand Down Expand Up @@ -137,9 +138,10 @@ int __find_rw_branch_cutlast(const char *path, int rw_hint) {

// Reminder rw_hint == -1 -> autodetect, we do not care which branch it is
if (uopt.branches[branch].rw
&& uopt.lbwrite == false
&& (rw_hint == -1 || branch == rw_hint)) goto out;

if (!uopt.cow_enabled) {
if (!uopt.cow_enabled && !uopt.lbwrite) {
// So path exists, but is not writable.
branch = -1;
errno = EACCES;
Expand All @@ -149,7 +151,12 @@ int __find_rw_branch_cutlast(const char *path, int rw_hint) {
int branch_rw;
// since it is a directory, any rw-branch is fine
if (rw_hint == -1)
branch_rw = find_lowest_rw_branch(uopt.nbranches);
if (uopt.lbwrite == true){
branch_rw = find_loadlowest_rw_branch(uopt.nbranches);
}
else{
branch_rw = find_lowest_rw_branch(uopt.nbranches);
}
else
branch_rw = rw_hint;

Expand Down Expand Up @@ -236,3 +243,31 @@ int find_lowest_rw_branch(int branch_ro) {

RETURN(-1);
}

/**
* Find load lowest possible writable branch but only lower than branch_ro.
*/
int find_loadlowest_rw_branch(int branch_ro) {
DBG_IN();
struct statfs stb;
int res, retVal = -1;
unsigned long long free_space,max_free_space = 0;

int i = 0;
for (i = 0; i < branch_ro; i++) {
res = statfs(uopt.branches[i].path, &stb);
if (res == -1) {
retVal = -errno;
break;
}
free_space=stb.f_bsize*stb.f_bfree;
DBG("branch %s free space %llu\n",uopt.branches[i].path,free_space);
if ( free_space > max_free_space ){
retVal = i;
max_free_space = free_space;
}
}

if (retVal >= 0) RETURN(retVal); // found it it.
RETURN(-1);
}
1 change: 1 addition & 0 deletions src/findbranch.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ typedef enum searchflag {

int find_rorw_branch(const char *path);
int find_lowest_rw_branch(int branch_ro);
int find_loadlowest_rw_branch(int branch_ro);
int find_rw_branch_cutlast(const char *path);
int __find_rw_branch_cutlast(const char *path, int rw_hint);
int find_rw_branch_cow(const char *path);
Expand Down
4 changes: 4 additions & 0 deletions src/opts.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ static void print_help(const char *progname) {
" -o relaxed_permissions Disable permissions checks, but only if\n"
" running neither as UID=0 or GID=0\n"
" -o statfs_omit_ro do not count blocks of ro-branches\n"
" -o lbwrite write to each disks load balancely\n"
"\n",
progname);
}
Expand Down Expand Up @@ -395,6 +396,9 @@ int unionfs_opt_proc(void *data, const char *arg, int key, struct fuse_args *out
case KEY_RELAXED_PERMISSIONS:
uopt.relaxed_permissions = true;
return 0;
case KEY_LBWRITE:
uopt.lbwrite = true;
return 0;
case KEY_VERSION:
printf("unionfs-fuse version: "VERSION"\n");
#ifdef HAVE_XATTR
Expand Down
2 changes: 2 additions & 0 deletions src/opts.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ typedef struct {
pthread_rwlock_t dbgpath_lock; // locks dbgpath
bool hide_meta_files;
bool relaxed_permissions;
bool lbwrite;

} uopt_t;

enum {
KEY_LBWRITE,
KEY_CHROOT,
KEY_COW,
KEY_DEBUG_FILE,
Expand Down
1 change: 1 addition & 0 deletions src/unionfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#endif

static struct fuse_opt unionfs_opts[] = {
FUSE_OPT_KEY("lbwrite", KEY_LBWRITE),
FUSE_OPT_KEY("chroot=%s,", KEY_CHROOT),
FUSE_OPT_KEY("cow", KEY_COW),
FUSE_OPT_KEY("debug_file=%s", KEY_DEBUG_FILE),
Expand Down
98 changes: 98 additions & 0 deletions test
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/bin/bash
# this is a legacy version (will be removed in the future). please use test.py.

set -v
set -e

rm -rf original union working-copy
mkdir original union working-copy original/play-dir original/del-dir
echo v1 > original/file
echo v1 > original/play-with-me
echo v1 > original/delete-me

cleanup() {
if [ -e "union" ]; then fusermount -u -q union; fi
rm -rf union original working-copy
}
trap cleanup EXIT

src/unionfs -d -o cow working-copy=rw:original=ro union >unionfs.log 2>&1 &

sleep 1

[ "$(cat union/file)" = "v1" ]

echo "v2" > union/file
[ "$(cat union/file)" = "v2" ]

echo "v2" > union/play-with-me
[ "$(cat union/play-with-me)" = "v2" ]

[ -f union/play-with-me ]
rm union/play-with-me
[ ! -f union/play-with-me ]

[ -f union/delete-me ]
rm union/delete-me
[ ! -f union/delete-me ]

[ "$(ls union/play-dir)" = "" ]
echo "fool" > union/play-dir/foo
[ "$(ls union/play-dir)" = "foo" ]
rm union/play-dir/foo
[ "$(ls union/play-dir)" = "" ]

[ -d union/play-dir ]
rmdir union/play-dir
[ ! -d union/play-dir ]

[ -d union/del-dir ]
rmdir union/del-dir
[ ! -d union/del-dir ]

! echo v1 > union/del-dir/foo

[ ! -d union/del-dir ]
mkdir union/del-dir
[ ! -f union/del-dir/foo ]
echo v1 > union/del-dir/foo
[ -f union/del-dir/foo ]
rm union/del-dir/foo
[ -d union/del-dir ]
rmdir union/del-dir
[ ! -d union/del-dir ]

# rmdir() test
set +e
set +v
rc=0
mkdir original/testdir
touch original/testdir/testfile
mkdir working-copy/testdir
rmdir union/testdir 2>/dev/null
if [ $? -eq 0 ]; then
echo "rmdir succeeded, although it must not"
rc=$(($rc + $?))
fi
rm union/testdir/testfile
rc=$(($rc + $?))
rmdir union/testdir/
rc=$(($rc + $?))
if [ $rc -ne 0 ]; then
echo "rmdir test failed"
exit 1
else
echo "rmdir test passed"
fi
set -e

fusermount -u union

[ "$(cat original/file)" = "v1" ]
[ "$(cat original/play-with-me)" = "v1" ]
[ "$(cat original/delete-me)" = "v1" ]
[ -d original/play-dir ]
[ -d original/del-dir ]
[ "$(cat working-copy/file)" = "v2" ]

echo "ALL TEST PASSED"
90 changes: 89 additions & 1 deletion test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ def read_from_file(fn):
def get_dir_contents(directory):
return [dirs for (_, dirs, _) in os.walk(directory)]


class Common:
def setUp(self):
self.unionfs_path = os.path.abspath('src/unionfs')
Expand Down Expand Up @@ -69,6 +68,57 @@ def tearDown(self):

shutil.rmtree(self.tmpdir)

class CommonWithImg:
def setUp(self):
self.unionfs_path = os.path.abspath('src/unionfs')
self.unionfsctl_path = os.path.abspath('src/unionfsctl')

self.tmpdir = tempfile.mkdtemp()
#self.tmpdir = "/tmp/123"
self.original_cwd = os.getcwd()
os.chdir(self.tmpdir)

self._dirs = ['ro1', 'ro2', 'rw1', 'rw2']

for d in self._dirs:
#call("dd if=/dev/zero of=%s/%s.img bs=4M count=25" % self.tmpdir,d)
call('dd if=/dev/zero of=%s/%s.img bs=4M count=5 2>/dev/null' % (str(self.tmpdir),d))
call('mkfs.ext4 %s/%s.img 2>/dev/null' % (self.tmpdir,d))
os.mkdir(d)
call('mount -o loop %s/%s.img %s/%s' % (self.tmpdir,d, self.tmpdir,d))
call('rm -rf %s/%s/*' % (self.tmpdir,d))
write_to_file('%s/%s_file' % (d, d), d)
write_to_file('%s/common_file' % d, d)

write_to_file('ro1/ro_common_file', 'ro1')
write_to_file('ro2/ro_common_file', 'ro2')
write_to_file('rw1/rw_common_file', 'rw1')
write_to_file('rw2/rw_common_file', 'rw2')

os.mkdir('union')

def tearDown(self):
# In User Mode Linux, fusermount -u union fails with a permission error
# when trying to lock the fuse lock file.

if os.environ.get('RUNNING_ON_TRAVIS_CI'):
# TODO: investigate the following
# the sleep seems to be needed for some users or else the umount fails
# anyway, everything works fine on my system, so why wait? ;-)
# if it fails for someone, let's find the race and fix it!
# actually had to re-enable it because travis-ci is one of the bad cases
time.sleep(1)

call('umount union')
else:
call('fusermount -u union')

os.chdir(self.original_cwd)
for d in self._dirs:
call("umount %s/%s" % (self.tmpdir,d))

shutil.rmtree(self.tmpdir)


class UnionFS_RO_RO_TestCase(Common, unittest.TestCase):
def setUp(self):
Expand Down Expand Up @@ -274,6 +324,44 @@ def test_posix_operations(self):
op(union)
self.assertNotEqual(get_dir_contents(union), get_dir_contents(cow_path))

class UnionFS_LBWRITE_TestCase(CommonWithImg, unittest.TestCase):
def setUp(self):
super().setUp()
call('%s -o lbwrite rw1=rw:rw2=rw union' % self.unionfs_path)

def test_listing(self):
lst = ['rw1_file', 'rw2_file', 'rw_common_file', 'common_file']
self.assertEqual(set(lst), set(os.listdir('union')))

def test_delete(self):
os.remove('union/rw1_file')

self.assertNotIn('rw1_file', os.listdir('union'))
self.assertNotIn('rw1_file', os.listdir('rw1'))

def test_write(self):
write_to_file('union/rw1_file', 'something')

self.assertEqual(read_from_file('union/rw1_file'), 'something')
self.assertEqual(read_from_file('rw1/rw1_file'), 'something')

def test_write_new(self):
write_to_file('union/new_file1', 'something1')
write_to_file('union/new_file2', 'something2')
self.assertEqual(read_from_file('union/new_file1'), 'something1')
self.assertEqual(read_from_file('union/new_file2'), 'something2')
self.assertEqual(read_from_file('rw1/new_file1'), 'something1')
self.assertEqual(read_from_file('rw2/new_file2'), 'something2')
self.assertNotIn('new_file1', os.listdir('rw2'))
self.assertNotIn('new_file2', os.listdir('rw1'))

def test_rename(self):
os.rename('union/rw1_file', 'union/rw1_file_renamed')
self.assertEqual(read_from_file('union/rw1_file_renamed'), 'rw1')

def test_copystat(self):
shutil.copystat('union/rw1_file', 'union/rw2_file')


class UnionFS_RO_RW_TestCase(Common, unittest.TestCase):
def setUp(self):
Expand Down