diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1bbd57c77ce6bb8dd18268ebcd79fb126b8a845a..129a04a0efbe1a7ca8c1c2b626935b8bc160764a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -97,6 +97,10 @@ include(FetchContent)
# that are substituted when generating defaults.cpp below
include(GNUInstallDirs)
+# CMakeDependentOption defines cmake_dependent_option() which is used to
+# define options that depend on other options
+include(CMakeDependentOption)
+
# Make sure that CMake can find our internal modules
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
@@ -149,6 +153,21 @@ message(STATUS "[${PROJECT_NAME}] server bind port: ${CARGO_BIND_PORT}")
option(CARGO_BUILD_TESTS "Build tests (disabled by default)" OFF)
+### MPI options that should be passed to ${MPIEXEC_EXECUTABLE} when starting
+### the server via the `cargoctl` script
+set(CARGOCTL_MPIEXEC_OPTIONS "--map-by node --oversubscribe" CACHE STRING
+ "Options passed to `${MPIEXEC_EXECUTABLE}` by `cargoctl` when starting the server")
+
+### systemd support
+option(CARGO_SYSTEMD_SUPPORT "Enable systemd support (enabled by default)" ON)
+
+cmake_dependent_option(
+ CARGO_SYSTEMD_INSTALL_UNIT_FILES
+ "Install systemd unit files (disabled by default)"
+ OFF
+ "CARGO_SYSTEMD_SUPPORT"
+ OFF)
+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# ##############################################################################
@@ -320,9 +339,13 @@ else ()
endif ()
add_subdirectory(etc)
+add_subdirectory(cli)
add_subdirectory(lib)
add_subdirectory(src)
-add_subdirectory(util)
+
+if(CARGO_SYSTEMD_SUPPORT)
+ add_subdirectory(systemd)
+endif()
if(CARGO_BUILD_TESTS)
add_subdirectory(tests)
@@ -334,6 +357,11 @@ endif()
# using find_package()
# ##############################################################################
+set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}"
+ CACHE PATH "Path where ${PROJECT_NAME} binaries will be installed")
+set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}"
+ CACHE PATH "Path where ${PROJECT_NAME} data files will be installed")
+
include(CMakePackageConfigHelpers)
configure_package_config_file(
@@ -341,6 +369,7 @@ configure_package_config_file(
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
INSTALL_DESTINATION
"${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}-${PROJECT_VERSION}"
+ PATH_VARS BIN_INSTALL_DIR DATA_INSTALL_DIR
)
write_basic_package_version_file(
diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d349e97143aa3d08f2d19f490edcdfda15be5f1b
--- /dev/null
+++ b/cli/CMakeLists.txt
@@ -0,0 +1,92 @@
+################################################################################
+# Copyright 2022-2023, Barcelona Supercomputing Center (BSC), Spain #
+# #
+# This software was partially supported by the EuroHPC-funded project ADMIRE #
+# (Project ID: 956748, https://www.admire-eurohpc.eu). #
+# #
+# This file is part of Cargo. #
+# #
+# Cargo is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# Cargo is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with Cargo. If not, see . #
+# #
+# SPDX-License-Identifier: GPL-3.0-or-later #
+################################################################################
+
+################################################################################
+## cargoctl: A CLI tool to interact with a Cargo server
+# TODO: This is a hack: `cargoctl` needs to know the full path to the
+# installed `cargo` and `cargo_shutdown` programs but CMake doesn't seem to
+# provide a way to get this information at this stage. Thus, we set it manually
+# here :(
+set(CARGO_PROGRAM ${CMAKE_INSTALL_FULL_BINDIR}/cargo)
+set(CARGO_SHUTDOWN_PROGRAM ${CMAKE_INSTALL_FULL_BINDIR}/cargo_shutdown)
+configure_file(cargoctl.in cargoctl @ONLY)
+
+
+################################################################################
+## cargo_ping: A CLI tool to check if a Cargo server is running
+add_executable(cargo_ping)
+
+target_sources(cargo_ping
+ PRIVATE
+ ping.cpp
+)
+
+target_link_libraries(cargo_ping
+ PUBLIC
+ fmt::fmt
+ CLI11::CLI11
+ net::rpc_client
+ cargo
+)
+
+################################################################################
+## cargo_shutdown: A CLI tool to shutdown a Cargo server
+add_executable(cargo_shutdown)
+
+target_sources(cargo_shutdown
+ PRIVATE
+ shutdown.cpp
+)
+
+target_link_libraries(cargo_shutdown
+ PUBLIC
+ fmt::fmt
+ CLI11::CLI11
+ net::rpc_client
+ cargo
+)
+
+################################################################################
+## ccp: A CLI tool to request a Cargo server to copy files between storage tiers
+add_executable(ccp)
+
+target_sources(ccp
+ PRIVATE
+ copy.cpp
+)
+
+target_link_libraries(ccp
+ PUBLIC
+ fmt::fmt
+ CLI11::CLI11
+ net::rpc_client
+ cargo
+)
+
+install(TARGETS cargo_ping cargo_shutdown ccp
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+)
+
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/cargoctl
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/cli/cargoctl.in b/cli/cargoctl.in
new file mode 100755
index 0000000000000000000000000000000000000000..ed81d0bde96c5139843350b16d9117ede97ca338
--- /dev/null
+++ b/cli/cargoctl.in
@@ -0,0 +1,172 @@
+#!/bin/bash
+################################################################################
+# Copyright 2022-2023, Barcelona Supercomputing Center (BSC), Spain #
+# #
+# This software was partially supported by the EuroHPC-funded project ADMIRE #
+# (Project ID: 956748, https://www.admire-eurohpc.eu). #
+# #
+# This file is part of Cargo. #
+# #
+# Cargo is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# Cargo is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with Cargo. If not, see . #
+# #
+# SPDX-License-Identifier: GPL-3.0-or-later #
+################################################################################
+
+progname=$(basename "$0")
+
+usage() {
+ echo "Usage: $progname COMMAND "
+ echo ""
+ echo " -h, --help: Show this help message and exit"
+ echo ""
+ echo "Valid commands:"
+ echo ""
+ echo "Start a Cargo server listening on a address , with workers "
+ echo "distributed over a pool of hosts :"
+ echo " $progname start -s -H -n "
+ echo ""
+ echo "NOTE: ADDR must be a valid Mercury address, and host1, host2, ..., "
+ echo "hostN must be valid hostnames. Also, ADDR should refer to one of the "
+ echo "defined hosts in the host pool."
+ echo ""
+ echo "Stop a Cargo server listening on a given address :"
+ echo " $progname stop -s "
+ exit 1
+}
+
+start() {
+ if [ $# -eq 0 ]; then
+ echo "$progname: ERROR: No options provided" >&2
+ usage
+ fi
+
+ local OPTIND opt workers address
+
+ while getopts ":s:H:n:" opt; do
+ case $opt in
+ s)
+ address="$OPTARG"
+ ;;
+ H)
+ hosts="$OPTARG"
+ ;;
+ n)
+ workers="$OPTARG"
+ ;;
+ \?)
+ echo "$progname: Invalid option: '-$OPTARG'" >&2
+ usage
+ ;;
+ :)
+ echo "Option -$OPTARG requires an argument." >&2
+ usage
+ ;;
+ esac
+ done
+
+ if [ -z "$workers" ]; then
+ echo "$progname: ERROR: Number of workers not provided" >&2
+ usage
+ fi
+
+ if [ -z "$address" ]; then
+ echo "$progname: ERROR: Bind address not provided" >&2
+ usage
+ fi
+
+ echo "Starting the Cargo server"
+ echo " Hosts: $hosts"
+ echo " Workers: $workers"
+ echo " Master address: $address"
+
+ if ! @MPIEXEC_EXECUTABLE@ \
+ -H "$hosts" \
+ -np "$workers" \
+ @CARGOCTL_MPIEXEC_OPTIONS@ \
+ @CARGO_PROGRAM@ --listen "$address"; then
+ echo "Failed to start the Cargo server"
+ exit 1
+ fi
+}
+
+stop() {
+
+ if [ $# -eq 0 ]; then
+ echo "$progname: ERROR: No options provided" >&2
+ usage
+ fi
+
+ local OPTIND opt address
+
+ while getopts ":s:" opt; do
+ case $opt in
+ s)
+ address="$OPTARG"
+ ;;
+ \?)
+ echo "$progname: Invalid option: '-$OPTARG'" >&2
+ usage
+ ;;
+ :)
+ echo "Option -$OPTARG requires an argument." >&2
+ usage
+ ;;
+ esac
+ done
+
+ if [ -z "$address" ]; then
+ echo "$progname: ERROR: Server address not provided" >&2
+ usage
+ fi
+
+ echo "Stopping the Cargo server at $address"
+ @CARGO_SHUTDOWN_PROGRAM@ -s "$address"
+}
+
+main() {
+ if [ $# -lt 1 ]; then
+ usage
+ fi
+
+ echo "Running $progname $*"
+
+ local cmd
+
+ for arg in "$@"; do
+ case $arg in
+ "-h" | "--help")
+ usage
+ ;;
+ *) ;;
+
+ esac
+ done
+
+ while [ $# -gt 0 ]; do
+ case $1 in
+ "start" | "stop")
+ cmd="$1"
+ shift
+ "$cmd" "$@"
+ exit 0
+ ;;
+ *)
+ echo "$progname: Invalid option: $1" >&2
+ usage
+ ;;
+ esac
+ done
+}
+
+main "$@"
diff --git a/util/copy.cpp b/cli/copy.cpp
similarity index 100%
rename from util/copy.cpp
rename to cli/copy.cpp
diff --git a/util/ping.cpp b/cli/ping.cpp
similarity index 100%
rename from util/ping.cpp
rename to cli/ping.cpp
diff --git a/util/shutdown.cpp b/cli/shutdown.cpp
similarity index 100%
rename from util/shutdown.cpp
rename to cli/shutdown.cpp
diff --git a/cmake/cargo-config.cmake.in b/cmake/cargo-config.cmake.in
index 3e4fe702ed65d91bc54f09f3063f5ddc9b8802ba..8cb239be0bb42b394d590c6f0e36df793e7ec9a9 100644
--- a/cmake/cargo-config.cmake.in
+++ b/cmake/cargo-config.cmake.in
@@ -28,4 +28,7 @@ check_required_components("@PROJECT_NAME@")
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Exports.cmake")
-check_required_components("@PROJECT_NAME@")
\ No newline at end of file
+set_and_check(CARGO_BIN_INSTALL_DIR "@PACKAGE_BIN_INSTALL_DIR@")
+set_and_check(CARGO_DATA_INSTALL_DIR "@PACKAGE_DATA_INSTALL_DIR@")
+
+check_required_components("@PROJECT_NAME@")
diff --git a/cmake/systemd.cmake b/cmake/systemd.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..f6a51fe8be974fd239b8a799350ad2dec81e3188
--- /dev/null
+++ b/cmake/systemd.cmake
@@ -0,0 +1,79 @@
+################################################################################
+# Copyright 2022-2023, Barcelona Supercomputing Center (BSC), Spain #
+# #
+# This software was partially supported by the EuroHPC-funded project ADMIRE #
+# (Project ID: 956748, https://www.admire-eurohpc.eu). #
+# #
+# This file is part of Cargo. #
+# #
+# cargo is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# cargo is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with cargo. If not, see . #
+# #
+# SPDX-License-Identifier: GPL-3.0-or-later #
+################################################################################
+
+
+include(CMakeParseArguments)
+
+#[=======================================================================[.rst:
+
+ get_systemd_unit_directory(OUTPUT_VARIABLE [USER])
+
+Initialize ``OUTPUT_VARIABLE`` to the directory where systemd unit files
+are installed. This function will use ``pkg-config`` to find the information.
+If ``USER`` is specified, the user unit directory will be returned instead.
+#]=======================================================================]
+function(get_systemd_unit_directory OUTPUT_VARIABLE)
+
+ set(OPTIONS USER)
+ set(SINGLE_VALUE)
+ set(MULTI_VALUE)
+
+ cmake_parse_arguments(
+ ARGS "${OPTIONS}" "${SINGLE_VALUE}" "${MULTI_VALUE}" ${ARGN}
+ )
+
+ if(ARGS_UNPARSED_ARGUMENTS)
+ message(WARNING "Unparsed arguments in get_systemd_unit_directory(): "
+ "this often indicates typos!\n"
+ "Unparsed arguments: ${ARGS_UNPARSED_ARGUMENTS}"
+ )
+ endif()
+
+ find_package(PkgConfig REQUIRED)
+
+ # Check for the systemd application, so that we can find out where to
+ # install the service unit files
+ pkg_check_modules(SYSTEMD_PROGRAM QUIET systemd)
+
+ if (ARGS_USER)
+ set(_pc_var systemduserunitdir)
+ else ()
+ set(_pc_var systemdsystemunitdir)
+ endif ()
+
+ if (SYSTEMD_PROGRAM_FOUND)
+ # Use pkg-config to look up the systemd unit install directory
+ execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE}
+ --variable=${_pc_var} systemd
+ OUTPUT_VARIABLE _systemd_unit_dir)
+ string(REGEX REPLACE "[ \t\n]+" "" _systemd_unit_dir "${_systemd_unit_dir}")
+
+ message(STATUS "systemd services install dir: ${_systemd_unit_dir}")
+
+ set(${OUTPUT_VARIABLE}
+ ${_systemd_unit_dir}
+ PARENT_SCOPE
+ )
+ endif ()
+endfunction()
diff --git a/util/CMakeLists.txt b/systemd/CMakeLists.txt
similarity index 72%
rename from util/CMakeLists.txt
rename to systemd/CMakeLists.txt
index 9b47c0046be7be64b8c0a9a3bd5e11532c074df4..16646188a5f178d7eb04167f80dcda2ac43268ee 100644
--- a/util/CMakeLists.txt
+++ b/systemd/CMakeLists.txt
@@ -22,51 +22,24 @@
# SPDX-License-Identifier: GPL-3.0-or-later #
################################################################################
-add_executable(cargo_ping)
-
-target_sources(cargo_ping
- PRIVATE
- ping.cpp
-)
-
-target_link_libraries(cargo_ping
- PUBLIC
- fmt::fmt
- CLI11::CLI11
- net::rpc_client
- cargo
-)
-
-add_executable(cargo_shutdown)
-
-target_sources(cargo_shutdown
- PRIVATE
- shutdown.cpp
-)
-
-target_link_libraries(cargo_shutdown
- PUBLIC
- fmt::fmt
- CLI11::CLI11
- net::rpc_client
- cargo
-)
-
-add_executable(ccp)
-
-target_sources(ccp
- PRIVATE
- copy.cpp
-)
-
-target_link_libraries(ccp
- PUBLIC
- fmt::fmt
- CLI11::CLI11
- net::rpc_client
- cargo
-)
-
-install(TARGETS cargo_ping cargo_shutdown ccp
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
-)
+configure_file(cargo@.service.in cargo@.service @ONLY)
+
+# If requested, install the systemd unit files to the system directory
+# for user-defined services. Otherwise, install them to the configured
+# `datadir` directory (usually `/usr/share`).
+if(CARGO_SYSTEMD_INSTALL_UNIT_FILES)
+ include(systemd)
+ get_systemd_unit_directory(SYSTEMD_UNIT_DIRECTORY USER)
+
+ if(NOT SYSTEMD_UNIT_DIRECTORY)
+ message(FATAL_ERROR "Could not find systemd unit directory")
+ endif()
+
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cargo@.service
+ DESTINATION ${SYSTEMD_UNIT_DIRECTORY}
+ )
+else()
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cargo@.service
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}
+ )
+endif()
diff --git a/systemd/cargo@.service.in b/systemd/cargo@.service.in
new file mode 100644
index 0000000000000000000000000000000000000000..6e1300670b462639b50bb24bce3ddaa01277e2fd
--- /dev/null
+++ b/systemd/cargo@.service.in
@@ -0,0 +1,11 @@
+[Unit]
+Description=Cargo parallel data stager
+
+[Service]
+Type=simple
+EnvironmentFile=%S/cargo/%I.cfg
+ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/cargoctl start -s ${CARGO_ADDRESS} -H ${CARGO_HOSTS} -n ${CARGO_NUM_NODES}
+ExecStop=@CMAKE_INSTALL_FULL_BINDIR@/cargoctl stop -s ${CARGO_ADDRESS}
+Restart=no
+PrivateTmp=true
+NoNewPrivileges=true