Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4624289
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
23 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment