diff --git a/Driver/Exceptions/MongoCommandException.cs b/Driver/Exceptions/MongoCommandException.cs index c510444a54..49c9489343 100644 --- a/Driver/Exceptions/MongoCommandException.cs +++ b/Driver/Exceptions/MongoCommandException.cs @@ -19,6 +19,8 @@ using System.Linq; using System.Runtime.Serialization; using System.Text; +using MongoDB.Bson; + namespace MongoDB.Driver { [Serializable] public class MongoCommandException : MongoException { @@ -36,6 +38,14 @@ namespace MongoDB.Driver { : base(message, innerException) { } + public MongoCommandException( + string message, + BsonDocument commandResult + ) + : this(message) { + Data.Add("CommandResult", commandResult); + } + // this constructor needed to support deserialization public MongoCommandException( SerializationInfo info, diff --git a/Driver/Internal/DirectConnector.cs b/Driver/Internal/DirectConnector.cs index a202fed399..f78802ba35 100644 --- a/Driver/Internal/DirectConnector.cs +++ b/Driver/Internal/DirectConnector.cs @@ -84,24 +84,10 @@ namespace MongoDB.Driver.Internal { bool isPrimary; try { - var command = new BsonDocument("ismaster", 1); - using ( - var message = new MongoQueryMessage( - "admin.$cmd", - QueryFlags.SlaveOk, - 0, // numberToSkip - 1, // numberToReturn - command, - null // fields - ) - ) { - connection.SendMessage(message, SafeMode.False); - } - var reply = connection.ReceiveMessage(); - var commandResult = reply.Documents[0]; - isPrimary = - commandResult["ok", false].ToBoolean() && - commandResult["ismaster", false].ToBoolean(); + var isMasterCommand = new BsonDocument("ismaster", 1); + var isMasterResult = connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, isMasterCommand); + + isPrimary = isMasterResult["ismaster", false].ToBoolean(); if (!isPrimary && !url.SlaveOk) { throw new MongoConnectionException("Server is not a primary and SlaveOk is false"); } diff --git a/Driver/Internal/MongoConnection.cs b/Driver/Internal/MongoConnection.cs index c5fc8b3d6a..d1cfb5a17c 100644 --- a/Driver/Internal/MongoConnection.cs +++ b/Driver/Internal/MongoConnection.cs @@ -78,24 +78,14 @@ namespace MongoDB.Driver.Internal { if (closed) { throw new InvalidOperationException("Connection is closed"); } lock (connectionLock) { var nonceCommand = new BsonDocument("getnonce", 1); - using ( - var nonceMessage = new MongoQueryMessage( - string.Format("{0}.$cmd", databaseName), // collectionFullName - QueryFlags.None, - 0, // numberToSkip - 1, // numberToReturn - nonceCommand, // query - null // fields - ) - ) { - SendMessage(nonceMessage, SafeMode.False); - } - var nonceReply = ReceiveMessage(); - var nonceCommandResult = nonceReply.Documents[0]; - if (!nonceCommandResult["ok", false].ToBoolean()) { - throw new MongoAuthenticationException("Error getting nonce for authentication"); + var commandCollectionName = string.Format("{0}.$cmd", databaseName); + string nonce; + try { + var nonceResult = RunCommand(commandCollectionName, QueryFlags.None, nonceCommand); + nonce = nonceResult["nonce"].AsString; + } catch (MongoCommandException ex) { + throw new MongoAuthenticationException("Error getting nonce for authentication", ex); } - var nonce = nonceCommandResult["nonce"].AsString; var passwordDigest = MongoUtils.Hash(credentials.Username + ":mongo:" + credentials.Password); var digest = MongoUtils.Hash(nonce + credentials.Username + passwordDigest); @@ -105,22 +95,11 @@ namespace MongoDB.Driver.Internal { { "nonce", nonce }, { "key", digest } }; - using ( - var authenticateMessage = new MongoQueryMessage( - string.Format("{0}.$cmd", databaseName), // collectionFullName - QueryFlags.None, - 0, // numberToSkip - 1, // numberToReturn - authenticateCommand, // query - null // fields - ) - ) { - SendMessage(authenticateMessage, SafeMode.False); - } - var authenticationReply = ReceiveMessage(); - var authenticationResult = authenticationReply.Documents[0]; - if (!authenticationResult["ok", false].ToBoolean()) { - throw new MongoAuthenticationException("Invalid credentials for database"); + try { + RunCommand(commandCollectionName, QueryFlags.None, authenticateCommand); + } catch (MongoCommandException ex) { + var message = string.Format("Invalid credentials for database: {0}", databaseName); + throw new MongoAuthenticationException(message, ex); } var authentication = new Authentication(credentials); @@ -259,28 +238,67 @@ namespace MongoDB.Driver.Internal { if (closed) { throw new InvalidOperationException("Connection is closed"); } lock (connectionLock) { var logoutCommand = new BsonDocument("logout", 1); - using ( - var logoutMessage = new MongoQueryMessage( - string.Format("{0}.$cmd", databaseName), // collectionFullName - QueryFlags.None, - 0, // numberToSkip - 1, // numberToReturn - logoutCommand, - null // fields - ) - ) { - SendMessage(logoutMessage, SafeMode.False); - } - var logoutReply = ReceiveMessage(); - var logoutCommandResult = logoutReply.Documents[0]; - if (!logoutCommandResult["ok", false].ToBoolean()) { - throw new MongoAuthenticationException("Error in logout"); + var commandCollectionName = string.Format("{0}.$cmd", databaseName); + try { + var logoutCommandResult = RunCommand(commandCollectionName, QueryFlags.None, logoutCommand); + } catch (MongoCommandException ex) { + throw new MongoAuthenticationException("Error logging off", ex); } authentications.Remove(databaseName); } } + // this is a low level method that doesn't require a MongoServer + // so it can be used while connecting to a MongoServer + internal BsonDocument RunCommand( + string collectionName, + QueryFlags queryFlags, + BsonDocument command + ) { + var commandName = command.GetElement(0).Name; + + using ( + var message = new MongoQueryMessage( + collectionName, + queryFlags, + 0, // numberToSkip + 1, // numberToReturn + command, + null // fields + ) + ) { + SendMessage(message, SafeMode.False); + } + + var reply = ReceiveMessage(); + if ((reply.ResponseFlags & ResponseFlags.QueryFailure) != 0) { + var message = string.Format("Command '{0}' failed (QueryFailure flag set)", commandName); + throw new MongoCommandException(message); + } + if (reply.NumberReturned != 1) { + var message = string.Format("Command '{0}' failed (wrong number of documents returned: {1})", commandName, reply.NumberReturned); + throw new MongoCommandException(message); + } + + var commandResult = reply.Documents[0]; + if (!commandResult.Contains("ok")) { + var message = string.Format("Command '{0}' failed (ok element missing in result)", commandName); + } + if (!commandResult["ok"].ToBoolean()) { + string message; + var err = commandResult["err", null]; + if (err == null || err.IsBsonNull) { + message = string.Format("Command '{0}' failed (no error message found)", commandName); + } else { + message = string.Format("Command '{0}' failed ({1})", commandName, err.ToString()); + } + throw new MongoCommandException(message, commandResult); + } + + return commandResult; + } + internal MongoReplyMessage ReceiveMessage() { if (closed) { throw new InvalidOperationException("Connection is closed"); } lock (connectionLock) { diff --git a/Driver/Internal/ReplicaSetConnector.cs b/Driver/Internal/ReplicaSetConnector.cs index a82a61fca4..2990f1f37d 100644 --- a/Driver/Internal/ReplicaSetConnector.cs +++ b/Driver/Internal/ReplicaSetConnector.cs @@ -122,16 +122,13 @@ namespace MongoDB.Driver.Internal { private List GetHostAddresses( QueryNodeResponse response ) { - if (!response.CommandResult.Contains("hosts")) { + if (!response.IsMasterResult.Contains("hosts")) { var message = string.Format("Server is not a member of a replica set: {0}", response.Address); throw new MongoConnectionException(message); } - if (url.ReplicaSetName != null) { - // TODO: check replica set name - } var nodes = new List(); - foreach (BsonString host in response.CommandResult["hosts"].AsBsonArray.Values) { + foreach (BsonString host in response.IsMasterResult["hosts"].AsBsonArray.Values) { var address = MongoServerAddress.Parse(host.Value); nodes.Add(address); } @@ -162,25 +159,23 @@ namespace MongoDB.Driver.Internal { try { var connection = new MongoConnection(null, args.Address); // no connection pool try { - var command = new BsonDocument("ismaster", 1); - using ( - var message = new MongoQueryMessage( - "admin.$cmd", - QueryFlags.SlaveOk, - 0, // numberToSkip - 1, // numberToReturn - command, - null // fields - ) - ) { - connection.SendMessage(message, SafeMode.False); - } - var reply = connection.ReceiveMessage(); - response.CommandResult = reply.Documents[0]; + var isMasterCommand = new BsonDocument("ismaster", 1); + var isMasterResult = connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, isMasterCommand); + + response.IsMasterResult = isMasterResult; response.Connection = connection; // might become the first connection in the connection pool - response.IsPrimary = - response.CommandResult["ok", false].ToBoolean() && - response.CommandResult["ismaster", false].ToBoolean(); + response.IsPrimary = isMasterResult["ismaster", false].ToBoolean(); + + if (url.ReplicaSetName != null) { + var getStatusCommand = new BsonDocument("replSetGetStatus", 1); + var getStatusResult = connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, getStatusCommand); + + var replicaSetName = getStatusResult["set"].AsString; + if (replicaSetName != url.ReplicaSetName) { + var message = string.Format("Host {0} belongs to a different replica set: {1}", args.Address, replicaSetName); + throw new MongoConnectionException(message); + } + } } catch { try { connection.Close(); } catch { } // ignore exceptions throw; @@ -203,7 +198,7 @@ namespace MongoDB.Driver.Internal { // note: OK to use automatic properties on private helper class private class QueryNodeResponse { public MongoServerAddress Address { get; set; } - public BsonDocument CommandResult { get; set; } + public BsonDocument IsMasterResult { get; set; } public bool IsPrimary { get; set; } public MongoConnection Connection { get; set; } public Exception Exception { get; set; }