/* Processes a single option/value pair. * Returns: * -1 if it worked, but the option really shouldn't be used * 0 if it worked * 1 if either name or value was missing * 2 if the option didn't exist * 3 on logic errors */ static int mongo_process_option(mongo_con_manager *manager, mongo_servers *servers, char *name, char *value, char *pos, char **error_message) { char *tmp_name; char *tmp_value; int retval = 0; if (!name || strcmp(name, "") == 0 || (name + 1 == value)) { *error_message = strdup("- Found an empty option name"); mongo_manager_log(manager, MLOG_PARSE, MLOG_WARN, "- Found an empty option name"); return 1; } if (!value) { *error_message = strdup("- Found an empty option value"); mongo_manager_log(manager, MLOG_PARSE, MLOG_WARN, "- Found an empty option value"); return 1; } tmp_name = mcon_strndup(name, value - name - 1); tmp_value = mcon_strndup(value, pos - value); retval = mongo_store_option(manager, servers, tmp_name, tmp_value, error_message); free(tmp_name); free(tmp_value); return retval; }
/* Returns just the host and port from the hash */ char *mongo_server_hash_to_server(char *hash) { char *ptr, *tmp; ptr = strchr(hash, ';'); tmp = mcon_strndup(hash, ptr - hash); return tmp; }
/* Option parser helpers */ static int parse_read_preference_tags(mongo_con_manager *manager, mongo_servers *servers, char *value, char **error_message) { mongo_read_preference_tagset *tmp_ts = calloc(1, sizeof(mongo_read_preference_tagset)); /* format = dc:ny,rack:1 - empty is allowed! */ if (strlen(value) == 0) { mongo_read_preference_add_tagset(&servers->read_pref, tmp_ts); } else { char *start, *end, *colon, *tmp_name, *tmp_value; start = value; while (1) { end = strchr(start, ','); colon = strchr(start, ':'); if (!colon) { int len = strlen(start) + sizeof("Error while trying to parse tags: No separator for ''"); *error_message = malloc(len + 1); snprintf(*error_message, len, "Error while trying to parse tags: No separator for '%s'", start); mongo_read_preference_tagset_dtor(tmp_ts); return 3; } tmp_name = mcon_strndup(start, colon - start); if (end) { tmp_value = mcon_strndup(colon + 1, end - colon - 1); start = end + 1; mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "- Found tag '%s': '%s'", tmp_name, tmp_value); mongo_read_preference_add_tag(tmp_ts, tmp_name, tmp_value); free(tmp_value); free(tmp_name); } else { mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "- Found tag '%s': '%s'", tmp_name, colon + 1); mongo_read_preference_add_tag(tmp_ts, tmp_name, colon + 1); free(tmp_name); break; } } mongo_read_preference_add_tagset(&servers->read_pref, tmp_ts); } return 0; }
/* Helpers */ static void mongo_add_parsed_server_addr(mongo_con_manager *manager, mongo_servers *servers, char *host_start, char *host_end, char *port_start) { mongo_server_def *tmp; tmp = malloc(sizeof(mongo_server_def)); memset(tmp, 0, sizeof(mongo_server_def)); tmp->username = tmp->password = tmp->db = tmp->authdb = NULL; tmp->mechanism = MONGO_AUTH_MECHANISM_MONGODB_CR; /* MONGODB-CR is the default authentication mechanism */ tmp->port = 27017; tmp->host = mcon_strndup(host_start, host_end - host_start); if (port_start) { tmp->port = atoi(port_start); } servers->server[servers->count] = tmp; servers->count++; mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "- Found node: %s:%d", tmp->host, tmp->port); }
int mongo_parse_server_spec(mongo_con_manager *manager, mongo_servers *servers, char *spec, char **error_message) { char *pos; /* Pointer to current parsing position */ char *tmp_user = NULL, *tmp_pass = NULL, *tmp_database = NULL; /* Stores parsed user/password/database to be copied to each server struct */ char *host_start, *host_end, *port_start, *db_start, *db_end, *last_slash; int i; /* Initialisation */ pos = spec; mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "Parsing %s", spec); if (strstr(spec, "mongodb://") == spec) { char *at, *colon; /* mongodb://user:pass@host:port,host:port * ^ */ pos += 10; /* mongodb://user:pass@host:port,host:port * ^ */ at = strchr(pos, '@'); /* mongodb://user:pass@host:port,host:port * ^ */ colon = strchr(pos, ':'); /* check for username:password */ if (at && colon && at - colon > 0) { tmp_user = mcon_strndup(pos, colon - pos); tmp_pass = mcon_strndup(colon + 1, at - (colon + 1)); /* move current * mongodb://user:pass@host:port,host:port * ^ */ pos = at + 1; mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "- Found user '%s' and a password", tmp_user); } } host_start = pos; host_end = NULL; port_start = NULL; last_slash = NULL; /* Now we parse the host part - there are two cases: * 1: mongodb://user:pass@host:port,host:port/database?opt=1 -- TCP/IP * ^ * 2: mongodb://user:pass@/tmp/mongo.sock/database?opt=1 -- Unix Domain sockets * ^ */ if (*pos != '/') { /* TCP/IP: * mongodb://user:pass@host:port,host:port/database?opt=1 -- TCP/IP * ^ */ do { if (*pos == ':') { host_end = pos; port_start = pos + 1; } if (*pos == ',') { if (!host_end) { host_end = pos; } mongo_add_parsed_server_addr(manager, servers, host_start, host_end, port_start); host_start = pos + 1; host_end = port_start = NULL; } if (*pos == '/') { if (!host_end) { host_end = pos; } break; } pos++; } while (*pos != '\0'); /* We are now either at the end of the string, or at / where the dbname * starts. We still have to add the last parser host/port combination * though: */ mongo_add_parsed_server_addr(manager, servers, host_start, host_end ? host_end : pos, port_start); } else if (*pos == '/') { host_start = pos; port_start = "0"; /* Unix Domain Socket * mongodb://user:pass@/tmp/mongo.sock * mongodb://user:pass@/tmp/mongo.sock/?opt=1 * mongodb://user:pass@/tmp/mongo.sock/database?opt=1 */ last_slash = strrchr(pos, '/'); /* The last component of the path *could* be a database name. The rule * is; if the last component has a dot, we use the full string since * "host_start" as host */ if (strchr(last_slash, '.')) { host_end = host_start + strlen(host_start); } else { host_end = last_slash; } pos = host_end; mongo_add_parsed_server_addr(manager, servers, host_start, host_end, port_start); } /* Set the default connection type, we might change this if we encounter * the replicaSet option later */ if (servers->count == 1) { servers->options.con_type = MONGO_CON_TYPE_STANDALONE; mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "- Connection type: STANDALONE"); } else { servers->options.con_type = MONGO_CON_TYPE_MULTIPLE; mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "- Connection type: MULTIPLE"); } /* Check for dbname * mongodb://user:pass@host:port,host:port/dbname?foo=bar * ^ */ db_start = NULL; db_end = spec + strlen(spec); if (*pos == '/') { char *question; question = strchr(pos, '?'); if (question) { if (pos + 1 == question) { db_start = NULL; } else { db_start = pos + 1; db_end = question; } } else { db_start = pos + 1; db_end = spec + strlen(spec); } /* Check for options * mongodb://user:pass@host:port,host:port/dbname?foo=bar * ^ */ if (question) { int retval = -1; retval = mongo_parse_options(manager, servers, question + 1, error_message); if (retval > 0) { free(tmp_user); free(tmp_pass); free(tmp_database); return retval; } } } /* Handling database name */ if (db_start && (db_end != db_start)) { tmp_database = mcon_strndup(db_start, db_end - db_start); mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "- Found database name '%s'", tmp_database); } else if (tmp_user && tmp_pass) { mongo_manager_log(manager, MLOG_PARSE, MLOG_INFO, "- No database name found for an authenticated connection. Using 'admin' as default database"); tmp_database = strdup("admin"); } /* Update all servers with user, password and dbname */ for (i = 0; i < servers->count; i++) { servers->server[i]->username = tmp_user ? strdup(tmp_user) : NULL; servers->server[i]->password = tmp_pass ? strdup(tmp_pass) : NULL; servers->server[i]->db = tmp_database ? strdup(tmp_database) : NULL; } free(tmp_user); free(tmp_pass); free(tmp_database); return 0; }
/* 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; }
/* Split a hash back into its constituent parts */ int mongo_server_split_hash(char *hash, char **host, int *port, char **repl_set_name, char **database, char **username, char **auth_hash, int *pid) { char *ptr, *pid_semi, *username_slash; ptr = hash; /* Find the host */ ptr = strchr(ptr, ':'); if (host) { *host = mcon_strndup(hash, ptr - hash); } /* Find the port */ if (port) { *port = atoi(ptr + 1); } /* Find the replica set name */ ptr = strchr(ptr, ';') + 1; if (ptr[0] != '-') { if (repl_set_name) { *repl_set_name = mcon_strndup(ptr, strchr(ptr, ';') - ptr); } } else { if (repl_set_name) { *repl_set_name = NULL; } } /* Find the database and username */ ptr = strchr(ptr, ';') + 1; if (ptr[0] != '.') { if (database) { *database = mcon_strndup(ptr, strchr(ptr, '/') - ptr); } username_slash = strchr(ptr, '/'); if (username) { *username = mcon_strndup(username_slash + 1, strchr(username_slash + 1, '/') - username_slash - 1); } pid_semi = strchr(ptr, ';'); if (auth_hash) { *auth_hash = mcon_strndup(strchr(username_slash + 1, '/') + 1, pid_semi - strchr(username_slash + 1, '/') - 1); } } else { if (database) { *database = NULL; } if (username) { *username = NULL; } if (auth_hash) { *auth_hash = NULL; } pid_semi = strchr(ptr, ';'); } /* Find the PID */ if (pid) { *pid = atoi(pid_semi + 1); } return 0; }
/** * Sends an ismaster command to the server and returns an array of new * connectable nodes * * Returns: * 0: when an error occurred * 1: when is master was run and worked * 2: when is master wasn't run due to the time-out limit * 3: when it all worked, but we need to remove the seed host (due to its name * not being what the server thought it is) - in that case, the server in * the last argument is changed */ int mongo_connection_ismaster(mongo_con_manager *manager, mongo_connection *con, char **repl_set_name, int *nr_hosts, char ***found_hosts, char **error_message, mongo_server_def *server) { mcon_str *packet; char *data_buffer; char *set = NULL; /* For replicaset in return */ char *hosts, *ptr, *string; unsigned char ismaster = 0, arbiter = 0; char *connected_name, *we_think_we_are; struct timeval now; int retval = 1; gettimeofday(&now, NULL); if ((con->last_ismaster + manager->ismaster_interval) > now.tv_sec) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: skipping: last ran at %ld, now: %ld, time left: %ld", con->last_ismaster, now.tv_sec, con->last_ismaster + manager->ismaster_interval - now.tv_sec); return 2; } mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "ismaster: start"); packet = bson_create_ismaster_packet(con); if (!mongo_connect_send_packet(manager, con, packet, &data_buffer, error_message)) { return 0; } /* Find data fields */ ptr = data_buffer + sizeof(int32_t); /* Skip the length */ /* We find out whether the machine we connected too, is actually the * one we thought we were connecting too */ if (!bson_find_field_as_string(ptr, "me", &connected_name)) { struct mcon_str *tmp; mcon_str_ptr_init(tmp); mcon_str_add(tmp, "Host does not seem to be a replicaset member (", 0); mcon_str_add(tmp, mongo_server_hash_to_server(con->hash), 1); mcon_str_add(tmp, ")", 0); *error_message = strdup(tmp->d); mcon_str_ptr_dtor(tmp); mongo_manager_log(manager, MLOG_CON, MLOG_WARN, *error_message); free(data_buffer); return 0; } we_think_we_are = mongo_server_hash_to_server(con->hash); if (strcmp(connected_name, we_think_we_are) == 0) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: the server name matches what we thought it'd be (%s).", we_think_we_are); } else { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "ismaster: the server name (%s) did not match with what we thought it'd be (%s).", connected_name, we_think_we_are); /* We reset the name as the server responded with a different name than * what we thought it was */ free(server->host); server->host = mcon_strndup(connected_name, strchr(connected_name, ':') - connected_name); server->port = atoi(strchr(connected_name, ':') + 1); retval = 3; } free(we_think_we_are); /* Do replica set name test */ bson_find_field_as_string(ptr, "setName", &set); if (!set) { char *errmsg = NULL; bson_find_field_as_string(ptr, "errmsg", &errmsg); if (errmsg) { *error_message = strdup(errmsg); } else { *error_message = strdup("Not a replicaset member"); } free(data_buffer); return 0; } else if (*repl_set_name) { if (strcmp(set, *repl_set_name) != 0) { struct mcon_str *tmp; mcon_str_ptr_init(tmp); mcon_str_add(tmp, "Host does not match replicaset name. Expected: ", 0); mcon_str_add(tmp, *repl_set_name, 0); mcon_str_add(tmp, "; Found: ", 0); mcon_str_add(tmp, set, 0); *error_message = strdup(tmp->d); mcon_str_ptr_dtor(tmp); free(data_buffer); return 0; } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: the found replicaset name matches the expected one (%s).", set); } } else if (*repl_set_name == NULL) { /* This can happen, in case the replicaset name was not given, but just * bool(true) (or string("1")) in the connection options. */ mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "ismaster: the replicaset name is not set, so we're using %s.", set); *repl_set_name = strdup(set); } /* If the server definition has not set the repl_set_name member yet, set it here */ if (!server->repl_set_name) { server->repl_set_name = strdup(set); } /* Check for flags */ bson_find_field_as_bool(ptr, "ismaster", &ismaster); bson_find_field_as_bool(ptr, "arbiterOnly", &arbiter); /* Find all hosts */ bson_find_field_as_array(ptr, "hosts", &hosts); mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "ismaster: set name: %s, ismaster: %d, is_arbiter: %d", set, ismaster, arbiter); *nr_hosts = 0; ptr = hosts; while (bson_array_find_next_string(&ptr, NULL, &string)) { (*nr_hosts)++; *found_hosts = realloc(*found_hosts, (*nr_hosts) * sizeof(char*)); (*found_hosts)[*nr_hosts-1] = strdup(string); mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "found host: %s", string); } /* Set connection type depending on flags */ if (ismaster) { con->connection_type = MONGO_NODE_PRIMARY; } else if (arbiter) { con->connection_type = MONGO_NODE_ARBITER; } else { con->connection_type = MONGO_NODE_SECONDARY; } /* TODO: case for mongos */ free(data_buffer); con->last_ismaster = now.tv_sec; mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "ismaster: last ran at %ld", con->last_ismaster); return retval; }
/* - 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); } }
/* Sends an ismaster command to the server and returns an array of new * connectable nodes * * Returns: * 0: when an error occurred * 1: when is master was run and worked * 2: when is master wasn't run due to the time-out limit * 3: when it all worked, but we need to remove the seed host (due to its name * not being what the server thought it is) - in that case, the server in * the last argument is changed * 4: when the call worked, but wasn't within our supported wire version range */ int mongo_connection_ismaster(mongo_con_manager *manager, mongo_connection *con, mongo_server_options *options, char **repl_set_name, int *nr_hosts, char ***found_hosts, char **error_message, mongo_server_def *server) { mcon_str *packet; char *data_buffer; int32_t max_bson_size = 0, max_message_size = 0, max_write_batch_size = 0; int32_t min_wire_version = 0, max_wire_version = 0; char *set = NULL; /* For replicaset in return */ char *hosts, *passives = NULL, *ptr, *string; char *msg; /* If set and its value is "isdbgrid", it signals we connected to a mongos */ unsigned char ismaster = 0, secondary = 0, arbiter = 0; char *connected_name, *we_think_we_are; char *tags; struct timeval now; int retval = 1; gettimeofday(&now, NULL); if (((server ? con->last_replcheck : con->last_ismaster) + manager->ismaster_interval) > now.tv_sec) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: skipping: last ran at %ld, now: %ld, time left: %ld", con->last_ismaster, now.tv_sec, con->last_ismaster + manager->ismaster_interval - now.tv_sec); return 2; } mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "ismaster: start"); packet = bson_create_ismaster_packet(con); if (!mongo_connect_send_packet(manager, con, options, packet, &data_buffer, error_message)) { return 0; } /* Find data fields */ ptr = data_buffer + sizeof(int32_t); /* Skip the length */ /* Find [min|max]WireVersion */ if (bson_find_field_as_int32(ptr, "minWireVersion", &min_wire_version)) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: setting minWireVersion to %d", min_wire_version); con->min_wire_version = min_wire_version; } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: can't find minWireVersion, defaulting to %d", con->min_wire_version); } if (bson_find_field_as_int32(ptr, "maxWireVersion", &max_wire_version)) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: setting maxWireVersion to %d", max_wire_version); con->max_wire_version = max_wire_version; } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: can't find maxWireVersion, defaulting to %d", con->max_wire_version); } if (!manager->supports_wire_version(con->min_wire_version, con->max_wire_version, error_message)) { /* Error message set by supports_wire_version */ free(data_buffer); return 4; } /* Find max bson size */ if (bson_find_field_as_int32(ptr, "maxBsonObjectSize", &max_bson_size)) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: setting maxBsonObjectSize to %d", max_bson_size); con->max_bson_size = max_bson_size; } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: can't find maxBsonObjectSize, defaulting to %d", con->max_bson_size); } /* Find max message size */ if (bson_find_field_as_int32(ptr, "maxMessageSizeBytes", &max_message_size)) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: setting maxMessageSizeBytes to %d", max_message_size); con->max_message_size = max_message_size; } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: can't find maxMessageSizeBytes, defaulting to %d", con->max_message_size); } /* Find max batch item size */ if (bson_find_field_as_int32(ptr, "maxWriteBatchSize", &max_write_batch_size)) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: setting maxWriteBatchSize to %d", max_write_batch_size); con->max_write_batch_size = max_write_batch_size; } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: can't find maxWriteBatchSize, defaulting to %d", con->max_write_batch_size); } /* Check for flags */ bson_find_field_as_bool(ptr, "ismaster", &ismaster); bson_find_field_as_bool(ptr, "secondary", &secondary); bson_find_field_as_bool(ptr, "arbiterOnly", &arbiter); bson_find_field_as_string(ptr, "setName", &set); mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "ismaster: set name: %s, ismaster: %d, secondary: %d, is_arbiter: %d", set, ismaster, secondary, arbiter); /* Set connection type depending on flags */ if (ismaster) { /* Find msg and whether it contains "isdbgrid" */ if (bson_find_field_as_string(ptr, "msg", (char**) &msg) && strcmp(msg, "isdbgrid") == 0) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: msg contains 'isdbgrid' - we're connected to a mongos"); con->connection_type = MONGO_NODE_MONGOS; } else if(set) { con->connection_type = MONGO_NODE_PRIMARY; } else { con->connection_type = MONGO_NODE_STANDALONE; } } else if (secondary) { con->connection_type = MONGO_NODE_SECONDARY; } else if (arbiter) { con->connection_type = MONGO_NODE_ARBITER; } else if (!set) { /* If there is no set, we assume this is an "old style" "slave" node */ con->connection_type = MONGO_NODE_SECONDARY; } else { con->connection_type = MONGO_NODE_INVALID; } if (con->connection_type == MONGO_NODE_INVALID) { *error_message = strdup("ismaster: got unknown node type"); free(data_buffer); return 0; } /* Find read preferences tags */ con->tag_count = 0; con->tags = NULL; if (bson_find_field_as_document(ptr, "tags", (char**) &tags)) { char *it, *name, *value; int length; it = tags; while (bson_array_find_next_string(&it, &name, &value)) { con->tags = realloc(con->tags, (con->tag_count + 1) * sizeof(char*)); length = strlen(name) + strlen(value) + 2; con->tags[con->tag_count] = malloc(length); snprintf(con->tags[con->tag_count], length, "%s:%s", name, value); free(name); mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: added tag %s", con->tags[con->tag_count]); con->tag_count++; } } /* If we get passed in a server it means we want to validate this node against it, along with discovery ReplicaSet stuff */ if (!server) { goto done; } /* We find out whether the machine we connected too, is actually the * one we thought we were connecting too */ /* MongoDB 1.8.x doesn't have the "me" field. * The replicaset verification is done next step (setName). */ if (bson_find_field_as_string(ptr, "me", &connected_name)) { we_think_we_are = mongo_server_hash_to_server(con->hash); if (strcmp(connected_name, we_think_we_are) == 0) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: the server name matches what we thought it'd be (%s).", we_think_we_are); } else { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "ismaster: the server name (%s) did not match with what we thought it'd be (%s).", connected_name, we_think_we_are); /* We reset the name as the server responded with a different name than * what we thought it was */ free(server->host); server->host = mcon_strndup(connected_name, strchr(connected_name, ':') - connected_name); server->port = atoi(strchr(connected_name, ':') + 1); retval = 3; } free(we_think_we_are); } else { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "Can't find 'me' in ismaster response, possibly not a replicaset (%s)", mongo_server_hash_to_server(con->hash)); } /* Do replica set name test */ if (!set) { char *errmsg = NULL; bson_find_field_as_string(ptr, "errmsg", &errmsg); if (errmsg) { *error_message = strdup(errmsg); } else { *error_message = strdup("Not a replicaset member"); } free(data_buffer); return 0; } else if (*repl_set_name) { if (strcmp(set, *repl_set_name) != 0) { struct mcon_str *tmp; mcon_str_ptr_init(tmp); mcon_str_add(tmp, "Host does not match replicaset name. Expected: ", 0); mcon_str_add(tmp, *repl_set_name, 0); mcon_str_add(tmp, "; Found: ", 0); mcon_str_add(tmp, set, 0); *error_message = strdup(tmp->d); mcon_str_ptr_dtor(tmp); free(data_buffer); return 0; } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: the found replicaset name matches the expected one (%s).", set); } } else if (*repl_set_name == NULL) { /* This can happen, in case the replicaset name was not given, but just * bool(true) (or string("1")) in the connection options. */ mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "ismaster: the replicaset name is not set, so we're using %s.", set); *repl_set_name = strdup(set); } /* If the server definition has not set the repl_set_name member yet, set it here */ if (!server->repl_set_name) { server->repl_set_name = strdup(set); } /* Find all hosts */ bson_find_field_as_array(ptr, "hosts", &hosts); bson_find_field_as_array(ptr, "passives", &passives); *nr_hosts = 0; /* Iterate over the "hosts" document */ ptr = hosts; while (bson_array_find_next_string(&ptr, NULL, &string)) { (*nr_hosts)++; *found_hosts = realloc(*found_hosts, (*nr_hosts) * sizeof(char*)); (*found_hosts)[*nr_hosts-1] = strdup(string); mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "found host: %s", string); } /* Iterate over the "passives" document (priority=0) */ if (passives) { ptr = passives; while (bson_array_find_next_string(&ptr, NULL, &string)) { (*nr_hosts)++; *found_hosts = realloc(*found_hosts, (*nr_hosts) * sizeof(char*)); (*found_hosts)[*nr_hosts-1] = strdup(string); mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "found host: %s (passive)", string); } } con->last_replcheck = now.tv_sec; done: free(data_buffer); con->last_ismaster = now.tv_sec; mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "ismaster: last ran at %ld", con->last_ismaster); return retval; }
/* Sends an ismaster command to the server and returns an array of new * connectable nodes * * Returns: * 0: when an error occurred * 1: when is master was run and worked * 2: when is master wasn't run due to the time-out limit * 3: when it all worked, but we need to remove the seed host (due to its name * not being what the server thought it is) - in that case, the server in * the last argument is changed */ int mongo_connection_ismaster(mongo_con_manager *manager, mongo_connection *con, mongo_server_options *options, char **repl_set_name, int *nr_hosts, char ***found_hosts, char **error_message, mongo_server_def *server) { mcon_str *packet; char *data_buffer; char *set = NULL; /* For replicaset in return */ char *hosts, *passives = NULL, *ptr, *string; char *connected_name, *we_think_we_are; struct timeval now; int retval = 1; gettimeofday(&now, NULL); if ((con->last_ismaster + manager->ismaster_interval) > now.tv_sec) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: skipping: last ran at %ld, now: %ld, time left: %ld", con->last_ismaster, now.tv_sec, con->last_ismaster + manager->ismaster_interval - now.tv_sec); return 2; } mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "ismaster: start"); packet = bson_create_ismaster_packet(con); if (!mongo_connect_send_packet(manager, con, options, packet, &data_buffer, error_message)) { return 0; } /* Find data fields */ ptr = data_buffer + sizeof(int32_t); /* Skip the length */ /* We find out whether the machine we connected too, is actually the * one we thought we were connecting too */ /* MongoDB 1.8.x doesn't have the "me" field. * The replicaset verification is done next step (setName). */ if (bson_find_field_as_string(ptr, "me", &connected_name)) { we_think_we_are = mongo_server_hash_to_server(con->hash); if (strcmp(connected_name, we_think_we_are) == 0) { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: the server name matches what we thought it'd be (%s).", we_think_we_are); } else { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "ismaster: the server name (%s) did not match with what we thought it'd be (%s).", connected_name, we_think_we_are); /* We reset the name as the server responded with a different name than * what we thought it was */ free(server->host); server->host = mcon_strndup(connected_name, strchr(connected_name, ':') - connected_name); server->port = atoi(strchr(connected_name, ':') + 1); retval = 3; } free(we_think_we_are); } else { mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "Can't find 'me' in ismaster response, possibly not a replicaset (%s)", mongo_server_hash_to_server(con->hash)); } /* Do replica set name test */ bson_find_field_as_string(ptr, "setName", &set); if (!set) { char *errmsg = NULL; bson_find_field_as_string(ptr, "errmsg", &errmsg); if (errmsg) { *error_message = strdup(errmsg); } else { *error_message = strdup("Not a replicaset member"); } free(data_buffer); return 0; } else if (*repl_set_name) { if (strcmp(set, *repl_set_name) != 0) { struct mcon_str *tmp; mcon_str_ptr_init(tmp); mcon_str_add(tmp, "Host does not match replicaset name. Expected: ", 0); mcon_str_add(tmp, *repl_set_name, 0); mcon_str_add(tmp, "; Found: ", 0); mcon_str_add(tmp, set, 0); *error_message = strdup(tmp->d); mcon_str_ptr_dtor(tmp); free(data_buffer); return 0; } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "ismaster: the found replicaset name matches the expected one (%s).", set); } } else if (*repl_set_name == NULL) { /* This can happen, in case the replicaset name was not given, but just * bool(true) (or string("1")) in the connection options. */ mongo_manager_log(manager, MLOG_CON, MLOG_WARN, "ismaster: the replicaset name is not set, so we're using %s.", set); *repl_set_name = strdup(set); } /* If the server definition has not set the repl_set_name member yet, set it here */ if (!server->repl_set_name) { server->repl_set_name = strdup(set); } /* Find all hosts */ bson_find_field_as_array(ptr, "hosts", &hosts); bson_find_field_as_array(ptr, "passives", &passives); *nr_hosts = 0; /* Iterate over the "hosts" document */ ptr = hosts; while (bson_array_find_next_string(&ptr, NULL, &string)) { (*nr_hosts)++; *found_hosts = (char**) realloc(*found_hosts, (*nr_hosts) * sizeof(char*)); (*found_hosts)[*nr_hosts-1] = strdup(string); mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "found host: %s", string); } /* Iterate over the "passives" document (priority=0) */ if (passives) { ptr = passives; while (bson_array_find_next_string(&ptr, NULL, &string)) { (*nr_hosts)++; *found_hosts = (char**) realloc(*found_hosts, (*nr_hosts) * sizeof(char*)); (*found_hosts)[*nr_hosts-1] = strdup(string); mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "found host: %s (passive)", string); } } free(data_buffer); con->last_ismaster = now.tv_sec; mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "ismaster: last ran at %ld", con->last_ismaster); return retval; }