mongo_connection *mongo_connection_create(mongo_con_manager *manager, char *hash, mongo_server_def *server_def, mongo_server_options *options, char **error_message) { mongo_connection *tmp; /* Init struct */ tmp = malloc(sizeof(mongo_connection)); memset(tmp, 0, sizeof(mongo_connection)); tmp->last_reqid = rand(); tmp->connection_type = MONGO_NODE_STANDALONE; /* Store hash */ tmp->hash = strdup(hash); /* Connect */ mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "connection_create: creating new connection for %s:%d", server_def->host, server_def->port); tmp->socket = manager->connect(manager, server_def, options, error_message); if (!tmp->socket) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "connection_create: error while creating connection for %s:%d: %s", server_def->host, server_def->port, *error_message); mongo_manager_blacklist_register(manager, tmp); free(tmp->hash); free(tmp); return NULL; } /* We call get_server_flags to the maxBsonObjectSize data */ if (!mongo_connection_get_server_flags(manager, tmp, options, error_message)) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "server_flags: error while getting the server configuration %s:%d: %s", server_def->host, server_def->port, *error_message); free(tmp); return NULL; } return tmp; }
/* This function does the actual connecting */ int mongo_connection_connect(char *host, int port, int timeout, char **error_message) { struct sockaddr* sa; struct sockaddr_in si; socklen_t sn; int family; struct timeval tval; int connected; int status; int tmp_socket; #ifdef WIN32 WORD version; WSADATA wsaData; int size, error; u_long no = 0; const char yes = 1; #else struct sockaddr_un su; uint size; int yes = 1; #endif *error_message = NULL; #ifdef WIN32 family = AF_INET; sa = (struct sockaddr*)(&si); sn = sizeof(si); version = MAKEWORD(2,2); error = WSAStartup(version, &wsaData); if (error != 0) { return -1; } /* create socket */ tmp_socket = socket(family, SOCK_STREAM, 0); if (tmp_socket == INVALID_SOCKET) { *error_message = strdup(strerror(errno)); return -1; } #else /* domain socket */ if (port == 0) { family = AF_UNIX; sa = (struct sockaddr*)(&su); sn = sizeof(su); } else { family = AF_INET; sa = (struct sockaddr*)(&si); sn = sizeof(si); } /* create socket */ if ((tmp_socket = socket(family, SOCK_STREAM, 0)) == -1) { *error_message = strdup(strerror(errno)); return -1; } #endif /* TODO: Move this to within the loop & use real timeout setting */ /* connection timeout: set in ms (current default 1000 secs) */ tval.tv_sec = timeout <= 0 ? 1000 : timeout / 1000; tval.tv_usec = timeout <= 0 ? 0 : (timeout % 1000) * 1000; /* get addresses */ if (mongo_util_connect__sockaddr(sa, family, host, port, error_message) == 0) { goto error; } setsockopt(tmp_socket, SOL_SOCKET, SO_KEEPALIVE, &yes, INT_32); setsockopt(tmp_socket, IPPROTO_TCP, TCP_NODELAY, &yes, INT_32); #ifdef WIN32 ioctlsocket(tmp_socket, FIONBIO, (u_long*)&yes); #else fcntl(tmp_socket, F_SETFL, FLAGS|O_NONBLOCK); #endif /* connect */ status = connect(tmp_socket, sa, sn); if (status < 0) { #ifdef WIN32 errno = WSAGetLastError(); if (errno != WSAEINPROGRESS && errno != WSAEWOULDBLOCK) { #else if (errno != EINPROGRESS) { #endif *error_message = strdup(strerror(errno)); goto error; } while (1) { fd_set rset, wset, eset; FD_ZERO(&rset); FD_SET(tmp_socket, &rset); FD_ZERO(&wset); FD_SET(tmp_socket, &wset); FD_ZERO(&eset); FD_SET(tmp_socket, &eset); if (select(tmp_socket+1, &rset, &wset, &eset, &tval) == 0) { *error_message = malloc(256); snprintf(*error_message, 256, "Timed out after %d ms", timeout); goto error; } /* if our descriptor has an error */ if (FD_ISSET(tmp_socket, &eset)) { *error_message = strdup(strerror(errno)); goto error; } /* if our descriptor is ready break out */ if (FD_ISSET(tmp_socket, &wset) || FD_ISSET(tmp_socket, &rset)) { break; } } size = sn; connected = getpeername(tmp_socket, sa, &size); if (connected == -1) { *error_message = strdup(strerror(errno)); goto error; } } /* reset flags */ #ifdef WIN32 ioctlsocket(tmp_socket, FIONBIO, &no); #else fcntl(tmp_socket, F_SETFL, FLAGS); #endif return tmp_socket; error: #ifdef WIN32 shutdown((tmp_socket), 2); closesocket(tmp_socket); WSACleanup(); #else shutdown((tmp_socket), 2); close(tmp_socket); #endif return -1; } mongo_connection *mongo_connection_create(mongo_con_manager *manager, mongo_server_def *server_def, char **error_message) { mongo_connection *tmp; /* Init struct */ tmp = malloc(sizeof(mongo_connection)); memset(tmp, 0, sizeof(mongo_connection)); tmp->last_reqid = rand(); tmp->connection_type = MONGO_NODE_STANDALONE; /* Connect */ mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "connection_create: creating new connection for %s:%d", server_def->host, server_def->port); tmp->socket = mongo_connection_connect(server_def->host, server_def->port, 1000, error_message); if (tmp->socket == -1) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "connection_create: error while creating connection for %s:%d: %s", server_def->host, server_def->port, *error_message); free(tmp); return NULL; } /* We call get_server_flags to the maxBsonObjectSize data */ mongo_connection_get_server_flags(manager, tmp, (char**) &error_message); return tmp; }
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; mcon_str_ptr_init(messages); /* Create a connection to every of the servers in the seed list */ for (i = 0; i < servers->count; i++) { 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 */ if (!mongo_connection_get_server_flags(manager, tmp, &servers->options, &con_error_message)) { 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_connection_destroy(manager, tmp, MONGO_CLOSE_BROKEN); tmp = NULL; found_connected_server--; } } if (!tmp && !(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 */ } } /* 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 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); con = NULL; } 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) { /* Do authentication if requested */ if (!manager->authenticate(manager, con, options, server, error_message)) { mongo_connection_destroy(manager, con, MONGO_CLOSE_BROKEN); free(hash); return NULL; } /* We call get_server_flags to the maxBsonObjectSize data */ if (!mongo_connection_get_server_flags(manager, con, options, error_message)) { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "server_flags: error while getting the server configuration %s:%d: %s", server->host, server->port, *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); 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; } /* 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 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 (!mongo_connection_get_server_flags(manager, new_con, &servers->options, &con_error_message)) { 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); } }