Akma/login migrations (#1728)

In this PR, we make the appropriate backend service changes in order to enable the login migrations feature in the SQL migration extension.

Changes include:

updating the Microsoft.SqlServer.Migration.Login NuGet version to the latest version
adding a new request handler for StartLoginMigrations calls, which makes the appropriate calls to the login NuGet
adding ExtensionMethod helper to properly combine exception maps login migration nuget calls

Co-authored-by: Akshay Mata <akma@microsoft.com>
This commit is contained in:
AkshayMata
2022-11-05 00:18:48 -04:00
committed by GitHub
parent acf3e92e83
commit c528617e18
7 changed files with 420 additions and 0 deletions

View File

@@ -28,6 +28,7 @@
<PackageReference Update="Microsoft.Azure.Kusto.Language" Version="9.0.4" />
<PackageReference Update="Microsoft.SqlServer.Assessment" Version="[1.1.9]" />
<PackageReference Update="Microsoft.SqlServer.Migration.Assessment" Version="1.0.20221028.23" />
<PackageReference Update="Microsoft.SqlServer.Migration.Logins" Version="1.0.20221103.24" />
<PackageReference Update="Microsoft.SqlServer.Management.SqlParser" Version="160.22519.0" />
<PackageReference Update="Microsoft.Azure.OperationalInsights" Version="1.0.0" />
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" />

View File

@@ -53,6 +53,7 @@
<PackageReference Include="Microsoft.Data.SqlClient" />
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" />
<PackageReference Include="Microsoft.SqlServer.Assessment" />
<PackageReference Include="Microsoft.SqlServer.Migration.Logins" />
<PackageReference Include="Microsoft.SqlServer.Management.SmoMetadataProvider" />
<PackageReference Include="Microsoft.SqlServer.SqlManagementObjects" />
<PackageReference Include="Microsoft.SqlServer.Management.SqlParser" />

View File

@@ -0,0 +1,135 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlServer.DataCollection.Common.Contracts.OperationsInfrastructure;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.Migration.Contracts
{
/// <summary>
/// Represents the steps in login migration.
/// </summary>
public enum LoginMigrationStep
{
/// <summary>
/// Run pre-migration validations
/// </summary>
StartValidations,
/// <summary>
/// Step to hash passwords and migrate logins
/// </summary>
MigrateLogins,
/// <summary>
/// Step to establish users and logins from source to target
/// </summary>
EstablishUserMapping,
/// <summary>
/// Step to migrate server roles
/// </summary>
MigrateServerRoles,
/// <summary>
/// Step to establish roles
/// </summary>
EstablishServerRoleMapping,
/// <summary>
/// Step to map all the grant/deny permissions for logins
/// </summary>
SetLoginPermissions,
/// <summary>
/// Step to map all server roles grant/deny permissions
/// </summary>
SetServerRolePermissions
}
public class StartLoginMigrationParams
{
/// <summary>
/// Connection string to connect to source
/// </summary>
public string SourceConnectionString { get; set; }
/// <summary>
/// Connection string to connect to target
/// </summary>
public string TargetConnectionString { get; set; }
/// <summary>
/// List of logins to migrate
/// </summary>
public List<string> LoginList { get; set; }
/// <summary>
/// Azure active directory domain name (required for Windows Auth)
/// </summary>
public string AADDomainName{ get; set; }
}
public class LoginMigrationResult
{
/// <summary>
/// Start time of the assessment
/// </summary>
public IDictionary<string, IEnumerable<ReportableException>> ExceptionMap { get; set; }
/// <summary>
/// The login migration step that just completed
/// </summary>
public LoginMigrationStep CompletedStep { get; set; }
/// <summary>
/// How long this step took
/// </summary>
public string ElapsedTime{ get; set; }
}
public class StartLoginMigrationRequest
{
public static readonly
RequestType<StartLoginMigrationParams, LoginMigrationResult> Type =
RequestType<StartLoginMigrationParams, LoginMigrationResult>.Create("migration/startloginmigration");
}
public class ValidateLoginMigrationRequest
{
public static readonly
RequestType<StartLoginMigrationParams, LoginMigrationResult> Type =
RequestType<StartLoginMigrationParams, LoginMigrationResult>.Create("migration/validateloginmigration");
}
public class MigrateLoginsRequest
{
public static readonly
RequestType<StartLoginMigrationParams, LoginMigrationResult> Type =
RequestType<StartLoginMigrationParams, LoginMigrationResult>.Create("migration/migratelogins");
}
public class EstablishUserMappingRequest
{
public static readonly
RequestType<StartLoginMigrationParams, LoginMigrationResult> Type =
RequestType<StartLoginMigrationParams, LoginMigrationResult>.Create("migration/establishusermapping");
}
public class MigrateServerRolesAndSetPermissionsRequest
{
public static readonly
RequestType<StartLoginMigrationParams, LoginMigrationResult> Type =
RequestType<StartLoginMigrationParams, LoginMigrationResult>.Create("migration/migrateserverrolesandsetpermissions");
}
public class LoginMigrationNotification
{
public static readonly
EventType<LoginMigrationResult> Type =
EventType<LoginMigrationResult>.Create("migration/loginmigrationnotification");
}
}

View File

@@ -24,6 +24,7 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.Migration.Contracts;
using Microsoft.SqlTools.Utility;
using Microsoft.SqlServer.Migration.Logins;
using Microsoft.SqlServer.Migration.SkuRecommendation.Aggregation;
using Microsoft.SqlServer.Migration.SkuRecommendation.Models.Sql;
using Microsoft.SqlServer.Migration.SkuRecommendation;
@@ -39,8 +40,12 @@ using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy.AzureSqlMa
using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy.AzureSqlDatabase;
using Microsoft.SqlServer.Migration.SkuRecommendation.Models;
using Microsoft.SqlServer.Migration.SkuRecommendation.Utils;
using Microsoft.SqlServer.DataCollection.Common.Contracts.OperationsInfrastructure;
using System.Threading;
using Microsoft.SqlServer.Migration.Logins.Contracts;
using Microsoft.SqlServer.Migration.Assessment.Common.Models;
using Microsoft.SqlServer.Migration.Assessment.Common.Utils;
using Microsoft.SqlTools.ServiceLayer.Migration.Utils;
namespace Microsoft.SqlTools.ServiceLayer.Migration
{
@@ -121,6 +126,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
this.ServiceHost.SetRequestHandler(StopPerfDataCollectionRequest.Type, HandleStopPerfDataCollectionRequest);
this.ServiceHost.SetRequestHandler(RefreshPerfDataCollectionRequest.Type, HandleRefreshPerfDataCollectionRequest);
this.ServiceHost.SetRequestHandler(GetSkuRecommendationsRequest.Type, HandleGetSkuRecommendationsRequest);
this.ServiceHost.SetRequestHandler(StartLoginMigrationRequest.Type, HandleStartLoginMigration);
this.ServiceHost.SetRequestHandler(ValidateLoginMigrationRequest.Type, HandleValidateLoginMigration);
this.ServiceHost.SetRequestHandler(MigrateLoginsRequest.Type, HandleMigrateLogins);
this.ServiceHost.SetRequestHandler(EstablishUserMappingRequest.Type, HandleEstablishUserMapping);
this.ServiceHost.SetRequestHandler(MigrateServerRolesAndSetPermissionsRequest.Type, HandleMigrateServerRolesAndSetPermissions);
}
/// <summary>
@@ -325,6 +335,231 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
}
}
internal async Task HandleStartLoginMigration(
StartLoginMigrationParams parameters,
RequestContext<LoginMigrationResult> requestContext)
{
try
{
ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
null, parameters.LoginList, parameters.AADDomainName);
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) );
exceptionMap.AddExceptions( await loginMigration.MigrateLogins(CancellationToken.None) );
exceptionMap.AddExceptions( loginMigration.MigrateServerRoles(CancellationToken.None) );
exceptionMap.AddExceptions( loginMigration.EstablishUserMapping(CancellationToken.None) );
exceptionMap.AddExceptions( await loginMigration.EstablishServerRoleMapping(CancellationToken.None) );
exceptionMap.AddExceptions( loginMigration.SetLoginPermissions(CancellationToken.None) );
exceptionMap.AddExceptions( loginMigration.SetServerRolePermissions(CancellationToken.None) );
LoginMigrationResult results = new LoginMigrationResult()
{
ExceptionMap = exceptionMap
};
await requestContext.SendResult(results);
}
catch (Exception e)
{
await requestContext.SendError(e.ToString());
}
}
internal async Task HandleValidateLoginMigration(
StartLoginMigrationParams parameters,
RequestContext<LoginMigrationResult> requestContext)
{
try
{
ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
null, parameters.LoginList, parameters.AADDomainName);
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) );
stopWatch.Stop();
TimeSpan elapsedTime = stopWatch.Elapsed;
LoginMigrationResult results = new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.StartValidations,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
};
await requestContext.SendResult(results);
}
catch (Exception e)
{
await requestContext.SendError(e.ToString());
}
}
internal async Task HandleMigrateLogins(
StartLoginMigrationParams parameters,
RequestContext<LoginMigrationResult> requestContext)
{
try
{
ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
null, parameters.LoginList, parameters.AADDomainName);
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) );
exceptionMap.AddExceptions( await loginMigration.MigrateLogins(CancellationToken.None) );
stopWatch.Stop();
TimeSpan elapsedTime = stopWatch.Elapsed;
LoginMigrationResult results = new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.MigrateLogins,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
};
await requestContext.SendResult(results);
}
catch (Exception e)
{
await requestContext.SendError(e.ToString());
}
}
internal async Task HandleEstablishUserMapping(
StartLoginMigrationParams parameters,
RequestContext<LoginMigrationResult> requestContext)
{
try
{
ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
null, parameters.LoginList, parameters.AADDomainName);
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) );
exceptionMap.AddExceptions( loginMigration.EstablishUserMapping(CancellationToken.None) );
stopWatch.Stop();
TimeSpan elapsedTime = stopWatch.Elapsed;
LoginMigrationResult results = new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.EstablishUserMapping,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
};
await requestContext.SendResult(results);
}
catch (Exception e)
{
await requestContext.SendError(e.ToString());
}
}
internal async Task HandleMigrateServerRolesAndSetPermissions(
StartLoginMigrationParams parameters,
RequestContext<LoginMigrationResult> requestContext)
{
try
{
ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
null, parameters.LoginList, parameters.AADDomainName);
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
exceptionMap.AddExceptions(await loginMigration.StartValidations(CancellationToken.None));
stopWatch.Stop();
TimeSpan elapsedTime = stopWatch.Elapsed;
await this.ServiceHost.SendEvent(
LoginMigrationNotification.Type,
new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.StartValidations,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
});
stopWatch.Restart();
exceptionMap.AddExceptions(loginMigration.MigrateServerRoles(CancellationToken.None));
stopWatch.Stop();
elapsedTime = stopWatch.Elapsed;
await this.ServiceHost.SendEvent(
LoginMigrationNotification.Type,
new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.MigrateServerRoles,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
});
stopWatch.Restart();
exceptionMap.AddExceptions(await loginMigration.EstablishServerRoleMapping(CancellationToken.None));
stopWatch.Stop();
elapsedTime = stopWatch.Elapsed;
await this.ServiceHost.SendEvent(
LoginMigrationNotification.Type,
new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.EstablishServerRoleMapping,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
});
stopWatch.Restart();
exceptionMap.AddExceptions(loginMigration.SetLoginPermissions(CancellationToken.None));
stopWatch.Stop();
elapsedTime = stopWatch.Elapsed;
await this.ServiceHost.SendEvent(
LoginMigrationNotification.Type,
new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.SetLoginPermissions,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
});
stopWatch.Restart();
exceptionMap.AddExceptions(loginMigration.SetServerRolePermissions(CancellationToken.None));
stopWatch.Stop();
elapsedTime = stopWatch.Elapsed;
await this.ServiceHost.SendEvent(
LoginMigrationNotification.Type,
new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.SetServerRolePermissions,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
});
LoginMigrationResult results = new LoginMigrationResult()
{
ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.SetServerRolePermissions,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
};
await requestContext.SendResult(results);
}
catch (Exception e)
{
await requestContext.SendError(e.ToString());
}
}
internal RecommendationResultSet GenerateBaselineRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters) {
RecommendationResultSet resultSet = new RecommendationResultSet();

View File

@@ -0,0 +1,31 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using System.Linq;
using Microsoft.SqlServer.DataCollection.Common.Contracts.OperationsInfrastructure;
namespace Microsoft.SqlTools.ServiceLayer.Migration.Utils
{
internal static class ExtensionMethods
{
public static void AddExceptions(this IDictionary<string, IEnumerable<ReportableException>> exceptionMap1, IDictionary<string, IEnumerable<ReportableException>> exceptionMap2)
{
foreach (var keyValuePair2 in exceptionMap2)
{
// If the dictionary already contains the key then merge them
if (exceptionMap1.ContainsKey(keyValuePair2.Key))
{
foreach (var value in keyValuePair2.Value)
{
exceptionMap1[keyValuePair2.Key].Append(value);
}
continue;
}
exceptionMap1.Add(keyValuePair2);
}
}
}
}

View File

@@ -0,0 +1,17 @@
//
// 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.SqlTools.ServiceLayer.Migration.Utils
{
internal static class MigrationServiceHelper
{
public static string FormatTimeSpan(TimeSpan ts)
{
return String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
}
}
}