Skip to content

Commit e591a17

Browse files
authored
RUST-91 Automate Atlas connectivity tests (#95)
1 parent 192088b commit e591a17

File tree

13 files changed

+169
-138
lines changed

13 files changed

+169
-138
lines changed

.evergreen/config.yml

+28
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,20 @@ functions:
129129
130130
.evergreen/run-tests.sh
131131
132+
"run atlas tests":
133+
- command: shell.exec
134+
type: test
135+
params:
136+
silent: true
137+
shell: bash
138+
working_dir: "src"
139+
script: |
140+
# DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
141+
export MONGO_ATLAS_TESTS=1
142+
export MONGO_ATLAS_FREE_TIER_REPL_URI='${MONGO_ATLAS_FREE_TIER_REPL_URI}'
143+
export MONGO_ATLAS_FREE_TIER_REPL_URI_SRV='${MONGO_ATLAS_FREE_TIER_REPL_URI_SRV}'
144+
.evergreen/run-atlas-tests.sh
145+
132146
"check rustfmt":
133147
- command: shell.exec
134148
type: test
@@ -323,9 +337,15 @@ tasks:
323337
TOPOLOGY: "sharded_cluster"
324338
- func: "run tests"
325339

340+
- name: "test-atlas-connectivity"
341+
tags: ["atlas-connect"]
342+
commands:
343+
- func: "run atlas tests"
344+
326345
- name: "check-rustfmt"
327346
commands:
328347
- func: "check rustfmt"
348+
329349
- name: "check-clippy"
330350
commands:
331351
- func: "check clippy"
@@ -409,6 +429,13 @@ buildvariants:
409429
- ".4.2"
410430
- ".4.0"
411431
- ".3.6"
432+
- matrix_name: "atlas-connect"
433+
matrix_spec:
434+
os: "*"
435+
display_name: "Atlas Connectivity ${os}"
436+
tasks:
437+
- ".atlas-connect"
438+
412439
-
413440
name: "lint"
414441
display_name: "Lint"
@@ -417,3 +444,4 @@ buildvariants:
417444
tasks:
418445
- name: "check-clippy"
419446
- name: "check-rustfmt"
447+

.evergreen/run-atlas-tests.sh

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
3+
set -o errexit
4+
5+
. ~/.cargo/env
6+
RUST_BACKTRACE=1 cargo test atlas_connectivity
7+

src/client/mod.rs

+2-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ use crate::{
2626
options::{ClientOptions, DatabaseOptions},
2727
sdam::{Server, Topology, TopologyUpdateCondvar},
2828
selection_criteria::{ReadPreference, SelectionCriteria},
29-
srv::SrvResolver,
3029
};
3130

3231
const DEFAULT_SERVER_SELECTION_TIMEOUT: Duration = Duration::from_secs(30);
@@ -73,29 +72,24 @@ struct ClientInner {
7372
options: ClientOptions,
7473
#[derivative(Debug = "ignore")]
7574
condvar: TopologyUpdateCondvar,
76-
#[derivative(Debug = "ignore")]
77-
resolver: SrvResolver,
7875
}
7976

8077
impl Client {
8178
/// Creates a new `Client` connected to the cluster specified by `uri`. `uri` must be a valid
8279
/// MongoDB connection string.
8380
pub fn with_uri_str(uri: &str) -> Result<Self> {
84-
let options = ClientOptions::parse_uri(uri)?;
81+
let options = ClientOptions::parse(uri)?;
8582

8683
Client::with_options(options)
8784
}
8885

8986
/// Creates a new `Client` connected to the cluster specified by `options`.
90-
pub fn with_options(mut options: ClientOptions) -> Result<Self> {
87+
pub fn with_options(options: ClientOptions) -> Result<Self> {
9188
let condvar = TopologyUpdateCondvar::new();
92-
let resolver = SrvResolver::new()?;
93-
resolver.resolve_and_update_client_opts(&mut options)?;
9489

9590
let inner = Arc::new(ClientInner {
9691
topology: Topology::new(condvar.clone(), options.clone())?,
9792
condvar,
98-
resolver,
9993
options,
10094
});
10195

src/client/options/mod.rs

+65-50
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,6 @@ pub struct ClientOptions {
155155
}]")]
156156
pub hosts: Vec<StreamAddress>,
157157

158-
#[builder(default = false)]
159-
pub srv: bool,
160-
161158
#[builder(default)]
162159
pub app_name: Option<String>,
163160

@@ -383,7 +380,6 @@ impl From<ClientOptionsParser> for ClientOptions {
383380
fn from(parser: ClientOptionsParser) -> Self {
384381
Self {
385382
hosts: parser.hosts,
386-
srv: parser.srv,
387383
app_name: parser.app_name,
388384
tls: parser.tls,
389385
heartbeat_freq: parser.heartbeat_freq,
@@ -417,31 +413,48 @@ impl ClientOptions {
417413
/// Parses a MongoDB connection string into a ClientOptions struct. If the string is malformed
418414
/// or one of the options has an invalid value, an error will be returned.
419415
///
420-
/// In the case that "mongodb+srv" is used, SRV and TXT record lookups will _not_ be done as
421-
/// part of this method. To manually update the ClientOptions hosts and options with the SRV
422-
/// and TXT records, call `ClientOptions::resolve_srv`. Note that calling
423-
/// `Client::with_options` will also automatically call `resolve_srv` if needed.
424-
pub fn parse_uri(s: &str) -> Result<Self> {
425-
ClientOptionsParser::parse(s).map(Into::into)
426-
}
416+
/// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done as
417+
/// part of this method.
418+
pub fn parse(s: &str) -> Result<Self> {
419+
let parser = ClientOptionsParser::parse(s)?;
420+
let srv = parser.srv;
421+
let auth_source_present = parser.auth_source.is_some();
422+
let mut options: Self = parser.into();
427423

428-
/// If self.srv is true, then the SRV and TXT records for the host will be looked up, and the
429-
/// ClientOptions will be updated with the hosts found in the SRV records and the options found
430-
/// in the TXT record. After returning, self.srv will be set to false.
431-
///
432-
/// If self.srv is false, this method will not change the ClientOptions.
433-
///
434-
/// Note that MongoDB requires that exactly one address is present in an SRV configuration, and
435-
/// that address must not have a port. If more a port or more than one address is present, an
436-
/// error will be returned.
437-
///
438-
/// See [the MongoDB documentation](https://docs.mongodb.com/manual/reference/connection-string/#dns-seedlist-connection-format) for more details.
439-
pub fn resolve_srv(&mut self) -> Result<()> {
440-
let resolver = SrvResolver::new()?;
441-
resolver.resolve_and_update_client_opts(self)?;
442-
self.srv = false;
424+
if srv {
425+
let resolver = SrvResolver::new()?;
426+
let mut config = resolver.resolve_client_options(&options.hosts[0].hostname)?;
443427

444-
Ok(())
428+
// Set the ClientOptions hosts to those found during the SRV lookup.
429+
options.hosts = config.hosts;
430+
431+
// Enable TLS unless the user explicitly disabled it.
432+
if options.tls.is_none() {
433+
options.tls = Some(Tls::Enabled(Default::default()));
434+
}
435+
436+
// Set the authSource TXT option found during SRV lookup unless the user already set it.
437+
// Note that this _does_ override the default database specified in the URI, since it is
438+
// supposed to be overriden by authSource.
439+
if !auth_source_present {
440+
if let Some(auth_source) = config.auth_source.take() {
441+
options
442+
.credential
443+
.get_or_insert_with(Default::default)
444+
.source = Some(auth_source);
445+
}
446+
}
447+
448+
// Set the replica set name TXT option found during SRV lookup unless the user already
449+
// set it.
450+
if options.repl_set_name.is_none() {
451+
if let Some(replica_set) = config.replica_set.take() {
452+
options.repl_set_name = Some(replica_set);
453+
}
454+
}
455+
}
456+
457+
Ok(options)
445458
}
446459

447460
pub(crate) fn tls_options(&self) -> Option<TlsOptions> {
@@ -692,7 +705,7 @@ impl ClientOptionsParser {
692705

693706
credential.source = options
694707
.auth_source
695-
.take()
708+
.clone()
696709
.or_else(|| Some(mechanism.default_source(db_str).into()));
697710

698711
if let Some(mut doc) = options.auth_mechanism_properties.take() {
@@ -725,7 +738,7 @@ impl ClientOptionsParser {
725738
// SCRAM default (i.e. "admin").
726739
credential.source = options
727740
.auth_source
728-
.take()
741+
.clone()
729742
.or(db)
730743
.or_else(|| Some("admin".into()));
731744
} else if authentication_requested {
@@ -1102,7 +1115,9 @@ impl ClientOptionsParser {
11021115
"authmechanism" => {
11031116
self.auth_mechanism = Some(AuthMechanism::from_str(value)?);
11041117
}
1105-
"authsource" => self.auth_source = Some(value.to_string()),
1118+
"authsource" => {
1119+
self.auth_source = Some(value.to_string());
1120+
}
11061121
"authmechanismproperties" => {
11071122
let mut doc = Document::new();
11081123
let err_func = || {
@@ -1223,35 +1238,35 @@ mod tests {
12231238

12241239
#[test]
12251240
fn fails_without_scheme() {
1226-
assert!(ClientOptions::parse_uri("localhost:27017").is_err());
1241+
assert!(ClientOptions::parse("localhost:27017").is_err());
12271242
}
12281243

12291244
#[test]
12301245
fn fails_with_invalid_scheme() {
1231-
assert!(ClientOptions::parse_uri("mangodb://localhost:27017").is_err());
1246+
assert!(ClientOptions::parse("mangodb://localhost:27017").is_err());
12321247
}
12331248

12341249
#[test]
12351250
fn fails_with_nothing_after_scheme() {
1236-
assert!(ClientOptions::parse_uri("mongodb://").is_err());
1251+
assert!(ClientOptions::parse("mongodb://").is_err());
12371252
}
12381253

12391254
#[test]
12401255
fn fails_with_only_slash_after_scheme() {
1241-
assert!(ClientOptions::parse_uri("mongodb:///").is_err());
1256+
assert!(ClientOptions::parse("mongodb:///").is_err());
12421257
}
12431258

12441259
#[test]
12451260
fn fails_with_no_host() {
1246-
assert!(ClientOptions::parse_uri("mongodb://:27017").is_err());
1261+
assert!(ClientOptions::parse("mongodb://:27017").is_err());
12471262
}
12481263

12491264
#[test]
12501265
fn no_port() {
12511266
let uri = "mongodb://localhost";
12521267

12531268
assert_eq!(
1254-
ClientOptions::parse_uri(uri).unwrap(),
1269+
ClientOptions::parse(uri).unwrap(),
12551270
ClientOptions {
12561271
hosts: vec![host_without_port("localhost")],
12571272
original_uri: Some(uri.into()),
@@ -1265,7 +1280,7 @@ mod tests {
12651280
let uri = "mongodb://localhost/";
12661281

12671282
assert_eq!(
1268-
ClientOptions::parse_uri(uri).unwrap(),
1283+
ClientOptions::parse(uri).unwrap(),
12691284
ClientOptions {
12701285
hosts: vec![host_without_port("localhost")],
12711286
original_uri: Some(uri.into()),
@@ -1279,7 +1294,7 @@ mod tests {
12791294
let uri = "mongodb://localhost/";
12801295

12811296
assert_eq!(
1282-
ClientOptions::parse_uri(uri).unwrap(),
1297+
ClientOptions::parse(uri).unwrap(),
12831298
ClientOptions {
12841299
hosts: vec![StreamAddress {
12851300
hostname: "localhost".to_string(),
@@ -1296,7 +1311,7 @@ mod tests {
12961311
let uri = "mongodb://localhost:27017/";
12971312

12981313
assert_eq!(
1299-
ClientOptions::parse_uri(uri).unwrap(),
1314+
ClientOptions::parse(uri).unwrap(),
13001315
ClientOptions {
13011316
hosts: vec![StreamAddress {
13021317
hostname: "localhost".to_string(),
@@ -1313,7 +1328,7 @@ mod tests {
13131328
let uri = "mongodb://localhost:27017/?readConcernLevel=foo";
13141329

13151330
assert_eq!(
1316-
ClientOptions::parse_uri(uri).unwrap(),
1331+
ClientOptions::parse(uri).unwrap(),
13171332
ClientOptions {
13181333
hosts: vec![StreamAddress {
13191334
hostname: "localhost".to_string(),
@@ -1328,7 +1343,7 @@ mod tests {
13281343

13291344
#[test]
13301345
fn with_w_negative_int() {
1331-
assert!(ClientOptions::parse_uri("mongodb://localhost:27017/?w=-1").is_err());
1346+
assert!(ClientOptions::parse("mongodb://localhost:27017/?w=-1").is_err());
13321347
}
13331348

13341349
#[test]
@@ -1337,7 +1352,7 @@ mod tests {
13371352
let write_concern = WriteConcern::builder().w(Acknowledgment::from(1)).build();
13381353

13391354
assert_eq!(
1340-
ClientOptions::parse_uri(uri).unwrap(),
1355+
ClientOptions::parse(uri).unwrap(),
13411356
ClientOptions {
13421357
hosts: vec![StreamAddress {
13431358
hostname: "localhost".to_string(),
@@ -1358,7 +1373,7 @@ mod tests {
13581373
.build();
13591374

13601375
assert_eq!(
1361-
ClientOptions::parse_uri(uri).unwrap(),
1376+
ClientOptions::parse(uri).unwrap(),
13621377
ClientOptions {
13631378
hosts: vec![StreamAddress {
13641379
hostname: "localhost".to_string(),
@@ -1373,7 +1388,7 @@ mod tests {
13731388

13741389
#[test]
13751390
fn with_invalid_j() {
1376-
assert!(ClientOptions::parse_uri("mongodb://localhost:27017/?journal=foo").is_err());
1391+
assert!(ClientOptions::parse("mongodb://localhost:27017/?journal=foo").is_err());
13771392
}
13781393

13791394
#[test]
@@ -1382,7 +1397,7 @@ mod tests {
13821397
let write_concern = WriteConcern::builder().journal(true).build();
13831398

13841399
assert_eq!(
1385-
ClientOptions::parse_uri(uri).unwrap(),
1400+
ClientOptions::parse(uri).unwrap(),
13861401
ClientOptions {
13871402
hosts: vec![StreamAddress {
13881403
hostname: "localhost".to_string(),
@@ -1397,12 +1412,12 @@ mod tests {
13971412

13981413
#[test]
13991414
fn with_wtimeout_non_int() {
1400-
assert!(ClientOptions::parse_uri("mongodb://localhost:27017/?wtimeoutMS=foo").is_err());
1415+
assert!(ClientOptions::parse("mongodb://localhost:27017/?wtimeoutMS=foo").is_err());
14011416
}
14021417

14031418
#[test]
14041419
fn with_wtimeout_negative_int() {
1405-
assert!(ClientOptions::parse_uri("mongodb://localhost:27017/?wtimeoutMS=-1").is_err());
1420+
assert!(ClientOptions::parse("mongodb://localhost:27017/?wtimeoutMS=-1").is_err());
14061421
}
14071422

14081423
#[test]
@@ -1413,7 +1428,7 @@ mod tests {
14131428
.build();
14141429

14151430
assert_eq!(
1416-
ClientOptions::parse_uri(uri).unwrap(),
1431+
ClientOptions::parse(uri).unwrap(),
14171432
ClientOptions {
14181433
hosts: vec![StreamAddress {
14191434
hostname: "localhost".to_string(),
@@ -1436,7 +1451,7 @@ mod tests {
14361451
.build();
14371452

14381453
assert_eq!(
1439-
ClientOptions::parse_uri(uri).unwrap(),
1454+
ClientOptions::parse(uri).unwrap(),
14401455
ClientOptions {
14411456
hosts: vec![StreamAddress {
14421457
hostname: "localhost".to_string(),
@@ -1466,7 +1481,7 @@ mod tests {
14661481
.build();
14671482

14681483
assert_eq!(
1469-
ClientOptions::parse_uri(uri).unwrap(),
1484+
ClientOptions::parse(uri).unwrap(),
14701485
ClientOptions {
14711486
hosts: vec![
14721487
StreamAddress {

0 commit comments

Comments
 (0)