//
// 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