// // 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.IO; using System.Linq; using System.Threading.Tasks; using System.Globalization; using System.Reflection; using System.Data; using System.Diagnostics; using Newtonsoft.Json; using Microsoft.SqlServer.DataCollection.Common; using Microsoft.SqlServer.Management.Assessment.Checks; using Microsoft.SqlServer.Management.Assessment; using Microsoft.SqlServer.Migration.Assessment.Common.Contracts.Models; using Microsoft.SqlServer.Migration.Assessment.Common.Engine; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.Migration.Contracts; using Microsoft.SqlTools.Utility; using Microsoft.SqlServer.Migration.SkuRecommendation.Aggregation; using Microsoft.SqlServer.Migration.SkuRecommendation.Models.Sql; using Microsoft.SqlServer.Migration.SkuRecommendation; using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Constants; using Microsoft.SqlServer.Migration.SkuRecommendation.Billing; using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts; using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models.Sku; using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models; using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Exceptions; using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models.Environment; using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy; using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy.AzureSqlManagedInstance; using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy.AzureSqlDatabase; using Microsoft.SqlServer.Migration.SkuRecommendation.Models; using Microsoft.SqlServer.Migration.SkuRecommendation.Utils; namespace Microsoft.SqlTools.ServiceLayer.Migration { /// /// Main class for Migration Service functionality /// public sealed class MigrationService : IDisposable { private static ConnectionService connectionService = null; private static readonly Lazy instance = new Lazy(() => new MigrationService()); private bool disposed; /// /// Construct a new MigrationService instance with default parameters /// public MigrationService() { } /// /// Gets the singleton instance object /// public static MigrationService Instance { get { return instance.Value; } } /// /// Internal for testing purposes only /// internal static ConnectionService ConnectionService { get { if (connectionService == null) { connectionService = ConnectionService.Instance; } return connectionService; } set { connectionService = value; } } /// /// Gets the used to run assessment operations. /// internal Engine Engine { get; } = new Engine(); /// /// Service host object for sending/receiving requests/events. /// Internal for testing purposes. /// internal IProtocolEndpoint ServiceHost { get; set; } /// /// Controller for collecting performance data for SKU recommendation /// internal SqlDataQueryController DataCollectionController { get; set; } /// /// Initializes the Migration Service instance /// public void InitializeService(ServiceHost serviceHost) { this.ServiceHost = serviceHost; this.ServiceHost.SetRequestHandler(MigrationAssessmentsRequest.Type, HandleMigrationAssessmentsRequest); this.ServiceHost.SetRequestHandler(StartPerfDataCollectionRequest.Type, HandleStartPerfDataCollectionRequest); this.ServiceHost.SetRequestHandler(StopPerfDataCollectionRequest.Type, HandleStopPerfDataCollectionRequest); this.ServiceHost.SetRequestHandler(RefreshPerfDataCollectionRequest.Type, HandleRefreshPerfDataCollectionRequest); this.ServiceHost.SetRequestHandler(GetSkuRecommendationsRequest.Type, HandleGetSkuRecommendationsRequest); } /// /// Handle request to start a migration session /// internal async Task HandleMigrationAssessmentsRequest( MigrationAssessmentsParams parameters, RequestContext requestContext) { string randomUri = Guid.NewGuid().ToString(); try { // get connection if (!ConnectionService.TryFindConnection(parameters.OwnerUri, out var connInfo)) { await requestContext.SendError("Could not find migration connection"); return; } ConnectParams connectParams = new ConnectParams { OwnerUri = randomUri, Connection = connInfo.ConnectionDetails, Type = ConnectionType.Default }; await ConnectionService.Connect(connectParams); var connection = await ConnectionService.Instance.GetOrOpenConnection(randomUri, ConnectionType.Default); var connectionStrings = new List(); if (parameters.Databases != null) { foreach (string database in parameters.Databases) { connInfo.ConnectionDetails.DatabaseName = database; connectionStrings.Add(ConnectionService.BuildConnectionString(connInfo.ConnectionDetails)); } string[] assessmentConnectionStrings = connectionStrings.ToArray(); var results = await GetAssessmentItems(assessmentConnectionStrings); await requestContext.SendResult(results); } } finally { ConnectionService.Disconnect(new DisconnectParams { OwnerUri = randomUri, Type = null }); } } /// /// Handle request to start performance data collection process /// internal async Task HandleStartPerfDataCollectionRequest( StartPerfDataCollectionParams parameters, RequestContext requestContext) { string randomUri = Guid.NewGuid().ToString(); try { // get connection if (!ConnectionService.TryFindConnection(parameters.OwnerUri, out var connInfo)) { await requestContext.SendError("Could not find migration connection"); return; } ConnectParams connectParams = new ConnectParams { OwnerUri = randomUri, Connection = connInfo.ConnectionDetails, Type = ConnectionType.Default }; await ConnectionService.Connect(connectParams); var connection = await ConnectionService.Instance.GetOrOpenConnection(randomUri, ConnectionType.Default); var connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails); this.DataCollectionController = new SqlDataQueryController( connectionString, parameters.DataFolder, parameters.PerfQueryIntervalInSec, parameters.NumberOfIterations, parameters.StaticQueryIntervalInSec, null); this.DataCollectionController.Start(); // TO-DO: what should be returned? await requestContext.SendResult(new StartPerfDataCollectionResult() { DateTimeStarted = DateTime.UtcNow }); } finally { ConnectionService.Disconnect(new DisconnectParams { OwnerUri = randomUri, Type = null }); } } /// /// Handle request to stop performance data collection process /// internal async Task HandleStopPerfDataCollectionRequest( StopPerfDataCollectionParams parameters, RequestContext requestContext) { this.DataCollectionController.Dispose(); // TO-DO: what should be returned? await requestContext.SendResult(new StopPerfDataCollectionResult() { DateTimeStopped = DateTime.UtcNow }); } /// /// Handle request to refresh performance data collection status /// internal async Task HandleRefreshPerfDataCollectionRequest( RefreshPerfDataCollectionParams parameters, RequestContext requestContext) { bool isCollecting = !(this.DataCollectionController is null) ? this.DataCollectionController.IsRunning() : false; List messages = !(this.DataCollectionController is null) ? this.DataCollectionController.FetchLatestMessages(parameters.LastRefreshedTime) : new List(); List errors = !(this.DataCollectionController is null) ? this.DataCollectionController.FetchLatestErrors(parameters.LastRefreshedTime) : new List(); RefreshPerfDataCollectionResult result = new RefreshPerfDataCollectionResult() { RefreshTime = DateTime.UtcNow, IsCollecting = isCollecting, Messages = messages, Errors = errors, }; await requestContext.SendResult(result); } /// /// Handle request to generate SKU recommendations /// internal async Task HandleGetSkuRecommendationsRequest( GetSkuRecommendationsParams parameters, RequestContext requestContext) { try { SqlAssessmentConfiguration.EnableLocalLogging = true; SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath = Path.GetDirectoryName(Logger.LogFileFullPath); CsvRequirementsAggregator aggregator = new CsvRequirementsAggregator(parameters.DataFolder); SqlInstanceRequirements req = aggregator.ComputeSqlInstanceRequirements( agentId: null, instanceId: parameters.TargetSqlInstance, targetPercentile: parameters.TargetPercentile, startTime: DateTime.ParseExact(parameters.StartTime, RecommendationConstants.TimestampDateTimeFormat, CultureInfo.InvariantCulture), endTime: DateTime.ParseExact(parameters.EndTime, RecommendationConstants.TimestampDateTimeFormat, CultureInfo.InvariantCulture), collectionInterval: parameters.PerfQueryIntervalInSec, dbsToInclude: new HashSet(parameters.DatabaseAllowList), hostRequirements: new SqlServerHostRequirements() { NICCount = 1 }); RecommendationResultSet baselineResults = GenerateBaselineRecommendations(req, parameters); RecommendationResultSet elasticResults = GenerateElasticRecommendations(req, parameters); GetSkuRecommendationsResult results = new GetSkuRecommendationsResult { SqlDbRecommendationResults = baselineResults.sqlDbResults, SqlDbRecommendationDurationInMs = baselineResults.sqlDbDurationInMs, SqlMiRecommendationResults = baselineResults.sqlMiResults, SqlMiRecommendationDurationInMs = baselineResults.sqlMiDurationInMs, SqlVmRecommendationResults = baselineResults.sqlVmResults, SqlVmRecommendationDurationInMs = baselineResults.sqlVmDurationInMs, ElasticSqlDbRecommendationResults = elasticResults.sqlDbResults, ElasticSqlDbRecommendationDurationInMs = elasticResults.sqlDbDurationInMs, ElasticSqlMiRecommendationResults = elasticResults.sqlMiResults, ElasticSqlMiRecommendationDurationInMs = elasticResults.sqlMiDurationInMs, ElasticSqlVmRecommendationResults = elasticResults.sqlVmResults, ElasticSqlVmRecommendationDurationInMs = elasticResults.sqlVmDurationInMs, InstanceRequirements = req, SkuRecommendationReportPaths = new List { baselineResults.sqlDbReportPath, baselineResults.sqlMiReportPath, baselineResults.sqlVmReportPath }, ElasticSkuRecommendationReportPaths = new List { elasticResults.sqlDbReportPath, elasticResults.sqlMiReportPath, elasticResults.sqlVmReportPath }, }; await requestContext.SendResult(results); } catch (FailedToQueryCountersException e) { await requestContext.SendError($"Unable to read collected performance data from {parameters.DataFolder}. Please specify another folder or start data collection instead."); } catch (Exception e) { await requestContext.SendError(e.ToString()); } } internal RecommendationResultSet GenerateBaselineRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters) { RecommendationResultSet resultSet = new RecommendationResultSet(); SkuRecommendationServiceProvider provider = new SkuRecommendationServiceProvider(new AzureSqlSkuBillingServiceProvider()); AzurePreferences prefs = new AzurePreferences() { EligibleSkuCategories = null, // eligible SKU list will be adjusted with each recommendation type ScalingFactor = parameters.ScalingFactor / 100.0, TargetEnvironment = TargetEnvironmentType.Production }; // 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); if (resultSet.sqlDbResults.Count < parameters.DatabaseAllowList.Count) { // if there are fewer recommendations than expected, find which databases didn't have a result generated and create a result with a null SKU List databasesWithRecommendation = resultSet.sqlDbResults.Select(db => db.DatabaseName).ToList(); foreach (var databaseWithoutRecommendation in parameters.DatabaseAllowList.Where(db => !databasesWithRecommendation.Contains(db))) { resultSet.sqlDbResults.Add(new SkuRecommendationResult() { //SqlInstanceName = sqlDbResults.FirstOrDefault().SqlInstanceName, SqlInstanceName = parameters.TargetSqlInstance, DatabaseName = databaseWithoutRecommendation, TargetSku = null, MonthlyCost = null, Ranking = -1, PositiveJustifications = null, NegativeJustifications = null, }); } } sqlDbStopwatch.Stop(); resultSet.sqlDbDurationInMs = sqlDbStopwatch.ElapsedMilliseconds; SkuRecommendationReport sqlDbReport = new SkuRecommendationReport( new Dictionary> { { 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, TargetSku = null, MonthlyCost = null, Ranking = -1, PositiveJustifications = null, NegativeJustifications = null, }); } sqlMiStopwatch.Stop(); resultSet.sqlMiDurationInMs = sqlMiStopwatch.ElapsedMilliseconds; SkuRecommendationReport sqlMiReport = new SkuRecommendationReport( new Dictionary> { { 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()) { resultSet.sqlVmResults.Add(new SkuRecommendationResult() { SqlInstanceName = parameters.TargetSqlInstance, DatabaseName = null, TargetSku = null, MonthlyCost = null, Ranking = -1, PositiveJustifications = null, NegativeJustifications = null, }); } sqlVmStopwatch.Stop(); resultSet.sqlVmDurationInMs = sqlVmStopwatch.ElapsedMilliseconds; SkuRecommendationReport sqlVmReport = new SkuRecommendationReport( new Dictionary> { { 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) { RecommendationResultSet resultSet = new RecommendationResultSet(); CsvAggregatorForElasticStrategy elasticaggregator = new CsvAggregatorForElasticStrategy( instanceId: parameters.TargetSqlInstance, directory: parameters.DataFolder, queryInterval: parameters.PerfQueryIntervalInSec, logger: null, dbsToInclude: new HashSet(parameters.DatabaseAllowList)); // generate SQL DB recommendations, if applicable if (parameters.TargetPlatforms.Contains("AzureSqlDatabase")) { Stopwatch sqlDbStopwatch = new Stopwatch(); sqlDbStopwatch.Start(); List eligibleSkuCategories = GetEligibleSkuCategories("AzureSqlDatabase", parameters.IncludePreviewSkus); ElasticStrategySKURecommendationPipeline pi = new ElasticStrategySKURecommendationPipeline(eligibleSkuCategories); DataTable SqlMISpec = pi.SqlMISpec.Copy(); if (!parameters.IncludePreviewSkus) { SqlMISpec = pi.SqlMISpec.AsEnumerable().Where( p => !p.Field("SLO").Contains("Premium")).CopyToDataTable(); } MISkuRecParams MiSkuRecParams = new MISkuRecParams(pi.SqlGPMIFileSpec, SqlMISpec, elasticaggregator.FileLevelTs, elasticaggregator.InstanceTs, pi.MILookupTable, Convert.ToDouble(parameters.ScalingFactor) / 100.0, parameters.TargetSqlInstance); DbSkuRecParams DbSkuRecParams = new DbSkuRecParams(pi.SqlDbSpec, elasticaggregator.DatabaseTs, pi.DbLookupTable, Convert.ToDouble(parameters.ScalingFactor) / 100.0, parameters.TargetSqlInstance); resultSet.sqlDbResults = pi.ElasticStrategyGetSkuRecommendation(MiSkuRecParams, DbSkuRecParams, req); if (resultSet.sqlDbResults.Count < parameters.DatabaseAllowList.Count) { // if there are fewer recommendations than expected, find which databases didn't have a result generated and create a result with a null SKU List databasesWithRecommendation = resultSet.sqlDbResults.Select(db => db.DatabaseName).ToList(); foreach (var databaseWithoutRecommendation in parameters.DatabaseAllowList.Where(db => !databasesWithRecommendation.Contains(db))) { resultSet.sqlDbResults.Add(new SkuRecommendationResult() { //SqlInstanceName = sqlDbResults.FirstOrDefault().SqlInstanceName, SqlInstanceName = parameters.TargetSqlInstance, DatabaseName = databaseWithoutRecommendation, TargetSku = null, MonthlyCost = null, Ranking = -1, PositiveJustifications = null, NegativeJustifications = null, }); } } sqlDbStopwatch.Stop(); resultSet.sqlDbDurationInMs = sqlDbStopwatch.ElapsedMilliseconds; SkuRecommendationReport sqlDbReport = new SkuRecommendationReport( new Dictionary> { { req, resultSet.sqlDbResults } }, AzureSqlTargetPlatform.AzureSqlDatabase.ToString()); var sqlDbRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlDatabase-Elastic-{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(); List eligibleSkuCategories = GetEligibleSkuCategories("AzureSqlManagedInstance", parameters.IncludePreviewSkus); ElasticStrategySKURecommendationPipeline pi = new ElasticStrategySKURecommendationPipeline(eligibleSkuCategories); DataTable SqlMISpec = pi.SqlMISpec.Copy(); if (!parameters.IncludePreviewSkus) { SqlMISpec = pi.SqlMISpec.AsEnumerable().Where( p => !p.Field("SLO").Contains("Premium")).CopyToDataTable(); } MISkuRecParams MiSkuRecParams = new MISkuRecParams(pi.SqlGPMIFileSpec, SqlMISpec, elasticaggregator.FileLevelTs, elasticaggregator.InstanceTs, pi.MILookupTable, Convert.ToDouble(parameters.ScalingFactor) / 100.0, parameters.TargetSqlInstance); DbSkuRecParams DbSkuRecParams = new DbSkuRecParams(pi.SqlDbSpec, elasticaggregator.DatabaseTs, pi.DbLookupTable, Convert.ToDouble(parameters.ScalingFactor) / 100.0, parameters.TargetSqlInstance); resultSet.sqlMiResults = pi.ElasticStrategyGetSkuRecommendation(MiSkuRecParams, DbSkuRecParams, 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, TargetSku = null, MonthlyCost = null, Ranking = -1, PositiveJustifications = null, NegativeJustifications = null, }); } sqlMiStopwatch.Stop(); resultSet.sqlMiDurationInMs = sqlMiStopwatch.ElapsedMilliseconds; SkuRecommendationReport sqlMiReport = new SkuRecommendationReport( new Dictionary> { { req, resultSet.sqlMiResults } }, AzureSqlTargetPlatform.AzureSqlManagedInstance.ToString()); var sqlMiRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlManagedInstance-Elastic-{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")) { // elastic model currently doesn't support VM recommendation, return null SKU for now resultSet.sqlVmResults = new List { new SkuRecommendationResult() { SqlInstanceName = parameters.TargetSqlInstance, DatabaseName = null, TargetSku = null, MonthlyCost = null, Ranking = -1, PositiveJustifications = null, NegativeJustifications = null, } }; resultSet.sqlVmDurationInMs = -1; resultSet.sqlVmReportPath = String.Empty; } return resultSet; } internal class AssessmentRequest : IAssessmentRequest { private readonly Check[] checks = null; public AssessmentRequest(ISqlObjectLocator locator) { Target = locator ?? throw new ArgumentNullException(nameof(locator)); } public EvaluationContext EvaluationContext { get; } public ISqlObjectLocator Target { get; } public IEnumerable Checks { get { return checks; } } public bool TryGetData(string column, out object value) { return EvaluationContext.TryGetData(column, out value); } } internal async Task GetAssessmentItems(string[] connectionStrings) { SqlAssessmentConfiguration.EnableLocalLogging = true; SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath = Path.GetDirectoryName(Logger.LogFileFullPath); DmaEngine engine = new DmaEngine(connectionStrings); ISqlMigrationAssessmentModel contextualizedAssessmentResult = await engine.GetTargetAssessmentResultsListWithCheck(System.Threading.CancellationToken.None); var assessmentReportFileName = String.Format("SqlAssessmentReport-{0}.json", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture)); var assessmentReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, assessmentReportFileName); engine.SaveAssessmentResultsToJson(contextualizedAssessmentResult, false, assessmentReportFullPath); var server = (contextualizedAssessmentResult.Servers.Count > 0) ? ParseServerAssessmentInfo(contextualizedAssessmentResult.Servers[0], engine) : null; return new MigrationAssessmentResult() { AssessmentResult = server, Errors = ParseAssessmentError(contextualizedAssessmentResult.Errors), StartTime = contextualizedAssessmentResult.StartedOn.ToString(), EndedTime = contextualizedAssessmentResult.EndedOn.ToString(), RawAssessmentResult = contextualizedAssessmentResult, AssessmentReportPath = assessmentReportFullPath }; } internal ServerAssessmentProperties ParseServerAssessmentInfo(IServerAssessmentInfo server, DmaEngine engine) { return new ServerAssessmentProperties() { CpuCoreCount = server.Properties.ServerCoreCount, PhysicalServerMemory = server.Properties.MaxServerMemoryInUse, ServerHostPlatform = server.Properties.ServerHostPlatform, ServerVersion = server.Properties.ServerVersion, ServerEngineEdition = server.Properties.ServerEngineEdition, ServerEdition = server.Properties.ServerEdition, IsClustered = server.Properties.IsClustered, NumberOfUserDatabases = server.Properties.NumberOfUserDatabases, SqlAssessmentStatus = (int)server.Status, AssessedDatabaseCount = server.Properties.NumberOfUserDatabases, SQLManagedInstanceTargetReadiness = server.TargetReadinesses[Microsoft.SqlServer.DataCollection.Common.Contracts.Advisor.TargetType.AzureSqlManagedInstance], Errors = ParseAssessmentError(server.Errors), Items = ParseAssessmentResult(server.ServerAssessments, engine), Databases = ParseDatabaseAssessmentInfo(server.Databases, engine), Name = server.Properties.ServerName }; } internal DatabaseAssessmentProperties[] ParseDatabaseAssessmentInfo(IList databases, DmaEngine engine) { return databases.Select(d => { return new DatabaseAssessmentProperties() { Name = d.Properties.Name, CompatibilityLevel = d.Properties.CompatibilityLevel.ToString(), DatabaseSize = d.Properties.SizeMB, IsReplicationEnabled = d.Properties.IsReplicationEnabled, AssessmentTimeInMilliseconds = d.Properties.TSqlScriptAnalysisTimeElapse.TotalMilliseconds, Errors = ParseAssessmentError(d.Errors), Items = ParseAssessmentResult(d.DatabaseAssessments, engine), SQLManagedInstanceTargetReadiness = d.TargetReadinesses[Microsoft.SqlServer.DataCollection.Common.Contracts.Advisor.TargetType.AzureSqlManagedInstance] }; }).ToArray(); } internal ErrorModel[] ParseAssessmentError(IList errors) { return errors.Select(e => { return new ErrorModel() { ErrorId = e.ErrorID.ToString(), Message = e.Message, ErrorSummary = e.ErrorSummary, PossibleCauses = e.PossibleCauses, Guidance = e.Guidance, }; }).ToArray(); } internal MigrationAssessmentInfo[] ParseAssessmentResult(IList assessmentResults, DmaEngine engine) { return assessmentResults.Select(r => { var check = (Microsoft.SqlServer.Management.Assessment.Checks.Check)r.Check; return new MigrationAssessmentInfo() { CheckId = check.Id, Description = check.Description, DisplayName = r.Message, HelpLink = check.HelpLink, Level = check.Level.ToString(), TargetType = r.TargetType.ToString(), DatabaseName = r.DatabaseName, ServerName = r.ServerName, Tags = check.Tags.ToArray(), RulesetName = Engine.Configuration.DefaultRuleset.Name, RulesetVersion = Engine.Configuration.DefaultRuleset.Version.ToString(), RuleId = r.FeatureId.ToString(), Message = check.Message, AppliesToMigrationTargetPlatform = r.AppliesToMigrationTargetPlatform.ToString(), IssueCategory = r.IssueCategory.ToString(), ImpactedObjects = ParseImpactedObjects(r.ImpactedObjects), DatabaseRestoreFails = r.DatabaseRestoreFails }; }).ToArray(); } internal ImpactedObjectInfo[] ParseImpactedObjects(IList impactedObjects) { return impactedObjects.Select(i => { return new ImpactedObjectInfo() { Name = i.Name, ImpactDetail = i.ImpactDetail, ObjectType = i.ObjectType }; }).ToArray(); } internal string CreateAssessmentResultKey(ISqlMigrationAssessmentResult assessment) { return assessment.ServerName + assessment.DatabaseName + assessment.FeatureId.ToString() + assessment.IssueCategory.ToString() + assessment.Message + assessment.TargetType.ToString() + assessment.AppliesToMigrationTargetPlatform.ToString(); } // Returns the list of eligible SKUs to consider, depending on the desired target platform internal static List GetEligibleSkuCategories(string targetPlatform, bool includePreviewSkus) { List eligibleSkuCategories = new List(); switch (targetPlatform) { case "AzureSqlDatabase": // Gen5 BC/GP DB eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( AzureSqlTargetPlatform.AzureSqlDatabase, AzureSqlPurchasingModel.vCore, AzureSqlPaaSServiceTier.BusinessCritical, ComputeTier.Provisioned, AzureSqlPaaSHardwareType.Gen5)); eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( AzureSqlTargetPlatform.AzureSqlDatabase, AzureSqlPurchasingModel.vCore, AzureSqlPaaSServiceTier.GeneralPurpose, ComputeTier.Provisioned, AzureSqlPaaSHardwareType.Gen5)); break; case "AzureSqlManagedInstance": // Gen5 BC/GP MI eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( AzureSqlTargetPlatform.AzureSqlManagedInstance, AzureSqlPurchasingModel.vCore, AzureSqlPaaSServiceTier.BusinessCritical, ComputeTier.Provisioned, AzureSqlPaaSHardwareType.Gen5)); eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( AzureSqlTargetPlatform.AzureSqlManagedInstance, AzureSqlPurchasingModel.vCore, AzureSqlPaaSServiceTier.GeneralPurpose, ComputeTier.Provisioned, AzureSqlPaaSHardwareType.Gen5)); // Premium BC/GP eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( AzureSqlTargetPlatform.AzureSqlManagedInstance, AzureSqlPurchasingModel.vCore, AzureSqlPaaSServiceTier.BusinessCritical, ComputeTier.Provisioned, AzureSqlPaaSHardwareType.PremiumSeries)); eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( AzureSqlTargetPlatform.AzureSqlManagedInstance, AzureSqlPurchasingModel.vCore, AzureSqlPaaSServiceTier.GeneralPurpose, ComputeTier.Provisioned, AzureSqlPaaSHardwareType.PremiumSeries)); if (includePreviewSkus) { // Premium Memory Optimized BC/GP eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( AzureSqlTargetPlatform.AzureSqlManagedInstance, AzureSqlPurchasingModel.vCore, AzureSqlPaaSServiceTier.BusinessCritical, ComputeTier.Provisioned, AzureSqlPaaSHardwareType.PremiumSeriesMemoryOptimized)); eligibleSkuCategories.Add(new AzureSqlSkuPaaSCategory( AzureSqlTargetPlatform.AzureSqlManagedInstance, AzureSqlPurchasingModel.vCore, AzureSqlPaaSServiceTier.GeneralPurpose, ComputeTier.Provisioned, AzureSqlPaaSHardwareType.PremiumSeriesMemoryOptimized)); } break; case "AzureSqlVirtualMachine": string assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); // load Azure VM capabilities string jsonFile = File.ReadAllText(Path.Combine(assemblyPath, RecommendationConstants.DataFolder, RecommendationConstants.SqlVmCapability)); List vmCapabilities = JsonConvert.DeserializeObject>(jsonFile); // Eb series capabilities stored separately string computePreviewFilePath = Path.Combine(assemblyPath, RecommendationConstants.DataFolder, RecommendationConstants.SqlVmPreviewCapability); if (File.Exists(computePreviewFilePath)) { jsonFile = File.ReadAllText(computePreviewFilePath); List vmPreviewCapabilities = JsonConvert.DeserializeObject>(jsonFile); vmCapabilities.AddRange(vmPreviewCapabilities); } foreach (VirtualMachineFamily family in AzureVirtualMachineFamilyGroup.FamilyGroups[VirtualMachineFamilyType.GeneralPurpose] .Concat(AzureVirtualMachineFamilyGroup.FamilyGroups[VirtualMachineFamilyType.MemoryOptimized])) { var skus = vmCapabilities.Where(c => string.Equals(c.Family, family.ToString(), StringComparison.OrdinalIgnoreCase)).Select(c => c.Name); AzureSqlSkuIaaSCategory category = new AzureSqlSkuIaaSCategory(family); category.AvailableVmSkus.AddRange(skus); eligibleSkuCategories.Add(category); } break; default: break; } return eligibleSkuCategories; } /// /// Disposes the Migration Service /// public void Dispose() { if (!disposed) { this.DataCollectionController.Dispose(); disposed = true; } } } }