diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/CommonUtilities.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/CommonUtilities.cs
index f7e1d759..18b93b49 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/CommonUtilities.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/CommonUtilities.cs
@@ -612,7 +612,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
backupComponent = RestoreConstants.ComponentDatabase;
break;
case BackupSetType.Differential:
- backupType = RestoreConstants.TypeTransactionLog;
+ backupType = RestoreConstants.TypeDifferential;
backupComponent = RestoreConstants.ComponentDatabase;
break;
case BackupSetType.FileOrFileGroup:
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/BackupSetsFilterInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/BackupSetsFilterInfo.cs
new file mode 100644
index 00000000..b2f471ed
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/BackupSetsFilterInfo.cs
@@ -0,0 +1,75 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.SqlServer.Management.Smo;
+
+namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
+{
+ ///
+ /// Class include info about selected back sets
+ ///
+ public class BackupSetsFilterInfo
+ {
+ private HashSet selectedBackupSets = new HashSet();
+
+ ///
+ /// Returns true if given backup set is selected
+ ///
+ public bool IsBackupSetSelected(Guid backupGuid)
+ {
+ bool isSelected = false;
+ if (backupGuid != Guid.Empty)
+ {
+ isSelected = this.selectedBackupSets.Any(x => x == backupGuid);
+ }
+ return isSelected;
+ }
+
+ ///
+ /// Returns true if given backup set is selected
+ ///
+ public bool IsBackupSetSelected(BackupSet backupSet)
+ {
+ return IsBackupSetSelected(backupSet != null ? backupSet.BackupSetGuid : Guid.Empty);
+ }
+
+ ///
+ /// Returns true if any backup set is selected
+ ///
+ public bool AnySelected
+ {
+ get
+ {
+ return this.selectedBackupSets != null && this.selectedBackupSets.Any();
+ }
+ }
+
+ ///
+ /// Adds backup set to selected list if not added aleady
+ ///
+ ///
+ public void Add(BackupSet backupSet)
+ {
+ if (backupSet != null)
+ {
+ if (!this.selectedBackupSets.Contains(backupSet.BackupSetGuid))
+ {
+ this.selectedBackupSets.Add(backupSet.BackupSetGuid);
+ }
+ }
+ }
+
+ ///
+ /// Clears the list
+ ///
+ public void Clear()
+ {
+ this.selectedBackupSets.Clear();
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs
index 97419b82..511ce104 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs
@@ -181,7 +181,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
response.PlanDetails.Add(LastBackupTaken, restoreDataObject.GetLastBackupTaken());
- response.BackupSetsToRestore = restoreDataObject.GetBackupSetInfo().Select(x => new DatabaseFileInfo(x.ConvertPropertiesToArray())).ToArray();
+ response.BackupSetsToRestore = restoreDataObject.GetSelectedBakupSets();
var dbNames = restoreDataObject.GetSourceDbNames();
response.DatabaseNamesFromBackupSets = dbNames == null ? new string[] { } : dbNames.ToArray();
@@ -236,14 +236,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
///
private static bool CanRestore(RestoreDatabaseTaskDataObject restoreDataObject)
{
- if (restoreDataObject != null)
- {
- var backupTypes = restoreDataObject.GetBackupSetInfo();
- var selectedBackupSets = restoreDataObject.RestoreParams.SelectedBackupSets;
- return backupTypes.Any(x => (selectedBackupSets == null || selectedBackupSets.Contains(x.GetPropertyValueAsString(DatabaseFileInfo.IdPropertyName)))
- && x.BackupType.StartsWith(RestoreConstants.TypeFull));
- }
- return false;
+ return restoreDataObject != null && restoreDataObject.RestorePlan != null && restoreDataObject.RestorePlan.RestoreOperations != null
+ && restoreDataObject.RestorePlan.RestoreOperations.Count > 0;
}
///
@@ -266,6 +260,10 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
this.restoreSessions.AddOrUpdate(sessionId, restoreTaskObject, (key, oldSession) => restoreTaskObject);
restoreTaskObject.SessionId = sessionId;
}
+ else
+ {
+ restoreTaskObject.RestoreParams = restoreParams;
+ }
return restoreTaskObject;
}
@@ -311,49 +309,46 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
///
private void UpdateRestorePlan(RestoreDatabaseTaskDataObject restoreDataObject)
{
- if (restoreDataObject.PlanUpdateRequired)
+ if (!string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePaths))
{
- if (!string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePaths))
- {
- restoreDataObject.AddFiles(restoreDataObject.RestoreParams.BackupFilePaths);
- }
- restoreDataObject.RestorePlanner.ReadHeaderFromMedia = !string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePaths);
-
- if (string.IsNullOrWhiteSpace(restoreDataObject.RestoreParams.SourceDatabaseName))
- {
- restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.DefaultDbName;
- }
- else
- {
- restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.RestoreParams.SourceDatabaseName;
- }
- restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName;
-
- restoreDataObject.RestoreOptions.KeepReplication = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.KeepReplication);
- restoreDataObject.RestoreOptions.ReplaceDatabase = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.ReplaceDatabase);
- restoreDataObject.RestoreOptions.SetRestrictedUser = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.SetRestrictedUser);
- string recoveryState = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.RecoveryState);
- object databaseRecoveryState;
- if (Enum.TryParse(typeof(DatabaseRecoveryState), recoveryState, out databaseRecoveryState))
- {
- restoreDataObject.RestoreOptions.RecoveryState = (DatabaseRecoveryState)databaseRecoveryState;
- }
- bool isTailLogBackupPossible = restoreDataObject.IsTailLogBackupPossible(restoreDataObject.RestorePlanner.DatabaseName);
- if (isTailLogBackupPossible)
- {
- restoreDataObject.RestorePlanner.BackupTailLog = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.BackupTailLog);
- restoreDataObject.TailLogBackupFile = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.TailLogBackupFile);
- restoreDataObject.TailLogWithNoRecovery = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.TailLogWithNoRecovery);
- }
- else
- {
- restoreDataObject.RestorePlanner.BackupTailLog = false;
- }
-
- restoreDataObject.CloseExistingConnections = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.CloseExistingConnections);
-
- restoreDataObject.UpdateRestorePlan(restoreDataObject.RestoreParams.RelocateDbFiles);
+ restoreDataObject.AddFiles(restoreDataObject.RestoreParams.BackupFilePaths);
}
+ restoreDataObject.RestorePlanner.ReadHeaderFromMedia = !string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePaths);
+
+ if (string.IsNullOrWhiteSpace(restoreDataObject.RestoreParams.SourceDatabaseName))
+ {
+ restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.DefaultDbName;
+ }
+ else
+ {
+ restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.RestoreParams.SourceDatabaseName;
+ }
+ restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName;
+
+ restoreDataObject.RestoreOptions.KeepReplication = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.KeepReplication);
+ restoreDataObject.RestoreOptions.ReplaceDatabase = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.ReplaceDatabase);
+ restoreDataObject.RestoreOptions.SetRestrictedUser = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.SetRestrictedUser);
+ string recoveryState = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.RecoveryState);
+ object databaseRecoveryState;
+ if (Enum.TryParse(typeof(DatabaseRecoveryState), recoveryState, out databaseRecoveryState))
+ {
+ restoreDataObject.RestoreOptions.RecoveryState = (DatabaseRecoveryState)databaseRecoveryState;
+ }
+ bool isTailLogBackupPossible = restoreDataObject.IsTailLogBackupPossible(restoreDataObject.RestorePlanner.DatabaseName);
+ if (isTailLogBackupPossible)
+ {
+ restoreDataObject.RestorePlanner.BackupTailLog = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.BackupTailLog);
+ restoreDataObject.TailLogBackupFile = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.TailLogBackupFile);
+ restoreDataObject.TailLogWithNoRecovery = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.TailLogWithNoRecovery);
+ }
+ else
+ {
+ restoreDataObject.RestorePlanner.BackupTailLog = false;
+ }
+
+ restoreDataObject.CloseExistingConnections = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.CloseExistingConnections);
+
+ restoreDataObject.UpdateRestorePlan(restoreDataObject.RestoreParams.RelocateDbFiles);
}
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs
index aba3e570..b30c801b 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs
@@ -23,6 +23,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
private const char BackupMediaNameSeparator = ',';
private DatabaseRestorePlanner restorePlanner;
private string tailLogBackupFile;
+ private BackupSetsFilterInfo backupSetsFilterInfo = new BackupSetsFilterInfo();
public RestoreDatabaseTaskDataObject(Server server, String databaseName)
{
@@ -132,25 +133,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
}
}
- ///
- /// Removes the backup sets that are filtered in the request
- ///
- public void RemoveFilteredBackupSets()
- {
- var backupSetIdsToRestore = RestoreParams.SelectedBackupSets;
- if (backupSetIdsToRestore != null)
- {
- var ids = backupSetIdsToRestore.Select(x =>
- {
- Guid guid;
- Guid.TryParse(x, out guid);
- return guid;
- }
- );
- restorePlan.RestoreOperations.RemoveAll(x => !ids.Contains(x.BackupSet.BackupSetGuid));
- }
- }
-
+
///
/// Returns the last backup taken
///
@@ -158,20 +141,22 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
public string GetLastBackupTaken()
{
string lastBackup = string.Empty;
- int lastIndexSel = 0; //TODO: find the selected backup set
+ int lastSelectedIndex = GetLastSelectedBackupSetIndex();
+ BackupSet lastSelectedBackupSet = lastSelectedIndex >= 0 && this.RestorePlan.RestoreOperations != null && this.RestorePlan.RestoreOperations.Count > 0 ?
+ this.RestorePlan.RestoreOperations[lastSelectedIndex].BackupSet : null;
if (this.RestorePlanner.RestoreToLastBackup &&
- this.RestorePlan.RestoreOperations[lastIndexSel] != null &&
+ lastSelectedBackupSet != null &&
this.RestorePlan.RestoreOperations.Count > 0 &&
- this.RestorePlan.RestoreOperations[lastIndexSel].BackupSet != null)
+ lastSelectedBackupSet != null)
{
- int lastIndex = this.RestorePlan.RestoreOperations.Count - 1;
- DateTime backupTime = this.RestorePlan.RestoreOperations[lastIndexSel].BackupSet.BackupStartDate;
+ bool isTheLastOneSelected = lastSelectedIndex == this.RestorePlan.RestoreOperations.Count - 1;
+ DateTime backupTime = lastSelectedBackupSet.BackupStartDate;
string backupTimeStr = backupTime.ToLongDateString() + " " + backupTime.ToLongTimeString();
- lastBackup = (lastIndexSel == lastIndex) ?
+ lastBackup = isTheLastOneSelected ?
string.Format(CultureInfo.CurrentCulture, SR.TheLastBackupTaken, (backupTimeStr)) : backupTimeStr;
}
//TODO: find the selected one
- else if (this.RestoreSelected[0] && !this.RestorePlanner.RestoreToLastBackup)
+ else if (GetFirstSelectedBackupSetIndex() == 0 && !this.RestorePlanner.RestoreToLastBackup)
{
lastBackup = this.CurrentRestorePointInTime.Value.ToLongDateString() +
" " + this.CurrentRestorePointInTime.Value.ToLongTimeString();
@@ -185,13 +170,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
///
public void Execute()
{
- RestorePlan restorePlan = RestorePlan;
+ RestorePlan restorePlan = GetRestorePlanForExecutionAndScript();
// ssms creates a new restore plan by calling GetRestorePlanForExecutionAndScript and
// Doens't use the plan already created here. not sure why, using the existing restore plan doesn't make
// any issue so far so keeping in it for now but we might want to double check later
if (restorePlan != null && restorePlan.RestoreOperations.Count > 0)
{
- RemoveFilteredBackupSets();
restorePlan.PercentComplete += (object sender, PercentCompleteEventArgs e) =>
{
if (SqlTask != null)
@@ -203,6 +187,49 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
}
}
+ ///
+ /// Gets RestorePlan to perform restore and to script
+ ///
+ 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.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.backupSetsFilterInfo.IsBackupSetSelected(res.BackupSet))
+ {
+ rp.RestoreOperations.Add(res);
+ }
+ i++;
+ }
+ }
+ this.SetRestorePlanProperties(rp);
+ RestorePlanToExecute = rp;
+ return rp;
+ }
+
+ ///
+ /// For test purpose only. The restore plan that's used to execute
+ ///
+ internal RestorePlan RestorePlanToExecute { get; set; }
+
///
/// Restore Util
///
@@ -478,8 +505,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
}
}
- public bool[] RestoreSelected;
-
///
/// The database being restored
///
@@ -526,34 +551,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
///
internal string ContainerSharedAccessPolicy = string.Empty;
- ///
- /// Updates the RestoreSelected Array to hold information about updated Restore Plan
- ///
- 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;
- }
- }
-
///
/// Returns the physical name for the target Db file.
/// It is the sourceDbName replaced with targetDbName in sourceFilename.
@@ -577,12 +574,38 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
List result = new List();
foreach (Restore restore in RestorePlan.RestoreOperations)
{
- result.Add(BackupSetInfo.Create(restore, Server));
+ BackupSetInfo backupSetInfo = BackupSetInfo.Create(restore, Server);
+ if (this.backupSetsFilterInfo.IsBackupSetSelected(restore.BackupSet))
+ {
+
+ }
+ result.Add(backupSetInfo);
}
return result;
}
+ ///
+ /// List of selected backupsets
+ ///
+ public DatabaseFileInfo[] GetSelectedBakupSets()
+ {
+ List result = new List();
+ IEnumerable backupSetInfos = GetBackupSetInfo();
+ foreach (var backupSetInfo in backupSetInfos)
+ {
+ var item = new DatabaseFileInfo(backupSetInfo.ConvertPropertiesToArray());
+ Guid backupSetGuid;
+ if(!Guid.TryParse(item.Id, out backupSetGuid))
+ {
+ backupSetGuid = Guid.Empty;
+ }
+ item.IsSelected = this.backupSetsFilterInfo.IsBackupSetSelected(backupSetGuid);
+ result.Add(item);
+ }
+ return result.ToArray();
+ }
+
///
/// Gets the files of the database
///
@@ -682,23 +705,15 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
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
+ if (this.restorePlan == null)
{
this.RestorePlan = new RestorePlan(this.Server);
this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName);
- this.RestoreSelected = new bool[0];
}
+
+ UpdateSelectedBackupSets();
}
-
///
/// Determine if restore plan of selected database does have Url
///
@@ -720,9 +735,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
}
rp.SetRestoreOptions(this.RestoreOptions);
rp.CloseExistingConnections = this.CloseExistingConnections;
- if (this.targetDbName != null && !this.targetDbName.Equals(string.Empty))
+ if (this.TargetDatabase != null && !this.TargetDatabase.Equals(string.Empty))
{
- rp.DatabaseName = targetDbName;
+ rp.DatabaseName = TargetDatabase;
}
rp.RestoreOperations[0].RelocateFiles.Clear();
foreach (DbFile dbFile in this.DbFiles)
@@ -789,109 +804,150 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
{
get
{
- if (this.RestorePlan == null || this.RestorePlan.RestoreOperations.Count == 0
- || this.RestoreSelected.Length == 0 || !this.RestoreSelected[0])
+ if (!IsAnyFullBackupSetSelected())
{
return null;
}
for (int i = this.RestorePlan.RestoreOperations.Count - 1; i >= 0; i--)
{
- if (this.RestoreSelected[i])
+ BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet;
+ if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet))
{
- if (this.RestorePlan.RestoreOperations[i].BackupSet == null
- || (this.RestorePlan.RestoreOperations[i].BackupSet.BackupSetType == BackupSetType.Log &&
+ if (backupSet == null
+ || (backupSet.BackupSetType == BackupSetType.Log &&
this.RestorePlan.RestoreOperations[i].ToPointInTime != null))
{
return this.RestorePlanner.RestoreToPointInTime;
}
- return this.RestorePlan.RestoreOperations[i].BackupSet.BackupStartDate;
+ return backupSet.BackupStartDate;
}
}
return null;
}
}
- public void ToggleSelectRestore(int index)
+
+
+
+
+ private bool IsAnyFullBackupSetSelected()
{
- RestorePlan rp = this.restorePlan;
- if (rp == null || rp.RestoreOperations.Count <= index)
+ bool isSelected = false;
+
+ if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected)
{
- return;
+ var fullBackupSet = this.RestorePlan.RestoreOperations.FirstOrDefault(x => x.BackupSet.BackupSetType == BackupSetType.Database);
+ isSelected = fullBackupSet != null && this.backupSetsFilterInfo.IsBackupSetSelected(fullBackupSet.BackupSet.BackupSetGuid);
}
- //the last index - this will include tail-Log restore operation if present
- if (index == rp.RestoreOperations.Count - 1)
+
+ return isSelected;
+ }
+
+ private int GetLastSelectedBackupSetIndex()
+ {
+ if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected)
{
- if (this.RestoreSelected[index])
+ for (int i = this.RestorePlan.RestoreOperations.Count -1; i >= 0; i--)
{
- this.RestoreSelected[index] = false;
- }
- else
- {
- for (int i = 0; i <= index; i++)
+ BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet;
+ if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet))
{
- this.RestoreSelected[i] = true;
+ return i;
}
}
- return;
}
- if (index == 0)
+ return -1;
+ }
+
+ private int GetFirstSelectedBackupSetIndex()
+ {
+ if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected)
{
- if (!this.RestoreSelected[index])
+ for (int i = 0; i < this.RestorePlan.RestoreOperations.Count - 1; i++)
{
- this.RestoreSelected[index] = true;
- }
- else
- {
- for (int i = index; i < rp.RestoreOperations.Count; i++)
+ BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet;
+ if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet))
{
- this.RestoreSelected[i] = false;
+ return i;
}
}
- return;
}
-
- if (index == 1 && rp.RestoreOperations[index].BackupSet.BackupSetType == BackupSetType.Differential)
+ return -1;
+ }
+
+
+ public void UpdateSelectedBackupSets()
+ {
+ this.backupSetsFilterInfo.Clear();
+ var selectedBackupSetsFromClient = this.RestoreParams.SelectedBackupSets;
+
+ if (this.RestorePlan != null && this.RestorePlan.RestoreOperations != null)
{
- if (!this.RestoreSelected[index])
+ for (int index = 0; index < this.RestorePlan.RestoreOperations.Count; 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++)
+ BackupSet backupSet = this.RestorePlan.RestoreOperations[index].BackupSet;
+ if (backupSet != null)
{
- this.RestoreSelected[i] = false;
+ // If the collection client sent is null, select everything; otherwise select the items that are selected in client
+ bool backupSetSelected = selectedBackupSetsFromClient == null || selectedBackupSetsFromClient.Any(x => BackUpSetGuidEqualsId(backupSet, x));
+
+ if (backupSetSelected)
+ {
+ AddBackupSetsToSelected(index, index);
+
+ //the last index - this will include tail-Log restore operation if present
+ //If the last item is selected, select all the items before that because the last item
+ //is tail-log
+ if (index == this.RestorePlan.RestoreOperations.Count - 1)
+ {
+ AddBackupSetsToSelected(0, index);
+ }
+
+ //If the second item is selected and it's a diff backup, the fist item (full backup) has to be selected
+ if (index == 1 && backupSet.BackupSetType == BackupSetType.Differential)
+ {
+ AddBackupSetsToSelected(0, 0);
+ }
+
+ //If the selected item is a log backup, select all the items before that
+ if (backupSet.BackupSetType == BackupSetType.Log)
+ {
+ AddBackupSetsToSelected(0, index);
+ }
+ }
+ else
+ {
+ // If the first item is not selected which is always the full backup, other backupsets cannot be selected
+ if (index == 0)
+ {
+ selectedBackupSetsFromClient = new string[] { };
+ }
+
+ //The a log is not selected, the logs after that cannot be selected
+ if (backupSet.BackupSetType == BackupSetType.Log)
+ {
+ break;
+ }
+ }
}
}
- return;
}
- if (rp.RestoreOperations[index].BackupSet.BackupSetType == BackupSetType.Log)
+ }
+
+ private bool BackUpSetGuidEqualsId(BackupSet backupSet, string id)
+ {
+ return backupSet != null && string.Compare(backupSet.BackupSetGuid.ToString(), id, StringComparison.OrdinalIgnoreCase) == 0;
+ }
+
+ private void AddBackupSetsToSelected(int from, int to)
+ {
+ if (this.RestorePlan != null && this.RestorePlan.RestoreOperations != null
+ && from < this.RestorePlan.RestoreOperations.Count && to < this.RestorePlan.RestoreOperations.Count)
{
- if (this.RestoreSelected[index])
+ for (int i = from; i <= to; i++)
{
- 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;
+ BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet;
+ this.backupSetsFilterInfo.Add(backupSet);
}
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs
index ec133a13..4fd8d57e 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs
@@ -12,7 +12,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
internal const string KeepReplication = "keepReplication";
internal const string ReplaceDatabase = "replaceDatabase";
internal const string SetRestrictedUser = "setRestrictedUser";
- internal const string RecoveryState = "eecoveryState";
+ internal const string RecoveryState = "recoveryState";
internal const string BackupTailLog = "backupTailLog";
internal const string DefaultBackupTailLog = "defaultBackupTailLog";
internal const string TailLogBackupFile = "tailLogBackupFile";
@@ -48,7 +48,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
Description = "Preserve the replication settings (WITH KEEP_REPLICATION)",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
- GroupName = "Restore options"
+ GroupName = "Restore options",
+ DefaultValue = "false"
},
new ServiceOption
{
@@ -57,7 +58,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
Description = "Overwrite the existing database (WITH REPLACE)",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
- GroupName = "Restore options"
+ GroupName = "Restore options",
+ DefaultValue = "false"
},
new ServiceOption
{
@@ -66,7 +68,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
Description = "Restrict access to the restored database (WITH RESTRICTED_USER)",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
- GroupName = "Restore options"
+ GroupName = "Restore options",
+ DefaultValue = "false"
},
new ServiceOption
{
@@ -93,7 +96,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
Name = "WithStandBy",
DisplayName = "RESTORE WITH STANDBY"
}
- }
+ },
+ DefaultValue = "WithRecovery"
},
new ServiceOption
{
@@ -158,7 +162,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
Description = "Relocate all files",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
- GroupName = "Restore database files as"
+ GroupName = "Restore database files as",
+ DefaultValue = "false"
},
new ServiceOption
{
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs
index 95a73c8b..0e34caf7 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs
@@ -3,6 +3,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
+using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Globalization;
@@ -32,7 +33,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
private ConnectionService _connectService = TestServiceProvider.Instance.ConnectionService;
private Mock serviceHostMock;
private DisasterRecoveryService service;
- private string fullBackUpDatabase;
+ private string fullBackupFilePath;
+ private string[] backupFilesToRecoverDatabase;
+
+ //The table names used in the script to create backup files for a database
+ //Each table is created after a backup script to verify recovering to different states
+ private string[] tableNames = new string[] { "tb1", "tb2", "tb3", "tb4", "tb5" };
public RestoreDatabaseServiceTests()
{
@@ -43,18 +49,110 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
private async Task VerifyBackupFileCreated()
{
- if(fullBackUpDatabase == null)
+ if(fullBackupFilePath == null)
{
- fullBackUpDatabase = await CreateBackupFile();
+ fullBackupFilePath = await CreateBackupFile();
}
}
+ private async Task GetBackupFilesToRecoverDatabaseCreated()
+ {
+ if(backupFilesToRecoverDatabase == null)
+ {
+ backupFilesToRecoverDatabase = await CreateBackupSetsToRecoverDatabase();
+ }
+ return backupFilesToRecoverDatabase;
+ }
+
[Fact]
public async void RestorePlanShouldCreatedSuccessfullyForFullBackup()
{
await VerifyBackupFileCreated();
bool canRestore = true;
- await VerifyRestore(fullBackUpDatabase, canRestore);
+ await VerifyRestore(fullBackupFilePath, canRestore);
+ }
+
+ [Fact]
+ public async void RestoreShouldNotRestoreAnyBackupSetsIfFullNotSelected()
+ {
+ var backupFiles = await GetBackupFilesToRecoverDatabaseCreated();
+ //Remove the full backupset
+ int indexToDelete = 0;
+ //Verify that all backupsets are restored
+ int[] expectedTable = new int[] { };
+
+ await VerifyRestoreMultipleBackupSets(backupFiles, indexToDelete, expectedTable);
+ }
+
+ [Fact]
+ public async void RestoreShouldRestoreTheBackupSetsThatAreSelected()
+ {
+ var backupFiles = await GetBackupFilesToRecoverDatabaseCreated();
+ //Remove the last backupset
+ int indexToDelete = 4;
+ //Verify that backupset is not restored
+ int[] expectedTable = new int[] { 0, 1, 2, 3 };
+
+ await VerifyRestoreMultipleBackupSets(backupFiles, indexToDelete, expectedTable);
+ }
+
+ [Fact]
+ public async void RestoreShouldNotRestoreTheLogBackupSetsIfOneNotSelected()
+ {
+ var backupFiles = await GetBackupFilesToRecoverDatabaseCreated();
+ //Remove the one of the log backup sets
+ int indexToDelete = 3;
+ //Verify the logs backup set that's removed and all logs after that are not restored
+ int[] expectedTable = new int[] { 0, 1, 2 };
+ await VerifyRestoreMultipleBackupSets(backupFiles, indexToDelete, expectedTable);
+ }
+
+ private async Task VerifyRestoreMultipleBackupSets(string[] backupFiles, int backupSetIndexToDelete, int[] expectedSelectedIndexes)
+ {
+ var testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "RestoreTest");
+ try
+ {
+ string targetDbName = testDb.DatabaseName;
+ bool canRestore = true;
+ var response = await VerifyRestore(backupFiles, canRestore, false, targetDbName, null, null);
+ Assert.True(response.BackupSetsToRestore.Count() >= 2);
+ var allIds = response.BackupSetsToRestore.Select(x => x.Id).ToList();
+ if (backupSetIndexToDelete >= 0)
+ {
+ allIds.RemoveAt(backupSetIndexToDelete);
+ }
+ string[] selectedIds = allIds.ToArray();
+ Dictionary options = new Dictionary();
+ options.Add(RestoreOptionsHelper.ReplaceDatabase, true);
+ response = await VerifyRestore(backupFiles, canRestore, true, targetDbName, selectedIds, options, (database) =>
+ {
+ bool tablesFound = true;
+ for (int i = 0; i < tableNames.Length; i++)
+ {
+ string tableName = tableNames[i];
+ if (!database.Tables.Contains(tableName) && expectedSelectedIndexes.Contains(i))
+ {
+ tablesFound = false;
+ break;
+ }
+ }
+ bool numberOfTableCreatedIsCorrect = database.Tables.Count == expectedSelectedIndexes.Length;
+ return numberOfTableCreatedIsCorrect && tablesFound;
+ });
+
+ for (int i = 0; i < response.BackupSetsToRestore.Count(); i++)
+ {
+ DatabaseFileInfo databaseInfo = response.BackupSetsToRestore[i];
+ Assert.Equal(databaseInfo.IsSelected, expectedSelectedIndexes.Contains(i));
+ }
+ }
+ finally
+ {
+ if (testDb != null)
+ {
+ testDb.Cleanup();
+ }
+ }
}
[Fact]
@@ -70,7 +168,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
Dictionary options = new Dictionary();
options.Add(RestoreOptionsHelper.ReplaceDatabase, true);
- await VerifyRestore(new string[] { fullBackUpDatabase }, canRestore, true, testDb.DatabaseName, null, options);
+ await VerifyRestore(new string[] { fullBackupFilePath }, canRestore, true, testDb.DatabaseName, null, options);
}
finally
{
@@ -92,7 +190,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
await VerifyBackupFileCreated();
bool canRestore = true;
- await VerifyRestore(new string[] { fullBackUpDatabase }, canRestore, false, testDb.DatabaseName, null, null);
+ await VerifyRestore(new string[] { fullBackupFilePath }, canRestore, false, testDb.DatabaseName, null, null);
}
finally
{
@@ -125,12 +223,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
if(fileInfo != null)
{
var selectedBackupSets = new string[] { fileInfo.Id };
- await VerifyRestore(backupFileNames, false, false, "RestoredFromTwoBackupFile", selectedBackupSets);
+ await VerifyRestore(backupFileNames, true, false, "RestoredFromTwoBackupFile", selectedBackupSets);
}
}
[Fact]
- public async void RestoreShouldCompletedSuccessfullyGivenTowBackupFilesButFilterDifferentialBackup()
+ public async void RestoreShouldCompletedSuccessfullyGivenTwoBackupFilesButFilterDifferentialBackup()
{
string[] backupFileNames = new string[] { "FullBackup.bak", "DiffBackup.bak" };
@@ -150,7 +248,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
{
await VerifyBackupFileCreated();
- string backupFileName = fullBackUpDatabase;
+ string backupFileName = fullBackupFilePath;
bool canRestore = true;
var restorePlan = await VerifyRestore(backupFileName, canRestore, true);
Assert.NotNull(restorePlan.BackupSetsToRestore);
@@ -161,24 +259,24 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
{
await VerifyBackupFileCreated();
- string backupFileName = fullBackUpDatabase;
+ string backupFileName = fullBackupFilePath;
bool canRestore = true;
var restorePlan = await VerifyRestore(backupFileName, canRestore, true, "NewRestoredDatabase");
}
[Fact]
- public async void RestorePlanShouldFailForDiffBackup()
+ public async void RestorePlanShouldCreatedSuccessfullyForDiffBackup()
{
string backupFileName = "DiffBackup.bak";
- bool canRestore = false;
+ bool canRestore = true;
await VerifyRestore(backupFileName, canRestore);
}
[Fact]
- public async void RestorePlanShouldFailForTransactionLogBackup()
+ public async void RestorePlanShouldCreatedSuccessfullyForTransactionLogBackup()
{
string backupFileName = "TransactionLogBackup.bak";
- bool canRestore = false;
+ bool canRestore = true;
await VerifyRestore(backupFileName, canRestore);
}
@@ -191,7 +289,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
{
TestConnectionResult connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath);
- string filePath = GetBackupFilePath(fullBackUpDatabase);
+ string filePath = GetBackupFilePath(fullBackupFilePath);
RestoreParams restoreParams = new RestoreParams
{
@@ -279,7 +377,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
bool execute = false,
string targetDatabase = null,
string[] selectedBackupSets = null,
- Dictionary options = null)
+ Dictionary options = null,
+ Func verifyDatabase = null)
{
var filePaths = backupFileNames.Select(x => GetBackupFilePath(x));
string backUpFilePath = filePaths.Aggregate((current, next) => current + " ," + next);
@@ -337,15 +436,22 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
request.SessionId = response.SessionId;
restoreDataObject = service.CreateRestoreDatabaseTaskDataObject(request);
Assert.Equal(response.SessionId, restoreDataObject.SessionId);
- //await DropDatabase(targetDatabase);
- //Thread.Sleep(2000);
request.RelocateDbFiles = !restoreDataObject.DbFilesLocationAreValid();
service.ExecuteRestore(restoreDataObject);
Assert.True(restoreDataObject.Server.Databases.Contains(targetDatabase));
- if(selectedBackupSets != null)
+
+ if(verifyDatabase != null)
{
- Assert.Equal(selectedBackupSets.Count(), restoreDataObject.RestorePlan.RestoreOperations.Count());
+ Assert.True(verifyDatabase(restoreDataObject.Server.Databases[targetDatabase]));
}
+
+ //To verify the backupset that are restored, verifying the database is a better options.
+ //Some tests still verify the number of backup sets that are executed which in some cases can be less than the selected list
+ if (verifyDatabase == null && selectedBackupSets != null)
+ {
+ Assert.Equal(selectedBackupSets.Count(), restoreDataObject.RestorePlanToExecute.RestoreOperations.Count());
+ }
+
await DropDatabase(targetDatabase);
}
}
@@ -403,6 +509,60 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
.RegisterSingleService(new DisasterRecoveryService());
}
+ public async Task CreateBackupSetsToRecoverDatabase()
+ {
+ List backupFiles = new List();
+ using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
+ {
+ string query = $"create table {tableNames[0]} (c1 int)";
+ SqlTestDb testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, query, "RestoreTest");
+ string databaseName = testDb.DatabaseName;
+ // Initialize backup service
+ var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName, queryTempFile.FilePath);
+ DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
+ SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
+ BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database);
+
+ string backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + "_full.bak");
+ query = $"BACKUP DATABASE [{databaseName}] TO DISK = N'{backupPath}' WITH NOFORMAT, NOINIT, NAME = N'{databaseName}-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", query);
+ backupFiles.Add(backupPath);
+
+ query = $"create table {tableNames[1]} (c1 int)";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, databaseName, query);
+ backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + "_diff.bak");
+ query = $"BACKUP DATABASE [{databaseName}] TO DISK = N'{backupPath}' WITH DIFFERENTIAL, NOFORMAT, NOINIT, NAME = N'{databaseName}-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", query);
+ backupFiles.Add(backupPath);
+
+ query = $"create table {tableNames[2]} (c1 int)";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, databaseName, query);
+ backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + "_log1.bak");
+ query = $"BACKUP Log [{databaseName}] TO DISK = N'{backupPath}' WITH NOFORMAT, NOINIT, NAME = N'{databaseName}-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", query);
+ backupFiles.Add(backupPath);
+
+ query = $"create table {tableNames[3]} (c1 int)";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, databaseName, query);
+ backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + "_log2.bak");
+ query = $"BACKUP Log [{databaseName}] TO DISK = N'{backupPath}' WITH NOFORMAT, NOINIT, NAME = N'{databaseName}-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", query);
+ backupFiles.Add(backupPath);
+
+ query = $"create table {tableNames[4]} (c1 int)";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, databaseName, query);
+ backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + "_log3.bak");
+ query = $"BACKUP Log [{databaseName}] TO DISK = N'{backupPath}' WITH NOFORMAT, NOINIT, NAME = N'{databaseName}-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10";
+ await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", query);
+ backupFiles.Add(backupPath);
+
+ // Clean up the database
+ testDb.Cleanup();
+ }
+ return backupFiles.ToArray();
+
+ }
+
public async Task CreateBackupFile()
{
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())