Skip to content
Commits on Source (9)
...@@ -203,6 +203,18 @@ include_directories( ...@@ -203,6 +203,18 @@ include_directories(
) )
include(GNUInstallDirs) include(GNUInstallDirs)
include(CheckSymbolExists)
check_cxx_source_compiles("
#include <fcntl.h>
#include <sys/stat.h>
int main() {
struct statx buf;
statx(AT_FDCWD, \"/foo\", AT_EMPTY_PATH, STATX_BASIC_STATS, &buf);
return 0;
}
" GLIBC_HAS_STATX)
# Global components # Global components
add_subdirectory(src/global) add_subdirectory(src/global)
......
...@@ -41,7 +41,9 @@ int gkfs_stat(const std::string& path, struct stat* buf, bool follow_links = tru ...@@ -41,7 +41,9 @@ int gkfs_stat(const std::string& path, struct stat* buf, bool follow_links = tru
// Implementation of statx, it uses the normal stat and maps the information to the statx structure // Implementation of statx, it uses the normal stat and maps the information to the statx structure
// Follow links is true by default // Follow links is true by default
#ifdef STATX_TYPE
int gkfs_statx(int dirfd, const std::string& path, int flags, unsigned int mask,struct statx* buf, bool follow_links = true ); int gkfs_statx(int dirfd, const std::string& path, int flags, unsigned int mask,struct statx* buf, bool follow_links = true );
#endif
int gkfs_statfs(struct statfs* buf); int gkfs_statfs(struct statfs* buf);
......
...@@ -30,7 +30,9 @@ int hook_close(int fd); ...@@ -30,7 +30,9 @@ int hook_close(int fd);
int hook_stat(const char* path, struct stat* buf); int hook_stat(const char* path, struct stat* buf);
#ifdef STATX_TYPE
int hook_statx(int dirfd, const char* path, int flags, unsigned int mask,struct statx* buf); int hook_statx(int dirfd, const char* path, int flags, unsigned int mask,struct statx* buf);
#endif
int hook_lstat(const char* path, struct stat* buf); int hook_lstat(const char* path, struct stat* buf);
...@@ -103,6 +105,8 @@ int hook_statfs(const char* path, struct statfs* buf); ...@@ -103,6 +105,8 @@ int hook_statfs(const char* path, struct statfs* buf);
int hook_fstatfs(unsigned int fd, struct statfs* buf); int hook_fstatfs(unsigned int fd, struct statfs* buf);
int hook_fsync(unsigned int fd);
} // namespace hook } // namespace hook
} // namespace gkfs } // namespace gkfs
......
...@@ -232,7 +232,7 @@ int gkfs_stat(const string& path, struct stat* buf, bool follow_links) { ...@@ -232,7 +232,7 @@ int gkfs_stat(const string& path, struct stat* buf, bool follow_links) {
return 0; return 0;
} }
#ifdef STATX_TYPE
int gkfs_statx(int dirfs, const std::string& path, int flags, unsigned int mask, struct statx* buf, bool follow_links) { int gkfs_statx(int dirfs, const std::string& path, int flags, unsigned int mask, struct statx* buf, bool follow_links) {
auto md = gkfs::util::get_metadata(path, follow_links); auto md = gkfs::util::get_metadata(path, follow_links);
if (!md) { if (!md) {
...@@ -268,6 +268,7 @@ int gkfs_statx(int dirfs, const std::string& path, int flags, unsigned int mask, ...@@ -268,6 +268,7 @@ int gkfs_statx(int dirfs, const std::string& path, int flags, unsigned int mask,
return 0; return 0;
} }
#endif
int gkfs_statfs(struct statfs* buf) { int gkfs_statfs(struct statfs* buf) {
auto blk_stat = gkfs::rpc::forward_get_chunk_stat(); auto blk_stat = gkfs::rpc::forward_get_chunk_stat();
......
...@@ -98,7 +98,7 @@ int hook_stat(const char* path, struct stat* buf) { ...@@ -98,7 +98,7 @@ int hook_stat(const char* path, struct stat* buf) {
return syscall_no_intercept(SYS_stat, rel_path.c_str(), buf); return syscall_no_intercept(SYS_stat, rel_path.c_str(), buf);
} }
#ifdef STATX_TYPE
int hook_statx(int dirfd, const char* path, int flags, unsigned int mask, struct ::statx* buf) { int hook_statx(int dirfd, const char* path, int flags, unsigned int mask, struct ::statx* buf) {
LOG(DEBUG, "{}() called with dirfd: '{}', path: \"{}\", flags: '{}', mask: '{}', buf: '{}'", LOG(DEBUG, "{}() called with dirfd: '{}', path: \"{}\", flags: '{}', mask: '{}', buf: '{}'",
...@@ -127,9 +127,7 @@ int hook_statx(int dirfd, const char* path, int flags, unsigned int mask, struct ...@@ -127,9 +127,7 @@ int hook_statx(int dirfd, const char* path, int flags, unsigned int mask, struct
return syscall_no_intercept(SYS_statx, dirfd, path, flags, mask, buf); return syscall_no_intercept(SYS_statx, dirfd, path, flags, mask, buf);
} }
#endif
int hook_lstat(const char* path, struct stat* buf) { int hook_lstat(const char* path, struct stat* buf) {
...@@ -816,5 +814,19 @@ int hook_fstatfs(unsigned int fd, struct statfs* buf) { ...@@ -816,5 +814,19 @@ int hook_fstatfs(unsigned int fd, struct statfs* buf) {
return syscall_no_intercept(SYS_fstatfs, fd, buf); return syscall_no_intercept(SYS_fstatfs, fd, buf);
} }
/* The function should broadcast a flush message (pmem_persist i.e.) if the application needs the capabilities*/
int hook_fsync(unsigned int fd) {
LOG(DEBUG, "{}() called with fd: {}",
__func__, fd);
if (CTX->file_map()->exist(fd)) {
errno = 0;
return 0;
}
return syscall_no_intercept(SYS_fsync, fd);
}
} // namespace hook } // namespace hook
} // namespace gkfs } // namespace gkfs
...@@ -466,7 +466,7 @@ int hook(long syscall_number, ...@@ -466,7 +466,7 @@ int hook(long syscall_number,
reinterpret_cast<struct stat*>(arg1)); reinterpret_cast<struct stat*>(arg1));
break; break;
#ifdef SYS_statx #ifdef STATX_TYPE
case SYS_statx: case SYS_statx:
*result = gkfs::hook::hook_statx(static_cast<int>(arg0), *result = gkfs::hook::hook_statx(static_cast<int>(arg0),
reinterpret_cast<char*>(arg1), reinterpret_cast<char*>(arg1),
...@@ -726,6 +726,10 @@ int hook(long syscall_number, ...@@ -726,6 +726,10 @@ int hook(long syscall_number,
reinterpret_cast<struct statfs*>(arg1)); reinterpret_cast<struct statfs*>(arg1));
break; break;
case SYS_fsync:
*result = gkfs::hook::hook_fsync(static_cast<unsigned int>(arg0));
break;
default: default:
// ignore any other syscalls, i.e.: pass them on to the kernel // ignore any other syscalls, i.e.: pass them on to the kernel
// (syscalls forwarded to the kernel that return are logged in // (syscalls forwarded to the kernel that return are logged in
......
...@@ -16,12 +16,14 @@ gkfs_add_python_test( ...@@ -16,12 +16,14 @@ gkfs_add_python_test(
SOURCE directories/test_directories.py SOURCE directories/test_directories.py
) )
gkfs_add_python_test( if (GLIBC_HAS_STATX)
NAME test_status gkfs_add_python_test(
PYTHON_VERSION 3.6 NAME test_status
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integration PYTHON_VERSION 3.6
SOURCE status/test_status.py WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integration
) SOURCE status/test_status.py
)
endif()
gkfs_add_python_test( gkfs_add_python_test(
NAME test_operations NAME test_operations
...@@ -44,6 +46,15 @@ gkfs_add_python_test( ...@@ -44,6 +46,15 @@ gkfs_add_python_test(
SOURCE shell/ SOURCE shell/
) )
gkfs_add_python_test(
NAME test_data
PYTHON_VERSION 3.6
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integration
SOURCE data/
)
if(GKFS_INSTALL_TESTS) if(GKFS_INSTALL_TESTS)
install(DIRECTORY harness install(DIRECTORY harness
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration
...@@ -87,6 +98,14 @@ if(GKFS_INSTALL_TESTS) ...@@ -87,6 +98,14 @@ if(GKFS_INSTALL_TESTS)
) )
install(DIRECTORY data
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration
FILES_MATCHING
REGEX ".*\\.py"
PATTERN "__pycache__" EXCLUDE
PATTERN ".pytest_cache" EXCLUDE
)
install(DIRECTORY shell install(DIRECTORY shell
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration
FILES_MATCHING FILES_MATCHING
......
# README
This directory contains functional tests for any data-related
functionalities in GekkoFS.
################################################################################
# Copyright 2018-2020, Barcelona Supercomputing Center (BSC), Spain #
# Copyright 2015-2020, 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. #
# #
# SPDX-License-Identifier: MIT #
################################################################################
import harness
from pathlib import Path
import errno
import stat
import os
import ctypes
import sh
import sys
import pytest
import string
import random
from harness.logger import logger
nonexisting = "nonexisting"
chunksize_start = 128192
chunksize_end = 2097153
step = 4096*9
def generate_random_data(size):
return ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(size)])
#@pytest.mark.xfail(reason="invalid errno returned on success")
def test_data_integrity(gkfs_daemon, gkfs_client):
"""Test several data write-read commands and check that the data is correct"""
topdir = gkfs_daemon.mountdir / "top"
file_a = topdir / "file_a"
# create topdir
ret = gkfs_client.mkdir(
topdir,
stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
assert ret.retval == 0
assert ret.errno == 115 #FIXME: Should be 0!
# test statx on existing dir
ret = gkfs_client.statx(0, topdir, 0, 0)
assert ret.retval == 0
assert ret.errno == 115 #FIXME: Should be 0!
assert stat.S_ISDIR(ret.statbuf.stx_mode)
ret = gkfs_client.open(file_a,
os.O_CREAT,
stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
assert ret.retval != -1
# test statx on existing file
ret = gkfs_client.statx(0, file_a, 0, 0)
assert ret.retval == 0
assert (stat.S_ISDIR(ret.statbuf.stx_mode)==0)
assert (ret.statbuf.stx_size == 0)
# Step 1 - small sizes
# Generate writes
# Read data
# Compare buffer
for i in range (1, 512, 64):
buf = bytes(generate_random_data(i), sys.stdout.encoding)
ret = gkfs_client.write(file_a, buf, i)
assert ret.retval == i
ret = gkfs_client.statx(0, file_a, 0, 0)
assert ret.retval == 0
assert (ret.statbuf.stx_size == i)
ret = gkfs_client.read(file_a, i)
assert ret.retval== i
assert ret.buf == buf
# Step 2 - Compare bigger sizes exceeding typical chunksize
for i in range (chunksize_start, chunksize_end, step):
ret = gkfs_client.write_validate(file_a, i)
assert ret.retval == 1
return
...@@ -26,6 +26,7 @@ add_executable(gkfs.io ...@@ -26,6 +26,7 @@ add_executable(gkfs.io
gkfs.io/pwritev.cpp gkfs.io/pwritev.cpp
gkfs.io/statx.cpp gkfs.io/statx.cpp
gkfs.io/lseek.cpp gkfs.io/lseek.cpp
gkfs.io/write_validate.cpp
) )
include(FetchContent) include(FetchContent)
......
...@@ -59,10 +59,15 @@ writev_init(CLI::App& app); ...@@ -59,10 +59,15 @@ writev_init(CLI::App& app);
void void
pwritev_init(CLI::App& app); pwritev_init(CLI::App& app);
#ifdef STATX_TYPE
void void
statx_init(CLI::App& app); statx_init(CLI::App& app);
#endif
void void
lseek_init(CLI::App& app); lseek_init(CLI::App& app);
void
write_validate_init(CLI::App& app);
#endif // IO_COMMANDS_HPP #endif // IO_COMMANDS_HPP
...@@ -34,8 +34,11 @@ init_commands(CLI::App& app) { ...@@ -34,8 +34,11 @@ init_commands(CLI::App& app) {
pwrite_init(app); pwrite_init(app);
writev_init(app); writev_init(app);
pwritev_init(app); pwritev_init(app);
#ifdef STATX_TYPE
statx_init(app); statx_init(app);
#endif
lseek_init(app); lseek_init(app);
write_validate_init(app);
} }
......
...@@ -105,6 +105,7 @@ struct adl_serializer<struct ::timespec> { ...@@ -105,6 +105,7 @@ struct adl_serializer<struct ::timespec> {
} }
}; };
#ifdef STATX_TYPE
// ADL specialization for struct ::statx_timestamp type // ADL specialization for struct ::statx_timestamp type
template <> template <>
struct adl_serializer<struct ::statx_timestamp> { struct adl_serializer<struct ::statx_timestamp> {
...@@ -116,8 +117,7 @@ struct adl_serializer<struct ::statx_timestamp> { ...@@ -116,8 +117,7 @@ struct adl_serializer<struct ::statx_timestamp> {
}; };
} }
}; };
#endif
// ADL specialization for struct ::dirent type // ADL specialization for struct ::dirent type
template <> template <>
...@@ -162,6 +162,7 @@ struct adl_serializer<struct ::stat> { ...@@ -162,6 +162,7 @@ struct adl_serializer<struct ::stat> {
} }
}; };
#ifdef STATX_TYPE
// ADL specialization for struct ::statx type // ADL specialization for struct ::statx type
template <> template <>
struct adl_serializer<struct ::statx> { struct adl_serializer<struct ::statx> {
...@@ -192,6 +193,7 @@ struct adl_serializer<struct ::statx> { ...@@ -192,6 +193,7 @@ struct adl_serializer<struct ::statx> {
}; };
} }
}; };
#endif
} // namespace nlohmann } // namespace nlohmann
......
...@@ -32,6 +32,7 @@ using json = nlohmann::json; ...@@ -32,6 +32,7 @@ using json = nlohmann::json;
unsigned int mask, struct statx *statxbuf); unsigned int mask, struct statx *statxbuf);
*/ */
#ifdef STATX_TYPE
struct statx_options { struct statx_options {
bool verbose; bool verbose;
int dirfd; int dirfd;
...@@ -135,4 +136,4 @@ statx_init(CLI::App& app) { ...@@ -135,4 +136,4 @@ statx_init(CLI::App& app) {
statx_exec(*opts); statx_exec(*opts);
}); });
} }
#endif
/*
Copyright 2018-2020, Barcelona Supercomputing Center (BSC), Spain
Copyright 2015-2020, 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.
SPDX-License-Identifier: MIT
*/
/* C++ includes */
#include <CLI/CLI.hpp>
#include <nlohmann/json.hpp>
#include <memory>
#include <fmt/format.h>
#include <commands.hpp>
#include <reflection.hpp>
#include <serialize.hpp>
#include <binary_buffer.hpp>
/* C includes */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using json = nlohmann::json;
struct write_validate_options {
bool verbose;
std::string pathname;
::size_t count;
REFL_DECL_STRUCT(write_validate_options,
REFL_DECL_MEMBER(bool, verbose),
REFL_DECL_MEMBER(std::string, pathname),
REFL_DECL_MEMBER(::size_t, count)
);
};
struct write_validate_output {
int retval;
int errnum;
REFL_DECL_STRUCT(write_validate_output,
REFL_DECL_MEMBER(int, retval),
REFL_DECL_MEMBER(int, errnum)
);
};
void
to_json(json& record,
const write_validate_output& out) {
record = serialize(out);
}
void
write_validate_exec(const write_validate_options& opts) {
int fd = ::open(opts.pathname.c_str(), O_WRONLY);
if(fd == -1) {
if(opts.verbose) {
fmt::print("write_validate(pathname=\"{}\", count={}) = {}, errno: {} [{}]\n",
opts.pathname, opts.count, fd, errno, ::strerror(errno));
return;
}
json out = write_validate_output{fd, errno};
fmt::print("{}\n", out.dump(2));
return;
}
std::string data = "";
for (::size_t i = 0 ; i < opts.count; i++)
{
data += char((i%10)+'0');
}
io::buffer buf(data);
auto rv = ::write(fd, buf.data(), opts.count);
if(opts.verbose) {
fmt::print("write_validate(pathname=\"{}\", count={}) = {}, errno: {} [{}]\n",
opts.pathname, opts.count, rv, errno, ::strerror(errno));
return;
}
if (rv < 0 or ::size_t(rv) != opts.count) {
json out = write_validate_output{(int)rv, errno};
fmt::print("{}\n", out.dump(2));
return;
}
io::buffer bufread(opts.count);
size_t total = 0;
do{
rv = ::read(fd, bufread.data(), opts.count-total);
total += rv;
} while (rv > 0 and total < opts.count);
if (rv < 0 and total != opts.count) {
json out = write_validate_output{(int)rv, errno};
fmt::print("{}\n", out.dump(2));
return;
}
if ( memcmp(buf.data(),bufread.data(),opts.count) ) {
rv = 1;
errno = 0;
json out = write_validate_output{(int)rv, errno};
fmt::print("{}\n", out.dump(2));
return;
}
else {
rv = 2;
errno = EINVAL;
json out = write_validate_output{(int)-1, errno};
fmt::print("{}\n", out.dump(2));
}
}
void
write_validate_init(CLI::App& app) {
// Create the option and subcommand objects
auto opts = std::make_shared<write_validate_options>();
auto* cmd = app.add_subcommand(
"write_validate",
"Execute the write()-read() system call and compare the content of the buffer");
// Add options to cmd, binding them to opts
cmd->add_flag(
"-v,--verbose",
opts->verbose,
"Produce human writeable output"
);
cmd->add_option(
"pathname",
opts->pathname,
"Directory name"
)
->required()
->type_name("");
cmd->add_option(
"count",
opts->count,
"Number of bytes to test"
)
->required()
->type_name("");
cmd->callback([opts]() {
write_validate_exec(*opts);
});
}
...@@ -293,6 +293,17 @@ class LseekOutputSchema(Schema): ...@@ -293,6 +293,17 @@ class LseekOutputSchema(Schema):
def make_object(self, data, **kwargs): def make_object(self, data, **kwargs):
return namedtuple('LseekReturn', ['retval', 'errno'])(**data) return namedtuple('LseekReturn', ['retval', 'errno'])(**data)
class WriteValidateOutputSchema(Schema):
"""Schema to deserialize the results of a write() execution"""
retval = fields.Integer(required=True)
errno = Errno(data_key='errnum', required=True)
@post_load
def make_object(self, data, **kwargs):
return namedtuple('WriteValidateReturn', ['retval', 'errno'])(**data)
class IOParser: class IOParser:
OutputSchemas = { OutputSchemas = {
...@@ -312,6 +323,7 @@ class IOParser: ...@@ -312,6 +323,7 @@ class IOParser:
'stat' : StatOutputSchema(), 'stat' : StatOutputSchema(),
'statx' : StatxOutputSchema(), 'statx' : StatxOutputSchema(),
'lseek' : LseekOutputSchema(), 'lseek' : LseekOutputSchema(),
'write_validate' : WriteValidateOutputSchema(),
} }
def parse(self, command, output): def parse(self, command, output):
......
...@@ -53,12 +53,12 @@ def test_lseek(gkfs_daemon, gkfs_client): ...@@ -53,12 +53,12 @@ def test_lseek(gkfs_daemon, gkfs_client):
assert ret.retval == 0 assert ret.retval == 0
assert ret.errno == 115 #FIXME: Should be 0! assert ret.errno == 115 #FIXME: Should be 0!
# test statx on existing dir # test stat on existing dir
ret = gkfs_client.statx(0, topdir, 0, 0) ret = gkfs_client.stat(topdir)
assert ret.retval == 0 assert ret.retval == 0
assert ret.errno == 115 #FIXME: Should be 0! assert ret.errno == 115 #FIXME: Should be 0!
assert stat.S_ISDIR(ret.statbuf.stx_mode) assert stat.S_ISDIR(ret.statbuf.st_mode)
ret = gkfs_client.open(file_a, ret = gkfs_client.open(file_a,
os.O_CREAT, os.O_CREAT,
...@@ -83,11 +83,11 @@ def test_lseek(gkfs_daemon, gkfs_client): ...@@ -83,11 +83,11 @@ def test_lseek(gkfs_daemon, gkfs_client):
assert ret.errno == 115 #FIXME: Should be 0 assert ret.errno == 115 #FIXME: Should be 0
# Size needs to be 0 # Size needs to be 0
ret = gkfs_client.statx(0, file_a, 0, 0) ret = gkfs_client.stat(file_a)
assert ret.retval == 0 assert ret.retval == 0
assert (stat.S_ISDIR(ret.statbuf.stx_mode)==0) assert (stat.S_ISDIR(ret.statbuf.st_mode)==0)
assert (ret.statbuf.stx_size == 0) assert (ret.statbuf.st_size == 0)
# next commands write at the end of the file (pos0), as the filedescriptor is not shared # next commands write at the end of the file (pos0), as the filedescriptor is not shared
...@@ -97,11 +97,13 @@ def test_lseek(gkfs_daemon, gkfs_client): ...@@ -97,11 +97,13 @@ def test_lseek(gkfs_daemon, gkfs_client):
assert ret.retval == 2 assert ret.retval == 2
# Size should be 2 # Size should be 2
ret = gkfs_client.statx(0, file_a, 0, 0) ret = gkfs_client.stat(file_a)
assert ret.retval == 0 assert ret.retval == 0
assert (ret.statbuf.stx_size == 2) assert (stat.S_ISDIR(ret.statbuf.st_mode)==0)
assert (ret.statbuf.st_size == 2)
ret = gkfs_client.lseek(file_a, 0, os.SEEK_END) ret = gkfs_client.lseek(file_a, 0, os.SEEK_END)
assert ret.retval == 2 #FAILS assert ret.retval == 2 #FAILS
assert ret.errno == 115 #FIXME: Should be 0 assert ret.errno == 115 #FIXME: Should be 0
......