Port Azure code from SSDT to the tools service (#477)

Porting of the vast majority of Azure-related code from SSDT. This is very large, so I want to put this out as one large "lift and shift" PR before I do the tools-service specific JSON-RPC service handlers, connect a new account handler (as the code to get necessary info from accounts and subscriptions isn't fully complete) and add tests over these

**What's in this PR**:

- Created 3 new projects:
  - Microsoft.SqlTools.ResourceProvider will host the executable that accepts requests for Azure-related actions over the JSON-RPC protocol. This must be separate from other DLLs since a direct dependency on the Azure SDK DLLs fails (they're NetStandard 1.4 and you can't reference them if you have RuntimeIdentifiers in your .csproj file)
  - Microsoft.SqlTools.ResourceProvider.Core is where all the main business logic is, including definitions and logic on how to navigate over resources and create firewall rules, etc.
  - Microsoft.SqlTools.ResourceProvider.DefaultImpl is the actual Azure implementation of the resource provider APIs. The reason for separating this is to support eventual integration back into other tools (since their Azure and Identity services will be different).
- Implemented the AzureResourceManager that connects to Azure via ARM APIs and handles creating firewall rule and querying databases. The dependent DLLs have had major breaking changes, so will need additional verification to ensure this works as expected
- Ported the unit tests for all code that was not a viewmodel. Viewmodel test code will be ported in a future update as we plumb through a service-equivalent to these. Also, the DependencyManager code which has overlap with our service provider code is commented out. Will work to uncomment in a future update as it has value to test some scenarios

**What's not in this PR**:
- Identity Services. We currently just have a stub for the interface, and even that will likely change a little
- anything JSON-RPC or registered service related. These will be adapted from the viewmodels and added in a separate PR
This commit is contained in:
Kevin Cunnane
2017-10-04 12:37:20 -07:00
committed by GitHub
parent 98f7e59f1a
commit ac64ac063b
108 changed files with 8181 additions and 21 deletions

View File

@@ -0,0 +1,204 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.ResourceProvider.Core;
using Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
{
/// <summary>
/// Tests for AzureDatabaseDiscoveryProvider to verify getting azure databases using azure resource manager
/// </summary>
public class AzureDatabaseDiscoveryProviderTest
{
[Fact]
public async Task GetShouldReturnDatabasesSuccessfully()
{
string databaseName1 = "server/db1";
string databaseName2 = "db2";
string databaseName3 = "server/";
string databaseName4 = "/db4";
List<string> databasesForSubscription = new List<string>()
{
databaseName1,
databaseName2
};
Dictionary<string, List<string>> subscriptionToDatabaseMap = new Dictionary<string, List<string>>();
subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), databasesForSubscription);
subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), new List<string>()
{
databaseName3,
databaseName4,
});
AzureDatabaseDiscoveryProvider databaseDiscoveryProvider = FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
ServiceResponse<DatabaseInstanceInfo> response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
List<DatabaseInstanceInfo> list = response.Data.ToList();
Assert.NotNull(list);
Assert.True(list.Any(x => x.Name == "db1" && x.ServerInstanceInfo.Name == "server"));
Assert.False(list.Any(x => x.Name == "db2" && x.ServerInstanceInfo.Name == ""));
Assert.True(list.Any(x => x.Name == "" && x.ServerInstanceInfo.Name == "server"));
Assert.False(list.Any(x => x.Name == "db4" && x.ServerInstanceInfo.Name == ""));
Assert.True(list.Count() == 2);
}
[Fact]
public async Task GetShouldReturnDatabasesEvenIfFailsForOneServer()
{
string databaseName1 = "server1/db1";
string databaseName2 = "server1/db2";
string databaseName3 = "error/db3";
string databaseName4 = "server2/db4";
List<string> databasesForSubscription = new List<string>()
{
databaseName1,
databaseName2
};
Dictionary<string, List<string>> subscriptionToDatabaseMap = new Dictionary<string, List<string>>();
subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), databasesForSubscription);
subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), new List<string>()
{
databaseName3,
databaseName4,
});
AzureDatabaseDiscoveryProvider databaseDiscoveryProvider = FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
ServiceResponse<DatabaseInstanceInfo> response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
List<DatabaseInstanceInfo> list = response.Data.ToList();
Assert.NotNull(list);
Assert.True(list.Any(x => x.Name == "db1" && x.ServerInstanceInfo.Name == "server1"));
Assert.True(list.Any(x => x.Name == "db2" && x.ServerInstanceInfo.Name == "server1"));
Assert.True(list.Any(x => x.Name == "db4" && x.ServerInstanceInfo.Name == "server2"));
Assert.False(list.Any(x => x.Name == "db3" && x.ServerInstanceInfo.Name == "error"));
Assert.True(list.Count() == 3);
Assert.NotNull(response.Errors);
Assert.True(response.Errors.Count() == 1);
}
[Fact]
public async Task GetShouldReturnDatabasesFromCacheIfGetCalledTwice()
{
Dictionary<string, List<string>> subscriptionToDatabaseMap = CreateSubscriptonMap(2);
AddDatabases(subscriptionToDatabaseMap, 3);
AzureDatabaseDiscoveryProvider databaseDiscoveryProvider = FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
ServiceResponse<DatabaseInstanceInfo> response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
List<DatabaseInstanceInfo> list = response.Data.ToList();
ValidateResult(subscriptionToDatabaseMap, list);
Dictionary<string, List<string>> subscriptionToDatabaseMap2 = CopySubscriptonMap(subscriptionToDatabaseMap);
AddDatabases(subscriptionToDatabaseMap2, 5);
AzureTestContext testContext = new AzureTestContext(subscriptionToDatabaseMap2);
databaseDiscoveryProvider.AccountManager = testContext.AzureAccountManager;
databaseDiscoveryProvider.AzureResourceManager = testContext.AzureResourceManager;
response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
list = response.Data.ToList();
//the provider should get the databases from cache for the list should match the first created data
ValidateResult(subscriptionToDatabaseMap, list);
}
[Fact]
public async Task GetShouldReturnDatabasesFromServiceIfGetCalledTwiceButRefreshed()
{
Dictionary<string, List<string>> subscriptionToDatabaseMap = CreateSubscriptonMap(2);
AddDatabases(subscriptionToDatabaseMap, 3);
AzureDatabaseDiscoveryProvider databaseDiscoveryProvider = FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
ServiceResponse<DatabaseInstanceInfo> response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
List<DatabaseInstanceInfo> list = response.Data.ToList();
ValidateResult(subscriptionToDatabaseMap, list);
Dictionary<string, List<string>> subscriptionToDatabaseMap2 = CopySubscriptonMap(subscriptionToDatabaseMap);
AddDatabases(subscriptionToDatabaseMap2, 5);
AzureTestContext testContext = new AzureTestContext(subscriptionToDatabaseMap2);
databaseDiscoveryProvider.AccountManager = testContext.AzureAccountManager;
databaseDiscoveryProvider.AzureResourceManager = testContext.AzureResourceManager;
await databaseDiscoveryProvider.ClearCacheAsync();
response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
list = response.Data.ToList();
//the provider should get the databases from cache for the list should match the first created data
ValidateResult(subscriptionToDatabaseMap2, list);
}
private void ValidateResult(Dictionary<string, List<string>> subscriptionToDatabaseMap, List<DatabaseInstanceInfo> result)
{
Assert.NotNull(result);
int numberOfDatabases = 0;
foreach (KeyValuePair<string, List<string>> item in subscriptionToDatabaseMap)
{
numberOfDatabases += item.Value.Count;
foreach (string databaseFullName in item.Value)
{
string serverName = AzureTestContext.GetServerName(databaseFullName);
string databaseName = databaseFullName.Replace(serverName + @"/", "");
Assert.True(result.Any(x => x.Name == databaseName && x.ServerInstanceInfo.Name == serverName));
}
}
Assert.True(result.Count() == numberOfDatabases);
}
private void AddDatabases(Dictionary<string, List<string>> subscriptionToDatabaseMap, int numberOfDatabases)
{
foreach (string key in subscriptionToDatabaseMap.Keys.ToList())
{
List<string> databases = CreateDatabases(numberOfDatabases);
subscriptionToDatabaseMap[key] = databases;
}
}
private List<string> CreateDatabases(int numberOfDatabases)
{
List<string> databases = new List<string>();
for (int j = 0; j < numberOfDatabases; j++)
{
databases.Add(string.Format(CultureInfo.InvariantCulture, @"{0}/{1}", Guid.NewGuid().ToString(), Guid.NewGuid().ToString()));
}
return databases;
}
private Dictionary<string, List<string>> CreateSubscriptonMap(int numberOfSubscriptions)
{
Dictionary<string, List<string>> subscriptionToDatabaseMap = new Dictionary<string, List<string>>();
for (int i = 0; i < numberOfSubscriptions; i++)
{
string id = Guid.NewGuid().ToString();
subscriptionToDatabaseMap.Add(id, null);
}
return subscriptionToDatabaseMap;
}
private Dictionary<string, List<string>> CopySubscriptonMap(Dictionary<string, List<string>> subscriptionToDatabaseMap)
{
Dictionary<string, List<string>> subscriptionToDatabaseMapCopy = new Dictionary<string, List<string>>();
foreach (KeyValuePair<string, List<string>> pair in subscriptionToDatabaseMap)
{
subscriptionToDatabaseMapCopy.Add(pair.Key, null);
}
return subscriptionToDatabaseMapCopy;
}
[Fact]
public async Task GetShouldReturnEmptyGivenNotSubscriptionFound()
{
Dictionary<string, List<string>> subscriptionToDatabaseMap = new Dictionary<string, List<string>>();
AzureDatabaseDiscoveryProvider databaseDiscoveryProvider =
FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
ServiceResponse<DatabaseInstanceInfo> response =
await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
Assert.NotNull(response);
Assert.NotNull(response.Data);
Assert.False(response.Data.Any());
}
}
}

View File

@@ -0,0 +1,60 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SqlTools.ResourceProvider.Core;
using Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
{
/// <summary>
/// Tests for AzureServerDiscoveryProvider to verify getting azure servers using azure resource manager
/// </summary>
public class AzureSqlServerDiscoveryProviderTest
{
[Fact]
public async Task GetShouldReturnServersSuccessfully()
{
string serverName = "server";
List<string> serversForSubscription = new List<string>()
{
Guid.NewGuid().ToString(),
serverName
};
Dictionary<string, List<string>> subscriptionToDatabaseMap = new Dictionary<string, List<string>>();
subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), serversForSubscription);
subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), new List<string>()
{
Guid.NewGuid().ToString(),
Guid.NewGuid().ToString(),
});
AzureSqlServerDiscoveryProvider discoveryProvider =
FakeDataFactory.CreateAzureServerDiscoveryProvider(subscriptionToDatabaseMap);
ServiceResponse<ServerInstanceInfo> response = await discoveryProvider.GetServerInstancesAsync();
IEnumerable<ServerInstanceInfo> servers = response.Data;
Assert.NotNull(servers);
Assert.True(servers.Any(x => x.Name == serverName));
Assert.True(servers.Count() == 4);
}
[Fact]
public async Task GetShouldReturnEmptyGivenNotSubscriptionFound()
{
Dictionary<string, List<string>> subscriptionToDatabaseMap = new Dictionary<string, List<string>>();
AzureSqlServerDiscoveryProvider discoveryProvider =
FakeDataFactory.CreateAzureServerDiscoveryProvider(subscriptionToDatabaseMap);
ServiceResponse<ServerInstanceInfo> response = await discoveryProvider.GetServerInstancesAsync();
IEnumerable<ServerInstanceInfo> servers = response.Data;
Assert.NotNull(servers);
Assert.False(servers.Any());
}
}
}

View File

@@ -0,0 +1,34 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using Microsoft.SqlTools.ResourceProvider.DefaultImpl;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
{
/// <summary>
/// Tests for AzureSubscriptionContextWrapper to verify the wrapper on azure subscription class
/// </summary>
public class AzureSubscriptionContextTest
{
[Fact]
public void SubscriptionNameShouldReturnNullGivenNullSubscription()
{
AzureSubscriptionContext subscriptionContext = new AzureSubscriptionContext(null);
Assert.True(subscriptionContext.SubscriptionName == String.Empty);
Assert.True(subscriptionContext.Subscription == null);
}
[Fact]
public void SubscriptionNameShouldReturnCorrectValueGivenValidSubscription()
{
string name = Guid.NewGuid().ToString();
AzureSubscriptionContext subscriptionContext = new AzureSubscriptionContext(new AzureSubscriptionIdentifier(null, name, null));
Assert.True(subscriptionContext.SubscriptionName == name);
Assert.True(subscriptionContext.Subscription != null);
}
}
}

View File

@@ -0,0 +1,121 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Management.Sql.Models;
using Microsoft.Rest;
using Microsoft.SqlTools.ResourceProvider.Core;
using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
using Microsoft.SqlTools.ResourceProvider.DefaultImpl;
using Moq;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
{
/// <summary>
/// A container to create test data and mock classes to test azure services and providers
/// </summary>
internal class AzureTestContext
{
public AzureTestContext(Dictionary<string, List<string>> subscriptionToDatabaseMap)
{
AzureAccountManagerMock = new Mock<IAzureAuthenticationManager>();
List<IAzureUserAccountSubscriptionContext> accountSubscriptions = new List
<IAzureUserAccountSubscriptionContext>();
AzureResourceManagerMock = new Mock<IAzureResourceManager>();
foreach (string subscriptionName in subscriptionToDatabaseMap.Keys)
{
var azureAccount = new AzureUserAccount();
AzureSubscriptionIdentifier subId = new AzureSubscriptionIdentifier(azureAccount, subscriptionName, null);
var subscription = new AzureUserAccountSubscriptionContext(subId, new TokenCredentials("dummy"));
accountSubscriptions.Add(subscription);
var sessionMock = new Mock<IAzureResourceManagementSession>();
IAzureResourceManagementSession session = sessionMock.Object;
sessionMock.Setup(x => x.SubscriptionContext).Returns(subscription);
AzureResourceManagerMock.Setup(x => x.CreateSessionAsync(subscription)).Returns(Task.FromResult(session));
MockServersAndDatabases(subscriptionToDatabaseMap[subscriptionName], session);
}
AzureAccountManagerMock.Setup(x => x.GetSelectedSubscriptionsAsync()).Returns
(Task.FromResult(accountSubscriptions as IEnumerable<IAzureUserAccountSubscriptionContext>));
}
private void MockServersAndDatabases(List<string> resourceNames, IAzureResourceManagementSession session)
{
IEnumerable<IAzureResource> azureResources = resourceNames.Select(
x => new AzureResourceWrapper(new TrackedResource(Guid.NewGuid().ToString(), "id", x, "type")) { ResourceGroupName = Guid.NewGuid().ToString()}
).ToList();
List<IAzureSqlServerResource> servers = new List<IAzureSqlServerResource>();
foreach (var azureResourceWrapper in azureResources.ToList())
{
var serverName = GetServerName(azureResourceWrapper.Name);
if (string.IsNullOrEmpty(serverName) || servers.Any(x => x.Name == serverName))
{
continue;
}
var databases = azureResources.Where(x => x.Name.StartsWith(serverName + "/"));
if (serverName.Equals("error", StringComparison.OrdinalIgnoreCase))
{
AzureResourceManagerMock.Setup(x => x.GetAzureDatabasesAsync(session, azureResourceWrapper.ResourceGroupName, serverName))
.Throws(new ApplicationException(serverName));
}
else
{
AzureResourceManagerMock.Setup(x => x.GetAzureDatabasesAsync(session, azureResourceWrapper.ResourceGroupName, serverName))
.Returns(Task.FromResult(databases));
}
Server azureSqlServer = new Server(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), serverName, null, null, null, null, null, null, null, null, fullyQualifiedDomainName: serverName + ".database.windows.net");
servers.Add(new SqlAzureResource(azureSqlServer)
{
ResourceGroupName = azureResourceWrapper.ResourceGroupName
});
}
AzureResourceManagerMock.Setup(x => x.GetSqlServerAzureResourcesAsync(session))
.Returns(Task.FromResult(servers as IEnumerable<IAzureSqlServerResource>));
}
internal static string GetServerName(string name)
{
string azureResourceName = name;
int separatorIndex = azureResourceName.IndexOf("/", StringComparison.OrdinalIgnoreCase);
if (separatorIndex >= 0)
{
return azureResourceName.Substring(0, separatorIndex);
}
else
{
return azureResourceName;
}
}
public Mock<IAzureAuthenticationManager> AzureAccountManagerMock
{
get;
set;
}
public IAzureAuthenticationManager AzureAccountManager
{
get { return AzureAccountManagerMock.Object; }
}
public IAzureResourceManager AzureResourceManager
{
get { return AzureResourceManagerMock.Object; }
}
public Mock<IAzureResourceManager> AzureResourceManagerMock
{
get;
set;
}
}
}

View File

@@ -0,0 +1,48 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Threading.Tasks;
using Microsoft.SqlTools.ResourceProvider.Core;
using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
{
/// <summary>
/// A fake server discovery class
/// </summary>
[Exportable(ServerTypes.SqlServer, Categories.Azure
, typeof(IServerDiscoveryProvider),
"Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure.FakeAzureServerDiscoveryProvider", 1)]
public class FakeAzureServerDiscoveryProvider : ExportableBase, IServerDiscoveryProvider, ISecureService
{
public FakeAzureServerDiscoveryProvider()
{
Metadata = new ExportableMetadata(ServerTypes.SqlServer, Categories.Azure,
"Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure.FakeAzureServerDiscoveryProvider", 1);
}
public Task<ServiceResponse<ServerInstanceInfo>> GetServerInstancesAsync()
{
throw new NotImplementedException();
}
public IAccountManager AccountManager
{
get;
set;
}
/// <summary>
/// This should always return null otherwise there's going to be a infinite loop
/// </summary>
public IServerDiscoveryProvider ServerDiscoveryProvider
{
get
{
return GetService<IServerDiscoveryProvider>();
}
}
}
}