summaryrefslogtreecommitdiff
path: root/src/mongo/client
diff options
context:
space:
mode:
authorADAM David Alan Martin <adam.martin@10gen.com>2017-10-31 15:49:49 -0400
committerADAM David Alan Martin <adam.martin@10gen.com>2017-10-31 15:49:49 -0400
commit400b86d2963b30730cccfe7ae6829e6101998ac8 (patch)
tree2741ecc5a785e60afc80e4d76ec1fa9f15144b5d /src/mongo/client
parent2a8818d4fd7b833b82997c9996aca9eb14471f09 (diff)
downloadmongo-400b86d2963b30730cccfe7ae6829e6101998ac8.tar.gz
SERVER-31061 Add `mongo+srv://` support for URIs.
The shell now supports parsing and handling `mongo+srv://` style URIs, as part of the DNS Seedlist support. These URIs require DNS SRV and TXT record lookups, for extra configuration options. The shell also supports a (limited) form of connection-failover -- when initially connecting to a non-replica-set cluster, the shell will try each host listed, in order, until a connection can be established.
Diffstat (limited to 'src/mongo/client')
-rw-r--r--src/mongo/client/SConscript3
-rw-r--r--src/mongo/client/connection_string.cpp3
-rw-r--r--src/mongo/client/connection_string.h15
-rw-r--r--src/mongo/client/connection_string_connect.cpp33
-rw-r--r--src/mongo/client/connpool.cpp4
-rw-r--r--src/mongo/client/mongo_uri.cpp172
-rw-r--r--src/mongo/client/mongo_uri.h35
-rw-r--r--src/mongo/client/mongo_uri_test.cpp158
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json6
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json10
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json14
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json4
12 files changed, 328 insertions, 129 deletions
diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript
index 16dd55dafa0..1956ae58109 100644
--- a/src/mongo/client/SConscript
+++ b/src/mongo/client/SConscript
@@ -24,6 +24,9 @@ env.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/util/net/network',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/util/dns_query',
]
)
diff --git a/src/mongo/client/connection_string.cpp b/src/mongo/client/connection_string.cpp
index 16a6e78440d..5e8a5f4e0bb 100644
--- a/src/mongo/client/connection_string.cpp
+++ b/src/mongo/client/connection_string.cpp
@@ -121,9 +121,6 @@ void ConnectionString::_finishInit() {
uassert(ErrorCodes::FailedToParse,
"Cannot specify a replica set name for a ConnectionString of type MASTER",
_setName.empty());
- uassert(ErrorCodes::FailedToParse,
- "ConnectionStrings of type MASTER must contain exactly one server",
- _servers.size() == 1);
break;
case SET:
uassert(ErrorCodes::FailedToParse,
diff --git a/src/mongo/client/connection_string.h b/src/mongo/client/connection_string.h
index 2e888b56dad..0467a77dce3 100644
--- a/src/mongo/client/connection_string.h
+++ b/src/mongo/client/connection_string.h
@@ -28,6 +28,7 @@
#pragma once
+#include <memory>
#include <sstream>
#include <string>
#include <vector>
@@ -119,10 +120,10 @@ public:
bool operator==(const ConnectionString& other) const;
bool operator!=(const ConnectionString& other) const;
- DBClientBase* connect(StringData applicationName,
- std::string& errmsg,
- double socketTimeout = 0,
- const MongoURI* uri = nullptr) const;
+ std::unique_ptr<DBClientBase> connect(StringData applicationName,
+ std::string& errmsg,
+ double socketTimeout = 0,
+ const MongoURI* uri = nullptr) const;
static StatusWith<ConnectionString> parse(const std::string& url);
@@ -139,9 +140,9 @@ public:
virtual ~ConnectionHook() {}
// Returns an alternative connection object for a string
- virtual DBClientBase* connect(const ConnectionString& c,
- std::string& errmsg,
- double socketTimeout) = 0;
+ virtual std::unique_ptr<DBClientBase> connect(const ConnectionString& c,
+ std::string& errmsg,
+ double socketTimeout) = 0;
};
static void setConnectionHook(ConnectionHook* hook) {
diff --git a/src/mongo/client/connection_string_connect.cpp b/src/mongo/client/connection_string_connect.cpp
index e4c99dd37e1..d824fa6be78 100644
--- a/src/mongo/client/connection_string_connect.cpp
+++ b/src/mongo/client/connection_string_connect.cpp
@@ -46,10 +46,10 @@ namespace mongo {
stdx::mutex ConnectionString::_connectHookMutex;
ConnectionString::ConnectionHook* ConnectionString::_connectHook = NULL;
-DBClientBase* ConnectionString::connect(StringData applicationName,
- std::string& errmsg,
- double socketTimeout,
- const MongoURI* uri) const {
+std::unique_ptr<DBClientBase> ConnectionString::connect(StringData applicationName,
+ std::string& errmsg,
+ double socketTimeout,
+ const MongoURI* uri) const {
MongoURI newURI{};
if (uri) {
newURI = *uri;
@@ -57,15 +57,18 @@ DBClientBase* ConnectionString::connect(StringData applicationName,
switch (_type) {
case MASTER: {
- auto c = stdx::make_unique<DBClientConnection>(true, 0, std::move(newURI));
-
- c->setSoTimeout(socketTimeout);
- LOG(1) << "creating new connection to:" << _servers[0];
- if (!c->connect(_servers[0], applicationName, errmsg)) {
- return 0;
+ for (const auto& server : _servers) {
+ auto c = stdx::make_unique<DBClientConnection>(true, 0, newURI);
+
+ c->setSoTimeout(socketTimeout);
+ LOG(1) << "creating new connection to:" << server;
+ if (!c->connect(server, applicationName, errmsg)) {
+ continue;
+ }
+ LOG(1) << "connected connection!";
+ return std::move(c);
}
- LOG(1) << "connected connection!";
- return c.release();
+ return nullptr;
}
case SET: {
@@ -74,9 +77,9 @@ DBClientBase* ConnectionString::connect(StringData applicationName,
if (!set->connect()) {
errmsg = "connect failed to replica set ";
errmsg += toString();
- return 0;
+ return nullptr;
}
- return set.release();
+ return std::move(set);
}
case CUSTOM: {
@@ -91,7 +94,7 @@ DBClientBase* ConnectionString::connect(StringData applicationName,
_connectHook);
// Double-checked lock, since this will never be active during normal operation
- DBClientBase* replacementConn = _connectHook->connect(*this, errmsg, socketTimeout);
+ auto replacementConn = _connectHook->connect(*this, errmsg, socketTimeout);
log() << "replacing connection to " << this->toString() << " with "
<< (replacementConn ? replacementConn->getServerAddress() : "(empty)");
diff --git a/src/mongo/client/connpool.cpp b/src/mongo/client/connpool.cpp
index 30325079fab..6c5a3d9cdfa 100644
--- a/src/mongo/client/connpool.cpp
+++ b/src/mongo/client/connpool.cpp
@@ -256,7 +256,7 @@ DBClientBase* DBConnectionPool::get(const ConnectionString& url, double socketTi
// If no connections for this host are available in the PoolForHost (that is, all the
// connections have been checked out, or none have been created yet), create a new connection.
string errmsg;
- c = url.connect(StringData(), errmsg, socketTimeout);
+ c = url.connect(StringData(), errmsg, socketTimeout).release();
uassert(13328, _name + ": connect failed " + url.toString() + " : " + errmsg, c);
return _finishCreate(url.toString(), socketTimeout, c);
@@ -277,7 +277,7 @@ DBClientBase* DBConnectionPool::get(const string& host, double socketTimeout) {
const ConnectionString cs(uassertStatusOK(ConnectionString::parse(host)));
string errmsg;
- c = cs.connect(StringData(), errmsg, socketTimeout);
+ c = cs.connect(StringData(), errmsg, socketTimeout).release();
if (!c)
throw SocketException(SocketException::CONNECT_ERROR,
host,
diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp
index e2356881290..e7c500d719b 100644
--- a/src/mongo/client/mongo_uri.cpp
+++ b/src/mongo/client/mongo_uri.cpp
@@ -34,24 +34,26 @@
#include <utility>
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/find_iterator.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
#include "mongo/base/status_with.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/client/dbclientinterface.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/db/namespace_string.h"
+#include "mongo/util/dns_query.h"
#include "mongo/util/hex.h"
#include "mongo/util/mongoutils/str.h"
-#include <boost/algorithm/string/case_conv.hpp>
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/find_iterator.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-
namespace {
constexpr std::array<char, 16> hexits{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
const mongo::StringData kURIPrefix{"mongodb://"};
-}
+const mongo::StringData kURISRVPrefix{"mongodb+srv://"};
+} // namespace
/**
* RFC 3986 Section 2.1 - Percent Encoding
@@ -94,7 +96,8 @@ namespace mongo {
namespace {
/**
- * Helper Method for MongoURI::parse() to split a string into exactly 2 pieces by a char delimeter
+ * Helper Method for MongoURI::parse() to split a string into exactly 2 pieces by a char
+ * delimeter.
*/
std::pair<StringData, StringData> partitionForward(StringData str, const char c) {
const auto delim = str.find(c);
@@ -105,8 +108,8 @@ std::pair<StringData, StringData> partitionForward(StringData str, const char c)
}
/**
- * Helper method for MongoURI::parse() to split a string into exactly 2 pieces by a char delimiter
- * searching backward from the end of the string.
+ * Helper method for MongoURI::parse() to split a string into exactly 2 pieces by a char
+ * delimiter searching backward from the end of the string.
*/
std::pair<StringData, StringData> partitionBackward(StringData str, const char c) {
const auto delim = str.rfind(c);
@@ -121,17 +124,17 @@ std::pair<StringData, StringData> partitionBackward(StringData str, const char c
*
* foo=bar&baz=qux&...
*/
-StatusWith<MongoURI::OptionsMap> parseOptions(StringData options, StringData url) {
+MongoURI::OptionsMap parseOptions(StringData options, StringData url) {
MongoURI::OptionsMap ret;
if (options.empty()) {
return ret;
}
if (options.find('?') != std::string::npos) {
- return Status(ErrorCodes::FailedToParse,
- str::stream()
- << "URI Cannot Contain multiple questions marks for mongodb:// URL: "
- << url);
+ throw DBException(ErrorCodes::FailedToParse,
+ str::stream()
+ << "URI Cannot Contain multiple questions marks for mongodb:// URL: "
+ << url);
}
const auto optionsStr = options.toString();
@@ -141,16 +144,16 @@ StatusWith<MongoURI::OptionsMap> parseOptions(StringData options, StringData url
++i) {
const auto opt = boost::copy_range<std::string>(*i);
if (opt.empty()) {
- return Status(ErrorCodes::FailedToParse,
- str::stream()
- << "Missing a key/value pair in the options for mongodb:// URL: "
- << url);
+ throw DBException(ErrorCodes::FailedToParse,
+ str::stream()
+ << "Missing a key/value pair in the options for mongodb:// URL: "
+ << url);
}
const auto kvPair = partitionForward(opt, '=');
const auto keyRaw = kvPair.first;
if (keyRaw.empty()) {
- return Status(
+ throw DBException(
ErrorCodes::FailedToParse,
str::stream()
<< "Missing a key for key/value pair in the options for mongodb:// URL: "
@@ -158,7 +161,7 @@ StatusWith<MongoURI::OptionsMap> parseOptions(StringData options, StringData url
}
const auto key = uriDecode(keyRaw);
if (!key.isOK()) {
- return Status(
+ throw DBException(
ErrorCodes::FailedToParse,
str::stream() << "Key '" << keyRaw
<< "' in options cannot properly be URL decoded for mongodb:// URL: "
@@ -166,14 +169,14 @@ StatusWith<MongoURI::OptionsMap> parseOptions(StringData options, StringData url
}
const auto valRaw = kvPair.second;
if (valRaw.empty()) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Missing value for key '" << keyRaw
- << "' in the options for mongodb:// URL: "
- << url);
+ throw DBException(ErrorCodes::FailedToParse,
+ str::stream() << "Missing value for key '" << keyRaw
+ << "' in the options for mongodb:// URL: "
+ << url);
}
const auto val = uriDecode(valRaw);
if (!val.isOK()) {
- return Status(
+ throw DBException(
ErrorCodes::FailedToParse,
str::stream() << "Value '" << valRaw << "' for key '" << keyRaw
<< "' in options cannot properly be URL decoded for mongodb:// URL: "
@@ -186,20 +189,38 @@ StatusWith<MongoURI::OptionsMap> parseOptions(StringData options, StringData url
return ret;
}
+MongoURI::OptionsMap addTXTOptions(MongoURI::OptionsMap options,
+ const std::string& host,
+ const StringData url,
+ const bool isSeedlist) {
+ // If there is no seedlist mode, then don't add any TXT options.
+ if (!isSeedlist)
+ return options;
+
+ // Get all TXT records and parse them as options, adding them to the options set.
+ const auto txtRecords = dns::getTXTRecords(host);
+
+ for (const auto& record : txtRecords) {
+ auto txtOptions = parseOptions(record, url);
+ // Note that, `std::map` and `std::unordered_map` insert does not replace existing
+ // values -- this gives the desired behavior that user-specified values override TXT
+ // record specified values.
+ options.insert(begin(txtOptions), end(txtOptions));
+ }
+
+ return options;
+}
} // namespace
-StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
+MongoURI MongoURI::parseImpl(const std::string& url) {
const StringData urlSD(url);
// 1. Validate and remove the scheme prefix mongodb://
- if (!urlSD.startsWith(kURIPrefix)) {
- const auto cs_status = ConnectionString::parse(url);
- if (!cs_status.isOK()) {
- return cs_status.getStatus();
- }
- return MongoURI(cs_status.getValue());
+ const bool isSeedlist = urlSD.startsWith(kURISRVPrefix);
+ if (!(urlSD.startsWith(kURIPrefix) || isSeedlist)) {
+ return MongoURI(uassertStatusOK(ConnectionString::parse(url)));
}
- const auto uriWithoutPrefix = urlSD.substr(kURIPrefix.size());
+ const auto uriWithoutPrefix = urlSD.substr(urlSD.find("://") + 3);
// 2. Split the string by the first, unescaped / (if any), yielding:
// split[0]: User information and host identifers
@@ -211,14 +232,15 @@ StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
// 2.b Make sure that there are no question marks in the left side of the /
// as any options after the ? must still have the / delimeter
if (databaseAndOptions.empty() && userAndHostInfo.find('?') != std::string::npos) {
- return Status(
+ throw DBException(
ErrorCodes::FailedToParse,
str::stream()
<< "URI must contain slash delimeter between hosts and options for mongodb:// URL: "
<< url);
}
- // 3. Split the user information and host identifiers string by the last, unescaped @, yielding:
+ // 3. Split the user information and host identifiers string by the last, unescaped @,
+ // yielding:
// split[0]: User information
// split[1]: Host identifiers;
const auto userAndHost = partitionBackward(userAndHostInfo, '@');
@@ -237,18 +259,21 @@ StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
};
if (containsColonOrAt(usernameSD)) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Username must be URL Encoded for mongodb:// URL: " << url);
+ throw DBException(ErrorCodes::FailedToParse,
+ str::stream() << "Username must be URL Encoded for mongodb:// URL: "
+ << url);
}
+
if (containsColonOrAt(passwordSD)) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Password must be URL Encoded for mongodb:// URL: " << url);
+ throw DBException(ErrorCodes::FailedToParse,
+ str::stream() << "Password must be URL Encoded for mongodb:// URL: "
+ << url);
}
// Get the username and make sure it did not fail to decode
const auto usernameWithStatus = uriDecode(usernameSD);
if (!usernameWithStatus.isOK()) {
- return Status(
+ throw DBException(
ErrorCodes::FailedToParse,
str::stream() << "Username cannot properly be URL decoded for mongodb:// URL: " << url);
}
@@ -257,7 +282,7 @@ StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
// Get the password and make sure it did not fail to decode
const auto passwordWithStatus = uriDecode(passwordSD);
if (!passwordWithStatus.isOK())
- return Status(
+ throw DBException(
ErrorCodes::FailedToParse,
str::stream() << "Password cannot properly be URL decoded for mongodb:// URL: " << url);
const auto password = passwordWithStatus.getValue();
@@ -271,7 +296,7 @@ StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
++i) {
const auto hostWithStatus = uriDecode(boost::copy_range<std::string>(*i));
if (!hostWithStatus.isOK()) {
- return Status(
+ throw DBException(
ErrorCodes::FailedToParse,
str::stream() << "Host cannot properly be URL decoded for mongodb:// URL: " << url);
}
@@ -282,23 +307,35 @@ StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
}
if ((host.find('/') != std::string::npos) && !StringData(host).endsWith(".sock")) {
- return Status(
+ throw DBException(
ErrorCodes::FailedToParse,
str::stream() << "'" << host << "' in '" << url
<< "' appears to be a unix socket, but does not end in '.sock'");
}
- const auto statusHostAndPort = HostAndPort::parse(host);
- if (!statusHostAndPort.isOK()) {
- return statusHostAndPort.getStatus();
- }
- servers.push_back(statusHostAndPort.getValue());
+ servers.push_back(uassertStatusOK(HostAndPort::parse(host)));
}
if (servers.empty()) {
- return Status(ErrorCodes::FailedToParse, "No server(s) specified");
+ throw DBException(ErrorCodes::FailedToParse, "No server(s) specified");
}
- // 6. Split the auth database and connection options string by the first, unescaped ?, yielding:
+ const std::string canonicalHost = servers.front().host();
+ // If we're in seedlist mode, lookup the SRV record for `_mongodb._tcp` on the specified
+ // domain name. Take that list of servers as the new list of servers.
+ if (isSeedlist) {
+ if (servers.size() > 1) {
+ throw DBException(ErrorCodes::FailedToParse,
+ "Only a single server may be specified with a mongo+srv:// url.");
+ }
+ auto srvEntries = dns::lookupSRVRecords("_mongodb._tcp." + canonicalHost);
+ servers.clear();
+ std::transform(begin(srvEntries), end(srvEntries), back_inserter(servers), [](auto& srv) {
+ return HostAndPort(std::move(srv.host), srv.port);
+ });
+ }
+
+ // 6. Split the auth database and connection options string by the first, unescaped ?,
+ // yielding:
// split[0] = auth database
// split[1] = connection options
const auto dbAndOpts = partitionForward(databaseAndOptions, '?');
@@ -306,10 +343,10 @@ StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
const auto connectionOptions = dbAndOpts.second;
const auto databaseWithStatus = uriDecode(databaseSD);
if (!databaseWithStatus.isOK()) {
- return Status(ErrorCodes::FailedToParse,
- str::stream()
- << "Database name cannot properly be URL decoded for mongodb:// URL: "
- << url);
+ throw DBException(ErrorCodes::FailedToParse,
+ str::stream() << "Database name cannot properly be URL "
+ "decoded for mongodb:// URL: "
+ << url);
}
const auto database = databaseWithStatus.getValue();
@@ -320,35 +357,32 @@ StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
if (!database.empty() &&
!NamespaceString::validDBName(database,
NamespaceString::DollarInDbNameBehavior::Disallow)) {
- return Status(ErrorCodes::FailedToParse,
- str::stream()
- << "Database name cannot have reserved characters for mongodb:// URL: "
- << url);
+ throw DBException(ErrorCodes::FailedToParse,
+ str::stream() << "Database name cannot have reserved "
+ "characters for mongodb:// URL: "
+ << url);
}
// 8. Validate, split, and URL decode the connection options
- const auto optsWith = parseOptions(connectionOptions, url);
- if (!optsWith.isOK()) {
- return optsWith.getStatus();
- }
- const auto options = optsWith.getValue();
+ auto options =
+ addTXTOptions(parseOptions(connectionOptions, url), canonicalHost, url, isSeedlist);
// If a replica set option was specified, store it in the 'setName' field.
const auto optIter = options.find("replicaSet");
std::string setName;
- if (optIter != options.end()) {
+ if (optIter != end(options)) {
setName = optIter->second;
invariant(!setName.empty());
}
- if ((servers.size() > 1) && setName.empty()) {
- return Status(ErrorCodes::FailedToParse,
- "Cannot list multiple servers in URL without 'replicaSet' option");
- }
-
ConnectionString cs(
setName.empty() ? ConnectionString::MASTER : ConnectionString::SET, servers, setName);
return MongoURI(std::move(cs), username, password, database, std::move(options));
}
+StatusWith<MongoURI> MongoURI::parse(const std::string& url) try {
+ return parseImpl(url);
+} catch (const std::exception&) {
+ return exceptionToStatus();
+}
} // namespace mongo
diff --git a/src/mongo/client/mongo_uri.h b/src/mongo/client/mongo_uri.h
index f7a7c4aeb81..11d948a526c 100644
--- a/src/mongo/client/mongo_uri.h
+++ b/src/mongo/client/mongo_uri.h
@@ -1,7 +1,7 @@
/**
* Copyright (C) 2015 MongoDB Inc.
*
- * This program is free software: you can redistribute it and/or modify
+ * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
@@ -43,7 +43,6 @@
#include "mongo/util/net/hostandport.h"
namespace mongo {
-
/**
* Encode a string for embedding in a URI.
* Replaces reserved bytes with %xx sequences.
@@ -51,6 +50,7 @@ namespace mongo {
* Optionally allows passthrough characters to remain unescaped.
*/
void uriEncode(std::ostream& ss, StringData str, StringData passthrough = ""_sd);
+
inline std::string uriEncode(StringData str, StringData passthrough = ""_sd) {
std::ostringstream ss;
uriEncode(ss, str, passthrough);
@@ -66,9 +66,13 @@ StatusWith<std::string> uriDecode(StringData str);
/**
* MongoURI handles parsing of URIs for mongodb, and falls back to old-style
* ConnectionString parsing. It's used primarily by the shell.
- * It parses URIs with the following format:
+ * It parses URIs with the following formats:
*
* mongodb://[usr:pwd@]host1[:port1]...[,hostN[:portN]]][/[db][?options]]
+ * mongodb+srv://[usr:pwd@]host[/[db][?options]]
+ *
+ * `mongodb+srv://` URIs will perform DNS SRV and TXT lookups and expand per the DNS Seedlist
+ * specification.
*
* While this format is generally RFC 3986 compliant, some exceptions do exist:
* 1. The 'host' field, as defined by section 3.2.2 is expanded in the following ways:
@@ -96,6 +100,11 @@ StatusWith<std::string> uriDecode(StringData str);
*/
class MongoURI {
public:
+ // Note that, because this map is used for DNS TXT record injection on options, there is a
+ // requirement on its behavior for `insert`: insert must not replace or update existing values
+ // -- this gives the desired behavior that user-specified values override TXT record specified
+ // values. `std::map` and `std::unordered_map` satisfy this requirement. Make sure that
+ // whichever map type is used provides that guarantee.
using OptionsMap = std::map<std::string, std::string>;
static StatusWith<MongoURI> parse(const std::string& url);
@@ -140,20 +149,21 @@ public:
// server (say a member of a replica-set), you can pass in its HostAndPort information to
// get a new URI with the same info, except type() will be MASTER and getServers() will
// be the single host you pass in.
- MongoURI cloneURIForServer(const HostAndPort& hostAndPort) const {
- return MongoURI(ConnectionString(hostAndPort), _user, _password, _database, _options);
+ MongoURI cloneURIForServer(HostAndPort hostAndPort) const {
+ return MongoURI(
+ ConnectionString(std::move(hostAndPort)), _user, _password, _database, _options);
}
ConnectionString::ConnectionType type() const {
return _connectString.type();
}
- explicit MongoURI(const ConnectionString connectString)
- : _connectString(std::move(connectString)){};
+ explicit MongoURI(const ConnectionString& connectString) : _connectString(connectString){};
MongoURI() = default;
friend std::ostream& operator<<(std::ostream&, const MongoURI&);
+
friend StringBuilder& operator<<(StringBuilder&, const MongoURI&);
private:
@@ -166,10 +176,12 @@ private:
_user(user),
_password(password),
_database(database),
- _options(std::move(options)){};
+ _options(std::move(options)) {}
BSONObj _makeAuthObjFromOptions(int maxWireVersion) const;
+ static MongoURI parseImpl(const std::string& url);
+
ConnectionString _connectString;
std::string _user;
std::string _password;
@@ -178,13 +190,10 @@ private:
};
inline std::ostream& operator<<(std::ostream& ss, const MongoURI& uri) {
- ss << uri._connectString;
- return ss;
+ return ss << uri._connectString;
}
inline StringBuilder& operator<<(StringBuilder& sb, const MongoURI& uri) {
- sb << uri._connectString;
- return sb;
+ return sb << uri._connectString;
}
-
} // namespace mongo
diff --git a/src/mongo/client/mongo_uri_test.cpp b/src/mongo/client/mongo_uri_test.cpp
index 00edcb110f4..5153452f9ec 100644
--- a/src/mongo/client/mongo_uri_test.cpp
+++ b/src/mongo/client/mongo_uri_test.cpp
@@ -1,7 +1,7 @@
/**
* Copyright (C) 2009-2015 MongoDB Inc.
*
- * This program is free software: you can redistribute it and/or modify
+ * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
@@ -506,8 +506,8 @@ TEST(MongoURI, CloneURIForServer) {
/**
* These tests come from the Mongo Uri Specifications for the drivers found at:
* https://github.com/mongodb/specifications/tree/master/source/connection-string/tests
- * They have been slighly altered as the Drivers specification is slighly different
- * from the server specification.
+ * They have been altered as the Drivers specification is somewhat different from the shell
+ * implementation.
*/
TEST(MongoURI, specTests) {
const std::string files[] = {
@@ -599,4 +599,156 @@ TEST(MongoURI, specTests) {
}
}
+TEST(MongoURI, srvRecordTest) {
+ using namespace mongo;
+ const struct {
+ std::string uri;
+ std::string user;
+ std::string password;
+ std::string database;
+ std::vector<HostAndPort> hosts;
+ std::map<std::string, std::string> options;
+ } tests[] = {
+ // Test some non-SRV URIs to make sure that they do not perform expansions
+ {"mongodb://test1.test.build.10gen.cc:12345/",
+ "",
+ "",
+ "",
+ {{"test1.test.build.10gen.cc", 12345}},
+ {}},
+ {"mongodb://test6.test.build.10gen.cc:12345/",
+ "",
+ "",
+ "",
+ {{"test6.test.build.10gen.cc", 12345}},
+ {}},
+
+ // Test a sample URI against each provided testing DNS entry
+ {"mongodb+srv://test1.test.build.10gen.cc/",
+ "",
+ "",
+ "",
+ {{"localhost.build.10gen.cc.", 27017}, {"localhost.build.10gen.cc.", 27018}},
+ {}},
+
+ {"mongodb+srv://user:password@test2.test.build.10gen.cc/"
+ "database?someOption=someValue&someOtherOption=someOtherValue",
+ "user",
+ "password",
+ "database",
+ {{"localhost.build.10gen.cc.", 27018}, {"localhost.build.10gen.cc.", 27019}},
+ {{"someOption", "someValue"}, {"someOtherOption", "someOtherValue"}}},
+
+
+ {"mongodb+srv://user:password@test3.test.build.10gen.cc/"
+ "database?someOption=someValue&someOtherOption=someOtherValue",
+ "user",
+ "password",
+ "database",
+ {{"localhost.build.10gen.cc.", 27017}},
+ {{"someOption", "someValue"}, {"someOtherOption", "someOtherValue"}}},
+
+
+ {"mongodb+srv://user:password@test5.test.build.10gen.cc/"
+ "database?someOption=someValue&someOtherOption=someOtherValue",
+ "user",
+ "password",
+ "database",
+ {{"localhost.build.10gen.cc.", 27017}},
+ {{"someOption", "someValue"},
+ {"someOtherOption", "someOtherValue"},
+ {"connectTimeoutMS", "300000"},
+ {"socketTimeoutMS", "300000"}}},
+
+ {"mongodb+srv://user:password@test5.test.build.10gen.cc/"
+ "database?someOption=someValue&socketTimeoutMS=100&someOtherOption=someOtherValue",
+ "user",
+ "password",
+ "database",
+ {{"localhost.build.10gen.cc.", 27017}},
+ {{"someOption", "someValue"},
+ {"someOtherOption", "someOtherValue"},
+ {"connectTimeoutMS", "300000"},
+ {"socketTimeoutMS", "100"}}},
+
+ {"mongodb+srv://test6.test.build.10gen.cc/",
+ "",
+ "",
+ "",
+ {{"localhost.build.10gen.cc.", 27017}},
+ {{"connectTimeoutMS", "200000"}, {"socketTimeoutMS", "200000"}}},
+
+ {"mongodb+srv://test6.test.build.10gen.cc/database",
+ "",
+ "",
+ "database",
+ {{"localhost.build.10gen.cc.", 27017}},
+ {{"connectTimeoutMS", "200000"}, {"socketTimeoutMS", "200000"}}},
+
+ {"mongodb+srv://test6.test.build.10gen.cc/?connectTimeoutMS=300000",
+ "",
+ "",
+ "",
+ {{"localhost.build.10gen.cc.", 27017}},
+ {{"connectTimeoutMS", "300000"}, {"socketTimeoutMS", "200000"}}},
+
+ {"mongodb+srv://test6.test.build.10gen.cc/?irrelevantOption=irrelevantValue",
+ "",
+ "",
+ "",
+ {{"localhost.build.10gen.cc.", 27017}},
+ {{"connectTimeoutMS", "200000"},
+ {"socketTimeoutMS", "200000"},
+ {"irrelevantOption", "irrelevantValue"}}},
+
+
+ {"mongodb+srv://test6.test.build.10gen.cc/"
+ "?irrelevantOption=irrelevantValue&connectTimeoutMS=300000",
+ "",
+ "",
+ "",
+ {{"localhost.build.10gen.cc.", 27017}},
+ {{"connectTimeoutMS", "300000"},
+ {"socketTimeoutMS", "200000"},
+ {"irrelevantOption", "irrelevantValue"}}},
+ };
+
+ for (const auto& test : tests) {
+ auto rs = MongoURI::parse(test.uri);
+ ASSERT_OK(rs.getStatus());
+ auto rv = rs.getValue();
+ ASSERT_EQ(rv.getUser(), test.user);
+ ASSERT_EQ(rv.getPassword(), test.password);
+ ASSERT_EQ(rv.getDatabase(), test.database);
+ std::vector<std::pair<std::string, std::string>> options(begin(rv.getOptions()),
+ end(rv.getOptions()));
+ std::sort(begin(options), end(options));
+ std::vector<std::pair<std::string, std::string>> expectedOptions(begin(test.options),
+ end(test.options));
+ std::sort(begin(expectedOptions), end(expectedOptions));
+
+ for (std::size_t i = 0; i < std::min(options.size(), expectedOptions.size()); ++i) {
+ if (options[i] != expectedOptions[i]) {
+ mongo::unittest::log() << "Option: \"" << options[i].first << "="
+ << options[i].second << "\" doesn't equal: \""
+ << expectedOptions[i].first << "="
+ << expectedOptions[i].second << "\"" << std::endl;
+ std::cerr << "Failing URI: \"" << test.uri << "\"" << std::endl;
+ ASSERT(false);
+ }
+ }
+ ASSERT_EQ(options.size(), expectedOptions.size());
+
+ std::vector<HostAndPort> hosts(begin(rv.getServers()), end(rv.getServers()));
+ std::sort(begin(hosts), end(hosts));
+ auto expectedHosts = test.hosts;
+ std::sort(begin(expectedHosts), end(expectedHosts));
+
+ for (std::size_t i = 0; i < std::min(hosts.size(), expectedHosts.size()); ++i) {
+ ASSERT_EQ(hosts[i], expectedHosts[i]);
+ }
+ ASSERT_TRUE(hosts.size() == expectedHosts.size());
+ }
+}
+
} // namespace
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json b/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json
index 8a1fda580a8..5af3797ccdb 100644
--- a/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json
@@ -108,7 +108,7 @@
{
"description": "Multiple hosts (mixed formats)",
"uri": "mongodb://127.0.0.1,[::1]:27018,example.com:27019",
- "valid": false,
+ "valid": true,
"warning": false,
"hosts": [
{
@@ -160,7 +160,7 @@
{
"description": "UTF-8 hosts",
"uri": "mongodb://bücher.example.com,umläut.example.com/",
- "valid": false,
+ "valid": true,
"warning": false,
"hosts": [
{
@@ -200,4 +200,4 @@
}
}
]
-} \ No newline at end of file
+}
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json
index d167c315bbc..f4f78883298 100644
--- a/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json
@@ -62,7 +62,7 @@
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -82,7 +82,7 @@
],
"options": null,
"uri": "mongodb://127.0.0.1:27017,%2Ftmp%2Fmongodb-27017.sock",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -102,7 +102,7 @@
],
"options": null,
"uri": "mongodb://mongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -260,7 +260,7 @@
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -312,7 +312,7 @@
"w": 1
},
"uri": "mongodb://bob:bar@%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?w=1",
- "valid": false,
+ "valid": true,
"warning": false
},
{
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json
index 8da45c4e1b9..a2d737ba72a 100644
--- a/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json
@@ -62,7 +62,7 @@
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -104,7 +104,7 @@
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -146,7 +146,7 @@
],
"options": null,
"uri": "mongodb://127.0.0.1:27017,rel%2Fmongodb-27017.sock",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -188,7 +188,7 @@
],
"options": null,
"uri": "mongodb://mongodb-27017.sock,rel%2Fmongodb-27018.sock",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -302,7 +302,7 @@
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -352,7 +352,7 @@
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -404,7 +404,7 @@
"w": 1
},
"uri": "mongodb://bob:bar@rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?w=1",
- "valid": false,
+ "valid": true,
"warning": false
},
{
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json b/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json
index a44236e7b62..68228c6ddec 100644
--- a/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json
@@ -135,7 +135,7 @@
],
"options": null,
"uri": "mongodb://alice:secret@127.0.0.1,example.com:27018",
- "valid": false,
+ "valid": true,
"warning": false
},
{
@@ -159,7 +159,7 @@
],
"options": null,
"uri": "mongodb://alice:secret@example.com,[::1]:27019/admin",
- "valid": false,
+ "valid": true,
"warning": false
},
{