mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-24 17:24:14 -05:00
Feature/restore db (#403)
* Added service handlers for restore database operations
This commit is contained in:
@@ -340,28 +340,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDestinationPathValid(string path, ref bool isFolder)
|
||||
{
|
||||
Enumerator en = null;
|
||||
DataTable dt;
|
||||
Request req = new Request();
|
||||
|
||||
en = new Enumerator();
|
||||
req.Urn = "Server/File[@FullName='" + Urn.EscapeString(path) + "']";
|
||||
dt = en.Process(this.sqlConnection, req);
|
||||
|
||||
if (dt.Rows.Count > 0)
|
||||
{
|
||||
isFolder = !(Convert.ToBoolean(dt.Rows[0]["IsFile"], System.Globalization.CultureInfo.InvariantCulture));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isFolder = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMediaNameFromBackupSetId(int backupSetId)
|
||||
{
|
||||
Enumerator en = null;
|
||||
@@ -422,72 +400,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetNewPhysicalRestoredFileName(string filePathParam, string dbName, bool isNewDatabase, string type, ref int fileIndex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePathParam))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string result = string.Empty;
|
||||
string filePath = filePathParam;
|
||||
int idx = filePath.LastIndexOf('\\');
|
||||
string folderPath = filePath.Substring(0,idx);
|
||||
|
||||
string fileName = filePath.Substring(idx + 1);
|
||||
idx = fileName.LastIndexOf('.');
|
||||
string fileExtension = fileName.Substring(idx + 1);
|
||||
|
||||
bool isFolder = true;
|
||||
bool isValidPath = IsDestinationPathValid(folderPath, ref isFolder);
|
||||
|
||||
if (!isValidPath || !isFolder)
|
||||
{
|
||||
SMO.Server server = new SMO.Server(this.sqlConnection);
|
||||
if (type != RestoreConstants.Log)
|
||||
{
|
||||
folderPath = server.Settings.DefaultFile;
|
||||
if (folderPath.Length == 0)
|
||||
{
|
||||
folderPath = server.Information.MasterDBPath;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
folderPath = server.Settings.DefaultLog;
|
||||
if (folderPath.Length == 0)
|
||||
{
|
||||
folderPath = server.Information.MasterDBLogPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isNewDatabase)
|
||||
{
|
||||
return filePathParam;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNewDatabase)
|
||||
{
|
||||
result = folderPath + "\\" + dbName + "." + fileExtension;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (0 != string.Compare(fileExtension, "mdf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = folderPath + "\\" + dbName + "_" + Convert.ToString(fileIndex, System.Globalization.CultureInfo.InvariantCulture) + "." + fileExtension;
|
||||
fileIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = folderPath + "\\" + dbName + "." + fileExtension;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// TODO: This is implemented as internal property in SMO.
|
||||
public bool IsLocalPrimaryReplica(string databaseName)
|
||||
@@ -663,29 +576,29 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
return dict;
|
||||
}*/
|
||||
|
||||
public void GetBackupSetTypeAndComponent(int numType, ref string backupType, ref string backupComponent)
|
||||
public static void GetBackupSetTypeAndComponent(BackupSetType backupSetType, out string backupType, out string backupComponent)
|
||||
{
|
||||
switch (numType)
|
||||
switch (backupSetType)
|
||||
{
|
||||
case 1:
|
||||
case BackupSetType.Database:
|
||||
backupType = RestoreConstants.TypeFull;
|
||||
backupComponent = RestoreConstants.ComponentDatabase;
|
||||
break;
|
||||
case 2:
|
||||
case BackupSetType.Differential:
|
||||
backupType = RestoreConstants.TypeTransactionLog;
|
||||
backupComponent = "";
|
||||
backupComponent = RestoreConstants.ComponentDatabase;
|
||||
break;
|
||||
case 4:
|
||||
case BackupSetType.FileOrFileGroup:
|
||||
backupType = RestoreConstants.TypeFilegroup;
|
||||
backupComponent = RestoreConstants.ComponentFile;
|
||||
break;
|
||||
case 5:
|
||||
case BackupSetType.FileOrFileGroupDifferential:
|
||||
backupType = RestoreConstants.TypeDifferential;
|
||||
backupComponent = RestoreConstants.ComponentDatabase;
|
||||
break;
|
||||
case 6:
|
||||
backupType = RestoreConstants.TypeFilegroupDifferential;
|
||||
backupComponent = RestoreConstants.ComponentFile;
|
||||
case BackupSetType.Log:
|
||||
backupType = RestoreConstants.Log;
|
||||
backupComponent = RestoreConstants.ComponentLog;
|
||||
break;
|
||||
default:
|
||||
backupType = RestoreConstants.NotKnown;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Restore request parameters
|
||||
/// </summary>
|
||||
public class RestoreParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The Uri to find the connection to do the restore operations
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The backup file path
|
||||
/// </summary>
|
||||
public string BackupFilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database name to restore from (either the back file path or database name can be used for restore operation,
|
||||
/// If the backup file is set, the database name will be ignored)
|
||||
/// </summary>
|
||||
public string DatabaseName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the db files will be relocated to default data location in the server
|
||||
/// </summary>
|
||||
public bool RelocateDbFiles { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore response
|
||||
/// </summary>
|
||||
public class RestoreResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if the restore task created successfully
|
||||
/// </summary>
|
||||
public bool Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The task id assosiated witht the restore operation
|
||||
/// </summary>
|
||||
public string TaskId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Errors occurred while creating the restore operation task
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore Plan Response
|
||||
/// </summary>
|
||||
public class RestorePlanResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The backup file path
|
||||
/// </summary>
|
||||
public string BackupFilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the restore operation is supported
|
||||
/// </summary>
|
||||
public bool CanRestore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Errors occurred while creating restore plan
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The db files included in the backup file
|
||||
/// </summary>
|
||||
public IEnumerable<string> DbFiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Server name
|
||||
/// </summary>
|
||||
public string ServerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database name to restore to
|
||||
/// </summary>
|
||||
public string DatabaseName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether relocating the db files is required
|
||||
/// because the original file paths are not valid in the target server
|
||||
/// </summary>
|
||||
public bool RelocateFilesNeeded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default Data folder path in the target server
|
||||
/// </summary>
|
||||
public string DefaultDataFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default log folder path in the target server
|
||||
/// </summary>
|
||||
public string DefaultLogFolder { get; set; }
|
||||
}
|
||||
|
||||
public class RestoreRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<RestoreParams, RestoreResponse> Type =
|
||||
RequestType<RestoreParams, RestoreResponse>.Create("disasterrecovery/restore");
|
||||
}
|
||||
|
||||
public class RestorePlanRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<RestoreParams, RestorePlanResponse> Type =
|
||||
RequestType<RestoreParams, RestorePlanResponse>.Create("disasterrecovery/restoreplan");
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
public static string TypeFilegroupDifferential = "Filegroup Differential";
|
||||
public static string ComponentDatabase = "Database";
|
||||
public static string ComponentFile = "File";
|
||||
public static string ComponentLog = "Log";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
{
|
||||
@@ -23,6 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
{
|
||||
private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
|
||||
private static ConnectionService connectionService = null;
|
||||
private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper();
|
||||
|
||||
/// <summary>
|
||||
/// Default, parameterless constructor.
|
||||
@@ -61,12 +65,17 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
/// <summary>
|
||||
/// Initializes the service instance
|
||||
/// </summary>
|
||||
public void InitializeService(ServiceHost serviceHost)
|
||||
public void InitializeService(IProtocolEndpoint serviceHost)
|
||||
{
|
||||
// Get database info
|
||||
serviceHost.SetRequestHandler(BackupConfigInfoRequest.Type, HandleBackupConfigInfoRequest);
|
||||
// Create backup
|
||||
serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest);
|
||||
|
||||
// Create respore task
|
||||
serviceHost.SetRequestHandler(RestoreRequest.Type, HandleRestoreRequest);
|
||||
// Create respore plan
|
||||
serviceHost.SetRequestHandler(RestorePlanRequest.Type, HandleRestorePlanRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,6 +109,81 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
await requestContext.SendResult(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a restore request
|
||||
/// </summary>
|
||||
internal async Task HandleRestorePlanRequest(
|
||||
RestoreParams restoreParams,
|
||||
RequestContext<RestorePlanResponse> requestContext)
|
||||
{
|
||||
RestorePlanResponse response = new RestorePlanResponse();
|
||||
ConnectionInfo connInfo;
|
||||
bool supported = IsBackupRestoreOperationSupported(restoreParams, out connInfo);
|
||||
|
||||
if (supported && connInfo != null)
|
||||
{
|
||||
RestoreDatabaseTaskDataObject restoreDataObject = this.restoreDatabaseService.CreateRestoreDatabaseTaskDataObject(restoreParams);
|
||||
response = this.restoreDatabaseService.CreateRestorePlanResponse(restoreDataObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.CanRestore = false;
|
||||
response.ErrorMessage = "Restore is not supported"; //TOOD: have a better error message
|
||||
}
|
||||
await requestContext.SendResult(response);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a restore request
|
||||
/// </summary>
|
||||
internal async Task HandleRestoreRequest(
|
||||
RestoreParams restoreParams,
|
||||
RequestContext<RestoreResponse> requestContext)
|
||||
{
|
||||
RestoreResponse response = new RestoreResponse();
|
||||
ConnectionInfo connInfo;
|
||||
bool supported = IsBackupRestoreOperationSupported(restoreParams, out connInfo);
|
||||
|
||||
if (supported && connInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
RestoreDatabaseTaskDataObject restoreDataObject = this.restoreDatabaseService.CreateRestoreDatabaseTaskDataObject(restoreParams);
|
||||
|
||||
if (restoreDataObject != null)
|
||||
{
|
||||
// create task metadata
|
||||
TaskMetadata metadata = new TaskMetadata();
|
||||
metadata.ServerName = connInfo.ConnectionDetails.ServerName;
|
||||
metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName;
|
||||
metadata.Name = SR.Backup_TaskName;
|
||||
metadata.IsCancelable = true;
|
||||
metadata.Data = restoreDataObject;
|
||||
|
||||
|
||||
// create restore task and perform
|
||||
SqlTask sqlTask = SqlTaskManager.Instance.CreateAndRun(metadata, this.restoreDatabaseService.RestoreTaskAsync, restoreDatabaseService.CancelTaskAsync);
|
||||
response.TaskId = sqlTask.TaskId.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
response.ErrorMessage = "Failed to create restore task";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.ErrorMessage = ex.Message;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.ErrorMessage = "Restore database is not supported"; //TOOD: have a better error message
|
||||
}
|
||||
|
||||
await requestContext.SendResult(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a backup request
|
||||
/// </summary>
|
||||
@@ -163,6 +247,37 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsBackupRestoreOperationSupported(RestoreParams restoreParams, out ConnectionInfo connectionInfo)
|
||||
{
|
||||
SqlConnection sqlConn = null;
|
||||
try
|
||||
{
|
||||
ConnectionInfo connInfo;
|
||||
DisasterRecoveryService.ConnectionServiceInstance.TryFindConnection(
|
||||
restoreParams.OwnerUri,
|
||||
out connInfo);
|
||||
|
||||
if (connInfo != null)
|
||||
{
|
||||
sqlConn = GetSqlConnection(connInfo);
|
||||
if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure)
|
||||
{
|
||||
connectionInfo = connInfo;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if(sqlConn != null)
|
||||
{
|
||||
sqlConn.Close();
|
||||
}
|
||||
}
|
||||
connectionInfo = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal BackupConfigInfo GetBackupConfigInfo(CDataContainer dataContainer, SqlConnection sqlConnection, string databaseName)
|
||||
{
|
||||
BackupOperation backupOperation = new BackupOperation();
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Backup set Information
|
||||
/// </summary>
|
||||
public class BackupSetInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Backup type (Full, Transaction Log, Differential ...)
|
||||
/// </summary>
|
||||
public string BackupType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Backup component (Database, File, Log ...)
|
||||
/// </summary>
|
||||
public string BackupComponent { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
//
|
||||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Includes method to all restore operations
|
||||
/// </summary>
|
||||
public class RestoreDatabaseHelper
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Create a backup task for execution and cancellation
|
||||
/// </summary>
|
||||
/// <param name="sqlTask"></param>
|
||||
/// <returns></returns>
|
||||
internal async Task<TaskResult> RestoreTaskAsync(SqlTask sqlTask)
|
||||
{
|
||||
sqlTask.AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true);
|
||||
RestoreDatabaseTaskDataObject restoreDataObject = sqlTask.TaskMetadata.Data as RestoreDatabaseTaskDataObject;
|
||||
TaskResult taskResult = null;
|
||||
|
||||
if (restoreDataObject != null)
|
||||
{
|
||||
// Create a task to perform backup
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
TaskResult result = new TaskResult();
|
||||
try
|
||||
{
|
||||
ExecuteRestore(restoreDataObject);
|
||||
result.TaskStatus = SqlTaskStatus.Succeeded;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.TaskStatus = SqlTaskStatus.Failed;
|
||||
result.ErrorMessage = ex.Message;
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
result.ErrorMessage += System.Environment.NewLine + ex.InnerException.Message;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
taskResult = new TaskResult();
|
||||
taskResult.TaskStatus = SqlTaskStatus.Failed;
|
||||
}
|
||||
|
||||
return taskResult;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Async task to cancel restore
|
||||
/// </summary>
|
||||
public async Task<TaskResult> CancelTaskAsync(SqlTask sqlTask)
|
||||
{
|
||||
RestoreDatabaseTaskDataObject restoreDataObject = sqlTask.TaskMetadata.Data as RestoreDatabaseTaskDataObject;
|
||||
TaskResult taskResult = null;
|
||||
|
||||
|
||||
if (restoreDataObject != null && restoreDataObject.IsValid)
|
||||
{
|
||||
// Create a task for backup cancellation request
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
|
||||
foreach (Restore restore in restoreDataObject.RestorePlan.RestoreOperations)
|
||||
{
|
||||
restore.Abort();
|
||||
}
|
||||
|
||||
|
||||
return new TaskResult
|
||||
{
|
||||
TaskStatus = SqlTaskStatus.Canceled
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
taskResult = new TaskResult();
|
||||
taskResult.TaskStatus = SqlTaskStatus.Failed;
|
||||
}
|
||||
|
||||
return taskResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a restore plan, The result includes the information about the backup set,
|
||||
/// the files and the database to restore to
|
||||
/// </summary>
|
||||
/// <param name="requestParam">Restore request</param>s
|
||||
/// <returns>Restore plan</returns>
|
||||
public RestorePlanResponse CreateRestorePlanResponse(RestoreDatabaseTaskDataObject restoreDataObject)
|
||||
{
|
||||
RestorePlanResponse response = new RestorePlanResponse()
|
||||
{
|
||||
DatabaseName = restoreDataObject.RestoreParams.DatabaseName
|
||||
};
|
||||
if (restoreDataObject != null && restoreDataObject.IsValid)
|
||||
{
|
||||
UpdateRestorePlan(restoreDataObject);
|
||||
|
||||
if (restoreDataObject != null && restoreDataObject.IsValid)
|
||||
{
|
||||
response.DatabaseName = restoreDataObject.RestorePlanner.DatabaseName;
|
||||
response.DbFiles = restoreDataObject.DbFiles.Select(x => x.PhysicalName);
|
||||
response.CanRestore = CanRestore(restoreDataObject);
|
||||
|
||||
if (!response.CanRestore)
|
||||
{
|
||||
response.ErrorMessage = "Backup not supported.";
|
||||
}
|
||||
|
||||
response.RelocateFilesNeeded = !restoreDataObject.DbFilesLocationAreValid();
|
||||
response.DefaultDataFolder = restoreDataObject.DefaultDataFileFolder;
|
||||
response.DefaultLogFolder = restoreDataObject.DefaultLogFileFolder;
|
||||
}
|
||||
else
|
||||
{
|
||||
response.ErrorMessage = "Failed to create restore plan";
|
||||
response.CanRestore = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.ErrorMessage = "Failed to create restore database plan";
|
||||
}
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the restoring the restoreDataObject is supported in the service
|
||||
/// </summary>
|
||||
private static bool CanRestore(RestoreDatabaseTaskDataObject restoreDataObject)
|
||||
{
|
||||
if (restoreDataObject != null)
|
||||
{
|
||||
var backupTypes = restoreDataObject.GetBackupSetInfo();
|
||||
return backupTypes.Any(x => x.BackupType.StartsWith(RestoreConstants.TypeFull));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates anew restore task object to do the restore operations
|
||||
/// </summary>
|
||||
/// <param name="restoreParams">Restore request parameters</param>
|
||||
/// <returns>Restore task object</returns>
|
||||
public RestoreDatabaseTaskDataObject CreateRestoreDatabaseTaskDataObject(RestoreParams restoreParams)
|
||||
{
|
||||
ConnectionInfo connInfo;
|
||||
DisasterRecoveryService.ConnectionServiceInstance.TryFindConnection(
|
||||
restoreParams.OwnerUri,
|
||||
out connInfo);
|
||||
|
||||
if (connInfo != null)
|
||||
{
|
||||
Server server = new Server(new ServerConnection(connInfo.ConnectionDetails.ServerName));
|
||||
|
||||
RestoreDatabaseTaskDataObject restoreDataObject = new RestoreDatabaseTaskDataObject(server, restoreParams.DatabaseName);
|
||||
restoreDataObject.RestoreParams = restoreParams;
|
||||
return restoreDataObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a restore data object that includes the plan to do the restore operation
|
||||
/// </summary>
|
||||
/// <param name="requestParam"></param>
|
||||
/// <returns></returns>
|
||||
private void UpdateRestorePlan(RestoreDatabaseTaskDataObject restoreDataObject)
|
||||
{
|
||||
// Server server = new Server(new ServerConnection(connInfo.ConnectionDetails.ServerName));
|
||||
//RestoreDatabaseTaskDataObject restoreDataObject = new RestoreDatabaseTaskDataObject(server, requestParam.DatabaseName);
|
||||
if (!string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePath))
|
||||
{
|
||||
restoreDataObject.AddFile(restoreDataObject.RestoreParams.BackupFilePath);
|
||||
}
|
||||
restoreDataObject.RestorePlanner.ReadHeaderFromMedia = !string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePath);
|
||||
var dbNames = restoreDataObject.GetSourceDbNames();
|
||||
string dbName = dbNames.First();
|
||||
restoreDataObject.RestorePlanner.DatabaseName = dbName;
|
||||
restoreDataObject.UpdateRestorePlan(restoreDataObject.RestoreParams.RelocateDbFiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the restore operation
|
||||
/// </summary>
|
||||
/// <param name="requestParam"></param>
|
||||
public void ExecuteRestore(RestoreDatabaseTaskDataObject restoreDataObject)
|
||||
{
|
||||
UpdateRestorePlan(restoreDataObject);
|
||||
|
||||
if (restoreDataObject != null && CanRestore(restoreDataObject))
|
||||
{
|
||||
restoreDataObject.RestorePlan.Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,816 @@
|
||||
//
|
||||
// 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 Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Includes the plan with all the data required to do a restore operation on server
|
||||
/// </summary>
|
||||
public class RestoreDatabaseTaskDataObject
|
||||
{
|
||||
public RestoreDatabaseTaskDataObject(Server server, String databaseName)
|
||||
{
|
||||
this.Server = server;
|
||||
this.Util = new RestoreUtil(server);
|
||||
restorePlanner = new DatabaseRestorePlanner(server);
|
||||
|
||||
if (String.IsNullOrEmpty(databaseName))
|
||||
{
|
||||
this.restorePlanner = new DatabaseRestorePlanner(server);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.restorePlanner = new DatabaseRestorePlanner(server, databaseName);
|
||||
this.targetDbName = databaseName;
|
||||
}
|
||||
|
||||
this.restorePlanner.TailLogBackupFile = this.Util.GetDefaultTailLogbackupFile(databaseName);
|
||||
this.restoreOptions = new RestoreOptions();
|
||||
//the server will send events in intervals of 5 percent
|
||||
this.restoreOptions.PercentCompleteNotification = 5;
|
||||
}
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Server != null && this.RestorePlanner != null;
|
||||
}
|
||||
}
|
||||
|
||||
public RestoreParams RestoreParams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Database names includes in the restore plan
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<String> GetSourceDbNames()
|
||||
{
|
||||
return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current sqlserver instance
|
||||
/// </summary>
|
||||
public Server Server;
|
||||
|
||||
/// <summary>
|
||||
/// Recent exception that was thrown
|
||||
/// Displayed at the top of the dialog
|
||||
/// </summary>
|
||||
public Exception ActiveException { get; set; }
|
||||
|
||||
public Exception CreateOrUpdateRestorePlanException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Add a backup file to restore plan media list
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
public void AddFile(string filePath)
|
||||
{
|
||||
this.RestorePlanner.BackupMediaList.Add(new BackupDeviceItem
|
||||
{
|
||||
DeviceType = DeviceType.File,
|
||||
Name = filePath
|
||||
});
|
||||
}
|
||||
|
||||
public RestoreUtil Util { get; set; }
|
||||
|
||||
private DatabaseRestorePlanner restorePlanner;
|
||||
|
||||
/// <summary>
|
||||
/// SMO database restore planner used to create a restore plan
|
||||
/// </summary>
|
||||
public DatabaseRestorePlanner RestorePlanner
|
||||
{
|
||||
get { return restorePlanner; }
|
||||
}
|
||||
|
||||
private string tailLogBackupFile;
|
||||
private bool planUpdateRequired = false;
|
||||
|
||||
/// <summary>
|
||||
/// File to backup tail log before doing the restore
|
||||
/// </summary>
|
||||
public string TailLogBackupFile
|
||||
{
|
||||
get { return tailLogBackupFile; }
|
||||
set
|
||||
{
|
||||
if (tailLogBackupFile == null || !tailLogBackupFile.Equals(value))
|
||||
{
|
||||
this.RestorePlanner.TailLogBackupFile = value;
|
||||
this.planUpdateRequired = true;
|
||||
this.tailLogBackupFile = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RestoreOptions restoreOptions;
|
||||
|
||||
public RestoreOptions RestoreOptions
|
||||
{
|
||||
get { return restoreOptions; }
|
||||
}
|
||||
|
||||
private string dataFilesFolder = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Folder for all data files when relocate all files option is used
|
||||
/// </summary>
|
||||
public string DataFilesFolder
|
||||
{
|
||||
get { return this.dataFilesFolder; }
|
||||
set
|
||||
{
|
||||
if (this.dataFilesFolder == null || !this.dataFilesFolder.Equals(value))
|
||||
{
|
||||
try
|
||||
{
|
||||
Uri pathUri;
|
||||
bool fUriCreated = Uri.TryCreate(value, UriKind.Absolute, out pathUri);
|
||||
|
||||
if (fUriCreated && pathUri.Scheme == "https")
|
||||
{
|
||||
this.dataFilesFolder = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.dataFilesFolder = PathWrapper.GetDirectoryName(value);
|
||||
}
|
||||
if (string.IsNullOrEmpty(this.dataFilesFolder))
|
||||
{
|
||||
this.dataFilesFolder = this.Server.DefaultFile;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ActiveException = ex;
|
||||
}
|
||||
|
||||
this.RelocateDbFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string logFilesFolder = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Folder for all log files when relocate all files option is used
|
||||
/// </summary>
|
||||
public string LogFilesFolder
|
||||
{
|
||||
get { return this.logFilesFolder; }
|
||||
set
|
||||
{
|
||||
if (this.logFilesFolder == null || !this.logFilesFolder.Equals(value))
|
||||
{
|
||||
try
|
||||
{
|
||||
Uri pathUri;
|
||||
bool fUriCreated = Uri.TryCreate(value, UriKind.Absolute, out pathUri);
|
||||
|
||||
if (fUriCreated && pathUri.Scheme == "https")
|
||||
{
|
||||
this.logFilesFolder = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logFilesFolder = PathWrapper.GetDirectoryName(value);
|
||||
}
|
||||
if (string.IsNullOrEmpty(this.logFilesFolder))
|
||||
{
|
||||
this.logFilesFolder = Server.DefaultLog;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ActiveException = ex;
|
||||
}
|
||||
this.RelocateDbFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [prompt before each backup].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [prompt before each backup]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool PromptBeforeEachBackup { get; set; }
|
||||
|
||||
private void RelocateDbFiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (DbFile dbFile in this.DbFiles)
|
||||
{
|
||||
string fileName = this.GetTargetDbFilePhysicalName(dbFile.PhysicalName);
|
||||
if (!dbFile.DbFileType.Equals("Log"))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(this.dataFilesFolder))
|
||||
{
|
||||
dbFile.PhysicalNameRelocate = PathWrapper.Combine(this.dataFilesFolder, fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbFile.PhysicalNameRelocate = fileName;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(this.logFilesFolder))
|
||||
{
|
||||
dbFile.PhysicalNameRelocate = PathWrapper.Combine(this.logFilesFolder, fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbFile.PhysicalNameRelocate = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ActiveException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
private List<DbFile> dbFiles = new List<DbFile>();
|
||||
|
||||
/// <summary>
|
||||
/// List of files of the source database or in the backup file
|
||||
/// </summary>
|
||||
public List<DbFile> DbFiles
|
||||
{
|
||||
get { return dbFiles; }
|
||||
}
|
||||
|
||||
internal RestorePlan restorePlan;
|
||||
|
||||
/// <summary>
|
||||
/// Restore plan to do the restore
|
||||
/// </summary>
|
||||
public RestorePlan RestorePlan
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.restorePlan == null)
|
||||
{
|
||||
this.UpdateRestorePlan(false);
|
||||
}
|
||||
return this.restorePlan;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
this.restorePlan = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool[] RestoreSelected;
|
||||
|
||||
/// <summary>
|
||||
/// The database being restored
|
||||
/// </summary>
|
||||
public string targetDbName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The database used to restore from
|
||||
/// </summary>
|
||||
public string sourceDbName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [close existing connections].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [close existing connections]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CloseExistingConnections { get; set; }
|
||||
|
||||
/*
|
||||
private BackupTimeLine.TimeLineDuration timeLineDuration = BackupTimeLine.TimeLineDuration.Day;
|
||||
|
||||
public BackupTimeLine.TimeLineDuration TimeLineDuration
|
||||
{
|
||||
get { return this.timeLineDuration; }
|
||||
set { this.timeLineDuration = value; }
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Sql server credential name used to restore from Microsoft Azure url
|
||||
/// </summary>
|
||||
internal string CredentialName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Azure container SAS policy
|
||||
/// </summary>
|
||||
internal string ContainerSharedAccessPolicy = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets RestorePlan to perform restore and to script
|
||||
/// </summary>
|
||||
public RestorePlan GetRestorePlanForExecutionAndScript()
|
||||
{
|
||||
this.ActiveException = null; //Clear any existing exceptions as the plan is getting recreated.
|
||||
//Clear any existing exceptions as new plan is getting recreated.
|
||||
this.CreateOrUpdateRestorePlanException = null;
|
||||
bool tailLogBackup = this.RestorePlanner.BackupTailLog;
|
||||
if (this.planUpdateRequired)
|
||||
{
|
||||
this.RestorePlan = this.RestorePlanner.CreateRestorePlan(this.RestoreOptions);
|
||||
this.UpdateRestoreSelected();
|
||||
this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName);
|
||||
}
|
||||
RestorePlan rp = new RestorePlan(this.Server);
|
||||
rp.RestoreAction = RestoreActionType.Database;
|
||||
if (this.RestorePlan != null)
|
||||
{
|
||||
if (this.RestorePlan.TailLogBackupOperation != null && tailLogBackup)
|
||||
{
|
||||
rp.TailLogBackupOperation = this.RestorePlan.TailLogBackupOperation;
|
||||
}
|
||||
int i = 0;
|
||||
foreach (Restore res in this.RestorePlan.RestoreOperations)
|
||||
{
|
||||
if (this.RestoreSelected[i] == true)
|
||||
{
|
||||
rp.RestoreOperations.Add(res);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
this.SetRestorePlanProperties(rp);
|
||||
return rp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the RestoreSelected Array to hold information about updated Restore Plan
|
||||
/// </summary>
|
||||
private void UpdateRestoreSelected()
|
||||
{
|
||||
int operationsCount = this.RestorePlan.RestoreOperations.Count;
|
||||
// The given condition will return true only if new backup has been added on database during lifetime of restore dialog.
|
||||
// This will happen when tail log backup is taken successfully and subsequent restores have failed.
|
||||
if (operationsCount > this.RestoreSelected.Length)
|
||||
{
|
||||
bool[] tempRestoreSel = new bool[this.RestorePlan.RestoreOperations.Count];
|
||||
for (int i = 0; i < operationsCount; i++)
|
||||
{
|
||||
if (i < RestoreSelected.Length)
|
||||
{
|
||||
//Retain all the old values.
|
||||
tempRestoreSel[i] = RestoreSelected[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
//Do not add the newly added backupset into Restore plan by default.
|
||||
tempRestoreSel[i] = false;
|
||||
}
|
||||
}
|
||||
this.RestoreSelected = tempRestoreSel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the physical name for the target Db file.
|
||||
/// It is the sourceDbName replaced with targetDbName in sourceFilename.
|
||||
/// If either sourceDbName or TargetDbName is empty, the source Db filename is returned.
|
||||
/// </summary>
|
||||
/// <param name="sourceDbFilePhysicalLocation">source DbFile physical location</param>
|
||||
/// <returns></returns>
|
||||
private string GetTargetDbFilePhysicalName(string sourceDbFilePhysicalLocation)
|
||||
{
|
||||
string fileName = Path.GetFileName(sourceDbFilePhysicalLocation);
|
||||
if (!string.IsNullOrEmpty(this.sourceDbName) && !string.IsNullOrEmpty(this.targetDbName))
|
||||
{
|
||||
string sourceFilename = fileName;
|
||||
fileName = sourceFilename.Replace(this.sourceDbName, this.targetDbName);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public IEnumerable<BackupSetInfo> GetBackupSetInfo()
|
||||
{
|
||||
List<BackupSetInfo> result = new List<BackupSetInfo>();
|
||||
foreach (Restore restore in RestorePlan.RestoreOperations)
|
||||
{
|
||||
BackupSet backupSet = restore.BackupSet;
|
||||
|
||||
String bkSetComponent;
|
||||
String bkSetType;
|
||||
CommonUtilities.GetBackupSetTypeAndComponent(backupSet.BackupSetType, out bkSetType, out bkSetComponent);
|
||||
|
||||
if (this.Server.Version.Major > 8 && backupSet.IsCopyOnly)
|
||||
{
|
||||
bkSetType += " (Copy Only)";
|
||||
}
|
||||
result.Add(new BackupSetInfo { BackupComponent = bkSetComponent, BackupType = bkSetType });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the files of the database
|
||||
/// </summary>
|
||||
public List<DbFile> GetDbFiles()
|
||||
{
|
||||
Database db = null;
|
||||
List<DbFile> ret = new List<DbFile>();
|
||||
if (!this.RestorePlanner.ReadHeaderFromMedia)
|
||||
{
|
||||
db = this.Server.Databases[this.RestorePlanner.DatabaseName];
|
||||
}
|
||||
if (restorePlan != null && restorePlan.RestoreOperations.Count > 0)
|
||||
{
|
||||
if (db != null && db.Status == DatabaseStatus.Normal)
|
||||
{
|
||||
ret = this.Util.GetDbFiles(db);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = this.Util.GetDbFiles(restorePlan.RestoreOperations[0]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public string DefaultDataFileFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
return Util.GetDefaultDataFileFolder();
|
||||
}
|
||||
}
|
||||
|
||||
public string DefaultLogFileFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
return Util.GetDefaultLogFileFolder();
|
||||
}
|
||||
}
|
||||
|
||||
internal RestorePlan CreateRestorePlan(DatabaseRestorePlanner planner, RestoreOptions restoreOptions)
|
||||
{
|
||||
this.CreateOrUpdateRestorePlanException = null;
|
||||
RestorePlan ret = null;
|
||||
|
||||
try
|
||||
{
|
||||
ret = planner.CreateRestorePlan(restoreOptions);
|
||||
if (ret == null || ret.RestoreOperations.Count == 0)
|
||||
{
|
||||
this.ActiveException = planner.GetBackupDeviceReadErrors();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ActiveException = ex;
|
||||
this.CreateOrUpdateRestorePlanException = this.ActiveException;
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates restore plan
|
||||
/// </summary>
|
||||
public void UpdateRestorePlan(bool relocateAllFiles = false)
|
||||
{
|
||||
this.ActiveException = null; //Clear any existing exceptions as the plan is getting recreated.
|
||||
//Clear any existing exceptions as new plan is getting recreated.
|
||||
this.CreateOrUpdateRestorePlanException = null;
|
||||
this.DbFiles.Clear();
|
||||
this.planUpdateRequired = false;
|
||||
this.restorePlan = null;
|
||||
if (String.IsNullOrEmpty(this.RestorePlanner.DatabaseName))
|
||||
{
|
||||
this.RestorePlan = new RestorePlan(this.Server);
|
||||
// this.LaunchAzureConnectToStorageDialog();
|
||||
this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
this.RestorePlan = this.CreateRestorePlan(this.RestorePlanner, this.RestoreOptions);
|
||||
this.Util.AddCredentialNameForUrlBackupSet(this.restorePlan, this.CredentialName);
|
||||
if (this.ActiveException == null)
|
||||
{
|
||||
this.dbFiles = this.GetDbFiles();
|
||||
if(relocateAllFiles)
|
||||
{
|
||||
RelocateDbFiles();
|
||||
}
|
||||
this.SetRestorePlanProperties(this.restorePlan);
|
||||
}
|
||||
}
|
||||
if (this.restorePlan != null)
|
||||
{
|
||||
this.RestoreSelected = new bool[this.restorePlan.RestoreOperations.Count];
|
||||
for (int i = 0; i < this.restorePlan.RestoreOperations.Count; i++)
|
||||
{
|
||||
this.RestoreSelected[i] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RestorePlan = new RestorePlan(this.Server);
|
||||
this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName);
|
||||
this.RestoreSelected = new bool[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determine if restore plan of selected database does have Url
|
||||
/// </summary>
|
||||
private bool IfRestorePlanHasUrl()
|
||||
{
|
||||
return (restorePlan.RestoreOperations.Any(
|
||||
res => res.BackupSet.BackupMediaSet.BackupMediaList.Any(t => t.MediaType == DeviceType.Url)));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets restore plan properties
|
||||
/// </summary>
|
||||
private void SetRestorePlanProperties(RestorePlan rp)
|
||||
{
|
||||
if (rp == null || rp.RestoreOperations.Count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
rp.SetRestoreOptions(this.RestoreOptions);
|
||||
rp.CloseExistingConnections = this.CloseExistingConnections;
|
||||
if (this.targetDbName != null && !this.targetDbName.Equals(string.Empty))
|
||||
{
|
||||
rp.DatabaseName = targetDbName;
|
||||
}
|
||||
rp.RestoreOperations[0].RelocateFiles.Clear();
|
||||
foreach (DbFile dbFile in this.DbFiles)
|
||||
{
|
||||
// For XStore path, we don't want to try the getFullPath.
|
||||
string newPhysicalPath;
|
||||
Uri pathUri;
|
||||
bool fUriCreated = Uri.TryCreate(dbFile.PhysicalNameRelocate, UriKind.Absolute, out pathUri);
|
||||
if (fUriCreated && pathUri.Scheme == "https")
|
||||
{
|
||||
newPhysicalPath = dbFile.PhysicalNameRelocate;
|
||||
}
|
||||
else
|
||||
{
|
||||
newPhysicalPath = Path.GetFullPath(dbFile.PhysicalNameRelocate);
|
||||
}
|
||||
if (!dbFile.PhysicalName.Equals(newPhysicalPath))
|
||||
{
|
||||
RelocateFile relocFile = new RelocateFile(dbFile.LogicalName, dbFile.PhysicalNameRelocate);
|
||||
rp.RestoreOperations[0].RelocateFiles.Add(relocFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bool indicating whether a tail log backup will be taken
|
||||
/// </summary>
|
||||
public bool BackupTailLog
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.RestorePlanner.BackupTailLog;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (this.RestorePlanner.BackupTailLog != value)
|
||||
{
|
||||
this.RestorePlanner.BackupTailLog = value;
|
||||
this.planUpdateRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bool indicating whether the database will be left in restoring state
|
||||
/// </summary>
|
||||
public bool TailLogWithNoRecovery
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.RestorePlanner.TailLogWithNoRecovery;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (this.RestorePlanner.TailLogWithNoRecovery != value)
|
||||
{
|
||||
this.RestorePlanner.TailLogWithNoRecovery = value;
|
||||
this.planUpdateRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime? CurrentRestorePointInTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.RestorePlan == null || this.RestorePlan.RestoreOperations.Count == 0
|
||||
|| this.RestoreSelected.Length == 0 || !this.RestoreSelected[0])
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (int i = this.RestorePlan.RestoreOperations.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (this.RestoreSelected[i])
|
||||
{
|
||||
if (this.RestorePlan.RestoreOperations[i].BackupSet == null
|
||||
|| (this.RestorePlan.RestoreOperations[i].BackupSet.BackupSetType == BackupSetType.Log &&
|
||||
this.RestorePlan.RestoreOperations[i].ToPointInTime != null))
|
||||
{
|
||||
return this.RestorePlanner.RestoreToPointInTime;
|
||||
}
|
||||
return this.RestorePlan.RestoreOperations[i].BackupSet.BackupStartDate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleSelectRestore(int index)
|
||||
{
|
||||
RestorePlan rp = this.restorePlan;
|
||||
if (rp == null || rp.RestoreOperations.Count <= index)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//the last index - this will include tail-Log restore operation if present
|
||||
if (index == rp.RestoreOperations.Count - 1)
|
||||
{
|
||||
if (this.RestoreSelected[index])
|
||||
{
|
||||
this.RestoreSelected[index] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i <= index; i++)
|
||||
{
|
||||
this.RestoreSelected[i] = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (index == 0)
|
||||
{
|
||||
if (!this.RestoreSelected[index])
|
||||
{
|
||||
this.RestoreSelected[index] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = index; i < rp.RestoreOperations.Count; i++)
|
||||
{
|
||||
this.RestoreSelected[i] = false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == 1 && rp.RestoreOperations[index].BackupSet.BackupSetType == BackupSetType.Differential)
|
||||
{
|
||||
if (!this.RestoreSelected[index])
|
||||
{
|
||||
this.RestoreSelected[0] = true;
|
||||
this.RestoreSelected[index] = true;
|
||||
}
|
||||
else if (rp.RestoreOperations[2].BackupSet == null)
|
||||
{
|
||||
this.RestoreSelected[index] = false;
|
||||
this.RestoreSelected[2] = false;
|
||||
}
|
||||
else if (this.Server.Version.Major < 9 || BackupSet.IsBackupSetsInSequence(rp.RestoreOperations[0].BackupSet, rp.RestoreOperations[2].BackupSet))
|
||||
{
|
||||
this.RestoreSelected[index] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = index; i < rp.RestoreOperations.Count; i++)
|
||||
{
|
||||
this.RestoreSelected[i] = false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (rp.RestoreOperations[index].BackupSet.BackupSetType == BackupSetType.Log)
|
||||
{
|
||||
if (this.RestoreSelected[index])
|
||||
{
|
||||
for (int i = index; i < rp.RestoreOperations.Count; i++)
|
||||
{
|
||||
this.RestoreSelected[i] = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i <= index; i++)
|
||||
{
|
||||
this.RestoreSelected[i] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the backup files location.
|
||||
/// </summary>
|
||||
internal void CheckBackupFilesLocation()
|
||||
{
|
||||
if (this.RestorePlan != null)
|
||||
{
|
||||
foreach (Restore restore in this.RestorePlan.RestoreOperations)
|
||||
{
|
||||
if (restore.BackupSet != null)
|
||||
{
|
||||
restore.BackupSet.CheckBackupFilesExistence();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool DbFilesLocationAreValid()
|
||||
{
|
||||
foreach (DbFile dbFile in this.DbFiles)
|
||||
{
|
||||
string newPhysicalPath = Path.GetFullPath(dbFile.PhysicalNameRelocate);
|
||||
if (string.Compare(dbFile.PhysicalName, dbFile.PhysicalNameRelocate, true) != 0)
|
||||
{
|
||||
bool isValidFolder = false;
|
||||
bool isValidPath = Util.IsDestinationPathValid(Path.GetDirectoryName(newPhysicalPath), ref isValidFolder);
|
||||
if (!(isValidFolder && isValidPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class RestoreDatabaseRecoveryState
|
||||
{
|
||||
public RestoreDatabaseRecoveryState(DatabaseRecoveryState recoveryState)
|
||||
{
|
||||
this.RecoveryState = recoveryState;
|
||||
}
|
||||
|
||||
public DatabaseRecoveryState RecoveryState;
|
||||
private static string RestoreWithRecovery = "RESTORE WITH RECOVERY";
|
||||
private static string RestoreWithNoRecovery = "RESTORE WITH NORECOVERY";
|
||||
private static string RestoreWithStandby = "RESTORE WITH STANDBY";
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (this.RecoveryState)
|
||||
{
|
||||
case DatabaseRecoveryState.WithRecovery:
|
||||
return RestoreDatabaseRecoveryState.RestoreWithRecovery;
|
||||
case DatabaseRecoveryState.WithNoRecovery:
|
||||
return RestoreDatabaseRecoveryState.RestoreWithNoRecovery;
|
||||
case DatabaseRecoveryState.WithStandBy:
|
||||
return RestoreDatabaseRecoveryState.RestoreWithStandby;
|
||||
}
|
||||
return RestoreDatabaseRecoveryState.RestoreWithRecovery;
|
||||
}
|
||||
|
||||
/*
|
||||
public string Info()
|
||||
{
|
||||
switch (this.RecoveryState)
|
||||
{
|
||||
case DatabaseRecoveryState.WithRecovery:
|
||||
return SR.RestoreWithRecoveryInfo;
|
||||
case DatabaseRecoveryState.WithNoRecovery:
|
||||
return SR.RestoreWithNoRecoveryInfo;
|
||||
case DatabaseRecoveryState.WithStandBy:
|
||||
return SR.RestoreWithStandbyInfo;
|
||||
}
|
||||
return SR.RestoreWithRecoveryInfo;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
//
|
||||
// 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 Microsoft.Data.Tools.DataSets;
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
|
||||
{
|
||||
public class RestoreUtil
|
||||
{
|
||||
public RestoreUtil(Server server)
|
||||
{
|
||||
this.server = server;
|
||||
this.excludedDB = new List<string> { "master", "tempdb" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current sql server instance
|
||||
/// </summary>
|
||||
private readonly Server server;
|
||||
private readonly IList<string> excludedDB;
|
||||
|
||||
public List<string> GetTargetDbNamesForPageRestore()
|
||||
{
|
||||
List<string> databaseNames = new List<string>();
|
||||
foreach (Database db in this.server.Databases)
|
||||
{
|
||||
if (!this.excludedDB.Contains(db.Name) &&
|
||||
(db.Status == DatabaseStatus.Normal || db.Status == DatabaseStatus.Suspect || db.Status == DatabaseStatus.EmergencyMode) &&
|
||||
db.RecoveryModel == RecoveryModel.Full)
|
||||
{
|
||||
databaseNames.Add(db.Name);
|
||||
}
|
||||
}
|
||||
return databaseNames;
|
||||
}
|
||||
|
||||
public List<string> GetTargetDbNames()
|
||||
{
|
||||
List<string> databaseNames = new List<string>();
|
||||
foreach (Database db in this.server.Databases)
|
||||
{
|
||||
if (!this.excludedDB.Contains(db.Name))
|
||||
{
|
||||
databaseNames.Add(db.Name);
|
||||
}
|
||||
}
|
||||
return databaseNames;
|
||||
}
|
||||
|
||||
internal DateTime GetServerCurrentDateTime()
|
||||
{
|
||||
DateTime dt = DateTime.MinValue;
|
||||
|
||||
//TODO: the code is moved from ssms and used for restore differential backups
|
||||
//Uncomment when restore operation for differential backups is supported
|
||||
/*
|
||||
string query = "SELECT GETDATE()";
|
||||
DataSet dataset = this.server.ExecutionManager.ExecuteWithResults(query);
|
||||
if (dataset != null && dataset.Tables.Count > 0 && dataset.Tables[0].Rows.Count > 0)
|
||||
{
|
||||
dt = Convert.ToDateTime(dataset.Tables[0].Rows[0][0], SmoApplication.DefaultCulture);
|
||||
}
|
||||
*/
|
||||
return dt;
|
||||
}
|
||||
|
||||
//TODO: the code is moved from ssms and used for restore differential backups
|
||||
//Uncomment when restore operation for differential backups is supported
|
||||
/*
|
||||
/// <summary>
|
||||
/// Queries msdb for source database names
|
||||
/// </summary>
|
||||
public List<String> GetSourceDbNames()
|
||||
{
|
||||
List<string> databaseNames = new List<string>();
|
||||
Request req = new Request();
|
||||
req.Urn = "Server/BackupSet";
|
||||
req.Fields = new string[1];
|
||||
req.Fields[0] = "DatabaseName";
|
||||
req.OrderByList = new OrderBy[1];
|
||||
req.OrderByList[0] = new OrderBy();
|
||||
req.OrderByList[0].Field = "DatabaseName";
|
||||
req.OrderByList[0].Dir = OrderBy.Direction.Asc;
|
||||
DataTable dt = server.ExecutionManager.GetEnumeratorData(req);
|
||||
string last = "";
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
string dbName = Convert.ToString(row["DatabaseName"], System.Globalization.CultureInfo.InvariantCulture);
|
||||
if (!this.excludedDB.Contains(dbName) && !dbName.Equals(last))
|
||||
{
|
||||
bool found = false;
|
||||
foreach (string str in databaseNames)
|
||||
{
|
||||
if (StrEqual(str, dbName))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == false)
|
||||
{
|
||||
databaseNames.Add(dbName);
|
||||
}
|
||||
}
|
||||
last = dbName;
|
||||
}
|
||||
return databaseNames;
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Reads backup file header to get source database names
|
||||
/// If valid credential name is not provided for URL throws exception while executing T-sql statement
|
||||
/// </summary>
|
||||
/// <param name="bkdevList">List of backup device items</param>
|
||||
/// <param name="credential">Optional Sqlserver credential name to read backup header from URL</param>
|
||||
public List<String> GetSourceDbNames(ICollection<BackupDeviceItem> bkdevList, string credential = null)
|
||||
{
|
||||
List<string> databaseNames = new List<string>();
|
||||
foreach (BackupDeviceItem bkdev in bkdevList)
|
||||
{
|
||||
// use the Restore public API to do the Restore Headeronly query
|
||||
Restore res = new Restore();
|
||||
res.CredentialName = credential;
|
||||
res.Devices.Add(bkdev);
|
||||
|
||||
DataTable dt = res.ReadBackupHeader(this.server);
|
||||
if (dt != null)
|
||||
{
|
||||
foreach (DataRow dr in dt.Rows)
|
||||
{
|
||||
if (dr != null && !(dr["DatabaseName"] is DBNull))
|
||||
{
|
||||
string dbName = (string)dr["DatabaseName"];
|
||||
bool found = false;
|
||||
foreach (string str in databaseNames)
|
||||
{
|
||||
if (StringComparer.OrdinalIgnoreCase.Compare(str, dbName) == 0)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == false)
|
||||
{
|
||||
databaseNames.Add(dbName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return databaseNames;
|
||||
}
|
||||
|
||||
public string GetNewPhysicalRestoredFileName(string filePathParam, string dbName, bool isNewDatabase, string type, ref int fileIndex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePathParam))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string result = string.Empty;
|
||||
string filePath = filePathParam;
|
||||
int idx = filePath.LastIndexOf('\\');
|
||||
string folderPath = filePath.Substring(0, idx);
|
||||
|
||||
string fileName = filePath.Substring(idx + 1);
|
||||
idx = fileName.LastIndexOf('.');
|
||||
string fileExtension = fileName.Substring(idx + 1);
|
||||
|
||||
bool isFolder = true;
|
||||
bool isValidPath = IsDestinationPathValid(folderPath, ref isFolder);
|
||||
|
||||
if (!isValidPath || !isFolder)
|
||||
{
|
||||
if (type != RestoreConstants.Log)
|
||||
{
|
||||
folderPath = server.Settings.DefaultFile;
|
||||
if (folderPath.Length == 0)
|
||||
{
|
||||
folderPath = server.Information.MasterDBPath;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
folderPath = server.Settings.DefaultLog;
|
||||
if (folderPath.Length == 0)
|
||||
{
|
||||
folderPath = server.Information.MasterDBLogPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isNewDatabase)
|
||||
{
|
||||
return filePathParam;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNewDatabase)
|
||||
{
|
||||
result = folderPath + "\\" + dbName + "." + fileExtension;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (0 != string.Compare(fileExtension, "mdf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = folderPath + "\\" + dbName + "_" + Convert.ToString(fileIndex, System.Globalization.CultureInfo.InvariantCulture) + "." + fileExtension;
|
||||
fileIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = folderPath + "\\" + dbName + "." + fileExtension;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsDestinationPathValid(string path, ref bool isFolder)
|
||||
{
|
||||
Enumerator en = null;
|
||||
DataTable dt;
|
||||
Request req = new Request();
|
||||
|
||||
en = new Enumerator();
|
||||
req.Urn = "Server/File[@FullName='" + Urn.EscapeString(path) + "']";
|
||||
dt = en.Process(this.server.ConnectionContext.SqlConnectionObject, req);
|
||||
|
||||
if (dt.Rows.Count > 0)
|
||||
{
|
||||
isFolder = !(Convert.ToBoolean(dt.Rows[0]["IsFile"], System.Globalization.CultureInfo.InvariantCulture));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isFolder = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of database files
|
||||
/// </summary>
|
||||
/// <param name="db">SMO database</param>
|
||||
/// <returns>a list of database files</returns>
|
||||
public List<DbFile> GetDbFiles(Database db)
|
||||
{
|
||||
List<DbFile> ret = new List<DbFile>();
|
||||
if (db == null)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
char fileType = '\0';
|
||||
foreach (FileGroup fg in db.FileGroups)
|
||||
{
|
||||
if ((fg.FileGroupType == FileGroupType.FileStreamDataFileGroup) || (fg.FileGroupType == FileGroupType.MemoryOptimizedDataFileGroup))
|
||||
{
|
||||
fileType = DbFile.FileStreamFileType;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileType = DbFile.RowFileType;
|
||||
}
|
||||
foreach (DataFile f in fg.Files)
|
||||
{
|
||||
DbFile dbFile = new DbFile(f.Name, fileType, f.FileName);
|
||||
ret.Add(dbFile);
|
||||
}
|
||||
}
|
||||
foreach (LogFile f in db.LogFiles)
|
||||
{
|
||||
DbFile dbFile = new DbFile(f.Name, DbFile.LogFileType, f.FileName);
|
||||
ret.Add(dbFile);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//TODO: the code is moved from ssms and used for other typs of restore operation
|
||||
//Uncomment when restore operation for those types are supported
|
||||
/*
|
||||
public List<DbFile> GetDbFiles(BackupSet bkSet)
|
||||
{
|
||||
List<DbFile> ret = new List<DbFile>();
|
||||
if (bkSet == null || bkSet.BackupMediaSet == null || bkSet.BackupMediaSet.BackupMediaList.Count() < 1)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
DataSet dataset = bkSet.FileList;
|
||||
if (dataset != null && dataset.Tables.Count > 0)
|
||||
{
|
||||
string logicalName = null;
|
||||
string physicalName = null;
|
||||
char type = '\0';
|
||||
foreach (DataRow dr in dataset.Tables[0].Rows)
|
||||
{
|
||||
if (!(dr["LogicalName"] is DBNull))
|
||||
{
|
||||
logicalName = (string)dr["LogicalName"];
|
||||
}
|
||||
if (!(dr["PhysicalName"] is DBNull))
|
||||
{
|
||||
physicalName = (string)dr["PhysicalName"];
|
||||
}
|
||||
if (!(dr["Type"] is DBNull))
|
||||
{
|
||||
// The data type of Type in a list obtained from RESTORE FILELISTONLY is char(1).
|
||||
string temp = (string)dr["Type"];
|
||||
if (!String.IsNullOrEmpty(temp))
|
||||
{
|
||||
type = temp[0];
|
||||
}
|
||||
}
|
||||
if (!String.IsNullOrEmpty(logicalName) && !String.IsNullOrEmpty(physicalName) && (type != '\0'))
|
||||
{
|
||||
DbFile dbFile = new DbFile(logicalName, type, physicalName);
|
||||
ret.Add(dbFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of database files in all the backup devices in the Restore object
|
||||
/// </summary>
|
||||
public List<DbFile> GetDbFiles(Restore restore)
|
||||
{
|
||||
List<DbFile> ret = new List<DbFile>();
|
||||
if (restore == null || restore.Devices == null || restore.Devices.Count < 1)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
// Using the Restore public API to do the Restore FilelistOnly
|
||||
Restore res = new Restore();
|
||||
res.CredentialName = restore.CredentialName;
|
||||
res.Devices.Add(restore.Devices[0]);
|
||||
res.FileNumber = restore.FileNumber;
|
||||
DataTable datatable = res.ReadFileList(this.server);
|
||||
if (datatable != null && datatable.Rows.Count > 0)
|
||||
{
|
||||
string logicalName = null;
|
||||
string physicalName = null;
|
||||
char type = '\0';
|
||||
foreach (DataRow dr in datatable.Rows)
|
||||
{
|
||||
if (!(dr["LogicalName"] is DBNull))
|
||||
{
|
||||
logicalName = (string)dr["LogicalName"];
|
||||
}
|
||||
if (!(dr["PhysicalName"] is DBNull))
|
||||
{
|
||||
physicalName = (string)dr["PhysicalName"];
|
||||
}
|
||||
if (!(dr["Type"] is DBNull))
|
||||
{
|
||||
// The data type of Type in a list obtained from RESTORE FILELISTONLY is char(1).
|
||||
string temp = (string)dr["Type"];
|
||||
if (!String.IsNullOrEmpty(temp))
|
||||
{
|
||||
type = temp[0];
|
||||
}
|
||||
}
|
||||
if (!String.IsNullOrEmpty(logicalName) && !String.IsNullOrEmpty(physicalName) && (type != '\0'))
|
||||
{
|
||||
DbFile dbFile = new DbFile(logicalName, type, physicalName);
|
||||
ret.Add(dbFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set credential name in the restore objects which have a backup set in Microsoft Azure
|
||||
/// From sql16, default credential is SAS credential so no explict credential needed for restore object.
|
||||
/// </summary>
|
||||
/// <param name="restorePlan">Restore plan created for the restore operation</param>
|
||||
/// <param name="credentialName">Sql server credential name</param>
|
||||
public void AddCredentialNameForUrlBackupSet(RestorePlan restorePlan, string credentialName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(credentialName) || restorePlan == null || restorePlan.RestoreOperations == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (restorePlan.Server.VersionMajor >= 13) // for sql16, default backup/restore URL will use SAS
|
||||
{
|
||||
return;
|
||||
}
|
||||
// If any of the backup media in the restore object is in URL, we assign the credential name to the CredentialName property of the Restore object
|
||||
foreach (Restore res in restorePlan.RestoreOperations)
|
||||
{
|
||||
|
||||
if (res.BackupSet != null && res.BackupSet.BackupMediaSet != null && res.BackupSet.BackupMediaSet.BackupMediaList != null)
|
||||
{
|
||||
foreach (BackupMedia bkMedia in res.BackupSet.BackupMediaSet.BackupMediaList)
|
||||
{
|
||||
if (bkMedia != null && bkMedia.MediaType == DeviceType.Url)
|
||||
{
|
||||
res.CredentialName = credentialName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.Devices != null)
|
||||
{
|
||||
foreach (BackupDeviceItem bkDevice in res.Devices)
|
||||
{
|
||||
if (bkDevice.DeviceType == DeviceType.Url)
|
||||
{
|
||||
res.CredentialName = credentialName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the backup file to which the tail log is going to be backed up is a file in Microsoft Azure,
|
||||
// we assign the credential name to the Credential Name property of the Backup object
|
||||
if (restorePlan.TailLogBackupOperation != null && restorePlan.TailLogBackupOperation.Devices != null)
|
||||
{
|
||||
foreach (BackupDeviceItem bkdevItem in restorePlan.TailLogBackupOperation.Devices)
|
||||
{
|
||||
if (bkdevItem != null && bkdevItem.DeviceType == DeviceType.Url)
|
||||
{
|
||||
restorePlan.TailLogBackupOperation.CredentialName = credentialName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal string GetDefaultDataFileFolder()
|
||||
{
|
||||
string ret = this.server.Settings.DefaultFile;
|
||||
if (string.IsNullOrEmpty(ret))
|
||||
{
|
||||
ret = this.server.Information.MasterDBPath;
|
||||
}
|
||||
|
||||
ret = ret.TrimEnd(server.PathSeparator[0]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal string GetDefaultLogFileFolder()
|
||||
{
|
||||
string ret = this.server.Settings.DefaultLog;
|
||||
if (string.IsNullOrEmpty(ret))
|
||||
{
|
||||
ret = this.server.Information.MasterDBLogPath;
|
||||
}
|
||||
|
||||
ret = ret.TrimEnd(server.PathSeparator[0]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal string GetDefaultBackupFolder()
|
||||
{
|
||||
string ret = this.server.Settings.BackupDirectory;
|
||||
ret = ret.TrimEnd(server.PathSeparator[0]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal string GetDefaultTailLogbackupFile(string databaseName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(databaseName))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var folderpath = GetDefaultBackupFolder();
|
||||
var filename = SanitizeFileName(databaseName) + "_LogBackup_" + GetServerCurrentDateTime().ToString("yyyy-MM-dd_HH-mm-ss") + ".bak";
|
||||
return PathWrapper.Combine(folderpath, filename);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a default location for tail log backup
|
||||
/// If the first backup media is from Microsoft Azure, a Microsoft Azure url for the Tail log backup file is returned
|
||||
/// </summary>
|
||||
internal string GetDefaultTailLogbackupFile(string databaseName, RestorePlan restorePlan)
|
||||
{
|
||||
if (string.IsNullOrEmpty(databaseName) || restorePlan == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (restorePlan.TailLogBackupOperation != null && restorePlan.TailLogBackupOperation.Devices != null)
|
||||
{
|
||||
restorePlan.TailLogBackupOperation.Devices.Clear();
|
||||
}
|
||||
string folderpath = string.Empty;
|
||||
BackupMedia firstBackupMedia = this.GetFirstBackupMedia(restorePlan);
|
||||
string filename = this.SanitizeFileName(databaseName) + "_LogBackup_" + this.GetServerCurrentDateTime().ToString("yyyy-MM-dd_HH-mm-ss") + ".bak";
|
||||
if (firstBackupMedia != null && firstBackupMedia.MediaType == DeviceType.Url)
|
||||
{
|
||||
// the uri will use the same container as the container of the first backup media
|
||||
Uri uri;
|
||||
if (Uri.TryCreate(firstBackupMedia.MediaName, UriKind.Absolute, out uri))
|
||||
{
|
||||
UriBuilder uriBuilder = new UriBuilder();
|
||||
uriBuilder.Scheme = uri.Scheme;
|
||||
uriBuilder.Host = uri.Host;
|
||||
if (uri.AbsolutePath.Length > 0)
|
||||
{
|
||||
string[] parts = uri.AbsolutePath.Split('/');
|
||||
string newPath = string.Join("/", parts, 0, parts.Length - 1);
|
||||
if (newPath.EndsWith("/"))
|
||||
{
|
||||
newPath = newPath.Substring(0, newPath.Length - 1);
|
||||
}
|
||||
uriBuilder.Host = uriBuilder.Host + newPath;
|
||||
}
|
||||
uriBuilder.Path = filename;
|
||||
string urlFilename = uriBuilder.Uri.AbsoluteUri;
|
||||
if (restorePlan.TailLogBackupOperation != null && restorePlan.TailLogBackupOperation.Devices != null)
|
||||
{
|
||||
restorePlan.TailLogBackupOperation.Devices.Add(new BackupDeviceItem(urlFilename, DeviceType.Url));
|
||||
}
|
||||
return urlFilename;
|
||||
}
|
||||
}
|
||||
folderpath = this.GetDefaultBackupFolder();
|
||||
if (restorePlan.TailLogBackupOperation != null && restorePlan.TailLogBackupOperation.Devices != null)
|
||||
{
|
||||
restorePlan.TailLogBackupOperation.Devices.Add(new BackupDeviceItem(PathWrapper.Combine(folderpath, filename), DeviceType.File));
|
||||
}
|
||||
return PathWrapper.Combine(folderpath, filename);
|
||||
}
|
||||
|
||||
internal string GetDefaultStandbyFile(string databaseName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(databaseName))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var folderpath = GetDefaultBackupFolder();
|
||||
var filename = SanitizeFileName(databaseName) + "_RollbackUndo_" + GetServerCurrentDateTime().ToString("yyyy-MM-dd_HH-mm-ss") + ".bak";
|
||||
return PathWrapper.Combine(folderpath, filename);
|
||||
}
|
||||
|
||||
//TODO: the code is moved from ssms and used for other typs of restore operation
|
||||
//Uncomment when restore operation for those types are supported
|
||||
/*
|
||||
internal DateTime GetLastBackupDate(DatabaseRestorePlanner planner)
|
||||
{
|
||||
BackupSetCollection bkSetColl = planner.BackupSets;
|
||||
if (bkSetColl.backupsetList.Count > 0)
|
||||
{
|
||||
return bkSetColl.backupsetList[bkSetColl.backupsetList.Count - 1].BackupStartDate;
|
||||
}
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Sanitizes the name of the file.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns></returns>
|
||||
internal string SanitizeFileName(string name)
|
||||
{
|
||||
char[] result = name.ToCharArray();
|
||||
string illegalCharacters = "\\/:*?\"<>|";
|
||||
|
||||
int resultLength = result.GetLength(0);
|
||||
int illegalLength = illegalCharacters.Length;
|
||||
|
||||
for (int resultIndex = 0; resultIndex < resultLength; resultIndex++)
|
||||
{
|
||||
for (int illegalIndex = 0; illegalIndex < illegalLength; illegalIndex++)
|
||||
{
|
||||
if (result[resultIndex] == illegalCharacters[illegalIndex])
|
||||
{
|
||||
result[resultIndex] = '_';
|
||||
}
|
||||
}
|
||||
}
|
||||
return new string(result);
|
||||
}
|
||||
|
||||
//TODO: the code is moved from ssms and used for other typs of restore operation
|
||||
//Uncomment when restore operation for those types are supported
|
||||
/*
|
||||
internal void MarkDuplicateSuspectPages(List<SuspectPageTaskDataObject> suspectPageObjList)
|
||||
{
|
||||
List<SuspectPageTaskDataObject> newList = new List<SuspectPageTaskDataObject>(suspectPageObjList);
|
||||
newList.Sort();
|
||||
newList[0].IsDuplicate = false;
|
||||
for (int i = 1; i < newList.Count; i++)
|
||||
{
|
||||
if (newList[i].CompareTo(newList[i - 1]) == 0)
|
||||
{
|
||||
newList[i].IsDuplicate = true;
|
||||
newList[i - 1].IsDuplicate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
newList[i].IsDuplicate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
internal void VerifyChecksumWorker(RestorePlan plan, IBackgroundOperationContext backgroundContext, EventHandler cancelEventHandler)
|
||||
{
|
||||
if (plan == null || plan.RestoreOperations.Count() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
backgroundContext.IsCancelable = true;
|
||||
backgroundContext.CancelRequested += cancelEventHandler;
|
||||
try
|
||||
{
|
||||
foreach (Restore res in plan.RestoreOperations)
|
||||
{
|
||||
if (!backgroundContext.IsCancelRequested && res.backupSet != null)
|
||||
{
|
||||
StringBuilder bkMediaNames = new StringBuilder();
|
||||
foreach (BackupDeviceItem item in res.Devices)
|
||||
{
|
||||
backgroundContext.Status = SR.Verifying + ":" + item.Name;
|
||||
try
|
||||
{
|
||||
// Use the Restore public API to do the Restore VerifyOnly query
|
||||
Restore restore = new Restore();
|
||||
restore.CredentialName = res.CredentialName;
|
||||
restore.Devices.Add(item);
|
||||
if (!res.SqlVerify(this.server))
|
||||
{
|
||||
throw new Exception(SR.BackupDeviceItemVerificationFailed(item.Name));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception(SR.BackupDeviceItemVerificationFailed(item.Name), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
backgroundContext.CancelRequested -= cancelEventHandler;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private BackupMedia GetFirstBackupMedia(RestorePlan restorePlan)
|
||||
{
|
||||
/*
|
||||
if (restorePlan == null || restorePlan.RestoreOperations == null || restorePlan.RestoreOperations.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Restore res = restorePlan.RestoreOperations[0];
|
||||
if (res == null || res.backupSet == null || res.backupSet.backupMediaSet == null || res.backupSet.backupMediaSet.BackupMediaList == null || res.backupSet.backupMediaSet.BackupMediaList.ToList().Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return res.backupSet.backupMediaSet.BackupMediaList.ToList()[0];
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class representing a database file
|
||||
/// </summary>
|
||||
public class DbFile
|
||||
{
|
||||
public DbFile(string logicalName, char type, string physicalName)
|
||||
{
|
||||
this.logicalName = logicalName;
|
||||
this.physicalName = physicalName;
|
||||
if (type != '\0')
|
||||
{
|
||||
this.dbFileType = type;
|
||||
}
|
||||
this.PhysicalNameRelocate = physicalName;
|
||||
}
|
||||
|
||||
// Database file types
|
||||
// When restoring backup, the engine returns the following file type values.
|
||||
public const char RowFileType = 'D';
|
||||
public const char LogFileType = 'L';
|
||||
public const char FullTextCatalogFileType = 'F';
|
||||
public const char FileStreamFileType = 'S';
|
||||
|
||||
private string logicalName;
|
||||
public string LogicalName
|
||||
{
|
||||
get { return logicalName; }
|
||||
}
|
||||
|
||||
private string physicalName;
|
||||
public string PhysicalName
|
||||
{
|
||||
get { return physicalName; }
|
||||
}
|
||||
|
||||
internal char dbFileType;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the database file type string to be displayed in the dialog
|
||||
/// </summary>
|
||||
public string DbFileType
|
||||
{
|
||||
get
|
||||
{
|
||||
string value = string.Empty;
|
||||
switch (dbFileType)
|
||||
{
|
||||
case DbFile.RowFileType:
|
||||
value = "RowData";//TODO SR.RowData;
|
||||
break;
|
||||
case DbFile.LogFileType:
|
||||
value = "Log";// SR.Log;
|
||||
break;
|
||||
case DbFile.FileStreamFileType:
|
||||
value = "FileStream";// SR.FileStream;
|
||||
break;
|
||||
case DbFile.FullTextCatalogFileType:
|
||||
value = "FullTextCatlog";// SR.FullTextCatlog;
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public string PhysicalNameRelocate;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user