Tde migration integration (#1806)

Adding certificate migration for encrypted databases.

Uses notifications to send events back to the client every time each database certificate is migrated.
This commit is contained in:
junierch
2023-01-11 21:20:00 -05:00
committed by GitHub
parent ac460adfc3
commit f99bda29b5
7 changed files with 390 additions and 119 deletions

View File

@@ -26,6 +26,7 @@
<PackageReference Update="Microsoft.SqlServer.Migration.Assessment" Version="1.0.20221028.23" /> <PackageReference Update="Microsoft.SqlServer.Migration.Assessment" Version="1.0.20221028.23" />
<PackageReference Update="Microsoft.SqlServer.Migration.Logins" Version="1.0.20221215.33" /> <PackageReference Update="Microsoft.SqlServer.Migration.Logins" Version="1.0.20221215.33" />
<PackageReference Update="Microsoft.SqlServer.Management.SqlParser" Version="160.22524.0" /> <PackageReference Update="Microsoft.SqlServer.Management.SqlParser" Version="160.22524.0" />
<PackageReference Update="Microsoft.SqlServer.Migration.Tde" Version="1.0.20230110.39" />
<PackageReference Update="Microsoft.Azure.OperationalInsights" Version="1.0.0" /> <PackageReference Update="Microsoft.Azure.OperationalInsights" Version="1.0.0" />
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" /> <PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" />
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" /> <PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />

View File

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

View File

@@ -0,0 +1,44 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Migration.Contracts
{
/// <summary>
/// Parameters for the certificate migration progress event
/// </summary>
public class CertificateMigrationProgressParams
{
/// <summary>
/// Database name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Message related to the success status. true should describe a positive outcome. false should have an error.
/// </summary>
public string Message { get; set; }
/// <summary>
/// Result of migration
/// </summary>
public bool Success { get; set; }
}
/// <summary>
/// Create a certificate migration request. This should be register at the client.
/// </summary>
public class CertificateMigrationProgressEvent
{
/// <summary>
/// Name and parameters for the event definition.
/// </summary>
public static readonly
EventType<CertificateMigrationProgressParams> Type =
EventType<CertificateMigrationProgressParams>.Create("migration/tdemigrationprogress");
}
}

View File

@@ -0,0 +1,111 @@
//
// 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 Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Migration.Contracts
{
/// <summary>
/// Parameters for the certificate migration operation
/// </summary>
public class CertificateMigrationParams
{
/// <summary>
/// List of databses to migrate the certificates
/// </summary>
public List<string> EncryptedDatabases { get; set; } = new List<string>();
/// <summary>
/// Source connection string to the server
/// </summary>
public string SourceSqlConnectionString { get; set; }
/// <summary>
/// Target subscription id
/// </summary>
public string TargetSubscriptionId { get; set; }
/// <summary>
/// Target resource group name
/// </summary>
public string TargetResourceGroupName { get; set; }
/// <summary>
/// Target manages instance name
/// </summary>
public string TargetManagedInstanceName { get; set; }
/// <summary>
/// Place where certificates will be exported
/// </summary>
public string NetworkSharePath { get; set; }
/// <summary>
/// Domain for the user credentials able to read from the shared path
/// </summary>
public string NetworkShareDomain { get; set; }
/// <summary>
/// Username for the credentials able to read from the shared path
/// </summary>
public string NetworkShareUserName { get; set; }
/// <summary>
/// Password for the credentials able to read from the shared path
/// </summary>
public string NetworkSharePassword { get; set; }
/// <summary>
/// Access token for the ARM client
/// </summary>
public string AccessToken { get; set; }
}
/// <summary>
/// Result for the certificate migration operation
/// </summary>
public class CertificateMigrationResult
{
/// <summary>
/// List of the status of each certificate migration result attempted.
/// </summary>
public List<CertificateMigrationEntryResult> MigrationStatuses { get; set; } = new List<CertificateMigrationEntryResult>();
}
/// <summary>
/// Result for an individual database certificate migration
/// </summary>
public class CertificateMigrationEntryResult
{
/// <summary>
/// The name of the database this result represent
/// </summary>
public string DbName { get; set; }
/// <summary>
/// The result of the migration.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Description of the success status or the error message encountered when the migration was not successful
/// </summary>
public string Message { get; set; }
}
/// <summary>
/// Certificate migration request definition
/// </summary>
public class CertificateMigrationRequest
{
/// <summary>
/// Name, parameter and return type for the certicate migration operation
/// </summary>
public static readonly
RequestType<CertificateMigrationParams, CertificateMigrationResult> Type =
RequestType<CertificateMigrationParams, CertificateMigrationResult>.Create("migration/tdemigration");
}
}

View File

@@ -46,6 +46,9 @@ using Microsoft.SqlServer.Migration.Logins.Contracts;
using Microsoft.SqlServer.Migration.Assessment.Common.Models; using Microsoft.SqlServer.Migration.Assessment.Common.Models;
using Microsoft.SqlServer.Migration.Assessment.Common.Utils; using Microsoft.SqlServer.Migration.Assessment.Common.Utils;
using Microsoft.SqlTools.ServiceLayer.Migration.Utils; using Microsoft.SqlTools.ServiceLayer.Migration.Utils;
using Microsoft.SqlServer.Migration.Tde;
using Microsoft.SqlServer.Migration.Tde.Common;
using Microsoft.SqlTools.ServiceLayer.Migration.Models;
namespace Microsoft.SqlTools.ServiceLayer.Migration namespace Microsoft.SqlTools.ServiceLayer.Migration
{ {
@@ -131,6 +134,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
this.ServiceHost.SetRequestHandler(MigrateLoginsRequest.Type, HandleMigrateLogins, true); this.ServiceHost.SetRequestHandler(MigrateLoginsRequest.Type, HandleMigrateLogins, true);
this.ServiceHost.SetRequestHandler(EstablishUserMappingRequest.Type, HandleEstablishUserMapping, true); this.ServiceHost.SetRequestHandler(EstablishUserMappingRequest.Type, HandleEstablishUserMapping, true);
this.ServiceHost.SetRequestHandler(MigrateServerRolesAndSetPermissionsRequest.Type, HandleMigrateServerRolesAndSetPermissions, true); this.ServiceHost.SetRequestHandler(MigrateServerRolesAndSetPermissionsRequest.Type, HandleMigrateServerRolesAndSetPermissions, true);
this.ServiceHost.SetRequestHandler(CertificateMigrationRequest.Type, HandleTdeCertificateMigrationRequest);
} }
/// <summary> /// <summary>
@@ -316,7 +320,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
ElasticSqlDbRecommendationDurationInMs = elasticResults.sqlDbDurationInMs, ElasticSqlDbRecommendationDurationInMs = elasticResults.sqlDbDurationInMs,
ElasticSqlMiRecommendationResults = elasticResults.sqlMiResults, ElasticSqlMiRecommendationResults = elasticResults.sqlMiResults,
ElasticSqlMiRecommendationDurationInMs = elasticResults.sqlMiDurationInMs, ElasticSqlMiRecommendationDurationInMs = elasticResults.sqlMiDurationInMs,
ElasticSqlVmRecommendationResults = elasticResults.sqlVmResults, ElasticSqlVmRecommendationResults = elasticResults.sqlVmResults,
ElasticSqlVmRecommendationDurationInMs = elasticResults.sqlVmDurationInMs, ElasticSqlVmRecommendationDurationInMs = elasticResults.sqlVmDurationInMs,
InstanceRequirements = req, InstanceRequirements = req,
SkuRecommendationReportPaths = new List<string> { baselineResults.sqlDbReportPath, baselineResults.sqlMiReportPath, baselineResults.sqlVmReportPath }, SkuRecommendationReportPaths = new List<string> { baselineResults.sqlDbReportPath, baselineResults.sqlMiReportPath, baselineResults.sqlVmReportPath },
@@ -336,23 +340,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
} }
internal async Task HandleStartLoginMigration( internal async Task HandleStartLoginMigration(
StartLoginMigrationParams parameters, StartLoginMigrationParams parameters,
RequestContext<LoginMigrationResult> requestContext) RequestContext<LoginMigrationResult> requestContext)
{ {
try try
{ {
ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString, ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
null, parameters.LoginList, parameters.AADDomainName); null, parameters.LoginList, parameters.AADDomainName);
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>(); IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) ); exceptionMap.AddExceptions(await loginMigration.StartValidations(CancellationToken.None));
exceptionMap.AddExceptions( await loginMigration.MigrateLogins(CancellationToken.None) ); exceptionMap.AddExceptions(await loginMigration.MigrateLogins(CancellationToken.None));
exceptionMap.AddExceptions( loginMigration.MigrateServerRoles(CancellationToken.None) ); exceptionMap.AddExceptions(loginMigration.MigrateServerRoles(CancellationToken.None));
exceptionMap.AddExceptions( loginMigration.EstablishUserMapping(CancellationToken.None) ); exceptionMap.AddExceptions(loginMigration.EstablishUserMapping(CancellationToken.None));
exceptionMap.AddExceptions( await loginMigration.EstablishServerRoleMapping(CancellationToken.None) ); exceptionMap.AddExceptions(await loginMigration.EstablishServerRoleMapping(CancellationToken.None));
exceptionMap.AddExceptions( loginMigration.SetLoginPermissions(CancellationToken.None) ); exceptionMap.AddExceptions(loginMigration.SetLoginPermissions(CancellationToken.None));
exceptionMap.AddExceptions( loginMigration.SetServerRolePermissions(CancellationToken.None) ); exceptionMap.AddExceptions(loginMigration.SetServerRolePermissions(CancellationToken.None));
LoginMigrationResult results = new LoginMigrationResult() LoginMigrationResult results = new LoginMigrationResult()
{ {
@@ -370,7 +374,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
internal async Task HandleValidateLoginMigration( internal async Task HandleValidateLoginMigration(
StartLoginMigrationParams parameters, StartLoginMigrationParams parameters,
RequestContext<LoginMigrationResult> requestContext) RequestContext<LoginMigrationResult> requestContext)
{ {
try try
{ {
ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString, ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
@@ -379,7 +383,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>(); IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
Stopwatch stopWatch = new Stopwatch(); Stopwatch stopWatch = new Stopwatch();
stopWatch.Start(); stopWatch.Start();
exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) ); exceptionMap.AddExceptions(await loginMigration.StartValidations(CancellationToken.None));
stopWatch.Stop(); stopWatch.Stop();
TimeSpan elapsedTime = stopWatch.Elapsed; TimeSpan elapsedTime = stopWatch.Elapsed;
@@ -411,8 +415,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>(); IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
Stopwatch stopWatch = new Stopwatch(); Stopwatch stopWatch = new Stopwatch();
stopWatch.Start(); stopWatch.Start();
exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) ); exceptionMap.AddExceptions(await loginMigration.StartValidations(CancellationToken.None));
exceptionMap.AddExceptions( await loginMigration.MigrateLogins(CancellationToken.None) ); exceptionMap.AddExceptions(await loginMigration.MigrateLogins(CancellationToken.None));
stopWatch.Stop(); stopWatch.Stop();
TimeSpan elapsedTime = stopWatch.Elapsed; TimeSpan elapsedTime = stopWatch.Elapsed;
@@ -441,11 +445,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
null, parameters.LoginList, parameters.AADDomainName); null, parameters.LoginList, parameters.AADDomainName);
IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>(); IDictionary<string, IEnumerable<ReportableException>> exceptionMap = new Dictionary<string, IEnumerable<ReportableException>>();
Stopwatch stopWatch = new Stopwatch(); Stopwatch stopWatch = new Stopwatch();
stopWatch.Start(); stopWatch.Start();
exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) ); exceptionMap.AddExceptions(await loginMigration.StartValidations(CancellationToken.None));
exceptionMap.AddExceptions( loginMigration.EstablishUserMapping(CancellationToken.None) ); exceptionMap.AddExceptions(loginMigration.EstablishUserMapping(CancellationToken.None));
stopWatch.Stop(); stopWatch.Stop();
TimeSpan elapsedTime = stopWatch.Elapsed; TimeSpan elapsedTime = stopWatch.Elapsed;
@@ -501,7 +505,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
ExceptionMap = exceptionMap, ExceptionMap = exceptionMap,
CompletedStep = LoginMigrationStep.MigrateServerRoles, CompletedStep = LoginMigrationStep.MigrateServerRoles,
ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime) ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
}); });
stopWatch.Restart(); stopWatch.Restart();
exceptionMap.AddExceptions(await loginMigration.EstablishServerRoleMapping(CancellationToken.None)); exceptionMap.AddExceptions(await loginMigration.EstablishServerRoleMapping(CancellationToken.None));
@@ -560,120 +564,123 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
} }
} }
internal RecommendationResultSet GenerateBaselineRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters) { internal RecommendationResultSet GenerateBaselineRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters)
RecommendationResultSet resultSet = new RecommendationResultSet(); {
RecommendationResultSet resultSet = new RecommendationResultSet();
SkuRecommendationServiceProvider provider = new SkuRecommendationServiceProvider(new AzureSqlSkuBillingServiceProvider()); SkuRecommendationServiceProvider provider = new SkuRecommendationServiceProvider(new AzureSqlSkuBillingServiceProvider());
AzurePreferences prefs = new AzurePreferences() { AzurePreferences prefs = new AzurePreferences()
EligibleSkuCategories = null, // eligible SKU list will be adjusted with each recommendation type {
ScalingFactor = parameters.ScalingFactor / 100.0, EligibleSkuCategories = null, // eligible SKU list will be adjusted with each recommendation type
TargetEnvironment = TargetEnvironmentType.Production ScalingFactor = parameters.ScalingFactor / 100.0,
}; TargetEnvironment = TargetEnvironmentType.Production
};
// generate SQL DB recommendations, if applicable
if (parameters.TargetPlatforms.Contains("AzureSqlDatabase")) // generate SQL DB recommendations, if applicable
if (parameters.TargetPlatforms.Contains("AzureSqlDatabase"))
{
Stopwatch sqlDbStopwatch = new Stopwatch();
sqlDbStopwatch.Start();
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlDatabase", parameters.IncludePreviewSkus);
resultSet.sqlDbResults = provider.GetSkuRecommendation(prefs, req);
sqlDbStopwatch.Stop();
resultSet.sqlDbDurationInMs = sqlDbStopwatch.ElapsedMilliseconds;
SkuRecommendationReport sqlDbReport = new SkuRecommendationReport(
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlDbResults } },
AzureSqlTargetPlatform.AzureSqlDatabase.ToString());
var sqlDbRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlDatabase-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
var sqlDbRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlDbRecommendationReportFileName);
ExportRecommendationResultsAction.ExportRecommendationResults(sqlDbReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlDbRecommendationReportFileName);
resultSet.sqlDbReportPath = sqlDbRecommendationReportFullPath + ".html";
}
// generate SQL MI recommendations, if applicable
if (parameters.TargetPlatforms.Contains("AzureSqlManagedInstance"))
{
Stopwatch sqlMiStopwatch = new Stopwatch();
sqlMiStopwatch.Start();
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlManagedInstance", parameters.IncludePreviewSkus);
resultSet.sqlMiResults = provider.GetSkuRecommendation(prefs, req);
// if no result was generated, create a result with a null SKU
if (!resultSet.sqlMiResults.Any())
{ {
Stopwatch sqlDbStopwatch = new Stopwatch(); resultSet.sqlMiResults.Add(new SkuRecommendationResult()
sqlDbStopwatch.Start();
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlDatabase", parameters.IncludePreviewSkus);
resultSet.sqlDbResults = provider.GetSkuRecommendation(prefs, req);
sqlDbStopwatch.Stop();
resultSet.sqlDbDurationInMs = sqlDbStopwatch.ElapsedMilliseconds;
SkuRecommendationReport sqlDbReport = new SkuRecommendationReport(
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlDbResults } },
AzureSqlTargetPlatform.AzureSqlDatabase.ToString());
var sqlDbRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlDatabase-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
var sqlDbRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlDbRecommendationReportFileName);
ExportRecommendationResultsAction.ExportRecommendationResults(sqlDbReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlDbRecommendationReportFileName);
resultSet.sqlDbReportPath = sqlDbRecommendationReportFullPath + ".html";
}
// generate SQL MI recommendations, if applicable
if (parameters.TargetPlatforms.Contains("AzureSqlManagedInstance"))
{
Stopwatch sqlMiStopwatch = new Stopwatch();
sqlMiStopwatch.Start();
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlManagedInstance", parameters.IncludePreviewSkus);
resultSet.sqlMiResults = provider.GetSkuRecommendation(prefs, req);
// if no result was generated, create a result with a null SKU
if (!resultSet.sqlMiResults.Any())
{ {
resultSet.sqlMiResults.Add(new SkuRecommendationResult() SqlInstanceName = parameters.TargetSqlInstance,
{ DatabaseName = null,
SqlInstanceName = parameters.TargetSqlInstance, TargetSku = null,
DatabaseName = null, MonthlyCost = null,
TargetSku = null, Ranking = -1,
MonthlyCost = null, PositiveJustifications = null,
Ranking = -1, NegativeJustifications = null,
PositiveJustifications = null, });
NegativeJustifications = null,
});
}
sqlMiStopwatch.Stop();
resultSet.sqlMiDurationInMs = sqlMiStopwatch.ElapsedMilliseconds;
SkuRecommendationReport sqlMiReport = new SkuRecommendationReport(
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlMiResults } },
AzureSqlTargetPlatform.AzureSqlManagedInstance.ToString());
var sqlMiRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlManagedInstance-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
var sqlMiRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlMiRecommendationReportFileName);
ExportRecommendationResultsAction.ExportRecommendationResults(sqlMiReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlMiRecommendationReportFileName);
resultSet.sqlMiReportPath = sqlMiRecommendationReportFullPath + ".html";
} }
// generate SQL VM recommendations, if applicable sqlMiStopwatch.Stop();
if (parameters.TargetPlatforms.Contains("AzureSqlVirtualMachine")) resultSet.sqlMiDurationInMs = sqlMiStopwatch.ElapsedMilliseconds;
SkuRecommendationReport sqlMiReport = new SkuRecommendationReport(
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlMiResults } },
AzureSqlTargetPlatform.AzureSqlManagedInstance.ToString());
var sqlMiRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlManagedInstance-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
var sqlMiRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlMiRecommendationReportFileName);
ExportRecommendationResultsAction.ExportRecommendationResults(sqlMiReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlMiRecommendationReportFileName);
resultSet.sqlMiReportPath = sqlMiRecommendationReportFullPath + ".html";
}
// generate SQL VM recommendations, if applicable
if (parameters.TargetPlatforms.Contains("AzureSqlVirtualMachine"))
{
Stopwatch sqlVmStopwatch = new Stopwatch();
sqlVmStopwatch.Start();
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlVirtualMachine", parameters.IncludePreviewSkus);
resultSet.sqlVmResults = provider.GetSkuRecommendation(prefs, req);
// if no result was generated, create a result with a null SKU
if (!resultSet.sqlVmResults.Any())
{ {
Stopwatch sqlVmStopwatch = new Stopwatch(); resultSet.sqlVmResults.Add(new SkuRecommendationResult()
sqlVmStopwatch.Start();
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlVirtualMachine", parameters.IncludePreviewSkus);
resultSet.sqlVmResults = provider.GetSkuRecommendation(prefs, req);
// if no result was generated, create a result with a null SKU
if (!resultSet.sqlVmResults.Any())
{ {
resultSet.sqlVmResults.Add(new SkuRecommendationResult() SqlInstanceName = parameters.TargetSqlInstance,
{ DatabaseName = null,
SqlInstanceName = parameters.TargetSqlInstance, TargetSku = null,
DatabaseName = null, MonthlyCost = null,
TargetSku = null, Ranking = -1,
MonthlyCost = null, PositiveJustifications = null,
Ranking = -1, NegativeJustifications = null,
PositiveJustifications = null, });
NegativeJustifications = null,
});
}
sqlVmStopwatch.Stop();
resultSet.sqlVmDurationInMs = sqlVmStopwatch.ElapsedMilliseconds;
SkuRecommendationReport sqlVmReport = new SkuRecommendationReport(
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlVmResults } },
AzureSqlTargetPlatform.AzureSqlVirtualMachine.ToString());
var sqlVmRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlVirtualMachine-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
var sqlVmRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlVmRecommendationReportFileName);
ExportRecommendationResultsAction.ExportRecommendationResults(sqlVmReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlVmRecommendationReportFileName);
resultSet.sqlVmReportPath = sqlVmRecommendationReportFullPath + ".html";
} }
return resultSet; sqlVmStopwatch.Stop();
resultSet.sqlVmDurationInMs = sqlVmStopwatch.ElapsedMilliseconds;
SkuRecommendationReport sqlVmReport = new SkuRecommendationReport(
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlVmResults } },
AzureSqlTargetPlatform.AzureSqlVirtualMachine.ToString());
var sqlVmRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlVirtualMachine-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
var sqlVmRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlVmRecommendationReportFileName);
ExportRecommendationResultsAction.ExportRecommendationResults(sqlVmReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlVmRecommendationReportFileName);
resultSet.sqlVmReportPath = sqlVmRecommendationReportFullPath + ".html";
}
return resultSet;
} }
internal RecommendationResultSet GenerateElasticRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters) { internal RecommendationResultSet GenerateElasticRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters)
{
RecommendationResultSet resultSet = new RecommendationResultSet(); RecommendationResultSet resultSet = new RecommendationResultSet();
CsvAggregatorForElasticStrategy elasticaggregator = new CsvAggregatorForElasticStrategy( CsvAggregatorForElasticStrategy elasticaggregator = new CsvAggregatorForElasticStrategy(
instanceId: parameters.TargetSqlInstance, instanceId: parameters.TargetSqlInstance,
directory: parameters.DataFolder, directory: parameters.DataFolder,
queryInterval: parameters.PerfQueryIntervalInSec, queryInterval: parameters.PerfQueryIntervalInSec,
logger: null, logger: null,
dbsToInclude: new HashSet<string>(parameters.DatabaseAllowList)); dbsToInclude: new HashSet<string>(parameters.DatabaseAllowList));
// generate SQL DB recommendations, if applicable // generate SQL DB recommendations, if applicable
@@ -698,7 +705,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
resultSet.sqlDbDurationInMs = sqlDbStopwatch.ElapsedMilliseconds; resultSet.sqlDbDurationInMs = sqlDbStopwatch.ElapsedMilliseconds;
SkuRecommendationReport sqlDbReport = new SkuRecommendationReport( SkuRecommendationReport sqlDbReport = new SkuRecommendationReport(
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlDbResults } }, new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlDbResults } },
AzureSqlTargetPlatform.AzureSqlDatabase.ToString()); AzureSqlTargetPlatform.AzureSqlDatabase.ToString());
var sqlDbRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlDatabase-Elastic-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture)); var sqlDbRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlDatabase-Elastic-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
var sqlDbRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlDbRecommendationReportFileName); var sqlDbRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlDbRecommendationReportFileName);
@@ -989,7 +996,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
AzureSqlPaaSHardwareType.PremiumSeries)); AzureSqlPaaSHardwareType.PremiumSeries));
if (includePreviewSkus) if (includePreviewSkus)
{ {
// Premium Memory Optimized BC/GP // Premium Memory Optimized BC/GP
eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory(
AzureSqlTargetPlatform.AzureSqlManagedInstance, AzureSqlTargetPlatform.AzureSqlManagedInstance,
@@ -1043,6 +1050,78 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
return eligibleSkuCategories; return eligibleSkuCategories;
} }
/// <summary>
/// Request handler for the certifica migration operation
/// </summary>
/// <param name="parameters">Parameters for the operation, as register during the type definition</param>
/// <param name="requestContext">Context provided by the framework</param>
/// <returns></returns>
internal async Task HandleTdeCertificateMigrationRequest(
CertificateMigrationParams parameters,
RequestContext<CertificateMigrationResult> requestContext)
{
var result = new CertificateMigrationResult();
var credentials = new StaticTokenCredential(parameters.AccessToken); //New token provided, will change to shared ADS cache later.
// Reuse the tde migration client
var tdeMigrationClient = new TdeMigration(
parameters.SourceSqlConnectionString,
parameters.TargetSubscriptionId,
parameters.TargetResourceGroupName,
parameters.TargetManagedInstanceName,
parameters.NetworkSharePath,
parameters.NetworkShareDomain,
parameters.NetworkShareUserName,
parameters.NetworkSharePassword,
credentials
);
foreach (var dbName in parameters.EncryptedDatabases)
{
var migrationResult = await MigrateCertificate(tdeMigrationClient, dbName);
var eventData = new CertificateMigrationProgressParams
{
Name = dbName,
Success = migrationResult.Success,
Message = migrationResult.Message
};
await requestContext.SendEvent(CertificateMigrationProgressEvent.Type, eventData);
result.MigrationStatuses.Add(migrationResult);
}
await requestContext.SendResult(result);
}
/// <summary>
/// Individual certificate migration operation
/// </summary>
/// <param name="tdeMigrationClient">Instance of the migration client</param>
/// <param name="dbName">Name of the database to migrate</param>
/// <returns></returns>
private async Task<CertificateMigrationEntryResult> MigrateCertificate(TdeMigration tdeMigrationClient, string dbName)
{
try
{
var result = await tdeMigrationClient.MigrateTdeCertificate(dbName, CancellationToken.None);
if (result is TdeExceptionResult tdeExceptionResult)
{
return new CertificateMigrationEntryResult { DbName = dbName, Success = result.IsSuccess, Message = tdeExceptionResult.Exception.Message };
}
else
{
return new CertificateMigrationEntryResult { DbName = dbName, Success = result.IsSuccess, Message = result.UserFriendlyMessage };
}
}
catch (Exception ex)
{
return new CertificateMigrationEntryResult { DbName = dbName, Success = false, Message = ex.Message };
}
}
/// <summary> /// <summary>
/// Disposes the Migration Service /// Disposes the Migration Service
/// </summary> /// </summary>

View File

@@ -0,0 +1,35 @@
//
// 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;
using System.Threading.Tasks;
using Azure.Core;
namespace Microsoft.SqlTools.ServiceLayer.Migration.Models
{
/// <summary>
/// Temp token creadentials to interact with ArmClient class.
/// The token passed to this class should be a newly request token, because this class doesn't renew the token.
/// Once MSAL is rolled out on ADS, we will implement a way to use the same ADS token cache configured by ADS.
/// </summary>
internal class StaticTokenCredential : TokenCredential
{
private readonly AccessToken _token;
/// <summary>
/// Build credentials using a token that will not change.
/// </summary>
/// <param name="accessToken">Newly created token that should last for the duration of the whole operation.</param>
public StaticTokenCredential(string accessToken) => _token = new AccessToken(
accessToken: accessToken,
expiresOn: DateTimeOffset.Now.AddHours(1)); //Default to an hour, the current duration of a newly create token.
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
=> _token;
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
=> new ValueTask<AccessToken>(_token);
}
}