// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlServer.Management.Assessment; using Microsoft.SqlServer.Management.Assessment.Checks; using Microsoft.SqlServer.Migration.Assessment.Common.Contracts.Models; using Microsoft.SqlServer.Migration.Assessment.Common.Engine; using Microsoft.SqlServer.Migration.Assessment.Common.Models; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.Migration.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlAssessment; using Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts; 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; } /// /// Initializes the Migration Service instance /// public void InitializeService(ServiceHost serviceHost) { this.ServiceHost = serviceHost; this.ServiceHost.SetRequestHandler(MigrationAssessmentsRequest.Type, HandleMigrationAssessmentsRequest); } /// /// 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 serverInfo = ReliableConnectionHelper.GetServerVersion(connection); var hostInfo = ReliableConnectionHelper.GetServerHostInfo(connection); var server = new SqlObjectLocator { Connection = connection, EngineEdition = SqlAssessmentService.GetEngineEdition(serverInfo.EngineEditionId), Name = serverInfo.ServerName, ServerName = serverInfo.ServerName, Type = SqlObjectType.Server, Urn = serverInfo.ServerName, Version = Version.Parse(serverInfo.ServerVersion), Platform = hostInfo.Platform }; var db = SqlAssessmentService.GetDatabaseLocator(server, connection.Database); var results = await GetAssessmentItems(server); var result = new MigrationAssessmentResult(); result.Items.AddRange(results); await requestContext.SendResult(result); } finally { ConnectionService.Disconnect(new DisconnectParams { OwnerUri = randomUri, Type = null }); } } 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(SqlObjectLocator target) { DmaEngine engine = new DmaEngine(target); var assessmentResults = await engine.GetTargetAssessmentResultsList(); var result = new List(); foreach (var r in assessmentResults) { var migrationResult = r as ISqlMigrationAssessmentResult; if (migrationResult == null) { continue; } var targetName = !string.IsNullOrWhiteSpace(migrationResult.DatabaseName) ? $"{target.ServerName}:{migrationResult.DatabaseName}" : target.Name; var item = new MigrationAssessmentInfo() { CheckId = r.Check.Id, Description = r.Check.Description, DisplayName = r.Check.DisplayName, HelpLink = r.Check.HelpLink, Level = r.Check.Level.ToString(), TargetName = targetName, Tags = r.Check.Tags.ToArray(), TargetType = target.Type, RulesetName = Engine.Configuration.DefaultRuleset.Name, RulesetVersion = Engine.Configuration.DefaultRuleset.Version.ToString(), Message = r.Message, AppliesToMigrationTargetPlatform = migrationResult.AppliesToMigrationTargetPlatform.ToString(), IssueCategory = "Category_Unknown" }; if (migrationResult.ImpactedObjects != null) { ImpactedObjectInfo[] impactedObjects = new ImpactedObjectInfo[migrationResult.ImpactedObjects.Count]; for (int i = 0; i < migrationResult.ImpactedObjects.Count; ++i) { var impactedObject = new ImpactedObjectInfo() { Name = migrationResult.ImpactedObjects[i].Name, ImpactDetail = migrationResult.ImpactedObjects[i].ImpactDetail, ObjectType = migrationResult.ImpactedObjects[i].ObjectType }; impactedObjects[i] = impactedObject; } item.ImpactedObjects = impactedObjects; } result.Add(item); } return result; } /// /// Disposes the Migration Service /// public void Dispose() { if (!disposed) { disposed = true; } } } }