mirror of
https://github.com/deepseek-ai/3FS
synced 2025-06-26 18:16:45 +00:00
Initial commit
This commit is contained in:
83
tests/fuse/concurrent_rmrf.py
Normal file
83
tests/fuse/concurrent_rmrf.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import os
|
||||
import argparse
|
||||
import uuid
|
||||
import time
|
||||
from hf3fs_utils.fs import FileSystem
|
||||
from functools import partial
|
||||
from multiprocessing import Pool
|
||||
from datetime import datetime
|
||||
|
||||
mnt = None
|
||||
fs = None
|
||||
repeat = 20
|
||||
|
||||
def task(fn):
|
||||
for _ in range(repeat):
|
||||
fn()
|
||||
|
||||
def rmrf_ln(dir):
|
||||
path = os.path.join(mnt, dir, str(uuid.uuid4()))
|
||||
os.makedirs(path)
|
||||
os.symlink(os.path.realpath(path), os.path.realpath(os.path.join(mnt, '3fs-virt', 'rm-rf', str(uuid.uuid4()))))
|
||||
|
||||
def rmrf_ioctl(dir):
|
||||
path = os.path.join(mnt, dir, str(uuid.uuid4()))
|
||||
os.makedirs(path)
|
||||
fs.remove(path, True)
|
||||
|
||||
def mv_ioctl(src, dst):
|
||||
src = os.path.join(mnt, src, str(uuid.uuid4()))
|
||||
dst = os.path.join(mnt, dst, str(uuid.uuid4()))
|
||||
os.makedirs(src)
|
||||
fs.rename(src, dst)
|
||||
|
||||
def parallel(tasks):
|
||||
with Pool(processes=len(tasks)) as pool:
|
||||
pool.map(task, tasks)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Process some paths.")
|
||||
parser.add_argument('--mnt_path', type=str, required=True, help='The mount path to process')
|
||||
args = parser.parse_args()
|
||||
|
||||
global mnt, fs, repeat
|
||||
mnt = args.mnt_path
|
||||
fs = FileSystem(mnt)
|
||||
|
||||
os.makedirs(os.path.join(mnt, "test", "src"), exist_ok=True)
|
||||
os.makedirs(os.path.join(mnt, "test", "dst"), exist_ok=True)
|
||||
|
||||
for c in [32, 64, 128]:
|
||||
print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), f"3fs-virt/rm-rf {c}")
|
||||
parallel([partial(rmrf_ln, "test")] * c)
|
||||
|
||||
print("=============")
|
||||
|
||||
for c in [32, 64, 128]:
|
||||
print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), f"ioctl rmrf {c}")
|
||||
parallel([partial(rmrf_ioctl, "test")] * c)
|
||||
|
||||
print("=============")
|
||||
for c in [32, 64, 128]:
|
||||
print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), f"ioctl mv {c}")
|
||||
parallel([partial(mv_ioctl, "test/src", "test/dst")] * c)
|
||||
|
||||
print("=============")
|
||||
for c in [32, 64, 128]:
|
||||
print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), f"ioctl mv2 {c}")
|
||||
parallel([partial(mv_ioctl, "test/src", "test/dst")] * c + [partial(mv_ioctl, "test/dst", "test/src")] * c)
|
||||
|
||||
print("=============")
|
||||
for c in [32, 64, 128]:
|
||||
print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), f"all {c}")
|
||||
parallel(
|
||||
(
|
||||
[partial(mv_ioctl, "test/src", "test/dst")] * c +
|
||||
[partial(mv_ioctl, "test/dst", "test/src")] * c +
|
||||
[partial(rmrf_ioctl, "test")] * c +
|
||||
[partial(rmrf_ioctl, "test")] * c
|
||||
)
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
79
tests/fuse/concurrent_rw.py
Normal file
79
tests/fuse/concurrent_rw.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import argparse
|
||||
|
||||
args = None
|
||||
|
||||
def gen_fixed_buffer(length, fill_char):
|
||||
wbuf = bytearray([fill_char % 256] * length)
|
||||
assert len(wbuf) == length
|
||||
return wbuf
|
||||
|
||||
def write_one_file():
|
||||
wpath = os.path.join(args.path1, 'data')
|
||||
rpath = os.path.join(args.path2, 'data')
|
||||
wfd = os.open(wpath, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o644)
|
||||
rfd = os.open(rpath, os.O_RDONLY)
|
||||
|
||||
length = 0
|
||||
for i in range(10):
|
||||
print(".", end="", flush=True)
|
||||
# write
|
||||
wbuf = gen_fixed_buffer(random.randint(0, 8 << 10), i)
|
||||
wsize = os.write(wfd, wbuf)
|
||||
assert wsize == len(wbuf), f'{wsize} != {len(wbuf)}'
|
||||
length += wsize
|
||||
|
||||
# note: should large than fuse cache timeout & periodic sync
|
||||
for j in range(12):
|
||||
time.sleep(5)
|
||||
st = os.stat(rpath)
|
||||
if st.st_size == length:
|
||||
break
|
||||
|
||||
# stat
|
||||
st = os.stat(rpath)
|
||||
assert st.st_size == length, f'{st.st_size} != {length}'
|
||||
# read
|
||||
rbuf = os.read(rfd, len(wbuf))
|
||||
assert rbuf == wbuf, f'{rbuf} != {wbuf}'
|
||||
print('\nwrite_one_file passed')
|
||||
|
||||
def write_many_files():
|
||||
fds = []
|
||||
for i in range(100):
|
||||
path = os.path.join(args.path1, f'{i}')
|
||||
wbuf = gen_fixed_buffer(random.randint(0, 8 << 10), i)
|
||||
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o644)
|
||||
wsize = os.write(fd, wbuf)
|
||||
assert wsize == len(wbuf), f'{wsize} != {len(wbuf)}'
|
||||
fds.append((fd, wbuf))
|
||||
|
||||
# note: period sync limit
|
||||
print('waiting...')
|
||||
time.sleep(15)
|
||||
|
||||
for i in range(100):
|
||||
path = os.path.join(args.path2, f'{i}')
|
||||
wbuf = fds[i][1]
|
||||
|
||||
st = os.stat(path)
|
||||
assert st.st_size == len(wbuf), f'{st.st_size} != {len(wbuf)}'
|
||||
|
||||
fd = os.open(path, os.O_RDONLY)
|
||||
rbuf = os.read(fd, st.st_size)
|
||||
assert rbuf == wbuf, f'{rbuf} != {wbuf}'
|
||||
|
||||
for fd, wbuf in fds:
|
||||
os.close(fd)
|
||||
print('write_many_files passed')
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("path1", type=str, help="")
|
||||
parser.add_argument("path2", type=str, help="")
|
||||
args = parser.parse_args()
|
||||
|
||||
write_one_file()
|
||||
write_many_files()
|
||||
15
tests/fuse/config/admin_cli.toml
Normal file
15
tests/fuse/config/admin_cli.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
cluster_id = '${FS_CLUSTER}'
|
||||
|
||||
[ib_devices]
|
||||
default_pkey_index = ${PKEY_INDEX}
|
||||
|
||||
[fdb]
|
||||
clusterFile = '${FDB_UNITTEST_CLUSTER}'
|
||||
|
||||
[mgmtd_client]
|
||||
mgmtd_server_addresses = [ 'RDMA://${ADDRESS}:${MGMTD_PORT}' ]
|
||||
|
||||
[user_info]
|
||||
uid = 0
|
||||
gid = 0
|
||||
token = '${TOKEN}'
|
||||
31
tests/fuse/config/hf3fs_fuse_main.toml
Normal file
31
tests/fuse/config/hf3fs_fuse_main.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
attr_timeout = 2
|
||||
entry_timeout = 2
|
||||
fsync_length_hint = true
|
||||
enable_read_cache = {{ env.READCACHE }}
|
||||
chunk_size_limit = '64KB'
|
||||
|
||||
[[common.log.categories]]
|
||||
categories = [ '.' ]
|
||||
handlers = [ 'normal' ]
|
||||
|
||||
[[common.log.handlers]]
|
||||
async = true
|
||||
name = 'normal'
|
||||
start_level = 'NONE'
|
||||
writer_type = 'FILE'
|
||||
file_path = '{{ env.LOG_FILE }}'
|
||||
|
||||
[mgmtd]
|
||||
mgmtd_server_addresses = [ 'RDMA://${ADDRESS}:${MGMTD_PORT}' ]
|
||||
|
||||
[meta]
|
||||
dynamic_stripe = {{ env.DYNAMIC_STRIPE }}
|
||||
|
||||
[periodic_sync]
|
||||
enable = true
|
||||
interval = '500ms'
|
||||
limit = 16
|
||||
|
||||
[io_bufs]
|
||||
write_buf_size = '{{ "1MB" if env.ENABLE_WRITE_BUFFER else "0MB" }}'
|
||||
max_buf_size = '1MB'
|
||||
9
tests/fuse/config/hf3fs_fuse_main_launcher.toml
Normal file
9
tests/fuse/config/hf3fs_fuse_main_launcher.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
allow_other = true # echo user_allow_other | sudo tee -a /etc/fuse.conf
|
||||
token_file = '${TOKEN_FILE}'
|
||||
cluster_id = '${FS_CLUSTER}'
|
||||
|
||||
[ib_devices]
|
||||
default_pkey_index = ${PKEY_INDEX}
|
||||
|
||||
[mgmtd_client]
|
||||
mgmtd_server_addresses = [ 'RDMA://${ADDRESS}:${MGMTD_PORT}' ]
|
||||
19
tests/fuse/config/meta_bench.toml
Normal file
19
tests/fuse/config/meta_bench.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
shuffle = true
|
||||
subdirs = 4
|
||||
files_per_dir = 10000
|
||||
threads = 4
|
||||
coroutines = 8
|
||||
cluster_id = '${FS_CLUSTER}'
|
||||
|
||||
[meta_client]
|
||||
network_type = 'RDMA'
|
||||
selection_mode = 'UniformRandom'
|
||||
|
||||
[mgmtd_client]
|
||||
mgmtd_server_addresses = [ 'RDMA://${ADDRESS}:${MGMTD_PORT}' ]
|
||||
|
||||
[net_client]
|
||||
default_timeout = '1s'
|
||||
|
||||
[[monitor.reporters]]
|
||||
type = 'log'
|
||||
44
tests/fuse/config/meta_main.toml
Normal file
44
tests/fuse/config/meta_main.toml
Normal file
@@ -0,0 +1,44 @@
|
||||
[[common.log.categories]]
|
||||
categories = [ '.' ]
|
||||
handlers = [ 'normal' ]
|
||||
|
||||
[[common.log.handlers]]
|
||||
async = true
|
||||
name = 'normal'
|
||||
start_level = 'NONE'
|
||||
writer_type = 'FILE'
|
||||
file_path = '{{ env.LOG_FILE }}'
|
||||
|
||||
[[server.base.groups]]
|
||||
[server.base.groups.listener]
|
||||
listen_port = {{ env.PORT }}
|
||||
|
||||
[[server.base.groups]]
|
||||
[server.base.groups.listener]
|
||||
listen_port = 0
|
||||
|
||||
[server.mgmtd_client]
|
||||
mgmtd_server_addresses = [ 'RDMA://${ADDRESS}:${MGMTD_PORT}' ]
|
||||
auto_extend_client_session_interval = '2s'
|
||||
auto_heartbeat_interval = '2s'
|
||||
auto_refresh_interval = '2s'
|
||||
|
||||
[server.meta]
|
||||
allow_owner_change_immutable = true
|
||||
time_granularity = '1ms'
|
||||
dynamic_stripe = true
|
||||
dynamic_stripe_initial = 1
|
||||
dynamic_stripe_growth = 2
|
||||
iflags_chain_allocation = true
|
||||
idempotent_record_clean = '1min'
|
||||
idempotent_record_expire = '5min'
|
||||
idempotent_remove = true
|
||||
idempotent_rename = true
|
||||
|
||||
[server.meta.gc]
|
||||
gc_file_delay = '5s'
|
||||
|
||||
[server.fdb]
|
||||
casual_read_risky = true
|
||||
clusterFile = '${FDB_UNITTEST_CLUSTER}'
|
||||
externalClientPath = '/lib/libfdb_c.so'
|
||||
7
tests/fuse/config/meta_main_launcher.toml
Normal file
7
tests/fuse/config/meta_main_launcher.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
cluster_id = '${FS_CLUSTER}'
|
||||
|
||||
[ib_devices]
|
||||
default_pkey_index = ${PKEY_INDEX}
|
||||
|
||||
[mgmtd_client]
|
||||
mgmtd_server_addresses = [ 'RDMA://${ADDRESS}:${MGMTD_PORT}' ]
|
||||
21
tests/fuse/config/mgmtd_main.toml
Normal file
21
tests/fuse/config/mgmtd_main.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[[common.log.categories]]
|
||||
categories = [ '.' ]
|
||||
handlers = [ 'normal' ]
|
||||
|
||||
[[common.log.handlers]]
|
||||
async = true
|
||||
name = 'normal'
|
||||
start_level = 'NONE'
|
||||
writer_type = 'FILE'
|
||||
file_path = '{{ env.LOG_FILE }}'
|
||||
|
||||
[[server.base.groups]]
|
||||
[server.base.groups.listener]
|
||||
listen_port = {{ env.PORT }}
|
||||
|
||||
[[server.base.groups]]
|
||||
[server.base.groups.listener]
|
||||
listen_port = 0
|
||||
|
||||
[server.service]
|
||||
allow_heartbeat_from_unregistered = true
|
||||
7
tests/fuse/config/mgmtd_main_launcher.toml
Normal file
7
tests/fuse/config/mgmtd_main_launcher.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
cluster_id = '${FS_CLUSTER}'
|
||||
|
||||
[ib_devices]
|
||||
default_pkey_index = ${PKEY_INDEX}
|
||||
|
||||
[kv_engine.fdb]
|
||||
clusterFile = '${FDB_UNITTEST_CLUSTER}'
|
||||
4
tests/fuse/config/pjdfstest.toml
Normal file
4
tests/fuse/config/pjdfstest.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[settings]
|
||||
# naptime is the duration of various short sleeps. It should be greater than
|
||||
# the timestamp granularity of the file system under test.
|
||||
naptime = 0.0001
|
||||
33
tests/fuse/config/storage_main.toml
Normal file
33
tests/fuse/config/storage_main.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[[common.log.categories]]
|
||||
categories = [ '.' ]
|
||||
handlers = [ 'normal' ]
|
||||
|
||||
[[common.log.handlers]]
|
||||
async = true
|
||||
name = 'normal'
|
||||
start_level = 'NONE'
|
||||
writer_type = 'FILE'
|
||||
file_path = '{{ env.LOG_FILE }}'
|
||||
|
||||
[[server.base.groups]]
|
||||
[server.base.groups.listener]
|
||||
listen_port = {{ env.PORT }}
|
||||
|
||||
[[server.base.groups]]
|
||||
[server.base.groups.listener]
|
||||
listen_port = 0
|
||||
|
||||
[server.allocate_worker]
|
||||
min_remain_groups = 0
|
||||
max_remain_groups = 1
|
||||
max_reserved_chunks = '1GB'
|
||||
|
||||
[server.mgmtd]
|
||||
mgmtd_server_addresses = [ 'RDMA://${ADDRESS}:${MGMTD_PORT}' ]
|
||||
|
||||
[server.targets]
|
||||
target_paths = [ ${TARGETS} ]
|
||||
allow_disk_without_uuid = true
|
||||
|
||||
[server.buffer_pool]
|
||||
rdmabuf_count = 256
|
||||
7
tests/fuse/config/storage_main_launcher.toml
Normal file
7
tests/fuse/config/storage_main_launcher.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
cluster_id = '${FS_CLUSTER}'
|
||||
|
||||
[ib_devices]
|
||||
default_pkey_index = ${PKEY_INDEX}
|
||||
|
||||
[mgmtd_client]
|
||||
mgmtd_server_addresses = [ 'RDMA://${ADDRESS}:${MGMTD_PORT}' ]
|
||||
586
tests/fuse/fuse_test_ci.py
Normal file
586
tests/fuse/fuse_test_ci.py
Normal file
@@ -0,0 +1,586 @@
|
||||
import struct
|
||||
import os
|
||||
import mmap
|
||||
import array
|
||||
import time
|
||||
import threading
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
np = None
|
||||
|
||||
|
||||
file_modes = ['r', 'rb', 'r+', 'rb+', 'w', 'wb', 'w+', 'a', 'ab', 'a+', 'ab+']
|
||||
path = ""
|
||||
text_path = ""
|
||||
binary_path = ""
|
||||
|
||||
def check_read(fd, path, val, desc):
|
||||
if fd == None:
|
||||
fd = open(path, 'r')
|
||||
v = fd.read(len(val))
|
||||
if v != val:
|
||||
raise RuntimeError(v, val, 'Check Error: ', desc)
|
||||
fd.close()
|
||||
|
||||
def write_file(fd, val, cls = False):
|
||||
fd.write(val)
|
||||
fd.flush()
|
||||
if cls:
|
||||
fd.close()
|
||||
|
||||
def print_file_data(fd, path, num):
|
||||
if fd == None:
|
||||
fd = open(path, 'r')
|
||||
fd.read(num)
|
||||
print('print_file_data', num, fd.read(num))
|
||||
fd.close()
|
||||
|
||||
def open_test():
|
||||
fd = open(text_path+'1', 'w+')
|
||||
write_file(fd, 'abcdefghigk')
|
||||
check_read(None, text_path+'1', 'abc', 'open w+ mode')
|
||||
|
||||
fd2 = open(text_path+'2', 'w+')
|
||||
fd2.close()
|
||||
|
||||
fd = open(text_path+'1', 'r')
|
||||
check_read(fd, '', 'abc', 'open r mode')
|
||||
fd = open(text_path+'1', 'rb')
|
||||
check_read(fd, '', b'abc', 'open rb mode')
|
||||
fd = open(text_path+'1', 'r+')
|
||||
check_read(fd, '', 'abc', 'open r+ mode')
|
||||
fd = open(text_path+'1', 'rb+')
|
||||
check_read(fd, '', b'abc', 'open rb+ mode')
|
||||
|
||||
#正常写文本文件
|
||||
fd = open(text_path+'2_w', 'w')
|
||||
write_file(fd, 'cba')
|
||||
check_read(None, text_path+'2_w', 'cba', 'open w mode')
|
||||
#将文本写入二进制文件
|
||||
fd = open(binary_path+'2_w', 'wb')
|
||||
#print_file_data(None, binary_path+'2', 6)
|
||||
write_file(fd, b'efg')
|
||||
check_read(None, binary_path+'2_w', 'efg', 'open wb mode')
|
||||
#读写打开,用同一个fd读写,校验
|
||||
fd = open(text_path+'2_w', 'w+')
|
||||
write_file(fd, 'etc', True)
|
||||
check_read(None, text_path+'2_w', 'etc', 'open w+ mode')
|
||||
|
||||
#追加文本文件
|
||||
fd = open(text_path+'2_w', 'a')
|
||||
write_file(fd, 'zzz', True)
|
||||
check_read(None, text_path+'2_w', 'etczzz', 'open a mode')
|
||||
#追加二进制文件
|
||||
fd = open(binary_path+'2_w', 'ab')
|
||||
write_file(fd, b'zzz', True)
|
||||
check_read(None, binary_path+'2_w', 'efgzzz', 'open ab mode')
|
||||
#读写文本文件,追加后再读取
|
||||
fd = open(text_path+'2_w', 'a+')
|
||||
write_file(fd, 'yyy', True)
|
||||
check_read(None, text_path+'2_w', 'etczzzyyy', 'open a+ mode')
|
||||
#读写二进制文件,追加后再读取
|
||||
# fd = open(binary_path+'389', 'ab+')
|
||||
# write_file(fd, b'xxx', True)
|
||||
# check_read(None, binary_path+'389', 'xxx', 'open ab+ mode')
|
||||
# fd.close()
|
||||
#测试非法模式
|
||||
is_err = True
|
||||
try:
|
||||
fd = open(binary_path+'389', 'aa')
|
||||
except ValueError as err:
|
||||
is_err = False
|
||||
print('open_test 1: {0}'.format(err))
|
||||
if is_err:
|
||||
raise RuntimeError('open_test 1 Error')
|
||||
|
||||
os.unlink(text_path+'2_w')
|
||||
print('open test over')
|
||||
|
||||
def seek_test():
|
||||
fd = open(text_path+'1', 'r+')
|
||||
#fd.seek(-1)
|
||||
#获取文件长度
|
||||
fd.seek(0, 2)
|
||||
len = fd.tell()
|
||||
print(text_path+'1', ' file_len = ', len)
|
||||
#seek到文件末尾之后,是成功的
|
||||
idx = fd.seek(len+2, 0)
|
||||
fd.read(2)
|
||||
#raise RuntimeError('seek_test len+1,', idx)
|
||||
#直接seek到最后+1,异常
|
||||
is_err = True
|
||||
try:
|
||||
fd.seek(1, 2)
|
||||
except ValueError as err:
|
||||
is_err = False
|
||||
print('seek_test 2: {0}'.format(err))
|
||||
if is_err:
|
||||
raise RuntimeError('seek_test 2 Error')
|
||||
fd.close()
|
||||
|
||||
print('seek test over')
|
||||
|
||||
def flush_test():
|
||||
fd = open(text_path+'2', 'r')
|
||||
fd.flush()
|
||||
fd = open(text_path+'2_w', 'wb')
|
||||
fd.flush()
|
||||
|
||||
print('flush test over')
|
||||
|
||||
def next_test():
|
||||
fd = open(text_path+'2_w', 'w+')
|
||||
#空文件获取行
|
||||
is_err = True
|
||||
try:
|
||||
val = next(fd)
|
||||
except StopIteration as err:
|
||||
is_err = False
|
||||
print('next_test 1: {0}'.format(err))
|
||||
if is_err:
|
||||
raise RuntimeError('next_test 1 Error')
|
||||
#写入不同数据,顺序读取
|
||||
fd.write('abc\nttt\nbbb\nccc\nggg')
|
||||
fd.flush()
|
||||
fd.seek(0)
|
||||
val = next(fd)
|
||||
if val.strip() != 'abc':
|
||||
raise RuntimeError('next_test 2 Error')
|
||||
val = next(fd)
|
||||
if val.strip() != 'ttt':
|
||||
raise RuntimeError('next_test 3 Error')
|
||||
val = next(fd)
|
||||
if val.strip() != 'bbb':
|
||||
raise RuntimeError('next_test 4 Error')
|
||||
|
||||
fd.close()
|
||||
os.remove(text_path+'2_w')
|
||||
print('next test over')
|
||||
|
||||
def read_test():
|
||||
fd = open(text_path+'1', 'r+')
|
||||
#读取长度为0的数据
|
||||
val = fd.read(0)
|
||||
if len(val) != 0:
|
||||
raise RuntimeError('read_test 1,', val)
|
||||
#在文件最后位置读取数据
|
||||
fd.seek(0, 2)
|
||||
val = fd.read(1)
|
||||
|
||||
if len(val) != 0:
|
||||
raise RuntimeError('read_test 2 Error')
|
||||
|
||||
fd.close()
|
||||
|
||||
#在写权限的文件读取
|
||||
is_err = True
|
||||
fd = open(text_path+'2_w', 'w')
|
||||
try:
|
||||
fd.read(1)
|
||||
except IOError as err:
|
||||
is_err = False
|
||||
print('read_test 3: {0}'.format(err))
|
||||
|
||||
if is_err:
|
||||
raise RuntimeError('read_test 3 Error')
|
||||
print('read test over')
|
||||
|
||||
def truncate_test():
|
||||
#读权限截取
|
||||
fd = open(text_path+'2_w', 'r')
|
||||
is_err = True
|
||||
try:
|
||||
fd.truncate()
|
||||
fd.close()
|
||||
except IOError as err:
|
||||
is_err = False
|
||||
print('truncate_test 1: {0}'.format(err))
|
||||
if is_err:
|
||||
raise RuntimeError('truncate_test 1 Error')
|
||||
|
||||
#正常截取
|
||||
fd = open(text_path+'2_w', 'w+')
|
||||
fd.truncate()
|
||||
fd.write('abcd\nefg')
|
||||
#当前位置截取
|
||||
fd.seek(4, 0)
|
||||
fd.truncate()
|
||||
fd.seek(0, 0)
|
||||
val = fd.read(4)
|
||||
print('val = ', val)
|
||||
if val != 'abcd':
|
||||
raise RuntimeError('truncate_test 2 Error')
|
||||
#非法位置截取
|
||||
fd.truncate(10)
|
||||
|
||||
fd.close()
|
||||
print('truncate test over')
|
||||
|
||||
def tell_test():
|
||||
#正常获取
|
||||
fd = open(text_path+'1', 'r')
|
||||
fd.seek(0, 2)
|
||||
len = fd.tell()
|
||||
fd.seek(1, 0)
|
||||
fd.read(5)
|
||||
fd.seek(0, 1)
|
||||
idx = fd.tell()
|
||||
if idx != 6:
|
||||
raise RuntimeError('tell_test 1 Error')
|
||||
#超出文件末尾获取
|
||||
fd.seek(len+1, 0)
|
||||
idx = fd.tell()
|
||||
if idx != len+1:
|
||||
raise RuntimeError('tell_test 2 Error')
|
||||
|
||||
print('tell test over')
|
||||
|
||||
def readlines_test():
|
||||
fd = open(text_path+'2_w', 'w+')
|
||||
#空文件获取行
|
||||
val = fd.readline(-1)
|
||||
fd.write('abc\nttt\nbbb\nccc\nggg')
|
||||
fd.flush()
|
||||
fd.seek(0, 2)
|
||||
f_len = fd.tell()
|
||||
fd.seek(0)
|
||||
val = fd.readline(-1)
|
||||
if val.strip() != 'abc':
|
||||
raise RuntimeError('readlines_test 1 Error')
|
||||
val = fd.readline(0)
|
||||
if len(val) != 0:
|
||||
raise RuntimeError('readlines_test 2 Error')
|
||||
|
||||
val = fd.readline(2)
|
||||
if val.strip() != 'tt':
|
||||
raise RuntimeError('readlines_test 3 Error')
|
||||
|
||||
val = fd.readline(f_len + 5)
|
||||
if val.strip() != 't':
|
||||
raise RuntimeError('readlines_test 4 Error')
|
||||
|
||||
print('readlines test over')
|
||||
|
||||
def mmap_test():
|
||||
fd = os.open(text_path+'1', os.O_RDWR | os.O_NONBLOCK)
|
||||
mm = mmap.mmap(fd, 0)
|
||||
if mm[:6] != b'abcdef':
|
||||
raise RuntimeError('mmap_test 1 Error')
|
||||
os.close(fd)
|
||||
print('mmap test over')
|
||||
|
||||
def os_open_test():
|
||||
fd = os.open(text_path+'2_rw', os.O_RDWR | os.O_CREAT | os.O_TRUNC)
|
||||
b1 = bytearray(b'123')
|
||||
b2 = bytearray(b'abc')
|
||||
b3 = bytearray(b'456')
|
||||
bufs = []
|
||||
bufs.append(b1)
|
||||
bufs.append(b2)
|
||||
bufs.append(b3)
|
||||
os.writev(fd, bufs)
|
||||
os.lseek(fd, 0, 0)
|
||||
val = os.read(fd, 5)
|
||||
if val != b'123ab':
|
||||
raise RuntimeError('os_open_test 1 Error')
|
||||
os.close(fd)
|
||||
|
||||
#open文件,但bufsize超出长度
|
||||
fd = os.open(text_path+'2_rw', os.O_RDONLY, 0)
|
||||
val = os.read(fd, 20)
|
||||
if val != b'123abc456':
|
||||
raise RuntimeError('os_open_test 2 Error')
|
||||
os.close(fd)
|
||||
|
||||
print('os_open test over')
|
||||
|
||||
def os_mkdir_test():
|
||||
#创建文件夹
|
||||
try:
|
||||
os.mkdir(text_path+'test_file')
|
||||
fd = open(text_path+'test_file/1_t', 'w')
|
||||
fd.close()
|
||||
#在文件夹里有文件的情况下删除
|
||||
os.rmdir(text_path+'test_file')
|
||||
except OSError as err:
|
||||
print('os_mkdir_test 1: {0} 预期的结果'.format(err))
|
||||
|
||||
os.unlink(text_path+'test_file/1_t')
|
||||
os.rmdir(text_path+'test_file')
|
||||
if os.path.exists(text_path+'test_file2'):
|
||||
os.rmdir(text_path+'test_file2')
|
||||
|
||||
print('os_mkdir test over')
|
||||
|
||||
def os_link_test():
|
||||
try:
|
||||
os.mkdir(text_path+'test_file')
|
||||
os.mkdir(text_path+'test_file2')
|
||||
fd = open(text_path+'test_file/1_t', 'w')
|
||||
os.link(text_path+'test_file/1_t', text_path+'test_file2/1_t2')
|
||||
except PermissionError as err:
|
||||
print('os_link_test 1: {0}'.format(err))
|
||||
|
||||
print('os_link test over')
|
||||
|
||||
def os_unlink_test():
|
||||
os.unlink(text_path+'test_file/1_t')
|
||||
os.unlink(text_path+'test_file2/1_t2')
|
||||
os.rmdir(text_path+'test_file')
|
||||
os.rmdir(text_path+'test_file2')
|
||||
|
||||
print('os_unlink test over')
|
||||
|
||||
letter_ary = []
|
||||
binary_ary = []
|
||||
for ch in range(97, 123):
|
||||
letter_ary.append(chr(ch))
|
||||
binary_ary.append(ch)
|
||||
|
||||
def thread_check_text(start, offset, ary, text_data):
|
||||
for i in range(offset):
|
||||
t = (start + i) % 26
|
||||
#print(t, text_data, text_data[i])
|
||||
if ary[t] != text_data[i]:
|
||||
raise RuntimeError('text_data check error: {0}, {1}, {2} right: {3}, real:{4}'.format( start, offset, i, ary[t], text_data[i]))
|
||||
|
||||
def thread_check_binary(start, offset, binary_data):
|
||||
for i in range(offset):
|
||||
t = (start + i + 1) % 126
|
||||
if t == 0:
|
||||
t = 126
|
||||
if t != binary_data[i]:
|
||||
raise RuntimeError('binary_data check error: start {0} offset {1} index {2} expected: {3} actual: {4}'.format( start, offset, i, t, binary_data[i]))
|
||||
|
||||
def thread_check_text_np(start, offset, ary, text_data):
|
||||
expected = np.arange(start, start + offset, dtype=np.int32)
|
||||
expected %= 26
|
||||
expected += ary[0]
|
||||
# actual = np.array(text_data, dtype=np.int32)
|
||||
actual = np.frombuffer(text_data, dtype=np.uint8)
|
||||
if not np.array_equal(expected, actual):
|
||||
i = np.argmin(expected == actual)
|
||||
raise RuntimeError('text_data check error: start {0} offset {1} index {2} expected: {3} actual: {4}'.format( start, offset, i, expected[i:i+16], actual[i:i+16]))
|
||||
|
||||
def thread_check_binary_np(start, offset, binary_data):
|
||||
expected = np.arange(start + 1, start + offset + 1, dtype=np.int32)
|
||||
expected %= 126
|
||||
expected[expected == 0] = 126
|
||||
# actual = np.array(binary_data, dtype=np.int32)
|
||||
actual = np.frombuffer(binary_data, dtype=np.uint8)
|
||||
if not np.array_equal(expected, actual):
|
||||
i = np.argmin(expected == actual)
|
||||
raise RuntimeError('binary_data check error: start {0} offset {1} index {2} expected: {3} actual: {4}'.format( start, offset, i, expected[i:i+16], actual[i:i+16]))
|
||||
|
||||
#文本校验
|
||||
def check_text_data(start, offset, text_data):
|
||||
if offset != len(text_data):
|
||||
raise RuntimeError('check_text_data parameter error: offset != len(text_data), {0} {1} {2}'.format(offset, len(text_data), start))
|
||||
|
||||
return
|
||||
|
||||
if type(text_data).__name__ == 'bytes':
|
||||
ary = binary_ary
|
||||
else:
|
||||
ary = letter_ary
|
||||
|
||||
if len(text_data) <= 1024:
|
||||
thread_check_text(start, offset, ary, text_data)
|
||||
else:
|
||||
thread_check_text_np(start, offset, ary, text_data)
|
||||
|
||||
# p = threading.Thread(target=thread_check_text_np, args=(start, offset, ary, text_data,))
|
||||
# p.start()
|
||||
|
||||
# for i in range(offset):
|
||||
# t = (start + i) % 26
|
||||
# #print(t, text_data, text_data[i])
|
||||
# if ary[t] != text_data[i]:
|
||||
# raise RuntimeError('text_data check error: {0}, {1}, {2} right: {3}, real:{4}'.format( start, offset, i, ary[t], text_data[i]))
|
||||
|
||||
#二进制校验
|
||||
def check_binary_data(start, offset, binary_data):
|
||||
if offset != len(binary_data):
|
||||
raise RuntimeError('check_binary_data parameter error: offset != len(binary_data), {0} {1}'.format(offset, len(binary_data)))
|
||||
|
||||
if len(binary_data) <= 1024:
|
||||
thread_check_binary(start, offset, binary_data)
|
||||
else:
|
||||
thread_check_binary_np(start, offset, binary_data)
|
||||
|
||||
# p = threading.Thread(target=thread_check_binary_np, args=(start, offset, binary_data,))
|
||||
# p.start()
|
||||
|
||||
# for i in range(offset):
|
||||
# t = (start + i + 1) % 126
|
||||
# if t == 0:
|
||||
# t = 126
|
||||
# if t != binary_data[i]:
|
||||
# raise RuntimeError('binary_data check error: {0}, {1}, {2} right: {3}, real:{4}'.format( start, offset, i, t, binary_data[i]))
|
||||
|
||||
def check_readv_results(file_path, read_offset, data_bufs, data_len):
|
||||
index = read_offset
|
||||
|
||||
readbufsize = sum(len(buf) for buf in data_bufs)
|
||||
filesize = os.path.getsize(file_path)
|
||||
if read_offset > filesize :
|
||||
assert data_len == 0, "read start exceeding file end, return size should be 0. now it's %d " % (data_len)
|
||||
return
|
||||
|
||||
expected_return_size = readbufsize if (read_offset + readbufsize <= filesize) else (filesize - read_offset)
|
||||
assert data_len == expected_return_size, "expected size %d, result return size %d" % (expected_return_size, data_len)
|
||||
|
||||
#print('data_bufs size = ', len(data_bufs[0]))
|
||||
#print(data_bufs)
|
||||
for buf in data_bufs:
|
||||
l = len(buf)
|
||||
if l <= data_len:
|
||||
val = bytes(buf)
|
||||
else:
|
||||
val = bytes(buf[:data_len])
|
||||
l = data_len
|
||||
#print('val = ', val, data_len)
|
||||
data_len -= l
|
||||
#print(index, l, val[:128])
|
||||
if file_path.find('binary') != -1:
|
||||
check_binary_data(index, l, val)
|
||||
else:
|
||||
check_text_data(index, l, val)
|
||||
index += l
|
||||
if data_len <= 0:
|
||||
break
|
||||
|
||||
def os_read_test_base(file_path, index, size, file_len = -1):
|
||||
fd = os.open(file_path, os.O_RDONLY )
|
||||
os.lseek(fd, index, 0)
|
||||
val = os.read(fd, size)
|
||||
os.close(fd)
|
||||
|
||||
readbufsize = size
|
||||
filesize = os.path.getsize(file_path)
|
||||
read_offset = index
|
||||
data_len = len(val)
|
||||
|
||||
if read_offset > filesize :
|
||||
assert data_len == 0, "read start exceeding file end, return size should be 0. now it's %d " % (data_len)
|
||||
return
|
||||
|
||||
expected_return_size = readbufsize if (read_offset + readbufsize <= filesize) else (filesize - read_offset)
|
||||
assert data_len == expected_return_size, "expected size %d, result return size %d" % (expected_return_size, data_len)
|
||||
|
||||
#print(index, l, val[:128])
|
||||
if file_path.find('binary') != -1:
|
||||
check_binary_data(index, data_len, val)
|
||||
else:
|
||||
check_text_data(index, data_len, val)
|
||||
|
||||
def os_readv_test_base(file_path, index, buffers_size, file_len = -1):
|
||||
buffers = []
|
||||
size = 0
|
||||
for s in buffers_size:
|
||||
buf = bytearray(s)
|
||||
buffers.append(buf)
|
||||
size += s
|
||||
print('bufs = ', buffers_size)
|
||||
fd = os.open(file_path, os.O_RDONLY ) #| os.O_DIRECT
|
||||
os.lseek(fd, index, 0)
|
||||
num_bytes = os.readv(fd, buffers)
|
||||
os.close(fd)
|
||||
|
||||
check_readv_results(file_path, index, buffers, num_bytes)
|
||||
|
||||
def test_continuous_readv(fd, index, buffers_size):
|
||||
buffers = []
|
||||
size = 0
|
||||
for s in buffers_size:
|
||||
buf = bytearray(s)
|
||||
buffers.append(buf)
|
||||
size += s
|
||||
print('bufs = ', buffers_size)
|
||||
os.lseek(fd, index, 0)
|
||||
num_bytes = os.readv(fd, buffers)
|
||||
#print('buffers size = ', len(buffers), num_bytes, size, buffers)
|
||||
if num_bytes == size:
|
||||
for buf in buffers:
|
||||
l = len(buf)
|
||||
val = bytes(buf)
|
||||
check_text_data(index, l, val)
|
||||
index += l
|
||||
else:
|
||||
raise RuntimeError('os_readv_test_base error: num_bytes != size {0}, {1}'.format( num_bytes, size))
|
||||
|
||||
def posix_io_text():
|
||||
print("=== [start test suite] posix_io_test basic funtions ===")
|
||||
open_test()
|
||||
seek_test()
|
||||
flush_test()
|
||||
next_test()
|
||||
read_test()
|
||||
truncate_test()
|
||||
tell_test()
|
||||
readlines_test()
|
||||
mmap_test()
|
||||
os_open_test()
|
||||
os_mkdir_test()
|
||||
os_link_test()
|
||||
os_unlink_test()
|
||||
print("=== [finish test suite] posix_io_test basic functions ===")
|
||||
|
||||
def os_write_run(path, open_mode, max_file_size, write_data):
|
||||
fd = os.open(path, open_mode)
|
||||
idx = 0
|
||||
print("write started", path, max_file_size)
|
||||
while idx < max_file_size:
|
||||
os.write(fd, write_data)
|
||||
idx += len(write_data)
|
||||
os.close(fd)
|
||||
print("write done", path, max_file_size)
|
||||
|
||||
def os_read_run(path, open_mode, max_file_size):
|
||||
fd = os.open(path, open_mode)
|
||||
idx = 0
|
||||
data = array.array('B')
|
||||
check_data = check_text_data if path.find('text') != -1 else check_binary_data
|
||||
|
||||
print("read started", path, max_file_size, flush=True)
|
||||
while idx < max_file_size - 26:
|
||||
val = os.read(fd, 26)
|
||||
data.frombytes(val)
|
||||
l = len(val)
|
||||
idx += l
|
||||
if l == 0:
|
||||
print("wait for more data...")
|
||||
time.sleep(0.5)
|
||||
|
||||
if idx % (max_file_size // 20) < 26:
|
||||
print(idx * 100 // max_file_size, 'percent done', 'idx', idx, 'max file size', max_file_size, flush=True)
|
||||
|
||||
print("read done", path, flush=True)
|
||||
os.close(fd)
|
||||
print("check data", flush=True)
|
||||
check_data(0, len(data), data)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("path", help="The path argument")
|
||||
args = parser.parse_args()
|
||||
path = args.path
|
||||
text_path = os.path.join(path, "text")
|
||||
binary_path = os.path.join(path, "binary")
|
||||
|
||||
print('Path', path)
|
||||
print('posix test')
|
||||
print('text_path = ', text_path)
|
||||
print('binary_path = ', binary_path)
|
||||
print('letter_ary = ', letter_ary)
|
||||
print('binary_ary = ', binary_ary)
|
||||
|
||||
posix_io_text()
|
||||
#
|
||||
# os_read_test()
|
||||
# os_readv_test()
|
||||
|
||||
|
||||
73
tests/fuse/lock_directory.py
Normal file
73
tests/fuse/lock_directory.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import sys
|
||||
import os
|
||||
import errno
|
||||
import uuid
|
||||
|
||||
def random_fname():
|
||||
unique_id = uuid.uuid4()
|
||||
return str(unique_id).replace("-", "")
|
||||
|
||||
def assert_errno(expected, fn, *args):
|
||||
code = 0
|
||||
try:
|
||||
fn(*args)
|
||||
except Exception as e:
|
||||
code = getattr(e, "errno", errno.EINVAL)
|
||||
assert code == expected, f"{code} != {expected}"
|
||||
|
||||
# Note:
|
||||
# setxattr: hf3fs.lock -> [try_lock, unlock, preempt_lock, clear]
|
||||
# getxattr: hf3fs.lock -> ENODATA if not locked else json string '{"client":{"uuid":"...","hostname":"..."}}'
|
||||
# removexattr('f3fs.lock') == setxattr(path, 'hf3fs.lock', b'clear')
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("Please provide exactly two mount points as arguments.")
|
||||
sys.exit(-1)
|
||||
|
||||
# two clients mount same filesystem
|
||||
mnt1 = sys.argv[1]
|
||||
mnt2 = sys.argv[2]
|
||||
|
||||
# return ENOTSUP should affect subsequent operations.
|
||||
assert_errno(errno.ENOTSUP, os.setxattr, mnt1, 'system.acl', b'soemthing')
|
||||
assert_errno(errno.ENOTSUP, os.setxattr, mnt1, 'hf3fs.invalid_key', b'soemthing')
|
||||
|
||||
os.removexattr(mnt1, 'hf3fs.lock')
|
||||
os.removexattr(mnt2, 'hf3fs.lock')
|
||||
assert_errno(errno.ENODATA, os.getxattr, mnt1, 'hf3fs.lock')
|
||||
assert_errno(errno.ENODATA, os.getxattr, mnt2, 'hf3fs.lock')
|
||||
|
||||
# directory not locked, both client1 & client2 can create
|
||||
os.open(os.path.join(mnt1, random_fname()), os.O_CREAT | os.O_RDONLY, 0o644)
|
||||
os.open(os.path.join(mnt2, random_fname()), os.O_CREAT | os.O_RDONLY, 0o644)
|
||||
|
||||
# client1 try_lock
|
||||
os.setxattr(mnt1, 'hf3fs.lock', b'try_lock')
|
||||
os.open(os.path.join(mnt1, 'file'), os.O_CREAT | os.O_RDONLY, 0o644)
|
||||
attr = os.getxattr(mnt1, 'hf3fs.lock')
|
||||
assert attr != ''
|
||||
print(f'getxattr hf3fs.lock: {attr}')
|
||||
attrs = os.listxattr(mnt1)
|
||||
assert len(attrs) != 0
|
||||
print(f'listxattr {attrs}')
|
||||
|
||||
# client2 can't try_lock or create
|
||||
assert_errno(errno.ENOLCK, os.setxattr, mnt2, 'hf3fs.lock', b'try_lock')
|
||||
assert_errno(errno.ENOLCK, os.open, os.path.join(mnt2, random_fname()), os.O_CREAT | os.O_RDONLY, 0o644)
|
||||
assert_errno(errno.ENOLCK, os.link, os.path.join(mnt2, 'file'), os.path.join(mnt2, random_fname()))
|
||||
assert_errno(errno.ENOLCK, os.remove, os.path.join(mnt2, 'file'))
|
||||
|
||||
# client2 can preempt lock
|
||||
os.setxattr(mnt2, 'hf3fs.lock', b'preempt_lock')
|
||||
os.open(os.path.join(mnt2, random_fname()), os.O_CREAT | os.O_RDONLY, 0o644)
|
||||
os.link(os.path.join(mnt2, 'file'), os.path.join(mnt2, random_fname()))
|
||||
os.remove(os.path.join(mnt2, 'file'))
|
||||
assert_errno(errno.ENOLCK, os.setxattr, mnt1, 'hf3fs.lock', b'unlock')
|
||||
|
||||
# client2 unlock, both client1 & client2 can create again
|
||||
os.setxattr(mnt2, 'hf3fs.lock', b'unlock')
|
||||
os.open(os.path.join(mnt1, random_fname()), os.O_CREAT | os.O_RDONLY, 0o644)
|
||||
os.open(os.path.join(mnt2, random_fname()), os.O_CREAT | os.O_RDONLY, 0o644)
|
||||
|
||||
print(f'test passed')
|
||||
92
tests/fuse/random_rw.py
Normal file
92
tests/fuse/random_rw.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import os
|
||||
import random
|
||||
import argparse
|
||||
import subprocess
|
||||
|
||||
def generate_random_data(length):
|
||||
data = bytearray(os.urandom(length))
|
||||
return data
|
||||
def write_random_blocks(file_path, data, close):
|
||||
blocks = []
|
||||
offset = 0
|
||||
while offset < len(data):
|
||||
length = min(random.randint(1, 4 << 20), len(data) - offset)
|
||||
blocks.append((offset, length))
|
||||
offset += length
|
||||
random.shuffle(blocks)
|
||||
|
||||
fd = os.open(file_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
|
||||
for offset, length in blocks:
|
||||
print("{:10d} {:10d}".format(offset, offset + length))
|
||||
os.pwrite(fd, data[offset:(offset + length)], offset)
|
||||
if not close:
|
||||
return fd
|
||||
os.close(fd)
|
||||
|
||||
def run_command(command):
|
||||
try:
|
||||
output = subprocess.check_output(command, shell=True, text=True)
|
||||
print(f"{command} output:")
|
||||
print(output)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error occurred while running the external program:", e)
|
||||
|
||||
def dump_buf(buf, data):
|
||||
with open('random_rw.buf', 'wb') as f:
|
||||
f.write(buf)
|
||||
with open('random_rw.data', 'wb') as f:
|
||||
f.write(data)
|
||||
run_command("cmp -l random_rw.buf random_rw.data | head")
|
||||
run_command("cmp -l random_rw.buf random_rw.data | tail")
|
||||
|
||||
def validate_random_access(file_path, data):
|
||||
total = 0
|
||||
|
||||
st = os.stat(file_path)
|
||||
assert st.st_size == len(data), f'{st.st_size} != {len(data)}'
|
||||
|
||||
fd = os.open(file_path, os.O_RDONLY)
|
||||
while total < 2 * len(data):
|
||||
offset = random.randint(0, len(data))
|
||||
length = min(random.randint(1, 4 << 20), len(data) - offset)
|
||||
buf = os.pread(fd, length, offset)
|
||||
total += length
|
||||
if buf != data[offset:(offset + length)]:
|
||||
print(f"validation failed for {file_path}, offset {offset}, length {length}, buf {len(buf)}")
|
||||
dump_buf(buf, data[offset:(offset + length)])
|
||||
assert False
|
||||
|
||||
buf = os.pread(fd, len(data), 0)
|
||||
if buf != data:
|
||||
print(f"validation failed for {file_path}, buf {len(buf)}, data {len(data)}")
|
||||
dump_buf(buf, data)
|
||||
assert False
|
||||
os.close(fd)
|
||||
|
||||
print(f"validation success for {file_path}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Use argparse to accept parameters
|
||||
parser = argparse.ArgumentParser(description='Generate specified number and size of random files and validate data correctness')
|
||||
parser.add_argument('--filesize', type=int, default=128 * 1024 * 1024, help='Maximum size of each file in bytes, default is 128MB')
|
||||
parser.add_argument('--filenum', type=int, default=10, help='Number of files to generate, default is 1000')
|
||||
parser.add_argument('--read_before_close', action='store_true', default=False, help='Enable the flag')
|
||||
parser.add_argument('path', type=str, help='Path for test run')
|
||||
args = parser.parse_args()
|
||||
|
||||
max_file_size = args.filesize
|
||||
file_num = args.filenum
|
||||
test_path = args.path
|
||||
|
||||
for i in range(file_num):
|
||||
file_size = random.randint(1, max_file_size)
|
||||
data = generate_random_data(file_size)
|
||||
|
||||
file_name = f"file_{file_size}_{i}.bin"
|
||||
file_path = os.path.join(test_path, file_name)
|
||||
|
||||
data = generate_random_data(file_size)
|
||||
fd = write_random_blocks(file_path, data, not args.read_before_close)
|
||||
validate_random_access(file_path, data)
|
||||
if args.read_before_close:
|
||||
os.close(fd)
|
||||
53
tests/fuse/read_after_write.py
Normal file
53
tests/fuse/read_after_write.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
import random
|
||||
import argparse
|
||||
import time
|
||||
|
||||
def generate_random_data(length):
|
||||
# Generate random data of length 'length'
|
||||
data = bytearray(os.urandom(length))
|
||||
return data
|
||||
|
||||
def write_random_data(file_path, data):
|
||||
# Write data to file
|
||||
with open(file_path, 'wb') as file:
|
||||
file.write(data)
|
||||
|
||||
def validate_data(file_path, data):
|
||||
st = os.stat(file_path)
|
||||
assert st.st_size == len(data), f'{file_path} {st.st_size} != {len(data)}'
|
||||
|
||||
# Open the file and validate if the data matches the original data
|
||||
with open(file_path, 'rb') as file:
|
||||
read_data = file.read()
|
||||
|
||||
assert read_data == data, f"validation failed for {file_path}"
|
||||
print(f"validation success for {file_path}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Use argparse to accept parameters
|
||||
parser = argparse.ArgumentParser(description='Generate specified number and size of random files and validate data correctness')
|
||||
parser.add_argument('--filesize', type=int, default=32 * 1024 * 1024, help='Maximum size of each file in bytes, default is 32MB')
|
||||
parser.add_argument('--filenum', type=int, default=10, help='Number of files to generate, default is 1000')
|
||||
parser.add_argument('--seconds', type=int, default=0, help='Run seconds')
|
||||
parser.add_argument('path', type=str, help='Path for test run')
|
||||
args = parser.parse_args()
|
||||
|
||||
max_file_size = args.filesize
|
||||
file_num = args.filenum
|
||||
test_path = args.path
|
||||
|
||||
start = time.time()
|
||||
|
||||
i = 0
|
||||
while time.time() - start < args.seconds or i < file_num:
|
||||
file_size = random.randint(1, max_file_size)
|
||||
data = generate_random_data(file_size)
|
||||
|
||||
file_name = f"file_{file_size}_{i}.bin"
|
||||
file_path = os.path.join(test_path, file_name)
|
||||
|
||||
write_random_data(file_path, data)
|
||||
validate_data(file_path, data)
|
||||
|
||||
i+= 1
|
||||
533
tests/fuse/run.sh
Normal file
533
tests/fuse/run.sh
Normal file
@@ -0,0 +1,533 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
function check_exists {
|
||||
if ! [ -e $1 ]; then
|
||||
echo $1 not exists
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function random_port {
|
||||
local min_port=8000
|
||||
local max_port=65535
|
||||
|
||||
while true; do
|
||||
random_port=$((min_port + RANDOM % (max_port - min_port + 1)))
|
||||
if ! lsof -i ":$random_port" >/dev/null; then
|
||||
echo "$random_port"
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function start_fdb {
|
||||
local PORT=$1
|
||||
local DATA=$2
|
||||
local LOG=$3
|
||||
local CLUSTER=${DATA}/fdb.cluster
|
||||
|
||||
echo fdbserver: $FDB
|
||||
echo fdbclient: $FDBCLI
|
||||
echo start fdb @ port ${PORT}
|
||||
echo test${FDB_PORT}:testdb${FDB_PORT}@127.0.0.1:${FDB_PORT} >${CLUSTER}
|
||||
${FDB} -p auto:${PORT} -d ${DATA} -L ${LOG} -C ${CLUSTER} &> ${LOG}/fdbserver.log &
|
||||
|
||||
echo "sleep 5s then create database"
|
||||
sleep 5 && $FDBCLI -C ${CLUSTER} --exec "configure new memory single"
|
||||
echo "sleep 5s then check foundationdb status"
|
||||
sleep 5 && $FDBCLI -C ${CLUSTER} --exec "status minimal"
|
||||
}
|
||||
|
||||
function create_app_config {
|
||||
local NODE=$1
|
||||
local FILE=$2
|
||||
|
||||
echo "allow_empty_node_id = false" >$FILE
|
||||
echo "node_id = $NODE" >>$FILE
|
||||
}
|
||||
|
||||
function wait_path {
|
||||
local file_path="$1"
|
||||
local timeout=$2
|
||||
local interval=1 # Check interval in seconds
|
||||
local elapsed_time=0
|
||||
|
||||
# Wait until the file exists or timeout is reached
|
||||
while [ ! -d "$file_path" ] && [ $elapsed_time -lt $timeout ]; do
|
||||
sleep $interval
|
||||
elapsed_time=$((elapsed_time + interval))
|
||||
done
|
||||
|
||||
if ! [ -d "$file_path" ]; then
|
||||
echo "Path '$file_path' not found within $timeout seconds."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function random_64 {
|
||||
local random_number=$(od -An -N8 -tu8 /dev/urandom | awk '{print $1}')
|
||||
echo "$random_number"
|
||||
}
|
||||
|
||||
if (($# < 2)); then
|
||||
echo "${0} <binary> <test-dir> <log-dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BINARY=${1%/}
|
||||
TEST_DIR=${2%/}
|
||||
LOG_DIR=${3:-}
|
||||
|
||||
function cleanup {
|
||||
rv=$?
|
||||
echo exit with ${rv}
|
||||
fusermount3 -u ${MOUNT1} || true
|
||||
fusermount3 -u ${MOUNT2} || true
|
||||
fusermount3 -u ${MOUNT3} || true
|
||||
fusermount3 -u ${MOUNT4} || true
|
||||
sleep 1
|
||||
jobs -p | xargs -r kill || true
|
||||
sleep 1
|
||||
jobs -p | xargs -r kill -9 &> /dev/null || true
|
||||
fusermount3 -u ${MOUNT1} &> /dev/null || true
|
||||
fusermount3 -u ${MOUNT2} &> /dev/null || true
|
||||
fusermount3 -u ${MOUNT3} &> /dev/null || true
|
||||
fusermount3 -u ${MOUNT4} &> /dev/null || true
|
||||
exit $rv
|
||||
}
|
||||
|
||||
trap "cleanup" EXIT
|
||||
set -euo pipefail
|
||||
|
||||
DATA=${TEST_DIR}/data
|
||||
CONFIG=${TEST_DIR}/config
|
||||
DEFAULT_LOG=${TEST_DIR}/log
|
||||
LOG=${LOG_DIR:=${DEFAULT_LOG}}
|
||||
MOUNT1=`realpath ${TEST_DIR}/mnt1`
|
||||
MOUNT2=`realpath ${TEST_DIR}/mnt2`
|
||||
MOUNT3=`realpath ${TEST_DIR}/mnt3`
|
||||
MOUNT4=`realpath ${TEST_DIR}/mnt4`
|
||||
|
||||
export FUSE_TEST_TIMEOUT="${FUSE_TEST_TIMEOUT:-3600}"
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
UPDATE_CONFIG="python3 ${SCRIPT_DIR}/update-config.py"
|
||||
|
||||
# NOTE: need regenerate chain table if change storage number or disk number
|
||||
NUM_META=2
|
||||
NUM_STORAGE=3
|
||||
NUM_DISK=4
|
||||
export TOKEN="AADHHSOs8QA92iRe2wB1fmuL"
|
||||
export TOKEN_FILE="${CONFIG}/token"
|
||||
export PKEY_INDEX=${PKEY_INDEX:-0}
|
||||
export FS_CLUSTER="ci_test"
|
||||
export FDB_UNITTEST_CLUSTER=${DATA}/foundationdb/fdb.cluster
|
||||
export TARGETS="$(seq -f \'{{\ env.STORAGE_DIR\ }}/data%g\', 1 ${NUM_DISK})"
|
||||
|
||||
if [ -z "${FDB_PATH+x}" ]; then
|
||||
FDB=$(which fdbserver)
|
||||
FDBCLI=$(which fdbcli)
|
||||
else
|
||||
FDB=$FDB_PATH/fdbserver
|
||||
FDBCLI=$FDB_PATH/fdbcli
|
||||
fi
|
||||
|
||||
# check network
|
||||
echo "### check network"
|
||||
export ADDRESS=$(ip -o -4 addr show | grep -E '(en|eth|ib)' | awk '{print $4}' | head -n 1 | awk -F'[/ ]' '{print $1}')
|
||||
export FDB_PORT=$(random_port)
|
||||
export MGMTD_PORT=$(random_port)
|
||||
if [ -n "${FUSE_TEST_FDB_PORT+x}" ]; then
|
||||
echo use FUSE_TEST_FDB_PORT ${FUSE_TEST_FDB_PORT}
|
||||
lsof -i ":${FUSE_TEST_FDB_PORT}" || echo port available
|
||||
export FDB_PORT=${FUSE_TEST_FDB_PORT}
|
||||
fi
|
||||
if [ -n "${FUSE_TEST_MGMTD_PORT+x}" ]; then
|
||||
echo use FUSE_TEST_MGMTD_PORT ${FUSE_TEST_MGMTD_PORT}
|
||||
lsof -i ":${FUSE_TEST_MGMTD_PORT}" || echo port available
|
||||
export MGMTD_PORT=${FUSE_TEST_MGMTD_PORT}
|
||||
fi
|
||||
echo "NIC address ${ADDRESS}"
|
||||
echo "FDB port ${FDB_PORT}"
|
||||
echo "MGMTD port ${MGMTD_PORT}"
|
||||
|
||||
# check directory
|
||||
echo "### check directory and file"
|
||||
check_exists ${TEST_DIR}
|
||||
rm -rf ${TEST_DIR}/*
|
||||
check_exists ${BINARY}
|
||||
check_exists ${BINARY}/mgmtd_main
|
||||
check_exists ${BINARY}/meta_main
|
||||
check_exists ${BINARY}/storage_main
|
||||
check_exists ${BINARY}/hf3fs_fuse_main
|
||||
check_exists ${BINARY}/admin_cli
|
||||
check_exists ${FDB}
|
||||
check_exists ${FDBCLI}
|
||||
|
||||
# create subdirectory
|
||||
echo "### create subdirectory"
|
||||
mkdir -p ${CONFIG}
|
||||
mkdir -p ${LOG}
|
||||
mkdir -p ${DATA}
|
||||
mkdir -p ${MOUNT1}
|
||||
mkdir -p ${MOUNT2}
|
||||
mkdir -p ${MOUNT3}
|
||||
mkdir -p ${MOUNT4}
|
||||
mkdir -p ${DATA}/foundationdb
|
||||
for i in $(seq 1 ${NUM_STORAGE}); do
|
||||
mkdir -p ${DATA}/storage-${i}
|
||||
for j in $(seq 1 ${NUM_DISK}); do
|
||||
mkdir -p ${DATA}/storage-${i}/data${j}
|
||||
done
|
||||
done
|
||||
|
||||
# generate app config
|
||||
echo "generate app config"
|
||||
create_app_config 1 ${CONFIG}/mgmtd_main_app.toml
|
||||
for i in $(seq 1 ${NUM_META}); do
|
||||
node=$(expr 50 + ${i})
|
||||
file=${CONFIG}/meta_main_app_${node}.toml
|
||||
create_app_config ${node} ${file}
|
||||
done
|
||||
create_app_config 51 ${CONFIG}/meta_main_app.toml
|
||||
for i in $(seq 1 ${NUM_STORAGE}); do
|
||||
node=$(expr 10000 + ${i})
|
||||
file=${CONFIG}/storage_main_app_${node}.toml
|
||||
create_app_config ${node} ${file}
|
||||
done
|
||||
|
||||
# generate config
|
||||
echo "generate config"
|
||||
echo ${TOKEN} > ${TOKEN_FILE}
|
||||
for file in ${SCRIPT_DIR}/config/*.toml; do
|
||||
echo "- generate $(basename $file)"
|
||||
cat ${file} | envsubst > ${CONFIG}/$(basename $file)
|
||||
done
|
||||
ADMIN_CLI="${BINARY}/admin_cli --cfg ${CONFIG}/admin_cli.toml --"
|
||||
|
||||
# start foundationdb
|
||||
echo "### create foundationdb"
|
||||
start_fdb $FDB_PORT ${DATA}/foundationdb ${LOG}/
|
||||
|
||||
# init cluster
|
||||
echo "### init cluster"
|
||||
echo "admin_cli: ${ADMIN_CLI}"
|
||||
${ADMIN_CLI} user-add --root --admin --token ${TOKEN} 0 root
|
||||
${ADMIN_CLI} user-set-token --new 0
|
||||
|
||||
echo ""
|
||||
${ADMIN_CLI} user-list
|
||||
echo ""
|
||||
${ADMIN_CLI} init-cluster \
|
||||
--mgmtd ${CONFIG}/mgmtd_main.toml \
|
||||
--meta ${CONFIG}/meta_main.toml \
|
||||
--storage ${CONFIG}/storage_main.toml \
|
||||
--fuse ${CONFIG}/hf3fs_fuse_main.toml \
|
||||
--skip-config-check 1 524288 8
|
||||
|
||||
# start mgmtd
|
||||
echo "### start mgmtd"
|
||||
LOG_FILE=${LOG}/mgmtd.log \
|
||||
PORT=${MGMTD_PORT} \
|
||||
${BINARY}/mgmtd_main \
|
||||
--app_cfg ${CONFIG}/mgmtd_main_app.toml \
|
||||
--launcher_cfg ${CONFIG}/mgmtd_main_launcher.toml \
|
||||
--cfg ${CONFIG}/mgmtd_main.toml \
|
||||
>${LOG}/mgmtd_main.stdout 2>${LOG}/mgmtd_main.stderr &
|
||||
sleep 5 && echo ""
|
||||
|
||||
# start meta
|
||||
echo "### start meta & storage"
|
||||
for i in $(seq 1 ${NUM_STORAGE}); do
|
||||
LOG_FILE=${LOG}/storage-${i}.log \
|
||||
PORT=0 \
|
||||
STORAGE_DIR=${DATA}/storage-${i}/ \
|
||||
${BINARY}/storage_main \
|
||||
--app_cfg ${CONFIG}/storage_main_app_$(expr 10000 + ${i}).toml \
|
||||
--launcher_cfg ${CONFIG}/storage_main_launcher.toml \
|
||||
--cfg ${CONFIG}/storage_main.toml \
|
||||
>${LOG}/storage_main-${i}.stdout 2>${LOG}/storage_main-${i}.stderr &
|
||||
done
|
||||
|
||||
for i in $(seq 1 ${NUM_META}); do
|
||||
LOG_FILE=${LOG}/meta-${i}.log \
|
||||
PORT=0 \
|
||||
${BINARY}/meta_main \
|
||||
--app_cfg ${CONFIG}/meta_main_app_$(expr 50 + ${i}).toml \
|
||||
--launcher_cfg ${CONFIG}/meta_main_launcher.toml \
|
||||
--cfg ${CONFIG}/meta_main.toml \
|
||||
>${LOG}/meta_main-${i}.stdout 2>${LOG}/meta_main-${i}.stderr &
|
||||
done
|
||||
|
||||
sleep 15 && echo ""
|
||||
|
||||
# list nodes
|
||||
echo "### list nodes"
|
||||
${ADMIN_CLI} list-nodes
|
||||
|
||||
# create chains
|
||||
echo "### create targets and chain table"
|
||||
echo "create targets"
|
||||
echo "ChainId,TargetId" >${CONFIG}/chains.csv
|
||||
echo "ChainId" >${CONFIG}/chain-table.csv
|
||||
for storage in $(seq 1 ${NUM_STORAGE}); do
|
||||
node=$(expr 10000 + ${storage})
|
||||
for disk in $(seq 1 ${NUM_DISK}); do
|
||||
target=$(printf "%d%02d001" $node $disk)
|
||||
args="create-target --node-id ${node} --disk-index $((disk - 1)) --target-id ${target} --chain-id ${target}"
|
||||
echo $args
|
||||
echo "${target},${target}" >>${CONFIG}/chains.csv
|
||||
echo "${target}" >>${CONFIG}/chain-table.csv
|
||||
${ADMIN_CLI} ${args}
|
||||
done
|
||||
done
|
||||
|
||||
echo "update chains and chain table"
|
||||
${ADMIN_CLI} upload-chains ${CONFIG}/chains.csv
|
||||
${ADMIN_CLI} upload-chain-table 1 ${CONFIG}/chain-table.csv --desc replica-1
|
||||
sleep 10
|
||||
${ADMIN_CLI} list-chains
|
||||
${ADMIN_CLI} list-chain-tables
|
||||
|
||||
echo "create test user"
|
||||
if [ $UID -ne 0 ]; then
|
||||
${ADMIN_CLI} user-add ${UID} test
|
||||
fi
|
||||
${ADMIN_CLI} user-list
|
||||
${ADMIN_CLI} mkdir --perm 0755 test
|
||||
${ADMIN_CLI} set-perm --uid ${UID} --gid ${UID} test
|
||||
|
||||
echo "### start fuse"
|
||||
echo "redirect stdout to ${LOG}/fuse.1.stdout, stderr to ${LOG}/fuse.1.stderr"
|
||||
LOG_FILE=${LOG}/fuse.1.log DYNAMIC_STRIPE=true READCACHE=false \
|
||||
timeout -k 10 ${FUSE_TEST_TIMEOUT} ${BINARY}/hf3fs_fuse_main \
|
||||
--launcher_cfg ${CONFIG}/hf3fs_fuse_main_launcher.toml \
|
||||
--launcher_config.mountpoint=${MOUNT1} \
|
||||
>${LOG}/fuse.1.stdout 2>${LOG}/fuse.1.stderr &
|
||||
|
||||
echo "redirect stdout to ${LOG}/fuse.2.stdout, stderr to ${LOG}/fuse.2.stderr"
|
||||
LOG_FILE=${LOG}/fuse.2.log ENABLE_WRITE_BUFFER=1 DYNAMIC_STRIPE=false READCACHE=false \
|
||||
timeout -k 10 ${FUSE_TEST_TIMEOUT} ${BINARY}/hf3fs_fuse_main \
|
||||
--launcher_cfg ${CONFIG}/hf3fs_fuse_main_launcher.toml \
|
||||
--launcher_config.mountpoint=${MOUNT2} \
|
||||
>${LOG}/fuse.2.stdout 2>${LOG}/fuse.2.stderr &
|
||||
|
||||
echo "redirect stdout to ${LOG}/fuse.3.stdout, stderr to ${LOG}/fuse.3.stderr"
|
||||
LOG_FILE=${LOG}/fuse.3.log DYNAMIC_STRIPE=false READCACHE=true \
|
||||
timeout -k 10 ${FUSE_TEST_TIMEOUT} ${BINARY}/hf3fs_fuse_main \
|
||||
--launcher_cfg ${CONFIG}/hf3fs_fuse_main_launcher.toml \
|
||||
--launcher_config.mountpoint=${MOUNT3} \
|
||||
>${LOG}/fuse.3.stdout 2>${LOG}/fuse.3.stderr &
|
||||
|
||||
echo "redirect stdout to ${LOG}/fuse.4.stdout, stderr to ${LOG}/fuse.4.stderr"
|
||||
LOG_FILE=${LOG}/fuse.4.log ENABLE_WRITE_BUFFER=1 DYNAMIC_STRIPE=true READCACHE=true \
|
||||
timeout -k 10 ${FUSE_TEST_TIMEOUT} ${BINARY}/hf3fs_fuse_main \
|
||||
--launcher_cfg ${CONFIG}/hf3fs_fuse_main_launcher.toml \
|
||||
--launcher_config.mountpoint=${MOUNT4} \
|
||||
>${LOG}/fuse.4.stdout 2>${LOG}/fuse.4.stderr &
|
||||
|
||||
wait_path ${MOUNT1}/test 30
|
||||
wait_path ${MOUNT2}/test 30
|
||||
wait_path ${MOUNT3}/test 30
|
||||
wait_path ${MOUNT4}/test 30
|
||||
|
||||
if [ -z "${FIO_BINARY+x}" ]; then
|
||||
if [ -f ${BINARY}/fio ]; then
|
||||
FIO_BINARY=${BINARY}/fio
|
||||
else
|
||||
FIO_BINARY=""
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Fuse mounted"
|
||||
|
||||
python3 ${SCRIPT_DIR}/concurrent_rmrf.py --mnt_path ${MOUNT1}
|
||||
|
||||
echo "### test gc permission check"
|
||||
mkdir -p ${MOUNT1}/test/data/subdir{1..1024}
|
||||
sudo mkdir ${MOUNT1}/test/data/subdir999/root
|
||||
sudo touch ${MOUNT1}/test/data/subdir999/root/file-{1..5}
|
||||
mv ${MOUNT1}/test/data ${MOUNT1}/3fs-virt/rm-rf
|
||||
sleep 5
|
||||
stat ${MOUNT1}/trash/gc-orphans
|
||||
ls -lR ${MOUNT1}/trash/gc-orphans
|
||||
|
||||
echo "### test trash"
|
||||
bash ${SCRIPT_DIR}/test_trash.sh ${BINARY} ${TEST_DIR}
|
||||
|
||||
echo "### test ioctl"
|
||||
sudo rm -rf ${MOUNT1}/trash/`id -un`/*
|
||||
python3 ${SCRIPT_DIR}/test_ioctl.py ${MOUNT1} ${MOUNT1}/test/ioctl
|
||||
|
||||
for MOUNT in ${MOUNT1} ${MOUNT2} ${MOUNT3} ${MOUNT4}; do
|
||||
new_chunk_engine=$((( RANDOM % 2 == 0 )) && echo "true" || echo "false")
|
||||
echo "### set new_chunk_engine to ${new_chunk_engine}"
|
||||
${ADMIN_CLI} hot-update-config --node-type META --string "server.meta.enable_new_chunk_engine=${new_chunk_engine}"
|
||||
|
||||
echo "### run test under ${MOUNT}"
|
||||
|
||||
SUBDIR=${MOUNT}/test/$(random_64)
|
||||
mkdir -p ${SUBDIR}
|
||||
echo "use tmp dir ${SUBDIR}"
|
||||
|
||||
echo "#### run ci test"
|
||||
mkdir ${SUBDIR}/fuse_test_ci
|
||||
python3 ${SCRIPT_DIR}/fuse_test_ci.py ${SUBDIR}/fuse_test_ci
|
||||
echo ""
|
||||
|
||||
echo "#### test read after write"
|
||||
mkdir ${SUBDIR}/read_after_write
|
||||
python3 ${SCRIPT_DIR}/read_after_write.py ${SUBDIR}/read_after_write
|
||||
echo ""
|
||||
|
||||
echo "### test client io during update chain table"
|
||||
# start a read/write task
|
||||
mkdir ${SUBDIR}/rw_during_update_chain_table
|
||||
python3 ${SCRIPT_DIR}/read_after_write.py ${SUBDIR}/rw_during_update_chain_table --seconds 60 &
|
||||
PID=$!
|
||||
# then update chain table in background
|
||||
sleep 10
|
||||
start_time=$(date +%s)
|
||||
while true; do
|
||||
current_time=$(date +%s)
|
||||
elapsed=$(( current_time - start_time ))
|
||||
if [ $elapsed -lt 30 ]; then
|
||||
echo "update chain table"
|
||||
(head -n 1 ${CONFIG}/chain-table.csv && tail -n +2 ${CONFIG}/chain-table.csv | shuf) > ${CONFIG}/chain-table-shuffle.csv
|
||||
${ADMIN_CLI} upload-chain-table 1 ${CONFIG}/chain-table-shuffle.csv --desc replica-1-shuflle
|
||||
${ADMIN_CLI} list-chain-tables
|
||||
else
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
wait $PID
|
||||
echo ""
|
||||
|
||||
echo "#### test random write and read"
|
||||
mkdir ${SUBDIR}/random_rw
|
||||
python3 ${SCRIPT_DIR}/random_rw.py ${SUBDIR}/random_rw
|
||||
echo ""
|
||||
|
||||
echo "#### test read before close"
|
||||
mkdir ${SUBDIR}/read_before_close
|
||||
python3 ${SCRIPT_DIR}/random_rw.py --read_before_close ${SUBDIR}/read_before_close
|
||||
echo ""
|
||||
|
||||
set +e
|
||||
python3 -c 'import hf3fs_fuse.io as h3io'
|
||||
ret=$?
|
||||
set -e
|
||||
if [ ${ret} -eq 0 ]; then
|
||||
# cat /proc/self/mountinfo
|
||||
# echo
|
||||
# grep hf3fs /proc/self/mountinfo | awk '{print $5, $(NF-2)}'
|
||||
# ls $(grep hf3fs /proc/self/mountinfo | awk '{print $5}')/
|
||||
# ls ${MOUNT}
|
||||
# echo ${SUBDIR}/usrbio
|
||||
echo '#### test usrbio'
|
||||
mkdir ${SUBDIR}/usrbio
|
||||
python3 ${SCRIPT_DIR}/usrbio.py ${SUBDIR}/usrbio ${MOUNT}
|
||||
else
|
||||
echo -e "${RED}#### can't inmport hf3fs_fuse.io, skip usrbio test!!!${NC}"
|
||||
fi
|
||||
|
||||
if [ -f "${FIO_BINARY}" ]; then
|
||||
echo "#### test fio with usrbio"
|
||||
mkdir ${SUBDIR}/fio
|
||||
for j in {0..3}; do
|
||||
for f in {0..2}; do
|
||||
truncate -s 4G ${SUBDIR}/fio/j${j}-f${f}
|
||||
done
|
||||
done
|
||||
date
|
||||
LD_PRELOAD="${BINARY}/libhf3fs_api_shared.so" ${FIO_BINARY} -name=test_write -rw=write -direct=1 -ioengine=hf3fs_ring -group_reporting -numjobs=4 -bs=4m -size=4G -directory=${SUBDIR}/fio -filename_format='j$jobnum-f$filenum' -thread=1 -mountpoint=${MOUNT} -verify=crc32c -nrfiles=2
|
||||
date
|
||||
ls -lh ${SUBDIR}/fio
|
||||
date
|
||||
LD_PRELOAD="${BINARY}/libhf3fs_api_shared.so" ${FIO_BINARY} -name=test_read -rw=randread -direct=1 -ioengine=hf3fs_ring -group_reporting -numjobs=4 -bs=4k -iodepth=100 -time_based -runtime=30 -directory=${SUBDIR}/fio -filename_format='j$jobnum-f$filenum' -thread=1 -mountpoint=${MOUNT} -iodepth_batch_submit=100 -iodepth_batch_complete_min=100 -iodepth_batch_complete_max=100 -size=4G -nrfiles=2
|
||||
date
|
||||
else
|
||||
echo "${RED}#### fio not found, skip fio test!!!${NC}"
|
||||
fi
|
||||
|
||||
if [ -f ${BINARY}/pjdfstest ]; then
|
||||
echo "#### run pjdfstest"
|
||||
${BINARY}/pjdfstest -c ${CONFIG}/pjdfstest.toml -p ${SUBDIR}
|
||||
else
|
||||
echo "${RED}#### pjdfstest not found, skip pjdfstest!!!${NC}"
|
||||
fi
|
||||
|
||||
touch ${SUBDIR}/test_iflags
|
||||
python3 ${SCRIPT_DIR}/test_iflags.py ${SUBDIR}/test_iflags
|
||||
done
|
||||
|
||||
echo "### test concurrent rw"
|
||||
mkdir -p ${MOUNT1}/test/concurrent_rw1 ${MOUNT2}/test/concurrent_rw1
|
||||
python3 ${SCRIPT_DIR}/concurrent_rw.py ${MOUNT1}/test/concurrent_rw1 ${MOUNT2}/test/concurrent_rw1
|
||||
|
||||
mkdir -p ${MOUNT1}/test/concurrent_rw2 ${MOUNT2}/test/concurrent_rw2
|
||||
python3 ${SCRIPT_DIR}/concurrent_rw.py ${MOUNT2}/test/concurrent_rw2 ${MOUNT1}/test/concurrent_rw2
|
||||
|
||||
echo "### test lock directory"
|
||||
mkdir -p ${MOUNT1}/test/test_dir_lock
|
||||
python3 ${SCRIPT_DIR}/lock_directory.py ${MOUNT1}/test/test_dir_lock ${MOUNT2}/test/test_dir_lock
|
||||
|
||||
echo "### test client io during update chain table"
|
||||
(head -n 1 ${CONFIG}/chain-table.csv && tail -n +2 ${CONFIG}/chain-table.csv | shuf) > ${CONFIG}/chain-table-2.csv
|
||||
${ADMIN_CLI} upload-chain-table 1 ${CONFIG}/chain-table-2.csv --desc replica-1-version-2
|
||||
${ADMIN_CLI} list-chain-tables
|
||||
|
||||
echo "### test chattr +i"
|
||||
sudo chattr +i ${MOUNT1}/test
|
||||
lsattr ${MOUNT1}
|
||||
sudo chattr -i ${MOUNT1}/test
|
||||
|
||||
echo "unmount fuse and stop all servers"
|
||||
fusermount3 -u ${MOUNT1}
|
||||
fusermount3 -u ${MOUNT2}
|
||||
fusermount3 -u ${MOUNT3}
|
||||
fusermount3 -u ${MOUNT4}
|
||||
|
||||
# 定义函数 kill_and_wait
|
||||
kill_and_wait() {
|
||||
local pid="$1"
|
||||
local cmd_name="$2"
|
||||
# 检查进程是否存在
|
||||
if ps -p "$pid" > /dev/null; then
|
||||
# 杀死进程
|
||||
kill "$pid"
|
||||
echo "Sent kill signal to process $cmd_name with PID $pid"
|
||||
# 等待并检查进程是否退出
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
if ! ps -p "$pid" > /dev/null; then
|
||||
echo "Process $cmd_name with PID $pid has exited."
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
# 如果5秒后进程仍然存在
|
||||
echo -e "\e[31mError: Process $cmd_name with PID $pid did not exit after 5 seconds.\e[0m"
|
||||
kill -9 "$pid"
|
||||
else
|
||||
echo "No process with PID $pid found."
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# 获取所有后台任务的PID和命令名
|
||||
jobs_array=()
|
||||
while read -r pid cmd; do
|
||||
jobs_array+=("$pid $cmd")
|
||||
done < <(jobs -l | awk '{print $2, $3, $4, $5, $6, $7, $8, $9, $10}')
|
||||
|
||||
# 按照启动顺序的相反顺序进行 kill
|
||||
for (( idx=${#jobs_array[@]}-1 ; idx>=0 ; idx-- )); do
|
||||
job_info=(${jobs_array[idx]})
|
||||
pid=${job_info[0]}
|
||||
cmd_name=${job_info[@]:1}
|
||||
kill_and_wait "$pid" "$cmd_name"
|
||||
done
|
||||
46
tests/fuse/test_iflags.py
Normal file
46
tests/fuse/test_iflags.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import argparse
|
||||
import fcntl
|
||||
import os
|
||||
import struct
|
||||
|
||||
FS_IOC_GETFLAGS = 0x80086601
|
||||
FS_IOC_SETFLAGS = 0x40086602
|
||||
FS_HUGE_FILE_FL = 0x00040000
|
||||
FS_UNSUPPORTED_FL = 0x00800000
|
||||
|
||||
|
||||
def get_iflags(file_path):
|
||||
with open(file_path, "rb") as f:
|
||||
buf = struct.pack("I", 0)
|
||||
flags = fcntl.ioctl(f.fileno(), FS_IOC_GETFLAGS, buf)
|
||||
iflags = struct.unpack("I", flags)[0]
|
||||
return iflags
|
||||
|
||||
|
||||
def set_iflags(file_path, iflags):
|
||||
with open(file_path, "rb") as f:
|
||||
buf = struct.pack("I", iflags)
|
||||
fcntl.ioctl(f.fileno(), FS_IOC_SETFLAGS, buf)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("file_path")
|
||||
args = parser.parse_args()
|
||||
|
||||
current_iflags = get_iflags(args.file_path)
|
||||
print(f"Current iflags: {current_iflags:#010x}")
|
||||
|
||||
new_iflags = current_iflags | FS_HUGE_FILE_FL
|
||||
set_iflags(args.file_path, new_iflags)
|
||||
|
||||
updated_iflags = get_iflags(args.file_path)
|
||||
print(f"Updated iflags: {updated_iflags:#010x}")
|
||||
assert updated_iflags == new_iflags
|
||||
|
||||
try:
|
||||
set_iflags(args.file_path, updated_iflags | FS_UNSUPPORTED_FL)
|
||||
assert False
|
||||
except OSError as ex:
|
||||
print("set FS_UNSUPPORTED_FL", ex)
|
||||
pass
|
||||
121
tests/fuse/test_ioctl.py
Normal file
121
tests/fuse/test_ioctl.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import os
|
||||
import argparse
|
||||
import errno
|
||||
import shutil
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from hf3fs_utils.fs import FileSystem
|
||||
from hf3fs_utils import trash
|
||||
|
||||
|
||||
def remove_trailing_separator(path: str):
|
||||
if path.endswith(os.path.sep):
|
||||
return os.path.dirname(path)
|
||||
return path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("mount_point", type=str)
|
||||
parser.add_argument("test_path", type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
shutil.rmtree(args.test_path, ignore_errors=True)
|
||||
|
||||
fs = FileSystem(args.mount_point)
|
||||
os.makedirs(os.path.join(args.test_path, "dir", "subdir"), exist_ok=True)
|
||||
try:
|
||||
fs.remove(os.path.join(args.test_path, "dir"), False)
|
||||
assert False
|
||||
except OSError as ex:
|
||||
assert ex.errno == errno.ENOTEMPTY, ex
|
||||
fs.remove(os.path.join(args.test_path, "dir"), True)
|
||||
assert not os.path.exists(os.path.join(args.test_path, "dir"))
|
||||
|
||||
try:
|
||||
fs.remove(os.path.join(args.mount_point, "../.."), True)
|
||||
assert False
|
||||
except RuntimeError as ex:
|
||||
print(ex)
|
||||
|
||||
os.makedirs(os.path.join(args.test_path, "src/subdir"), exist_ok=True)
|
||||
fs.rename(os.path.join(args.test_path, "src"), os.path.join(args.test_path, "dst"))
|
||||
assert os.path.exists(os.path.join(args.test_path, "dst"))
|
||||
os.makedirs(os.path.join(args.test_path, "src"), exist_ok=True)
|
||||
try:
|
||||
fs.rename(
|
||||
os.path.join(args.test_path, "src"), os.path.join(args.test_path, "dst")
|
||||
)
|
||||
assert False
|
||||
except OSError as ex:
|
||||
assert ex.errno in [errno.EEXIST, errno.ENOTEMPTY], ex
|
||||
fs.rename(
|
||||
os.path.join(args.test_path, "src"), os.path.join(args.test_path, "dst", "src")
|
||||
)
|
||||
assert os.path.exists(os.path.join(args.test_path, "dst", "src"))
|
||||
|
||||
os.makedirs(os.path.join(args.test_path, "src/subdir"), exist_ok=True)
|
||||
try:
|
||||
fs.rename(os.path.join(args.test_path, "src"), os.path.expanduser("~"))
|
||||
assert False
|
||||
except RuntimeError as ex:
|
||||
print(ex)
|
||||
|
||||
# test trash
|
||||
# test trash timestamp
|
||||
raised = False
|
||||
try:
|
||||
trash.format_date(datetime.now())
|
||||
except:
|
||||
raised = True
|
||||
assert raised
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
str = trash.format_date(now)
|
||||
assert trash.parse_date(str) == now
|
||||
for tz in range(-12, 12):
|
||||
tz_time = now.astimezone(timezone(timedelta(hours=tz)))
|
||||
tz_str = trash.format_date(tz_time)
|
||||
assert tz_str == str, (tz_str, str)
|
||||
assert tz_time == now, (tz_time, now)
|
||||
assert trash.parse_date(tz_str) == now, (trash.parse_date(tz_str), now)
|
||||
|
||||
try:
|
||||
trash.TrashConfig("5s", timedelta(seconds=5), timedelta(seconds=5))
|
||||
assert False
|
||||
except AssertionError:
|
||||
pass
|
||||
for i in range(100):
|
||||
os.makedirs(os.path.join(args.test_path, f"dir-{i}", "data", "subdir"))
|
||||
trash_config = trash.TrashConfig("2m", timedelta(minutes=2), timedelta(minutes=1))
|
||||
my_trash = trash.Trash(fs)
|
||||
assert not os.listdir(my_trash.user_trash_path), os.listdir(
|
||||
my_trash.user_trash_path
|
||||
)
|
||||
|
||||
trash_paths = set()
|
||||
for i in range(100):
|
||||
trash_path = my_trash.move_to_trash(
|
||||
os.path.join(args.test_path, f"dir-{i}", "data"), trash_config
|
||||
)
|
||||
assert trash_path not in trash_paths, trash_path
|
||||
trash_paths.add(trash_path)
|
||||
# time.sleep(1)
|
||||
assert len(trash_paths) == 100
|
||||
for t in trash_paths:
|
||||
assert os.path.exists(t)
|
||||
|
||||
# test clean trash
|
||||
my_trash.move_to_trash(
|
||||
os.path.join(args.test_path, f"dir-0"),
|
||||
trash.TrashConfig("1h", timedelta(hours=1), timedelta(minutes=10)),
|
||||
)
|
||||
# print(f"before clean", os.listdir(my_trash.user_trash_path))
|
||||
# trash_cleaner = TrashCleaner(fs)
|
||||
# trash_cleaner.run(True)
|
||||
# print(f"after clean", os.listdir(my_trash.user_trash_path))
|
||||
# time.sleep(180)
|
||||
# # create some unknown type under
|
||||
# trash_cleaner.run(True)
|
||||
# print(f"after clean", os.listdir(my_trash.user_trash_path))
|
||||
# assert os.listdir(my_trash.user_trash_path)
|
||||
|
||||
print("test finish")
|
||||
185
tests/fuse/test_trash.sh
Normal file
185
tests/fuse/test_trash.sh
Normal file
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
set -e
|
||||
|
||||
error_handler() {
|
||||
echo "Error occurred on line $1"
|
||||
}
|
||||
|
||||
# 设置 trap 来捕获 ERR 信号
|
||||
trap 'error_handler $LINENO' ERR
|
||||
|
||||
check_exists() {
|
||||
local path="$1"
|
||||
echo check $path
|
||||
if [ -e "$path" ]; then
|
||||
echo "ok, 路径存在: $path"
|
||||
else
|
||||
echo "${RED}error, 路径不存在: $path"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查路径是否不存在
|
||||
check_not_exists() {
|
||||
local path="$1"
|
||||
if [ ! -e "$path" ]; then
|
||||
echo "ok, 路径不存在: $path"
|
||||
else
|
||||
echo "${RED}error, 路径存在: $path"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_fail() {
|
||||
set +e
|
||||
|
||||
# 执行传入的命令(包括管道)
|
||||
eval "$@"
|
||||
|
||||
# 检查整个管道的退出状态码
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Assertion failed: Command '$*' succeeded, but expected to fail."
|
||||
exit 1
|
||||
else
|
||||
echo "Assertion passed: Command '$*' failed as expected."
|
||||
fi
|
||||
|
||||
set -e
|
||||
}
|
||||
|
||||
if (($# < 2)); then
|
||||
echo "${0} <binary> <test-dir> <log-dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BINARY=${1%/}
|
||||
TEST_DIR=${2%/}
|
||||
LOG_DIR=${3:-}
|
||||
DEFAULT_LOG=${TEST_DIR}/log
|
||||
LOG=${LOG_DIR:=${DEFAULT_LOG}}
|
||||
|
||||
MOUNT1=${TEST_DIR}/mnt1
|
||||
MOUNT2=${TEST_DIR}/mnt2
|
||||
export HF3FS_CLI_MOUNTPOINT=`realpath ${MOUNT1}`
|
||||
|
||||
# test hf3fs trash
|
||||
sudo rm -rf ${MOUNT1}/trash
|
||||
# without trash directory, can't rmtree
|
||||
mkdir -p ${MOUNT1}/test/subdir
|
||||
assert_fail 'yes | hf3fs_cli rmtree --expire 3hours ${MOUNT1}/test/subdir'
|
||||
|
||||
sudo mkdir ${MOUNT1}/trash
|
||||
sudo mkdir -p ${MOUNT1}/trash/`id -un`
|
||||
sudo chown ${UID}:${UID} ${MOUNT1}/trash/`id -un`
|
||||
mkdir -p ${MOUNT1}/trash/`id -un`/invalid-name
|
||||
mkdir -p ${MOUNT1}/trash/`id -un`/1d-20240801_1200-20240801_1000 # invalid timestamp, begin < end
|
||||
mkdir -p ${MOUNT1}/trash/`id -un`/1d-20240801_1200-20240801_1300 # expired
|
||||
mkdir -p ${MOUNT1}/trash/`id -un`/1d-20240801_1200-20240801_1300/data-{1..200}
|
||||
mkdir -p ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300 # not expired
|
||||
mkdir -p ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300/data-{1..200}
|
||||
# trash cleaner 在删除时使用的是普通用户的 uid/gid, 不应该有 root 权限
|
||||
sudo mkdir -p ${MOUNT1}/trash/`id -un`/noperm-20240801_1200-20240801_1300/data-{1..20}
|
||||
|
||||
# root user's directory
|
||||
sudo mkdir ${MOUNT1}/trash/user-root
|
||||
sudo mkdir -p ${MOUNT1}/trash/user-root/1d-20240801_1200-20240801_1300/data
|
||||
# another user's directory
|
||||
sudo mkdir ${MOUNT1}/trash/user-1000
|
||||
sudo chown 1000:1000 ${MOUNT1}/trash/user-1000
|
||||
sudo mkdir -p ${MOUNT1}/trash/user-1000/1d-20240801_1200-20240801_1300/data
|
||||
sudo mkdir -p ${MOUNT1}/trash/user-1000/1d-20240801_1200-20240801_1310/data
|
||||
sudo chown -R 1000:1000 ${MOUNT1}/trash/user-1000/1d-20240801_1200-20240801_1310
|
||||
|
||||
sudo ${BINARY}/trash_cleaner --interval 0 --paths ${MOUNT1}/trash/
|
||||
|
||||
# after clean check some directory is cleaned, some still exists
|
||||
echo ==== ${MOUNT1}/trash/`id -un` ====
|
||||
ls ${MOUNT1}/trash/`id -un`
|
||||
check_exists ${MOUNT1}/trash/`id -un`/invalid-name
|
||||
check_exists ${MOUNT1}/trash/`id -un`/1d-20240801_1200-20240801_1000
|
||||
check_exists ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300
|
||||
check_not_exists ${MOUNT1}/trash/`id -un`/1d-20240801_1200-20240801_1300
|
||||
check_exists ${MOUNT1}/trash/`id -un`/noperm-20240801_1200-20240801_1300/data-{1..20}
|
||||
|
||||
echo ==== ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300 ====
|
||||
check_exists ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300
|
||||
ls ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300
|
||||
# rmtree to remove trash immediately
|
||||
assert_fail 'yes | hf3fs_cli rmtree ${MOUNT1}/trash/`id -un`/'
|
||||
assert_fail 'yes | hf3fs_cli rmtree ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300 --expire 3hours'
|
||||
check_exists ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300
|
||||
yes | hf3fs_cli rmtree ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300
|
||||
check_not_exists ${MOUNT1}/trash/`id -un`/1d-20300801_1200-20300801_1300
|
||||
|
||||
# root user's trash directory should exists
|
||||
check_exists ${MOUNT1}/trash/user-root
|
||||
check_exists ${MOUNT1}/trash/user-root/1d-20240801_1200-20240801_1300/data
|
||||
|
||||
# another user's trash directory
|
||||
check_exists ${MOUNT1}/trash/user-1000/
|
||||
# no permission to remove
|
||||
check_exists ${MOUNT1}/trash/user-1000/1d-20240801_1200-20240801_1300/data
|
||||
check_not_exists ${MOUNT1}/trash/user-1000/1d-20240801_1200-20240801_1310/data
|
||||
|
||||
# test hf3fs cli
|
||||
sudo rm -rf ${MOUNT1}/root
|
||||
sudo mkdir ${MOUNT1}/root
|
||||
sudo mkdir ${MOUNT1}/root/`id -un`
|
||||
sudo chown ${UID}:${UID} ${MOUNT1}/root/`id -un`
|
||||
mkdir ${MOUNT1}/root/`id -un`/subdir
|
||||
|
||||
assert_fail 'yes | hf3fs_cli rmtree --expire 3hours ${MOUNT1}/test/some-not-found-path'
|
||||
assert_fail 'hf3fs_cli mv ${MOUNT1}/test/some-not-found-path ${MOUNT1}/'
|
||||
assert_fail 'hf3fs_cli mv ${MOUNT1}/root/`id -un`/subdir ${MOUNT1}/root/`id -un`/subdir'
|
||||
|
||||
# don't support ..
|
||||
assert_fail 'yes | hf3fs_cli rmtree --expire 1h ${MOUNT1}/root/../root/`id -un`/subdir'
|
||||
assert_fail 'yes | hf3fs_cli rmtree --expire 1h ${MOUNT1}/root/`id -un`/subdir2/../subdir'
|
||||
|
||||
hf3fs_cli mv ${MOUNT1}/root/`id -un`/subdir ${MOUNT1}/root/`id -un`/another-dir
|
||||
assert_fail 'hf3fs_cli mv ${MOUNT1}/root/`id -un`/another-dir ${MOUNT1}/root/`id -un`/another-dir/another-dir'
|
||||
mkdir ${MOUNT1}/root/`id -un`/subdir
|
||||
hf3fs_cli mv ${MOUNT1}/root/`id -un`/subdir ${MOUNT1}/root/`id -un`/another-dir
|
||||
check_exists ${MOUNT1}/root/`id -un`/another-dir/subdir
|
||||
mkdir ${MOUNT1}/root/`id -un`/subdir
|
||||
assert_fail 'hf3fs_cli mv ${MOUNT1}/root/`id -un`/subdir ${MOUNT1}/root/`id -un`/another-dir'
|
||||
|
||||
yes | hf3fs_cli rmtree --expire 3hours ${MOUNT1}/root/`id -un`/another-dir
|
||||
mkdir ${MOUNT1}/root/`id -un`/another-dir
|
||||
yes | hf3fs_cli rmtree --expire 3hours ${MOUNT1}/root/`id -un`/another-dir
|
||||
mkdir ${MOUNT1}/root/`id -un`/another-dir
|
||||
yes | hf3fs_cli rmtree --expire 3hours ${MOUNT1}/root/`id -un`/another-dir
|
||||
stat ${MOUNT1}/trash/`id -un`/*/another-dir
|
||||
stat ${MOUNT1}/trash/`id -un`/*/another-dir.*
|
||||
|
||||
#don't allow move into trash
|
||||
mkdir ${MOUNT1}/root/`id -un`/adir
|
||||
assert_fail 'mv ${MOUNT1}/root/`id -un`/adir ${MOUNT1}/trash/`id -un`/'
|
||||
# allow mv between trash
|
||||
mkdir ${MOUNT1}/trash/`id -un`/srctrash
|
||||
mv ${MOUNT1}/trash/`id -un`/srctrash ${MOUNT1}/trash/`id -un`/dsttrash
|
||||
|
||||
# rmtree don't support symlink
|
||||
pushd ${MOUNT1}/root/`id -un`/
|
||||
mkdir -p dir1/subdir
|
||||
ln -s dir1 symlink-to-dir1
|
||||
yes | hf3fs_cli rmtree --expire 3hours symlink-to-dir1
|
||||
check_not_exists symlink-to-dir1
|
||||
check_exists dir1
|
||||
yes | hf3fs_cli rmtree --expire 3hours dir1
|
||||
check_not_exists dir1
|
||||
|
||||
mkdir -p dir-otheruser/subdir
|
||||
sudo chown -R 999:999 dir-otheruser
|
||||
sudo chmod 0777 dir-otheruser
|
||||
assert_fail 'yes | hf3fs_cli rmtree --expire 3hours dir-otheruser'
|
||||
check_exists dir-otheruser
|
||||
popd
|
||||
|
||||
# don't allow cross filesystem link
|
||||
mkdir ${MOUNT1}/root/`id -un`/src
|
||||
assert_fail 'hf3fs_cli mv ${MOUNT1}/root/`id -un`/src ${MOUNT2}/root/`id -un`/dest'
|
||||
hf3fs_cli mv ${MOUNT1}/root/`id -un`/src ${MOUNT1}/root/`id -un`/dest
|
||||
echo ======= 测试完成 ========
|
||||
101
tests/fuse/test_trash_cleaner.py
Normal file
101
tests/fuse/test_trash_cleaner.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import os
|
||||
import argparse
|
||||
import time
|
||||
import shutil
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
UTC8_TZ = timezone(timedelta(hours=8))
|
||||
DATE_FORMAT = "%Y%m%d_%H%M"
|
||||
|
||||
|
||||
def format_date(t: datetime) -> str:
|
||||
assert t.tzinfo
|
||||
return t.astimezone(tz=UTC8_TZ).strftime(DATE_FORMAT)
|
||||
|
||||
|
||||
def make_trash(trash_dir: str, mode=0o755):
|
||||
tmp_path = f"{trash_dir}.tmp"
|
||||
os.makedirs(tmp_path)
|
||||
for i in range(0, 10):
|
||||
os.makedirs(os.path.join(tmp_path, f"subdir-{i}"))
|
||||
for i in range(0, 10):
|
||||
with open(os.path.join(tmp_path, "file-{i}"), "w") as f:
|
||||
f.write("some txt")
|
||||
os.chmod(tmp_path, mode)
|
||||
os.rename(tmp_path, trash_dir)
|
||||
return trash_dir
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("trash", type=str, help="path to trash directory")
|
||||
args = parser.parse_args()
|
||||
trash = args.trash
|
||||
|
||||
invalid_dir = make_trash(os.path.join(trash, "invalid_name"))
|
||||
nonutf8_dir = os.path.join(trash.encode('utf8'), b"\xee\x80\x81\x82")
|
||||
os.mkdir(nonutf8_dir)
|
||||
|
||||
now = datetime.now(tz=UTC8_TZ)
|
||||
befores = [
|
||||
now - 2 * timedelta(minutes=10),
|
||||
now - timedelta(minutes=10),
|
||||
]
|
||||
afters = [now + timedelta(minutes=10), now + 2 * timedelta(minutes=10)]
|
||||
|
||||
expired = os.path.join(
|
||||
trash, f"expired-{format_date(befores[0])}-{format_date(befores[1])}"
|
||||
)
|
||||
make_trash(expired)
|
||||
expired_no_perm = os.path.join(
|
||||
trash, f"expired_no_perm-{format_date(befores[0])}-{format_date(befores[1])}"
|
||||
)
|
||||
make_trash(expired_no_perm, 0o000)
|
||||
expired_invalid = os.path.join(
|
||||
trash, f"expired-invalid-{format_date(befores[0])}-{format_date(befores[1])}"
|
||||
)
|
||||
make_trash(expired_invalid)
|
||||
expired_file = os.path.join(
|
||||
trash, f"expiredfile-{format_date(befores[0])}-{format_date(befores[1])}"
|
||||
)
|
||||
with open(expired_file, "w") as f:
|
||||
f.write(expired_file)
|
||||
expired_invalid_ts = os.path.join(
|
||||
trash, f"expired-{format_date(befores[1])}-{format_date(befores[0])}"
|
||||
)
|
||||
make_trash(expired_invalid_ts)
|
||||
|
||||
notexpired = [
|
||||
os.path.join(
|
||||
trash, f"notexpired-{format_date(befores[1])}-{format_date(afters[0])}"
|
||||
),
|
||||
os.path.join(
|
||||
trash, f"notexpired-{format_date(afters[0])}-{format_date(afters[1])}"
|
||||
),
|
||||
]
|
||||
for p in notexpired:
|
||||
make_trash(p)
|
||||
|
||||
print("sleep 180s")
|
||||
time.sleep(180)
|
||||
|
||||
exists = [
|
||||
nonutf8_dir,
|
||||
invalid_dir, expired_no_perm, expired_invalid, expired_invalid_ts, expired_file
|
||||
] + notexpired
|
||||
notexists = [ expired ]
|
||||
|
||||
print("check exists")
|
||||
for e in exists:
|
||||
assert os.path.exists(e), f"{e} not exists"
|
||||
print("check not exists")
|
||||
for ne in notexists:
|
||||
assert not os.path.exists(ne), f"{ne} exists"
|
||||
print("test finished, clean")
|
||||
for e in exists:
|
||||
os.chmod(e, 0o777)
|
||||
if os.path.isdir(e):
|
||||
shutil.rmtree(e)
|
||||
else:
|
||||
os.unlink(e)
|
||||
print("clean finished")
|
||||
65
tests/fuse/usrbio.py
Normal file
65
tests/fuse/usrbio.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import hf3fs_fuse.io as h3io
|
||||
|
||||
import numpy as np
|
||||
import os
|
||||
import sys
|
||||
import multiprocessing as mp
|
||||
import multiprocessing.shared_memory
|
||||
import time
|
||||
|
||||
fn = sys.argv[1] + '/file'
|
||||
fd = os.open(fn, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
|
||||
|
||||
data = np.random.randn(200 << 20).tobytes()
|
||||
os.write(fd, data)
|
||||
os.close(fd)
|
||||
|
||||
h3mp = h3io.extract_mount_point(sys.argv[1])
|
||||
|
||||
bs = h3io.read_file(fn, hf3fs_mount_point=h3mp, priority=h3io.IorPriority.HIGH)
|
||||
assert data == bs, f'{fn} read out data not equal to those written in'
|
||||
|
||||
shm = mp.shared_memory.SharedMemory(size=10<<20, create=True)
|
||||
iov = h3io.make_iovec(shm, h3mp, 0, -1)
|
||||
shm.unlink()
|
||||
ior = h3io.make_ioring(h3mp, 100, False, 0)
|
||||
|
||||
fn = sys.argv[1] + '/file2'
|
||||
fd = os.open(fn, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
|
||||
h3io.register_fd(fd)
|
||||
|
||||
shm.buf[:] = bytes(len(shm.buf))
|
||||
ior.prepare(iov[:], False, fd, 0)
|
||||
write_len = ior.submit().wait(min_results=1)[0].result
|
||||
assert write_len == len(shm.buf)
|
||||
|
||||
time.sleep(6) # After linux inode cache invalidated
|
||||
assert os.path.getsize(fn) == len(shm.buf)
|
||||
|
||||
ior.prepare(iov[:], False, fd, len(shm.buf))
|
||||
write_len = ior.submit().wait(min_results=1)[0].result
|
||||
assert write_len == len(shm.buf)
|
||||
|
||||
h3io.force_fsync(fd) # Force update length
|
||||
assert os.path.getsize(fn) == 2 * len(shm.buf)
|
||||
|
||||
h3io.deregister_fd(fd)
|
||||
os.close(fd)
|
||||
|
||||
print('usrbio read test succeeded', len(shm.buf))
|
||||
|
||||
fn = sys.argv[1] + '/file3'
|
||||
fd = os.open(fn, os.O_RDONLY | os.O_CREAT | os.O_EXCL, 0o666)
|
||||
os.close(fd)
|
||||
fd = os.open(fn, os.O_RDONLY) # No write permission
|
||||
h3io.register_fd(fd)
|
||||
erred = False
|
||||
try:
|
||||
ior.prepare(iov[:], False, fd, 0)
|
||||
except OSError as e:
|
||||
erred = True
|
||||
assert e.errno == 13, "Errno not correct" # Should raise EPERM
|
||||
assert erred, "Didn't raise EPERM"
|
||||
h3io.deregister_fd(fd)
|
||||
os.close(fd)
|
||||
print('usrbio permission test succeeded')
|
||||
Reference in New Issue
Block a user