summaryrefslogtreecommitdiffstats
path: root/lib/cib/cib_utils.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/cib/cib_utils.c511
1 files changed, 394 insertions, 117 deletions
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
index c75d844..0082eef 100644
--- a/lib/cib/cib_utils.c
+++ b/lib/cib/cib_utils.c
@@ -20,6 +20,7 @@
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
+#include <crm/common/cib_internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/pengine/rules.h>
@@ -78,6 +79,154 @@ cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *upda
}
/*!
+ * \internal
+ * \brief Get the XML patchset from a CIB diff notification
+ *
+ * \param[in] msg CIB diff notification
+ * \param[out] patchset Where to store XML patchset
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset)
+{
+ int rc = pcmk_err_generic;
+
+ CRM_ASSERT(patchset != NULL);
+ *patchset = NULL;
+
+ if (msg == NULL) {
+ crm_err("CIB diff notification received with no XML");
+ return ENOMSG;
+ }
+
+ if ((crm_element_value_int(msg, F_CIB_RC, &rc) != 0) || (rc != pcmk_ok)) {
+ crm_warn("Ignore failed CIB update: %s " CRM_XS " rc=%d",
+ pcmk_strerror(rc), rc);
+ crm_log_xml_debug(msg, "failed");
+ return pcmk_legacy2rc(rc);
+ }
+
+ *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
+
+ if (*patchset == NULL) {
+ crm_err("CIB diff notification received with no patchset");
+ return ENOMSG;
+ }
+ return pcmk_rc_ok;
+}
+
+#define XPATH_DIFF_V1 "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
+
+/*!
+ * \internal
+ * \brief Check whether a given CIB element was modified in a CIB patchset (v1)
+ *
+ * \param[in] patchset CIB XML patchset
+ * \param[in] element XML tag of CIB element to check (\c NULL is equivalent
+ * to \c XML_TAG_CIB)
+ *
+ * \return \c true if \p element was modified, or \c false otherwise
+ */
+static bool
+element_in_patchset_v1(const xmlNode *patchset, const char *element)
+{
+ char *xpath = crm_strdup_printf(XPATH_DIFF_V1 "//%s",
+ pcmk__s(element, XML_TAG_CIB));
+ xmlXPathObject *xpath_obj = xpath_search(patchset, xpath);
+
+ free(xpath);
+
+ if (xpath_obj == NULL) {
+ return false;
+ }
+ freeXpathObject(xpath_obj);
+ return true;
+}
+
+/*!
+ * \internal
+ * \brief Check whether a given CIB element was modified in a CIB patchset (v2)
+ *
+ * \param[in] patchset CIB XML patchset
+ * \param[in] element XML tag of CIB element to check (\c NULL is equivalent
+ * to \c XML_TAG_CIB). Supported values include any CIB
+ * element supported by \c pcmk__cib_abs_xpath_for().
+ *
+ * \return \c true if \p element was modified, or \c false otherwise
+ */
+static bool
+element_in_patchset_v2(const xmlNode *patchset, const char *element)
+{
+ const char *element_xpath = pcmk__cib_abs_xpath_for(element);
+ const char *parent_xpath = pcmk_cib_parent_name_for(element);
+ char *element_regex = NULL;
+ bool rc = false;
+
+ CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
+
+ // Matches if and only if element_xpath is part of a changed path
+ element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
+
+ for (const xmlNode *change = first_named_child(patchset, XML_DIFF_CHANGE);
+ change != NULL; change = crm_next_same_xml(change)) {
+
+ const char *op = crm_element_value(change, F_CIB_OPERATION);
+ const char *diff_xpath = crm_element_value(change, XML_DIFF_PATH);
+
+ if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
+ // Change to an existing element
+ rc = true;
+ break;
+ }
+
+ if (pcmk__str_eq(op, "create", pcmk__str_none)
+ && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
+ && pcmk__xe_is(pcmk__xml_first_child(change), element)) {
+
+ // Newly added element
+ rc = true;
+ break;
+ }
+ }
+
+ free(element_regex);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Check whether a given CIB element was modified in a CIB patchset
+ *
+ * \param[in] patchset CIB XML patchset
+ * \param[in] element XML tag of CIB element to check (\c NULL is equivalent
+ * to \c XML_TAG_CIB). Supported values include any CIB
+ * element supported by \c pcmk__cib_abs_xpath_for().
+ *
+ * \return \c true if \p element was modified, or \c false otherwise
+ */
+bool
+cib__element_in_patchset(const xmlNode *patchset, const char *element)
+{
+ int format = 1;
+
+ CRM_ASSERT(patchset != NULL);
+
+ crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
+ switch (format) {
+ case 1:
+ return element_in_patchset_v1(patchset, element);
+
+ case 2:
+ return element_in_patchset_v2(patchset, element);
+
+ default:
+ crm_warn("Unknown patch format: %d", format);
+ return false;
+ }
+}
+
+/*!
* \brief Create XML for a new (empty) CIB
*
* \param[in] cib_epoch What to use as "epoch" CIB property
@@ -141,30 +290,79 @@ cib_acl_enabled(xmlNode *xml, const char *user)
return rc;
}
+/*!
+ * \internal
+ * \brief Determine whether to perform operations on a scratch copy of the CIB
+ *
+ * \param[in] op CIB operation
+ * \param[in] section CIB section
+ * \param[in] call_options CIB call options
+ *
+ * \return \p true if we should make a copy of the CIB, or \p false otherwise
+ */
+static bool
+should_copy_cib(const char *op, const char *section, int call_options)
+{
+ if (pcmk_is_set(call_options, cib_dryrun)) {
+ // cib_dryrun implies a scratch copy by definition; no side effects
+ return true;
+ }
+
+ if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) {
+ /* Commit-transaction must make a copy for atomicity. We must revert to
+ * the original CIB if the entire transaction cannot be applied
+ * successfully.
+ */
+ return true;
+ }
+
+ if (pcmk_is_set(call_options, cib_transaction)) {
+ /* If cib_transaction is set, then we're in the process of committing a
+ * transaction. The commit-transaction request already made a scratch
+ * copy, and we're accumulating changes in that copy.
+ */
+ return false;
+ }
+
+ if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_none)) {
+ /* Copying large CIBs accounts for a huge percentage of our CIB usage,
+ * and this avoids some of it.
+ *
+ * @TODO: Is this safe? See discussion at
+ * https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690.
+ */
+ return false;
+ }
+
+ // Default behavior is to operate on a scratch copy
+ return true;
+}
+
int
-cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query,
- const char *section, xmlNode * req, xmlNode * input,
- gboolean manage_counters, gboolean * config_changed,
- xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output)
+cib_perform_op(const char *op, int call_options, cib__op_fn_t fn, bool is_query,
+ const char *section, xmlNode *req, xmlNode *input,
+ bool manage_counters, bool *config_changed,
+ xmlNode **current_cib, xmlNode **result_cib, xmlNode **diff,
+ xmlNode **output)
{
int rc = pcmk_ok;
- gboolean check_schema = TRUE;
+ bool check_schema = true;
+ bool make_copy = true;
xmlNode *top = NULL;
xmlNode *scratch = NULL;
+ xmlNode *patchset_cib = NULL;
xmlNode *local_diff = NULL;
const char *new_version = NULL;
const char *user = crm_element_value(req, F_CIB_USER);
- bool with_digest = FALSE;
-
- pcmk__output_t *out = NULL;
- int out_rc = pcmk_rc_no_output;
+ bool with_digest = false;
crm_trace("Begin %s%s%s op",
(pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
(is_query? "read-only " : ""), op);
CRM_CHECK(output != NULL, return -ENOMSG);
+ CRM_CHECK(current_cib != NULL, return -ENOMSG);
CRM_CHECK(result_cib != NULL, return -ENOMSG);
CRM_CHECK(config_changed != NULL, return -ENOMSG);
@@ -173,25 +371,26 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
}
*result_cib = NULL;
- *config_changed = FALSE;
+ *config_changed = false;
if (fn == NULL) {
return -EINVAL;
}
if (is_query) {
- xmlNode *cib_ro = current_cib;
+ xmlNode *cib_ro = *current_cib;
xmlNode *cib_filtered = NULL;
- if(cib_acl_enabled(cib_ro, user)) {
- if(xml_acl_filtered_copy(user, current_cib, current_cib, &cib_filtered)) {
- if (cib_filtered == NULL) {
- crm_debug("Pre-filtered the entire cib");
- return -EACCES;
- }
- cib_ro = cib_filtered;
- crm_log_xml_trace(cib_ro, "filtered");
+ if (cib_acl_enabled(cib_ro, user)
+ && xml_acl_filtered_copy(user, *current_cib, *current_cib,
+ &cib_filtered)) {
+
+ if (cib_filtered == NULL) {
+ crm_debug("Pre-filtered the entire cib");
+ return -EACCES;
}
+ cib_ro = cib_filtered;
+ crm_log_xml_trace(cib_ro, "filtered");
}
rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
@@ -202,14 +401,14 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
} else if(cib_filtered == *output) {
cib_filtered = NULL; /* Let them have this copy */
- } else if(*output == current_cib) {
+ } else if (*output == *current_cib) {
/* They already know not to free it */
} else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
/* We're about to free the document of which *output is a part */
*output = copy_xml(*output);
- } else if((*output)->doc == current_cib->doc) {
+ } else if ((*output)->doc == (*current_cib)->doc) {
/* Give them a copy they can free */
*output = copy_xml(*output);
}
@@ -218,31 +417,41 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
return rc;
}
+ make_copy = should_copy_cib(op, section, call_options);
- if (pcmk_is_set(call_options, cib_zero_copy)) {
+ if (!make_copy) {
/* Conditional on v2 patch style */
- scratch = current_cib;
+ scratch = *current_cib;
- /* Create a shallow copy of current_cib for the version details */
- current_cib = create_xml_node(NULL, (const char *)scratch->name);
- copy_in_properties(current_cib, scratch);
- top = current_cib;
+ // Make a copy of the top-level element to store version details
+ top = create_xml_node(NULL, (const char *) scratch->name);
+ copy_in_properties(top, scratch);
+ patchset_cib = top;
xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
+ /* If scratch points to a new object now (for example, after an erase
+ * operation), then *current_cib should point to the same object.
+ */
+ *current_cib = scratch;
+
} else {
- scratch = copy_xml(current_cib);
+ scratch = copy_xml(*current_cib);
+ patchset_cib = *current_cib;
+
xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
- rc = (*fn) (op, call_options, section, req, input, current_cib, &scratch, output);
+ rc = (*fn) (op, call_options, section, req, input, *current_cib,
+ &scratch, output);
- if(scratch && xml_tracking_changes(scratch) == FALSE) {
+ if ((scratch != NULL) && !xml_tracking_changes(scratch)) {
crm_trace("Inferring changes after %s op", op);
- xml_track_changes(scratch, user, current_cib, cib_acl_enabled(current_cib, user));
- xml_calculate_changes(current_cib, scratch);
+ xml_track_changes(scratch, user, *current_cib,
+ cib_acl_enabled(*current_cib, user));
+ xml_calculate_changes(*current_cib, scratch);
}
- CRM_CHECK(current_cib != scratch, return -EINVAL);
+ CRM_CHECK(*current_cib != scratch, return -EINVAL);
}
xml_acl_disable(scratch); /* Allow the system to make any additional changes */
@@ -271,12 +480,12 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
}
}
- if (current_cib) {
+ if (patchset_cib != NULL) {
int old = 0;
int new = 0;
crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new);
- crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old);
+ crm_element_value_int(patchset_cib, XML_ATTR_GENERATION_ADMIN, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
@@ -287,7 +496,7 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
} else if (old == new) {
crm_element_value_int(scratch, XML_ATTR_GENERATION, &new);
- crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old);
+ crm_element_value_int(patchset_cib, XML_ATTR_GENERATION, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
XML_ATTR_GENERATION, old, new, call_options);
@@ -302,13 +511,14 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
pcmk__strip_xml_text(scratch);
fix_plus_plus_recursive(scratch);
- if (pcmk_is_set(call_options, cib_zero_copy)) {
- /* At this point, current_cib is just the 'cib' tag and its properties,
+ if (!make_copy) {
+ /* At this point, patchset_cib is just the "cib" tag and its properties.
*
* The v1 format would barf on this, but we know the v2 patch
* format only needs it for the top-level version fields
*/
- local_diff = xml_create_patchset(2, current_cib, scratch, (bool*)config_changed, manage_counters);
+ local_diff = xml_create_patchset(2, patchset_cib, scratch,
+ config_changed, manage_counters);
} else {
static time_t expires = 0;
@@ -316,63 +526,38 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
if (expires < tm_now) {
expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */
- with_digest = TRUE;
+ with_digest = true;
}
- local_diff = xml_create_patchset(0, current_cib, scratch, (bool*)config_changed, manage_counters);
+ local_diff = xml_create_patchset(0, patchset_cib, scratch,
+ config_changed, manage_counters);
}
- // Create a log output object only if we're going to use it
- pcmk__if_tracing(
- {
- rc = pcmk_rc2legacy(pcmk__log_output_new(&out));
- CRM_CHECK(rc == pcmk_ok, goto done);
-
- pcmk__output_set_log_level(out, LOG_TRACE);
- out_rc = pcmk__xml_show_changes(out, scratch);
- },
- {}
- );
+ pcmk__log_xml_changes(LOG_TRACE, scratch);
xml_accept_changes(scratch);
if(local_diff) {
- int temp_rc = pcmk_rc_no_output;
-
- patchset_process_digest(local_diff, current_cib, scratch, with_digest);
-
- if (out == NULL) {
- rc = pcmk_rc2legacy(pcmk__log_output_new(&out));
- CRM_CHECK(rc == pcmk_ok, goto done);
- }
- pcmk__output_set_log_level(out, LOG_INFO);
- temp_rc = out->message(out, "xml-patchset", local_diff);
- out_rc = pcmk__output_select_rc(rc, temp_rc);
-
+ patchset_process_digest(local_diff, patchset_cib, scratch, with_digest);
+ pcmk__log_xml_patchset(LOG_INFO, local_diff);
crm_log_xml_trace(local_diff, "raw patch");
}
- if (out != NULL) {
- out->finish(out, pcmk_rc2exitc(out_rc), true, NULL);
- pcmk__output_free(out);
- out = NULL;
- }
-
- if (!pcmk_is_set(call_options, cib_zero_copy) && (local_diff != NULL)) {
+ if (make_copy && (local_diff != NULL)) {
// Original to compare against doesn't exist
pcmk__if_tracing(
{
// Validate the calculated patch set
int test_rc = pcmk_ok;
int format = 1;
- xmlNode *cib_copy = copy_xml(current_cib);
+ xmlNode *cib_copy = copy_xml(patchset_cib);
- crm_element_value_int(local_diff, "format", &format);
+ crm_element_value_int(local_diff, PCMK_XA_FORMAT, &format);
test_rc = xml_apply_patchset(cib_copy, local_diff,
manage_counters);
if (test_rc != pcmk_ok) {
save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
- save_xml_to_file(current_cib, "PatchApply:input", NULL);
+ save_xml_to_file(patchset_cib, "PatchApply:input", NULL);
save_xml_to_file(scratch, "PatchApply:actual", NULL);
save_xml_to_file(local_diff, "PatchApply:diff", NULL);
crm_err("v%d patchset error, patch failed to apply: %s "
@@ -391,7 +576,7 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
* a) we don't really care whats in the status section
* b) we don't validate any of its contents at the moment anyway
*/
- check_schema = FALSE;
+ check_schema = false;
}
/* === scratch must not be modified after this point ===
@@ -420,19 +605,35 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
/* Does the CIB support the "update-*" attributes... */
if (current_schema >= minimum_schema) {
+ /* Ensure values of origin, client, and user in scratch match
+ * the values in req
+ */
const char *origin = crm_element_value(req, F_ORIG);
+ const char *client = crm_element_value(req, F_CIB_CLIENTNAME);
+
+ if (origin != NULL) {
+ crm_xml_add(scratch, XML_ATTR_UPDATE_ORIG, origin);
+ } else {
+ xml_remove_prop(scratch, XML_ATTR_UPDATE_ORIG);
+ }
- CRM_LOG_ASSERT(origin != NULL);
- crm_xml_replace(scratch, XML_ATTR_UPDATE_ORIG, origin);
- crm_xml_replace(scratch, XML_ATTR_UPDATE_CLIENT,
- crm_element_value(req, F_CIB_CLIENTNAME));
- crm_xml_replace(scratch, XML_ATTR_UPDATE_USER, crm_element_value(req, F_CIB_USER));
+ if (client != NULL) {
+ crm_xml_add(scratch, XML_ATTR_UPDATE_CLIENT, user);
+ } else {
+ xml_remove_prop(scratch, XML_ATTR_UPDATE_CLIENT);
+ }
+
+ if (user != NULL) {
+ crm_xml_add(scratch, XML_ATTR_UPDATE_USER, user);
+ } else {
+ xml_remove_prop(scratch, XML_ATTR_UPDATE_USER);
+ }
}
}
}
crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
- if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, TRUE)) {
+ if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, true)) {
const char *current_schema = crm_element_value(scratch,
XML_ATTR_VALIDATION);
@@ -444,13 +645,17 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
done:
*result_cib = scratch;
- if(rc != pcmk_ok && cib_acl_enabled(current_cib, user)) {
- if(xml_acl_filtered_copy(user, current_cib, scratch, result_cib)) {
- if (*result_cib == NULL) {
- crm_debug("Pre-filtered the entire cib result");
- }
- free_xml(scratch);
+
+ /* @TODO: This may not work correctly with !make_copy, since we don't
+ * keep the original CIB.
+ */
+ if ((rc != pcmk_ok) && cib_acl_enabled(patchset_cib, user)
+ && xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) {
+
+ if (*result_cib == NULL) {
+ crm_debug("Pre-filtered the entire cib result");
}
+ free_xml(scratch);
}
if(diff) {
@@ -464,36 +669,117 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer
return rc;
}
-xmlNode *
-cib_create_op(int call_id, const char *op, const char *host,
- const char *section, xmlNode *data, int call_options,
- const char *user_name)
+int
+cib__create_op(cib_t *cib, const char *op, const char *host,
+ const char *section, xmlNode *data, int call_options,
+ const char *user_name, const char *client_name,
+ xmlNode **op_msg)
{
- xmlNode *op_msg = create_xml_node(NULL, "cib_command");
+ CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO);
- CRM_CHECK(op_msg != NULL, return NULL);
-
- crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command");
+ *op_msg = create_xml_node(NULL, T_CIB_COMMAND);
+ if (*op_msg == NULL) {
+ return -EPROTO;
+ }
- crm_xml_add(op_msg, F_TYPE, T_CIB);
- crm_xml_add(op_msg, F_CIB_OPERATION, op);
- crm_xml_add(op_msg, F_CIB_HOST, host);
- crm_xml_add(op_msg, F_CIB_SECTION, section);
- crm_xml_add_int(op_msg, F_CIB_CALLID, call_id);
- if (user_name) {
- crm_xml_add(op_msg, F_CIB_USER, user_name);
+ cib->call_id++;
+ if (cib->call_id < 1) {
+ cib->call_id = 1;
}
+
+ crm_xml_add(*op_msg, F_XML_TAGNAME, T_CIB_COMMAND);
+ crm_xml_add(*op_msg, F_TYPE, T_CIB);
+ crm_xml_add(*op_msg, F_CIB_OPERATION, op);
+ crm_xml_add(*op_msg, F_CIB_HOST, host);
+ crm_xml_add(*op_msg, F_CIB_SECTION, section);
+ crm_xml_add(*op_msg, F_CIB_USER, user_name);
+ crm_xml_add(*op_msg, F_CIB_CLIENTNAME, client_name);
+ crm_xml_add_int(*op_msg, F_CIB_CALLID, cib->call_id);
+
crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
- crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options);
+ crm_xml_add_int(*op_msg, F_CIB_CALLOPTS, call_options);
if (data != NULL) {
- add_message_xml(op_msg, F_CIB_CALLDATA, data);
+ add_message_xml(*op_msg, F_CIB_CALLDATA, data);
}
- if (call_options & cib_inhibit_bcast) {
- CRM_CHECK((call_options & cib_scope_local), return NULL);
+ if (pcmk_is_set(call_options, cib_inhibit_bcast)) {
+ CRM_CHECK(pcmk_is_set(call_options, cib_scope_local),
+ free_xml(*op_msg); return -EPROTO);
}
- return op_msg;
+ return pcmk_ok;
+}
+
+/*!
+ * \internal
+ * \brief Check whether a CIB request is supported in a transaction
+ *
+ * \param[in] request CIB request
+ *
+ * \return Standard Pacemaker return code
+ */
+static int
+validate_transaction_request(const xmlNode *request)
+{
+ const char *op = crm_element_value(request, F_CIB_OPERATION);
+ const char *host = crm_element_value(request, F_CIB_HOST);
+ const cib__operation_t *operation = NULL;
+ int rc = cib__get_operation(op, &operation);
+
+ if (rc != pcmk_rc_ok) {
+ // cib__get_operation() logs error
+ return rc;
+ }
+
+ if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)) {
+ crm_err("Operation %s is not supported in CIB transactions", op);
+ return EOPNOTSUPP;
+ }
+
+ if (host != NULL) {
+ crm_err("Operation targeting a specific node (%s) is not supported in "
+ "a CIB transaction",
+ host);
+ return EOPNOTSUPP;
+ }
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Append a CIB request to a CIB transaction
+ *
+ * \param[in,out] cib CIB client whose transaction to extend
+ * \param[in,out] request Request to add to transaction
+ *
+ * \return Legacy Pacemaker return code
+ */
+int
+cib__extend_transaction(cib_t *cib, xmlNode *request)
+{
+ int rc = pcmk_rc_ok;
+
+ CRM_ASSERT((cib != NULL) && (request != NULL));
+
+ rc = validate_transaction_request(request);
+
+ if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) {
+ rc = pcmk_rc_no_transaction;
+ }
+
+ if (rc == pcmk_rc_ok) {
+ add_node_copy(cib->transaction, request);
+
+ } else {
+ const char *op = crm_element_value(request, F_CIB_OPERATION);
+ const char *client_id = NULL;
+
+ cib->cmds->client_id(cib, NULL, &client_id);
+ crm_err("Failed to add '%s' operation to transaction for client %s: %s",
+ op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc));
+ crm_log_xml_info(request, "failed");
+ }
+ return pcmk_rc2legacy(rc);
}
void
@@ -701,16 +987,7 @@ cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
}
if (level > LOG_CRIT) {
- pcmk__output_t *out = NULL;
-
- rc = pcmk_rc2legacy(pcmk__log_output_new(&out));
- CRM_CHECK(rc == pcmk_ok, return rc);
-
- pcmk__output_set_log_level(out, level);
- rc = out->message(out, "xml-patchset", diff);
- out->finish(out, pcmk_rc2exitc(rc), true, NULL);
- pcmk__output_free(out);
- rc = pcmk_ok;
+ pcmk__log_xml_patchset(level, diff);
}
if (input != NULL) {