From abcda5f20cfa0a0d8dae89ef44bff0872d8e8f4f Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 15 Nov 2024 10:17:28 +0000 Subject: [PATCH] Add tests for handling Client/Server configuration Re ECFLOW-1985 --- libs/base/src/ecflow/base/Openssl.cpp | 11 +- libs/client/CMakeLists.txt | 1 + .../src/ecflow/client/ClientEnvironment.hpp | 1 + .../src/ecflow/client/ClientInvoker.cpp | 10 +- .../src/ecflow/client/ClientInvoker.hpp | 8 + .../src/ecflow/client/ClientOptions.cpp | 17 +- libs/client/test/TestClientConfigurations.cpp | 307 ++++++++++++++++++ libs/server/CMakeLists.txt | 1 + .../src/ecflow/server/ServerEnvironment.cpp | 33 +- .../src/ecflow/server/ServerEnvironment.hpp | 10 +- .../src/ecflow/server/ServerOptions.cpp | 15 +- .../src/ecflow/server/ServerOptions.hpp | 3 + libs/server/test/TestServerConfigurations.cpp | 163 ++++++++++ 13 files changed, 543 insertions(+), 37 deletions(-) create mode 100644 libs/client/test/TestClientConfigurations.cpp create mode 100644 libs/server/test/TestServerConfigurations.cpp diff --git a/libs/base/src/ecflow/base/Openssl.cpp b/libs/base/src/ecflow/base/Openssl.cpp index b6c6e32b7..c0ef08427 100644 --- a/libs/base/src/ecflow/base/Openssl.cpp +++ b/libs/base/src/ecflow/base/Openssl.cpp @@ -161,9 +161,14 @@ std::string Openssl::get_password() const { } std::string Openssl::certificates_dir() const { - std::string home_path = getenv("HOME"); - home_path += "/.ecflowrc/ssl/"; - return home_path; + if (auto found = getenv("ECF_SSL_DIR"); found) { + return found; + } + else { + std::string home_path = getenv("HOME"); + home_path += "/.ecflowrc/ssl/"; + return home_path; + } } std::string Openssl::pem() const { diff --git a/libs/client/CMakeLists.txt b/libs/client/CMakeLists.txt index 6d02dd73f..e23d563e2 100644 --- a/libs/client/CMakeLists.txt +++ b/libs/client/CMakeLists.txt @@ -56,6 +56,7 @@ set(test_srcs test/InvokeServer.hpp # Sources test/TestClient_main.cpp # test entry point + test/TestClientConfigurations.cpp test/TestClientEnvironment.cpp test/TestClientOptions.cpp test/TestClientInterface.cpp diff --git a/libs/client/src/ecflow/client/ClientEnvironment.hpp b/libs/client/src/ecflow/client/ClientEnvironment.hpp index 34fd1fa6e..64569cd36 100644 --- a/libs/client/src/ecflow/client/ClientEnvironment.hpp +++ b/libs/client/src/ecflow/client/ClientEnvironment.hpp @@ -93,6 +93,7 @@ class ClientEnvironment final : public AbstractClientEnv { #ifdef ECF_OPENSSL /// return true if this is a ssl enabled server ecf::Openssl& openssl() { return ssl_; } + const ecf::Openssl& openssl() const { return ssl_; } bool ssl() const { return ssl_.enabled(); } void enable_ssl_if_defined() { ssl_.enable_if_defined(host(), port()); diff --git a/libs/client/src/ecflow/client/ClientInvoker.cpp b/libs/client/src/ecflow/client/ClientInvoker.cpp index 40b7eef5f..18b094ad6 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.cpp +++ b/libs/client/src/ecflow/client/ClientInvoker.cpp @@ -202,6 +202,13 @@ void ClientInvoker::set_connection_attempts(unsigned int attempts) { connection_attempts_ = 1; } +std::optional ClientInvoker::get_cmd_from_args(const CommandLine& cl) const { + Cmd_ptr cts_cmd; + if (get_cmd_from_args(cl, cts_cmd) == 1) + return std::nullopt; + return cts_cmd; +} + int ClientInvoker::get_cmd_from_args(const CommandLine& cl, Cmd_ptr& cts_cmd) const { try { // read in program option, and construct the client to server commands from them. @@ -267,8 +274,9 @@ int ClientInvoker::invoke(const CommandLine& cl) const { server_reply_.get_error_msg().clear(); Cmd_ptr cts_cmd; - if (get_cmd_from_args(cl, cts_cmd) == 1) + if (get_cmd_from_args(cl, cts_cmd) == 1) { return 1; + } if (!cts_cmd) return 0; // For --help and --debug, --load defs check_only, no command is created diff --git a/libs/client/src/ecflow/client/ClientInvoker.hpp b/libs/client/src/ecflow/client/ClientInvoker.hpp index 71a008efa..7f5206efb 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.hpp +++ b/libs/client/src/ecflow/client/ClientInvoker.hpp @@ -42,6 +42,8 @@ class ClientInvoker { ClientInvoker(const std::string& host, const std::string& port); ClientInvoker(const std::string& host, int port); + const ClientEnvironment& environment() const { return clientEnv_; } + /// for debug allow the current client environment to be printed std::string to_string() const { return clientEnv_.toString(); } @@ -395,7 +397,13 @@ class ClientInvoker { bool alias = false, bool run = true); // ecFlowview SUBMIT_FILE + std::optional get_cmd_from_args(const CommandLine& cl) const; + private: + /** + * @return 1 when command is selected; 0 if no command is selected (e.g. --help) + * @throws std::runtime_error if the command could not be selected + */ int get_cmd_from_args(const CommandLine& cl, Cmd_ptr& cts_cmd) const; /// returns 1 on error and 0 on success. The errorMsg can be accessed via errorMsg() diff --git a/libs/client/src/ecflow/client/ClientOptions.cpp b/libs/client/src/ecflow/client/ClientOptions.cpp index 413b43ae0..b90984888 100644 --- a/libs/client/src/ecflow/client/ClientOptions.cpp +++ b/libs/client/src/ecflow/client/ClientOptions.cpp @@ -179,18 +179,25 @@ Cmd_ptr ClientOptions::parse(const CommandLine& cl, ClientEnvironment* env) cons #ifdef ECF_OPENSSL if (auto ecf_ssl = getenv("ECF_SSL"); vm.count("ssl") || ecf_ssl) { - if (env->debug()) { - if (!vm.count("ssl") && ecf_ssl) { + if (!vm.count("ssl") && ecf_ssl) { + if (env->debug()) { std::cout << " ssl enabled via environment variable\n"; } - else if (!vm.count("ssl") && ecf_ssl) { + env->enable_ssl_if_defined(); + } + else if (vm.count("ssl") && !ecf_ssl) { + if (env->debug()) { std::cout << " ssl explicitly enabled via command line\n"; } - else { + env->enable_ssl(); + } + else { + if (env->debug()) { std::cout << " ssl explicitly enabled via command line, but also enabled via environment variable\n"; } + env->enable_ssl(); } - env->enable_ssl(); + if (env->debug()) { std::cout << " ssl certificate: '" << env->openssl().info() << "' \n"; } diff --git a/libs/client/test/TestClientConfigurations.cpp b/libs/client/test/TestClientConfigurations.cpp new file mode 100644 index 000000000..a1eecb610 --- /dev/null +++ b/libs/client/test/TestClientConfigurations.cpp @@ -0,0 +1,307 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include + +#include "ecflow/base/cts/user/CtsCmd.hpp" +#include "ecflow/client/ClientInvoker.hpp" +#include "ecflow/core/CommandLine.hpp" +#include "ecflow/core/Filesystem.hpp" +#include "ecflow/core/Host.hpp" +#include "ecflow/test/scaffold/Provisioning.hpp" + +struct MockClientInvoker +{ + explicit MockClientInvoker(const std::string& commandline) : client_() { + client_.set_cli(true); + // Process the command line arguments to trigger the environment initialization + auto b = client_.get_cmd_from_args(CommandLine(commandline)); + } + + const ClientEnvironment& environment() const { return client_.environment(); }; + +private: + ClientInvoker client_; +}; + +BOOST_AUTO_TEST_SUITE(S_Client) + +BOOST_AUTO_TEST_SUITE(T_ClientConfiguration) + +/* + * The following exports an environment variable used for tests, which changes the location of the SSL certificates. + * Instead of the default location (HOME/.ecflowrc/ssl), we use the current test directory. + */ +WithTestEnvironmentVariable ecf_ssl_dir("ECF_SSL_DIR", "./"); + +std::string somehost = "somehost"; +std::string someport = "31415"; +std::string someuser = "someuser"; +std::string somepass = "somepass"; + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_shared__options_none__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", somehost); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", "1"); + + MockClientInvoker client("ecflow_client -d --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_specific__options_none__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", somehost); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", somehost + '.' + someport); + + MockClientInvoker client("ecflow_client -d --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); + + // Note: + // The actual value of ECF_SSL is not used find the specific certificate. + // Instead, the host name is resolved by the OS, and the selected port is used. +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_shared__options_none__certificates_specific_only) { + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + + WithTestEnvironmentVariable ecf_host("ECF_HOST", somehost); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", "1"); + + MockClientInvoker client("ecflow_client -d --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_shared__options_none__certificates_shared_only) { + WithTestFile shared_crt("server.crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", somehost); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", "1"); + + MockClientInvoker client("ecflow_client -d --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_shared__options_ssl__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", somehost); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", "1"); + + MockClientInvoker client("ecflow_client -d --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_ssl__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", somehost); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); + + // Note: + // When only using the command line option, if both shared and specific certificates are available, + // there is no way to select the specific certificate. +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_ssl__certificates_specific_only) { + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", somehost); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_ssl__certificates_shared_only) { + WithTestFile shared_crt("server.crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", somehost); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_host_ssl__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --host " + somehost + " --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_host_ssl__certificates_specific_only) { + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --host " + somehost + " --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_host_ssl__certificates_shared_only) { + WithTestFile shared_crt("server.crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --host " + somehost + " --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_host_request_none__options_host_ssl__certificates_shared_only) { + WithTestFile shared_crt("server.crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", "to_be_overriden"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --host " + somehost + " --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_host_request_none__options_host_ssl__certificates_specific_only) { + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", "to_be_overriden"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --host " + somehost + " --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_host_request_none__options_host_ssl__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", "to_be_overriden"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --host " + somehost + " --ssl --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment_without_ssl) { + WithTestFile shared_crt("server.crt"); + WithTestEnvironmentVariable ecf_host("ECF_HOST", "to_be_overriden"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_user("ECF_USER", someuser); + + MockClientInvoker client("ecflow_client -d --host " + somehost + " --ping"); + const ClientEnvironment& env = client.environment(); + + BOOST_CHECK_EQUAL(env.host(), somehost); + BOOST_CHECK_EQUAL(env.port(), someport); + BOOST_CHECK_EQUAL(env.get_user_name(), someuser); + BOOST_CHECK(!env.ssl()); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/server/CMakeLists.txt b/libs/server/CMakeLists.txt index c0b344d1b..da163ed37 100644 --- a/libs/server/CMakeLists.txt +++ b/libs/server/CMakeLists.txt @@ -59,6 +59,7 @@ ecbuild_add_test( src SOURCES test/TestCheckPtSaver.cpp + test/TestServerConfigurations.cpp test/TestServerEnvironment.cpp test/TestServer_main.cpp # test entry point test/TestServer.cpp diff --git a/libs/server/src/ecflow/server/ServerEnvironment.cpp b/libs/server/src/ecflow/server/ServerEnvironment.cpp index 1e97b4ad7..ea41a6f65 100644 --- a/libs/server/src/ecflow/server/ServerEnvironment.cpp +++ b/libs/server/src/ecflow/server/ServerEnvironment.cpp @@ -59,7 +59,7 @@ const int defaultSubmitJobsInterval = 60; // class ServerEnvironment: /////////////////////////////////////////////////////////////////////////////////////////// -ServerEnvironment::ServerEnvironment(int argc, char* argv[]) +ServerEnvironment::ServerEnvironment(const CommandLine& cl, const std::string& path_to_config_file) : serverHost_(host_name_.name()), serverPort_(0), checkPtInterval_(0), @@ -74,33 +74,20 @@ ServerEnvironment::ServerEnvironment(int argc, char* argv[]) tcp_protocol_(boost::asio::ip::tcp::v4()) { Ecf::set_server(true); - init(argc, argv, "server_environment.cfg"); - if (debug_) + init(cl, path_to_config_file); + if (debug_) { std::cout << dump() << "\n"; + } } -// This is ONLY used in test -ServerEnvironment::ServerEnvironment(int argc, char* argv[], const std::string& path_to_config_file) - : serverHost_(host_name_.name()), - serverPort_(0), - checkPtInterval_(0), - checkpt_save_time_alarm_(CheckPt::default_save_time_alarm()), - submitJobsInterval_(defaultSubmitJobsInterval), - ecf_prune_node_log_(0), - jobGeneration_(true), - debug_(false), - help_option_(false), - version_option_(false), - checkMode_(ecf::CheckPt::ON_TIME), - tcp_protocol_(boost::asio::ip::tcp::v4()) { - Ecf::set_server(true); +ServerEnvironment::ServerEnvironment(int argc, char* argv[]) : ServerEnvironment(CommandLine(argc, argv)) { +} - init(argc, argv, path_to_config_file); - if (debug_) - std::cout << dump() << "\n"; +ServerEnvironment::ServerEnvironment(int argc, char* argv[], const std::string& path_to_config_file) + : ServerEnvironment(CommandLine(argc, argv), path_to_config_file) { } -void ServerEnvironment::init(int argc, char* argv[], const std::string& path_to_config_file) { +void ServerEnvironment::init(const CommandLine& cl, const std::string& path_to_config_file) { std::string log_file_name; try { read_config_file(log_file_name, path_to_config_file); @@ -127,7 +114,7 @@ void ServerEnvironment::init(int argc, char* argv[], const std::string& path_to_ // std::cout << "PID = " << ecf_pid_ << "\n"; // The options(argc/argv) must be read after the environment, since they override everything else - ServerOptions options(argc, argv, this); + ServerOptions options(cl, this); help_option_ = options.help_option(); if (help_option_) return; // User is printing the help diff --git a/libs/server/src/ecflow/server/ServerEnvironment.hpp b/libs/server/src/ecflow/server/ServerEnvironment.hpp index 25592d686..a73012fb4 100644 --- a/libs/server/src/ecflow/server/ServerEnvironment.hpp +++ b/libs/server/src/ecflow/server/ServerEnvironment.hpp @@ -26,10 +26,10 @@ #include #include "ecflow/core/CheckPt.hpp" +#include "ecflow/core/CommandLine.hpp" #include "ecflow/core/Host.hpp" #include "ecflow/core/PasswdFile.hpp" #include "ecflow/core/WhiteListFile.hpp" - #ifdef ECF_OPENSSL #include "ecflow/base/Openssl.hpp" #endif @@ -44,8 +44,11 @@ class ServerEnvironmentException : public std::runtime_error { class ServerEnvironment { public: + ServerEnvironment(const CommandLine& cl, const std::string& path_to_config_file = "server_environment.cfg"); + ServerEnvironment(int argc, char* argv[]); - ServerEnvironment(int argc, char* argv[], const std::string& path_to_config_file); // *only used in test* + ServerEnvironment(int argc, char* argv[], const std::string& path_to_config_file); + // Disable copy (and move) semantics ServerEnvironment(const ServerEnvironment&) = delete; const ServerEnvironment& operator=(const ServerEnvironment&) = delete; @@ -65,6 +68,7 @@ class ServerEnvironment { #ifdef ECF_OPENSSL /// return true if ssl enable via command line, AND SSL libraries are found ecf::Openssl& openssl() { return ssl_; } + const ecf::Openssl& openssl() const { return ssl_; } bool ssl() const { return ssl_.enabled(); } void enable_ssl() { ssl_.enable(serverHost_, the_port()); } // search server.crt first, then ..crt #endif @@ -187,7 +191,7 @@ class ServerEnvironment { static std::vector expected_variables(); private: - void init(int argc, char* argv[], const std::string& path_to_config_file); + void init(const CommandLine& cl, const std::string& path_to_config_file); /// defaults are read from a config file void read_config_file(std::string& log_file_name, const std::string& path_to_config_file); diff --git a/libs/server/src/ecflow/server/ServerOptions.cpp b/libs/server/src/ecflow/server/ServerOptions.cpp index e8cfd9b05..ab9115830 100644 --- a/libs/server/src/ecflow/server/ServerOptions.cpp +++ b/libs/server/src/ecflow/server/ServerOptions.cpp @@ -23,7 +23,7 @@ using namespace std; using namespace ecf; namespace po = boost::program_options; -ServerOptions::ServerOptions(int argc, char* argv[], ServerEnvironment* env) { +ServerOptions::ServerOptions(const CommandLine& cl, ServerEnvironment* env) { std::stringstream ss; ss << "\n" << Version::description() << "\nServer options"; po::options_description desc(ss.str(), po::options_description::m_default_line_length + 80); @@ -124,7 +124,14 @@ ServerOptions::ServerOptions(int argc, char* argv[], ServerEnvironment* env) { "version,v", "Show ecflow version number,boost library version, compiler used and compilation date, then exit"); - po::store(po::parse_command_line(argc, argv, desc), vm_); + // 1) Parse the CLI options + po::parsed_options parsed_options = po::command_line_parser(cl.tokens()) + .options(desc) + .style(po::command_line_style::default_style) + .run(); + + // 2) Store the CLI options into the variable map + po::store(parsed_options, vm_); po::notify(vm_); if (vm_.count("help")) @@ -166,6 +173,10 @@ ServerOptions::ServerOptions(int argc, char* argv[], ServerEnvironment* env) { #endif } +ServerOptions::ServerOptions(int argc, char* argv[], ServerEnvironment* env) + : ServerOptions(CommandLine(argc, argv), env) { +} + bool ServerOptions::help_option() const { if (vm_.count("help")) return true; diff --git a/libs/server/src/ecflow/server/ServerOptions.hpp b/libs/server/src/ecflow/server/ServerOptions.hpp index a58ef57f4..2861b0d61 100644 --- a/libs/server/src/ecflow/server/ServerOptions.hpp +++ b/libs/server/src/ecflow/server/ServerOptions.hpp @@ -17,10 +17,13 @@ /// #include + +#include "ecflow/core/CommandLine.hpp" class ServerEnvironment; class ServerOptions { public: + ServerOptions(const CommandLine& cl, ServerEnvironment*); ServerOptions(int argc, char* argv[], ServerEnvironment*); // Disable copy (and move) semantics ServerOptions(const ServerOptions&) = delete; diff --git a/libs/server/test/TestServerConfigurations.cpp b/libs/server/test/TestServerConfigurations.cpp new file mode 100644 index 000000000..fd3b28142 --- /dev/null +++ b/libs/server/test/TestServerConfigurations.cpp @@ -0,0 +1,163 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include + +#include "ecflow/base/cts/user/CtsCmd.hpp" +#include "ecflow/client/ClientInvoker.hpp" +#include "ecflow/core/CommandLine.hpp" +#include "ecflow/core/Filesystem.hpp" +#include "ecflow/core/Host.hpp" +#include "ecflow/server/ServerEnvironment.hpp" +#include "ecflow/test/scaffold/Provisioning.hpp" + +class MockServerInvoker { +public: + explicit MockServerInvoker(const std::string& commandline) : env_(CommandLine(commandline)) {} + + const ServerEnvironment& environment() const { return env_; }; + +private: + ServerEnvironment env_; +}; + +BOOST_AUTO_TEST_SUITE(U_Server) + +/* + * The following exports an environment variable used for tests, which changes the location of the SSL certificates. + * Instead of the default location (HOME/.ecflowrc/ssl), we use the current test directory. + */ +WithTestEnvironmentVariable ecf_ssl_dir("ECF_SSL_DIR", "./"); + +std::string somehost = ecf::Host().name(); +std::string someport = "31415"; +std::string someuser = "someuser"; +std::string somepass = "somepass"; + +BOOST_AUTO_TEST_SUITE(T_ServerConfiguration) + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_shared__options_none__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", "1"); + + MockServerInvoker server("ecflow_server -d"); + const ServerEnvironment& env = server.environment(); + + BOOST_CHECK_EQUAL(std::to_string(env.port()), someport); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_specific__options_none__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", somehost + '.' + someport); + + MockServerInvoker server("ecflow_server -d"); + const ServerEnvironment& env = server.environment(); + + BOOST_CHECK_EQUAL(std::to_string(env.port()), someport); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); + + // Note: + // The actual value of ECF_SSL is not used find the specific certificate. + // Instead, the host name is resolved by the OS, and the selected port is used. +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_shared__options_none__certificates_shared_only) { + WithTestFile shared_crt("server.crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", "1"); + + MockServerInvoker server("ecflow_server -d"); + const ServerEnvironment& env = server.environment(); + + BOOST_CHECK_EQUAL(std::to_string(env.port()), someport); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_specific__options_none__certificates_shared_only) { + WithTestFile shared_crt("server.crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", somehost + '.' + someport); + + BOOST_CHECK_THROW(MockServerInvoker server("ecflow_server -d"), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_shared__options_none__certificates_specific_only) { + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", "1"); + + BOOST_CHECK_THROW(MockServerInvoker server("ecflow_server -d"), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_specific__options_none__certificates_specific_only) { + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + WithTestEnvironmentVariable ecf_ssl("ECF_SSL", somehost + '.' + someport); + + MockServerInvoker server("ecflow_server -d"); + const ServerEnvironment& env = server.environment(); + + BOOST_CHECK_EQUAL(std::to_string(env.port()), someport); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_ssl__certificates_shared_and_specific) { + WithTestFile shared_crt("server.crt"); + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + + MockServerInvoker server("ecflow_server -d --ssl"); + const ServerEnvironment& env = server.environment(); + + BOOST_CHECK_EQUAL(std::to_string(env.port()), someport); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); + + // Note: + // When only using the command line option, if both shared and specific certificates are available, + // there is no way to select the specific certificate. +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_ssl__certificates_shared_only) { + WithTestFile shared_crt("server.crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + + MockServerInvoker server("ecflow_server -d --ssl"); + const ServerEnvironment& env = server.environment(); + + BOOST_CHECK_EQUAL(std::to_string(env.port()), someport); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), "1"); +} + +BOOST_AUTO_TEST_CASE(can_setup_environment__env_request_none__options_ssl__certificates_specific_only) { + WithTestFile specific_crt(somehost + '.' + someport + ".crt"); + WithTestEnvironmentVariable ecf_port("ECF_PORT", someport); + + MockServerInvoker server("ecflow_server -d --ssl"); + const ServerEnvironment& env = server.environment(); + + BOOST_CHECK_EQUAL(std::to_string(env.port()), someport); + BOOST_CHECK(env.ssl()); + BOOST_CHECK_EQUAL(env.openssl().ssl(), somehost + '.' + someport); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END()