summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2016-08-04 17:29:34 -0400
committerMark Benvenuto <mark.benvenuto@mongodb.com>2016-08-04 17:29:34 -0400
commitad27c92e01758c96e7ace4cba13574e0d97a761d (patch)
tree9ef9c726765d76b531c7090063900484b65ea4c8
parent931a227eedca19bc05fc6318996ffd3c6a2c6f4b (diff)
downloadmongo-ad27c92e01758c96e7ace4cba13574e0d97a761d.tar.gz
SERVER-24611 Implement ClientMetadata class
-rw-r--r--jstests/core/client_metadata_ismaster.js12
-rw-r--r--jstests/noPassthrough/currentop_query.js2
-rw-r--r--src/mongo/base/error_codes.err4
-rw-r--r--src/mongo/client/connection_pool.cpp2
-rw-r--r--src/mongo/client/connection_string.h4
-rw-r--r--src/mongo/client/connection_string_connect.cpp9
-rw-r--r--src/mongo/client/connpool.cpp4
-rw-r--r--src/mongo/client/dbclient.cpp24
-rw-r--r--src/mongo/client/dbclient_rs.cpp6
-rw-r--r--src/mongo/client/dbclient_rs.h2
-rw-r--r--src/mongo/client/dbclient_rs_test.cpp52
-rw-r--r--src/mongo/client/dbclientinterface.h7
-rw-r--r--src/mongo/client/mongo_uri.h2
-rw-r--r--src/mongo/client/mongo_uri_connect.cpp4
-rw-r--r--src/mongo/client/mongo_uri_test.cpp3
-rw-r--r--src/mongo/client/scoped_db_conn_test.cpp2
-rw-r--r--src/mongo/db/clientlistplugin.cpp11
-rw-r--r--src/mongo/db/cloner.cpp2
-rw-r--r--src/mongo/db/commands/clone_collection.cpp2
-rw-r--r--src/mongo/db/commands/copydb.cpp2
-rw-r--r--src/mongo/db/commands/copydb_start_commands.cpp4
-rw-r--r--src/mongo/db/commands/current_op.cpp11
-rw-r--r--src/mongo/db/repl/SConscript1
-rw-r--r--src/mongo/db/repl/oplogreader.cpp2
-rw-r--r--src/mongo/db/repl/replication_info.cpp33
-rw-r--r--src/mongo/db/repl/rollback_source_impl.cpp4
-rw-r--r--src/mongo/dbtests/mock/mock_conn_registry.cpp2
-rw-r--r--src/mongo/dbtests/mock/mock_dbclient_connection.cpp4
-rw-r--r--src/mongo/dbtests/mock/mock_dbclient_connection.h6
-rw-r--r--src/mongo/executor/network_interface_asio_auth.cpp3
-rw-r--r--src/mongo/rpc/SConscript26
-rw-r--r--src/mongo/rpc/metadata/client_metadata.cpp400
-rw-r--r--src/mongo/rpc/metadata/client_metadata.h230
-rw-r--r--src/mongo/rpc/metadata/client_metadata_ismaster.cpp77
-rw-r--r--src/mongo/rpc/metadata/client_metadata_ismaster.h89
-rw-r--r--src/mongo/rpc/metadata/client_metadata_test.cpp296
-rw-r--r--src/mongo/s/commands/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_is_master_cmd.cpp34
-rw-r--r--src/mongo/scripting/mozjs/mongo.cpp2
-rw-r--r--src/mongo/shell/bench.cpp2
-rw-r--r--src/mongo/shell/shell_utils.cpp2
-rw-r--r--src/mongo/shell/shell_utils_launcher.cpp3
-rw-r--r--src/mongo/tools/sniffer.cpp2
43 files changed, 1325 insertions, 65 deletions
diff --git a/jstests/core/client_metadata_ismaster.js b/jstests/core/client_metadata_ismaster.js
new file mode 100644
index 00000000000..e5aa7d2547a
--- /dev/null
+++ b/jstests/core/client_metadata_ismaster.js
@@ -0,0 +1,12 @@
+// Test that verifies client metadata behavior for isMaster
+
+(function() {
+ "use strict";
+
+ // Verify that a isMaster request fails if it contains client metadata, and it is not first.
+ // The shell sends isMaster on the first connection
+ var result = db.runCommand({"isMaster": 1, "client": {"application": "foobar"}});
+ assert.commandFailed(result);
+ assert.eq(result.code, ErrorCodes.ClientMetadataCannotBeMutated, tojson(result));
+
+})();
diff --git a/jstests/noPassthrough/currentop_query.js b/jstests/noPassthrough/currentop_query.js
index 287678b4eab..e01413ab58c 100644
--- a/jstests/noPassthrough/currentop_query.js
+++ b/jstests/noPassthrough/currentop_query.js
@@ -71,6 +71,8 @@
assert.commandWorked(result);
if (result.inprog.length === 1) {
+ assert.eq(result.inprog[0].appName, "MongoDB Shell", tojson(result));
+
return true;
}
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index cee5d694d4d..104ed4c8f84 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -181,6 +181,10 @@ error_code("WindowsPdhError", 179)
error_code("BadPerfCounterPath", 180)
error_code("AmbiguousIndexKeyPattern", 181)
error_code("InvalidViewDefinition", 182);
+error_code("ClientMetadataMissingField", 183)
+error_code("ClientMetadataAppNameTooLarge", 184)
+error_code("ClientMetadataDocumentTooLarge", 185)
+error_code("ClientMetadataCannotBeMutated", 186)
# Non-sequential error codes (for compatibility only)
error_code("SocketException", 9001)
diff --git a/src/mongo/client/connection_pool.cpp b/src/mongo/client/connection_pool.cpp
index 5278ba4a854..b70acd25daf 100644
--- a/src/mongo/client/connection_pool.cpp
+++ b/src/mongo/client/connection_pool.cpp
@@ -184,7 +184,7 @@ ConnectionPool::ConnectionList::iterator ConnectionPool::acquireConnection(
// the number of seconds with a fractional part.
conn->setSoTimeout(durationCount<Milliseconds>(timeout) / 1000.0);
- uassertStatusOK(conn->connect(target));
+ uassertStatusOK(conn->connect(target, StringData()));
conn->port().setTag(conn->port().getTag() | _messagingPortTags);
if (isInternalAuthSet()) {
diff --git a/src/mongo/client/connection_string.h b/src/mongo/client/connection_string.h
index 91836e36812..245174794d9 100644
--- a/src/mongo/client/connection_string.h
+++ b/src/mongo/client/connection_string.h
@@ -116,7 +116,9 @@ public:
bool operator==(const ConnectionString& other) const;
bool operator!=(const ConnectionString& other) const;
- DBClientBase* connect(std::string& errmsg, double socketTimeout = 0) const;
+ DBClientBase* connect(StringData applicationName,
+ std::string& errmsg,
+ double socketTimeout = 0) const;
static StatusWith<ConnectionString> parse(const std::string& url);
diff --git a/src/mongo/client/connection_string_connect.cpp b/src/mongo/client/connection_string_connect.cpp
index 4356735492e..64c2258fc5d 100644
--- a/src/mongo/client/connection_string_connect.cpp
+++ b/src/mongo/client/connection_string_connect.cpp
@@ -45,13 +45,15 @@ namespace mongo {
stdx::mutex ConnectionString::_connectHookMutex;
ConnectionString::ConnectionHook* ConnectionString::_connectHook = NULL;
-DBClientBase* ConnectionString::connect(std::string& errmsg, double socketTimeout) const {
+DBClientBase* ConnectionString::connect(StringData applicationName,
+ std::string& errmsg,
+ double socketTimeout) const {
switch (_type) {
case MASTER: {
auto c = stdx::make_unique<DBClientConnection>(true);
c->setSoTimeout(socketTimeout);
LOG(1) << "creating new connection to:" << _servers[0];
- if (!c->connect(_servers[0], errmsg)) {
+ if (!c->connect(_servers[0], applicationName, errmsg)) {
return 0;
}
LOG(1) << "connected connection!";
@@ -59,7 +61,8 @@ DBClientBase* ConnectionString::connect(std::string& errmsg, double socketTimeou
}
case SET: {
- auto set = stdx::make_unique<DBClientReplicaSet>(_setName, _servers, socketTimeout);
+ auto set = stdx::make_unique<DBClientReplicaSet>(
+ _setName, _servers, applicationName, socketTimeout);
if (!set->connect()) {
errmsg = "connect failed to replica set ";
errmsg += toString();
diff --git a/src/mongo/client/connpool.cpp b/src/mongo/client/connpool.cpp
index 411ff30177e..6b35ea9f399 100644
--- a/src/mongo/client/connpool.cpp
+++ b/src/mongo/client/connpool.cpp
@@ -235,7 +235,7 @@ DBClientBase* DBConnectionPool::get(const ConnectionString& url, double socketTi
}
string errmsg;
- c = url.connect(errmsg, socketTimeout);
+ c = url.connect(StringData(), errmsg, socketTimeout);
uassert(13328, _name + ": connect failed " + url.toString() + " : " + errmsg, c);
return _finishCreate(url.toString(), socketTimeout, c);
@@ -256,7 +256,7 @@ DBClientBase* DBConnectionPool::get(const string& host, double socketTimeout) {
const ConnectionString cs(uassertStatusOK(ConnectionString::parse(host)));
string errmsg;
- c = cs.connect(errmsg, socketTimeout);
+ c = cs.connect(StringData(), errmsg, socketTimeout);
if (!c)
throw SocketException(SocketException::CONNECT_ERROR,
host,
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp
index cbf72c4ae7d..af53009904a 100644
--- a/src/mongo/client/dbclient.cpp
+++ b/src/mongo/client/dbclient.cpp
@@ -55,6 +55,7 @@
#include "mongo/rpc/factory.h"
#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/rpc/metadata.h"
+#include "mongo/rpc/metadata/client_metadata.h"
#include "mongo/rpc/reply_interface.h"
#include "mongo/rpc/request_builder_interface.h"
#include "mongo/s/stale_exception.h" // for RecvStaleConfigException
@@ -74,6 +75,7 @@
#include "mongo/util/password_digest.h"
#include "mongo/util/represent_as.h"
#include "mongo/util/time_support.h"
+#include "mongo/util/version.h"
namespace mongo {
@@ -712,7 +714,8 @@ private:
/**
* Initializes the wire version of conn, and returns the isMaster reply.
*/
-executor::RemoteCommandResponse initWireVersion(DBClientConnection* conn) {
+executor::RemoteCommandResponse initWireVersion(DBClientConnection* conn,
+ StringData applicationName) {
try {
// We need to force the usage of OP_QUERY on this command, even if we have previously
// detected support for OP_COMMAND on a connection. This is necessary to handle the case
@@ -731,6 +734,12 @@ executor::RemoteCommandResponse initWireVersion(DBClientConnection* conn) {
bob.append("hostInfo", sb.str());
}
+ Status serializeStatus = ClientMetadata::serialize(
+ "MongoDB Internal Client", mongo::versionString, applicationName, &bob);
+ if (!serializeStatus.isOK()) {
+ return serializeStatus;
+ }
+
Date_t start{Date_t::now()};
auto result =
conn->runCommandWithMetadata("admin", "isMaster", rpc::makeEmptyMetadata(), bob.done());
@@ -754,8 +763,10 @@ executor::RemoteCommandResponse initWireVersion(DBClientConnection* conn) {
} // namespace
-bool DBClientConnection::connect(const HostAndPort& server, std::string& errmsg) {
- auto connectStatus = connect(server);
+bool DBClientConnection::connect(const HostAndPort& server,
+ StringData applicationName,
+ std::string& errmsg) {
+ auto connectStatus = connect(server, applicationName);
if (!connectStatus.isOK()) {
errmsg = connectStatus.reason();
return false;
@@ -763,13 +774,14 @@ bool DBClientConnection::connect(const HostAndPort& server, std::string& errmsg)
return true;
}
-Status DBClientConnection::connect(const HostAndPort& serverAddress) {
+Status DBClientConnection::connect(const HostAndPort& serverAddress, StringData applicationName) {
auto connectStatus = connectSocketOnly(serverAddress);
if (!connectStatus.isOK()) {
return connectStatus;
}
- auto swIsMasterReply = initWireVersion(this);
+ _applicationName = applicationName.toString();
+ auto swIsMasterReply = initWireVersion(this, applicationName);
if (!swIsMasterReply.isOK()) {
_failed = true;
return swIsMasterReply.status;
@@ -903,7 +915,7 @@ void DBClientConnection::_checkConnection() {
LOG(_logLevel) << "trying reconnect to " << toString() << endl;
string errmsg;
_failed = false;
- auto connectStatus = connect(_serverAddress);
+ auto connectStatus = connect(_serverAddress, _applicationName);
if (!connectStatus.isOK()) {
_failed = true;
LOG(_logLevel) << "reconnect " << toString() << " failed " << errmsg << endl;
diff --git a/src/mongo/client/dbclient_rs.cpp b/src/mongo/client/dbclient_rs.cpp
index 3c0015b7636..ae18bd070d0 100644
--- a/src/mongo/client/dbclient_rs.cpp
+++ b/src/mongo/client/dbclient_rs.cpp
@@ -135,8 +135,9 @@ bool DBClientReplicaSet::_authPooledSecondaryConn = true;
DBClientReplicaSet::DBClientReplicaSet(const string& name,
const vector<HostAndPort>& servers,
+ StringData applicationName,
double so_timeout)
- : _setName(name), _so_timeout(so_timeout) {
+ : _setName(name), _applicationName(applicationName.toString()), _so_timeout(so_timeout) {
_rsm =
ReplicaSetMonitor::createIfNeeded(name, set<HostAndPort>(servers.begin(), servers.end()));
}
@@ -303,7 +304,8 @@ DBClientConnection* DBClientReplicaSet::checkMaster() {
// Needs to perform a dynamic_cast because we need to set the replSet
// callback. We should eventually not need this after we remove the
// callback.
- newConn = dynamic_cast<DBClientConnection*>(connStr.connect(errmsg, _so_timeout));
+ newConn = dynamic_cast<DBClientConnection*>(
+ connStr.connect(_applicationName, errmsg, _so_timeout));
} catch (const AssertionException& ex) {
errmsg = ex.toString();
}
diff --git a/src/mongo/client/dbclient_rs.h b/src/mongo/client/dbclient_rs.h
index 12951a2cf69..60e615d54dc 100644
--- a/src/mongo/client/dbclient_rs.h
+++ b/src/mongo/client/dbclient_rs.h
@@ -59,6 +59,7 @@ public:
* connections. */
DBClientReplicaSet(const std::string& name,
const std::vector<HostAndPort>& servers,
+ StringData applicationName,
double so_timeout = 0);
virtual ~DBClientReplicaSet();
@@ -295,6 +296,7 @@ private:
ReplicaSetMonitorPtr _getMonitor();
std::string _setName;
+ std::string _applicationName;
std::shared_ptr<ReplicaSetMonitor> _rsm;
HostAndPort _masterHost;
diff --git a/src/mongo/client/dbclient_rs_test.cpp b/src/mongo/client/dbclient_rs_test.cpp
index bb70542f3d2..b9500da611a 100644
--- a/src/mongo/client/dbclient_rs_test.cpp
+++ b/src/mongo/client/dbclient_rs_test.cpp
@@ -99,7 +99,7 @@ private:
void assertOneOfNodesSelected(MockReplicaSet* replSet,
ReadPreference rp,
const std::vector<std::string> hostNames) {
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
ReplicaSetMonitor::get(replSet->getSetName())->startOrContinueRefresh().refreshAll();
bool secondaryOk = (rp != ReadPreference::PrimaryOnly);
auto tagSet = secondaryOk ? TagSet() : TagSet::primaryOnly();
@@ -119,7 +119,7 @@ void assertNodeSelected(MockReplicaSet* replSet, ReadPreference rp, StringData h
TEST_F(BasicRS, QueryPrimary) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::PrimaryOnly, BSONArray());
@@ -136,7 +136,7 @@ TEST_F(BasicRS, CommandPrimary) {
TEST_F(BasicRS, QuerySecondaryOnly) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::SecondaryOnly, BSONArray());
@@ -154,7 +154,7 @@ TEST_F(BasicRS, CommandSecondaryOnly) {
TEST_F(BasicRS, QueryPrimaryPreferred) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
// Need up-to-date view, since either host is valid if view is stale.
ReplicaSetMonitor::get(replSet->getSetName())->startOrContinueRefresh().refreshAll();
@@ -174,7 +174,7 @@ TEST_F(BasicRS, CommandPrimaryPreferred) {
TEST_F(BasicRS, QuerySecondaryPreferred) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
// Need up-to-date view, since either host is valid if view is stale.
ReplicaSetMonitor::get(replSet->getSetName())->startOrContinueRefresh().refreshAll();
@@ -232,7 +232,7 @@ void assertRunCommandWithReadPrefThrows(MockReplicaSet* replSet, ReadPreference
bool secondaryOk = !isPrimaryOnly;
TagSet ts = isPrimaryOnly ? TagSet::primaryOnly() : TagSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
ASSERT_THROWS(replConn.runCommandWithMetadata(
"foo", "whoami", makeMetadata(rp, ts, secondaryOk), BSON("dbStats" << 1)),
AssertionException);
@@ -240,7 +240,7 @@ void assertRunCommandWithReadPrefThrows(MockReplicaSet* replSet, ReadPreference
TEST_F(AllNodesDown, QueryPrimary) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::PrimaryOnly, BSONArray());
@@ -253,7 +253,7 @@ TEST_F(AllNodesDown, CommandPrimary) {
TEST_F(AllNodesDown, QuerySecondaryOnly) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::SecondaryOnly, BSONArray());
@@ -266,7 +266,7 @@ TEST_F(AllNodesDown, CommandSecondaryOnly) {
TEST_F(AllNodesDown, QueryPrimaryPreferred) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::PrimaryPreferred, BSONArray());
@@ -279,7 +279,7 @@ TEST_F(AllNodesDown, CommandPrimaryPreferred) {
TEST_F(AllNodesDown, QuerySecondaryPreferred) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::SecondaryPreferred, BSONArray());
@@ -292,7 +292,7 @@ TEST_F(AllNodesDown, CommandSecondaryPreferred) {
TEST_F(AllNodesDown, QueryNearest) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::Nearest, BSONArray());
@@ -333,7 +333,7 @@ private:
TEST_F(PrimaryDown, QueryPrimary) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::PrimaryOnly, BSONArray());
@@ -346,7 +346,7 @@ TEST_F(PrimaryDown, CommandPrimary) {
TEST_F(PrimaryDown, QuerySecondaryOnly) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::SecondaryOnly, BSONArray());
@@ -364,7 +364,7 @@ TEST_F(PrimaryDown, CommandSecondaryOnly) {
TEST_F(PrimaryDown, QueryPrimaryPreferred) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::PrimaryPreferred, BSONArray());
@@ -382,7 +382,7 @@ TEST_F(PrimaryDown, CommandPrimaryPreferred) {
TEST_F(PrimaryDown, QuerySecondaryPreferred) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::SecondaryPreferred, BSONArray());
@@ -400,7 +400,7 @@ TEST_F(PrimaryDown, CommandSecondaryPreferred) {
TEST_F(PrimaryDown, Nearest) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::Nearest, BSONArray());
@@ -440,7 +440,7 @@ private:
TEST_F(SecondaryDown, QueryPrimary) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::PrimaryOnly, BSONArray());
@@ -457,7 +457,7 @@ TEST_F(SecondaryDown, CommandPrimary) {
TEST_F(SecondaryDown, QuerySecondaryOnly) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::SecondaryOnly, BSONArray());
@@ -470,7 +470,7 @@ TEST_F(SecondaryDown, CommandSecondaryOnly) {
TEST_F(SecondaryDown, QueryPrimaryPreferred) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::PrimaryPreferred, BSONArray());
@@ -487,7 +487,7 @@ TEST_F(SecondaryDown, CommandPrimaryPreferred) {
TEST_F(SecondaryDown, QuerySecondaryPreferred) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::SecondaryPreferred, BSONArray());
@@ -504,7 +504,7 @@ TEST_F(SecondaryDown, CommandSecondaryPreferred) {
TEST_F(SecondaryDown, QueryNearest) {
MockReplicaSet* replSet = getReplSet();
- DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts());
+ DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData());
Query query;
query.readPref(mongo::ReadPreference::Nearest, BSONArray());
@@ -647,7 +647,7 @@ TEST_F(TaggedFiveMemberRS, ConnShouldPinIfSameSettings) {
vector<HostAndPort> seedList;
seedList.push_back(HostAndPort(replSet->getPrimary()));
- DBClientReplicaSet replConn(replSet->getSetName(), seedList);
+ DBClientReplicaSet replConn(replSet->getSetName(), seedList, StringData());
string dest;
{
@@ -675,7 +675,7 @@ TEST_F(TaggedFiveMemberRS, ConnShouldNotPinIfHostMarkedAsFailed) {
vector<HostAndPort> seedList;
seedList.push_back(HostAndPort(replSet->getPrimary()));
- DBClientReplicaSet replConn(replSet->getSetName(), seedList);
+ DBClientReplicaSet replConn(replSet->getSetName(), seedList, StringData());
string dest;
{
@@ -708,7 +708,7 @@ TEST_F(TaggedFiveMemberRS, ConnShouldNotPinIfDiffMode) {
vector<HostAndPort> seedList;
seedList.push_back(HostAndPort(replSet->getPrimary()));
- DBClientReplicaSet replConn(replSet->getSetName(), seedList);
+ DBClientReplicaSet replConn(replSet->getSetName(), seedList, StringData());
// Need up-to-date view to ensure there are multiple valid choices.
ReplicaSetMonitor::get(replSet->getSetName())->startOrContinueRefresh().refreshAll();
@@ -740,7 +740,7 @@ TEST_F(TaggedFiveMemberRS, ConnShouldNotPinIfDiffTag) {
vector<HostAndPort> seedList;
seedList.push_back(HostAndPort(replSet->getPrimary()));
- DBClientReplicaSet replConn(replSet->getSetName(), seedList);
+ DBClientReplicaSet replConn(replSet->getSetName(), seedList, StringData());
// Need up-to-date view to ensure there are multiple valid choices.
ReplicaSetMonitor::get(replSet->getSetName())->startOrContinueRefresh().refreshAll();
@@ -776,7 +776,7 @@ TEST_F(TaggedFiveMemberRS, SlaveConnReturnsSecConn) {
vector<HostAndPort> seedList;
seedList.push_back(HostAndPort(replSet->getPrimary()));
- DBClientReplicaSet replConn(replSet->getSetName(), seedList);
+ DBClientReplicaSet replConn(replSet->getSetName(), seedList, StringData());
// Need up-to-date view since slaveConn() uses SecondaryPreferred, and this test assumes it
// knows about at least one secondary.
diff --git a/src/mongo/client/dbclientinterface.h b/src/mongo/client/dbclientinterface.h
index cc2be6c600e..8eeef420cb0 100644
--- a/src/mongo/client/dbclientinterface.h
+++ b/src/mongo/client/dbclientinterface.h
@@ -975,7 +975,9 @@ public:
* @param errmsg any relevant error message will appended to the string
* @return false if fails to connect.
*/
- virtual bool connect(const HostAndPort& server, std::string& errmsg);
+ virtual bool connect(const HostAndPort& server,
+ StringData applicationName,
+ std::string& errmsg);
/**
* Semantically equivalent to the previous connect method, but returns a Status
@@ -986,7 +988,7 @@ public:
* @param a hook to validate the 'isMaster' reply received during connection. If the hook
* fails, the connection will be terminated and a non-OK status will be returned.
*/
- Status connect(const HostAndPort& server);
+ Status connect(const HostAndPort& server, StringData applicationName);
/**
* This version of connect does not run 'isMaster' after creating a TCP connection to the
@@ -1131,6 +1133,7 @@ protected:
HostAndPort _serverAddress;
std::string _resolvedAddress;
+ std::string _applicationName;
void _checkConnection();
diff --git a/src/mongo/client/mongo_uri.h b/src/mongo/client/mongo_uri.h
index 2898fed2cb2..8a0574540c8 100644
--- a/src/mongo/client/mongo_uri.h
+++ b/src/mongo/client/mongo_uri.h
@@ -72,7 +72,7 @@ public:
static StatusWith<MongoURI> parse(const std::string& url);
- DBClientBase* connect(std::string& errmsg) const;
+ DBClientBase* connect(StringData applicationName, std::string& errmsg) const;
const std::string& getUser() const {
return _user;
diff --git a/src/mongo/client/mongo_uri_connect.cpp b/src/mongo/client/mongo_uri_connect.cpp
index d5b1bb0d0df..7909e0bd5cd 100644
--- a/src/mongo/client/mongo_uri_connect.cpp
+++ b/src/mongo/client/mongo_uri_connect.cpp
@@ -164,7 +164,7 @@ BSONObj MongoURI::_makeAuthObjFromOptions(int maxWireVersion) const {
return bob.obj();
}
-DBClientBase* MongoURI::connect(std::string& errmsg) const {
+DBClientBase* MongoURI::connect(StringData applicationName, std::string& errmsg) const {
double socketTimeout = 0.0;
OptionsMap::const_iterator it = _options.find("socketTimeoutMS");
@@ -177,7 +177,7 @@ DBClientBase* MongoURI::connect(std::string& errmsg) const {
}
}
- auto ret = _connectString.connect(errmsg, socketTimeout);
+ auto ret = _connectString.connect(applicationName, errmsg, socketTimeout);
if (!ret) {
return ret;
}
diff --git a/src/mongo/client/mongo_uri_test.cpp b/src/mongo/client/mongo_uri_test.cpp
index 81e626e4607..05d288818d8 100644
--- a/src/mongo/client/mongo_uri_test.cpp
+++ b/src/mongo/client/mongo_uri_test.cpp
@@ -30,6 +30,7 @@
#include "mongo/client/mongo_uri.h"
+#include "mongo/base/string_data.h"
#include "mongo/unittest/unittest.h"
namespace {
@@ -330,7 +331,7 @@ TEST(MongoURI, ValidButBadURIsFailToConnect) {
ASSERT_TRUE(uri.isValid());
std::string errmsg;
- auto dbclient = uri.connect(errmsg);
+ auto dbclient = uri.connect(mongo::StringData(), errmsg);
ASSERT_EQ(dbclient, static_cast<decltype(dbclient)>(nullptr));
}
diff --git a/src/mongo/client/scoped_db_conn_test.cpp b/src/mongo/client/scoped_db_conn_test.cpp
index ba54e4ec733..cd86ed99839 100644
--- a/src/mongo/client/scoped_db_conn_test.cpp
+++ b/src/mongo/client/scoped_db_conn_test.cpp
@@ -224,7 +224,7 @@ public:
// Make sure the dummy server is up and running before proceeding
while (true) {
- auto connectStatus = conn.connect(HostAndPort{TARGET_HOST});
+ auto connectStatus = conn.connect(HostAndPort{TARGET_HOST}, StringData());
if (connectStatus.isOK()) {
break;
}
diff --git a/src/mongo/db/clientlistplugin.cpp b/src/mongo/db/clientlistplugin.cpp
index 713e9a176f0..8204eeb74a9 100644
--- a/src/mongo/db/clientlistplugin.cpp
+++ b/src/mongo/db/clientlistplugin.cpp
@@ -40,6 +40,8 @@
#include "mongo/db/operation_context.h"
#include "mongo/db/service_context.h"
#include "mongo/db/stats/fill_locker_info.h"
+#include "mongo/rpc/metadata/client_metadata.h"
+#include "mongo/rpc/metadata/client_metadata_ismaster.h"
#include "mongo/util/mongoutils/html.h"
#include "mongo/util/stringutils.h"
@@ -199,6 +201,15 @@ private:
client->reportState(b);
+ const auto& clientMetadata =
+ ClientMetadataIsMasterState::get(client).getClientMetadata();
+ if (clientMetadata) {
+ auto appName = clientMetadata.get().getApplicationName();
+ if (!appName.empty()) {
+ b.append("applicationName", appName);
+ }
+ }
+
const OperationContext* txn = client->getOperationContext();
b.appendBool("active", static_cast<bool>(txn));
if (txn) {
diff --git a/src/mongo/db/cloner.cpp b/src/mongo/db/cloner.cpp
index e924fa7312b..d43b145d776 100644
--- a/src/mongo/db/cloner.cpp
+++ b/src/mongo/db/cloner.cpp
@@ -542,7 +542,7 @@ Status Cloner::copyDb(OperationContext* txn,
// nothing to do
} else if (!masterSameProcess) {
std::string errmsg;
- unique_ptr<DBClientBase> con(cs.connect(errmsg));
+ unique_ptr<DBClientBase> con(cs.connect(StringData(), errmsg));
if (!con.get()) {
return Status(ErrorCodes::HostUnreachable, errmsg);
}
diff --git a/src/mongo/db/commands/clone_collection.cpp b/src/mongo/db/commands/clone_collection.cpp
index 76ef1377673..a6539a5c179 100644
--- a/src/mongo/db/commands/clone_collection.cpp
+++ b/src/mongo/db/commands/clone_collection.cpp
@@ -148,7 +148,7 @@ public:
Cloner cloner;
unique_ptr<DBClientConnection> myconn;
myconn.reset(new DBClientConnection());
- if (!myconn->connect(HostAndPort(fromhost), errmsg))
+ if (!myconn->connect(HostAndPort(fromhost), StringData(), errmsg))
return false;
cloner.setConnection(myconn.release());
diff --git a/src/mongo/db/commands/copydb.cpp b/src/mongo/db/commands/copydb.cpp
index 92d721b70e1..9bfbde8d67a 100644
--- a/src/mongo/db/commands/copydb.cpp
+++ b/src/mongo/db/commands/copydb.cpp
@@ -201,7 +201,7 @@ public:
// If fromSelf leave the cloner's conn empty, it will use a DBDirectClient instead.
const ConnectionString cs(uassertStatusOK(ConnectionString::parse(fromhost)));
- DBClientBase* conn = cs.connect(errmsg);
+ DBClientBase* conn = cs.connect(StringData(), errmsg);
if (!conn) {
return false;
}
diff --git a/src/mongo/db/commands/copydb_start_commands.cpp b/src/mongo/db/commands/copydb_start_commands.cpp
index 8426b14d072..1af5e4be983 100644
--- a/src/mongo/db/commands/copydb_start_commands.cpp
+++ b/src/mongo/db/commands/copydb_start_commands.cpp
@@ -113,7 +113,7 @@ public:
const ConnectionString cs(uassertStatusOK(ConnectionString::parse(fromhost)));
auto& authConn = CopyDbAuthConnection::forClient(txn->getClient());
- authConn.reset(cs.connect(errmsg));
+ authConn.reset(cs.connect(StringData(), errmsg));
if (!authConn) {
return false;
}
@@ -202,7 +202,7 @@ public:
}
auto& authConn = CopyDbAuthConnection::forClient(txn->getClient());
- authConn.reset(cs.connect(errmsg));
+ authConn.reset(cs.connect(StringData(), errmsg));
if (!authConn.get()) {
return false;
}
diff --git a/src/mongo/db/commands/current_op.cpp b/src/mongo/db/commands/current_op.cpp
index 10cf0f81272..555fbd1efe5 100644
--- a/src/mongo/db/commands/current_op.cpp
+++ b/src/mongo/db/commands/current_op.cpp
@@ -45,6 +45,8 @@
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/stats/fill_locker_info.h"
+#include "mongo/rpc/metadata/client_metadata.h"
+#include "mongo/rpc/metadata/client_metadata_ismaster.h"
#include "mongo/util/log.h"
namespace mongo {
@@ -146,6 +148,15 @@ public:
// The client information
client->reportState(infoBuilder);
+ const auto& clientMetadata =
+ ClientMetadataIsMasterState::get(txn->getClient()).getClientMetadata();
+ if (clientMetadata) {
+ auto appName = clientMetadata.get().getApplicationName();
+ if (!appName.empty()) {
+ infoBuilder.append("appName", appName);
+ }
+ }
+
// Operation context specific information
infoBuilder.appendBool("active", static_cast<bool>(opCtx));
if (opCtx) {
diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript
index 959f4e07611..3fd9ec9a787 100644
--- a/src/mongo/db/repl/SConscript
+++ b/src/mongo/db/repl/SConscript
@@ -1072,6 +1072,7 @@ env.Library(
'$BUILD_DIR/mongo/client/clientdriver',
'$BUILD_DIR/mongo/db/auth/authcore',
'$BUILD_DIR/mongo/db/commands',
+ '$BUILD_DIR/mongo/rpc/client_metadata',
'$BUILD_DIR/mongo/db/concurrency/lock_manager',
'$BUILD_DIR/mongo/db/curop',
'$BUILD_DIR/mongo/db/lasterror',
diff --git a/src/mongo/db/repl/oplogreader.cpp b/src/mongo/db/repl/oplogreader.cpp
index 749c6112db9..cb83a12d676 100644
--- a/src/mongo/db/repl/oplogreader.cpp
+++ b/src/mongo/db/repl/oplogreader.cpp
@@ -93,7 +93,7 @@ bool OplogReader::connect(const HostAndPort& host) {
_conn = shared_ptr<DBClientConnection>(
new DBClientConnection(false, durationCount<Seconds>(kSocketTimeout)));
string errmsg;
- if (!_conn->connect(host, errmsg) || !replAuthenticate(_conn.get())) {
+ if (!_conn->connect(host, StringData(), errmsg) || !replAuthenticate(_conn.get())) {
resetConnection();
error() << errmsg << endl;
return false;
diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp
index be4ddb9e3e4..cb74b9ec9af 100644
--- a/src/mongo/db/repl/replication_info.cpp
+++ b/src/mongo/db/repl/replication_info.cpp
@@ -25,6 +25,7 @@
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kFTDC
#include "mongo/platform/basic.h"
@@ -50,6 +51,8 @@
#include "mongo/db/storage/storage_options.h"
#include "mongo/db/wire_version.h"
#include "mongo/executor/network_interface.h"
+#include "mongo/rpc/metadata/client_metadata.h"
+#include "mongo/rpc/metadata/client_metadata_ismaster.h"
#include "mongo/s/write_ops/batched_command_request.h"
#include "mongo/util/map_util.h"
@@ -243,6 +246,36 @@ public:
executor::NetworkInterface::kMessagingPortKeepOpen);
}
}
+
+ auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(txn->getClient());
+ bool seenIsMaster = clientMetadataIsMasterState.hasSeenIsMaster();
+ if (!seenIsMaster) {
+ clientMetadataIsMasterState.setSeenIsMaster();
+ }
+
+ BSONElement element = cmdObj[kMetadataDocumentName];
+ if (!element.eoo()) {
+ if (seenIsMaster) {
+ return Command::appendCommandStatus(
+ result,
+ Status(ErrorCodes::ClientMetadataCannotBeMutated,
+ "The client metadata document may only be sent in the first isMaster"));
+ }
+
+ auto swParseClientMetadata = ClientMetadata::parse(element);
+
+ if (!swParseClientMetadata.getStatus().isOK()) {
+ return Command::appendCommandStatus(result, swParseClientMetadata.getStatus());
+ }
+
+ invariant(swParseClientMetadata.getValue());
+
+ swParseClientMetadata.getValue().get().logClientMetadata(txn->getClient());
+
+ clientMetadataIsMasterState.setClientMetadata(
+ txn->getClient(), std::move(swParseClientMetadata.getValue()));
+ }
+
appendReplicationInfo(txn, result, 0);
if (serverGlobalParams.clusterRole == ClusterRole::ConfigServer) {
diff --git a/src/mongo/db/repl/rollback_source_impl.cpp b/src/mongo/db/repl/rollback_source_impl.cpp
index f416af8c716..27513953dca 100644
--- a/src/mongo/db/repl/rollback_source_impl.cpp
+++ b/src/mongo/db/repl/rollback_source_impl.cpp
@@ -72,7 +72,9 @@ void RollbackSourceImpl::copyCollectionFromRemote(OperationContext* txn,
const NamespaceString& nss) const {
std::string errmsg;
std::unique_ptr<DBClientConnection> tmpConn(new DBClientConnection());
- uassert(15908, errmsg, tmpConn->connect(_source, errmsg) && replAuthenticate(tmpConn.get()));
+ uassert(15908,
+ errmsg,
+ tmpConn->connect(_source, StringData(), errmsg) && replAuthenticate(tmpConn.get()));
// cloner owns _conn in unique_ptr
Cloner cloner;
diff --git a/src/mongo/dbtests/mock/mock_conn_registry.cpp b/src/mongo/dbtests/mock/mock_conn_registry.cpp
index 2a34309a044..61388e337f7 100644
--- a/src/mongo/dbtests/mock/mock_conn_registry.cpp
+++ b/src/mongo/dbtests/mock/mock_conn_registry.cpp
@@ -91,7 +91,7 @@ mongo::DBClientBase* MockConnRegistry::MockConnHook::connect(const ConnectionStr
const string hostName(connString.toString());
MockDBClientConnection* conn = _registry->connect(hostName);
- if (!conn->connect(hostName.c_str(), errmsg)) {
+ if (!conn->connect(hostName.c_str(), StringData(), errmsg)) {
// Assumption: connect never throws, so no leak.
delete conn;
diff --git a/src/mongo/dbtests/mock/mock_dbclient_connection.cpp b/src/mongo/dbtests/mock/mock_dbclient_connection.cpp
index 5815f9d959e..8fbcb3319b5 100644
--- a/src/mongo/dbtests/mock/mock_dbclient_connection.cpp
+++ b/src/mongo/dbtests/mock/mock_dbclient_connection.cpp
@@ -46,7 +46,9 @@ MockDBClientConnection::MockDBClientConnection(MockRemoteDBServer* remoteServer,
MockDBClientConnection::~MockDBClientConnection() {}
-bool MockDBClientConnection::connect(const char* hostName, std::string& errmsg) {
+bool MockDBClientConnection::connect(const char* hostName,
+ StringData applicationName,
+ std::string& errmsg) {
if (_remoteServer->isRunning()) {
_remoteServerInstanceID = _remoteServer->getInstanceID();
return true;
diff --git a/src/mongo/dbtests/mock/mock_dbclient_connection.h b/src/mongo/dbtests/mock/mock_dbclient_connection.h
index 21e2bdc3c83..d680fbdad99 100644
--- a/src/mongo/dbtests/mock/mock_dbclient_connection.h
+++ b/src/mongo/dbtests/mock/mock_dbclient_connection.h
@@ -58,10 +58,10 @@ public:
// DBClientBase methods
//
- bool connect(const char* hostName, std::string& errmsg);
+ bool connect(const char* hostName, StringData applicationName, std::string& errmsg);
- inline bool connect(const HostAndPort& host, std::string& errmsg) {
- return connect(host.toString().c_str(), errmsg);
+ inline bool connect(const HostAndPort& host, StringData applicationName, std::string& errmsg) {
+ return connect(host.toString().c_str(), applicationName, errmsg);
}
bool runCommand(const std::string& dbname,
diff --git a/src/mongo/executor/network_interface_asio_auth.cpp b/src/mongo/executor/network_interface_asio_auth.cpp
index 5f5fe523f9c..5dacda44e20 100644
--- a/src/mongo/executor/network_interface_asio_auth.cpp
+++ b/src/mongo/executor/network_interface_asio_auth.cpp
@@ -42,10 +42,12 @@
#include "mongo/rpc/factory.h"
#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/rpc/legacy_request_builder.h"
+#include "mongo/rpc/metadata/client_metadata.h"
#include "mongo/rpc/reply_interface.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/log.h"
#include "mongo/util/net/ssl_manager.h"
+#include "mongo/util/version.h"
namespace mongo {
namespace executor {
@@ -62,6 +64,7 @@ void NetworkInterfaceASIO::_runIsMaster(AsyncOp* op) {
BSONObjBuilder bob;
bob.append("isMaster", 1);
bob.append("hangUpOnStepDown", false);
+ ClientMetadata::serialize(_options.instanceName, mongo::versionString, &bob);
if (Command::testCommandsEnabled) {
// Only include the host:port of this process in the isMaster command request if test
diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript
index 7822e216189..c3b2b73ddaf 100644
--- a/src/mongo/rpc/SConscript
+++ b/src/mongo/rpc/SConscript
@@ -139,6 +139,7 @@ env.Library(
'metadata/repl_set_metadata.cpp',
],
LIBDEPS=[
+ 'client_metadata',
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/bson/util/bson_extract',
'$BUILD_DIR/mongo/client/read_preference',
@@ -194,3 +195,28 @@ env.CppUnitTest(
],
LIBDEPS=['metadata']
)
+
+env.Library(
+ target='client_metadata',
+ source=[
+ 'metadata/client_metadata.cpp',
+ 'metadata/client_metadata_ismaster.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ "$BUILD_DIR/mongo/util/concurrency/spin_lock",
+ '$BUILD_DIR/mongo/util/decorable',
+ '$BUILD_DIR/mongo/util/net/hostandport',
+ "$BUILD_DIR/mongo/util/processinfo",
+ ],
+)
+
+env.CppUnitTest(
+ target='client_metadata_test',
+ source=[
+ 'metadata/client_metadata_test.cpp',
+ ],
+ LIBDEPS=[
+ 'client_metadata',
+ ]
+)
diff --git a/src/mongo/rpc/metadata/client_metadata.cpp b/src/mongo/rpc/metadata/client_metadata.cpp
new file mode 100644
index 00000000000..ea03d5e0f84
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata.cpp
@@ -0,0 +1,400 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/rpc/metadata/client_metadata.h"
+
+#include <string>
+
+#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/util/log.h"
+#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/processinfo.h"
+
+namespace mongo {
+
+namespace {
+
+constexpr auto kApplication = "application"_sd;
+constexpr auto kDriver = "driver"_sd;
+constexpr auto kOperatingSystem = "os"_sd;
+
+constexpr auto kArchitecture = "architecture"_sd;
+constexpr auto kName = "name"_sd;
+constexpr auto kType = "type"_sd;
+constexpr auto kVersion = "version"_sd;
+
+constexpr uint32_t kMaxMetadataDocumentByteLength = 512U;
+constexpr uint32_t kMaxApplicationNameByteLength = 128U;
+
+} // namespace
+
+StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElement& element) {
+ if (element.eoo()) {
+ return {boost::none};
+ }
+
+ if (!element.isABSONObj()) {
+ return Status(ErrorCodes::TypeMismatch, "The client metadata document must be a document");
+ }
+
+ ClientMetadata clientMetadata;
+ Status s = clientMetadata.parseClientMetadataDocument(element.Obj());
+ if (!s.isOK()) {
+ return s;
+ }
+
+ return {std::move(clientMetadata)};
+}
+
+Status ClientMetadata::parseClientMetadataDocument(const BSONObj& doc) {
+ if (static_cast<uint32_t>(doc.objsize()) > kMaxMetadataDocumentByteLength) {
+ return Status(ErrorCodes::ClientMetadataDocumentTooLarge,
+ str::stream() << "The client metadata document must be less then or equal to "
+ << kMaxMetadataDocumentByteLength
+ << "bytes");
+ }
+
+ // Get a copy so that we can take a stable reference to the app name inside
+ BSONObj docOwned = doc.getOwned();
+
+ StringData appName;
+ bool foundDriver = false;
+ bool foundOperatingSystem = false;
+
+ BSONObjIterator i(docOwned);
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ if (name == kApplication) {
+ // Application is an optional sub-document, but we require it to be a document if
+ // specified.
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kApplication
+ << "' field is required to be a BSON document in the "
+ "client metadata document");
+ }
+
+ auto swAppName = parseApplicationDocument(e.Obj());
+ if (!swAppName.getStatus().isOK()) {
+ return swAppName.getStatus();
+ }
+
+ appName = swAppName.getValue();
+
+ } else if (name == kDriver) {
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kDriver << "' field is required to be a "
+ "BSON document in the client "
+ "metadata document");
+ }
+
+ Status s = validateDriverDocument(e.Obj());
+ if (!s.isOK()) {
+ return s;
+ }
+
+ foundDriver = true;
+ } else if (name == kOperatingSystem) {
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kOperatingSystem
+ << "' field is required to be a BSON document in the "
+ "client metadata document");
+ }
+
+ Status s = validateOperatingSystemDocument(e.Obj());
+ if (!s.isOK()) {
+ return s;
+ }
+
+ foundOperatingSystem = true;
+ }
+
+ // Ignore other fields as extra fields are allowed.
+ }
+
+ // Driver is a required sub document.
+ if (!foundDriver) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required sub-document '" << kDriver
+ << "' in the client metadata document");
+ }
+
+ // OS is a required sub document.
+ if (!foundOperatingSystem) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required sub-document '" << kOperatingSystem
+ << "' in the client metadata document");
+ }
+
+ _document = std::move(docOwned);
+ _appName = std::move(appName);
+
+ return Status::OK();
+}
+
+StatusWith<StringData> ClientMetadata::parseApplicationDocument(const BSONObj& doc) {
+ BSONObjIterator i(doc);
+
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ // Name is the only required field, and any other fields are simply ignored.
+ if (name == kName) {
+
+ if (e.type() != String) {
+ return {
+ ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kApplication << "." << kName
+ << "' field must be a string in the client metadata document"};
+ }
+
+ StringData value = e.checkAndGetStringData();
+
+ if (value.size() > kMaxApplicationNameByteLength) {
+ return {ErrorCodes::ClientMetadataAppNameTooLarge,
+ str::stream() << "The '" << kApplication << "." << kName
+ << "' field must be less then or equal to "
+ << kMaxApplicationNameByteLength
+ << " bytes in the client metadata document"};
+ }
+
+ return {std::move(value)};
+ }
+ }
+
+ return {StringData()};
+}
+
+Status ClientMetadata::validateDriverDocument(const BSONObj& doc) {
+ bool foundName = false;
+ bool foundVersion = false;
+
+ BSONObjIterator i(doc);
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ if (name == kName) {
+ if (e.type() != String) {
+ return Status(
+ ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kDriver << "." << kName
+ << "' field must be a string in the client metadata document");
+ }
+
+ foundName = true;
+ } else if (name == kVersion) {
+ if (e.type() != String) {
+ return Status(
+ ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kDriver << "." << kVersion
+ << "' field must be a string in the client metadata document");
+ }
+
+ foundVersion = true;
+ }
+ }
+
+ if (foundName == false) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required field '" << kDriver << "." << kName
+ << "' in the client metadata document");
+ }
+
+ if (foundVersion == false) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required field '" << kDriver << "." << kVersion
+ << "' in the client metadata document");
+ }
+
+ return Status::OK();
+}
+
+Status ClientMetadata::validateOperatingSystemDocument(const BSONObj& doc) {
+ bool foundType = false;
+
+ BSONObjIterator i(doc);
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ if (name == kType) {
+ if (e.type() != String) {
+ return Status(
+ ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kOperatingSystem << "." << kType
+ << "' field must be a string in the client metadata document");
+ }
+
+ foundType = true;
+ }
+ }
+
+ if (foundType == false) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required field '" << kOperatingSystem << "."
+ << kType
+ << "' in the client metadata document");
+ }
+
+ return Status::OK();
+}
+
+void ClientMetadata::serialize(StringData driverName,
+ StringData driverVersion,
+ BSONObjBuilder* builder) {
+
+ ProcessInfo processInfo;
+
+ serializePrivate(driverName,
+ driverVersion,
+ processInfo.getOsType(),
+ processInfo.getOsName(),
+ processInfo.getArch(),
+ processInfo.getOsVersion(),
+ builder);
+}
+
+void ClientMetadata::serializePrivate(StringData driverName,
+ StringData driverVersion,
+ StringData osType,
+ StringData osName,
+ StringData osArchitecture,
+ StringData osVersion,
+ BSONObjBuilder* builder) {
+ invariant(!driverName.empty() && !driverVersion.empty() && !osType.empty() && !osName.empty() &&
+ !osArchitecture.empty() && !osVersion.empty());
+
+ BSONObjBuilder metaObjBuilder(builder->subobjStart(kMetadataDocumentName));
+
+ {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kDriver));
+ subObjBuilder.append(kName, driverName);
+ subObjBuilder.append(kVersion, driverVersion);
+ }
+
+ {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kOperatingSystem));
+ subObjBuilder.append(kType, osType);
+ subObjBuilder.append(kName, osName);
+ subObjBuilder.append(kArchitecture, osArchitecture);
+ subObjBuilder.append(kVersion, osVersion);
+ }
+}
+
+Status ClientMetadata::serialize(StringData driverName,
+ StringData driverVersion,
+ StringData appName,
+ BSONObjBuilder* builder) {
+
+ ProcessInfo processInfo;
+
+ return serializePrivate(driverName,
+ driverVersion,
+ processInfo.getOsType(),
+ processInfo.getOsName(),
+ processInfo.getArch(),
+ processInfo.getOsVersion(),
+ appName,
+ builder);
+}
+
+Status ClientMetadata::serializePrivate(StringData driverName,
+ StringData driverVersion,
+ StringData osType,
+ StringData osName,
+ StringData osArchitecture,
+ StringData osVersion,
+ StringData appName,
+ BSONObjBuilder* builder) {
+ invariant(!driverName.empty() && !driverVersion.empty() && !osType.empty() && !osName.empty() &&
+ !osArchitecture.empty() && !osVersion.empty());
+
+ if (appName.size() > kMaxApplicationNameByteLength) {
+ return Status(ErrorCodes::ClientMetadataAppNameTooLarge,
+ str::stream() << "The '" << kApplication << "." << kName
+ << "' field must be less then or equal to "
+ << kMaxApplicationNameByteLength
+ << " bytes in the client metadata document");
+ }
+
+ {
+ BSONObjBuilder metaObjBuilder(builder->subobjStart(kMetadataDocumentName));
+
+ if (!appName.empty()) {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kApplication));
+ subObjBuilder.append(kName, appName);
+ }
+
+ {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kDriver));
+ subObjBuilder.append(kName, driverName);
+ subObjBuilder.append(kVersion, driverVersion);
+ }
+
+ {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kOperatingSystem));
+ subObjBuilder.append(kType, osType);
+ subObjBuilder.append(kName, osName);
+ subObjBuilder.append(kArchitecture, osArchitecture);
+ subObjBuilder.append(kVersion, osVersion);
+ }
+ }
+
+ return Status::OK();
+}
+
+StringData ClientMetadata::getApplicationName() const {
+ return _appName;
+}
+
+const BSONObj& ClientMetadata::getDocument() const {
+ return _document;
+}
+
+void ClientMetadata::logClientMetadata(Client* client) const {
+ invariant(!getDocument().isEmpty());
+ log() << "received client metadata from " << client->getRemote().toString() << " "
+ << client->desc() << ": " << getDocument();
+}
+
+} // namespace mongo
diff --git a/src/mongo/rpc/metadata/client_metadata.h b/src/mongo/rpc/metadata/client_metadata.h
new file mode 100644
index 00000000000..92646e3a88d
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata.h
@@ -0,0 +1,230 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+
+namespace mongo {
+
+class Client;
+class ClientBasic;
+class OperationContext;
+
+constexpr auto kMetadataDocumentName = "client"_sd;
+
+/**
+ * The ClientMetadata class is responsible for parsing the client metadata document that is received
+ * in isMaster from clients. This class also provides static methods for client libraries to create
+ * a valid client metadata document.
+ *
+ * Example document of isMaster request with client metadata document:
+ * {
+ * "isMaster" : 1,
+ * "client" : {
+ * "application" : { // Optional
+ * "name" : "string" // Optional with caveats
+ * },
+ * "driver" : { // Required, Informational Only
+ * "name" : "string", // Required, Informational Only
+ * "version" : "string" // Required, Informational Only
+ * },
+ * "os" : { // Required, Informational Only
+ * "type" : "string", // Required, Informational Only, See note
+ * "name" : "string", // Optional, Informational Only
+ * "architecture" : "string", // Optional, Informational Only
+ * "version" : "string" // Optional, Informational Only
+ * }
+ * }
+ * }
+ *
+ * For this classes' purposes, the client metadata document is the sub-document in "client". It is
+ * allowed to contain additional fields that are not listed in the example above. These additional
+ * fields are ignore by this class. The "os" document "type" field is required (defaults to
+ * "unknown" in Mongo Drivers). The "driver", and "os" documents while required, are for
+ * informational purposes only. The content is logged to disk but otherwise ignored.
+ *
+ * See Driver Specification: "MongoDB Handshake" for more information.
+ */
+class ClientMetadata {
+ MONGO_DISALLOW_COPYING(ClientMetadata);
+
+public:
+ ClientMetadata(ClientMetadata&&) = default;
+ ClientMetadata& operator=(ClientMetadata&&) = default;
+
+ /**
+ * Parse and validate a client metadata document contained in an isMaster request.
+ *
+ * Empty or non-existent sub-documents are permitted. Non-empty documents are required to have
+ * the fields driver.name, driver.version, and os.type which must be strings.
+ *
+ * Returns an empty optional if element is empty.
+ */
+ static StatusWith<boost::optional<ClientMetadata>> parse(const BSONElement& element);
+
+ /**
+ * Create a new client metadata document with os information from the ProcessInfo class.
+ *
+ * This method outputs the "client" field, and client metadata sub-document in the
+ * BSONObjBuilder:
+ *
+ * "client" : {
+ * "driver" : {
+ * "name" : "string",
+ * "version" : "string"
+ * },
+ * "os" : {
+ * "type" : "string",
+ * "name" : "string",
+ * "architecture" : "string",
+ * "version" : "string"
+ * }
+ * }
+ */
+ static void serialize(StringData driverName, StringData driverVersion, BSONObjBuilder* builder);
+
+ /**
+ * Create a new client metadata document with os information from the ProcessInfo class.
+ *
+ * driverName - name of the driver, must not be empty
+ * driverVersion - a string for the driver version, must not be empty
+ *
+ * Notes: appName must be <= 128 bytes otherwise an error is returned. It may be empty in which
+ * case it is omitted from the output document.
+ *
+ * This method outputs the "client" field, and client metadata sub-document in the
+ * BSONObjBuilder:
+ *
+ * "client" : {
+ * "application" : {
+ * "name" : "string"
+ * },
+ * "driver" : {
+ * "name" : "string",
+ * "version" : "string"
+ * },
+ * "os" : {
+ * "type" : "string",
+ * "name" : "string",
+ * "architecture" : "string",
+ * "version" : "string"
+ * }
+ * }
+ */
+ static Status serialize(StringData driverName,
+ StringData driverVersion,
+ StringData appName,
+ BSONObjBuilder* builder);
+
+ /**
+ * Get the Application Name for the client metadata document.
+ *
+ * Used to log Application Name in slow operation reports, and into system.profile.
+ * Return: May be empty.
+ */
+ StringData getApplicationName() const;
+
+ /**
+ * Get the BSON Document of the client metadata document. In the example above in the class
+ * comment, this is the document in the "client" field.
+ *
+ * Return: May be empty.
+ */
+ const BSONObj& getDocument() const;
+
+ /**
+ * Log client and client metadata information to disk.
+ */
+ void logClientMetadata(Client* client) const;
+
+public:
+ /**
+ * Create a new client metadata document.
+ *
+ * Exposed for Unit Test purposes
+ */
+ static void serializePrivate(StringData driverName,
+ StringData driverVersion,
+ StringData osType,
+ StringData osName,
+ StringData osArchitecture,
+ StringData osVersion,
+ BSONObjBuilder* builder);
+
+ /**
+ * Create a new client metadata document.
+ *
+ * driverName - name of the driver
+ * driverVersion - a string for the driver version
+ * osType - name of host operating system of client, i.e. uname -s
+ * osName - name of operating system distro, i.e. "Ubuntu..." or "Microsoft Windows 8"
+ * osArchitecture - architecture of host operating system, i.e. uname -p
+ * osVersion - operating system version, i.e. uname -v
+ *
+ * Notes: appName must be <= 128 bytes otherwise an error is returned. It may be empty in which
+ * case it is omitted from the output document. All other fields must not be empty.
+ *
+ * Exposed for Unit Test purposes
+ */
+ static Status serializePrivate(StringData driverName,
+ StringData driverVersion,
+ StringData osType,
+ StringData osName,
+ StringData osArchitecture,
+ StringData osVersion,
+ StringData appName,
+ BSONObjBuilder* builder);
+
+private:
+ ClientMetadata() = default;
+
+ Status parseClientMetadataDocument(const BSONObj& doc);
+ static Status validateDriverDocument(const BSONObj& doc);
+ static Status validateOperatingSystemDocument(const BSONObj& doc);
+ static StatusWith<StringData> parseApplicationDocument(const BSONObj& doc);
+
+private:
+ // Parsed Client Metadata document
+ // May be empty
+ // Owned
+ BSONObj _document;
+
+ // Application Name extracted from the client metadata document.
+ // May be empty
+ StringData _appName;
+};
+
+} // namespace mongo
diff --git a/src/mongo/rpc/metadata/client_metadata_ismaster.cpp b/src/mongo/rpc/metadata/client_metadata_ismaster.cpp
new file mode 100644
index 00000000000..53164ed33d2
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata_ismaster.cpp
@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/rpc/metadata/client_metadata_ismaster.h"
+
+#include <string>
+
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+#include "mongo/db/client.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/service_context.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+
+namespace {
+
+const auto getClientMetadataIsMasterState =
+ ClientBasic::declareDecoration<ClientMetadataIsMasterState>();
+
+} // namespace
+
+ClientMetadataIsMasterState& ClientMetadataIsMasterState::get(ClientBasic* client) {
+ return getClientMetadataIsMasterState(*client);
+}
+
+bool ClientMetadataIsMasterState::hasSeenIsMaster() const {
+ return _hasSeenIsMaster;
+}
+
+void ClientMetadataIsMasterState::setSeenIsMaster() {
+ invariant(!_hasSeenIsMaster);
+ _hasSeenIsMaster = true;
+}
+
+const boost::optional<ClientMetadata>& ClientMetadataIsMasterState::getClientMetadata() const {
+ return _clientMetadata;
+}
+
+void ClientMetadataIsMasterState::setClientMetadata(
+ Client* client, boost::optional<ClientMetadata> clientMetadata) {
+ auto& state = get(client);
+
+ stdx::lock_guard<Client> lk(*client);
+ state._clientMetadata = std::move(clientMetadata);
+}
+
+
+} // namespace mongo
diff --git a/src/mongo/rpc/metadata/client_metadata_ismaster.h b/src/mongo/rpc/metadata/client_metadata_ismaster.h
new file mode 100644
index 00000000000..9bd274ae4f6
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata_ismaster.h
@@ -0,0 +1,89 @@
+/**
+* Copyright (C) 2016 MongoDB Inc.
+*
+* 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.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* As a special exception, the copyright holders give permission to link the
+* code of portions of this program with the OpenSSL library under certain
+* conditions as described in each individual source file and distribute
+* linked combinations including the program with the OpenSSL library. You
+* must comply with the GNU Affero General Public License in all respects
+* for all of the code used other than as permitted herein. If you modify
+* file(s) with this exception, you may extend this exception to your
+* version of the file(s), but you are not obligated to do so. If you do not
+* wish to do so, delete this exception statement from your version. If you
+* delete this exception statement from all source files in the program,
+* then also delete it in the license file.
+*/
+
+#pragma once
+
+#include <boost/optional.hpp>
+#include <memory>
+#include <string>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/rpc/metadata/client_metadata.h"
+
+namespace mongo {
+
+class Client;
+class ClientBasic;
+
+/**
+ * ClientMetadataIsMasterState is responsible for tracking whether the client metadata document has
+ * been received by the specified Client object.
+ */
+class ClientMetadataIsMasterState {
+ MONGO_DISALLOW_COPYING(ClientMetadataIsMasterState);
+
+public:
+ ClientMetadataIsMasterState() = default;
+
+ static ClientMetadataIsMasterState& get(ClientBasic* client);
+
+ /**
+ * Get the optional client metadata object.
+ */
+ const boost::optional<ClientMetadata>& getClientMetadata() const;
+
+ /**
+ * Set the optional client metadata object.
+ */
+ static void setClientMetadata(Client* client, boost::optional<ClientMetadata> clientMetadata);
+
+ /**
+ * Check a flag to indicate that isMaster has been seen for this Client.
+ */
+ bool hasSeenIsMaster() const;
+
+ /**
+ * Set a flag to indicate that isMaster has been seen for this Client.
+ */
+ void setSeenIsMaster();
+
+private:
+ // Optional client metadata document.
+ // Set if client sees isMaster cmd or as part of OP_Command processing.
+ // Thread-Safety:
+ // Can be read and written from the thread owning "Client".
+ // Can be read from other threads if they hold the "Client" lock.
+ boost::optional<ClientMetadata> _clientMetadata{boost::none};
+
+ // Indicates whether we have seen an is master for this client.
+ // Thread-Safety:
+ // None - must be only be read and written from the thread owning "Client".
+ bool _hasSeenIsMaster{false};
+};
+
+} // namespace mongo
diff --git a/src/mongo/rpc/metadata/client_metadata_test.cpp b/src/mongo/rpc/metadata/client_metadata_test.cpp
new file mode 100644
index 00000000000..8d64e5e7697
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata_test.cpp
@@ -0,0 +1,296 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/rpc/metadata/client_metadata.h"
+
+#include <boost/filesystem.hpp>
+#include <map>
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+constexpr auto kMetadataDoc = "client"_sd;
+constexpr auto kApplication = "application"_sd;
+constexpr auto kDriver = "driver"_sd;
+constexpr auto kName = "name"_sd;
+constexpr auto kType = "type"_sd;
+constexpr auto kVersion = "version"_sd;
+constexpr auto kOperatingSystem = "os"_sd;
+constexpr auto kArchitecture = "architecture"_sd;
+
+constexpr auto kUnknown = "unkown"_sd;
+
+#define ASSERT_DOC_OK(...) \
+ do { \
+ auto _swParseStatus = \
+ ClientMetadata::parse(BSON(kMetadataDoc << BSON(__VA_ARGS__))[kMetadataDoc]); \
+ ASSERT_OK(_swParseStatus.getStatus()); \
+ } while (0)
+
+#define ASSERT_DOC_NOT_OK(...) \
+ do { \
+ auto _swParseStatus = \
+ ClientMetadata::parse(BSON(kMetadataDoc << BSON(__VA_ARGS__))[kMetadataDoc]); \
+ ASSERT_NOT_OK(_swParseStatus.getStatus()); \
+ } while (0)
+
+
+TEST(ClientMetadatTest, TestLoopbackTest) {
+ // Serialize without application name
+ {
+ BSONObjBuilder builder;
+ ASSERT_OK(ClientMetadata::serializePrivate("a", "b", "c", "d", "e", "f", "g", &builder));
+
+ auto obj = builder.obj();
+ auto swParseStatus = ClientMetadata::parse(obj[kMetadataDoc]);
+ ASSERT_OK(swParseStatus.getStatus());
+ ASSERT_EQUALS("g", swParseStatus.getValue().get().getApplicationName());
+
+ BSONObj outDoc =
+ BSON(kMetadataDoc << BSON(
+ kApplication << BSON(kName << "g") << kDriver
+ << BSON(kName << "a" << kVersion << "b")
+ << kOperatingSystem
+ << BSON(kType << "c" << kName << "d" << kArchitecture << "e"
+ << kVersion
+ << "f")));
+ ASSERT_EQUALS(obj, outDoc);
+ }
+
+ // Serialize without application name
+ {
+ BSONObjBuilder builder;
+ ClientMetadata::serializePrivate("a", "b", "c", "d", "e", "f", &builder);
+
+ auto obj = builder.obj();
+ auto swParseStatus = ClientMetadata::parse(obj[kMetadataDoc]);
+ ASSERT_OK(swParseStatus.getStatus());
+
+ BSONObj outDoc = BSON(
+ kMetadataDoc << BSON(
+ kDriver << BSON(kName << "a" << kVersion << "b") << kOperatingSystem
+ << BSON(kType << "c" << kName << "d" << kArchitecture << "e" << kVersion
+ << "f")));
+ ASSERT_EQUALS(obj, outDoc);
+ }
+
+ // Serialize with the os information automatically computed
+ {
+ BSONObjBuilder builder;
+ ASSERT_OK(ClientMetadata::serialize("a", "b", "f", &builder));
+
+ auto obj = builder.obj();
+
+ auto swParse = ClientMetadata::parse(obj[kMetadataDoc]);
+ ASSERT_OK(swParse.getStatus());
+ ASSERT_EQUALS("f", swParse.getValue().get().getApplicationName());
+ }
+}
+
+// Mixed: no client metadata document
+TEST(ClientMetadatTest, TestEmptyDoc) {
+ {
+ auto parseStatus = ClientMetadata::parse(BSONElement());
+
+ ASSERT_OK(parseStatus.getStatus());
+ }
+
+ {
+ auto obj = BSON("client" << BSONObj());
+ auto parseStatus = ClientMetadata::parse(obj[kMetadataDoc]);
+
+ ASSERT_NOT_OK(parseStatus.getStatus());
+ }
+}
+
+// Positive: test with only required fields
+TEST(ClientMetadatTest, TestRequiredOnlyFields) {
+ // Without app name
+ ASSERT_DOC_OK(kDriver << BSON(kName << "n1" << kVersion << "v1") << kOperatingSystem
+ << BSON(kType << kUnknown));
+
+ // With AppName
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+}
+
+
+// Positive: test with app_name spelled wrong fields
+TEST(ClientMetadatTest, TestWithAppNameSpelledWrong) {
+ ASSERT_DOC_OK(kApplication << BSON("extra"
+ << "1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+}
+
+// Positive: test with empty application document
+TEST(ClientMetadatTest, TestWithEmptyApplication) {
+ ASSERT_DOC_OK(kApplication << BSONObj() << kDriver << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+}
+
+// Negative: test with appplication wrong type
+TEST(ClientMetadatTest, TestNegativeWithAppNameWrongType) {
+ ASSERT_DOC_NOT_OK(kApplication << "1" << kDriver << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+}
+
+// Positive: test with extra fields
+TEST(ClientMetadatTest, TestExtraFields) {
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1"
+ << "extra"
+ << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown << "extra"
+ << "v1"));
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown)
+ << "extra"
+ << "v1");
+}
+
+// Negative: only application specified
+TEST(ClientMetadatTest, TestNegativeOnlyApplication) {
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1"));
+}
+
+// Negative: all combinations of only missing 1 required field
+TEST(ClientMetadatTest, TestNegativeMissingRequiredOneField) {
+ ASSERT_DOC_NOT_OK(kDriver << BSON(kVersion << "v1") << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kDriver << BSON(kName << "n1") << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kDriver << BSON(kName << "n1" << kVersion << "v1"));
+}
+
+// Negative: document with wrong types for required fields
+TEST(ClientMetadatTest, TestNegativeWrongTypes) {
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << 1) << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << 1 << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << 1)
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << 1));
+}
+
+// Negative: document larger than 512 bytes
+TEST(ClientMetadatTest, TestNegativeLargeDocument) {
+ {
+ std::string str(350, 'x');
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << "1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown)
+ << "extra"
+ << str);
+ }
+ {
+ std::string str(512, 'x');
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << "1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown)
+ << "extra"
+ << str);
+ }
+}
+
+// Negative: document with app_name larger than 128 bytes
+TEST(ClientMetadatTest, TestNegativeLargeAppName) {
+ {
+ std::string str(128, 'x');
+ ASSERT_DOC_OK(kApplication << BSON(kName << str) << kDriver
+ << BSON(kName << "n1" << kVersion << "1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+
+ BSONObjBuilder builder;
+ ASSERT_OK(ClientMetadata::serialize("n1", "1", str, &builder));
+ }
+ {
+ std::string str(129, 'x');
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << str) << kDriver
+ << BSON(kName << "n1" << kVersion << "1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+
+ BSONObjBuilder builder;
+ ASSERT_NOT_OK(ClientMetadata::serialize("n1", "1", str, &builder));
+ }
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript
index 7306f9a5fac..3a16737708f 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -77,6 +77,7 @@ env.Library(
'$BUILD_DIR/mongo/db/commands/killcursors_common',
'$BUILD_DIR/mongo/db/pipeline/aggregation',
'$BUILD_DIR/mongo/db/views/views',
+ '$BUILD_DIR/mongo/rpc/client_metadata',
'$BUILD_DIR/mongo/s/cluster_ops_impl',
'$BUILD_DIR/mongo/s/coreshard',
'$BUILD_DIR/mongo/s/mongoscore',
diff --git a/src/mongo/s/commands/cluster_is_master_cmd.cpp b/src/mongo/s/commands/cluster_is_master_cmd.cpp
index 191e04e7f54..391ad28b039 100644
--- a/src/mongo/s/commands/cluster_is_master_cmd.cpp
+++ b/src/mongo/s/commands/cluster_is_master_cmd.cpp
@@ -28,10 +28,14 @@
#include "mongo/platform/basic.h"
+#include "mongo/db/client.h"
#include "mongo/db/commands.h"
+#include "mongo/db/operation_context.h"
#include "mongo/db/server_options.h"
#include "mongo/db/server_parameters.h"
#include "mongo/db/wire_version.h"
+#include "mongo/rpc/metadata/client_metadata.h"
+#include "mongo/rpc/metadata/client_metadata_ismaster.h"
#include "mongo/s/grid.h"
#include "mongo/s/write_ops/batched_command_request.h"
#include "mongo/util/map_util.h"
@@ -68,6 +72,36 @@ public:
int options,
std::string& errmsg,
BSONObjBuilder& result) {
+
+ auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(txn->getClient());
+ bool seenIsMaster = clientMetadataIsMasterState.hasSeenIsMaster();
+ if (!seenIsMaster) {
+ clientMetadataIsMasterState.setSeenIsMaster();
+ }
+
+ BSONElement element = cmdObj[kMetadataDocumentName];
+ if (!element.eoo()) {
+ if (seenIsMaster) {
+ return Command::appendCommandStatus(
+ result,
+ Status(ErrorCodes::ClientMetadataCannotBeMutated,
+ "The client metadata document may only be sent in the first isMaster"));
+ }
+
+ auto swParseClientMetadata = ClientMetadata::parse(element);
+
+ if (!swParseClientMetadata.getStatus().isOK()) {
+ return Command::appendCommandStatus(result, swParseClientMetadata.getStatus());
+ }
+
+ invariant(swParseClientMetadata.getValue());
+
+ swParseClientMetadata.getValue().get().logClientMetadata(txn->getClient());
+
+ clientMetadataIsMasterState.setClientMetadata(
+ txn->getClient(), std::move(swParseClientMetadata.getValue()));
+ }
+
result.appendBool("ismaster", true);
result.append("msg", "isdbgrid");
result.appendNumber("maxBsonObjectSize", BSONObjMaxUserSize);
diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp
index e9cbc1991f4..ad7daff0f61 100644
--- a/src/mongo/scripting/mozjs/mongo.cpp
+++ b/src/mongo/scripting/mozjs/mongo.cpp
@@ -640,7 +640,7 @@ void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) {
auto cs = statusWithHost.getValue();
std::string errmsg;
- std::unique_ptr<DBClientBase> conn(cs.connect(errmsg));
+ std::unique_ptr<DBClientBase> conn(cs.connect("MongoDB Shell", errmsg));
if (!conn.get()) {
uasserted(ErrorCodes::InternalError, errmsg);
diff --git a/src/mongo/shell/bench.cpp b/src/mongo/shell/bench.cpp
index 2f5fc52359c..111dda578df 100644
--- a/src/mongo/shell/bench.cpp
+++ b/src/mongo/shell/bench.cpp
@@ -485,7 +485,7 @@ DBClientBase* BenchRunConfig::createConnection() const {
const ConnectionString connectionString = uassertStatusOK(ConnectionString::parse(host));
std::string errorMessage;
- DBClientBase* connection = connectionString.connect(errorMessage);
+ DBClientBase* connection = connectionString.connect("BenchRun", errorMessage);
uassert(16158, errorMessage, connection != NULL);
return connection;
diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp
index ee65163e09e..e4edd8d9391 100644
--- a/src/mongo/shell/shell_utils.cpp
+++ b/src/mongo/shell/shell_utils.cpp
@@ -318,7 +318,7 @@ void ConnectionRegistry::killOperationsOnAllConnections(bool withPrompt) const {
const ConnectionString cs(status.getValue());
string errmsg;
- std::unique_ptr<DBClientWithCommands> conn(cs.connect(errmsg));
+ std::unique_ptr<DBClientWithCommands> conn(cs.connect("MongoDB Shell", errmsg));
if (!conn) {
continue;
}
diff --git a/src/mongo/shell/shell_utils_launcher.cpp b/src/mongo/shell/shell_utils_launcher.cpp
index 73e0cd6a65f..7d094589922 100644
--- a/src/mongo/shell/shell_utils_launcher.cpp
+++ b/src/mongo/shell/shell_utils_launcher.cpp
@@ -856,7 +856,8 @@ inline void kill_wrapper(ProcessId pid, int sig, int port, const BSONObj& opt) {
//
try {
DBClientConnection conn;
- conn.connect(HostAndPort{"127.0.0.1:" + BSONObjBuilder::numStr(port)});
+ conn.connect(HostAndPort{"127.0.0.1:" + BSONObjBuilder::numStr(port)},
+ "MongoDB Shell");
BSONElement authObj = opt["auth"];
diff --git a/src/mongo/tools/sniffer.cpp b/src/mongo/tools/sniffer.cpp
index b5081a361ed..70b1d77dfb3 100644
--- a/src/mongo/tools/sniffer.cpp
+++ b/src/mongo/tools/sniffer.cpp
@@ -404,7 +404,7 @@ void processMessage(Connection& c, Message& m) {
std::shared_ptr<DBClientConnection> conn = forwarder[c];
if (!conn) {
conn.reset(new DBClientConnection(true));
- uassertStatusOK(conn->connect(mongo::HostAndPort{forwardAddress}));
+ uassertStatusOK(conn->connect(mongo::HostAndPort{forwardAddress}, "mongosniff"));
forwarder[c] = conn;
}
if (m.operation() == mongo::dbQuery || m.operation() == mongo::dbGetMore) {