diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d8f49b8ded7fac2e0786bb4de59116b6082bc09..ce2b80ba44bf89b91e6681c8e8d068636ddce552 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### New
- Added cppcheck code checking capabilities ([!214](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/214))
-
+ - Tests to cover proxy and malleability ([!222](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/222))
+
### Changed
- Tests check ret for -1 instead of 10000 fd ([!320](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/320))
- Allow some more script tests to run as pthread_at_fork solved some issues.
diff --git a/CMakePresets.json b/CMakePresets.json
index 9e61710fa0058c422a331e35da053c0c07cdcb30..f01b9ef0c4b37619f4af33786d557815de33fab7 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -72,6 +72,7 @@
"CMAKE_INSTALL_PREFIX": "${sourceDir}/gkfs/install",
"GKFS_USE_GUIDED_DISTRIBUTION": true,
"GKFS_ENABLE_PARALLAX": false,
+ "GKFS_BUILD_TOOLS": true,
"GKFS_BUILD_TESTS": true,
"GKFS_INSTALL_TESTS": true,
"GKFS_CHUNK_STATS": true,
@@ -142,4 +143,4 @@
]
}
]
-}
+}
\ No newline at end of file
diff --git a/src/proxy/util.cpp b/src/proxy/util.cpp
index 81c3ddba6807038b8ded9fbb846c2314f00ce20e..ac963ba8c22e53340e99947017789583f52c4ced 100644
--- a/src/proxy/util.cpp
+++ b/src/proxy/util.cpp
@@ -83,7 +83,7 @@ load_hostfile(const std::string& lfpath) {
uri.find("na+sm") != std::string::npos) {
PROXY_DATA->use_auto_sm(true);
PROXY_DATA->log()->info(
- "{}() auto_sm detected in daemon hosefile. Enabling it on proxy ...",
+ "{}() auto_sm detected in daemon hostfile. Enabling it on proxy ...",
__func__);
}
diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt
index 3b1db61bef5967edfed01bf7d20900ee34b215d6..1bb3bdf8155a38b304a5ff51b9e93d8abcc8043e 100644
--- a/tests/integration/CMakeLists.txt
+++ b/tests/integration/CMakeLists.txt
@@ -61,6 +61,9 @@ gkfs_enable_python_testing(
${CMAKE_BINARY_DIR}/src/client/
${CMAKE_BINARY_DIR}/tests/integration/harness/
${CMAKE_BINARY_DIR}/examples/gfind/
+ ${CMAKE_BINARY_DIR}/examples/user_library/
+ ${CMAKE_BINARY_DIR}/tools/
+ ${CMAKE_BINARY_DIR}/src/proxy/
LIBRARY_PREFIX_DIRECTORIES ${CMAKE_PREFIX_PATH}
)
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
index 55c5614a4c507d5ba25708d373e941e8b9d11c6a..019cb5cc2624c0ef2abd85b000af78194f11711b 100644
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -34,7 +34,7 @@ from pathlib import Path
from harness.logger import logger, initialize_logging, finalize_logging
from harness.cli import add_cli_options, set_default_log_formatter
from harness.workspace import Workspace, FileCreator
-from harness.gkfs import Daemon, Client, ShellClient, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator
+from harness.gkfs import Daemon, Client, Proxy, ShellClient, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator
from harness.reporter import report_test_status, report_test_headline, report_assertion_pass
def pytest_configure(config):
@@ -122,6 +122,22 @@ def gkfs_daemon_parallaxdb(test_workspace, request):
def gkfs_daemon(request):
return request.getfixturevalue(request.param)
+@pytest.fixture
+def gkfs_daemon_proxy(test_workspace, request):
+ interface = request.config.getoption('--interface')
+ daemon = Daemon(interface, "rocksdb", test_workspace, True)
+
+ yield daemon.run()
+ daemon.shutdown()
+
+@pytest.fixture
+def gkfs_proxy(test_workspace):
+ """
+ Sets up a gekkofs proxy environment.
+ """
+ proxy = Proxy(test_workspace)
+ yield proxy.run()
+ proxy.shutdown()
@pytest.fixture
def gkfs_client(test_workspace):
@@ -133,6 +149,16 @@ def gkfs_client(test_workspace):
return Client(test_workspace)
+@pytest.fixture
+def gkfs_client_proxy(test_workspace):
+ """
+ Sets up a gekkofs client environment so that
+ operations (system calls, library calls, ...) can
+ be requested from a co-running daemon.
+ """
+
+ return Client(test_workspace, True)
+
@pytest.fixture
def gkfs_shell(test_workspace):
"""
@@ -141,6 +167,15 @@ def gkfs_shell(test_workspace):
"""
return ShellClient(test_workspace)
+
+@pytest.fixture
+def gkfs_shell_proxy(test_workspace):
+ """
+ Sets up a gekkofs environment so that shell commands
+ (stat, ls, mkdir, etc.) can be issued to a co-running daemon.
+ """
+
+ return ShellClient(test_workspace,True)
@pytest.fixture
def file_factory(test_workspace):
diff --git a/tests/integration/conftest.template b/tests/integration/conftest.template
index 55c5614a4c507d5ba25708d373e941e8b9d11c6a..019cb5cc2624c0ef2abd85b000af78194f11711b 100644
--- a/tests/integration/conftest.template
+++ b/tests/integration/conftest.template
@@ -34,7 +34,7 @@ from pathlib import Path
from harness.logger import logger, initialize_logging, finalize_logging
from harness.cli import add_cli_options, set_default_log_formatter
from harness.workspace import Workspace, FileCreator
-from harness.gkfs import Daemon, Client, ShellClient, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator
+from harness.gkfs import Daemon, Client, Proxy, ShellClient, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator
from harness.reporter import report_test_status, report_test_headline, report_assertion_pass
def pytest_configure(config):
@@ -122,6 +122,22 @@ def gkfs_daemon_parallaxdb(test_workspace, request):
def gkfs_daemon(request):
return request.getfixturevalue(request.param)
+@pytest.fixture
+def gkfs_daemon_proxy(test_workspace, request):
+ interface = request.config.getoption('--interface')
+ daemon = Daemon(interface, "rocksdb", test_workspace, True)
+
+ yield daemon.run()
+ daemon.shutdown()
+
+@pytest.fixture
+def gkfs_proxy(test_workspace):
+ """
+ Sets up a gekkofs proxy environment.
+ """
+ proxy = Proxy(test_workspace)
+ yield proxy.run()
+ proxy.shutdown()
@pytest.fixture
def gkfs_client(test_workspace):
@@ -133,6 +149,16 @@ def gkfs_client(test_workspace):
return Client(test_workspace)
+@pytest.fixture
+def gkfs_client_proxy(test_workspace):
+ """
+ Sets up a gekkofs client environment so that
+ operations (system calls, library calls, ...) can
+ be requested from a co-running daemon.
+ """
+
+ return Client(test_workspace, True)
+
@pytest.fixture
def gkfs_shell(test_workspace):
"""
@@ -141,6 +167,15 @@ def gkfs_shell(test_workspace):
"""
return ShellClient(test_workspace)
+
+@pytest.fixture
+def gkfs_shell_proxy(test_workspace):
+ """
+ Sets up a gekkofs environment so that shell commands
+ (stat, ls, mkdir, etc.) can be issued to a co-running daemon.
+ """
+
+ return ShellClient(test_workspace,True)
@pytest.fixture
def file_factory(test_workspace):
diff --git a/tests/integration/data/test_truncate.py b/tests/integration/data/test_truncate.py
index b05ca8abf70a88bf742b3fc533bb44a99e01b709..377a818b8322b51977f24852647a231ad684423e 100644
--- a/tests/integration/data/test_truncate.py
+++ b/tests/integration/data/test_truncate.py
@@ -158,3 +158,91 @@ def test_fail_truncate(gkfs_daemon, gkfs_client):
assert ret.statbuf.st_size == buf_length+1
+def test_truncate_proxy(gkfs_daemon_proxy, gkfs_proxy, gkfs_client_proxy):
+ """Testing truncate:
+ 1. create a large file over multiple chunks
+ 2. truncate it in the middle and compare it with a fresh file with equal contents (exactly at chunk border)
+ 3. truncate it again so that in truncates in the middle of the chunk and compare with fresh file
+ TODO chunksize needs to be respected to make sure chunk border and in the middle of chunk truncates are honored
+ """
+ truncfile = gkfs_daemon_proxy.mountdir / "trunc_file"
+
+ # open and create test file
+ ret = gkfs_client_proxy.open(truncfile, os.O_CREAT | os.O_WRONLY, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ # write a multi MB file (16mb)
+ buf_length = 16777216
+ ret = gkfs_client_proxy.write_random(truncfile, buf_length)
+
+ assert ret.retval == buf_length
+
+ ret = gkfs_client_proxy.stat(truncfile)
+
+ assert ret.statbuf.st_size == buf_length
+
+ # truncate it
+ # split exactly in the middle
+ trunc_size = buf_length // 2
+ ret = gkfs_client_proxy.truncate(truncfile, trunc_size)
+
+ assert ret.retval == 0
+ # check file length
+ ret = gkfs_client_proxy.stat(truncfile)
+
+ assert ret.statbuf.st_size == trunc_size
+
+ # verify contents by writing a new file (random content is seeded) and checksum both
+ truncfile_verify = gkfs_daemon_proxy.mountdir / "trunc_file_verify"
+
+ # open and create test file
+ ret = gkfs_client_proxy.open(truncfile_verify, os.O_CREAT | os.O_WRONLY, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ # write trunc_size of data to new file
+ ret = gkfs_client_proxy.write_random(truncfile_verify, trunc_size)
+
+ assert ret.retval == trunc_size
+
+ ret = gkfs_client_proxy.stat(truncfile_verify)
+
+ assert ret.statbuf.st_size == trunc_size
+
+ ret = gkfs_client_proxy.file_compare(truncfile, truncfile_verify, trunc_size)
+
+ assert ret.retval == 0
+
+ # trunc at byte 712345 (middle of chunk)
+ # TODO feed chunksize into test to make sure it is always in the middle of the chunk
+ trunc_size = 712345
+ ret = gkfs_client_proxy.truncate(truncfile, trunc_size)
+
+ assert ret.retval == 0
+
+ # check file length
+ ret = gkfs_client_proxy.stat(truncfile)
+
+ assert ret.statbuf.st_size == trunc_size
+
+ # verify contents by writing a new file (random content is seeded) and checksum both
+ truncfile_verify_2 = gkfs_daemon_proxy.mountdir / "trunc_file_verify_2"
+
+ # open and create test file
+ ret = gkfs_client_proxy.open(truncfile_verify_2, os.O_CREAT | os.O_WRONLY, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ # write trunc_size of data to new file
+ ret = gkfs_client_proxy.write_random(truncfile_verify_2, trunc_size)
+
+ assert ret.retval == trunc_size
+
+ ret = gkfs_client_proxy.stat(truncfile_verify_2)
+
+ assert ret.statbuf.st_size == trunc_size
+
+ ret = gkfs_client_proxy.file_compare(truncfile, truncfile_verify_2, trunc_size)
+
+ assert ret.retval == 0
\ No newline at end of file
diff --git a/tests/integration/directories/test_directories.py b/tests/integration/directories/test_directories.py
index a5148c5e5ec7bfe82560f7084e4dea241a71e4d4..c6c08b541d5263a2ba94bdff71ec65171e1d412b 100644
--- a/tests/integration/directories/test_directories.py
+++ b/tests/integration/directories/test_directories.py
@@ -299,3 +299,89 @@ def test_opendir(gkfs_daemon, gkfs_client, directory_path):
assert ret.dirp is None
assert ret.errno == errno.ENOENT
+def test_finedir_proxy(gkfs_daemon_proxy, gkfs_proxy, gkfs_client_proxy):
+ """Tests several corner cases for directories scan"""
+
+ topdir = gkfs_daemon_proxy.mountdir / "finetop"
+ file_a = topdir / "file_"
+
+
+ # create topdir
+ ret = gkfs_client_proxy.mkdir(
+ topdir,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval == 0
+
+ ret = gkfs_client_proxy.readdir(topdir)
+
+ # XXX: This might change in the future if we add '.' and '..'
+ assert len(ret.dirents) == 0
+
+ # populate top directory
+
+ for files in range (1,4):
+ ret = gkfs_client_proxy.directory_validate(
+ topdir, 1)
+ assert ret.retval == files
+
+
+ ret = gkfs_client_proxy.directory_validate(
+ topdir, 1000)
+ assert ret.retval == 1000+3
+
+
+def test_extended_proxy(gkfs_daemon_proxy, gkfs_proxy, gkfs_shell_proxy, gkfs_client_proxy):
+ topdir = gkfs_daemon_proxy.mountdir / "test_extended"
+ dir_a = topdir / "dir_a"
+ dir_b = topdir / "dir_b"
+ file_a = topdir / "file_a"
+ subdir_a = dir_a / "subdir_a"
+
+ # create topdir
+ ret = gkfs_client_proxy.mkdir(
+ topdir,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval == 0
+
+ ret = gkfs_client_proxy.mkdir(
+ dir_a,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval == 0
+
+ ret = gkfs_client_proxy.mkdir(
+ dir_b,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval == 0
+
+ ret = gkfs_client_proxy.mkdir(
+ subdir_a,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval == 0
+
+ ret = gkfs_client_proxy.open(file_a,
+ os.O_CREAT,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+ assert ret.retval != -1
+
+ buf = b'42'
+ ret = gkfs_client_proxy.write(file_a, buf, 1)
+
+ assert ret.retval == 1
+
+ cmd = gkfs_shell_proxy.sfind(
+ topdir,
+ '-M',
+ gkfs_daemon_proxy.mountdir,
+ '-S',
+ 1,
+ '-name',
+ '*_k*'
+ )
+
+ assert cmd.exit_code == 0
+ assert cmd.stdout.decode() == "MATCHED 0/4\n"
\ No newline at end of file
diff --git a/tests/integration/forwarding/test_map.py b/tests/integration/forwarding/test_map.py
index 92eae8fa5430db091a66f646dc0735491f7eb7b7..0ba66184e53af1a094fe6ea772846e4c2f950b0f 100644
--- a/tests/integration/forwarding/test_map.py
+++ b/tests/integration/forwarding/test_map.py
@@ -138,6 +138,10 @@ def test_two_io_nodes(gkfwd_daemon_factory, gkfwd_client_factory):
assert ion == '1'
+ d00.shutdown()
+ d01.shutdown()
+
+
def test_two_io_nodes_remap(gkfwd_daemon_factory, gkfwd_client_factory):
"""Write files from two clients using two daemons"""
@@ -201,6 +205,8 @@ def test_two_io_nodes_remap(gkfwd_daemon_factory, gkfwd_client_factory):
ion = line.split()[-1]
assert ion == '1'
+ d00.shutdown()
+ d01.shutdown()
def test_two_io_nodes_operations(gkfwd_daemon_factory, gkfwd_client_factory):
"""Write files from one client and read in the other using two daemons"""
@@ -284,4 +290,6 @@ def test_two_io_nodes_operations(gkfwd_daemon_factory, gkfwd_client_factory):
if 'Forward to' in line:
ion = line.split()[-1]
- assert ion == '1'
\ No newline at end of file
+ assert ion == '1'
+ d00.shutdown()
+ d01.shutdown()
\ No newline at end of file
diff --git a/tests/integration/harness/gkfs.py b/tests/integration/harness/gkfs.py
index 67846ec83f40ec5e8f7b69edbab65c6bf03ca173..7a5460679ccb3259c639cbcd0307dce898f78e0d 100644
--- a/tests/integration/harness/gkfs.py
+++ b/tests/integration/harness/gkfs.py
@@ -48,6 +48,12 @@ gkfs_client_log_level = 'all'
gkfs_client_log_syscall_filter = 'epoll_wait,epoll_create'
gkfs_daemon_active_log_pattern = r'Startup successful. Daemon is ready.'
+gkfs_proxy_cmd = 'gkfs_proxy'
+gkfs_proxy_pid = 'gkfs_proxy.pid'
+gkfs_proxy_log_file = 'gkfs_proxy.log'
+gkfs_proxy_log_level = '100'
+gkfs_proxy_active_log_pattern = r'Initializing environment'
+
gkfwd_daemon_cmd = 'gkfs_daemon'
gkfwd_client_cmd = 'gkfs.io'
gkfwd_client_lib_file = 'libgkfs_intercept.so'
@@ -225,7 +231,7 @@ class FwdClientCreator:
class Daemon:
- def __init__(self, interface, database, workspace):
+ def __init__(self, interface, database, workspace, proxy = False):
self._address = get_ephemeral_address(interface)
self._workspace = workspace
@@ -233,6 +239,8 @@ class Daemon:
self._cmd = sh.Command(gkfs_daemon_cmd, self._workspace.bindirs)
self._env = os.environ.copy()
self._metadir = self.rootdir
+ self._proxy = proxy
+
libdirs = ':'.join(
filter(None, [os.environ.get('LD_LIBRARY_PATH', '')] +
[str(p) for p in self._workspace.libdirs]))
@@ -257,6 +265,9 @@ class Daemon:
'--enable-chunkstats']
if self._database == "parallaxdb" :
args.append('--clean-rootdir-finish')
+ if self._proxy == True:
+ args.append('--proxy-protocol')
+ args.append('ofi+sockets')
logger.debug(f"spawning daemon")
logger.debug(f"cmdline: {self._cmd} " + " ".join(map(str, args)))
@@ -367,6 +378,136 @@ class Daemon:
def interface(self):
return self._interface
+class Proxy:
+ def __init__(self, workspace):
+ self._parser = IOParser()
+ self._workspace = workspace
+ self._cmd = sh.Command(gkfs_proxy_cmd, self._workspace.bindirs)
+ self._env = os.environ.copy()
+
+ libdirs = ':'.join(
+ filter(None, [os.environ.get('LD_LIBRARY_PATH', '')] +
+ [str(p) for p in self._workspace.libdirs]))
+
+ self._patched_env = {
+ 'LD_LIBRARY_PATH' : libdirs,
+ 'GKFS_PROXY_LOG_PATH' : str(self.logdir / gkfs_proxy_log_file),
+ 'GKFS_PROXY_LOG_LEVEL': gkfs_proxy_log_level
+ }
+ self._env.update(self._patched_env)
+
+
+ def run(self):
+
+ args = ['-H', str(self.cwd / gkfs_hosts_file),
+ '--pid-path', str(self.cwd / gkfs_proxy_pid),
+ '-p', 'ofi+sockets']
+
+
+ logger.debug(f"spawning proxy")
+ logger.debug(f"cmdline: {self._cmd} " + " ".join(map(str, args)))
+ logger.debug(f"patched env:\n{pformat(self._patched_env)}")
+
+ self._proc = self._cmd(
+ args,
+ _env=self._env,
+ _out=sys.stdout,
+ _err=sys.stderr,
+ _bg=True,
+ )
+
+ logger.debug(f"proxy process spawned (PID={self._proc.pid})")
+ logger.debug("waiting for proxy to be ready")
+
+ try:
+ self.wait_until_active(self._proc.pid, 720.0)
+ except Exception as ex:
+ logger.error(f"proxy initialization failed: {ex}")
+
+ # if the daemon initialized correctly but took longer than
+ # `timeout`, we may be leaving running processes behind
+ if _process_exists(self._proc.pid):
+ self.shutdown()
+
+ logger.critical(f"proxy was shut down, what is ex? {ex.__repr__}?")
+ raise ex
+
+ logger.debug("proxy is ready")
+ return self
+
+
+
+ def wait_until_active(self, pid, timeout, max_lines=50):
+ """
+ Waits until a GKFS daemon is active or until a certain timeout
+ has expired. Checks if the daemon is running by searching its
+ log for a pre-defined readiness message.
+
+ Parameters
+ ----------
+ pid: `int`
+ The PID of the daemon process to wait for.
+
+ timeout: `number`
+ The number of seconds to wait for
+
+ max_lines: `int`
+ The maximum number of log lines to check for a match.
+ """
+
+
+
+ init_time = perf_counter()
+
+ while perf_counter() - init_time < timeout:
+ try:
+ # logger.debug(f"checking log file")
+ with open(self.logdir / gkfs_proxy_log_file) as log:
+ for line in islice(log, max_lines):
+ if re.search(gkfs_proxy_active_log_pattern, line) is not None:
+ return
+ except FileNotFoundError:
+ # Log is missing, the daemon might have crashed...
+ logger.debug(f"proxy log file missing, checking if daemon is alive...")
+
+ pid=self._proc.pid
+
+ if not _process_exists(pid):
+ raise RuntimeError(f"process {pid} is not running")
+
+ # ... or it might just be lazy. let's give it some more time
+ logger.debug(f"proxy {pid} found, retrying...")
+ time.sleep(1)
+ raise RuntimeError("initialization timeout exceeded")
+
+ def shutdown(self):
+ logger.debug(f"terminating proxy")
+ try:
+ self._proc.terminate()
+ err = self._proc.wait()
+ except sh.SignalException_SIGTERM:
+ pass
+ except Exception:
+ raise
+
+
+ @property
+ def cwd(self):
+ return self._workspace.twd
+
+ @property
+ def rootdir(self):
+ return self._workspace.rootdir
+
+ @property
+ def logdir(self):
+ return self._workspace.logdir
+
+ @property
+ def interface(self):
+ return self._interface
+
+
class _proxy_exec():
def __init__(self, client, name):
self._client = client
@@ -383,11 +524,12 @@ class Client:
function calls, be them system calls (e.g. read()) or glibc I/O functions
(e.g. opendir()).
"""
- def __init__(self, workspace):
+ def __init__(self, workspace, proxy = False):
self._parser = IOParser()
self._workspace = workspace
self._cmd = sh.Command(gkfs_client_cmd, self._workspace.bindirs)
self._env = os.environ.copy()
+ self._proxy = proxy
libdirs = ':'.join(
filter(None, [os.environ.get('LD_LIBRARY_PATH', '')] +
@@ -424,6 +566,9 @@ class Client:
'LIBGKFS_LOG_SYSCALL_FILTER': gkfs_client_log_syscall_filter
}
+ if (self._proxy == True):
+ self._patched_env['LIBGKFS_PROXY_PID_FILE'] = str(self.cwd / gkfs_proxy_pid)
+
self._env.update(self._patched_env)
@property
@@ -489,10 +634,11 @@ class ShellClient:
on a GekkoFS instance.
"""
- def __init__(self, workspace):
+ def __init__(self, workspace, proxy = False):
self._workspace = workspace
self._search_paths = _find_search_paths(self._workspace.bindirs)
self._env = os.environ.copy()
+ self._proxy = proxy
libdirs = ':'.join(
filter(None, [os.environ.get('LD_LIBRARY_PATH', '')] +
@@ -525,6 +671,9 @@ class ShellClient:
'LIBGKFS_LOG_SYSCALL_FILTER': gkfs_client_log_syscall_filter
}
+ if (self._proxy == True):
+ self._patched_env['LIBGKFS_PROXY_PID_FILE'] = str(self.cwd / gkfs_proxy_pid)
+
self._env.update(self._patched_env)
@property
@@ -713,7 +862,7 @@ class FwdDaemon:
self._address = get_ephemeral_address(interface)
self._workspace = workspace
-
+ self._hostfile = str(self.cwd / gkfwd_hosts_file)
self._cmd = sh.Command(gkfwd_daemon_cmd, self._workspace.bindirs)
self._env = os.environ.copy()
@@ -848,6 +997,9 @@ class FwdDaemon:
def interface(self):
return self._interface
+ @property
+ def hostfile(self):
+ return self._hostfile
class FwdClient:
"""
diff --git a/tests/integration/operations/test_read_operations.py b/tests/integration/operations/test_read_operations.py
index d3635465e04c2f83bc5c91e71103b2fc5ecfe387..cdc670da6930fd117dc15616ad15c4079dcb2bcb 100644
--- a/tests/integration/operations/test_read_operations.py
+++ b/tests/integration/operations/test_read_operations.py
@@ -39,7 +39,6 @@ from harness.logger import logger
nonexisting = "nonexisting"
-
def test_read(gkfs_daemon, gkfs_client):
file = gkfs_daemon.mountdir / "file"
@@ -70,6 +69,36 @@ def test_read(gkfs_daemon, gkfs_client):
assert ret.buf == buf
assert ret.retval == len(buf) # Return the number of read bytes
+
+def test_read_proxy(gkfs_daemon_proxy, gkfs_proxy, gkfs_client_proxy):
+ file = gkfs_daemon_proxy.mountdir / "file_read_proxy"
+
+ # create a file in gekkofs
+ ret = gkfs_client_proxy.open(file,
+ os.O_CREAT | os.O_WRONLY,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ # write a buffer we know
+ buf = b'42'
+ ret = gkfs_client_proxy.write(file, buf, len(buf))
+
+ assert ret.retval == len(buf) # Return the number of written bytes
+
+ # open the file to read
+ ret = gkfs_client_proxy.open(file,
+ os.O_RDONLY,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ # read the file
+ ret = gkfs_client_proxy.read(file, len(buf))
+
+ assert ret.buf == buf
+ assert ret.retval == len(buf) # Return the number of read bytes
+
def test_pread(gkfs_daemon, gkfs_client):
file = gkfs_daemon.mountdir / "file"
diff --git a/tests/integration/operations/test_unlink_operations.py b/tests/integration/operations/test_unlink_operations.py
index 22cff1bc4d081657c88db6b2a37e118b0d661837..f9ea326d7918b2bfe16d2ded46ce5290e75fd555 100644
--- a/tests/integration/operations/test_unlink_operations.py
+++ b/tests/integration/operations/test_unlink_operations.py
@@ -38,8 +38,6 @@ from harness.logger import logger
nonexisting = "nonexisting"
-
-
def test_unlink(gkfs_daemon, gkfs_client):
file = gkfs_daemon.mountdir / "file"
@@ -97,3 +95,62 @@ def test_unlink(gkfs_daemon, gkfs_client):
ret = gkfs_client.unlink(file) # Remove renamed file (extra chunks, success)
assert ret.retval == 0
+
+
+def test_unlink_proxy(gkfs_daemon_proxy, gkfs_proxy, gkfs_client_proxy):
+
+ file = gkfs_daemon_proxy.mountdir / "file_proxy"
+ dir = gkfs_daemon_proxy.mountdir / "dir_proxy"
+
+
+ # Delete an unexistent file
+ ret = gkfs_client_proxy.unlink(file)
+ assert ret.retval == -1
+ assert ret.errno == errno.ENOENT
+
+
+
+
+
+ # create a file in gekkofs
+ ret = gkfs_client_proxy.open(file,
+ os.O_CREAT | os.O_WRONLY,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ # write a buffer we know
+ buf = b'42'
+ ret = gkfs_client_proxy.write(file, buf, len(buf))
+ assert ret.retval == len(buf) # Return the number of written bytes
+
+
+
+ ret = gkfs_client_proxy.unlink(file) # Remove renamed file (success)
+ assert ret.retval == 0
+
+ ret = gkfs_client_proxy.stat(file) # file does not exist
+ assert ret.retval != 0
+ assert ret.errno == errno.ENOENT
+
+ ret = gkfs_client_proxy.mkdir(dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # Create a directory
+ assert ret.retval == 0
+
+ ret = gkfs_client_proxy.unlink(dir)
+ assert ret.retval == -1
+ assert ret.errno == errno.EISDIR
+
+
+ # create a file in gekkofs
+ ret = gkfs_client_proxy.open(file,
+ os.O_CREAT | os.O_WRONLY,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ # > 4 chunks
+ ret = gkfs_client_proxy.write_validate(file, 2097153)
+ assert ret.retval == 1
+
+ ret = gkfs_client_proxy.unlink(file) # Remove renamed file (extra chunks, success)
+ assert ret.retval == 0
\ No newline at end of file
diff --git a/tests/integration/operations/test_write_operations.py b/tests/integration/operations/test_write_operations.py
index 42c62c28df65e306a4db8fea8cdaf7ceb6f1bfcc..49799ce5583f8fbe924a8f5b71bf2cba547f8db3 100644
--- a/tests/integration/operations/test_write_operations.py
+++ b/tests/integration/operations/test_write_operations.py
@@ -39,10 +39,9 @@ from harness.logger import logger
nonexisting = "nonexisting"
-
def test_write(gkfs_daemon, gkfs_client):
- file = gkfs_daemon.mountdir / "file"
+ file = gkfs_daemon.mountdir / "file_write"
ret = gkfs_client.open(file,
os.O_CREAT | os.O_WRONLY,
@@ -85,6 +84,50 @@ def test_write(gkfs_daemon, gkfs_client):
assert ret.retval == 0
assert ret.statbuf.st_size == (len(str1) + len(str2) + len(str3))
+def test_write_proxy(gkfs_daemon_proxy, gkfs_proxy, gkfs_client_proxy):
+
+ file = gkfs_daemon_proxy.mountdir / "file_write_proxy"
+
+ ret = gkfs_client_proxy.open(file,
+ os.O_CREAT | os.O_WRONLY,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ buf = b'42'
+ ret = gkfs_client_proxy.write(file, buf, len(buf))
+
+ assert ret.retval == len(buf) # Return the number of written bytes
+
+ file_append = gkfs_daemon_proxy.mountdir / "file_append"
+
+ ret = gkfs_client_proxy.open(file_append,
+ os.O_CREAT | os.O_WRONLY | os.O_APPEND,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ str1 = b'Hello'
+ str2 = b', World!'
+ str3 = b' This is a test.\n'
+
+ ret = gkfs_client_proxy.write(file_append, str1, len(str1), True)
+ assert ret.retval == len(str1)
+ ret = gkfs_client_proxy.stat(file_append)
+ assert ret.retval == 0
+ assert ret.statbuf.st_size == len(str1)
+
+ ret = gkfs_client_proxy.write(file_append, str2, len(str2), True)
+ assert ret.retval == len(str2)
+ ret = gkfs_client_proxy.stat(file_append)
+ assert ret.retval == 0
+ assert ret.statbuf.st_size == (len(str1) + len(str2))
+
+ ret = gkfs_client_proxy.write(file_append, str3, len(str3), True)
+ assert ret.retval == len(str3)
+ ret = gkfs_client_proxy.stat(file_append)
+ assert ret.retval == 0
+ assert ret.statbuf.st_size == (len(str1) + len(str2) + len(str3))
def test_pwrite(gkfs_daemon, gkfs_client):
file = gkfs_daemon.mountdir / "file"
@@ -101,7 +144,6 @@ def test_pwrite(gkfs_daemon, gkfs_client):
assert ret.retval == len(buf) # Return the number of written bytes
-
def test_writev(gkfs_daemon, gkfs_client):
file = gkfs_daemon.mountdir / "file"
@@ -117,7 +159,6 @@ def test_writev(gkfs_daemon, gkfs_client):
assert ret.retval == len(buf_0) + len(buf_1) # Return the number of written bytes
-
def test_pwritev(gkfs_daemon, gkfs_client):
file = gkfs_daemon.mountdir / "file"
diff --git a/tests/integration/syscalls/test_malleability.py b/tests/integration/syscalls/test_malleability.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce33360138eb032943784809117e50069d74c402
--- /dev/null
+++ b/tests/integration/syscalls/test_malleability.py
@@ -0,0 +1,94 @@
+################################################################################
+# Copyright 2018-2025, Barcelona Supercomputing Center (BSC), Spain #
+# Copyright 2015-2025, Johannes Gutenberg Universitaet Mainz, Germany #
+# #
+# This software was partially supported by the #
+# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). #
+# #
+# This software was partially supported by the #
+# ADA-FS project under the SPPEXA project funded by the DFG. #
+# #
+# This file is part of GekkoFS. #
+# #
+# GekkoFS is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# GekkoFS is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with GekkoFS. If not, see . #
+# #
+# SPDX-License-Identifier: GPL-3.0-or-later #
+################################################################################
+
+import harness
+from pathlib import Path
+import errno
+import stat
+import os
+import ctypes
+import sh
+import sys
+import pytest
+from harness.logger import logger
+
+nonexisting = "nonexisting"
+
+def test_malleability(gkfwd_daemon_factory, gkfs_client, gkfs_shell):
+ import time
+ d00 = gkfwd_daemon_factory.create()
+ # Add "#FS_INSTANCE_END" in the file with name d00.hostfile
+ d01 = gkfwd_daemon_factory.create()
+
+ time.sleep(10)
+ with open(d00.hostfile, 'a') as f:
+ f.write("#FS_INSTANCE_END\n")
+
+ # loop 10 times, and create a file in each iteration
+
+ for i in range(3):
+ file = d00.mountdir / f"file{i}"
+ # create a file in gekkofs
+ ret = gkfs_client.open(file,
+ os.O_CREAT | os.O_WRONLY,
+ stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ assert ret.retval != -1
+
+ ret = gkfs_client.write_validate(file, 64096)
+ assert ret.retval == 1
+
+ # Create content
+
+ d02 = gkfwd_daemon_factory.create()
+ d03 = gkfwd_daemon_factory.create()
+
+ cmd = gkfs_shell.gkfs_malleability('expand','status')
+ assert cmd.exit_code == 0
+ assert cmd.stdout.decode() == "No expansion running/finished.\n"
+
+ cmd = gkfs_shell.gkfs_malleability('expand','start')
+ assert cmd.stdout.decode() == "Expansion process from 2 nodes to 4 nodes launched...\n"
+ assert cmd.exit_code == 0
+
+ while True:
+ cmd = gkfs_shell.gkfs_malleability('expand','status')
+ if cmd.stdout.decode() == "No expansion running/finished.\n":
+ break
+ time.sleep(1)
+
+ cmd = gkfs_shell.gkfs_malleability('expand','finalize')
+ assert cmd.stdout.decode() == "Expand finalize 0\n"
+ assert cmd.exit_code == 0
+
+
+ d00.shutdown()
+ d01.shutdown()
+ d02.shutdown()
+ d03.shutdown()
+
\ No newline at end of file
diff --git a/tests/integration/syscalls/test_userlibrary.py b/tests/integration/syscalls/test_userlibrary.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd291a5d4597bb642e136726627b839b3ac820ed
--- /dev/null
+++ b/tests/integration/syscalls/test_userlibrary.py
@@ -0,0 +1,46 @@
+################################################################################
+# Copyright 2018-2025, Barcelona Supercomputing Center (BSC), Spain #
+# Copyright 2015-2025, Johannes Gutenberg Universitaet Mainz, Germany #
+# #
+# This software was partially supported by the #
+# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). #
+# #
+# This software was partially supported by the #
+# ADA-FS project under the SPPEXA project funded by the DFG. #
+# #
+# This file is part of GekkoFS. #
+# #
+# GekkoFS is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# GekkoFS is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with GekkoFS. If not, see . #
+# #
+# SPDX-License-Identifier: GPL-3.0-or-later #
+################################################################################
+
+import harness
+from pathlib import Path
+import errno
+import stat
+import os
+import ctypes
+import sh
+import sys
+import pytest
+from harness.logger import logger
+
+nonexisting = "nonexisting"
+
+def test_user(gkfs_daemon, gkfs_shell):
+
+ file = gkfs_daemon.mountdir / "file"
+
+ cmd = gkfs_shell.gkfs_lib_example()
\ No newline at end of file