3278 Kusto Unit Test Refactor (#1053)

* 3278 Moved functions related to Metadata from DataSourceFactory to MetadataFactory.cs

* 3278 Refactored DataSourceFactory to not be static. Added IDataSourceFactory interface.

* 3278 Refactored IKustoIntellisenseHelper.cs to not be static. Added IKustoIntellisenseHelper.cs interface.

* 3278 Removed unused functions from Scripter and deleted ScripterCore.cs because it was unused. Refactored Scripter.cs methods to not be static and created IScripter.cs

* 3278 Refactored datsasourceConnectionFactory in ConnectionService to use MEF through the InitializeService function

* 3278 Refactored IAutoCompleteHelper to not be static and added IAutoCompleteHelper interface.

* 3278 Removed unused classes DatabaseFullAccessException and FeatureWithFullDbAccess. Refactored ObjectExplorerService to use ImportingConstructor attribute. Removed commented out in KustoResultsReader. Removed unused functions from DatabaseLocksManager. Removed unused IDataSourceConnectionFactory from ConnectionService

* 3278 Moved SqlConnectionOpener class to new file. Added new interfaces IConnectedBindingQueue.cs and ISqlConnectionOpener.cs Added sqlConnectionOpener to dependency in ConnectedBindingQueue.

* 3278 Removed needsMetadata param from ConnectedBindingQueue. Added param to AddConnectionContext where it's used

* 3278 Refactored ConnectedBindingQueue to use MEF. Refactored usages to run against interface.

* 3278 Corrected ServiceHost namespace. Removed unused dependency to sqlToolsContext in LanguageService. Updated dependency in MetadataService and LanguageService to IProtocolEndpoint instead of ServiceHost

* 3278 Added back NextResult function and summary to KustoResultsReader. Renamed instance to _instance in ConnectionService and DatabaseLocksManager to stay consistent. Changed OpenDataSourceConnection to private in ConnectionService

* 3278 Converted helper methods back to static and removed backing interfaces

* 3278 Reverted AdminService > InitializeService to use ServiceHost as param
This commit is contained in:
Justin M
2020-08-24 13:18:00 -07:00
committed by GitHub
parent 61ada47820
commit 14f5a3e0f1
34 changed files with 355 additions and 1295 deletions

View File

@@ -38,18 +38,13 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <summary>
/// Singleton service instance
/// </summary>
private static readonly Lazy<ConnectionService> instance
private static readonly Lazy<ConnectionService> _instance
= new Lazy<ConnectionService>(() => new ConnectionService());
/// <summary>
/// Gets the singleton service instance
/// </summary>
public static ConnectionService Instance => instance.Value;
/// <summary>
/// The SQL connection factory object
/// </summary>
private IDataSourceConnectionFactory connectionFactory;
public static ConnectionService Instance => _instance.Value;
private DatabaseLocksManager lockedDatabaseManager;
@@ -113,9 +108,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// </summary>
public ConnectionService()
{
var defaultQueue = new ConnectedBindingQueue(needsMetadata: false);
connectedQueues.AddOrUpdate("Default", defaultQueue, (key, old) => defaultQueue);
this.LockedDatabaseManager.ConnectionService = this;
}
/// <summary>
@@ -181,25 +173,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <summary>
/// Gets the SQL connection factory instance
/// </summary>
public IDataSourceConnectionFactory ConnectionFactory
{
get
{
if (this.connectionFactory == null)
{
this.connectionFactory = new DataSourceConnectionFactory();
}
return this.connectionFactory;
}
internal set { this.connectionFactory = value; }
}
/// <summary>
/// Test constructor that injects dependency interfaces
/// </summary>
/// <param name="testFactory"></param>
public ConnectionService(IDataSourceConnectionFactory testFactory) => this.connectionFactory = testFactory;
private IDataSourceConnectionFactory _dataSourceConnectionFactory;
// Attempts to link a URI to an actively used connection for this URI
public virtual bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo) => this.OwnerToConnectionMap.TryGetValue(ownerUri, out connectionInfo);
@@ -254,7 +228,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
bool connectionChanged = false;
if (!OwnerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo))
{
connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection);
connectionInfo = new ConnectionInfo(_dataSourceConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection);
}
else if (IsConnectionChanged(connectionParams, connectionInfo))
{
@@ -267,7 +241,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
if (connectionChanged)
{
connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection);
connectionInfo = new ConnectionInfo(_dataSourceConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection);
}
// Try to open a connection with the given ConnectParams
@@ -362,11 +336,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
return false;
}
private bool IsDefaultConnectionType(string connectionType)
{
return string.IsNullOrEmpty(connectionType) || ConnectionType.Default.Equals(connectionType, StringComparison.CurrentCultureIgnoreCase);
}
private void DisconnectExistingConnectionIfNeeded(ConnectParams connectionParams, ConnectionInfo connectionInfo, bool disconnectAll)
{
// Resolve if it is an existing connection
@@ -425,7 +394,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
var reliableConnection = connection as ReliableDataSourceConnection;
IDataSource dataSource = reliableConnection.GetUnderlyingConnection();
DataSourceObjectMetadata clusterMetadata = DataSourceFactory.CreateClusterMetadata(connectionInfo.ConnectionDetails.ServerName);
DataSourceObjectMetadata clusterMetadata = MetadataFactory.CreateClusterMetadata(connectionInfo.ConnectionDetails.ServerName);
DiagnosticsInfo clusterDiagnostics = dataSource.GetDiagnostics(clusterMetadata);
ReliableConnectionHelper.ServerInfo serverInfo = DataSourceFactory.ConvertToServerinfoFormat(DataSourceType.Kusto, clusterDiagnostics);
@@ -801,14 +770,14 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
ConnectionDetails connectionDetails = info.ConnectionDetails.Clone();
IDataSource dataSource = OpenDataSourceConnection(info);
DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(info.ConnectionDetails.ServerName);
DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(info.ConnectionDetails.ServerName);
ListDatabasesResponse response = new ListDatabasesResponse();
// Mainly used by "manage" dashboard
if(listDatabasesParams.IncludeDetails.HasTrue()){
IEnumerable<DataSourceObjectMetadata> databaseMetadataInfo = dataSource.GetChildObjects(objectMetadata, true);
List<DatabaseInfo> metadata = DataSourceFactory.ConvertToDatabaseInfo(databaseMetadataInfo);
List<DatabaseInfo> metadata = MetadataFactory.ConvertToDatabaseInfo(databaseMetadataInfo);
response.Databases = metadata.ToArray();
return response;
@@ -819,9 +788,14 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
return response;
}
public void InitializeService(IProtocolEndpoint serviceHost)
public void InitializeService(IProtocolEndpoint serviceHost, IDataSourceConnectionFactory dataSourceConnectionFactory,
IConnectedBindingQueue connectedBindingQueue)
{
this.ServiceHost = serviceHost;
ServiceHost = serviceHost;
_dataSourceConnectionFactory = dataSourceConnectionFactory;
connectedQueues.AddOrUpdate("Default", connectedBindingQueue, (key, old) => connectedBindingQueue);
LockedDatabaseManager.ConnectionService = this;
// Register request and event handlers with the Service Host
serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest);
@@ -1429,12 +1403,12 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <param name="connInfo">The connection info to connect with</param>
/// <param name="featureName">A plaintext string that will be included in the application name for the connection</param>
/// <returns>A SqlConnection created with the given connection info</returns>
internal static IDataSource OpenDataSourceConnection(ConnectionInfo connInfo, string featureName = null)
private IDataSource OpenDataSourceConnection(ConnectionInfo connInfo, string featureName = null)
{
try
{
// generate connection string
string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails);
string connectionString = BuildConnectionString(connInfo.ConnectionDetails);
// TODOKusto: Pass in type of DataSource needed to make this generic. Hard coded to Kusto right now.
return DataSourceFactory.Create(DataSourceType.Kusto, connectionString, connInfo.ConnectionDetails.AzureAccountToken);

View File

@@ -3,8 +3,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Data.Common;
using Microsoft.Kusto.ServiceLayer.Connection;
using System.Composition;
using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
namespace Microsoft.Kusto.ServiceLayer.Connection
@@ -14,6 +14,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// The purpose of the factory is to make it easier to mock out the database
/// in 'offline' unit test scenarios.
/// </summary>
[Export(typeof(IDataSourceConnectionFactory))]
public class DataSourceConnectionFactory : IDataSourceConnectionFactory
{
/// <summary>

View File

@@ -1,28 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.Kusto.ServiceLayer.Connection
{
public class DatabaseFullAccessException: Exception
{
public DatabaseFullAccessException()
: base()
{
}
public DatabaseFullAccessException(string message, Exception exception)
: base(message, exception)
{
}
public DatabaseFullAccessException(string message)
: base(message)
{
}
}
}

View File

@@ -3,7 +3,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.Kusto.ServiceLayer.LanguageServices;
using System;
using System.Collections.Generic;
using System.Threading;
@@ -12,100 +11,23 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
{
public class DatabaseLocksManager: IDisposable
{
internal DatabaseLocksManager(int waitToGetFullAccess)
{
this.waitToGetFullAccess = waitToGetFullAccess;
}
private static DatabaseLocksManager instance = new DatabaseLocksManager(DefaultWaitToGetFullAccess);
private static readonly DatabaseLocksManager _instance = new DatabaseLocksManager();
public static DatabaseLocksManager Instance
{
get
{
return instance;
return _instance;
}
}
public ConnectionService ConnectionService { get; set; }
private Dictionary<string, ManualResetEvent> databaseAccessEvents = new Dictionary<string, ManualResetEvent>();
private object databaseAccessLock = new object();
public const int DefaultWaitToGetFullAccess = 10000;
public int waitToGetFullAccess = DefaultWaitToGetFullAccess;
private ManualResetEvent GetResetEvent(string serverName, string databaseName)
{
string key = GenerateKey(serverName, databaseName);
ManualResetEvent resetEvent = null;
lock (databaseAccessLock)
{
if (!databaseAccessEvents.TryGetValue(key, out resetEvent))
{
resetEvent = new ManualResetEvent(true);
databaseAccessEvents.Add(key, resetEvent);
}
}
return resetEvent;
}
public bool GainFullAccessToDatabase(string serverName, string databaseName)
{
/*
* TODO: add the lock so not two process can get full access at the same time
ManualResetEvent resetEvent = GetResetEvent(serverName, databaseName);
if (resetEvent.WaitOne(this.waitToGetFullAccess))
{
resetEvent.Reset();
foreach (IConnectedBindingQueue item in ConnectionService.ConnectedQueues)
{
item.CloseConnections(serverName, databaseName);
}
return true;
}
else
{
throw new DatabaseFullAccessException($"Waited more than {waitToGetFullAccess} milli seconds for others to release the lock");
}
*/
foreach (IConnectedBindingQueue item in ConnectionService.ConnectedQueues)
{
item.CloseConnections(serverName, databaseName, DefaultWaitToGetFullAccess);
}
return true;
}
public bool ReleaseAccess(string serverName, string databaseName)
{
/*
ManualResetEvent resetEvent = GetResetEvent(serverName, databaseName);
foreach (IConnectedBindingQueue item in ConnectionService.ConnectedQueues)
{
item.OpenConnections(serverName, databaseName);
}
resetEvent.Set();
*/
foreach (IConnectedBindingQueue item in ConnectionService.ConnectedQueues)
{
item.OpenConnections(serverName, databaseName, DefaultWaitToGetFullAccess);
}
return true;
}
private string GenerateKey(string serverName, string databaseName)
{
return $"{serverName.ToLowerInvariant()}-{databaseName.ToLowerInvariant()}";
}
private readonly Dictionary<string, ManualResetEvent> _databaseAccessEvents = new Dictionary<string, ManualResetEvent>();
public void Dispose()
{
foreach (var resetEvent in databaseAccessEvents)
foreach (var resetEvent in _databaseAccessEvents)
{
resetEvent.Value.Dispose();
}

View File

@@ -1,40 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.Kusto.ServiceLayer.Connection
{
/// <summary>
/// Any operation that needs full access to databas should implement this interface.
/// Make sure to call GainAccessToDatabase before the operation and ReleaseAccessToDatabase after
/// </summary>
public interface IFeatureWithFullDbAccess
{
/// <summary>
/// Database Lock Manager
/// </summary>
DatabaseLocksManager LockedDatabaseManager { get; set; }
/// <summary>
/// Makes sure the feature has fill access to the database
/// </summary>
bool GainAccessToDatabase();
/// <summary>
/// Release the access to db
/// </summary>
bool ReleaseAccessToDatabase();
/// <summary>
/// Server name
/// </summary>
string ServerName { get; }
/// <summary>
/// Database name
/// </summary>
string DatabaseName { get; }
}
}