// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // // This code is copied from the source described in the comment below. // ======================================================================================= // Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series // // This sample is supplemental to the technical guidance published on the community // blog at http://blogs.msdn.com/appfabriccat/ and copied from // sqlmain ./sql/manageability/mfx/common/ // // ======================================================================================= // Copyright © 2012 Microsoft Corporation. All rights reserved. // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT. // ======================================================================================= using System; using System.Data.SqlClient; using System.Threading; using System.Threading.Tasks; using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.Kusto.ServiceLayer.DataSource; namespace Microsoft.Kusto.ServiceLayer.Connection { /// /// Provides a reliable way of opening connections to and executing commands /// taking into account potential network unreliability and a requirement for connection retry. /// public sealed class ReliableDataSourceConnection : IDisposable { private IDataSource _dataSource; private readonly RetryPolicy _connectionRetryPolicy; private RetryPolicy _commandRetryPolicy; private readonly Guid _azureSessionId = Guid.NewGuid(); private readonly string _connectionString; private readonly string _azureAccountToken; /// /// Initializes a new instance of the ReliableKustoClient class with a given connection string /// and a policy defining whether to retry a request if the connection fails to be opened or a command /// fails to be successfully executed. /// /// The connection string used to open the SQL Azure database. /// The retry policy defining whether to retry a request if a connection fails to be established. /// The retry policy defining whether to retry a request if a command fails to be executed. public ReliableDataSourceConnection(string connectionString, RetryPolicy connectionRetryPolicy, RetryPolicy commandRetryPolicy, string azureAccountToken) { _connectionString = connectionString; _azureAccountToken = azureAccountToken; _dataSource = DataSourceFactory.Create(DataSourceType.Kusto, connectionString, azureAccountToken); _connectionRetryPolicy = connectionRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy(); _commandRetryPolicy = commandRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy(); _connectionRetryPolicy.RetryOccurred += RetryConnectionCallback; _commandRetryPolicy.RetryOccurred += RetryCommandCallback; } private void RetryCommandCallback(RetryState retryState) { RetryPolicyUtils.RaiseSchemaAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry, _azureSessionId); } private void RetryConnectionCallback(RetryState retryState) { RetryPolicyUtils.RaiseSchemaAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.ConnectionRetry, _azureSessionId); } /// /// Disposes resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Performs application-defined tasks associated with freeing, releasing, or /// resetting managed and unmanaged resources. /// /// A flag indicating that managed resources must be released. public void Dispose(bool disposing) { if (disposing) { if (_connectionRetryPolicy != null) { _connectionRetryPolicy.RetryOccurred -= RetryConnectionCallback; } if (_commandRetryPolicy != null) { _commandRetryPolicy.RetryOccurred -= RetryCommandCallback; } _dataSource.Dispose(); } } /// /// Gets or sets the connection string for opening a connection to the SQL Azure database. /// public string ConnectionString { get; set; } /// /// Gets the policy which decides whether to retry a connection request, based on how many /// times the request has been made and the reason for the last failure. /// public RetryPolicy ConnectionRetryPolicy { get { return _connectionRetryPolicy; } } /// /// Gets the policy which decides whether to retry a command, based on how many /// times the request has been made and the reason for the last failure. /// public RetryPolicy CommandRetryPolicy { get { return _commandRetryPolicy; } set { Validate.IsNotNull(nameof(value), value); if (_commandRetryPolicy != null) { _commandRetryPolicy.RetryOccurred -= RetryCommandCallback; } _commandRetryPolicy = value; _commandRetryPolicy.RetryOccurred += RetryCommandCallback; } } /// /// Gets the server name from the underlying connection. /// public string ClusterName { get { return _dataSource.ClusterName; } } /// /// If the underlying SqlConnection absolutely has to be accessed, for instance /// to pass to external APIs that require this type of connection, then this /// can be used. /// /// public IDataSource GetUnderlyingConnection() { return _dataSource; } /// /// Changes the current database for an open Connection object. /// /// The name of the database to use in place of the current database. public void ChangeDatabase(string databaseName) { _dataSource.UpdateDatabase(databaseName); } /// /// Opens a database connection with the settings specified by the ConnectionString /// property of the provider-specific Connection object. /// public void Open() { // TODOKusto: Should we initialize in the constructor or here. Set a breapoint and check. // Check if retry policy was specified, if not, disable retries by executing the Open method using RetryPolicy.NoRetry. if(_dataSource == null) { _connectionRetryPolicy.ExecuteAction(() => { _dataSource = DataSourceFactory.Create(DataSourceType.Kusto, _connectionString, _azureAccountToken); }); } } /// /// Opens a database connection with the settings specified by the ConnectionString /// property of the provider-specific Connection object. /// public Task OpenAsync(CancellationToken token) { // Make sure that the token isn't cancelled before we try if (token.IsCancellationRequested) { return Task.FromCanceled(token); } // Check if retry policy was specified, if not, disable retries by executing the Open method using RetryPolicy.NoRetry. try { return _connectionRetryPolicy.ExecuteAction(async () => { await Task.Run(() => Open()); }); } catch (Exception e) { return Task.FromException(e); } } /// /// Closes the connection to the database. /// public void Close() { } /// /// Gets the time to wait while trying to establish a connection before terminating /// the attempt and generating an error. /// public int ConnectionTimeout { get { return 30; } } /// /// Gets the name of the current database or the database to be used after a /// connection is opened. /// public string Database { get { return _dataSource.DatabaseName; } } private void VerifyConnectionOpen(ReliableDataSourceConnection conn) { if(conn.GetUnderlyingConnection() == null) { conn.Open(); } } } }