Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions libs/core/src/ecflow/core/Environment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ constexpr const char* ECF_USER = "ECF_USER";
constexpr const char* ECF_DEBUG_CLIENT = "ECF_DEBUG_CLIENT";
constexpr const char* ECF_DEBUG_LEVEL = "ECF_DEBUG_LEVEL";

// Mirror kill-switches
constexpr const char* ECF_MIRROR_DISABLE = "ECF_MIRROR_DISABLE";
constexpr const char* ECF_MIRROR_DISABLE_QUERIES = "ECF_MIRROR_DISABLE_QUERIES";

namespace /* anonymous */ {

template <typename T>
Expand Down
11 changes: 11 additions & 0 deletions libs/node/src/ecflow/node/MirrorAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <sstream>

#include "ecflow/core/Ecf.hpp"
#include "ecflow/core/Environment.hpp"
#include "ecflow/core/Message.hpp"
#include "ecflow/core/Overload.hpp"
#include "ecflow/core/exceptions/Exceptions.hpp"
Expand Down Expand Up @@ -90,6 +91,16 @@ void MirrorAttr::finish() {
void MirrorAttr::mirror() {
SLOG(D, "MirrorAttr: poll Mirror attribute '" << absolute_name() << "'");

// Check if local mirrors are disabled via ECF_MIRROR_DISABLE variable
if (parent_) {
std::string disable_mirrors;
if (parent_->findParentVariableValue(ecf::environment::ECF_MIRROR_DISABLE, disable_mirrors)) {
// ECF_MIRROR_DISABLE is defined and non-empty; skip mirror polling
SLOG(D, "MirrorAttr: Mirror attribute '" << absolute_name() << "' disabled via ECF_MIRROR_DISABLE");
return;
}
}

start_controller();
if (!controller_) {
return; // Can't continue without a running controller...
Expand Down
45 changes: 45 additions & 0 deletions libs/node/test/parser/TestMirrorAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,51 @@ BOOST_AUTO_TEST_CASE(can_parse_mirror_attribute_on_task_with_all_attributes) {
BOOST_CHECK_EQUAL(mirror.ssl(), true);
}

BOOST_AUTO_TEST_CASE(mirror_disabled_when_ECF_MIRROR_DISABLE_is_set) {
ECF_NAME_THIS_TEST();

using namespace ecf;

std::string definition = R"(
suite s1
edit ECF_MIRROR_DISABLE "yes"
family f1
task t1
mirror --name A --remote_path /s1/f1/t2 --remote_host invalid-host --remote_port 9999 --polling 1
endfamily
)";

Defs defs;
DefsStructureParser parser(&defs, definition, true);

std::string errorMsg, warningMsg;
bool parsedOK = parser.doParse(errorMsg, warningMsg);
BOOST_CHECK_MESSAGE(parsedOK, "Failed to parse definition: " << errorMsg);

const auto& suites = defs.suiteVec();
BOOST_CHECK_EQUAL(suites.size(), static_cast<size_t>(1));

const auto& families = suites[0]->familyVec();
BOOST_CHECK_EQUAL(families.size(), static_cast<size_t>(1));

const auto& tasks = families[0]->taskVec();
BOOST_CHECK_EQUAL(tasks.size(), static_cast<size_t>(1));

Task* task = tasks[0].get();
auto& mirrors = task->mirrors();
BOOST_CHECK_EQUAL(mirrors.size(), static_cast<size_t>(1));

// Verify the flag is initially clear
BOOST_CHECK(!task->get_flag().is_set(Flag::REMOTE_ERROR));

// Call mirror() which would normally set REMOTE_ERROR if the controller fails to start
// But with ECF_MIRROR_DISABLE set, it should return early and NOT set REMOTE_ERROR
mirrors[0].mirror();

// Verify the flag remains clear (proving mirror() did not attempt to start controller)
BOOST_CHECK(!task->get_flag().is_set(Flag::REMOTE_ERROR));
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
Loading