//
// 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);
}
}
}