0c6c6d
From de05f6b52c667155d262ceeb541dc1041d079d71 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 8 Sep 2022 11:36:58 -0400
0c6c6d
Subject: [PATCH 01/26] Refactor: tools: Use a uint32_t for attr_options.
0c6c6d
0c6c6d
---
0c6c6d
 tools/attrd_updater.c | 2 +-
0c6c6d
 1 file changed, 1 insertion(+), 1 deletion(-)
0c6c6d
0c6c6d
diff --git a/tools/attrd_updater.c b/tools/attrd_updater.c
0c6c6d
index d90567a..b85a281 100644
0c6c6d
--- a/tools/attrd_updater.c
0c6c6d
+++ b/tools/attrd_updater.c
0c6c6d
@@ -47,7 +47,7 @@ struct {
0c6c6d
     gchar *attr_node;
0c6c6d
     gchar *attr_set;
0c6c6d
     char *attr_value;
0c6c6d
-    int attr_options;
0c6c6d
+    uint32_t attr_options;
0c6c6d
     gboolean query_all;
0c6c6d
     gboolean quiet;
0c6c6d
 } options = {
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From c6637520b474d44553ade52c0dbe9e36e873135f Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Fri, 21 Oct 2022 14:31:16 -0400
0c6c6d
Subject: [PATCH 02/26] Refactor: libcrmcommon: Make pcmk__xe_match more
0c6c6d
 broadly useful.
0c6c6d
0c6c6d
If attr_v is NULL, simply return the first node with a matching name.
0c6c6d
---
0c6c6d
 lib/common/xml.c | 10 ++++++----
0c6c6d
 1 file changed, 6 insertions(+), 4 deletions(-)
0c6c6d
0c6c6d
diff --git a/lib/common/xml.c b/lib/common/xml.c
0c6c6d
index 036dd87..ac6f46a 100644
0c6c6d
--- a/lib/common/xml.c
0c6c6d
+++ b/lib/common/xml.c
0c6c6d
@@ -510,7 +510,7 @@ find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
0c6c6d
  * \param[in] parent     XML element to search
0c6c6d
  * \param[in] node_name  If not NULL, only match children of this type
0c6c6d
  * \param[in] attr_n     If not NULL, only match children with an attribute
0c6c6d
- *                       of this name and a value of \p attr_v
0c6c6d
+ *                       of this name.
0c6c6d
  * \param[in] attr_v     If \p attr_n and this are not NULL, only match children
0c6c6d
  *                       with an attribute named \p attr_n and this value
0c6c6d
  *
0c6c6d
@@ -520,14 +520,16 @@ xmlNode *
0c6c6d
 pcmk__xe_match(const xmlNode *parent, const char *node_name,
0c6c6d
                const char *attr_n, const char *attr_v)
0c6c6d
 {
0c6c6d
-    /* ensure attr_v specified when attr_n is */
0c6c6d
-    CRM_CHECK(attr_n == NULL || attr_v != NULL, return NULL);
0c6c6d
+    CRM_CHECK(parent != NULL, return NULL);
0c6c6d
+    CRM_CHECK(attr_v == NULL || attr_n != NULL, return NULL);
0c6c6d
 
0c6c6d
     for (xmlNode *child = pcmk__xml_first_child(parent); child != NULL;
0c6c6d
          child = pcmk__xml_next(child)) {
0c6c6d
         if (pcmk__str_eq(node_name, (const char *) (child->name),
0c6c6d
                          pcmk__str_null_matches)
0c6c6d
-            && ((attr_n == NULL) || attr_matches(child, attr_n, attr_v))) {
0c6c6d
+            && ((attr_n == NULL) ||
0c6c6d
+                (attr_v == NULL && xmlHasProp(child, (pcmkXmlStr) attr_n)) ||
0c6c6d
+                (attr_v != NULL && attr_matches(child, attr_n, attr_v)))) {
0c6c6d
             return child;
0c6c6d
         }
0c6c6d
     }
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From dd520579484c6ec091f7fbb550347941302dad0e Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Fri, 21 Oct 2022 14:32:46 -0400
0c6c6d
Subject: [PATCH 03/26] Tests: libcrmcommon: Add tests for pcmk__xe_match.
0c6c6d
0c6c6d
---
0c6c6d
 lib/common/tests/xml/Makefile.am           |   3 +-
0c6c6d
 lib/common/tests/xml/pcmk__xe_match_test.c | 105 +++++++++++++++++++++
0c6c6d
 2 files changed, 107 insertions(+), 1 deletion(-)
0c6c6d
 create mode 100644 lib/common/tests/xml/pcmk__xe_match_test.c
0c6c6d
0c6c6d
diff --git a/lib/common/tests/xml/Makefile.am b/lib/common/tests/xml/Makefile.am
0c6c6d
index 342ca07..0ccdcc3 100644
0c6c6d
--- a/lib/common/tests/xml/Makefile.am
0c6c6d
+++ b/lib/common/tests/xml/Makefile.am
0c6c6d
@@ -11,6 +11,7 @@ include $(top_srcdir)/mk/tap.mk
0c6c6d
 include $(top_srcdir)/mk/unittest.mk
0c6c6d
 
0c6c6d
 # Add "_test" to the end of all test program names to simplify .gitignore.
0c6c6d
-check_PROGRAMS =	pcmk__xe_foreach_child_test
0c6c6d
+check_PROGRAMS =	pcmk__xe_foreach_child_test \
0c6c6d
+					pcmk__xe_match_test
0c6c6d
 
0c6c6d
 TESTS = $(check_PROGRAMS)
0c6c6d
diff --git a/lib/common/tests/xml/pcmk__xe_match_test.c b/lib/common/tests/xml/pcmk__xe_match_test.c
0c6c6d
new file mode 100644
0c6c6d
index 0000000..fd529ba
0c6c6d
--- /dev/null
0c6c6d
+++ b/lib/common/tests/xml/pcmk__xe_match_test.c
0c6c6d
@@ -0,0 +1,105 @@
0c6c6d
+/*
0c6c6d
+ * Copyright 2022 the Pacemaker project contributors
0c6c6d
+ *
0c6c6d
+ * The version control history for this file may have further details.
0c6c6d
+ *
0c6c6d
+ * This source code is licensed under the GNU Lesser General Public License
0c6c6d
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
0c6c6d
+ */
0c6c6d
+
0c6c6d
+#include <crm_internal.h>
0c6c6d
+
0c6c6d
+#include <crm/common/unittest_internal.h>
0c6c6d
+#include <crm/common/xml_internal.h>
0c6c6d
+
0c6c6d
+const char *str1 =
0c6c6d
+    "<xml>\n"
0c6c6d
+    "  \n"
0c6c6d
+    "  <nodeA attrA=\"123\" id=\"1\">\n"
0c6c6d
+    "    content\n"
0c6c6d
+    "  </nodeA>\n"
0c6c6d
+    "  \n"
0c6c6d
+    "  <nodeA attrA=\"456\" id=\"2\">\n"
0c6c6d
+    "    content\n"
0c6c6d
+    "  </nodeA>\n"
0c6c6d
+    "  \n"
0c6c6d
+    "  <nodeA attrB=\"XYZ\" id=\"3\">\n"
0c6c6d
+    "    content\n"
0c6c6d
+    "  </nodeA>\n"
0c6c6d
+    "  \n"
0c6c6d
+    "  <nodeB attrA=\"123\" id=\"4\">\n"
0c6c6d
+    "    content\n"
0c6c6d
+    "  </nodeA>\n"
0c6c6d
+    "  \n"
0c6c6d
+    "  <nodeB attrB=\"ABC\" id=\"5\">\n"
0c6c6d
+    "    content\n"
0c6c6d
+    "  </nodeA>\n"
0c6c6d
+    "</xml>";
0c6c6d
+
0c6c6d
+static void
0c6c6d
+bad_input(void **state) {
0c6c6d
+    xmlNode *xml = string2xml(str1);
0c6c6d
+
0c6c6d
+    assert_null(pcmk__xe_match(NULL, NULL, NULL, NULL));
0c6c6d
+    assert_null(pcmk__xe_match(NULL, NULL, NULL, "attrX"));
0c6c6d
+
0c6c6d
+    free_xml(xml);
0c6c6d
+}
0c6c6d
+
0c6c6d
+static void
0c6c6d
+not_found(void **state) {
0c6c6d
+    xmlNode *xml = string2xml(str1);
0c6c6d
+
0c6c6d
+    /* No node with an attrX attribute */
0c6c6d
+    assert_null(pcmk__xe_match(xml, NULL, "attrX", NULL));
0c6c6d
+    /* No nodeX node */
0c6c6d
+    assert_null(pcmk__xe_match(xml, "nodeX", NULL, NULL));
0c6c6d
+    /* No nodeA node with attrX */
0c6c6d
+    assert_null(pcmk__xe_match(xml, "nodeA", "attrX", NULL));
0c6c6d
+    /* No nodeA node with attrA=XYZ */
0c6c6d
+    assert_null(pcmk__xe_match(xml, "nodeA", "attrA", "XYZ"));
0c6c6d
+
0c6c6d
+    free_xml(xml);
0c6c6d
+}
0c6c6d
+
0c6c6d
+static void
0c6c6d
+find_attrB(void **state) {
0c6c6d
+    xmlNode *xml = string2xml(str1);
0c6c6d
+    xmlNode *result = NULL;
0c6c6d
+
0c6c6d
+    /* Find the first node with attrB */
0c6c6d
+    result = pcmk__xe_match(xml, NULL, "attrB", NULL);
0c6c6d
+    assert_non_null(result);
0c6c6d
+    assert_string_equal(crm_element_value(result, "id"), "3");
0c6c6d
+
0c6c6d
+    /* Find the first nodeB with attrB */
0c6c6d
+    result = pcmk__xe_match(xml, "nodeB", "attrB", NULL);
0c6c6d
+    assert_non_null(result);
0c6c6d
+    assert_string_equal(crm_element_value(result, "id"), "5");
0c6c6d
+
0c6c6d
+    free_xml(xml);
0c6c6d
+}
0c6c6d
+
0c6c6d
+static void
0c6c6d
+find_attrA_matching(void **state) {
0c6c6d
+    xmlNode *xml = string2xml(str1);
0c6c6d
+    xmlNode *result = NULL;
0c6c6d
+
0c6c6d
+    /* Find attrA=456 */
0c6c6d
+    result = pcmk__xe_match(xml, NULL, "attrA", "456");
0c6c6d
+    assert_non_null(result);
0c6c6d
+    assert_string_equal(crm_element_value(result, "id"), "2");
0c6c6d
+
0c6c6d
+    /* Find a nodeB with attrA=123 */
0c6c6d
+    result = pcmk__xe_match(xml, "nodeB", "attrA", "123");
0c6c6d
+    assert_non_null(result);
0c6c6d
+    assert_string_equal(crm_element_value(result, "id"), "4");
0c6c6d
+
0c6c6d
+    free_xml(xml);
0c6c6d
+}
0c6c6d
+
0c6c6d
+PCMK__UNIT_TEST(NULL, NULL,
0c6c6d
+                cmocka_unit_test(bad_input),
0c6c6d
+                cmocka_unit_test(not_found),
0c6c6d
+                cmocka_unit_test(find_attrB),
0c6c6d
+                cmocka_unit_test(find_attrA_matching));
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 03af8498d8aaf21c509cec9b0ec4b78475da41d7 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 8 Sep 2022 12:22:26 -0400
0c6c6d
Subject: [PATCH 04/26] Feature: libcrmcommon: Add attrd options for specifying
0c6c6d
 a sync point.
0c6c6d
0c6c6d
---
0c6c6d
 include/crm/common/attrd_internal.h | 16 +++++++++-------
0c6c6d
 1 file changed, 9 insertions(+), 7 deletions(-)
0c6c6d
0c6c6d
diff --git a/include/crm/common/attrd_internal.h b/include/crm/common/attrd_internal.h
0c6c6d
index f7033ad..389be48 100644
0c6c6d
--- a/include/crm/common/attrd_internal.h
0c6c6d
+++ b/include/crm/common/attrd_internal.h
0c6c6d
@@ -16,13 +16,15 @@ extern "C" {
0c6c6d
 
0c6c6d
 // Options for clients to use with functions below
0c6c6d
 enum pcmk__node_attr_opts {
0c6c6d
-    pcmk__node_attr_none    = 0,
0c6c6d
-    pcmk__node_attr_remote  = (1 << 0),
0c6c6d
-    pcmk__node_attr_private = (1 << 1),
0c6c6d
-    pcmk__node_attr_pattern = (1 << 2),
0c6c6d
-    pcmk__node_attr_value   = (1 << 3),
0c6c6d
-    pcmk__node_attr_delay   = (1 << 4),
0c6c6d
-    pcmk__node_attr_perm    = (1 << 5),
0c6c6d
+    pcmk__node_attr_none           = 0,
0c6c6d
+    pcmk__node_attr_remote         = (1 << 0),
0c6c6d
+    pcmk__node_attr_private        = (1 << 1),
0c6c6d
+    pcmk__node_attr_pattern        = (1 << 2),
0c6c6d
+    pcmk__node_attr_value          = (1 << 3),
0c6c6d
+    pcmk__node_attr_delay          = (1 << 4),
0c6c6d
+    pcmk__node_attr_perm           = (1 << 5),
0c6c6d
+    pcmk__node_attr_sync_local     = (1 << 6),
0c6c6d
+    pcmk__node_attr_sync_cluster   = (1 << 7),
0c6c6d
 };
0c6c6d
 
0c6c6d
 #define pcmk__set_node_attr_flags(node_attr_flags, flags_to_set) do {   \
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 5c8825293ee21d3823bdcd01b0df9c7d39739940 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 8 Sep 2022 12:23:09 -0400
0c6c6d
Subject: [PATCH 05/26] Feature: libcrmcommon: Add sync point to IPC request
0c6c6d
 XML.
0c6c6d
0c6c6d
If one of the pcmk__node_attr_sync_* options is provided, add an
0c6c6d
attribute to the request XML.  This will later be inspected by the
0c6c6d
server to determine when to send the reply to the client.
0c6c6d
---
0c6c6d
 include/crm/common/options_internal.h | 2 ++
0c6c6d
 include/crm_internal.h                | 1 +
0c6c6d
 lib/common/ipc_attrd.c                | 6 ++++++
0c6c6d
 3 files changed, 9 insertions(+)
0c6c6d
0c6c6d
diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h
0c6c6d
index b153c67..f29ba3f 100644
0c6c6d
--- a/include/crm/common/options_internal.h
0c6c6d
+++ b/include/crm/common/options_internal.h
0c6c6d
@@ -145,9 +145,11 @@ bool pcmk__valid_sbd_timeout(const char *value);
0c6c6d
 #define PCMK__META_ALLOW_UNHEALTHY_NODES    "allow-unhealthy-nodes"
0c6c6d
 
0c6c6d
 // Constants for enumerated values for various options
0c6c6d
+#define PCMK__VALUE_CLUSTER                 "cluster"
0c6c6d
 #define PCMK__VALUE_CUSTOM                  "custom"
0c6c6d
 #define PCMK__VALUE_FENCING                 "fencing"
0c6c6d
 #define PCMK__VALUE_GREEN                   "green"
0c6c6d
+#define PCMK__VALUE_LOCAL                   "local"
0c6c6d
 #define PCMK__VALUE_MIGRATE_ON_RED          "migrate-on-red"
0c6c6d
 #define PCMK__VALUE_NONE                    "none"
0c6c6d
 #define PCMK__VALUE_NOTHING                 "nothing"
0c6c6d
diff --git a/include/crm_internal.h b/include/crm_internal.h
0c6c6d
index e6e2e96..08193c3 100644
0c6c6d
--- a/include/crm_internal.h
0c6c6d
+++ b/include/crm_internal.h
0c6c6d
@@ -71,6 +71,7 @@
0c6c6d
 #define PCMK__XA_ATTR_RESOURCE          "attr_resource"
0c6c6d
 #define PCMK__XA_ATTR_SECTION           "attr_section"
0c6c6d
 #define PCMK__XA_ATTR_SET               "attr_set"
0c6c6d
+#define PCMK__XA_ATTR_SYNC_POINT        "attr_sync_point"
0c6c6d
 #define PCMK__XA_ATTR_USER              "attr_user"
0c6c6d
 #define PCMK__XA_ATTR_UUID              "attr_key"
0c6c6d
 #define PCMK__XA_ATTR_VALUE             "attr_value"
0c6c6d
diff --git a/lib/common/ipc_attrd.c b/lib/common/ipc_attrd.c
0c6c6d
index f6cfbc4..4606509 100644
0c6c6d
--- a/lib/common/ipc_attrd.c
0c6c6d
+++ b/lib/common/ipc_attrd.c
0c6c6d
@@ -431,6 +431,12 @@ populate_update_op(xmlNode *op, const char *node, const char *name, const char *
0c6c6d
                     pcmk_is_set(options, pcmk__node_attr_remote));
0c6c6d
     crm_xml_add_int(op, PCMK__XA_ATTR_IS_PRIVATE,
0c6c6d
                     pcmk_is_set(options, pcmk__node_attr_private));
0c6c6d
+
0c6c6d
+    if (pcmk_is_set(options, pcmk__node_attr_sync_local)) {
0c6c6d
+        crm_xml_add(op, PCMK__XA_ATTR_SYNC_POINT, PCMK__VALUE_LOCAL);
0c6c6d
+    } else if (pcmk_is_set(options, pcmk__node_attr_sync_cluster)) {
0c6c6d
+        crm_xml_add(op, PCMK__XA_ATTR_SYNC_POINT, PCMK__VALUE_CLUSTER);
0c6c6d
+    }
0c6c6d
 }
0c6c6d
 
0c6c6d
 int
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From e2b3fee630caf0846ca8bbffcef4d6d2acfd32a5 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 8 Sep 2022 12:26:28 -0400
0c6c6d
Subject: [PATCH 06/26] Feature: tools: Add --wait= parameter to attrd_updater.
0c6c6d
0c6c6d
This command line option is used to specify the sync point to use.  For
0c6c6d
the moment, it has no effect.
0c6c6d
---
0c6c6d
 tools/attrd_updater.c | 24 ++++++++++++++++++++++++
0c6c6d
 1 file changed, 24 insertions(+)
0c6c6d
0c6c6d
diff --git a/tools/attrd_updater.c b/tools/attrd_updater.c
0c6c6d
index b85a281..c4779a6 100644
0c6c6d
--- a/tools/attrd_updater.c
0c6c6d
+++ b/tools/attrd_updater.c
0c6c6d
@@ -97,6 +97,22 @@ section_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError
0c6c6d
     return TRUE;
0c6c6d
 }
0c6c6d
 
0c6c6d
+static gboolean
0c6c6d
+wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
0c6c6d
+    if (pcmk__str_eq(optarg, "no", pcmk__str_none)) {
0c6c6d
+        pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
0c6c6d
+        return TRUE;
0c6c6d
+    } else if (pcmk__str_eq(optarg, PCMK__VALUE_LOCAL, pcmk__str_none)) {
0c6c6d
+        pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
0c6c6d
+        pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local);
0c6c6d
+        return TRUE;
0c6c6d
+    } else {
0c6c6d
+        g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE,
0c6c6d
+                    "--wait= must be one of 'no', 'local', 'cluster'");
0c6c6d
+        return FALSE;
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
 #define INDENT "                              "
0c6c6d
 
0c6c6d
 static GOptionEntry required_entries[] = {
0c6c6d
@@ -175,6 +191,14 @@ static GOptionEntry addl_entries[] = {
0c6c6d
       "If this creates a new attribute, never write the attribute to CIB",
0c6c6d
       NULL },
0c6c6d
 
0c6c6d
+    { "wait", 'W', 0, G_OPTION_ARG_CALLBACK, wait_cb,
0c6c6d
+      "Wait for some event to occur before returning.  Values are 'no' (wait\n"
0c6c6d
+      INDENT "only for the attribute daemon to acknowledge the request) or\n"
0c6c6d
+      INDENT "'local' (wait until the change has propagated to where a local\n"
0c6c6d
+      INDENT "query will return the request value, or the value set by a\n"
0c6c6d
+      INDENT "later request).  Default is 'no'.",
0c6c6d
+      "UNTIL" },
0c6c6d
+
0c6c6d
     { NULL }
0c6c6d
 };
0c6c6d
 
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 52d51ab41b2f00e72724ab39835b3db86605a96b Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 20 Oct 2022 14:40:13 -0400
0c6c6d
Subject: [PATCH 07/26] Feature: daemons: Add functions for checking a request
0c6c6d
 for a sync point.
0c6c6d
0c6c6d
---
0c6c6d
 daemons/attrd/Makefile.am       |  1 +
0c6c6d
 daemons/attrd/attrd_sync.c      | 38 +++++++++++++++++++++++++++++++++
0c6c6d
 daemons/attrd/pacemaker-attrd.h |  3 +++
0c6c6d
 3 files changed, 42 insertions(+)
0c6c6d
 create mode 100644 daemons/attrd/attrd_sync.c
0c6c6d
0c6c6d
diff --git a/daemons/attrd/Makefile.am b/daemons/attrd/Makefile.am
0c6c6d
index 1a3d360..6bb81c4 100644
0c6c6d
--- a/daemons/attrd/Makefile.am
0c6c6d
+++ b/daemons/attrd/Makefile.am
0c6c6d
@@ -32,6 +32,7 @@ pacemaker_attrd_SOURCES	= attrd_alerts.c 	\
0c6c6d
 						  attrd_elections.c \
0c6c6d
 						  attrd_ipc.c 		\
0c6c6d
 						  attrd_messages.c 		\
0c6c6d
+						  attrd_sync.c 		\
0c6c6d
 						  attrd_utils.c 	\
0c6c6d
 						  pacemaker-attrd.c
0c6c6d
 
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
new file mode 100644
0c6c6d
index 0000000..92759d2
0c6c6d
--- /dev/null
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -0,0 +1,38 @@
0c6c6d
+/*
0c6c6d
+ * Copyright 2022 the Pacemaker project contributors
0c6c6d
+ *
0c6c6d
+ * The version control history for this file may have further details.
0c6c6d
+ *
0c6c6d
+ * This source code is licensed under the GNU General Public License version 2
0c6c6d
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
0c6c6d
+ */
0c6c6d
+
0c6c6d
+#include <crm_internal.h>
0c6c6d
+
0c6c6d
+#include <crm/msg_xml.h>
0c6c6d
+#include <crm/common/attrd_internal.h>
0c6c6d
+
0c6c6d
+#include "pacemaker-attrd.h"
0c6c6d
+
0c6c6d
+const char *
0c6c6d
+attrd_request_sync_point(xmlNode *xml)
0c6c6d
+{
0c6c6d
+    if (xml_has_children(xml)) {
0c6c6d
+        xmlNode *child = pcmk__xe_match(xml, XML_ATTR_OP, PCMK__XA_ATTR_SYNC_POINT, NULL);
0c6c6d
+
0c6c6d
+        if (child) {
0c6c6d
+            return crm_element_value(child, PCMK__XA_ATTR_SYNC_POINT);
0c6c6d
+        } else {
0c6c6d
+            return NULL;
0c6c6d
+        }
0c6c6d
+
0c6c6d
+    } else {
0c6c6d
+        return crm_element_value(xml, PCMK__XA_ATTR_SYNC_POINT);
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
+bool
0c6c6d
+attrd_request_has_sync_point(xmlNode *xml)
0c6c6d
+{
0c6c6d
+    return attrd_request_sync_point(xml) != NULL;
0c6c6d
+}
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
0c6c6d
index 71ce90a..ff850bb 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.h
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.h
0c6c6d
@@ -182,4 +182,7 @@ mainloop_timer_t *attrd_add_timer(const char *id, int timeout_ms, attribute_t *a
0c6c6d
 void attrd_unregister_handlers(void);
0c6c6d
 void attrd_handle_request(pcmk__request_t *request);
0c6c6d
 
0c6c6d
+const char *attrd_request_sync_point(xmlNode *xml);
0c6c6d
+bool attrd_request_has_sync_point(xmlNode *xml);
0c6c6d
+
0c6c6d
 #endif /* PACEMAKER_ATTRD__H */
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 2e0509a12ee7d4a612133ee65b75245eea7d271d Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 20 Oct 2022 14:42:04 -0400
0c6c6d
Subject: [PATCH 08/26] Refactor: daemons: Don't ACK update requests that give
0c6c6d
 a sync point.
0c6c6d
0c6c6d
The ACK is the only response from the server for update messages.  If
0c6c6d
the message specified that it wanted to wait for a sync point, we need
0c6c6d
to delay sending that response until the sync point is reached.
0c6c6d
Therefore, do not always immediately send the ACK.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_messages.c | 19 ++++++++++++++-----
0c6c6d
 1 file changed, 14 insertions(+), 5 deletions(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c
0c6c6d
index de4a28a..9e8ae40 100644
0c6c6d
--- a/daemons/attrd/attrd_messages.c
0c6c6d
+++ b/daemons/attrd/attrd_messages.c
0c6c6d
@@ -137,12 +137,21 @@ handle_update_request(pcmk__request_t *request)
0c6c6d
         attrd_peer_update(peer, request->xml, host, false);
0c6c6d
         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
         return NULL;
0c6c6d
+
0c6c6d
     } else {
0c6c6d
-        /* Because attrd_client_update can be called recursively, we send the ACK
0c6c6d
-         * here to ensure that the client only ever receives one.
0c6c6d
-         */
0c6c6d
-        attrd_send_ack(request->ipc_client, request->ipc_id,
0c6c6d
-                       request->flags|crm_ipc_client_response);
0c6c6d
+        if (!attrd_request_has_sync_point(request->xml)) {
0c6c6d
+            /* If the client doesn't want to wait for a sync point, go ahead and send
0c6c6d
+             * the ACK immediately.  Otherwise, we'll send the ACK when the appropriate
0c6c6d
+             * sync point is reached.
0c6c6d
+             *
0c6c6d
+             * In the normal case, attrd_client_update can be called recursively which
0c6c6d
+             * makes where to send the ACK tricky.  Doing it here ensures the client
0c6c6d
+             * only ever receives one.
0c6c6d
+             */
0c6c6d
+            attrd_send_ack(request->ipc_client, request->ipc_id,
0c6c6d
+                           request->flags|crm_ipc_client_response);
0c6c6d
+        }
0c6c6d
+
0c6c6d
         return attrd_client_update(request);
0c6c6d
     }
0c6c6d
 }
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 2a0ff66cdf0085c4c8ab1992ef7e785a4facc8c7 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 20 Oct 2022 14:48:48 -0400
0c6c6d
Subject: [PATCH 09/26] Feature: daemons: Add support for local sync points on
0c6c6d
 updates.
0c6c6d
0c6c6d
In the IPC dispatcher for attrd, add the client to a wait list if its
0c6c6d
request specifies a sync point.  When the attribute's value is changed
0c6c6d
on the local attrd, alert any clients waiting on a local sync point by
0c6c6d
then sending the previously delayed ACK.
0c6c6d
0c6c6d
Sync points for other requests and the global sync point are not yet
0c6c6d
supported.
0c6c6d
0c6c6d
Fixes T35.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_corosync.c  |  18 +++++
0c6c6d
 daemons/attrd/attrd_messages.c  |  12 ++-
0c6c6d
 daemons/attrd/attrd_sync.c      | 137 ++++++++++++++++++++++++++++++++
0c6c6d
 daemons/attrd/pacemaker-attrd.h |   7 ++
0c6c6d
 4 files changed, 173 insertions(+), 1 deletion(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c
0c6c6d
index 539e5bf..4337280 100644
0c6c6d
--- a/daemons/attrd/attrd_corosync.c
0c6c6d
+++ b/daemons/attrd/attrd_corosync.c
0c6c6d
@@ -568,14 +568,32 @@ void
0c6c6d
 attrd_peer_update(const crm_node_t *peer, xmlNode *xml, const char *host,
0c6c6d
                   bool filter)
0c6c6d
 {
0c6c6d
+    bool handle_sync_point = false;
0c6c6d
+
0c6c6d
     if (xml_has_children(xml)) {
0c6c6d
         for (xmlNode *child = first_named_child(xml, XML_ATTR_OP); child != NULL;
0c6c6d
              child = crm_next_same_xml(child)) {
0c6c6d
             copy_attrs(xml, child);
0c6c6d
             attrd_peer_update_one(peer, child, filter);
0c6c6d
+
0c6c6d
+            if (attrd_request_has_sync_point(child)) {
0c6c6d
+                handle_sync_point = true;
0c6c6d
+            }
0c6c6d
         }
0c6c6d
 
0c6c6d
     } else {
0c6c6d
         attrd_peer_update_one(peer, xml, filter);
0c6c6d
+
0c6c6d
+        if (attrd_request_has_sync_point(xml)) {
0c6c6d
+            handle_sync_point = true;
0c6c6d
+        }
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    /* If the update XML specified that the client wanted to wait for a sync
0c6c6d
+     * point, process that now.
0c6c6d
+     */
0c6c6d
+    if (handle_sync_point) {
0c6c6d
+        crm_debug("Hit local sync point for attribute update");
0c6c6d
+        attrd_ack_waitlist_clients(attrd_sync_point_local, xml);
0c6c6d
     }
0c6c6d
 }
0c6c6d
diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c
0c6c6d
index 9e8ae40..c96700f 100644
0c6c6d
--- a/daemons/attrd/attrd_messages.c
0c6c6d
+++ b/daemons/attrd/attrd_messages.c
0c6c6d
@@ -139,7 +139,17 @@ handle_update_request(pcmk__request_t *request)
0c6c6d
         return NULL;
0c6c6d
 
0c6c6d
     } else {
0c6c6d
-        if (!attrd_request_has_sync_point(request->xml)) {
0c6c6d
+        if (attrd_request_has_sync_point(request->xml)) {
0c6c6d
+            /* If this client supplied a sync point it wants to wait for, add it to
0c6c6d
+             * the wait list.  Clients on this list will not receive an ACK until
0c6c6d
+             * their sync point is hit which will result in the client stalled there
0c6c6d
+             * until it receives a response.
0c6c6d
+             *
0c6c6d
+             * All other clients will receive the expected response as normal.
0c6c6d
+             */
0c6c6d
+            attrd_add_client_to_waitlist(request);
0c6c6d
+
0c6c6d
+        } else {
0c6c6d
             /* If the client doesn't want to wait for a sync point, go ahead and send
0c6c6d
              * the ACK immediately.  Otherwise, we'll send the ACK when the appropriate
0c6c6d
              * sync point is reached.
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index 92759d2..2981bd0 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -14,6 +14,143 @@
0c6c6d
 
0c6c6d
 #include "pacemaker-attrd.h"
0c6c6d
 
0c6c6d
+/* A hash table storing clients that are waiting on a sync point to be reached.
0c6c6d
+ * The key is waitlist_client - just a plain int.  The obvious key would be
0c6c6d
+ * the IPC client's ID, but this is not guaranteed to be unique.  A single client
0c6c6d
+ * could be waiting on a sync point for multiple attributes at the same time.
0c6c6d
+ *
0c6c6d
+ * It is not expected that this hash table will ever be especially large.
0c6c6d
+ */
0c6c6d
+static GHashTable *waitlist = NULL;
0c6c6d
+static int waitlist_client = 0;
0c6c6d
+
0c6c6d
+struct waitlist_node {
0c6c6d
+    /* What kind of sync point does this node describe? */
0c6c6d
+    enum attrd_sync_point sync_point;
0c6c6d
+
0c6c6d
+    /* Information required to construct and send a reply to the client. */
0c6c6d
+    char *client_id;
0c6c6d
+    uint32_t ipc_id;
0c6c6d
+    uint32_t flags;
0c6c6d
+};
0c6c6d
+
0c6c6d
+static void
0c6c6d
+next_key(void)
0c6c6d
+{
0c6c6d
+    do {
0c6c6d
+        waitlist_client++;
0c6c6d
+        if (waitlist_client < 0) {
0c6c6d
+            waitlist_client = 1;
0c6c6d
+        }
0c6c6d
+    } while (g_hash_table_contains(waitlist, GINT_TO_POINTER(waitlist_client)));
0c6c6d
+}
0c6c6d
+
0c6c6d
+static void
0c6c6d
+free_waitlist_node(gpointer data)
0c6c6d
+{
0c6c6d
+    struct waitlist_node *wl = (struct waitlist_node *) data;
0c6c6d
+
0c6c6d
+    free(wl->client_id);
0c6c6d
+    free(wl);
0c6c6d
+}
0c6c6d
+
0c6c6d
+static const char *
0c6c6d
+sync_point_str(enum attrd_sync_point sync_point)
0c6c6d
+{
0c6c6d
+    if (sync_point == attrd_sync_point_local) {
0c6c6d
+        return PCMK__VALUE_LOCAL;
0c6c6d
+    } else if  (sync_point == attrd_sync_point_cluster) {
0c6c6d
+        return PCMK__VALUE_CLUSTER;
0c6c6d
+    } else {
0c6c6d
+        return "unknown";
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
+void
0c6c6d
+attrd_add_client_to_waitlist(pcmk__request_t *request)
0c6c6d
+{
0c6c6d
+    const char *sync_point = attrd_request_sync_point(request->xml);
0c6c6d
+    struct waitlist_node *wl = NULL;
0c6c6d
+
0c6c6d
+    if (sync_point == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    if (waitlist == NULL) {
0c6c6d
+        waitlist = pcmk__intkey_table(free_waitlist_node);
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    wl = calloc(sizeof(struct waitlist_node), 1);
0c6c6d
+
0c6c6d
+    CRM_ASSERT(wl != NULL);
0c6c6d
+
0c6c6d
+    wl->client_id = strdup(request->ipc_client->id);
0c6c6d
+
0c6c6d
+    CRM_ASSERT(wl->client_id);
0c6c6d
+
0c6c6d
+    if (pcmk__str_eq(sync_point, PCMK__VALUE_LOCAL, pcmk__str_none)) {
0c6c6d
+        wl->sync_point = attrd_sync_point_local;
0c6c6d
+    } else if (pcmk__str_eq(sync_point, PCMK__VALUE_CLUSTER, pcmk__str_none)) {
0c6c6d
+        wl->sync_point = attrd_sync_point_cluster;
0c6c6d
+    } else {
0c6c6d
+        free_waitlist_node(wl);
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    wl->ipc_id = request->ipc_id;
0c6c6d
+    wl->flags = request->flags;
0c6c6d
+
0c6c6d
+    crm_debug("Added client %s to waitlist for %s sync point",
0c6c6d
+              wl->client_id, sync_point_str(wl->sync_point));
0c6c6d
+
0c6c6d
+    next_key();
0c6c6d
+    pcmk__intkey_table_insert(waitlist, waitlist_client, wl);
0c6c6d
+
0c6c6d
+    /* And then add the key to the request XML so we can uniquely identify
0c6c6d
+     * it when it comes time to issue the ACK.
0c6c6d
+     */
0c6c6d
+    crm_xml_add_int(request->xml, XML_LRM_ATTR_CALLID, waitlist_client);
0c6c6d
+}
0c6c6d
+
0c6c6d
+void
0c6c6d
+attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml)
0c6c6d
+{
0c6c6d
+    int callid;
0c6c6d
+    gpointer value;
0c6c6d
+
0c6c6d
+    if (waitlist == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    if (crm_element_value_int(xml, XML_LRM_ATTR_CALLID, &callid) == -1) {
0c6c6d
+        crm_warn("Could not get callid from request XML");
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    value = pcmk__intkey_table_lookup(waitlist, callid);
0c6c6d
+    if (value != NULL) {
0c6c6d
+        struct waitlist_node *wl = (struct waitlist_node *) value;
0c6c6d
+        pcmk__client_t *client = NULL;
0c6c6d
+
0c6c6d
+        if (wl->sync_point != sync_point) {
0c6c6d
+            return;
0c6c6d
+        }
0c6c6d
+
0c6c6d
+        crm_debug("Alerting client %s for reached %s sync point",
0c6c6d
+                  wl->client_id, sync_point_str(wl->sync_point));
0c6c6d
+
0c6c6d
+        client = pcmk__find_client_by_id(wl->client_id);
0c6c6d
+        if (client == NULL) {
0c6c6d
+            return;
0c6c6d
+        }
0c6c6d
+
0c6c6d
+        attrd_send_ack(client, wl->ipc_id, wl->flags | crm_ipc_client_response);
0c6c6d
+
0c6c6d
+        /* And then remove the client so it doesn't get alerted again. */
0c6c6d
+        pcmk__intkey_table_remove(waitlist, callid);
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
 const char *
0c6c6d
 attrd_request_sync_point(xmlNode *xml)
0c6c6d
 {
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
0c6c6d
index ff850bb..9dd8320 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.h
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.h
0c6c6d
@@ -182,6 +182,13 @@ mainloop_timer_t *attrd_add_timer(const char *id, int timeout_ms, attribute_t *a
0c6c6d
 void attrd_unregister_handlers(void);
0c6c6d
 void attrd_handle_request(pcmk__request_t *request);
0c6c6d
 
0c6c6d
+enum attrd_sync_point {
0c6c6d
+    attrd_sync_point_local,
0c6c6d
+    attrd_sync_point_cluster,
0c6c6d
+};
0c6c6d
+
0c6c6d
+void attrd_add_client_to_waitlist(pcmk__request_t *request);
0c6c6d
+void attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml);
0c6c6d
 const char *attrd_request_sync_point(xmlNode *xml);
0c6c6d
 bool attrd_request_has_sync_point(xmlNode *xml);
0c6c6d
 
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 59caaf1682191a91d6062358b770f8b9457ba3eb Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 20 Oct 2022 14:56:58 -0400
0c6c6d
Subject: [PATCH 10/26] Feature: daemons: If a client disconnects, remove it
0c6c6d
 from the waitlist.
0c6c6d
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_ipc.c       |  5 +++++
0c6c6d
 daemons/attrd/attrd_sync.c      | 21 +++++++++++++++++++++
0c6c6d
 daemons/attrd/pacemaker-attrd.h |  1 +
0c6c6d
 3 files changed, 27 insertions(+)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c
0c6c6d
index 7e4a1c0..8aa39c2 100644
0c6c6d
--- a/daemons/attrd/attrd_ipc.c
0c6c6d
+++ b/daemons/attrd/attrd_ipc.c
0c6c6d
@@ -438,8 +438,13 @@ attrd_ipc_closed(qb_ipcs_connection_t *c)
0c6c6d
         crm_trace("Ignoring request to clean up unknown connection %p", c);
0c6c6d
     } else {
0c6c6d
         crm_trace("Cleaning up closed client connection %p", c);
0c6c6d
+
0c6c6d
+        /* Remove the client from the sync point waitlist if it's present. */
0c6c6d
+        attrd_remove_client_from_waitlist(client);
0c6c6d
+
0c6c6d
         pcmk__free_client(client);
0c6c6d
     }
0c6c6d
+
0c6c6d
     return FALSE;
0c6c6d
 }
0c6c6d
 
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index 2981bd0..7293318 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -112,6 +112,27 @@ attrd_add_client_to_waitlist(pcmk__request_t *request)
0c6c6d
     crm_xml_add_int(request->xml, XML_LRM_ATTR_CALLID, waitlist_client);
0c6c6d
 }
0c6c6d
 
0c6c6d
+void
0c6c6d
+attrd_remove_client_from_waitlist(pcmk__client_t *client)
0c6c6d
+{
0c6c6d
+    GHashTableIter iter;
0c6c6d
+    gpointer value;
0c6c6d
+
0c6c6d
+    if (waitlist == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    g_hash_table_iter_init(&iter, waitlist);
0c6c6d
+
0c6c6d
+    while (g_hash_table_iter_next(&iter, NULL, &value)) {
0c6c6d
+        struct waitlist_node *wl = (struct waitlist_node *) value;
0c6c6d
+
0c6c6d
+        if (wl->client_id == client->id) {
0c6c6d
+            g_hash_table_iter_remove(&iter);
0c6c6d
+        }
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
 void
0c6c6d
 attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml)
0c6c6d
 {
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
0c6c6d
index 9dd8320..b6ecb75 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.h
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.h
0c6c6d
@@ -189,6 +189,7 @@ enum attrd_sync_point {
0c6c6d
 
0c6c6d
 void attrd_add_client_to_waitlist(pcmk__request_t *request);
0c6c6d
 void attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml);
0c6c6d
+void attrd_remove_client_from_waitlist(pcmk__client_t *client);
0c6c6d
 const char *attrd_request_sync_point(xmlNode *xml);
0c6c6d
 bool attrd_request_has_sync_point(xmlNode *xml);
0c6c6d
 
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From b28042e1d64b48c96dbd9da1e9ee3ff481bbf620 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Mon, 10 Oct 2022 11:00:20 -0400
0c6c6d
Subject: [PATCH 11/26] Feature: daemons: Add support for local sync points on
0c6c6d
 clearing failures.
0c6c6d
0c6c6d
attrd_clear_client_failure just calls attrd_client_update underneath, so
0c6c6d
that function will handle all the rest of the sync point functionality
0c6c6d
for us.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_ipc.c      |  2 --
0c6c6d
 daemons/attrd/attrd_messages.c | 19 +++++++++++++++++++
0c6c6d
 2 files changed, 19 insertions(+), 2 deletions(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c
0c6c6d
index 8aa39c2..2e614e8 100644
0c6c6d
--- a/daemons/attrd/attrd_ipc.c
0c6c6d
+++ b/daemons/attrd/attrd_ipc.c
0c6c6d
@@ -101,8 +101,6 @@ attrd_client_clear_failure(pcmk__request_t *request)
0c6c6d
     xmlNode *xml = request->xml;
0c6c6d
     const char *rsc, *op, *interval_spec;
0c6c6d
 
0c6c6d
-    attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
0c6c6d
-
0c6c6d
     if (minimum_protocol_version >= 2) {
0c6c6d
         /* Propagate to all peers (including ourselves).
0c6c6d
          * This ends up at attrd_peer_message().
0c6c6d
diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c
0c6c6d
index c96700f..3ba14a6 100644
0c6c6d
--- a/daemons/attrd/attrd_messages.c
0c6c6d
+++ b/daemons/attrd/attrd_messages.c
0c6c6d
@@ -42,6 +42,25 @@ handle_clear_failure_request(pcmk__request_t *request)
0c6c6d
         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
         return NULL;
0c6c6d
     } else {
0c6c6d
+        if (attrd_request_has_sync_point(request->xml)) {
0c6c6d
+            /* If this client supplied a sync point it wants to wait for, add it to
0c6c6d
+             * the wait list.  Clients on this list will not receive an ACK until
0c6c6d
+             * their sync point is hit which will result in the client stalled there
0c6c6d
+             * until it receives a response.
0c6c6d
+             *
0c6c6d
+             * All other clients will receive the expected response as normal.
0c6c6d
+             */
0c6c6d
+            attrd_add_client_to_waitlist(request);
0c6c6d
+
0c6c6d
+        } else {
0c6c6d
+            /* If the client doesn't want to wait for a sync point, go ahead and send
0c6c6d
+             * the ACK immediately.  Otherwise, we'll send the ACK when the appropriate
0c6c6d
+             * sync point is reached.
0c6c6d
+             */
0c6c6d
+            attrd_send_ack(request->ipc_client, request->ipc_id,
0c6c6d
+                           request->ipc_flags);
0c6c6d
+        }
0c6c6d
+
0c6c6d
         return attrd_client_clear_failure(request);
0c6c6d
     }
0c6c6d
 }
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 291dc3b91e57f2584bbf88cfbe3a360e0332e814 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Mon, 10 Oct 2022 13:17:24 -0400
0c6c6d
Subject: [PATCH 12/26] Refactor: daemons: Free the waitlist on attrd exit.
0c6c6d
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_sync.c      | 11 +++++++++++
0c6c6d
 daemons/attrd/attrd_utils.c     |  2 ++
0c6c6d
 daemons/attrd/pacemaker-attrd.c |  1 +
0c6c6d
 daemons/attrd/pacemaker-attrd.h |  1 +
0c6c6d
 4 files changed, 15 insertions(+)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index 7293318..557e49a 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -112,6 +112,17 @@ attrd_add_client_to_waitlist(pcmk__request_t *request)
0c6c6d
     crm_xml_add_int(request->xml, XML_LRM_ATTR_CALLID, waitlist_client);
0c6c6d
 }
0c6c6d
 
0c6c6d
+void
0c6c6d
+attrd_free_waitlist(void)
0c6c6d
+{
0c6c6d
+    if (waitlist == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    g_hash_table_destroy(waitlist);
0c6c6d
+    waitlist = NULL;
0c6c6d
+}
0c6c6d
+
0c6c6d
 void
0c6c6d
 attrd_remove_client_from_waitlist(pcmk__client_t *client)
0c6c6d
 {
0c6c6d
diff --git a/daemons/attrd/attrd_utils.c b/daemons/attrd/attrd_utils.c
0c6c6d
index 6a19009..00b879b 100644
0c6c6d
--- a/daemons/attrd/attrd_utils.c
0c6c6d
+++ b/daemons/attrd/attrd_utils.c
0c6c6d
@@ -93,6 +93,8 @@ attrd_shutdown(int nsig)
0c6c6d
     mainloop_destroy_signal(SIGUSR2);
0c6c6d
     mainloop_destroy_signal(SIGTRAP);
0c6c6d
 
0c6c6d
+    attrd_free_waitlist();
0c6c6d
+
0c6c6d
     if ((mloop == NULL) || !g_main_loop_is_running(mloop)) {
0c6c6d
         /* If there's no main loop active, just exit. This should be possible
0c6c6d
          * only if we get SIGTERM in brief windows at start-up and shutdown.
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.c b/daemons/attrd/pacemaker-attrd.c
0c6c6d
index 2100db4..1336542 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.c
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.c
0c6c6d
@@ -300,6 +300,7 @@ main(int argc, char **argv)
0c6c6d
         attrd_ipc_fini();
0c6c6d
         attrd_lrmd_disconnect();
0c6c6d
         attrd_cib_disconnect();
0c6c6d
+        attrd_free_waitlist();
0c6c6d
         g_hash_table_destroy(attributes);
0c6c6d
     }
0c6c6d
 
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
0c6c6d
index b6ecb75..537bf85 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.h
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.h
0c6c6d
@@ -52,6 +52,7 @@ void attrd_run_mainloop(void);
0c6c6d
 
0c6c6d
 void attrd_set_requesting_shutdown(void);
0c6c6d
 void attrd_clear_requesting_shutdown(void);
0c6c6d
+void attrd_free_waitlist(void);
0c6c6d
 bool attrd_requesting_shutdown(void);
0c6c6d
 bool attrd_shutting_down(void);
0c6c6d
 void attrd_shutdown(int nsig);
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 7715ce617c520e14687a82e11ff794c93cd7f64a Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Mon, 10 Oct 2022 13:21:16 -0400
0c6c6d
Subject: [PATCH 13/26] Feature: includes: Bump CRM_FEATURE_SET for local sync
0c6c6d
 points.
0c6c6d
0c6c6d
---
0c6c6d
 include/crm/crm.h | 2 +-
0c6c6d
 1 file changed, 1 insertion(+), 1 deletion(-)
0c6c6d
0c6c6d
diff --git a/include/crm/crm.h b/include/crm/crm.h
0c6c6d
index 5710e4b..7c5c602 100644
0c6c6d
--- a/include/crm/crm.h
0c6c6d
+++ b/include/crm/crm.h
0c6c6d
@@ -66,7 +66,7 @@ extern "C" {
0c6c6d
  * >=3.0.13: Fail counts include operation name and interval
0c6c6d
  * >=3.2.0:  DC supports PCMK_EXEC_INVALID and PCMK_EXEC_NOT_CONNECTED
0c6c6d
  */
0c6c6d
-#  define CRM_FEATURE_SET		"3.16.1"
0c6c6d
+#  define CRM_FEATURE_SET		"3.16.2"
0c6c6d
 
0c6c6d
 /* Pacemaker's CPG protocols use fixed-width binary fields for the sender and
0c6c6d
  * recipient of a CPG message. This imposes an arbitrary limit on cluster node
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From b9054425a76d03f538cd0b3ae27490b1874eee8a Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Fri, 28 Oct 2022 14:23:49 -0400
0c6c6d
Subject: [PATCH 14/26] Refactor: daemons: Add comments for previously added
0c6c6d
 sync point code.
0c6c6d
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_sync.c | 63 ++++++++++++++++++++++++++++++++++++++
0c6c6d
 1 file changed, 63 insertions(+)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index 557e49a..e9690b5 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -66,6 +66,20 @@ sync_point_str(enum attrd_sync_point sync_point)
0c6c6d
     }
0c6c6d
 }
0c6c6d
 
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Add a client to the attrd waitlist
0c6c6d
+ *
0c6c6d
+ * Typically, a client receives an ACK for its XML IPC request immediately.  However,
0c6c6d
+ * some clients want to wait until their request has been processed and taken effect.
0c6c6d
+ * This is called a sync point.  Any client placed on this waitlist will have its
0c6c6d
+ * ACK message delayed until either its requested sync point is hit, or until it
0c6c6d
+ * times out.
0c6c6d
+ *
0c6c6d
+ * The XML IPC request must specify the type of sync point it wants to wait for.
0c6c6d
+ *
0c6c6d
+ * \param[in,out] request   The request describing the client to place on the waitlist.
0c6c6d
+ */
0c6c6d
 void
0c6c6d
 attrd_add_client_to_waitlist(pcmk__request_t *request)
0c6c6d
 {
0c6c6d
@@ -112,6 +126,11 @@ attrd_add_client_to_waitlist(pcmk__request_t *request)
0c6c6d
     crm_xml_add_int(request->xml, XML_LRM_ATTR_CALLID, waitlist_client);
0c6c6d
 }
0c6c6d
 
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Free all memory associated with the waitlist.  This is most typically
0c6c6d
+ *        used when attrd shuts down.
0c6c6d
+ */
0c6c6d
 void
0c6c6d
 attrd_free_waitlist(void)
0c6c6d
 {
0c6c6d
@@ -123,6 +142,13 @@ attrd_free_waitlist(void)
0c6c6d
     waitlist = NULL;
0c6c6d
 }
0c6c6d
 
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Unconditionally remove a client from the waitlist, such as when the client
0c6c6d
+ *        node disconnects from the cluster
0c6c6d
+ *
0c6c6d
+ * \param[in] client    The client to remove
0c6c6d
+ */
0c6c6d
 void
0c6c6d
 attrd_remove_client_from_waitlist(pcmk__client_t *client)
0c6c6d
 {
0c6c6d
@@ -144,6 +170,18 @@ attrd_remove_client_from_waitlist(pcmk__client_t *client)
0c6c6d
     }
0c6c6d
 }
0c6c6d
 
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Send an IPC ACK message to all awaiting clients
0c6c6d
+ *
0c6c6d
+ * This function will search the waitlist for all clients that are currently awaiting
0c6c6d
+ * an ACK indicating their attrd operation is complete.  Only those clients with a
0c6c6d
+ * matching sync point type and callid from their original XML IPC request will be
0c6c6d
+ * ACKed.  Once they have received an ACK, they will be removed from the waitlist.
0c6c6d
+ *
0c6c6d
+ * \param[in] sync_point What kind of sync point have we hit?
0c6c6d
+ * \param[in] xml        The original XML IPC request.
0c6c6d
+ */
0c6c6d
 void
0c6c6d
 attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml)
0c6c6d
 {
0c6c6d
@@ -183,6 +221,23 @@ attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml)
0c6c6d
     }
0c6c6d
 }
0c6c6d
 
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Return the sync point attribute for an IPC request
0c6c6d
+ *
0c6c6d
+ * This function will check both the top-level element of \p xml for a sync
0c6c6d
+ * point attribute, as well as all of its \p op children, if any.  The latter
0c6c6d
+ * is useful for newer versions of attrd that can put multiple IPC requests
0c6c6d
+ * into a single message.
0c6c6d
+ *
0c6c6d
+ * \param[in] xml   An XML IPC request
0c6c6d
+ *
0c6c6d
+ * \note It is assumed that if one child element has a sync point attribute,
0c6c6d
+ *       all will have a sync point attribute and they will all be the same
0c6c6d
+ *       sync point.  No other configuration is supported.
0c6c6d
+ *
0c6c6d
+ * \return The sync point attribute of \p xml, or NULL if none.
0c6c6d
+ */
0c6c6d
 const char *
0c6c6d
 attrd_request_sync_point(xmlNode *xml)
0c6c6d
 {
0c6c6d
@@ -200,6 +255,14 @@ attrd_request_sync_point(xmlNode *xml)
0c6c6d
     }
0c6c6d
 }
0c6c6d
 
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Does an IPC request contain any sync point attribute?
0c6c6d
+ *
0c6c6d
+ * \param[in] xml   An XML IPC request
0c6c6d
+ *
0c6c6d
+ * \return true if there's a sync point attribute, false otherwise
0c6c6d
+ */
0c6c6d
 bool
0c6c6d
 attrd_request_has_sync_point(xmlNode *xml)
0c6c6d
 {
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 64219fb7075ee58d29f94f077a3b8f94174bb32a Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Wed, 26 Oct 2022 12:43:05 -0400
0c6c6d
Subject: [PATCH 15/26] Feature: tools: Add --wait=cluster option to
0c6c6d
 attrd_updater.
0c6c6d
0c6c6d
---
0c6c6d
 tools/attrd_updater.c | 10 ++++++++--
0c6c6d
 1 file changed, 8 insertions(+), 2 deletions(-)
0c6c6d
0c6c6d
diff --git a/tools/attrd_updater.c b/tools/attrd_updater.c
0c6c6d
index c4779a6..3cd766d 100644
0c6c6d
--- a/tools/attrd_updater.c
0c6c6d
+++ b/tools/attrd_updater.c
0c6c6d
@@ -106,6 +106,10 @@ wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **
0c6c6d
         pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
0c6c6d
         pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local);
0c6c6d
         return TRUE;
0c6c6d
+    } else if (pcmk__str_eq(optarg, PCMK__VALUE_CLUSTER, pcmk__str_none)) {
0c6c6d
+        pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
0c6c6d
+        pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_cluster);
0c6c6d
+        return TRUE;
0c6c6d
     } else {
0c6c6d
         g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE,
0c6c6d
                     "--wait= must be one of 'no', 'local', 'cluster'");
0c6c6d
@@ -193,10 +197,12 @@ static GOptionEntry addl_entries[] = {
0c6c6d
 
0c6c6d
     { "wait", 'W', 0, G_OPTION_ARG_CALLBACK, wait_cb,
0c6c6d
       "Wait for some event to occur before returning.  Values are 'no' (wait\n"
0c6c6d
-      INDENT "only for the attribute daemon to acknowledge the request) or\n"
0c6c6d
+      INDENT "only for the attribute daemon to acknowledge the request),\n"
0c6c6d
       INDENT "'local' (wait until the change has propagated to where a local\n"
0c6c6d
       INDENT "query will return the request value, or the value set by a\n"
0c6c6d
-      INDENT "later request).  Default is 'no'.",
0c6c6d
+      INDENT "later request), or 'cluster' (wait until the change has propagated\n"
0c6c6d
+      INDENT "to where a query anywhere on the cluster will return the requested\n"
0c6c6d
+      INDENT "value, or the value set by a later request).  Default is 'no'.",
0c6c6d
       "UNTIL" },
0c6c6d
 
0c6c6d
     { NULL }
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 1bc5511fadf6ad670508bd3a2a55129bde16f774 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Fri, 16 Sep 2022 14:55:06 -0400
0c6c6d
Subject: [PATCH 16/26] Refactor: daemons: Add a confirm= attribute to attrd
0c6c6d
 messages.
0c6c6d
0c6c6d
This allows informing the originator of a message that the message has
0c6c6d
been received and processed.  As yet, there is no mechanism for handling
0c6c6d
and returning the confirmation, only for requesting it.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_corosync.c  |  6 +++---
0c6c6d
 daemons/attrd/attrd_ipc.c       | 26 +++++++++++++++++++++-----
0c6c6d
 daemons/attrd/attrd_messages.c  | 11 +++++++++--
0c6c6d
 daemons/attrd/pacemaker-attrd.h |  7 ++++---
0c6c6d
 include/crm_internal.h          |  1 +
0c6c6d
 5 files changed, 38 insertions(+), 13 deletions(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c
0c6c6d
index 4337280..e86ca07 100644
0c6c6d
--- a/daemons/attrd/attrd_corosync.c
0c6c6d
+++ b/daemons/attrd/attrd_corosync.c
0c6c6d
@@ -124,7 +124,7 @@ broadcast_local_value(const attribute_t *a)
0c6c6d
 
0c6c6d
     crm_xml_add(sync, PCMK__XA_TASK, PCMK__ATTRD_CMD_SYNC_RESPONSE);
0c6c6d
     attrd_add_value_xml(sync, a, v, false);
0c6c6d
-    attrd_send_message(NULL, sync);
0c6c6d
+    attrd_send_message(NULL, sync, false);
0c6c6d
     free_xml(sync);
0c6c6d
     return v;
0c6c6d
 }
0c6c6d
@@ -387,7 +387,7 @@ broadcast_unseen_local_values(void)
0c6c6d
 
0c6c6d
     if (sync != NULL) {
0c6c6d
         crm_debug("Broadcasting local-only values");
0c6c6d
-        attrd_send_message(NULL, sync);
0c6c6d
+        attrd_send_message(NULL, sync, false);
0c6c6d
         free_xml(sync);
0c6c6d
     }
0c6c6d
 }
0c6c6d
@@ -539,7 +539,7 @@ attrd_peer_sync(crm_node_t *peer, xmlNode *xml)
0c6c6d
     }
0c6c6d
 
0c6c6d
     crm_debug("Syncing values to %s", peer?peer->uname:"everyone");
0c6c6d
-    attrd_send_message(peer, sync);
0c6c6d
+    attrd_send_message(peer, sync, false);
0c6c6d
     free_xml(sync);
0c6c6d
 }
0c6c6d
 
0c6c6d
diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c
0c6c6d
index 2e614e8..0fc5e93 100644
0c6c6d
--- a/daemons/attrd/attrd_ipc.c
0c6c6d
+++ b/daemons/attrd/attrd_ipc.c
0c6c6d
@@ -105,7 +105,7 @@ attrd_client_clear_failure(pcmk__request_t *request)
0c6c6d
         /* Propagate to all peers (including ourselves).
0c6c6d
          * This ends up at attrd_peer_message().
0c6c6d
          */
0c6c6d
-        attrd_send_message(NULL, xml);
0c6c6d
+        attrd_send_message(NULL, xml, false);
0c6c6d
         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
         return NULL;
0c6c6d
     }
0c6c6d
@@ -184,7 +184,7 @@ attrd_client_peer_remove(pcmk__request_t *request)
0c6c6d
     if (host) {
0c6c6d
         crm_info("Client %s is requesting all values for %s be removed",
0c6c6d
                  pcmk__client_name(request->ipc_client), host);
0c6c6d
-        attrd_send_message(NULL, xml); /* ends up at attrd_peer_message() */
0c6c6d
+        attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
0c6c6d
         free(host_alloc);
0c6c6d
     } else {
0c6c6d
         crm_info("Ignoring request by client %s to remove all peer values without specifying peer",
0c6c6d
@@ -314,7 +314,7 @@ attrd_client_update(pcmk__request_t *request)
0c6c6d
                 }
0c6c6d
             }
0c6c6d
 
0c6c6d
-            attrd_send_message(NULL, xml);
0c6c6d
+            attrd_send_message(NULL, xml, false);
0c6c6d
             pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
 
0c6c6d
         } else {
0c6c6d
@@ -358,7 +358,7 @@ attrd_client_update(pcmk__request_t *request)
0c6c6d
                 if (status == 0) {
0c6c6d
                     crm_trace("Matched %s with %s", attr, regex);
0c6c6d
                     crm_xml_add(xml, PCMK__XA_ATTR_NAME, attr);
0c6c6d
-                    attrd_send_message(NULL, xml);
0c6c6d
+                    attrd_send_message(NULL, xml, false);
0c6c6d
                 }
0c6c6d
             }
0c6c6d
 
0c6c6d
@@ -388,7 +388,23 @@ attrd_client_update(pcmk__request_t *request)
0c6c6d
     crm_debug("Broadcasting %s[%s]=%s%s", attr, crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME),
0c6c6d
               value, (attrd_election_won()? " (writer)" : ""));
0c6c6d
 
0c6c6d
-    attrd_send_message(NULL, xml); /* ends up at attrd_peer_message() */
0c6c6d
+    if (pcmk__str_eq(attrd_request_sync_point(xml), PCMK__VALUE_CLUSTER, pcmk__str_none)) {
0c6c6d
+        /* The client is waiting on the cluster-wide sync point.  In this case,
0c6c6d
+         * the response ACK is not sent until this attrd broadcasts the update
0c6c6d
+         * and receives its own confirmation back from all peers.
0c6c6d
+         */
0c6c6d
+        attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
0c6c6d
+
0c6c6d
+    } else {
0c6c6d
+        /* The client is either waiting on the local sync point or was not
0c6c6d
+         * waiting on any sync point at all.  For the local sync point, the
0c6c6d
+         * response ACK is sent in attrd_peer_update.  For clients not
0c6c6d
+         * waiting on any sync point, the response ACK is sent in
0c6c6d
+         * handle_update_request immediately before this function was called.
0c6c6d
+         */
0c6c6d
+        attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
0c6c6d
+    }
0c6c6d
+
0c6c6d
     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
     return NULL;
0c6c6d
 }
0c6c6d
diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c
0c6c6d
index 3ba14a6..78df0d0 100644
0c6c6d
--- a/daemons/attrd/attrd_messages.c
0c6c6d
+++ b/daemons/attrd/attrd_messages.c
0c6c6d
@@ -279,16 +279,23 @@ attrd_broadcast_protocol(void)
0c6c6d
     crm_debug("Broadcasting attrd protocol version %s for node %s",
0c6c6d
               ATTRD_PROTOCOL_VERSION, attrd_cluster->uname);
0c6c6d
 
0c6c6d
-    attrd_send_message(NULL, attrd_op); /* ends up at attrd_peer_message() */
0c6c6d
+    attrd_send_message(NULL, attrd_op, false); /* ends up at attrd_peer_message() */
0c6c6d
 
0c6c6d
     free_xml(attrd_op);
0c6c6d
 }
0c6c6d
 
0c6c6d
 gboolean
0c6c6d
-attrd_send_message(crm_node_t * node, xmlNode * data)
0c6c6d
+attrd_send_message(crm_node_t *node, xmlNode *data, bool confirm)
0c6c6d
 {
0c6c6d
     crm_xml_add(data, F_TYPE, T_ATTRD);
0c6c6d
     crm_xml_add(data, PCMK__XA_ATTR_VERSION, ATTRD_PROTOCOL_VERSION);
0c6c6d
+
0c6c6d
+    /* Request a confirmation from the destination peer node (which could
0c6c6d
+     * be all if node is NULL) that the message has been received and
0c6c6d
+     * acted upon.
0c6c6d
+     */
0c6c6d
+    pcmk__xe_set_bool_attr(data, PCMK__XA_CONFIRM, confirm);
0c6c6d
+
0c6c6d
     attrd_xml_add_writer(data);
0c6c6d
     return send_cluster_message(node, crm_msg_attrd, data, TRUE);
0c6c6d
 }
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
0c6c6d
index 537bf85..25f7c8a 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.h
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.h
0c6c6d
@@ -39,10 +39,11 @@
0c6c6d
  *                      PCMK__ATTRD_CMD_UPDATE_DELAY
0c6c6d
  *     2       1.1.17   PCMK__ATTRD_CMD_CLEAR_FAILURE
0c6c6d
  *     3       2.1.1    PCMK__ATTRD_CMD_SYNC_RESPONSE indicates remote nodes
0c6c6d
- *     4       2.2.0    Multiple attributes can be updated in a single IPC
0c6c6d
+ *     4       2.1.5    Multiple attributes can be updated in a single IPC
0c6c6d
  *                      message
0c6c6d
+ *     5       2.1.5    Peers can request confirmation of a sent message
0c6c6d
  */
0c6c6d
-#define ATTRD_PROTOCOL_VERSION "4"
0c6c6d
+#define ATTRD_PROTOCOL_VERSION "5"
0c6c6d
 
0c6c6d
 #define attrd_send_ack(client, id, flags) \
0c6c6d
     pcmk__ipc_send_ack((client), (id), (flags), "ack", ATTRD_PROTOCOL_VERSION, CRM_EX_INDETERMINATE)
0c6c6d
@@ -162,7 +163,7 @@ xmlNode *attrd_client_clear_failure(pcmk__request_t *request);
0c6c6d
 xmlNode *attrd_client_update(pcmk__request_t *request);
0c6c6d
 xmlNode *attrd_client_refresh(pcmk__request_t *request);
0c6c6d
 xmlNode *attrd_client_query(pcmk__request_t *request);
0c6c6d
-gboolean attrd_send_message(crm_node_t * node, xmlNode * data);
0c6c6d
+gboolean attrd_send_message(crm_node_t *node, xmlNode *data, bool confirm);
0c6c6d
 
0c6c6d
 xmlNode *attrd_add_value_xml(xmlNode *parent, const attribute_t *a,
0c6c6d
                              const attribute_value_t *v, bool force_write);
0c6c6d
diff --git a/include/crm_internal.h b/include/crm_internal.h
0c6c6d
index 08193c3..63a1726 100644
0c6c6d
--- a/include/crm_internal.h
0c6c6d
+++ b/include/crm_internal.h
0c6c6d
@@ -79,6 +79,7 @@
0c6c6d
 #define PCMK__XA_ATTR_WRITER            "attr_writer"
0c6c6d
 #define PCMK__XA_CONFIG_ERRORS          "config-errors"
0c6c6d
 #define PCMK__XA_CONFIG_WARNINGS        "config-warnings"
0c6c6d
+#define PCMK__XA_CONFIRM                "confirm"
0c6c6d
 #define PCMK__XA_GRAPH_ERRORS           "graph-errors"
0c6c6d
 #define PCMK__XA_GRAPH_WARNINGS         "graph-warnings"
0c6c6d
 #define PCMK__XA_MODE                   "mode"
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 6f389038fc0b11f6291c022c99f188666c65f530 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Wed, 26 Oct 2022 14:44:42 -0400
0c6c6d
Subject: [PATCH 17/26] Feature: daemons: Respond to received attrd
0c6c6d
 confirmation requests.
0c6c6d
0c6c6d
On the receiving peer side, if the XML request contains confirm="true",
0c6c6d
construct a confirmation message after handling the request completes
0c6c6d
and send it back to the originating peer.
0c6c6d
0c6c6d
On the originating peer side, add a skeleton handler for confirmation
0c6c6d
messages.  This does nothing at the moment except log it.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_corosync.c | 38 ++++++++++++++++++++++++++++++++++
0c6c6d
 daemons/attrd/attrd_messages.c | 13 ++++++++++++
0c6c6d
 include/crm_internal.h         |  1 +
0c6c6d
 3 files changed, 52 insertions(+)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c
0c6c6d
index e86ca07..1245d9c 100644
0c6c6d
--- a/daemons/attrd/attrd_corosync.c
0c6c6d
+++ b/daemons/attrd/attrd_corosync.c
0c6c6d
@@ -25,6 +25,19 @@
0c6c6d
 
0c6c6d
 extern crm_exit_t attrd_exit_status;
0c6c6d
 
0c6c6d
+static xmlNode *
0c6c6d
+attrd_confirmation(int callid)
0c6c6d
+{
0c6c6d
+    xmlNode *node = create_xml_node(NULL, __func__);
0c6c6d
+
0c6c6d
+    crm_xml_add(node, F_TYPE, T_ATTRD);
0c6c6d
+    crm_xml_add(node, F_ORIG, get_local_node_name());
0c6c6d
+    crm_xml_add(node, PCMK__XA_TASK, PCMK__ATTRD_CMD_CONFIRM);
0c6c6d
+    crm_xml_add_int(node, XML_LRM_ATTR_CALLID, callid);
0c6c6d
+
0c6c6d
+    return node;
0c6c6d
+}
0c6c6d
+
0c6c6d
 static void
0c6c6d
 attrd_peer_message(crm_node_t *peer, xmlNode *xml)
0c6c6d
 {
0c6c6d
@@ -57,6 +70,31 @@ attrd_peer_message(crm_node_t *peer, xmlNode *xml)
0c6c6d
         CRM_CHECK(request.op != NULL, return);
0c6c6d
 
0c6c6d
         attrd_handle_request(&request);
0c6c6d
+
0c6c6d
+        /* Having finished handling the request, check to see if the originating
0c6c6d
+         * peer requested confirmation.  If so, send that confirmation back now.
0c6c6d
+         */
0c6c6d
+        if (pcmk__xe_attr_is_true(xml, PCMK__XA_CONFIRM)) {
0c6c6d
+            int callid = 0;
0c6c6d
+            xmlNode *reply = NULL;
0c6c6d
+
0c6c6d
+            /* Add the confirmation ID for the message we are confirming to the
0c6c6d
+             * response so the originating peer knows what they're a confirmation
0c6c6d
+             * for.
0c6c6d
+             */
0c6c6d
+            crm_element_value_int(xml, XML_LRM_ATTR_CALLID, &callid);
0c6c6d
+            reply = attrd_confirmation(callid);
0c6c6d
+
0c6c6d
+            /* And then send the confirmation back to the originating peer.  This
0c6c6d
+             * ends up right back in this same function (attrd_peer_message) on the
0c6c6d
+             * peer where it will have to do something with a PCMK__XA_CONFIRM type
0c6c6d
+             * message.
0c6c6d
+             */
0c6c6d
+            crm_debug("Sending %s a confirmation", peer->uname);
0c6c6d
+            attrd_send_message(peer, reply, false);
0c6c6d
+            free_xml(reply);
0c6c6d
+        }
0c6c6d
+
0c6c6d
         pcmk__reset_request(&request);
0c6c6d
     }
0c6c6d
 }
0c6c6d
diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c
0c6c6d
index 78df0d0..9c792b2 100644
0c6c6d
--- a/daemons/attrd/attrd_messages.c
0c6c6d
+++ b/daemons/attrd/attrd_messages.c
0c6c6d
@@ -65,6 +65,18 @@ handle_clear_failure_request(pcmk__request_t *request)
0c6c6d
     }
0c6c6d
 }
0c6c6d
 
0c6c6d
+static xmlNode *
0c6c6d
+handle_confirm_request(pcmk__request_t *request)
0c6c6d
+{
0c6c6d
+    if (request->peer != NULL) {
0c6c6d
+        crm_debug("Received confirmation from %s", request->peer);
0c6c6d
+        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
+        return NULL;
0c6c6d
+    } else {
0c6c6d
+        return handle_unknown_request(request);
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
 static xmlNode *
0c6c6d
 handle_flush_request(pcmk__request_t *request)
0c6c6d
 {
0c6c6d
@@ -190,6 +202,7 @@ attrd_register_handlers(void)
0c6c6d
 {
0c6c6d
     pcmk__server_command_t handlers[] = {
0c6c6d
         { PCMK__ATTRD_CMD_CLEAR_FAILURE, handle_clear_failure_request },
0c6c6d
+        { PCMK__ATTRD_CMD_CONFIRM, handle_confirm_request },
0c6c6d
         { PCMK__ATTRD_CMD_FLUSH, handle_flush_request },
0c6c6d
         { PCMK__ATTRD_CMD_PEER_REMOVE, handle_remove_request },
0c6c6d
         { PCMK__ATTRD_CMD_QUERY, handle_query_request },
0c6c6d
diff --git a/include/crm_internal.h b/include/crm_internal.h
0c6c6d
index 63a1726..f60e7b4 100644
0c6c6d
--- a/include/crm_internal.h
0c6c6d
+++ b/include/crm_internal.h
0c6c6d
@@ -108,6 +108,7 @@
0c6c6d
 #define PCMK__ATTRD_CMD_SYNC            "sync"
0c6c6d
 #define PCMK__ATTRD_CMD_SYNC_RESPONSE   "sync-response"
0c6c6d
 #define PCMK__ATTRD_CMD_CLEAR_FAILURE   "clear-failure"
0c6c6d
+#define PCMK__ATTRD_CMD_CONFIRM         "confirm"
0c6c6d
 
0c6c6d
 #define PCMK__CONTROLD_CMD_NODES        "list-nodes"
0c6c6d
 
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From dfb730e9ced9dc75886fda9452c584860573fe30 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Wed, 26 Oct 2022 15:58:00 -0400
0c6c6d
Subject: [PATCH 18/26] Feature: daemons: Keep track of #attrd-protocol from
0c6c6d
 each peer.
0c6c6d
0c6c6d
This information can be used in the future when dealing with
0c6c6d
cluster-wide sync points to know which peers we are waiting on a reply
0c6c6d
from.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_corosync.c  |  3 +-
0c6c6d
 daemons/attrd/attrd_utils.c     | 60 ++++++++++++++++++++++++++++++---
0c6c6d
 daemons/attrd/pacemaker-attrd.h |  4 ++-
0c6c6d
 3 files changed, 60 insertions(+), 7 deletions(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c
0c6c6d
index 1245d9c..6f88ab6 100644
0c6c6d
--- a/daemons/attrd/attrd_corosync.c
0c6c6d
+++ b/daemons/attrd/attrd_corosync.c
0c6c6d
@@ -268,6 +268,7 @@ attrd_peer_change_cb(enum crm_status_type kind, crm_node_t *peer, const void *da
0c6c6d
     // Remove votes from cluster nodes that leave, in case election in progress
0c6c6d
     if (gone && !is_remote) {
0c6c6d
         attrd_remove_voter(peer);
0c6c6d
+        attrd_remove_peer_protocol_ver(peer->uname);
0c6c6d
 
0c6c6d
     // Ensure remote nodes that come up are in the remote node cache
0c6c6d
     } else if (!gone && is_remote) {
0c6c6d
@@ -395,7 +396,7 @@ attrd_peer_update_one(const crm_node_t *peer, xmlNode *xml, bool filter)
0c6c6d
      * version, check to see if it's a new minimum version.
0c6c6d
      */
0c6c6d
     if (pcmk__str_eq(attr, CRM_ATTR_PROTOCOL, pcmk__str_none)) {
0c6c6d
-        attrd_update_minimum_protocol_ver(value);
0c6c6d
+        attrd_update_minimum_protocol_ver(peer->uname, value);
0c6c6d
     }
0c6c6d
 }
0c6c6d
 
0c6c6d
diff --git a/daemons/attrd/attrd_utils.c b/daemons/attrd/attrd_utils.c
0c6c6d
index 00b879b..421faed 100644
0c6c6d
--- a/daemons/attrd/attrd_utils.c
0c6c6d
+++ b/daemons/attrd/attrd_utils.c
0c6c6d
@@ -29,6 +29,11 @@ static bool requesting_shutdown = false;
0c6c6d
 static bool shutting_down = false;
0c6c6d
 static GMainLoop *mloop = NULL;
0c6c6d
 
0c6c6d
+/* A hash table storing information on the protocol version of each peer attrd.
0c6c6d
+ * The key is the peer's uname, and the value is the protocol version number.
0c6c6d
+ */
0c6c6d
+GHashTable *peer_protocol_vers = NULL;
0c6c6d
+
0c6c6d
 /*!
0c6c6d
  * \internal
0c6c6d
  * \brief  Set requesting_shutdown state
0c6c6d
@@ -94,6 +99,10 @@ attrd_shutdown(int nsig)
0c6c6d
     mainloop_destroy_signal(SIGTRAP);
0c6c6d
 
0c6c6d
     attrd_free_waitlist();
0c6c6d
+    if (peer_protocol_vers != NULL) {
0c6c6d
+        g_hash_table_destroy(peer_protocol_vers);
0c6c6d
+        peer_protocol_vers = NULL;
0c6c6d
+    }
0c6c6d
 
0c6c6d
     if ((mloop == NULL) || !g_main_loop_is_running(mloop)) {
0c6c6d
         /* If there's no main loop active, just exit. This should be possible
0c6c6d
@@ -273,16 +282,57 @@ attrd_free_attribute(gpointer data)
0c6c6d
     }
0c6c6d
 }
0c6c6d
 
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief When a peer node leaves the cluster, stop tracking its protocol version.
0c6c6d
+ *
0c6c6d
+ * \param[in] host  The peer node's uname to be removed
0c6c6d
+ */
0c6c6d
+void
0c6c6d
+attrd_remove_peer_protocol_ver(const char *host)
0c6c6d
+{
0c6c6d
+    if (peer_protocol_vers != NULL) {
0c6c6d
+        g_hash_table_remove(peer_protocol_vers, host);
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief When a peer node broadcasts a message with its protocol version, keep
0c6c6d
+ *        track of that information.
0c6c6d
+ *
0c6c6d
+ * We keep track of each peer's protocol version so we know which peers to
0c6c6d
+ * expect confirmation messages from when handling cluster-wide sync points.
0c6c6d
+ * We additionally keep track of the lowest protocol version supported by all
0c6c6d
+ * peers so we know when we can send IPC messages containing more than one
0c6c6d
+ * request.
0c6c6d
+ *
0c6c6d
+ * \param[in] host  The peer node's uname to be tracked
0c6c6d
+ * \param[in] value The peer node's protocol version
0c6c6d
+ */
0c6c6d
 void
0c6c6d
-attrd_update_minimum_protocol_ver(const char *value)
0c6c6d
+attrd_update_minimum_protocol_ver(const char *host, const char *value)
0c6c6d
 {
0c6c6d
     int ver;
0c6c6d
 
0c6c6d
+    if (peer_protocol_vers == NULL) {
0c6c6d
+        peer_protocol_vers = pcmk__strkey_table(free, NULL);
0c6c6d
+    }
0c6c6d
+
0c6c6d
     pcmk__scan_min_int(value, &ver, 0);
0c6c6d
 
0c6c6d
-    if (ver > 0 && (minimum_protocol_version == -1 || ver < minimum_protocol_version)) {
0c6c6d
-        minimum_protocol_version = ver;
0c6c6d
-        crm_trace("Set minimum attrd protocol version to %d",
0c6c6d
-                  minimum_protocol_version);
0c6c6d
+    if (ver > 0) {
0c6c6d
+        char *host_name = strdup(host);
0c6c6d
+
0c6c6d
+        /* Record the peer attrd's protocol version. */
0c6c6d
+        CRM_ASSERT(host_name != NULL);
0c6c6d
+        g_hash_table_insert(peer_protocol_vers, host_name, GINT_TO_POINTER(ver));
0c6c6d
+
0c6c6d
+        /* If the protocol version is a new minimum, record it as such. */
0c6c6d
+        if (minimum_protocol_version == -1 || ver < minimum_protocol_version) {
0c6c6d
+            minimum_protocol_version = ver;
0c6c6d
+            crm_trace("Set minimum attrd protocol version to %d",
0c6c6d
+                      minimum_protocol_version);
0c6c6d
+        }
0c6c6d
     }
0c6c6d
 }
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
0c6c6d
index 25f7c8a..302ef63 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.h
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.h
0c6c6d
@@ -145,6 +145,7 @@ typedef struct attribute_value_s {
0c6c6d
 
0c6c6d
 extern crm_cluster_t *attrd_cluster;
0c6c6d
 extern GHashTable *attributes;
0c6c6d
+extern GHashTable *peer_protocol_vers;
0c6c6d
 
0c6c6d
 #define CIB_OP_TIMEOUT_S 120
0c6c6d
 
0c6c6d
@@ -177,7 +178,8 @@ void attrd_write_attributes(bool all, bool ignore_delay);
0c6c6d
 void attrd_write_or_elect_attribute(attribute_t *a);
0c6c6d
 
0c6c6d
 extern int minimum_protocol_version;
0c6c6d
-void attrd_update_minimum_protocol_ver(const char *value);
0c6c6d
+void attrd_remove_peer_protocol_ver(const char *host);
0c6c6d
+void attrd_update_minimum_protocol_ver(const char *host, const char *value);
0c6c6d
 
0c6c6d
 mainloop_timer_t *attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr);
0c6c6d
 
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 945f0fe51d3bf69c2cb1258b394f2f11b8996525 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 27 Oct 2022 14:42:59 -0400
0c6c6d
Subject: [PATCH 19/26] Feature: daemons: Handle cluster-wide sync points in
0c6c6d
 attrd.
0c6c6d
0c6c6d
When an attrd receives an IPC request to update some value, record the
0c6c6d
protocol versions of all peer attrds.  Additionally register a function
0c6c6d
that will be called when all confirmations are received.
0c6c6d
0c6c6d
The originating IPC cilent (attrd_updater for instance) will sit there
0c6c6d
waiting for an ACK until its timeout is hit.
0c6c6d
0c6c6d
As each confirmation message comes back to attrd, mark it off the list
0c6c6d
of peers we are waiting on.  When no more peers are expected, call the
0c6c6d
previously registered function.
0c6c6d
0c6c6d
For attribute updates, this function just sends an ack back to
0c6c6d
attrd_updater.
0c6c6d
0c6c6d
Fixes T35
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_corosync.c  |   1 +
0c6c6d
 daemons/attrd/attrd_ipc.c       |   4 +
0c6c6d
 daemons/attrd/attrd_messages.c  |  10 ++
0c6c6d
 daemons/attrd/attrd_sync.c      | 260 +++++++++++++++++++++++++++++++-
0c6c6d
 daemons/attrd/attrd_utils.c     |   2 +
0c6c6d
 daemons/attrd/pacemaker-attrd.h |   8 +
0c6c6d
 6 files changed, 281 insertions(+), 4 deletions(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c
0c6c6d
index 6f88ab6..37701aa 100644
0c6c6d
--- a/daemons/attrd/attrd_corosync.c
0c6c6d
+++ b/daemons/attrd/attrd_corosync.c
0c6c6d
@@ -269,6 +269,7 @@ attrd_peer_change_cb(enum crm_status_type kind, crm_node_t *peer, const void *da
0c6c6d
     if (gone && !is_remote) {
0c6c6d
         attrd_remove_voter(peer);
0c6c6d
         attrd_remove_peer_protocol_ver(peer->uname);
0c6c6d
+        attrd_do_not_expect_from_peer(peer->uname);
0c6c6d
 
0c6c6d
     // Ensure remote nodes that come up are in the remote node cache
0c6c6d
     } else if (!gone && is_remote) {
0c6c6d
diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c
0c6c6d
index 0fc5e93..c70aa1b 100644
0c6c6d
--- a/daemons/attrd/attrd_ipc.c
0c6c6d
+++ b/daemons/attrd/attrd_ipc.c
0c6c6d
@@ -393,6 +393,7 @@ attrd_client_update(pcmk__request_t *request)
0c6c6d
          * the response ACK is not sent until this attrd broadcasts the update
0c6c6d
          * and receives its own confirmation back from all peers.
0c6c6d
          */
0c6c6d
+        attrd_expect_confirmations(request, attrd_cluster_sync_point_update);
0c6c6d
         attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
0c6c6d
 
0c6c6d
     } else {
0c6c6d
@@ -456,6 +457,9 @@ attrd_ipc_closed(qb_ipcs_connection_t *c)
0c6c6d
         /* Remove the client from the sync point waitlist if it's present. */
0c6c6d
         attrd_remove_client_from_waitlist(client);
0c6c6d
 
0c6c6d
+        /* And no longer wait for confirmations from any peers. */
0c6c6d
+        attrd_do_not_wait_for_client(client);
0c6c6d
+
0c6c6d
         pcmk__free_client(client);
0c6c6d
     }
0c6c6d
 
0c6c6d
diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c
0c6c6d
index 9c792b2..f7b9c7c 100644
0c6c6d
--- a/daemons/attrd/attrd_messages.c
0c6c6d
+++ b/daemons/attrd/attrd_messages.c
0c6c6d
@@ -69,7 +69,17 @@ static xmlNode *
0c6c6d
 handle_confirm_request(pcmk__request_t *request)
0c6c6d
 {
0c6c6d
     if (request->peer != NULL) {
0c6c6d
+        int callid;
0c6c6d
+
0c6c6d
         crm_debug("Received confirmation from %s", request->peer);
0c6c6d
+
0c6c6d
+        if (crm_element_value_int(request->xml, XML_LRM_ATTR_CALLID, &callid) == -1) {
0c6c6d
+            pcmk__set_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
0c6c6d
+                             "Could not get callid from XML");
0c6c6d
+        } else {
0c6c6d
+            attrd_handle_confirmation(callid, request->peer);
0c6c6d
+        }
0c6c6d
+
0c6c6d
         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
         return NULL;
0c6c6d
     } else {
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index e9690b5..d3d7108 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -34,6 +34,51 @@ struct waitlist_node {
0c6c6d
     uint32_t flags;
0c6c6d
 };
0c6c6d
 
0c6c6d
+/* A hash table storing information on in-progress IPC requests that are awaiting
0c6c6d
+ * confirmations.  These requests are currently being processed by peer attrds and
0c6c6d
+ * we are waiting to receive confirmation messages from each peer indicating that
0c6c6d
+ * processing is complete.
0c6c6d
+ *
0c6c6d
+ * Multiple requests could be waiting on confirmations at the same time.
0c6c6d
+ *
0c6c6d
+ * The key is the unique callid for the IPC request, and the value is a
0c6c6d
+ * confirmation_action struct.
0c6c6d
+ */
0c6c6d
+static GHashTable *expected_confirmations = NULL;
0c6c6d
+
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief A structure describing a single IPC request that is awaiting confirmations
0c6c6d
+ */
0c6c6d
+struct confirmation_action {
0c6c6d
+    /*!
0c6c6d
+     * \brief A list of peer attrds that we are waiting to receive confirmation
0c6c6d
+     *        messages from
0c6c6d
+     *
0c6c6d
+     * This list is dynamic - as confirmations arrive from peer attrds, they will
0c6c6d
+     * be removed from this list.  When the list is empty, all peers have processed
0c6c6d
+     * the request and the associated confirmation action will be taken.
0c6c6d
+     */
0c6c6d
+    GList *respondents;
0c6c6d
+
0c6c6d
+    /*!
0c6c6d
+     * \brief A function to run when all confirmations have been received
0c6c6d
+     */
0c6c6d
+    attrd_confirmation_action_fn fn;
0c6c6d
+
0c6c6d
+    /*!
0c6c6d
+     * \brief Information required to construct and send a reply to the client
0c6c6d
+     */
0c6c6d
+    char *client_id;
0c6c6d
+    uint32_t ipc_id;
0c6c6d
+    uint32_t flags;
0c6c6d
+
0c6c6d
+    /*!
0c6c6d
+     * \brief The XML request containing the callid associated with this action
0c6c6d
+     */
0c6c6d
+    void *xml;
0c6c6d
+};
0c6c6d
+
0c6c6d
 static void
0c6c6d
 next_key(void)
0c6c6d
 {
0c6c6d
@@ -114,12 +159,13 @@ attrd_add_client_to_waitlist(pcmk__request_t *request)
0c6c6d
     wl->ipc_id = request->ipc_id;
0c6c6d
     wl->flags = request->flags;
0c6c6d
 
0c6c6d
-    crm_debug("Added client %s to waitlist for %s sync point",
0c6c6d
-              wl->client_id, sync_point_str(wl->sync_point));
0c6c6d
-
0c6c6d
     next_key();
0c6c6d
     pcmk__intkey_table_insert(waitlist, waitlist_client, wl);
0c6c6d
 
0c6c6d
+    crm_trace("Added client %s to waitlist for %s sync point",
0c6c6d
+              wl->client_id, sync_point_str(wl->sync_point));
0c6c6d
+    crm_trace("%d clients now on waitlist", g_hash_table_size(waitlist));
0c6c6d
+
0c6c6d
     /* And then add the key to the request XML so we can uniquely identify
0c6c6d
      * it when it comes time to issue the ACK.
0c6c6d
      */
0c6c6d
@@ -166,6 +212,7 @@ attrd_remove_client_from_waitlist(pcmk__client_t *client)
0c6c6d
 
0c6c6d
         if (wl->client_id == client->id) {
0c6c6d
             g_hash_table_iter_remove(&iter);
0c6c6d
+            crm_trace("%d clients now on waitlist", g_hash_table_size(waitlist));
0c6c6d
         }
0c6c6d
     }
0c6c6d
 }
0c6c6d
@@ -206,7 +253,7 @@ attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml)
0c6c6d
             return;
0c6c6d
         }
0c6c6d
 
0c6c6d
-        crm_debug("Alerting client %s for reached %s sync point",
0c6c6d
+        crm_trace("Alerting client %s for reached %s sync point",
0c6c6d
                   wl->client_id, sync_point_str(wl->sync_point));
0c6c6d
 
0c6c6d
         client = pcmk__find_client_by_id(wl->client_id);
0c6c6d
@@ -218,9 +265,28 @@ attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml)
0c6c6d
 
0c6c6d
         /* And then remove the client so it doesn't get alerted again. */
0c6c6d
         pcmk__intkey_table_remove(waitlist, callid);
0c6c6d
+
0c6c6d
+        crm_trace("%d clients now on waitlist", g_hash_table_size(waitlist));
0c6c6d
     }
0c6c6d
 }
0c6c6d
 
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Action to take when a cluster sync point is hit for a
0c6c6d
+ *        PCMK__ATTRD_CMD_UPDATE* message.
0c6c6d
+ *
0c6c6d
+ * \param[in] xml  The request that should be passed along to
0c6c6d
+ *                 attrd_ack_waitlist_clients.  This should be the original
0c6c6d
+ *                 IPC request containing the callid for this update message.
0c6c6d
+ */
0c6c6d
+int
0c6c6d
+attrd_cluster_sync_point_update(xmlNode *xml)
0c6c6d
+{
0c6c6d
+    crm_trace("Hit cluster sync point for attribute update");
0c6c6d
+    attrd_ack_waitlist_clients(attrd_sync_point_cluster, xml);
0c6c6d
+    return pcmk_rc_ok;
0c6c6d
+}
0c6c6d
+
0c6c6d
 /*!
0c6c6d
  * \internal
0c6c6d
  * \brief Return the sync point attribute for an IPC request
0c6c6d
@@ -268,3 +334,189 @@ attrd_request_has_sync_point(xmlNode *xml)
0c6c6d
 {
0c6c6d
     return attrd_request_sync_point(xml) != NULL;
0c6c6d
 }
0c6c6d
+
0c6c6d
+static void
0c6c6d
+free_action(gpointer data)
0c6c6d
+{
0c6c6d
+    struct confirmation_action *action = (struct confirmation_action *) data;
0c6c6d
+    g_list_free_full(action->respondents, free);
0c6c6d
+    free_xml(action->xml);
0c6c6d
+    free(action->client_id);
0c6c6d
+    free(action);
0c6c6d
+}
0c6c6d
+
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief When a peer disconnects from the cluster, no longer wait for its confirmation
0c6c6d
+ *        for any IPC action.  If this peer is the last one being waited on, this will
0c6c6d
+ *        trigger the confirmation action.
0c6c6d
+ *
0c6c6d
+ * \param[in] host   The disconnecting peer attrd's uname
0c6c6d
+ */
0c6c6d
+void
0c6c6d
+attrd_do_not_expect_from_peer(const char *host)
0c6c6d
+{
0c6c6d
+    GList *keys = g_hash_table_get_keys(expected_confirmations);
0c6c6d
+
0c6c6d
+    crm_trace("Removing peer %s from expected confirmations", host);
0c6c6d
+
0c6c6d
+    for (GList *node = keys; node != NULL; node = node->next) {
0c6c6d
+        int callid = *(int *) node->data;
0c6c6d
+        attrd_handle_confirmation(callid, host);
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    g_list_free(keys);
0c6c6d
+}
0c6c6d
+
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief When a client disconnects from the cluster, no longer wait on confirmations
0c6c6d
+ *        for it.  Because the peer attrds may still be processing the original IPC
0c6c6d
+ *        message, they may still send us confirmations.  However, we will take no
0c6c6d
+ *        action on them.
0c6c6d
+ *
0c6c6d
+ * \param[in] client    The disconnecting client
0c6c6d
+ */
0c6c6d
+void
0c6c6d
+attrd_do_not_wait_for_client(pcmk__client_t *client)
0c6c6d
+{
0c6c6d
+    GHashTableIter iter;
0c6c6d
+    gpointer value;
0c6c6d
+
0c6c6d
+    if (expected_confirmations == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    g_hash_table_iter_init(&iter, expected_confirmations);
0c6c6d
+
0c6c6d
+    while (g_hash_table_iter_next(&iter, NULL, &value)) {
0c6c6d
+        struct confirmation_action *action = (struct confirmation_action *) value;
0c6c6d
+
0c6c6d
+        if (pcmk__str_eq(action->client_id, client->id, pcmk__str_none)) {
0c6c6d
+            crm_trace("Removing client %s from expected confirmations", client->id);
0c6c6d
+            g_hash_table_iter_remove(&iter);
0c6c6d
+            crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations));
0c6c6d
+            break;
0c6c6d
+        }
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Register some action to be taken when IPC request confirmations are
0c6c6d
+ *        received
0c6c6d
+ *
0c6c6d
+ * When this function is called, a list of all peer attrds that support confirming
0c6c6d
+ * requests is generated.  As confirmations from these peer attrds are received,
0c6c6d
+ * they are removed from this list.  When the list is empty, the registered action
0c6c6d
+ * will be called.
0c6c6d
+ *
0c6c6d
+ * \note This function should always be called before attrd_send_message is called
0c6c6d
+ *       to broadcast to the peers to ensure that we know what replies we are
0c6c6d
+ *       waiting on.  Otherwise, it is possible the peer could finish and confirm
0c6c6d
+ *       before we know to expect it.
0c6c6d
+ *
0c6c6d
+ * \param[in] request The request that is awaiting confirmations
0c6c6d
+ * \param[in] fn      A function to be run after all confirmations are received
0c6c6d
+ */
0c6c6d
+void
0c6c6d
+attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_fn fn)
0c6c6d
+{
0c6c6d
+    struct confirmation_action *action = NULL;
0c6c6d
+    GHashTableIter iter;
0c6c6d
+    gpointer host, ver;
0c6c6d
+    GList *respondents = NULL;
0c6c6d
+    int callid;
0c6c6d
+
0c6c6d
+    if (expected_confirmations == NULL) {
0c6c6d
+        expected_confirmations = pcmk__intkey_table((GDestroyNotify) free_action);
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    if (crm_element_value_int(request->xml, XML_LRM_ATTR_CALLID, &callid) == -1) {
0c6c6d
+        crm_err("Could not get callid from xml");
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    if (pcmk__intkey_table_lookup(expected_confirmations, callid)) {
0c6c6d
+        crm_err("Already waiting on confirmations for call id %d", callid);
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    g_hash_table_iter_init(&iter, peer_protocol_vers);
0c6c6d
+    while (g_hash_table_iter_next(&iter, &host, &ver)) {
0c6c6d
+        if (GPOINTER_TO_INT(ver) >= 5) {
0c6c6d
+            char *s = strdup((char *) host);
0c6c6d
+
0c6c6d
+            CRM_ASSERT(s != NULL);
0c6c6d
+            respondents = g_list_prepend(respondents, s);
0c6c6d
+        }
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    action = calloc(1, sizeof(struct confirmation_action));
0c6c6d
+    CRM_ASSERT(action != NULL);
0c6c6d
+
0c6c6d
+    action->respondents = respondents;
0c6c6d
+    action->fn = fn;
0c6c6d
+    action->xml = copy_xml(request->xml);
0c6c6d
+
0c6c6d
+    action->client_id = strdup(request->ipc_client->id);
0c6c6d
+    CRM_ASSERT(action->client_id != NULL);
0c6c6d
+
0c6c6d
+    action->ipc_id = request->ipc_id;
0c6c6d
+    action->flags = request->flags;
0c6c6d
+
0c6c6d
+    pcmk__intkey_table_insert(expected_confirmations, callid, action);
0c6c6d
+    crm_trace("Callid %d now waiting on %d confirmations", callid, g_list_length(respondents));
0c6c6d
+    crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations));
0c6c6d
+}
0c6c6d
+
0c6c6d
+void
0c6c6d
+attrd_free_confirmations(void)
0c6c6d
+{
0c6c6d
+    if (expected_confirmations != NULL) {
0c6c6d
+        g_hash_table_destroy(expected_confirmations);
0c6c6d
+        expected_confirmations = NULL;
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
+/*!
0c6c6d
+ * \internal
0c6c6d
+ * \brief Process a confirmation message from a peer attrd
0c6c6d
+ *
0c6c6d
+ * This function is called every time a PCMK__ATTRD_CMD_CONFIRM message is
0c6c6d
+ * received from a peer attrd.  If this is the last confirmation we are waiting
0c6c6d
+ * on for a given operation, the registered action will be called.
0c6c6d
+ *
0c6c6d
+ * \param[in] callid The unique callid for the XML IPC request
0c6c6d
+ * \param[in] host   The confirming peer attrd's uname
0c6c6d
+ */
0c6c6d
+void
0c6c6d
+attrd_handle_confirmation(int callid, const char *host)
0c6c6d
+{
0c6c6d
+    struct confirmation_action *action = NULL;
0c6c6d
+    GList *node = NULL;
0c6c6d
+
0c6c6d
+    if (expected_confirmations == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    action = pcmk__intkey_table_lookup(expected_confirmations, callid);
0c6c6d
+    if (action == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    node = g_list_find_custom(action->respondents, host, (GCompareFunc) strcasecmp);
0c6c6d
+
0c6c6d
+    if (node == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    action->respondents = g_list_remove(action->respondents, node->data);
0c6c6d
+    crm_trace("Callid %d now waiting on %d confirmations", callid, g_list_length(action->respondents));
0c6c6d
+
0c6c6d
+    if (action->respondents == NULL) {
0c6c6d
+        action->fn(action->xml);
0c6c6d
+        pcmk__intkey_table_remove(expected_confirmations, callid);
0c6c6d
+        crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations));
0c6c6d
+    }
0c6c6d
+}
0c6c6d
diff --git a/daemons/attrd/attrd_utils.c b/daemons/attrd/attrd_utils.c
0c6c6d
index 421faed..f3a2059 100644
0c6c6d
--- a/daemons/attrd/attrd_utils.c
0c6c6d
+++ b/daemons/attrd/attrd_utils.c
0c6c6d
@@ -99,6 +99,8 @@ attrd_shutdown(int nsig)
0c6c6d
     mainloop_destroy_signal(SIGTRAP);
0c6c6d
 
0c6c6d
     attrd_free_waitlist();
0c6c6d
+    attrd_free_confirmations();
0c6c6d
+
0c6c6d
     if (peer_protocol_vers != NULL) {
0c6c6d
         g_hash_table_destroy(peer_protocol_vers);
0c6c6d
         peer_protocol_vers = NULL;
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
0c6c6d
index 302ef63..bcc329d 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.h
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.h
0c6c6d
@@ -191,8 +191,16 @@ enum attrd_sync_point {
0c6c6d
     attrd_sync_point_cluster,
0c6c6d
 };
0c6c6d
 
0c6c6d
+typedef int (*attrd_confirmation_action_fn)(xmlNode *);
0c6c6d
+
0c6c6d
 void attrd_add_client_to_waitlist(pcmk__request_t *request);
0c6c6d
 void attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml);
0c6c6d
+int attrd_cluster_sync_point_update(xmlNode *xml);
0c6c6d
+void attrd_do_not_expect_from_peer(const char *host);
0c6c6d
+void attrd_do_not_wait_for_client(pcmk__client_t *client);
0c6c6d
+void attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_fn fn);
0c6c6d
+void attrd_free_confirmations(void);
0c6c6d
+void attrd_handle_confirmation(int callid, const char *host);
0c6c6d
 void attrd_remove_client_from_waitlist(pcmk__client_t *client);
0c6c6d
 const char *attrd_request_sync_point(xmlNode *xml);
0c6c6d
 bool attrd_request_has_sync_point(xmlNode *xml);
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 07a032a7eb2f03dce18a7c94c56b8c837dedda15 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Fri, 28 Oct 2022 14:54:15 -0400
0c6c6d
Subject: [PATCH 20/26] Refactor: daemons: Add some attrd version checking
0c6c6d
 macros.
0c6c6d
0c6c6d
These are just to make it a little more obvious what is actually being
0c6c6d
asked in the code, instead of having magic numbers sprinkled around.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_ipc.c       | 2 +-
0c6c6d
 daemons/attrd/attrd_sync.c      | 2 +-
0c6c6d
 daemons/attrd/pacemaker-attrd.h | 3 +++
0c6c6d
 3 files changed, 5 insertions(+), 2 deletions(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c
0c6c6d
index c70aa1b..16bfff4 100644
0c6c6d
--- a/daemons/attrd/attrd_ipc.c
0c6c6d
+++ b/daemons/attrd/attrd_ipc.c
0c6c6d
@@ -294,7 +294,7 @@ attrd_client_update(pcmk__request_t *request)
0c6c6d
      * two ways we can handle that.
0c6c6d
      */
0c6c6d
     if (xml_has_children(xml)) {
0c6c6d
-        if (minimum_protocol_version >= 4) {
0c6c6d
+        if (ATTRD_SUPPORTS_MULTI_MESSAGE(minimum_protocol_version)) {
0c6c6d
             /* First, if all peers support a certain protocol version, we can
0c6c6d
              * just broadcast the big message and they'll handle it.  However,
0c6c6d
              * we also need to apply all the transformations in this function
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index d3d7108..e48f82e 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -444,7 +444,7 @@ attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_f
0c6c6d
 
0c6c6d
     g_hash_table_iter_init(&iter, peer_protocol_vers);
0c6c6d
     while (g_hash_table_iter_next(&iter, &host, &ver)) {
0c6c6d
-        if (GPOINTER_TO_INT(ver) >= 5) {
0c6c6d
+        if (ATTRD_SUPPORTS_CONFIRMATION(GPOINTER_TO_INT(ver))) {
0c6c6d
             char *s = strdup((char *) host);
0c6c6d
 
0c6c6d
             CRM_ASSERT(s != NULL);
0c6c6d
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
0c6c6d
index bcc329d..83d7c6b 100644
0c6c6d
--- a/daemons/attrd/pacemaker-attrd.h
0c6c6d
+++ b/daemons/attrd/pacemaker-attrd.h
0c6c6d
@@ -45,6 +45,9 @@
0c6c6d
  */
0c6c6d
 #define ATTRD_PROTOCOL_VERSION "5"
0c6c6d
 
0c6c6d
+#define ATTRD_SUPPORTS_MULTI_MESSAGE(x) ((x) >= 4)
0c6c6d
+#define ATTRD_SUPPORTS_CONFIRMATION(x)  ((x) >= 5)
0c6c6d
+
0c6c6d
 #define attrd_send_ack(client, id, flags) \
0c6c6d
     pcmk__ipc_send_ack((client), (id), (flags), "ack", ATTRD_PROTOCOL_VERSION, CRM_EX_INDETERMINATE)
0c6c6d
 
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 811361b96c6f26a1f5eccc54b6e8bf6e6fd003be Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Mon, 31 Oct 2022 12:53:22 -0400
0c6c6d
Subject: [PATCH 21/26] Low: attrd: Fix removing clients from the waitlist when
0c6c6d
 they disconnect.
0c6c6d
0c6c6d
The client ID is a string, so it must be compared like a string.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_sync.c | 2 +-
0c6c6d
 1 file changed, 1 insertion(+), 1 deletion(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index e48f82e..c9b4784 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -210,7 +210,7 @@ attrd_remove_client_from_waitlist(pcmk__client_t *client)
0c6c6d
     while (g_hash_table_iter_next(&iter, NULL, &value)) {
0c6c6d
         struct waitlist_node *wl = (struct waitlist_node *) value;
0c6c6d
 
0c6c6d
-        if (wl->client_id == client->id) {
0c6c6d
+        if (pcmk__str_eq(wl->client_id, client->id, pcmk__str_none)) {
0c6c6d
             g_hash_table_iter_remove(&iter);
0c6c6d
             crm_trace("%d clients now on waitlist", g_hash_table_size(waitlist));
0c6c6d
         }
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 4e933ad14456af85c60701410c3b23b4eab03f86 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Tue, 1 Nov 2022 12:35:12 -0400
0c6c6d
Subject: [PATCH 22/26] Feature: daemons: Handle an attrd client timing out.
0c6c6d
0c6c6d
If the update confirmations do not come back in time, use a main loop
0c6c6d
timer to remove the client from the table.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_sync.c | 49 ++++++++++++++++++++++++++++++++++++++
0c6c6d
 1 file changed, 49 insertions(+)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index c9b4784..9d07796 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -61,6 +61,12 @@ struct confirmation_action {
0c6c6d
      */
0c6c6d
     GList *respondents;
0c6c6d
 
0c6c6d
+    /*!
0c6c6d
+     * \brief A timer that will be used to remove the client should it time out
0c6c6d
+     *        before receiving all confirmations
0c6c6d
+     */
0c6c6d
+    mainloop_timer_t *timer;
0c6c6d
+
0c6c6d
     /*!
0c6c6d
      * \brief A function to run when all confirmations have been received
0c6c6d
      */
0c6c6d
@@ -340,11 +346,51 @@ free_action(gpointer data)
0c6c6d
 {
0c6c6d
     struct confirmation_action *action = (struct confirmation_action *) data;
0c6c6d
     g_list_free_full(action->respondents, free);
0c6c6d
+    mainloop_timer_del(action->timer);
0c6c6d
     free_xml(action->xml);
0c6c6d
     free(action->client_id);
0c6c6d
     free(action);
0c6c6d
 }
0c6c6d
 
0c6c6d
+/* Remove an IPC request from the expected_confirmations table if the peer attrds
0c6c6d
+ * don't respond before the timeout is hit.  We set the timeout to 15s.  The exact
0c6c6d
+ * number isn't critical - we just want to make sure that the table eventually gets
0c6c6d
+ * cleared of things that didn't complete.
0c6c6d
+ */
0c6c6d
+static gboolean
0c6c6d
+confirmation_timeout_cb(gpointer data)
0c6c6d
+{
0c6c6d
+    struct confirmation_action *action = (struct confirmation_action *) data;
0c6c6d
+
0c6c6d
+    GHashTableIter iter;
0c6c6d
+    gpointer value;
0c6c6d
+
0c6c6d
+    if (expected_confirmations == NULL) {
0c6c6d
+        return G_SOURCE_REMOVE;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    g_hash_table_iter_init(&iter, expected_confirmations);
0c6c6d
+
0c6c6d
+    while (g_hash_table_iter_next(&iter, NULL, &value)) {
0c6c6d
+        if (value == action) {
0c6c6d
+            pcmk__client_t *client = pcmk__find_client_by_id(action->client_id);
0c6c6d
+            if (client == NULL) {
0c6c6d
+                return G_SOURCE_REMOVE;
0c6c6d
+            }
0c6c6d
+
0c6c6d
+            crm_trace("Timed out waiting for confirmations for client %s", client->id);
0c6c6d
+            pcmk__ipc_send_ack(client, action->ipc_id, action->flags | crm_ipc_client_response,
0c6c6d
+                               "ack", ATTRD_PROTOCOL_VERSION, CRM_EX_TIMEOUT);
0c6c6d
+
0c6c6d
+            g_hash_table_iter_remove(&iter);
0c6c6d
+            crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations));
0c6c6d
+            break;
0c6c6d
+        }
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    return G_SOURCE_REMOVE;
0c6c6d
+}
0c6c6d
+
0c6c6d
 /*!
0c6c6d
  * \internal
0c6c6d
  * \brief When a peer disconnects from the cluster, no longer wait for its confirmation
0c6c6d
@@ -465,6 +511,9 @@ attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_f
0c6c6d
     action->ipc_id = request->ipc_id;
0c6c6d
     action->flags = request->flags;
0c6c6d
 
0c6c6d
+    action->timer = mainloop_timer_add(NULL, 15000, FALSE, confirmation_timeout_cb, action);
0c6c6d
+    mainloop_timer_start(action->timer);
0c6c6d
+
0c6c6d
     pcmk__intkey_table_insert(expected_confirmations, callid, action);
0c6c6d
     crm_trace("Callid %d now waiting on %d confirmations", callid, g_list_length(respondents));
0c6c6d
     crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations));
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 101896383cbe0103c98078e46540c076af08f040 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Wed, 2 Nov 2022 14:40:30 -0400
0c6c6d
Subject: [PATCH 23/26] Refactor: Demote a sync point related message to trace.
0c6c6d
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_corosync.c | 2 +-
0c6c6d
 1 file changed, 1 insertion(+), 1 deletion(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c
0c6c6d
index 37701aa..5cbed7e 100644
0c6c6d
--- a/daemons/attrd/attrd_corosync.c
0c6c6d
+++ b/daemons/attrd/attrd_corosync.c
0c6c6d
@@ -633,7 +633,7 @@ attrd_peer_update(const crm_node_t *peer, xmlNode *xml, const char *host,
0c6c6d
      * point, process that now.
0c6c6d
      */
0c6c6d
     if (handle_sync_point) {
0c6c6d
-        crm_debug("Hit local sync point for attribute update");
0c6c6d
+        crm_trace("Hit local sync point for attribute update");
0c6c6d
         attrd_ack_waitlist_clients(attrd_sync_point_local, xml);
0c6c6d
     }
0c6c6d
 }
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From acd13246d4c2bef7982ca103e34896efcad22348 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Thu, 3 Nov 2022 10:29:20 -0400
0c6c6d
Subject: [PATCH 24/26] Low: daemons: Avoid infinite confirm loops in attrd.
0c6c6d
0c6c6d
On the sending side, do not add confirm="yes" to a message with
0c6c6d
op="confirm".  On the receiving side, do not confirm a message with
0c6c6d
op="confirm" even if confirm="yes" is set.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_corosync.c | 3 ++-
0c6c6d
 daemons/attrd/attrd_messages.c | 6 +++++-
0c6c6d
 2 files changed, 7 insertions(+), 2 deletions(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c
0c6c6d
index 5cbed7e..88c1ecc 100644
0c6c6d
--- a/daemons/attrd/attrd_corosync.c
0c6c6d
+++ b/daemons/attrd/attrd_corosync.c
0c6c6d
@@ -74,7 +74,8 @@ attrd_peer_message(crm_node_t *peer, xmlNode *xml)
0c6c6d
         /* Having finished handling the request, check to see if the originating
0c6c6d
          * peer requested confirmation.  If so, send that confirmation back now.
0c6c6d
          */
0c6c6d
-        if (pcmk__xe_attr_is_true(xml, PCMK__XA_CONFIRM)) {
0c6c6d
+        if (pcmk__xe_attr_is_true(xml, PCMK__XA_CONFIRM) &&
0c6c6d
+            !pcmk__str_eq(request.op, PCMK__ATTRD_CMD_CONFIRM, pcmk__str_none)) {
0c6c6d
             int callid = 0;
0c6c6d
             xmlNode *reply = NULL;
0c6c6d
 
0c6c6d
diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c
0c6c6d
index f7b9c7c..184176a 100644
0c6c6d
--- a/daemons/attrd/attrd_messages.c
0c6c6d
+++ b/daemons/attrd/attrd_messages.c
0c6c6d
@@ -310,6 +310,8 @@ attrd_broadcast_protocol(void)
0c6c6d
 gboolean
0c6c6d
 attrd_send_message(crm_node_t *node, xmlNode *data, bool confirm)
0c6c6d
 {
0c6c6d
+    const char *op = crm_element_value(data, PCMK__XA_TASK);
0c6c6d
+
0c6c6d
     crm_xml_add(data, F_TYPE, T_ATTRD);
0c6c6d
     crm_xml_add(data, PCMK__XA_ATTR_VERSION, ATTRD_PROTOCOL_VERSION);
0c6c6d
 
0c6c6d
@@ -317,7 +319,9 @@ attrd_send_message(crm_node_t *node, xmlNode *data, bool confirm)
0c6c6d
      * be all if node is NULL) that the message has been received and
0c6c6d
      * acted upon.
0c6c6d
      */
0c6c6d
-    pcmk__xe_set_bool_attr(data, PCMK__XA_CONFIRM, confirm);
0c6c6d
+    if (!pcmk__str_eq(op, PCMK__ATTRD_CMD_CONFIRM, pcmk__str_none)) {
0c6c6d
+        pcmk__xe_set_bool_attr(data, PCMK__XA_CONFIRM, confirm);
0c6c6d
+    }
0c6c6d
 
0c6c6d
     attrd_xml_add_writer(data);
0c6c6d
     return send_cluster_message(node, crm_msg_attrd, data, TRUE);
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 115e6c3a0d8db4df3eccf6da1c344168799f890d Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Tue, 15 Nov 2022 09:35:28 -0500
0c6c6d
Subject: [PATCH 25/26] Fix: daemons: Check for NULL in
0c6c6d
 attrd_do_not_expect_from_peer.
0c6c6d
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_sync.c | 8 +++++++-
0c6c6d
 1 file changed, 7 insertions(+), 1 deletion(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c
0c6c6d
index 9d07796..6936771 100644
0c6c6d
--- a/daemons/attrd/attrd_sync.c
0c6c6d
+++ b/daemons/attrd/attrd_sync.c
0c6c6d
@@ -402,7 +402,13 @@ confirmation_timeout_cb(gpointer data)
0c6c6d
 void
0c6c6d
 attrd_do_not_expect_from_peer(const char *host)
0c6c6d
 {
0c6c6d
-    GList *keys = g_hash_table_get_keys(expected_confirmations);
0c6c6d
+    GList *keys = NULL;
0c6c6d
+
0c6c6d
+    if (expected_confirmations == NULL) {
0c6c6d
+        return;
0c6c6d
+    }
0c6c6d
+
0c6c6d
+    keys = g_hash_table_get_keys(expected_confirmations);
0c6c6d
 
0c6c6d
     crm_trace("Removing peer %s from expected confirmations", host);
0c6c6d
 
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d
0c6c6d
From 05da14f97ccd4f63f53801acc107ad661e5fd0c8 Mon Sep 17 00:00:00 2001
0c6c6d
From: Chris Lumens <clumens@redhat.com>
0c6c6d
Date: Wed, 16 Nov 2022 17:37:44 -0500
0c6c6d
Subject: [PATCH 26/26] Low: daemons: Support cluster-wide sync points for
0c6c6d
 multi IPC messages.
0c6c6d
0c6c6d
Supporting cluster-wide sync points means attrd_expect_confirmations
0c6c6d
needs to be called, and then attrd_send_message needs "true" as a third
0c6c6d
argument.  This indicates attrd wants confirmations back from all its
0c6c6d
peers when they have applied the update.
0c6c6d
0c6c6d
We're already doing this at the end of attrd_client_update for
0c6c6d
single-update IPC messages, and handling it for multi-update messages is
0c6c6d
a simple matter of breaking that code out into a function and making
0c6c6d
sure it's called.
0c6c6d
0c6c6d
Note that this leaves two other spots where sync points still need to be
0c6c6d
dealt with:
0c6c6d
0c6c6d
* An update message that uses a regex.  See
0c6c6d
  https://projects.clusterlabs.org/T600 for details.
0c6c6d
0c6c6d
* A multi-update IPC message in a cluster where that is not supported.
0c6c6d
  See https://projects.clusterlabs.org/T601 for details.
0c6c6d
---
0c6c6d
 daemons/attrd/attrd_ipc.c | 43 ++++++++++++++++++++++-----------------
0c6c6d
 1 file changed, 24 insertions(+), 19 deletions(-)
0c6c6d
0c6c6d
diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c
0c6c6d
index 16bfff4..8c5660d 100644
0c6c6d
--- a/daemons/attrd/attrd_ipc.c
0c6c6d
+++ b/daemons/attrd/attrd_ipc.c
0c6c6d
@@ -283,6 +283,28 @@ handle_value_expansion(const char **value, xmlNode *xml, const char *op,
0c6c6d
     return pcmk_rc_ok;
0c6c6d
 }
0c6c6d
 
0c6c6d
+static void
0c6c6d
+send_update_msg_to_cluster(pcmk__request_t *request, xmlNode *xml)
0c6c6d
+{
0c6c6d
+    if (pcmk__str_eq(attrd_request_sync_point(xml), PCMK__VALUE_CLUSTER, pcmk__str_none)) {
0c6c6d
+        /* The client is waiting on the cluster-wide sync point.  In this case,
0c6c6d
+         * the response ACK is not sent until this attrd broadcasts the update
0c6c6d
+         * and receives its own confirmation back from all peers.
0c6c6d
+         */
0c6c6d
+        attrd_expect_confirmations(request, attrd_cluster_sync_point_update);
0c6c6d
+        attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
0c6c6d
+
0c6c6d
+    } else {
0c6c6d
+        /* The client is either waiting on the local sync point or was not
0c6c6d
+         * waiting on any sync point at all.  For the local sync point, the
0c6c6d
+         * response ACK is sent in attrd_peer_update.  For clients not
0c6c6d
+         * waiting on any sync point, the response ACK is sent in
0c6c6d
+         * handle_update_request immediately before this function was called.
0c6c6d
+         */
0c6c6d
+        attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
0c6c6d
+    }
0c6c6d
+}
0c6c6d
+
0c6c6d
 xmlNode *
0c6c6d
 attrd_client_update(pcmk__request_t *request)
0c6c6d
 {
0c6c6d
@@ -314,7 +336,7 @@ attrd_client_update(pcmk__request_t *request)
0c6c6d
                 }
0c6c6d
             }
0c6c6d
 
0c6c6d
-            attrd_send_message(NULL, xml, false);
0c6c6d
+            send_update_msg_to_cluster(request, xml);
0c6c6d
             pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
 
0c6c6d
         } else {
0c6c6d
@@ -388,24 +410,7 @@ attrd_client_update(pcmk__request_t *request)
0c6c6d
     crm_debug("Broadcasting %s[%s]=%s%s", attr, crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME),
0c6c6d
               value, (attrd_election_won()? " (writer)" : ""));
0c6c6d
 
0c6c6d
-    if (pcmk__str_eq(attrd_request_sync_point(xml), PCMK__VALUE_CLUSTER, pcmk__str_none)) {
0c6c6d
-        /* The client is waiting on the cluster-wide sync point.  In this case,
0c6c6d
-         * the response ACK is not sent until this attrd broadcasts the update
0c6c6d
-         * and receives its own confirmation back from all peers.
0c6c6d
-         */
0c6c6d
-        attrd_expect_confirmations(request, attrd_cluster_sync_point_update);
0c6c6d
-        attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
0c6c6d
-
0c6c6d
-    } else {
0c6c6d
-        /* The client is either waiting on the local sync point or was not
0c6c6d
-         * waiting on any sync point at all.  For the local sync point, the
0c6c6d
-         * response ACK is sent in attrd_peer_update.  For clients not
0c6c6d
-         * waiting on any sync point, the response ACK is sent in
0c6c6d
-         * handle_update_request immediately before this function was called.
0c6c6d
-         */
0c6c6d
-        attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
0c6c6d
-    }
0c6c6d
-
0c6c6d
+    send_update_msg_to_cluster(request, xml);
0c6c6d
     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
0c6c6d
     return NULL;
0c6c6d
 }
0c6c6d
-- 
0c6c6d
2.31.1
0c6c6d