diff options
Diffstat (limited to 'daemons/controld/controld_metadata.c')
-rw-r--r-- | daemons/controld/controld_metadata.c | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/daemons/controld/controld_metadata.c b/daemons/controld/controld_metadata.c new file mode 100644 index 0000000..240a978 --- /dev/null +++ b/daemons/controld/controld_metadata.c @@ -0,0 +1,320 @@ +/* + * Copyright 2017-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 <stdio.h> +#include <glib.h> +#include <regex.h> + +#include <crm/crm.h> +#include <crm/lrmd.h> + +#include <pacemaker-controld.h> + +static void +ra_param_free(void *param) +{ + if (param) { + struct ra_param_s *p = (struct ra_param_s *) param; + + if (p->rap_name) { + free(p->rap_name); + } + free(param); + } +} + +static void +metadata_free(void *metadata) +{ + if (metadata) { + struct ra_metadata_s *md = (struct ra_metadata_s *) metadata; + + g_list_free_full(md->ra_params, ra_param_free); + free(metadata); + } +} + +GHashTable * +metadata_cache_new(void) +{ + return pcmk__strkey_table(free, metadata_free); +} + +void +metadata_cache_free(GHashTable *mdc) +{ + if (mdc) { + crm_trace("Destroying metadata cache with %d members", g_hash_table_size(mdc)); + g_hash_table_destroy(mdc); + } +} + +void +metadata_cache_reset(GHashTable *mdc) +{ + if (mdc) { + crm_trace("Resetting metadata cache with %d members", + g_hash_table_size(mdc)); + g_hash_table_remove_all(mdc); + } +} + +static struct ra_param_s * +ra_param_from_xml(xmlNode *param_xml) +{ + const char *param_name = crm_element_value(param_xml, "name"); + struct ra_param_s *p; + + p = calloc(1, sizeof(struct ra_param_s)); + if (p == NULL) { + return NULL; + } + + p->rap_name = strdup(param_name); + if (p->rap_name == NULL) { + free(p); + return NULL; + } + + if (pcmk__xe_attr_is_true(param_xml, "reloadable")) { + controld_set_ra_param_flags(p, ra_param_reloadable); + } + + if (pcmk__xe_attr_is_true(param_xml, "unique")) { + controld_set_ra_param_flags(p, ra_param_unique); + } + + if (pcmk__xe_attr_is_true(param_xml, "private")) { + controld_set_ra_param_flags(p, ra_param_private); + } + return p; +} + +static void +log_ra_ocf_version(const char *ra_key, const char *ra_ocf_version) +{ + if (pcmk__str_empty(ra_ocf_version)) { + crm_warn("%s does not advertise OCF version supported", ra_key); + + } else if (compare_version(ra_ocf_version, "2") >= 0) { + crm_warn("%s supports OCF version %s (this Pacemaker version supports " + PCMK_OCF_VERSION " and might not work properly with agent)", + ra_key, ra_ocf_version); + + } else if (compare_version(ra_ocf_version, PCMK_OCF_VERSION) > 0) { + crm_info("%s supports OCF version %s (this Pacemaker version supports " + PCMK_OCF_VERSION " and might not use all agent features)", + ra_key, ra_ocf_version); + + } else { + crm_debug("%s supports OCF version %s", ra_key, ra_ocf_version); + } +} + +struct ra_metadata_s * +controld_cache_metadata(GHashTable *mdc, const lrmd_rsc_info_t *rsc, + const char *metadata_str) +{ + char *key = NULL; + const char *reason = NULL; + xmlNode *metadata = NULL; + xmlNode *match = NULL; + struct ra_metadata_s *md = NULL; + bool any_private_params = false; + bool ocf1_1 = false; + + CRM_CHECK(mdc && rsc && metadata_str, return NULL); + + key = crm_generate_ra_key(rsc->standard, rsc->provider, rsc->type); + if (!key) { + reason = "Invalid resource agent standard or type"; + goto err; + } + + metadata = string2xml(metadata_str); + if (!metadata) { + reason = "Metadata is not valid XML"; + goto err; + } + + md = calloc(1, sizeof(struct ra_metadata_s)); + if (md == NULL) { + reason = "Could not allocate memory"; + goto err; + } + + if (strcmp(rsc->standard, PCMK_RESOURCE_CLASS_OCF) == 0) { + xmlChar *content = NULL; + xmlNode *version_element = first_named_child(metadata, "version"); + + if (version_element != NULL) { + content = xmlNodeGetContent(version_element); + } + log_ra_ocf_version(key, (const char *) content); + if (content != NULL) { + ocf1_1 = (compare_version((const char *) content, "1.1") >= 0); + xmlFree(content); + } + } + + // Check supported actions + match = first_named_child(metadata, "actions"); + for (match = first_named_child(match, "action"); match != NULL; + match = crm_next_same_xml(match)) { + + const char *action_name = crm_element_value(match, "name"); + + if (pcmk__str_eq(action_name, CRMD_ACTION_RELOAD_AGENT, + pcmk__str_none)) { + if (ocf1_1) { + controld_set_ra_flags(md, key, ra_supports_reload_agent); + } else { + crm_notice("reload-agent action will not be used with %s " + "because it does not support OCF 1.1 or later", key); + } + + } else if (!ocf1_1 && pcmk__str_eq(action_name, CRMD_ACTION_RELOAD, + pcmk__str_casei)) { + controld_set_ra_flags(md, key, ra_supports_legacy_reload); + } + } + + // Build a parameter list + match = first_named_child(metadata, "parameters"); + for (match = first_named_child(match, "parameter"); match != NULL; + match = crm_next_same_xml(match)) { + + const char *param_name = crm_element_value(match, "name"); + + if (param_name == NULL) { + crm_warn("Metadata for %s:%s:%s has parameter without a name", + rsc->standard, rsc->provider, rsc->type); + } else { + struct ra_param_s *p = ra_param_from_xml(match); + + if (p == NULL) { + reason = "Could not allocate memory"; + goto err; + } + if (pcmk_is_set(p->rap_flags, ra_param_private)) { + any_private_params = true; + } + md->ra_params = g_list_prepend(md->ra_params, p); + } + } + + /* Newer resource agents support the "private" parameter attribute to + * indicate sensitive parameters. For backward compatibility with older + * agents, implicitly treat a few common names as private when the agent + * doesn't specify any explicitly. + */ + if (!any_private_params) { + for (GList *iter = md->ra_params; iter != NULL; iter = iter->next) { + struct ra_param_s *p = iter->data; + + if (pcmk__str_any_of(p->rap_name, "password", "passwd", "user", + NULL)) { + controld_set_ra_param_flags(p, ra_param_private); + } + } + } + + g_hash_table_replace(mdc, key, md); + free_xml(metadata); + return md; + +err: + crm_warn("Unable to update metadata for %s (%s%s%s:%s): %s", + rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), + pcmk__s(rsc->provider, ""), rsc->type, reason); + free(key); + free_xml(metadata); + metadata_free(md); + return NULL; +} + +/*! + * \internal + * \brief Get meta-data for a resource + * + * \param[in,out] lrm_state Use meta-data cache from this executor connection + * \param[in] rsc Resource to get meta-data for + * \param[in] source Allowed meta-data sources (bitmask of + * enum controld_metadata_source_e values) + * + * \return Meta-data cache entry for given resource, or NULL if not available + */ +struct ra_metadata_s * +controld_get_rsc_metadata(lrm_state_t *lrm_state, const lrmd_rsc_info_t *rsc, + uint32_t source) +{ + struct ra_metadata_s *metadata = NULL; + char *metadata_str = NULL; + char *key = NULL; + int rc = pcmk_ok; + + CRM_CHECK((lrm_state != NULL) && (rsc != NULL), return NULL); + + if (pcmk_is_set(source, controld_metadata_from_cache)) { + key = crm_generate_ra_key(rsc->standard, rsc->provider, rsc->type); + if (key != NULL) { + metadata = g_hash_table_lookup(lrm_state->metadata_cache, key); + free(key); + } + if (metadata != NULL) { + crm_debug("Retrieved metadata for %s (%s%s%s:%s) from cache", + rsc->id, rsc->standard, + ((rsc->provider == NULL)? "" : ":"), + ((rsc->provider == NULL)? "" : rsc->provider), + rsc->type); + return metadata; + } + } + + if (!pcmk_is_set(source, controld_metadata_from_agent)) { + return NULL; + } + + /* For most actions, metadata was cached asynchronously before action + * execution (via metadata_complete()). + * + * However if that failed, and for other actions, retrieve the metadata now + * via a local, synchronous, direct execution of the agent. + * + * This has multiple issues, which is why this is just a fallback: the + * executor should execute agents, not the controller; metadata for + * Pacemaker Remote nodes should be collected on those nodes, not locally; + * the metadata call shouldn't eat into the timeout of the real action being + * performed; and the synchronous call blocks the controller (which also + * means that if the metadata action tries to contact the controller, + * everything will hang until the timeout). + */ + crm_debug("Retrieving metadata for %s (%s%s%s:%s) synchronously", + rsc->id, rsc->standard, + ((rsc->provider == NULL)? "" : ":"), + ((rsc->provider == NULL)? "" : rsc->provider), + rsc->type); + rc = lrm_state_get_metadata(lrm_state, rsc->standard, rsc->provider, + rsc->type, &metadata_str, 0); + if (rc != pcmk_ok) { + crm_warn("Failed to get metadata for %s (%s%s%s:%s): %s", + rsc->id, rsc->standard, + ((rsc->provider == NULL)? "" : ":"), + ((rsc->provider == NULL)? "" : rsc->provider), + rsc->type, pcmk_strerror(rc)); + return NULL; + } + + metadata = controld_cache_metadata(lrm_state->metadata_cache, rsc, + metadata_str); + free(metadata_str); + return metadata; +} |