From f95a652174028e035958c3c81de5c22f492a4ad4 Mon Sep 17 00:00:00 2001 From: Leila Lali Date: Wed, 16 Aug 2017 08:54:05 -0700 Subject: [PATCH] fixed the bugs with creating new restore plan (#433) * fixed the bug with source db name not setting correctly when new plan created --- .../RestoreOperation/RestoreDatabaseHelper.cs | 49 ++--- .../RestoreDatabaseTaskDataObject.cs | 125 +++++++++-- .../RestoreOperation/RestoreOptionFactory.cs | 90 +++++++- .../DisasterRecovery/RestoreOptionsHelper.cs | 7 +- .../RestoreDatabaseTaskDataObjectStub.cs | 11 + .../RestoreOptionsHelperTests.cs | 197 +++++++++++++++--- 6 files changed, 390 insertions(+), 89 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs index 816161e7..f2b934e3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs @@ -171,14 +171,13 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation if (restoreDataObject != null && restoreDataObject.IsValid) { response.SessionId = restoreDataObject.SessionId; - response.DatabaseName = restoreDataObject.TargetDatabase; - response.PlanDetails.Add(RestoreOptionsHelper.TargetDatabaseName, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.TargetDatabaseName, - currentValue: restoreDataObject.TargetDatabase, - isReadOnly: !CanChangeTargetDatabase(restoreDataObject))); - response.PlanDetails.Add(RestoreOptionsHelper.SourceDatabaseName, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.SourceDatabaseName, - currentValue: restoreDataObject.RestorePlanner.DatabaseName)); + response.DatabaseName = restoreDataObject.TargetDatabaseName; + + response.PlanDetails.Add(RestoreOptionsHelper.TargetDatabaseName, + RestoreOptionFactory.Instance.CreateAndValidate(RestoreOptionsHelper.TargetDatabaseName, restoreDataObject)); + response.PlanDetails.Add(RestoreOptionsHelper.SourceDatabaseName, + RestoreOptionFactory.Instance.CreateAndValidate(RestoreOptionsHelper.SourceDatabaseName, restoreDataObject)); + response.PlanDetails.Add(RestoreOptionsHelper.ReadHeaderFromMedia, RestorePlanDetailInfo.Create( name: RestoreOptionsHelper.ReadHeaderFromMedia, currentValue: restoreDataObject.RestorePlanner.ReadHeaderFromMedia)); @@ -191,16 +190,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation }); response.CanRestore = CanRestore(restoreDataObject); - if (!response.CanRestore) - { - response.ErrorMessage = SR.RestoreNotSupported; - } - response.PlanDetails.Add(LastBackupTaken, RestorePlanDetailInfo.Create(name: LastBackupTaken, currentValue: restoreDataObject.GetLastBackupTaken(), isReadOnly: true)); response.BackupSetsToRestore = restoreDataObject.GetSelectedBakupSets(); - var dbNames = restoreDataObject.GetPossibleTargerDbNames(); + var dbNames = restoreDataObject.SourceDbNames; response.DatabaseNamesFromBackupSets = dbNames == null ? new string[] { } : dbNames.ToArray(); RestoreOptionsHelper.AddOptions(response, restoreDataObject); @@ -259,11 +253,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation if (!sessions.TryGetValue(sessionId, out restoreTaskObject)) { restoreTaskObject = CreateRestoreForNewSession(restoreParams.OwnerUri, restoreParams.TargetDatabaseName); + sessions.AddOrUpdate(sessionId, restoreTaskObject, (key, old) => restoreTaskObject); } restoreTaskObject.SessionId = sessionId; restoreTaskObject.RestoreParams = restoreParams; - restoreTaskObject.TargetDatabase = restoreParams.TargetDatabaseName; - restoreTaskObject.RestorePlanner.DatabaseName = restoreParams.TargetDatabaseName; + return restoreTaskObject; } @@ -308,33 +302,24 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation /// private void UpdateRestorePlan(RestoreDatabaseTaskDataObject restoreDataObject) { + bool shouldCreateNewPlan = restoreDataObject.ShouldCreateNewPlan(); + if (!string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePaths)) { restoreDataObject.AddFiles(restoreDataObject.RestoreParams.BackupFilePaths); } restoreDataObject.RestorePlanner.ReadHeaderFromMedia = restoreDataObject.RestoreParams.ReadHeaderFromMedia; - if (string.IsNullOrWhiteSpace(restoreDataObject.RestoreParams.SourceDatabaseName)) - { - restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.DefaultDbName; - } - else - { - restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.RestoreParams.SourceDatabaseName; - } + RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.SourceDatabaseName, restoreDataObject); + RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.TargetDatabaseName, restoreDataObject); - if (CanChangeTargetDatabase(restoreDataObject)) + if (shouldCreateNewPlan) { - restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName; + restoreDataObject.CreateNewRestorePlan(); } - else - { - restoreDataObject.TargetDatabase = restoreDataObject.Server.ConnectionContext.DatabaseName; - } - - restoreDataObject.UpdateRestorePlan(); + } private bool CanChangeTargetDatabase(RestoreDatabaseTaskDataObject restoreDataObject) diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs index 44a2e8de..046db852 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs @@ -12,6 +12,7 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; +using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { @@ -43,6 +44,19 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation RestoreParams RestoreParams { get; set; } bool BackupTailLog { get; set; } + + string DefaultSourceDbName { get; } + + string SourceDatabaseName { get; set; } + + List SourceDbNames { get; } + + bool CanChangeTargetDatabase { get; } + + string DefaultTargetDbName { get; } + string TargetDatabaseName { get; set; } + + } /// /// Includes the plan with all the data required to do a restore operation on server @@ -56,6 +70,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation private BackupSetsFilterInfo backupSetsFilterInfo = new BackupSetsFilterInfo(); private bool? isTailLogBackupPossible = false; private bool? isTailLogBackupWithNoRecoveryPossible = false; + private string backupMediaList = string.Empty; public RestoreDatabaseTaskDataObject(Server server, String databaseName) { @@ -95,11 +110,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation /// public SqlTask SqlTask { get; set; } - public string TargetDatabase + public string TargetDatabaseName { get { - return string.IsNullOrEmpty(targetDbName) ? DefaultDbName : targetDbName; + return string.IsNullOrEmpty(targetDbName) ? DefaultSourceDbName : targetDbName; } set { @@ -121,9 +136,19 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation /// Database names includes in the restore plan /// /// - public List GetPossibleTargerDbNames() + public List SourceDbNames { - return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName); + get + { + if (RestorePlanner.ReadHeaderFromMedia) + { + return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName); + } + else + { + return Util.GetSourceDbNames(); + } + } } /// @@ -135,6 +160,23 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation return Util.GetSourceDbNames(); } + public bool CanChangeTargetDatabase + { + get + { + return DatabaseUtils.IsSystemDatabaseConnection(Server.ConnectionContext.DatabaseName); + } + } + + public string DefaultTargetDbName + { + get + { + return Server.ConnectionContext.DatabaseName; + } + } + + /// /// Current sqlserver instance /// @@ -154,6 +196,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation /// public void AddFiles(string filePaths) { + backupMediaList = filePaths; PlanUpdateRequired = true; if (!string.IsNullOrWhiteSpace(filePaths)) { @@ -634,16 +677,40 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation /// /// The database from the backup file used to restore to by default /// - public string DefaultDbName + public string DefaultSourceDbName { get { - var dbNames = GetPossibleTargerDbNames(); + var dbNames = SourceDbNames; string dbName = dbNames.FirstOrDefault(); return dbName; } } + /// + /// Current value of source db name in the planner + /// + public string SourceDatabaseName + { + get + { + return this.RestorePlanner == null ? string.Empty : this.RestorePlanner.DatabaseName; + } + set + { + if(this.RestorePlanner != null) + { + string currentDatabaseName = this.RestorePlanner.DatabaseName; + this.RestorePlanner.DatabaseName = value; + + if (string.Compare(currentDatabaseName, value, StringComparison.InvariantCultureIgnoreCase) != 0) + { + ResetOptions(); + } + } + } + } + /// /// Gets or sets a value indicating whether [close existing connections]. /// @@ -682,10 +749,10 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation private string GetTargetDbFilePhysicalName(string sourceDbFilePhysicalLocation) { string fileName = Path.GetFileName(sourceDbFilePhysicalLocation); - if (!string.IsNullOrEmpty(this.DefaultDbName) && !string.IsNullOrEmpty(this.targetDbName)) + if (!string.IsNullOrEmpty(this.DefaultSourceDbName) && !string.IsNullOrEmpty(this.targetDbName)) { string sourceFilename = fileName; - fileName = sourceFilename.Replace(this.DefaultDbName, this.targetDbName); + fileName = sourceFilename.Replace(this.DefaultSourceDbName, this.targetDbName); } return fileName; } @@ -798,13 +865,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation bool isTailLogBackupWithNoRecoveryPossibleValue = isTailLogBackupPossibleValue; } - /// - /// Updates restore plan - /// - public void UpdateRestorePlan() + public void CreateNewRestorePlan() { - - ResetOptions(); 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; @@ -820,17 +882,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { this.RestorePlan = this.CreateRestorePlan(this.RestorePlanner, this.RestoreOptions); this.Util.AddCredentialNameForUrlBackupSet(this.restorePlan, this.CredentialName); - RestoreOptionsHelper.UpdateOptionsInPlan(this); + if (this.ActiveException == null) { this.dbFiles = this.GetDbFiles(); - UpdateDBFilesPhysicalRelocate(); - if (RelocateAllFiles) - { - UpdateDbFiles(); - } - this.SetRestorePlanProperties(this.restorePlan); + UpdateDBFilesPhysicalRelocate(); + + } } if (this.restorePlan == null) @@ -838,6 +897,26 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation this.RestorePlan = new RestorePlan(this.Server); this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName); } + } + + public bool ShouldCreateNewPlan() + { + return string.Compare(RestorePlanner.DatabaseName, this.RestoreParams.GetOptionValue(RestoreOptionsHelper.SourceDatabaseName), StringComparison.InvariantCultureIgnoreCase) != 0 || + RestorePlanner.ReadHeaderFromMedia != this.RestoreParams.ReadHeaderFromMedia || + string.Compare(this.backupMediaList, RestoreParams.BackupFilePaths, StringComparison.InvariantCultureIgnoreCase) != 0; + } + + /// + /// Updates restore plan + /// + public void UpdateRestorePlan() + { + RestoreOptionsHelper.UpdateOptionsInPlan(this); + if (RelocateAllFiles) + { + UpdateDbFiles(); + } + this.SetRestorePlanProperties(this.restorePlan); UpdateSelectedBackupSets(); } @@ -863,9 +942,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation } rp.SetRestoreOptions(this.RestoreOptions); rp.CloseExistingConnections = this.CloseExistingConnections; - if (this.TargetDatabase != null && !this.TargetDatabase.Equals(string.Empty)) + if (this.TargetDatabaseName != null && !this.TargetDatabaseName.Equals(string.Empty)) { - rp.DatabaseName = TargetDatabase; + rp.DatabaseName = TargetDatabaseName; } rp.RestoreOperations[0].RelocateFiles.Clear(); foreach (DbFile dbFile in this.DbFiles) diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs index fbf31a6a..fc4117bc 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.Utility; @@ -32,6 +33,13 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation } } + public RestorePlanDetailInfo CreateAndValidate(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject) + { + RestorePlanDetailInfo restorePlanDetailInfo = CreateOptionInfo(optionKey, restoreDataObject); + UpdateOption(optionKey, restoreDataObject, restorePlanDetailInfo); + return restorePlanDetailInfo; + } + /// /// Create option info using the current values /// @@ -74,13 +82,24 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation } /// - /// Set the option value if restore tak object using the values in the restore request + /// Set the option value in restore task object using the values in the restore request + /// + /// + /// + public void SetAndValidate(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject) + { + this.SetValue(optionKey, restoreDataObject); + this.ValidateOption(optionKey, restoreDataObject); + } + + /// + /// Set the option value in restore task object using the values in the restore request /// /// /// public void SetValue(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject) { - if(restoreDataObject != null) + if (restoreDataObject != null) { if (optionBuilders.ContainsKey(optionKey)) { @@ -107,7 +126,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation var defaultValue = builder.DefaultValueFunction(restoreDataObject); builder.SetValueFunction(restoreDataObject, defaultValue); } - catch(Exception) + catch (Exception) { Logger.Write(LogLevel.Warning, $"Failed to set restore option {optionKey} to default value"); } @@ -143,10 +162,15 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation errorMessage = $"{optionKey} is ready only and cannot be modified"; } } + if (!string.IsNullOrEmpty(result.ErrorMessage)) + { + errorMessage = result.ErrorMessage; + builder.SetValueFunction(restoreDataObject, defaultValue); + } } else { - errorMessage = "cannot find restore option builder for {optionKey}"; + errorMessage = $"cannot find restore option builder for {optionKey}"; Logger.Write(LogLevel.Warning, errorMessage); } @@ -469,6 +493,64 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation return true; } }); + Register(RestoreOptionsHelper.SourceDatabaseName, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.DefaultSourceDbName; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.SourceDatabaseName; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + string errorMessage = string.Empty; + var sourceDbNames = restoreDataObject.SourceDbNames; + if (currentValue == null || (sourceDbNames != null && + !sourceDbNames.Any(x => string.Compare(x, currentValue.ToString(), StringComparison.InvariantCultureIgnoreCase) == 0))) + { + errorMessage = "Source database name is not valid"; + } + return new OptionValidationResult() + { + ErrorMessage = errorMessage + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.SourceDatabaseName = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.TargetDatabaseName, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.CanChangeTargetDatabase ? restoreDataObject.DefaultSourceDbName : restoreDataObject.DefaultTargetDbName; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.TargetDatabaseName; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + + return new OptionValidationResult() + { + IsReadOnly = !restoreDataObject.CanChangeTargetDatabase + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.TargetDatabaseName = GetValueAs(value); + return true; + } + }); } internal T GetValueAs(object value) diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs index a5bba18d..e684ac6d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs @@ -15,7 +15,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { public class RestoreOptionsHelper { - //The list of name that service sends to client as options + //The list of names service uses to sends restore options to client private static string[] optionNames = new string[] { KeepReplication, ReplaceDatabase , SetRestrictedUser, RecoveryState , BackupTailLog , TailLogBackupFile, TailLogWithNoRecovery, CloseExistingConnections, RelocateDbFiles, DataFileFolder, LogFileFolder, StandbyFile, @@ -241,6 +241,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery Dictionary options = new Dictionary(); RestoreOptionFactory restoreOptionFactory = RestoreOptionFactory.Instance; + + //Create the options using the current values foreach (var optionKey in optionNames) { var optionInfo = restoreOptionFactory.CreateOptionInfo(optionKey, restoreDataObject); @@ -248,6 +250,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery } // After all options are set verify them all again to set the read only + // Because some options can change the readonly mode of other options.( e.g Recovery state can affect StandBy to be readyonly) foreach (var optionKey in optionNames) { restoreOptionFactory.UpdateOption(optionKey, restoreDataObject, options[optionKey]); @@ -292,7 +295,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery string error = restoreOptionFactory.ValidateOption(optionKey, restoreDataObject); if (!string.IsNullOrEmpty(error)) { - //TODO: we could send back the error message so client knows the option is set incorrectly + //TODO: we could send back the error message so client knows the option is set incorrectly } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreDatabaseTaskDataObjectStub.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreDatabaseTaskDataObjectStub.cs index 6a865701..ddc22c94 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreDatabaseTaskDataObjectStub.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreDatabaseTaskDataObjectStub.cs @@ -41,5 +41,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery public string DefaultStandbyFile { get; set; } public string DefaultTailLogbackupFile { get; set; } + + public string DefaultSourceDbName { get; set; } + public string SourceDatabaseName { get; set; } + + public List SourceDbNames { get; set; } + + public bool CanChangeTargetDatabase { get; set; } + + public string DefaultTargetDbName { get; set; } + + public string TargetDatabaseName { get; set; } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs index 59d9ef37..277fa209 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs @@ -3,14 +3,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using System; using System.Collections.Generic; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation; using Microsoft.SqlTools.ServiceLayer.Utility; -using Moq; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery @@ -33,14 +31,73 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery { GeneralRequestDetails optionValues = CreateOptionsTestData(); optionValues.Options["DbFiles"] = new List(); + optionValues.Options[RestoreOptionsHelper.RelocateDbFiles] = true; IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); - VerifyOptions(result, optionValues); Assert.True(result[RestoreOptionsHelper.RelocateDbFiles].IsReadOnly); } + [Fact] + public void DataFileFolderShouldBeReadOnlyGivenRelocateAllFilesSetToFalse() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options["DbFiles"] = new List() { new DbFile("", '1', "") }; + optionValues.Options[RestoreOptionsHelper.RelocateDbFiles] = false; + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); + Assert.NotNull(result); + Assert.True(result[RestoreOptionsHelper.DataFileFolder].IsReadOnly); + Assert.True(result[RestoreOptionsHelper.LogFileFolder].IsReadOnly); + } + + [Fact] + public void DataFileFolderShouldBeCurrentValueGivenRelocateAllFilesSetToTrue() + { + string dataFile = "data files"; + string logFile = "log files"; + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options["DbFiles"] = new List() { new DbFile("", '1', "") }; + optionValues.Options[RestoreOptionsHelper.RelocateDbFiles] = true; + optionValues.Options[RestoreOptionsHelper.DataFileFolder] = dataFile; + optionValues.Options[RestoreOptionsHelper.LogFileFolder] = logFile; + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); + Assert.NotNull(result); + Assert.False(result[RestoreOptionsHelper.DataFileFolder].IsReadOnly); + Assert.False(result[RestoreOptionsHelper.LogFileFolder].IsReadOnly); + Assert.Equal(result[RestoreOptionsHelper.DataFileFolder].CurrentValue, dataFile); + Assert.Equal(result[RestoreOptionsHelper.LogFileFolder].CurrentValue, logFile); + } + + + [Fact] + public void KeepReplicationShouldBeReadOnlyGivenRecoveryStateWithNoRecovery() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithNoRecovery; + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); + Assert.NotNull(result); + Assert.True(result[RestoreOptionsHelper.KeepReplication].IsReadOnly); + } + + [Fact] + public void StandbyFileShouldBeReadOnlyGivenRecoveryStateNotWithStandBy() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithNoRecovery; + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); + Assert.NotNull(result); + Assert.True(result[RestoreOptionsHelper.StandbyFile].IsReadOnly); + } + [Fact] public void BackupTailLogShouldBeReadOnlyTailLogBackupNotPossible() { @@ -50,7 +107,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); - VerifyOptions(result, optionValues); Assert.True(result[RestoreOptionsHelper.BackupTailLog].IsReadOnly); Assert.True(result[RestoreOptionsHelper.TailLogBackupFile].IsReadOnly); } @@ -64,7 +120,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); - VerifyOptions(result, optionValues); Assert.True(result[RestoreOptionsHelper.TailLogWithNoRecovery].IsReadOnly); } @@ -77,7 +132,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); - VerifyOptions(result, optionValues); Assert.False(result[RestoreOptionsHelper.StandbyFile].IsReadOnly); } @@ -90,7 +144,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); - VerifyOptions(result, optionValues); Assert.True(result[RestoreOptionsHelper.KeepReplication].IsReadOnly); } @@ -130,6 +183,82 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery } + [Fact] + public void SourceDatabaseNameShouldSetToDefaultIfNotValid() + { + RestoreParams restoreParams = CreateOptionsTestData(); + string defaultDbName = "default"; + string currentDbName = "db3"; + restoreParams.Options["SourceDbNames"] = new List { "db1", "db2" }; + restoreParams.Options["DefaultSourceDbName"] = defaultDbName; + restoreParams.Options[RestoreOptionsHelper.SourceDatabaseName] = currentDbName; + + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); + + RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.SourceDatabaseName, restoreDatabaseTaskDataObject); + + string actual = restoreDatabaseTaskDataObject.SourceDatabaseName; + string expected = defaultDbName; + Assert.Equal(actual, expected); + } + + [Fact] + public void SourceDatabaseNameShouldStayTheSameIfValid() + { + RestoreParams restoreParams = CreateOptionsTestData(); + string defaultDbName = "default"; + string currentDbName = "db3"; + restoreParams.Options["SourceDbNames"] = new List { "db1", "db2", "db3" }; + restoreParams.Options["DefaultSourceDbName"] = defaultDbName; + restoreParams.Options[RestoreOptionsHelper.SourceDatabaseName] = currentDbName; + + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); + + RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.SourceDatabaseName, restoreDatabaseTaskDataObject); + + string actual = restoreDatabaseTaskDataObject.SourceDatabaseName; + string expected = currentDbName; + Assert.Equal(actual, expected); + } + + [Fact] + public void TargetDatabaseNameShouldSetToDefaultIfNotValid() + { + RestoreParams restoreParams = CreateOptionsTestData(); + string defaultDbName = "default"; + string currentDbName = "db3"; + restoreParams.Options["DefaultTargetDbName"] = defaultDbName; + restoreParams.Options[RestoreOptionsHelper.TargetDatabaseName] = currentDbName; + restoreParams.Options["CanChangeTargetDatabase"] = false; + + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); + + RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.TargetDatabaseName, restoreDatabaseTaskDataObject); + + string actual = restoreDatabaseTaskDataObject.TargetDatabaseName; + string expected = defaultDbName; + Assert.Equal(actual, expected); + } + + [Fact] + public void TargetDatabaseNameShouldStayTheSameIfValid() + { + RestoreParams restoreParams = CreateOptionsTestData(); + string defaultDbName = "default"; + string currentDbName = "db3"; + restoreParams.Options["DefaultTargetDbName"] = defaultDbName; + restoreParams.Options[RestoreOptionsHelper.TargetDatabaseName] = currentDbName; + restoreParams.Options["CanChangeTargetDatabase"] = true; + + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); + + RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.TargetDatabaseName, restoreDatabaseTaskDataObject); + + string actual = restoreDatabaseTaskDataObject.TargetDatabaseName; + string expected = currentDbName; + Assert.Equal(actual, expected); + } + private RestoreParams CreateOptionsTestData() { @@ -143,16 +272,22 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery optionValues.Options.Add("IsTailLogBackupWithNoRecoveryPossible", true); optionValues.Options.Add("GetDefaultStandbyFile", "default standby file"); optionValues.Options.Add("GetDefaultTailLogbackupFile", "default tail log backup file"); - optionValues.Options.Add("LogFilesFolder", "Log file folder"); - optionValues.Options.Add("RelocateAllFiles", false); + optionValues.Options.Add(RestoreOptionsHelper.LogFileFolder, "Log file folder"); + optionValues.Options.Add(RestoreOptionsHelper.RelocateDbFiles, true); optionValues.Options.Add("TailLogBackupFile", "tail log backup file"); optionValues.Options.Add("TailLogWithNoRecovery", false); - optionValues.Options.Add("BackupTailLog", false); + optionValues.Options.Add(RestoreOptionsHelper.BackupTailLog, false); optionValues.Options.Add(RestoreOptionsHelper.KeepReplication, false); - optionValues.Options.Add("ReplaceDatabase", false); - optionValues.Options.Add("SetRestrictedUser", false); - optionValues.Options.Add("StandbyFile", "Stand by file"); + optionValues.Options.Add(RestoreOptionsHelper.ReplaceDatabase, false); + optionValues.Options.Add(RestoreOptionsHelper.SetRestrictedUser, false); + optionValues.Options.Add(RestoreOptionsHelper.StandbyFile, "Stand by file"); optionValues.Options.Add(RestoreOptionsHelper.RecoveryState, DatabaseRecoveryState.WithNoRecovery.ToString()); + optionValues.Options.Add(RestoreOptionsHelper.TargetDatabaseName, "target db name"); + optionValues.Options.Add(RestoreOptionsHelper.SourceDatabaseName, "source db name"); + optionValues.Options.Add("CanChangeTargetDatabase", true); + optionValues.Options.Add("DefaultSourceDbName", "DefaultSourceDbName"); + optionValues.Options.Add("DefaultTargetDbName", "DefaultTargetDbName"); + optionValues.Options.Add("SourceDbNames", new List()); return optionValues; } @@ -168,18 +303,24 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery restoreDataObject.IsTailLogBackupWithNoRecoveryPossible = optionValues.GetOptionValue("IsTailLogBackupWithNoRecoveryPossible"); restoreDataObject.DefaultStandbyFile = optionValues.GetOptionValue("GetDefaultStandbyFile"); restoreDataObject.DefaultTailLogbackupFile = optionValues.GetOptionValue("GetDefaultTailLogbackupFile"); - restoreDataObject.LogFilesFolder = optionValues.GetOptionValue("LogFilesFolder"); - restoreDataObject.RelocateAllFiles = optionValues.GetOptionValue("RelocateAllFiles"); + restoreDataObject.LogFilesFolder = optionValues.GetOptionValue(RestoreOptionsHelper.LogFileFolder); + restoreDataObject.RelocateAllFiles = optionValues.GetOptionValue(RestoreOptionsHelper.RelocateDbFiles); restoreDataObject.TailLogBackupFile = optionValues.GetOptionValue("TailLogBackupFile"); + restoreDataObject.SourceDatabaseName = optionValues.GetOptionValue(RestoreOptionsHelper.SourceDatabaseName); + restoreDataObject.TargetDatabaseName = optionValues.GetOptionValue(RestoreOptionsHelper.TargetDatabaseName); restoreDataObject.TailLogWithNoRecovery = optionValues.GetOptionValue("TailLogWithNoRecovery"); - restoreDataObject.BackupTailLog = optionValues.GetOptionValue("BackupTailLog"); + restoreDataObject.CanChangeTargetDatabase = optionValues.GetOptionValue("CanChangeTargetDatabase"); + restoreDataObject.DefaultSourceDbName = optionValues.GetOptionValue("DefaultSourceDbName"); + restoreDataObject.SourceDbNames = optionValues.GetOptionValue>("SourceDbNames"); + restoreDataObject.DefaultTargetDbName = optionValues.GetOptionValue("DefaultTargetDbName"); + restoreDataObject.BackupTailLog = optionValues.GetOptionValue(RestoreOptionsHelper.BackupTailLog); restoreDataObject.RestoreParams = optionValues as RestoreParams; restoreDataObject.RestorePlan = null; RestoreOptions restoreOptions = new RestoreOptions(); restoreOptions.KeepReplication = optionValues.GetOptionValue(RestoreOptionsHelper.KeepReplication); - restoreOptions.ReplaceDatabase = optionValues.GetOptionValue("ReplaceDatabase"); - restoreOptions.SetRestrictedUser = optionValues.GetOptionValue("SetRestrictedUser"); - restoreOptions.StandByFile = optionValues.GetOptionValue("StandbyFile"); + restoreOptions.ReplaceDatabase = optionValues.GetOptionValue(RestoreOptionsHelper.ReplaceDatabase); + restoreOptions.SetRestrictedUser = optionValues.GetOptionValue(RestoreOptionsHelper.SetRestrictedUser); + restoreOptions.StandByFile = optionValues.GetOptionValue(RestoreOptionsHelper.StandbyFile); restoreOptions.RecoveryState = optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState); restoreDataObject.RestoreOptions = restoreOptions; @@ -191,29 +332,29 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery { RestorePlanDetailInfo planDetailInfo = optionInResponse[RestoreOptionsHelper.DataFileFolder]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.DataFileFolder); - Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("RelocateAllFiles")); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue(RestoreOptionsHelper.RelocateDbFiles)); Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.DataFileFolder)); Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("DefaultDataFileFolder")); Assert.Equal(planDetailInfo.IsVisiable, true); planDetailInfo = optionInResponse[RestoreOptionsHelper.LogFileFolder]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.LogFileFolder); - Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("RelocateAllFiles")); - Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("LogFilesFolder")); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue(RestoreOptionsHelper.RelocateDbFiles)); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.LogFileFolder)); Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("DefaultLogFileFolder")); Assert.Equal(planDetailInfo.IsVisiable, true); planDetailInfo = optionInResponse[RestoreOptionsHelper.RelocateDbFiles]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.RelocateDbFiles); Assert.Equal(planDetailInfo.IsReadOnly, (optionValues.GetOptionValue>("DbFiles").Count == 0)); - Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("LogFilesFolder")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.RelocateDbFiles)); Assert.Equal(planDetailInfo.DefaultValue, false); Assert.Equal(planDetailInfo.IsVisiable, true); planDetailInfo = optionInResponse[RestoreOptionsHelper.ReplaceDatabase]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.ReplaceDatabase); Assert.Equal(planDetailInfo.IsReadOnly, false); - Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("ReplaceDatabase")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.ReplaceDatabase)); Assert.Equal(planDetailInfo.DefaultValue, false); Assert.Equal(planDetailInfo.IsVisiable, true); @@ -227,7 +368,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery planDetailInfo = optionInResponse[RestoreOptionsHelper.SetRestrictedUser]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.SetRestrictedUser); Assert.Equal(planDetailInfo.IsReadOnly, false); - Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("SetRestrictedUser")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.SetRestrictedUser)); Assert.Equal(planDetailInfo.DefaultValue, false); Assert.Equal(planDetailInfo.IsVisiable, true); @@ -241,14 +382,14 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery planDetailInfo = optionInResponse[RestoreOptionsHelper.StandbyFile]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.StandbyFile); Assert.Equal(planDetailInfo.IsReadOnly, optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState) != DatabaseRecoveryState.WithStandBy); - Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("StandbyFile")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.StandbyFile)); Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("GetDefaultStandbyFile")); Assert.Equal(planDetailInfo.IsVisiable, true); planDetailInfo = optionInResponse[RestoreOptionsHelper.BackupTailLog]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.BackupTailLog); Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("IsTailLogBackupPossible")); - Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("BackupTailLog")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.BackupTailLog)); Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("IsTailLogBackupPossible")); Assert.Equal(planDetailInfo.IsVisiable, true); @@ -271,7 +412,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery planDetailInfo = optionInResponse[RestoreOptionsHelper.CloseExistingConnections]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.CloseExistingConnections); Assert.Equal(planDetailInfo.IsReadOnly, false); - Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("CloseExistingConnections")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.CloseExistingConnections)); Assert.Equal(planDetailInfo.DefaultValue, false); Assert.Equal(planDetailInfo.IsVisiable, true); }