static mongo_connection *mongo_get_connection_multiple(mongo_con_manager *manager, mongo_servers *servers, int connection_flags, char **error_message) { mongo_connection *con = NULL; mongo_connection *tmp; mcon_collection *collection = NULL; mongo_read_preference tmp_rp; /* We only support NEAREST for MULTIPLE right now */ int i; int found_connected_server = 0; mcon_str *messages; int found_supported_wire_version = 1; mcon_str_ptr_init(messages); /* Create a connection to every of the servers in the seed list */ for (i = 0; i < servers->count; i++) { int ismaster_error = 0; char *con_error_message = NULL; tmp = mongo_get_connection_single(manager, servers->server[i], &servers->options, connection_flags, (char **) &con_error_message); if (tmp) { found_connected_server++; /* Run ismaster, if needed, to extract server flags */ ismaster_error = mongo_connection_ismaster(manager, tmp, &servers->options, NULL, NULL, NULL, &con_error_message, NULL); switch (ismaster_error) { case 1: /* Run just fine */ case 2: /* ismaster() skipped due to interval */ break; case 0: /* Some error */ case 3: /* Run just fine, but hostname didn't match what we expected */ case 4: /* Danger danger, reported wire version does not overlap what we support */ default: mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "server_flags: error while getting the server configuration %s:%d: %s", servers->server[i]->host, servers->server[i]->port, con_error_message); /* If it failed because of wire version, we have to bail out completely * later on, but we should continue to aggregate the errors in case more * servers are unsupported */ if (ismaster_error == 4) { mongo_manager_connection_deregister(manager, tmp); found_supported_wire_version = 0; } else { mongo_connection_destroy(manager, tmp, MONGO_CLOSE_BROKEN); } tmp = NULL; found_connected_server--; } } if (!tmp) { if (!(connection_flags & MONGO_CON_FLAG_DONT_CONNECT)) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "Couldn't connect to '%s:%d': %s", servers->server[i]->host, servers->server[i]->port, con_error_message); if (messages->l) { mcon_str_addl(messages, "; ", 2, 0); } mcon_str_add(messages, "Failed to connect to: ", 0); mcon_str_add(messages, servers->server[i]->host, 0); mcon_str_addl(messages, ":", 1, 0); mcon_str_add_int(messages, servers->server[i]->port); mcon_str_addl(messages, ": ", 2, 0); mcon_str_add(messages, con_error_message, 1); /* Also frees con_error_message */ } else { free(con_error_message); } } } if (!found_supported_wire_version) { *error_message = strdup("Found a server running unsupported wire version. Please upgrade the driver, or take the server out of rotation"); mcon_str_ptr_dtor(messages); return NULL; } /* If we don't have a connected server then there is no point in continueing */ if (!found_connected_server && (connection_flags & MONGO_CON_FLAG_DONT_CONNECT)) { mcon_str_ptr_dtor(messages); return NULL; } /* When selecting a *mongos* node, readPreferences make no sense as we * don't have a "primary" or "secondary" mongos. The mongos nodes aren't * tagged either. To pick a mongos we therefore simply pick the "nearest" * mongos node. */ tmp_rp.type = MONGO_RP_NEAREST; tmp_rp.tagsets = NULL; tmp_rp.tagset_count = 0; collection = mongo_find_candidate_servers(manager, &tmp_rp, servers); if (!collection || collection->count == 0) { if (messages->l) { *error_message = strdup(messages->d); } else { *error_message = strdup("No candidate servers found"); } goto bailout; } collection = mongo_sort_servers(manager, collection, &servers->read_pref); collection = mongo_select_nearest_servers(manager, collection, &servers->options, &servers->read_pref); if (!collection) { *error_message = strdup("No server near us"); goto bailout; } con = mongo_pick_server_from_set(manager, collection, &servers->read_pref); bailout: /* Cleaning up */ mcon_str_ptr_dtor(messages); if (collection) { mcon_collection_free(collection); } return con; }
/* - Helpers */ static void mongo_discover_topology(mongo_con_manager *manager, mongo_servers *servers) { int i, j; char *hash; mongo_connection *con; char *error_message; char *repl_set_name = servers->options.repl_set_name ? strdup(servers->options.repl_set_name) : NULL; int nr_hosts; char **found_hosts = NULL; char *tmp_hash; int res; for (i = 0; i < servers->count; i++) { hash = mongo_server_create_hash(servers->server[i]); mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "discover_topology: checking ismaster for %s", hash); con = mongo_manager_connection_find_by_hash(manager, hash); if (!con) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "discover_topology: couldn't create a connection for %s", hash); free(hash); continue; } res = mongo_connection_ismaster(manager, con, &servers->options, (char**) &repl_set_name, (int*) &nr_hosts, (char***) &found_hosts, (char**) &error_message, servers->server[i]); switch (res) { case 0: /* Something is wrong with the connection, we need to remove * this from our list */ mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "discover_topology: ismaster return with an error for %s:%d: [%s]", servers->server[i]->host, servers->server[i]->port, error_message); free(error_message); mongo_manager_connection_deregister(manager, con); break; case 3: mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "discover_topology: ismaster worked, but we need to remove the seed host's connection"); mongo_manager_connection_deregister(manager, con); /* Break intentionally missing */ case 1: mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "discover_topology: ismaster worked"); /* Update the replica set name in the parsed "servers" struct * so that we can consistently compare it to the information * that is stored in the connection hashes. */ if (!servers->options.repl_set_name && repl_set_name) { servers->options.repl_set_name = strdup(repl_set_name); } /* Now loop over all the hosts that were found */ for (j = 0; j < nr_hosts; j++) { mongo_server_def *tmp_def; mongo_connection *new_con; char *con_error_message = NULL; /* Create a temp server definition to create a new connection on-demand if we didn't have one already */ tmp_def = calloc(1, sizeof(mongo_server_def)); tmp_def->username = servers->server[i]->username ? strdup(servers->server[i]->username) : NULL; tmp_def->password = servers->server[i]->password ? strdup(servers->server[i]->password) : NULL; tmp_def->repl_set_name = servers->server[i]->repl_set_name ? strdup(servers->server[i]->repl_set_name) : NULL; tmp_def->db = servers->server[i]->db ? strdup(servers->server[i]->db) : NULL; tmp_def->authdb = servers->server[i]->authdb ? strdup(servers->server[i]->authdb) : NULL; tmp_def->host = mcon_strndup(found_hosts[j], strchr(found_hosts[j], ':') - found_hosts[j]); tmp_def->port = atoi(strchr(found_hosts[j], ':') + 1); /* Create a hash so that we can check whether we already have a * connection for this server definition. If we don't create * the connection, register it (done in * mongo_get_connection_single) and add it to the list of * servers that we're processing so we might use this host to * find more servers. */ tmp_hash = mongo_server_create_hash(tmp_def); if (!mongo_manager_connection_find_by_hash(manager, tmp_hash)) { mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "discover_topology: found new host: %s:%d", tmp_def->host, tmp_def->port); new_con = mongo_get_connection_single(manager, tmp_def, &servers->options, MONGO_CON_FLAG_WRITE, (char **) &con_error_message); if (new_con) { servers->server[servers->count] = tmp_def; servers->count++; } else { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "discover_topology: could not connect to new host: %s:%d: %s", tmp_def->host, tmp_def->port, con_error_message); free(con_error_message); } } else { mongo_server_def_dtor(tmp_def); } free(tmp_hash); /* Cleanup */ free(found_hosts[j]); } free(found_hosts); found_hosts = NULL; break; case 2: mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "discover_topology: ismaster got skipped"); break; } free(hash); } if (repl_set_name) { free(repl_set_name); } }
/* Returns: * 1 on success * 0 on total failure (e.g. unsupported wire version) */ static int mongo_discover_topology(mongo_con_manager *manager, mongo_servers *servers) { int i, j; char *hash; mongo_connection *con; char *error_message; char *repl_set_name = servers->options.repl_set_name ? strdup(servers->options.repl_set_name) : NULL; int nr_hosts; char **found_hosts = NULL; char *tmp_hash; int res; int found_supported_wire_version = 1; /* Innocent unless proven guilty */ for (i = 0; i < servers->count; i++) { hash = mongo_server_create_hash(servers->server[i]); mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "discover_topology: checking ismaster for %s", hash); con = mongo_manager_connection_find_by_hash(manager, hash); if (!con) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "discover_topology: couldn't create a connection for %s", hash); free(hash); continue; } /* Run ismaster, if needed, to extract server flags - and fetch the other known hosts */ res = mongo_connection_ismaster(manager, con, &servers->options, (char**) &repl_set_name, (int*) &nr_hosts, (char***) &found_hosts, (char**) &error_message, servers->server[i]); switch (res) { case 4: /* The server is running unsupported wire versions */ found_supported_wire_version = 0; /* break omitted intentionally */ case 0: /* Something is wrong with the connection, we need to remove * this from our list */ mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "discover_topology: ismaster return with an error for %s:%d: [%s]", servers->server[i]->host, servers->server[i]->port, error_message); free(error_message); mongo_manager_connection_deregister(manager, con); break; case 3: mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "discover_topology: ismaster worked, but we need to remove the seed host's connection"); mongo_manager_connection_deregister(manager, con); /* Break intentionally missing */ case 1: mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "discover_topology: ismaster worked"); /* Update the replica set name in the parsed "servers" struct * so that we can consistently compare it to the information * that is stored in the connection hashes. */ if (!servers->options.repl_set_name && repl_set_name) { servers->options.repl_set_name = strdup(repl_set_name); } /* Now loop over all the hosts that were found */ for (j = 0; j < nr_hosts; j++) { mongo_server_def *tmp_def; mongo_connection *new_con; char *con_error_message = NULL; /* Create a temp server definition to create a new * connection on-demand if we didn't have one already */ tmp_def = calloc(1, sizeof(mongo_server_def)); tmp_def->username = servers->server[i]->username ? strdup(servers->server[i]->username) : NULL; tmp_def->password = servers->server[i]->password ? strdup(servers->server[i]->password) : NULL; tmp_def->repl_set_name = servers->server[i]->repl_set_name ? strdup(servers->server[i]->repl_set_name) : NULL; tmp_def->db = servers->server[i]->db ? strdup(servers->server[i]->db) : NULL; tmp_def->authdb = servers->server[i]->authdb ? strdup(servers->server[i]->authdb) : NULL; tmp_def->host = mcon_strndup(found_hosts[j], strchr(found_hosts[j], ':') - found_hosts[j]); tmp_def->port = atoi(strchr(found_hosts[j], ':') + 1); tmp_def->mechanism = servers->server[i]->mechanism; /* Create a hash so that we can check whether we already have a * connection for this server definition. If we don't create * the connection, register it (done in * mongo_get_connection_single) and add it to the list of * servers that we're processing so we might use this host to * find more servers. */ tmp_hash = mongo_server_create_hash(tmp_def); if (!mongo_manager_connection_find_by_hash(manager, tmp_hash)) { mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "discover_topology: found new host: %s:%d", tmp_def->host, tmp_def->port); new_con = mongo_get_connection_single(manager, tmp_def, &servers->options, MONGO_CON_FLAG_WRITE, (char **) &con_error_message); if (new_con) { int ismaster_error = mongo_connection_ismaster(manager, new_con, &servers->options, NULL, NULL, NULL, &con_error_message, NULL); switch (ismaster_error) { case 1: /* Run just fine */ case 2: /* ismaster() skipped due to interval */ break; case 4: /* Danger danger, reported wire version does not overlap what we support */ found_supported_wire_version = 0; /* break omitted intentionally */ case 0: /* Some error */ case 3: /* Run just fine, but hostname didn't match what we expected */ default: mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "server_flags: error while getting the server configuration %s:%d: %s", servers->server[i]->host, servers->server[i]->port, con_error_message); mongo_manager_connection_deregister(manager, new_con); new_con = NULL; } } if (new_con) { servers->server[servers->count] = tmp_def; servers->count++; } else { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "discover_topology: could not connect to new host: %s:%d: %s", tmp_def->host, tmp_def->port, con_error_message); free(con_error_message); } } else { mongo_server_def_dtor(tmp_def); } free(tmp_hash); /* Cleanup */ free(found_hosts[j]); } free(found_hosts); found_hosts = NULL; break; case 2: mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "discover_topology: ismaster got skipped"); break; } free(hash); } if (repl_set_name) { free(repl_set_name); } return found_supported_wire_version; }
/* Helpers */ static mongo_connection *mongo_get_connection_single(mongo_con_manager *manager, mongo_server_def *server, mongo_server_options *options, int connection_flags, char **error_message) { char *hash; mongo_connection *con = NULL; mongo_connection_blacklist *blacklist = NULL; hash = mongo_server_create_hash(server); /* See if a connection is in our blacklist to short-circut trying to * connect to a node that is known to be down. This is done so we don't * waste precious time in connecting to unreachable nodes */ blacklist = mongo_manager_blacklist_find_by_hash(manager, hash); if (blacklist) { struct timeval start; /* It is blacklisted, but it may have been a long time again and * chances are we should give it another try */ if (mongo_connection_ping_check(manager, blacklist->last_ping, &start)) { /* The connection is blacklisted, but we've reached our ping * interval so lets remove the blacklisting and pretend we didn't * know about it */ mongo_manager_blacklist_deregister(manager, blacklist, hash); } else { /* Otherwise short-circut the connection attempt, and say we failed * right away */ free(hash); *error_message = strdup("Previous connection attempts failed, server blacklisted"); return NULL; } } con = mongo_manager_connection_find_by_hash(manager, hash); /* If we aren't about to (re-)connect then all we care about if it was a * known connection or not */ if (connection_flags & MONGO_CON_FLAG_DONT_CONNECT) { free(hash); return con; } /* If we found a valid connection check if we need to ping it */ if (con) { /* Do the ping, if needed */ if (!mongo_connection_ping(manager, con, options, error_message)) { /* If the ping failed, deregister the connection */ mongo_manager_connection_deregister(manager, con); /* Set the return value to NULL, as the connection is broken and * has been removed */ con = NULL; } free(hash); return con; } /* Since we didn't find an existing connection, lets make one! */ con = mongo_connection_create(manager, hash, server, options, error_message); if (con) { /* isMaster() _must_ be the first command on all new connections. * This is for node discovery so we don't issue f.e. authentication to nodes in STARTUP * state, or arbiters */ if (!mongo_connection_ismaster(manager, con, options, NULL, 0, NULL, error_message, NULL)) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "ismaster: error running ismaster: %s", *error_message); mongo_connection_destroy(manager, con, MONGO_CLOSE_BROKEN); free(hash); return NULL; } /* When we make a connection, we need to figure out the server version it is */ if (!mongo_connection_get_server_version(manager, con, options, error_message)) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "server_version: error while getting the server version %s:%d: %s", server->host, server->port, *error_message); mongo_connection_destroy(manager, con, MONGO_CLOSE_BROKEN); free(hash); return NULL; } /* Do authentication if requested */ /* Note: Arbiters don't contain any data, including auth stuff, so you cannot authenticate on an arbiter */ if (con->connection_type != MONGO_NODE_ARBITER) { if (!manager->authenticate(manager, con, options, server, error_message)) { mongo_connection_destroy(manager, con, MONGO_CLOSE_BROKEN); free(hash); return NULL; } } /* Do the first-time ping to record the latency of the connection */ if (mongo_connection_ping(manager, con, options, error_message)) { /* Register the connection on successful pinging */ mongo_manager_connection_register(manager, con); } else { /* Or kill it and reset the return value if the ping somehow failed */ mongo_connection_destroy(manager, con, MONGO_CLOSE_BROKEN); con = NULL; } } free(hash); if (con) { con->connected = 1; } return con; }