added restore errors to resx (#407)

* added restore errors to resx

* including the restore error messages in the result

* added more error handling and tests
This commit is contained in:
Leila Lali
2017-07-12 10:20:47 -07:00
committed by GitHub
parent 11097f9cc5
commit 64e671ca2a
10 changed files with 233 additions and 36 deletions

View File

@@ -24,8 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
public string BackupFilePath { get; set; } public string BackupFilePath { get; set; }
/// <summary> /// <summary>
/// Database name to restore from (either the back file path or database name can be used for restore operation, /// Target Database name to restore to
/// If the backup file is set, the database name will be ignored)
/// </summary> /// </summary>
public string DatabaseName { get; set; } public string DatabaseName { get; set; }

View File

@@ -128,7 +128,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
else else
{ {
response.CanRestore = false; response.CanRestore = false;
response.ErrorMessage = "Restore is not supported"; //TOOD: have a better error message response.ErrorMessage = SR.RestoreNotSupported;
} }
await requestContext.SendResult(response); await requestContext.SendResult(response);
@@ -157,18 +157,17 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
TaskMetadata metadata = new TaskMetadata(); TaskMetadata metadata = new TaskMetadata();
metadata.ServerName = connInfo.ConnectionDetails.ServerName; metadata.ServerName = connInfo.ConnectionDetails.ServerName;
metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName; metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName;
metadata.Name = SR.Backup_TaskName; metadata.Name = SR.RestoreTaskName;
metadata.IsCancelable = true; metadata.IsCancelable = true;
metadata.Data = restoreDataObject; metadata.Data = restoreDataObject;
// create restore task and perform // create restore task and perform
SqlTask sqlTask = SqlTaskManager.Instance.CreateAndRun(metadata, this.restoreDatabaseService.RestoreTaskAsync, restoreDatabaseService.CancelTaskAsync); SqlTask sqlTask = SqlTaskManager.Instance.CreateAndRun(metadata, this.restoreDatabaseService.RestoreTaskAsync, restoreDatabaseService.CancelTaskAsync);
response.TaskId = sqlTask.TaskId.ToString(); response.TaskId = sqlTask.TaskId.ToString();
} }
else else
{ {
response.ErrorMessage = "Failed to create restore task"; response.ErrorMessage = SR.RestorePlanFailed;
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -178,7 +177,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
else else
{ {
response.ErrorMessage = "Restore database is not supported"; //TOOD: have a better error message response.ErrorMessage = SR.RestoreNotSupported;
} }
await requestContext.SendResult(response); await requestContext.SendResult(response);

View File

@@ -38,17 +38,36 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
{ {
TaskResult result = new TaskResult(); TaskResult result = new TaskResult();
try try
{
if (restoreDataObject.IsValid)
{ {
ExecuteRestore(restoreDataObject); ExecuteRestore(restoreDataObject);
result.TaskStatus = SqlTaskStatus.Succeeded; result.TaskStatus = SqlTaskStatus.Succeeded;
} }
else
{
result.TaskStatus = SqlTaskStatus.Failed;
if (restoreDataObject.ActiveException != null)
{
result.ErrorMessage = restoreDataObject.ActiveException.Message;
}
else
{
result.ErrorMessage = SR.RestoreNotSupported;
}
}
}
catch (Exception ex) catch (Exception ex)
{ {
result.TaskStatus = SqlTaskStatus.Failed; result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = ex.Message; result.ErrorMessage = ex.Message;
if (ex.InnerException != null) if (ex.InnerException != null)
{ {
result.ErrorMessage += System.Environment.NewLine + ex.InnerException.Message; result.ErrorMessage += Environment.NewLine + ex.InnerException.Message;
}
if (restoreDataObject != null && restoreDataObject.ActiveException != null)
{
result.ErrorMessage += Environment.NewLine + restoreDataObject.ActiveException.Message;
} }
} }
return result; return result;
@@ -114,6 +133,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
{ {
DatabaseName = restoreDataObject.RestoreParams.DatabaseName DatabaseName = restoreDataObject.RestoreParams.DatabaseName
}; };
try
{
if (restoreDataObject != null && restoreDataObject.IsValid) if (restoreDataObject != null && restoreDataObject.IsValid)
{ {
UpdateRestorePlan(restoreDataObject); UpdateRestorePlan(restoreDataObject);
@@ -126,7 +147,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
if (!response.CanRestore) if (!response.CanRestore)
{ {
response.ErrorMessage = "Backup not supported."; response.ErrorMessage = SR.RestoreNotSupported;
} }
response.RelocateFilesNeeded = !restoreDataObject.DbFilesLocationAreValid(); response.RelocateFilesNeeded = !restoreDataObject.DbFilesLocationAreValid();
@@ -135,13 +156,31 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
} }
else else
{ {
response.ErrorMessage = "Failed to create restore plan"; if (restoreDataObject.ActiveException != null)
{
response.ErrorMessage = restoreDataObject.ActiveException.Message;
}
else
{
response.ErrorMessage = SR.RestorePlanFailed;
}
response.CanRestore = false; response.CanRestore = false;
} }
} }
else else
{ {
response.ErrorMessage = "Failed to create restore database plan"; response.ErrorMessage = SR.RestorePlanFailed;
}
}
catch(Exception ex)
{
response.ErrorMessage = ex.Message;
if (ex.InnerException != null)
{
response.ErrorMessage += Environment.NewLine;
response.ErrorMessage += ex.InnerException.Message;
}
} }
return response; return response;
@@ -190,8 +229,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
/// <returns></returns> /// <returns></returns>
private void UpdateRestorePlan(RestoreDatabaseTaskDataObject restoreDataObject) 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)) if (!string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePath))
{ {
restoreDataObject.AddFile(restoreDataObject.RestoreParams.BackupFilePath); restoreDataObject.AddFile(restoreDataObject.RestoreParams.BackupFilePath);
@@ -215,6 +252,10 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
{ {
restoreDataObject.RestorePlan.Execute(); restoreDataObject.RestorePlan.Execute();
} }
else
{
throw new InvalidOperationException(SR.RestoreNotSupported);
}
} }
} }
} }

View File

@@ -43,7 +43,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
{ {
get get
{ {
return this.Server != null && this.RestorePlanner != null; return this.Server != null && this.RestorePlanner != null && ActiveException == null;
} }
} }

View File

@@ -3301,6 +3301,54 @@ namespace Microsoft.SqlTools.ServiceLayer
} }
} }
public static string ConflictWithNoRecovery
{
get
{
return Keys.GetString(Keys.ConflictWithNoRecovery);
}
}
public static string InvalidPathForDatabaseFile
{
get
{
return Keys.GetString(Keys.InvalidPathForDatabaseFile);
}
}
public static string Log
{
get
{
return Keys.GetString(Keys.Log);
}
}
public static string RestorePlanFailed
{
get
{
return Keys.GetString(Keys.RestorePlanFailed);
}
}
public static string RestoreNotSupported
{
get
{
return Keys.GetString(Keys.RestoreNotSupported);
}
}
public static string RestoreTaskName
{
get
{
return Keys.GetString(Keys.RestoreTaskName);
}
}
public static string ConnectionServiceListDbErrorNotConnected(string uri) public static string ConnectionServiceListDbErrorNotConnected(string uri)
{ {
return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri); return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri);
@@ -4648,6 +4696,24 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string Task_Completed = "Task_Completed"; public const string Task_Completed = "Task_Completed";
public const string ConflictWithNoRecovery = "ConflictWithNoRecovery";
public const string InvalidPathForDatabaseFile = "InvalidPathForDatabaseFile";
public const string Log = "Log";
public const string RestorePlanFailed = "RestorePlanFailed";
public const string RestoreNotSupported = "RestoreNotSupported";
public const string RestoreTaskName = "RestoreTaskName";
private Keys() private Keys()
{ } { }

View File

@@ -1823,4 +1823,28 @@
<value>Completed</value> <value>Completed</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="ConflictWithNoRecovery" xml:space="preserve">
<value>Specifying this option when restoring a backup with the NORECOVERY option is not permitted.</value>
<comment></comment>
</data>
<data name="InvalidPathForDatabaseFile" xml:space="preserve">
<value>Invalid path for database file: '{0}'</value>
<comment></comment>
</data>
<data name="Log" xml:space="preserve">
<value>Log</value>
<comment></comment>
</data>
<data name="RestorePlanFailed" xml:space="preserve">
<value>Failed to create restore plan</value>
<comment></comment>
</data>
<data name="RestoreNotSupported" xml:space="preserve">
<value>Restore database is not supported</value>
<comment></comment>
</data>
<data name="RestoreTaskName" xml:space="preserve">
<value>Restore Database</value>
<comment></comment>
</data>
</root> </root>

View File

@@ -820,3 +820,4 @@ InvalidPathForDatabaseFile = Invalid path for database file: '{0}'
Log = Log Log = Log
RestorePlanFailed = Failed to create restore plan RestorePlanFailed = Failed to create restore plan
RestoreNotSupported = Restore database is not supported RestoreNotSupported = Restore database is not supported
RestoreTaskName = Restore Database

View File

@@ -2129,6 +2129,36 @@
<target state="new">Parameterization</target> <target state="new">Parameterization</target>
<note></note> <note></note>
</trans-unit> </trans-unit>
<trans-unit id="ConflictWithNoRecovery">
<source>Specifying this option when restoring a backup with the NORECOVERY option is not permitted.</source>
<target state="new">Specifying this option when restoring a backup with the NORECOVERY option is not permitted.</target>
<note></note>
</trans-unit>
<trans-unit id="InvalidPathForDatabaseFile">
<source>Invalid path for database file: '{0}'</source>
<target state="new">Invalid path for database file: '{0}'</target>
<note></note>
</trans-unit>
<trans-unit id="Log">
<source>Log</source>
<target state="new">Log</target>
<note></note>
</trans-unit>
<trans-unit id="RestorePlanFailed">
<source>Failed to create restore plan</source>
<target state="new">Failed to create restore plan</target>
<note></note>
</trans-unit>
<trans-unit id="RestoreNotSupported">
<source>Restore database is not supported</source>
<target state="new">Restore database is not supported</target>
<note></note>
</trans-unit>
<trans-unit id="RestoreTaskName">
<source>Restore Database</source>
<target state="new">Restore Database</target>
<note></note>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@@ -118,7 +118,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// <returns></returns> /// <returns></returns>
public SqlTask CreateAndRun(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel) public SqlTask CreateAndRun(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel)
{ {
var sqlTask = CreateTask(taskMetadata, taskToRun, null); var sqlTask = CreateTask(taskMetadata, taskToRun, taskToCancel);
sqlTask.Run(); sqlTask.Run();
return sqlTask; return sqlTask;
} }

View File

@@ -53,6 +53,14 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
var restorePlan = await VerifyRestore(backupFileName, canRestore, true); var restorePlan = await VerifyRestore(backupFileName, canRestore, true);
} }
[Fact]
public async void RestoreToAnotherDatabaseShouldExecuteSuccessfullyForFullBackup()
{
string backupFileName = "FullBackup.bak";
bool canRestore = true;
var restorePlan = await VerifyRestore(backupFileName, canRestore, true, "NewRestoredDatabase");
}
[Fact] [Fact]
public async void RestorePlanShouldFailForDiffBackup() public async void RestorePlanShouldFailForDiffBackup()
{ {
@@ -101,7 +109,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
{ {
TestConnectionResult connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); TestConnectionResult connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath);
string filePath = GetBackupFilePath("FullBackup.bak"); string filePath = GetBackupFilePath("SomeFile.bak");
RestoreParams restoreParams = new RestoreParams RestoreParams restoreParams = new RestoreParams
{ {
@@ -121,6 +129,31 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
} }
} }
[Fact]
public async Task RestorePlanRequestShouldReturnErrorMessageGivenInvalidFilePath()
{
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
{
TestConnectionResult connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath);
string filePath = GetBackupFilePath("InvalidFilePath");
RestoreParams restoreParams = new RestoreParams
{
BackupFilePath = filePath,
OwnerUri = queryTempFile.FilePath
};
await RunAndVerify<RestorePlanResponse>(
test: (requestContext) => service.HandleRestorePlanRequest(restoreParams, requestContext),
verify: ((result) =>
{
Assert.False(string.IsNullOrEmpty(result.ErrorMessage));
Assert.False(result.CanRestore);
}));
}
}
private async Task DropDatabase(string databaseName) private async Task DropDatabase(string databaseName)
{ {
string dropDatabaseQuery = string.Format(CultureInfo.InvariantCulture, string dropDatabaseQuery = string.Format(CultureInfo.InvariantCulture,
@@ -129,7 +162,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", dropDatabaseQuery); await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", dropDatabaseQuery);
} }
private async Task<RestorePlanResponse> VerifyRestore(string backupFileName, bool canRestore, bool execute = false) private async Task<RestorePlanResponse> VerifyRestore(string backupFileName, bool canRestore, bool execute = false, string targetDatabase = null)
{ {
string filePath = GetBackupFilePath(backupFileName); string filePath = GetBackupFilePath(backupFileName);
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
@@ -140,7 +173,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
var request = new RestoreParams var request = new RestoreParams
{ {
BackupFilePath = filePath, BackupFilePath = filePath,
DatabaseName = string.Empty, DatabaseName = targetDatabase,
OwnerUri = queryTempFile.FilePath OwnerUri = queryTempFile.FilePath
}; };
@@ -153,14 +186,18 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
{ {
Assert.True(response.DbFiles.Any()); Assert.True(response.DbFiles.Any());
Assert.Equal(response.DatabaseName, "BackupTestDb"); Assert.Equal(response.DatabaseName, "BackupTestDb");
if (string.IsNullOrEmpty(targetDatabase))
{
targetDatabase = response.DatabaseName;
}
if(execute) if(execute)
{ {
await DropDatabase(response.DatabaseName); await DropDatabase(targetDatabase);
Thread.Sleep(2000); Thread.Sleep(2000);
request.RelocateDbFiles = response.RelocateFilesNeeded; request.RelocateDbFiles = response.RelocateFilesNeeded;
service.ExecuteRestore(restoreDataObject); service.ExecuteRestore(restoreDataObject);
Assert.True(restoreDataObject.Server.Databases.Contains(response.DatabaseName)); Assert.True(restoreDataObject.Server.Databases.Contains(targetDatabase));
await DropDatabase(response.DatabaseName); await DropDatabase(targetDatabase);
} }
} }