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