diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9281846c69fa82e4999471457f971c0b6ec9cc1e..662f559a871faa23cee222148d8eb364f2a8badc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,9 +3,13 @@ # can use verions as well, like gcc:5.2 # see https://hub.docker.com/_/gcc/ +variables: + GIT_SUBMODULE_STRATEGY: normal + stages: - build - test + - deploy # Install dependencies for GCC builds before_script: @@ -22,6 +26,39 @@ before_script: protobuf-c-compiler libyaml-cpp-dev libyaml-dev + libtar-dev + cmake + + - pushd . && + git clone https://github.com/ofiwg/libfabric.git && + cd libfabric && + ./autogen.sh && + mkdir build && + cd build && + ../configure && + make -j $(nproc) && + make install && + popd + + - pushd . && + git clone https://github.com/mercury-hpc/mercury.git && + cd mercury && + mkdir build && + cd build && + cmake + -DCMAKE_BUILD_TYPE:STRING=Debug + -DBUILD_TESTING:BOOL=OFF + -DMERCURY_USE_SM_ROUTING:BOOL=OFF + -DMERCURY_USE_SELF_FORWARD:BOOL=OFF + -DMERCURY_USE_CHECKSUMS:BOOL=OFF + -DMERCURY_USE_BOOST_PP:BOOL=ON + -DMERCURY_USE_EAGER_BULK:BOOL=ON + -DBUILD_SHARED_LIBS:BOOL=ON + -DNA_USE_OFI:BOOL=ON + .. && + make -j $(nproc) && + make install && + popd ### GCC 5 @@ -34,10 +71,10 @@ build:gcc:5: - mkdir build && cd build - ../configure --enable-tests - - make -j4 CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" + - make -j$(nproc) CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" - cd tests - - make -j4 CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" core - - make -j4 CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" api + - make -j$(nproc) CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" core + - make -j$(nproc) CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" api ### GCC 6 @@ -50,10 +87,10 @@ build:gcc:6: - mkdir build && cd build - ../configure --enable-tests - - make -j4 CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" + - make -j$(nproc) CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" - cd tests - - make -j4 CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" core - - make -j4 CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" api + - make -j$(nproc) CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" core + - make -j$(nproc) CPPFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" api ### GCC 7 @@ -66,10 +103,10 @@ build:gcc:7: - mkdir build && cd build - ../configure --enable-tests - - make -j4 + - make -j$(nproc) - cd tests - - make -j4 - - make -j4 + - make -j$(nproc) + - make -j$(nproc) ### GCC 8 @@ -82,16 +119,16 @@ build:gcc:8: - mkdir build && cd build - ../configure --enable-tests - - make -j4 + - make -j$(nproc) - cd tests - - make -j4 - - make -j4 + - make -j$(nproc) + - make -j$(nproc) ################################################################################ # test scripts ################################################################################ -test:ubuntu:latest: +test:coverage: image: ubuntu:latest stage: test @@ -100,6 +137,7 @@ test:ubuntu:latest: - apt-get update && apt-get upgrade -y && apt-get install -y + git build-essential autotools-dev automake @@ -117,8 +155,44 @@ test:ubuntu:latest: protobuf-c-compiler libyaml-cpp-dev libyaml-dev + libtar-dev libcap2-bin valgrind + cmake + lcov + + - pushd . && + git clone https://github.com/ofiwg/libfabric.git && + cd libfabric && + ./autogen.sh && + mkdir build && + cd build && + ../configure && + make -j $(nproc) && + make install && + popd && + ldconfig + + - pushd . && + git clone https://github.com/mercury-hpc/mercury.git && + cd mercury && + mkdir build && + cd build && + cmake + -DCMAKE_BUILD_TYPE:STRING=Debug + -DBUILD_TESTING:BOOL=OFF + -DMERCURY_USE_SM_ROUTING:BOOL=OFF + -DMERCURY_USE_SELF_FORWARD:BOOL=OFF + -DMERCURY_USE_CHECKSUMS:BOOL=OFF + -DMERCURY_USE_BOOST_PP:BOOL=ON + -DMERCURY_USE_EAGER_BULK:BOOL=ON + -DBUILD_SHARED_LIBS:BOOL=ON + -DNA_USE_OFI:BOOL=ON + .. && + make -j $(nproc) && + make install && + popd && + ldconfig # Build and test script: @@ -126,36 +200,173 @@ test:ubuntu:latest: - mkdir build && cd build - ../configure --enable-tests -# CFLAGS="-fsanitize=address" -# CXXFLAGS="-fsanitize=address" -# LDFLAGS="-fsanitize=address" -# CPPFLAGS="-D__LOGGER_ENABLE_DEBUG__" - - make -j4 + CFLAGS="-O0 --coverage" + CXXFLAGS="-O0 --coverage" + LDFLAGS="--coverage" + - make -j$(nproc) - cd tests - - make -j4 core - - ./core -as - - make -j4 api -# - NORNS_DEBUG_OUTPUT_TO_STDERR=1 NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_register_namespace]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_unregister_namespace]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_copy_local_posix_files]" + - make -j$(nproc) core + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./core -as + - make -j$(nproc) api + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::NORNS_TASK]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_iotask_init]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_resource_init]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_status]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit]" - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_copy_buffer_to_file]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_register_job]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_update_job]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_unregister_job]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_copy_local_posix_files]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_pull_errors]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_pull_links]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_pull_to_posix_file]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_pull_to_posix_subdir]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_errors]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_links]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_memory_to_posix_file]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_memory_to_posix_file_errors]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_to_posix_file]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_to_posix_subdir]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_remove_local_posix_files]" - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_add_process]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_register_job]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_register_namespace]" - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_remove_process]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_resource_init]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_iotask_init]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_send_command]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_status]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_unregister_job]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_unregister_namespace]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_update_job]" + + after_script: + - (cd build && ../gencov.sh) + - genhtml -o build/html/coverage build/norns.info + - if [[ -e build/tests.log ]]; + then + cat $(tail -1 build/tests.log)/config/urd.log; + fi + + artifacts: + paths: + - build/html/coverage/ + +test:optimized: + image: ubuntu:latest + stage: test + + # Install dependencies + before_script: + - apt-get update && + apt-get upgrade -y && + apt-get install -y + git + build-essential + autotools-dev + automake + autoconf + libtool + pkg-config + libboost-system-dev + libboost-filesystem-dev + libboost-program-options-dev + libboost-thread-dev + libboost-regex-dev + libprotobuf-dev + protobuf-compiler + libprotobuf-c-dev + protobuf-c-compiler + libyaml-cpp-dev + libyaml-dev + libtar-dev + libcap2-bin + valgrind + cmake + + - pushd . && + git clone https://github.com/ofiwg/libfabric.git && + cd libfabric && + ./autogen.sh && + mkdir build && + cd build && + ../configure && + make -j $(nproc) && + make install && + popd && + ldconfig + + - pushd . && + git clone https://github.com/mercury-hpc/mercury.git && + cd mercury && + mkdir build && + cd build && + cmake + -DCMAKE_BUILD_TYPE:STRING=Debug + -DBUILD_TESTING:BOOL=OFF + -DMERCURY_USE_SM_ROUTING:BOOL=OFF + -DMERCURY_USE_SELF_FORWARD:BOOL=OFF + -DMERCURY_USE_CHECKSUMS:BOOL=OFF + -DMERCURY_USE_BOOST_PP:BOOL=ON + -DMERCURY_USE_EAGER_BULK:BOOL=ON + -DBUILD_SHARED_LIBS:BOOL=ON + -DNA_USE_OFI:BOOL=ON + .. && + make -j $(nproc) && + make install && + popd && + ldconfig + + # Build and test + script: + - ./bootstrap.sh + - mkdir build && cd build + - ../configure + --enable-tests + - make -j$(nproc) + - cd tests + - make -j$(nproc) core + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./core -as + - make -j$(nproc) api - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::NORNS_TASK]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_iotask_init]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_resource_init]" - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_status]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_status]" - - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_send_command]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_copy_buffer_to_file]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_copy_local_posix_files]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_pull_errors]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_pull_links]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_pull_to_posix_file]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_pull_to_posix_subdir]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_errors]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_links]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_memory_to_posix_file]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_memory_to_posix_file_errors]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_to_posix_file]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_push_to_posix_subdir]" - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::norns_submit_remove_local_posix_files]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_add_process]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_register_job]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_register_namespace]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_remove_process]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_send_command]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_status]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_unregister_job]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_unregister_namespace]" + - NORNS_DEBUG_CONFIG_FILE_OVERRIDE=1 ./api -as "[api::nornsctl_update_job]" + after_script: - - pwd - - if [[ -e tests.log ]]; + - if [[ -e build/tests.log ]]; then - cat $(tail -1 tests.log)/config/urd.log; + cat $(tail -1 build/tests.log)/config/urd.log; fi + +pages: + stage: deploy + dependencies: + - test:coverage + script: + - mv coverage/ public/ + artifacts: + paths: + - public + expire_in: 30 days + only: + - master diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..f1ecde20ccf4b5df7bfa3fbb8c23e8d6e30f18eb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/externals/hermes"] + path = src/externals/hermes + url = ../hermes.git diff --git a/README.md b/README.md index 3588a2216f2712a29804f13666b1eaf723dccfe1..cdd6aebb62e2b3071e10be938f66eab11f7575cf 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,105 @@ +# Norns [![pipeline status](https://storage.bsc.es/gitlab/hpc/norns/badges/master/pipeline.svg)](https://storage.bsc.es/gitlab/hpc/norns/commits/master) +[![coverage report](https://storage.bsc.es/gitlab/hpc/norns/badges/21-add-support-for-remote-transfers-2/coverage.svg)](https://storage.bsc.es/gitlab/hpc/norns/commits/21-add-support-for-remote-transfers-2) +Norns is an open-source data scheduling service that orchestrates asynchronous +data transfers between different storage backends in an HPC cluster. Through +its API, Norns provides three different functions. First, it allows system +administrators to expose the storage architecture of an HPC cluster by creating +**dataspaces** associated to different storage backends such as node-local NVMs +and POSIX file systems, or system-wide parallel file systems and object stores, +thus making them available to applications, services, and users. +Second, it provides a framework for submitting and monitoring the asynchronous +transfers of **data resources** between the (local and remote) dataspaces +available to a user, such as process buffers, POSIX files and directories or +objects. Third, it arbitrates requests by managing a queue of pending work and +evaluating requests to maximize dataspace throughput while minimizing the +interferences with normal application I/O. -Norns Data Scheduler -==================== +Norns has currently been tested only under GNU/Linux. -Build dependencies: +## Building and installing from source -- A c++11-conforming compiler -- libboost-system >= 1.53 -- libboost-filesystem >= 1.53 -- libboost-program-options >= 1.53 -- libboost-thread >= 1.53 -- libboost-regex >= 1.53 (only if self tests are also built) -- libprotobuf + protobuf compiler >= 2.5.0 -- libprotobuf-c + protobuf-c compiler >= 1.0.2 -- libyaml-cpp >= 0.5.1 -- libyaml >= 0.1.4 +Distribution tarballs are available from the [releases](releases) tab. If you +are building Norns from a developer Git clone, you must first run the +`bootstrap.sh` script, which will invoke the GNU Autotools to bootstrap Norns' +configuration and build mechanisms. If you are building Norns from an official +distribiution tarball, there is no need to run the `bootstrap.sh` script, since +all distribution tarballs are already boostrapped. +### Dependencies -- Installation in CentOS 7 -git clone git@git.ph.ed.ac.uk:nextgenio/norns.git && cd norns -./bootstrap.sh -mkdir -cd -./configure --prefix= --sysconfdir= -make -make install -cp /etc/norns.service /usr/lib/systemd/system/norns.service +Compiling and running Norns requires up-to-date versions of the following +software packages (note that, though it may compile and run, using excessively +old versions of these packages can cause indirect errors that are very +difficult to track down): -sudo setcap cap_sys_ptrace,cap_chown=+ep ./urd +- A standard **C++11** conforming compiler (the code is routinely tested + against GCC 4.9/Clang 3.3 and higher). +- Autotools (autoconf 2.69 or higher, automake 1.14.1 or higher, and libtool + 2.4.2 or higher) and CMake (3.10.0 or higher). +- The following Boost libraries (1.53 or higher): `system`, `filesystem`, + `program_options`, and `thread`. Optionally, the `regex` library may also be + required if self tests are enabled with the `--enable-tests` option. +- Google's Protocol Buffers for [C](https://github.com/protobuf-c/protobuf-c) + (1.0.2 or higher) and for [C++](https://github.com/protocolbuffers/protobuf) + (2.5.0 or higher). +- [LibYAML](https://github.com/yaml/libyaml) (0.1.4 or higher) and + [yaml-cpp](https://github.com/jbeder/yaml-cpp) (0.5.1 or higher). +- [Mercury](https://github.com/mercury-hpc/mercury) 1.0 or higher + (**IMPORTANT** Mercury may require additional dependencies such as libfabric + depending on the desired transport protocol). +- [Hermes](https://storage.bsc.es/gitlab/hpc/hermes) (our own C++ wrapper for + Mercury. It should be automatically downloaded when cloning with the + `--recursive` option). + +#### Installation in CentOS + +TODO + +#### Installation in Ubuntu + +```bash +# Installing dependencies avaiable through package manager +$ apt-get install -y libboost-system-dev libboost-filesystem-dev \ + libboost-program-options-dev libboost-thread-dev \ + libboost-regex-dev libprotobuf-dev protobuf-compiler \ + libprotobuf-c-dev protobuf-c-compiler \ + libyaml-cpp-dev libyaml-dev libtar-dev + +# Building and installing libfabric (required for Mercury's OFI/libfabric plugin) +$ git clone https://github.com/ofiwg/libfabric.git && +$ cd libfabric +$ ./autogen.sh +$ mkdir build && cd build +$ ../configure && make && make install + +# Building and installing Mercury with OFI/libfabric plugin +$ git clone https://github.com/mercury-hpc/mercury.git +$ cd mercury +$ mkdir build && cd build +$ cmake -DCMAKE_BUILD_TYPE:STRING=Debug -DBUILD_TESTING:BOOL=OFF \ + -DMERCURY_USE_SM_ROUTING:BOOL=OFF -DMERCURY_USE_SELF_FORWARD:BOOL=OFF \ + -DMERCURY_USE_CHECKSUMS:BOOL=OFF -DMERCURY_USE_BOOST_PP:BOOL=ON \ + -DMERCURY_USE_EAGER_BULK:BOOL=ON -DBUILD_SHARED_LIBS:BOOL=ON \ + -DNA_USE_OFI:BOOL=ON \ + .. +$ make && make install + +# Building, testing and installing Norns under '/usr/local/', with configuration +# files under '/etc/norns/' and temporary files under '/var/run/norns/' +$ git clone --recursive https://storage.bsc.es/gitlab/hpc/norns.git +$ cd norns +$ ./bootstrap.sh +$ mkdir build && cd build +$ ../configure \ + --enable-tests \ + --prefix=/usr/local \ + --sysconfdir=/etc/norns \ + --localstatedir=/var/run/norns +$ make && make check && make install + +# Optional: providing file system permission override capabilities to Norns +# control daemon +$ setcap cap_sys_ptrace,cap_chown=+ep /usr/local/bin/urd +``` diff --git a/configure.ac b/configure.ac index 8ad287ac9f9f92da8d4e25d9d0d7ffdf688beb36..cc8858aa88aec052876ee8dd0e654811d27a0d76 100644 --- a/configure.ac +++ b/configure.ac @@ -40,6 +40,9 @@ AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([1.9 foreign subdir-objects]) +AC_LANG([C]) +AC_LANG([C++]) + # Checks for programs. AC_PROG_AWK AC_PROG_SED @@ -119,6 +122,9 @@ AS_IF([test "x$is_enabled_build_tests" = "xyes"], AC_CONFIG_FILES(tests/Makefile) ], []) +# check for mercury +PKG_CHECK_MODULES([MERCURY], [mercury >= 0.26]) + # check for libyaml-cpp PKG_CHECK_MODULES([YAMLCPP], [yaml-cpp >= 0.5.1]) @@ -141,7 +147,13 @@ AS_IF([test "x${PROTOC}" == "x"], AC_SEARCH_LIBS([yaml_parser_initialize], [yaml], [YAML_LIBS="-lyaml" AC_SUBST(YAML_LIBS)], - [AC_MSG_ERROR([This software required libyaml >= 0.1.4])]) + [AC_MSG_ERROR([This software requires libyaml >= 0.1.4])]) + +# check for libtar manually (since it doesn't provide a pkgconfig file) +AC_SEARCH_LIBS([tar_open], [tar], + [TAR_LIBS="-ltar" + AC_SUBST(TAR_LIBS)], + [AC_MSG_ERROR([This software requires libtar >= 1.2.0])]) # Checks for header files. @@ -149,7 +161,13 @@ AC_SEARCH_LIBS([yaml_parser_initialize], [yaml], AC_CHECK_HEADER_STDBOOL # Checks for library functions. +AC_CHECK_FUNC([fallocate],[fallocate],[fallocate]) +AS_IF([test "x${PROTOC}" == "x"], + [AC_MSG_ERROR([ProtoBuf compiler "protoc" not found.])]) +AC_CHECK_FUNC([fallocate], + [AC_DEFINE([HAVE_FALLOCATE], + [1], [Define if file preallocation is available])]) ################################################################################ ### write makefiles diff --git a/etc/norns.conf.in b/etc/norns.conf.in index 125ab3f6edb3e9c8b69017af6741cc6363862ca7..4fa1a43c02f420892d2fe8dd2db4830a85a81aec 100644 --- a/etc/norns.conf.in +++ b/etc/norns.conf.in @@ -17,12 +17,18 @@ global_settings: [ # path to pidfile pidfile: "@localstatedir@/urd.pid", + + # address to bind to + bind_address: "127.0.0.1", # incoming port for remote connections remote_port: 42000, # number of worker threads to serve I/O requests - workers: 4 + workers: 4, + + # staging dir for temporary resources + staging_directory: "/tmp/urd/" ] ## list of namespaces available by default when service starts diff --git a/gencov.sh b/gencov.sh new file mode 100755 index 0000000000000000000000000000000000000000..758750b8cd097d8f6bf7c92076e503ab7af45426 --- /dev/null +++ b/gencov.sh @@ -0,0 +1,23 @@ +#!/bin/bash -x + +GIT_ROOTDIR=`git rev-parse --show-toplevel` +LCOV=`which lcov` + +${LCOV} \ + `find ${GIT_ROOTDIR} -name "*.gcda" 2>/dev/null | xargs -I{} dirname {} | uniq | xargs -I {} echo -n " --directory "{}` \ + --capture \ + --output-file gcov.info + +${LCOV} \ + --remove gcov.info \ + '/usr/include/*' \ + '/usr/local/include/*' \ + '*/externals/*' \ + '*/spdlog/*' \ + '*/build/*' \ + '*/tests/*' \ + -o norns.info + +${LCOV} -l norns.info + +#genhtml -o buiild/html/coverage norns.info diff --git a/include/norns/norns_error.h b/include/norns/norns_error.h index bbb23dfe5fad69e5317ebe313ab971e1f3f35bcf..23e7a73ba979f04367b846dea03a4dcf13c362f4 100644 --- a/include/norns/norns_error.h +++ b/include/norns/norns_error.h @@ -77,6 +77,10 @@ extern "C" { #define NORNS_EFINISHED -102 #define NORNS_EFINISHEDWERROR -103 +/* errors resources */ +#define NORNS_ERESOURCEEXISTS -110 +#define NORNS_ENOSUCHRESOURCE -111 + /* misc errors */ #define NORNS_ENOTSUPPORTED -200 #define NORNS_ESYSTEMERROR -201 diff --git a/lib/errors.c b/lib/errors.c index 8c1b274277bf3c16a75f33a1f3b46b2f9eed6b59..c312e288e3307b7ce080bca46ad084139817029d 100644 --- a/lib/errors.c +++ b/lib/errors.c @@ -61,6 +61,10 @@ const char* const norns_errlist[NORNS_ERRMAX + 1] = { [ERR_REMAP(NORNS_ETOOMANYTASKS)] = "Too many pending tasks", [ERR_REMAP(NORNS_ETASKSPENDING)] = "There are still pending tasks", + /* resource errors */ + [ERR_REMAP(NORNS_ERESOURCEEXISTS)] = "Resource already exists", + [ERR_REMAP(NORNS_ENOSUCHRESOURCE)] = "Resource does not exist", + /* misc errors */ [ERR_REMAP(NORNS_ENOTSUPPORTED)] = "Not supported", [ERR_REMAP(NORNS_ESYSTEMERROR)] = "Operating system error", diff --git a/src/Makefile.am b/src/Makefile.am index e994b4a3b8ac086982d0edf2e632898f3aaa2d08..82a82caa89e65a9eab8dc26e68f965a22fa25274 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -48,6 +48,11 @@ liburd_resources_la_SOURCES = \ resources/memory_buffer/detail/memory-region-impl.hpp \ resources/memory_buffer/detail/memory-region-info.cpp \ resources/memory_buffer/detail/memory-region-info.hpp \ + resources/remote_resource/remote-resource.hpp \ + resources/remote_resource/detail/remote-resource-impl.cpp \ + resources/remote_resource/detail/remote-resource-impl.hpp \ + resources/remote_resource/detail/remote-resource-info.cpp \ + resources/remote_resource/detail/remote-resource-info.hpp \ resources/remote_posix_path/remote-path.hpp \ resources/remote_posix_path/detail/remote-path-impl.cpp \ resources/remote_posix_path/detail/remote-path-impl.hpp \ @@ -67,9 +72,11 @@ liburd_resources_la_CXXFLAGS = \ liburd_resources_la_CPPFLAGS = \ -DSPDLOG_ENABLE_SYSLOG \ + -DHERMES_DISABLE_INTERNAL_MAKE_UNIQUE \ @BOOST_CPPFLAGS@ \ -I$(top_srcdir)/include \ -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/externals/hermes/include \ -I$(top_srcdir)/src/resources \ -I$(top_srcdir)/rpc \ -I$(top_builddir)/rpc @@ -81,7 +88,9 @@ liburd_resources_la_LDFLAGS = \ @BOOST_PROGRAM_OPTIONS_LIB@ \ @BOOST_SYSTEM_LIB@ \ @BOOST_THREAD_LIB@ \ + @MERCURY_LIBS@ \ @PROTOBUF_LIBS@ \ + @TAR_LIBS@ \ -pthread @@ -136,13 +145,18 @@ liburd_aux_la_SOURCES = \ config/parsers.cpp \ config/parsers.hpp \ config/defaults.hpp \ + context.hpp \ io.hpp \ - io/task.cpp \ io/task.hpp \ + io/task-copy.hpp \ io/task-info.cpp \ io/task-info.hpp \ io/task-manager.cpp \ io/task-manager.hpp \ + io/task-move.hpp \ + io/task-noop.hpp \ + io/task-remote-transfer.hpp \ + io/task-remove.hpp \ io/task-stats.cpp \ io/task-stats.hpp \ io/transferors.hpp \ @@ -151,19 +165,25 @@ liburd_aux_la_SOURCES = \ io/transferors/local-path-to-local-path.hpp \ io/transferors/local-path-to-shared-path.cpp \ io/transferors/local-path-to-shared-path.hpp \ - io/transferors/local-path-to-remote-path.cpp \ - io/transferors/local-path-to-remote-path.hpp \ + io/transferors/local-path-to-remote-resource.cpp \ + io/transferors/local-path-to-remote-resource.hpp \ + io/transferors/remote-resource-to-local-path.cpp \ + io/transferors/remote-resource-to-local-path.hpp \ io/transferors/memory-to-local-path.cpp \ io/transferors/memory-to-local-path.hpp \ io/transferors/memory-to-shared-path.cpp \ io/transferors/memory-to-shared-path.hpp \ io/transferors/memory-to-remote-path.cpp \ io/transferors/memory-to-remote-path.hpp \ + io/transferors/memory-to-remote-resource.cpp \ + io/transferors/memory-to-remote-resource.hpp \ io/transferor-registry.cpp \ io/transferor-registry.hpp \ job.hpp \ logger.hpp \ resources.hpp \ + rpcs.cpp \ + rpcs.hpp \ signal-listener.hpp \ thread-pool.hpp \ thread-pool/thread-pool.hpp \ @@ -171,7 +191,12 @@ liburd_aux_la_SOURCES = \ urd.cpp \ urd.hpp \ utils.cpp \ - utils.hpp + utils.hpp \ + utils/file-handle.hpp \ + utils/tar-archive.cpp \ + utils/tar-archive.hpp \ + utils/temporary-file.hpp \ + utils/temporary-file.cpp nodist_liburd_aux_la_SOURCES = \ config/defaults.cpp \ @@ -183,9 +208,11 @@ liburd_aux_la_CXXFLAGS = \ liburd_aux_la_CPPFLAGS = \ -DSPDLOG_ENABLE_SYSLOG \ + -DHERMES_DISABLE_INTERNAL_MAKE_UNIQUE \ @BOOST_CPPFLAGS@ \ -I$(top_srcdir)/include \ -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/externals/hermes/include \ -I$(top_srcdir)/src/resources \ -I$(top_srcdir)/rpc \ -I$(top_builddir)/rpc @@ -197,8 +224,10 @@ liburd_aux_la_LDFLAGS = \ @BOOST_PROGRAM_OPTIONS_LIB@ \ @BOOST_SYSTEM_LIB@ \ @BOOST_THREAD_LIB@ \ + @MERCURY_LIBS@ \ @PROTOBUF_LIBS@ \ @YAMLCPP_LIBS@ \ + @TAR_LIBS@ \ liburd_resources.la \ -pthread @@ -228,10 +257,12 @@ config/defaults.cpp: Makefile \ echo " const char* global_socket = \"$(localstatedir)/global.socket.2\";"; \ echo " const char* control_socket = \"$(localstatedir)/control.socket.2\";"; \ + echo " const char* bind_address = \"127.0.0.1\";"; \ echo " const in_port_t remote_port = 42000;"; \ echo " const char* pidfile = \"$(localstatedir)/urd.pid\";"; \ \ echo " const uint32_t workers_in_pool = std::thread::hardware_concurrency();"; \ + echo " const char* staging_directory = \"/tmp/urd/\";"; \ echo " const uint32_t backlog_size = 128;"; \ echo " const char* config_file = \"$(sysconfdir)/norns.conf\";"; \ echo "} // namespace defaults"; \ @@ -256,9 +287,11 @@ urd_CXXFLAGS = \ urd_CPPFLAGS = \ -DSPDLOG_ENABLE_SYSLOG \ + -DHERMES_DISABLE_INTERNAL_MAKE_UNIQUE \ @BOOST_CPPFLAGS@ \ -I$(top_srcdir)/include \ -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/externals/hermes/include \ -I$(top_srcdir)/rpc \ -I$(top_builddir)/rpc @@ -269,7 +302,9 @@ urd_LDFLAGS = \ @BOOST_PROGRAM_OPTIONS_LIB@ \ @BOOST_SYSTEM_LIB@ \ @BOOST_THREAD_LIB@ \ + @MERCURY_LIBS@ \ @PROTOBUF_LIBS@ \ + @TAR_LIBS@ \ liburd_aux.la # we also need to include it as an additional dependency, since automake diff --git a/src/api/request.cpp b/src/api/request.cpp index c0328523dcd34c84381b4b6fc726b6f88c0e8b2f..9dcca44fa1b713313fc27a4a38cd3c0c1a5c79be 100644 --- a/src/api/request.cpp +++ b/src/api/request.cpp @@ -166,6 +166,7 @@ create_from(const norns::rpc::Request_Task_Resource& res) { using norns::data::local_path_info; using norns::data::shared_path_info; using norns::data::remote_path_info; + using norns::data::remote_resource_info; assert(is_valid(res)); @@ -187,9 +188,13 @@ create_from(const norns::rpc::Request_Task_Resource& res) { // R_REMOTE assert(res.type() & R_REMOTE); - return std::make_shared(res.path().nsid(), - res.path().hostname(), - res.path().datapath()); + //return std::make_shared(res.path().nsid(), + // res.path().hostname(), + // res.path().datapath()); + + return std::make_shared(res.path().hostname(), + res.path().nsid(), + res.path().datapath()); } assert(res.type() & NORNS_NULL_RESOURCE); diff --git a/src/api/signal-listener.hpp b/src/api/signal-listener.hpp index 245a994a83c768641ceaae88e54bca72212fe3ff..688f57432ca80c6e26e8172ecfef72644226e65f 100644 --- a/src/api/signal-listener.hpp +++ b/src/api/signal-listener.hpp @@ -37,16 +37,17 @@ namespace ba = boost::asio; namespace norns { namespace api { +template +void do_for(F /*f*/) { + // Parameter pack is empty. +} + template void do_for(F f, First first, Rest... rest) { f(first); do_for(f, rest...); } -template -void do_for(F /*f*/) { - // Parameter pack is empty. -} struct signal_listener { diff --git a/src/backends.hpp b/src/backends.hpp index bf9ea431b65be5d30ca711f632d4b72574d04b40..7565c8252a845b6855648eb4ba2eb8ce9be969c3 100644 --- a/src/backends.hpp +++ b/src/backends.hpp @@ -38,8 +38,11 @@ namespace norns { namespace storage { - static const auto process_memory_backend = std::make_shared(); - static const auto remote_backend = std::make_shared(); + + constexpr static const auto memory_nsid = "[[internal::memory]]"; + + static const auto process_memory_backend = std::make_shared(memory_nsid); +// static const auto remote_backend = std::make_shared(); } // namespace storage } // namespace norns diff --git a/src/backends/backend-base.cpp b/src/backends/backend-base.cpp index 88b7d1a32b0b0ec770d32144159f16db0d2c2fbd..313e5315033b57ae0a87419c32e0ff4e0b242bb8 100644 --- a/src/backends/backend-base.cpp +++ b/src/backends/backend-base.cpp @@ -41,8 +41,11 @@ backend_factory::get() { } std::shared_ptr -backend_factory::create(const backend_type type, bool track, - const bfs::path& mount, uint32_t quota) const { +backend_factory::create(const backend_type type, + const std::string& nsid, + bool track, + const bfs::path& mount, + uint32_t quota) const { boost::system::error_code ec; @@ -58,7 +61,8 @@ backend_factory::create(const backend_type type, bool track, const auto& it = m_registrar.find(id); if(it != m_registrar.end()){ - return std::shared_ptr(it->second(track, canonical_mount, quota)); + return std::shared_ptr( + it->second(nsid, track, canonical_mount, quota)); } else{ throw std::invalid_argument("Unrecognized backend type!"); diff --git a/src/backends/backend-base.hpp b/src/backends/backend-base.hpp index cbe7ee6bf006d6bb7aebcab91f1e83e4e3014ad0..807892f90a219559855e430a2886b155f9536bd5 100644 --- a/src/backends/backend-base.hpp +++ b/src/backends/backend-base.hpp @@ -59,6 +59,7 @@ protected: public: virtual ~backend() {}; + virtual std::string nsid() const = 0; virtual bool is_tracked() const = 0; virtual bool is_empty() const = 0; virtual bfs::path mount() const = 0; @@ -85,8 +86,13 @@ public: class backend_factory { - using creator_function = std::function< - std::shared_ptr(bool track, const bfs::path&, uint32_t)>; + using creator_function = + std::function< + std::shared_ptr( + const std::string&, + bool track, + const bfs::path&, + uint32_t)>; public: @@ -98,7 +104,7 @@ public: try { return get().create(std::forward(args)...); } - catch(std::invalid_argument) { + catch(const std::invalid_argument&) { return std::shared_ptr(); } } @@ -140,8 +146,8 @@ public: private: std::shared_ptr - create(const backend_type type, bool track, const bfs::path& mount, - uint32_t quota) const; + create(const backend_type type, const std::string&, bool track, + const bfs::path& mount, uint32_t quota) const; protected: backend_factory() {} diff --git a/src/backends/lustre-fs.cpp b/src/backends/lustre-fs.cpp index 5e614ae5818b26b56ba6a0ed91554b6dc3a8db39..1ae3d0c7bc0a6ff9adabe0128c49dd4c6baf5644 100644 --- a/src/backends/lustre-fs.cpp +++ b/src/backends/lustre-fs.cpp @@ -33,11 +33,17 @@ namespace norns { namespace storage { -lustre::lustre(bool track, const bfs::path& mount, uint32_t quota) - : m_track(track), +lustre::lustre(const std::string& nsid, bool track, const bfs::path& mount, uint32_t quota) + : m_nsid(nsid), + m_track(track), m_mount(mount), m_quota(quota) { } +std::string +lustre::nsid() const { + return m_nsid; +} + bool lustre::is_tracked() const { return m_track; diff --git a/src/backends/lustre-fs.hpp b/src/backends/lustre-fs.hpp index 7489f89fd73066f87d5e45527ff257227714b93d..d09ec8dd0fe8c511eb28038794cc00565bc70ca0 100644 --- a/src/backends/lustre-fs.hpp +++ b/src/backends/lustre-fs.hpp @@ -41,8 +41,9 @@ namespace storage { class lustre final : public storage::backend { public: - lustre(bool track, const bfs::path& mount, uint32_t quota); + lustre(const std::string& nsid, bool track, const bfs::path& mount, uint32_t quota); + std::string nsid() const override final; bool is_tracked() const override final; bool is_empty() const override final; bfs::path mount() const override final; @@ -57,9 +58,10 @@ public: std::string to_string() const override final; protected: - bool m_track; - bfs::path m_mount; - uint32_t m_quota; + std::string m_nsid; + bool m_track; + bfs::path m_mount; + uint32_t m_quota; }; //NORNS_REGISTER_BACKEND(backend_type::lustre, lustre); diff --git a/src/backends/nvml-dax.cpp b/src/backends/nvml-dax.cpp index 6b08824ab3e06935190b94d0cd6d3906f3d3e3a8..0fe2ee3f6f6e23747dd6d3339e058216956647c9 100644 --- a/src/backends/nvml-dax.cpp +++ b/src/backends/nvml-dax.cpp @@ -33,11 +33,17 @@ namespace norns { namespace storage { -nvml_dax::nvml_dax(bool track, const bfs::path& mount, uint32_t quota) - : m_track(track), +nvml_dax::nvml_dax(const std::string& nsid, bool track, const bfs::path& mount, uint32_t quota) + : m_nsid(nsid), + m_track(track), m_mount(mount), m_quota(quota) { } +std::string +nvml_dax::nsid() const { + return m_nsid; +} + bool nvml_dax::is_tracked() const { return m_track; diff --git a/src/backends/nvml-dax.hpp b/src/backends/nvml-dax.hpp index 76b0cbe2cea3f604bba0a618e83df54b2047d274..075502ca462a5acac3727c4a3be82d6843ec6804 100644 --- a/src/backends/nvml-dax.hpp +++ b/src/backends/nvml-dax.hpp @@ -40,8 +40,9 @@ namespace storage { class nvml_dax final : public storage::backend { public: - nvml_dax(bool track, const bfs::path& mount, uint32_t quota); + nvml_dax(const std::string& nsid, bool track, const bfs::path& mount, uint32_t quota); + std::string nsid() const override final; bool is_tracked() const override final; bool is_empty() const override final; bfs::path mount() const override final; @@ -56,9 +57,10 @@ public: std::string to_string() const final; private: - bool m_track; - bfs::path m_mount; - uint32_t m_quota; + std::string m_nsid; + bool m_track; + bfs::path m_mount; + uint32_t m_quota; }; //NORNS_REGISTER_BACKEND(backend_type::nvml, nvml_dax); diff --git a/src/backends/posix-fs.cpp b/src/backends/posix-fs.cpp index 0db04a6a4e94b4d3ffe33dbda541661f2fb37594..5bc8ad6a37b6483c87d72a7fde6ae54fa6f28c20 100644 --- a/src/backends/posix-fs.cpp +++ b/src/backends/posix-fs.cpp @@ -55,12 +55,20 @@ bool contains(const bfs::path& p1, const bfs::path& p2) { namespace norns { namespace storage { -posix_filesystem::posix_filesystem(bool track, const bfs::path& mount, +posix_filesystem::posix_filesystem(const std::string& nsid, + bool track, + const bfs::path& mount, uint32_t quota) - : m_track(track), + : m_nsid(nsid), + m_track(track), m_mount(mount), m_quota(quota) { } +std::string +posix_filesystem::nsid() const { + return m_nsid; +} + bool posix_filesystem::is_tracked() const { return m_track; diff --git a/src/backends/posix-fs.hpp b/src/backends/posix-fs.hpp index 23cdbb597a4b89b1a946a5c0d55a9db49789bc83..5e558b87b4eadbb0a67bd0e24f736c9086b1d472 100644 --- a/src/backends/posix-fs.hpp +++ b/src/backends/posix-fs.hpp @@ -41,8 +41,9 @@ namespace storage { class posix_filesystem final : public storage::backend { public: - posix_filesystem(bool track, const bfs::path& mount, uint32_t quota); + posix_filesystem(const std::string& nsid, bool track, const bfs::path& mount, uint32_t quota); + std::string nsid() const override final; bool is_tracked() const override final; bool is_empty() const override final; bfs::path mount() const override final; @@ -57,9 +58,10 @@ public: std::string to_string() const override final; private: - bool m_track; - bfs::path m_mount; - uint32_t m_quota; + std::string m_nsid; + bool m_track; + bfs::path m_mount; + uint32_t m_quota; }; //NORNS_REGISTER_BACKEND(backend_type::posix_filesystem, posix_filesystem); diff --git a/src/backends/process-memory.cpp b/src/backends/process-memory.cpp index f2a05b2c1c8a7b69b18f414e120f69639b2215e4..cb32c3a559fe4304ddaf93ba446b1adcdf57c6ee 100644 --- a/src/backends/process-memory.cpp +++ b/src/backends/process-memory.cpp @@ -33,7 +33,13 @@ namespace norns { namespace storage { namespace detail { -process_memory::process_memory() { } +process_memory::process_memory(const std::string& nsid) : + m_nsid(nsid) { } + +std::string +process_memory::nsid() const { + return m_nsid; +} bool process_memory::is_tracked() const { diff --git a/src/backends/process-memory.hpp b/src/backends/process-memory.hpp index d3727afc26a5c69bff41c0ab69bb20e8e3d2e2a9..9db0236b87e5644562d4ebb52dea6282b5831168 100644 --- a/src/backends/process-memory.hpp +++ b/src/backends/process-memory.hpp @@ -42,8 +42,9 @@ namespace detail { class process_memory final : public storage::backend { public: - process_memory(); + process_memory(const std::string& nsid); + std::string nsid() const override final; bool is_tracked() const override final; bool is_empty() const override final; bfs::path mount() const override final; @@ -56,6 +57,9 @@ public: bool accepts(resource_info_ptr res) const override final; std::string to_string() const override final; + +private: + std::string m_nsid; }; // no need to register it since it's never going to be created diff --git a/src/backends/remote-backend.cpp b/src/backends/remote-backend.cpp index d5ae203cd6eca86e2985561827ccdacfb8cefdcf..15b7a650a6d3575a2e5c9d687abf3f3e72a7c9e6 100644 --- a/src/backends/remote-backend.cpp +++ b/src/backends/remote-backend.cpp @@ -28,12 +28,19 @@ #include "backend-base.hpp" #include "resources.hpp" #include "remote-backend.hpp" +#include "logger.hpp" namespace norns { namespace storage { namespace detail { -remote_backend::remote_backend() {} +remote_backend::remote_backend(const std::string& nsid) : + m_nsid(nsid) {} + +std::string +remote_backend::nsid() const { + return m_nsid; +} bool remote_backend::is_tracked() const { @@ -45,11 +52,13 @@ remote_backend::is_empty() const { return false; } -bfs::path remote_backend::mount() const { +bfs::path +remote_backend::mount() const { return ""; } -uint32_t remote_backend::quota() const { +uint32_t +remote_backend::quota() const { return 0; } @@ -57,34 +66,44 @@ backend::resource_ptr remote_backend::new_resource(const resource_info_ptr& rinfo, const bool is_collection, std::error_code& ec) const { - (void) rinfo; (void) is_collection; (void) ec; - return std::make_shared(shared_from_this()); //XXX + + const auto d_rinfo = + std::static_pointer_cast(rinfo); + + return std::make_shared(shared_from_this(), d_rinfo); } backend::resource_ptr -remote_backend::get_resource(const resource_info_ptr& rinfo, std::error_code& ec) const { - (void) rinfo; +remote_backend::get_resource(const resource_info_ptr& rinfo, + std::error_code& ec) const { (void) ec; - return std::make_shared(shared_from_this()); //XXX + + const auto d_rinfo = + std::static_pointer_cast(rinfo); + + return std::make_shared(shared_from_this(), d_rinfo); } void -remote_backend::remove(const resource_info_ptr& rinfo, std::error_code& ec) const { +remote_backend::remove(const resource_info_ptr& rinfo, + std::error_code& ec) const { (void) rinfo; (void) ec; } std::size_t -remote_backend::get_size(const resource_info_ptr& rinfo, std::error_code& ec) const { +remote_backend::get_size(const resource_info_ptr& rinfo, + std::error_code& ec) const { (void) rinfo; (void) ec; return 0; //XXX } -bool remote_backend::accepts(resource_info_ptr res) const { +bool +remote_backend::accepts(resource_info_ptr res) const { switch(res->type()) { case data::resource_type::local_posix_path: return true; diff --git a/src/backends/remote-backend.hpp b/src/backends/remote-backend.hpp index 3e944bc32cab4362b4f1c00de3d8707752c2cec0..b4d54832330059e2c55929e4218cf9b1876face9 100644 --- a/src/backends/remote-backend.hpp +++ b/src/backends/remote-backend.hpp @@ -41,8 +41,9 @@ namespace detail { class remote_backend final : public storage::backend { public: - remote_backend(); + remote_backend(const std::string& nsid); + std::string nsid() const override final; bool is_tracked() const override final; bool is_empty() const override final; bfs::path mount() const override final; @@ -55,6 +56,9 @@ public: bool accepts(resource_info_ptr res) const override final; std::string to_string() const override final; + +private: + std::string m_nsid; }; // no need to register it since it's never going to be created diff --git a/src/common/types.cpp b/src/common/types.cpp index 2edaa8d8a43bbc865a61bbf0a0615209ff3d02a6..2c2e5e95ae62c39c82294d1f68ad2f73e99e6069 100644 --- a/src/common/types.cpp +++ b/src/common/types.cpp @@ -55,6 +55,8 @@ std::string to_string(iotask_type type) { return "DATA_MOVE"; case iotask_type::remove: return "DATA_REMOVE"; + case iotask_type::remote_transfer: + return "DATA_TRANSFER"; default: return "UNKNOWN_IOTASK"; } @@ -107,6 +109,10 @@ std::string to_string(urd_error ecode) { return "NORNS_ETASKSPENDING"; case urd_error::accept_paused: return "NORNS_EACCEPTPAUSED"; + case urd_error::resource_exists: + return "NORNS_ERESOURCEEXISTS"; + case urd_error::no_such_resource: + return "NORNS_ENOSUCHRESOURCE"; default: return "UNKNOWN_ERROR"; } diff --git a/src/common/types.hpp b/src/common/types.hpp index 098ad00343bd4778de75923220478aad2bb492d3..aa3fad5401a27a334a20af24f6f13bc8cb02fa51 100644 --- a/src/common/types.hpp +++ b/src/common/types.hpp @@ -48,6 +48,7 @@ enum class iotask_type { copy, move, remove, + remote_transfer, noop, unknown }; @@ -106,6 +107,10 @@ enum class urd_error : norns_error_t { no_such_task = NORNS_ENOSUCHTASK, too_many_tasks = NORNS_ETOOMANYTASKS, tasks_pending = NORNS_ETASKSPENDING, + + /* errors about resources */ + resource_exists = NORNS_ERESOURCEEXISTS, + no_such_resource = NORNS_ENOSUCHRESOURCE, }; namespace utils { diff --git a/src/config/config-schema.hpp b/src/config/config-schema.hpp index 81bfb683c9932d529fa7dd08931337895e393d89..e03c6554e799cccd7ab2dea96337fa4ec9e4435c 100644 --- a/src/config/config-schema.hpp +++ b/src/config/config-schema.hpp @@ -84,6 +84,10 @@ const file_schema valid_options = declare_file({ opt_type::mandatory, converter(parsers::parse_path)), + declare_option( + keywords::bind_address, + opt_type::mandatory), + declare_option( keywords::remote_port, opt_type::mandatory, @@ -98,6 +102,11 @@ const file_schema valid_options = declare_file({ keywords::workers, opt_type::mandatory, converter(parsers::parse_number)), + + declare_option( + keywords::staging_directory, + opt_type::mandatory, + converter(parsers::parse_path)), }) ), diff --git a/src/config/defaults.hpp b/src/config/defaults.hpp index 1a38ec6ad74ff5bcc558f03dd5d2d6a7399ad9fb..ff2488f993e920646e0bffd233af4baca874d0cf 100644 --- a/src/config/defaults.hpp +++ b/src/config/defaults.hpp @@ -48,9 +48,11 @@ namespace defaults { extern const uint32_t dry_run_duration; extern const char* global_socket; extern const char* control_socket; + extern const char* bind_address; extern const in_port_t remote_port; extern const char* pidfile; extern const uint32_t workers_in_pool; + extern const char* staging_directory; extern const uint32_t backlog_size; extern const char* config_file; diff --git a/src/config/keywords.hpp b/src/config/keywords.hpp index e5bf86bd8c925a14b481320ef1bb4d5a43e771f9..e599c38384a58436211b7130bf49362d35cfeec0 100644 --- a/src/config/keywords.hpp +++ b/src/config/keywords.hpp @@ -48,9 +48,11 @@ constexpr static const auto log_file_max_size = "log_file_max_size"; constexpr static const auto dry_run = "dry_run"; constexpr static const auto global_socket = "global_socket"; constexpr static const auto control_socket = "control_socket"; +constexpr static const auto bind_address = "bind_address"; constexpr static const auto remote_port = "remote_port"; constexpr static const auto pidfile = "pidfile"; constexpr static const auto workers = "workers"; +constexpr static const auto staging_directory = "staging_directory"; // option names for 'namespaces' section constexpr static const auto nsid = "nsid"; diff --git a/src/config/settings.cpp b/src/config/settings.cpp index accbf0f20c7ca1eb1cdd716bd49428761c6b27e4..f1881183f240760eb77087ae077618a959ce41a8 100644 --- a/src/config/settings.cpp +++ b/src/config/settings.cpp @@ -44,7 +44,9 @@ namespace bfs = boost::filesystem; namespace norns { namespace config { -settings::settings() { } +settings::settings() { + this->load_defaults(); +} settings::settings(const std::string& progname, bool daemonize, @@ -56,9 +58,11 @@ settings::settings(const std::string& progname, uint32_t dry_run_duration, const bfs::path& global_socket, const bfs::path& control_socket, + const std::string& bind_address, uint32_t remote_port, const bfs::path& pidfile, uint32_t workers, + const bfs::path& staging_directory, uint32_t backlog_size, const bfs::path& cfgfile, const std::list& defns) : @@ -72,14 +76,17 @@ settings::settings(const std::string& progname, m_dry_run_duration(dry_run_duration), m_global_socket(global_socket), m_control_socket(control_socket), + m_bind_address(bind_address), m_remote_port(remote_port), m_daemon_pidfile(pidfile), m_workers_in_pool(workers), + m_staging_directory(staging_directory), m_backlog_size(backlog_size), m_config_file(cfgfile), m_default_namespaces(defns) { } -void settings::load_defaults() { +void +settings::load_defaults() { m_progname = defaults::progname; m_daemonize = defaults::daemonize; m_use_syslog = defaults::use_syslog; @@ -90,15 +97,18 @@ void settings::load_defaults() { m_dry_run_duration = defaults::dry_run_duration; m_global_socket = defaults::global_socket; m_control_socket = defaults::control_socket; + m_bind_address = defaults::bind_address; m_remote_port = defaults::remote_port; m_daemon_pidfile = defaults::pidfile; m_workers_in_pool = defaults::workers_in_pool; + m_staging_directory = defaults::staging_directory; m_backlog_size = defaults::backlog_size; m_config_file = defaults::config_file; m_default_namespaces.clear(); } -void settings::load_from_file(const bfs::path& filename) { +void +settings::load_from_file(const bfs::path& filename) { file_options::options_map opt_map; file_options::parse_yaml_file(filename, config::valid_options, opt_map); @@ -124,9 +134,12 @@ void settings::load_from_file(const bfs::path& filename) { m_dry_run_duration = defaults::dry_run_duration; m_global_socket = gsettings.get_as(keywords::global_socket); m_control_socket = gsettings.get_as(keywords::control_socket); + m_bind_address = gsettings.get_as(keywords::bind_address); m_remote_port = gsettings.get_as(keywords::remote_port); m_daemon_pidfile = gsettings.get_as(keywords::pidfile); m_workers_in_pool = gsettings.get_as(keywords::workers); + m_staging_directory = + gsettings.get_as(keywords::staging_directory); m_backlog_size = defaults::backlog_size; // load definitions for default namespaces @@ -144,7 +157,8 @@ void settings::load_from_file(const bfs::path& filename) { } } -std::string settings::to_string() const { +std::string +settings::to_string() const { std::string str = std::string("settings {\n") + " m_progname: " + m_progname + ",\n" + " m_daemonize: " + (m_daemonize ? "true" : "false") + ",\n" + @@ -156,9 +170,11 @@ std::string settings::to_string() const { " m_dry_run_duration: " + std::to_string(m_dry_run_duration) + ",\n" + " m_global_socket: " + m_global_socket.string() + ",\n" + " m_control_socket: " + m_control_socket.string() + ",\n" + + " m_bind_address: " + m_bind_address + ",\n" + " m_remote_port: " + std::to_string(m_remote_port) + ",\n" + " m_pidfile: " + m_daemon_pidfile.string() + ",\n" + " m_workers: " + std::to_string(m_workers_in_pool) + ",\n" + + " m_staging_directory: " + m_staging_directory.string() + ",\n" + " m_backlog_size: " + std::to_string(m_backlog_size) + ",\n" + " m_config_file: " + m_config_file.string() + ",\n" + "};"; @@ -166,70 +182,186 @@ std::string settings::to_string() const { return str; } -std::string& settings::progname() { +std::string +settings::progname() const { return m_progname; } -bool& settings::daemonize() { +void +settings::progname(const std::string& progname) { + m_progname = progname; +} + +bool +settings::daemonize() const { return m_daemonize; } -bool& settings::use_syslog() { +void +settings::daemonize(bool daemonize) { + m_daemonize = daemonize; +} + +bool +settings::use_syslog() const { return m_use_syslog; } -bool& settings::use_console() { +void +settings::use_syslog(bool use_syslog) { + m_use_syslog = use_syslog; +} + +bool +settings::use_console() const { return m_use_console; } -bfs::path& settings::log_file() { +void +settings::use_console(bool use_console) { + m_use_console = use_console; +} + +bfs::path +settings::log_file() const { return m_log_file; } -uint32_t& settings::log_file_max_size() { +void +settings::log_file(const bfs::path& log_file) { + m_log_file = log_file; +} + +uint32_t +settings::log_file_max_size() const { return m_log_file_max_size; } -bool& settings::dry_run() { +void +settings::log_file_max_size(uint32_t log_file_max_size) { + m_log_file_max_size = log_file_max_size; +} + +bool +settings::dry_run() const { return m_dry_run; } -uint32_t& settings::dry_run_duration() { +void +settings::dry_run(bool dry_run) { + m_dry_run = dry_run; +} + +uint32_t +settings::dry_run_duration() const { return m_dry_run_duration; } -bfs::path& settings::global_socket() { +void +settings::dry_run_duration(uint32_t dry_run_duration) { + m_dry_run_duration = dry_run_duration; +} + +bfs::path +settings::global_socket() const { return m_global_socket; } -bfs::path& settings::control_socket() { +void +settings::global_socket(const bfs::path& global_socket) { + m_global_socket = global_socket; +} + +bfs::path +settings::control_socket() const { return m_control_socket; } -in_port_t& settings::remote_port() { +void +settings::control_socket(const bfs::path& control_socket) { + m_control_socket = control_socket; +} + +std::string +settings::bind_address() const { + return m_bind_address; +} + +void +settings::bind_address(const std::string& bind_address) { + m_bind_address = bind_address; +} + +in_port_t +settings::remote_port() const { return m_remote_port; } -bfs::path& settings::pidfile() { +void +settings::remote_port(in_port_t remote_port) { + m_remote_port = remote_port; +} + +bfs::path +settings::pidfile() const { return m_daemon_pidfile; } -uint32_t& settings::workers_in_pool() { +void +settings::pidfile(const bfs::path& pidfile) { + m_daemon_pidfile = pidfile; +} + +uint32_t +settings::workers_in_pool() const { return m_workers_in_pool; } -uint32_t& settings::backlog_size() { +void +settings::workers_in_pool(uint32_t workers_in_pool) { + m_workers_in_pool = workers_in_pool; +} + +bfs::path +settings::staging_directory() const { + return m_staging_directory; +} + +void +settings::staging_directory(const bfs::path& staging_directory) { + m_staging_directory = staging_directory; +} + +uint32_t +settings::backlog_size() const { return m_backlog_size; } -bfs::path& settings::config_file() { +void +settings::backlog_size(uint32_t backlog_size) { + m_backlog_size = backlog_size; +} + +bfs::path +settings::config_file() const { return m_config_file; } -std::list& settings::default_namespaces() { +void +settings::config_file(const bfs::path& config_file) { + m_config_file = config_file; +} + +std::list +settings::default_namespaces() const { return m_default_namespaces; } +void +settings::default_namespaces( + const std::list& default_namespaces) { + m_default_namespaces = default_namespaces; +} + } // namespace config } // namespace norns - diff --git a/src/config/settings.hpp b/src/config/settings.hpp index 309a515d0b24a60de30c3df679d79cb9d8392f3c..d0c62b2027b062d3b92e67b909e375c1c6561ce3 100644 --- a/src/config/settings.hpp +++ b/src/config/settings.hpp @@ -52,41 +52,58 @@ struct namespace_def { m_capacity(capacity), m_visibility(visibility) { } - std::string nsid() const { + namespace_def(const namespace_def& other) = default; + + namespace_def(namespace_def&& rhs) = default; + + namespace_def& + operator=(const namespace_def& other) = default; + + namespace_def& + operator=(namespace_def&& rhs) = default; + + std::string + nsid() const { return m_nsid; } - bool track() const { + bool + track() const { return m_track; } - bfs::path mountpoint() const { + bfs::path + mountpoint() const { return m_mountpoint; } - std::string alias() const { + std::string + alias() const { return m_alias; } - uint64_t capacity() const { + uint64_t + capacity() const { return m_capacity; } - std::string visibility() const { + std::string + visibility() const { return m_visibility; } - const std::string m_nsid; - const bool m_track; - const bfs::path m_mountpoint; - const std::string m_alias; - const uint64_t m_capacity; - const std::string m_visibility; + std::string m_nsid; + bool m_track; + bfs::path m_mountpoint; + std::string m_alias; + uint64_t m_capacity; + std::string m_visibility; }; struct settings { settings(); + settings(const std::string& progname, bool daemonize, bool use_syslog, @@ -97,48 +114,161 @@ struct settings { uint32_t dry_run_duration, const bfs::path& global_socket, const bfs::path& control_socket, + const std::string& bind_address, uint32_t remote_port, const bfs::path& pidfile, uint32_t workers, + const bfs::path& staging_directory, uint32_t backlog_size, const bfs::path& cfgfile, const std::list& defns); - void load_defaults(); - void load_from_file(const bfs::path& filename); - std::string to_string() const; - - std::string& progname(); - bool& daemonize(); - bool& use_syslog(); - bool& use_console(); - bfs::path& log_file(); - uint32_t& log_file_max_size(); - bool& dry_run(); - uint32_t& dry_run_duration(); - bfs::path& global_socket(); - bfs::path& control_socket(); - in_port_t& remote_port(); - bfs::path& pidfile(); - uint32_t& workers_in_pool(); - uint32_t& backlog_size(); - bfs::path& config_file(); - std::list& default_namespaces(); - - std::string m_progname = defaults::progname; - bool m_daemonize = defaults::daemonize; - bool m_use_syslog = defaults::use_syslog; - bool m_use_console = defaults::use_console; - bfs::path m_log_file = defaults::log_file; - uint32_t m_log_file_max_size = defaults::log_file_max_size; - bool m_dry_run = defaults::dry_run; - uint32_t m_dry_run_duration = defaults::dry_run_duration; - bfs::path m_global_socket = defaults::global_socket; - bfs::path m_control_socket = defaults::control_socket; - in_port_t m_remote_port = defaults::remote_port; - bfs::path m_daemon_pidfile = defaults::pidfile; - uint32_t m_workers_in_pool = defaults::workers_in_pool; - uint32_t m_backlog_size = defaults::backlog_size; - bfs::path m_config_file = defaults::config_file; + + void + load_defaults(); + + void + load_from_file(const bfs::path& filename); + + std::string + to_string() const; + + settings(const settings& other) = default; + + settings(settings&& rhs) = default; + + settings& + operator=(const settings& other) = default; + + settings& + operator=(settings&& rhs) = default; + + ~settings() = default; + + std::string + progname() const; + + void + progname(const std::string& progname); + + bool + daemonize() const; + + void + daemonize(bool daemonize); + + bool + use_syslog() const; + + void + use_syslog(bool use_syslog); + + bool + use_console() const; + + void + use_console(bool use_console); + + bfs::path + log_file() const; + + void + log_file(const bfs::path& log_file); + + uint32_t + log_file_max_size() const; + + void + log_file_max_size(uint32_t log_file_max_size); + + bool + dry_run() const; + + void + dry_run(bool dry_run); + + uint32_t + dry_run_duration() const; + + void + dry_run_duration(uint32_t dry_run_duration); + + bfs::path + global_socket() const; + + void + global_socket(const bfs::path& global_socket); + + bfs::path + control_socket() const; + + void + control_socket(const bfs::path& control_socket); + + std::string + bind_address() const; + + void + bind_address(const std::string& bind_address); + + in_port_t + remote_port() const; + + void + remote_port(in_port_t remote_port); + + bfs::path + pidfile() const; + + void + pidfile(const bfs::path& pidfile); + + uint32_t + workers_in_pool() const; + + void + workers_in_pool(uint32_t workers_in_pool); + + bfs::path + staging_directory() const; + + void + staging_directory(const bfs::path& staging_directory); + + uint32_t + backlog_size() const; + + void + backlog_size(uint32_t backlog_size); + + bfs::path + config_file() const; + + void + config_file(const bfs::path& config_file); + + std::list + default_namespaces() const; + + void + default_namespaces(const std::list& default_namespaces); + + std::string m_progname; + bool m_daemonize; + bool m_use_syslog; + bool m_use_console; + bfs::path m_log_file; + uint32_t m_log_file_max_size; + bool m_dry_run; + uint32_t m_dry_run_duration; + bfs::path m_global_socket; + bfs::path m_control_socket; + std::string m_bind_address; + in_port_t m_remote_port; + bfs::path m_daemon_pidfile; + uint32_t m_workers_in_pool; + bfs::path m_staging_directory; + uint32_t m_backlog_size; + bfs::path m_config_file; std::list m_default_namespaces; }; diff --git a/src/context.hpp b/src/context.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8530e94bc8307c5553c90e311d733ed3713b9447 --- /dev/null +++ b/src/context.hpp @@ -0,0 +1,65 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef NORNS_CONTEXT_HPP +#define NORNS_CONTEXT_HPP + +#include +#include + +namespace bfs = boost::filesystem; + +namespace hermes { + class async_engine; +} // namespace hermes + +namespace norns { + +struct context { + + context(bfs::path staging_directory, + std::shared_ptr network_service) : + m_staging_directory(std::move(staging_directory)), + m_network_service(std::move(network_service)) { } + + bfs::path + staging_directory() const { + return m_staging_directory; + } + + std::shared_ptr + network_service() const { + return m_network_service; + } + + bfs::path m_staging_directory; + std::shared_ptr m_network_service; +}; + +} // namespace norns + +#endif // NORNS_CONTEXT_HPP diff --git a/src/externals/hermes b/src/externals/hermes new file mode 160000 index 0000000000000000000000000000000000000000..c5211af77d4dca1f596f7da2d3a6f13cf1bee014 --- /dev/null +++ b/src/externals/hermes @@ -0,0 +1 @@ +Subproject commit c5211af77d4dca1f596f7da2d3a6f13cf1bee014 diff --git a/src/io/task-copy.hpp b/src/io/task-copy.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e0e709315f20566608aeb9dad1fddbe2407146c8 --- /dev/null +++ b/src/io/task-copy.hpp @@ -0,0 +1,100 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __IO_TASK_COPY_HPP__ +#define __IO_TASK_COPY_HPP__ + +namespace norns { +namespace io { + +///////////////////////////////////////////////////////////////////////////////// +// specializations for copy tasks +///////////////////////////////////////////////////////////////////////////////// +template<> +inline void +task::operator()() { + + const auto tid = m_task_info->id(); + const auto type = m_task_info->type(); + const auto auth = m_task_info->auth(); + const auto src_backend = m_task_info->src_backend(); + const auto src_rinfo = m_task_info->src_rinfo(); + const auto dst_backend = m_task_info->dst_backend(); + const auto dst_rinfo = m_task_info->dst_rinfo(); + std::error_code ec; + + // helper lambda for error reporting + const auto log_error = [&] (const std::string& msg) { + + m_task_info->update_status(task_status::finished_with_error, + urd_error::system_error, ec); + std::string r_msg = "[{}] " + msg + ": {}"; + + LOGGER_ERROR(r_msg.c_str(), tid, ec.message()); + LOGGER_WARN("[{}] I/O task completed with error", tid); + + }; + + LOGGER_WARN("[{}] Starting I/O task", tid); + LOGGER_WARN("[{}] TYPE: {}", tid, utils::to_string(type)); + LOGGER_WARN("[{}] FROM: {}", tid, src_backend->to_string()); + LOGGER_WARN("[{}] TO: {}", tid, dst_backend->to_string()); + + m_task_info->update_status(task_status::running); + + auto src = src_backend->get_resource(src_rinfo, ec); + + if(ec) { + log_error("Could not access input data " + src_rinfo->to_string()); + return; + } + + auto dst = dst_backend->new_resource(dst_rinfo, src->is_collection(), ec); + + if(ec) { + log_error("Could not create output data " + dst_rinfo->to_string()); + return; + } + + ec = m_transferor->transfer(auth, m_task_info, src, dst); + + if(ec) { + log_error("Transfer failed"); + return; + } + + LOGGER_WARN("[{}] I/O task completed successfully [{} MiB/s]", + tid, m_task_info->bandwidth()); + + m_task_info->update_status(task_status::finished, urd_error::success, + std::make_error_code(static_cast(ec.value()))); +} + +} // namespace io +} // namespace norns + +#endif // __IO_TASK_COPY_HPP__ diff --git a/src/io/task-info.cpp b/src/io/task-info.cpp index 11d80de3bcf3ced878fe366f858f8e9a795c620d..0d394ec186ab7154b26157a9472b7f2f2fd81da1 100644 --- a/src/io/task-info.cpp +++ b/src/io/task-info.cpp @@ -34,17 +34,24 @@ namespace norns { namespace io { -task_info::task_info(const iotask_id tid, const iotask_type type, - const auth::credentials& auth, - const backend_ptr src_backend, const resource_info_ptr src_rinfo, - const backend_ptr dst_backend, const resource_info_ptr dst_rinfo) : +task_info::task_info(const iotask_id tid, + const iotask_type type, + const bool is_remote, + const auth::credentials& auth, + const backend_ptr src_backend, + const resource_info_ptr src_rinfo, + const backend_ptr dst_backend, + const resource_info_ptr dst_rinfo, + const boost::any& ctx) : m_id(tid), m_type(type), + m_is_remote(is_remote), m_auth(auth), m_src_backend(src_backend), m_src_rinfo(src_rinfo), m_dst_backend(dst_backend), m_dst_rinfo(dst_rinfo), + m_ctx(ctx), m_status(task_status::pending), m_task_error(urd_error::success), m_sys_error(), @@ -52,6 +59,10 @@ task_info::task_info(const iotask_id tid, const iotask_type type, m_sent_bytes(), m_total_bytes() { + if(!src_rinfo) { + return; + } + std::error_code ec; m_total_bytes = src_backend->get_size(src_rinfo, ec); @@ -61,6 +72,8 @@ task_info::task_info(const iotask_id tid, const iotask_type type, } } +task_info::~task_info() { } + iotask_id task_info::id() const { return m_id; @@ -71,6 +84,11 @@ task_info::type() const { return m_type; } +bool +task_info::is_remote() const { + return m_is_remote; +} + auth::credentials task_info::auth() const { return m_auth; @@ -96,6 +114,16 @@ task_info::dst_rinfo() const { return m_dst_rinfo; } +boost::any +task_info::context() const { + return m_ctx; +} + +void +task_info::set_context(const boost::any& ctx) { + m_ctx = ctx; +} + task_status task_info::status() const { boost::shared_lock lock(m_mutex); @@ -118,6 +146,17 @@ task_info::update_status(const task_status st, const urd_error ec, m_sys_error = sc; } +urd_error +task_info::task_error() const { + boost::shared_lock lock(m_mutex); + return m_task_error; +} + +std::error_code +task_info::sys_error() const { + return m_sys_error; +} + std::size_t task_info::sent_bytes() const { boost::shared_lock lock(m_mutex); diff --git a/src/io/task-info.hpp b/src/io/task-info.hpp index c59dedbfa16c6da0251aa6e7efd81234d19af8fb..0f260b8e4b3bcd9eef78fc9dab60f01db333c20a 100644 --- a/src/io/task-info.hpp +++ b/src/io/task-info.hpp @@ -28,6 +28,7 @@ #ifndef __TASK_INFO_HPP__ #define __TASK_INFO_HPP__ +#include #include #include "backends.hpp" #include "resources.hpp" @@ -45,31 +46,81 @@ struct task_info { using backend_ptr = std::shared_ptr; using resource_info_ptr = std::shared_ptr; - task_info(const iotask_id tid, const iotask_type type, - const auth::credentials& creds, - const backend_ptr src_backend, const resource_info_ptr src_rinfo, - const backend_ptr dst_backend, const resource_info_ptr dst_rinfo); + task_info(const iotask_id tid, + const iotask_type type, + const bool is_remote, + const auth::credentials& creds, + const backend_ptr src_backend, + const resource_info_ptr src_rinfo, + const backend_ptr dst_backend, + const resource_info_ptr dst_rinfo, + const boost::any& ctx = {}); - iotask_id id() const; - iotask_type type() const; - auth::credentials auth() const ; - backend_ptr src_backend() const; - resource_info_ptr src_rinfo() const; - backend_ptr dst_backend() const; - resource_info_ptr dst_rinfo() const; + ~task_info(); - task_status status() const; - void update_status(const task_status st); - void update_status(const task_status st, const urd_error ec, - const std::error_code& sc); + iotask_id + id() const; - std::size_t sent_bytes() const; - std::size_t total_bytes() const; - double bandwidth() const; - void update_bandwidth(std::size_t bytes, double usecs); - void record_transfer(std::size_t bytes, double usecs); + iotask_type + type() const; - task_stats stats() const; + bool + is_remote() const; + + auth::credentials + auth() const ; + + backend_ptr + src_backend() const; + + resource_info_ptr + src_rinfo() const; + + backend_ptr + dst_backend() const; + + resource_info_ptr + dst_rinfo() const; + + boost::any + context() const; + + void + set_context(const boost::any& ctx); + + task_status + status() const; + + void + update_status(const task_status st); + + void + update_status(const task_status st, const urd_error ec, + const std::error_code& sc); + + urd_error + task_error() const; + + std::error_code + sys_error() const; + + std::size_t + sent_bytes() const; + + std::size_t + total_bytes() const; + + double + bandwidth() const; + + void + update_bandwidth(std::size_t bytes, double usecs); + + void + record_transfer(std::size_t bytes, double usecs); + + task_stats + stats() const; boost::shared_lock lock_shared() const; @@ -82,6 +133,7 @@ struct task_info { // task id and type const iotask_id m_id; const iotask_type m_type; + const bool m_is_remote; // user credentials const auth::credentials m_auth; @@ -92,6 +144,9 @@ struct task_info { const backend_ptr m_dst_backend; const resource_info_ptr m_dst_rinfo; + // optional task context + boost::any m_ctx; + // general task status task_status m_status; urd_error m_task_error; diff --git a/src/io/task-manager.cpp b/src/io/task-manager.cpp index 0651d5daa576346ceca60f8d5fe12f9736321fa0..6a1e4f40f6567ccfac2bb81c5714a1a4b0838670 100644 --- a/src/io/task-manager.cpp +++ b/src/io/task-manager.cpp @@ -137,7 +137,8 @@ task_manager::register_transfer_plugin(const data::resource_type t1, std::tuple> -task_manager::create_task(iotask_type type, const auth::credentials& auth, +task_manager::create_task(iotask_type type, + const auth::credentials& auth, const std::vector& backend_ptrs, const std::vector& rinfo_ptrs) { @@ -168,7 +169,7 @@ task_manager::create_task(iotask_type type, const auth::credentials& auth, auto it = m_task_info.end(); std::tie(it, std::ignore) = m_task_info.emplace(tid, - std::make_shared(tid, type, auth, + std::make_shared(tid, type, false, auth, src_backend, src_rinfo, dst_backend, dst_rinfo)); return it->second; @@ -294,6 +295,228 @@ task_manager::create_task(iotask_type type, const auth::credentials& auth, return std::make_tuple(urd_error::success, tid); } +std::tuple> +task_manager::create_local_initiated_task(iotask_type type, + const auth::credentials& auth, + const std::vector& backend_ptrs, + const std::vector& rinfo_ptrs) { + + boost::unique_lock lock(m_mutex); + + // fetch an iotask_id for this task + iotask_id tid = ++m_id_base; + + if(m_task_info.count(tid) != 0) { + --m_id_base; + return std::make_tuple(urd_error::too_many_tasks, boost::none); + } + + // immediately-invoked lambda to create the appropriate metadata for the task + // and register it into m_task_info + const auto task_info_ptr = [&]() { + + assert(backend_ptrs.size() == 1 || backend_ptrs.size() == 2); + + const backend_ptr src_backend = backend_ptrs[0]; + const resource_info_ptr src_rinfo = rinfo_ptrs[0]; + const backend_ptr dst_backend = (backend_ptrs.size() == 1 ? + nullptr : backend_ptrs[1]); + const resource_info_ptr dst_rinfo = (rinfo_ptrs.size() == 1 ? + nullptr : rinfo_ptrs[1]); + + auto it = m_task_info.end(); + std::tie(it, std::ignore) = m_task_info.emplace(tid, + std::make_shared(tid, type, false, auth, + src_backend, src_rinfo, + dst_backend, dst_rinfo)); + return it->second; + }(); + + + std::shared_ptr tx_ptr; + + if(type == iotask_type::copy || type == iotask_type::move) { + + assert(backend_ptrs.size() == 2); + + tx_ptr = m_transferor_registry.get(rinfo_ptrs[0]->type(), + rinfo_ptrs[1]->type()); + if(!tx_ptr) { + return std::make_tuple(urd_error::not_supported, boost::none); + } + + LOGGER_DEBUG("Selected plugin: {}", tx_ptr->to_string()); + + if(!tx_ptr->validate(rinfo_ptrs[0], rinfo_ptrs[1])) { + return std::make_tuple(urd_error::bad_args, boost::none); + } + } + + if(m_dry_run) { + type = iotask_type::noop; + } + + switch(type) { + case iotask_type::remove: + { + assert(backend_ptrs.size() == 1); + + return std::make_tuple( + urd_error::success, + generic_task(type, + io::task( + std::move(task_info_ptr)))); + } + + case iotask_type::copy: + { + return std::make_tuple( + urd_error::success, + generic_task(type, + io::task( + std::move(task_info_ptr), std::move(tx_ptr)))); + break; + } + + case iotask_type::move: + { + + return std::make_tuple( + urd_error::success, + generic_task(type, + io::task( + std::move(task_info_ptr), std::move(tx_ptr)))); + break; + } + + case iotask_type::noop: + { + return std::make_tuple( + urd_error::success, + generic_task(type, + io::task( + std::move(task_info_ptr), m_dry_run_duration))); + break; + } + + default: + break; + } + + return std::make_tuple(urd_error::bad_args, boost::none); +} + +std::tuple> +task_manager::create_remote_initiated_task(iotask_type task_type, + const auth::credentials& auth, + const boost::any& ctx, + const backend_ptr src_backend, + const resource_info_ptr src_rinfo, + const backend_ptr dst_backend, + const resource_info_ptr dst_rinfo) { + + boost::unique_lock lock(m_mutex); + + // fetch an iotask_id for this task + iotask_id tid = ++m_id_base; + + if(m_task_info.count(tid) != 0) { + --m_id_base; + return std::make_tuple(urd_error::too_many_tasks, boost::none); + } + + // immediately-invoked lambda to create the appropriate metadata for the task + // and register it into m_task_info + const auto task_info_ptr = [&]() { + + auto it = m_task_info.end(); + std::tie(it, std::ignore) = m_task_info.emplace(tid, + std::make_shared(tid, task_type, true, auth, + src_backend, src_rinfo, + dst_backend, dst_rinfo, + ctx)); + return it->second; + }(); + + //auto tx_ptr = + // m_transferor_registry.get(src_rinfo->type(), dst_rinfo->type()); + + // XXX for a remote-initiated task the order of the types + // is swapped + auto tx_ptr = + m_transferor_registry.get(dst_rinfo->type(), src_rinfo->type()); + + if(!tx_ptr) { + return std::make_tuple(urd_error::not_supported, boost::none); + } + + LOGGER_DEBUG("Selected plugin: {}", tx_ptr->to_string()); + + if(!tx_ptr->validate(src_rinfo, dst_rinfo)) { + return std::make_tuple(urd_error::bad_args, boost::none); + } + + return std::make_tuple( + urd_error::success, + generic_task(task_type, + io::task( + std::move(task_info_ptr), std::move(tx_ptr)))); +} + + +urd_error +task_manager::enqueue_task(io::generic_task&& t) { + + // helper lambda to register the completion of tasks so that we can keep track + // of the consumed bandwidth by each task + // N.B: we use capture-by-value here so that the task_info_ptr is valid when + // the callback is invoked. + const auto completion_callback = [this, t]() { + assert(t.info()->status() == task_status::finished || + t.info()->status() == task_status::finished_with_error); + + LOGGER_DEBUG("Task {} finished [{} MiB/s]", + t.info()->id(), t.info()->bandwidth()); + + auto bw = t.info()->bandwidth(); + + // bw might be nan if the task did not finish correctly + if(!std::isnan(bw)) { + + const auto key = std::make_pair(t.info()->src_rinfo()->nsid(), + t.info()->dst_rinfo()->nsid()); + + if(!m_bandwidth_backlog.count(key)) { + m_bandwidth_backlog.emplace(key, + boost::circular_buffer(m_backlog_size)); + } + + m_bandwidth_backlog.at(key).push_back(bw); + } + }; + + switch(t.m_type) { + case iotask_type::remove: + case iotask_type::noop: + { + m_runners.submit_and_forget(t); + break; + } + + case iotask_type::copy: + case iotask_type::move: + { + m_runners.submit_with_epilog_and_forget(t, completion_callback); + break; + } + + default: + return urd_error::bad_args; + } + + return urd_error::success; +} + std::shared_ptr task_manager::find(iotask_id tid) const { diff --git a/src/io/task-manager.hpp b/src/io/task-manager.hpp index 5529ef0efe64b3be2ffeb4de4c8d49c5dd9d3821..36a10c7d795e29280da2f7c3c67361e14d705637 100644 --- a/src/io/task-manager.hpp +++ b/src/io/task-manager.hpp @@ -91,6 +91,24 @@ struct task_manager { const std::vector>& backend_ptrs, const std::vector>& rinfo_ptrs); + std::tuple> + create_local_initiated_task(iotask_type type, + const auth::credentials& auth, + const std::vector& backend_ptrs, + const std::vector& rinfo_ptrs); + + std::tuple> + create_remote_initiated_task(iotask_type task_type, + const auth::credentials& auth, + const boost::any& ctx, + const backend_ptr src_backend, + const resource_info_ptr src_rinfo, + const backend_ptr dst_backend, + const resource_info_ptr dst_rinfo); + + urd_error + enqueue_task(io::generic_task&& t); + std::shared_ptr find(iotask_id) const; diff --git a/src/io/task-move.hpp b/src/io/task-move.hpp new file mode 100644 index 0000000000000000000000000000000000000000..61c84c7040ff2f6e64e5c4963defa644ca002731 --- /dev/null +++ b/src/io/task-move.hpp @@ -0,0 +1,100 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __IO_TASK_MOVE_HPP__ +#define __IO_TASK_MOVE_HPP__ + +namespace norns { +namespace io { + +///////////////////////////////////////////////////////////////////////////////// +// specializations for move tasks +///////////////////////////////////////////////////////////////////////////////// +template<> +inline void +task::operator()() { + + std::error_code ec; + + const auto tid = m_task_info->id(); + const auto type = m_task_info->type(); + const auto src_backend = m_task_info->src_backend(); + const auto src_rinfo = m_task_info->src_rinfo(); + const auto dst_backend = m_task_info->dst_backend(); + const auto dst_rinfo = m_task_info->dst_rinfo(); + const auto auth = m_task_info->auth(); + + // helper lambda for error reporting + const auto log_error = [&] (const std::string& msg) { + m_task_info->update_status(task_status::finished_with_error, + urd_error::system_error, ec); + + std::string r_msg = "[{}] " + msg + ": {}"; + + LOGGER_ERROR(r_msg.c_str(), tid, ec.message()); + LOGGER_WARN("[{}] I/O task completed with error", tid); + }; + + LOGGER_WARN("[{}] Starting I/O task", tid); + LOGGER_WARN("[{}] TYPE: {}", tid, utils::to_string(type)); + LOGGER_WARN("[{}] FROM: {}", tid, src_backend->to_string()); + LOGGER_WARN("[{}] TO: {}", tid, dst_backend->to_string()); + + m_task_info->update_status(task_status::running); + + auto src = src_backend->get_resource(src_rinfo, ec); + + if(ec) { + log_error("Could not access input data " + src_rinfo->to_string()); + return; + } + + auto dst = dst_backend->new_resource(dst_rinfo, src->is_collection(), ec); + + if(ec) { + log_error("Could not create output data " + dst_rinfo->to_string()); + return; + } + + ec = m_transferor->transfer(auth, m_task_info, src, dst); + + if(ec) { + log_error("Transfer failed"); + return; + } + + LOGGER_WARN("[{}] I/O task completed successfully [{} MiB/s]", + tid, m_task_info->bandwidth()); + + m_task_info->update_status(task_status::finished, urd_error::success, + std::make_error_code(static_cast(ec.value()))); +} + +} // namespace io +} // namespace norns + +#endif // __IO_TASK_MOVE_HPP__ diff --git a/src/io/task-noop.hpp b/src/io/task-noop.hpp new file mode 100644 index 0000000000000000000000000000000000000000..73de719c4d8c8ade2bf21e4815efae8ba81598f1 --- /dev/null +++ b/src/io/task-noop.hpp @@ -0,0 +1,78 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __IO_TASK_NOOP_HPP__ +#define __IO_TASK_NOOP_HPP__ + +namespace norns { +namespace io { + +///////////////////////////////////////////////////////////////////////////////// +// specializations for noop tasks +///////////////////////////////////////////////////////////////////////////////// +template <> +struct task { + + using task_info_ptr = std::shared_ptr; + + task(const task_info_ptr&& task_info, uint32_t sleep_duration) + : m_task_info(std::move(task_info)), + m_sleep_duration(sleep_duration) { } + + task(const task& other) = default; + task(task&& rhs) = default; + task& operator=(const task& other) = default; + task& operator=(task&& rhs) = default; + + void operator()() { + const auto tid = m_task_info->id(); + + LOGGER_WARN("[{}] Starting noop I/O task", tid); + + LOGGER_DEBUG("[{}] Sleep for {} usecs", tid, m_sleep_duration); + usleep(m_sleep_duration); + + m_task_info->update_status(task_status::running); + + LOGGER_WARN("[{}] noop I/O task \"running\"", tid); + + LOGGER_DEBUG("[{}] Sleep for {} usecs", tid, m_sleep_duration); + usleep(m_sleep_duration); + + m_task_info->update_status(task_status::finished); + + LOGGER_WARN("[{}] noop I/O task completed successfully", tid); + } + + task_info_ptr m_task_info; + uint32_t m_sleep_duration; +}; + +} // namespace io +} // namespace norns + +#endif // __IO_TASK_NOOP_HPP__ diff --git a/src/io/task-remote-transfer.hpp b/src/io/task-remote-transfer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..53c5966b5ab1ffc93ee90814a389e525cc1ed142 --- /dev/null +++ b/src/io/task-remote-transfer.hpp @@ -0,0 +1,111 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __IO_TASK_REMOTE_TRANSFER_HPP__ +#define __IO_TASK_REMOTE_TRANSFER_HPP__ + +namespace norns { +namespace io { + +///////////////////////////////////////////////////////////////////////////////// +// specializations for remote transfer tasks +///////////////////////////////////////////////////////////////////////////////// +template<> +inline void +task::operator()() { + + const auto tid = m_task_info->id(); + const auto type = m_task_info->type(); + const auto auth = m_task_info->auth(); + const auto src_backend = m_task_info->src_backend(); + const auto src_rinfo = m_task_info->src_rinfo(); + const auto dst_backend = m_task_info->dst_backend(); + const auto dst_rinfo = m_task_info->dst_rinfo(); + std::error_code ec; + + // helper lambda for error reporting + const auto log_error = [&] (const std::string& msg) { + + m_task_info->update_status(task_status::finished_with_error, + urd_error::system_error, ec); + std::string r_msg = "[{}] " + msg + ": {}"; + + LOGGER_ERROR(r_msg.c_str(), tid, ec.message()); + LOGGER_WARN("[{}] I/O task completed with error", tid); + + }; + + LOGGER_WARN("[{}] Starting I/O task", tid); + LOGGER_WARN("[{}] TYPE: {}", tid, utils::to_string(type)); + LOGGER_WARN("[{}] FROM: {}", tid, src_rinfo->to_string());//src_backend->to_string()); + LOGGER_WARN("[{}] TO: {}", tid, dst_rinfo->to_string());//dst_backend->to_string()); + + m_task_info->update_status(task_status::running); + + auto src = src_backend->get_resource(src_rinfo, ec); + + if(ec) { + log_error("Could not access input data " + src_rinfo->to_string()); + return; + } + + auto dst = dst_backend->new_resource(dst_rinfo, src->is_collection(), ec); + + if(ec) { + log_error("Could not create output data " + dst_rinfo->to_string()); + return; + } + + if(m_task_info->is_remote()) { + ec = m_transferor->accept_transfer(auth, m_task_info, src, dst); + } + else { + ec = m_transferor->transfer(auth, m_task_info, src, dst); + } + + + if(ec) { + log_error("Transfer failed"); + return; + } + + if(m_task_info->is_remote()) { + LOGGER_WARN("[{}] I/O task completed successfully", tid); + } + else { + LOGGER_WARN("[{}] I/O task completed successfully [{} MiB/s]", + tid, m_task_info->bandwidth()); + } + + m_task_info->update_status(task_status::finished, urd_error::success, + std::make_error_code(static_cast(ec.value()))); +} + +} // namespace io +} // namespace norns + +#endif // __IO_TASK_REMOTE_TRANSFER_HPP__ diff --git a/src/io/task-remove.hpp b/src/io/task-remove.hpp new file mode 100644 index 0000000000000000000000000000000000000000..49ac5a64a79d473a372d28906e6c03ee523faa79 --- /dev/null +++ b/src/io/task-remove.hpp @@ -0,0 +1,81 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __IO_TASK_REMOVE_HPP__ +#define __IO_TASK_REMOVE_HPP__ + +namespace norns { +namespace io { + +///////////////////////////////////////////////////////////////////////////////// +// specializations for remove tasks +///////////////////////////////////////////////////////////////////////////////// +template<> +inline void +task::operator()() { + + std::error_code ec; + + const auto tid = m_task_info->id(); + const auto type = m_task_info->type(); + const auto src_backend = m_task_info->src_backend(); + const auto src_rinfo = m_task_info->src_rinfo(); + //const auto auth = m_task_info->auth(); + + // helper lambda for error reporting + const auto log_error = [&] (const std::string& msg) { + m_task_info->update_status(task_status::finished_with_error, + urd_error::system_error, ec); + + std::string r_msg = "[{}] " + msg + ": {}"; + + LOGGER_ERROR(r_msg.c_str(), tid, ec.message()); + LOGGER_WARN("[{}] I/O task completed with error", tid); + }; + + LOGGER_WARN("[{}] Starting I/O task", tid); + LOGGER_WARN("[{}] TYPE: {}", tid, utils::to_string(type)); + LOGGER_WARN("[{}] FROM: {}", tid, src_backend->to_string()); + + m_task_info->update_status(task_status::running); + + src_backend->remove(src_rinfo, ec); + + if(ec) { + log_error("Failed to remove resource " + src_rinfo->to_string()); + return; + } + + LOGGER_WARN("[{}] I/O task completed successfully", tid); + m_task_info->update_status(task_status::finished, urd_error::success, + std::make_error_code(static_cast(ec.value()))); +} + +} // namespace io +} // namespace norns + +#endif // __IO_TASK_REMOVE_HPP__ diff --git a/src/io/transferors/local-path-to-remote-path.hpp b/src/io/task-unknown.hpp similarity index 66% rename from src/io/transferors/local-path-to-remote-path.hpp rename to src/io/task-unknown.hpp index db5fbd3471f1f53dc4c7501418d0272bfabbae64..c3d5f8d0dcbc8b53c501727de8899bb4727d4e08 100644 --- a/src/io/transferors/local-path-to-remote-path.hpp +++ b/src/io/task-unknown.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * * Centro Nacional de Supercomputacion * * All rights reserved. * * * @@ -25,38 +25,26 @@ * . * *************************************************************************/ -#ifndef __IO_LOCAL_PATH_TO_REMOTE_PATH_TX__ -#define __IO_LOCAL_PATH_TO_REMOTE_PATH_TX__ - -#include -#include -#include "transferor.hpp" +#ifndef __IO_TASK_UNKNOWN_HPP__ +#define __IO_TASK_UNKNOWN_HPP__ namespace norns { - -// forward declarations -namespace auth { -struct credentials; -} - -namespace data { -struct resource_info; -struct resource; -} - namespace io { -struct local_path_to_remote_path_transferor : public transferor { - bool validate(const std::shared_ptr& src_info, - const std::shared_ptr& dst_info) const override final; - std::error_code transfer(const auth::credentials& auth, - const std::shared_ptr& task_info, - const std::shared_ptr& src, - const std::shared_ptr& dst) const override final; - std::string to_string() const override final; +///////////////////////////////////////////////////////////////////////////////// +// specializations for unknown tasks +///////////////////////////////////////////////////////////////////////////////// +template <> +struct task { + task() { } + task(const task& other) = default; + task(task&& rhs) = default; + task& operator=(const task& other) = default; + task& operator=(task&& rhs) = default; + void operator()() { } }; } // namespace io } // namespace norns -#endif /* __LOCAL_PATH_TO_REMOTE_PATH_TX__ */ +#endif // __IO_TASK_UNKNOWN_HPP__ diff --git a/src/io/task.cpp b/src/io/task.cpp index c4ed40ad7e78e0a2f54186e9fc1d401775a4da5d..7c4efb9d3fcce9ba684071ef8daee449d8c3e852 100644 --- a/src/io/task.cpp +++ b/src/io/task.cpp @@ -36,185 +36,10 @@ namespace norns { namespace io { -///////////////////////////////////////////////////////////////////////////////// -// specializations for copy tasks -///////////////////////////////////////////////////////////////////////////////// -template<> -void -task::operator()() { - std::error_code ec; - const auto tid = m_task_info->id(); - const auto type = m_task_info->type(); - const auto src_backend = m_task_info->src_backend(); - const auto src_rinfo = m_task_info->src_rinfo(); - const auto dst_backend = m_task_info->dst_backend(); - const auto dst_rinfo = m_task_info->dst_rinfo(); - const auto auth = m_task_info->auth(); - // helper lambda for error reporting - const auto log_error = [&] (const std::string& msg) { - m_task_info->update_status(task_status::finished_with_error, - urd_error::system_error, ec); - std::string r_msg = "[{}] " + msg + ": {}"; - - LOGGER_ERROR(r_msg.c_str(), tid, ec.message()); - LOGGER_WARN("[{}] I/O task completed with error", tid); - }; - - LOGGER_WARN("[{}] Starting I/O task", tid); - LOGGER_WARN("[{}] TYPE: {}", tid, utils::to_string(type)); - LOGGER_WARN("[{}] FROM: {}", tid, src_backend->to_string()); - LOGGER_WARN("[{}] TO: {}", tid, dst_backend->to_string()); - - m_task_info->update_status(task_status::running); - - auto src = src_backend->get_resource(src_rinfo, ec); - - if(ec) { - log_error("Could not access input data " + src_rinfo->to_string()); - return; - } - - auto dst = dst_backend->new_resource(dst_rinfo, src->is_collection(), ec); - - if(ec) { - log_error("Could not create output data " + dst_rinfo->to_string()); - return; - } - - ec = m_transferor->transfer(auth, m_task_info, src, dst); - - if(ec) { - log_error("Transfer failed"); - return; - } - - LOGGER_WARN("[{}] I/O task completed successfully", tid); - m_task_info->update_status(task_status::finished, urd_error::success, - std::make_error_code(static_cast(ec.value()))); -} - - -///////////////////////////////////////////////////////////////////////////////// -// specializations for move tasks -///////////////////////////////////////////////////////////////////////////////// -template<> -void -task::operator()() { - - std::error_code ec; - - const auto tid = m_task_info->id(); - const auto type = m_task_info->type(); - const auto src_backend = m_task_info->src_backend(); - const auto src_rinfo = m_task_info->src_rinfo(); - const auto dst_backend = m_task_info->dst_backend(); - const auto dst_rinfo = m_task_info->dst_rinfo(); - const auto auth = m_task_info->auth(); - - // helper lambda for error reporting - const auto log_error = [&] (const std::string& msg) { - m_task_info->update_status(task_status::finished_with_error, - urd_error::system_error, ec); - - std::string r_msg = "[{}] " + msg + ": {}"; - - LOGGER_ERROR(r_msg.c_str(), tid, ec.message()); - LOGGER_WARN("[{}] I/O task completed with error", tid); - }; - - LOGGER_WARN("[{}] Starting I/O task", tid); - LOGGER_WARN("[{}] TYPE: {}", tid, utils::to_string(type)); - LOGGER_WARN("[{}] FROM: {}", tid, src_backend->to_string()); - LOGGER_WARN("[{}] TO: {}", tid, dst_backend->to_string()); - - m_task_info->update_status(task_status::running); - - auto src = src_backend->get_resource(src_rinfo, ec); - - if(ec) { - log_error("Could not access input data " + src_rinfo->to_string()); - return; - } - - auto dst = dst_backend->new_resource(dst_rinfo, src->is_collection(), ec); - - if(ec) { - log_error("Could not create output data " + dst_rinfo->to_string()); - return; - } - - ec = m_transferor->transfer(auth, m_task_info, src, dst); - - if(ec) { - log_error("Transfer failed"); - return; - } - - LOGGER_WARN("[{}] I/O task completed successfully", tid); - m_task_info->update_status(task_status::finished, urd_error::success, - std::make_error_code(static_cast(ec.value()))); -} - - -///////////////////////////////////////////////////////////////////////////////// -// specializations for remove tasks -///////////////////////////////////////////////////////////////////////////////// -template<> -void -task::operator()() { - - std::error_code ec; - - const auto tid = m_task_info->id(); - const auto type = m_task_info->type(); - const auto src_backend = m_task_info->src_backend(); - const auto src_rinfo = m_task_info->src_rinfo(); - const auto auth = m_task_info->auth(); - - // helper lambda for error reporting - const auto log_error = [&] (const std::string& msg) { - m_task_info->update_status(task_status::finished_with_error, - urd_error::system_error, ec); - - std::string r_msg = "[{}] " + msg + ": {}"; - - LOGGER_ERROR(r_msg.c_str(), tid, ec.message()); - LOGGER_WARN("[{}] I/O task completed with error", tid); - }; - - LOGGER_WARN("[{}] Starting I/O task", tid); - LOGGER_WARN("[{}] TYPE: {}", tid, utils::to_string(type)); - LOGGER_WARN("[{}] FROM: {}", tid, src_backend->to_string()); - - m_task_info->update_status(task_status::running); - - src_backend->remove(src_rinfo, ec); - - if(ec) { - log_error("Failed to remove resource " + src_rinfo->to_string()); - return; - } - - LOGGER_WARN("[{}] I/O task completed successfully", tid); - m_task_info->update_status(task_status::finished, urd_error::success, - std::make_error_code(static_cast(ec.value()))); -} - -///////////////////////////////////////////////////////////////////////////////// -// specializations for unknown tasks -///////////////////////////////////////////////////////////////////////////////// -template<> -void -task::operator()() { - - const auto tid = m_task_info->id(); - - LOGGER_CRITICAL("[{}] Unknown task type detected!", tid); -} } // namespace io } // namespace norns diff --git a/src/io/task.hpp b/src/io/task.hpp index cd02e3a4e4219af77b1043ee4c2abdfa450f4508..c1769c5a8cfd1e1a4e89ddeff17d56cad11333e2 100644 --- a/src/io/task.hpp +++ b/src/io/task.hpp @@ -30,6 +30,7 @@ #include #include +#include #include "logger.hpp" #include "common.hpp" @@ -61,48 +62,158 @@ struct task { : m_task_info(std::move(task_info)), m_transferor(std::move(tx_ptr)) { } + task(const task& other) = default; + task(task&& rhs) = default; + task& operator=(const task& other) = default; + task& operator=(task&& rhs) = default; + void operator()(); - const task_info_ptr m_task_info; - const transferor_ptr m_transferor; + task_info_ptr m_task_info; + transferor_ptr m_transferor; }; -///////////////////////////////////////////////////////////////////////////////// -// specializations for noop tasks -///////////////////////////////////////////////////////////////////////////////// -template <> -struct task { - - using task_info_ptr = std::shared_ptr; - - task(const task_info_ptr&& task_info, uint32_t sleep_duration) - : m_task_info(std::move(task_info)), - m_sleep_duration(sleep_duration) { } - - void operator()() { - const auto tid = m_task_info->id(); +} // namespace io +} // namespace norns - LOGGER_WARN("[{}] Starting noop I/O task", tid); - LOGGER_DEBUG("[{}] Sleep for {} usecs", tid, m_sleep_duration); - usleep(m_sleep_duration); +#include "task-copy.hpp" +#include "task-move.hpp" +#include "task-remove.hpp" +#include "task-remote-transfer.hpp" +#include "task-noop.hpp" +#include "task-unknown.hpp" - m_task_info->update_status(task_status::running); - LOGGER_WARN("[{}] noop I/O task \"running\"", tid); +namespace norns { +namespace io { - LOGGER_DEBUG("[{}] Sleep for {} usecs", tid, m_sleep_duration); - usleep(m_sleep_duration); +struct generic_task { + + generic_task() : + m_type(iotask_type::unknown), + m_impl(task()) {} + + template + generic_task(iotask_type type, + TaskImpl&& impl) : + m_type(type), + m_impl(std::forward(impl)) { } + + generic_task(const generic_task& other) = default; + generic_task(generic_task&& rhs) = default; + generic_task& operator=(const generic_task& other) = default; + generic_task& operator=(generic_task&& rhs) = default; + + iotask_id + id() const { + switch(m_type) { + case iotask_type::noop: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info->id(); + } + + case iotask_type::copy: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info->id(); + } + + case iotask_type::move: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info->id(); + } + + case iotask_type::remove: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info->id(); + } + + case iotask_type::remote_transfer: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info->id(); + } + + default: + return static_cast(0); + } + } - m_task_info->update_status(task_status::finished); + std::shared_ptr + info() const { + switch(m_type) { + case iotask_type::noop: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info; + } + + case iotask_type::copy: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info; + } + + case iotask_type::move: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info; + } + + case iotask_type::remove: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info; + } + + case iotask_type::remote_transfer: + { + using TaskType = io::task; + return boost::get(m_impl).m_task_info; + } + + default: + return {}; + } + } - LOGGER_WARN("[{}] noop I/O task completed successfully", tid); + void + operator()() { + switch(m_type) { + case iotask_type::noop: + boost::get>(m_impl)(); + break; + case iotask_type::copy: + boost::get>(m_impl)(); + break; + case iotask_type::move: + boost::get>(m_impl)(); + break; + case iotask_type::remove: + boost::get>(m_impl)(); + break; + case iotask_type::remote_transfer: + boost::get>(m_impl)(); + break; + default: + break; + } } - const task_info_ptr m_task_info; - const uint32_t m_sleep_duration; -}; + iotask_type m_type; + boost::variant< + io::task, + io::task, + io::task, + io::task, + io::task, + io::task> m_impl; +}; } // namespace io } // namespace norns diff --git a/src/io/transferor-registry.hpp b/src/io/transferor-registry.hpp index cd6123eddf169fa56aeadbd18351fb373fe3c88a..83a474518f24717aa1ab1643bcdfb1d4e39da78b 100644 --- a/src/io/transferor-registry.hpp +++ b/src/io/transferor-registry.hpp @@ -47,27 +47,18 @@ struct transferor; struct transferor_registry { - struct transferor_hash { - template - std::size_t operator()(const std::pair &x) const { - std::size_t seed = 0; - boost::hash_combine(seed, x.first); - boost::hash_combine(seed, x.second); - return seed; - } - }; - bool add(const data::resource_type t1, const data::resource_type t2, std::shared_ptr&& tr); std::shared_ptr get(const data::resource_type t1, const data::resource_type t2) const; - std::unordered_map, + using key_type = + std::pair; + + std::unordered_map, - transferor_hash> m_transferors; + boost::hash> m_transferors; }; } // namespace io diff --git a/src/io/transferors.hpp b/src/io/transferors.hpp index 12b4232f2e79469bf149a544d22bf839b005ec1e..66665031a6e003cdd0e9d53c7f598bec8c65c110 100644 --- a/src/io/transferors.hpp +++ b/src/io/transferors.hpp @@ -30,9 +30,11 @@ #include "transferors/local-path-to-local-path.hpp" #include "transferors/local-path-to-shared-path.hpp" -#include "transferors/local-path-to-remote-path.hpp" +#include "transferors/local-path-to-remote-resource.hpp" #include "transferors/memory-to-local-path.hpp" #include "transferors/memory-to-shared-path.hpp" #include "transferors/memory-to-remote-path.hpp" +#include "transferors/remote-resource-to-local-path.hpp" +#include "transferors/memory-to-remote-resource.hpp" #endif /* __IO_TRANSFERORS_HPP__ */ diff --git a/src/io/transferors/local-path-to-local-path.cpp b/src/io/transferors/local-path-to-local-path.cpp index 851b00dbe0e7ff2d4e17ed398dcd607a06e1d717..f4d0f955986b37decaf03f8c21f12dba14ca186b 100644 --- a/src/io/transferors/local-path-to-local-path.cpp +++ b/src/io/transferors/local-path-to-local-path.cpp @@ -30,6 +30,7 @@ #include #include #include +#include "config.h" #include "utils.hpp" #include "logger.hpp" @@ -71,15 +72,21 @@ do_sendfile(int in_fd, int out_fd) { } // preallocate output file +#ifdef HAVE_FALLOCATE if(::fallocate(out_fd, 0, 0, sz) == -1) { + if(errno != EOPNOTSUPP) { + return static_cast(-1); + } +#endif // HAVE_FALLOCATE + // filesystem doesn't support fallocate(), fallback to truncate() - if(errno == EOPNOTSUPP) { - if(::ftruncate(out_fd, sz) != 0) { - return static_cast(-1); - } + if(::ftruncate(out_fd, sz) != 0) { + return static_cast(-1); } - return static_cast(-1); + +#ifdef HAVE_FALLOCATE } +#endif // HAVE_FALLOCATE // copy data off_t offset = 0; @@ -192,6 +199,10 @@ copy_directory(const std::shared_ptr& task_info, namespace norns { namespace io { +local_path_to_local_path_transferor::local_path_to_local_path_transferor( + const context& ctx) : + m_ctx(ctx) {} + bool local_path_to_local_path_transferor::validate( const std::shared_ptr& src_info, @@ -231,6 +242,22 @@ local_path_to_local_path_transferor::transfer( d_dst.canonical_path()); } +std::error_code +local_path_to_local_path_transferor::accept_transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + (void) task_info; + (void) src; + (void) dst; + + LOGGER_ERROR("This function should never be called for this transfer type"); + return std::make_error_code(static_cast(0)); +} + std::string local_path_to_local_path_transferor::to_string() const { return "transferor[local_path => local_path]"; diff --git a/src/io/transferors/local-path-to-local-path.hpp b/src/io/transferors/local-path-to-local-path.hpp index 2d4f802ad8ac875c6d1e6a43f44f270aa674a168..dee04f2cb2cef33375f8cbae89027d25ebd42b26 100644 --- a/src/io/transferors/local-path-to-local-path.hpp +++ b/src/io/transferors/local-path-to-local-path.hpp @@ -30,6 +30,7 @@ #include #include +#include "context.hpp" #include "transferor.hpp" namespace norns { @@ -48,13 +49,32 @@ namespace io { struct local_path_to_local_path_transferor : public transferor { - bool validate(const std::shared_ptr& src_info, - const std::shared_ptr& dst_info) const override final; - std::error_code transfer(const auth::credentials& auth, - const std::shared_ptr& task_info, - const std::shared_ptr& src, - const std::shared_ptr& dst) const override final; - std::string to_string() const override final; + local_path_to_local_path_transferor(const context& ctx); + + bool + validate(const std::shared_ptr &src_info, + const std::shared_ptr &dst_info) + const override final; + + std::error_code + transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::error_code + accept_transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::string + to_string() const override final; + +private: + context m_ctx; }; diff --git a/src/io/transferors/local-path-to-remote-resource.cpp b/src/io/transferors/local-path-to-remote-resource.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8399caa406f079c29f48d9690b475e2d8fab5abc --- /dev/null +++ b/src/io/transferors/local-path-to-remote-resource.cpp @@ -0,0 +1,437 @@ +/************************************************************************* + * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include +#include +#include +#include +#include +#include "config.h" + +#include "utils.hpp" +#include "logger.hpp" +#include "resources.hpp" +#include "auth.hpp" +#include "io/task-info.hpp" +#include "io/task-stats.hpp" +#include "hermes.hpp" +#include "rpcs.hpp" +#include "local-path-to-remote-resource.hpp" + +namespace { + +struct archive_entry { + bool m_is_directory; + bfs::path m_realpath; + bfs::path m_archive_path; +}; + +bfs::path +pack_archive(const std::string& name_pattern, + const bfs::path& parent_path, + const std::vector& entries, + std::error_code& ec) { + + using norns::utils::tar; + + bfs::path ar_path = parent_path / bfs::unique_path(name_pattern); + + tar ar(ar_path, tar::create, ec); + + if(ec) { + LOGGER_ERROR("Failed to create archive: {}", ec.message()); + return {}; + } + + LOGGER_INFO("Archive created in {}", ar.path()); + + for(auto&& e : entries) { + e.m_is_directory ? + ar.add_directory(e.m_realpath, e.m_archive_path, ec) : + ar.add_file(e.m_realpath, e.m_archive_path, ec); + + if(ec) { + LOGGER_ERROR("Failed to add entry to archive: {}", ec.message()); + return {}; + } + } + + return ar.path(); +} + +std::error_code +unpack_archive(const bfs::path& archive_path, + const bfs::path& parent_path) { + + using norns::utils::tar; + std::error_code ec; + + boost::system::error_code bec; + tar ar(archive_path, tar::open, ec); + + if(ec) { + LOGGER_ERROR("Failed to open archive {}: {}", + archive_path, ec.message()); + return ec; + } + + ar.extract(parent_path, ec); + + if(ec) { + LOGGER_ERROR("Failed to extract archive {} into {}: {}", + ar.path(), parent_path, ec.message()); + return ec; + } + + LOGGER_DEBUG("Archive {} extracted into {}, removing archive", + ar.path(), parent_path); + + bfs::remove(ar.path(), bec); + + if(bec) { + LOGGER_ERROR("Failed to remove archive {}: {}", + ar.path(), bec.message()); + ec.assign(bec.value(), std::generic_category()); + return ec; + } + + return ec; +} + +} // anonymous namespace + +namespace norns { +namespace io { + +local_path_to_remote_resource_transferor:: + local_path_to_remote_resource_transferor(const context& ctx) : + m_staging_directory(ctx.staging_directory()), + m_network_service(ctx.network_service()) { } + +bool +local_path_to_remote_resource_transferor::validate( + const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) const { + + (void) src_info; + (void) dst_info; + + LOGGER_WARN("Validation not implemented"); + + return true; +} + +std::error_code +local_path_to_remote_resource_transferor::transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + using utils::tar; + + (void) auth; + + std::error_code ec; + const auto& d_src = + reinterpret_cast(*src); + const auto& d_dst = + reinterpret_cast(*dst); + auto tempfile = std::make_shared(); + + bfs::path input_path = + !d_src.is_collection() ? + d_src.canonical_path() : + [&]() -> bfs::path { + + LOGGER_DEBUG("[{}] Creating temporary archive from local directory", + task_info->id()); + + const bfs::path ar_path = + ::pack_archive("norns-archive-%%%%-%%%%-%%%%.tar", + m_staging_directory, + {{true, d_src.canonical_path(), d_dst.name()}}, + ec); + + if(ec) { + LOGGER_ERROR("Failed to create temporary archive: {}", + ec.message()); + return {}; + } + + tempfile->manage(ar_path, ec); + + if(ec) { + LOGGER_ERROR("Failed to create temporary archive: {}", + ec.message()); + return {}; + } + + return tempfile->path(); + }(); // <<== XXX (IILE) + + + LOGGER_DEBUG("[{}] start_transfer: {} -> {}", + task_info->id(), d_src.canonical_path(), d_dst.to_string()); + + hermes::endpoint endp = m_network_service->lookup(d_dst.address()); + + try { + hermes::mapped_buffer input_buffer(input_path.string(), + hermes::access_mode::read_only, + &ec); + + if(ec) { + LOGGER_ERROR("Failed mapping input data: {}", ec.value()); + return ec; + } + + std::vector bufvec{ + hermes::mutable_buffer{input_buffer.data(), input_buffer.size()} + }; + + auto local_buffers = + m_network_service->expose(bufvec, hermes::access_mode::read_only); + + auto resp = + m_network_service->post( + endp, + rpc::push_resource::input{ + m_network_service->self_address(), + d_src.parent()->nsid(), + d_dst.parent()->nsid(), + // XXX this resource_type should not be needed, but we + // XXX cannot (easily) find it out right now in the server, + // XXX for now we propagate it, but we should implement + // XXX a lookup()/stat() function in backends to retrieve + // XXX this information locally from the resource id + static_cast( + data::resource_type::local_posix_path), + d_src.is_collection(), + d_src.name(), + d_dst.name(), + local_buffers + }).get(); + + if(static_cast(resp.at(0).status()) == + task_status::finished_with_error) { + // XXX error interface should be improved + return std::make_error_code( + static_cast(resp.at(0).sys_errnum())); + } + + task_info->record_transfer(input_buffer.size(), + resp.at(0).elapsed_time()); + + LOGGER_DEBUG("Remote pull request completed with output " + "{{status: {}, task_error: {}, sys_errnum: {}}} " + "({} bytes, {} usecs)", + resp.at(0).status(), resp.at(0).task_error(), + resp.at(0).sys_errnum(), input_buffer.size(), + resp.at(0).elapsed_time()); + + return ec; + } + catch(const std::exception& ex) { + LOGGER_ERROR(ex.what()); + return std::make_error_code(static_cast(-1)); + } +} + + +std::error_code +local_path_to_remote_resource_transferor::accept_transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + +// LOGGER_CRITICAL("accept_remote_request: {}", +// std::chrono::duration_cast( +// std::chrono::steady_clock::now().time_since_epoch()) +// .count()); + + std::error_code ec; + const auto& d_src = + reinterpret_cast(*src); + const auto& d_dst = + reinterpret_cast(*dst); + + // retrieve task context + const auto ctx = boost::any_cast< + std::shared_ptr< + hermes::request>>(task_info->context()); + auto req = std::move(*ctx); + + LOGGER_DEBUG("[{}] accept_push: {} -> {}", task_info->id(), + d_src.to_string(), d_dst.canonical_path()); + + hermes::exposed_memory remote_buffers = d_src.buffers(); + + LOGGER_DEBUG("remote_buffers{{count={}, total_size={}}}", + remote_buffers.count(), + remote_buffers.size()); + + assert(remote_buffers.count() == 1); + + bool is_collection = d_src.is_collection(); + + // TODO this should probably go into validate(), but we need to change + // its interface to accept resources rather than resource_infos + // to be able to determine whether d_dst is a directory + if(d_src.name().empty() && bfs::is_directory(d_dst.canonical_path())) { + LOGGER_ERROR("Failed to transfer unnamed resource to directory " + "(target should be a named file)"); + ec.assign(EISDIR, std::generic_category()); + *ctx = std::move(req); // restore ctx + return ec; + } + + auto tempfile = + std::make_shared( + /* output_path */ + std::string{is_collection ? + "norns-archive-%%%%-%%%%-%%%%.tar" : + d_dst.name()}, + /* parent_path */ + bfs::path{is_collection ? + m_staging_directory : + d_dst.parent()->mount()}, + remote_buffers.size(), + ec); + + if(ec) { + LOGGER_ERROR("Failed to create temporary file: {}", ec.message()); + *ctx = std::move(req); // restore ctx + return ec; + } + + LOGGER_DEBUG("created local resource: {}", tempfile->path()); + + auto output_buffer = + std::make_shared( + tempfile->path().string(), + hermes::access_mode::write_only, + &ec); + + if(ec) { + LOGGER_ERROR("Failed mmapping output buffer: {}", ec.value()); + *ctx = std::move(req); // restore ctx + return ec; + } + + // let's prepare some local buffers + std::vector bufseq{ + hermes::mutable_buffer{output_buffer->data(), output_buffer->size()} + }; + + hermes::exposed_memory local_buffers = + m_network_service->expose(bufseq, hermes::access_mode::write_only); + + LOGGER_DEBUG("pulling remote data into {}", tempfile->path()); + + auto start = std::chrono::steady_clock::now(); + + // N.B. IMPORTANT: we NEED to capture output_buffer by value here so that + // the mapped_buffer doesn't get released before completion_callback() + // is called. + const auto completion_callback = + [this, is_collection, tempfile, d_dst, output_buffer, start]( + hermes::request&& req) { + +// LOGGER_CRITICAL("completion_callback invoked: {}", +// std::chrono::duration_cast( +// std::chrono::steady_clock::now().time_since_epoch()) +// .count()); + + uint32_t usecs = + std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count(); + + //TODO: hermes offers no way to check for an error yet + LOGGER_DEBUG("Pull completed ({} usecs)", usecs); + + // default response (success) + rpc::push_resource::output out = { + static_cast(task_status::finished), + static_cast(urd_error::success), + 0, + usecs}; + + if(is_collection) { + std::error_code ec = + ::unpack_archive(tempfile->path(), d_dst.parent()->mount()); + + if(ec) { + out = rpc::push_resource::output{ + static_cast( + task_status::finished_with_error), + static_cast(urd_error::system_error), + static_cast(ec.value()), + 0}; + + goto respond; + } + + LOGGER_DEBUG("Archive {} extracted into {}", + tempfile->path(), d_dst.parent()->mount()); + + goto respond; + } + + // prevent output file from being removed by tempfile's destructor + (void) tempfile->release(); + +respond: + if(req.requires_response()) { + m_network_service->respond( + std::move(req), out); + } + }; + +// LOGGER_CRITICAL("async_pull posted: {}", +// std::chrono::duration_cast( +// std::chrono::steady_clock::now().time_since_epoch()) +// .count()); + + m_network_service->async_pull(remote_buffers, + local_buffers, + std::move(req), + completion_callback); + + return ec; +} + +std::string +local_path_to_remote_resource_transferor::to_string() const { + return "transferor[local_path => remote_resource]"; +} + +} // namespace io +} // namespace norns diff --git a/src/io/transferors/local-path-to-remote-resource.hpp b/src/io/transferors/local-path-to-remote-resource.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a42d5f126efc16b6af819dddcb639a142cf3bbcf --- /dev/null +++ b/src/io/transferors/local-path-to-remote-resource.hpp @@ -0,0 +1,92 @@ +/************************************************************************* + * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __IO_LOCAL_PATH_TO_REMOTE_RESOURCE_TX__ +#define __IO_LOCAL_PATH_TO_REMOTE_RESOURCE_TX__ + +#include +#include +#include "context.hpp" +#include "transferor.hpp" + +namespace hermes { +class async_engine; + +template class request; + +} // namespace hermes + +namespace norns { + +// forward declarations +namespace auth { +struct credentials; +} + +namespace data { +struct resource_info; +struct resource; +} + +namespace io { + +struct local_path_to_remote_resource_transferor : public transferor { + + local_path_to_remote_resource_transferor(const context& ctx); + + bool + validate(const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) + const override final; + + std::error_code + transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::error_code + accept_transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::string + to_string() const override final; + +private: + bfs::path m_staging_directory; + std::shared_ptr m_network_service; + +}; + +} // namespace io +} // namespace norns + +#endif /* __LOCAL_PATH_TO_REMOTE_RESOURCE_TX__ */ diff --git a/src/io/transferors/local-path-to-shared-path.cpp b/src/io/transferors/local-path-to-shared-path.cpp index f3f1a6f090135ff289158b8af96216c816865b75..5cc544757e8eed939fb619432d99ac2e81524eef 100644 --- a/src/io/transferors/local-path-to-shared-path.cpp +++ b/src/io/transferors/local-path-to-shared-path.cpp @@ -41,6 +41,10 @@ namespace norns { namespace io { +local_path_to_shared_path_transferor::local_path_to_shared_path_transferor( + const context& ctx) : + m_ctx(ctx) { } + bool local_path_to_shared_path_transferor::validate( const std::shared_ptr& src_info, @@ -66,8 +70,24 @@ local_path_to_shared_path_transferor::transfer( (void) src; (void) dst; - LOGGER_WARN("Transfer not implemented"); + LOGGER_WARN("transfer not implemented"); + + return std::make_error_code(static_cast(0)); +} + +std::error_code +local_path_to_shared_path_transferor::accept_transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + (void) task_info; + (void) src; + (void) dst; + LOGGER_ERROR("This function should never be called for this transfer type"); return std::make_error_code(static_cast(0)); } diff --git a/src/io/transferors/local-path-to-shared-path.hpp b/src/io/transferors/local-path-to-shared-path.hpp index 40eb978a943a5fe0a09823e11af4b9c8315855dc..028fb24a34ebcaeb78c6bed62477364782d2b8d1 100644 --- a/src/io/transferors/local-path-to-shared-path.hpp +++ b/src/io/transferors/local-path-to-shared-path.hpp @@ -30,6 +30,7 @@ #include #include +#include "context.hpp" #include "transferor.hpp" namespace norns { @@ -47,13 +48,33 @@ struct resource; namespace io { struct local_path_to_shared_path_transferor : public transferor { - bool validate(const std::shared_ptr& src_info, - const std::shared_ptr& dst_info) const override final; - std::error_code transfer(const auth::credentials& auth, - const std::shared_ptr& task_info, - const std::shared_ptr& src, - const std::shared_ptr& dst) const override final; - std::string to_string() const override final; + + local_path_to_shared_path_transferor(const context& ctx); + + bool + validate(const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) + const override final; + + std::error_code + transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::error_code + accept_transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::string + to_string() const override final; + +private: + context m_ctx; }; } // namespace io diff --git a/src/io/transferors/memory-to-local-path.cpp b/src/io/transferors/memory-to-local-path.cpp index 5c4edf3f5ccd891fcfa23b76e5438c4b1e62f7da..984aeda800c07221b42fa11b392d96065745b372 100644 --- a/src/io/transferors/memory-to-local-path.cpp +++ b/src/io/transferors/memory-to-local-path.cpp @@ -63,19 +63,26 @@ copy_memory_region(const std::shared_ptr& task_info, return std::make_error_code(static_cast(errno)); } + // preallocate output file +#ifdef HAVE_FALLOCATE if(::fallocate(out_fd, 0, 0, size) == -1) { + if(errno != EOPNOTSUPP) { + LOGGER_ERROR("fallocate() error"); + rv = errno; + goto cleanup_on_error; + } +#endif // HAVE_FALLOCATE + // filesystem doesn't support fallocate(), fallback to truncate() - if(errno == EOPNOTSUPP) { - if(::ftruncate(out_fd, size) != 0) { - LOGGER_ERROR("ftruncate() error on {}", dst); - rv = errno; - goto cleanup_on_error; - } + if(::ftruncate(out_fd, size) != 0) { + LOGGER_ERROR("ftruncate() error on {}", dst); + rv = errno; + goto cleanup_on_error; } - LOGGER_ERROR("fallocate() error"); - rv = errno; - goto cleanup_on_error; + +#ifdef HAVE_FALLOCATE } +#endif // HAVE_FALLOCATE dst_addr = ::mmap(NULL, size, PROT_WRITE, MAP_SHARED, out_fd, 0); @@ -141,6 +148,10 @@ retry_close: namespace norns { namespace io { +memory_region_to_local_path_transferor:: + memory_region_to_local_path_transferor(const context &ctx) : + m_ctx(ctx) { } + bool memory_region_to_local_path_transferor::validate( const std::shared_ptr& src_info, @@ -183,6 +194,22 @@ memory_region_to_local_path_transferor::transfer( d_src.size(), d_dst.canonical_path()); } +std::error_code +memory_region_to_local_path_transferor::accept_transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + (void) task_info; + (void) src; + (void) dst; + + LOGGER_ERROR("This function should never be called for this transfer type"); + return std::make_error_code(static_cast(0)); +} + std::string memory_region_to_local_path_transferor::to_string() const { return "transferor[memory_region => local_path]"; diff --git a/src/io/transferors/memory-to-local-path.hpp b/src/io/transferors/memory-to-local-path.hpp index 4102739e8c53f1e1a85c04847dba646735cce7b5..5875e51a57bacdd56b68616b82ff975f63dafcfa 100644 --- a/src/io/transferors/memory-to-local-path.hpp +++ b/src/io/transferors/memory-to-local-path.hpp @@ -30,6 +30,7 @@ #include #include +#include "context.hpp" #include "transferor.hpp" namespace norns { @@ -47,13 +48,32 @@ struct resource; namespace io { struct memory_region_to_local_path_transferor : public transferor { - bool validate(const std::shared_ptr& src_info, - const std::shared_ptr& dst_info) const override final; - std::error_code transfer(const auth::credentials& auth, - const std::shared_ptr& task_info, - const std::shared_ptr& src, - const std::shared_ptr& dst) const override final; - std::string to_string() const override final; + + memory_region_to_local_path_transferor(const context& ctx); + + bool + validate(const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) + const override final; + + std::error_code + transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::error_code + accept_transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::string + to_string() const override final; + + context m_ctx; }; } // namespace io diff --git a/src/io/transferors/memory-to-remote-path.cpp b/src/io/transferors/memory-to-remote-path.cpp index 5135c07058901ee77b72c27dfa797467245c7bb1..24503c6dee101563e1ffcba5052d1c2829a6daa0 100644 --- a/src/io/transferors/memory-to-remote-path.cpp +++ b/src/io/transferors/memory-to-remote-path.cpp @@ -65,11 +65,27 @@ memory_region_to_remote_path_transferor::transfer( (void) d_src; (void) d_dst; - LOGGER_WARN("Transfer not implemented"); + LOGGER_WARN("transfer not implemented"); return std::make_error_code(static_cast(0)); } +std::error_code +memory_region_to_remote_path_transferor::accept_transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + (void) task_info; + (void) src; + (void) dst; + + LOGGER_ERROR("This function should never be called for this transfer type"); + return std::make_error_code(static_cast(0)); +} + std::string memory_region_to_remote_path_transferor::to_string() const { return "transferor[memory_region => remote_path]"; diff --git a/src/io/transferors/memory-to-remote-path.hpp b/src/io/transferors/memory-to-remote-path.hpp index 393495de07c8eb14e28adf1b5efa35ad8b3e90d4..15f2bbd6ade9e42f43cb1fd1c37fa6e3222b2eaf 100644 --- a/src/io/transferors/memory-to-remote-path.hpp +++ b/src/io/transferors/memory-to-remote-path.hpp @@ -47,13 +47,28 @@ struct resource; namespace io { struct memory_region_to_remote_path_transferor : public transferor { - bool validate(const std::shared_ptr& src_info, - const std::shared_ptr& dst_info) const override final; - std::error_code transfer(const auth::credentials& auth, - const std::shared_ptr& task_info, - const std::shared_ptr& src, - const std::shared_ptr& dst) const override final; - std::string to_string() const override final; + + bool + validate(const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) + const override final; + + std::error_code + transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::error_code + accept_transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::string + to_string() const override final; }; } // namespace io diff --git a/src/io/transferors/memory-to-remote-resource.cpp b/src/io/transferors/memory-to-remote-resource.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25cf6365b381d09ba6b45cf4a511212d730ead21 --- /dev/null +++ b/src/io/transferors/memory-to-remote-resource.cpp @@ -0,0 +1,266 @@ +/************************************************************************* + * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include "utils.hpp" +#include "logger.hpp" +#include "resources.hpp" +#include "auth.hpp" +#include "io/task-info.hpp" +#include "io/task-stats.hpp" +#include "memory-to-remote-resource.hpp" + +namespace { + +std::error_code +copy_from_process(pid_t pid, + void* input_address, + void* output_address, + size_t size) { + + std::error_code ec; + + struct iovec local_region, remote_region; + local_region.iov_base = output_address; + remote_region.iov_base = input_address; + local_region.iov_len = remote_region.iov_len = size; + + ssize_t nbytes = + ::process_vm_readv(pid, &local_region, 1, &remote_region, 1, 0); + + if(nbytes == -1) { + LOGGER_ERROR("process_vm_readv() error"); + ec.assign(errno, std::generic_category()); + return ec; + } + + // according to the documentation, partial reads should only happen + // at the granularity of iovec elements. Given that we only have one + // element in our 'src' iovec, we should never hit this, but just in case + // we return an EIO + if(static_cast(nbytes) < size) { + LOGGER_ERROR("process_vm_readv() received fewer data than expected"); + ec.assign(EIO, std::generic_category()); + return ec; + } + + if(::msync(output_address, size, MS_SYNC) != 0) { + LOGGER_ERROR("Failed to sync mapped buffer to file"); + ec.assign(errno, std::generic_category()); + return ec; + } + + return ec; +} + +} // anonymous namespace + +namespace norns { +namespace io { + +memory_region_to_remote_resource_transferor:: + memory_region_to_remote_resource_transferor(const context& ctx) : + m_staging_directory(ctx.staging_directory()), + m_network_service(ctx.network_service()) {} + +bool +memory_region_to_remote_resource_transferor::validate( + const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) const { + + const auto& d_src = + reinterpret_cast(*src_info); + const auto& d_dst = + reinterpret_cast(*dst_info); + + // region length cannot be zero + if(d_src.size() == 0) { + return false; + } + + // we don't allow destination paths that look like directories + if(d_dst.name().back() == '/') { + return false; + } + + return true; +} + +std::error_code +memory_region_to_remote_resource_transferor::transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + (void) task_info; + + std::error_code ec; + const auto& d_src = + reinterpret_cast(*src); + const auto& d_dst = + reinterpret_cast(*dst); + + LOGGER_DEBUG("[{}] transfer: [{} {}+{}] -> {}", task_info->id(), + auth.pid(), utils::n2hexstr(d_src.address()), d_src.size(), + d_dst.to_string()); + + // copy the user buffer to a temporary file local file using + // ::process_vm_readv() so that we can safely send it to remote peers + auto tempfile = + std::make_shared( + std::string{"norns-tempfile-%%%%-%%%%-%%%%"}, + bfs::path{m_staging_directory}, + d_src.size(), + ec); + + if(ec) { + LOGGER_ERROR("Failed to create temporary file: {}", ec.message()); + return ec; + } + + LOGGER_DEBUG("created temporary file: {}", tempfile->path()); + + auto output_buffer = + std::make_shared( + tempfile->path().string(), + hermes::access_mode::write_only, + &ec); + + if(ec) { + LOGGER_ERROR("Failed mapping output buffer: {}", ec.value()); + return ec; + } + + if((ec = ::copy_from_process(auth.pid(), + reinterpret_cast(d_src.address()), + output_buffer->data(), d_src.size()))) { + LOGGER_ERROR("Failed to copy data from process memory: {}", + ec.message()); + return ec; + } + + // TODO: release() + remap() + output_buffer->protect(hermes::access_mode::read_only, &ec); + + if(ec) { + LOGGER_ERROR("Failed changing protections from output buffer: {}", + ec.value()); + return ec; + } + + try { + + hermes::endpoint endp = m_network_service->lookup(d_dst.address()); + + std::vector bufvec{ + hermes::mutable_buffer{output_buffer->data(), output_buffer->size()} + }; + + auto local_buffers = + m_network_service->expose(bufvec, hermes::access_mode::read_only); + +// LOGGER_CRITICAL("push_resource RPC posted: {}", +// std::chrono::duration_cast( +// std::chrono::steady_clock::now().time_since_epoch()) +// .count()); + + auto resp = + m_network_service->post( + endp, + rpc::push_resource::input{ + m_network_service->self_address(), + d_src.parent()->nsid(), + d_dst.parent()->nsid(), + // XXX this resource_type should not be needed, but we + // XXX cannot (easily) find it out right now in the server, + // XXX for now we propagate it, but we should implement + // XXX a lookup()/stat() function in backends to retrieve + // XXX this information locally from the resource id + static_cast( + data::resource_type::local_posix_path), + d_src.is_collection(), + d_src.name(), + d_dst.name(), + local_buffers + }).get(); + +// LOGGER_CRITICAL("push_resource response retrieved: {}", +// std::chrono::duration_cast( +// std::chrono::steady_clock::now().time_since_epoch()) +// .count()); + + if(static_cast(resp.at(0).status()) == + task_status::finished_with_error) { + // XXX error interface should be improved + return std::make_error_code( + static_cast(resp.at(0).sys_errnum())); + } + + task_info->record_transfer(output_buffer->size(), + resp.at(0).elapsed_time()); + + LOGGER_DEBUG("Remote pull request completed with output " + "{{status: {}, task_error: {}, sys_errnum: {}}} " + "({} bytes, {} usecs)", + resp.at(0).status(), resp.at(0).task_error(), + resp.at(0).sys_errnum(), output_buffer->size(), + resp.at(0).elapsed_time()); + + return ec; + } + catch(const std::exception& ex) { + LOGGER_ERROR(ex.what()); + return std::make_error_code(static_cast(-1)); + } + + return ec; +} + +std::error_code +memory_region_to_remote_resource_transferor::accept_transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + (void) task_info; + (void) src; + (void) dst; + + LOGGER_ERROR("This function should never be called for this transfer type"); + return std::make_error_code(static_cast(0)); +} + +std::string +memory_region_to_remote_resource_transferor::to_string() const { + return "transferor[memory_region => remote_resource]"; +} + +} // namespace io +} // namespace norns diff --git a/src/io/transferors/memory-to-remote-resource.hpp b/src/io/transferors/memory-to-remote-resource.hpp new file mode 100644 index 0000000000000000000000000000000000000000..06843dd587822ac972a83c5e8337221ee1db0567 --- /dev/null +++ b/src/io/transferors/memory-to-remote-resource.hpp @@ -0,0 +1,84 @@ +/************************************************************************* + * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __IO_MEM_TO_REMOTE_RESOURCE_TX__ +#define __IO_MEM_TO_REMOTE_RESOURCE_TX__ + +#include +#include +#include "context.hpp" +#include "transferor.hpp" + +namespace norns { + +// forward declarations +namespace auth { +struct credentials; +} + +namespace data { +struct resource_info; +struct resource; +} + +namespace io { + +struct memory_region_to_remote_resource_transferor : public transferor { + + memory_region_to_remote_resource_transferor(const context& ctx); + + bool + validate(const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) + const override final; + + std::error_code + transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::error_code + accept_transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::string + to_string() const override final; + +private: + bfs::path m_staging_directory; + std::shared_ptr m_network_service; +}; + +} // namespace io +} // namespace norns + +#endif /* __MEM_TO_REMOTE_RESOURCE_TX__ */ diff --git a/src/io/transferors/memory-to-shared-path.cpp b/src/io/transferors/memory-to-shared-path.cpp index c573bb22a949b51ad71ef08846fd10dfb076670b..b094f050b71b6b450e09b0ada6c1efdf8f2a367e 100644 --- a/src/io/transferors/memory-to-shared-path.cpp +++ b/src/io/transferors/memory-to-shared-path.cpp @@ -34,6 +34,10 @@ namespace norns { namespace io { +memory_region_to_shared_path_transferor:: + memory_region_to_shared_path_transferor(const context &ctx) : + m_ctx(ctx) { } + bool memory_region_to_shared_path_transferor::validate( const std::shared_ptr& src_info, @@ -65,8 +69,24 @@ memory_region_to_shared_path_transferor::transfer( (void) d_src; (void) d_dst; - LOGGER_WARN("Transfer not implemented"); + LOGGER_WARN("transfer not implemented"); + + return std::make_error_code(static_cast(0)); +} + +std::error_code +memory_region_to_shared_path_transferor::accept_transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + (void) task_info; + (void) src; + (void) dst; + LOGGER_ERROR("This function should never be called for this transfer type"); return std::make_error_code(static_cast(0)); } diff --git a/src/io/transferors/memory-to-shared-path.hpp b/src/io/transferors/memory-to-shared-path.hpp index 25d8e4e3fac8ef6f96f88ec9ee4eeb2964eb976a..66721fc556fd41e60d7744e0d9af52c2e8522920 100644 --- a/src/io/transferors/memory-to-shared-path.hpp +++ b/src/io/transferors/memory-to-shared-path.hpp @@ -30,6 +30,7 @@ #include #include +#include "context.hpp" #include "transferor.hpp" namespace norns { @@ -47,13 +48,32 @@ struct resource; namespace io { struct memory_region_to_shared_path_transferor : public transferor { - bool validate(const std::shared_ptr& src_info, - const std::shared_ptr& dst_info) const override final; - std::error_code transfer(const auth::credentials& auth, - const std::shared_ptr& task_info, - const std::shared_ptr& src, - const std::shared_ptr& dst) const override final; - std::string to_string() const override final; + + memory_region_to_shared_path_transferor(const context& ctx); + + bool + validate(const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) + const override final; + + std::error_code + transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::error_code + accept_transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::string + to_string() const override final; + + context m_ctx; }; } // namespace io diff --git a/src/io/transferors/remote-resource-to-local-path.cpp b/src/io/transferors/remote-resource-to-local-path.cpp new file mode 100644 index 0000000000000000000000000000000000000000..07eba07e89320c6d0fc54c7540a5bc459d82b8c9 --- /dev/null +++ b/src/io/transferors/remote-resource-to-local-path.cpp @@ -0,0 +1,400 @@ +/************************************************************************* + * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include "utils.hpp" +#include "logger.hpp" +#include "resources.hpp" +#include "auth.hpp" +#include "io/task-info.hpp" +#include "io/task-stats.hpp" +#include "hermes.hpp" +#include "rpcs.hpp" +#include "remote-resource-to-local-path.hpp" + +namespace { + +struct archive_entry { + bool m_is_directory; + bfs::path m_realpath; + bfs::path m_archive_path; +}; + +bfs::path +pack_archive(const std::string& name_pattern, + const bfs::path& parent_path, + const std::vector& entries, + std::error_code& ec) { + + using norns::utils::tar; + + bfs::path ar_path = parent_path / bfs::unique_path(name_pattern); + + tar ar(ar_path, tar::create, ec); + + if(ec) { + LOGGER_ERROR("Failed to create archive: {}", ec.message()); + return {}; + } + + LOGGER_INFO("Archive created in {}", ar.path()); + + for(auto&& e : entries) { + e.m_is_directory ? + ar.add_directory(e.m_realpath, e.m_archive_path, ec) : + ar.add_file(e.m_realpath, e.m_archive_path, ec); + + if(ec) { + LOGGER_ERROR("Failed to add entry to archive: {}", ec.message()); + return {}; + } + } + + return ar.path(); +} + +std::error_code +unpack_archive(const bfs::path& archive_path, + const bfs::path& parent_path) { + + using norns::utils::tar; + std::error_code ec; + + boost::system::error_code bec; + tar ar(archive_path, tar::open, ec); + + if(ec) { + LOGGER_ERROR("Failed to open archive {}: {}", + archive_path, ec.message()); + return ec; + } + + ar.extract(parent_path, ec); + + if(ec) { + LOGGER_ERROR("Failed to extract archive {} into {}: {}", + ar.path(), parent_path, ec.message()); + return ec; + } + + LOGGER_DEBUG("Archive {} extracted into {}, removing archive", + ar.path(), parent_path); + + bfs::remove(ar.path(), bec); + + if(bec) { + LOGGER_ERROR("Failed to remove archive {}: {}", + ar.path(), bec.message()); + ec.assign(bec.value(), std::generic_category()); + return ec; + } + + return ec; +} + +} // anonymous namespace + +namespace norns { +namespace io { + +remote_resource_to_local_path_transferor:: + remote_resource_to_local_path_transferor(const context& ctx) : + m_staging_directory(ctx.staging_directory()), + m_network_service(ctx.network_service()) { } + +bool +remote_resource_to_local_path_transferor::validate( + const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) const { + + (void) src_info; + (void) dst_info; + + LOGGER_WARN("Validation not implemented"); + + return true; +} + +std::error_code +remote_resource_to_local_path_transferor::transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + + std::error_code ec; + const auto& d_src = + reinterpret_cast(*src); + const auto& d_dst = + reinterpret_cast(*dst); + + LOGGER_DEBUG("[{}] request_transfer: {} -> {}", + task_info->id(), d_src.to_string(), d_dst.canonical_path()); + + hermes::endpoint endp = m_network_service->lookup(d_src.address()); + + auto resp = + m_network_service->post( + endp, + rpc::stat_resource::input{ + m_network_service->self_address(), + d_src.parent()->nsid(), + static_cast(data::resource_type::local_posix_path), + d_src.name() + }).get(); + + LOGGER_DEBUG("remote_stat returned [task_error: {}, sys_errnum: {}, " + "is_collection: {}, packed_size: {}]", + resp.at(0).task_error(), + resp.at(0).sys_errnum(), + resp.at(0).is_collection(), + resp.at(0).packed_size()); + + if(static_cast(resp.at(0).task_error()) != + urd_error::success) { + return std::make_error_code( + static_cast(resp.at(0).sys_errnum())); + } + + utils::temporary_file tempfile( + /* output_path */ + {resp.at(0).is_collection() ? + "norns-archive-%%%%-%%%%-%%%%.tar" : + d_dst.name()}, + /* parent_dir */ + {resp.at(0).is_collection() ? + m_staging_directory : + d_dst.parent()->mount()}, + resp.at(0).packed_size(), + ec); + + if(ec) { + LOGGER_ERROR("Failed to create temporary file: {}", ec.message()); + return ec; + } + + LOGGER_DEBUG("created local resource: {}", tempfile.path()); + + auto output_buffer = + std::make_shared( + tempfile.path().string(), + hermes::access_mode::write_only, + &ec); + + if(ec) { + LOGGER_ERROR("Failed mmapping output buffer: {}", ec.value()); + return ec; + } + + // let's prepare some local buffers + std::vector bufseq{ + hermes::mutable_buffer{output_buffer->data(), output_buffer->size()} + }; + + hermes::exposed_memory local_buffers = + m_network_service->expose(bufseq, hermes::access_mode::write_only); + + auto resp2 = + m_network_service->post( + endp, + rpc::pull_resource::input{ + d_src.parent()->nsid(), + d_src.name(), + // XXX this resource_type should not be needed, but we + // XXX cannot (easily) find it out right now in the server, + // XXX for now we propagate it, but we should implement + // XXX a lookup()/stat() function in backends to retrieve + // XXX this information locally from the resource id + static_cast( + data::resource_type::local_posix_path), + m_network_service->self_address(), + d_dst.parent()->nsid(), + d_dst.name(), + local_buffers + }).get(); + + LOGGER_DEBUG("Remote push request completed with output " + "{{status: {}, task_error: {}, sys_errnum: {}}} " + "({} bytes, {} usecs)", + resp2.at(0).status(), resp2.at(0).task_error(), + resp2.at(0).sys_errnum(), output_buffer->size(), + resp2.at(0).elapsed_time()); + + if(static_cast(resp2.at(0).status()) == + task_status::finished_with_error) { + // XXX error interface should be improved + return std::make_error_code( + static_cast(resp2.at(0).sys_errnum())); + } + + task_info->record_transfer(output_buffer->size(), + resp2.at(0).elapsed_time()); + + if(resp.at(0).is_collection()) { + return ::unpack_archive(tempfile.path(), d_dst.parent()->mount()); + } + + // prevent output file from being removed by tempfile's destructor + (void) tempfile.release(); + + return ec; +} + + +std::error_code +remote_resource_to_local_path_transferor::accept_transfer( + const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) const { + + (void) auth; + + std::error_code ec; + const auto& d_src = + reinterpret_cast(*src); + const auto& d_dst = + reinterpret_cast(*dst); + auto tempfile = std::make_shared(); + + bfs::path input_path = + !d_src.is_collection() ? + d_src.canonical_path() : + [&]() -> bfs::path { + + LOGGER_DEBUG("[{}] Creating temporary archive from local directory", + task_info->id()); + + const bfs::path ar_path = + ::pack_archive("norns-archive-%%%%-%%%%-%%%%.tar", + m_staging_directory, + {{true, d_src.canonical_path(), d_dst.name()}}, + ec); + + if(ec) { + LOGGER_ERROR("Failed to create temporary archive: {}", + ec.message()); + return {}; + } + + tempfile->manage(ar_path, ec); + + if(ec) { + LOGGER_ERROR("Failed to create temporary archive: {}", + ec.message()); + return {}; + } + + return tempfile->path(); + }(); // <<== XXX (IILE) + + // retrieve task context + const auto ctx = boost::any_cast< + std::shared_ptr< + hermes::request>>(task_info->context()); + auto req = std::move(*ctx); + + LOGGER_DEBUG("[{}] accept_pull: {} -> {}", task_info->id(), + d_src.canonical_path(), d_dst.to_string()); + + // create local buffers from local input data + auto input_buffer = + std::make_shared( + input_path.string(), + hermes::access_mode::read_only, + &ec); + + if(ec) { + LOGGER_ERROR("Failed mapping input data: {}", ec.message()); + *ctx = std::move(req); // restore ctx + return ec; + } + + std::vector bufvec{ + hermes::mutable_buffer{input_buffer->data(), input_buffer->size()} + }; + + auto local_buffers = + m_network_service->expose(bufvec, hermes::access_mode::read_only); + + // retrieve remote buffers descriptor + hermes::exposed_memory remote_buffers = d_dst.buffers(); + + LOGGER_DEBUG("remote_buffers{{count={}, total_size={}}}", + remote_buffers.count(), + remote_buffers.size()); + + auto start = std::chrono::steady_clock::now(); + + // N.B. IMPORTANT: we NEED to capture 'input_buffer' by value here so that + // the mapped_buffer doesn't get released before completion_callback() + // is called. + // We also capture 'tempfile' by value so that it gets automatically + // released (and the associated file erased) when the callback finishes + // FIXME: with C++14 we could simply std::move both into the capture rather + // than using shared_ptrs :/ + const auto completion_callback = + [this, tempfile, input_buffer, start]( + hermes::request&& req) { + + uint32_t usecs = + std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count(); + + // default response + rpc::pull_resource::output out( + static_cast(task_status::finished), + static_cast(urd_error::success), + 0, + usecs); + + //TODO: hermes offers no way to check for an error yet + LOGGER_DEBUG("Push completed"); + + if(req.requires_response()) { + m_network_service->respond( + std::move(req), + out); + } + }; + + m_network_service->async_push(local_buffers, + remote_buffers, + std::move(req), + completion_callback); + + return ec; +} + +std::string +remote_resource_to_local_path_transferor::to_string() const { + return "transferor[remote_resource => local_path]"; +} + +} // namespace io +} // namespace norns diff --git a/src/io/transferors/remote-resource-to-local-path.hpp b/src/io/transferors/remote-resource-to-local-path.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1e63a4c70b71899d49950de60b2e1a65b18f82c7 --- /dev/null +++ b/src/io/transferors/remote-resource-to-local-path.hpp @@ -0,0 +1,92 @@ +/************************************************************************* + * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __IO_REMOTE_RESOURCE_TO_LOCAL_PATH_TX__ +#define __IO_REMOTE_RESOURCE_TO_LOCAL_PATH_TX__ + +#include +#include + +#include "context.hpp" +#include "transferor.hpp" + +namespace hermes { +class async_engine; + +template class request; + +} // namespace hermes + +namespace norns { + +// forward declarations +namespace auth { +struct credentials; +} + +namespace data { +struct resource_info; +struct resource; +} + +namespace io { + +struct remote_resource_to_local_path_transferor : public transferor { + + remote_resource_to_local_path_transferor(const context& ctx); + + bool + validate(const std::shared_ptr& src_info, + const std::shared_ptr& dst_info) + const override final; + + std::error_code + transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::error_code + accept_transfer(const auth::credentials& auth, + const std::shared_ptr& task_info, + const std::shared_ptr& src, + const std::shared_ptr& dst) + const override final; + + std::string + to_string() const override final; + +private: + bfs::path m_staging_directory; + std::shared_ptr m_network_service; +}; + +} // namespace io +} // namespace norns + +#endif // __IO_REMOTE_RESOURCE_TO_LOCAL_PATH_TX__ diff --git a/src/io/transferors/transferor.hpp b/src/io/transferors/transferor.hpp index 4fd98f3383959d97e11d9cc1203d7cf16c893d56..186efc320e96b7bd97c223298728a1deda982eb4 100644 --- a/src/io/transferors/transferor.hpp +++ b/src/io/transferors/transferor.hpp @@ -49,15 +49,24 @@ struct task_info; struct transferor { - virtual bool validate(const std::shared_ptr& src_info, - const std::shared_ptr& dst_info) const = 0; + virtual bool + validate(const std::shared_ptr &src_info, + const std::shared_ptr &dst_info) const = 0; - virtual std::error_code transfer(const auth::credentials& auth, - const std::shared_ptr& task_info, - const std::shared_ptr& src, - const std::shared_ptr& dst) const = 0; + virtual std::error_code + transfer(const auth::credentials &auth, + const std::shared_ptr &task_info, + const std::shared_ptr &src, + const std::shared_ptr &dst) const = 0; - virtual std::string to_string() const = 0; + virtual std::error_code + accept_transfer(const auth::credentials &auth, + const std::shared_ptr &task_info, + const std::shared_ptr &src, + const std::shared_ptr &dst) const = 0; + + virtual std::string + to_string() const = 0; }; } // namespace io diff --git a/src/logger.hpp b/src/logger.hpp index 870d6ceea97e0abe8e39cc039caf38a83662611e..7a04567306a7ae9420ca1eeb0de912ed44c8f566 100644 --- a/src/logger.hpp +++ b/src/logger.hpp @@ -31,9 +31,17 @@ #include #include +#include #include #include +#if FMT_VERSION < 50000 +namespace fmt { +template +inline const void *ptr(const T *p) { return p; } +} // namespace fmt +#endif // FMT_VERSION + namespace bfs = boost::filesystem; class logger { @@ -123,30 +131,65 @@ public: return global_logger(); } - // some macros to make it more convenient to use the global logger +// some macros to make it more convenient to use the global logger +#define LOGGER_INFO(...) \ +do { \ + if(logger::get_global_logger()) { \ + logger::get_global_logger()->info(__VA_ARGS__); \ + } \ +} while(0); -#define LOGGER_INFO(...) \ - logger::get_global_logger()->info(__VA_ARGS__) #ifdef __LOGGER_ENABLE_DEBUG__ + #define LOGGER_DEBUG(...) \ - logger::get_global_logger()->debug(__VA_ARGS__) +do { \ + if(logger::get_global_logger()) { \ + logger::get_global_logger()->debug(__VA_ARGS__); \ + } \ +} while(0); + +#define LOGGER_FLUSH() \ +do { \ + if(logger::get_global_logger()) { \ + logger::get_global_logger()->flush(); \ + } \ +} while(0); + #else -#define LOGGER_DEBUG(...) \ - do {} while(0) -#endif + +#define LOGGER_DEBUG(...) do {} while(0); +#define LOGGER_FLUSH() do {} while(0); + +#endif // __LOGGER_ENABLE_DEBUG__ #define LOGGER_WARN(...) \ - logger::get_global_logger()->warn(__VA_ARGS__) +do { \ + if(logger::get_global_logger()) { \ + logger::get_global_logger()->warn(__VA_ARGS__); \ + } \ +} while(0); #define LOGGER_ERROR(...) \ - logger::get_global_logger()->error(__VA_ARGS__) +do { \ + if(logger::get_global_logger()) { \ + logger::get_global_logger()->error(__VA_ARGS__); \ + } \ +} while(0); #define LOGGER_ERRNO(...) \ - logger::get_global_logger()->error_errno(__VA_ARGS__) +do { \ + if(logger::get_global_logger()) { \ + logger::get_global_logger()->error_errno(__VA_ARGS__); \ + } \ +} while(0); #define LOGGER_CRITICAL(...) \ - logger::get_global_logger()->critical(__VA_ARGS__) +do { \ + if(logger::get_global_logger()) { \ + logger::get_global_logger()->critical(__VA_ARGS__); \ + } \ +} while(0); // the following member functions can be used to interact // with a specific logger instance @@ -178,6 +221,15 @@ public: m_internal_logger->error(fmt, args...); } + static inline std::string + errno_message(int errno_value) { + // 1024 should be more than enough for most locales + constexpr const std::size_t MAX_ERROR_MSG = 1024; + std::array errstr; + char* msg = strerror_r(errno_value, errstr.data(), MAX_ERROR_MSG); + return std::string{msg}; + } + template inline void error_errno(const char* fmt, const Args&... args) { @@ -251,4 +303,49 @@ private: std::string m_type; }; +// override the logging macros of the hermes library +// and replace it with ours, which use the same fmt definitions +// to format message strings +#ifdef HERMES_INFO +#undef HERMES_INFO +#endif +#define HERMES_INFO(...) LOGGER_INFO(__VA_ARGS__) + +#ifdef HERMES_WARNING +#undef HERMES_WARNING +#endif +#define HERMES_WARNING(...) LOGGER_WARN(__VA_ARGS__) + +#ifdef HERMES_DEBUG +#undef HERMES_DEBUG +#endif +#define HERMES_DEBUG(...) LOGGER_DEBUG(__VA_ARGS__) + +#ifdef HERMES_DEBUG2 +#undef HERMES_DEBUG2 +#endif +#define HERMES_DEBUG2(...) LOGGER_DEBUG(__VA_ARGS__) + +#ifdef HERMES_DEBUG3 +#undef HERMES_DEBUG3 +#endif +#define HERMES_DEBUG3(...) LOGGER_DEBUG(__VA_ARGS__) + +#ifdef HERMES_DEBUG4 +#undef HERMES_DEBUG4 +#endif +#define HERMES_DEBUG4(...) // LOGGER_DEBUG(__VA_ARGS__) + +#ifdef HERMES_ERROR +#undef HERMES_ERROR +#endif +#define HERMES_ERROR(...) LOGGER_ERROR(__VA_ARGS__) + +#ifdef HERMES_FATAL +#undef HERMES_FATAL +#endif +#define HERMES_FATAL(...) LOGGER_CRITICAL(__VA_ARGS__) + +#include + #endif /* __LOGGER_HPP__ */ diff --git a/src/main.cpp b/src/main.cpp index b8e30f033ba9160fdc155228892a52cb1096d556..ca0478e7f611ce988796eb761e2b06325ed6416e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -63,7 +63,7 @@ main(int argc, char* argv[]) { ->default_value(false) ->notifier( [&](const bool& flag_value) { - cfg.daemonize() = !flag_value; + cfg.daemonize(!flag_value); }), "foreground operation") @@ -74,8 +74,8 @@ main(int argc, char* argv[]) { ->implicit_value(100) ->notifier( [&](const uint32_t& duration_value) { - cfg.dry_run() = true; - cfg.dry_run_duration() = duration_value; + cfg.dry_run(true); + cfg.dry_run_duration(duration_value); }), "don't actually execute tasks, but wait N microseconds per task if " "an argument is provided") @@ -87,7 +87,7 @@ main(int argc, char* argv[]) { ->zero_tokens() ->notifier( [&](const std::string&) { - cfg.use_console() = true; + cfg.use_console(true); }), "override any logging options defined in configuration files and " "send all daemon output to the console" diff --git a/src/namespaces/namespace-manager.hpp b/src/namespaces/namespace-manager.hpp index e29b88d19a08be684c8fca127e8acd96bc1441e0..a35c6844536639a632eb554b26f8638d40f7d806 100644 --- a/src/namespaces/namespace-manager.hpp +++ b/src/namespaces/namespace-manager.hpp @@ -94,7 +94,7 @@ struct namespace_manager { if(is_remote) { return static_cast>( - std::make_shared()); + std::make_shared(nsid)); } if(m_namespaces.count(nsid) == 0) { diff --git a/src/resources.hpp b/src/resources.hpp index 03e6b39439092451d66af93d3c16c110c7417a8b..b2805072cca95978b5f00f21bc34c35618e33df8 100644 --- a/src/resources.hpp +++ b/src/resources.hpp @@ -35,6 +35,7 @@ #include "resources/local_posix_path/local-path.hpp" #include "resources/memory_buffer/memory-buffer.hpp" #include "resources/shared_posix_path/shared-path.hpp" +#include "resources/remote_resource/remote-resource.hpp" #include "resources/remote_posix_path/remote-path.hpp" #endif /* __RESOURCES_HPP__ */ diff --git a/src/resources/local_posix_path/detail/local-path-impl.cpp b/src/resources/local_posix_path/detail/local-path-impl.cpp index 35de7e4b883afd31d26ef35899680e8a47882f31..6568583e360aedf2b6f33bbec3d53bf79bf8a0ba 100644 --- a/src/resources/local_posix_path/detail/local-path-impl.cpp +++ b/src/resources/local_posix_path/detail/local-path-impl.cpp @@ -36,6 +36,7 @@ namespace bfs = boost::filesystem; #include "local-path-info.hpp" #include "local-path-impl.hpp" #include "backends/posix-fs.hpp" +#include "utils.hpp" #include "logger.hpp" namespace norns { @@ -47,14 +48,14 @@ using local_path_resource = resource_impl; local_path_resource::resource_impl(const std::shared_ptr parent, const bfs::path& name) : - m_namespace_name(name), + m_name_in_namespace(name), m_canonical_path(parent->mount() / name), m_is_collection(bfs::is_directory(m_canonical_path)), m_parent(std::static_pointer_cast(std::move(parent))) { } std::string local_path_resource::name() const { - return m_namespace_name.string(); + return m_name_in_namespace.string(); } resource_type @@ -67,6 +68,18 @@ local_path_resource::is_collection() const { return m_is_collection; } +std::size_t +local_path_resource::packed_size() const { + std::error_code ec; + boost::system::error_code error; + + std::size_t sz = m_is_collection ? + utils::tar::estimate_size_once_packed(m_canonical_path, ec) : + bfs::file_size(m_canonical_path, error); + + return (ec || error) ? 0 : sz; +} + const std::shared_ptr local_path_resource::parent() const { return std::static_pointer_cast(m_parent); diff --git a/src/resources/local_posix_path/detail/local-path-impl.hpp b/src/resources/local_posix_path/detail/local-path-impl.hpp index ce7588b408410b49e790ddeaa484557150bd03b6..ddb4dddd1598cbc9155c2a310f6c78cb9c62b424 100644 --- a/src/resources/local_posix_path/detail/local-path-impl.hpp +++ b/src/resources/local_posix_path/detail/local-path-impl.hpp @@ -58,12 +58,13 @@ struct resource_impl : public resource { std::string name() const override final; resource_type type() const override final; bool is_collection() const override final; + std::size_t packed_size() const override final; const std::shared_ptr parent() const override final; std::string to_string() const override final; bfs::path canonical_path() const; - const bfs::path m_namespace_name; // absolute pathname w.r.t. backend's mount point + const bfs::path m_name_in_namespace; // absolute pathname w.r.t. backend's mount point const bfs::path m_canonical_path; // canonical pathname const bool m_is_collection; const std::shared_ptr m_parent; diff --git a/src/resources/memory_buffer/detail/memory-region-impl.cpp b/src/resources/memory_buffer/detail/memory-region-impl.cpp index 45d6e80189d854b4333e18bddaae65b48021fe8e..89f3417bb76ebf1c5c124dd4f92e507a0820029e 100644 --- a/src/resources/memory_buffer/detail/memory-region-impl.cpp +++ b/src/resources/memory_buffer/detail/memory-region-impl.cpp @@ -58,6 +58,11 @@ bool memory_region_resource::is_collection() const { return false; } +std::size_t +memory_region_resource::packed_size() const { + return m_size; +} + const std::shared_ptr memory_region_resource::parent() const { return std::static_pointer_cast(m_parent); diff --git a/src/resources/memory_buffer/detail/memory-region-impl.hpp b/src/resources/memory_buffer/detail/memory-region-impl.hpp index 22b632aaf0ce012e85ca47ec93f6d934425f0ca1..1e087134725f774a4ce94d5ed8d611280facbf8b 100644 --- a/src/resources/memory_buffer/detail/memory-region-impl.hpp +++ b/src/resources/memory_buffer/detail/memory-region-impl.hpp @@ -52,6 +52,7 @@ struct resource_impl : public resource { std::string name() const override final; resource_type type() const override final; bool is_collection() const override final; + std::size_t packed_size() const override final; const std::shared_ptr parent() const override final; std::string to_string() const override final; diff --git a/src/resources/remote_posix_path/detail/remote-path-impl.cpp b/src/resources/remote_posix_path/detail/remote-path-impl.cpp index ef04869b6eed058433c0b066595ad458cd3dcce1..e41949f91f063215cdae2e4e5037f1881f9415cc 100644 --- a/src/resources/remote_posix_path/detail/remote-path-impl.cpp +++ b/src/resources/remote_posix_path/detail/remote-path-impl.cpp @@ -43,11 +43,13 @@ namespace detail { // remote alias for convenience using remote_path_resource = resource_impl; -remote_path_resource::resource_impl(const std::shared_ptr parent) : - m_parent(std::static_pointer_cast(std::move(parent))) { } +remote_path_resource::resource_impl( + const std::shared_ptr parent) + : m_parent(std::static_pointer_cast( + std::move(parent))) {} std::string remote_path_resource::name() const { - return "PENDING"; + return "PENDING: " + m_parent->to_string(); } resource_type remote_path_resource::type() const { @@ -58,6 +60,11 @@ bool remote_path_resource::is_collection() const { return false; } +std::size_t +remote_path_resource::packed_size() const { + return 0; +} + const std::shared_ptr remote_path_resource::parent() const { return std::static_pointer_cast(m_parent); diff --git a/src/resources/remote_posix_path/detail/remote-path-impl.hpp b/src/resources/remote_posix_path/detail/remote-path-impl.hpp index 6abfb116fa6532b237d2cf85ad485516106499c6..b775f483944a4594f2271b18866cd2d082e7b5a4 100644 --- a/src/resources/remote_posix_path/detail/remote-path-impl.hpp +++ b/src/resources/remote_posix_path/detail/remote-path-impl.hpp @@ -52,9 +52,12 @@ struct resource_impl : public resource { std::string name() const override final; resource_type type() const override final; bool is_collection() const override final; + std::size_t packed_size() const override final; const std::shared_ptr parent() const override final; std::string to_string() const override final; + + const bfs::path m_name_in_namespace; const std::shared_ptr m_parent; }; diff --git a/src/resources/remote_posix_path/detail/remote-path-info.cpp b/src/resources/remote_posix_path/detail/remote-path-info.cpp index 2f0b465502fe2505e9a415a86c8f2775cf74d80b..46faf5028865b580e9134dbfb724a6d92d3ee5e6 100644 --- a/src/resources/remote_posix_path/detail/remote-path-info.cpp +++ b/src/resources/remote_posix_path/detail/remote-path-info.cpp @@ -59,6 +59,11 @@ std::string remote_path_info::to_string() const { return "REMOTE_PATH[\"" + m_hostname + "\", \"" + m_nsid + "\", \"" + m_datapath + "\"]"; } +std::string +remote_path_info::hostname() const { + return m_hostname; +} + std::string remote_path_info::datapath() const { return m_datapath; } diff --git a/src/resources/remote_resource/detail/remote-resource-impl.cpp b/src/resources/remote_resource/detail/remote-resource-impl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..510e5c8fe659ae4e9670d66d3f5adfb715eb1845 --- /dev/null +++ b/src/resources/remote_resource/detail/remote-resource-impl.cpp @@ -0,0 +1,107 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include + +#include "common.hpp" +#include "resource-type.hpp" +#include "resource.hpp" +#include "remote-resource-info.hpp" +#include "remote-resource-impl.hpp" +#include "backends/remote-backend.hpp" +#include "logger.hpp" + +namespace norns { +namespace data { +namespace detail { + +// local alias for convenience +using remote_resource = resource_impl; + +remote_resource::resource_impl( + const std::shared_ptr parent, + const std::shared_ptr rinfo) : + m_name(rinfo->m_name), + m_address(rinfo->m_address), + m_nsid(rinfo->m_nsid), + m_buffers(rinfo->m_buffers), + m_is_collection(rinfo->m_is_collection), + m_parent(std::static_pointer_cast(std::move(parent))) { } + +std::string +remote_resource::name() const { + return m_name; +} + +resource_type +remote_resource::type() const { + return resource_type::remote_resource; +} + +bool +remote_resource::is_collection() const { + return m_is_collection; +} + +std::size_t +remote_resource::packed_size() const { + return 0; +} + +const std::shared_ptr +remote_resource::parent() const { + return std::static_pointer_cast(m_parent); +} + +std::string +remote_resource::to_string() const { + return "REMOTE_RESOURCE[" + m_nsid + "@" + m_address + ":" + m_name + "]"; +} + +std::string +remote_resource::address() const { + return m_address; +} + +std::string +remote_resource::nsid() const { + return m_nsid; +} + +bool +remote_resource::has_buffer() const { + return m_buffers.count() != 0; +} + +hermes::exposed_memory +remote_resource::buffers() const { + return m_buffers; +} + +} // namespace detail +} // namespace data +} // namespace norns diff --git a/src/resources/remote_resource/detail/remote-resource-impl.hpp b/src/resources/remote_resource/detail/remote-resource-impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a41fd57c332fbeeef8143a8ab947b0d8d94b9076 --- /dev/null +++ b/src/resources/remote_resource/detail/remote-resource-impl.hpp @@ -0,0 +1,89 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __REMOTE_RESOURCE_IMPL_HPP__ +#define __REMOTE_RESOURCE_IMPL_HPP__ + +#include +#include +#include +#include +#include + +namespace norns { + +// forward declarations +namespace storage { +namespace detail { +class remote_backend; +} +} + +namespace data { + +enum class resource_type; + +namespace detail { + +template <> +struct resource_impl : public resource { + + resource_impl(const std::shared_ptr parent, + const std::shared_ptr rinfo); + + std::string name() const override final; + resource_type type() const override final; + bool is_collection() const override final; + std::size_t packed_size() const override final; + const std::shared_ptr parent() const override final; + std::string to_string() const override final; + + std::string + address() const; + + std::string + nsid() const; + + bool + has_buffer() const; + + hermes::exposed_memory + buffers() const; + + const std::string m_name; + const std::string m_address; + const std::string m_nsid; + const hermes::exposed_memory m_buffers; + const bool m_is_collection; + const std::shared_ptr m_parent; +}; + +} // namespace detail +} // namespace data +} // namespace norns + +#endif /* __REMOTE_RESOURCE_IMPL_HPP__ */ diff --git a/src/resources/remote_resource/detail/remote-resource-info.cpp b/src/resources/remote_resource/detail/remote-resource-info.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5f1f023b91f0b1e58680ca07e4d34e0b94e2681e --- /dev/null +++ b/src/resources/remote_resource/detail/remote-resource-info.cpp @@ -0,0 +1,107 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include "resource-type.hpp" +#include "resource-info.hpp" +#include "remote-resource-info.hpp" + +namespace norns { +namespace data { +namespace detail { + +/*! Remote path data */ +remote_resource_info::remote_resource_info( + const std::string& address, + const std::string& nsid, + const std::string& name) : + m_address(address), + m_nsid(nsid), + m_is_collection(false), + m_name(name) { } + +remote_resource_info::remote_resource_info( + const std::string& address, + const std::string& nsid, + bool is_collection, + const std::string& name, + const hermes::exposed_memory& buffers) : + m_address(address), + m_nsid(nsid), + m_is_collection(is_collection), + m_name(name), + m_buffers(buffers) { } + +remote_resource_info::~remote_resource_info() { } + +resource_type +remote_resource_info::type() const { + return resource_type::remote_resource; +} + +std::string +remote_resource_info::address() const { + return m_address; +} + +std::string +remote_resource_info::nsid() const { + return m_nsid; +} + +bool +remote_resource_info::is_remote() const { + return true; +} + +std::string +remote_resource_info::to_string() const { + return "REMOTE_RESOURCE[" + m_nsid + "@" + m_address + ":" + m_name + "]"; +} + +std::string +remote_resource_info::name() const { + return m_name; +} + +bool +remote_resource_info::is_collection() const { + return m_is_collection; +} + +bool +remote_resource_info::has_buffers() const { + return m_buffers.count() != 0; +} + +hermes::exposed_memory +remote_resource_info::buffers() const { + return m_buffers; +} + +} // namespace detail +} // namespace data +} // namespace norns diff --git a/src/resources/remote_resource/detail/remote-resource-info.hpp b/src/resources/remote_resource/detail/remote-resource-info.hpp new file mode 100644 index 0000000000000000000000000000000000000000..87f5f85cc281384352c699ec8f5159230a6aeff6 --- /dev/null +++ b/src/resources/remote_resource/detail/remote-resource-info.hpp @@ -0,0 +1,87 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __REMOTE_RESOURCE_INFO_HPP__ +#define __REMOTE_RESOURCE_INFO_HPP__ + +#include +#include +#include "resource-info.hpp" +#include "rpcs.hpp" + +namespace norns { +namespace data { + +enum class resource_type; + +namespace detail { + +/*! Local filesystem path data */ +struct remote_resource_info : public resource_info { + + remote_resource_info(const std::string& address, + const std::string& nsid, + const std::string& name); + + remote_resource_info(const std::string& address, + const std::string& nsid, + bool is_collection, + const std::string& name, + const hermes::exposed_memory& buffers); + ~remote_resource_info(); + resource_type type() const override final; + std::string nsid() const override final; + bool is_remote() const override final; + std::string to_string() const override final; + + std::string + address() const; + + std::string + name() const; + + bool + is_collection() const; + + bool + has_buffers() const; + + hermes::exposed_memory + buffers() const; + + std::string m_address; + std::string m_nsid; + bool m_is_collection; + std::string m_name; + hermes::exposed_memory m_buffers; +}; + +} // namespace detail +} // namespace data +} // namespace norns + +#endif // __REMOTE_RESOURCE_INFO_HPP__ diff --git a/src/resources/remote_resource/remote-resource.hpp b/src/resources/remote_resource/remote-resource.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8dceb1e3930ee4a68999082eaeef1e71c0a3d392 --- /dev/null +++ b/src/resources/remote_resource/remote-resource.hpp @@ -0,0 +1,47 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef __REMOTE_RESOURCE_HPP__ +#define __REMOTE_RESOURCE_HPP__ + +#include "resource-type.hpp" +#include "resource.hpp" + +#include "detail/remote-resource-info.hpp" +#include "detail/remote-resource-impl.hpp" + +namespace norns { +namespace data { + +using remote_resource_info = detail::remote_resource_info; +using remote_resource = + detail::resource_impl; + +} // namespace data +} // namespace norns + +#endif // __REMOTE_RESOURCE_HPP__ diff --git a/src/resources/resource-type.hpp b/src/resources/resource-type.hpp index ec9aa9f8d5a588dda02b8e22561e128a7010c4a5..9d88a7260cfd2930bf0802282796044f7a9d9be9 100644 --- a/src/resources/resource-type.hpp +++ b/src/resources/resource-type.hpp @@ -39,6 +39,7 @@ enum class resource_type { local_posix_path, shared_posix_path, remote_posix_path, + remote_resource, ignorable }; @@ -56,6 +57,8 @@ static inline std::string to_string(data::resource_type type) { return "SHARED_PATH"; case data::resource_type::remote_posix_path: return "REMOTE_PATH"; + case data::resource_type::remote_resource: + return "REMOTE_RESOURCE"; default: return "UNKNOWN_RESOURCE_TYPE"; } diff --git a/src/resources/resource.hpp b/src/resources/resource.hpp index 19d5161c275f3dfd9a2a57b617b6e44c29fc3134..8a3c553bd1eb012baf580fff00c5f63ccc1fddec 100644 --- a/src/resources/resource.hpp +++ b/src/resources/resource.hpp @@ -49,6 +49,7 @@ struct resource : public std::enable_shared_from_this { virtual resource_type type() const = 0; virtual std::string name() const = 0; virtual bool is_collection() const = 0; + virtual std::size_t packed_size() const = 0; virtual const std::shared_ptr parent() const = 0; virtual std::string to_string() const = 0; }; diff --git a/src/resources/shared_posix_path/detail/shared-path-impl.cpp b/src/resources/shared_posix_path/detail/shared-path-impl.cpp index 0f83fbd70969f551a5b7d47e681fc56b6c3d1e87..f5fca7a6311e9b8f6457fb7343c9c26840540746 100644 --- a/src/resources/shared_posix_path/detail/shared-path-impl.cpp +++ b/src/resources/shared_posix_path/detail/shared-path-impl.cpp @@ -45,13 +45,13 @@ using shared_path_resource = resource_impl; shared_path_resource::resource_impl(const std::shared_ptr parent, const bfs::path& name) : - m_namespace_name(name), + m_name_in_namespace(name), m_canonical_path(parent->mount() / name), m_is_collection(bfs::is_directory(m_canonical_path)), m_parent(std::static_pointer_cast(std::move(parent))) { } std::string shared_path_resource::name() const { - return m_namespace_name.string(); + return m_name_in_namespace.string(); } resource_type shared_path_resource::type() const { @@ -62,6 +62,11 @@ bool shared_path_resource::is_collection() const { return m_is_collection; } +std::size_t +shared_path_resource::packed_size() const { + return 0; +} + const std::shared_ptr shared_path_resource::parent() const { return std::static_pointer_cast(m_parent); diff --git a/src/resources/shared_posix_path/detail/shared-path-impl.hpp b/src/resources/shared_posix_path/detail/shared-path-impl.hpp index 270b4071f4243dad133130ad007084cd17071568..9c4d4eda10b1a6c595c6c055db552243358e061d 100644 --- a/src/resources/shared_posix_path/detail/shared-path-impl.hpp +++ b/src/resources/shared_posix_path/detail/shared-path-impl.hpp @@ -58,10 +58,11 @@ struct resource_impl : public resource { std::string name() const override final; resource_type type() const override final; bool is_collection() const override final; + std::size_t packed_size() const override final; const std::shared_ptr parent() const override final; std::string to_string() const override final; - const bfs::path m_namespace_name; // absolute pathname w.r.t. backend's mount point + const bfs::path m_name_in_namespace; // absolute pathname w.r.t. backend's mount point const bfs::path m_canonical_path; // canonical pathname const bool m_is_collection; const std::shared_ptr m_parent; diff --git a/src/rpcs.cpp b/src/rpcs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfe288353dd83646833e6466c275add2c473438b --- /dev/null +++ b/src/rpcs.cpp @@ -0,0 +1,17 @@ +//#include "common.hpp" +#include +#include "rpcs.hpp" + +namespace hermes { namespace detail { + +//============================================================================== +// register request types so that they can be used by users and the engine +// +void +register_user_request_types() { + (void) registered_requests().add(); + (void) registered_requests().add(); + (void) registered_requests().add(); +} + +}} // namespace hermes::detail diff --git a/src/rpcs.hpp b/src/rpcs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d487c743de943e47b920866bb6d96d4d646848b2 --- /dev/null +++ b/src/rpcs.hpp @@ -0,0 +1,983 @@ +#ifndef __NORNS_IO_TRANSFERORS_RPCS_HPP__ +#define __NORNS_IO_TRANSFERORS_RPCS_HPP__ + +// C includes +#include +#include +#include + +// C++ includes +#include + +// hermes includes +#include + +#ifndef HG_GEN_PROC_NAME +#define HG_GEN_PROC_NAME(struct_type_name) \ + hermes::detail::hg_proc_ ## struct_type_name +#endif + +// forward declarations +namespace hermes { namespace detail { + +template +hg_return_t post_to_mercury(ExecutionContext* ctx); + +}} // namespace hermes::detail + +//============================================================================== +// definitions for norns::rpc::push_resource +namespace hermes { namespace detail { + +// Generate Mercury types and serialization functions (field names match +// those defined by push_resource::input and push_resource::output). These +// definitions are internal and should not be used directly. Classes +// push_resource::input and push_resource::output are provided for public use. +MERCURY_GEN_PROC(push_resource_in_t, + ((hg_const_string_t) (in_address)) + ((hg_const_string_t) (in_nsid)) + ((hg_const_string_t) (in_resource_name)) + ((hg_bool_t) (in_is_collection)) + ((hg_bulk_t) (in_buffers)) + ((hg_const_string_t) (out_nsid)) + ((uint32_t) (out_resource_type)) + ((hg_const_string_t) (out_resource_name))) + +MERCURY_GEN_PROC(push_resource_out_t, + ((uint32_t) (status)) + ((uint32_t) (task_error)) + ((uint32_t) (sys_errnum)) + ((uint32_t) (elapsed_time))) + +MERCURY_GEN_PROC(pull_resource_in_t, + ((hg_const_string_t) (in_nsid)) + ((hg_const_string_t) (in_resource_name)) + ((uint32_t) (in_resource_type)) + ((hg_const_string_t) (out_address)) + ((hg_const_string_t) (out_nsid)) + ((hg_const_string_t) (out_resource_name)) + ((hg_bulk_t) (out_buffers))) + +MERCURY_GEN_PROC(pull_resource_out_t, + ((uint32_t) (status)) + ((uint32_t) (task_error)) + ((uint32_t) (sys_errnum)) + ((uint32_t) (elapsed_time))) + +MERCURY_GEN_PROC(stat_resource_in_t, + ((hg_const_string_t) (address)) + ((hg_const_string_t) (nsid)) + ((uint32_t) (resource_type)) + ((hg_const_string_t) (resource_name))) + +MERCURY_GEN_PROC(stat_resource_out_t, + ((uint32_t) (task_error)) + ((uint32_t) (sys_errnum)) + ((hg_bool_t) (is_collection)) + ((uint64_t) (packed_size))) + +}} // namespace hermes::detail + + +namespace norns { +namespace rpc { + +struct push_resource { + + // forward declarations of public input/output types for this RPC + class input; + class output; + + // traits used so that the engine knows what to do with the RPC + using self_type = push_resource; + using handle_type = hermes::rpc_handle; + using input_type = input; + using output_type = output; + using mercury_input_type = hermes::detail::push_resource_in_t; + using mercury_output_type = hermes::detail::push_resource_out_t; + + // RPC public identifier + constexpr static const uint16_t public_id = 43; + + // RPC internal Mercury identifier + constexpr static const uint16_t mercury_id = public_id; + + // RPC name + constexpr static const auto name = "push_resource"; + + // requires response? + constexpr static const auto requires_response = true; + + // Mercury callback to serialize input arguments + constexpr static const auto mercury_in_proc_cb = + HG_GEN_PROC_NAME(push_resource_in_t); + + // Mercury callback to serialize output arguments + constexpr static const auto mercury_out_proc_cb = + HG_GEN_PROC_NAME(push_resource_out_t); + + class input { + + template + friend hg_return_t hermes::detail::post_to_mercury(ExecutionContext*); + + public: + input(const std::string& in_address, + const std::string& in_nsid, + const std::string& out_nsid, + uint32_t out_resource_type, + uint32_t in_is_collection, + const std::string& in_resource_name, + const std::string& out_resource_name, + const hermes::exposed_memory& in_buffers) : + m_in_address(in_address), + m_in_nsid(in_nsid), + m_in_resource_name(in_resource_name), + m_in_is_collection(in_is_collection), + m_in_buffers(in_buffers), + m_out_nsid(out_nsid), + m_out_resource_type(out_resource_type), + m_out_resource_name(out_resource_name) { + +#ifdef HERMES_DEBUG_BUILD + this->print("this", __PRETTY_FUNCTION__); +#endif + + } + +#ifdef HERMES_DEBUG_BUILD + input(input&& rhs) : + m_in_address(std::move(rhs.m_in_address)), + m_in_nsid(std::move(rhs.m_in_nsid)), + m_in_resource_name(std::move(rhs.m_in_resource_name)), + m_in_is_collection(std::move(rhs.m_in_is_collection)), + m_in_buffers(std::move(rhs.m_in_buffers)), + m_out_nsid(std::move(rhs.m_out_nsid)), + m_out_resource_type(std::move(rhs.m_out_resource_type)), + m_out_resource_name(std::move(rhs.m_out_resource_name)) { + + rhs.m_in_is_collection = false; + rhs.m_out_resource_type = 0; + + this->print("this", __PRETTY_FUNCTION__); + rhs.print("rhs", __PRETTY_FUNCTION__); + } + + input(const input& other) : + m_in_address(other.m_in_address), + m_in_nsid(other.m_in_nsid), + m_in_resource_name(other.m_in_resource_name), + m_in_is_collection(other.m_in_is_collection), + m_in_buffers(other.m_in_buffers), + m_out_nsid(other.m_out_nsid), + m_out_resource_type(other.m_out_resource_type), + m_out_resource_name(other.m_out_resource_name) { + + this->print("this", __PRETTY_FUNCTION__); + other.print("other", __PRETTY_FUNCTION__); + } + + input& + operator=(input&& rhs) { + + if(this != &rhs) { + m_in_address = std::move(rhs.m_in_address); + m_in_nsid = std::move(rhs.m_in_nsid); + m_in_resource_name = std::move(rhs.m_in_resource_name); + m_in_is_collection = std::move(rhs.m_in_is_collection); + m_in_buffers = std::move(rhs.m_in_buffers); + m_out_nsid = std::move(rhs.m_out_nsid); + m_out_resource_type = std::move(rhs.m_out_resource_type); + m_out_resource_name = std::move(rhs.m_out_resource_name); + + rhs.m_in_is_collection = false; + rhs.m_out_resource_type = 0; + } + + this->print("this", __PRETTY_FUNCTION__); + rhs.print("rhs", __PRETTY_FUNCTION__); + + return *this; + } + + input& + operator=(const input& other) { + + if(this != &other) { + m_in_address = other.m_in_address; + m_in_nsid = other.m_in_nsid; + m_in_resource_name = other.m_in_resource_name; + m_in_is_collection = other.m_in_is_collection; + m_in_buffers = other.m_in_buffers; + m_out_nsid = other.m_out_nsid; + m_out_resource_type = other.m_out_resource_type; + m_out_resource_name = other.m_out_resource_name; + } + + this->print("this", __PRETTY_FUNCTION__); + other.print("other", __PRETTY_FUNCTION__); + + return *this; + } +#else // HERMES_DEBUG_BUILD + input(input&& rhs) = default; + input(const input& other) = default; + input& operator=(input&& rhs) = default; + input& operator=(const input& other) = default; +#endif // ! HERMES_DEBUG_BUILD + + std::string + in_address() const { + return m_in_address; + } + + std::string + in_nsid() const { + return m_in_nsid; + } + + std::string + in_resource_name() const { + return m_in_resource_name; + } + + bool + in_is_collection() const { + return m_in_is_collection; + } + + hermes::exposed_memory + in_buffers() const { + return m_in_buffers; + } + + std::string + out_nsid() const { + return m_out_nsid; + } + + uint32_t + out_resource_type() const { + return m_out_resource_type; + } + + std::string + out_resource_name() const { + return m_out_resource_name; + } + +#ifdef HERMES_DEBUG_BUILD + void + print(const std::string& id, + const std::string& caller = "") const { + + (void) id; + auto c = caller.empty() ? "unknown_caller" : caller; + + HERMES_DEBUG2("{}, {} ({}) = {{", caller, id, fmt::ptr(this)); + HERMES_DEBUG2(" m_in_address: \"{}\" ({} -> {}),", + m_in_address, fmt::ptr(&m_in_address), + fmt::ptr(m_in_address.c_str())); + HERMES_DEBUG2(" m_in_nsid: \"{}\" ({} -> {}),", + m_in_nsid, fmt::ptr(&m_in_nsid), + fmt::ptr(m_in_nsid.c_str())); + HERMES_DEBUG2(" m_in_resource_name: \"{}\" ({} -> {}),", + m_in_resource_name, fmt::ptr(&m_in_resource_name), + fmt::ptr(m_in_resource_name.c_str())); + HERMES_DEBUG2(" m_in_is_collection: {},", + m_in_is_collection); + HERMES_DEBUG2(" m_in_buffers: {...},"); + HERMES_DEBUG2(" m_out_nsid: \"{}\" ({} -> {}),", + m_out_nsid, fmt::ptr(&m_out_nsid), + fmt::ptr(m_out_nsid.c_str())); + HERMES_DEBUG2(" m_out_resource_type: {},", + m_out_resource_type); + HERMES_DEBUG2(" m_out_resource_name: \"{}\" ({} -> {}),", + m_out_resource_name, fmt::ptr(&m_out_resource_name), + fmt::ptr(m_out_resource_name.c_str())); + HERMES_DEBUG2("}}"); + } +#endif // ! HERMES_DEBUG_BUILD + +//TODO: make private + explicit + input(const hermes::detail::push_resource_in_t& other) : + m_in_address(other.in_address), + m_in_nsid(other.in_nsid), + m_in_resource_name(other.in_resource_name), + m_in_is_collection(other.in_is_collection), + m_in_buffers(other.in_buffers), + m_out_nsid(other.out_nsid), + m_out_resource_type(other.out_resource_type), + m_out_resource_name(other.out_resource_name) { + +#ifdef HERMES_DEBUG_BUILD + this->print("this", __PRETTY_FUNCTION__); +#endif // ! HERMES_DEBUG_BUILD + } + + explicit + operator hermes::detail::push_resource_in_t() { + return {m_in_address.c_str(), + m_in_nsid.c_str(), + m_in_resource_name.c_str(), + m_in_is_collection, + hg_bulk_t(m_in_buffers), + m_out_nsid.c_str(), + m_out_resource_type, + m_out_resource_name.c_str()}; + } + + + private: + std::string m_in_address; + std::string m_in_nsid; + std::string m_in_resource_name; + bool m_in_is_collection; + hermes::exposed_memory m_in_buffers; + std::string m_out_nsid; + uint32_t m_out_resource_type; + std::string m_out_resource_name; + }; + + class output { + + template + friend hg_return_t hermes::detail::post_to_mercury(ExecutionContext*); + + public: + output(uint32_t status, + uint32_t task_error, + uint32_t sys_errnum, + uint32_t elapsed_time) : + m_status(status), + m_task_error(task_error), + m_sys_errnum(sys_errnum), + m_elapsed_time(elapsed_time) {} + + uint32_t + status() const { + return m_status; + } + + void + set_retval(uint32_t status) { + m_status = status; + } + + uint32_t + task_error() const { + return m_task_error; + } + + void + set_task_error(uint32_t task_error) { + m_task_error = task_error; + } + + uint32_t + sys_errnum() const { + return m_sys_errnum; + } + + uint32_t + elapsed_time() const { + return m_elapsed_time; + } + + void + set_sys_errnum(uint32_t errnum) { + m_sys_errnum = errnum; + } + + explicit + output(const hermes::detail::push_resource_out_t& out) { + m_status = out.status; + m_task_error = out.task_error; + m_sys_errnum = out.sys_errnum; + m_elapsed_time = out.elapsed_time; + } + + explicit + operator hermes::detail::push_resource_out_t() { + return {m_status, m_task_error, m_sys_errnum, m_elapsed_time}; + } + + private: + uint32_t m_status; + uint32_t m_task_error; + uint32_t m_sys_errnum; + uint32_t m_elapsed_time; + }; +}; + +struct pull_resource { + + // forward declarations of public input/output types for this RPC + class input; + class output; + + // traits used so that the engine knows what to do with the RPC + using self_type = pull_resource; + using handle_type = hermes::rpc_handle; + using input_type = input; + using output_type = output; + using mercury_input_type = hermes::detail::pull_resource_in_t; + using mercury_output_type = hermes::detail::pull_resource_out_t; + + // RPC public identifier + constexpr static const uint16_t public_id = 44; + + // RPC internal Mercury identifier + constexpr static const uint16_t mercury_id = public_id; + + // RPC name + constexpr static const auto name = "pull_resource"; + + // requires response? + constexpr static const auto requires_response = true; + + // Mercury callback to serialize input arguments + constexpr static const auto mercury_in_proc_cb = + HG_GEN_PROC_NAME(pull_resource_in_t); + + // Mercury callback to serialize output arguments + constexpr static const auto mercury_out_proc_cb = + HG_GEN_PROC_NAME(pull_resource_out_t); + + class input { + + template + friend hg_return_t hermes::detail::post_to_mercury(ExecutionContext*); + + public: + input(const std::string& in_nsid, + const std::string& in_resource_name, + uint32_t in_resource_type, + const std::string& out_address, + const std::string& out_nsid, + const std::string& out_resource_name, + const hermes::exposed_memory& out_buffers) : + m_in_nsid(in_nsid), + m_in_resource_name(in_resource_name), + m_in_resource_type(in_resource_type), + m_out_address(out_address), + m_out_nsid(out_nsid), + m_out_resource_name(out_resource_name), + m_out_buffers(out_buffers) { + +#ifdef HERMES_DEBUG_BUILD + this->print("this", __PRETTY_FUNCTION__); +#endif + + } + +#ifdef HERMES_DEBUG_BUILD + input(input&& rhs) : + m_in_nsid(std::move(rhs.m_in_nsid)), + m_out_address(std::move(rhs.m_out_address)), + m_out_nsid(std::move(rhs.m_out_nsid)), + m_in_resource_type(std::move(rhs.m_in_resource_type)), + m_in_resource_name(std::move(rhs.m_in_resource_name)), + m_buffers(std::move(rhs.m_buffers)) { + + rhs.m_in_resource_type = 0; + + this->print("this", __PRETTY_FUNCTION__); + rhs.print("rhs", __PRETTY_FUNCTION__); + } + + input(const input& other) : + m_in_nsid(other.m_in_nsid), + m_out_address(other.m_out_address), + m_out_nsid(other.m_out_nsid), + m_in_resource_type(other.m_in_resource_type), + m_in_resource_name(other.m_in_resource_name), + m_buffers(other.m_buffers) { + + this->print("this", __PRETTY_FUNCTION__); + other.print("other", __PRETTY_FUNCTION__); + } + + input& + operator=(input&& rhs) { + + if(this != &rhs) { + m_in_nsid = std::move(rhs.m_in_nsid); + m_out_address = std::move(rhs.m_out_address); + m_out_nsid = std::move(rhs.m_out_nsid); + m_in_resource_type = std::move(rhs.m_in_resource_type); + m_in_resource_name = std::move(rhs.m_in_resource_name); + m_buffers = std::move(rhs.m_buffers); + + rhs.m_in_resource_type = 0; + rhs.m_is_collection = false; + } + + this->print("this", __PRETTY_FUNCTION__); + rhs.print("rhs", __PRETTY_FUNCTION__); + + return *this; + } + + input& + operator=(const input& other) { + + if(this != &other) { + m_in_nsid = other.m_in_nsid; + m_out_address = other.m_out_address; + m_out_nsid = other.m_out_nsid; + m_in_resource_type = other.m_in_resource_type; + m_in_resource_name = other.m_in_resource_name; + m_buffers = other.m_buffers; + } + + this->print("this", __PRETTY_FUNCTION__); + other.print("other", __PRETTY_FUNCTION__); + + return *this; + } +#else // HERMES_DEBUG_BUILD + input(input&& rhs) = default; + input(const input& other) = default; + input& operator=(input&& rhs) = default; + input& operator=(const input& other) = default; +#endif // ! HERMES_DEBUG_BUILD + + std::string + in_nsid() const { + return m_in_nsid; + } + + uint32_t + in_resource_type() const { + return m_in_resource_type; + } + + std::string + in_resource_name() const { + return m_in_resource_name; + } + + std::string + out_address() const { + return m_out_address; + } + + std::string + out_nsid() const { + return m_out_nsid; + } + + std::string + out_resource_name() const { + return m_out_resource_name; + } + + hermes::exposed_memory + out_buffers() const { + return m_out_buffers; + } + +#ifdef HERMES_DEBUG_BUILD + void + print(const std::string& id, + const std::string& caller = "") const { + + (void) id; + auto c = caller.empty() ? "unknown_caller" : caller; + + HERMES_DEBUG2("{}, {} ({}) = {{", caller, id, fmt::ptr(this)); + HERMES_DEBUG2(" m_in_nsid: \"{}\" ({} -> {}),", + m_in_nsid, fmt::ptr(&m_in_nsid), + fmt::ptr(m_in_nsid.c_str())); + HERMES_DEBUG2(" m_in_resource_name: \"{}\" ({} -> {}),", + m_in_resource_name, fmt::ptr(&m_in_resource_name), + fmt::ptr(m_in_resource_name.c_str())); + HERMES_DEBUG2(" m_in_resource_type: {},", + m_in_resource_type); + HERMES_DEBUG2(" m_out_address: \"{}\" ({} -> {}),", + m_out_address, fmt::ptr(&m_out_address), + fmt::ptr(m_out_address.c_str())); + HERMES_DEBUG2(" m_out_nsid: \"{}\" ({} -> {}),", + m_out_nsid, fmt::ptr(&m_out_nsid), + fmt::ptr(m_out_nsid.c_str())); + HERMES_DEBUG2(" m_out_resource_name: \"{}\" ({} -> {}),", + m_out_resource_name, fmt::ptr(&m_out_resource_name), + fmt::ptr(m_out_resource_name.c_str())); + HERMES_DEBUG2(" m_out_buffers: {...},"); + HERMES_DEBUG2("}}"); + } +#endif // ! HERMES_DEBUG_BUILD + +//TODO: make private + explicit + input(const hermes::detail::pull_resource_in_t& other) : + m_in_nsid(other.in_nsid), + m_in_resource_name(other.in_resource_name), + m_in_resource_type(other.in_resource_type), + m_out_address(other.out_address), + m_out_nsid(other.out_nsid), + m_out_resource_name(other.out_resource_name), + m_out_buffers(other.out_buffers) { + +#ifdef HERMES_DEBUG_BUILD + this->print("this", __PRETTY_FUNCTION__); + rhs.print("other", __PRETTY_FUNCTION__); +#endif + } + + explicit + operator hermes::detail::pull_resource_in_t() { + return {m_in_nsid.c_str(), + m_in_resource_name.c_str(), + m_in_resource_type, + m_out_address.c_str(), + m_out_nsid.c_str(), + m_out_resource_name.c_str(), + hg_bulk_t(m_out_buffers)}; + } + + + private: + std::string m_in_nsid; + std::string m_in_resource_name; + uint32_t m_in_resource_type; + std::string m_out_address; + std::string m_out_nsid; + std::string m_out_resource_name; + hermes::exposed_memory m_out_buffers; + }; + + class output { + + template + friend hg_return_t hermes::detail::post_to_mercury(ExecutionContext*); + + public: + output(uint32_t status, + uint32_t task_error, + uint32_t sys_errnum, + uint32_t elapsed_time) : + m_status(status), + m_task_error(task_error), + m_sys_errnum(sys_errnum), + m_elapsed_time(elapsed_time) {} + + uint32_t + status() const { + return m_status; + } + + void + set_retval(uint32_t status) { + m_status = status; + } + + uint32_t + task_error() const { + return m_task_error; + } + + void + set_task_error(uint32_t task_error) { + m_task_error = task_error; + } + + uint32_t + sys_errnum() const { + return m_sys_errnum; + } + + uint32_t + elapsed_time() const { + return m_elapsed_time; + } + + void + set_sys_errnum(uint32_t errnum) { + m_sys_errnum = errnum; + } + + explicit + output(const hermes::detail::pull_resource_out_t& out) { + m_status = out.status; + m_task_error = out.task_error; + m_sys_errnum = out.sys_errnum; + m_elapsed_time = out.elapsed_time; + } + + explicit + operator hermes::detail::pull_resource_out_t() { + return {m_status, m_task_error, m_sys_errnum, m_elapsed_time}; + } + + private: + uint32_t m_status; + uint32_t m_task_error; + uint32_t m_sys_errnum; + uint32_t m_elapsed_time; + }; +}; + +struct stat_resource { + // forward declarations of public input/output types for this RPC + class input; + class output; + + // traits used so that the engine knows what to do with the RPC + using self_type = stat_resource; + using handle_type = hermes::rpc_handle; + using input_type = input; + using output_type = output; + using mercury_input_type = hermes::detail::stat_resource_in_t; + using mercury_output_type = hermes::detail::stat_resource_out_t; + + // RPC public identifier + constexpr static const uint16_t public_id = 45; + + // RPC internal Mercury identifier + constexpr static const uint16_t mercury_id = public_id; + + // RPC name + constexpr static const auto name = "stat_resource"; + + // requires response? + constexpr static const auto requires_response = true; + + // Mercury callback to serialize input arguments + constexpr static const auto mercury_in_proc_cb = + HG_GEN_PROC_NAME(stat_resource_in_t); + + // Mercury callback to serialize output arguments + constexpr static const auto mercury_out_proc_cb = + HG_GEN_PROC_NAME(stat_resource_out_t); + + class input { + + template + friend hg_return_t hermes::detail::post_to_mercury(ExecutionContext*); + + public: + input(const std::string& address, + const std::string& nsid, + uint32_t resource_type, + const std::string& resource_name) : + m_address(address), + m_nsid(nsid), + m_resource_type(resource_type), + m_resource_name(resource_name) { + +#ifdef HERMES_DEBUG_BUILD + this->print("this", __PRETTY_FUNCTION__); +#endif + + } + +#ifdef HERMES_DEBUG_BUILD + input(input&& rhs) : + m_address(std::move(rhs.m_address)), + m_nsid(std::move(rhs.m_nsid)), + m_resource_type(std::move(rhs.m_resource_type)), + m_resource_name(std::move(rhs.m_resource_name)) { + + rhs.m_resource_type = 0; + + this->print("this", __PRETTY_FUNCTION__); + rhs.print("rhs", __PRETTY_FUNCTION__); + } + + input(const input& other) : + m_address(other.m_address), + m_nsid(other.m_nsid), + m_resource_type(other.m_resource_type), + m_resource_name(other.m_resource_name) { + + this->print("this", __PRETTY_FUNCTION__); + other.print("other", __PRETTY_FUNCTION__); + } + + input& + operator=(input&& rhs) { + + if(this != &rhs) { + m_address = std::move(rhs.m_address); + m_nsid = std::move(rhs.m_nsid); + m_resource_type = std::move(rhs.m_resource_type); + m_resource_name = std::move(rhs.m_resource_name); + + rhs.m_resource_type = 0; + } + + this->print("this", __PRETTY_FUNCTION__); + rhs.print("rhs", __PRETTY_FUNCTION__); + + return *this; + } + + input& + operator=(const input& other) { + + if(this != &other) { + m_address = other.m_address; + m_nsid = other.m_nsid; + m_resource_type = other.m_resource_type; + m_resource_name = other.m_resource_name; + } + + this->print("this", __PRETTY_FUNCTION__); + other.print("other", __PRETTY_FUNCTION__); + + return *this; + } +#else // HERMES_DEBUG_BUILD + input(input&& rhs) = default; + input(const input& other) = default; + input& operator=(input&& rhs) = default; + input& operator=(const input& other) = default; +#endif // ! HERMES_DEBUG_BUILD + + std::string + address() const { + return m_address; + } + + std::string + nsid() const { + return m_nsid; + } + + uint32_t + resource_type() const { + return m_resource_type; + } + + std::string + resource_name() const { + return m_resource_name; + } + +#ifdef HERMES_DEBUG_BUILD + void + print(const std::string& id, + const std::string& caller = "") const { + + (void) id; + auto c = caller.empty() ? "unknown_caller" : caller; + + HERMES_DEBUG2("{}, {} ({}) = {{", caller, id, fmt::ptr(this)); + HERMES_DEBUG2(" m_address: \"{}\" ({} -> {}),", + m_address, fmt::ptr(&m_address), + fmt::ptr(m_address.c_str())); + HERMES_DEBUG2(" m_nsid: \"{}\" ({} -> {}),", + m_nsid, fmt::ptr(&m_nsid), + fmt::ptr(m_nsid.c_str())); + HERMES_DEBUG2(" m_resource_type: {},", + m_resource_type); + HERMES_DEBUG2(" m_resource_name: \"{}\" ({} -> {}),", + m_resource_name, fmt::ptr(&m_resource_name), + fmt::ptr(m_resource_name.c_str())); + HERMES_DEBUG2("}}"); + } +#endif // ! HERMES_DEBUG_BUILD + +//TODO: make private + explicit + input(const hermes::detail::stat_resource_in_t& other) : + m_address(other.address), + m_nsid(other.nsid), + m_resource_type(other.resource_type), + m_resource_name(other.resource_name) { + + HERMES_DEBUG("input::input(const hermes::detail::stat_resource_in_t&){{"); + HERMES_DEBUG(" m_address: {} ({}),", + m_address, fmt::ptr(&m_address)); + HERMES_DEBUG(" m_nsid: {} ({}),", + m_nsid, fmt::ptr(&m_nsid)); + HERMES_DEBUG(" m_resource_type: {},", + m_resource_type); + HERMES_DEBUG(" m_resource_name: \"{}\" ({} -> {}),", + m_resource_name, fmt::ptr(&m_resource_name), + fmt::ptr(m_resource_name.c_str())); + HERMES_DEBUG("}}"); + } + + explicit + operator hermes::detail::stat_resource_in_t() { + return {m_address.c_str(), + m_nsid.c_str(), + m_resource_type, + m_resource_name.c_str()}; + } + + + private: + std::string m_address; + std::string m_nsid; + uint32_t m_resource_type; + std::string m_resource_name; + }; + + class output { + + template + friend hg_return_t hermes::detail::post_to_mercury(ExecutionContext*); + + public: + output(uint32_t task_error, + uint32_t sys_errnum, + bool is_collection, + uint64_t packed_size) : + m_task_error(task_error), + m_sys_errnum(sys_errnum), + m_is_collection(is_collection), + m_packed_size(packed_size) {} + + uint32_t + task_error() const { + return m_task_error; + } + + uint32_t + sys_errnum() const { + return m_sys_errnum; + } + + bool + is_collection() const { + return m_is_collection; + } + + uint64_t + packed_size() const { + return m_packed_size; + } + + explicit + output(const hermes::detail::stat_resource_out_t& out) { + m_task_error = out.task_error; + m_sys_errnum = out.sys_errnum; + m_is_collection = out.is_collection; + m_packed_size = out.packed_size; + } + + explicit + operator hermes::detail::stat_resource_out_t() { + return {m_task_error, m_sys_errnum, + m_is_collection, m_packed_size}; + } + + private: + uint32_t m_task_error; + uint32_t m_sys_errnum; + bool m_is_collection; + uint64_t m_packed_size; + }; +}; + +} // namespace rpc +} // namespace norns + +#undef HG_GEN_PROC_NAME + +#endif // __NORNS_IO_TRANSFERORS_RPCS_HPP__ diff --git a/src/urd.cpp b/src/urd.cpp index e608360943a89bedd9261a8e012b601f03a3765c..f93853d8cb4ab51e18cff7028b9091b0cefe8fb6 100644 --- a/src/urd.cpp +++ b/src/urd.cpp @@ -42,6 +42,8 @@ #include #include +#include +#include #include #include @@ -54,6 +56,9 @@ #include "io.hpp" #include "namespaces.hpp" #include "fmt.hpp" +#include "hermes.hpp" +#include "rpcs.hpp" +#include "context.hpp" #include "urd.hpp" namespace norns { @@ -186,15 +191,14 @@ urd_error urd::validate_iotask_args(iotask_type type, return urd_error::bad_args; } - // src_resource cannot be remote - if(src_rinfo->type() == data::resource_type::remote_posix_path) { + if(src_rinfo->is_remote() && dst_rinfo->is_remote()) { return urd_error::not_supported; } - // dst_resource cannot be a memory region - if(dst_rinfo->type() == data::resource_type::memory_region) { - return urd_error::not_supported; - } +// // dst_resource cannot be a memory region +// if(dst_rinfo->type() == data::resource_type::memory_region) { +// return urd_error::not_supported; +// } return urd_error::success; } @@ -204,10 +208,13 @@ urd_error urd::validate_iotask_args(iotask_type type, // handlers for user requests /////////////////////////////////////////////////////////////////////////////// -response_ptr urd::iotask_create_handler(const request_ptr base_request) { +response_ptr +urd::iotask_create_handler(const request_ptr base_request) { // downcast the generic request to the concrete implementation - auto request = utils::static_unique_ptr_cast(std::move(base_request)); + auto request = + utils::static_unique_ptr_cast( + std::move(base_request)); const auto type = request->get<0>(); const auto src_rinfo = request->get<1>(); @@ -219,6 +226,7 @@ response_ptr urd::iotask_create_handler(const request_ptr base_request) { std::vector> rinfo_ptrs; boost::optional tid; boost::optional auth; + boost::optional t; response_ptr resp; urd_error rv = urd_error::success; @@ -235,6 +243,11 @@ response_ptr urd::iotask_create_handler(const request_ptr base_request) { goto log_and_return; } +// if(src_rinfo->is_remote()) { +// rv = urd_error::not_supported; +// goto log_and_return; +// } + for(const auto& rinfo : {src_rinfo, dst_rinfo}) { if(rinfo) { nsids.push_back(rinfo->nsid()); @@ -277,7 +290,35 @@ response_ptr urd::iotask_create_handler(const request_ptr base_request) { } #endif - std::tie(rv, tid) = m_task_mgr->create_task(type, *auth, backend_ptrs, rinfo_ptrs); + + //FIXME: use appropriate args for each task rather than a vector of nullptrs + switch(type) { + case iotask_type::move: + case iotask_type::copy: + std::tie(rv, t) = + m_task_mgr->create_local_initiated_task(type, *auth, backend_ptrs, rinfo_ptrs); + break; + case iotask_type::remove: + std::tie(rv, t) = + m_task_mgr->create_local_initiated_task(type, *auth, backend_ptrs, rinfo_ptrs); + break; + case iotask_type::noop: + std::tie(rv, t) = + m_task_mgr->create_local_initiated_task(type, *auth, backend_ptrs, rinfo_ptrs); + break; + default: + rv = urd_error::bad_args; + goto log_and_return; + } + + if(rv == urd_error::success) { + tid = t->id(); + + // enqueue task so that it's eventually run by a worker thread + m_task_mgr->enqueue_task(std::move(*t)); + } + +// std::tie(rv, tid) = m_task_mgr->create_task(type, *auth, backend_ptrs, rinfo_ptrs); log_and_return: resp = std::make_unique(tid.get_value_or(0)); @@ -417,7 +458,7 @@ response_ptr urd::process_add_handler(const request_ptr base_request) { auto request = utils::static_unique_ptr_cast(std::move(base_request)); uint32_t jobid = request->get<0>(); - pid_t uid = request->get<1>(); + //pid_t uid = request->get<1>(); gid_t gid = request->get<2>(); pid_t pid = request->get<3>(); @@ -446,7 +487,7 @@ response_ptr urd::process_remove_handler(const request_ptr base_request) { auto request = utils::static_unique_ptr_cast(std::move(base_request)); uint32_t jobid = request->get<0>(); - pid_t uid = request->get<1>(); + //pid_t uid = request->get<1>(); gid_t gid = request->get<2>(); pid_t pid = request->get<3>(); @@ -495,7 +536,7 @@ urd_error urd::create_namespace(const std::string& nsid, backend_type type, } if(auto bptr = storage::backend_factory::create_from( - type, track, mount, quota)) { + type, nsid, track, mount, quota)) { m_namespace_mgr->add(nsid, bptr); return urd_error::success; } @@ -625,6 +666,315 @@ response_ptr urd::unknown_request_handler(const request_ptr /*base_request*/) { return resp; } +// N.B. This function is called by the progress thread internal to +// m_network_service rather than by the main execution thread +void +urd::push_resource_handler(hermes::request&& req) { + + const auto args = req.args(); + + LOGGER_WARN("incoming rpc::push_resource(from: \"{}@{}:{}\", to: \"{}:{}\")", + args.in_nsid(), + args.in_address(), + args.in_resource_name(), + args.out_nsid(), + args.out_resource_name()); + + urd_error rv = urd_error::success; + boost::optional t; + std::shared_ptr src_backend; + boost::optional> dst_backend; + auto dst_rtype = static_cast(args.out_resource_type()); + auth::credentials auth; //XXX fake credentials for now + + const auto create_rinfo = + [&](const data::resource_type& rtype) -> + std::shared_ptr { + + switch(rtype) { + case data::resource_type::remote_resource: + return std::make_shared( + args.in_address(), args.in_nsid(), + args.in_is_collection(), args.in_resource_name(), + args.in_buffers()); + case data::resource_type::local_posix_path: + case data::resource_type::shared_posix_path: + return std::make_shared( + args.out_nsid(), args.out_resource_name()); + default: + rv = urd_error::not_supported; + return {}; + } + }; + + if(m_is_paused) { + rv = urd_error::accept_paused; + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(io::task_status::finished_with_error), + static_cast(rv), + 0, + 0); + return; + } + + auto src_rinfo = create_rinfo(data::resource_type::remote_resource); + auto dst_rinfo = create_rinfo(dst_rtype); + + //TODO: check src_rinfo and dst_rinfo + + + // TODO: actually retrieve and validate credentials, etc + + + src_backend = + std::make_shared(args.in_nsid()); + + { + boost::shared_lock lock(m_namespace_mgr_mutex); + dst_backend = m_namespace_mgr->find(args.out_nsid()); + } + + if(!dst_backend) { + rv = urd_error::no_such_namespace; + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(io::task_status::finished_with_error), + static_cast(rv), + 0, + 0); + return; + } + + LOGGER_DEBUG("nsid: {}, bptr: {}", args.in_nsid(), dst_backend); + + auto ctx = + std::make_shared>(std::move(req)); + + std::tie(rv, t) = + m_task_mgr->create_remote_initiated_task( + iotask_type::remote_transfer, auth, + ctx, src_backend, src_rinfo, + *dst_backend, dst_rinfo); + + if(rv == urd_error::success) { + // run the task and check that it started correctly + (*t)(); + + auto req = std::move(*ctx); + + if(t->info()->status() == io::task_status::finished_with_error) { + rv = t->info()->task_error(); + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(io::task_status::finished_with_error), + static_cast(t->info()->task_error()), + static_cast(t->info()->sys_error().value()), + 0); + } + } +} + +void +urd::pull_resource_handler(hermes::request&& req) { + + const auto args = req.args(); + + LOGGER_WARN("incoming rpc::push_request(from: \"{}:{}\", to: \"{}@{}:{}\")", + args.in_nsid(), + args.in_resource_name(), + args.out_address(), + args.out_nsid(), + args.out_resource_name()); + + urd_error rv = urd_error::success; + boost::optional tsk; + boost::optional> src_backend; + std::shared_ptr dst_backend; + + /// XXX this information should be retrievable from the backend + auto src_rtype = static_cast(args.in_resource_type()); + + auth::credentials auth; //XXX fake credentials for now + + const auto create_rinfo = + [&](const data::resource_type& rtype) -> + std::shared_ptr { + + switch(rtype) { + case data::resource_type::remote_resource: + return std::make_shared( + args.out_address(), args.out_nsid(), false, + args.out_resource_name(), args.out_buffers()); + case data::resource_type::local_posix_path: + case data::resource_type::shared_posix_path: + return std::make_shared( + args.in_nsid(), args.in_resource_name()); + default: + rv = urd_error::not_supported; + return {}; + } + }; + + if(m_is_paused) { + rv = urd_error::accept_paused; + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(io::task_status::finished_with_error), + static_cast(rv), + 0, + 0); + return; + } + + auto src_rinfo = create_rinfo(src_rtype); + auto dst_rinfo = create_rinfo(data::resource_type::remote_resource); + + //TODO: check src_rinfo and dst_rinfo + + + // TODO: actually retrieve and validate credentials, etc + + { + boost::shared_lock lock(m_namespace_mgr_mutex); + src_backend = m_namespace_mgr->find(args.in_nsid()); + } + + if(!src_backend) { + rv = urd_error::no_such_namespace; + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(io::task_status::finished_with_error), + static_cast(rv), + 0, + 0); + return; + } + + LOGGER_DEBUG("nsid: {}, bptr: {}", args.in_nsid(), src_backend); + + dst_backend = + std::make_shared(args.out_nsid()); + + auto ctx = + std::make_shared>(std::move(req)); + + std::tie(rv, tsk) = + m_task_mgr->create_remote_initiated_task( + iotask_type::remote_transfer, auth, + ctx, *src_backend, src_rinfo, + dst_backend, dst_rinfo); + + if(rv == urd_error::success) { + // run the task and check that it started correctly + (*tsk)(); + + auto req = std::move(*ctx); + + if(tsk->info()->status() == io::task_status::finished_with_error) { + rv = tsk->info()->task_error(); + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(io::task_status::finished_with_error), + static_cast(tsk->info()->task_error()), + static_cast(tsk->info()->sys_error().value()), + 0); + } + } +} + +void +urd::stat_resource_handler(hermes::request&& req) { + + const auto args = req.args(); + + LOGGER_WARN("incoming rpc::stat_resource(\"{}:{}\")", + args.nsid(), + args.resource_name()); + + urd_error rv = urd_error::success; + boost::optional> dst_backend; + + { + boost::shared_lock lock(m_namespace_mgr_mutex); + dst_backend = m_namespace_mgr->find(args.nsid()); + } + + if(!dst_backend) { + rv = urd_error::no_such_namespace; + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(rv), + false, + //XXX ENOENT should not be required: + // the transfer() interface should be + // richer rather than returning only a + // std::error_code + ENOENT, + 0); + return; + } + + auto rtype = static_cast(args.resource_type()); + + const auto create_rinfo = + [&](const data::resource_type& rtype) -> + std::shared_ptr { + + switch(rtype) { + case data::resource_type::local_posix_path: + case data::resource_type::shared_posix_path: + return std::make_shared( + args.nsid(), args.resource_name()); + default: + return {}; + } + }; + + auto rinfo = create_rinfo(rtype); + + if(!rinfo) { + LOGGER_ERROR("Failed to access resource {}", rinfo->to_string()); + rv = urd_error::not_supported; + + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(rv), + //XXX EOPNOTSUPP should not be required: + // the transfer() interface should be + // richer rather than returning only a + // std::error_code + EOPNOTSUPP, + false, + 0); + return; + } + + std::error_code ec; + auto rsrc = (*dst_backend)->get_resource(rinfo, ec); + + if(ec) { + LOGGER_ERROR("Failed to access resource {}", rinfo->to_string()); + rv = urd_error::no_such_resource; + + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(rv), + ec.value(), + false, + 0); + return; + } + + LOGGER_INFO("IOTASK_RECEIVE() = {}", utils::to_string(rv)); + m_network_service->respond(std::move(req), + static_cast(urd_error::success), + 0, + rsrc->is_collection(), + rsrc->packed_size()); +} + + void urd::configure(const config::settings& settings) { m_settings = std::make_shared(settings); } @@ -687,7 +1037,7 @@ void urd::init_event_handlers() { // create (but not start) the API listener // and register handlers for each request type try { - m_api_listener = std::make_unique(); + m_ipc_service = std::make_unique(); } catch(const std::exception& e) { LOGGER_ERROR("Failed to create the event listener. This should " @@ -716,7 +1066,7 @@ void urd::init_event_handlers() { ::umask(S_IXUSR | S_IRWXG | S_IRWXO); // u=rw-, g=---, o=--- try { - m_api_listener->register_endpoint(m_settings->control_socket()); + m_ipc_service->register_endpoint(m_settings->control_socket()); } catch(const std::exception& e) { LOGGER_ERROR("Failed to create control API socket: {}", e.what()); @@ -739,7 +1089,7 @@ void urd::init_event_handlers() { ::umask(S_IXUSR | S_IXGRP | S_IXOTH); // u=rw-, g=rw-, o=rw- try { - m_api_listener->register_endpoint(m_settings->global_socket()); + m_ipc_service->register_endpoint(m_settings->global_socket()); } catch(const std::exception& e) { LOGGER_ERROR("Failed to create user API socket: {}", e.what()); @@ -747,9 +1097,31 @@ void urd::init_event_handlers() { exit(EXIT_FAILURE); } + // restore the umask + ::umask(old_mask); + + try { + + const std::string bind_address = + m_settings->bind_address() + ":" + + std::to_string(m_settings->remote_port()); + + m_network_service = + std::make_shared( + hermes::transport::ofi_tcp, + bind_address, + true); + } + catch(const std::exception& e) { + LOGGER_ERROR("Failed to create remote listener: {}", e.what()); + teardown(); + exit(EXIT_FAILURE); + } + +#if 0 // setup socket for remote connections try { - m_api_listener->register_endpoint(m_settings->remote_port()); + m_ipc_service->register_endpoint(m_settings->remote_port()); } catch(const std::exception& e) { LOGGER_ERROR("Failed to create socket for remote connections: {}", @@ -757,74 +1129,89 @@ void urd::init_event_handlers() { teardown(); exit(EXIT_FAILURE); } +#endif - // restore the umask - ::umask(old_mask); LOGGER_INFO(" * Installing message handlers..."); /* user-level functionalities */ - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::iotask_create, std::bind(&urd::iotask_create_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::iotask_status, std::bind(&urd::iotask_status_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::ping, std::bind(&urd::ping_handler, this, std::placeholders::_1)); /* admin-level functionalities */ - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::job_register, std::bind(&urd::job_register_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::job_update, std::bind(&urd::job_update_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::job_unregister, std::bind(&urd::job_remove_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::process_register, std::bind(&urd::process_add_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::process_unregister, std::bind(&urd::process_remove_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( - api::request_type::backend_register, - std::bind(&urd::namespace_register_handler, this, std::placeholders::_1)); + m_ipc_service->register_callback( + api::request_type::backend_register, + std::bind(&urd::namespace_register_handler, this, + std::placeholders::_1)); -/* m_api_listener->register_callback( - api::request_type::backend_update, - std::bind(&urd::namespace_update_handler, this, std::placeholders::_1));*/ + /* m_ipc_service->register_callback( + api::request_type::backend_update, + std::bind(&urd::namespace_update_handler, this, + std::placeholders::_1));*/ - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::backend_unregister, std::bind(&urd::namespace_remove_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::global_status, std::bind(&urd::global_status_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::command, std::bind(&urd::command_handler, this, std::placeholders::_1)); - m_api_listener->register_callback( + m_ipc_service->register_callback( api::request_type::bad_request, std::bind(&urd::unknown_request_handler, this, std::placeholders::_1)); + /* remote event handlers */ + m_network_service->register_handler( + std::bind(&urd::push_resource_handler, this, + std::placeholders::_1)); + + m_network_service->register_handler( + std::bind(&urd::pull_resource_handler, this, + std::placeholders::_1)); + + m_network_service->register_handler( + std::bind(&urd::stat_resource_handler, this, + std::placeholders::_1)); + + // signal handlers must be installed AFTER daemonizing LOGGER_INFO(" * Installing signal handlers..."); - m_api_listener->set_signal_handler( + m_ipc_service->set_signal_handler( std::bind(&urd::signal_handler, this, std::placeholders::_1), SIGHUP, SIGTERM, SIGINT); } @@ -874,9 +1261,10 @@ void urd::load_backend_plugins() { storage::backend_factory::get(). register_backend( backend_type::posix_filesystem, - [](bool track, const bfs::path& mount, uint32_t quota) { + [](const std::string& nsid, bool track, + const bfs::path& mount, uint32_t quota) { return std::shared_ptr( - new storage::posix_filesystem(track, mount, quota)); + new storage::posix_filesystem(nsid, track, mount, quota)); }); storage::backend_factory::get(). @@ -888,9 +1276,10 @@ void urd::load_backend_plugins() { storage::backend_factory::get(). register_backend( backend_type::nvml, - [](bool track, const bfs::path& mount, uint32_t quota) { + [](const std::string& nsid, bool track, + const bfs::path& mount, uint32_t quota) { return std::shared_ptr( - new storage::nvml_dax(track, mount, quota)); + new storage::nvml_dax(nsid, track, mount, quota)); }); storage::backend_factory::get(). @@ -900,9 +1289,10 @@ void urd::load_backend_plugins() { storage::backend_factory::get(). register_backend( backend_type::lustre, - [](bool track, const bfs::path& mount, uint32_t quota) { + [](const std::string& nsid, bool track, + const bfs::path& mount, uint32_t quota) { return std::shared_ptr( - new storage::lustre(track, mount, quota)); + new storage::lustre(nsid, track, mount, quota)); }); storage::backend_factory::get(). register_alias("Lustre", backend_type::lustre); @@ -925,6 +1315,7 @@ void urd::load_transfer_plugins() { const data::resource_type t2, std::shared_ptr&& trp) { +#if 0 // if(!m_transferor_registry->add(t1, t2, // std::forward>(trp))) { @@ -935,49 +1326,65 @@ void urd::load_transfer_plugins() { // return; // } +#else if(!m_task_mgr->register_transfer_plugin(t1, t2, std::forward>(trp))) { LOGGER_WARN(" Failed to load transfer plugin ({} to {}):\n" - " Another plugin was already registered for this " + " Another plugin was already registered for this\n" " combination (Plugin ignored)", utils::to_string(t1), utils::to_string(t2)); return; } - +#endif LOGGER_INFO(" Loaded transfer plugin ({} => {})", utils::to_string(t1), utils::to_string(t2)); }; + context ctx(m_settings->staging_directory(), + m_network_service); + // memory region -> local path - load_plugin(data::resource_type::memory_region, - data::resource_type::local_posix_path, - std::make_shared()); + load_plugin( + data::resource_type::memory_region, + data::resource_type::local_posix_path, + std::make_shared(ctx)); // memory region -> shared path - load_plugin(data::resource_type::memory_region, - data::resource_type::shared_posix_path, - std::make_shared()); + load_plugin( + data::resource_type::memory_region, + data::resource_type::shared_posix_path, + std::make_shared(ctx)); // memory region -> remote path - load_plugin(data::resource_type::memory_region, - data::resource_type::remote_posix_path, - std::make_shared()); + load_plugin( + data::resource_type::memory_region, + data::resource_type::remote_resource, + std::make_shared(ctx)); // local path -> local path - load_plugin(data::resource_type::local_posix_path, - data::resource_type::local_posix_path, - std::make_shared()); + load_plugin( + data::resource_type::local_posix_path, + data::resource_type::local_posix_path, + std::make_shared(ctx)); // local path -> shared path - load_plugin(data::resource_type::local_posix_path, - data::resource_type::shared_posix_path, - std::make_shared()); - - // local path -> remote path - load_plugin(data::resource_type::local_posix_path, - data::resource_type::remote_posix_path, - std::make_shared()); + load_plugin( + data::resource_type::local_posix_path, + data::resource_type::shared_posix_path, + std::make_shared(ctx)); + + // local path -> remote resource + load_plugin( + data::resource_type::local_posix_path, + data::resource_type::remote_resource, + std::make_shared(ctx)); + + // remote resource -> local path + load_plugin( + data::resource_type::remote_resource, + data::resource_type::local_posix_path, + std::make_shared(ctx)); } void urd::load_default_namespaces() { @@ -997,6 +1404,28 @@ void urd::load_default_namespaces() { } } +void +urd::check_configuration() { + + // check that the staging directory exists and that we can write to it + if(!bfs::exists(m_settings->staging_directory())) { + LOGGER_ERROR("Staging directory {} does not exist", + m_settings->staging_directory()); + teardown_and_exit(); + } + + auto s = bfs::status(m_settings->staging_directory()); + + auto expected_perms = bfs::perms::owner_read | + bfs::perms::owner_write; + + if((s.permissions() & expected_perms) != expected_perms) { + LOGGER_ERROR("Unable to read from/write to staging directory {}", + m_settings->staging_directory()); + teardown_and_exit(); + } +} + void urd::print_greeting() { const char greeting[] = "Starting {} daemon (pid {})"; const auto gsep = std::string(sizeof(greeting) - 4 + @@ -1026,6 +1455,7 @@ void urd::print_configuration() { LOGGER_INFO(" - pidfile: {}", m_settings->pidfile()); LOGGER_INFO(" - control socket: {}", m_settings->control_socket()); LOGGER_INFO(" - global socket: {}", m_settings->global_socket()); + LOGGER_INFO(" - staging directory: {}", m_settings->staging_directory()); LOGGER_INFO(" - port for remote requests: {}", m_settings->remote_port()); LOGGER_INFO(" - workers: {}", m_settings->workers_in_pool()); LOGGER_INFO(""); @@ -1095,6 +1525,9 @@ int urd::run() { // initialize logging facilities init_logger(); + // validate settings + check_configuration(); + // daemonize if needed if(m_settings->daemonize() && daemonize() != 0) { /* parent clean ups and exits, child continues */ @@ -1112,13 +1545,22 @@ int urd::run() { init_event_handlers(); init_namespace_manager(); load_backend_plugins(); - load_transfer_plugins(); load_default_namespaces(); + // load plugins now so that when we propagate the daemon context to them + // everything is set up + load_transfer_plugins(); + + // start the listener for remote transfers + // N.B. This call returns immediately + m_network_service->run(); + LOGGER_INFO(""); LOGGER_INFO("[[ Start up successful, awaiting requests... ]]"); - m_api_listener->run(); + // N.B. This call blocks here, which means that everything after it + // will only run when a shutdown command is received + m_ipc_service->run(); print_farewell(); teardown(); @@ -1138,10 +1580,10 @@ void urd::teardown() { // //m_signal_listener.reset(); // } - if(m_api_listener) { + if(m_ipc_service) { LOGGER_INFO("* Stopping API listener..."); - m_api_listener->stop(); - m_api_listener.reset(); + m_ipc_service->stop(); + m_ipc_service.reset(); } api_listener::cleanup(); @@ -1166,9 +1608,15 @@ void urd::teardown() { } } +void +urd::teardown_and_exit() { + teardown(); + exit(EXIT_FAILURE); +} + void urd::shutdown() { - if(m_api_listener) { - m_api_listener->stop(); + if(m_ipc_service) { + m_ipc_service->stop(); } } diff --git a/src/urd.hpp b/src/urd.hpp index 8943dad06df4be5db6c2c6933919021c76977535..bb2973ec7ee161ec094cfd6c5dd0d4fb4e568f2f 100644 --- a/src/urd.hpp +++ b/src/urd.hpp @@ -39,15 +39,16 @@ #include "job.hpp" - - +namespace hermes { + class async_engine; + template class request; +} namespace norns { /*! Aliases for convenience */ using api_listener = api::listener>; -using api_listener_ptr = std::unique_ptr; using request_ptr = std::unique_ptr; using response_ptr = std::unique_ptr; @@ -70,6 +71,12 @@ namespace ns { struct namespace_manager; } +namespace rpc { + struct push_resource; + struct pull_resource; + struct stat_resource; +} + enum class urd_error; class urd { @@ -82,6 +89,7 @@ public: int run(); void shutdown(); void teardown(); + void teardown_and_exit(); private: int daemonize(); @@ -94,6 +102,7 @@ private: void load_backend_plugins(); void load_transfer_plugins(); void load_default_namespaces(); + void check_configuration(); void print_greeting(); void print_configuration(); void print_farewell(); @@ -117,6 +126,10 @@ private: response_ptr command_handler(const request_ptr req); response_ptr unknown_request_handler(const request_ptr req); + void push_resource_handler(hermes::request&& req); + void pull_resource_handler(hermes::request&& req); + void stat_resource_handler(hermes::request&& req); + // TODO: add helpers for remove and update urd_error create_namespace(const config::namespace_def& nsdef); urd_error create_namespace(const std::string& nsid, backend_type type, @@ -134,7 +147,9 @@ private: std::shared_ptr m_settings; std::unique_ptr m_transferor_registry; - api_listener_ptr m_api_listener; + std::unique_ptr m_ipc_service; + + std::shared_ptr m_network_service; std::unique_ptr m_namespace_mgr; mutable boost::shared_mutex m_namespace_mgr_mutex; diff --git a/src/utils.cpp b/src/utils.cpp index b8f5bb7b7ef9f3fe94f759a83b63a8b10bb8e64a..a42f9f86bef308c8c9c507508e612e940bfe27bb 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -152,6 +152,26 @@ boost::filesystem::path lexical_normalize(const boost::filesystem::path& pathnam } +// remove a trailing separator +// (adapted from boost::filesystem::path::remove_trailing_separator()) +boost::filesystem::path +remove_trailing_separator(const boost::filesystem::path& pathname) { + + using boost::filesystem::path; + + std::string str{pathname.generic_string()}; + + auto is_directory_separator = [](path::value_type c) { + return c == '/'; + }; + + if(!str.empty() && is_directory_separator(str[str.size() - 1])) { + str.erase(str.size() - 1); + } + + return path{str}; +} + } // namespace utils } // namespace norns diff --git a/src/utils.hpp b/src/utils.hpp index 94f6c730121427018ca0735bd51c6a9f9a3ae18c..b1327b560689f11474cd0d8cb7f2d55fa8de8a9a 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -25,6 +25,9 @@ * . * *************************************************************************/ +#ifndef NORNS_UTILS_HPP +#define NORNS_UTILS_HPP + #include #include #include @@ -33,6 +36,8 @@ #include #include "common.hpp" +#include "utils/tar-archive.hpp" +#include "utils/temporary-file.hpp" namespace norns { namespace utils { @@ -54,6 +59,9 @@ std::string n2hexstr(T i, bool zero_pad=false) { boost::filesystem::path lexical_normalize(const boost::filesystem::path& pathname, bool as_directory=false); +boost::filesystem::path +remove_trailing_separator(const boost::filesystem::path& pathname); + } // namespace utils } // namespace norns @@ -68,3 +76,5 @@ path relative(path from_path, path to_path); }} // namespace boost::filesystem #endif + +#endif // NORNS_UTILS_HPP diff --git a/src/io/transferors/local-path-to-remote-path.cpp b/src/utils/file-handle.hpp similarity index 61% rename from src/io/transferors/local-path-to-remote-path.cpp rename to src/utils/file-handle.hpp index 1f79d4c788b8e8637d43d0993f4f26df22e2f03a..3b885d9ea1e1c369529a55b71fc4f49d6d21ac24 100644 --- a/src/io/transferors/local-path-to-remote-path.cpp +++ b/src/utils/file-handle.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * * Centro Nacional de Supercomputacion * * All rights reserved. * * * @@ -25,56 +25,63 @@ * . * *************************************************************************/ -#include -#include -#include -#include - -#include "utils.hpp" #include "logger.hpp" -#include "resources.hpp" -#include "auth.hpp" -#include "io/task-info.hpp" -#include "backends/posix-fs.hpp" -#include "local-path-to-remote-path.hpp" namespace norns { -namespace io { +namespace utils { + +struct file_handle { + + constexpr static const int init_value{-1}; + + file_handle() = default; + + explicit file_handle(int fd) noexcept : + m_fd(fd) { } + + file_handle(file_handle&& rhs) = default; + file_handle(const file_handle& other) = delete; + file_handle& operator=(file_handle&& rhs) = default; + file_handle& operator=(const file_handle& other) = delete; -bool -local_path_to_remote_path_transferor::validate( - const std::shared_ptr& src_info, - const std::shared_ptr& dst_info) const { + explicit operator bool() const noexcept { + return valid(); + } - (void) src_info; - (void) dst_info; + bool operator!() const noexcept { + return !valid(); + } - LOGGER_WARN("Validation not implemented"); + bool + valid() const noexcept { + return m_fd != init_value; + } - return true; -} + int + native() const noexcept { + return m_fd; + } -std::error_code -local_path_to_remote_path_transferor::transfer( - const auth::credentials& auth, - const std::shared_ptr& task_info, - const std::shared_ptr& src, - const std::shared_ptr& dst) const { + int + release() noexcept { + int ret = m_fd; + m_fd = init_value; + return ret; + } - (void) auth; - (void) task_info; - (void) src; - (void) dst; + ~file_handle() { + if(m_fd != init_value) { + if(::close(m_fd) == -1) { + LOGGER_ERROR("Failed to close file descriptor: {}", + logger::errno_message(errno)); + } + } + } - LOGGER_WARN("Transfer not implemented"); + int m_fd; +}; - return std::make_error_code(static_cast(0)); -} -std::string -local_path_to_remote_path_transferor::to_string() const { - return "transferor[local_path => remote_path]"; -} -} // namespace io +} // namespace utils } // namespace norns diff --git a/src/utils/tar-archive.cpp b/src/utils/tar-archive.cpp new file mode 100644 index 0000000000000000000000000000000000000000..73282c87d405bf03842fea4c1bd583c30b274bf5 --- /dev/null +++ b/src/utils/tar-archive.cpp @@ -0,0 +1,214 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include "utils.hpp" +#include "logger.hpp" +#include "tar-archive.hpp" + +namespace { + +constexpr std::size_t +align(std::size_t n, std::size_t block_size) { + return n & ~(block_size - 1); +} + +constexpr std::size_t +xalign(std::size_t n, std::size_t block_size) { + return (n - align(n, block_size)) != 0 ? + align(n, block_size) + block_size : + n; +} + +} // anonymous namespace + +namespace norns { +namespace utils { + +tar::tar(const bfs::path& filename, + openmode op, + std::error_code& ec) : + m_path(filename), + m_openmode(op) { + + if(filename.empty()) { + return; + } + + constexpr const std::array flags = { + {O_WRONLY | O_CREAT| O_EXCL, O_RDONLY} + }; + + constexpr const std::array modes = { + {S_IRUSR | S_IWUSR, 0} + }; + + if(tar_open(&m_tar, m_path.c_str(), NULL, + flags[static_cast(op)], + modes[static_cast(op)], TAR_GNU) != 0) { + ec = std::make_error_code(static_cast(errno)); + LOGGER_ERROR("Failed to open archive for writing: {}", + logger::errno_message(ec.value())); + return; + } +} + +void +tar::add_file(const bfs::path& real_name, + const bfs::path& archive_name, + std::error_code& ec) { + + if(m_tar == nullptr) { + ec = std::make_error_code(static_cast(EINVAL)); + return; + } + + if(tar_append_file(m_tar, const_cast(real_name.c_str()), + const_cast(archive_name.c_str())) != 0) { + ec = std::make_error_code(static_cast(errno)); + return; + } + + ec = std::make_error_code(static_cast(0)); +} + +void +tar::add_directory(const bfs::path& real_dir, + const bfs::path& archive_dir, + std::error_code& ec) { + + if(m_tar == nullptr) { + ec = std::make_error_code(static_cast(EINVAL)); + return; + } + + const bfs::path rd = + norns::utils::remove_trailing_separator(real_dir); + const bfs::path ad = + norns::utils::remove_trailing_separator(archive_dir); + + if(tar_append_tree(m_tar, const_cast(rd.c_str()), + const_cast(ad.c_str())) != 0) { + ec = std::make_error_code(static_cast(errno)); + return; + } + + ec = std::make_error_code(static_cast(0)); +} + +void +tar::extract(const bfs::path& parent_dir, + std::error_code& ec) { + + if(m_tar == nullptr) { + ec = std::make_error_code(static_cast(EINVAL)); + return; + } + + if(tar_extract_all(m_tar, const_cast(parent_dir.c_str())) != 0) { + ec = std::make_error_code(static_cast(errno)); + } +} + + +bfs::path +tar::path() const { + return m_path; +} + +std::size_t +tar::estimate_size_once_packed(const bfs::path& path, + std::error_code& ec) { + + std::size_t sz = 0; + boost::system::error_code error; + + if(bfs::is_directory(path)) { + for(bfs::recursive_directory_iterator it(path, error); + it != bfs::recursive_directory_iterator(); + ++it) { + + if(error) { + LOGGER_ERROR("Failed to traverse path {}", path); + ec = std::make_error_code( + static_cast(error.value())); + return 0; + } + + if(bfs::is_directory(*it)) { + sz += T_BLOCKSIZE; + } + else if(bfs::is_regular(*it) || bfs::is_symlink(*it)) { + sz += T_BLOCKSIZE + + ::xalign(bfs::file_size(*it, error), T_BLOCKSIZE); + + if(error) { + LOGGER_ERROR("Failed to determine size for {}", *it); + ec = std::make_error_code( + static_cast(error.value())); + return 0; + } + } + else { + // not a critical error, report it and go on + LOGGER_ERROR("Unhandled file type at {}", *it); + } + } + + // we need to take into account the header for the basedir 'path' + // since recursive_directory_iterator skips it + sz += T_BLOCKSIZE; + } + else { + sz += T_BLOCKSIZE + ::xalign(bfs::file_size(path), T_BLOCKSIZE); + } + + // EOF + sz += 2*T_BLOCKSIZE; + + return sz; +} + +tar::~tar() { + + if(m_tar != nullptr) { + + if(m_openmode == tar::create) { + if(tar_append_eof(m_tar)) { + LOGGER_ERROR("Failed to append EOF to TAR archive: {}", + logger::errno_message(errno)); + } + } + + if(tar_close(m_tar) != 0) { + LOGGER_ERROR("Failed to close TAR archive: {}", + logger::errno_message(errno)); + } + } +} + +} // namespace utils +} // namespace norns diff --git a/src/utils/tar-archive.hpp b/src/utils/tar-archive.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4054ebefda0e3f25fa0a0385fde12aff783a01ac --- /dev/null +++ b/src/utils/tar-archive.hpp @@ -0,0 +1,83 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef NORNS_UTILS_TAR_ARCHIVE_HPP +#define NORNS_UTILS_TAR_ARCHIVE_HPP + +#include +#include +#include + +namespace bfs = boost::filesystem; + +namespace norns { +namespace utils { + +struct tar { + + enum class openmode : int { + create = 0, + open = 1, + }; + + constexpr static openmode create = openmode::create; + constexpr static openmode open = openmode::open; + + tar(const bfs::path& filename, openmode op, std::error_code& ec); + + void + add_file(const bfs::path& real_name, + const bfs::path& archive_name, + std::error_code& ec); + + void + add_directory(const bfs::path& real_dir, + const bfs::path& archive_dir, + std::error_code& ec); + + void + extract(const bfs::path& parent_dir, + std::error_code& ec); + + bfs::path + path() const; + + static std::size_t + estimate_size_once_packed(const bfs::path& path, + std::error_code& ec); + + ~tar(); + + TAR* m_tar = nullptr; + bfs::path m_path; + openmode m_openmode; +}; + +} // namespace utils +} // namespace norns + +#endif // NORNS_UTILS_TAR_ARCHIVE_HPP diff --git a/src/utils/temporary-file.cpp b/src/utils/temporary-file.cpp new file mode 100644 index 0000000000000000000000000000000000000000..64dc1b5cc94c7bb4e38db27e6c32e231f1f11a3d --- /dev/null +++ b/src/utils/temporary-file.cpp @@ -0,0 +1,198 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include "config.h" + +#include +#include + +#include "temporary-file.hpp" +#include "file-handle.hpp" +#include "logger.hpp" + +namespace { + +using norns::utils::file_handle; + +void +reallocate(const file_handle& fh, + std::size_t size, + std::error_code& ec) noexcept { + + if(!fh) { + ec.assign(EINVAL, std::generic_category()); + return; + } + +#ifdef HAVE_FALLOCATE + if(::fallocate(fh.native(), 0, 0, size) == -1) { + if(errno != EOPNOTSUPP) { + ec.assign(errno, std::generic_category()); + return; + } +#endif // HAVE_FALLOCATE + + // filesystem doesn't support fallocate(), + // fallback to truncate() + if(::ftruncate(fh.native(), size) != 0) { + ec.assign(errno, std::generic_category()); + return; + } + +#ifdef HAVE_FALLOCATE + } +#endif // HAVE_FALLOCATE +} + +} // anonymous namespace + +namespace norns { +namespace utils { + +temporary_file::temporary_file() noexcept { } + +temporary_file::temporary_file(const std::string& pattern, + const bfs::path& parent_dir, + std::error_code& ec) noexcept : + temporary_file(pattern, parent_dir, 0, ec) {} + +temporary_file::temporary_file(const std::string& pattern, + const bfs::path& parent_dir, + std::size_t reserve_size, + std::error_code& ec) noexcept { + + boost::system::error_code bec; + + const auto filename = parent_dir / bfs::unique_path(pattern, bec); + + if(bec) { + // LOGGER_ERROR("Failed to create unique path from pattern: {}", + // bec.message()); + ec.assign(bec.value(), std::generic_category()); + return; + } + + if(!bfs::exists(parent_dir, bec)) { + // LOGGER_ERROR("parent_dir does not exist: {}", bec.message()); + ec.assign(ENOENT, std::generic_category()); + return; + } + + if(bec) { + // LOGGER_ERROR("parent_dir does not exist: {}", bec.message()); + ec.assign(bec.value(), std::generic_category()); + return; + } + + file_handle fh( + ::open(filename.c_str(), + O_CREAT | O_WRONLY | O_EXCL, + S_IRUSR | S_IWUSR)); + + if(!fh) { + ec.assign(errno, std::generic_category()); + LOGGER_ERROR("Failed to create temporary file {}: {}", + filename, ec.message()); + return; + } + + if(reserve_size != 0) { + ::reallocate(fh, reserve_size, ec); + } + + m_filename = filename; +} + +temporary_file::temporary_file(const bfs::path& filename, + std::error_code& ec) noexcept { + this->manage(filename, ec); +} + +temporary_file::~temporary_file() { + + if(m_filename.empty()) { + return; + } + + LOGGER_DEBUG("{} Removing temporary file {}", + __PRETTY_FUNCTION__, m_filename); + + boost::system::error_code bec; + bfs::remove(m_filename, bec); + + if(bec) { + LOGGER_ERROR("Failed to remove temporary_file {}: {}", + m_filename, bec.message()); + } +} + +bfs::path +temporary_file::path() const noexcept { + return m_filename; +} + +void +temporary_file::reserve(std::size_t size, + std::error_code& ec) const noexcept { + + file_handle fh{::open(m_filename.c_str(), O_WRONLY)}; + + if(!fh) { + ec.assign(errno, std::generic_category()); + // LOGGER_ERROR("Failed to allocate space for file: {}", ec.message()); + return; + } + + ::reallocate(fh, size, ec); +} + +void +temporary_file::manage(const bfs::path& filename, + std::error_code& ec) noexcept { + + boost::system::error_code bec; + const auto existing_filename = bfs::canonical(filename, bec); + + if(bec) { + // LOGGER_ERROR("Failed to get canonical path from filename: {}", + // bec.message()); + ec.assign(bec.value(), std::generic_category()); + return; + } + + m_filename = existing_filename; +} + +bfs::path +temporary_file::release() noexcept { + const bfs::path ret{std::move(m_filename)}; + m_filename.clear(); + return ret; +} + +} // namespace utils +} // namespace norns diff --git a/src/utils/temporary-file.hpp b/src/utils/temporary-file.hpp new file mode 100644 index 0000000000000000000000000000000000000000..64d734dc2ac63d1959a6b1439929b2d5b95f1c44 --- /dev/null +++ b/src/utils/temporary-file.hpp @@ -0,0 +1,88 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#ifndef NORNS_UTILS_TEMP_FILE_HPP +#define NORNS_UTILS_TEMP_FILE_HPP + +#include +#include + +namespace bfs = boost::filesystem; + +namespace norns { +namespace utils { + +struct temporary_file { + + temporary_file() noexcept; + + // create an empty temporary file from pattern at parent_dir + temporary_file(const std::string& pattern, + const bfs::path& parent_dir, + std::error_code& ec) noexcept; + + // create a temporary file of size 'prealloc_size' from pattern at parent_dir + temporary_file(const std::string& pattern, + const bfs::path& parent_dir, + std::size_t prealloc_size, + std::error_code& ec) noexcept; + + // initialize temporary file from an already existing file + temporary_file(const bfs::path& filename, + std::error_code& ec) noexcept; + + temporary_file(const temporary_file& other) = delete; + + temporary_file(temporary_file&& rhs) = default; + + temporary_file& operator=(const temporary_file& other) = delete; + + temporary_file& operator=(temporary_file&& rhs) = default; + + ~temporary_file(); + + bfs::path + path() const noexcept; + + void + reserve(std::size_t size, + std::error_code& ec) const noexcept; + + void + manage(const bfs::path& filename, + std::error_code& ec) noexcept; + + bfs::path + release() noexcept; + + bfs::path m_filename; +}; + +} // namespace utils +} // namespace norns + +#endif // NORNS_UTILS_TEMP_FILE_HPP diff --git a/tests/Makefile.am b/tests/Makefile.am index 2ff26e665f873239c3c4d4277763cb40103d0360..8de929bd3902425cc42bff9b69aaa02e7cb37dd3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -41,6 +41,7 @@ api_CPPFLAGS = \ -I$(top_srcdir)/include \ -I$(top_srcdir)/rpc \ -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/externals/hermes/include \ -D__NORNS_DEBUG__ \ $(END) @@ -50,6 +51,7 @@ api_SOURCES = \ api-namespace-register.cpp \ api-namespace-unregister.cpp \ api-copy-local-data.cpp \ + api-copy-remote-data.cpp \ api-remove-local-data.cpp \ api-job-register.cpp \ api-job-update.cpp \ @@ -80,6 +82,7 @@ api_LDFLAGS = \ @BOOST_THREAD_LIB@ \ @BOOST_REGEX_LIB@ \ @PROTOBUF_LIBS@ \ + -no-install \ -Wl,-rpath,$(top_builddir)/lib/.libs \ $(top_builddir)/src/liburd_aux.la \ $(top_builddir)/lib/libnorns_debug.la \ @@ -132,17 +135,23 @@ core_CXXFLAGS = \ core_CPPFLAGS = \ -I$(top_srcdir)/include \ + -I$(top_srcdir)/src/externals/hermes/include \ -I$(top_srcdir)/rpc \ -I$(top_srcdir)/src \ + -DUSE_REAL_DAEMON \ $(END) core_SOURCES = \ catch.hpp \ api-main.cpp \ utils-path-normalize.cpp \ + utils-tar.cpp \ + test-env.cpp \ + test-env.hpp \ $(END) core_LDFLAGS = \ + -no-install \ @BOOST_ASIO_LIB@ \ @BOOST_LDFLAGS@ \ @BOOST_FILESYSTEM_LIB@ \ diff --git a/tests/api-copy-remote-data.cpp b/tests/api-copy-remote-data.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5447d1c5d62cee43c8b9c1950ba99924809d7bda --- /dev/null +++ b/tests/api-copy-remote-data.cpp @@ -0,0 +1,4240 @@ +/************************************************************************* + * Copyright (C) 2017-2018 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include "norns.h" +#include "nornsctl.h" +#include "test-env.hpp" +#include "compare-files.hpp" +#include "catch.hpp" + +namespace bfs = boost::filesystem; + +/******************************************************************************/ +/* tests for push transfers (errors) */ +/******************************************************************************/ +SCENARIO("errors copying local POSIX path to remote POSIX path", + "[api::norns_submit_push_errors]") { + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env(false); + + const char* nsid0 = "tmp0"; + const char* nsid1 = "tmp1"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path src_mnt, dst_mnt; + + // create namespaces + std::tie(std::ignore, src_mnt) = + env.create_namespace(nsid0, "mnt/tmp0", 16384); + std::tie(std::ignore, dst_mnt) = + env.create_namespace(nsid1, "mnt/tmp1", 16384); + + // define input names + const bfs::path src_file_at_root = "/file0"; + const bfs::path src_file_at_subdir = "/a/b/c/d/file0"; + const bfs::path src_invalid_file = "/a/b/c/d/does_not_exist_file0"; + const bfs::path src_invalid_dir = "/a/b/c/d/does_not_exist_dir0"; + const bfs::path src_subdir0 = "/input_dir0"; + const bfs::path src_subdir1 = "/input_dir0/a/b/c/input_dir1"; + const bfs::path src_empty_dir = "/empty_dir0"; + + const bfs::path src_noperms_file0 = "/noperms_file0"; + const bfs::path src_noperms_file1 = "/noperms/a/b/c/d/noperms_file0"; // parents accessible + const bfs::path src_noperms_file2 = "/noperms/noperms_subdir0/file0"; // parents non-accessible + const bfs::path src_noperms_subdir0 = "/noperms_subdir0"; // subdir non-accessible + const bfs::path src_noperms_subdir1 = "/noperms/a/b/c/d/noperms_subdir1"; // child subdir non-accessible + const bfs::path src_noperms_subdir2 = "/noperms/noperms_subdir2/a"; // parent subdir non-accessible + + const bfs::path src_symlink_at_root0 = "/symlink0"; + const bfs::path src_symlink_at_root1 = "/symlink1"; + const bfs::path src_symlink_at_root2 = "/symlink2"; + const bfs::path src_symlink_at_subdir0 = "/foo/bar/baz/symlink0"; + const bfs::path src_symlink_at_subdir1 = "/foo/bar/baz/symlink1"; + const bfs::path src_symlink_at_subdir2 = "/foo/bar/baz/symlink2"; + + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0"; + const bfs::path dst_subdir1 = "/output_dir1"; + const bfs::path dst_file_at_root0 = "/file0"; // same basename + const bfs::path dst_file_at_root1 = "/file1"; // different basename + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; // same fullname + const bfs::path dst_file_at_subdir1 = "/a/b/c/d/file1"; // same parents, different basename + const bfs::path dst_file_at_subdir2 = "/e/f/g/h/i/file0"; // different parents, same basename + const bfs::path dst_file_at_subdir3 = "/e/f/g/h/i/file1"; // different fullname + + // create input data + env.add_to_namespace(nsid0, src_file_at_root, 40000); + env.add_to_namespace(nsid0, src_file_at_subdir, 80000); + env.add_to_namespace(nsid0, src_subdir0); + env.add_to_namespace(nsid0, src_subdir1); + env.add_to_namespace(nsid0, src_empty_dir); + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir0 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir1 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + // create input data with special permissions + auto p = env.add_to_namespace(nsid0, src_noperms_file0, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file1, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file2, 0); + env.remove_access(p.parent_path()); + + p = env.add_to_namespace(nsid0, src_noperms_subdir0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir1); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir2); + env.remove_access(p.parent_path()); + + // add symlinks to the namespace + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_root0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_root1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_root2); + + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_subdir0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_subdir1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_subdir2); + + // manually create a symlink leading outside namespace 0 + boost::system::error_code ec; + const bfs::path out_symlink = "/out_symlink"; + bfs::create_symlink(dst_mnt, src_mnt / out_symlink, ec); + REQUIRE(!ec); + + // create required output directories + env.add_to_namespace(nsid1, dst_subdir1); + + /**********************************************************************/ + /* begin tests */ + /**********************************************************************/ + // - trying to copy a non-existing file + WHEN("copying a non-existing NORNS_LOCAL_PATH file") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_invalid_file.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and ENOENT are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == ENOENT); + } + } + } + } + + // - trying to copy a non-existing directory + WHEN("copying a non-existing NORNS_LOCAL_PATH directory") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_invalid_dir.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and ENOENT are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == ENOENT); + } + } + } + } + + // - trying to copy an empty directory + WHEN("copying an empty NORNS_LOCAL_PATH directory") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_empty_dir.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_SUCCESS and ENOENT are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + REQUIRE(stats.st_task_error == NORNS_SUCCESS); + REQUIRE(stats.st_sys_errno == 0); + + REQUIRE(bfs::exists(dst_mnt / dst_file_at_root0)); + } + } + } + } + +//FIXME: DISABLED in CI until impersonation is implemented or capabilities can be added to the docker service +#ifdef __SETCAP_TESTS__ + + // - trying to copy a file from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH file from \"/\" without appropriate " + "permissions to access it") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_noperms_file0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL " + "are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a file from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH file from a subdir without " + "appropriate permissions to access it") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_noperms_file1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a file from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH file from a subdir without " + "appropriate permissions to access a parent") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_noperms_file2.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL " + "are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a subdir from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH subdir from \"/\" without " + "appropriate permissions to access it") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_noperms_subdir0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL " + "are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a subdir from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH subdir from another subdir " + "without appropriate permissions to access it") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_noperms_subdir1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a subdir from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH subdir from another subdir without " + "appropriate permissions to access a parent") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_noperms_subdir2.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL " + "are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // symlink leading out of namespace + WHEN("copying a NORNS_LOCAL_PATH through a symbolic link that leads " + "out of the SRC namespace") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + out_symlink.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and ENOENT are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == ENOENT); + } + } + } + } +#endif + + env.notify_success(); + } +} + +/******************************************************************************/ +/* tests for push transfers (single files) */ +/******************************************************************************/ +SCENARIO("copy local POSIX file to remote POSIX file", + "[api::norns_submit_push_to_posix_file]") { + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env(false); + + const char* nsid0 = "tmp0"; + const char* nsid1 = "tmp1"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path src_mnt, dst_mnt; + + // create namespaces + std::tie(std::ignore, src_mnt) = + env.create_namespace(nsid0, "mnt/tmp0", 16384); + std::tie(std::ignore, dst_mnt) = + env.create_namespace(nsid1, "mnt/tmp1", 16384); + + // define input names + const bfs::path src_file_at_root = "/file0"; + const bfs::path src_file_at_subdir = "/a/b/c/d/file0"; + const bfs::path src_invalid_file = "/a/b/c/d/does_not_exist_file0"; + const bfs::path src_invalid_dir = "/a/b/c/d/does_not_exist_dir0"; + const bfs::path src_subdir0 = "/input_dir0"; + const bfs::path src_subdir1 = "/input_dir0/a/b/c/input_dir1"; + const bfs::path src_empty_dir = "/empty_dir0"; + + const bfs::path src_noperms_file0 = "/noperms_file0"; + const bfs::path src_noperms_file1 = "/noperms/a/b/c/d/noperms_file0"; // parents accessible + const bfs::path src_noperms_file2 = "/noperms/noperms_subdir0/file0"; // parents non-accessible + const bfs::path src_noperms_subdir0 = "/noperms_subdir0"; // subdir non-accessible + const bfs::path src_noperms_subdir1 = "/noperms/a/b/c/d/noperms_subdir1"; // child subdir non-accessible + const bfs::path src_noperms_subdir2 = "/noperms/noperms_subdir2/a"; // parent subdir non-accessible + + const bfs::path src_symlink_at_root0 = "/symlink0"; + const bfs::path src_symlink_at_root1 = "/symlink1"; + const bfs::path src_symlink_at_root2 = "/symlink2"; + const bfs::path src_symlink_at_subdir0 = "/foo/bar/baz/symlink0"; + const bfs::path src_symlink_at_subdir1 = "/foo/bar/baz/symlink1"; + const bfs::path src_symlink_at_subdir2 = "/foo/bar/baz/symlink2"; + + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0"; + const bfs::path dst_subdir1 = "/output_dir1"; + const bfs::path dst_file_at_root0 = "/file0"; // same basename + const bfs::path dst_file_at_root1 = "/file1"; // different basename + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; // same fullname + const bfs::path dst_file_at_subdir1 = "/a/b/c/d/file1"; // same parents, different basename + const bfs::path dst_file_at_subdir2 = "/e/f/g/h/i/file0"; // different parents, same basename + const bfs::path dst_file_at_subdir3 = "/e/f/g/h/i/file1"; // different fullname + + // create input data + env.add_to_namespace(nsid0, src_file_at_root, 40000); + env.add_to_namespace(nsid0, src_file_at_subdir, 80000); + env.add_to_namespace(nsid0, src_subdir0); + env.add_to_namespace(nsid0, src_subdir1); + env.add_to_namespace(nsid0, src_empty_dir); + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir0 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir1 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + // create input data with special permissions + auto p = env.add_to_namespace(nsid0, src_noperms_file0, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file1, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file2, 0); + env.remove_access(p.parent_path()); + + p = env.add_to_namespace(nsid0, src_noperms_subdir0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir1); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir2); + env.remove_access(p.parent_path()); + + // add symlinks to the namespace + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_root0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_root1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_root2); + + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_subdir0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_subdir1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_subdir2); + + // manually create a symlink leading outside namespace 0 + boost::system::error_code ec; + const bfs::path out_symlink = "/out_symlink"; + bfs::create_symlink(dst_mnt, src_mnt / out_symlink, ec); + REQUIRE(!ec); + + // create required output directories + env.add_to_namespace(nsid1, dst_subdir1); + + + /**********************************************************************/ + /* begin tests */ + /**********************************************************************/ + // cp -r ns0://file0.txt + // -> ns1:// = ns1://file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from SRC namespace's root to " + "another NORNS_REMOTE_PATH at DST namespace's root " + "(keeping the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_root.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_root); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_root0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://file0.txt + // -> ns1://file1.txt = ns1://file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from SRC namespace's root to " + "another NORNS_LOCAL_PATH at DST namespace's root " + "(changing the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_root.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_root); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_root1); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../d/file0.txt + // -> ns1://file0.txt = ns1://file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at DST namespace's root (keeping " + "the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_subdir.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_root0); + + REQUIRE(compare_files(src, dst) == true); + } + } + + } + } + } + + // cp -r ns0://a/b/c/.../d/file0.txt + // -> ns1://file1.txt = ns1://file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at DST namespace's root (changing " + "the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_file_at_subdir.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_root1); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://file0.txt + // -> ns1://a/b/c/.../file0.txt = ns1://a/b/c/.../file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from SRC namespace's root to " + "another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(keeping the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_root.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_root); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://file0.txt + // -> ns1://a/b/c/.../file1.txt = ns1://a/b/c/.../file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from SRC namespace's root to " + "another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(changing the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_root.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_root); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir1); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../file0.txt + // -> ns1://a/b/c/.../file0.txt = ns1://a/b/c/.../file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(keeping the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_subdir.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../file0.txt + // -> ns1://a/b/c/.../file1.txt = ns1://a/b/c/.../file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(changing the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_subdir.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir1); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../file0.txt + // -> ns1://e/f/g/.../file0.txt = ns1://e/f/g/.../file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(changing the parents names)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_subdir.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_subdir2.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir2); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../file0.txt + // -> ns1://e/f/g/.../file1.txt = ns1://e/f/g/.../file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(changing the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_file_at_subdir.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_subdir3.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir3); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + env.notify_success(); + } +} + +/******************************************************************************/ +/* tests for push transfers (directories) */ +/******************************************************************************/ +SCENARIO("copy local POSIX file to remote POSIX subdir", + "[api::norns_submit_push_to_posix_subdir]") { + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env(false); + + const char* nsid0 = "tmp0"; + const char* nsid1 = "tmp1"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path src_mnt, dst_mnt; + + // create namespaces + std::tie(std::ignore, src_mnt) = + env.create_namespace(nsid0, "mnt/tmp0", 16384); + std::tie(std::ignore, dst_mnt) = + env.create_namespace(nsid1, "mnt/tmp1", 16384); + + // define input names + const bfs::path src_file_at_root = "/file0"; + const bfs::path src_file_at_subdir = "/a/b/c/d/file0"; + const bfs::path src_invalid_file = "/a/b/c/d/does_not_exist_file0"; + const bfs::path src_invalid_dir = "/a/b/c/d/does_not_exist_dir0"; + const bfs::path src_subdir0 = "/input_dir0"; + const bfs::path src_subdir1 = "/input_dir0/a/b/c/input_dir1"; + const bfs::path src_empty_dir = "/empty_dir0"; + + const bfs::path src_noperms_file0 = "/noperms_file0"; + const bfs::path src_noperms_file1 = "/noperms/a/b/c/d/noperms_file0"; // parents accessible + const bfs::path src_noperms_file2 = "/noperms/noperms_subdir0/file0"; // parents non-accessible + const bfs::path src_noperms_subdir0 = "/noperms_subdir0"; // subdir non-accessible + const bfs::path src_noperms_subdir1 = "/noperms/a/b/c/d/noperms_subdir1"; // child subdir non-accessible + const bfs::path src_noperms_subdir2 = "/noperms/noperms_subdir2/a"; // parent subdir non-accessible + + const bfs::path src_symlink_at_root0 = "/symlink0"; + const bfs::path src_symlink_at_root1 = "/symlink1"; + const bfs::path src_symlink_at_root2 = "/symlink2"; + const bfs::path src_symlink_at_subdir0 = "/foo/bar/baz/symlink0"; + const bfs::path src_symlink_at_subdir1 = "/foo/bar/baz/symlink1"; + const bfs::path src_symlink_at_subdir2 = "/foo/bar/baz/symlink2"; + + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0"; + const bfs::path dst_subdir1 = "/output_dir1"; + const bfs::path dst_file_at_root0 = "/file0"; // same basename + const bfs::path dst_file_at_root1 = "/file1"; // different basename + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; // same fullname + const bfs::path dst_file_at_subdir1 = "/a/b/c/d/file1"; // same parents, different basename + const bfs::path dst_file_at_subdir2 = "/e/f/g/h/i/file0"; // different parents, same basename + const bfs::path dst_file_at_subdir3 = "/e/f/g/h/i/file1"; // different fullname + + // create input data + env.add_to_namespace(nsid0, src_file_at_root, 40000); + env.add_to_namespace(nsid0, src_file_at_subdir, 80000); + env.add_to_namespace(nsid0, src_subdir0); + env.add_to_namespace(nsid0, src_subdir1); + env.add_to_namespace(nsid0, src_empty_dir); + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir0 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir1 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + // create input data with special permissions + auto p = env.add_to_namespace(nsid0, src_noperms_file0, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file1, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file2, 0); + env.remove_access(p.parent_path()); + + p = env.add_to_namespace(nsid0, src_noperms_subdir0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir1); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir2); + env.remove_access(p.parent_path()); + + // add symlinks to the namespace + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_root0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_root1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_root2); + + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_subdir0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_subdir1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_subdir2); + + // manually create a symlink leading outside namespace 0 + boost::system::error_code ec; + const bfs::path out_symlink = "/out_symlink"; + bfs::create_symlink(dst_mnt, src_mnt / out_symlink, ec); + REQUIRE(!ec); + + + // create required output directories + env.add_to_namespace(nsid1, dst_subdir1); + + /**********************************************************************/ + /* begin tests */ + /**********************************************************************/ + // cp -r /a/contents.* -> / = /contents.* + WHEN("copying the contents of a NORNS_LOCAL_PATH subdir from SRC " + "namespace's root to DST namespace's root\n" + " cp -r /a/contents.* -> / = /contents.* ") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_subdir0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_root.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_root); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/b/c/.../contents.* -> / = /contents.* + WHEN("copying the contents of a NORNS_LOCAL_PATH arbitrary subdir to " + "DST namespace's root\n" + " cp -r /a/b/c/.../contents.* -> / = /contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_subdir1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_root.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_root); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/contents.* -> /c = /c/contents.* + // (c did not exist previously) + WHEN("copying the contents of a NORNS_LOCAL_PATH subdir from SRC " + "namespace's root to another NORNS_REMOTE_PATH subdir at DST " + "namespace's root while changing its name\n" + " cp -r /a/contents.* -> /c = /c/contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_subdir0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_subdir0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/contents.* -> /c = /c/contents.* + // (c did exist previously) + WHEN("copying the contents of a NORNS_LOCAL_PATH subdir from SRC " + "namespace's root to another NORNS_REMOTE_PATH subdir at DST " + "namespace's root while changing its name\n" + " cp -r /a/contents.* -> /c / /c/contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_subdir0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_subdir1); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/b/c/.../contents.* -> /c = /c/a/b/c/.../contents.* + // (c did not exist previously) + WHEN("copying the contents of a NORNS_LOCAL_PATH subdir from SRC " + "namespace's root to a NORNS_REMOTE_PATH subdir at DST " + "namespace's root while changing its name\n" + "cp -r /a/b/c/.../contents.* -> /c = /c/a/b/c/.../contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_subdir1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_subdir0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/b/c/.../contents.* -> /c = /c/a/b/c/.../contents.* + // (c did exist previously) + WHEN("copying the contents of a NORNS_LOCAL_PATH subdir from SRC " + "namespace's root to another NORNS_REMOTE_PATH subdir at DST " + "namespace's root while changing its name:" + " cp -r /a/b/c/.../contents.* -> /c = /c/a/b/c/.../contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_subdir1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_subdir1); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + env.notify_success(); + } +} + +/******************************************************************************/ +/* tests for push transfers (memory buffers) */ +/******************************************************************************/ +SCENARIO("copy local memory region to remote POSIX file", + "[api::norns_submit_push_memory_to_posix_file]") { + + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env; + + const char* nsid0 = "tmp0"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path dst_mnt; + + // create namespaces + std::tie(std::ignore, dst_mnt) = env.create_namespace(nsid0, "mnt/tmp0", 16384); + + // create input data buffer (around 40MiBs) + std::vector input_data(10000000, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + // output names + const bfs::path dst_file_at_root0 = "/file0"; + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0/"; // existing + const bfs::path dst_subdir1 = "/output_dir0"; // existing but does not look as a directory + const bfs::path dst_subdir2 = "/output_dir0/a/b/c/d/"; // existing + const bfs::path dst_subdir3 = "/output_dir0/a/b/c/d"; // existing but does not look as a directory + const bfs::path dst_subdir4 = "/output_dir1/"; // non-existing + const bfs::path dst_subdir5 = "/output_dir1/a/b/c/d/"; // non-existing + + // create required output directories + env.add_to_namespace(nsid0, dst_subdir0); + env.add_to_namespace(nsid0, dst_subdir2); + + // + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH file " + "located at DST's namespace root '/'") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() return NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Output file contains buffer data") { + + bfs::path dst = + env.get_from_namespace( + nsid0, dst_file_at_root0); + + REQUIRE(compare(input_data, dst) == true); + } + } + } + } + } + + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH file " + "located at a DST's subdir") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_file_at_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("norns_wait() return NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("Output file contains buffer data") { + + bfs::path dst = + env.get_from_namespace( + nsid0, dst_file_at_subdir0); + + REQUIRE(compare(input_data, dst) == true); + } + } + } + } + } + + env.notify_success(); + } + +#ifndef USE_REAL_DAEMON + GIVEN("a non-running urd instance") { + WHEN("attempting to request a transfer") { + + norns_iotask_t task = NORNS_IOTASK( + NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH("nvml0://", "/a/b/c/"), + NORNS_REMOTE_PATH("nvml0://", "node1", "/a/b/d/")); + + norns_error_t rv = norns_submit(&task); + + THEN("NORNS_ECONNFAILED is returned") { + REQUIRE(rv == NORNS_ECONNFAILED); + } + } + } +#endif +} + + +/******************************************************************************/ +/* tests for push transfers (memory buffers, errors) */ +/******************************************************************************/ +SCENARIO("errors copying local memory region to remote POSIX file", + "[api::norns_submit_push_memory_to_posix_file_errors]") { + + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env; + + const char* nsid0 = "tmp0"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path dst_mnt; + + // create namespaces + std::tie(std::ignore, dst_mnt) = env.create_namespace(nsid0, "mnt/tmp0", 16384); + + // create input data buffer (around 40MiBs) + std::vector input_data(10000000, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + // output names + const bfs::path dst_file_at_root0 = "/file0"; + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0/"; // existing + const bfs::path dst_subdir1 = "/output_dir0"; // existing but does not look as a directory + const bfs::path dst_subdir2 = "/output_dir0/a/b/c/d/"; // existing + const bfs::path dst_subdir3 = "/output_dir0/a/b/c/d"; // existing but does not look as a directory + const bfs::path dst_subdir4 = "/output_dir1/"; // non-existing + const bfs::path dst_subdir5 = "/output_dir1/a/b/c/d/"; // non-existing + + // create required output directories + env.add_to_namespace(nsid0, dst_subdir0); + env.add_to_namespace(nsid0, dst_subdir2); + + //TODO + // - copy a valid but removed memory region (cannot control => undefined behavior) + // - providing a non-existing directory path (i.e. finished with /) as output name + // - providing an existing path that points to a directory as output name + WHEN("copying an invalid memory region to a NORNS_REMOTE_PATH") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION((void*) 0x42, 42000), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() return NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_ESYSTEMERROR and " + "EFAULT") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == EFAULT); + } + } + } + } + + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH at " + "DST's /") { + + // create input data buffer + std::vector input_data(100, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_root.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_EBADARGS") { + REQUIRE(rv == NORNS_EBADARGS); + } + } + + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH existing " + "directory at DST's /") { + + // create input data buffer + std::vector input_data(100, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_EBADARGS") { + REQUIRE(rv == NORNS_EBADARGS); + } + } + + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH existing " + "directory at / that does not look like a directory") { + + // create input data buffer + std::vector input_data(100, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() return NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_ESYSTEMERROR and " + "EISDIR") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == EISDIR); + } + } + } + } + + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH existing " + "directory at a DST's arbitary subdir") { + + // create input data buffer + std::vector input_data(100, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_subdir2.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_EBADARGS") { + REQUIRE(rv == NORNS_EBADARGS); + } + } + + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH existing " + "directory at a DST's arbitary subdir that does not look like " + "a directory") { + + // create input data buffer + std::vector input_data(100, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_subdir3.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() return NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_ESYSTEMERROR and " + "EISDIR") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == EISDIR); + } + } + } + } + + + // i.e. a destination path that 'looks like' a directory + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH " + "corresponding to a non-existing directory at DST's /") { + + // create input data buffer + std::vector input_data(100, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_subdir4.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_EBADARGS") { + REQUIRE(rv == NORNS_EBADARGS); + } + } + + WHEN("copying a valid memory region to a NORNS_REMOTE_PATH " + "non-existing arbitrary subdir at DST") { + + // create input data buffer + std::vector input_data(100, 42); + void* region_addr = input_data.data(); + size_t region_size = input_data.size() * sizeof(int); + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(region_addr, region_size), + NORNS_REMOTE_PATH(nsid0, + remote_host, + dst_subdir5.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_EBADARGS") { + REQUIRE(rv == NORNS_EBADARGS); + } + } + + + env.notify_success(); + } + +#ifndef USE_REAL_DAEMON + GIVEN("a non-running urd instance") { + WHEN("attempting to request a transfer") { + + norns_iotask_t task = NORNS_IOTASK( + NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH("nvml0://", "/a/b/c/"), + NORNS_REMOTE_PATH("nvml0://", "node1", "/a/b/d/")); + + norns_error_t rv = norns_submit(&task); + + THEN("NORNS_ECONNFAILED is returned") { + REQUIRE(rv == NORNS_ECONNFAILED); + } + } + } +#endif +} + +/******************************************************************************/ +/* tests for push transfers (links) */ +/******************************************************************************/ +SCENARIO("copy local POSIX path to remote POSIX path involving links", + "[api::norns_submit_push_links]") { + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env(false); + + const char* nsid0 = "tmp0"; + const char* nsid1 = "tmp1"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path src_mnt, dst_mnt; + + // create namespaces + std::tie(std::ignore, src_mnt) = + env.create_namespace(nsid0, "mnt/tmp0", 16384); + std::tie(std::ignore, dst_mnt) = + env.create_namespace(nsid1, "mnt/tmp1", 16384); + + // define input names + const bfs::path src_file_at_root = "/file0"; + const bfs::path src_file_at_subdir = "/a/b/c/d/file0"; + const bfs::path src_invalid_file = "/a/b/c/d/does_not_exist_file0"; + const bfs::path src_invalid_dir = "/a/b/c/d/does_not_exist_dir0"; + const bfs::path src_subdir0 = "/input_dir0"; + const bfs::path src_subdir1 = "/input_dir0/a/b/c/input_dir1"; + const bfs::path src_empty_dir = "/empty_dir0"; + + const bfs::path src_noperms_file0 = "/noperms_file0"; + const bfs::path src_noperms_file1 = "/noperms/a/b/c/d/noperms_file0"; // parents accessible + const bfs::path src_noperms_file2 = "/noperms/noperms_subdir0/file0"; // parents non-accessible + const bfs::path src_noperms_subdir0 = "/noperms_subdir0"; // subdir non-accessible + const bfs::path src_noperms_subdir1 = "/noperms/a/b/c/d/noperms_subdir1"; // child subdir non-accessible + const bfs::path src_noperms_subdir2 = "/noperms/noperms_subdir2/a"; // parent subdir non-accessible + + const bfs::path src_symlink_at_root0 = "/symlink0"; + const bfs::path src_symlink_at_root1 = "/symlink1"; + const bfs::path src_symlink_at_root2 = "/symlink2"; + const bfs::path src_symlink_at_subdir0 = "/foo/bar/baz/symlink0"; + const bfs::path src_symlink_at_subdir1 = "/foo/bar/baz/symlink1"; + const bfs::path src_symlink_at_subdir2 = "/foo/bar/baz/symlink2"; + + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0"; + const bfs::path dst_subdir1 = "/output_dir1"; + const bfs::path dst_file_at_root0 = "/file0"; // same basename + const bfs::path dst_file_at_root1 = "/file1"; // different basename + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; // same fullname + const bfs::path dst_file_at_subdir1 = "/a/b/c/d/file1"; // same parents, different basename + const bfs::path dst_file_at_subdir2 = "/e/f/g/h/i/file0"; // different parents, same basename + const bfs::path dst_file_at_subdir3 = "/e/f/g/h/i/file1"; // different fullname + + // create input data + env.add_to_namespace(nsid0, src_file_at_root, 40000); + env.add_to_namespace(nsid0, src_file_at_subdir, 80000); + env.add_to_namespace(nsid0, src_subdir0); + env.add_to_namespace(nsid0, src_subdir1); + env.add_to_namespace(nsid0, src_empty_dir); + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir0 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir1 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + // create input data with special permissions + auto p = env.add_to_namespace(nsid0, src_noperms_file0, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file1, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file2, 0); + env.remove_access(p.parent_path()); + + p = env.add_to_namespace(nsid0, src_noperms_subdir0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir1); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir2); + env.remove_access(p.parent_path()); + + // add symlinks to the namespace + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_root0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_root1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_root2); + + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_subdir0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_subdir1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_subdir2); + + // manually create a symlink leading outside namespace 0 + boost::system::error_code ec; + const bfs::path out_symlink = "/out_symlink"; + bfs::create_symlink(dst_mnt, src_mnt / out_symlink, ec); + REQUIRE(!ec); + + + // create required output directories + env.add_to_namespace(nsid1, dst_subdir1); + + /**********************************************************************/ + /* begin tests */ + /**********************************************************************/ + WHEN("copying a single NORNS_LOCAL_PATH file from SRC namespace's '/' " + "through a symlink also located at '/'" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_symlink_at_root0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, src_symlink_at_root0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_LOCAL_PATH subdir from SRC " + "namespace's '/' through a symlink also located at '/'" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_symlink_at_root1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Directories are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, src_symlink_at_root1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_LOCAL_PATH arbitrary subdir" + "through a symlink also located at '/'" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_symlink_at_root2.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Directories are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, src_symlink_at_root2); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_LOCAL_PATH file from SRC namespace's '/' " + "through a symlink located in a subdir" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_symlink_at_subdir0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, + src_symlink_at_subdir0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_LOCAL_PATH subdir from SRC " + "namespace's '/' through a symlink also located at subdir" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_symlink_at_subdir1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Directories are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, + src_symlink_at_subdir1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_LOCAL_PATH arbitrary subdir" + "through a symlink also located at a subdir" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_symlink_at_subdir2.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Directories are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, + src_symlink_at_subdir2); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + env.notify_success(); + } +} + +/******************************************************************************/ +/* tests for pull transfers (errors) */ +/******************************************************************************/ +SCENARIO("errors copying remote POSIX path to local POSIX path", + "[api::norns_submit_pull_errors]") { + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env(false); + + const char* nsid0 = "tmp0"; + const char* nsid1 = "tmp1"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path src_mnt, dst_mnt; + + // create namespaces + std::tie(std::ignore, src_mnt) = + env.create_namespace(nsid0, "mnt/tmp0", 16384); + std::tie(std::ignore, dst_mnt) = + env.create_namespace(nsid1, "mnt/tmp1", 16384); + + // define input names + const bfs::path src_file_at_root = "/file0"; + const bfs::path src_file_at_subdir = "/a/b/c/d/file0"; + const bfs::path src_invalid_file = "/a/b/c/d/does_not_exist_file0"; + const bfs::path src_invalid_dir = "/a/b/c/d/does_not_exist_dir0"; + const bfs::path src_subdir0 = "/input_dir0"; + const bfs::path src_subdir1 = "/input_dir0/a/b/c/input_dir1"; + const bfs::path src_empty_dir = "/empty_dir0"; + + const bfs::path src_noperms_file0 = "/noperms_file0"; + const bfs::path src_noperms_file1 = "/noperms/a/b/c/d/noperms_file0"; // parents accessible + const bfs::path src_noperms_file2 = "/noperms/noperms_subdir0/file0"; // parents non-accessible + const bfs::path src_noperms_subdir0 = "/noperms_subdir0"; // subdir non-accessible + const bfs::path src_noperms_subdir1 = "/noperms/a/b/c/d/noperms_subdir1"; // child subdir non-accessible + const bfs::path src_noperms_subdir2 = "/noperms/noperms_subdir2/a"; // parent subdir non-accessible + + const bfs::path src_symlink_at_root0 = "/symlink0"; + const bfs::path src_symlink_at_root1 = "/symlink1"; + const bfs::path src_symlink_at_root2 = "/symlink2"; + const bfs::path src_symlink_at_subdir0 = "/foo/bar/baz/symlink0"; + const bfs::path src_symlink_at_subdir1 = "/foo/bar/baz/symlink1"; + const bfs::path src_symlink_at_subdir2 = "/foo/bar/baz/symlink2"; + + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0"; + const bfs::path dst_subdir1 = "/output_dir1"; + const bfs::path dst_file_at_root0 = "/file0"; // same basename + const bfs::path dst_file_at_root1 = "/file1"; // different basename + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; // same fullname + const bfs::path dst_file_at_subdir1 = "/a/b/c/d/file1"; // same parents, different basename + const bfs::path dst_file_at_subdir2 = "/e/f/g/h/i/file0"; // different parents, same basename + const bfs::path dst_file_at_subdir3 = "/e/f/g/h/i/file1"; // different fullname + + // create input data + env.add_to_namespace(nsid0, src_file_at_root, 40000); + env.add_to_namespace(nsid0, src_file_at_subdir, 80000); + env.add_to_namespace(nsid0, src_subdir0); + env.add_to_namespace(nsid0, src_subdir1); + env.add_to_namespace(nsid0, src_empty_dir); + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir0 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir1 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + // create input data with special permissions + auto p = env.add_to_namespace(nsid0, src_noperms_file0, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file1, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file2, 0); + env.remove_access(p.parent_path()); + + p = env.add_to_namespace(nsid0, src_noperms_subdir0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir1); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir2); + env.remove_access(p.parent_path()); + + // add symlinks to the namespace + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_root0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_root1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_root2); + + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_subdir0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_subdir1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_subdir2); + + // manually create a symlink leading outside namespace 0 + boost::system::error_code ec; + const bfs::path out_symlink = "/out_symlink"; + bfs::create_symlink(dst_mnt, src_mnt / out_symlink, ec); + REQUIRE(!ec); + + // create required output directories + env.add_to_namespace(nsid1, dst_subdir1); + + /**********************************************************************/ + /* begin tests */ + /**********************************************************************/ + // - trying to copy a non-existing file + WHEN("copying a non-existing NORNS_LOCAL_PATH file") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_invalid_file.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and ENOENT are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == ENOENT); + } + } + } + } + + // - trying to copy a non-existing directory + WHEN("copying a non-existing NORNS_LOCAL_PATH directory") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_invalid_dir.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and ENOENT are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == ENOENT); + } + } + } + } + + // - trying to copy an empty directory + WHEN("copying an empty NORNS_LOCAL_PATH directory") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_empty_dir.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_SUCCESS and ENOENT are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + REQUIRE(stats.st_task_error == NORNS_SUCCESS); + REQUIRE(stats.st_sys_errno == 0); + + REQUIRE(bfs::exists(dst_mnt / dst_file_at_root0)); + } + } + } + } + +//FIXME: DISABLED in CI until impersonation is implemented or capabilities can be added to the docker service +#ifdef __SETCAP_TESTS__ + +#if 0 // not adapted to pull semantics + // - trying to copy a file from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH file from \"/\" without appropriate " + "permissions to access it") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_noperms_file0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL " + "are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a file from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH file from a subdir without " + "appropriate permissions to access it") { + + norns_op_t task_op = NORNS_IOTASK_COPY; + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_noperms_file1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a file from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH file from a subdir without " + "appropriate permissions to access a parent") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, src_noperms_file2.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL " + "are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a subdir from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH subdir from \"/\" without " + "appropriate permissions to access it") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_noperms_subdir0.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL " + "are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a subdir from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH subdir from another subdir " + "without appropriate permissions to access it") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_noperms_subdir1.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // - trying to copy a subdir from namespace root with invalid access permissions + WHEN("copying a NORNS_LOCAL_PATH subdir from another subdir without " + "appropriate permissions to access a parent") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + src_noperms_subdir2.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and EACCES|EPERM|EINVAL " + "are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(( (stats.st_sys_errno == EACCES) || + (stats.st_sys_errno == EPERM ) || + (stats.st_sys_errno == EINVAL) )); + } + } + } + } + + // symlink leading out of namespace + WHEN("copying a NORNS_LOCAL_PATH through a symbolic link that leads " + "out of the SRC namespace") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH(nsid0, + out_symlink.c_str()), + NORNS_REMOTE_PATH(nsid1, + remote_host, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("NORNS_ESYSTEMERROR and ENOENT are reported") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHEDWERROR); + REQUIRE(stats.st_task_error == NORNS_ESYSTEMERROR); + REQUIRE(stats.st_sys_errno == ENOENT); + } + } + } + } +#endif +#endif + env.notify_success(); + } +} + +/******************************************************************************/ +/* tests for pull transfers (single files) */ +/******************************************************************************/ +SCENARIO("copy remote POSIX file to local POSIX file", + "[api::norns_submit_pull_to_posix_file]") { + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env(false); + + const char* nsid0 = "tmp0"; + const char* nsid1 = "tmp1"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path src_mnt, dst_mnt; + + // create namespaces + std::tie(std::ignore, src_mnt) = + env.create_namespace(nsid0, "mnt/tmp0", 16384); + std::tie(std::ignore, dst_mnt) = + env.create_namespace(nsid1, "mnt/tmp1", 16384); + + // define input names + const bfs::path src_file_at_root = "/file0"; + const bfs::path src_file_at_subdir = "/a/b/c/d/file0"; + const bfs::path src_invalid_file = "/a/b/c/d/does_not_exist_file0"; + const bfs::path src_invalid_dir = "/a/b/c/d/does_not_exist_dir0"; + const bfs::path src_subdir0 = "/input_dir0"; + const bfs::path src_subdir1 = "/input_dir0/a/b/c/input_dir1"; + const bfs::path src_empty_dir = "/empty_dir0"; + + const bfs::path src_noperms_file0 = "/noperms_file0"; + const bfs::path src_noperms_file1 = "/noperms/a/b/c/d/noperms_file0"; // parents accessible + const bfs::path src_noperms_file2 = "/noperms/noperms_subdir0/file0"; // parents non-accessible + const bfs::path src_noperms_subdir0 = "/noperms_subdir0"; // subdir non-accessible + const bfs::path src_noperms_subdir1 = "/noperms/a/b/c/d/noperms_subdir1"; // child subdir non-accessible + const bfs::path src_noperms_subdir2 = "/noperms/noperms_subdir2/a"; // parent subdir non-accessible + + const bfs::path src_symlink_at_root0 = "/symlink0"; + const bfs::path src_symlink_at_root1 = "/symlink1"; + const bfs::path src_symlink_at_root2 = "/symlink2"; + const bfs::path src_symlink_at_subdir0 = "/foo/bar/baz/symlink0"; + const bfs::path src_symlink_at_subdir1 = "/foo/bar/baz/symlink1"; + const bfs::path src_symlink_at_subdir2 = "/foo/bar/baz/symlink2"; + + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0"; + const bfs::path dst_subdir1 = "/output_dir1"; + const bfs::path dst_file_at_root0 = "/file0"; // same basename + const bfs::path dst_file_at_root1 = "/file1"; // different basename + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; // same fullname + const bfs::path dst_file_at_subdir1 = "/a/b/c/d/file1"; // same parents, different basename + const bfs::path dst_file_at_subdir2 = "/e/f/g/h/i/file0"; // different parents, same basename + const bfs::path dst_file_at_subdir3 = "/e/f/g/h/i/file1"; // different fullname + + // create input data + env.add_to_namespace(nsid0, src_file_at_root, 40000); + env.add_to_namespace(nsid0, src_file_at_subdir, 80000); + env.add_to_namespace(nsid0, src_subdir0); + env.add_to_namespace(nsid0, src_subdir1); + env.add_to_namespace(nsid0, src_empty_dir); + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir0 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir1 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + // create input data with special permissions + auto p = env.add_to_namespace(nsid0, src_noperms_file0, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file1, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file2, 0); + env.remove_access(p.parent_path()); + + p = env.add_to_namespace(nsid0, src_noperms_subdir0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir1); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir2); + env.remove_access(p.parent_path()); + + // add symlinks to the namespace + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_root0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_root1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_root2); + + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_subdir0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_subdir1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_subdir2); + + // manually create a symlink leading outside namespace 0 + boost::system::error_code ec; + const bfs::path out_symlink = "/out_symlink"; + bfs::create_symlink(dst_mnt, src_mnt / out_symlink, ec); + REQUIRE(!ec); + + + // create required output directories + env.add_to_namespace(nsid1, dst_subdir1); + + /**********************************************************************/ + /* begin tests */ + /**********************************************************************/ + // cp -r ns0://file0.txt + // -> ns1:// = ns1://file0.txt + WHEN("copying a single NORNS_REMOTE_PATH from SRC namespace's root to " + "another NORNS_LOCAL_PATH at DST namespace's root " + "(keeping the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_root.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() return NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, src_file_at_root); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://file0.txt + // -> ns1://file1.txt = ns1://file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from SRC namespace's root to " + "another NORNS_LOCAL_PATH at DST namespace's root " + "(changing the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_root.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() return NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace(nsid0, src_file_at_root); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root1); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../d/file0.txt + // -> ns1://file0.txt = ns1://file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at DST namespace's root (keeping " + "the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_subdir.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_root0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../d/file0.txt + // -> ns1://file1.txt = ns1://file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at DST namespace's root (changing " + "the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_subdir.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_root1); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://file0.txt + // -> ns1://a/b/c/.../file0.txt = ns1://a/b/c/.../file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from SRC namespace's root to " + "another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(keeping the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_root.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace(nsid0, + src_file_at_root); + bfs::path dst = + env.get_from_namespace(nsid1, + dst_file_at_subdir0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://file0.txt + // -> ns1://a/b/c/.../file1.txt = ns1://a/b/c/.../file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from SRC namespace's root to " + "another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(changing the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_root.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_root); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir1); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../file0.txt + // -> ns1://a/b/c/.../file0.txt = ns1://a/b/c/.../file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(keeping the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_subdir.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../file0.txt + // -> ns1://a/b/c/.../file1.txt = ns1://a/b/c/.../file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(changing the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_subdir.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir1); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../file0.txt + // -> ns1://e/f/g/.../file0.txt = ns1://e/f/g/.../file0.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(changing the parents names)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_subdir.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_subdir2.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir2); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + // cp -r ns0://a/b/c/.../file0.txt + // -> ns1://e/f/g/.../file1.txt = ns1://e/f/g/.../file1.txt + WHEN("copying a single NORNS_LOCAL_PATH from a SRC namespace's subdir " + "to another NORNS_LOCAL_PATH at a DST namespace's subdir " + "(changing the name)") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_file_at_subdir.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_subdir3.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + bfs::path src = + env.get_from_namespace( + nsid0, src_file_at_subdir); + bfs::path dst = + env.get_from_namespace( + nsid1, dst_file_at_subdir3); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + env.notify_success(); + } +} + +/******************************************************************************/ +/* tests for pull transfers (directories) */ +/******************************************************************************/ +SCENARIO("copy remote POSIX subdir to local POSIX subdir", + "[api::norns_submit_pull_to_posix_subdir]") { + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env(false); + + const char* nsid0 = "tmp0"; + const char* nsid1 = "tmp1"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path src_mnt, dst_mnt; + + // create namespaces + std::tie(std::ignore, src_mnt) = + env.create_namespace(nsid0, "mnt/tmp0", 16384); + std::tie(std::ignore, dst_mnt) = + env.create_namespace(nsid1, "mnt/tmp1", 16384); + + // define input names + const bfs::path src_file_at_root = "/file0"; + const bfs::path src_file_at_subdir = "/a/b/c/d/file0"; + const bfs::path src_invalid_file = "/a/b/c/d/does_not_exist_file0"; + const bfs::path src_invalid_dir = "/a/b/c/d/does_not_exist_dir0"; + const bfs::path src_subdir0 = "/input_dir0"; + const bfs::path src_subdir1 = "/input_dir0/a/b/c/input_dir1"; + const bfs::path src_empty_dir = "/empty_dir0"; + + const bfs::path src_noperms_file0 = "/noperms_file0"; + const bfs::path src_noperms_file1 = "/noperms/a/b/c/d/noperms_file0"; // parents accessible + const bfs::path src_noperms_file2 = "/noperms/noperms_subdir0/file0"; // parents non-accessible + const bfs::path src_noperms_subdir0 = "/noperms_subdir0"; // subdir non-accessible + const bfs::path src_noperms_subdir1 = "/noperms/a/b/c/d/noperms_subdir1"; // child subdir non-accessible + const bfs::path src_noperms_subdir2 = "/noperms/noperms_subdir2/a"; // parent subdir non-accessible + + const bfs::path src_symlink_at_root0 = "/symlink0"; + const bfs::path src_symlink_at_root1 = "/symlink1"; + const bfs::path src_symlink_at_root2 = "/symlink2"; + const bfs::path src_symlink_at_subdir0 = "/foo/bar/baz/symlink0"; + const bfs::path src_symlink_at_subdir1 = "/foo/bar/baz/symlink1"; + const bfs::path src_symlink_at_subdir2 = "/foo/bar/baz/symlink2"; + + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0"; + const bfs::path dst_subdir1 = "/output_dir1"; + const bfs::path dst_file_at_root0 = "/file0"; // same basename + const bfs::path dst_file_at_root1 = "/file1"; // different basename + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; // same fullname + const bfs::path dst_file_at_subdir1 = "/a/b/c/d/file1"; // same parents, different basename + const bfs::path dst_file_at_subdir2 = "/e/f/g/h/i/file0"; // different parents, same basename + const bfs::path dst_file_at_subdir3 = "/e/f/g/h/i/file1"; // different fullname + + // create input data + env.add_to_namespace(nsid0, src_file_at_root, 40000); + env.add_to_namespace(nsid0, src_file_at_subdir, 80000); + env.add_to_namespace(nsid0, src_subdir0); + env.add_to_namespace(nsid0, src_subdir1); + env.add_to_namespace(nsid0, src_empty_dir); + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir0 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir1 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + // create input data with special permissions + auto p = env.add_to_namespace(nsid0, src_noperms_file0, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file1, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file2, 0); + env.remove_access(p.parent_path()); + + p = env.add_to_namespace(nsid0, src_noperms_subdir0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir1); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir2); + env.remove_access(p.parent_path()); + + // add symlinks to the namespace + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_root0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_root1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_root2); + + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_subdir0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_subdir1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_subdir2); + + // manually create a symlink leading outside namespace 0 + boost::system::error_code ec; + const bfs::path out_symlink = "/out_symlink"; + bfs::create_symlink(dst_mnt, src_mnt / out_symlink, ec); + REQUIRE(!ec); + + + // create required output directories + env.add_to_namespace(nsid1, dst_subdir1); + + // cp -r /a/contents.* -> / = /contents.* + WHEN("copying the contents of a NORNS_REMOTE_PATH subdir from SRC " + "namespace's root to a NORNS_LOCAL_PATH at DST namespace's root\n" + " cp -r /a/contents.* -> / = /contents.* ") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_subdir0.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_root.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied subdirs are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_root); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/b/c/.../contents.* -> / = /contents.* + WHEN("copying the contents of a NORNS_REMOTE_PATH arbitrary subdir to " + "a NORNS_LOCAL_PATH at DST namespace's root\n" + " cp -r /a/b/c/.../contents.* -> / = /contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_subdir1.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_root.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied subdirs are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_root); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/contents.* -> /c = /c/contents.* + // (c did not exist previously) + WHEN("copying the contents of a NORNS_REMOTE_PATH subdir from SRC " + "namespace's root to another NORNS_LOCAL_PATH subdir at DST " + "namespace's root while changing its name\n" + " cp -r /a/contents.* -> /c = /c/contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_subdir0.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied subdirs are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_subdir0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/contents.* -> /c = /c/contents.* + // (c did exist previously) + WHEN("copying the contents of a NORNS_REMOTE_PATH subdir from SRC " + "namespace's root to another NORNS_LOCAL_PATH subdir at DST " + "namespace's root while changing its name\n" + " cp -r /a/contents.* -> /c / /c/contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_subdir0.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_subdir1); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/b/c/.../contents.* -> /c = /c/a/b/c/.../contents.* + // (c did not exist previously) + WHEN("copying the contents of a NORNS_REMOTE_PATH subdir from SRC " + "namespace's root to a NORNS_LOCAL_PATH subdir at DST " + "namespace's root while changing its name\n" + "cp -r /a/b/c/.../contents.* -> /c = /c/a/b/c/.../contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_subdir1.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_subdir0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_subdir0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + // cp -r /a/b/c/.../contents.* -> /c = /c/a/b/c/.../contents.* + // (c did exist previously) + WHEN("copying the contents of a NORNS_REMOTE_PATH subdir from SRC " + "namespace's root to another NORNS_LOCAL_PATH subdir at DST " + "namespace's root while changing its name:" + " cp -r /a/b/c/.../contents.* -> /c = /c/a/b/c/.../contents.*") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_subdir1.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_subdir1.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Copied files are identical to original") { + bfs::path src = + env.get_from_namespace(nsid0, src_subdir1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_subdir1); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + env.notify_success(); + } + +#ifndef USE_REAL_DAEMON + GIVEN("a non-running urd instance") { + WHEN("attempting to request a transfer") { + + norns_iotask_t task = NORNS_IOTASK( + NORNS_IOTASK_COPY, + NORNS_LOCAL_PATH("nvml0://", "/a/b/c/"), + NORNS_REMOTE_PATH("nvml0://", "node1", "/a/b/d/")); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_ECONNFAILED") { + REQUIRE(rv == NORNS_ECONNFAILED); + } + } + } +#endif +} + +/******************************************************************************/ +/* tests for pull transfers (links) */ +/******************************************************************************/ +SCENARIO("copy remote POSIX path to local POSIX path involving links", + "[api::norns_submit_pull_links]") { + GIVEN("a running urd instance") { + + /**********************************************************************/ + /* setup common environment */ + /**********************************************************************/ + test_env env(false); + + const char* nsid0 = "tmp0"; + const char* nsid1 = "tmp1"; + const char* remote_host = "127.0.0.1:42000"; + bfs::path src_mnt, dst_mnt; + + // create namespaces + std::tie(std::ignore, src_mnt) = + env.create_namespace(nsid0, "mnt/tmp0", 16384); + std::tie(std::ignore, dst_mnt) = + env.create_namespace(nsid1, "mnt/tmp1", 16384); + + // define input names + const bfs::path src_file_at_root = "/file0"; + const bfs::path src_file_at_subdir = "/a/b/c/d/file0"; + const bfs::path src_invalid_file = "/a/b/c/d/does_not_exist_file0"; + const bfs::path src_invalid_dir = "/a/b/c/d/does_not_exist_dir0"; + const bfs::path src_subdir0 = "/input_dir0"; + const bfs::path src_subdir1 = "/input_dir0/a/b/c/input_dir1"; + const bfs::path src_empty_dir = "/empty_dir0"; + + const bfs::path src_noperms_file0 = "/noperms_file0"; + const bfs::path src_noperms_file1 = "/noperms/a/b/c/d/noperms_file0"; // parents accessible + const bfs::path src_noperms_file2 = "/noperms/noperms_subdir0/file0"; // parents non-accessible + const bfs::path src_noperms_subdir0 = "/noperms_subdir0"; // subdir non-accessible + const bfs::path src_noperms_subdir1 = "/noperms/a/b/c/d/noperms_subdir1"; // child subdir non-accessible + const bfs::path src_noperms_subdir2 = "/noperms/noperms_subdir2/a"; // parent subdir non-accessible + + const bfs::path src_symlink_at_root0 = "/symlink0"; + const bfs::path src_symlink_at_root1 = "/symlink1"; + const bfs::path src_symlink_at_root2 = "/symlink2"; + const bfs::path src_symlink_at_subdir0 = "/foo/bar/baz/symlink0"; + const bfs::path src_symlink_at_subdir1 = "/foo/bar/baz/symlink1"; + const bfs::path src_symlink_at_subdir2 = "/foo/bar/baz/symlink2"; + + const bfs::path dst_root = "/"; + const bfs::path dst_subdir0 = "/output_dir0"; + const bfs::path dst_subdir1 = "/output_dir1"; + const bfs::path dst_file_at_root0 = "/file0"; // same basename + const bfs::path dst_file_at_root1 = "/file1"; // different basename + const bfs::path dst_file_at_subdir0 = "/a/b/c/d/file0"; // same fullname + const bfs::path dst_file_at_subdir1 = "/a/b/c/d/file1"; // same parents, different basename + const bfs::path dst_file_at_subdir2 = "/e/f/g/h/i/file0"; // different parents, same basename + const bfs::path dst_file_at_subdir3 = "/e/f/g/h/i/file1"; // different fullname + + // create input data + env.add_to_namespace(nsid0, src_file_at_root, 40000); + env.add_to_namespace(nsid0, src_file_at_subdir, 80000); + env.add_to_namespace(nsid0, src_subdir0); + env.add_to_namespace(nsid0, src_subdir1); + env.add_to_namespace(nsid0, src_empty_dir); + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir0 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + for(int i=0; i<10; ++i) { + const bfs::path p{src_subdir1 / ("file" + std::to_string(i))}; + env.add_to_namespace(nsid0, p, 4096+i*10); + } + + // create input data with special permissions + auto p = env.add_to_namespace(nsid0, src_noperms_file0, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file1, 0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_file2, 0); + env.remove_access(p.parent_path()); + + p = env.add_to_namespace(nsid0, src_noperms_subdir0); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir1); + env.remove_access(p); + + p = env.add_to_namespace(nsid0, src_noperms_subdir2); + env.remove_access(p.parent_path()); + + // add symlinks to the namespace + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_root0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_root1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_root2); + + env.add_to_namespace(nsid0, src_file_at_root, src_symlink_at_subdir0); + env.add_to_namespace(nsid0, src_subdir0, src_symlink_at_subdir1); + env.add_to_namespace(nsid0, src_subdir1, src_symlink_at_subdir2); + + // manually create a symlink leading outside namespace 0 + boost::system::error_code ec; + const bfs::path out_symlink = "/out_symlink"; + bfs::create_symlink(dst_mnt, src_mnt / out_symlink, ec); + REQUIRE(!ec); + + + // create required output directories + env.add_to_namespace(nsid1, dst_subdir1); + + /**********************************************************************/ + /* begin tests */ + /**********************************************************************/ + WHEN("copying a single NORNS_REMOTE_PATH file from SRC namespace's '/' " + "through a symlink also located at '/'" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_symlink_at_root0.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, src_symlink_at_root0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_REMOTE_PATH subdir from SRC " + "namespace's '/' through a symlink also located at '/'" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_symlink_at_root1.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Directories are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, src_symlink_at_root1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_REMOTE_PATH arbitrary subdir" + "through a symlink also located at '/'" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_symlink_at_root2.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Directories are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, src_symlink_at_root2); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_REMOTE_PATH file from SRC namespace's '/' " + "through a symlink located in a subdir" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_symlink_at_subdir0.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Files are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, + src_symlink_at_subdir0); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_files(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_REMOTE_PATH subdir from SRC " + "namespace's '/' through a symlink also located at subdir" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_symlink_at_subdir1.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Directories are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, + src_symlink_at_subdir1); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + WHEN("copying a single NORNS_REMOTE_PATH arbitrary subdir" + "through a symlink also located at a subdir" ) { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + remote_host, + src_symlink_at_subdir2.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file_at_root0.c_str())); + + norns_error_t rv = norns_submit(&task); + + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(task.t_id != 0); + + // wait until the task completes + rv = norns_wait(&task); + + THEN("norns_wait() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); + + THEN("norns_status() reports NORNS_EFINISHED") { + norns_stat_t stats; + rv = norns_status(&task, &stats); + + REQUIRE(rv == NORNS_SUCCESS); + REQUIRE(stats.st_status == NORNS_EFINISHED); + + THEN("Directories are equal") { + + bfs::path src = + env.get_from_namespace(nsid0, + src_symlink_at_subdir2); + bfs::path dst = + env.get_from_namespace(nsid1, dst_file_at_root0); + + REQUIRE(compare_directories(src, dst) == true); + } + } + } + } + } + + env.notify_success(); + } +} diff --git a/tests/api-send-command.cpp b/tests/api-send-command.cpp index c5b564684b5c889f8d03e2c0d9c968fcd9919275..ab5d518592211836d48d68a63ba48059be63cdff 100644 --- a/tests/api-send-command.cpp +++ b/tests/api-send-command.cpp @@ -153,6 +153,8 @@ SCENARIO("send control commands to urd", "[api::nornsctl_send_command]") { } } } + + env.notify_success(); } #ifndef USE_REAL_DAEMON diff --git a/tests/api-task-submit.cpp b/tests/api-task-submit.cpp index 0ef2e8f285b0c6e1d25c947a8ce10adcdaf69b3a..1111a544981e9adcc68390dc9a94f3bca05e2fdb 100644 --- a/tests/api-task-submit.cpp +++ b/tests/api-task-submit.cpp @@ -144,18 +144,20 @@ SCENARIO("submit request", "[api::norns_submit]") { } } - WHEN("submitting a request to copy from NORNS_MEMORY_REGION to NORNS_REMOTE_PATH") { + WHEN("submitting a request to copy from a NORNS_MEMORY_REGION to " + "a NORNS_REMOTE_PATH") { - norns_op_t task_op = NORNS_IOTASK_COPY; - norns_iotask_t task = NORNS_IOTASK(task_op, - NORNS_MEMORY_REGION(src_mem_addr, src_mem_size), - NORNS_REMOTE_PATH(nsid0, dst_host0, dst_file0.c_str())); + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_MEMORY_REGION(src_mem_addr, src_mem_size), + NORNS_REMOTE_PATH(nsid0, + dst_host0, + dst_file0.c_str())); norns_error_t rv = norns_submit(&task); - THEN("NORNS_SUCCESS is returned") { + THEN("norns_submit() returns NORNS_SUCCESS") { REQUIRE(rv == NORNS_SUCCESS); - REQUIRE(task.t_id != 0); } } @@ -206,17 +208,21 @@ SCENARIO("submit request", "[api::norns_submit]") { } /* using a remote node as source is not allowed (yet) */ - WHEN("submitting a request to copy from NORNS_REMOTE_PATH to NORNS_LOCAL_PATH") { - - norns_op_t task_op = NORNS_IOTASK_COPY; - norns_iotask_t task = NORNS_IOTASK(task_op, - NORNS_REMOTE_PATH(nsid0, src_host0, src_file0.c_str()), - NORNS_LOCAL_PATH(nsid1, dst_file1.c_str())); + WHEN("submitting a request to copy from NORNS_REMOTE_PATH to " + "NORNS_LOCAL_PATH") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_COPY, + NORNS_REMOTE_PATH(nsid0, + src_host0, + src_file0.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file1.c_str())); norns_error_t rv = norns_submit(&task); - THEN("NORNS_ENOTSUPPORTED is returned") { - REQUIRE(rv == NORNS_ENOTSUPPORTED); + THEN("norns_submit() return NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); } } @@ -332,16 +338,17 @@ SCENARIO("submit request", "[api::norns_submit]") { WHEN("submitting a request to move from NORNS_MEMORY_REGION to NORNS_REMOTE_PATH") { - norns_op_t task_op = NORNS_IOTASK_MOVE; - norns_iotask_t task = NORNS_IOTASK(task_op, - NORNS_MEMORY_REGION(src_mem_addr, src_mem_size), - NORNS_REMOTE_PATH(nsid1, dst_host0, dst_file0.c_str())); + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_MOVE, + NORNS_MEMORY_REGION(src_mem_addr, src_mem_size), + NORNS_REMOTE_PATH(nsid1, + dst_host0, + dst_file0.c_str())); norns_error_t rv = norns_submit(&task); - THEN("NORNS_SUCCESS is returned") { + THEN("norns_submit() returns NORNS_SUCCESS") { REQUIRE(rv == NORNS_SUCCESS); - REQUIRE(task.t_id != 0); } } @@ -392,17 +399,21 @@ SCENARIO("submit request", "[api::norns_submit]") { } /* using a remote node as source is not allowed (yet) */ - WHEN("submitting a request to move from NORNS_REMOTE_PATH to NORNS_LOCAL_PATH") { - - norns_op_t task_op = NORNS_IOTASK_MOVE; - norns_iotask_t task = NORNS_IOTASK(task_op, - NORNS_REMOTE_PATH(nsid0, src_host0, src_file0.c_str()), - NORNS_LOCAL_PATH(nsid1, dst_file1.c_str())); + WHEN("submitting a request to move from NORNS_REMOTE_PATH to " + "NORNS_LOCAL_PATH") { + + norns_iotask_t task = + NORNS_IOTASK(NORNS_IOTASK_MOVE, + NORNS_REMOTE_PATH(nsid0, + src_host0, + src_file0.c_str()), + NORNS_LOCAL_PATH(nsid1, + dst_file1.c_str())); norns_error_t rv = norns_submit(&task); - THEN("NORNS_ENOTSUPPORTED is returned") { - REQUIRE(rv == NORNS_ENOTSUPPORTED); + THEN("norns_submit() returns NORNS_SUCCESS") { + REQUIRE(rv == NORNS_SUCCESS); } } diff --git a/tests/fake-daemon.cpp b/tests/fake-daemon.cpp index def24778fb780d661585ebef9770e82187ecfd62..ed83c7a2ba7a62fead219df897f926ea7ba6fd56 100644 --- a/tests/fake-daemon.cpp +++ b/tests/fake-daemon.cpp @@ -34,7 +34,7 @@ #include "nornsctl.h" #include "fake-daemon.hpp" -norns::config::settings test_cfg( +const norns::config::settings fake_daemon::default_cfg( "test_urd", /* progname */ false, /* daemonize */ false, /* use syslog */ @@ -45,9 +45,11 @@ norns::config::settings test_cfg( 100, /* dry run duration */ "./test_urd.global.socket", /* global_socket */ "./test_urd.control.socket", /* control_socket */ + "127.0.0.1", /* bind address */ 42002, /* remote port */ "./test_urd.pid", /* daemon_pidfile */ 2, /* api workers */ + "./tmp/", /* staging directory */ 128, "./", {} @@ -69,74 +71,149 @@ fake_daemon::~fake_daemon() { void fake_daemon::configure(const bfs::path& config_file, const fake_daemon_cfg& override_cfg) { +#if 1 + // most settings are taken from the default configuration for tests + m_config = default_cfg; + + // some settings are taken from the configuration file auto-generated + // for this test (mostly those path-related) + norns::config::settings file_config; + file_config.load_from_file(config_file); + + m_config.log_file(file_config.log_file()); + m_config.global_socket(file_config.global_socket()); + m_config.control_socket(file_config.control_socket()); + m_config.pidfile(file_config.pidfile()); + m_config.staging_directory(file_config.staging_directory()); + m_config.config_file(config_file); + + // we allow some settings to be overriden + m_config.dry_run(override_cfg.m_dry_run); + m_config.dry_run_duration(override_cfg.m_dry_run_duration); + + // for now default_namespaces is empty + m_config.default_namespaces().clear(); + +#endif + +#if 0 m_config.load_from_file(config_file); - m_config.progname() = test_cfg.progname(); - m_config.daemonize() = test_cfg.daemonize(); - m_config.use_syslog() = test_cfg.use_syslog(); - m_config.dry_run() = override_cfg.m_dry_run; - m_config.dry_run_duration() = override_cfg.m_dry_run_duration; - m_config.remote_port() = test_cfg.remote_port(); - m_config.workers_in_pool() = test_cfg.workers_in_pool(); - m_config.config_file() = config_file; + m_config.progname(default_cfg.progname()); + m_config.daemonize(default_cfg.daemonize()); + m_config.use_syslog(default_cfg.use_syslog()); + m_config.use_console(default_cfg.use_console()); + m_config.dry_run(override_cfg.m_dry_run); + m_config.dry_run_duration(override_cfg.m_dry_run_duration); + m_config.bind_address(default_cfg.bind_address()); + m_config.remote_port(default_cfg.remote_port()); + m_config.workers_in_pool(default_cfg.workers_in_pool()); + m_config.staging_directory(default_cfg.staging_directory()); + m_config.config_file(config_file); m_config.default_namespaces().clear(); +#endif } -void fake_daemon::configure(const bfs::path& config_file) { +void fake_daemon::configure(const bfs::path& config_file, + const std::string& alias) { + +#if 1 + // most settings are taken from the configuration file auto-generated + // for this test (mostly those path-related) m_config.load_from_file(config_file); - m_config.progname() = test_cfg.progname(); - m_config.daemonize() = test_cfg.daemonize(); - m_config.use_syslog() = test_cfg.use_syslog(); - m_config.dry_run() = test_cfg.dry_run(); - m_config.remote_port() = test_cfg.remote_port(); - m_config.workers_in_pool() = test_cfg.workers_in_pool(); - m_config.config_file() = config_file; + + m_config.progname(default_cfg.progname() + alias); + m_config.daemonize(default_cfg.daemonize()); + m_config.use_console(default_cfg.use_console()); + m_config.config_file(config_file); +#endif + +#if 0 + m_config.load_from_file(config_file); + + m_config.progname(default_cfg.progname() + alias); + m_config.daemonize(default_cfg.daemonize()); + m_config.use_syslog(default_cfg.use_syslog()); + m_config.use_console(default_cfg.use_console()); + m_config.dry_run(default_cfg.dry_run()); + m_config.dry_run_duration(default_cfg.dry_run_duration()); + m_config.bind_address(default_cfg.bind_address()); + m_config.remote_port(default_cfg.remote_port()); + m_config.workers_in_pool(default_cfg.workers_in_pool()); + m_config.staging_directory(default_cfg.staging_directory()); + m_config.config_file(config_file); m_config.default_namespaces().clear(); +#endif } +//extern "C" void __gcov_flush (void); + void fake_daemon::run() { m_running = true; - m_pid = fork(); - if(m_pid != 0) { + switch(m_pid) { + /* child code */ + case 0: + { + // disable any signal handlers set by Catch2 + // so that we can receive control signals from the parent process + for(auto signum : { SIGINT, SIGILL, SIGFPE, + SIGSEGV, SIGTERM, SIGABRT }) { + + struct sigaction sa; + sa.sa_handler = SIG_DFL; + + if(::sigaction(signum, &sa, NULL) != 0) { + throw std::runtime_error("Failed to reset Catch2 signal handler"); + } + } + + m_daemon.configure(m_config); + m_daemon.run(); + m_daemon.teardown(); #ifdef DEBUG_OUTPUT - std::cerr << "[" << getpid() << "] daemon process spawned (" << m_pid << ")\n"; + std::cerr << "[" << getpid() << "] exitting...\n"; #endif - int rv; - int retries = 20; +// __gcov_flush(); + exit(0); + } - do { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - rv = nornsctl_send_command(NORNSCTL_CMD_PING, NULL); - } while(rv != NORNS_SUCCESS && --retries != 0); + /* error code */ + case -1: + throw std::runtime_error("Failed to spawn test daemon"); - if(retries == 0) { - // the daemon may be running even if we don't receive a reply, - // try to stop it to avoid leaving a dangling process - stop(); - throw std::runtime_error("Failed to ping test daemon"); - } + /* parent code */ + default: + { #ifdef DEBUG_OUTPUT - std::cerr << "[" << getpid() << "] daemon process ready\n"; + std::cerr << "[" << getpid() << "] daemon process spawned (" << m_pid << ")\n"; #endif - return; - } + int rv; + int retries = 20; + + do { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + rv = nornsctl_send_command(NORNSCTL_CMD_PING, NULL); + } while(rv != NORNS_SUCCESS && --retries != 0); - m_daemon.configure(m_config); - m_daemon.run(); - m_daemon.teardown(); + if(retries == 0) { + // the daemon may be running even if we don't receive a reply, + // try to stop it to avoid leaving a dangling process + stop(); + throw std::runtime_error("Failed to ping test daemon"); + } #ifdef DEBUG_OUTPUT - std::cerr << "[" << getpid() << "] exitting...\n"; + std::cerr << "[" << getpid() << "] daemon process ready\n"; #endif - - exit(0); + } + } } int fake_daemon::stop() { diff --git a/tests/fake-daemon.hpp b/tests/fake-daemon.hpp index 79293728fb232f770a9798ba5d2350e9f7442e29..219400978ddc975722e416881558c94559e67500 100644 --- a/tests/fake-daemon.hpp +++ b/tests/fake-daemon.hpp @@ -46,10 +46,12 @@ struct fake_daemon_cfg { struct fake_daemon { + static const norns::config::settings default_cfg; + fake_daemon(); ~fake_daemon(); void configure(const bfs::path& config_file, const fake_daemon_cfg& override_cfg); - void configure(const bfs::path& config_file); + void configure(const bfs::path& config_file, const std::string& alias = ""); void run(); int stop(); diff --git a/tests/test-env.cpp b/tests/test-env.cpp index db5fa3fddef5a0214eb265452323bfc6273f4dc3..452fb89caacb4dc0a3b402297bdf54b83aa3f7f9 100644 --- a/tests/test-env.cpp +++ b/tests/test-env.cpp @@ -40,7 +40,6 @@ #include "config-template.hpp" //#define __DEBUG_OUTPUT__ - namespace bfs = boost::filesystem; namespace { @@ -50,40 +49,72 @@ std::string generate_testid() { return std::string("test_") + seed.substr(0, 8); } + + #ifndef USE_REAL_DAEMON -bfs::path create_config_file(const bfs::path& basedir) { - const bfs::path cfgdir = basedir / "config"; - bool res = bfs::create_directory(cfgdir); - REQUIRE(res == true); +using replacement_list = + std::vector< + std::pair>; + +bfs::path +create_config_file(const bfs::path& basedir, + const std::string& alias, + const replacement_list& reps = replacement_list()) { + + const bfs::path cfgdir = basedir / ("config" + alias); + + if(!bfs::exists(cfgdir)) { + bool res = bfs::create_directory(cfgdir); + REQUIRE(res == true); + } + + const bfs::path stagingdir = cfgdir / + fake_daemon::default_cfg.m_staging_directory; + + if(!bfs::exists(stagingdir)) { + bool res = bfs::create_directory(stagingdir); + REQUIRE(res == true); + } + + const auto stdir = bfs::canonical(stagingdir); + + const std::string name = "test.conf" + alias; + + const bfs::path config_file = cfgdir / name; + + auto outstr = boost::regex_replace(config_file::cftemplate, + boost::regex("@localstatedir@"), + cfgdir.string()); + + outstr = boost::regex_replace(outstr, + boost::regex("(staging_directory:)\\s*?\".*?\"(,?)$"), + "\\1 \"" + stdir.string() + "\"\\2"); + + for(const auto& r : reps) { + outstr = boost::regex_replace(outstr, boost::regex(r.first), r.second); + } - const bfs::path config_file = cfgdir / "test.conf"; bfs::ofstream outf(config_file); - outf << boost::regex_replace(config_file::cftemplate, - boost::regex("@localstatedir@"), - cfgdir.string()); + outf << outstr; outf.close(); return config_file; } -bfs::path patch_libraries(const bfs::path& basedir) { - - const auto config_file = create_config_file(basedir); +void +patch_libraries(const bfs::path& config_file) { int rv = ::setenv("NORNS_CONFIG_FILE", config_file.c_str(), 1); REQUIRE(rv != -1); libnorns_reload_config_file(); libnornsctl_reload_config_file(); - - return config_file; } #endif - } -test_env::test_env() : +test_env::test_env(bool requires_remote_peer) : m_test_succeeded(false), m_uid(generate_testid()), m_base_dir(bfs::absolute(bfs::current_path() / m_uid)) { @@ -97,9 +128,28 @@ test_env::test_env() : } #ifndef USE_REAL_DAEMON - const auto config_file = patch_libraries(m_base_dir); - m_td.configure(config_file); - m_td.run(); + const std::string alias(".local"); + const auto local_config = ::create_config_file(m_base_dir, alias); + ::patch_libraries(local_config); + m_ltd.configure(local_config, alias); + m_ltd.run(); + + // if the test requires a remote peer, we need to spawn another daemon + if(requires_remote_peer) { + const std::string alias(".remote"); + const auto remote_config = + ::create_config_file(m_base_dir, alias, + { {"(remote_port:)\\s*?42000\\s*?,$", "\\1 50000,"} }); + + // temporarily patch the libraries so that we can + // ping the "remote" daemon + ::patch_libraries(remote_config); + m_rtd.configure(remote_config, alias); + m_rtd.run(); + + // restore the libraries for the client + ::patch_libraries(local_config); + } #endif } @@ -118,9 +168,10 @@ test_env::test_env(const fake_daemon_cfg& cfg) : } #ifndef USE_REAL_DAEMON - const auto config_file = patch_libraries(m_base_dir); - m_td.configure(config_file, cfg); - m_td.run(); + const auto config_file = ::create_config_file(m_base_dir, ""); + ::patch_libraries(config_file); + m_ltd.configure(config_file, cfg); + m_ltd.run(); #else (void) cfg; #endif @@ -166,7 +217,7 @@ test_env::~test_env() { } #ifndef USE_REAL_DAEMON - int ret = m_td.stop(); + int ret = m_ltd.stop(); //REQUIRE(ret == 0); #endif diff --git a/tests/test-env.hpp b/tests/test-env.hpp index b8360f0a86bb64f3f930d31936956f576cc8d94a..76c4733e67e9ac44a71cb09c30caed447f258645 100644 --- a/tests/test-env.hpp +++ b/tests/test-env.hpp @@ -44,7 +44,7 @@ namespace bfs = boost::filesystem; struct test_env { - test_env(); + test_env(bool requires_remote_peer = false); test_env(const fake_daemon_cfg& cfg); ~test_env(); @@ -71,7 +71,8 @@ struct test_env { bfs::path m_base_dir; #ifndef USE_REAL_DAEMON - fake_daemon m_td; + fake_daemon m_ltd; + fake_daemon m_rtd; #endif std::set m_dirs; diff --git a/tests/utils-tar.cpp b/tests/utils-tar.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2c0b95f0be31afa433bae33b112160ad3ba23415 --- /dev/null +++ b/tests/utils-tar.cpp @@ -0,0 +1,349 @@ +/************************************************************************* + * Copyright (C) 2017-2019 Barcelona Supercomputing Center * + * Centro Nacional de Supercomputacion * + * All rights reserved. * + * * + * This file is part of the NORNS Data Scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * See AUTHORS file in the top level directory for information * + * regarding developers and contributors. * + * * + * The NORNS Data Scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation, either * + * version 3 of the License, or (at your option) any later version. * + * * + * The NORNS Data Scheduler 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General * + * Public License along with the NORNS Data Scheduler. If not, see * + * . * + *************************************************************************/ + +#include +#include +#include "utils.hpp" +#include "test-env.hpp" +#include "catch.hpp" + +namespace { + +static constexpr std::size_t tar_header_size = T_BLOCKSIZE; +static constexpr std::size_t tar_eof_size = 2*T_BLOCKSIZE; + +constexpr std::size_t +tar_aligned_size(std::size_t sz) { + return sz % T_BLOCKSIZE != 0 ? + ((sz & ~(T_BLOCKSIZE - 1)) + T_BLOCKSIZE) : + sz; +} + + +using rng = boost::mt19937; +using distribution = boost::uniform_int<>; +using size_generator = + boost::variate_generator; + +bfs::path +create_hierarchy(test_env& env, + size_generator& size_gen, + const bfs::path& subdir, + const bfs::path& parent, + std::size_t subdirs_per_level, + std::size_t files_per_subdir, + std::size_t levels) { + + if(levels == 0) { + return {}; + } + + bfs::path self = env.create_directory(subdir, parent); + + for(std::size_t i = 0; i < files_per_subdir; ++i) { + std::string fname = "/regular_file" + std::to_string(i); + auto p = env.create_file(fname, self, size_gen()); + } + + if(levels == 1) { + return self; + } + + for(std::size_t i = 0; i < subdirs_per_level; ++i) { + std::string dname = "/subdir" + std::to_string(i); + auto child = env.create_directory(dname, self); + + (void) create_hierarchy(env, size_gen, dname, self, subdirs_per_level, + files_per_subdir, levels - 1); + } + + return self; +} + +} + +SCENARIO("tar size estimation", "[utils::tar::estimate_packed_size()]") { + + GIVEN("a path to a regular file of 0 bytes") { + + test_env env; + using norns::utils::tar; + + bfs::path file = env.create_file("/regular_file", env.basedir(), 0); + + THEN("estimated size follows formula") { + std::error_code ec; + std::size_t psize = tar::estimate_size_once_packed(file, ec); + + REQUIRE(psize == tar_header_size + + tar_eof_size); + + AND_THEN("size of generated TAR archive matches estimated one") { + + bfs::path file_tar = file; + file_tar.replace_extension(".tar"); + + { + tar t(file_tar, tar::create, ec); + REQUIRE(!ec); + + t.add_file(file, bfs::relative(file, env.basedir()), ec); + REQUIRE(!ec); + } + + // this check needs to be done after tar::~tar() has been + // called, since it appends the EOF blocks + REQUIRE(psize == bfs::file_size(file_tar)); + } + } + + env.notify_success(); + } + + GIVEN("a path to a regular file of T_BLOCKSIZE bytes") { + + test_env env; + using norns::utils::tar; + + bfs::path file = + env.create_file("/regular_file", env.basedir(), tar_header_size); + + THEN("estimated size follows formula") { + std::error_code ec; + std::size_t psize = tar::estimate_size_once_packed(file, ec); + + REQUIRE(psize == tar_header_size + + tar_header_size + + tar_eof_size); + + AND_THEN("size of generated TAR archive matches estimated one") { + + bfs::path file_tar = file; + file_tar.replace_extension(".tar"); + + { + tar t(file_tar, tar::create, ec); + REQUIRE(!ec); + + t.add_file(file, bfs::relative(file, env.basedir()), ec); + REQUIRE(!ec); + } + + // this check needs to be done after tar::~tar() has been + // called, since it appends the EOF blocks + REQUIRE(psize == bfs::file_size(file_tar)); + } + } + + env.notify_success(); + } + + GIVEN("a path to a regular file of 2*T_BLOCKSIZE bytes") { + + test_env env; + using norns::utils::tar; + + bfs::path file = + env.create_file("/regular_file", env.basedir(), 2*tar_header_size); + + THEN("estimated size follows formula") { + std::error_code ec; + std::size_t psize = tar::estimate_size_once_packed(file, ec); + + REQUIRE(psize == tar_header_size + + 2*tar_header_size + + tar_eof_size); + + AND_THEN("size of generated TAR archive matches estimated one") { + + bfs::path file_tar = file; + file_tar.replace_extension(".tar"); + + { + tar t(file_tar, tar::create, ec); + REQUIRE(!ec); + + t.add_file(file, bfs::relative(file, env.basedir()), ec); + REQUIRE(!ec); + } + + // this check needs to be done after tar::~tar() has been + // called, since it appends the EOF blocks + REQUIRE(psize == bfs::file_size(file_tar)); + } + } + + env.notify_success(); + } + + GIVEN("a path to a regular file of 10000 bytes") { + + test_env env; + using norns::utils::tar; + + bfs::path file = + env.create_file("/regular_file", env.basedir(), 10000); + + THEN("estimated size follows formula") { + std::error_code ec; + std::size_t psize = tar::estimate_size_once_packed(file, ec); + + REQUIRE(psize == tar_header_size + + tar_aligned_size(10000) + + tar_eof_size); + + AND_THEN("size of generated TAR archive matches estimated one") { + + bfs::path file_tar = file; + file_tar.replace_extension(".tar"); + + { + tar t(file_tar, tar::create, ec); + REQUIRE(!ec); + + t.add_directory(file, + bfs::relative(file, env.basedir()), ec); + REQUIRE(!ec); + } + + // this check needs to be done after tar::~tar() has been + // called, since it appends the EOF blocks + REQUIRE(psize == bfs::file_size(file_tar)); + } + } + + env.notify_success(); + } + + + GIVEN("a path to an empty directory") { + + test_env env; + using norns::utils::tar; + + bfs::path subdir = env.create_directory("/subdir", env.basedir()); + + THEN("estimated size follows formula") { + std::error_code ec; + std::size_t psize = tar::estimate_size_once_packed(subdir, ec); + + REQUIRE(psize == tar_header_size + + tar_eof_size); + + AND_THEN("size of generated TAR archive matches estimated one") { + + bfs::path subdir_tar = subdir; + subdir_tar.replace_extension(".tar"); + + { + tar t(subdir_tar, tar::create, ec); + REQUIRE(!ec); + + t.add_file(subdir, bfs::relative(subdir, env.basedir()), ec); + REQUIRE(!ec); + } + + // this check needs to be done after tar::~tar() has been + // called, since it appends the EOF blocks + REQUIRE(psize == bfs::file_size(subdir_tar)); + } + } + + env.notify_success(); + } + + GIVEN("a path to a directory hierarchy containing empty files") { + + test_env env; + using norns::utils::tar; + + size_generator gen(rng{42}, distribution{0, 0}); + + bfs::path subdir = + create_hierarchy(env, gen, "/subdir", env.basedir(), 5, 5, 5); + + THEN("size of generated TAR archive matches estimated one") { + + std::error_code ec; + std::size_t psize = tar::estimate_size_once_packed(subdir, ec); + + bfs::path subdir_tar = subdir; + subdir_tar.replace_extension(".tar"); + + { + tar t(subdir_tar, tar::create, ec); + REQUIRE(!ec); + + t.add_directory(subdir, + bfs::relative(subdir, env.basedir()), ec); + REQUIRE(!ec); + } + + // this check needs to be done after tar::~tar() has been + // called, since it appends the EOF blocks + REQUIRE(psize == bfs::file_size(subdir_tar)); + } + + env.notify_success(); + } + + GIVEN("a path to a directory hierarchy containing arbitrary files") { + + test_env env; + using norns::utils::tar; + + size_generator gen(rng{42}, distribution{100, 42*1024}); + + bfs::path subdir = + create_hierarchy(env, gen, "/subdir", env.basedir(), 5, 5, 5); + + THEN("size of generated TAR archive matches estimated one") { + + std::error_code ec; + std::size_t psize = tar::estimate_size_once_packed(subdir, ec); + + bfs::path subdir_tar = subdir; + subdir_tar.replace_extension(".tar"); + + { + tar t(subdir_tar, tar::create, ec); + REQUIRE(!ec); + + t.add_directory(subdir, + bfs::relative(subdir, env.basedir()), ec); + REQUIRE(!ec); + } + + // this check needs to be done after tar::~tar() has been + // called, since it appends the EOF blocks + REQUIRE(psize == bfs::file_size(subdir_tar)); + } + + env.notify_success(); + } + +}