Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/lib/common/tests/patchset/pcmk__cib_element_in_patchset_test.c b/lib/common/tests/patchset/pcmk__cib_element_in_patchset_test.c
index 75d77393fd..dfe9e859f9 100644
--- a/lib/common/tests/patchset/pcmk__cib_element_in_patchset_test.c
+++ b/lib/common/tests/patchset/pcmk__cib_element_in_patchset_test.c
@@ -1,242 +1,243 @@
/*
* Copyright 2024-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/unittest_internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#define ORIG_CIB \
"<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH "=\"0\"" \
" " PCMK_XA_EPOCH "=\"0\"" \
" " PCMK_XA_NUM_UPDATES "=\"0\">" \
"<" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_CRM_CONFIG "/>" \
"<" PCMK_XE_NODES ">" \
"<" PCMK_XE_NODE " " PCMK_XA_ID "=\"1\"" \
" " PCMK_XA_UNAME "=\"node-1\"/>" \
"</" PCMK_XE_NODES ">" \
"<" PCMK_XE_RESOURCES "/>" \
"<" PCMK_XE_CONSTRAINTS "/>" \
"</" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_STATUS "/>" \
"</" PCMK_XE_CIB ">"
static void
assert_in_patchset(const char *source_s, const char *target_s,
const char *element, bool reference)
{
xmlNode *source = pcmk__xml_parse(source_s);
xmlNode *target = pcmk__xml_parse(target_s);
xmlNode *patchset = NULL;
- xml_calculate_significant_changes(source, target);
+ pcmk__xml_doc_set_flags(target->doc, pcmk__xf_ignore_attr_pos);
+ pcmk__xml_mark_changes(source, target);
patchset = xml_create_patchset(2, source, target, NULL, false);
if (reference) {
assert_true(pcmk__cib_element_in_patchset(patchset, element));
} else {
assert_false(pcmk__cib_element_in_patchset(patchset, element));
}
pcmk__xml_free(source);
pcmk__xml_free(target);
pcmk__xml_free(patchset);
}
static void
null_patchset_asserts(void **state)
{
pcmk__assert_asserts(pcmk__cib_element_in_patchset(NULL, NULL));
pcmk__assert_asserts(pcmk__cib_element_in_patchset(NULL, PCMK_XE_NODES));
}
// PCMK_XE_ALERTS element has been created relative to ORIG_CIB
#define CREATE_CIB \
"<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH "=\"0\"" \
" " PCMK_XA_EPOCH "=\"0\"" \
" " PCMK_XA_NUM_UPDATES "=\"0\">" \
"<" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_CRM_CONFIG "/>" \
"<" PCMK_XE_NODES ">" \
"<" PCMK_XE_NODE " " PCMK_XA_ID "=\"1\"" \
" " PCMK_XA_UNAME "=\"node-1\"/>" \
"</" PCMK_XE_NODES ">" \
"<" PCMK_XE_RESOURCES "/>" \
"<" PCMK_XE_CONSTRAINTS "/>" \
"<" PCMK_XE_ALERTS "/>" \
"</" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_STATUS "/>" \
"</" PCMK_XE_CIB ">"
static void
create_op(void **state)
{
// Requested element was created
assert_in_patchset(ORIG_CIB, CREATE_CIB, PCMK_XE_ALERTS, true);
// Requested element's descendant was created
assert_in_patchset(ORIG_CIB, CREATE_CIB, PCMK_XE_CONFIGURATION, true);
assert_in_patchset(ORIG_CIB, CREATE_CIB, NULL, true);
// Requested element was not changed
assert_in_patchset(ORIG_CIB, CREATE_CIB, PCMK_XE_STATUS, false);
}
static void
delete_op(void **state)
{
// Requested element was deleted
assert_in_patchset(CREATE_CIB, ORIG_CIB, PCMK_XE_ALERTS, true);
// Requested element's descendant was deleted
assert_in_patchset(CREATE_CIB, ORIG_CIB, PCMK_XE_CONFIGURATION, true);
assert_in_patchset(CREATE_CIB, ORIG_CIB, NULL, true);
// Requested element was not changed
assert_in_patchset(CREATE_CIB, ORIG_CIB, PCMK_XE_STATUS, false);
}
// PCMK_XE_CIB XML attribute was added relative to ORIG_CIB
#define MODIFY_ADD_CIB \
"<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH "=\"0\"" \
" " PCMK_XA_EPOCH "=\"0\"" \
" " PCMK_XA_NUM_UPDATES "=\"0\"" \
" " PCMK_XA_CRM_FEATURE_SET "=\"3.19.7\">" \
"<" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_CRM_CONFIG "/>" \
"<" PCMK_XE_NODES ">" \
"<" PCMK_XE_NODE " " PCMK_XA_ID "=\"1\"" \
" " PCMK_XA_UNAME "=\"node-1\"/>" \
"</" PCMK_XE_NODES ">" \
"<" PCMK_XE_RESOURCES "/>" \
"<" PCMK_XE_CONSTRAINTS "/>" \
"</" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_STATUS "/>" \
"</" PCMK_XE_CIB ">"
// PCMK_XE_CIB XML attribute was updated relative to ORIG_CIB
#define MODIFY_UPDATE_CIB \
"<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH "=\"0\"" \
" " PCMK_XA_EPOCH "=\"0\"" \
" " PCMK_XA_NUM_UPDATES "=\"1\">" \
"<" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_CRM_CONFIG "/>" \
"<" PCMK_XE_NODES ">" \
"<" PCMK_XE_NODE " " PCMK_XA_ID "=\"1\"" \
" " PCMK_XA_UNAME "=\"node-1\"/>" \
"</" PCMK_XE_NODES ">" \
"<" PCMK_XE_RESOURCES "/>" \
"<" PCMK_XE_CONSTRAINTS "/>" \
"</" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_STATUS "/>" \
"</" PCMK_XE_CIB ">"
// PCMK_XE_NODE XML attribute was added relative to ORIG_CIB
#define MODIFY_ADD_NODE_CIB \
"<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH "=\"0\"" \
" " PCMK_XA_EPOCH "=\"0\"" \
" " PCMK_XA_NUM_UPDATES "=\"0\">" \
"<" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_CRM_CONFIG "/>" \
"<" PCMK_XE_NODES ">" \
"<" PCMK_XE_NODE " " PCMK_XA_ID "=\"1\"" \
" " PCMK_XA_UNAME "=\"node-1\"" \
" " PCMK_XA_TYPE "=\"member\"/>" \
"</" PCMK_XE_NODES ">" \
"<" PCMK_XE_RESOURCES "/>" \
"<" PCMK_XE_CONSTRAINTS "/>" \
"</" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_STATUS "/>" \
"</" PCMK_XE_CIB ">"
// PCMK_XE_NODE XML attribute was updated relative to ORIG_CIB
#define MODIFY_UPDATE_NODE_CIB \
"<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH "=\"0\"" \
" " PCMK_XA_EPOCH "=\"0\"" \
" " PCMK_XA_NUM_UPDATES "=\"0\">" \
"<" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_CRM_CONFIG "/>" \
"<" PCMK_XE_NODES ">" \
"<" PCMK_XE_NODE " " PCMK_XA_ID "=\"1\"" \
" " PCMK_XA_UNAME "=\"node-2\"/>" \
"</" PCMK_XE_NODES ">" \
"<" PCMK_XE_RESOURCES "/>" \
"<" PCMK_XE_CONSTRAINTS "/>" \
"</" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_STATUS "/>" \
"</" PCMK_XE_CIB ">"
static void
modify_op(void **state)
{
// Requested element was modified (attribute added)
assert_in_patchset(ORIG_CIB, MODIFY_ADD_CIB, PCMK_XE_CIB, true);
// Requested element was modified (attribute updated)
assert_in_patchset(ORIG_CIB, MODIFY_UPDATE_CIB, PCMK_XE_CIB, true);
// Requested element was modified (attribute deleted)
assert_in_patchset(MODIFY_ADD_CIB, ORIG_CIB, PCMK_XE_CIB, true);
// Requested element's descendant was modified (attribute added)
assert_in_patchset(ORIG_CIB, MODIFY_ADD_NODE_CIB, PCMK_XE_CIB, true);
assert_in_patchset(ORIG_CIB, MODIFY_ADD_NODE_CIB, NULL, true);
// Requested element's descendant was modified (attribute updated)
assert_in_patchset(ORIG_CIB, MODIFY_UPDATE_NODE_CIB, PCMK_XE_CIB, true);
assert_in_patchset(ORIG_CIB, MODIFY_UPDATE_NODE_CIB, NULL, true);
// Requested element's descenant was modified (attribute deleted)
assert_in_patchset(MODIFY_ADD_NODE_CIB, ORIG_CIB, PCMK_XE_CIB, true);
assert_in_patchset(MODIFY_ADD_NODE_CIB, ORIG_CIB, NULL, true);
// Requested element was not changed
assert_in_patchset(ORIG_CIB, MODIFY_ADD_CIB, PCMK_XE_STATUS, false);
assert_in_patchset(ORIG_CIB, MODIFY_UPDATE_CIB, PCMK_XE_STATUS, false);
assert_in_patchset(ORIG_CIB, MODIFY_ADD_NODE_CIB, PCMK_XE_STATUS, false);
assert_in_patchset(ORIG_CIB, MODIFY_UPDATE_NODE_CIB, PCMK_XE_STATUS, false);
}
// PCMK_XE_RESOURCES and PCMK_XE_CONSTRAINTS are swapped relative to ORIG_CIB
#define MOVE_CIB \
"<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH "=\"0\"" \
" " PCMK_XA_EPOCH "=\"0\"" \
" " PCMK_XA_NUM_UPDATES "=\"0\">" \
"<" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_CRM_CONFIG "/>" \
"<" PCMK_XE_NODES "/>" \
"<" PCMK_XE_CONSTRAINTS "/>" \
"<" PCMK_XE_RESOURCES "/>" \
"</" PCMK_XE_CONFIGURATION ">" \
"<" PCMK_XE_STATUS "/>" \
"</" PCMK_XE_CIB ">"
static void
move_op(void **state)
{
// Requested element was moved
assert_in_patchset(ORIG_CIB, MOVE_CIB, PCMK_XE_RESOURCES, true);
assert_in_patchset(ORIG_CIB, MOVE_CIB, PCMK_XE_CONSTRAINTS, true);
// Requested element's descendant was moved
assert_in_patchset(ORIG_CIB, MOVE_CIB, PCMK_XE_CONFIGURATION, true);
assert_in_patchset(ORIG_CIB, MOVE_CIB, NULL, true);
// Requested element was not changed
assert_in_patchset(ORIG_CIB, MOVE_CIB, PCMK_XE_STATUS, false);
}
PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group,
cmocka_unit_test(null_patchset_asserts),
cmocka_unit_test(create_op),
cmocka_unit_test(delete_op),
cmocka_unit_test(modify_op),
cmocka_unit_test(move_op))
diff --git a/tools/crm_diff.c b/tools/crm_diff.c
index 57f0f8a23e..27eb6d155c 100644
--- a/tools/crm_diff.c
+++ b/tools/crm_diff.c
@@ -1,337 +1,336 @@
/*
* Copyright 2005-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/output_internal.h>
#include <crm/common/xml.h>
#include <crm/common/ipc.h>
#include <crm/cib.h>
#define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \
"or apply such an output as a patch"
struct {
gboolean apply;
gboolean as_cib;
gboolean no_version;
gboolean raw_original;
gboolean raw_new;
gboolean use_stdin;
char *xml_file_original;
char *xml_file_new;
} options;
gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
static GOptionEntry original_xml_entries[] = {
{ "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_original,
"XML is contained in the named file",
"FILE" },
{ "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb,
"XML is contained in the supplied string",
"STRING" },
{ NULL }
};
static GOptionEntry operation_entries[] = {
{ "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_new,
"Compare the original XML to the contents of the named file",
"FILE" },
{ "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb,
"Compare the original XML with the contents of the supplied string",
"STRING" },
{ "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb,
"Patch the original XML with the contents of the named file",
"FILE" },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib,
"Compare/patch the inputs as a CIB (includes versions details)",
NULL },
{ "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin,
"",
NULL },
{ "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version,
"Generate the difference without versions details",
NULL },
{ NULL }
};
gboolean
new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.raw_new = TRUE;
pcmk__str_update(&options.xml_file_new, optarg);
return TRUE;
}
gboolean
original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.raw_original = TRUE;
pcmk__str_update(&options.xml_file_original, optarg);
return TRUE;
}
gboolean
patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.apply = TRUE;
pcmk__str_update(&options.xml_file_new, optarg);
return TRUE;
}
static void
print_patch(xmlNode *patch)
{
GString *buffer = g_string_sized_new(1024);
pcmk__xml_string(patch, pcmk__xml_fmt_pretty, buffer, 0);
printf("%s", buffer->str);
g_string_free(buffer, TRUE);
fflush(stdout);
}
// \return Standard Pacemaker return code
static int
apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
{
xmlNode *output = pcmk__xml_copy(NULL, input);
int rc = xml_apply_patchset(output, patch, as_cib);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
fprintf(stderr, "Could not apply patch: %s\n", pcmk_rc_str(rc));
pcmk__xml_free(output);
return rc;
}
if (output != NULL) {
char *buffer;
print_patch(output);
buffer = pcmk__digest_xml(output, true);
crm_trace("Digest: %s", pcmk__s(buffer, "<null>\n"));
free(buffer);
pcmk__xml_free(output);
}
return pcmk_rc_ok;
}
static void
log_patch_cib_versions(xmlNode *patch)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *fmt = NULL;
const char *digest = NULL;
xml_patch_versions(patch, add, del);
fmt = crm_element_value(patch, PCMK_XA_FORMAT);
digest = crm_element_value(patch, PCMK__XA_DIGEST);
if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
}
}
// \return Standard Pacemaker return code
static int
generate_patch(xmlNode *object_original, xmlNode *object_new, const char *xml_file_new,
gboolean as_cib, gboolean no_version)
{
const char *vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
xmlNode *output = NULL;
/* If we're ignoring the version, make the version information
* identical, so it isn't detected as a change. */
if (no_version) {
int lpc;
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
crm_copy_xml_element(object_original, object_new, vfields[lpc]);
}
}
- if(as_cib) {
- xml_calculate_significant_changes(object_original, object_new);
- } else {
- pcmk__xml_mark_changes(object_original, object_new);
+ if (as_cib) {
+ pcmk__xml_doc_set_flags(object_new->doc, pcmk__xf_ignore_attr_pos);
}
+ pcmk__xml_mark_changes(object_original, object_new);
crm_log_xml_debug(object_new, (xml_file_new? xml_file_new: "target"));
output = xml_create_patchset(0, object_original, object_new, NULL, FALSE);
pcmk__log_xml_changes(LOG_INFO, object_new);
pcmk__xml_commit_changes(object_new->doc);
if (output == NULL) {
return pcmk_rc_ok; // No changes
}
patchset_process_digest(output, object_original, object_new, as_cib);
if (as_cib) {
log_patch_cib_versions(output);
} else if (no_version) {
pcmk__xml_free(pcmk__xe_first_child(output, PCMK_XE_VERSION, NULL,
NULL));
}
pcmk__log_xml_patchset(LOG_NOTICE, output);
print_patch(output);
pcmk__xml_free(output);
/* pcmk_rc_error means there's a non-empty diff.
* @COMPAT Choose a more descriptive return code, like one that maps to
* CRM_EX_DIGEST?
*/
return pcmk_rc_error;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args) {
GOptionContext *context = NULL;
const char *description = "Examples:\n\n"
"Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
"\t# cibadmin --query > cib-old.xml\n\n"
"\t# cibadmin --query > cib-new.xml\n\n"
"Calculate and save the difference between the two files:\n\n"
"\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
"Apply the patch to the original file:\n\n"
"\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
"Apply the patch to the running cluster:\n\n"
"\t# cibadmin --patch -x patch.xml\n";
context = pcmk__build_arg_context(args, NULL, NULL, NULL);
g_option_context_set_description(context, description);
pcmk__add_arg_group(context, "xml", "Original XML:",
"Show original XML options", original_xml_entries);
pcmk__add_arg_group(context, "operation", "Operation:",
"Show operation options", operation_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
xmlNode *object_original = NULL;
xmlNode *object_new = NULL;
crm_exit_t exit_code = CRM_EX_OK;
GError *error = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, "nopNO");
GOptionContext *context = build_arg_context(args);
int rc = pcmk_rc_ok;
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
pcmk__cli_init_logging("crm_diff", args->verbosity);
if (args->version) {
g_strfreev(processed_args);
pcmk__free_arg_context(context);
/* FIXME: When crm_diff is converted to use formatted output, this can go. */
pcmk__cli_help('v');
}
if (options.apply && options.no_version) {
fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
} else if (options.as_cib && options.no_version) {
fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
exit_code = CRM_EX_USAGE;
goto done;
}
if (options.raw_original) {
object_original = pcmk__xml_parse(options.xml_file_original);
} else if (options.use_stdin) {
fprintf(stderr, "Input first XML fragment:");
object_original = pcmk__xml_read(NULL);
} else if (options.xml_file_original != NULL) {
object_original = pcmk__xml_read(options.xml_file_original);
}
if (options.raw_new) {
object_new = pcmk__xml_parse(options.xml_file_new);
} else if (options.use_stdin) {
fprintf(stderr, "Input second XML fragment:");
object_new = pcmk__xml_read(NULL);
} else if (options.xml_file_new != NULL) {
object_new = pcmk__xml_read(options.xml_file_new);
}
if (object_original == NULL) {
fprintf(stderr, "Could not parse the first XML fragment\n");
exit_code = CRM_EX_DATAERR;
goto done;
}
if (object_new == NULL) {
fprintf(stderr, "Could not parse the second XML fragment\n");
exit_code = CRM_EX_DATAERR;
goto done;
}
if (options.apply) {
rc = apply_patch(object_original, object_new, options.as_cib);
} else {
rc = generate_patch(object_original, object_new, options.xml_file_new, options.as_cib, options.no_version);
}
exit_code = pcmk_rc2exitc(rc);
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
free(options.xml_file_original);
free(options.xml_file_new);
pcmk__xml_free(object_original);
pcmk__xml_free(object_new);
pcmk__output_and_clear_error(&error, NULL);
crm_exit(exit_code);
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:11 PM (17 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1984622
Default Alt Text
(23 KB)

Event Timeline