| /* |
| * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <crm_internal.h> |
| #include <dlfcn.h> |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <time.h> |
| #include <sys/param.h> |
| #include <sys/types.h> |
| #include <sys/utsname.h> |
| |
| #include <crm/crm.h> |
| #include <crm/msg_xml.h> |
| |
| #include <crm/common/ipc.h> |
| #include <crm/cluster/internal.h> |
| |
| CRM_TRACE_INIT_DATA(cluster); |
| |
| #if SUPPORT_HEARTBEAT |
| void *hb_library = NULL; |
| #endif |
| |
| static char * |
| get_heartbeat_uuid(const char *uname) |
| { |
| char *uuid_calc = NULL; |
| |
| #if SUPPORT_HEARTBEAT |
| cl_uuid_t uuid_raw; |
| const char *unknown = "00000000-0000-0000-0000-000000000000"; |
| |
| if (heartbeat_cluster == NULL) { |
| crm_warn("No connection to heartbeat, using uuid=uname"); |
| return NULL; |
| } else if(uname == NULL) { |
| return NULL; |
| } |
| |
| if (heartbeat_cluster->llc_ops->get_uuid_by_name(heartbeat_cluster, uname, &uuid_raw) == |
| HA_FAIL) { |
| crm_err("get_uuid_by_name() call failed for host %s", uname); |
| free(uuid_calc); |
| return NULL; |
| } |
| |
| uuid_calc = calloc(1, 50); |
| cl_uuid_unparse(&uuid_raw, uuid_calc); |
| |
| if (safe_str_eq(uuid_calc, unknown)) { |
| crm_warn("Could not calculate UUID for %s", uname); |
| free(uuid_calc); |
| return NULL; |
| } |
| #endif |
| return uuid_calc; |
| } |
| |
| static gboolean |
| uname_is_uuid(void) |
| { |
| static const char *uuid_pref = NULL; |
| |
| if (uuid_pref == NULL) { |
| uuid_pref = getenv("PCMK_uname_is_uuid"); |
| } |
| |
| if (uuid_pref == NULL) { |
| /* true is legacy mode */ |
| uuid_pref = "false"; |
| } |
| |
| return crm_is_true(uuid_pref); |
| } |
| |
| int |
| get_corosync_id(int id, const char *uuid) |
| { |
| if (id == 0 && !uname_is_uuid() && is_corosync_cluster()) { |
| id = crm_atoi(uuid, "0"); |
| } |
| |
| return id; |
| } |
| |
| char * |
| get_corosync_uuid(crm_node_t *node) |
| { |
| if(node == NULL) { |
| return NULL; |
| |
| } else if (!uname_is_uuid() && is_corosync_cluster()) { |
| if (node->id > 0) { |
| int len = 32; |
| char *buffer = NULL; |
| |
| buffer = calloc(1, (len + 1)); |
| if (buffer != NULL) { |
| snprintf(buffer, len, "%u", node->id); |
| } |
| |
| return buffer; |
| |
| } else { |
| crm_info("Node %s is not yet known by corosync", node->uname); |
| } |
| |
| } else if (node->uname != NULL) { |
| return strdup(node->uname); |
| } |
| |
| return NULL; |
| } |
| |
| const char * |
| crm_peer_uuid(crm_node_t *peer) |
| { |
| char *uuid = NULL; |
| enum cluster_type_e type = get_cluster_type(); |
| |
| /* avoid blocking heartbeat calls where possible */ |
| if(peer == NULL) { |
| return NULL; |
| |
| } else if (peer->uuid) { |
| return peer->uuid; |
| } |
| |
| switch (type) { |
| case pcmk_cluster_corosync: |
| uuid = get_corosync_uuid(peer); |
| break; |
| |
| case pcmk_cluster_cman: |
| case pcmk_cluster_classic_ais: |
| if (peer->uname) { |
| uuid = strdup(peer->uname); |
| } |
| break; |
| |
| case pcmk_cluster_heartbeat: |
| uuid = get_heartbeat_uuid(peer->uname); |
| break; |
| |
| case pcmk_cluster_unknown: |
| case pcmk_cluster_invalid: |
| crm_err("Unsupported cluster type"); |
| break; |
| } |
| |
| peer->uuid = uuid; |
| return peer->uuid; |
| } |
| |
| gboolean |
| crm_cluster_connect(crm_cluster_t * cluster) |
| { |
| enum cluster_type_e type = get_cluster_type(); |
| |
| crm_notice("Connecting to cluster infrastructure: %s", name_for_cluster_type(type)); |
| #if SUPPORT_COROSYNC |
| if (is_openais_cluster()) { |
| crm_peer_init(); |
| return init_cs_connection(cluster); |
| } |
| #endif |
| |
| #if SUPPORT_HEARTBEAT |
| if (is_heartbeat_cluster()) { |
| int rv; |
| |
| /* coverity[var_deref_op] False positive */ |
| if (cluster->hb_conn == NULL) { |
| /* No object passed in, create a new one. */ |
| ll_cluster_t *(*new_cluster) (const char *llctype) = |
| find_library_function(&hb_library, HEARTBEAT_LIBRARY, "ll_cluster_new", 1); |
| |
| cluster->hb_conn = (*new_cluster) ("heartbeat"); |
| /* dlclose(handle); */ |
| |
| } else { |
| /* Object passed in. Disconnect first, then reconnect below. */ |
| cluster->hb_conn->llc_ops->signoff(cluster->hb_conn, FALSE); |
| } |
| |
| /* make sure we are disconnected first with the old object, if any. */ |
| if (heartbeat_cluster && heartbeat_cluster != cluster->hb_conn) { |
| heartbeat_cluster->llc_ops->signoff(heartbeat_cluster, FALSE); |
| } |
| |
| CRM_ASSERT(cluster->hb_conn != NULL); |
| heartbeat_cluster = cluster->hb_conn; |
| |
| rv = register_heartbeat_conn(cluster); |
| if (rv) { |
| /* we'll benefit from a bigger queue length on heartbeat side. |
| * Otherwise, if peers send messages faster than we can consume |
| * them right now, heartbeat messaging layer will kick us out once |
| * it's (small) default queue fills up :( |
| * If we fail to adjust the sendq length, that's not yet fatal, though. |
| */ |
| if (HA_OK != heartbeat_cluster->llc_ops->set_sendq_len(heartbeat_cluster, 1024)) { |
| crm_warn("Cannot set sendq length: %s", |
| heartbeat_cluster->llc_ops->errmsg(heartbeat_cluster)); |
| } |
| } |
| return rv; |
| } |
| #endif |
| crm_info("Unsupported cluster stack: %s", getenv("HA_cluster_type")); |
| return FALSE; |
| } |
| |
| void |
| crm_cluster_disconnect(crm_cluster_t * cluster) |
| { |
| enum cluster_type_e type = get_cluster_type(); |
| const char *type_str = name_for_cluster_type(type); |
| |
| crm_info("Disconnecting from cluster infrastructure: %s", type_str); |
| #if SUPPORT_COROSYNC |
| if (is_openais_cluster()) { |
| crm_peer_destroy(); |
| terminate_cs_connection(cluster); |
| crm_info("Disconnected from %s", type_str); |
| return; |
| } |
| #endif |
| |
| #if SUPPORT_HEARTBEAT |
| if (is_heartbeat_cluster()) { |
| if (cluster == NULL) { |
| crm_info("No cluster connection"); |
| return; |
| |
| } else if (cluster->hb_conn) { |
| cluster->hb_conn->llc_ops->signoff(cluster->hb_conn, TRUE); |
| cluster->hb_conn = NULL; |
| crm_info("Disconnected from %s", type_str); |
| return; |
| |
| } else { |
| crm_info("No %s connection", type_str); |
| return; |
| } |
| } |
| #endif |
| crm_info("Unsupported cluster stack: %s", getenv("HA_cluster_type")); |
| } |
| |
| gboolean |
| send_cluster_message(crm_node_t * node, enum crm_ais_msg_types service, xmlNode * data, |
| gboolean ordered) |
| { |
| |
| #if SUPPORT_COROSYNC |
| if (is_openais_cluster()) { |
| return send_cluster_message_cs(data, FALSE, node, service); |
| } |
| #endif |
| #if SUPPORT_HEARTBEAT |
| if (is_heartbeat_cluster()) { |
| return send_ha_message(heartbeat_cluster, data, node ? node->uname : NULL, ordered); |
| } |
| #endif |
| return FALSE; |
| } |
| |
| const char * |
| get_local_node_name(void) |
| { |
| static char *name = NULL; |
| |
| if(name) { |
| return name; |
| } |
| name = get_node_name(0); |
| return name; |
| } |
| |
| char * |
| get_node_name(uint32_t nodeid) |
| { |
| char *name = NULL; |
| const char *isolation_host = NULL; |
| enum cluster_type_e stack; |
| |
| if (nodeid == 0) { |
| isolation_host = getenv("OCF_RESKEY_"CRM_META"_isolation_host"); |
| if (isolation_host) { |
| return strdup(isolation_host); |
| } |
| } |
| |
| stack = get_cluster_type(); |
| switch (stack) { |
| case pcmk_cluster_heartbeat: |
| break; |
| |
| #if SUPPORT_PLUGIN |
| case pcmk_cluster_classic_ais: |
| name = classic_node_name(nodeid); |
| break; |
| #else |
| # if SUPPORT_COROSYNC |
| case pcmk_cluster_corosync: |
| name = corosync_node_name(0, nodeid); |
| break; |
| # endif |
| #endif |
| |
| #if SUPPORT_CMAN |
| case pcmk_cluster_cman: |
| name = cman_node_name(nodeid); |
| break; |
| #endif |
| |
| default: |
| crm_err("Unknown cluster type: %s (%d)", name_for_cluster_type(stack), stack); |
| } |
| |
| if(name == NULL && nodeid == 0) { |
| struct utsname res; |
| int rc = uname(&res); |
| |
| if (rc == 0) { |
| crm_notice("Defaulting to uname -n for the local %s node name", |
| name_for_cluster_type(stack)); |
| name = strdup(res.nodename); |
| } |
| |
| if (name == NULL) { |
| crm_err("Could not obtain the local %s node name", name_for_cluster_type(stack)); |
| crm_exit(DAEMON_RESPAWN_STOP); |
| } |
| } |
| |
| if (name == NULL) { |
| crm_notice("Could not obtain a node name for %s nodeid %u", |
| name_for_cluster_type(stack), nodeid); |
| } |
| return name; |
| } |
| |
| /*! |
| * \brief Get the node name corresponding to a node UUID |
| * |
| * \param[in] uuid UUID of desired node |
| * |
| * \return name of desired node |
| * |
| * \note This relies on the remote peer cache being populated with all |
| * remote nodes in the cluster, so callers should maintain that cache. |
| */ |
| const char * |
| crm_peer_uname(const char *uuid) |
| { |
| GHashTableIter iter; |
| crm_node_t *node = NULL; |
| |
| CRM_CHECK(uuid != NULL, return NULL); |
| |
| /* remote nodes have the same uname and uuid */ |
| if (g_hash_table_lookup(crm_remote_peer_cache, uuid)) { |
| return uuid; |
| } |
| |
| /* avoid blocking calls where possible */ |
| g_hash_table_iter_init(&iter, crm_peer_cache); |
| while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) { |
| if(node->uuid && strcasecmp(node->uuid, uuid) == 0) { |
| if(node->uname) { |
| return node->uname; |
| } |
| break; |
| } |
| } |
| node = NULL; |
| |
| #if SUPPORT_COROSYNC |
| if (is_openais_cluster()) { |
| if (uname_is_uuid() == FALSE && is_corosync_cluster()) { |
| uint32_t id = crm_int_helper(uuid, NULL); |
| if(id != 0) { |
| node = crm_find_peer(id, NULL); |
| } else { |
| crm_err("Invalid node id: %s", uuid); |
| } |
| |
| } else { |
| node = crm_find_peer(0, uuid); |
| } |
| |
| if (node) { |
| crm_info("Setting uuid for node %s[%u] to '%s'", node->uname, node->id, uuid); |
| node->uuid = strdup(uuid); |
| if(node->uname) { |
| return node->uname; |
| } |
| } |
| return NULL; |
| } |
| #endif |
| |
| #if SUPPORT_HEARTBEAT |
| if (is_heartbeat_cluster()) { |
| if (heartbeat_cluster != NULL) { |
| cl_uuid_t uuid_raw; |
| char *uuid_copy = strdup(uuid); |
| char *uname = malloc(MAX_NAME); |
| |
| cl_uuid_parse(uuid_copy, &uuid_raw); |
| |
| if (heartbeat_cluster->llc_ops->get_name_by_uuid(heartbeat_cluster, &uuid_raw, uname, |
| MAX_NAME) == HA_FAIL) { |
| crm_err("Could not calculate uname for %s", uuid); |
| } else { |
| node = crm_get_peer(0, uname); |
| } |
| |
| free(uuid_copy); |
| free(uname); |
| } |
| |
| if (node) { |
| crm_info("Setting uuid for node %s to '%s'", node->uname, uuid); |
| node->uuid = strdup(uuid); |
| if(node->uname) { |
| return node->uname; |
| } |
| } |
| return NULL; |
| } |
| #endif |
| |
| return NULL; |
| } |
| |
| void |
| set_uuid(xmlNode *xml, const char *attr, crm_node_t *node) |
| { |
| const char *uuid_calc = crm_peer_uuid(node); |
| |
| crm_xml_add(xml, attr, uuid_calc); |
| return; |
| } |
| |
| const char * |
| name_for_cluster_type(enum cluster_type_e type) |
| { |
| switch (type) { |
| case pcmk_cluster_classic_ais: |
| return "classic openais (with plugin)"; |
| case pcmk_cluster_cman: |
| return "cman"; |
| case pcmk_cluster_corosync: |
| return "corosync"; |
| case pcmk_cluster_heartbeat: |
| return "heartbeat"; |
| case pcmk_cluster_unknown: |
| return "unknown"; |
| case pcmk_cluster_invalid: |
| return "invalid"; |
| } |
| crm_err("Invalid cluster type: %d", type); |
| return "invalid"; |
| } |
| |
| /* Do not expose these two */ |
| int set_cluster_type(enum cluster_type_e type); |
| static enum cluster_type_e cluster_type = pcmk_cluster_unknown; |
| |
| int |
| set_cluster_type(enum cluster_type_e type) |
| { |
| if (cluster_type == pcmk_cluster_unknown) { |
| crm_info("Cluster type set to: %s", name_for_cluster_type(type)); |
| cluster_type = type; |
| return 0; |
| |
| } else if (cluster_type == type) { |
| return 0; |
| |
| } else if (pcmk_cluster_unknown == type) { |
| cluster_type = type; |
| return 0; |
| } |
| |
| crm_err("Cluster type already set to %s, ignoring %s", |
| name_for_cluster_type(cluster_type), name_for_cluster_type(type)); |
| return -1; |
| } |
| enum cluster_type_e |
| get_cluster_type(void) |
| { |
| bool detected = FALSE; |
| const char *cluster = NULL; |
| |
| /* Return the previous calculation, if any */ |
| if (cluster_type != pcmk_cluster_unknown) { |
| return cluster_type; |
| } |
| |
| cluster = getenv("HA_cluster_type"); |
| |
| #if SUPPORT_HEARTBEAT |
| /* If nothing is defined in the environment, try heartbeat (if supported) */ |
| if(cluster == NULL) { |
| ll_cluster_t *hb; |
| ll_cluster_t *(*new_cluster) (const char *llctype) = find_library_function( |
| &hb_library, HEARTBEAT_LIBRARY, "ll_cluster_new", 1); |
| |
| hb = (*new_cluster) ("heartbeat"); |
| |
| crm_debug("Testing with Heartbeat"); |
| /* |
| * Test as "casual" client (clientid == NULL; will be replaced by |
| * current pid). We are trying to detect if we can communicate with |
| * heartbeat, not if we can register as some specific service. |
| * Otherwise all but one of several concurrent invocations will get |
| * HA_FAIL because of: |
| * WARN: duplicate client add request |
| * ERROR: api_process_registration_msg: cannot add client() |
| * and then likely fail :( |
| */ |
| if (hb->llc_ops->signon(hb, NULL) == HA_OK) { |
| hb->llc_ops->signoff(hb, FALSE); |
| |
| cluster_type = pcmk_cluster_heartbeat; |
| detected = TRUE; |
| goto done; |
| } |
| } |
| #endif |
| |
| #if SUPPORT_COROSYNC |
| /* If nothing is defined in the environment, try corosync (if supported) */ |
| if(cluster == NULL) { |
| crm_debug("Testing with Corosync"); |
| cluster_type = find_corosync_variant(); |
| if (cluster_type != pcmk_cluster_unknown) { |
| detected = TRUE; |
| goto done; |
| } |
| } |
| #endif |
| |
| /* Something was defined in the environment, test it against what we support */ |
| crm_info("Verifying cluster type: '%s'", cluster?cluster:"-unspecified-"); |
| if (cluster == NULL) { |
| |
| #if SUPPORT_HEARTBEAT |
| } else if (safe_str_eq(cluster, "heartbeat")) { |
| cluster_type = pcmk_cluster_heartbeat; |
| #endif |
| |
| #if SUPPORT_COROSYNC |
| } else if (safe_str_eq(cluster, "openais") |
| || safe_str_eq(cluster, "classic openais (with plugin)")) { |
| cluster_type = pcmk_cluster_classic_ais; |
| |
| } else if (safe_str_eq(cluster, "corosync")) { |
| cluster_type = pcmk_cluster_corosync; |
| #endif |
| |
| #if SUPPORT_CMAN |
| } else if (safe_str_eq(cluster, "cman")) { |
| cluster_type = pcmk_cluster_cman; |
| #endif |
| |
| } else { |
| cluster_type = pcmk_cluster_invalid; |
| goto done; /* Keep the compiler happy when no stacks are supported */ |
| } |
| |
| done: |
| if (cluster_type == pcmk_cluster_unknown) { |
| crm_notice("Could not determine the current cluster type"); |
| |
| } else if (cluster_type == pcmk_cluster_invalid) { |
| crm_notice("This installation does not support the '%s' cluster infrastructure: terminating.", |
| cluster); |
| crm_exit(DAEMON_RESPAWN_STOP); |
| |
| } else { |
| crm_info("%s an active '%s' cluster", detected?"Detected":"Assuming", name_for_cluster_type(cluster_type)); |
| } |
| |
| return cluster_type; |
| } |
| |
| gboolean |
| is_cman_cluster(void) |
| { |
| return get_cluster_type() == pcmk_cluster_cman; |
| } |
| |
| gboolean |
| is_corosync_cluster(void) |
| { |
| return get_cluster_type() == pcmk_cluster_corosync; |
| } |
| |
| gboolean |
| is_classic_ais_cluster(void) |
| { |
| return get_cluster_type() == pcmk_cluster_classic_ais; |
| } |
| |
| gboolean |
| is_openais_cluster(void) |
| { |
| enum cluster_type_e type = get_cluster_type(); |
| |
| if (type == pcmk_cluster_classic_ais) { |
| return TRUE; |
| } else if (type == pcmk_cluster_corosync) { |
| return TRUE; |
| } else if (type == pcmk_cluster_cman) { |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| gboolean |
| is_heartbeat_cluster(void) |
| { |
| return get_cluster_type() == pcmk_cluster_heartbeat; |
| } |
| |
| gboolean |
| node_name_is_valid(const char *key, const char *name) |
| { |
| int octet; |
| |
| if (name == NULL) { |
| crm_trace("%s is empty", key); |
| return FALSE; |
| |
| } else if (sscanf(name, "%d.%d.%d.%d", &octet, &octet, &octet, &octet) == 4) { |
| crm_trace("%s contains an ipv4 address, ignoring: %s", key, name); |
| return FALSE; |
| |
| } else if (strstr(name, ":") != NULL) { |
| crm_trace("%s contains an ipv6 address, ignoring: %s", key, name); |
| return FALSE; |
| } |
| crm_trace("%s is valid", key); |
| return TRUE; |
| } |