Skip to content

Commit

Permalink
SNOW-902608 unit tests for Connection Pool Manager; introduction of S…
Browse files Browse the repository at this point in the history
…essionFactory for unit testing of connection pool manager and session pooling
  • Loading branch information
sfc-gh-mhofman committed Oct 16, 2023
1 parent 73bb45c commit 9d367be
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 23 deletions.
215 changes: 215 additions & 0 deletions Snowflake.Data.Tests/UnitTests/ConnectionPoolManagerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
* Copyright (c) 2023 Snowflake Computing Inc. All rights reserved.
*/

using System;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Snowflake.Data.Core;
using Snowflake.Data.Core.Session;
using Moq;

namespace Snowflake.Data.Tests.UnitTests
{
[TestFixture, NonParallelizable]
class ConnectionPoolManagerTest
{
private readonly ConnectionPoolManager _connectionPoolManager = new ConnectionPoolManager();
private readonly string _connectionString1 = "database=D1;warehouse=W1;account=A1;user=U1;password=P1;role=R1;";
private readonly string _connectionString2 = "database=D2;warehouse=W2;account=A2;user=U2;password=P2;role=R2;";
private readonly SecureString _password = new SecureString();

[OneTimeSetUp]
public static void BeforeAllTests()
{
// SnowflakeDbConnectionPool.SwapVersion(); // TODO: swap when new version is the default
SessionPool.SessionFactory = new MockSessionFactory();
}

[OneTimeTearDown]
public void AfterAllTests()
{
SessionPool.SessionFactory = new SessionFactory();
}

[Test]
public void TestPoolManagerReturnsSessionPoolForGivenConnectionString()
{
// Act
var sessionPool = _connectionPoolManager.GetPool(_connectionString1, _password);

// Assert
Assert.AreEqual(_connectionString1, sessionPool.ConnectionString);
Assert.AreEqual(_password, sessionPool.Password);
}

[Test]
public void TestPoolManagerReturnsSamePoolForGivenConnectionString()
{
// Arrange
var anotherConnectionString = _connectionString1;

// Act
var sessionPool1 = _connectionPoolManager.GetPool(_connectionString1, _password);
var sessionPool2 = _connectionPoolManager.GetPool(anotherConnectionString, _password);

// Assert
Assert.AreEqual(sessionPool1, sessionPool2);
}

[Test]
public void TestDifferentPoolsAreReturnedForDifferentConnectionStrings()
{
// Arrange
Assert.AreNotSame(_connectionString1, _connectionString2);

// Act
var sessionPool1 = _connectionPoolManager.GetPool(_connectionString1, _password);
var sessionPool2 = _connectionPoolManager.GetPool(_connectionString2, _password);

// Assert
Assert.AreNotSame(sessionPool1, sessionPool2);
Assert.AreEqual(_connectionString1, sessionPool1.ConnectionString);
Assert.AreEqual(_connectionString2, sessionPool2.ConnectionString);
}


[Test]
public void TestGetSessionWorksForSpecifiedConnectionString()
{
// Act
var sfSession = _connectionPoolManager.GetSession(_connectionString1, _password);

// Assert
Assert.AreEqual(_connectionString1, sfSession.ConnectionString);
Assert.AreEqual(_password, sfSession.Password);
}

[Test]
public async Task TestGetSessionAsyncWorksForSpecifiedConnectionString()
{
// Act
var sfSession = await _connectionPoolManager.GetSessionAsync(_connectionString1, _password, CancellationToken.None);

// Assert
Assert.AreEqual(_connectionString1, sfSession.ConnectionString);
Assert.AreEqual(_password, sfSession.Password);
}

[Test]
[Ignore("Enable after completion of SNOW-937189")] // TODO:
public void TestCountingOfSessionProvidedByPool()
{
// Act
_connectionPoolManager.GetSession(_connectionString1, _password);

// Assert
var sessionPool = _connectionPoolManager.GetPool(_connectionString1, _password);
Assert.AreEqual(1, sessionPool.GetCurrentPoolSize());
}

[Test]
[Ignore("Enable after completion of SNOW-937189")] // TODO:
public void TestCountingOfSessionReturnedBackToPool()
{
// Arrange
var sfSession = _connectionPoolManager.GetSession(_connectionString1, _password);

// Act
_connectionPoolManager.AddSession(sfSession);

// Assert
var sessionPool = _connectionPoolManager.GetPool(_connectionString1, _password);
Assert.AreEqual(1, sessionPool.GetCurrentPoolSize());
}

[Test]
public void TestSetMaxPoolSizeForAllPools()
{
// Arrange
var sessionPool1 = _connectionPoolManager.GetPool(_connectionString1, _password);
var sessionPool2 = _connectionPoolManager.GetPool(_connectionString2, _password);

// Act
_connectionPoolManager.SetMaxPoolSize(3);

// Assert
Assert.AreEqual(3, sessionPool1.GetMaxPoolSize());
Assert.AreEqual(3, sessionPool2.GetMaxPoolSize());
}

[Test]
public void TestSetTimeoutForAllPools()
{
// Arrange
var sessionPool1 = _connectionPoolManager.GetPool(_connectionString1, _password);
var sessionPool2 = _connectionPoolManager.GetPool(_connectionString2, _password);

// Act
_connectionPoolManager.SetTimeout(3000);

// Assert
Assert.AreEqual(3000, sessionPool1.GetTimeout());
Assert.AreEqual(3000, sessionPool2.GetTimeout());
}

[Test]
public void TestSetPoolingDisabledForAllPools()
{
// Arrange
var sessionPool1 = _connectionPoolManager.GetPool(_connectionString1, _password);

// Act
_connectionPoolManager.SetPooling(false);

// Assert
Assert.AreEqual(false, sessionPool1.GetPooling());
}

[Test]
public void TestSetPoolingEnabledBack()
{
// Arrange
var sessionPool1 = _connectionPoolManager.GetPool(_connectionString1, _password);
_connectionPoolManager.SetPooling(false);

// Act
_connectionPoolManager.SetPooling(true);

// Assert
Assert.AreEqual(true, sessionPool1.GetPooling());
}

[Test]
public void TestGetPoolingOnManagerLevelNotSupported()
{
Assert.Throws<NotSupportedException>(() => _connectionPoolManager.GetPooling());
}

[Test]
public void TestGetTimeoutOnManagerLevelNotSupported()
{
Assert.Throws<NotSupportedException>(() => _connectionPoolManager.GetTimeout());
}

[Test]
public void TestGetMaxPoolSizeOnManagerLevelNotSupported()
{
Assert.Throws<NotSupportedException>(() => _connectionPoolManager.GetMaxPoolSize());
}
}

class MockSessionFactory : ISessionFactory
{
public SFSession NewSession(string connectionString, SecureString password)
{
var mockSfSession = new Mock<SFSession>(connectionString, password);
mockSfSession.Setup(x => x.Open()).Verifiable();
mockSfSession.Setup(x => x.OpenAsync(default)).Returns(Task.FromResult(this));
return mockSfSession.Object;
}
}

}
8 changes: 4 additions & 4 deletions Snowflake.Data/Client/SnowflakeDbConnectionPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ private static IConnectionManager ConnectionManager
return s_connectionManager;
lock (s_connectionManagerInstanceLock)
{
s_connectionManager = new ConnectionManagerV1(); // old implementation of the pool as a default
s_connectionManager = new ConnectionManagerV1(); // TODO: change to ConnectionPoolManager once new pool implementation is complete
}
return s_connectionManager;
}
Expand Down Expand Up @@ -97,16 +97,16 @@ public static bool GetPooling()
return ConnectionManager.GetPooling();
}

internal static void SwapVersion()
internal static void SwapVersion() // TODO: make public once development of entire ConnectionPoolManager is complete
{
lock (s_connectionManagerInstanceLock)
{
if (ConnectionManager is ConnectionManagerV1)
{
s_connectionManager.ClearAllPools();
s_connectionManager = new ConnectionManagerV2();
s_connectionManager = new ConnectionPoolManager();
}
if (ConnectionManager is ConnectionManagerV2)
if (ConnectionManager is ConnectionPoolManager)
{
s_connectionManager.ClearAllPools();
s_connectionManager = new ConnectionManagerV1();
Expand Down
2 changes: 1 addition & 1 deletion Snowflake.Data/Core/Session/ConnectionManagerV1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Snowflake.Data.Core.Session
{
internal sealed class ConnectionManagerV1 : IConnectionManager
{
private readonly SessionPool _sessionPool = SessionPool.CreateSessionPoolV1();
private readonly SessionPool _sessionPool = SessionPool.CreateSessionCache();
public SFSession GetSession(string connectionString, SecureString password) => _sessionPool.GetSession(connectionString, password);
public Task<SFSession> GetSessionAsync(string connectionString, SecureString password, CancellationToken cancellationToken)
=> _sessionPool.GetSessionAsync(connectionString, password, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@

namespace Snowflake.Data.Core.Session
{
internal sealed class ConnectionManagerV2 : IConnectionManager
internal sealed class ConnectionPoolManager : IConnectionManager
{
private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<ConnectionManagerV2>();
private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<ConnectionPoolManager>();
private static readonly Object s_poolsLock = new Object();
private readonly Dictionary<string, SessionPool> _pools;

internal ConnectionManagerV2()
internal ConnectionPoolManager()
{
lock (s_poolsLock)
{
Expand Down Expand Up @@ -92,7 +92,7 @@ internal SessionPool GetPool(string connectionString, SecureString password)
return item;
lock (s_poolsLock)
{
var pool = SessionPool.CreateSessionPoolV2(connectionString, password);
var pool = SessionPool.CreateSessionPool(connectionString, password);
_pools.Add(poolKey, pool);
return pool;
}
Expand Down
9 changes: 9 additions & 0 deletions Snowflake.Data/Core/Session/ISessionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Security;

namespace Snowflake.Data.Core.Session
{
internal interface ISessionFactory
{
SFSession NewSession(string connectionString, SecureString password);
}
}
4 changes: 2 additions & 2 deletions Snowflake.Data/Core/Session/SFSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ internal Uri BuildUri(string path, Dictionary<string, string> queryParams = null
return uriBuilder.Uri;
}

internal void Open()
internal virtual void Open()
{
logger.Debug("Open Session");

Expand All @@ -228,7 +228,7 @@ internal void Open()
authenticator.Authenticate();
}

internal async Task OpenAsync(CancellationToken cancellationToken)
internal virtual async Task OpenAsync(CancellationToken cancellationToken)
{
logger.Debug("Open Session Async");

Expand Down
12 changes: 12 additions & 0 deletions Snowflake.Data/Core/Session/SessionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Security;

namespace Snowflake.Data.Core.Session
{
internal class SessionFactory : ISessionFactory
{
public SFSession NewSession(string connectionString, SecureString password)
{
return new SFSession(connectionString, password);
}
}
}
25 changes: 13 additions & 12 deletions Snowflake.Data/Core/Session/SessionPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ sealed class SessionPool : IDisposable
private long _timeout;
private const int MaxPoolSize = 10;
private const long Timeout = 3600;
private string _connectionString;
private SecureString _password;
internal string ConnectionString;
internal SecureString Password;
private bool _pooling = true;
private bool _allowExceedMaxPoolSize = true;

Check warning on line 28 in Snowflake.Data/Core/Session/SessionPool.cs

View workflow job for this annotation

GitHub Actions / Tests on Windows (net472, GCP)

The field 'SessionPool._allowExceedMaxPoolSize' is assigned but its value is never used

Check warning on line 28 in Snowflake.Data/Core/Session/SessionPool.cs

View workflow job for this annotation

GitHub Actions / Tests on Windows (net471, AWS)

The field 'SessionPool._allowExceedMaxPoolSize' is assigned but its value is never used

Check warning on line 28 in Snowflake.Data/Core/Session/SessionPool.cs

View workflow job for this annotation

GitHub Actions / Tests on Windows (net471, GCP)

The field 'SessionPool._allowExceedMaxPoolSize' is assigned but its value is never used

Check warning on line 28 in Snowflake.Data/Core/Session/SessionPool.cs

View workflow job for this annotation

GitHub Actions / Tests on Windows (net472, AZURE)

The field 'SessionPool._allowExceedMaxPoolSize' is assigned but its value is never used

Check warning on line 28 in Snowflake.Data/Core/Session/SessionPool.cs

View workflow job for this annotation

GitHub Actions / Tests on Windows (net472, AWS)

The field 'SessionPool._allowExceedMaxPoolSize' is assigned but its value is never used

Check warning on line 28 in Snowflake.Data/Core/Session/SessionPool.cs

View workflow job for this annotation

GitHub Actions / Tests on Windows (net471, AZURE)

The field 'SessionPool._allowExceedMaxPoolSize' is assigned but its value is never used
internal static ISessionFactory SessionFactory = new SessionFactory();

internal SessionPool()
private SessionPool()
{
lock (s_sessionPoolLock)
{
Expand All @@ -37,16 +38,16 @@ internal SessionPool()
}
}

internal SessionPool(string connectionString, SecureString password) : this()
private SessionPool(string connectionString, SecureString password) : this()
{
_connectionString = connectionString;
_password = password;
ConnectionString = connectionString;
Password = password;
_allowExceedMaxPoolSize = false; // TODO: SNOW-937190
}

internal static SessionPool CreateSessionPoolV1() => new SessionPool();
internal static SessionPool CreateSessionCache() => new SessionPool();

internal static SessionPool CreateSessionPoolV2(string connectionString, SecureString password) =>
internal static SessionPool CreateSessionPool(string connectionString, SecureString password) =>
new SessionPool(connectionString, password);

~SessionPool()
Expand Down Expand Up @@ -95,10 +96,10 @@ internal Task<SFSession> GetSessionAsync(string connStr, SecureString password,
return session != null ? Task.FromResult(session) : NewSessionAsync(connStr, password, cancellationToken);
}

internal SFSession GetSession() => GetSession(_connectionString, _password);
internal SFSession GetSession() => GetSession(ConnectionString, Password);

internal Task<SFSession> GetSessionAsync(CancellationToken cancellationToken) =>
GetSessionAsync(_connectionString, _password, cancellationToken);
GetSessionAsync(ConnectionString, Password, cancellationToken);

private SFSession GetIdleSession(string connStr)
{
Expand Down Expand Up @@ -133,7 +134,7 @@ private SFSession NewSession(String connectionString, SecureString password)
s_logger.Debug("SessionPool::NewSession");
try
{
var session = new SFSession(connectionString, password);
var session = SessionFactory.NewSession(connectionString, password);
session.Open();
return session;
}
Expand All @@ -153,7 +154,7 @@ private SFSession NewSession(String connectionString, SecureString password)
private Task<SFSession> NewSessionAsync(String connectionString, SecureString password, CancellationToken cancellationToken)
{
s_logger.Debug("SessionPool::NewSessionAsync");
var session = new SFSession(connectionString, password);
var session = SessionFactory.NewSession(connectionString, password);
return session
.OpenAsync(cancellationToken)
.ContinueWith(previousTask =>
Expand Down

0 comments on commit 9d367be

Please sign in to comment.