diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d45744fd7b..d69c0e4aede 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -222,6 +222,8 @@ NOT tested, enable them with: * `MONGOC_TEST_DNS_LOADBALANCED=on` assumes a load balanced sharded cluster is running with mongoses on ports 27017 and 27018 and TLS enabled. The load balancer can be listening on any port. +* `MONGOC_TEST_DNS_SRV_POLLING=on` assumes a sharded cluster is running with mongoses on ports 27017, 27018, 27019, and 27020 and TLS enabled. + The mock server timeout threshold for future functions can be set with: * `MONGOC_TEST_FUTURE_TIMEOUT_MS=` diff --git a/src/libmongoc/doc/mongoc_uri_t.rst b/src/libmongoc/doc/mongoc_uri_t.rst index 921079546c3..3daba049ef9 100644 --- a/src/libmongoc/doc/mongoc_uri_t.rst +++ b/src/libmongoc/doc/mongoc_uri_t.rst @@ -101,6 +101,7 @@ MONGOC_URI_SOCKETTIMEOUTMS sockettimeoutms 300 MONGOC_URI_REPLICASET replicaset Empty (no replicaset) The name of the Replica Set that the driver should connect to. MONGOC_URI_ZLIBCOMPRESSIONLEVEL zlibcompressionlevel -1 When the MONGOC_URI_COMPRESSORS includes "zlib" this options configures the zlib compression level, when the zlib compressor is used to compress client data. MONGOC_URI_LOADBALANCED loadbalanced false If true, this indicates the driver is connecting to a MongoDB cluster behind a load balancer. +MONGOC_URI_SRVMAXHOSTS srvmaxhosts 0 If zero, the number of hosts in DNS results is unlimited. If greater than zero, the number of hosts in DNS results is limited to being less than or equal to the given value. ========================================== ================================= ================================= ============================================================================================================================================================================================================================================ Setting any of the \*timeoutMS options above to ``0`` will be interpreted as "use the default value". diff --git a/src/libmongoc/src/mongoc/mongoc-host-list-private.h b/src/libmongoc/src/mongoc/mongoc-host-list-private.h index b830a7e546f..a64cec5dfbd 100644 --- a/src/libmongoc/src/mongoc/mongoc-host-list-private.h +++ b/src/libmongoc/src/mongoc/mongoc-host-list-private.h @@ -53,7 +53,7 @@ _mongoc_host_list_from_hostport_with_err (mongoc_host_list_t *host_list, bson_error_t *error); int -_mongoc_host_list_length (mongoc_host_list_t *list); +_mongoc_host_list_length (const mongoc_host_list_t *list); bool _mongoc_host_list_compare_one (const mongoc_host_list_t *host_a, diff --git a/src/libmongoc/src/mongoc/mongoc-host-list.c b/src/libmongoc/src/mongoc/mongoc-host-list.c index 2d17f8460d6..becef5cbc66 100644 --- a/src/libmongoc/src/mongoc/mongoc-host-list.c +++ b/src/libmongoc/src/mongoc/mongoc-host-list.c @@ -131,9 +131,9 @@ _mongoc_host_list_copy_all (const mongoc_host_list_t *src) } int -_mongoc_host_list_length (mongoc_host_list_t *list) +_mongoc_host_list_length (const mongoc_host_list_t *list) { - mongoc_host_list_t *tmp; + const mongoc_host_list_t *tmp; int counter = 0; tmp = list; @@ -240,8 +240,7 @@ _mongoc_host_list_from_string_with_err (mongoc_host_list_t *link_, MONGOC_ERROR_COMMAND, MONGOC_ERROR_COMMAND_INVALID_ARG, "If present, port should immediately follow the \"]\"" - "in an IPv6 address" - ); + "in an IPv6 address"); return false; } @@ -259,8 +258,7 @@ _mongoc_host_list_from_string_with_err (mongoc_host_list_t *link_, bson_set_error (error, MONGOC_ERROR_COMMAND, MONGOC_ERROR_COMMAND_INVALID_ARG, - "Missing matching bracket \"[\"" - ); + "Missing matching bracket \"[\""); return false; } @@ -278,8 +276,7 @@ _mongoc_host_list_from_string_with_err (mongoc_host_list_t *link_, bson_set_error (error, MONGOC_ERROR_COMMAND, MONGOC_ERROR_COMMAND_INVALID_ARG, - "Bad address, \":\" should not be first character" - ); + "Bad address, \":\" should not be first character"); return false; } @@ -287,8 +284,7 @@ _mongoc_host_list_from_string_with_err (mongoc_host_list_t *link_, bson_set_error (error, MONGOC_ERROR_COMMAND, MONGOC_ERROR_COMMAND_INVALID_ARG, - "Port could not be parsed" - ); + "Port could not be parsed"); return false; } diff --git a/src/libmongoc/src/mongoc/mongoc-topology-description-private.h b/src/libmongoc/src/mongoc/mongoc-topology-description-private.h index e5fb572ed57..2b82c432650 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology-description-private.h +++ b/src/libmongoc/src/mongoc/mongoc-topology-description-private.h @@ -47,6 +47,7 @@ struct _mongoc_topology_description_t { bson_oid_t max_election_id; bson_error_t compatibility_error; uint32_t max_server_id; + int32_t max_hosts; /* srvMaxHosts */ bool stale; unsigned int rand_seed; diff --git a/src/libmongoc/src/mongoc/mongoc-topology-description.c b/src/libmongoc/src/mongoc/mongoc-topology-description.c index c121fcdb3f8..b83eee5e138 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology-description.c +++ b/src/libmongoc/src/mongoc/mongoc-topology-description.c @@ -150,6 +150,7 @@ _mongoc_topology_description_copy_to (const mongoc_topology_description_t *src, &src->compatibility_error, sizeof (bson_error_t)); dst->max_server_id = src->max_server_id; + dst->max_hosts = src->max_hosts; dst->stale = src->stale; memcpy (&dst->apm_callbacks, &src->apm_callbacks, @@ -2282,12 +2283,35 @@ mongoc_topology_description_get_servers ( return sds; } +typedef struct { + mongoc_host_list_t *host_list; + size_t num_missing; +} _count_num_hosts_to_remove_ctx_t; + +static bool +_count_num_hosts_to_remove (void *sd_void, void *ctx_void) +{ + mongoc_server_description_t *sd; + _count_num_hosts_to_remove_ctx_t *ctx; + mongoc_host_list_t *host_list; + + sd = sd_void; + ctx = ctx_void; + host_list = ctx->host_list; + + if (!_mongoc_host_list_contains_one (host_list, &sd->host)) { + ++ctx->num_missing; + } + + return true; +} + typedef struct { mongoc_host_list_t *host_list; mongoc_topology_description_t *td; } _remove_if_not_in_host_list_ctx_t; -bool +static bool _remove_if_not_in_host_list_cb (void *sd_void, void *ctx_void) { _remove_if_not_in_host_list_ctx_t *ctx; @@ -2311,19 +2335,93 @@ void mongoc_topology_description_reconcile (mongoc_topology_description_t *td, mongoc_host_list_t *host_list) { - mongoc_host_list_t *host; - _remove_if_not_in_host_list_ctx_t ctx; + mongoc_set_t *servers; + size_t host_list_length; + size_t num_missing; + + BSON_ASSERT_PARAM (td); + + servers = mc_tpld_servers (td); + host_list_length = _mongoc_host_list_length (host_list); + + /* Avoid removing all servers in topology, even temporarily, by deferring + * actual removal until after new hosts have been added. */ + { + _count_num_hosts_to_remove_ctx_t ctx; + + ctx.host_list = host_list; + ctx.num_missing = 0u; + + mongoc_set_for_each (servers, _count_num_hosts_to_remove, &ctx); + + num_missing = ctx.num_missing; + } + + /* Polling SRV Records for mongos Discovery Spec: If srvMaxHosts is zero or + * greater than or equal to the number of valid hosts, each valid new host + * MUST be added to the topology as Unknown. */ + if (td->max_hosts == 0 || (size_t) td->max_hosts >= host_list_length) { + mongoc_host_list_t *host; + + LL_FOREACH (host_list, host) + { + /* "add" is really an "upsert" */ + mongoc_topology_description_add_server (td, host->host_and_port, NULL); + } + } + + /* Polling SRV Records for mongos Discovery Spec: If srvMaxHosts is greater + * than zero and less than the number of valid hosts, valid new hosts MUST be + * randomly selected and added to the topology as Unknown until the topology + * has srvMaxHosts hosts. */ + else { + const size_t max_with_missing = td->max_hosts + num_missing; + + size_t idx = 0u; + size_t hl_array_size = 0u; - LL_FOREACH (host_list, host) + /* Polling SRV Records for mongos Discovery Spec: Drivers MUST use the + * same randomization algorithm as they do for initial selection. + * Do not limit size of results yet (pass host_list_length) as we want to + * update any existing hosts in the topology, but add new hosts. + */ + const mongoc_host_list_t *const *hl_array = _mongoc_apply_srv_max_hosts ( + host_list, host_list_length, &hl_array_size); + + for (idx = 0u; + servers->items_len < max_with_missing && idx < hl_array_size; + ++idx) { + const mongoc_host_list_t *const elem = hl_array[idx]; + + /* "add" is really an "upsert" */ + mongoc_topology_description_add_server (td, elem->host_and_port, NULL); + } + + /* There should not be a situation where all items in the valid host list + * were traversed without the number of hosts in the topology reaching + * srvMaxHosts. */ + BSON_ASSERT (servers->items_len == max_with_missing); + + bson_free ((void *) hl_array); + } + + /* Polling SRV Records for mongos Discovery Spec: For all verified host + * names, as returned through the DNS SRV query, the driver MUST remove + * all hosts that are part of the topology, but are no longer in the + * returned set of valid hosts. */ { - /* "add" is really an "upsert" */ - mongoc_topology_description_add_server (td, host->host_and_port, NULL); + _remove_if_not_in_host_list_ctx_t ctx; + + ctx.host_list = host_list; + ctx.td = td; + + mongoc_set_for_each (servers, _remove_if_not_in_host_list_cb, &ctx); } - ctx.host_list = host_list; - ctx.td = td; - mongoc_set_for_each ( - mc_tpld_servers (td), _remove_if_not_in_host_list_cb, &ctx); + /* At this point, the number of hosts in the host list should not exceed + * srvMaxHosts. */ + BSON_ASSERT (td->max_hosts == 0 || + servers->items_len <= (size_t) td->max_hosts); } diff --git a/src/libmongoc/src/mongoc/mongoc-topology-private.h b/src/libmongoc/src/mongoc/mongoc-topology-private.h index d7168540980..f674dd53928 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology-private.h +++ b/src/libmongoc/src/mongoc/mongoc-topology-private.h @@ -588,4 +588,16 @@ _mongoc_topology_invalidate_server (mongoc_topology_t *td, uint32_t server_id) mc_tpld_modify_commit (tdmod); } +/* Return an array view to `max_hosts` or fewer elements of `hl`, or NULL if + * `hl` is empty. The size of the returned array is written to `hl_array_size` + * even if `hl` is empty. + * + * The returned array must be freed with `bson_free()`. The elements of the + * array must not be freed, as they are still owned by `hl`. + */ +const mongoc_host_list_t ** +_mongoc_apply_srv_max_hosts (const mongoc_host_list_t *hl, + int32_t max_hosts, + size_t *hl_array_size); + #endif diff --git a/src/libmongoc/src/mongoc/mongoc-topology.c b/src/libmongoc/src/mongoc/mongoc-topology.c index 0b6e2b73794..2a1b1a14f65 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology.c +++ b/src/libmongoc/src/mongoc/mongoc-topology.c @@ -35,6 +35,8 @@ #include "utlist.h" +#include + static void _topology_collect_errors (const mongoc_topology_description_t *topology, bson_error_t *error_out); @@ -263,6 +265,59 @@ _tpld_destroy_and_free (void *tpl_descr) mongoc_topology_description_destroy (td); } +const mongoc_host_list_t ** +_mongoc_apply_srv_max_hosts (const mongoc_host_list_t *hl, + const int32_t max_hosts, + size_t *const hl_array_size) +{ + size_t hl_size; + size_t idx; + const mongoc_host_list_t **hl_array; + + BSON_ASSERT (max_hosts >= 0); + BSON_ASSERT_PARAM (hl_array_size); + + hl_size = (size_t) _mongoc_host_list_length (hl); + + if (hl_size == 0) { + *hl_array_size = 0; + return NULL; + } + + hl_array = bson_malloc (hl_size * sizeof (mongoc_host_list_t *)); + + for (idx = 0u; hl; hl = hl->next) { + hl_array[idx++] = hl; + } + + if (max_hosts == 0 || /* Unlimited. */ + hl_size == 1u || /* Trivial case. */ + hl_size <= (size_t) max_hosts /* Already satisfies limit. */ + ) { + /* No random shuffle or selection required. */ + *hl_array_size = hl_size; + return hl_array; + } + + /* Initial DNS Seedlist Discovery Spec: If `srvMaxHosts` is greater than zero + * and less than the number of hosts in the DNS result, the driver MUST + * randomly select that many hosts and use them to populate the seedlist. + * Drivers SHOULD use the `Fisher-Yates shuffle` for randomization. */ + for (idx = hl_size - 1u; idx > 0u; --idx) { + /* 0 <= swap_pos <= idx */ + const size_t swap_pos = + _mongoc_rand_size_t (0u, idx, _mongoc_simple_rand_size_t); + + const mongoc_host_list_t *tmp = hl_array[swap_pos]; + hl_array[swap_pos] = hl_array[idx]; + hl_array[idx] = tmp; + } + + *hl_array_size = max_hosts; + + return hl_array; +} + /* *------------------------------------------------------------------------- * @@ -285,9 +340,9 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded) int64_t heartbeat; mongoc_topology_t *topology; mongoc_topology_description_type_t init_type; + mongoc_topology_description_t *td; const char *service; char *prefixed_service; - uint32_t id; const mongoc_host_list_t *hl; mongoc_rr_data_t rr_data; bool has_directconnection; @@ -322,11 +377,10 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded) topology->_shared_descr_._sptr_ = mongoc_shared_ptr_create ( bson_malloc0 (sizeof (mongoc_topology_description_t)), _tpld_destroy_and_free); - mongoc_topology_description_init (mc_tpld_unsafe_get_mutable (topology), - heartbeat); + td = mc_tpld_unsafe_get_mutable (topology); + mongoc_topology_description_init (td, heartbeat); - mc_tpld_unsafe_get_mutable (topology)->set_name = - bson_strdup (mongoc_uri_get_replica_set (uri)); + td->set_name = bson_strdup (mongoc_uri_get_replica_set (uri)); topology->uri = mongoc_uri_copy (uri); topology->cse_state = MONGOC_CSE_DISABLED; @@ -461,6 +515,13 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded) topology->valid = false; } + if (!mongoc_uri_finalize_srv (topology->uri, &topology->scanner->error)) { + topology->valid = false; + } + + td->max_hosts = + mongoc_uri_get_option_as_int32 (uri, MONGOC_URI_SRVMAXHOSTS, 0); + /* * Set topology type from URI: * + if directConnection=true @@ -517,7 +578,7 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded) } } - mc_tpld_unsafe_get_mutable (topology)->type = init_type; + td->type = init_type; if (!topology->single_threaded) { topology->server_monitors = mongoc_set_new (1, NULL, NULL); @@ -533,16 +594,27 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded) return topology; } - while (hl) { - mongoc_topology_description_add_server ( - mc_tpld_unsafe_get_mutable (topology), hl->host_and_port, &id); - mongoc_topology_scanner_add (topology->scanner, hl, id, false); + { + size_t idx = 0u; + size_t hl_array_size = 0u; + uint32_t id = 0u; + + const mongoc_host_list_t *const *hl_array = + _mongoc_apply_srv_max_hosts (hl, td->max_hosts, &hl_array_size); + + for (idx = 0u; idx < hl_array_size; ++idx) { + const mongoc_host_list_t *const elem = hl_array[idx]; - hl = hl->next; + mongoc_topology_description_add_server (td, elem->host_and_port, &id); + mongoc_topology_scanner_add (topology->scanner, elem, id, false); + } + + bson_free ((void *) hl_array); } return topology; } + /* *------------------------------------------------------------------------- * diff --git a/src/libmongoc/src/mongoc/mongoc-uri-private.h b/src/libmongoc/src/mongoc/mongoc-uri-private.h index c75c206fc4d..9a4565e5bc4 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri-private.h +++ b/src/libmongoc/src/mongoc/mongoc-uri-private.h @@ -88,6 +88,16 @@ _mongoc_uri_init_scram (const mongoc_uri_t *uri, bool mongoc_uri_finalize_loadbalanced (const mongoc_uri_t *uri, bson_error_t *error); +/* mongoc_uri_finalize_srv validates constraints for SRV URI options. + * For example, it is invalid to have loadBalanced=true with srvMaxHosts=1. + * This is expected to be called whenever URI options may change (e.g. + * parsing a new URI or applying TXT records). + * Returns false and sets @error on failure. + * Returns true and does not modify @error on success. + */ +bool +mongoc_uri_finalize_srv (const mongoc_uri_t *uri, bson_error_t *error); + BSON_END_DECLS diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index e1d35ed6646..5a14bd1ed73 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -735,7 +735,8 @@ mongoc_uri_option_is_int32 (const char *key) !strcasecmp (key, MONGOC_URI_MAXIDLETIMEMS) || !strcasecmp (key, MONGOC_URI_WAITQUEUEMULTIPLE) || !strcasecmp (key, MONGOC_URI_WAITQUEUETIMEOUTMS) || - !strcasecmp (key, MONGOC_URI_ZLIBCOMPRESSIONLEVEL); + !strcasecmp (key, MONGOC_URI_ZLIBCOMPRESSIONLEVEL) || + !strcasecmp (key, MONGOC_URI_SRVMAXHOSTS); } bool @@ -1132,7 +1133,8 @@ mongoc_uri_apply_options (mongoc_uri_t *uri, MONGOC_ERROR_COMMAND, MONGOC_ERROR_COMMAND_INVALID_ARG, "Failed to set %s to %d", - canon, bval); + canon, + bval); return false; } } else { @@ -1571,6 +1573,10 @@ mongoc_uri_parse (mongoc_uri_t *uri, const char *str, bson_error_t *error) goto error; } + if (!mongoc_uri_finalize_srv (uri, error)) { + goto error; + } + bson_free (before_slash); return true; @@ -2316,8 +2322,7 @@ mongoc_uri_unescape (const char *escaped_string) #else (1 != sscanf (&ptr[1], "%02x", &hex)) #endif - || - 0 == hex) { + || 0 == hex) { bson_string_free (str, true); MONGOC_WARNING ("Invalid %% escape sequence"); return NULL; @@ -3118,10 +3123,14 @@ mongoc_uri_finalize_loadbalanced (const mongoc_uri_t *uri, bson_error_t *error) return true; } - if (!uri->hosts || (uri->hosts && uri->hosts->next)) { - MONGOC_URI_ERROR (error, - "URI with \"%s\" enabled must contain exactly one host", - MONGOC_URI_LOADBALANCED); + /* Load Balancer Spec: When `loadBalanced=true` is provided in the connection + * string, the driver MUST throw an exception if the connection string + * contains more than one host/port. */ + if (uri->hosts && uri->hosts->next) { + MONGOC_URI_ERROR ( + error, + "URI with \"%s\" enabled must not contain more than one host", + MONGOC_URI_LOADBALANCED); return false; } @@ -3147,3 +3156,61 @@ mongoc_uri_finalize_loadbalanced (const mongoc_uri_t *uri, bson_error_t *error) return true; } + +bool +mongoc_uri_finalize_srv (const mongoc_uri_t *uri, bson_error_t *error) +{ + /* Initial DNS Seedlist Discovery Spec: The driver MUST report an error if + * either the `srvServiceName` or `srvMaxHosts` URI options are specified + * with a non-SRV URI */ + if (!uri->is_srv && mongoc_uri_has_option (uri, MONGOC_URI_SRVMAXHOSTS)) { + MONGOC_URI_ERROR (error, + "%s must not be specified with a non-SRV URI", + MONGOC_URI_SRVMAXHOSTS); + return false; + } + + if (uri->is_srv) { + const int32_t max_hosts = + mongoc_uri_get_option_as_int32 (uri, MONGOC_URI_SRVMAXHOSTS, 0); + + /* Initial DNS Seedless Discovery Spec: This option requires a + * non-negative integer and defaults to zero (i.e. no limit). */ + if (max_hosts < 0) { + MONGOC_URI_ERROR (error, + "%s is required to be a non-negative integer, but " + "has value %" PRId32, + MONGOC_URI_SRVMAXHOSTS, + max_hosts); + return false; + } + + if (max_hosts > 0) { + /* Initial DNS Seedless Discovery spec: If srvMaxHosts is a positive + * integer, the driver MUST throw an error if the connection string + * contains a `replicaSet` option. */ + if (mongoc_uri_has_option (uri, MONGOC_URI_REPLICASET)) { + MONGOC_URI_ERROR (error, + "%s must not be specified with %s", + MONGOC_URI_SRVMAXHOSTS, + MONGOC_URI_REPLICASET); + return false; + } + + /* Initial DNS Seedless Discovery Spec: If srvMaxHosts is a positive + * integer, the driver MUST throw an error if the connection string + * contains a `loadBalanced` option with a value of `true`. + */ + if (mongoc_uri_get_option_as_bool ( + uri, MONGOC_URI_LOADBALANCED, false)) { + MONGOC_URI_ERROR (error, + "%s must not be specified with %s=true", + MONGOC_URI_SRVMAXHOSTS, + MONGOC_URI_LOADBALANCED); + return false; + } + } + } + + return true; +} diff --git a/src/libmongoc/src/mongoc/mongoc-uri.h b/src/libmongoc/src/mongoc/mongoc-uri.h index 96d8711ef05..2a5246dd884 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.h +++ b/src/libmongoc/src/mongoc/mongoc-uri.h @@ -62,6 +62,7 @@ #define MONGOC_URI_SLAVEOK "slaveok" #define MONGOC_URI_SOCKETCHECKINTERVALMS "socketcheckintervalms" #define MONGOC_URI_SOCKETTIMEOUTMS "sockettimeoutms" +#define MONGOC_URI_SRVMAXHOSTS "srvMaxHosts" #define MONGOC_URI_TLS "tls" #define MONGOC_URI_TLSCERTIFICATEKEYFILE "tlscertificatekeyfile" #define MONGOC_URI_TLSCERTIFICATEKEYFILEPASSWORD "tlscertificatekeyfilepassword" diff --git a/src/libmongoc/src/mongoc/mongoc-util-private.h b/src/libmongoc/src/mongoc/mongoc-util-private.h index 1a1bc522801..b04fd9737df 100644 --- a/src/libmongoc/src/mongoc/mongoc-util-private.h +++ b/src/libmongoc/src/mongoc/mongoc-util-private.h @@ -26,6 +26,8 @@ #include #endif +#include + /* string comparison functions for Windows */ #ifdef _WIN32 #define strcasecmp _stricmp @@ -157,6 +159,78 @@ _mongoc_document_is_pipeline (const bson_t *document); char * _mongoc_getenv (const char *name); +/* Returns a uniformly-distributed uint32_t generated using + * `_mongoc_rand_bytes()` if a source of cryptographic randomness is available + * (defined only if `MONGOC_ENABLE_CRYPTO` is defined). + */ +uint32_t +_mongoc_crypto_rand_uint32_t (void); + +/* Returns a uniformly-distributed uint64_t generated using + * `_mongoc_rand_bytes()` if a source of cryptographic randomness is available + * (defined only if `MONGOC_ENABLE_CRYPTO` is defined). + */ +uint64_t +_mongoc_crypto_rand_uint64_t (void); + +/* Returns a uniformly-distributed size_t generated using + * `_mongoc_rand_bytes()` if a source of cryptographic randomness is available + * (defined only if `MONGOC_ENABLE_CRYPTO` is defined). + */ +size_t +_mongoc_crypto_rand_size_t (void); + +/* Returns a uniformly-distributed random uint32_t generated using `rand()`. + * Note: may invoke `srand()`, which may not be thread-safe. Concurrent calls to + * `_mongoc_simple_rand_*()` functions, however, is thread-safe. */ +uint32_t +_mongoc_simple_rand_uint32_t (void); + +/* Returns a uniformly-distributed random uint64_t generated using `rand()`. + * Note: may invoke `srand()`, which may not be thread-safe. Concurrent calls to + * `_mongoc_simple_rand_*()` functions, however, is thread-safe. */ +uint64_t +_mongoc_simple_rand_uint64_t (void); + +/* Returns a uniformly-distributed random size_t generated using `rand()`. + * Note: may invoke `srand()`, which may not be thread-safe. Concurrent calls to + * `_mongoc_simple_rand_*()` functions, however, is thread-safe. */ +size_t +_mongoc_simple_rand_size_t (void); + +/* Returns a uniformly-distributed random integer in the range [min, max]. + * + * The size of the range [min, max] must not equal the size of the representable + * range of uint32_t (`min == 0 && max == UINT32_MAX` must not be true). + * + * The generator `rand` must return a random integer uniformly distributed in + * the full range of representable values of uint32_t. + */ +uint32_t +_mongoc_rand_uint32_t (uint32_t min, uint32_t max, uint32_t (*rand) (void)); + +/* Returns a uniformly-distributed random integer in the range [min, max]. + * + * The size of the range [min, max] must not equal the size of the representable + * range of uint64_t (`min == 0 && max == UINT64_MAX` must not be true). + * + * The generator `rand` must return a random integer uniformly distributed in + * the full range of representable values of uint64_t. + */ +uint64_t +_mongoc_rand_uint64_t (uint64_t min, uint64_t max, uint64_t (*rand) (void)); + +/* Returns a uniformly-distributed random integer in the range [min, max]. + * + * The size of the range [min, max] must not equal the size of the representable + * range of size_t (`min == 0 && max == SIZE_MAX` must not be true). + * + * The generator `rand` must return a random integer uniformly distributed in + * the full range of representable values of size_t. + */ +size_t +_mongoc_rand_size_t (size_t min, size_t max, size_t (*rand) (void)); + BSON_END_DECLS #endif /* MONGOC_UTIL_PRIVATE_H */ diff --git a/src/libmongoc/src/mongoc/mongoc-util.c b/src/libmongoc/src/mongoc/mongoc-util.c index 8d08c59ecc2..952a3d88813 100644 --- a/src/libmongoc/src/mongoc/mongoc-util.c +++ b/src/libmongoc/src/mongoc/mongoc-util.c @@ -20,7 +20,11 @@ #include +#include "bson/bson.h" + #include "common-md5-private.h" +#include "common-thread-private.h" +#include "mongoc-rand-private.h" #include "mongoc-util-private.h" #include "mongoc-client.h" #include "mongoc-client-session-private.h" @@ -626,3 +630,192 @@ _mongoc_getenv (const char *name) #endif } + +/* Nearly Divisionless (Algorithm 5): https://arxiv.org/abs/1805.10941 */ +static uint32_t +_mongoc_rand_nduid32 (uint32_t s, uint32_t (*rand32) (void)) +{ + const uint64_t limit = UINT32_MAX; /* 2^L */ + uint64_t x, m, l; + + x = rand32 (); + m = x * s; + l = m % limit; + + if (l < s) { + const uint64_t t = (limit - s) % s; + + while (l < t) { + x = rand32 (); + m = x * s; + l = m % limit; + } + } + + return (uint32_t) (m / limit); +} + +/* Java Algorithm (Algorithm 4): https://arxiv.org/abs/1805.10941 + * The 64-bit version of the nearly divisionless algorithm requires 128-bit + * integer arithmetic. Instead of trying to deal with cross-platform support for + * `__int128`, fallback to using the Java algorithm for 64-bit instead. */ +static uint64_t +_mongoc_rand_java64 (uint64_t s, uint64_t (*rand64) (void)) +{ + const uint64_t limit = UINT64_MAX; /* 2^L */ + uint64_t x, r; + + x = rand64 (); + r = x % s; + + while ((x - r) > (limit - s)) { + x = rand64 (); + r = x % s; + } + + return r; +} + +#if defined(MONGOC_ENABLE_CRYPTO) + +uint32_t +_mongoc_crypto_rand_uint32_t (void) +{ + uint32_t res; + + (void) _mongoc_rand_bytes ((uint8_t *) &res, sizeof (res)); + + return res; +} + +uint64_t +_mongoc_crypto_rand_uint64_t (void) +{ + uint64_t res; + + (void) _mongoc_rand_bytes ((uint8_t *) &res, sizeof (res)); + + return res; +} + +size_t +_mongoc_crypto_rand_size_t (void) +{ + size_t res; + + (void) _mongoc_rand_bytes ((uint8_t *) &res, sizeof (res)); + + return res; +} + +#endif /* defined(MONGOC_ENABLE_CRYPTO) */ + +static BSON_ONCE_FUN (_mongoc_simple_rand_init) +{ + struct timeval tv; + unsigned int seed = 0; + + bson_gettimeofday (&tv); + + seed ^= (unsigned int) tv.tv_sec; + seed ^= (unsigned int) tv.tv_usec; + + srand (seed); + + BSON_ONCE_RETURN; +} + +static bson_once_t _mongoc_simple_rand_init_once = BSON_ONCE_INIT; + +uint32_t +_mongoc_simple_rand_uint32_t (void) +{ + bson_once (&_mongoc_simple_rand_init_once, _mongoc_simple_rand_init); + + /* Ensure *all* bits are random, as RAND_MAX is only required to be at least + * 32767 (2^15). */ + return (((uint32_t) rand () & 0x7FFFu) << 0u) | + (((uint32_t) rand () & 0x7FFFu) << 15u) | + (((uint32_t) rand () & 0x0003u) << 30u); +} + +uint64_t +_mongoc_simple_rand_uint64_t (void) +{ + bson_once (&_mongoc_simple_rand_init_once, _mongoc_simple_rand_init); + + /* Ensure *all* bits are random, as RAND_MAX is only required to be at least + * 32767 (2^15). */ + return (((uint64_t) rand () & 0x7FFFu) << 0u) | + (((uint64_t) rand () & 0x7FFFu) << 15u) | + (((uint64_t) rand () & 0x7FFFu) << 30u) | + (((uint64_t) rand () & 0x7FFFu) << 45u) | + (((uint64_t) rand () & 0x0003u) << 60u); +} + +uint32_t +_mongoc_rand_uint32_t (uint32_t min, uint32_t max, uint32_t (*rand) (void)) +{ + BSON_ASSERT (min <= max); + BSON_ASSERT (min != 0u || max != UINT32_MAX); + + return _mongoc_rand_nduid32 (max - min + 1u, rand) + min; +} + +uint64_t +_mongoc_rand_uint64_t (uint64_t min, uint64_t max, uint64_t (*rand) (void)) +{ + BSON_ASSERT (min <= max); + BSON_ASSERT (min != 0u || max != UINT64_MAX); + + return _mongoc_rand_java64 (max - min + 1u, rand) + min; +} + +#if SIZE_MAX == UINT64_MAX + +BSON_STATIC_ASSERT2 (_mongoc_simple_rand_size_t, + sizeof (size_t) == sizeof (uint64_t)); + +size_t +_mongoc_simple_rand_size_t (void) +{ + return (size_t) _mongoc_simple_rand_uint64_t (); +} + +size_t +_mongoc_rand_size_t (size_t min, size_t max, size_t (*rand) (void)) +{ + BSON_ASSERT (min <= max); + BSON_ASSERT (min != 0u || max != UINT64_MAX); + + return _mongoc_rand_java64 (max - min + 1u, (uint64_t (*) (void)) rand) + + min; +} + +#elif SIZE_MAX == UINT32_MAX + +BSON_STATIC_ASSERT2 (_mongoc_simple_rand_size_t, + sizeof (size_t) == sizeof (uint32_t)); + +size_t +_mongoc_simple_rand_size_t (void) +{ + return (size_t) _mongoc_simple_rand_uint32_t (); +} + +size_t +_mongoc_rand_size_t (size_t min, size_t max, size_t (*rand) (void)) +{ + BSON_ASSERT (min <= max); + BSON_ASSERT (min != 0u || max != UINT32_MAX); + + return _mongoc_rand_nduid32 (max - min + 1u, (uint32_t (*) (void)) rand) + + min; +} + +#else + +#error \ + "Implementation of _mongoc_simple_rand_size_t() requires size_t be exactly 32-bit or 64-bit" + +#endif diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true-txt.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true-txt.json new file mode 100644 index 00000000000..a7600a8a7b1 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true-txt.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test20.test.build.10gen.cc/?srvMaxHosts=1", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because positive integer for srvMaxHosts conflicts with loadBalanced=true (TXT)" +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true.json new file mode 100644 index 00000000000..d03a174b1eb --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test3.test.build.10gen.cc/?loadBalanced=true&srvMaxHosts=1", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because positive integer for srvMaxHosts conflicts with loadBalanced=true" +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-zero-txt.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-zero-txt.json new file mode 100644 index 00000000000..8d48b5bbb93 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-zero-txt.json @@ -0,0 +1,14 @@ +{ + "uri": "mongodb+srv://test20.test.build.10gen.cc/?srvMaxHosts=0", + "seeds": [ + "localhost.test.build.10gen.cc:27017" + ], + "hosts": [ + "localhost.test.build.10gen.cc:27017" + ], + "options": { + "loadBalanced": true, + "srvMaxHosts": 0, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-zero.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-zero.json new file mode 100644 index 00000000000..2382fccf852 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/load-balanced/srvMaxHosts-zero.json @@ -0,0 +1,14 @@ +{ + "uri": "mongodb+srv://test3.test.build.10gen.cc/?loadBalanced=true&srvMaxHosts=0", + "seeds": [ + "localhost.test.build.10gen.cc:27017" + ], + "hosts": [ + "localhost.test.build.10gen.cc:27017" + ], + "options": { + "loadBalanced": true, + "srvMaxHosts": 0, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-conflicts_with_replicaSet-txt.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-conflicts_with_replicaSet-txt.json new file mode 100644 index 00000000000..6de1e37fa55 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-conflicts_with_replicaSet-txt.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test5.test.build.10gen.cc/?srvMaxHosts=1", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because positive integer for srvMaxHosts conflicts with replicaSet option (TXT)" +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-conflicts_with_replicaSet.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-conflicts_with_replicaSet.json new file mode 100644 index 00000000000..f968757502d --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-conflicts_with_replicaSet.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?replicaSet=repl0&srvMaxHosts=1", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because positive integer for srvMaxHosts conflicts with replicaSet option" +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-equal_to_srv_records.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-equal_to_srv_records.json new file mode 100644 index 00000000000..d9765ac6631 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-equal_to_srv_records.json @@ -0,0 +1,17 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2", + "numSeeds": 2, + "seeds": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "hosts": [ + "localhost:27017", + "localhost:27018", + "localhost:27019" + ], + "options": { + "srvMaxHosts": 2, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-greater_than_srv_records.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-greater_than_srv_records.json new file mode 100644 index 00000000000..494bb87687a --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-greater_than_srv_records.json @@ -0,0 +1,16 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=3", + "seeds": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "hosts": [ + "localhost:27017", + "localhost:27018", + "localhost:27019" + ], + "options": { + "srvMaxHosts": 3, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-invalid_integer.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-invalid_integer.json new file mode 100644 index 00000000000..5ba1a3b540c --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-invalid_integer.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?replicaSet=repl0&srvMaxHosts=-1", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because srvMaxHosts is not greater than or equal to zero" +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-invalid_type.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-invalid_type.json new file mode 100644 index 00000000000..79e75b9b158 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-invalid_type.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?replicaSet=repl0&srvMaxHosts=foo", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because srvMaxHosts is not an integer" +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-less_than_srv_records.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-less_than_srv_records.json new file mode 100644 index 00000000000..66a5e90dada --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-less_than_srv_records.json @@ -0,0 +1,13 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=1", + "numSeeds": 1, + "hosts": [ + "localhost:27017", + "localhost:27018", + "localhost:27019" + ], + "options": { + "srvMaxHosts": 1, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-zero-txt.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-zero-txt.json new file mode 100644 index 00000000000..241a901c649 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-zero-txt.json @@ -0,0 +1,17 @@ +{ + "uri": "mongodb+srv://test5.test.build.10gen.cc/?srvMaxHosts=0", + "seeds": [ + "localhost.test.build.10gen.cc:27017" + ], + "hosts": [ + "localhost:27017", + "localhost:27018", + "localhost:27019" + ], + "options": { + "authSource": "thisDB", + "replicaSet": "repl0", + "srvMaxHosts": 0, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-zero.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-zero.json new file mode 100644 index 00000000000..c68610a2012 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/replica-set/srvMaxHosts-zero.json @@ -0,0 +1,17 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?replicaSet=repl0&srvMaxHosts=0", + "seeds": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "hosts": [ + "localhost:27017", + "localhost:27018", + "localhost:27019" + ], + "options": { + "replicaSet": "repl0", + "srvMaxHosts": 0, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-equal_to_srv_records.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-equal_to_srv_records.json new file mode 100644 index 00000000000..46390726f0d --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-equal_to_srv_records.json @@ -0,0 +1,16 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2", + "numSeeds": 2, + "seeds": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "hosts": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "options": { + "srvMaxHosts": 2, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-greater_than_srv_records.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-greater_than_srv_records.json new file mode 100644 index 00000000000..e02d72bf280 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-greater_than_srv_records.json @@ -0,0 +1,15 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=3", + "seeds": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "hosts": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "options": { + "srvMaxHosts": 3, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-invalid_integer.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-invalid_integer.json new file mode 100644 index 00000000000..0939624fc3a --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-invalid_integer.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=-1", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because srvMaxHosts is not greater than or equal to zero" +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-invalid_type.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-invalid_type.json new file mode 100644 index 00000000000..c228d266120 --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-invalid_type.json @@ -0,0 +1,7 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=foo", + "seeds": [], + "hosts": [], + "error": true, + "comment": "Should fail because srvMaxHosts is not an integer" +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-less_than_srv_records.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-less_than_srv_records.json new file mode 100644 index 00000000000..fdcc1692c0f --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-less_than_srv_records.json @@ -0,0 +1,9 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=1", + "numSeeds": 1, + "numHosts": 1, + "options": { + "srvMaxHosts": 1, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-zero.json b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-zero.json new file mode 100644 index 00000000000..10ab9e656dc --- /dev/null +++ b/src/libmongoc/tests/json/initial_dns_seedlist_discovery/sharded/srvMaxHosts-zero.json @@ -0,0 +1,15 @@ +{ + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=0", + "seeds": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "hosts": [ + "localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018" + ], + "options": { + "srvMaxHosts": 0, + "ssl": true + } +} diff --git a/src/libmongoc/tests/json/uri-options/srv-options.json b/src/libmongoc/tests/json/uri-options/srv-options.json new file mode 100644 index 00000000000..9cd4e3c8825 --- /dev/null +++ b/src/libmongoc/tests/json/uri-options/srv-options.json @@ -0,0 +1,96 @@ +{ + "tests": [ + { + "description": "SRV URI with srvMaxHosts", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "srvMaxHosts": 2 + } + }, + { + "description": "SRV URI with negative integer for srvMaxHosts", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=-1", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "SRV URI with invalid type for srvMaxHosts", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=foo", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "Non-SRV URI with srvMaxHosts", + "uri": "mongodb://example.com/?srvMaxHosts=2", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "SRV URI with positive srvMaxHosts and replicaSet", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&replicaSet=foo", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "SRV URI with positive srvMaxHosts and loadBalanced=true", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&loadBalanced=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "SRV URI with positive srvMaxHosts and loadBalanced=false", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&loadBalanced=false", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "loadBalanced": false, + "srvMaxHosts": 2 + } + }, + { + "description": "SRV URI with srvMaxHosts=0 and replicaSet", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=0&replicaSet=foo", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "replicaSet": "foo", + "srvMaxHosts": 0 + } + }, + { + "description": "SRV URI with srvMaxHosts=0 and loadBalanced=true", + "uri": "mongodb+srv://test3.test.build.10gen.cc/?srvMaxHosts=0&loadBalanced=true", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "loadBalanced": true, + "srvMaxHosts": 0 + } + } + ] +} diff --git a/src/libmongoc/tests/test-mongoc-connection-uri.c b/src/libmongoc/tests/test-mongoc-connection-uri.c index e43b1217226..7802a544918 100644 --- a/src/libmongoc/tests/test-mongoc-connection-uri.c +++ b/src/libmongoc/tests/test-mongoc-connection-uri.c @@ -101,7 +101,9 @@ run_uri_test (const char *uri_string, strstr (uri_string, "heartbeatFrequencyMS=-2") || strstr (uri_string, "w=-2") || strstr (uri_string, "wTimeoutMS=-2") || strstr (uri_string, "zlibCompressionLevel=-2") || - strstr (uri_string, "zlibCompressionLevel=10")) { + strstr (uri_string, "zlibCompressionLevel=10") || + strstr (uri_string, "srvMaxHosts=-1") || + strstr (uri_string, "srvMaxHosts=foo")) { MONGOC_WARNING ("Error parsing URI: '%s'", error.message); return; } diff --git a/src/libmongoc/tests/test-mongoc-dns.c b/src/libmongoc/tests/test-mongoc-dns.c index dbeff729293..e17f354ae9b 100644 --- a/src/libmongoc/tests/test-mongoc-dns.c +++ b/src/libmongoc/tests/test-mongoc-dns.c @@ -1,13 +1,15 @@ -#include -#include +#include "mongoc/mongoc-util-private.h" +#include "mongoc/mongoc-client-pool-private.h" #include "mongoc/mongoc.h" #include "mongoc/mongoc-host-list-private.h" +#include "mongoc/mongoc-thread-private.h" +#include "mongoc/mongoc-uri-private.h" +#include "mongoc/utlist.h" + #ifdef MONGOC_ENABLE_SSL #include "mongoc/mongoc-ssl.h" #include "mongoc/mongoc-ssl-private.h" #endif -#include "mongoc/mongoc-thread-private.h" -#include "mongoc/mongoc-uri-private.h" #include "json-test.h" #include "test-libmongoc.h" @@ -127,10 +129,15 @@ hosts_count (const bson_t *test) bson_iter_t hosts; int c = 0; - BSON_ASSERT (bson_iter_init_find (&iter, test, "hosts")); - BSON_ASSERT (bson_iter_recurse (&iter, &hosts)); - while (bson_iter_next (&hosts)) { - c++; + if (bson_iter_init_find (&iter, test, "hosts")) { + BSON_ASSERT (bson_iter_recurse (&iter, &hosts)); + while (bson_iter_next (&hosts)) { + c++; + } + } + + else if (bson_iter_init_find (&iter, test, "numHosts")) { + c = bson_iter_as_int64 (&iter); } return c; @@ -145,22 +152,36 @@ _host_list_matches (const bson_t *test, context_t *ctx) const char *host_and_port; bool ret = true; - BSON_ASSERT (bson_iter_init_find (&iter, test, "hosts")); - BSON_ASSERT (bson_iter_recurse (&iter, &hosts)); - - bson_mutex_lock (&ctx->mutex); - BSON_ASSERT (bson_iter_recurse (&iter, &hosts)); - while (bson_iter_next (&hosts)) { - host_and_port = bson_iter_utf8 (&hosts, NULL); - if (!host_list_contains (ctx->hosts, host_and_port)) { - ret = false; - break; + if (bson_iter_init_find (&iter, test, "hosts")) { + BSON_ASSERT (bson_iter_recurse (&iter, &hosts)); + + bson_mutex_lock (&ctx->mutex); + BSON_ASSERT (bson_iter_recurse (&iter, &hosts)); + while (bson_iter_next (&hosts)) { + host_and_port = bson_iter_utf8 (&hosts, NULL); + if (!host_list_contains (ctx->hosts, host_and_port)) { + ret = false; + break; + } } + + _mongoc_host_list_destroy_all (ctx->hosts); + ctx->hosts = NULL; + bson_mutex_unlock (&ctx->mutex); } - _mongoc_host_list_destroy_all (ctx->hosts); - ctx->hosts = NULL; - bson_mutex_unlock (&ctx->mutex); + else if (bson_iter_init_find (&iter, test, "numHosts")) { + const int expected = bson_iter_as_int64 (&iter); + int actual = 0; + + bson_mutex_lock (&ctx->mutex); + actual = _mongoc_host_list_length (ctx->hosts); + _mongoc_host_list_destroy_all (ctx->hosts); + ctx->hosts = NULL; + bson_mutex_unlock (&ctx->mutex); + + ret = expected == actual; + } return ret; } @@ -364,6 +385,12 @@ test_dns_check_loadbalanced (void) return test_framework_getenv_bool ("MONGOC_TEST_DNS_LOADBALANCED") ? 1 : 0; } +static int +test_dns_check_srv_polling (void) +{ + return test_framework_getenv_bool ("MONGOC_TEST_DNS_SRV_POLLING") ? 1 : 0; +} + /* ensure mongoc_topology_select_server_id handles a NULL error pointer in the * code path it follows when the topology scanner is invalid */ @@ -410,6 +437,17 @@ test_all_spec_tests (TestSuite *suite) test_dns, test_dns_check_loadbalanced, test_framework_skip_if_no_crypto); + + test_framework_resolve_path ( + JSON_DIR "/initial_dns_seedlist_discovery/sharded", resolved); + install_json_test_suite_with_check ( + suite, + resolved, + test_dns, + /* Topology of load-balancer tests satisfy topology requirements of + * sharded tests, even though a load balancer is not required. */ + test_dns_check_loadbalanced, + test_framework_skip_if_no_crypto); } extern bool @@ -446,7 +484,9 @@ dump_hosts (mongoc_host_list_t *hosts) mongoc_host_list_t *host; MONGOC_DEBUG ("hosts:"); - for (host = hosts; host; host = hosts->next) { + + LL_FOREACH (hosts, host) + { MONGOC_DEBUG ("- %s", host->host_and_port); } } @@ -609,18 +649,18 @@ test_small_initial_buffer (void *unused) } bool -_mock_resolver (const char *service, - mongoc_rr_type_t rr_type, - mongoc_rr_data_t *rr_data, - size_t initial_buffer_size, - bson_error_t *error) +_mock_rr_resolver_prose_test_9 (const char *service, + mongoc_rr_type_t rr_type, + mongoc_rr_data_t *rr_data, + size_t initial_buffer_size, + bson_error_t *error) { test_error ("Expected mock resolver to not be called"); return true; } static void -_prose_loadbalanced_ping (mongoc_client_t *client) +_prose_test_ping (mongoc_client_t *client) { bson_error_t error; bson_t *cmd = BCON_NEW ("ping", BCON_INT32 (1)); @@ -633,102 +673,555 @@ _prose_loadbalanced_ping (mongoc_client_t *client) bson_destroy (cmd); } -/* - Implements prose test 9 as described in the SRV polling test README: - Test that SRV polling is not done for load balanced clusters. Connect to - mongodb+srv://test3.test.build.10gen.cc/?loadBalanced=true, mock the addition - of the following DNS record, wait until 2*rescanSRVIntervalMS, and assert - that the final topology description only contains one server - (localhost.test.build.10gen.cc. at port 27017). - _mongodb._tcp.test3.test.build.10gen.cc. 86400 IN SRV 27018 - localhost.test.build.10gen.cc. -*/ -static void -_prose_test_9 (bool pooled) +/* SRV Polling Tests Spec: rescanSRVIntervalMS */ +#define RESCAN_INTERVAL_MS 500 + +static void * +_prose_test_init_resource_single (const mongoc_uri_t *uri, + _mongoc_rr_resolver_fn fn) { - mongoc_client_pool_t *pool; - mongoc_uri_t *uri; mongoc_client_t *client; - mongoc_host_list_t *expected_hosts; mongoc_topology_t *topology; - mc_tpld_modification tdmod; -#define RESCAN_INTERVAL_MS 500 -#ifdef MONGOC_ENABLE_SSL - mongoc_ssl_opt_t ssl_opts = *test_framework_get_ssl_opts (); - - ssl_opts.allow_invalid_hostname = true; -#endif + BSON_ASSERT_PARAM (uri); + BSON_ASSERT_PARAM (fn); - uri = mongoc_uri_new ("mongodb+srv://test3.test.build.10gen.cc"); - mongoc_uri_set_option_as_bool (uri, MONGOC_URI_LOADBALANCED, true); - /* Single-threaded clients will only enter SRV polling during monitoring in - * mongoc_topology_scan_once. Reducing the heartbeatFrequencyMS will exercise - * the code path that would poll for SRV records. That should be bypassed - * because of the load balanced topology type. */ - mongoc_uri_set_option_as_int32 ( - uri, MONGOC_URI_HEARTBEATFREQUENCYMS, RESCAN_INTERVAL_MS); - - if (pooled) { - pool = mongoc_client_pool_new (uri); - topology = _mongoc_client_pool_get_topology (pool); + client = mongoc_client_new_from_uri (uri); + topology = client->topology; -#ifdef MONGOC_ENABLE_SSL - mongoc_client_pool_set_ssl_opts (pool, &ssl_opts); -#endif + _mongoc_topology_set_rr_resolver (topology, fn); + _mongoc_topology_set_srv_polling_rescan_interval_ms (topology, + RESCAN_INTERVAL_MS); - } else { - client = mongoc_client_new_from_uri (uri); -#ifdef MONGOC_ENABLE_SSL +#if defined(MONGOC_ENABLE_SSL) + { + mongoc_ssl_opt_t ssl_opts = *test_framework_get_ssl_opts (); + ssl_opts.allow_invalid_hostname = true; mongoc_client_set_ssl_opts (client, &ssl_opts); -#endif - topology = client->topology; } +#endif /* defined(MONGOC_ENABLE_SSL) */ - _mongoc_topology_set_rr_resolver (topology, _mock_resolver); + return client; +} + +static void * +_prose_test_init_resource_pooled (const mongoc_uri_t *uri, + _mongoc_rr_resolver_fn fn) +{ + mongoc_client_pool_t *pool; + mongoc_topology_t *topology; + + BSON_ASSERT_PARAM (uri); + BSON_ASSERT_PARAM (fn); + + pool = mongoc_client_pool_new (uri); + topology = _mongoc_client_pool_get_topology (pool); + + _mongoc_topology_set_rr_resolver (topology, fn); _mongoc_topology_set_srv_polling_rescan_interval_ms (topology, RESCAN_INTERVAL_MS); - if (pooled) { - client = mongoc_client_pool_pop (pool); +#if defined(MONGOC_ENABLE_SSL) + { + mongoc_ssl_opt_t ssl_opts = *test_framework_get_ssl_opts (); + ssl_opts.allow_invalid_hostname = true; + mongoc_client_pool_set_ssl_opts (pool, &ssl_opts); } +#endif /* defined(MONGOC_ENABLE_SSL) */ + + return pool; +} + +static void +_prose_test_free_resource_single (void *resource) +{ + mongoc_client_destroy ((mongoc_client_t *) resource); +} + +static void +_prose_test_free_resource_pooled (void *resource) +{ + mongoc_client_pool_destroy ((mongoc_client_pool_t *) resource); +} + +static mongoc_client_t * +_prose_test_get_client_single (void *resource) +{ + BSON_ASSERT_PARAM (resource); + return (mongoc_client_t *) resource; +} + +static mongoc_client_t * +_prose_test_get_client_pooled (void *resource) +{ + BSON_ASSERT_PARAM (resource); + return mongoc_client_pool_pop (((mongoc_client_pool_t *) resource)); +} - _mongoc_usleep (2 * RESCAN_INTERVAL_MS * 1000); - /* For single-threaded, perform an operation since SRV polling occurs as a - * part of topology scanning. */ - if (!pooled) { - _prose_loadbalanced_ping (client); +static void +_prose_test_release_client_single (void *resource, mongoc_client_t *client) +{ + BSON_ASSERT_PARAM (resource); + BSON_ASSERT_PARAM (client); + /* Nothing to do. */ +} + +static void +_prose_test_release_client_pooled (void *resource, mongoc_client_t *client) +{ + BSON_ASSERT_PARAM (resource); + BSON_ASSERT_PARAM (client); + mongoc_client_pool_push ((mongoc_client_pool_t *) resource, client); +} + +static void +_prose_test_update_srv_single (void *resource) +{ + mongoc_client_t *client; + + BSON_ASSERT_PARAM (resource); + + client = resource; + + _mongoc_usleep (2000 * RESCAN_INTERVAL_MS); + + /* Avoid ping given `loadBalanced=true`; see prose test 9. */ + if (!mongoc_uri_get_option_as_bool ( + client->uri, MONGOC_URI_LOADBALANCED, false)) { + _prose_test_ping (client); } +} - expected_hosts = MAKE_HOSTS ("localhost.test.build.10gen.cc:27017"); - /* We don't intend to modify the topology, but we need a writeable instance - * in a thread-safe manner. Open a modification to get a writeable copy. */ - tdmod = mc_tpld_modify_begin (client->topology); - check_topology_description (tdmod.new_td, expected_hosts); - /* Just throw the modification. It will be a no-op anyway. */ - mc_tpld_modify_drop (tdmod); +static void +_prose_test_update_srv_pooled (void *resource) +{ + BSON_ASSERT_PARAM (resource); - if (pooled) { - mongoc_client_pool_push (pool, client); - mongoc_client_pool_destroy (pool); - } else { - mongoc_client_destroy (client); + _mongoc_usleep (2000 * RESCAN_INTERVAL_MS); +} + +typedef struct { + void *(*init_resource) (const mongoc_uri_t *uri, _mongoc_rr_resolver_fn fn); + void (*free_resource) (void *); + mongoc_client_t *(*get_client) (void *resource); + void (*release_client) (void *resource, mongoc_client_t *client); + void (*update_srv) (void *); +} _prose_test_fns_t; + +static const _prose_test_fns_t _prose_test_single_fns = { + _prose_test_init_resource_single, + _prose_test_free_resource_single, + _prose_test_get_client_single, + _prose_test_release_client_single, + _prose_test_update_srv_single}; + +static const _prose_test_fns_t _prose_test_pooled_fns = { + _prose_test_init_resource_pooled, + _prose_test_free_resource_pooled, + _prose_test_get_client_pooled, + _prose_test_release_client_pooled, + _prose_test_update_srv_pooled}; + +static void +_prose_test_9 (const _prose_test_fns_t *fns) +{ + void *resource; + + BSON_ASSERT_PARAM (fns); + + { + mongoc_uri_t *const uri = + mongoc_uri_new ("mongodb+srv://test3.test.build.10gen.cc"); + + mongoc_uri_set_option_as_bool (uri, MONGOC_URI_LOADBALANCED, true); + mongoc_uri_set_option_as_int32 ( + uri, MONGOC_URI_HEARTBEATFREQUENCYMS, RESCAN_INTERVAL_MS); + + resource = fns->init_resource (uri, _mock_rr_resolver_prose_test_9); + + mongoc_uri_destroy (uri); } - mongoc_uri_destroy (uri); - _mongoc_host_list_destroy_all (expected_hosts); + { + mongoc_host_list_t *const expected = + MAKE_HOSTS ("localhost.test.build.10gen.cc:27017"); + mongoc_client_t *const client = fns->get_client (resource); + + { + mc_tpld_modification tdmod = mc_tpld_modify_begin (client->topology); + check_topology_description (tdmod.new_td, expected); + mc_tpld_modify_drop (tdmod); + } + + fns->release_client (resource, client); + _mongoc_host_list_destroy_all (expected); + } + + fns->update_srv (resource); + + { + mongoc_host_list_t *const expected = + MAKE_HOSTS ("localhost.test.build.10gen.cc:27017"); + mongoc_client_t *const client = fns->get_client (resource); + + { + mc_tpld_modification tdmod = mc_tpld_modify_begin (client->topology); + check_topology_description (tdmod.new_td, expected); + mc_tpld_modify_drop (tdmod); + } + + fns->release_client (resource, client); + _mongoc_host_list_destroy_all (expected); + } + + fns->free_resource (resource); } static void prose_test_9_single (void *unused) { - _prose_test_9 (false); + _prose_test_9 (&_prose_test_single_fns); } static void prose_test_9_pooled (void *unused) { - _prose_test_9 (true); + _prose_test_9 (&_prose_test_pooled_fns); +} + +static bool +_mock_rr_resolver_prose_test_10 (const char *service, + mongoc_rr_type_t rr_type, + mongoc_rr_data_t *rr_data, + size_t initial_buffer_size, + bson_error_t *error) +{ + BSON_ASSERT_PARAM (service); + BSON_ASSERT_PARAM (rr_data); + BSON_ASSERT_PARAM (error); + + /* Silence unused parameter warning. */ + BSON_ASSERT (initial_buffer_size >= 0u); + + if (rr_type == MONGOC_RR_SRV) { + rr_data->hosts = MAKE_HOSTS ("localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27019", + "localhost.test.build.10gen.cc:27020"); + rr_data->count = _mongoc_host_list_length (rr_data->hosts); + rr_data->min_ttl = 0u; + rr_data->txt_record_opts = NULL; + } + + error->code = 0u; + + return true; +} + +static void +_prose_test_10 (const _prose_test_fns_t *fns) +{ + void *resource; + + BSON_ASSERT_PARAM (fns); + + { + mongoc_uri_t *const uri = + mongoc_uri_new ("mongodb+srv://test1.test.build.10gen.cc"); + + mongoc_uri_set_option_as_int32 (uri, MONGOC_URI_SRVMAXHOSTS, 0); + mongoc_uri_set_option_as_int32 ( + uri, MONGOC_URI_HEARTBEATFREQUENCYMS, RESCAN_INTERVAL_MS); + + resource = fns->init_resource (uri, _mock_rr_resolver_prose_test_10); + + mongoc_uri_destroy (uri); + } + + { + mongoc_host_list_t *const expected = + MAKE_HOSTS ("localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018"); + mongoc_client_t *const client = fns->get_client (resource); + + { + mc_tpld_modification tdmod = mc_tpld_modify_begin (client->topology); + check_topology_description (tdmod.new_td, expected); + mc_tpld_modify_drop (tdmod); + } + + fns->release_client (resource, client); + _mongoc_host_list_destroy_all (expected); + } + + fns->update_srv (resource); + + { + mongoc_host_list_t *const expected = + MAKE_HOSTS ("localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27019", + "localhost.test.build.10gen.cc:27020"); + mongoc_client_t *const client = fns->get_client (resource); + + { + mc_tpld_modification tdmod = mc_tpld_modify_begin (client->topology); + check_topology_description (tdmod.new_td, expected); + mc_tpld_modify_drop (tdmod); + } + + fns->release_client (resource, client); + _mongoc_host_list_destroy_all (expected); + } + + fns->free_resource (resource); +} + +static void +prose_test_10_single (void *unused) +{ + _prose_test_10 (&_prose_test_single_fns); +} + +static void +prose_test_10_pooled (void *unused) +{ + _prose_test_10 (&_prose_test_pooled_fns); +} + +static bool +_mock_rr_resolver_prose_test_11 (const char *service, + mongoc_rr_type_t rr_type, + mongoc_rr_data_t *rr_data, + size_t initial_buffer_size, + bson_error_t *error) +{ + BSON_ASSERT_PARAM (service); + BSON_ASSERT_PARAM (rr_data); + BSON_ASSERT_PARAM (error); + + /* Silence unused parameter warning. */ + BSON_ASSERT (initial_buffer_size >= 0u); + + if (rr_type == MONGOC_RR_SRV) { + rr_data->hosts = MAKE_HOSTS ("localhost.test.build.10gen.cc:27019", + "localhost.test.build.10gen.cc:27020"); + rr_data->count = _mongoc_host_list_length (rr_data->hosts); + rr_data->min_ttl = 0u; + rr_data->txt_record_opts = NULL; + } + + error->code = 0u; + + return true; +} + +static void +_prose_test_11 (const _prose_test_fns_t *fns) +{ + void *resource; + + BSON_ASSERT_PARAM (fns); + + { + mongoc_uri_t *const uri = + mongoc_uri_new ("mongodb+srv://test1.test.build.10gen.cc"); + + mongoc_uri_set_option_as_int32 (uri, MONGOC_URI_SRVMAXHOSTS, 2); + mongoc_uri_set_option_as_int32 ( + uri, MONGOC_URI_HEARTBEATFREQUENCYMS, RESCAN_INTERVAL_MS); + + resource = fns->init_resource (uri, _mock_rr_resolver_prose_test_11); + + mongoc_uri_destroy (uri); + } + + { + mongoc_host_list_t *const expected = + MAKE_HOSTS ("localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018"); + mongoc_client_t *const client = fns->get_client (resource); + + { + mc_tpld_modification tdmod = mc_tpld_modify_begin (client->topology); + check_topology_description (tdmod.new_td, expected); + mc_tpld_modify_drop (tdmod); + } + + fns->release_client (resource, client); + _mongoc_host_list_destroy_all (expected); + } + + fns->update_srv (resource); + + { + mongoc_host_list_t *const expected = + MAKE_HOSTS ("localhost.test.build.10gen.cc:27019", + "localhost.test.build.10gen.cc:27020"); + mongoc_client_t *const client = fns->get_client (resource); + + { + mc_tpld_modification tdmod = mc_tpld_modify_begin (client->topology); + check_topology_description (tdmod.new_td, expected); + mc_tpld_modify_drop (tdmod); + } + + fns->release_client (resource, client); + _mongoc_host_list_destroy_all (expected); + } + + fns->free_resource (resource); +} + +static void +prose_test_11_single (void *unused) +{ + _prose_test_11 (&_prose_test_single_fns); +} + +static void +prose_test_11_pooled (void *unused) +{ + _prose_test_11 (&_prose_test_pooled_fns); +} + +static bool +_mock_rr_resolver_prose_test_12 (const char *service, + mongoc_rr_type_t rr_type, + mongoc_rr_data_t *rr_data, + size_t initial_buffer_size, + bson_error_t *error) +{ + BSON_ASSERT_PARAM (service); + BSON_ASSERT_PARAM (rr_data); + BSON_ASSERT_PARAM (error); + + /* Silence unused parameter warning. */ + BSON_ASSERT (initial_buffer_size >= 0u); + + if (rr_type == MONGOC_RR_SRV) { + rr_data->hosts = MAKE_HOSTS ("localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27019", + "localhost.test.build.10gen.cc:27020"); + rr_data->count = _mongoc_host_list_length (rr_data->hosts); + rr_data->min_ttl = 0u; + rr_data->txt_record_opts = NULL; + } + + error->code = 0u; + + return true; +} + +typedef struct { + size_t num_existing; + size_t num_new_valid; +} _prose_test_12_ctx_t; + +static bool +_prose_test_12_cb (const void *sd_void, void *ctx_void) +{ + const mongoc_server_description_t *sd; + _prose_test_12_ctx_t *ctx; + const mongoc_host_list_t *host; + + BSON_ASSERT_PARAM (sd_void); + BSON_ASSERT_PARAM (ctx_void); + + sd = sd_void; + ctx = ctx_void; + host = &sd->host; + + ASSERT_CMPSTR (host->host, "localhost.test.build.10gen.cc"); + + if (host->port == 27017u) { + ++ctx->num_existing; + } + + else { + ASSERT (host->port == 27019 || host->port == 27020); + ++ctx->num_new_valid; + } + + return true; +} + +static void +_prose_test_12 (const _prose_test_fns_t *fns) +{ + void *resource; + + BSON_ASSERT_PARAM (fns); + + { + mongoc_uri_t *const uri = + mongoc_uri_new ("mongodb+srv://test1.test.build.10gen.cc"); + + mongoc_uri_set_option_as_int32 (uri, MONGOC_URI_SRVMAXHOSTS, 2); + mongoc_uri_set_option_as_int32 ( + uri, MONGOC_URI_HEARTBEATFREQUENCYMS, RESCAN_INTERVAL_MS); + + resource = fns->init_resource (uri, _mock_rr_resolver_prose_test_12); + + mongoc_uri_destroy (uri); + } + + { + mongoc_host_list_t *const expected = + MAKE_HOSTS ("localhost.test.build.10gen.cc:27017", + "localhost.test.build.10gen.cc:27018"); + mongoc_client_t *const client = fns->get_client (resource); + + { + mc_tpld_modification tdmod = mc_tpld_modify_begin (client->topology); + check_topology_description (tdmod.new_td, expected); + mc_tpld_modify_drop (tdmod); + } + + fns->release_client (resource, client); + _mongoc_host_list_destroy_all (expected); + } + + fns->update_srv (resource); + + { + mongoc_client_t *const client = fns->get_client (resource); + _prose_test_12_ctx_t ctx; + + ctx.num_existing = 0u; + ctx.num_new_valid = 0u; + + { + mc_tpld_modification tdmod = mc_tpld_modify_begin (client->topology); + const mongoc_set_t *servers = mc_tpld_servers_const (tdmod.new_td); + mongoc_set_for_each_const (servers, _prose_test_12_cb, &ctx); + mc_tpld_modify_drop (tdmod); + } + + ASSERT_WITH_MSG ( + ctx.num_existing > 0u, + "hosts that have not changed must be left alone and unchanged"); + ASSERT_WITH_MSG (ctx.num_existing == 1u, + "only a single host should have remained, but found %zu", + ctx.num_existing); + + ASSERT_WITH_MSG (ctx.num_new_valid == 1u, + "exactly one valid new hosts should have been added"); + + fns->release_client (resource, client); + } + + fns->free_resource (resource); +} + +static void +prose_test_12_single (void *unused) +{ + _prose_test_12 (&_prose_test_single_fns); +} + +static void +prose_test_12_pooled (void *unused) +{ + _prose_test_12 (&_prose_test_pooled_fns); } /* cb_stats_t tracks counters for the test_invalid_topology_pooled and @@ -743,47 +1236,58 @@ typedef struct { /* invalid_topology_opening is used as a callback for the * test_invalid_topology_pooled and test_invalid_topology_single tests. */ static void -invalid_topology_opening (const mongoc_apm_topology_opening_t *event) { - cb_stats_t *stats = (cb_stats_t*) mongoc_apm_topology_opening_get_context (event); +invalid_topology_opening (const mongoc_apm_topology_opening_t *event) +{ + cb_stats_t *stats = + (cb_stats_t *) mongoc_apm_topology_opening_get_context (event); stats->num_topology_opening++; } /* invalid_topology_closed is used as a callback for the * test_invalid_topology_pooled and test_invalid_topology_single tests. */ static void -invalid_topology_closed (const mongoc_apm_topology_closed_t *event) { - cb_stats_t *stats = (cb_stats_t*) mongoc_apm_topology_closed_get_context (event); +invalid_topology_closed (const mongoc_apm_topology_closed_t *event) +{ + cb_stats_t *stats = + (cb_stats_t *) mongoc_apm_topology_closed_get_context (event); stats->num_topology_closed++; } /* invalid_server_closed is used as a callback for the * test_invalid_topology_pooled and test_invalid_topology_single tests. */ static void -invalid_server_closed (const mongoc_apm_server_closed_t *event) { - cb_stats_t *stats = (cb_stats_t*) mongoc_apm_server_closed_get_context (event); +invalid_server_closed (const mongoc_apm_server_closed_t *event) +{ + cb_stats_t *stats = + (cb_stats_t *) mongoc_apm_server_closed_get_context (event); stats->num_server_closed++; } /* invalid_server_opening is used as a callback for the * test_invalid_topology_pooled and test_invalid_topology_single tests. */ static void -invalid_server_opening (const mongoc_apm_server_opening_t *event) { - cb_stats_t *stats = (cb_stats_t*) mongoc_apm_server_opening_get_context (event); +invalid_server_opening (const mongoc_apm_server_opening_t *event) +{ + cb_stats_t *stats = + (cb_stats_t *) mongoc_apm_server_opening_get_context (event); stats->num_server_opening++; } /* CDRIVER-4184 Test that an invalid topology does not emit a topology_closed * event. */ static void -test_invalid_topology_pooled (void *unused) { +test_invalid_topology_pooled (void *unused) +{ mongoc_client_pool_t *pool; mongoc_client_t *client; mongoc_uri_t *uri; mongoc_apm_callbacks_t *cbs; cb_stats_t stats = {0}; - /* TXT record for test20.test.build.10gen.cc resolves to "loadBalanced=true". */ - uri = mongoc_uri_new ("mongodb+srv://test20.test.build.10gen.cc/?replicaSet=rs0"); + /* TXT record for test20.test.build.10gen.cc resolves to "loadBalanced=true". + */ + uri = mongoc_uri_new ( + "mongodb+srv://test20.test.build.10gen.cc/?replicaSet=rs0"); pool = mongoc_client_pool_new (uri); cbs = mongoc_apm_callbacks_new (); mongoc_apm_set_topology_opening_cb (cbs, invalid_topology_opening); @@ -792,9 +1296,10 @@ test_invalid_topology_pooled (void *unused) { mongoc_apm_set_server_closed_cb (cbs, invalid_server_closed); mongoc_client_pool_set_apm_callbacks (pool, cbs, &stats); - ASSERT_CMPINT(stats.num_topology_opening, ==, 0); + ASSERT_CMPINT (stats.num_topology_opening, ==, 0); - /* Pop a client to attempt to start monitoring. Monitoring emits the topology_opening event on valid topologies. */ + /* Pop a client to attempt to start monitoring. Monitoring emits the + * topology_opening event on valid topologies. */ client = mongoc_client_pool_pop (pool); mongoc_client_pool_push (pool, client); @@ -802,16 +1307,17 @@ test_invalid_topology_pooled (void *unused) { mongoc_client_pool_destroy (pool); mongoc_uri_destroy (uri); - ASSERT_CMPINT(stats.num_topology_opening, ==, 0); - ASSERT_CMPINT(stats.num_server_opening, ==, 0); - ASSERT_CMPINT(stats.num_server_closed, ==, 0); - ASSERT_CMPINT(stats.num_topology_closed, ==, 0); + ASSERT_CMPINT (stats.num_topology_opening, ==, 0); + ASSERT_CMPINT (stats.num_server_opening, ==, 0); + ASSERT_CMPINT (stats.num_server_closed, ==, 0); + ASSERT_CMPINT (stats.num_topology_closed, ==, 0); } /* CDRIVER-4184 Test that an invalid topology does not emit a topology_closed * event. */ static void -test_invalid_topology_single (void *unused) { +test_invalid_topology_single (void *unused) +{ mongoc_client_t *client; mongoc_uri_t *uri; mongoc_apm_callbacks_t *cbs; @@ -819,8 +1325,10 @@ test_invalid_topology_single (void *unused) { bson_error_t error; mongoc_server_description_t *sd; - /* TXT records for test20.test.build.10gen.cc resolve to loadBalanced=true. */ - uri = mongoc_uri_new ("mongodb+srv://test20.test.build.10gen.cc/?replicaSet=true"); + /* TXT records for test20.test.build.10gen.cc resolve to loadBalanced=true. + */ + uri = mongoc_uri_new ( + "mongodb+srv://test20.test.build.10gen.cc/?replicaSet=true"); client = mongoc_client_new_from_uri (uri); cbs = mongoc_apm_callbacks_new (); mongoc_apm_set_topology_opening_cb (cbs, invalid_topology_opening); @@ -829,27 +1337,27 @@ test_invalid_topology_single (void *unused) { mongoc_apm_set_server_closed_cb (cbs, invalid_server_closed); mongoc_client_set_apm_callbacks (client, cbs, &stats); - ASSERT_CMPINT(stats.num_topology_opening, ==, 0); + ASSERT_CMPINT (stats.num_topology_opening, ==, 0); /* Perform server selection. Server selection emits the topology_opening * event on valid topologies. */ sd = mongoc_client_select_server ( client, false /* for_writes */, NULL /* read_prefs */, &error); - ASSERT_ERROR_CONTAINS ( - error, - MONGOC_ERROR_SERVER_SELECTION, - MONGOC_ERROR_SERVER_SELECTION_FAILURE, - "URI with \"loadbalanced\" enabled must not contain option \"replicaset\""); + ASSERT_ERROR_CONTAINS (error, + MONGOC_ERROR_SERVER_SELECTION, + MONGOC_ERROR_SERVER_SELECTION_FAILURE, + "URI with \"loadbalanced\" enabled must not contain " + "option \"replicaset\""); ASSERT (!sd); mongoc_apm_callbacks_destroy (cbs); mongoc_client_destroy (client); mongoc_uri_destroy (uri); - ASSERT_CMPINT(stats.num_topology_opening, ==, 0); - ASSERT_CMPINT(stats.num_server_opening, ==, 0); - ASSERT_CMPINT(stats.num_server_closed, ==, 0); - ASSERT_CMPINT(stats.num_topology_closed, ==, 0); + ASSERT_CMPINT (stats.num_topology_opening, ==, 0); + ASSERT_CMPINT (stats.num_server_opening, ==, 0); + ASSERT_CMPINT (stats.num_server_closed, ==, 0); + ASSERT_CMPINT (stats.num_topology_closed, ==, 0); } void @@ -885,7 +1393,7 @@ test_dns_install (TestSuite *suite) prose_test_9_single, NULL, NULL, - test_dns_check_loadbalanced); + test_dns_check_srv_polling); TestSuite_AddFull ( suite, @@ -893,7 +1401,55 @@ test_dns_install (TestSuite *suite) prose_test_9_pooled, NULL, NULL, - test_dns_check_loadbalanced); + test_dns_check_srv_polling); + + TestSuite_AddFull ( + suite, + "/initial_dns_seedlist_discovery/srv_polling/prose_test_10/single", + prose_test_10_single, + NULL, + NULL, + test_dns_check_srv_polling); + + TestSuite_AddFull ( + suite, + "/initial_dns_seedlist_discovery/srv_polling/prose_test_10/pooled", + prose_test_10_pooled, + NULL, + NULL, + test_dns_check_srv_polling); + + TestSuite_AddFull ( + suite, + "/initial_dns_seedlist_discovery/srv_polling/prose_test_11/single", + prose_test_11_single, + NULL, + NULL, + test_dns_check_srv_polling); + + TestSuite_AddFull ( + suite, + "/initial_dns_seedlist_discovery/srv_polling/prose_test_11/pooled", + prose_test_11_pooled, + NULL, + NULL, + test_dns_check_srv_polling); + + TestSuite_AddFull ( + suite, + "/initial_dns_seedlist_discovery/srv_polling/prose_test_12/single", + prose_test_12_single, + NULL, + NULL, + test_dns_check_srv_polling); + + TestSuite_AddFull ( + suite, + "/initial_dns_seedlist_discovery/srv_polling/prose_test_12/pooled", + prose_test_12_pooled, + NULL, + NULL, + test_dns_check_srv_polling); TestSuite_AddFull ( suite,