Improve performance of Create Firewall Rule code (#489)

* Multi-thread server lookup to increase perf for multiple subscriptions

* Use parallel execution for listsubscriptions
- Also refactored parallel execution code to be a bit more generic
This commit is contained in:
Kevin Cunnane
2017-10-11 11:14:47 -07:00
committed by GitHub
parent 4945aead96
commit eab9339c14
5 changed files with 104 additions and 48 deletions

View File

@@ -121,7 +121,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
IEnumerable<IAzureUserAccountSubscriptionContext> subscriptions = await GetSubscriptionsAsync(string.IsNullOrEmpty(serverName));
if (!cancellationToken.IsCancellationRequested)
{
result = await AzureUtil.ExecuteGetAzureResourceAsParallel(null, subscriptions, serverName, cancellationToken,
result = await AzureUtil.ExecuteGetAzureResourceAsParallel((object)null, subscriptions, serverName, cancellationToken,
GetDatabaseForSubscriptionAsync);
}
@@ -215,7 +215,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
/// <summary>
/// Returns a list of Azure sql databases for given subscription
/// </summary>
private async Task<ServiceResponse<DatabaseInstanceInfo>> GetDatabaseForSubscriptionAsync(IAzureResourceManagementSession parentSession,
private async Task<ServiceResponse<DatabaseInstanceInfo>> GetDatabaseForSubscriptionAsync(object notRequired,
IAzureUserAccountSubscriptionContext input, string serverName,
CancellationToken cancellationToken, CancellationToken internalCancellationToken)
{

View File

@@ -10,25 +10,30 @@ using System.Threading.Tasks;
namespace Microsoft.SqlTools.ResourceProvider.Core
{
internal static class AzureUtil
/// <summary>
/// Utils class supporting parallel querying of Azure resources
/// </summary>
public static class AzureUtil
{
/// <summary>
/// Execute an async action for each input in the a list of input in parallel.
/// If any task fails, adds the exeption message to the response errors
/// If cancellation token is set to cancel, returns empty response
/// </summary>
/// If cancellation token is set to cancel, returns empty response.
/// Note: Will not throw if errors occur. Instead the caller should check for errors and either notify
/// or rethrow as needed.
/// </summary>
/// <param name="session">Resource management session to use to call the resource manager</param>
/// <param name="inputs">List of inputs</param>
/// <param name="serverName">server name to filter the result</param>
/// <param name="lookupKey">optional lookup key to filter the result</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="asyncAction">Async action</param>
/// <returns>ServiceResponse including the list of data and errors</returns>
public static async Task<ServiceResponse<TResult>> ExecuteGetAzureResourceAsParallel<TInput, TResult>(
IAzureResourceManagementSession session,
public static async Task<ServiceResponse<TResult>> ExecuteGetAzureResourceAsParallel<TConfig, TInput, TResult>(
TConfig config,
IEnumerable<TInput> inputs,
string serverName,
string lookupKey,
CancellationToken cancellationToken,
Func<IAzureResourceManagementSession,
Func<TConfig,
TInput,
string,
CancellationToken,
@@ -51,10 +56,10 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
var tasks = Enumerable.Range(0, inputList.Count())
.Select(async i =>
{
ServiceResponse<TResult> result = await GetResult(session, inputList[i], serverName, cancellationToken,
ServiceResponse<TResult> result = await GetResult(config, inputList[i], lookupKey, cancellationToken,
cancellationTokenSource.Token, asyncAction);
//server name is used to filter the result and if the data is already found not, we need to cancel the other tasks
if (!string.IsNullOrEmpty(serverName) && result.Found)
if (!string.IsNullOrEmpty(lookupKey) && result.Found)
{
cancellationTokenSource.Cancel();
}
@@ -82,13 +87,13 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
return new ServiceResponse<TResult>(mergedResult, mergedErrors);
}
private static async Task<ServiceResponse<TResult>> GetResult<TInput, TResult>(
IAzureResourceManagementSession session,
private static async Task<ServiceResponse<TResult>> GetResult<TConfig, TInput, TResult>(
TConfig config,
TInput input,
string serverName,
string lookupKey,
CancellationToken cancellationToken,
CancellationToken internalCancellationToken,
Func<IAzureResourceManagementSession,
Func<TConfig,
TInput,
string,
CancellationToken,
@@ -102,7 +107,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
}
try
{
return await asyncAction(session, input, serverName, cancellationToken, internalCancellationToken);
return await asyncAction(config, input, lookupKey, cancellationToken, internalCancellationToken);
}
catch (Exception ex)
{

View File

@@ -5,8 +5,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall
@@ -73,7 +76,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall
IAzureAuthenticationManager authenticationManager = AuthenticationManager;
if (authenticationManager != null && !await authenticationManager.GetUserNeedsReauthenticationAsync())
{
{
FirewallRuleResource firewallRuleResource = await FindAzureResourceAsync(serverName);
firewallRuleResponse = await CreateFirewallRule(firewallRuleResource, startIpAddress, endIpAddress);
}
@@ -158,22 +161,22 @@ namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall
throw new FirewallRuleException(SR.FirewallRuleCreationFailed);
}
foreach (IAzureUserAccountSubscriptionContext subscription in subscriptions)
ServiceResponse<FirewallRuleResource> response = await AzureUtil.ExecuteGetAzureResourceAsParallel((object)null,
subscriptions, serverName, new CancellationToken(), TryFindAzureResourceForSubscriptionAsync);
if (response != null)
{
using (IAzureResourceManagementSession session = await ResourceManager.CreateSessionAsync(subscription))
if (response.Data != null && response.Data.Any())
{
IAzureSqlServerResource azureSqlServer = await FindAzureResourceForSubscriptionAsync(serverName, session);
if (azureSqlServer != null)
{
return new FirewallRuleResource()
{
SubscriptionContext = subscription,
AzureResource = azureSqlServer
};
}
return response.Data.First();
}
if (response.HasError)
{
var error = response.Errors.FirstOrDefault();
throw new FirewallRuleException(error.Message, error);
}
}
// Else throw as we couldn't find the resource as expected
var currentUser = await AuthenticationManager.GetCurrentAccountAsync();
throw new FirewallRuleException(string.Format(CultureInfo.CurrentCulture, SR.AzureServerNotFound, serverName, currentUser != null ? currentUser.UniqueId : string.Empty));
@@ -187,6 +190,34 @@ namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall
throw new FirewallRuleException(SR.FirewallRuleCreationFailed, ex);
}
}
/// <summary>
/// Returns a list of Azure sql databases for given subscription
/// </summary>
private async Task<ServiceResponse<FirewallRuleResource>> TryFindAzureResourceForSubscriptionAsync(object notRequired,
IAzureUserAccountSubscriptionContext input, string serverName,
CancellationToken cancellationToken, CancellationToken internalCancellationToken)
{
ServiceResponse<FirewallRuleResource> result = null;
if (!cancellationToken.IsCancellationRequested)
{
using (IAzureResourceManagementSession session = await ResourceManager.CreateSessionAsync(input))
{
IAzureSqlServerResource azureSqlServer = await FindAzureResourceForSubscriptionAsync(serverName, session);
if (azureSqlServer != null)
{
result = new ServiceResponse<FirewallRuleResource>(new FirewallRuleResource()
{
SubscriptionContext = input,
AzureResource = azureSqlServer
}.SingleItemAsEnumerable());
result.Found = true;
}
}
}
return result ?? new ServiceResponse<FirewallRuleResource>();
}
/// <summary>
/// Throws a firewallRule exception based on give status code