diff options
Diffstat (limited to 'daemons/schedulerd')
-rw-r--r-- | daemons/schedulerd/Makefile.am | 53 | ||||
-rw-r--r-- | daemons/schedulerd/pacemaker-schedulerd.c | 181 | ||||
-rw-r--r-- | daemons/schedulerd/pacemaker-schedulerd.h | 20 | ||||
-rw-r--r-- | daemons/schedulerd/schedulerd_messages.c | 335 |
4 files changed, 589 insertions, 0 deletions
diff --git a/daemons/schedulerd/Makefile.am b/daemons/schedulerd/Makefile.am new file mode 100644 index 0000000..57e819b --- /dev/null +++ b/daemons/schedulerd/Makefile.am @@ -0,0 +1,53 @@ +# +# Copyright 2004-2021 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 $(top_srcdir)/mk/common.mk +include $(top_srcdir)/mk/man.mk + +AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) + +halibdir = $(CRM_DAEMON_DIR) + +## binary progs + +halib_PROGRAMS = pacemaker-schedulerd + +if BUILD_XML_HELP +man7_MANS = pacemaker-schedulerd.7 +endif + +## SOURCES + +noinst_HEADERS = pacemaker-schedulerd.h + +pacemaker_schedulerd_CFLAGS = $(CFLAGS_HARDENED_EXE) +pacemaker_schedulerd_LDFLAGS = $(LDFLAGS_HARDENED_EXE) +pacemaker_schedulerd_LDADD = $(top_builddir)/lib/common/libcrmcommon.la \ + $(top_builddir)/lib/pengine/libpe_status.la \ + $(top_builddir)/lib/pacemaker/libpacemaker.la +# libcib for get_object_root() +pacemaker_schedulerd_SOURCES = pacemaker-schedulerd.c +pacemaker_schedulerd_SOURCES += schedulerd_messages.c + +install-exec-local: + $(INSTALL) -d -m 750 $(DESTDIR)/$(PE_STATE_DIR) + -chown $(CRM_DAEMON_USER):$(CRM_DAEMON_GROUP) $(DESTDIR)/$(PE_STATE_DIR) + +if BUILD_LEGACY_LINKS +install-exec-hook: + cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f pengine && $(LN_S) pacemaker-schedulerd pengine + +uninstall-hook: + cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f pengine +endif + +uninstall-local: + -rmdir $(DESTDIR)/$(PE_STATE_DIR) + +CLEANFILES = $(man7_MANS) diff --git a/daemons/schedulerd/pacemaker-schedulerd.c b/daemons/schedulerd/pacemaker-schedulerd.c new file mode 100644 index 0000000..3f2a3e8 --- /dev/null +++ b/daemons/schedulerd/pacemaker-schedulerd.c @@ -0,0 +1,181 @@ +/* + * Copyright 2004-2023 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/crm.h> +#include <stdio.h> +#include <stdbool.h> + +#include <stdlib.h> +#include <errno.h> + +#include <crm/common/cmdline_internal.h> +#include <crm/common/ipc_internal.h> +#include <crm/common/mainloop.h> +#include <crm/pengine/internal.h> +#include <pacemaker-internal.h> + +#include "pacemaker-schedulerd.h" + +#define SUMMARY "pacemaker-schedulerd - daemon for calculating a Pacemaker cluster's response to events" + +struct { + gchar **remainder; +} options; + +pcmk__output_t *logger_out = NULL; +pcmk__output_t *out = NULL; + +static GMainLoop *mainloop = NULL; +static qb_ipcs_service_t *ipcs = NULL; +static crm_exit_t exit_code = CRM_EX_OK; + +pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_NONE, + PCMK__SUPPORTED_FORMAT_TEXT, + PCMK__SUPPORTED_FORMAT_XML, + { NULL, NULL, NULL } +}; + +void pengine_shutdown(int nsig); + +static GOptionContext * +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { + GOptionContext *context = NULL; + + GOptionEntry extra_prog_entries[] = { + { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &options.remainder, + NULL, + NULL }, + + { NULL } + }; + + context = pcmk__build_arg_context(args, "text (default), xml", group, + "[metadata]"); + pcmk__add_main_args(context, extra_prog_entries); + return context; +} + +int +main(int argc, char **argv) +{ + GError *error = NULL; + int rc = pcmk_rc_ok; + + GOptionGroup *output_group = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + gchar **processed_args = pcmk__cmdline_preproc(argv, NULL); + GOptionContext *context = build_arg_context(args, &output_group); + + crm_log_preinit(NULL, argc, argv); + mainloop_add_signal(SIGTERM, pengine_shutdown); + + pcmk__register_formats(output_group, formats); + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + exit_code = CRM_EX_USAGE; + goto done; + } + + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); + if ((rc != pcmk_rc_ok) || (out == NULL)) { + exit_code = CRM_EX_FATAL; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", + args->output_ty, pcmk_rc_str(rc)); + goto done; + } + + pe__register_messages(out); + pcmk__register_lib_messages(out); + + if (options.remainder) { + if (g_strv_length(options.remainder) == 1 && + pcmk__str_eq("metadata", options.remainder[0], pcmk__str_casei)) { + pe_metadata(out); + goto done; + } else { + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Unsupported extra command line parameters"); + goto done; + } + } + + if (args->version) { + out->version(out, false); + goto done; + } + + pcmk__cli_init_logging("pacemaker-schedulerd", args->verbosity); + crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); + crm_notice("Starting Pacemaker scheduler"); + + if (pcmk__daemon_can_write(PE_STATE_DIR, NULL) == FALSE) { + crm_err("Terminating due to bad permissions on " PE_STATE_DIR); + exit_code = CRM_EX_FATAL; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "ERROR: Bad permissions on %s (see logs for details)", PE_STATE_DIR); + goto done; + } + + ipcs = pcmk__serve_schedulerd_ipc(&ipc_callbacks); + if (ipcs == NULL) { + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Failed to create pacemaker-schedulerd server: exiting and inhibiting respawn"); + exit_code = CRM_EX_FATAL; + goto done; + } + + if (pcmk__log_output_new(&logger_out) != pcmk_rc_ok) { + exit_code = CRM_EX_FATAL; + goto done; + } + pe__register_messages(logger_out); + pcmk__register_lib_messages(logger_out); + pcmk__output_set_log_level(logger_out, LOG_TRACE); + + /* Create the mainloop and run it... */ + mainloop = g_main_loop_new(NULL, FALSE); + crm_notice("Pacemaker scheduler successfully started and accepting connections"); + g_main_loop_run(mainloop); + +done: + g_strfreev(options.remainder); + g_strfreev(processed_args); + pcmk__free_arg_context(context); + + pcmk__output_and_clear_error(&error, out); + pengine_shutdown(0); +} + +void +pengine_shutdown(int nsig) +{ + if (ipcs != NULL) { + crm_trace("Closing IPC server"); + mainloop_del_ipc_server(ipcs); + ipcs = NULL; + } + + if (logger_out != NULL) { + logger_out->finish(logger_out, exit_code, true, NULL); + pcmk__output_free(logger_out); + logger_out = NULL; + } + + if (out != NULL) { + out->finish(out, exit_code, true, NULL); + pcmk__output_free(out); + out = NULL; + } + + pcmk__unregister_formats(); + crm_exit(exit_code); +} diff --git a/daemons/schedulerd/pacemaker-schedulerd.h b/daemons/schedulerd/pacemaker-schedulerd.h new file mode 100644 index 0000000..cbb07e1 --- /dev/null +++ b/daemons/schedulerd/pacemaker-schedulerd.h @@ -0,0 +1,20 @@ +/* + * Copyright 2004-2022 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. + */ + +#ifndef PCMK__PACEMAKER_SCHEDULERD__H +#define PCMK__PACEMAKER_SCHEDULERD__H + +#include <crm_internal.h> +#include <crm/pengine/pe_types.h> + +extern pcmk__output_t *logger_out; +extern pcmk__output_t *out; +extern struct qb_ipcs_service_handlers ipc_callbacks; + +#endif diff --git a/daemons/schedulerd/schedulerd_messages.c b/daemons/schedulerd/schedulerd_messages.c new file mode 100644 index 0000000..1c124d2 --- /dev/null +++ b/daemons/schedulerd/schedulerd_messages.c @@ -0,0 +1,335 @@ +/* + * Copyright 2004-2022 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/crm.h> +#include <crm/msg_xml.h> +#include <pacemaker-internal.h> + +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "pacemaker-schedulerd.h" + +static GHashTable *schedulerd_handlers = NULL; + +static pe_working_set_t * +init_working_set(void) +{ + pe_working_set_t *data_set = pe_new_working_set(); + + CRM_ASSERT(data_set != NULL); + + crm_config_error = FALSE; + crm_config_warning = FALSE; + + was_processing_error = FALSE; + was_processing_warning = FALSE; + + data_set->priv = logger_out; + return data_set; +} + +static xmlNode * +handle_pecalc_request(pcmk__request_t *request) +{ + static struct series_s { + const char *name; + const char *param; + + /* Maximum number of inputs of this kind to save to disk. + * If -1, save all; if 0, save none. + */ + int wrap; + } series[] = { + { "pe-error", "pe-error-series-max", -1 }, + { "pe-warn", "pe-warn-series-max", 5000 }, + { "pe-input", "pe-input-series-max", 4000 }, + }; + + xmlNode *msg = request->xml; + xmlNode *xml_data = get_message_xml(msg, F_CRM_DATA); + + static char *last_digest = NULL; + static char *filename = NULL; + + unsigned int seq; + int series_id = 0; + int series_wrap = 0; + char *digest = NULL; + const char *value = NULL; + time_t execution_date = time(NULL); + xmlNode *converted = NULL; + xmlNode *reply = NULL; + bool is_repoke = false; + bool process = true; + pe_working_set_t *data_set = init_working_set(); + + pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags, + "ack", NULL, CRM_EX_INDETERMINATE); + + digest = calculate_xml_versioned_digest(xml_data, FALSE, FALSE, + CRM_FEATURE_SET); + converted = copy_xml(xml_data); + if (!cli_config_update(&converted, NULL, TRUE)) { + data_set->graph = create_xml_node(NULL, XML_TAG_GRAPH); + crm_xml_add_int(data_set->graph, "transition_id", 0); + crm_xml_add_int(data_set->graph, "cluster-delay", 0); + process = false; + free(digest); + + } else if (pcmk__str_eq(digest, last_digest, pcmk__str_casei)) { + is_repoke = true; + free(digest); + + } else { + free(last_digest); + last_digest = digest; + } + + if (process) { + pcmk__schedule_actions(converted, + pe_flag_no_counts + |pe_flag_no_compat + |pe_flag_show_utilization, data_set); + } + + // Get appropriate index into series[] array + if (was_processing_error) { + series_id = 0; + } else if (was_processing_warning) { + series_id = 1; + } else { + series_id = 2; + } + + value = pe_pref(data_set->config_hash, series[series_id].param); + if ((value == NULL) + || (pcmk__scan_min_int(value, &series_wrap, -1) != pcmk_rc_ok)) { + series_wrap = series[series_id].wrap; + } + + if (pcmk__read_series_sequence(PE_STATE_DIR, series[series_id].name, + &seq) != pcmk_rc_ok) { + // @TODO maybe handle errors better ... + seq = 0; + } + crm_trace("Series %s: wrap=%d, seq=%u, pref=%s", + series[series_id].name, series_wrap, seq, value); + + data_set->input = NULL; + reply = create_reply(msg, data_set->graph); + + if (reply == NULL) { + pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR, + "Failed building ping reply for client %s", + pcmk__client_name(request->ipc_client)); + goto done; + } + + if (series_wrap == 0) { // Don't save any inputs of this kind + free(filename); + filename = NULL; + + } else if (!is_repoke) { // Input changed, save to disk + free(filename); + filename = pcmk__series_filename(PE_STATE_DIR, + series[series_id].name, seq, true); + } + + crm_xml_add(reply, F_CRM_TGRAPH_INPUT, filename); + crm_xml_add_int(reply, PCMK__XA_GRAPH_ERRORS, was_processing_error); + crm_xml_add_int(reply, PCMK__XA_GRAPH_WARNINGS, was_processing_warning); + crm_xml_add_int(reply, PCMK__XA_CONFIG_ERRORS, crm_config_error); + crm_xml_add_int(reply, PCMK__XA_CONFIG_WARNINGS, crm_config_warning); + + pcmk__log_transition_summary(filename); + + if (series_wrap == 0) { + crm_debug("Not saving input to disk (disabled by configuration)"); + + } else if (is_repoke) { + crm_info("Input has not changed since last time, not saving to disk"); + + } else { + unlink(filename); + crm_xml_add_ll(xml_data, "execution-date", (long long) execution_date); + write_xml_file(xml_data, filename, TRUE); + pcmk__write_series_sequence(PE_STATE_DIR, series[series_id].name, + ++seq, series_wrap); + } + + pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); + +done: + free_xml(converted); + pe_free_working_set(data_set); + + return reply; +} + +static xmlNode * +handle_unknown_request(pcmk__request_t *request) +{ + pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags, + "ack", NULL, CRM_EX_INVALID_PARAM); + + pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID, + "Unknown IPC request type '%s' (bug?)", + pcmk__client_name(request->ipc_client)); + return NULL; +} + +static xmlNode * +handle_hello_request(pcmk__request_t *request) +{ + pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags, + "ack", NULL, CRM_EX_INDETERMINATE); + + crm_trace("Received IPC hello from %s", pcmk__client_name(request->ipc_client)); + + pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); + return NULL; +} + +static void +schedulerd_register_handlers(void) +{ + pcmk__server_command_t handlers[] = { + { CRM_OP_HELLO, handle_hello_request }, + { CRM_OP_PECALC, handle_pecalc_request }, + { NULL, handle_unknown_request }, + }; + + schedulerd_handlers = pcmk__register_handlers(handlers); +} + +static int32_t +pe_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + crm_trace("Connection %p", c); + if (pcmk__new_client(c, uid, gid) == NULL) { + return -EIO; + } + return 0; +} + +static int32_t +pe_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size) +{ + uint32_t id = 0; + uint32_t flags = 0; + xmlNode *msg = NULL; + pcmk__client_t *c = pcmk__find_client(qbc); + const char *sys_to = NULL; + + CRM_CHECK(c != NULL, return 0); + + if (schedulerd_handlers == NULL) { + schedulerd_register_handlers(); + } + + msg = pcmk__client_data2xml(c, data, &id, &flags); + if (msg == NULL) { + pcmk__ipc_send_ack(c, id, flags, "ack", NULL, CRM_EX_PROTOCOL); + return 0; + } + + sys_to = crm_element_value(msg, F_CRM_SYS_TO); + + if (pcmk__str_eq(crm_element_value(msg, F_CRM_MSG_TYPE), + XML_ATTR_RESPONSE, pcmk__str_none)) { + pcmk__ipc_send_ack(c, id, flags, "ack", NULL, CRM_EX_INDETERMINATE); + crm_info("Ignoring IPC reply from %s", pcmk__client_name(c)); + + } else if (!pcmk__str_eq(sys_to, CRM_SYSTEM_PENGINE, pcmk__str_none)) { + pcmk__ipc_send_ack(c, id, flags, "ack", NULL, CRM_EX_INDETERMINATE); + crm_info("Ignoring invalid IPC message: to '%s' not " + CRM_SYSTEM_PENGINE, pcmk__s(sys_to, "")); + + } else { + char *log_msg = NULL; + const char *reason = NULL; + xmlNode *reply = NULL; + + pcmk__request_t request = { + .ipc_client = c, + .ipc_id = id, + .ipc_flags = flags, + .peer = NULL, + .xml = msg, + .call_options = 0, + .result = PCMK__UNKNOWN_RESULT, + }; + + request.op = crm_element_value_copy(request.xml, F_CRM_TASK); + CRM_CHECK(request.op != NULL, return 0); + + reply = pcmk__process_request(&request, schedulerd_handlers); + + if (reply != NULL) { + pcmk__ipc_send_xml(c, id, reply, crm_ipc_server_event); + free_xml(reply); + } + + reason = request.result.exit_reason; + + log_msg = crm_strdup_printf("Processed %s request from %s %s: %s%s%s%s", + request.op, pcmk__request_origin_type(&request), + pcmk__request_origin(&request), + pcmk_exec_status_str(request.result.execution_status), + (reason == NULL)? "" : " (", + (reason == NULL)? "" : reason, + (reason == NULL)? "" : ")"); + + if (!pcmk__result_ok(&request.result)) { + crm_warn("%s", log_msg); + } else { + crm_debug("%s", log_msg); + } + + free(log_msg); + pcmk__reset_request(&request); + } + + free_xml(msg); + return 0; +} + +/* Error code means? */ +static int32_t +pe_ipc_closed(qb_ipcs_connection_t * c) +{ + pcmk__client_t *client = pcmk__find_client(c); + + if (client == NULL) { + return 0; + } + crm_trace("Connection %p", c); + pcmk__free_client(client); + return 0; +} + +static void +pe_ipc_destroy(qb_ipcs_connection_t * c) +{ + crm_trace("Connection %p", c); + pe_ipc_closed(c); +} + +struct qb_ipcs_service_handlers ipc_callbacks = { + .connection_accept = pe_ipc_accept, + .connection_created = NULL, + .msg_process = pe_ipc_dispatch, + .connection_closed = pe_ipc_closed, + .connection_destroyed = pe_ipc_destroy +}; |