// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // #nullable disable using System; using System.Linq; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.Utility; using System.Collections.Concurrent; using Microsoft.SqlTools.ServiceLayer.Utility; using System.Diagnostics; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { /// /// Includes method to all restore operations /// public class RestoreDatabaseHelper { public const string LastBackupTaken = "lastBackupTaken"; private ConcurrentDictionary sessions = new ConcurrentDictionary(); /// /// Creates response which includes information about the server given to restore (default data location, db names with backupsets) /// public RestoreConfigInfoResponse CreateConfigInfoResponse(RestoreConfigInfoRequestParams restoreConfigInfoRequest) { RestoreConfigInfoResponse response = new RestoreConfigInfoResponse(); RestoreDatabaseTaskDataObject restoreTaskObject = null; try { restoreTaskObject = CreateRestoreForNewSession(restoreConfigInfoRequest.OwnerUri); if (restoreTaskObject != null) { // Default Data folder path in the target server response.ConfigInfo.Add(RestoreOptionsHelper.DataFileFolder, restoreTaskObject.DefaultDataFileFolder); // Default log folder path in the target server response.ConfigInfo.Add(RestoreOptionsHelper.LogFileFolder, restoreTaskObject.DefaultLogFileFolder); // The db names with backup set response.ConfigInfo.Add(RestoreOptionsHelper.SourceDatabaseNamesWithBackupSets, restoreTaskObject.GetDatabaseNamesWithBackupSets()); // Default backup folder path in the target server response.ConfigInfo.Add(RestoreOptionsHelper.DefaultBackupFolder, restoreTaskObject.DefaultBackupFolder); } } catch(Exception ex) { Logger.Write(TraceEventType.Warning, $"Failed to create restore config info. error: { ex.Message}"); response.ErrorMessage = ex.Message; } finally { ServerConnection serverConnection = restoreTaskObject?.Server?.ConnectionContext; if (serverConnection != null && serverConnection.IsOpen) { restoreTaskObject.Server.ConnectionContext.Disconnect(); } } return response; } /// /// Cancels existing restore plan /// public bool CancelRestorePlan(RestoreParams restoreParams) { RestoreDatabaseTaskDataObject restoreTaskObject = null; string sessionId = restoreParams.SessionId; if (!string.IsNullOrEmpty(sessionId) && sessions.TryGetValue(sessionId, out restoreTaskObject)) { ServerConnection connection = restoreTaskObject?.Server?.ConnectionContext; if (connection != null && connection.IsOpen) { connection.Disconnect(); } sessions.TryRemove(sessionId, out restoreTaskObject); return true; } return false; } /// /// Creates a restore plan, The result includes the information about the backup set, /// the files and the database to restore to /// /// Restore requests /// Restore plan public RestorePlanResponse CreateRestorePlanResponse(RestoreDatabaseTaskDataObject restoreDataObject) { RestorePlanResponse response = new RestorePlanResponse() { DatabaseName = restoreDataObject.RestoreParams.TargetDatabaseName, PlanDetails = new System.Collections.Generic.Dictionary() }; try { if (restoreDataObject != null && restoreDataObject.IsValid) { restoreDataObject.UpdateRestoreTaskObject(); if (restoreDataObject != null && restoreDataObject.IsValid) { response.SessionId = restoreDataObject.SessionId; response.DatabaseName = restoreDataObject.TargetDatabaseName; response.PlanDetails.Add(RestoreOptionsHelper.TargetDatabaseName, RestoreOptionFactory.Instance.CreateAndValidate(RestoreOptionsHelper.TargetDatabaseName, restoreDataObject)); response.PlanDetails.Add(RestoreOptionsHelper.SourceDatabaseName, RestoreOptionFactory.Instance.CreateAndValidate(RestoreOptionsHelper.SourceDatabaseName, restoreDataObject)); response.PlanDetails.Add(RestoreOptionsHelper.ReadHeaderFromMedia, RestorePlanDetailInfo.Create( name: RestoreOptionsHelper.ReadHeaderFromMedia, currentValue: restoreDataObject.RestorePlanner.ReadHeaderFromMedia)); response.DbFiles = restoreDataObject.DbFiles.Select(x => new RestoreDatabaseFileInfo { FileType = x.DbFileType, LogicalFileName = x.LogicalName, OriginalFileName = x.PhysicalName, RestoreAsFileName = x.PhysicalNameRelocate }); response.CanRestore = CanRestore(restoreDataObject); response.PlanDetails.Add(LastBackupTaken, RestorePlanDetailInfo.Create(name: LastBackupTaken, currentValue: restoreDataObject.GetLastBackupTaken(), isReadOnly: true)); response.BackupSetsToRestore = restoreDataObject.GetSelectedBakupSets(); var dbNames = restoreDataObject.SourceDbNames; response.DatabaseNamesFromBackupSets = dbNames == null ? new string[] { } : dbNames.ToArray(); RestoreOptionsHelper.AddOptions(response, restoreDataObject); } else { if (restoreDataObject.ActiveException != null) { response.ErrorMessage = restoreDataObject.ActiveException.Message; } else { response.ErrorMessage = SR.RestorePlanFailed; } response.CanRestore = false; } } else { response.ErrorMessage = SR.RestorePlanFailed; } } catch(Exception ex) { response.ErrorMessage = ex.Message; if (ex.InnerException != null) { response.ErrorMessage += Environment.NewLine; response.ErrorMessage += ex.InnerException.Message; } Logger.Write(TraceEventType.Information, $"Failed to create restore plan. error: { response.ErrorMessage}"); } return response; } /// /// Returns true if the restoring the restoreDataObject is supported in the service /// private static bool CanRestore(RestoreDatabaseTaskDataObject restoreDataObject) { return restoreDataObject != null && restoreDataObject.RestorePlan != null && restoreDataObject.RestorePlan.RestoreOperations != null && restoreDataObject.RestorePlan.RestoreOperations.Count > 0; } /// /// Creates anew restore task object to do the restore operations /// /// Restore request parameters /// Restore task object public RestoreDatabaseTaskDataObject CreateRestoreDatabaseTaskDataObject(RestoreParams restoreParams, ConnectionInfo connectionInfo = null) { RestoreDatabaseTaskDataObject restoreTaskObject = null; string sessionId = string.IsNullOrWhiteSpace(restoreParams.SessionId) ? Guid.NewGuid().ToString() : restoreParams.SessionId; if (!sessions.TryGetValue(sessionId, out restoreTaskObject)) { restoreTaskObject = CreateRestoreForNewSession(restoreParams.OwnerUri, restoreParams.TargetDatabaseName); sessions.AddOrUpdate(sessionId, restoreTaskObject, (key, old) => restoreTaskObject); } restoreTaskObject.SessionId = sessionId; restoreTaskObject.RestoreParams = restoreParams; if (connectionInfo != null) { restoreTaskObject.ConnectionInfo = connectionInfo; } return restoreTaskObject; } private RestoreDatabaseTaskDataObject CreateRestoreForNewSession(string ownerUri, string targetDatabaseName = null) { ConnectionInfo connInfo; DisasterRecoveryService.ConnectionServiceInstance.TryFindConnection( ownerUri, out connInfo); if (connInfo != null) { Server server = new Server(ConnectionService.OpenServerConnection(connInfo, "Restore")); RestoreDatabaseTaskDataObject restoreDataObject = new RestoreDatabaseTaskDataObject(server, targetDatabaseName); return restoreDataObject; } return null; } private bool CanChangeTargetDatabase(RestoreDatabaseTaskDataObject restoreDataObject) { return DatabaseUtils.IsSystemDatabaseConnection(restoreDataObject.Server.ConnectionContext.DatabaseName); } } }