diff --git a/maint/mocked/Makefile b/maint/mocked/Makefile index 240c521e73..1b729c9d52 100644 --- a/maint/mocked/Makefile +++ b/maint/mocked/Makefile @@ -1,41 +1,44 @@ # # Copyright 2019 the Pacemaker project contributors # # The version control history for this file may have further details. # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. # #BASED_LDFLAGS = $$(pkgconf -libs glib-2.0) \ # $$(pkgconf -libs libxml-2.0) \ # $$(pkgconf -libs libqb) \ # $$(pkgconf -libs pacemaker) BASED_LDFLAGS = $$(pkgconf -libs glib-2.0) \ $$(pkgconf -libs libxml-2.0) \ $$(pkgconf -libs libqb) \ -Wl,-rpath=$(CURDIR)/../../lib/common/.libs \ -L../../lib/common/.libs -lcrmcommon \ -L../../lib/pacemaker/.libs -lpacemaker BASED_CPPFLAGS = $$(pkgconf -cflags glib-2.0) \ $$(pkgconf -cflags libxml-2.0) \ $$(pkgconf -cflags libqb) \ -I ../.. -I ../../include -g PROGRAMS = based BASED_OBJECTS = based.o +# include or not the modules as you wish +BASED_OBJECTS += based-notifyfenced.o + all: ${PROGRAMS} based: $(BASED_OBJECTS) $(CC) $(BASED_LDFLAGS) $^ -o $@ $(BASED_OBJECTS): %.o: %.c $(CC) $(BASED_CPPFLAGS) $(BASED_LDFLAGS) -c $< -o $@ clean: rm ${PROGRAMS} $(BASED_OBJECTS) diff --git a/maint/mocked/based-notifyfenced.c b/maint/mocked/based-notifyfenced.c new file mode 100644 index 0000000000..a07bf2a3e4 --- /dev/null +++ b/maint/mocked/based-notifyfenced.c @@ -0,0 +1,243 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * Licensed under the GNU General Public License version 2 or later (GPLv2+). + */ + +/* + * Intended demo use case: + * + * - as root, start corosync + * - start "./based -N"; hint: + * su -s /bin/sh -c './based -N' hacluster + * - start pacemaker-fenced; hint: + * su -c 'env PCMK_logpriority=crit ../../daemons/fenced/pacemaker-fenced' + * - wait a bit (5 < seconds < 20) + * - as haclient group (or root), run "stonith admin --list-registered" + * - observe whether such invocation is blocked or not + */ + + +#include /* printf, perror */ + +#include "crm/cib.h" /* cib_zero_copy */ +#include "crm/cib/internal.h" /* CIB_OP_CREATE */ +#include "crm/msg_xml.h" /* F_SUBTYPE */ +#include "daemons/based/pacemaker-based.h" /* cib_notify_diff */ + +#include "based.h" + + +#define OPTCHAR 'N' +static size_t module_handle; + + +struct cib_notification_s { + xmlNode *msg; + struct iovec *iov; + int32_t iov_size; +}; + +/* see based/based_notify.c:cib_notify_send_one */ +static bool +mock_based_cib_notify_send_one(crm_client_t *client, xmlNode *xml) +{ + const char *type = NULL; + bool do_send = false; + + struct iovec *iov; + ssize_t rc = crm_ipc_prepare(0, xml, &iov, 0); + struct cib_notification_s update = { + .msg = xml, + .iov = iov, + .iov_size = rc, + }; + + CRM_CHECK(client != NULL, return true); + if (client->ipcs == NULL && client->remote == NULL) { + crm_warn("Skipping client with NULL channel"); + return FALSE; + } + + type = crm_element_value(update.msg, F_SUBTYPE); + CRM_LOG_ASSERT(type != NULL); + if (is_set(client->options, cib_notify_diff) + && safe_str_eq(type, T_CIB_DIFF_NOTIFY)) { + + if (crm_ipcs_sendv(client, update.iov, crm_ipc_server_event) < 0) + crm_warn("Notification of client %s/%s failed", client->name, client->id); + + } + pcmk_free_ipc_event(iov); + + return FALSE; +} + +/* see based/based_notify.c:do_cib_notify + cib_notify_send */ +void +do_cib_notify(crm_client_t *cib_client, int options, const char *op, + xmlNode *update, int result, xmlNode *result_data, + const char *msg_type) +{ + xmlNode *update_msg = NULL; + const char *id = NULL; + + update_msg = create_xml_node(NULL, "notify"); + + + crm_xml_add(update_msg, F_TYPE, T_CIB_NOTIFY); + crm_xml_add(update_msg, F_SUBTYPE, msg_type); + crm_xml_add(update_msg, F_CIB_OPERATION, op); + crm_xml_add_int(update_msg, F_CIB_RC, result); + + if (result_data != NULL) { + id = crm_element_value(result_data, XML_ATTR_ID); + if (id != NULL) + crm_xml_add(update_msg, F_CIB_OBJID, id); + } + + if (update != NULL) { + crm_trace("Setting type to update->name: %s", crm_element_name(update)); + crm_xml_add(update_msg, F_CIB_OBJTYPE, crm_element_name(update)); + + } else if (result_data != NULL) { + crm_trace("Setting type to new_obj->name: %s", crm_element_name(result_data)); + crm_xml_add(update_msg, F_CIB_OBJTYPE, crm_element_name(result_data)); + + } else { + crm_trace("Not Setting type"); + } + +#if 0 + attach_cib_generation(update_msg, "cib_generation", the_cib); +#endif + + if (update != NULL) { + add_message_xml(update_msg, F_CIB_UPDATE, update); + } + if (result_data != NULL) { + add_message_xml(update_msg, F_CIB_UPDATE_RESULT, result_data); + } + + mock_based_cib_notify_send_one(cib_client, update_msg); + free_xml(update_msg); +} + +static gboolean +mock_based_notifyfencedmer_callback_worker(gpointer data) +{ + crm_client_t *cib_client = (crm_client_t *) data; + + xmlNode *result_data; + xmlNode *input, *update; + int options; + char update_str[4096]; + + options |= cib_zero_copy; + + + input = create_xml_node(NULL, "cib"); + + /* spam it */ +#if 0 + for (size_t i = 0; i < SIZE_MAX - 1; i++) { +#else + for (size_t i = 0; i < 10000; i++) { +#endif + /* NOTE: we need to trigger fenced attention, add new fence device */ + snprintf(update_str, sizeof(update_str), +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n", i, i+1); + update = xmlReadMemory(update_str, sizeof(update_str), + "file:///tmp/update", NULL, 0)->children; + do_cib_notify(cib_client, options, CIB_OP_CREATE, input, pcmk_ok, + update, T_CIB_DIFF_NOTIFY); + free_xml(update); + }; + + free_xml(input); +} + +static void +mock_based_notifyfenced_cib_notify_hook(crm_client_t *cib_client) +{ + + /* MOCK: client asked for upcoming diff's, let's + spam it a bit after a while... */ + crm_info("Going to spam %s (%s) in 5 seconds...", + cib_client->name, cib_client->id); + mainloop_timer_start(mainloop_timer_add("spammer", 5000, FALSE, + mock_based_notifyfencedmer_callback_worker, + cib_client)); +} + +/* * */ + +static int +mock_based_notifyfenced_argparse_hook(struct mock_based_context_s *ctxt, + bool usage, int argc_to_go, + const char *argv_to_go[]) +{ + const char *opt = *argv_to_go; +restart: + switch(*opt) { + case '-': + if (opt == *argv_to_go) { + opt++; + goto restart; + } + break; + case OPTCHAR: + if (usage) { + printf("spam the \"cib diff\" notification client" + " (targeting pacemaker-fenced in particular)\n"); + + } else { +#if 0 + ctxt->modules[module_handle]->priv = + malloc(sizeof(mock_based_notifyfenced_priv_t)); + if (ctxt->modules[module_handle]->priv == NULL) { + perror("malloc"); + return -1; + } +#endif + } + return 1; + } + return 0; +} + +#if 0 +static void +mock_based_notifyfenced_destroy_hook(module_t *mod) { + free(mod->priv); +} +#endif + +__attribute__((__constructor__)) +void +mock_based_notifyfenced_init(void) { + module_handle = mock_based_register_module((module_t){ + .shortopt = OPTCHAR, + .hooks = { + .argparse = mock_based_notifyfenced_argparse_hook, + //.destroy = mock_based_notifyfenced_destroy_hook, + /* specialized hooks */ + .cib_notify = mock_based_notifyfenced_cib_notify_hook, + } + }); +} diff --git a/maint/mocked/based.c b/maint/mocked/based.c index ef6efb765e..70ac64042d 100644 --- a/maint/mocked/based.c +++ b/maint/mocked/based.c @@ -1,327 +1,335 @@ /* * Copyright 2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * Licensed under the GNU General Public License version 2 or later (GPLv2+). */ /* * Clean room attempt (admittedly with lot of code borrowed or inspired from * the full-blown daemon), minimalistic implementation of based daemon, with * only important aspects being implemented at the moment. * * Hopefully easy to adapt for variety of purposes. * * NOTE: currently, only cib_rw API end-point is opened, future refinements * as new modules are added should conditionalize per what the module * indicates in the context (which is intentionally very loose data glue * between the skeleton and modules themselves (like CGI variables so * to say, but more structurally predestined so as to avoid complexities * of hash table lookups etc.) */ #include #if 0 #include "crm/common/ipcs.h" /* crm_client_t */ #include "crm/common/xml.h" /* crm_xml_add */ #endif #include "crm/msg_xml.h" /* F_SUBTYPE */ #include "daemons/based/pacemaker-based.h" /* cib_notify_diff */ #include /* qb_ipcs_connection_t */ #include "based.h" /* direct global access violated in one case only - mock_based_ipc_accept adds a reference to it to crm_cient_t->userdata */ mock_based_context_t mock_based_context; /* see based/based_callbacks.c:cib_ipc_accept */ static int32_t mock_based_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { int32_t ret = 0; crm_client_t *cib_client; crm_trace("Connection %p", c); if ((cib_client = crm_client_new(c, uid, gid)) == NULL) { ret = -EIO; } cib_client->userdata = &mock_based_context; return ret; } /* see based/based_callbacks.c:cib_ipc_created */ static void mock_based_ipc_created(qb_ipcs_connection_t *c) { crm_trace("Connection %p", c); } /* see based/based_callbacks.c:cib_ipc_closed */ static int32_t mock_based_ipc_closed(qb_ipcs_connection_t *c) { crm_client_t *client = crm_client_get(c); if (client != NULL) { crm_trace("Connection %p", c); crm_client_destroy(client); } return 0; } /* see based/based_callbacks.c:cib_ipc_destroy */ static void mock_based_ipc_destroy(qb_ipcs_connection_t *c) { crm_trace("Connection %p", c); mock_based_ipc_closed(c); } /* see based/based_callbacks.c:cib_process_command (and more) */ static void mock_based_handle_query(crm_client_t *cib_client, uint32_t flags, const xmlNode *op_request) { xmlNode *reply, *cib; const char cib_str[] = #if 0 ""; #else ""\ " "\ " "\ " "\ " "\ " "\ " "\ " "\ ""; #endif cib = xmlReadMemory(cib_str, sizeof(cib_str), "file:///tmp/foo", NULL, 0)->children; reply = create_xml_node(NULL, "cib-reply"); crm_xml_add(reply, F_TYPE, T_CIB); crm_xml_add(reply, F_CIB_OPERATION, crm_element_value(op_request, F_CIB_OPERATION)); crm_xml_add(reply, F_CIB_CALLID, crm_element_value(op_request, F_CIB_CALLID)); crm_xml_add(reply, F_CIB_CLIENTID, crm_element_value(op_request, F_CIB_CLIENTID)); crm_xml_add_int(reply, F_CIB_CALLOPTS, flags); crm_xml_add_int(reply, F_CIB_RC, pcmk_ok); if (cib != NULL) { crm_trace("Attaching reply output"); add_message_xml(reply, F_CIB_CALLDATA, cib); } crm_ipcs_send(cib_client, cib_client->request_id, reply, (flags & cib_sync_call) ? crm_ipc_flags_none : crm_ipc_server_event); free_xml(reply); free_xml(cib); } /* see based/based_callbacks.c:cib_common_callback_worker */ static void mock_based_common_callback_worker(uint32_t id, uint32_t flags, xmlNode *op_request, crm_client_t *cib_client) { const char *op = crm_element_value(op_request, F_CIB_OPERATION); + mock_based_context_t *ctxt; if (!strcmp(op, CRM_OP_REGISTER)) { if (flags & crm_ipc_client_response) { xmlNode *ack = create_xml_node(NULL, __FUNCTION__); crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER); crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id); crm_ipcs_send(cib_client, id, ack, flags); cib_client->request_id = 0; free_xml(ack); } } else if (!strcmp(op, T_CIB_NOTIFY)) { int on_off = 0; const char *type = crm_element_value(op_request, F_CIB_NOTIFY_TYPE); crm_element_value_int(op_request, F_CIB_NOTIFY_ACTIVATE, &on_off); crm_debug("Setting %s callbacks for %s (%s): %s", type, cib_client->name, cib_client->id, on_off ? "on" : "off"); if (!strcmp(type, T_CIB_DIFF_NOTIFY) && on_off) { cib_client->options |= cib_notify_diff; } + ctxt = (mock_based_context_t *) cib_client->userdata; + for (size_t c = ctxt->modules_cnt; c > 0; c--) { + if (ctxt->modules[c - 1]->hooks.cib_notify != NULL) { + ctxt->modules[c - 1]->hooks.cib_notify(cib_client); + } + } + if (flags & crm_ipc_client_response) { crm_ipcs_send_ack(cib_client, id, flags, "ack", __FUNCTION__, __LINE__); } } else if (!strcmp(op, CIB_OP_QUERY)) { mock_based_handle_query(cib_client, flags, op_request); } else { crm_notice("Discarded request %s", op); } } /* see based/based_callbacks.c:cib_ipc_dispatch_rw */ static int32_t mock_based_dispatch_command(qb_ipcs_connection_t *c, void *data, size_t size) { uint32_t id = 0, flags = 0; int call_options = 0; crm_client_t *cib_client = crm_client_get(c); xmlNode *op_request = crm_ipcs_recv(cib_client, data, size, &id, &flags); crm_notice("Got connection %p", c); assert(op_request != NULL); if (cib_client == NULL || op_request == NULL) { if (op_request == NULL) { crm_trace("Invalid message from %p", c); crm_ipcs_send_ack(cib_client, id, flags, "nack", __FUNCTION__, __LINE__); } return 0; } crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); if (call_options & cib_sync_call) { assert(flags & crm_ipc_client_response); cib_client->request_id = id; /* reply only to last in-flight request */ } assert(cib_client->name == NULL); crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); mock_based_common_callback_worker(id, flags, op_request, cib_client); free_xml(op_request); return 0; } /* * */ size_t mock_based_register_module(module_t mod) { module_t *module; size_t ret = mock_based_context.modules_cnt++; mock_based_context.modules = realloc(mock_based_context.modules, sizeof(*mock_based_context.modules) * mock_based_context.modules_cnt); if (mock_based_context.modules == NULL || (module = malloc(sizeof(module_t))) == NULL) { abort(); } memcpy(module, &mod, sizeof(mod)); mock_based_context.modules[mock_based_context.modules_cnt - 1] = module; return ret; } static int mock_based_options(mock_based_context_t *ctxt, bool usage, int argc, const char *argv[]) { const char **args2argv; char *s; int ret = 0; if (argc <= 1) { const char *help_argv[] = {argv[0], "-h"}; return mock_based_options(ctxt, false, 2, (const char **) &help_argv); } for (size_t i = 1; i < argc; i++) { if (argv[i][0] == '-' && argv[i][1] != '-' && argv[i][1] != '\0') { if (usage) { printf("\t-%c\t", argv[i][1]); } switch(argv[i][1]) { case 'h': if (usage) { printf("show this help message\n"); ret = 1; } else { if ((args2argv = malloc((ctxt->modules_cnt + 2) * sizeof(*args2argv))) == NULL || (s = malloc((ctxt->modules_cnt * 2 + 2) * sizeof(*s))) == NULL) { return -1; } s[0] = 'h'; args2argv[ctxt->modules_cnt + 1] = (char[]){'-', 'h', '\0'}; for (size_t c = ctxt->modules_cnt; c > 0; c--) { args2argv[c] = (char[]){'-', ctxt->modules[c - 1]->shortopt, '\0'}; s[(ctxt->modules_cnt - i) + 1] = '|'; s[(ctxt->modules_cnt - i) + 2] = ctxt->modules[c - 1]->shortopt; } s[ctxt->modules_cnt * 2 + 1] = '\0'; printf("Usage: %s [-{%s}]\n", argv[0], s); (void) mock_based_options(ctxt, true, 2 + ctxt->modules_cnt, args2argv); free(args2argv); free(s); } return ret; default: for (size_t c = ctxt->modules_cnt; c > 0; c--) { if (ctxt->modules[c - 1]->shortopt == argv[i][1]) { ret = ctxt->modules[c - 1]->hooks.argparse(ctxt, usage, argc - i, &argv[i]); if (ret < 0) { break; } else if (ret > 1) { i += (ret - 1); } } } if (ret == 0) { printf("uknown option \"%s\"\n", argv[i]); } break; } } } return ret; } int main(int argc, char *argv[]) { mock_based_context_t *ctxt = &mock_based_context; if (mock_based_options(ctxt, false, argc, (const char **) argv) > 0) { struct qb_ipcs_service_handlers cib_ipc_callbacks = { .connection_accept = mock_based_ipc_accept, .connection_created = mock_based_ipc_created, .msg_process = mock_based_dispatch_command, .connection_closed = mock_based_ipc_closed, .connection_destroyed = mock_based_ipc_destroy, }; crm_log_preinit(NULL, argc, argv); crm_log_init(NULL, LOG_DEBUG, false, true, argc, argv, false); qb_ipcs_service_t *ipcs_command = mainloop_add_ipc_server(CIB_CHANNEL_RW, QB_IPC_NATIVE, &cib_ipc_callbacks); g_main_loop_run(g_main_loop_new(NULL, false)); qb_ipcs_destroy(ipcs_command); } for (size_t c = ctxt->modules_cnt; c > 0; c--) { if (ctxt->modules[c - 1]->hooks.destroy != NULL) { ctxt->modules[c - 1]->hooks.destroy(ctxt->modules[c - 1]); } free(mock_based_context.modules[c - 1]); } free(mock_based_context.modules); } diff --git a/maint/mocked/based.h b/maint/mocked/based.h index 04d8eedfb0..dcebf0e038 100644 --- a/maint/mocked/based.h +++ b/maint/mocked/based.h @@ -1,47 +1,49 @@ /* * Copyright 2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * Licensed under the GNU General Public License version 2 or later (GPLv2+). */ #pragma once #include /* size_t */ #include /* bool */ #include /* crm_client_t */ struct module_s; typedef struct mock_based_context_s { size_t modules_cnt; struct module_s** modules; } mock_based_context_t; typedef int (*mock_based_argparse_hook)(mock_based_context_t *, bool, int, const char *[]); typedef void (*mock_based_destroy_hook)(struct module_s *); /* specialized callbacks... */ +typedef void (*mock_based_cib_notify_hook)(crm_client_t *); typedef struct mock_based_hooks_s { /* generic ones */ mock_based_argparse_hook argparse; mock_based_destroy_hook destroy; /* specialized callbacks... */ + mock_based_cib_notify_hook cib_notify; } mock_based_hooks_t; typedef struct module_s { char shortopt; mock_based_hooks_t hooks; void *priv; } module_t; size_t mock_based_register_module(module_t mod);