diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e8700fff..69cbaf500 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.7 ) +project( ecflow LANGUAGES CXX VERSION 5.13.8 ) # 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 01e6db859..36e72a898 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ def get_ecflow_version(): - version = "5.13.7" + version = "5.13.8" ecflow_version = version.split(".") print("Extracted ecflow version '" + str(ecflow_version)) return ecflow_version diff --git a/docs/glossary.rst b/docs/glossary.rst index f75fbd303..7bcc6c32c 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -66,6 +66,10 @@ - :code:`polling`, the value (in seconds) used to periodically contact the Aviso server - :code:`auth`, the location to the Aviso authentication credentials file + .. note:: + + The `listener` parameter is expected to be a valid single line JSON string, enclosed in single quotes. + The value of the properties :code:`url`, :code:`schema`, :code:`polling`, and :code:`auth` can be composed of :term:`Variables`. When these properties are not provided, the following default values are used: @@ -92,7 +96,9 @@ 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 `_: + The authentication credentials file is expected to be in JSON format, following + the `ECMWF Web API `_ + (this is conventionally stored in a file located at `$HOME/.ecmwfapirc`): .. code-block:: json @@ -104,6 +110,10 @@ Only the fields :code:`url`, :code:`key`, and :code:`email` are required; any additional fields are ignored. + The Aviso schema file is a JSON file that defines the event listener schema. This is used by both Aviso server + and client (thus, by ecFlow) to define the valid event types and request parameters used when polling for + notifications. The schema file path must be provided to the `schema` option (or via the `ECF_AVISO_SCHEMA` variable). + check point The check point file is like the :term:`suite definition`, but includes all the state information. diff --git a/docs/python_api/AvisoAttr.rst b/docs/python_api/AvisoAttr.rst index d106dff92..142ee3b28 100644 --- a/docs/python_api/AvisoAttr.rst +++ b/docs/python_api/AvisoAttr.rst @@ -12,9 +12,14 @@ An :term:`aviso` attribute, assigned to a :term:`node`, represents an external t Although :term:`aviso` attributes can be set at any level (Suite, Family, Task), it only makes sense to assign aviso attributes to tasks, and only one aviso attribute per node is allowed. -Constructor:: - - AvisoAttr(name, listener, ...) +Constructors:: + + AvisoAttr(name, listener) (1) + AvisoAttr(name, listener, url) + AvisoAttr(name, listener, url, schema) + AvisoAttr(name, listener, url, schema, polling) + AvisoAttr(name, listener, url, schema, polling, auth) + with: string name: The Aviso attribute name string listener: The Aviso listener configuration (in JSON format) string url: The URL used to contact the Aviso server @@ -22,16 +27,26 @@ Constructor:: string polling: The polling interval used to contact the Aviso server string auth: The path to the Aviso Authentication credentials +Note: Default values, based on %ECF_AVISO_...% variables, will be used for the calls where +the parameters url, schema, polling, and auth are not provided + +We suggest to specify :code:`%ECF_AVISO_***%` variables once (at suite level), and then create the +Aviso attributes passing just the name and the listener definition as per call `(1)`. + +.. note:: The `listener` parameter is expected to be a valid single line JSON string, enclosed in single quotes. + As a convenience, missing surrounding single quotes are detected and will automatically be added. + +Details regarding the format of `listener` are in the section describing the :term:`aviso` attribute. + Usage: .. code-block:: python - t1 = Task('t1', - AvisoAttr('name', '{...}', 'http://aviso.com', '60', '/path/to/auth')) + t1 = Task('t1', AvisoAttr('name', "'{...}'")) t2 = Task('t2') - t2.add_aviso('name', '{...}', 'http://aviso.com', '60', '/path/to/auth') + t2.add_aviso('name', "'{...}'", 'http://aviso.com', '60', '/path/to/auth') The parameters `url`, `schema`, `polling`, and `auth` are optional diff --git a/docs/release_notes/version_5.13.rst b/docs/release_notes/version_5.13.rst index 79e347f46..f3982f3df 100644 --- a/docs/release_notes/version_5.13.rst +++ b/docs/release_notes/version_5.13.rst @@ -6,6 +6,26 @@ Version 5.13 updates .. role:: jiraissue :class: hidden +Version 5.13.8 +============== + +* `Released `__\ on 2025-03-12 + +General +------- + +- **Fix** correct Aviso notification retrieval after automatic requeueing :jiraissue:`ECFLOW-2010` + +Python +------ + +- **Fix** correct quote handling for ecflow.AvisoAttr listener :jiraissue:`ECFLOW-2011` + +Documentation +------------- + +- **Improvement** clarify use of schema for ecFlow Aviso attribute :jiraissue:`ECFLOW-2008` +- **Improvement** clarify how to define ecFlow Aviso authentication :jiraissue:`ECFLOW-2008` Version 5.13.7 ============== diff --git a/docs/ug/cookbook/how_to_trigger_a_task_based_on_aviso_notification.rst b/docs/ug/cookbook/how_to_trigger_a_task_based_on_aviso_notification.rst index 61ab03855..1ea2c3307 100644 --- a/docs/ug/cookbook/how_to_trigger_a_task_based_on_aviso_notification.rst +++ b/docs/ug/cookbook/how_to_trigger_a_task_based_on_aviso_notification.rst @@ -53,6 +53,28 @@ Follow the recommended practice of defining ecFlow :term:`variables` a endfamily endsuite +To enable Aviso authentication, set the `--auth` option (directly or via the `ECF_AVISO_AUTH` +variable) to the path of the JSON file containing the authentication credentials, which follows the +`ECMWF Web API `_. The credentials file +is conventionally located at `$HOME/.ecmwfapirc`, with the following content: + +.. code-block:: json + + { + "url" : "https://api.ecmwf.int/v1", + "key" : "", + "email" : "" + } + +The Aviso schema file is a JSON file that defines the event listener schema. This is used by +both Aviso server and client (thus, by ecFlow) to define the valid event types and request +parameters used when polling for notifications. The schema file path must be provided to the +`--schema` option (or via the `ECF_AVISO_SCHEMA` variable). + +The Aviso schema, `event_listener_schema.json`, used by ECMWF is available at ``_, +and is specific to the Aviso server being used (access is currently restricted; if necessary, please contact +support to request the files). + Define a Suite with an `Aviso` dependent Task ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/ug/user_manual/text_based_suite_definition/external/aviso.rst b/docs/ug/user_manual/text_based_suite_definition/external/aviso.rst index 56ee73cbe..3674c9f7e 100644 --- a/docs/ug/user_manual/text_based_suite_definition/external/aviso.rst +++ b/docs/ug/user_manual/text_based_suite_definition/external/aviso.rst @@ -19,8 +19,11 @@ notification. The options defining the attribute can be provided in any order. # --auth %ECF_AVISO_AUTH% # --polling %ECF_AVISO_POLLING% -Notice that the :code:`--listener` option must be surrounded by single quotes, -and is composed as a single line `JSON`. The `JSON` must define two fields: +.. note:: + + The `listener` parameter is expected to be a valid single line JSON string, enclosed in single quotes. + +The listener must define two fields (as per the `Aviso Listerner `_ definition): - :code:`event`, specifies the type of Aviso event - :code:`request`, specifies a dictionary with the parameters used to check for matches of Aviso notifications @@ -34,12 +37,19 @@ The following are some examples: '{ "event": "mars", "request": { "class": "od", "expver": "0001", "domain": "g", "stream": "abcd", "step": 0 } }' The Authentication credentials, provided via option :code:`--auth`, are -provided in a `JSON` file with the following content: +provided in a `JSON` file with the content following the +`ECMWF Web API `_ +(this is conventionally stored in a file located at `$HOME/.ecmwfapirc`): .. code-block:: json { - "url": "http://host.int:1234", - "key": "*******************************************************************", - "email": "user@host.int" + "url" : "https://api.ecmwf.int/v1", + "key" : "", + "email" : "" } + +The Aviso schema file is a JSON file that defines the event listener schema. This is used by +both Aviso server and client (thus, by ecFlow) to define the valid event types and request +parameters used when polling for notifications. The schema file path must be provided to the +`--schema` option (or via the `ECF_AVISO_SCHEMA` variable). diff --git a/libs/attribute/CMakeLists.txt b/libs/attribute/CMakeLists.txt index 75d441378..6c069b04f 100644 --- a/libs/attribute/CMakeLists.txt +++ b/libs/attribute/CMakeLists.txt @@ -12,6 +12,7 @@ set(test_srcs # Sources test/TestAttributes_main.cpp test/TestAttrSerialization.cpp + test/TestAvisoAttr.cpp test/TestCron.cpp test/TestDateAttr.cpp test/TestDayAttr.cpp diff --git a/libs/attribute/test/TestAvisoAttr.cpp b/libs/attribute/test/TestAvisoAttr.cpp new file mode 100644 index 000000000..2ac70177c --- /dev/null +++ b/libs/attribute/test/TestAvisoAttr.cpp @@ -0,0 +1,70 @@ +/* + * 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 + +#include "ecflow/node/AvisoAttr.hpp" + +BOOST_AUTO_TEST_SUITE(U_Attributes) + +BOOST_AUTO_TEST_SUITE(T_AvisoAttr) + +BOOST_AUTO_TEST_CASE(can_create_aviso_attribute_with_all_parameters) { + + using namespace ecf; + + auto aviso = AvisoAttr{nullptr, + "A", + R"({ "event": "mars", "request": { "class": "od"} })", + "http://host:port", + "/path/to/schema", + "60", + 0, + "/path/to/auth", + "'this is a reason'"}; + + BOOST_CHECK_EQUAL(aviso.name(), "A"); + BOOST_CHECK_EQUAL(aviso.listener(), R"('{ "event": "mars", "request": { "class": "od"} }')"); + BOOST_CHECK_EQUAL(aviso.url(), "http://host:port"); + BOOST_CHECK_EQUAL(aviso.schema(), "/path/to/schema"); + BOOST_CHECK_EQUAL(aviso.auth(), "/path/to/auth"); + BOOST_CHECK_EQUAL(aviso.polling(), "60"); + BOOST_CHECK_EQUAL(aviso.reason(), "'this is a reason'"); +} + +BOOST_AUTO_TEST_CASE(can_create_aviso_attribute_ensuring_single_quotes_when_absent) { + + using namespace ecf; + + // Notice the absence of single quotes, on both the listener and reason + auto aviso = AvisoAttr{nullptr, + "A", + R"({ "event": "mars", "request": { "class": "od"} })", + "http://host:port", + "/path/to/schema", + "60", + 0, + "/path/to/auth", + "this is a reason"}; + + BOOST_CHECK_EQUAL(aviso.name(), "A"); + BOOST_CHECK_EQUAL(aviso.listener(), R"('{ "event": "mars", "request": { "class": "od"} }')"); + BOOST_CHECK_EQUAL(aviso.url(), "http://host:port"); + BOOST_CHECK_EQUAL(aviso.schema(), "/path/to/schema"); + BOOST_CHECK_EQUAL(aviso.auth(), "/path/to/auth"); + BOOST_CHECK_EQUAL(aviso.polling(), "60"); + BOOST_CHECK_EQUAL(aviso.reason(), "'this is a reason'"); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/node/src/ecflow/node/AvisoAttr.cpp b/libs/node/src/ecflow/node/AvisoAttr.cpp index 321aebac5..12cc86f4a 100644 --- a/libs/node/src/ecflow/node/AvisoAttr.cpp +++ b/libs/node/src/ecflow/node/AvisoAttr.cpp @@ -21,28 +21,42 @@ namespace ecf { +namespace implementation { + +std::string ensure_single_quotes(const AvisoAttr::listener_t listener) { + using namespace std::string_literals; + if (!listener.empty() && listener.front() == '\'' && listener.back() == '\'') { + return listener; + } + else { + return "'"s + listener + "'"s; + } +} + +} // namespace implementation + bool AvisoAttr::is_valid_name(const std::string& name) { return ecf::Str::valid_name(name); } AvisoAttr::AvisoAttr(Node* parent, name_t name, - listener_t listener, + const listener_t& listener, url_t url, schema_t schema, polling_t polling, revision_t revision, auth_t auth, - reason_t reason) + const reason_t& reason) : parent_{parent}, parent_path_{parent ? parent->absNodePath() : ""}, name_{std::move(name)}, - listener_{std::move(listener)}, + listener_{implementation::ensure_single_quotes(listener)}, url_{std::move(url)}, schema_{std::move(schema)}, polling_{std::move(polling)}, auth_{std::move(auth)}, - reason_{std::move(reason)}, + reason_{implementation::ensure_single_quotes(reason)}, revision_{revision}, controller_{nullptr} { if (!ecf::Str::valid_name(name_)) { @@ -112,7 +126,9 @@ bool AvisoAttr::isFree() const { if (notifications.empty()) { // No notifications, nothing to do -- task continues to wait - SLOG(D, "AvisoAttr: (path: " << this->path() << ", name: " << name_ << ", listener: " << listener_ << "): no notifications found"); + SLOG(D, + "AvisoAttr: (path: " << this->path() << ", name: " << name_ << ", listener: " << listener_ + << "): no notifications found"); return false; } @@ -151,7 +167,9 @@ bool AvisoAttr::isFree() const { ecf::visit_parents(*parent_, [n = this->state_change_no_](Node& node) { node.set_state_change_no(n); }); - SLOG(D, "AvisoAttr: (path: " << this->path() << ", name: " << name_ << ", listener: " << listener_ << ") " << std::string{(is_free ? "" : "no ")} + "notifications found"); + SLOG(D, + "AvisoAttr: (path: " << this->path() << ", name: " << name_ << ", listener: " << listener_ << ") " + << std::string{(is_free ? "" : "no ")} + "notifications found"); return is_free; } diff --git a/libs/node/src/ecflow/node/AvisoAttr.hpp b/libs/node/src/ecflow/node/AvisoAttr.hpp index aa11e392b..8db6ccfc8 100644 --- a/libs/node/src/ecflow/node/AvisoAttr.hpp +++ b/libs/node/src/ecflow/node/AvisoAttr.hpp @@ -69,13 +69,13 @@ class AvisoAttr { AvisoAttr() = default; AvisoAttr(Node* parent, name_t name, - listener_t handle, + const listener_t& handle, url_t url, schema_t schema, polling_t polling, revision_t revision, auth_t auth, - reason_t reason); + const reason_t& reason); AvisoAttr(const AvisoAttr& rhs) = default; AvisoAttr& operator=(const AvisoAttr& rhs) = default; diff --git a/libs/node/src/ecflow/node/Submittable.cpp b/libs/node/src/ecflow/node/Submittable.cpp index 8f615b9db..56346fc60 100644 --- a/libs/node/src/ecflow/node/Submittable.cpp +++ b/libs/node/src/ecflow/node/Submittable.cpp @@ -88,7 +88,12 @@ void Submittable::init(const std::string& the_process_or_remote_id) { } void Submittable::complete() { - // cout << "Completed " << debugNodePath() << " at " << suite()->calendar().toString() << "\n"; + + // + // Completing the node might trigger an immediate Node requeue, so we must first finish the Aviso background thread. + // + std::for_each(std::begin(avisos()), std::end(avisos()), [](auto&& aviso) { aviso.finish(); }); + set_state(NState::COMPLETE); flag().clear(ecf::Flag::ZOMBIE); @@ -99,23 +104,21 @@ void Submittable::complete() { /// Hence to reduce network bandwidth we chose to clear the strings clear(); // jobs password, process_id, aborted_reason - for (auto&& aviso : avisos()) { - aviso.finish(); - } - #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Submittable::complete()\n"; #endif } void Submittable::aborted(const std::string& reason) { + + // + // Aborting the node might trigger an immediate Node requeue, so we must first finish the Aviso background thread. + // + std::for_each(std::begin(avisos()), std::end(avisos()), [](auto&& aviso) { aviso.finish(); }); + // Called during *abnormal* child termination // This will bubble the state, and decrement any limits set_aborted_only(reason); - - for (auto&& aviso : avisos()) { - aviso.finish(); - } } void Submittable::set_process_or_remote_id(const std::string& id) { diff --git a/libs/node/test/parser/TestAvisoAttr.cpp b/libs/node/test/parser/TestAvisoAttr.cpp index 1255e7d54..73ccf8f30 100644 --- a/libs/node/test/parser/TestAvisoAttr.cpp +++ b/libs/node/test/parser/TestAvisoAttr.cpp @@ -73,7 +73,7 @@ BOOST_AUTO_TEST_CASE(can_parse_aviso_attribute_on_task_with_all_parameters) { suite s1 family f1 task t1 - aviso --name A --listener '{ "event": "mars", "request": { "class": "od"} }' --url http://host:port --schema /path/to/schema --auth /path/to/auth --polling 60 + aviso --name A --listener '{ "event": "mars", "request": { "class": "od"} }' --url http://host:port --schema /path/to/schema --auth /path/to/auth --polling 60 --reason 'this is a reason' endfamily )"; diff --git a/libs/pyext/ecflow/__init__.py b/libs/pyext/ecflow/__init__.py index aa22a3e5e..9de290169 100644 --- a/libs/pyext/ecflow/__init__.py +++ b/libs/pyext/ecflow/__init__.py @@ -15,6 +15,6 @@ The ecFlow python module """ -__version__ = '5.13.7' +__version__ = '5.13.8' # http://stackoverflow.com/questions/13040646/how-do-i-create-documentation-with-pydoc diff --git a/libs/pyext/src/ecflow/python/NodeAttrDoc.cpp b/libs/pyext/src/ecflow/python/NodeAttrDoc.cpp index 5954496c1..9e21f9324 100644 --- a/libs/pyext/src/ecflow/python/NodeAttrDoc.cpp +++ b/libs/pyext/src/ecflow/python/NodeAttrDoc.cpp @@ -713,8 +713,13 @@ const char* NodeAttrDoc::aviso_doc() { "Although `aviso`_ attributes can be set at any level (Suite, Family, Task), it only makes sense to assign " "aviso attributes to tasks, and only one aviso attribute per node is allowed.\n" "\n" - "\nConstructor::\n\n" - " AvisoAttr(name, listener, ...)\n" + "\nConstructors::\n\n" + " AvisoAttr(name, listener) (1)\n" + " AvisoAttr(name, listener, url)\n" + " AvisoAttr(name, listener, url, schema)\n" + " AvisoAttr(name, listener, url, schema, polling)\n" + " AvisoAttr(name, listener, url, schema, polling, auth)\n" + " with:\n" " string name: The Aviso attribute name\n" " string listener: The Aviso listener configuration (in JSON format)\n" " string url: The URL used to contact the Aviso server\n" @@ -722,13 +727,24 @@ const char* NodeAttrDoc::aviso_doc() { " string polling: The polling interval used to contact the Aviso server\n" " string auth: The path to the Aviso Authentication credentials\n" "\n" + "Note: Default values, based on %ECF_AVISO_...% variables, will be used for the calls where\n" + "the parameters url, schema, polling, and auth are not provided\n" + "\n" + "We suggest to specify :code:`%ECF_AVISO_***%` variables once (at suite level), and then create the\n" + "Aviso attributes passing just the name and the listener definition as per call `(1)`.\n" + "\n" + ".. note::" + " The `listener` parameter is expected to be a valid single line JSON string, enclosed in single quotes.\n" + " As a convenience, missing surrounding single quotes are detected and will automatically be added.\n" + "\n" + "Details regarding the format of `listener` are in the section describing the `aviso`_ attribute.\n" + "\n" "\nUsage:\n\n" ".. code-block:: python\n\n" - " t1 = Task('t1',\n" - " AvisoAttr('name', '{...}', 'http://aviso.com', '60', '/path/to/auth'))\n" + " t1 = Task('t1', AvisoAttr('name', \"'{...}'\"))\n" "\n" " t2 = Task('t2')\n" - " t2.add_aviso('name', '{...}', 'http://aviso.com', '60', '/path/to/auth')\n" + " t2.add_aviso('name', \"'{...}'\", 'http://aviso.com', '60', '/path/to/auth')\n" "\n" "The parameters `url`, `schema`, `polling`, and `auth` are optional\n"; } diff --git a/libs/pyext/test/py_u_TestAviso.py b/libs/pyext/test/py_u_TestAviso.py index 99d7a516e..3ff93f050 100644 --- a/libs/pyext/test/py_u_TestAviso.py +++ b/libs/pyext/test/py_u_TestAviso.py @@ -9,16 +9,23 @@ # import os +import io import ecflow as ecf import itertools as it import ecflow_test_util as Test +def to_str(defs): + buffer = io.StringIO() + print(defs, file=buffer) + return buffer.getvalue() + + def can_create_aviso_from_parameters(): aviso = ecf.AvisoAttr("name", "listener", "url", "schema", "polling", "auth") assert aviso.name() == "name" - assert aviso.listener() == "listener" + assert aviso.listener() == "'listener'" assert aviso.url() == "url" assert aviso.schema() == "schema" assert aviso.polling() == "polling" @@ -36,7 +43,7 @@ def can_create_aviso_from_default_parameters_0(): actual = list(task.avisos)[0] assert actual.name() == "name" - assert actual.listener() == "listener" + assert actual.listener() == "'listener'" assert actual.url() == "%ECF_AVISO_URL%" assert actual.schema() == "%ECF_AVISO_SCHEMA%" assert actual.polling() == "%ECF_AVISO_POLLING%" @@ -54,7 +61,7 @@ def can_create_aviso_from_default_parameters_1(): actual = list(task.avisos)[0] assert actual.name() == "name" - assert actual.listener() == "listener" + assert actual.listener() == "'listener'" assert actual.url() == "url" assert actual.schema() == "%ECF_AVISO_SCHEMA%" assert actual.polling() == "%ECF_AVISO_POLLING%" @@ -72,7 +79,7 @@ def can_create_aviso_from_default_parameters_2(): actual = list(task.avisos)[0] assert actual.name() == "name" - assert actual.listener() == "listener" + assert actual.listener() == "'listener'" assert actual.url() == "url" assert actual.schema() == "schema" assert actual.polling() == "%ECF_AVISO_POLLING%" @@ -90,13 +97,37 @@ def can_create_aviso_from_default_parameters_3(): actual = list(task.avisos)[0] assert actual.name() == "name" - assert actual.listener() == "listener" + assert actual.listener() == "'listener'" assert actual.url() == "url" assert actual.schema() == "schema" assert actual.polling() == "polling" assert actual.auth() == "%ECF_AVISO_AUTH%" +def can_create_aviso_with_listener_details(): + defs = ecf.Defs() + suite = defs.add_suite("s") + family = ecf.Family("f") + suite.add_family(family) + task = ecf.Task('t', + ecf.AvisoAttr('aviso', + '{ "event": "dissemination", "request": { "destination": "CL1", "class": "od", "expver": "1", "stream": "oper", "step": [0, 12] } }', + 'https://aviso.ecmwf.int', + 'schema.json', + '60', + '/.ecmwfapirc')) + family.add_task(task) + + content = to_str(defs) + + assert f"aviso" in content + assert f'--listener \'{{ "event": "dissemination", "request": {{ "destination": "CL1", "class": "od", "expver": "1", "stream": "oper", "step": [0, 12] }} }}\'' in content + assert f"--url https://aviso.ecmwf.int" in content + assert f"--schema schema.json" in content + assert f"--polling 60" in content + assert f"--auth /.ecmwfapirc" in content + + def can_add_aviso_to_task(): suite = ecf.Suite("s1") @@ -112,7 +143,7 @@ def can_add_aviso_to_task(): actual = list(task.avisos)[0] assert actual.name() == "name" - assert actual.listener() == "listener" + assert actual.listener() == "'listener'" assert actual.url() == "url" assert actual.schema() == "schema" assert actual.polling() == "polling" @@ -130,7 +161,7 @@ def can_embed_aviso_into_task(): actual = list(task.avisos)[0] assert actual.name() == "name" - assert actual.listener() == "listener" + assert actual.listener() == "'listener'" assert actual.url() == "url" assert actual.schema() == "schema" assert actual.polling() == "polling" @@ -177,6 +208,7 @@ def can_check_job_creation_with_aviso(): can_create_aviso_from_default_parameters_1() can_create_aviso_from_default_parameters_2() can_create_aviso_from_default_parameters_3() + can_create_aviso_with_listener_details() can_add_aviso_to_task() can_embed_aviso_into_task() cannot_have_multiple_avisos_in_single_task() diff --git a/libs/service/src/ecflow/service/aviso/etcd/Client.cpp b/libs/service/src/ecflow/service/aviso/etcd/Client.cpp index c054a4008..6f15a88d5 100644 --- a/libs/service/src/ecflow/service/aviso/etcd/Client.cpp +++ b/libs/service/src/ecflow/service/aviso/etcd/Client.cpp @@ -52,6 +52,8 @@ std::vector> Client::poll(std::string_view k .dump(); std::string content_type = "application/json"; + SLOG(D, "EtcdClient: Requesting notifications from " << address_ << "/" << endpoint_path); + httplib::Result result = client_.Post(endpoint_path, headers, request_body, content_type); if (!result) { throw std::runtime_error(Message("EtcdClient: Unable to retrieve result, due to ", result.error()).str());