Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/dotnet/ZooKeeperNet.Tests/AbstractZooKeeperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ protected ZooKeeper CreateClientWithAddress(string address)
return new ZooKeeper(address, new TimeSpan(0, 0, 0, 10000), watcher);
}

protected virtual ZooKeeper CreateClientWithSasl(ISaslClient saslClient)
protected virtual ZooKeeper CreateClientWithSasl(IWatcher watcher, ISaslClient saslClient)
{
CountdownWatcher watcher = new CountdownWatcher();
return new ZooKeeper("127.0.0.1:2181", new TimeSpan(0, 0, 0, 10000), watcher, saslClient);
// "Only" use a 10s session timeout as failed tests can
// otherwise spin for a very long time in the client's
// Dispose method.
return new ZooKeeper("127.0.0.1:2181", new TimeSpan(0, 0, 0, 10), watcher, saslClient);
}

public class CountdownWatcher : IWatcher
Expand Down
87 changes: 80 additions & 7 deletions src/dotnet/ZooKeeperNet.Tests/SaslTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ namespace ZooKeeperNet.Tests
using S22.Sasl;
using System.Net;
using System.Collections.Generic;
using System.Threading;

class S22SaslClient : ISaslClient
internal class S22SaslClient : ISaslClient
{
// The following must be configured in zoo.conf:
//
Expand All @@ -45,8 +46,8 @@ class S22SaslClient : ISaslClient
//
// See https://cwiki.apache.org/confluence/display/ZOOKEEPER/Client-Server+mutual+authentication#Client-Servermutualauthentication-ServerConfiguration
// for additional details.
private const string Username = "bob";
private const string Password = "bobsecret";
public string Username { get; set; } = "bob";
public string Password { get; set; } = "bobsecret";

private SaslMechanism m = null;

Expand Down Expand Up @@ -89,24 +90,72 @@ public void Finish()
}
}

[TestFixture]
internal class StateWatcher : IWatcher
{
private EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset);

private KeeperState waitFor;
private IList<KeeperState> observed;

public StateWatcher(KeeperState waitFor, bool collectObserved = false)
{
this.waitFor = waitFor;
this.observed = collectObserved ? new List<KeeperState>() : null;
}

public void Process(WatchedEvent @event)
{
if (this.observed != null)
{
this.observed.Add(@event.State);
}

if (@event.State == waitFor)
{
ewh.Set();
}
}

public bool WaitSignaled(int ms)
{
return ewh.WaitOne(ms);
}

public IList<KeeperState> Observed
{
get
{
return observed;
}
}
}

[TestFixture, Explicit]
public class SaslTests : AbstractZooKeeperTests
{
private static readonly int maxWaitMs = 250;

[Test]
public void testSasl()
{
string name = "/" + Guid.NewGuid() + "sasltest";

using (var zk = CreateClientWithSasl(new S22SaslClient()))
var authWatcher = new StateWatcher(KeeperState.SaslAuthenticated);
using (var zk = CreateClientWithSasl(authWatcher, new S22SaslClient()))
{
Assert.IsTrue(authWatcher.WaitSignaled(maxWaitMs));

List<ACL> acl = new List<ACL>();
acl.Add(new ACL(Perms.ALL, new ZKId("sasl", "bob")));

Assert.AreEqual(name, zk.Create(name, new byte[0], acl, CreateMode.Persistent));
}

using (var zk = CreateClient())
var connWatcher = new StateWatcher(KeeperState.SyncConnected);
using (var zk = CreateClient(connWatcher))
{
Assert.IsTrue(connWatcher.WaitSignaled(maxWaitMs));

try
{
zk.GetData(name, false, new Stat());
Expand All @@ -118,9 +167,33 @@ public void testSasl()
}
}

using (var zk = CreateClientWithSasl(new S22SaslClient()))
var authWatcher2 = new StateWatcher(KeeperState.SaslAuthenticated);
using (var zk = CreateClientWithSasl(authWatcher2, new S22SaslClient()))
{
Assert.IsTrue(authWatcher2.WaitSignaled(maxWaitMs));

zk.GetData(name, false, new Stat());
zk.Delete(name, -1);
}
}

[Test]
public void testSaslBadCredentials()
{
var saslClient = new S22SaslClient()
{
Password = "evewashere"
};

var watcher = new StateWatcher(KeeperState.AuthFailed, true);
using (var zk = CreateClientWithSasl(watcher, saslClient))
{
Assert.IsTrue(watcher.WaitSignaled(maxWaitMs));
Assert.AreEqual(ZooKeeper.States.AUTH_FAILED, zk.State);
Assert.IsFalse(zk.State.IsAlive());

// We must have been connected before failing authentication.
Assert.IsTrue(watcher.Observed.Contains(KeeperState.SyncConnected));
}
}
}
Expand Down
25 changes: 22 additions & 3 deletions src/dotnet/ZooKeeperNet/ClientConnectionRequestProducer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class ClientConnectionRequestProducer : IStartable, IDisposable
{
private static readonly ILog LOG = LogManager.GetLogger(typeof(ClientConnectionRequestProducer));
private const string RETRY_CONN_MSG = ", closing socket connection and attempting reconnect";
private const string AUTH_FAILED_MSG = ", closing socket connection and setting AUTH_FAILED state";

private readonly ClientConnection conn;
private readonly ZooKeeper zooKeeper;
Expand Down Expand Up @@ -186,7 +187,13 @@ private void SendRequests()
else
{
// this is ugly, you have a better way speak up
if (e is KeeperException.SessionExpiredException)
if (e is KeeperException.AuthFailedException)
{
LOG.InfoFormat("{0}{1}", e.Message, AUTH_FAILED_MSG);
// AUTH_FAILED state, causing IsAlive() => false.
zooKeeper.State = ZooKeeper.States.AUTH_FAILED;
}
else if (e is KeeperException.SessionExpiredException)
LOG.InfoFormat("{0}, closing socket connection", e.Message);
else if (e is SessionTimeoutException)
LOG.InfoFormat("{0}{1}", e.Message, RETRY_CONN_MSG);
Expand All @@ -203,6 +210,10 @@ private void SendRequests()
{
conn.consumer.QueueEvent(new WatchedEvent(KeeperState.Disconnected, EventType.None, null));
}
else if(zooKeeper.State.IsAuthFailed())
{
conn.consumer.QueueEvent(new WatchedEvent(KeeperState.AuthFailed, EventType.None, null));
}
}
}
}
Expand Down Expand Up @@ -460,8 +471,8 @@ private void PrimeConnection()
try
{
bool lastPacket = false;
while (true)

do
{
RequestHeader h = new RequestHeader();
ReplyHeader r = new ReplyHeader();
Expand Down Expand Up @@ -505,12 +516,20 @@ private void PrimeConnection()
}
}
}
// We must have received a 'ConnectResponse' by
// now, as we have waited for 1+ packets.
while (zooKeeper.State.IsConnected());
}
finally
{
conn.saslClient.Finish();
}
}

if (zooKeeper.State.IsConnected())
{
conn.consumer.QueueEvent(new WatchedEvent(KeeperState.SaslAuthenticated, EventType.None, null));
}
}

bool hasOutgoingRequests;
Expand Down
2 changes: 2 additions & 0 deletions src/dotnet/ZooKeeperNet/KeeperState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public enum KeeperState
Disconnected = 0,
NoSyncConnected = 1,
SyncConnected = 3,
AuthFailed = 4,
SaslAuthenticated = 6,
Expired = -112
}
}
10 changes: 10 additions & 0 deletions src/dotnet/ZooKeeperNet/ZooKeeper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ public bool IsAlive()
return this != CLOSED && this != AUTH_FAILED;
}

public bool IsConnected()
{
return this == CONNECTED; // || this == CONNECTEDREADONLY
}

public bool IsAuthFailed()
{
return this == AUTH_FAILED;
}

public bool Equals(States other)
{
if (ReferenceEquals(null, other)) return false;
Expand Down