From 9876f2d94f50949a282aabf5f50b266be00fff8c Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 4 Nov 2024 17:58:20 +0000 Subject: [PATCH 01/33] Add FindCrypt.cmake module This allows to find libcrypt.so by full path, and avoid being dependent on the compiler directory search. Re ECFLOW-1987 --- cmake/Dependencies.cmake | 12 +++++++ cmake/FindCrypt.cmake | 76 ++++++++++++++++++++++++++++++++++++++++ libs/CMakeLists.txt | 2 +- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 cmake/FindCrypt.cmake diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index dc745a7bd..4645fa718 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -42,3 +42,15 @@ if (ENABLE_HTTP_COMPRESSION) message(FATAL_ERROR "HTTP compression support requested, but zlib was not found") endif () endif () + +# ========================================================================================= +# Crypt +# ========================================================================================= +ecbuild_info( "Locating Crypt" ) + +find_package(Crypt) + +ecbuild_info( "Crypt details:" ) +ecbuild_info( " * Crypt_FOUND : ${Crypt_FOUND}" ) +ecbuild_info( " * Crypt_INCLUDE_DIRS : ${Crypt_INCLUDE_DIRS}" ) +ecbuild_info( " * Crypt_LIBRARIES : ${Crypt_LIBRARIES}" ) diff --git a/cmake/FindCrypt.cmake b/cmake/FindCrypt.cmake new file mode 100644 index 000000000..a91f7cbbc --- /dev/null +++ b/cmake/FindCrypt.cmake @@ -0,0 +1,76 @@ +# +# 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. +# + +# FindCrypt +# --------- +# + +# +# Find Crypt library and include dirs +# +# Use this module by invoking find_package with the form: +# +# find_package(Crypt +# [REQUIRED] # Fail with error if library is not found +# ) +# +# This module finds headers and libraries, specifying the following variables: +# +# Crypt_FOUND - True if library is found +# Crypt_INCLUDE_DIRS - Include directories to be used +# Crypt_DEFINITIONS - Compiler flags to be used +# +# The following `IMPORTED` targets are also defined: +# +# crypt::crypt - Generic target for the Crypt library +# +# + +# +# ----------------------------------------------------------------------------- +# Search for include DIRs +# ----------------------------------------------------------------------------- + +find_path(Crypt_INCLUDE_DIRS + NAMES unistd.h + HINTS + /usr/include) + +find_library(Crypt_LIBRARIES + NAMES libcrypt.so + HINTS + /usr/lib) + +# +# ----------------------------------------------------------------------------- +# Handle find_package() REQUIRED and QUIET parameters +# ----------------------------------------------------------------------------- + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Crypt + REQUIRED_VARS + Crypt_INCLUDE_DIRS + Crypt_LIBRARIES) + +# +# ----------------------------------------------------------------------------- +# Define library as exported targets +# ----------------------------------------------------------------------------- + +set(NAME "crypt") + +add_library(${NAME} INTERFACE IMPORTED GLOBAL) + +set_target_properties(${NAME} + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Crypt_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${Crypt_LIBRARIES}") + +add_library(crypt::crypt ALIAS crypt) diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 57caa08c5..11dfeabb3 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -566,7 +566,7 @@ ecbuild_add_library( Boost::date_time Boost::program_options $<$:OpenSSL::SSL> - $<$>:crypt> + $<$:crypt::crypt> Threads::Threads DEFINITIONS CMAKE From 68ee8159c23c4a2b1f228cafab7e93911079ed3e Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 11 Nov 2024 08:47:22 +0000 Subject: [PATCH 02/33] Avoid reinitialization of ClientInvoker --- libs/service/src/ecflow/service/mirror/MirrorClient.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/service/src/ecflow/service/mirror/MirrorClient.cpp b/libs/service/src/ecflow/service/mirror/MirrorClient.cpp index d129a742e..45899722c 100644 --- a/libs/service/src/ecflow/service/mirror/MirrorClient.cpp +++ b/libs/service/src/ecflow/service/mirror/MirrorClient.cpp @@ -43,7 +43,6 @@ MirrorData MirrorClient::get_node_status(const std::string& remote_host, SLOG(D, "MirrorClient: Authentication Credentials: " << remote_username << ":" << remote_password); try { - impl_ = std::make_unique(); impl_->invoker_.set_host_port(remote_host, remote_port); if (ssl) { impl_->invoker_.enable_ssl(); From 27d1cdcd820c8d2eb18b5b309029c849caf5e7a5 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 11 Nov 2024 10:23:02 +0000 Subject: [PATCH 03/33] Use handles to avoid unnecessary memory usage/leak --- .../ecflow/service/mirror/MirrorClient.cpp | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/libs/service/src/ecflow/service/mirror/MirrorClient.cpp b/libs/service/src/ecflow/service/mirror/MirrorClient.cpp index 45899722c..582e456db 100644 --- a/libs/service/src/ecflow/service/mirror/MirrorClient.cpp +++ b/libs/service/src/ecflow/service/mirror/MirrorClient.cpp @@ -20,12 +20,24 @@ #include "ecflow/service/Log.hpp" namespace ecf::service::mirror { -/* MirrorClient */ + +namespace { + std::string get_suite_name(const std::string& node_path) { + std::string trimmed = node_path.substr(1); + trimmed = trimmed.substr(0, trimmed.find('/')); + return trimmed; + } +} struct MirrorClient::Impl { - std::shared_ptr defs_; ClientInvoker invoker_; + bool initialized_ = false; + + ~Impl() { + // Release Suite filter handle + invoker_.ch1_drop(); + } }; MirrorClient::MirrorClient() : impl_(std::make_unique()) { @@ -43,27 +55,34 @@ MirrorData MirrorClient::get_node_status(const std::string& remote_host, SLOG(D, "MirrorClient: Authentication Credentials: " << remote_username << ":" << remote_password); try { - impl_->invoker_.set_host_port(remote_host, remote_port); - if (ssl) { - impl_->invoker_.enable_ssl(); - } - if (!remote_username.empty()) { - impl_->invoker_.set_user_name(remote_username); - } - if (!remote_password.empty()) { - // Extremely important: the password actually needs to be encrypted before being set in the invoker! - impl_->invoker_.set_password(PasswordEncryption::encrypt(remote_password, remote_username)); + if (!impl_->initialized_) { + // Setup Access/Authentication + impl_->invoker_.set_host_port(remote_host, remote_port); + if (ssl) { + impl_->invoker_.enable_ssl(); + } + if (!remote_username.empty()) { + impl_->invoker_.set_user_name(remote_username); + } + if (!remote_password.empty()) { + // Extremely important: the password actually needs to be encrypted before being set in the invoker! + impl_->invoker_.set_password(PasswordEncryption::encrypt(remote_password, remote_username)); + } + // Setup Suite filter handle + auto selected_suite = get_suite_name(node_path); + impl_->invoker_.ch1_register(false, std::vector{selected_suite}); } SLOG(D, "MirrorClient: retrieving the latest defs"); - impl_->invoker_.sync(impl_->defs_); + impl_->invoker_.sync_local(); - if (!impl_->defs_) { + auto defs = impl_->invoker_.defs(); + if (!defs) { SLOG(E, "MirrorClient: unable to sync with remote defs"); throw std::runtime_error("MirrorClient: Failed to sync with remote defs"); } - auto node = impl_->defs_->findAbsNode(node_path); + auto node = defs->findAbsNode(node_path); if (!node) { throw std::runtime_error( From 0db490b80ce0922477cd8c216450ac33d139e4e9 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 14 Nov 2024 07:42:42 +0000 Subject: [PATCH 04/33] Enable reloading of Mirror configuration Mirror attribute is reset with the latest configuration when "reload" is used as value on a Alter Mirror command Re ECFLOW-1986 --- libs/node/src/ecflow/node/MirrorAttr.cpp | 16 +++++++++++++--- libs/node/src/ecflow/node/MirrorAttr.hpp | 9 +++++++++ libs/node/src/ecflow/node/NodeChange.cpp | 14 +++++++++----- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/libs/node/src/ecflow/node/MirrorAttr.cpp b/libs/node/src/ecflow/node/MirrorAttr.cpp index bce3abbcd..4cfedfb0e 100644 --- a/libs/node/src/ecflow/node/MirrorAttr.cpp +++ b/libs/node/src/ecflow/node/MirrorAttr.cpp @@ -74,7 +74,16 @@ void MirrorAttr::reset() { start_controller(); } +void MirrorAttr::reload() { + if (controller_) { + state_change_no_ = Ecf::incr_state_change_no(); + stop_controller(); + start_controller(); + } +} + void MirrorAttr::finish() { + state_change_no_ = Ecf::incr_state_change_no(); stop_controller(); } @@ -190,7 +199,7 @@ std::string MirrorAttr::resolve_cfg(const std::string& value, } void MirrorAttr::start_controller() { - if (controller_ == nullptr) { + if (!controller_) { // Resolve variables in configuration // In the case of the 'remote_host', we have to resolve the configuration @@ -217,7 +226,8 @@ void MirrorAttr::start_controller() { SLOG(D, "MirrorAttr: start polling Mirror attribute '" << absolute_name() << "', from " << remote_path_ << " @ " - << remote_host << ':' << remote_port << ")"); + << remote_host << ':' << remote_port << ") using polling: " + << polling << " s"); std::uint32_t polling_value; try { @@ -247,7 +257,7 @@ void MirrorAttr::start_controller() { } void MirrorAttr::stop_controller() { - if (controller_ != nullptr) { + if (controller_) { SLOG(D, "MirrorAttr: finishing polling for Mirror attribute \"" << parent_->absNodePath() << ":" << name_ << "\", from host: " << remote_host_ diff --git a/libs/node/src/ecflow/node/MirrorAttr.hpp b/libs/node/src/ecflow/node/MirrorAttr.hpp index 45786fa7f..199bc73a3 100644 --- a/libs/node/src/ecflow/node/MirrorAttr.hpp +++ b/libs/node/src/ecflow/node/MirrorAttr.hpp @@ -59,6 +59,8 @@ class MirrorAttr { static constexpr const char* fallback_polling = "120"; static constexpr const char* fallback_remote_auth = ""; + static constexpr const char* reload_option_value = "reload"; + static bool is_valid_name(const std::string& name); /** @@ -104,8 +106,15 @@ class MirrorAttr { /** * Initialises the Mirror procedure, which effectively starts the background polling mechanism. + * Typically, called when traversing the tree -- does nothing if Mirror service is already set up. */ void reset(); + /** + * Restarts the Mirror procedure, which effectively stops before restarting the background polling mechanism. + * Typicallly, called explicitly via Alter command -- forces the reinitialisation of the Mirror service, + * guaranteeing that parameters, given as ECF variables, are reevaluated. + */ + void reload(); void finish(); /** diff --git a/libs/node/src/ecflow/node/NodeChange.cpp b/libs/node/src/ecflow/node/NodeChange.cpp index 8abbe8e4d..8befadb15 100644 --- a/libs/node/src/ecflow/node/NodeChange.cpp +++ b/libs/node/src/ecflow/node/NodeChange.cpp @@ -201,11 +201,15 @@ void Node::changeMirror(const std::string& name, const std::string& value) { throw std::runtime_error("Node::changeMirror: Could not find mirror " + name); } - auto attr = MirrorParser::parse_mirror_line(value, name, this); - - // The following delete/add enforces the reconfiguration of the attribute backend thread - this->deleteMirror(name); // delete the mirror if it exists (to avoid duplicates - this->addMirror(attr); + if (value == MirrorAttr::reload_option_value) { + found->reload(); + } else { + auto attr = MirrorParser::parse_mirror_line(value, name, this); + + // The following delete/add enforces the reconfiguration of the attribute backend thread + this->deleteMirror(name); // delete the mirror if it exists (to avoid duplicates + this->addMirror(attr); + } state_change_no_ = Ecf::incr_state_change_no(); } From 9948ab57f4277a814c6cafe5fdf35b61ac222267 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 14 Nov 2024 07:44:00 +0000 Subject: [PATCH 05/33] Enable reloading of Aviso configuration Re ECFLOW-1986 --- libs/node/src/ecflow/node/AvisoAttr.cpp | 8 ++++++++ libs/node/src/ecflow/node/AvisoAttr.hpp | 13 +++++++++++++ libs/node/src/ecflow/node/NodeChange.cpp | 6 +++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/libs/node/src/ecflow/node/AvisoAttr.cpp b/libs/node/src/ecflow/node/AvisoAttr.cpp index a5c5eb369..321aebac5 100644 --- a/libs/node/src/ecflow/node/AvisoAttr.cpp +++ b/libs/node/src/ecflow/node/AvisoAttr.cpp @@ -93,6 +93,14 @@ void AvisoAttr::reset() { } } +void AvisoAttr::reload() { + if (controller_) { + state_change_no_ = Ecf::incr_state_change_no(); + finish(); + start(); + } +} + bool AvisoAttr::isFree() const { if (controller_ == nullptr) { diff --git a/libs/node/src/ecflow/node/AvisoAttr.hpp b/libs/node/src/ecflow/node/AvisoAttr.hpp index 4c8e33245..aa11e392b 100644 --- a/libs/node/src/ecflow/node/AvisoAttr.hpp +++ b/libs/node/src/ecflow/node/AvisoAttr.hpp @@ -56,6 +56,8 @@ class AvisoAttr { static constexpr const char* default_polling = "%ECF_AVISO_POLLING%"; static constexpr const char* default_auth = "%ECF_AVISO_AUTH%"; + static constexpr const char* reload_option_value = "reload"; + static bool is_valid_name(const std::string& name); /** @@ -98,8 +100,19 @@ class AvisoAttr { bool why(std::string& theReasonWhy) const; + /** + * Initialises the Aviso procedure, which effectively starts the background polling mechanism. + * Typically, called when traversing the tree -- does nothing if Aviso service is already set up. + */ void reset(); + /** + * Restarts the Aviso procedure, which effectively stops before restarting the background polling mechanism. + * Typicallly, called explicitly via Alter command -- forces the reinitialisation of the Aviso service, + * guaranteeing that parameters, given as ECF variables, are reevaluated. + */ + void reload(); + [[nodiscard]] bool isFree() const; void start() const; diff --git a/libs/node/src/ecflow/node/NodeChange.cpp b/libs/node/src/ecflow/node/NodeChange.cpp index 8befadb15..6f31e9c4f 100644 --- a/libs/node/src/ecflow/node/NodeChange.cpp +++ b/libs/node/src/ecflow/node/NodeChange.cpp @@ -172,7 +172,11 @@ void Node::changeAviso(const std::string& name, const std::string& value) { throw std::runtime_error("Node::changeAviso: Could not find aviso " + name); } - *found = AvisoParser::parse_aviso_line(value, name); + if (value == AvisoAttr::reload_option_value) { + found->reload(); + } else { + *found = AvisoParser::parse_aviso_line(value, name); + } state_change_no_ = Ecf::incr_state_change_no(); } From 5a4ebc1963e85ec974f4d72ffa81768070ef0907 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 14 Nov 2024 07:44:40 +0000 Subject: [PATCH 06/33] Update documentation Re ECFLOW-1986 --- docs/client_api/api/alter.rst | 5 +++- docs/glossary.rst | 25 ++++++++++++++----- .../src/ecflow/base/cts/user/AlterCmd.cpp | 5 +++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/client_api/api/alter.rst b/docs/client_api/api/alter.rst index 909f1305a..6495c1e41 100644 --- a/docs/client_api/api/alter.rst +++ b/docs/client_api/api/alter.rst @@ -20,7 +20,7 @@ alter For change: [ variable | clock_type | clock_gain | clock_date | clock_sync | event | meter | label | trigger | complete | repeat | limit_max | limit_value | defstatus | late | time | - today, aviso, mirror ] + today | aviso | mirror ] *NOTE* If the clock is changed, then the suite will need to be re-queued in order for the change to take effect fully. For add: @@ -47,6 +47,9 @@ alter * for mirror, "--listener '{ \"event\": \"mars\", \"request\": { \"class\": "od" } }' --url http://aviso/ --schema /path/to/schema --polling 60" + For both aviso and mirror, the special value "reload" can be used to force reloading the configuration. + n.b. This is typically useful after updating variables used to configure these kind of attributes. + Usage: ecflow_client --alter=add variable GLOBAL "value" / # add server variable diff --git a/docs/glossary.rst b/docs/glossary.rst index 57f92e111..5c721dfc5 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -82,9 +82,15 @@ the user. Each aviso attribute implies that a background thread is spawned whenever - the associated :term:`node` is (re)queued. This background thread is - responsible for polling the Aviso server, and periodically processing the - latest notifications. + the associated :term:`node` is (re)queued. This independent background thread, + responsible for polling the Aviso server and periodically processing the latest notifications, + uses the configuriguration available when the associated task is queued. + + .. note:: + + If any variables provinding the configuration are updated, the Aviso configuration + can be reloaded (without unqueuing the Task) by issuing an Alter change command with + the value :code:`reload` to the relevant Aviso attribute. The authentication credentials file is expected to be in JSON format, following the `ECMWF Web API `_: @@ -1424,9 +1430,16 @@ (empty string), which effectively disables Authentication Each mirror attribute implies that a background thread is spawned whenever - the ecFlow server is :term:`running`. This background thread is - responsible for polling the remote ecFlow server, and periodically - synchronise node status. + the ecFlow server is :term:`running` (i.e. when the server is shutdown or halted the + thread is terminated and the mirroring process is completely stopped). + This independent background thread, responsible for polling the remote ecFlow server and periodically + synchronise node status, uses the configuration available when the server is restarted. + + .. note:: + + If any variables provinding the configuration are updated, the Mirror configuration can be + reloaded (without restarting the Server) by issuing an Alter change command with the value + :code:`reload` to the relevant attributes. The authentication credentials file is expected to be in JSON, according to the following format: diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp index 517c736cc..c10f6ab47 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp @@ -613,7 +613,7 @@ const char* AlterCmd::desc() { " For change:\n" " [ variable | clock_type | clock_gain | clock_date | clock_sync | event | meter | label |\n" " trigger | complete | repeat | limit_max | limit_value | defstatus | late | time |\n" - " today, aviso, mirror ]\n" + " today | aviso | mirror ]\n" " *NOTE* If the clock is changed, then the suite will need to be re-queued in order for\n" " the change to take effect fully.\n" " For add:\n" @@ -640,6 +640,9 @@ const char* AlterCmd::desc() { " * for mirror, \"--listener '{ \\\"event\\\": \\\"mars\\\", \\\"request\\\": { \\\"class\\\": \"od\" } }'\n" " --url http://aviso/ --schema /path/to/schema --polling 60\"\n" "\n" + "For both aviso and mirror, the special value \"reload\" can be used to force reloading the configuration.\n" + " n.b. This is typically useful after updating variables used to configure these kind of attributes.\n" + "\n" "Usage:\n\n" " ecflow_client --alter=add variable GLOBAL \"value\" / # add server variable\n" " ecflow_client --alter=add variable FRED \"value\" /path/to/node # add node variable\n" From 91730eb7c14072e2d7de05281ca14e88d3aa63bd Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 14 Nov 2024 11:11:54 +0000 Subject: [PATCH 07/33] Avoid crash when logging from Mirror/Aviso threads This ensures that only one thread is able to write to the log at any given instant Re ECFLOW-1986 --- libs/core/src/ecflow/core/Log.cpp | 61 +++++++++++++++++++------------ libs/core/src/ecflow/core/Log.hpp | 3 ++ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/libs/core/src/ecflow/core/Log.cpp b/libs/core/src/ecflow/core/Log.cpp index e742b4926..4ebdae6e1 100644 --- a/libs/core/src/ecflow/core/Log.cpp +++ b/libs/core/src/ecflow/core/Log.cpp @@ -35,8 +35,9 @@ void Log::create(const std::string& filename) { } void Log::destroy() { - if (instance_) + if (instance_) { instance_->flush(); + } delete instance_; instance_ = nullptr; @@ -52,11 +53,9 @@ void Log::create_logimpl() { } bool Log::log(Log::LogType lt, const std::string& message) { - create_logimpl(); + std::lock_guard lock(mx_); - // if (!logImpl_->log_open_error().empty()) { - // cerr << "Log::log: " << message << "\n"; - // } + create_logimpl(); if (!logImpl_->log(lt, message)) { // handle write failure and Get the failure reason. This will delete logImpl_ & recreate @@ -70,11 +69,9 @@ bool Log::log(Log::LogType lt, const std::string& message) { } bool Log::log_no_newline(Log::LogType lt, const std::string& message) { - create_logimpl(); + std::lock_guard lock(mx_); - // if (!logImpl_->log_open_error().empty()) { - // cerr << "Log::log_no_newline : " << message << "\n"; - // } + create_logimpl(); if (!logImpl_->log_no_newline(lt, message)) { // handle write failure and Get the failure reason. This will delete logImpl_ & recreate @@ -88,11 +85,9 @@ bool Log::log_no_newline(Log::LogType lt, const std::string& message) { } bool Log::append(const std::string& message) { - create_logimpl(); + std::lock_guard lock(mx_); - // if (!logImpl_->log_open_error().empty()) { - // cerr << "Log::append : " << message << "\n"; - // } + create_logimpl(); if (!logImpl_->append(message)) { // handle write failure and Get the failure reason. This will delete logImpl_ & recreate @@ -106,26 +101,36 @@ bool Log::append(const std::string& message) { } void Log::cache_time_stamp() { + std::lock_guard lock(mx_); + create_logimpl(); logImpl_->create_time_stamp(); } const std::string& Log::get_cached_time_stamp() const { + std::lock_guard lock(mx_); + return (logImpl_) ? logImpl_->get_cached_time_stamp() : Str::EMPTY(); } void Log::flush() { + std::lock_guard lock(mx_); + // will close ofstream and force data to be written to disk. // Forcing writing to physical medium can't be guaranteed though! logImpl_.reset(); } void Log::flush_only() { - if (logImpl_) + std::lock_guard lock(mx_); + + if (logImpl_) { logImpl_->flush(); + } } void Log::clear() { + std::lock_guard lock(mx_); flush(); // Open and truncate the file. @@ -136,6 +141,8 @@ void Log::clear() { } void Log::new_path(const std::string& the_new_path) { + std::lock_guard lock(mx_); + check_new_path(the_new_path); // flush and close log file @@ -174,6 +181,8 @@ void Log::check_new_path(const std::string& new_path) { } std::string Log::path() const { + std::lock_guard lock(mx_); + if (!fileName_.empty() && fileName_[0] == '/') { // Path is absolute return as is return fileName_; @@ -185,6 +194,8 @@ std::string Log::path() const { } std::string Log::contents(int get_last_n_lines) { + std::lock_guard lock(mx_); + if (get_last_n_lines == 0) { return string(); } @@ -200,6 +211,8 @@ std::string Log::contents(int get_last_n_lines) { } std::string Log::handle_write_failure() { + std::lock_guard lock(mx_); + std::string msg = logImpl_->log_open_error(); if (msg.empty()) { msg += "\nFailed to write to log file: "; @@ -213,22 +226,20 @@ std::string Log::handle_write_failure() { logImpl_.reset(); create_logimpl(); - if (logImpl_->log_open_error().empty()) + if (logImpl_->log_open_error().empty()) { msg += "\nAttempting to close/reopen log file."; - else + } + else { msg += "\nAttempting to close/reopen log file did not work!"; + } - if (LogToCout::ok()) + if (LogToCout::ok()) { Indentor::indent(cout) << msg << '\n'; + } return msg; } bool log(Log::LogType lt, const std::string& message) { - // For debug of simulator enable this - // if (LogToCout::ok()) { - // Indentor::indent(cout) << message << '\n'; - // } - if (Log::instance()) { return Log::instance()->log(lt, message); } @@ -367,8 +378,9 @@ bool LogImpl::do_log(Log::LogType lt, const std::string& message, bool newline) // XXX:[HH:MM:SS D.M.YYYY] chd:fullname [+additional information] // XXX:[HH:MM:SS D.M.YYYY] -- [+additional information] - if (time_stamp_.empty() || lt == Log::ERR || lt == Log::WAR || lt == Log::DBG) + if (time_stamp_.empty() || lt == Log::ERR || lt == Log::WAR || lt == Log::DBG) { create_time_stamp(); + } // re-use memory allocated to log_type_and_time_stamp_ log_type_and_time_stamp_.clear(); @@ -377,8 +389,9 @@ bool LogImpl::do_log(Log::LogType lt, const std::string& message, bool newline) if (message.find("\n") == std::string::npos) { file_ << log_type_and_time_stamp_ << message; - if (newline) + if (newline) { file_ << '\n'; + } } else { // If message has \n then split into multiple lines diff --git a/libs/core/src/ecflow/core/Log.hpp b/libs/core/src/ecflow/core/Log.hpp index bf6f228a9..b88c66897 100644 --- a/libs/core/src/ecflow/core/Log.hpp +++ b/libs/core/src/ecflow/core/Log.hpp @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -121,6 +122,8 @@ class Log { std::unique_ptr logImpl_; std::string fileName_; std::string log_error_; + + mutable std::recursive_mutex mx_; }; // Flush log on destruction From 349c1acb6756ef738cb54200667ff2bc2a2d4dbb Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 14 Nov 2024 11:16:51 +0000 Subject: [PATCH 08/33] Terminate Mirror/Aviso background threads also when server is shutdown Before this change, the background threads were only destroyed when the server state changed from running -> halted. This introduced an issue, as changing state from halted -> shutdown and running -> shutdown would allow reaching the shutdown state with sometimes running threads. Re ECFLOW-1986 --- libs/server/src/ecflow/server/BaseServer.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libs/server/src/ecflow/server/BaseServer.cpp b/libs/server/src/ecflow/server/BaseServer.cpp index 40370a826..3ce8e9fe7 100644 --- a/libs/server/src/ecflow/server/BaseServer.cpp +++ b/libs/server/src/ecflow/server/BaseServer.cpp @@ -323,8 +323,9 @@ void BaseServer::shutdown() { /// RUNNING yes yes yes yes /// SHUTDOWN yes yes no yes /// HALTED yes no no no - if (serverEnv_.debug()) + if (serverEnv_.debug()) { cout << " BaseServer::shutdown. Stop Scheduling new jobs only" << endl; + } // Stop server from creating new jobs. Don't stop the checkPtSaver_ since // the jobs communication with server can still change state. Which we want @@ -336,6 +337,9 @@ void BaseServer::shutdown() { // If we go from HALTED --> SHUTDOWN, then check pointing needs to be enabled checkPtSaver_.start(); + // Stop all Mirror/Aviso attributes (i.e. background threads are stopped) + ecf::visit_all(*defs_, ShutdownDefs{}); + // Will update defs as well to stop job scheduling set_server_state(SState::SHUTDOWN); } @@ -345,10 +349,11 @@ void BaseServer::halted() { /// RUNNING yes yes yes yes /// SHUTDOWN yes yes no yes /// HALTED yes no no no - if (serverEnv_.debug()) + if (serverEnv_.debug()) { cout << " BaseServer::halted. Stop Scheduling new jobs *and* block task communication. Stop check pointing. " "Only accept user request" << endl; + } // Stop server from creating new jobs. i.e Job scheduling. traverser_.stop(); @@ -360,6 +365,7 @@ void BaseServer::halted() { // Added after discussion with Axel. checkPtSaver_.stop(); + // Stop all Mirror/Aviso attributes (i.e. background threads are stopped) ecf::visit_all(*defs_, ShutdownDefs{}); // Stop the task communication with server. Hence nodes can be stuck @@ -374,8 +380,9 @@ void BaseServer::restart() { /// RUNNING yes yes yes yes /// SHUTDOWN yes yes no yes /// HALTED yes no no no - if (serverEnv_.debug()) + if (serverEnv_.debug()) { std::cout << " BaseServer::restart" << endl; + } // The server state *MUST* be set, *before* traverser_.start(), since that can kick off job traversal. // Job Scheduling can only be done under RUNNING state, hence must be before traverser_.start(); From 639a3cc0c34cfdfdadadb8de85e2d8ff5e01eaf3 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 5 Jul 2024 08:37:42 +0000 Subject: [PATCH 09/33] Automatic package building ECFLOW-1967 --- .github/workflows/cd.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/cd.yml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 000000000..56034fd79 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,16 @@ +name: cd + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + + # Trigger the workflow manually + workflow_dispatch: ~ + +jobs: + deploy: + uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@v2 + with: + skip_checks: true + secrets: inherit From 01753e50a7f28a957d8a4032938dee582db5f8d2 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 5 Jul 2024 08:43:32 +0000 Subject: [PATCH 10/33] Automatic package building ECFLOW-1967 --- .github/workflows/cd.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 56034fd79..79e7988dd 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,9 +1,10 @@ name: cd on: - push: - tags: - - '[0-9]+.[0-9]+.[0-9]+' + push + #push: + # tags: + # - '[0-9]+.[0-9]+.[0-9]+' # Trigger the workflow manually workflow_dispatch: ~ From 26e4d8e66404e04c9f5ae9e22580712667a0c697 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 5 Jul 2024 08:44:51 +0000 Subject: [PATCH 11/33] Automatic package building ECFLOW-1967 --- .github/workflows/cd.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 79e7988dd..0eb13f1e5 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -5,7 +5,6 @@ on: #push: # tags: # - '[0-9]+.[0-9]+.[0-9]+' - # Trigger the workflow manually workflow_dispatch: ~ From fd73861792879d3c916f89da9bec3bef39528745 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 5 Jul 2024 08:52:20 +0000 Subject: [PATCH 12/33] Automatic package building ECFLOW-1967 --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0eb13f1e5..a87286bfc 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,7 +1,7 @@ name: cd on: - push + push: ~ #push: # tags: # - '[0-9]+.[0-9]+.[0-9]+' From 9a44d1a7bb3b45fbda18871ac10a1a248b53af76 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 16 Aug 2024 16:40:19 +0100 Subject: [PATCH 13/33] Automatic package building ECFLOW-1967 --- .github/ci-config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ci-config.yml b/.github/ci-config.yml index 81701b80e..165ebade0 100644 --- a/.github/ci-config.yml +++ b/.github/ci-config.yml @@ -1,4 +1,5 @@ -cmake_options: -DENABLE_ALL_TESTS=ON -DBOOST_ROOT=${BOOST_ROOT_DIR} -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF +cmake_options: -DENABLE_ALL_TESTS=ON +#-DBOOST_ROOT=${BOOST_ROOT_DIR} -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF ctest_options: -L nightly -E s_test|s_zombies dependencies: | ecmwf/ecbuild From d0f171c5b064972ffd94e23af5b505343f394099 Mon Sep 17 00:00:00 2001 From: Iain Russell <40060766+iainrussell@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:49:01 +0100 Subject: [PATCH 14/33] Automatic package building ECFLOW-1967 --- .github/ci-config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/ci-config.yml b/.github/ci-config.yml index 165ebade0..81701b80e 100644 --- a/.github/ci-config.yml +++ b/.github/ci-config.yml @@ -1,5 +1,4 @@ -cmake_options: -DENABLE_ALL_TESTS=ON -#-DBOOST_ROOT=${BOOST_ROOT_DIR} -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF +cmake_options: -DENABLE_ALL_TESTS=ON -DBOOST_ROOT=${BOOST_ROOT_DIR} -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF ctest_options: -L nightly -E s_test|s_zombies dependencies: | ecmwf/ecbuild From b1de19c2aa27a3fb14cd13c242fd91948c9ab704 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Thu, 19 Sep 2024 17:04:45 +0100 Subject: [PATCH 15/33] Automatic package building ECFLOW-1967 --- .github/cd-server-config.yml | 10 ++++++++++ .github/workflows/cd.yml | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .github/cd-server-config.yml diff --git a/.github/cd-server-config.yml b/.github/cd-server-config.yml new file mode 100644 index 000000000..82080cc7b --- /dev/null +++ b/.github/cd-server-config.yml @@ -0,0 +1,10 @@ +cmake_options: >- + -DBOOST_ROOT=${BOOST_ROOT_DIR} -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} + -DBoost_DEBUG=ON -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF + -DINSTALL_PYTHON3_DIR=lib/python3/dist-packages + -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=ON -DENABLE_UI=OFF +ctest_options: -L nightly -E s_test|s_zombies +dependencies: | + ecmwf/ecbuild +dependency_branch: develop +parallelism_factor: 8 diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a87286bfc..ea5a46ab2 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -10,7 +10,9 @@ on: jobs: deploy: - uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@v2 + uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@create-packages-custom-config with: skip_checks: true + restrict_matrix_jobs: debian12 + build_config: .github/cd-server-config.yml secrets: inherit From e51ac6b21cd87649e4636ccf95d97dcc1535844d Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Thu, 19 Sep 2024 17:06:08 +0100 Subject: [PATCH 16/33] Automatic package building ECFLOW-1967 --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ea5a46ab2..b3567ad6c 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -13,6 +13,6 @@ jobs: uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@create-packages-custom-config with: skip_checks: true - restrict_matrix_jobs: debian12 + restrict_matrix_jobs: debian-12 build_config: .github/cd-server-config.yml secrets: inherit From a62346166f7dfa836f43d81dfdd05b36b5021d5b Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Thu, 19 Sep 2024 17:07:52 +0100 Subject: [PATCH 17/33] Automatic package building ECFLOW-1967 --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index b3567ad6c..84f4600aa 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -13,6 +13,6 @@ jobs: uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@create-packages-custom-config with: skip_checks: true - restrict_matrix_jobs: debian-12 + restrict_matrix_jobs: gnu@debian-12 build_config: .github/cd-server-config.yml secrets: inherit From e861c2baafbbfc5e3e9eaed074a3b4bfe0bc34de Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 20 Sep 2024 16:09:25 +0100 Subject: [PATCH 18/33] Automatic package building ECFLOW-1967 --- .github/cd-ui-config.yml | 12 ++++++++++++ .github/workflows/cd.yml | 13 +++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 .github/cd-ui-config.yml diff --git a/.github/cd-ui-config.yml b/.github/cd-ui-config.yml new file mode 100644 index 000000000..a9d40c08c --- /dev/null +++ b/.github/cd-ui-config.yml @@ -0,0 +1,12 @@ +cmake_options: >- + -DBOOST_ROOT=${BOOST_ROOT_DIR} -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} + -DBoost_DEBUG=ON -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF + -DINSTALL_PYTHON3_DIR=lib/python3/dist-packages + -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=OFF -DENABLE_HTTP=OFF -DENABLE_UDP=OFF -DENABLE_PYTHON=OFF + -DENABLE_UI=ON -DUI_SYSTEM_SERVERS_LIST=/ec/vol/ecflow_def/servers.list.all + -DCMAKE_INSTALL_PREFIX=/opt/ecmwf/ecflow-ui +ctest_options: -L nightly -E s_test|s_zombies +dependencies: | + ecmwf/ecbuild +dependency_branch: develop +parallelism_factor: 8 diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 84f4600aa..8f3684351 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -9,10 +9,19 @@ on: workflow_dispatch: ~ jobs: - deploy: - uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@create-packages-custom-config + deploy-server: + uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@v2 with: skip_checks: true restrict_matrix_jobs: gnu@debian-12 build_config: .github/cd-server-config.yml secrets: inherit + + deploy-ui: + uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@v2 + with: + skip_checks: true + restrict_matrix_jobs: gnu@debian-12 + build_config: .github/cd-ui-config.yml + cpack_options: -DCPACK_PACKAGE_NAME=ecflow-ui + secrets: inherit From dc5801621062be85dc36858f83283179b57aa793 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 20 Sep 2024 16:49:30 +0100 Subject: [PATCH 19/33] Automatic package building ECFLOW-1967 --- .github/cd-ui-config.yml | 2 +- .github/workflows/cd.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/cd-ui-config.yml b/.github/cd-ui-config.yml index a9d40c08c..5f98aca77 100644 --- a/.github/cd-ui-config.yml +++ b/.github/cd-ui-config.yml @@ -4,8 +4,8 @@ cmake_options: >- -DINSTALL_PYTHON3_DIR=lib/python3/dist-packages -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=OFF -DENABLE_HTTP=OFF -DENABLE_UDP=OFF -DENABLE_PYTHON=OFF -DENABLE_UI=ON -DUI_SYSTEM_SERVERS_LIST=/ec/vol/ecflow_def/servers.list.all - -DCMAKE_INSTALL_PREFIX=/opt/ecmwf/ecflow-ui ctest_options: -L nightly -E s_test|s_zombies +install_dir: ${{ runner.temp }}/install/ecflow-ui dependencies: | ecmwf/ecbuild dependency_branch: develop diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 8f3684351..defba280d 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -21,7 +21,7 @@ jobs: uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@v2 with: skip_checks: true - restrict_matrix_jobs: gnu@debian-12 + restrict_matrix_jobs: gnu@rocky-8.6 build_config: .github/cd-ui-config.yml cpack_options: -DCPACK_PACKAGE_NAME=ecflow-ui secrets: inherit From be000472c46bd269baab41d045aacb3b68b43047 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 20 Sep 2024 16:51:21 +0100 Subject: [PATCH 20/33] Automatic package building ECFLOW-1967 --- .github/workflows/cd.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index defba280d..7a7860602 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -21,7 +21,8 @@ jobs: uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@v2 with: skip_checks: true - restrict_matrix_jobs: gnu@rocky-8.6 + #restrict_matrix_jobs: gnu@rocky-8.6 + restrict_matrix_jobs: gnu@debian-12 build_config: .github/cd-ui-config.yml cpack_options: -DCPACK_PACKAGE_NAME=ecflow-ui secrets: inherit From 477c18ff93463075afd3c6d801e07a82cad6a4a2 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 20 Sep 2024 16:54:03 +0100 Subject: [PATCH 21/33] Automatic package building ECFLOW-1967 --- .github/cd-ui-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/cd-ui-config.yml b/.github/cd-ui-config.yml index 5f98aca77..407f60c0a 100644 --- a/.github/cd-ui-config.yml +++ b/.github/cd-ui-config.yml @@ -5,7 +5,7 @@ cmake_options: >- -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=OFF -DENABLE_HTTP=OFF -DENABLE_UDP=OFF -DENABLE_PYTHON=OFF -DENABLE_UI=ON -DUI_SYSTEM_SERVERS_LIST=/ec/vol/ecflow_def/servers.list.all ctest_options: -L nightly -E s_test|s_zombies -install_dir: ${{ runner.temp }}/install/ecflow-ui +install_dir: "${{ runner.temp }}/install/ecflow-ui" dependencies: | ecmwf/ecbuild dependency_branch: develop From 0493878b39a052f8f9a91dbe572a05f17ce0bfcf Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Fri, 20 Sep 2024 17:23:53 +0100 Subject: [PATCH 22/33] Automatic package building ECFLOW-1967 --- .github/cd-ui-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/cd-ui-config.yml b/.github/cd-ui-config.yml index 407f60c0a..ad1e915ce 100644 --- a/.github/cd-ui-config.yml +++ b/.github/cd-ui-config.yml @@ -5,7 +5,7 @@ cmake_options: >- -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=OFF -DENABLE_HTTP=OFF -DENABLE_UDP=OFF -DENABLE_PYTHON=OFF -DENABLE_UI=ON -DUI_SYSTEM_SERVERS_LIST=/ec/vol/ecflow_def/servers.list.all ctest_options: -L nightly -E s_test|s_zombies -install_dir: "${{ runner.temp }}/install/ecflow-ui" +install_dir: "$RUNNER_TEMP/install/ecflow-ui" dependencies: | ecmwf/ecbuild dependency_branch: develop From 6abdd1a764986ec4f0534a88c5f5c77501920a01 Mon Sep 17 00:00:00 2001 From: Iain Russell Date: Sat, 21 Sep 2024 13:14:45 +0100 Subject: [PATCH 23/33] Automatic package building ECFLOW-1967 --- .github/cd-server-config.yml | 1 + .github/cd-ui-config.yml | 2 +- .github/workflows/cd.yml | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/cd-server-config.yml b/.github/cd-server-config.yml index 82080cc7b..2fc2a7a05 100644 --- a/.github/cd-server-config.yml +++ b/.github/cd-server-config.yml @@ -3,6 +3,7 @@ cmake_options: >- -DBoost_DEBUG=ON -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF -DINSTALL_PYTHON3_DIR=lib/python3/dist-packages -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=ON -DENABLE_UI=OFF + -DCPACK_PACKAGE_NAME=ecflow ctest_options: -L nightly -E s_test|s_zombies dependencies: | ecmwf/ecbuild diff --git a/.github/cd-ui-config.yml b/.github/cd-ui-config.yml index ad1e915ce..06b7c6d6e 100644 --- a/.github/cd-ui-config.yml +++ b/.github/cd-ui-config.yml @@ -4,8 +4,8 @@ cmake_options: >- -DINSTALL_PYTHON3_DIR=lib/python3/dist-packages -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=OFF -DENABLE_HTTP=OFF -DENABLE_UDP=OFF -DENABLE_PYTHON=OFF -DENABLE_UI=ON -DUI_SYSTEM_SERVERS_LIST=/ec/vol/ecflow_def/servers.list.all + -DCPACK_PACKAGE_NAME=ecflow-ui ctest_options: -L nightly -E s_test|s_zombies -install_dir: "$RUNNER_TEMP/install/ecflow-ui" dependencies: | ecmwf/ecbuild dependency_branch: develop diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 7a7860602..4d6aac02a 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -21,8 +21,8 @@ jobs: uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@v2 with: skip_checks: true - #restrict_matrix_jobs: gnu@rocky-8.6 - restrict_matrix_jobs: gnu@debian-12 + restrict_matrix_jobs: gnu@rocky-8.6 + #restrict_matrix_jobs: gnu@debian-12 build_config: .github/cd-ui-config.yml - cpack_options: -DCPACK_PACKAGE_NAME=ecflow-ui + #cpack_options: -DCPACK_PACKAGE_NAME=ecflow-ui secrets: inherit From c2cc15ea4aeef355bdd775a86418b278c8b0198d Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 14 Nov 2024 14:02:57 +0000 Subject: [PATCH 24/33] Remove commented lines in workflow file - List CMake options one per line Re ECFLOW-1967 --- .github/cd-server-config.yml | 13 ++++++++++--- .github/cd-ui-config.yml | 18 ++++++++++++++---- .github/workflows/cd.yml | 6 +----- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.github/cd-server-config.yml b/.github/cd-server-config.yml index 2fc2a7a05..4287075af 100644 --- a/.github/cd-server-config.yml +++ b/.github/cd-server-config.yml @@ -1,8 +1,15 @@ cmake_options: >- - -DBOOST_ROOT=${BOOST_ROOT_DIR} -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} - -DBoost_DEBUG=ON -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF + -DBOOST_ROOT=${BOOST_ROOT_DIR} + -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} + -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} + -DBoost_DEBUG=ON + -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} + -DENABLE_STATIC_BOOST_LIBS=OFF -DINSTALL_PYTHON3_DIR=lib/python3/dist-packages - -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=ON -DENABLE_UI=OFF + -DENABLE_ALL_TESTS=ON + -DENABLE_SSL=ON + -DENABLE_SERVER=ON + -DENABLE_UI=OFF -DCPACK_PACKAGE_NAME=ecflow ctest_options: -L nightly -E s_test|s_zombies dependencies: | diff --git a/.github/cd-ui-config.yml b/.github/cd-ui-config.yml index 06b7c6d6e..736ec9254 100644 --- a/.github/cd-ui-config.yml +++ b/.github/cd-ui-config.yml @@ -1,9 +1,19 @@ cmake_options: >- - -DBOOST_ROOT=${BOOST_ROOT_DIR} -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} - -DBoost_DEBUG=ON -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DENABLE_STATIC_BOOST_LIBS=OFF + -DBOOST_ROOT=${BOOST_ROOT_DIR} + -DBOOST_INCLUDEDIR=${BOOST_INCLUDE_DIR} + -DBOOST_LIBRARYDIR=${BOOST_LIB_DIR} + -DBoost_DEBUG=ON + -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} + -DENABLE_STATIC_BOOST_LIBS=OFF -DINSTALL_PYTHON3_DIR=lib/python3/dist-packages - -DENABLE_ALL_TESTS=ON -DENABLE_SSL=ON -DENABLE_SERVER=OFF -DENABLE_HTTP=OFF -DENABLE_UDP=OFF -DENABLE_PYTHON=OFF - -DENABLE_UI=ON -DUI_SYSTEM_SERVERS_LIST=/ec/vol/ecflow_def/servers.list.all + -DENABLE_ALL_TESTS=ON + -DENABLE_SSL=ON + -DENABLE_SERVER=OFF + -DENABLE_HTTP=OFF + -DENABLE_UDP=OFF + -DENABLE_PYTHON=OFF + -DENABLE_UI=ON + -DUI_SYSTEM_SERVERS_LIST=/ec/vol/ecflow_def/servers.list.all -DCPACK_PACKAGE_NAME=ecflow-ui ctest_options: -L nightly -E s_test|s_zombies dependencies: | diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 4d6aac02a..ed54cd3de 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -2,9 +2,7 @@ name: cd on: push: ~ - #push: - # tags: - # - '[0-9]+.[0-9]+.[0-9]+' + # Trigger the workflow manually workflow_dispatch: ~ @@ -22,7 +20,5 @@ jobs: with: skip_checks: true restrict_matrix_jobs: gnu@rocky-8.6 - #restrict_matrix_jobs: gnu@debian-12 build_config: .github/cd-ui-config.yml - #cpack_options: -DCPACK_PACKAGE_NAME=ecflow-ui secrets: inherit From d91bdaf51420dbdf05cc7cb70150006a005b9bc9 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Thu, 14 Nov 2024 16:14:17 +0000 Subject: [PATCH 25/33] Customise CD triggers - CD can be triggered manually, or automatically whenever a new tag is pushed - Changes to configuration also trigger an automatic CD run Re ECFLOW-1967 --- .github/workflows/cd.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ed54cd3de..2ffafde5b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,11 +1,18 @@ name: cd on: - push: ~ - # Trigger the workflow manually workflow_dispatch: ~ + push: + # Trigger the workflow when new tags are pushed + tags: + - '**' + # Trigger the workflow when the CD workflow/configuration is updated + paths: + - .github/cd-*.yml + - .github/workflows/cd.yml + jobs: deploy-server: uses: ecmwf-actions/reusable-workflows/.github/workflows/create-package.yml@v2 From 1650fc9845ccd40afb886719aef274f4d845f94e Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 28 Oct 2024 11:08:34 +0000 Subject: [PATCH 26/33] Enable SSL only after processing CLI options Re ECFLOW-1985 --- .../src/ecflow/client/ClientEnvironment.cpp | 22 ++++++------------- .../src/ecflow/client/ClientEnvironment.hpp | 3 --- .../src/ecflow/client/ClientInvoker.hpp | 1 - .../src/ecflow/client/ClientOptions.cpp | 18 ++++++++++++--- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/libs/client/src/ecflow/client/ClientEnvironment.cpp b/libs/client/src/ecflow/client/ClientEnvironment.cpp index 0511895bd..fd914ee23 100644 --- a/libs/client/src/ecflow/client/ClientEnvironment.cpp +++ b/libs/client/src/ecflow/client/ClientEnvironment.cpp @@ -169,12 +169,13 @@ void ClientEnvironment::set_host_port(const std::string& the_host, const std::st // When there is only one host:port in host_vec_, calling get_next_host() will always return host_vec_[0] host_file_read_ = true; -#ifdef ECF_OPENSSL - // Must be done *AFTER* host and port set - // Avoid enabling SSL for the GUI, via environment, this must be done explicitly by the GUI - if (!gui_) - enable_ssl_if_defined(); -#endif + // Caution: + // + // We don't (re)enable SSL immediatelly after setting host/port, as this might happen multiple times + // during the execution (e.g. when loading environment variables, and later processing command line options). + // + // It is up to the user of this class to enable SSL if needed. + // } bool ClientEnvironment::checkTaskPathAndPassword(std::string& errorMsg) const { @@ -311,15 +312,6 @@ void ClientEnvironment::read_environment_variables() { host_vec_.clear(); // remove previous setting if any host_vec_.emplace_back(host, port); } - -#ifdef ECF_OPENSSL - // Note: This must be placed here for child commands, where we we typically only use environment variables - // Must be done last *AFTER* host and port set - // Can't use enable_sll(), since that calls host()/port() which use host_vec_, which may be empty - // Avoid enabling SSL for the GUI, via environment, this must be done explicitly by the GUI - if (!gui_) - ssl_.enable_if_defined(host, port); -#endif } bool ClientEnvironment::parseHostsFile(std::string& errorMsg) { diff --git a/libs/client/src/ecflow/client/ClientEnvironment.hpp b/libs/client/src/ecflow/client/ClientEnvironment.hpp index ec939f83c..34fd1fa6e 100644 --- a/libs/client/src/ecflow/client/ClientEnvironment.hpp +++ b/libs/client/src/ecflow/client/ClientEnvironment.hpp @@ -98,9 +98,6 @@ class ClientEnvironment final : public AbstractClientEnv { ssl_.enable_if_defined(host(), port()); } // IF ECF_SSL=1,search server.crt, ELSE search ..crt void enable_ssl() { ssl_.enable(host(), port()); } // search server.crt first, then ..crt - bool enable_ssl_no_throw() { - return ssl_.enable_no_throw(host(), port()); - } // search server.crt first, then ..crt void disable_ssl() { ssl_.disable(); } // override environment setting for ECF_SSL #endif diff --git a/libs/client/src/ecflow/client/ClientInvoker.hpp b/libs/client/src/ecflow/client/ClientInvoker.hpp index 515fe8acd..71a008efa 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.hpp +++ b/libs/client/src/ecflow/client/ClientInvoker.hpp @@ -69,7 +69,6 @@ class ClientInvoker { #ifdef ECF_OPENSSL /// Override any ssl read from environment(ECF_SSL) or command line args(-ssl) void enable_ssl() { clientEnv_.enable_ssl(); } - bool enable_ssl_no_throw() { return clientEnv_.enable_ssl_no_throw(); } void disable_ssl() { clientEnv_.disable_ssl(); } // override environment setting for ECF_SSL #endif diff --git a/libs/client/src/ecflow/client/ClientOptions.cpp b/libs/client/src/ecflow/client/ClientOptions.cpp index 91b11ff22..413b43ae0 100644 --- a/libs/client/src/ecflow/client/ClientOptions.cpp +++ b/libs/client/src/ecflow/client/ClientOptions.cpp @@ -178,10 +178,22 @@ Cmd_ptr ClientOptions::parse(const CommandLine& cl, ClientEnvironment* env) cons } #ifdef ECF_OPENSSL - if (vm.count("ssl")) { - if (env->debug()) - std::cout << " ssl set via command line\n"; + if (auto ecf_ssl = getenv("ECF_SSL"); vm.count("ssl") || ecf_ssl) { + if (env->debug()) { + if (!vm.count("ssl") && ecf_ssl) { + std::cout << " ssl enabled via environment variable\n"; + } + else if (!vm.count("ssl") && ecf_ssl) { + std::cout << " ssl explicitly enabled via command line\n"; + } + else { + std::cout << " ssl explicitly enabled via command line, but also enabled via environment variable\n"; + } + } env->enable_ssl(); + if (env->debug()) { + std::cout << " ssl certificate: '" << env->openssl().info() << "' \n"; + } } #endif From 00b1ac8d5ef97ea994b39d95b8fb7abe52e9391d Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 15 Nov 2024 13:54:23 +0000 Subject: [PATCH 27/33] Create a scaffold library to store basic helper classes Re ECFLOW-1985 --- libs/attribute/CMakeLists.txt | 1 + libs/attribute/test/TestAttrSerialization.cpp | 2 +- libs/attribute/test/TestMigration.cpp | 2 +- libs/client/CMakeLists.txt | 1 + libs/core/CMakeLists.txt | 11 ++-- libs/core/test/TestCereal.cpp | 2 +- libs/core/test/TestMigration.cpp | 2 +- libs/core/test/TestSerialisation.cpp | 3 +- libs/core/test/TestVersioning.hpp | 2 +- .../ecflow/test/scaffold/Provisioning.hpp | 65 +++++++++++++++++++ .../ecflow/test/scaffold/Serialisation.hpp} | 6 +- libs/node/CMakeLists.txt | 2 +- libs/node/test/TestInLimit.cpp | 2 +- libs/node/test/TestLimit.cpp | 2 +- libs/node/test/TestMigration.cpp | 2 +- libs/server/CMakeLists.txt | 1 + 16 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 libs/core/test/scaffold/ecflow/test/scaffold/Provisioning.hpp rename libs/core/test/{TestSerialisation.hpp => scaffold/ecflow/test/scaffold/Serialisation.hpp} (91%) diff --git a/libs/attribute/CMakeLists.txt b/libs/attribute/CMakeLists.txt index 240881fc4..3d287160f 100644 --- a/libs/attribute/CMakeLists.txt +++ b/libs/attribute/CMakeLists.txt @@ -39,6 +39,7 @@ ecbuild_add_test( ../core/test LIBS ecflow_all + test_scaffold TEST_DEPENDS u_core Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) diff --git a/libs/attribute/test/TestAttrSerialization.cpp b/libs/attribute/test/TestAttrSerialization.cpp index 99b135cb7..1b7f2c529 100644 --- a/libs/attribute/test/TestAttrSerialization.cpp +++ b/libs/attribute/test/TestAttrSerialization.cpp @@ -10,7 +10,6 @@ #include -#include "TestSerialisation.hpp" #include "ecflow/attribute/AutoArchiveAttr.hpp" #include "ecflow/attribute/AutoCancelAttr.hpp" #include "ecflow/attribute/ClockAttr.hpp" @@ -28,6 +27,7 @@ #include "ecflow/attribute/VerifyAttr.hpp" #include "ecflow/attribute/ZombieAttr.hpp" #include "ecflow/core/Calendar.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" using namespace std; using namespace ecf; diff --git a/libs/attribute/test/TestMigration.cpp b/libs/attribute/test/TestMigration.cpp index 4649f31f3..9dca708ed 100644 --- a/libs/attribute/test/TestMigration.cpp +++ b/libs/attribute/test/TestMigration.cpp @@ -11,7 +11,6 @@ #include #include -#include "TestSerialisation.hpp" #include "ecflow/attribute/AutoArchiveAttr.hpp" #include "ecflow/attribute/AutoCancelAttr.hpp" #include "ecflow/attribute/ClockAttr.hpp" @@ -31,6 +30,7 @@ #include "ecflow/core/File.hpp" #include "ecflow/core/TimeSlot.hpp" #include "ecflow/core/cereal_boost_time.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" using namespace std; using namespace ecf; diff --git a/libs/client/CMakeLists.txt b/libs/client/CMakeLists.txt index 2fc3f618c..6d02dd73f 100644 --- a/libs/client/CMakeLists.txt +++ b/libs/client/CMakeLists.txt @@ -101,6 +101,7 @@ ecbuild_add_test( ../node/test LIBS ecflow_all + test_scaffold Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) Boost::timer $<$:OpenSSL::SSL> diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index ba0d1bce9..601727591 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -14,15 +14,16 @@ ecbuild_add_library( TARGET - test_support + test_scaffold NOINSTALL TYPE INTERFACE SOURCES - test/TestSerialisation.hpp + test/scaffold/ecflow/test/scaffold/Provisioning.hpp + test/scaffold/ecflow/test/scaffold/Serialisation.hpp PUBLIC_INCLUDES - test + test/scaffold ) -target_clangformat(test_support) +target_clangformat(test_scaffold) set(test_srcs # Headers @@ -69,8 +70,8 @@ ecbuild_add_test( SOURCES ${test_srcs} LIBS - test_support ecflow_all + test_scaffold Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) Boost::timer ) diff --git a/libs/core/test/TestCereal.cpp b/libs/core/test/TestCereal.cpp index 5250d466b..69160cf0a 100644 --- a/libs/core/test/TestCereal.cpp +++ b/libs/core/test/TestCereal.cpp @@ -12,8 +12,8 @@ #include -#include "TestSerialisation.hpp" #include "ecflow/core/Filesystem.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" using namespace ecf; using namespace boost; diff --git a/libs/core/test/TestMigration.cpp b/libs/core/test/TestMigration.cpp index f6c8c8502..5a3a69b68 100644 --- a/libs/core/test/TestMigration.cpp +++ b/libs/core/test/TestMigration.cpp @@ -10,13 +10,13 @@ #include -#include "TestSerialisation.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/DState.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/File.hpp" #include "ecflow/core/NState.hpp" #include "ecflow/core/TimeSeries.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" using namespace std; using namespace ecf; diff --git a/libs/core/test/TestSerialisation.cpp b/libs/core/test/TestSerialisation.cpp index a0e536530..c262c6a8d 100644 --- a/libs/core/test/TestSerialisation.cpp +++ b/libs/core/test/TestSerialisation.cpp @@ -8,12 +8,11 @@ * nor does it submit to any jurisdiction. */ -#include "TestSerialisation.hpp" - #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/TimeSeries.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" using namespace std; using namespace ecf; diff --git a/libs/core/test/TestVersioning.hpp b/libs/core/test/TestVersioning.hpp index 351ccbbb8..460919aad 100644 --- a/libs/core/test/TestVersioning.hpp +++ b/libs/core/test/TestVersioning.hpp @@ -11,8 +11,8 @@ #ifndef ecflow_core_test_TestVersioning_HPP #define ecflow_core_test_TestVersioning_HPP -#include "TestSerialisation.hpp" #include "ecflow/core/Converter.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" /// To simulate changing of data model over time, we will /// namespace's. The actual serialisation does not appears to diff --git a/libs/core/test/scaffold/ecflow/test/scaffold/Provisioning.hpp b/libs/core/test/scaffold/ecflow/test/scaffold/Provisioning.hpp new file mode 100644 index 000000000..f0c507629 --- /dev/null +++ b/libs/core/test/scaffold/ecflow/test/scaffold/Provisioning.hpp @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#ifndef ecflow_test_scaffold_Provisioning_HPP +#define ecflow_test_scaffold_Provisioning_HPP + +#include +#include + +#include "ecflow/core/Filesystem.hpp" + +/** + * The following classes help to provision the test environment, by handling + * the automatic creation and cleanup of test artifacts (e.g. files and + * environment variables). + */ + +class WithTestEnvironmentVariable { +public: + WithTestEnvironmentVariable(std::string variable, std::string value) : variable_(variable) { + setenv(variable_.c_str(), value.c_str(), 1); + } + WithTestEnvironmentVariable(const WithTestEnvironmentVariable&) = default; + WithTestEnvironmentVariable& operator=(const WithTestEnvironmentVariable&) = default; + WithTestEnvironmentVariable(WithTestEnvironmentVariable&&) noexcept = default; + WithTestEnvironmentVariable& operator=(WithTestEnvironmentVariable&&) noexcept = default; + + ~WithTestEnvironmentVariable() { unsetenv(variable_.c_str()); } + +private: + std::string variable_; +}; + +class WithTestFile { +public: + explicit WithTestFile(fs::path location) : location_{location} { + // Caution: We assume that existing test files can be overwritten + + { + std::ofstream os(location_.string(), std::ios::out | std::ios::trunc); + os << "This is a dummy test file." << std::endl; + } + + // Now that the file actually exists, we update the location to the canonical path + location_ = fs::canonical(location_); + } + WithTestFile(const WithTestFile&) = default; + WithTestFile& operator=(const WithTestFile&) = default; + WithTestFile(WithTestFile&&) noexcept = default; + WithTestFile& operator=(WithTestFile&&) noexcept = default; + + ~WithTestFile() { fs::remove(location_); } + +private: + fs::path location_; +}; + +#endif /* ecflow_test_scaffold_Provisioning_HPP */ diff --git a/libs/core/test/TestSerialisation.hpp b/libs/core/test/scaffold/ecflow/test/scaffold/Serialisation.hpp similarity index 91% rename from libs/core/test/TestSerialisation.hpp rename to libs/core/test/scaffold/ecflow/test/scaffold/Serialisation.hpp index 115a2c56c..178e40b5f 100644 --- a/libs/core/test/TestSerialisation.hpp +++ b/libs/core/test/scaffold/ecflow/test/scaffold/Serialisation.hpp @@ -8,8 +8,8 @@ * nor does it submit to any jurisdiction. */ -#ifndef ecflow_core_TestSerialisation_HPP -#define ecflow_core_TestSerialisation_HPP +#ifndef ecflow_core_scaffold_TestSerialisation_HPP +#define ecflow_core_scaffold_TestSerialisation_HPP #include @@ -65,4 +65,4 @@ void doSaveAndRestore(const std::string& fileName) { } // namespace ecf -#endif /* ecflow_core_TestSerialisation_HPP */ +#endif /* ecflow_core_scaffold_TestSerialisation_HPP */ diff --git a/libs/node/CMakeLists.txt b/libs/node/CMakeLists.txt index ccd2488d2..2954e7625 100644 --- a/libs/node/CMakeLists.txt +++ b/libs/node/CMakeLists.txt @@ -65,8 +65,8 @@ ecbuild_add_test( SOURCES ${test_srcs} LIBS - test_support ecflow_all + test_scaffold Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) TEST_DEPENDS u_attributes diff --git a/libs/node/test/TestInLimit.cpp b/libs/node/test/TestInLimit.cpp index ae00a5d93..6e6d0434d 100644 --- a/libs/node/test/TestInLimit.cpp +++ b/libs/node/test/TestInLimit.cpp @@ -13,9 +13,9 @@ #include -#include "TestSerialisation.hpp" #include "ecflow/node/InLimit.hpp" #include "ecflow/node/Task.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" using namespace std; using namespace ecf; diff --git a/libs/node/test/TestLimit.cpp b/libs/node/test/TestLimit.cpp index 9f6d3a921..70b15a386 100644 --- a/libs/node/test/TestLimit.cpp +++ b/libs/node/test/TestLimit.cpp @@ -12,10 +12,10 @@ #include -#include "TestSerialisation.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/node/Limit.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" using namespace std; using namespace ecf; diff --git a/libs/node/test/TestMigration.cpp b/libs/node/test/TestMigration.cpp index 83abd1a86..cd0aa5213 100644 --- a/libs/node/test/TestMigration.cpp +++ b/libs/node/test/TestMigration.cpp @@ -11,13 +11,13 @@ #include #include "MyDefsFixture.hpp" -#include "TestSerialisation.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/File.hpp" #include "ecflow/node/Defs.hpp" #include "ecflow/node/Family.hpp" #include "ecflow/node/Suite.hpp" #include "ecflow/node/Task.hpp" +#include "ecflow/test/scaffold/Serialisation.hpp" using namespace std; using namespace ecf; diff --git a/libs/server/CMakeLists.txt b/libs/server/CMakeLists.txt index bc2c3d88f..c0b344d1b 100644 --- a/libs/server/CMakeLists.txt +++ b/libs/server/CMakeLists.txt @@ -64,6 +64,7 @@ ecbuild_add_test( test/TestServer.cpp LIBS ecflow_all + test_scaffold Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) $<$:OpenSSL::SSL> Threads::Threads From e55987478549777daae89a163ff5d7bc296dea8e Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 15 Nov 2024 10:17:28 +0000 Subject: [PATCH 28/33] Add tests for handling Client/Server configuration Re ECFLOW-1985 --- libs/base/src/ecflow/base/Openssl.cpp | 12 +- libs/client/CMakeLists.txt | 1 + .../src/ecflow/client/ClientEnvironment.hpp | 1 + .../src/ecflow/client/ClientInvoker.cpp | 11 +- .../src/ecflow/client/ClientInvoker.hpp | 10 + .../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 | 172 ++++++++++ 13 files changed, 556 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..73fd2c28d 100644 --- a/libs/base/src/ecflow/base/Openssl.cpp +++ b/libs/base/src/ecflow/base/Openssl.cpp @@ -161,9 +161,15 @@ 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) { + // This is used for testing, to avoid using the default location + 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..8af4286ff 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.cpp +++ b/libs/client/src/ecflow/client/ClientInvoker.cpp @@ -202,6 +202,14 @@ 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 +275,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..2248cbaf1 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.hpp +++ b/libs/client/src/ecflow/client/ClientInvoker.hpp @@ -11,6 +11,8 @@ #ifndef ecflow_client_ClientInvoker_HPP #define ecflow_client_ClientInvoker_HPP +#include + #include #include "ecflow/base/Cmd.hpp" @@ -42,6 +44,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 +399,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..667895a9d --- /dev/null +++ b/libs/server/test/TestServerConfigurations.cpp @@ -0,0 +1,172 @@ +/* + * 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"); + + 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: + // Even though ECF_SSL specifies the use of shared certificate, the specific certificate is selected + // since it is the only one kind available. +} + +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(), "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_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() From 010807122e3d0e5a88144a543e6c234508a38dde Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Wed, 20 Nov 2024 15:47:00 +0000 Subject: [PATCH 29/33] Add Repeat progress details to tooltip Re ECFLOW-1988 --- Viewer/ecflowUI/src/NodeViewDelegate.cpp | 7 +- Viewer/ecflowUI/src/VRepeatAttr.cpp | 152 ++--- Viewer/ecflowUI/src/VRepeatAttr.hpp | 19 +- libs/CMakeLists.txt | 1 + libs/attribute/CMakeLists.txt | 1 + .../src/ecflow/attribute/RepeatAttr.hpp | 26 +- .../src/ecflow/attribute/RepeatRange.hpp | 304 +++++++++ libs/attribute/test/TestRepeatRange.cpp | 611 ++++++++++++++++++ libs/core/src/ecflow/core/Cal.cpp | 11 +- 9 files changed, 994 insertions(+), 138 deletions(-) create mode 100644 libs/attribute/src/ecflow/attribute/RepeatRange.hpp create mode 100644 libs/attribute/test/TestRepeatRange.cpp diff --git a/Viewer/ecflowUI/src/NodeViewDelegate.cpp b/Viewer/ecflowUI/src/NodeViewDelegate.cpp index 148f75a63..9664bddff 100644 --- a/Viewer/ecflowUI/src/NodeViewDelegate.cpp +++ b/Viewer/ecflowUI/src/NodeViewDelegate.cpp @@ -1699,7 +1699,7 @@ void NodeViewDelegate::renderRepeat(QPainter* painter, size = QSize(totalWidth, attrBox_->fullHeight); - if (data.count() < 9) + if (data.count() < 10) return; QString type = data.at(1); @@ -1708,8 +1708,9 @@ void NodeViewDelegate::renderRepeat(QPainter* painter, QString step = data.at(6); QString pos = data.at(8); - if (data.count() == 10) - name.prepend(data[9] + ":"); + if (data.count() == 11) { + name.prepend(data[10] + ":"); + } bool selected = option.state & QStyle::State_Selected; diff --git a/Viewer/ecflowUI/src/VRepeatAttr.cpp b/Viewer/ecflowUI/src/VRepeatAttr.cpp index a46ed4774..222f50d45 100644 --- a/Viewer/ecflowUI/src/VRepeatAttr.cpp +++ b/Viewer/ecflowUI/src/VRepeatAttr.cpp @@ -14,6 +14,8 @@ #include "VAttributeType.hpp" #include "VNode.hpp" +#include "ecflow/attribute/RepeatRange.hpp" +#include "ecflow/core/Cal.hpp" std::string VRepeatDateAttr::subType_("date"); std::string VRepeatDateTimeAttr::subType_("datetime"); @@ -23,81 +25,12 @@ std::string VRepeatStringAttr::subType_("string"); std::string VRepeatEnumAttr::subType_("enumerated"); std::string VRepeatDayAttr::subType_("day"); -static long ecf_repeat_date_to_julian(long ddate); -static long ecf_repeat_julian_to_date(long jdate); - -long ecf_repeat_julian_to_date(long jdate) { - long x = 0, y = 0, d = 0, m = 0, e = 0; - long day = 0, month = 0, year = 0; - - x = 4 * jdate - 6884477; - y = (x / 146097) * 100; - e = x % 146097; - d = e / 4; - - x = 4 * d + 3; - y = (x / 1461) + y; - e = x % 1461; - d = e / 4 + 1; - - x = 5 * d - 3; - m = x / 153 + 1; - e = x % 153; - d = e / 5 + 1; - - if (m < 11) - month = m + 2; - else - month = m - 10; - - day = d; - year = y + m / 11; - - return year * 10000 + month * 100 + day; -} - -long ecf_repeat_date_to_julian(long ddate) { - long m1 = 0, y1 = 0, a = 0, b = 0, c = 0, d = 0, j1 = 0; - - long month = 0, day = 0, year = 0; - - year = ddate / 10000; - ddate %= 10000; - month = ddate / 100; - ddate %= 100; - day = ddate; - - if (false) { - a = (14 - month) / 12; - y1 = year + 4800 - a; - m1 = month + 12 * a - 3; - j1 = day + (153 * m1 + 2) / 5 + 365 * y1 + y1 / 4 - y1 / 100 + y1 / 400 - 32045; - return j1 - 0.5; - } - - if (month > 2) { - m1 = month - 3; - y1 = year; - } - else { - m1 = month + 9; - y1 = year - 1; - } - a = 146097 * (y1 / 100) / 4; - d = y1 % 100; - b = 1461 * d / 4; - c = (153 * m1 + 2) / 5 + day + 1721119; - j1 = a + b + c; - - return j1; -} - //================================ // VRepeatAttrType //================================ VRepeatAttrType::VRepeatAttrType() : VAttributeType("repeat") { - dataCount_ = 9; + dataCount_ = 10; searchKeyToData_["repeat_name"] = NameIndex; searchKeyToData_["repeat_value"] = ValueIndex; searchKeyToData_["name"] = NameIndex; @@ -118,6 +51,7 @@ QString VRepeatAttrType::toolTip(QStringList d) const { if (d[SubtypeIndex] == "integer" || d[SubtypeIndex] == "date") { t += "
Step: " + d[StepIndex]; } + t += "
Complete: " + d[ProgressIndex]; } else { t += "Step: " + d[StepIndex]; @@ -159,10 +93,23 @@ void VRepeatAttrType::encode(const Repeat& r, // We try to avoid creating a VRepeat object everytime we are here // std::string type=VRepeat::type(r); - data << qName_ << QString::fromStdString(type) << QString::fromStdString(r.name()) - << QString::fromStdString(r.valueAsString()) << ra->startValue() << ra->endValue() - << QString::fromStdString(ecf::Duration::format(ecf::Duration{std::chrono::seconds{r.step()}})) << allValues - << QString::number(ra->currentPosition()); + data << qName_; + data << QString::fromStdString(type); // TypeIndex + data << QString::fromStdString(r.name()); // SubtypeIndex + data << QString::fromStdString(r.valueAsString()); // NameIndex + data << ra->startValue(); // StartIndex + data << ra->endValue(); // EndIndex + if (type == "datetime") { + data << QString::fromStdString( + ecf::Duration::format(ecf::Duration{std::chrono::seconds{r.step()}})); // StepIndex + } + else { + data << QString::number(r.step()); // StepIndex + } + data << allValues; // AllValuesIndex + data << QString::number(ra->currentPosition()); // CurrentPosIndex + ecf::Limits limits = ecf::limits_of(r.repeatBase()); + data << QString::number(limits.current) + " of " + QString::number(limits.end); // ProgressIndex } //===================================================== @@ -248,8 +195,8 @@ int VRepeatDateAttr::endIndex() const { if (node_ptr node = parent_->node()) { const Repeat& r = node->repeat(); if (r.step() > 0) { - long jStart = ecf_repeat_date_to_julian(r.start()); - long jEnd = ecf_repeat_date_to_julian(r.end()); + long jStart = Cal::date_to_julian(r.start()); + long jEnd = Cal::date_to_julian(r.end()); int index = (jEnd - jStart) / r.step(); long val = jStart + index * r.step(); @@ -266,7 +213,7 @@ int VRepeatDateAttr::endIndex() const { int VRepeatDateAttr::currentIndex() const { if (node_ptr node = parent_->node()) { const Repeat& r = node->repeat(); - int cur = (ecf_repeat_date_to_julian(r.index_or_value()) - ecf_repeat_date_to_julian(r.start())) / r.step(); + int cur = (Cal::date_to_julian(r.index_or_value()) - Cal::date_to_julian(r.start())) / r.step(); return cur; } return 0; @@ -292,7 +239,7 @@ std::string VRepeatDateAttr::value(int index) const { std::stringstream ss; if (node_ptr node = parent_->node()) { const Repeat& r = node->repeat(); - ss << (ecf_repeat_julian_to_date(ecf_repeat_date_to_julian(r.start()) + index * r.step())); + ss << (Cal::julian_to_date(Cal::date_to_julian(r.start()) + index * r.step())); } return ss.str(); @@ -305,8 +252,7 @@ int VRepeatDateAttr::currentPosition() const { return -1; else if (r.value() == r.start()) return 0; - else if (r.value() == r.end() || - ecf_repeat_date_to_julian(r.value()) + r.step() > ecf_repeat_date_to_julian(r.end())) + else if (r.value() == r.end() || Cal::date_to_julian(r.value()) + r.step() > Cal::date_to_julian(r.end())) return 2; else return 1; @@ -323,49 +269,29 @@ int VRepeatDateAttr::currentPosition() const { int VRepeatDateTimeAttr::endIndex() const { if (node_ptr node = parent_->node()) { - const Repeat& r = node->repeat(); - if (r.step() > 0) { - auto jStart = ecf::coerce_to_instant(r.start()); - auto jEnd = ecf::coerce_to_instant(r.end()); - - int index = (jEnd - jStart).as_seconds().count() / r.step(); - auto val = jStart + ecf::Duration{std::chrono::seconds{index * r.step()}}; - while (val > jEnd && index >= 1) { - index--; - val = jStart + ecf::Duration{std::chrono::seconds{index * r.step()}}; - } - return index; - } + return ecf::make_range(node->repeat()).end(); } return 0; } int VRepeatDateTimeAttr::currentIndex() const { if (node_ptr node = parent_->node()) { - const Repeat& r = node->repeat(); - auto jStart = ecf::coerce_to_instant(r.start()); - auto jEnd = ecf::coerce_to_instant(r.end()); - - int cur = (jEnd - jStart).as_seconds().count() / r.step(); - return cur; + return ecf::make_range(node->repeat()).current_index(); } return 0; } QString VRepeatDateTimeAttr::startValue() const { if (node_ptr node = parent_->node()) { - const Repeat& r = node->repeat(); - std::string s = ecf::Instant::format(ecf::coerce_to_instant(r.start())); - return QString::fromStdString(s); + return QString::fromStdString( + ecf::Instant::format(ecf::front(ecf::make_range(node->repeat())))); } return {}; } QString VRepeatDateTimeAttr::endValue() const { if (node_ptr node = parent_->node()) { - const Repeat& r = node->repeat(); - std::string s = ecf::Instant::format(ecf::coerce_to_instant(r.end())); - return QString::fromStdString(s); + return QString::fromStdString(ecf::Instant::format(ecf::back(ecf::make_range(node->repeat())))); } return {}; } @@ -373,8 +299,7 @@ QString VRepeatDateTimeAttr::endValue() const { std::string VRepeatDateTimeAttr::value(int index) const { std::stringstream ss; if (node_ptr node = parent_->node()) { - const Repeat& r = node->repeat(); - ss << ecf::Instant::format(ecf::coerce_to_instant(r.start() + index * r.step())); + ss << ecf::Instant::format(ecf::make_range(node->repeat()).current_value()); } return ss.str(); } @@ -382,14 +307,19 @@ std::string VRepeatDateTimeAttr::value(int index) const { int VRepeatDateTimeAttr::currentPosition() const { if (node_ptr node = parent_->node()) { const Repeat& r = node->repeat(); - if (r.start() == r.end()) + if (r.start() == r.end()) { return -1; - else if (r.value() == r.start()) + } + else if (r.value() == r.start()) { return 0; - else if (r.value() == r.end() || ecf::coerce_to_instant(r.value() + r.step()) > ecf::coerce_to_instant(r.end())) + } + else if (r.value() == r.end() || + ecf::coerce_to_instant(r.value() + r.step()) > ecf::coerce_to_instant(r.end())) { return 2; - else + } + else { return 1; + } } return -1; diff --git a/Viewer/ecflowUI/src/VRepeatAttr.hpp b/Viewer/ecflowUI/src/VRepeatAttr.hpp index 2f0684387..7a0d5eea8 100644 --- a/Viewer/ecflowUI/src/VRepeatAttr.hpp +++ b/Viewer/ecflowUI/src/VRepeatAttr.hpp @@ -32,15 +32,16 @@ class VRepeatAttrType : public VAttributeType { private: enum DataIndex { - TypeIndex = 0, - SubtypeIndex = 1, - NameIndex = 2, - ValueIndex = 3, - StartIndex = 4, - EndIndex = 5, - StepIndex = 6, - AllValuesIndex = 7, - CurrentPosIdex = 8 + TypeIndex = 0, + SubtypeIndex = 1, + NameIndex = 2, + ValueIndex = 3, + StartIndex = 4, + EndIndex = 5, + StepIndex = 6, + AllValuesIndex = 7, + CurrentPosIndex = 8, + ProgressIndex = 9 }; }; diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 11dfeabb3..241c45f4d 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -28,6 +28,7 @@ set(srcs attribute/src/ecflow/attribute/NodeAttr.hpp attribute/src/ecflow/attribute/QueueAttr.hpp attribute/src/ecflow/attribute/RepeatAttr.hpp + attribute/src/ecflow/attribute/RepeatRange.hpp attribute/src/ecflow/attribute/TimeAttr.hpp attribute/src/ecflow/attribute/TodayAttr.hpp attribute/src/ecflow/attribute/Variable.hpp diff --git a/libs/attribute/CMakeLists.txt b/libs/attribute/CMakeLists.txt index 3d287160f..75d441378 100644 --- a/libs/attribute/CMakeLists.txt +++ b/libs/attribute/CMakeLists.txt @@ -19,6 +19,7 @@ set(test_srcs test/TestLateAttr.cpp test/TestMigration.cpp test/TestRepeat.cpp + test/TestRepeatRange.cpp test/TestSizeOf.cpp test/TestTimeAttr.cpp test/TestTodayAttr.cpp diff --git a/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp b/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp index c7d42c107..56dbf5fb1 100644 --- a/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp +++ b/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp @@ -20,6 +20,7 @@ /// a mechanism to stop this, when reset is called, via server this is disabled /// +#include #include #include #include @@ -28,6 +29,7 @@ #include #include "ecflow/attribute/Variable.hpp" +#include "ecflow/core/Cal.hpp" #include "ecflow/core/Chrono.hpp" ///////////////////////////////////////////////////////////////////////// @@ -319,6 +321,10 @@ class RepeatDateList final : public RepeatBase { int end() const override; int step() const override { return 1; } long value() const override; // return value at index otherwise return 0 + long value_at(size_t i) const { + assert(i < list_.size()); + return list_[i]; + }; long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; long last_valid_value_minus(int value) const override; @@ -564,8 +570,7 @@ class RepeatDay final : public RepeatBase { int start() const override { return 0; } int end() const override { return 0; } int step() const override { return step_; } - void increment() override { /* do nothing */ - } + void increment() override { /* do nothing */ } long value() const override { return step_; } long index_or_value() const override { return step_; } long last_valid_value() const override { return step_; } @@ -578,15 +583,11 @@ class RepeatDay final : public RepeatBase { std::string next_value_as_string() const override { return std::string(); } std::string prev_value_as_string() const override { return std::string(); } - void setToLastValue() override { /* do nothing ?? */ - } + void setToLastValue() override { /* do nothing ?? */ } void reset() override { valid_ = true; } - void change(const std::string& /*newValue*/) override { /* do nothing */ - } - void changeValue(long /*newValue*/) override { /* do nothing */ - } - void set_value(long /*newValue*/) override { /* do nothing */ - } + void change(const std::string& /*newValue*/) override { /* do nothing */ } + void changeValue(long /*newValue*/) override { /* do nothing */ } + void set_value(long /*newValue*/) override { /* do nothing */ } void write(std::string&) const override; std::string dump() const override; bool isDay() const override { return true; } @@ -706,6 +707,11 @@ class Repeat { /// Expose base for the GUI only, use with caution RepeatBase* repeatBase() const { return type_.get(); } + template + const T& as() const { + return dynamic_cast(*repeatBase()); + } + private: void write(std::string& ret) const { if (type_) diff --git a/libs/attribute/src/ecflow/attribute/RepeatRange.hpp b/libs/attribute/src/ecflow/attribute/RepeatRange.hpp new file mode 100644 index 000000000..cef13d9f6 --- /dev/null +++ b/libs/attribute/src/ecflow/attribute/RepeatRange.hpp @@ -0,0 +1,304 @@ +/* + * 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. + */ + +#ifndef ecflow_attribute_RepeatRange_HPP +#define ecflow_attribute_RepeatRange_HPP + +#include "ecflow/attribute/RepeatAttr.hpp" + +namespace ecf { + +template +struct Range +{ + explicit Range(const R& r) : r_(r) {} + const R& r_; +}; + +template <> +struct Range +{ + using size_type = std::size_t; + using iterator = std::size_t; + using value_type = int; + + explicit Range(const RepeatDay& r) : r_(r) {} + + iterator begin() const { return 0; } + iterator end() const { return 0; } + iterator current_index() const { return r_.index_or_value(); } + + value_type current_value() const { return r_.value(); } + + size_type size() const { return end() - begin(); } + +private: + const RepeatDay& r_; +}; + +template <> +struct Range +{ + using size_type = std::size_t; + using iterator = std::size_t; + using value_type = int; + + explicit Range(const RepeatDate& r) : r_(r) {} + + iterator begin() const { return 0; } + iterator end() const { + int i = Cal::date_to_julian(r_.start()); + int j = Cal::date_to_julian(r_.end()); + int s = r_.step(); + int d = (j - i) + 1; + return (d / s) + ((d % s == 0) ? 0 : 1); + } + iterator current_index() const { + auto i = Cal::date_to_julian(r_.start()); + auto s = r_.step(); + auto v = Cal::date_to_julian(r_.value()); + auto idx = (v - i) / s; + return idx; + } + + value_type current_value() const { return r_.value(); } + + value_type at(iterator i) const { + int j = Cal::date_to_julian(r_.start()); + return Cal::julian_to_date(j + i * r_.step()); + } + + size_type size() const { return end() - begin(); } + +private: + const RepeatDate& r_; +}; + +template <> +struct Range +{ + using date_t = int; + using size_type = std::size_t; + using iterator = std::size_t; + using value_type = date_t; + + explicit Range(const RepeatDateList& r) : r_(r) {} + + iterator begin() const { return 0; } + iterator end() const { return r_.indexNum(); } + iterator current_index() const { return r_.index_or_value(); } + + value_type current_value() const { return r_.value(); } + + value_type at(iterator i) const { return r_.value_at(i); } + + size_type size() const { return end() - begin(); } + +private: + const RepeatDateList& r_; +}; + +template <> +struct Range +{ + using size_type = std::size_t; + using iterator = std::size_t; + using value_type = Instant; + + explicit Range(const RepeatDateTime& r) : r_(r) {} + + iterator begin() const { return 0; } + iterator end() const { + auto i = r_.start_instant(); + auto j = r_.end_instant(); + auto s = r_.step_duration(); + auto d = (j - i); + + auto idx = d.as_seconds().count() / s.as_seconds().count(); + auto last = i + Duration{std::chrono::seconds{s.as_seconds().count() * idx}}; + if (last <= j) { + idx++; + } + return idx; + } + iterator current_index() const { + auto i = r_.start_instant(); + auto s = r_.step_duration(); + auto v = r_.value_instant(); + auto idx = (v - i).as_seconds().count() / s.as_seconds().count(); + return idx; + } + + value_type current_value() const { return r_.value_instant(); } + + value_type at(iterator i) const { + auto j = r_.start_instant(); + auto s = r_.step_duration(); + return j + Duration{std::chrono::seconds{s.as_seconds().count() * i}}; + } + + size_type size() const { return end() - begin(); } + +private: + const RepeatDateTime& r_; +}; + +template <> +struct Range +{ + using size_type = std::size_t; + using iterator = std::size_t; + using value_type = int; + + explicit Range(const RepeatInteger& r) : r_(r) {} + + iterator begin() const { return 0; } + iterator end() const { + int i = r_.start(); + int j = r_.end(); + int s = r_.step(); + int d = (j - i) + 1; + auto idx = (d / s) + ((d % s == 0) ? 0 : 1); + return idx; + } + iterator current_index() const { + auto i = r_.start(); + auto s = r_.step(); + auto v = r_.value(); + auto idx = (v - i) / s; + return idx; + } + + value_type current_value() const { return r_.value(); } + + value_type at(iterator i) const { return r_.start() + i * r_.step(); } + + size_type size() const { return end() - begin(); } + +private: + const RepeatInteger& r_; +}; + +template <> +struct Range +{ + using size_type = std::size_t; + using iterator = std::size_t; + using value_type = std::string; + + explicit Range(const RepeatEnumerated& r) : r_(r) {} + + iterator begin() const { return 0; } + iterator end() const { return r_.end() + 1; } + iterator current_index() const { return r_.index_or_value(); } + + value_type current_value() const { return r_.valueAsString(); } + + value_type at(iterator i) const { return r_.value_as_string(i); } + + size_type size() const { return end() - begin(); } + +private: + const RepeatEnumerated& r_; +}; + +template <> +struct Range +{ + using size_type = std::size_t; + using iterator = std::size_t; + using value_type = std::string; + + explicit Range(const RepeatString& r) : r_(r) {} + + iterator begin() const { return 0; } + iterator end() const { return r_.end() + 1; } + iterator current_index() const { return r_.index_or_value(); } + + value_type current_value() const { return r_.valueAsString(); } + + value_type at(iterator i) const { return r_.value_as_string(i); } + + size_type size() const { return end() - begin(); } + +private: + const RepeatString& r_; +}; + +template +auto front(const Range& rng) { + assert(rng.size() > 0); + return rng.at(rng.begin()); +} + +template +auto back(const Range& rng) { + assert(rng.size() > 0); + return rng.at(rng.end() - 1); +} + +template +auto size(const Range& rng) { + return rng.size(); +} + +template +bool empty(const Range& rng) { + return size(rng) == 0; +} + +struct Limits +{ + size_t begin; + size_t end; + size_t current; +}; + +Limits limits_of(const RepeatBase* repeat) { + if (auto r1 = dynamic_cast(repeat)) { + Range rng(*r1); + return {rng.begin(), rng.end(), rng.current_index()}; + } + else if (auto r2 = dynamic_cast(repeat)) { + Range rng(*r2); + return {rng.begin(), rng.end(), rng.current_index()}; + } + else if (auto r3 = dynamic_cast(repeat)) { + Range rng(*r3); + return {rng.begin(), rng.end(), rng.current_index()}; + } + else if (auto r4 = dynamic_cast(repeat)) { + Range rng(*r4); + return {rng.begin(), rng.end(), rng.current_index()}; + } + else if (auto r5 = dynamic_cast(repeat)) { + Range rng(*r5); + return {rng.begin(), rng.end(), rng.current_index()}; + } + else if (auto r6 = dynamic_cast(repeat)) { + Range rng(*r6); + return {rng.begin(), rng.end(), rng.current_index()}; + } + else if (auto r7 = dynamic_cast(repeat)) { + Range rng(*r7); + return {rng.begin(), rng.end(), rng.current_index()}; + } + assert(false); // Should never be reached! + return {0, 0, 0}; +} + +template +auto make_range(const Repeat& repeat) { + return Range{repeat.as()}; +} + +} // namespace ecf + +#endif /* ecflow_attribute_RepeatRange_HPP */ diff --git a/libs/attribute/test/TestRepeatRange.cpp b/libs/attribute/test/TestRepeatRange.cpp new file mode 100644 index 000000000..67dc6ffc5 --- /dev/null +++ b/libs/attribute/test/TestRepeatRange.cpp @@ -0,0 +1,611 @@ +/* + * 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/attribute/RepeatRange.hpp" + +#define CHECK_EQUAL(A, E) BOOST_CHECK_EQUAL(A, static_cast(E)) + +BOOST_AUTO_TEST_SUITE(U_Attributes) + +BOOST_AUTO_TEST_SUITE(T_RepeatRange) + +/* + * Test Suite: ::test_repeat_datelist + * ************************************************************ */ + +BOOST_AUTO_TEST_SUITE(test_repeat_datelist) + +BOOST_AUTO_TEST_CASE(can_construct) { + using namespace ecf; + { + RepeatDateList repeat("R", {20190929}); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 1); + CHECK_EQUAL(rng.size(), 1); + } + { + RepeatDateList repeat("R", {20190929, 20190131}); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 2); + CHECK_EQUAL(rng.size(), 2); + } +} + +BOOST_AUTO_TEST_CASE(can_access_iterating) { + using namespace ecf; + { + RepeatDateList repeat("R", {20190929, 20190131}); + + Range rng(repeat); + std::vector expected = {20190929, 20190131}; + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), expected[i]); + } + } +} + +BOOST_AUTO_TEST_CASE(can_access_current) { + using namespace ecf; + { + RepeatDateList repeat("R", {20190929, 20190131, 20190929}); + + Range rng(repeat); + BOOST_CHECK_EQUAL(repeat.value(), 20190929); + BOOST_CHECK_EQUAL(rng.current_value(), 20190929); + CHECK_EQUAL(rng.current_index(), 0); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.value(), 20190131); + BOOST_CHECK_EQUAL(rng.current_value(), 20190131); + CHECK_EQUAL(rng.current_index(), 1); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.value(), 20190929); + BOOST_CHECK_EQUAL(rng.current_value(), 20190929); + CHECK_EQUAL(rng.current_index(), 2); + } +} + +BOOST_AUTO_TEST_SUITE_END() // test_repeat_datelist + +/* + * Test Suite: ::test_repeat_date + * ************************************************************ */ + +BOOST_AUTO_TEST_SUITE(test_repeat_date) + +BOOST_AUTO_TEST_CASE(can_construct) { + using namespace ecf; + { // range of a single value + RepeatDate repeat("R", 20241129, 20241129, 1); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 1); + CHECK_EQUAL(rng.size(), 1); + } + { // ranges of sequencial values, with step 1 + RepeatDate repeat("R", 20241129, 20241203, 1); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 5); + CHECK_EQUAL(rng.size(), 5); + } + { // ranges of values, with step 2; first/last value included in given values + RepeatDate repeat("R", 20241129, 20241203, 2); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } + { // ranges of values, with step 2; last value not included in given values + RepeatDate repeat("R", 20241129, 20241204, 2); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } + { // ranges of values, with step 2; first/last value included in given values + RepeatDate repeat("R", 20241129, 20241205, 2); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 4); + CHECK_EQUAL(rng.size(), 4); + } + { // ranges of values, with step 3; first/last value included in given values + RepeatDate repeat("R", 20241129, 20241205, 3); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } + { // ranges of values, with step 3; last value not included in given values + RepeatDate repeat("R", 20241129, 20241206, 3); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } + { // ranges of values, with step 2; last value not included in given values + RepeatDate repeat("R", 20241129, 20241207, 3); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } + { // ranges of values, with step 2; first/last value included in given values + RepeatDate repeat("R", 20241129, 20241208, 3); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 4); + CHECK_EQUAL(rng.size(), 4); + } +} + +BOOST_AUTO_TEST_CASE(can_access_iterating) { + using namespace ecf; + { + RepeatDate repeat("R", 20241129, 20241203, 1); + + Range rng(repeat); + std::vector expected = {20241129, 20241130, 20241201, 20241202, 20241203}; + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), expected[i]); + } + } + { + RepeatDate repeat("R", 20241129, 20241207, 3); + + Range rng(repeat); + std::vector expected = {20241129, 20241202, 20241205}; + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), expected[i]); + } + } + { + RepeatDate repeat("R", 20241129, 20241208, 3); + + Range rng(repeat); + std::vector expected = {20241129, 20241202, 20241205, 20241208}; + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), expected[i]); + } + } +} + +BOOST_AUTO_TEST_CASE(can_access_current) { + using namespace ecf; + { + RepeatDate repeat("R", 20241129, 20241207, 3); + + Range rng(repeat); + BOOST_CHECK_EQUAL(repeat.value(), 20241129); + BOOST_CHECK_EQUAL(rng.current_value(), 20241129); + CHECK_EQUAL(rng.current_index(), 0); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.value(), 20241202); + BOOST_CHECK_EQUAL(rng.current_value(), 20241202); + CHECK_EQUAL(rng.current_index(), 1); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.value(), 20241205); + BOOST_CHECK_EQUAL(rng.current_value(), 20241205); + CHECK_EQUAL(rng.current_index(), 2); + } +} + +BOOST_AUTO_TEST_SUITE_END() // test_repeat_date + +/* + * Test Suite: ::test_repeat_datetime + * ************************************************************ */ + +BOOST_AUTO_TEST_SUITE(test_repeat_datetime) + +BOOST_AUTO_TEST_CASE(can_construct) { + using namespace ecf; + { + RepeatDateTime repeat("R", "19700101T000001", "19700101T000001", "24:00:00"); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 1); + CHECK_EQUAL(rng.size(), 1); + } + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000000", "24:00:00"); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 1); + CHECK_EQUAL(rng.size(), 1); + } + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000001", "24:00:00"); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 2); + CHECK_EQUAL(rng.size(), 2); + } + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000001", "0:01:00"); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 1441); + CHECK_EQUAL(rng.size(), static_cast(1441)); + } + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000000", "0:01:00"); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 1440); + CHECK_EQUAL(rng.size(), static_cast(1440)); + } + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000001", "0:00:01"); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 86401); + CHECK_EQUAL(rng.size(), static_cast(86401)); + } + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000000", "0:00:01"); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 86400); + CHECK_EQUAL(rng.size(), static_cast(86400)); + } +} + +BOOST_AUTO_TEST_CASE(can_access_iterating) { + using namespace ecf; + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000001", "06:00:00"); + + Range rng(repeat); + std::vector expected = { + "19700101T000001", "19700101T060001", "19700101T120001", "19700101T180001", "19700102T000001"}; + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(Instant::format(rng.at(i)), expected[i]); + } + } + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000000", "06:00:00"); + + Range rng(repeat); + std::vector expected = {"19700101T000001", "19700101T060001", "19700101T120001", "19700101T180001"}; + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(Instant::format(rng.at(i)), expected[i]); + } + } +} + +BOOST_AUTO_TEST_CASE(can_access_current) { + using namespace ecf; + { + RepeatDateTime repeat("R", "19700101T000001", "19700102T000001", "12:00:00"); + + Range rng(repeat); + BOOST_CHECK_EQUAL(repeat.valueAsString(), "19700101T000001"); + BOOST_CHECK_EQUAL(Instant::format(rng.current_value()), "19700101T000001"); + CHECK_EQUAL(rng.current_index(), 0); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.valueAsString(), "19700101T120001"); + BOOST_CHECK_EQUAL(Instant::format(rng.current_value()), "19700101T120001"); + CHECK_EQUAL(rng.current_index(), 1); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.valueAsString(), "19700102T000001"); + BOOST_CHECK_EQUAL(Instant::format(rng.current_value()), "19700102T000001"); + CHECK_EQUAL(rng.current_index(), 2); + } +} + +BOOST_AUTO_TEST_SUITE_END() // test_repeat_datetime + +/* + * Test Suite: ::test_repeat_enumerated + * ************************************************************ */ + +BOOST_AUTO_TEST_SUITE(test_repeat_enumerated) + +BOOST_AUTO_TEST_CASE(can_construct) { + using namespace ecf; + using namespace std::string_literals; + const std::vector strings = {"1"s, "2"s, "3"s}; + { + RepeatEnumerated repeat("R", strings); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } +} + +BOOST_AUTO_TEST_CASE(can_access_iterating) { + using namespace ecf; + using namespace std::string_literals; + const std::vector strings = {"1"s, "2"s, "3"s}; + { + RepeatEnumerated repeat("R", strings); + + Range rng(repeat); + CHECK_EQUAL(rng.size(), strings.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), strings[i]); + } + } +} + +BOOST_AUTO_TEST_CASE(can_access_current) { + using namespace ecf; + using namespace std::string_literals; + const std::vector strings = {"1"s, "2"s, "3"s}; + { + RepeatEnumerated repeat("R", strings); + + Range rng(repeat); + BOOST_CHECK_EQUAL(repeat.valueAsString(), strings[0]); + BOOST_CHECK_EQUAL(rng.current_value(), strings[0]); + CHECK_EQUAL(rng.current_index(), 0); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.valueAsString(), strings[1]); + BOOST_CHECK_EQUAL(rng.current_value(), strings[1]); + CHECK_EQUAL(rng.current_index(), 1); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.valueAsString(), strings[2]); + BOOST_CHECK_EQUAL(rng.current_value(), strings[2]); + CHECK_EQUAL(rng.current_index(), 2); + } +} + +BOOST_AUTO_TEST_SUITE_END() // test_repeat_enumerated + +/* + * Test Suite: ::test_repeat_integer + * ************************************************************ */ + +BOOST_AUTO_TEST_SUITE(test_repeat_integer) + +BOOST_AUTO_TEST_CASE(can_construct) { + using namespace ecf; + + { + RepeatInteger repeat("R", 0, 10, 1); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 11); + CHECK_EQUAL(rng.size(), 11); + } + { + RepeatInteger repeat("R", 5, 10, 1); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 6); + CHECK_EQUAL(rng.size(), 6); + } + { + RepeatInteger repeat("R", 1, 100, 1); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 100); + CHECK_EQUAL(rng.size(), static_cast(100)); + } + { + RepeatInteger repeat("R", 0, 100, 1); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 101); + CHECK_EQUAL(rng.size(), static_cast(101)); + } + { + RepeatInteger repeat("R", 0, 4, 2); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } + { + RepeatInteger repeat("R", 0, 6, 3); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } +} + +BOOST_AUTO_TEST_CASE(can_access_iterating) { + using namespace ecf; + { + RepeatInteger repeat("R", 0, 6, 1); + std::vector expected = {0, 1, 2, 3, 4, 5, 6}; + + Range rng(repeat); + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), expected[i]); + } + } + { + RepeatInteger repeat("R", 0, 6, 2); + std::vector expected = {0, 2, 4, 6}; + + Range rng(repeat); + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), expected[i]); + } + } + { + RepeatInteger repeat("R", 1, 6, 1); + std::vector expected = {1, 2, 3, 4, 5, 6}; + + Range rng(repeat); + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), expected[i]); + } + } + { + RepeatInteger repeat("R", 1, 6, 2); + std::vector expected = {1, 3, 5}; + + Range rng(repeat); + CHECK_EQUAL(rng.size(), expected.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), expected[i]); + } + } +} + +BOOST_AUTO_TEST_CASE(can_access_current) { + using namespace ecf; + { + RepeatInteger repeat("R", 1, 6, 2); + + Range rng(repeat); + BOOST_CHECK_EQUAL(repeat.value(), 1); + BOOST_CHECK_EQUAL(rng.current_value(), 1); + CHECK_EQUAL(rng.current_index(), 0); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.value(), 3); + BOOST_CHECK_EQUAL(rng.current_value(), 3); + CHECK_EQUAL(rng.current_index(), 1); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.value(), 5); + BOOST_CHECK_EQUAL(rng.current_value(), 5); + CHECK_EQUAL(rng.current_index(), 2); + } +} + +BOOST_AUTO_TEST_SUITE_END() // test_repeat_integer + +/* + * Test Suite: ::test_repeat_day + * ************************************************************ */ + +BOOST_AUTO_TEST_SUITE(test_repeat_day) + +BOOST_AUTO_TEST_CASE(can_construct) { + using namespace ecf; + { + RepeatDay repeat(2); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 0); + CHECK_EQUAL(rng.size(), 0); + } +} + +BOOST_AUTO_TEST_SUITE_END() // test_repeat_day + +/* + * Test Suite: ::test_repeat_string + * ************************************************************ */ + +BOOST_AUTO_TEST_SUITE(test_repeat_string) + +BOOST_AUTO_TEST_CASE(can_construct) { + using namespace ecf; + using namespace std::string_literals; + const std::vector strings = {"a"s, "b"s, "c"s}; + { + RepeatString repeat("R", strings); + + Range rng(repeat); + CHECK_EQUAL(rng.begin(), 0); + CHECK_EQUAL(rng.end(), 3); + CHECK_EQUAL(rng.size(), 3); + } +} + +BOOST_AUTO_TEST_CASE(can_access_iterating) { + using namespace ecf; + using namespace std::string_literals; + const std::vector strings = {"a"s, "b"s, "c"s}; + { + RepeatString repeat("R", strings); + + Range rng(repeat); + CHECK_EQUAL(rng.size(), strings.size()); + for (auto i = std::begin(rng); i != std::end(rng); ++i) { + BOOST_CHECK_EQUAL(rng.at(i), strings[i]); + } + } +} + +BOOST_AUTO_TEST_CASE(can_access_current) { + using namespace ecf; + using namespace std::string_literals; + const std::vector strings = {"a"s, "b"s, "c"s}; + { + RepeatString repeat("R", strings); + + Range rng(repeat); + BOOST_CHECK_EQUAL(repeat.valueAsString(), strings[0]); + BOOST_CHECK_EQUAL(rng.current_value(), strings[0]); + CHECK_EQUAL(rng.current_index(), 0); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.valueAsString(), strings[1]); + BOOST_CHECK_EQUAL(rng.current_value(), strings[1]); + CHECK_EQUAL(rng.current_index(), 1); + + repeat.increment(); + BOOST_CHECK_EQUAL(repeat.valueAsString(), strings[2]); + BOOST_CHECK_EQUAL(rng.current_value(), strings[2]); + CHECK_EQUAL(rng.current_index(), 2); + } +} + +BOOST_AUTO_TEST_SUITE_END() // test_repeat_string + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/core/src/ecflow/core/Cal.cpp b/libs/core/src/ecflow/core/Cal.cpp index 61d622b20..d98ab9a5d 100644 --- a/libs/core/src/ecflow/core/Cal.cpp +++ b/libs/core/src/ecflow/core/Cal.cpp @@ -11,8 +11,8 @@ #include "ecflow/core/Cal.hpp" long Cal::julian_to_date(long jdate) { - long x, y, d, m, e; - long day, month, year; + long x = 0, y = 0, d = 0, m = 0, e = 0; + long day = 0, month = 0, year = 0; x = 4 * jdate - 6884477; y = (x / 146097) * 100; @@ -41,8 +41,9 @@ long Cal::julian_to_date(long jdate) { } long Cal::date_to_julian(long ddate) { - long m1, y1, a, b, c, d, j1; - long month, day, year; + long m1 = 0, y1 = 0, a = 0, b = 0, c = 0, d = 0, j1 = 0; + + long month = 0, day = 0, year = 0; year = ddate / 10000; ddate %= 10000; @@ -64,7 +65,7 @@ long Cal::date_to_julian(long ddate) { c = (153 * m1 + 2) / 5 + day + 1721119; j1 = a + b + c; - return (j1); + return j1; } // int main(int ac, char** av){ int a=Cal::date_to_julian(20170219) % 7; printf("%d", a); return 0;} From f089f075ff8afbc779d22b8c09b24793bbd21426 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Wed, 20 Nov 2024 18:05:33 +0000 Subject: [PATCH 30/33] Enable editor for RepeatDateTime Re ECFLOW-1988 --- Viewer/ecflowUI/src/RepeatEditor.cpp | 57 ++++++++++++++++++++++++++++ Viewer/ecflowUI/src/RepeatEditor.hpp | 16 ++++++++ 2 files changed, 73 insertions(+) diff --git a/Viewer/ecflowUI/src/RepeatEditor.cpp b/Viewer/ecflowUI/src/RepeatEditor.cpp index 8ec32d55c..59050240f 100644 --- a/Viewer/ecflowUI/src/RepeatEditor.cpp +++ b/Viewer/ecflowUI/src/RepeatEditor.cpp @@ -369,8 +369,65 @@ void RepeatDateEditor::apply() { CommandHandler::run(info_, cmd); } +//================================================================ +// +// RepeatDateTimeEditor +// +//================================================================ + +RepeatDateTimeEditor::RepeatDateTimeEditor(VInfo_ptr info, QWidget* parent) : RepeatEditor(info, parent) { + // if(!repeat_) + // return; + + w_->hideRow(w_->valueSpin_); + w_->valueLe_->setText(oriVal_); + + connect(w_->valueLe_, SIGNAL(textEdited(QString)), this, SLOT(slotValueEdited(QString))); + + checkButtonStatus(); +} + +void RepeatDateTimeEditor::setValue(QString val) { + w_->valueLe_->setText(val); +} + +void RepeatDateTimeEditor::slotValueEdited(QString txt) { + if (isListMode()) { + int row = modelData_.indexOf(txt); + if (row != -1) { + w_->valueView_->setCurrentIndex(model_->index(row, 0)); + } + else { + w_->valueView_->clearSelection(); + w_->valueView_->setCurrentIndex(QModelIndex()); + } + } + checkButtonStatus(); +} + +void RepeatDateTimeEditor::resetValue() { + w_->valueLe_->setText(oriVal_); + slotValueEdited(oriVal_); + checkButtonStatus(); +} + +bool RepeatDateTimeEditor::isValueChanged() { + return (oriVal_ != w_->valueLe_->text()); +} + +void RepeatDateTimeEditor::apply() { + std::string val = w_->valueLe_->text().toStdString(); + // std::string name=w_->nameLabel_->text().toStdString(); + + std::vector cmd; + VAttribute::buildAlterCommand(cmd, "change", "repeat", val); + CommandHandler::run(info_, cmd); +} + + static AttributeEditorMaker makerStr1("repeat_integer"); static AttributeEditorMaker makerStr2("repeat_string"); static AttributeEditorMaker makerStr3("repeat_enumerated"); static AttributeEditorMaker makerStr4("repeat_date"); static AttributeEditorMaker makerStr5("repeat_datelist"); +static AttributeEditorMaker makerStr6("repeat_datetime"); diff --git a/Viewer/ecflowUI/src/RepeatEditor.hpp b/Viewer/ecflowUI/src/RepeatEditor.hpp index 0b7a4a5e7..98c675c42 100644 --- a/Viewer/ecflowUI/src/RepeatEditor.hpp +++ b/Viewer/ecflowUI/src/RepeatEditor.hpp @@ -27,6 +27,7 @@ class RepeatEditorWidget : public QWidget, protected Ui::RepeatEditorWidget { friend class RepeatIntEditor; friend class RepeatStringEditor; friend class RepeatDateEditor; + friend class RepeatDateTimeEditor; public: explicit RepeatEditorWidget(QWidget* parent = nullptr); @@ -105,4 +106,19 @@ protected Q_SLOTS: bool isValueChanged() override; }; +class RepeatDateTimeEditor : public RepeatEditor { + Q_OBJECT +public: + explicit RepeatDateTimeEditor(VInfo_ptr, QWidget* parent = nullptr); + +protected Q_SLOTS: + void slotValueEdited(QString); + +protected: + void apply() override; + void setValue(QString val) override; + void resetValue() override; + bool isValueChanged() override; +}; + #endif /* ecflow_viewer_RepeatEditor_HPP */ From 66aadf855cd49521991ea6e8dbd0d10b2de8ee60 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 25 Nov 2024 10:57:11 +0000 Subject: [PATCH 31/33] Suppress Clang compilation error related to Boost --- cmake/CompilerOptions.cmake | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 397009696..ca129e7b5 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -35,6 +35,15 @@ ecbuild_info( "CMAKE_C_COMPILER_VERSION : ${CMAKE_C_COMPILER_VERSION}") ecbuild_info( "CMAKE_CXX_COMPILER_ID : ${CMAKE_CXX_COMPILER_ID}") ecbuild_info( "CMAKE_CXX_COMPILER_VERSION : ${CMAKE_CXX_COMPILER_VERSION}") +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + # + # In case of using Clang 18.1+ in Linux, we disable the following error (present in Boost headers): + # + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 18.1 AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + ecbuild_add_cxx_flags("-Wno-enum-constexpr-conversion") + endif () + +endif() if (HAVE_WARNINGS) From 009cf50454e5d545003ecc93acd959092612b1b0 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 29 Nov 2024 08:19:38 +0000 Subject: [PATCH 32/33] Fix registration of RepeatDateTime serialisation Re ECFLOW-1992 --- .../attribute/src/ecflow/attribute/RepeatAttr.cpp | 3 +++ libs/core/src/ecflow/core/Chrono.cpp | 15 +++++++++++++++ libs/core/src/ecflow/core/Chrono.hpp | 12 ++++++++++++ libs/core/src/ecflow/core/Serialization.hpp | 1 + 4 files changed, 31 insertions(+) diff --git a/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp b/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp index bc7f89ae0..ba522ab4f 100644 --- a/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp +++ b/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp @@ -1779,6 +1779,7 @@ bool RepeatDay::operator==(const RepeatDay& rhs) const { } return true; } + // ========================================================================================= template @@ -1840,6 +1841,7 @@ void Repeat::serialize(Archive& ar, std::uint32_t const version) { CEREAL_TEMPLATE_SPECIALIZE(RepeatBase); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDate); +CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDateTime); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDateList); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatInteger); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatEnumerated); @@ -1848,6 +1850,7 @@ CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDay); CEREAL_TEMPLATE_SPECIALIZE_V(Repeat); CEREAL_REGISTER_TYPE(RepeatDate) +CEREAL_REGISTER_TYPE(RepeatDateTime) CEREAL_REGISTER_TYPE(RepeatDateList) CEREAL_REGISTER_TYPE(RepeatInteger) CEREAL_REGISTER_TYPE(RepeatEnumerated) diff --git a/libs/core/src/ecflow/core/Chrono.cpp b/libs/core/src/ecflow/core/Chrono.cpp index 17ad3fc65..cbe89a7ef 100644 --- a/libs/core/src/ecflow/core/Chrono.cpp +++ b/libs/core/src/ecflow/core/Chrono.cpp @@ -17,6 +17,8 @@ #include +#include "ecflow/core/Serialization.hpp" + namespace ecf { namespace { @@ -237,4 +239,17 @@ bool operator>=(const Duration& rhs, const Duration& lhs) { return !(rhs < lhs); } +template +void Instant::serialize(Archive& ar, std::uint32_t const version) { + ar(instant_); +} + +template +void Duration::serialize(Archive& ar, std::uint32_t const version) { + ar(duration_); +} + +CEREAL_TEMPLATE_SPECIALIZE_V(Instant); +CEREAL_TEMPLATE_SPECIALIZE_V(Duration); + } // namespace ecf diff --git a/libs/core/src/ecflow/core/Chrono.hpp b/libs/core/src/ecflow/core/Chrono.hpp index d590f8979..9e96c515d 100644 --- a/libs/core/src/ecflow/core/Chrono.hpp +++ b/libs/core/src/ecflow/core/Chrono.hpp @@ -14,6 +14,10 @@ #include #include +namespace cereal { +class access; +} + namespace ecf { class Instant; @@ -47,6 +51,10 @@ class Instant { private: instant_t instant_; + + friend class cereal::access; + template + void serialize(Archive& ar, std::uint32_t const version); }; std::ostream& operator<<(std::ostream& o, const Instant& v); @@ -76,6 +84,10 @@ class Duration { private: duration_t duration_; + + friend class cereal::access; + template + void serialize(Archive& ar, std::uint32_t const version); }; std::ostream& operator<<(std::ostream& o, const Duration& v); diff --git a/libs/core/src/ecflow/core/Serialization.hpp b/libs/core/src/ecflow/core/Serialization.hpp index 8eb45e579..20a5d5ac6 100644 --- a/libs/core/src/ecflow/core/Serialization.hpp +++ b/libs/core/src/ecflow/core/Serialization.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include From a3c110f1fbf7b0ab608af2ede1be6034c4ad6e71 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 29 Nov 2024 14:26:33 +0000 Subject: [PATCH 33/33] Prepare ecFlow 5.13.6 release --- CMakeLists.txt | 2 +- docs/conf.py | 2 +- docs/release_notes/version_5.13.rst | 19 +++++++++++++++++++ libs/pyext/ecflow/__init__.py | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 53e8b98a4..08433f691 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ endif() find_package( ecbuild 3.4 REQUIRED HINTS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../ecbuild /workspace/ecbuild) # Before project() -project( ecflow LANGUAGES CXX VERSION 5.13.5 ) +project( ecflow LANGUAGES CXX VERSION 5.13.6 ) # Important: the project version is used, as generated CMake variables, to filter .../ecflow/core/ecflow_version.h.in diff --git a/docs/conf.py b/docs/conf.py index 9f1dc5d68..71d3c1683 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ def get_ecflow_version(): - version = "5.13.5" + version = "5.13.6" ecflow_version = version.split(".") print("Extracted ecflow version '" + str(ecflow_version)) return ecflow_version diff --git a/docs/release_notes/version_5.13.rst b/docs/release_notes/version_5.13.rst index ee6ba59ca..ab89490bd 100644 --- a/docs/release_notes/version_5.13.rst +++ b/docs/release_notes/version_5.13.rst @@ -6,6 +6,25 @@ Version 5.13 updates .. role:: jiraissue :class: hidden +Version 5.13.6 +============== + +* `Released `__\ on 2024-11-29 + +General +------- + +- **Improvement** enable explicit 'reload' of Mirror attribute configuration :jiraissue:`ECFLOW-1986` +- **Improvement** enable automatic builds of rpm/deb packages :jiraissue:`ECFLOW-1967` +- **Fix** correct replacement of nodes with Repeat DateTime attributes :jiraissue:`ECFLOW-1992` +- **Fix** correct handling of :code:`ecflow_client` option :code:`--host` when searching for SSL certificate file :jiraissue:`ECFLOW-1985` +- **Fix** correct build setup used on conda-forge with Python 3.13 :jiraissue:`ECFLOW-1987` + +ecFlow UI +--------- + +- **Improvement** enable Repeat DateTime attribute editor :jiraissue:`ECFLOW-1988` + Version 5.13.5 ============== diff --git a/libs/pyext/ecflow/__init__.py b/libs/pyext/ecflow/__init__.py index 48334cbdf..3e8d0b632 100644 --- a/libs/pyext/ecflow/__init__.py +++ b/libs/pyext/ecflow/__init__.py @@ -15,6 +15,6 @@ The ecFlow python module """ -__version__ = '5.13.5' +__version__ = '5.13.6' # http://stackoverflow.com/questions/13040646/how-do-i-create-documentation-with-pydoc