diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs index ea0d3757f7..e381656151 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs @@ -35,6 +35,11 @@ public class RestorePlanDetailInfo /// public object DefaultValue { get; set; } + /// + /// Error message if the current value is not valid + /// + public object ErrorMessage { get; set; } + internal static RestorePlanDetailInfo Create(string name, object currentValue, bool isReadOnly = false, bool isVisible = true, object defaultValue = null) { return new RestorePlanDetailInfo diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs index 7c3d51bdaa..816161e769 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs @@ -15,6 +15,8 @@ using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.Utility; +using System.Collections.Concurrent; +using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { @@ -24,6 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation public class RestoreDatabaseHelper { public const string LastBackupTaken = "lastBackupTaken"; + private ConcurrentDictionary sessions = new ConcurrentDictionary(); /// /// Create a backup task for execution and cancellation @@ -169,6 +172,16 @@ public RestorePlanResponse CreateRestorePlanResponse(RestoreDatabaseTaskDataObje { 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.PlanDetails.Add(RestoreOptionsHelper.ReadHeaderFromMedia, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.ReadHeaderFromMedia, + currentValue: restoreDataObject.RestorePlanner.ReadHeaderFromMedia)); response.DbFiles = restoreDataObject.DbFiles.Select(x => new RestoreDatabaseFileInfo { FileType = x.DbFileType, @@ -183,10 +196,11 @@ public RestorePlanResponse CreateRestorePlanResponse(RestoreDatabaseTaskDataObje response.ErrorMessage = SR.RestoreNotSupported; } - response.PlanDetails.Add(LastBackupTaken, RestorePlanDetailInfo.Create(LastBackupTaken, restoreDataObject.GetLastBackupTaken())); + response.PlanDetails.Add(LastBackupTaken, + RestorePlanDetailInfo.Create(name: LastBackupTaken, currentValue: restoreDataObject.GetLastBackupTaken(), isReadOnly: true)); response.BackupSetsToRestore = restoreDataObject.GetSelectedBakupSets(); - var dbNames = restoreDataObject.GetSourceDbNames(); + var dbNames = restoreDataObject.GetPossibleTargerDbNames(); response.DatabaseNamesFromBackupSets = dbNames == null ? new string[] { } : dbNames.ToArray(); RestoreOptionsHelper.AddOptions(response, restoreDataObject); @@ -241,10 +255,15 @@ private static bool CanRestore(RestoreDatabaseTaskDataObject restoreDataObject) public RestoreDatabaseTaskDataObject CreateRestoreDatabaseTaskDataObject(RestoreParams restoreParams) { RestoreDatabaseTaskDataObject restoreTaskObject = null; - restoreTaskObject = CreateRestoreForNewSession(restoreParams.OwnerUri, restoreParams.TargetDatabaseName); string sessionId = string.IsNullOrWhiteSpace(restoreParams.SessionId) ? Guid.NewGuid().ToString() : restoreParams.SessionId; + if (!sessions.TryGetValue(sessionId, out restoreTaskObject)) + { + restoreTaskObject = CreateRestoreForNewSession(restoreParams.OwnerUri, restoreParams.TargetDatabaseName); + } restoreTaskObject.SessionId = sessionId; restoreTaskObject.RestoreParams = restoreParams; + restoreTaskObject.TargetDatabase = restoreParams.TargetDatabaseName; + restoreTaskObject.RestorePlanner.DatabaseName = restoreParams.TargetDatabaseName; return restoreTaskObject; } @@ -303,13 +322,26 @@ private void UpdateRestorePlan(RestoreDatabaseTaskDataObject restoreDataObject) { restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.RestoreParams.SourceDatabaseName; } - restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName; - RestoreOptionsHelper.UpdateOptionsInPlan(restoreDataObject); + if (CanChangeTargetDatabase(restoreDataObject)) + { + restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName; + } + else + { + restoreDataObject.TargetDatabase = restoreDataObject.Server.ConnectionContext.DatabaseName; + } + + restoreDataObject.UpdateRestorePlan(); } + private bool CanChangeTargetDatabase(RestoreDatabaseTaskDataObject restoreDataObject) + { + return DatabaseUtils.IsSystemDatabaseConnection(restoreDataObject.Server.ConnectionContext.DatabaseName); + } + /// /// Executes the restore operation /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs index 59e6da0c73..44a2e8de1c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs @@ -26,15 +26,15 @@ public interface IRestoreDatabaseTaskDataObject RestoreOptions RestoreOptions { get; } - string GetDefaultStandbyFile(string databaseName); - bool IsTailLogBackupPossible(string databaseName); - bool IsTailLogBackupWithNoRecoveryPossible(string databaseName); + string DefaultStandbyFile { get; } + bool IsTailLogBackupPossible { get; } + bool IsTailLogBackupWithNoRecoveryPossible { get; } bool TailLogWithNoRecovery { get; set; } string TailLogBackupFile { get; set; } - string GetDefaultTailLogbackupFile(string databaseName); + string DefaultTailLogbackupFile { get; } RestorePlan RestorePlan { get; } @@ -54,6 +54,8 @@ public class RestoreDatabaseTaskDataObject : IRestoreDatabaseTaskDataObject private DatabaseRestorePlanner restorePlanner; private string tailLogBackupFile; private BackupSetsFilterInfo backupSetsFilterInfo = new BackupSetsFilterInfo(); + private bool? isTailLogBackupPossible = false; + private bool? isTailLogBackupWithNoRecoveryPossible = false; public RestoreDatabaseTaskDataObject(Server server, String databaseName) { @@ -119,7 +121,7 @@ public bool IsValid /// Database names includes in the restore plan /// /// - public List GetSourceDbNames() + public List GetPossibleTargerDbNames() { return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName); } @@ -169,7 +171,7 @@ public void AddFiles(string filePaths) } } - var itemsToRemove = this.RestorePlanner.BackupMediaList.Where(x => !files.Contains(x.Name)); + var itemsToRemove = this.RestorePlanner.BackupMediaList.Where(x => !files.Contains(x.Name)).ToList(); foreach (var item in itemsToRemove) { this.RestorePlanner.BackupMediaList.Remove(item); @@ -349,7 +351,7 @@ public string DataFilesFolder } else { - this.dataFilesFolder = PathWrapper.GetDirectoryName(value); + this.dataFilesFolder = PathWrapper.GetDirectoryName(value + PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext)); } if (string.IsNullOrEmpty(this.dataFilesFolder)) { @@ -397,7 +399,7 @@ public string LogFilesFolder } else { - this.logFilesFolder = PathWrapper.GetDirectoryName(value); + this.logFilesFolder = PathWrapper.GetDirectoryName(value + PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext)); } if (string.IsNullOrEmpty(this.logFilesFolder)) { @@ -419,32 +421,44 @@ public string LogFilesFolder /// /// true if [is tail log backup possible]; otherwise, false. /// - public bool IsTailLogBackupPossible(string databaseName) + public bool IsTailLogBackupPossible { - if (this.Server.Version.Major < 9 || String.IsNullOrEmpty(this.restorePlanner.DatabaseName)) - { - return false; - } - - Database db = this.Server.Databases[databaseName]; - if (db == null) - { - return false; - } - else + get { - db.Refresh(); - } + if (!isTailLogBackupPossible.HasValue) + { + if (this.Server.Version.Major < 9 || String.IsNullOrEmpty(this.restorePlanner.DatabaseName)) + { + isTailLogBackupPossible = false; + } + else + { + Database db = this.Server.Databases[this.RestorePlanner.DatabaseName]; + if (db == null) + { + isTailLogBackupPossible = false; + } + else + { + db.Refresh(); + if (db.Status != DatabaseStatus.Normal && db.Status != DatabaseStatus.Suspect && db.Status != DatabaseStatus.EmergencyMode) + { + isTailLogBackupPossible = false; + } + else if (db.RecoveryModel == RecoveryModel.Full || db.RecoveryModel == RecoveryModel.BulkLogged) + { + isTailLogBackupPossible = true; + } + else + { + isTailLogBackupPossible = false; + } + } + } + } - if (db.Status != DatabaseStatus.Normal && db.Status != DatabaseStatus.Suspect && db.Status != DatabaseStatus.EmergencyMode) - { - return false; + return isTailLogBackupPossible.Value; } - if (db.RecoveryModel == RecoveryModel.Full || db.RecoveryModel == RecoveryModel.BulkLogged) - { - return true; - } - return false; } /// @@ -453,37 +467,56 @@ public bool IsTailLogBackupPossible(string databaseName) /// /// true if [is tail log backup with NORECOVERY possible]; otherwise, false. /// - public bool IsTailLogBackupWithNoRecoveryPossible(string databaseName) + public bool IsTailLogBackupWithNoRecoveryPossible { - if (!IsTailLogBackupPossible(databaseName)) - { - return false; - } - - Database db = this.Server.Databases[databaseName]; - if (db == null) - { - return false; - } - if (Server.Version.Major > 10 && db.DatabaseEngineType == DatabaseEngineType.Standalone && !String.IsNullOrEmpty(db.AvailabilityGroupName)) - { - return false; - } - if (db.DatabaseEngineType == DatabaseEngineType.Standalone && db.IsMirroringEnabled) + get { - return false; + if (!isTailLogBackupWithNoRecoveryPossible.HasValue) + { + string databaseName = this.RestorePlanner.DatabaseName; + if (!IsTailLogBackupPossible) + { + isTailLogBackupWithNoRecoveryPossible = false; + } + else + { + Database db = this.Server.Databases[databaseName]; + if (db == null) + { + isTailLogBackupWithNoRecoveryPossible = false; + } + else if (Server.Version.Major > 10 && db.DatabaseEngineType == DatabaseEngineType.Standalone && !String.IsNullOrEmpty(db.AvailabilityGroupName)) + { + isTailLogBackupWithNoRecoveryPossible = false; + } + else if (db.DatabaseEngineType == DatabaseEngineType.Standalone && db.IsMirroringEnabled) + { + isTailLogBackupWithNoRecoveryPossible = false; + } + else + { + isTailLogBackupWithNoRecoveryPossible = true; + } + } + } + return isTailLogBackupWithNoRecoveryPossible.Value; } - return true; } - public string GetDefaultStandbyFile(string databaseName) + public string DefaultStandbyFile { - return Util.GetDefaultStandbyFile(databaseName); + get + { + return Util.GetDefaultStandbyFile(this.RestorePlan != null ? this.RestorePlan.DatabaseName : string.Empty); + } } - public string GetDefaultTailLogbackupFile(string databaseName) + public string DefaultTailLogbackupFile { - return Util.GetDefaultTailLogbackupFile(databaseName); + get + { + return Util.GetDefaultTailLogbackupFile(this.RestorePlan != null ? this.RestorePlan.DatabaseName : string.Empty); + } } /// @@ -605,7 +638,7 @@ public string DefaultDbName { get { - var dbNames = GetSourceDbNames(); + var dbNames = GetPossibleTargerDbNames(); string dbName = dbNames.FirstOrDefault(); return dbName; } @@ -663,10 +696,6 @@ public IEnumerable GetBackupSetInfo() foreach (Restore restore in RestorePlan.RestoreOperations) { BackupSetInfo backupSetInfo = BackupSetInfo.Create(restore, Server); - if (this.backupSetsFilterInfo.IsBackupSetSelected(restore.BackupSet)) - { - - } result.Add(backupSetInfo); } @@ -761,11 +790,21 @@ internal RestorePlan CreateRestorePlan(DatabaseRestorePlanner planner, RestoreOp return ret; } + private void ResetOptions() + { + isTailLogBackupPossible = null; + isTailLogBackupWithNoRecoveryPossible = null; + bool isTailLogBackupPossibleValue = IsTailLogBackupPossible; + bool isTailLogBackupWithNoRecoveryPossibleValue = isTailLogBackupPossibleValue; + } + /// /// Updates restore plan /// public void UpdateRestorePlan() { + + 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; @@ -781,6 +820,7 @@ public void UpdateRestorePlan() { 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(); @@ -979,6 +1019,7 @@ public void UpdateSelectedBackupSets() // 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); @@ -1016,6 +1057,22 @@ public void UpdateSelectedBackupSets() { break; } + + //If the second item is not selected and it's a diff backup + if (index == 1 && backupSet.BackupSetType == BackupSetType.Differential) + { + if (this.Server.Version.Major < 9 || + (this.RestorePlan.RestoreOperations.Count >= 3 && + BackupSet.IsBackupSetsInSequence(this.RestorePlan.RestoreOperations[0].BackupSet, this.RestorePlan.RestoreOperations[2].BackupSet))) + { + // only the item at index 1 won't be selected + } + else + { + // nothing after index 1 should be selected + break; + } + } } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs new file mode 100644 index 0000000000..fbf31a6ac3 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreOptionFactory.cs @@ -0,0 +1,525 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections.Generic; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; +using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation +{ + /// + /// A factory class to create restore option info + /// + public class RestoreOptionFactory + { + private static RestoreOptionFactory instance = new RestoreOptionFactory(); + + Dictionary optionBuilders = new Dictionary(); + + /// + /// Singleton instance + /// + public static RestoreOptionFactory Instance + { + get + { + return instance; + } + } + + /// + /// Create option info using the current values + /// + /// Option name + /// Restore task object + /// + public RestorePlanDetailInfo CreateOptionInfo(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject) + { + if(optionBuilders.ContainsKey(optionKey)) + { + return Create(optionKey, restoreDataObject, optionBuilders[optionKey]); + } + else + { + Logger.Write(LogLevel.Warning, $"cannot find restore option builder for {optionKey}"); + return null; + } + } + + /// + /// Update the option info by validating the option + /// + /// + /// + /// + public void UpdateOption(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject, RestorePlanDetailInfo optionInfo) + { + if (optionBuilders.ContainsKey(optionKey)) + { + var builder = optionBuilders[optionKey]; + var currentValue = builder.CurrentValueFunction(restoreDataObject); + var defaultValue = builder.DefaultValueFunction(restoreDataObject); + var validateResult = builder.ValidateFunction(restoreDataObject, currentValue, defaultValue); + optionInfo.IsReadOnly = validateResult.IsReadOnly; + } + else + { + Logger.Write(LogLevel.Warning, $"cannot find restore option builder for {optionKey}"); + } + } + + /// + /// Set the option value if restore tak object using the values in the restore request + /// + /// + /// + public void SetValue(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject) + { + if(restoreDataObject != null) + { + if (optionBuilders.ContainsKey(optionKey)) + { + var builder = optionBuilders[optionKey]; + if (restoreDataObject.RestoreParams != null && restoreDataObject.RestoreParams.Options.ContainsKey(optionKey)) + { + try + { + var value = restoreDataObject.RestoreParams.GetOptionValue(optionKey); + builder.SetValueFunction(restoreDataObject, value); + } + catch (Exception ex) + { + var defaultValue = builder.DefaultValueFunction(restoreDataObject); + builder.SetValueFunction(restoreDataObject, defaultValue); + Logger.Write(LogLevel.Warning, $"Failed tp set restore option {optionKey} error:{ex.Message}"); + + } + } + else + { + try + { + var defaultValue = builder.DefaultValueFunction(restoreDataObject); + builder.SetValueFunction(restoreDataObject, defaultValue); + } + catch(Exception) + { + Logger.Write(LogLevel.Warning, $"Failed to set restore option {optionKey} to default value"); + } + } + } + else + { + Logger.Write(LogLevel.Warning, $"cannot find restore option builder for {optionKey}"); + } + } + } + + /// + /// Validates the options, if option is not set correctly, set to default and return the error + /// + /// + /// + /// + public string ValidateOption(string optionKey, IRestoreDatabaseTaskDataObject restoreDataObject) + { + string errorMessage = string.Empty; + if (optionBuilders.ContainsKey(optionKey)) + { + var builder = optionBuilders[optionKey]; + var currentValue = builder.CurrentValueFunction(restoreDataObject); + var defaultValue = builder.DefaultValueFunction(restoreDataObject); + OptionValidationResult result = optionBuilders[optionKey].ValidateFunction(restoreDataObject, currentValue, defaultValue); + if (result.IsReadOnly) + { + if(!ValueEqualsDefault(currentValue, defaultValue)) + { + builder.SetValueFunction(restoreDataObject, defaultValue); + errorMessage = $"{optionKey} is ready only and cannot be modified"; + } + } + } + else + { + errorMessage = "cannot find restore option builder for {optionKey}"; + Logger.Write(LogLevel.Warning, errorMessage); + } + + return errorMessage; + } + + private bool ValueEqualsDefault(object currentValue, object defaultValue) + { + if(currentValue == null && defaultValue == null) + { + return true; + } + if(currentValue == null && defaultValue != null) + { + return false; + } + if (currentValue != null && defaultValue == null) + { + return false; + } + return currentValue.Equals(defaultValue); + } + + + private RestoreOptionFactory() + { + Register(RestoreOptionsHelper.RelocateDbFiles, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return false; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.RelocateAllFiles; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult + { + IsReadOnly = restoreDataObject.DbFiles.Count == 0 + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.RelocateAllFiles = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.RelocateDbFiles); + return true; + } + }); + Register(RestoreOptionsHelper.DataFileFolder, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.DefaultDataFileFolder; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.DataFilesFolder; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult + { + IsReadOnly = !restoreDataObject.RelocateAllFiles + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.DataFilesFolder = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.LogFileFolder, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.DefaultLogFileFolder; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.LogFilesFolder; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult + { + IsReadOnly = !restoreDataObject.RelocateAllFiles + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.LogFilesFolder = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.ReplaceDatabase, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return false; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.RestoreOptions.ReplaceDatabase; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult(); + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.RestoreOptions.ReplaceDatabase = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.KeepReplication, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return false; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.RestoreOptions.KeepReplication; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult() + { + IsReadOnly = restoreDataObject.RestoreOptions.RecoveryState == DatabaseRecoveryState.WithNoRecovery + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.RestoreOptions.KeepReplication = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.SetRestrictedUser, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return false; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.RestoreOptions.SetRestrictedUser; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult() + { + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.RestoreOptions.SetRestrictedUser = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.RecoveryState, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return DatabaseRecoveryState.WithRecovery.ToString(); + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.RestoreOptions.RecoveryState.ToString(); + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult() + { + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.RestoreOptions.RecoveryState = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.StandbyFile, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.DefaultStandbyFile; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.RestoreOptions.StandByFile; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult() + { + IsReadOnly = restoreDataObject.RestoreOptions.RecoveryState != DatabaseRecoveryState.WithStandBy + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.RestoreOptions.StandByFile = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.BackupTailLog, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.IsTailLogBackupPossible; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.BackupTailLog; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult() + { + IsReadOnly = !restoreDataObject.IsTailLogBackupPossible + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.BackupTailLog = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.TailLogBackupFile, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.DefaultTailLogbackupFile; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.TailLogBackupFile; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult() + { + IsReadOnly = !restoreDataObject.BackupTailLog | !restoreDataObject.IsTailLogBackupPossible + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.TailLogBackupFile = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.TailLogWithNoRecovery, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.IsTailLogBackupWithNoRecoveryPossible; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.TailLogWithNoRecovery; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult() + { + IsReadOnly = !restoreDataObject.BackupTailLog | !restoreDataObject.IsTailLogBackupWithNoRecoveryPossible + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.TailLogWithNoRecovery = GetValueAs(value); + return true; + } + }); + Register(RestoreOptionsHelper.CloseExistingConnections, + new OptionBuilder + { + DefaultValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return false; + }, + CurrentValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject) => + { + return restoreDataObject.CloseExistingConnections; + }, + ValidateFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object currentValue, object defaultValue) => + { + return new OptionValidationResult() + { + //TODO: make the method public in SMO bool canDropExistingConnections = restoreDataObject.RestorePlan.CanDropExistingConnections(this.Data.RestorePlanner.DatabaseName); + IsReadOnly = false + }; + }, + SetValueFunction = (IRestoreDatabaseTaskDataObject restoreDataObject, object value) => + { + + restoreDataObject.CloseExistingConnections = GetValueAs(value); + return true; + } + }); + } + + internal T GetValueAs(object value) + { + return GeneralRequestDetails.GetValueAs(value); + } + + private void Register(string optionKey, OptionBuilder optionBuilder) + { + optionBuilders.Add(optionKey, optionBuilder); + } + + private RestorePlanDetailInfo Create( + string optionKey, + IRestoreDatabaseTaskDataObject restoreDataObject, + OptionBuilder optionBuilder) + { + object currnetValue = optionBuilder.CurrentValueFunction(restoreDataObject); + object defaultValue = optionBuilder.DefaultValueFunction(restoreDataObject); + OptionValidationResult validationResult = optionBuilder.ValidateFunction(restoreDataObject, currnetValue, defaultValue); + return new RestorePlanDetailInfo + { + Name = optionKey, + CurrentValue = currnetValue, + DefaultValue = defaultValue, + IsReadOnly = validationResult.IsReadOnly, + IsVisiable = validationResult.IsVisible, + ErrorMessage = validationResult.ErrorMessage + }; + } + } + + internal class OptionBuilder + { + public Func DefaultValueFunction { get; set; } + public Func ValidateFunction { get; set; } + public Func CurrentValueFunction { get; set; } + public Func SetValueFunction { get; set; } + } + + internal class OptionValidationResult + { + public OptionValidationResult() + { + IsVisible = true; + IsReadOnly = false; + ErrorMessage = string.Empty; + } + public bool IsReadOnly { get; set; } + public bool IsVisible { get; set; } + + public string ErrorMessage { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs index 1df2207463..a5bba18d54 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs @@ -15,6 +15,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { public class RestoreOptionsHelper { + //The list of name that service sends to client as options + private static string[] optionNames = new string[] { KeepReplication, ReplaceDatabase , SetRestrictedUser, RecoveryState , + BackupTailLog , TailLogBackupFile, TailLogWithNoRecovery, CloseExistingConnections, RelocateDbFiles, DataFileFolder, LogFileFolder, + StandbyFile, + }; //The key names of restore info in the resquest of response //Option name keepReplication @@ -235,125 +240,21 @@ internal static Dictionary CreateRestorePlanOptio Validate.IsNotNull(nameof(restoreDataObject), restoreDataObject); Dictionary options = new Dictionary(); - string databaseName = restoreDataObject.RestorePlan == null ? string.Empty : restoreDataObject.RestorePlan.DatabaseName; - //Files - - // Default Data folder path in the target server - options.Add(RestoreOptionsHelper.DataFileFolder, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.DataFileFolder, - currentValue: restoreDataObject.DataFilesFolder, - defaultValue: restoreDataObject.DefaultDataFileFolder, - isReadOnly: !restoreDataObject.RelocateAllFiles, - isVisible: true - )); - - // Default log folder path in the target server - options.Add(RestoreOptionsHelper.LogFileFolder, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.LogFileFolder, - currentValue: restoreDataObject.LogFilesFolder, - defaultValue: restoreDataObject.DefaultLogFileFolder, - isReadOnly: !restoreDataObject.RelocateAllFiles, - isVisible: true - )); - - // Relocate all files - options.Add(RestoreOptionsHelper.RelocateDbFiles, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.RelocateDbFiles, - currentValue: restoreDataObject.RelocateAllFiles, - defaultValue: false, - isReadOnly: restoreDataObject.DbFiles.Count == 0, - isVisible: true - )); - - - //Options - - //With Replace - options.Add(RestoreOptionsHelper.ReplaceDatabase, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.ReplaceDatabase, - currentValue: restoreDataObject.RestoreOptions.ReplaceDatabase, - defaultValue: false, - isReadOnly: false, - isVisible: true - )); - - //Keep replication - options.Add(RestoreOptionsHelper.KeepReplication, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.KeepReplication, - currentValue: restoreDataObject.RestoreOptions.KeepReplication, - defaultValue: false, - isReadOnly: restoreDataObject.RestoreOptions.RecoveryState == DatabaseRecoveryState.WithNoRecovery, - isVisible: true - )); - - //Restricted user - options.Add(RestoreOptionsHelper.SetRestrictedUser, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.SetRestrictedUser, - currentValue: restoreDataObject.RestoreOptions.SetRestrictedUser, - defaultValue: false, - isReadOnly: false, - isVisible: true - )); - - //State recovery - options.Add(RestoreOptionsHelper.RecoveryState, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.RecoveryState, - currentValue: restoreDataObject.RestoreOptions.RecoveryState.ToString(), - defaultValue: DatabaseRecoveryState.WithRecovery.ToString(), - isReadOnly: false, - isVisible: true - )); - - // stand by file path for when RESTORE WITH STANDBY is selected - options.Add(RestoreOptionsHelper.StandbyFile, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.StandbyFile, - currentValue: restoreDataObject.RestoreOptions.StandByFile, - defaultValue: restoreDataObject.GetDefaultStandbyFile(databaseName), - isReadOnly: restoreDataObject.RestoreOptions.RecoveryState != DatabaseRecoveryState.WithStandBy, - isVisible: true - )); - - // Tail-log backup - // TODO:These methods are internal in SMO. after making them public, they can be removed from RestoreDatabaseTaskDataObject - bool isTailLogBackupPossible = restoreDataObject.IsTailLogBackupPossible(databaseName); - bool isTailLogBackupWithNoRecoveryPossible = restoreDataObject.IsTailLogBackupWithNoRecoveryPossible(databaseName); - - options.Add(RestoreOptionsHelper.BackupTailLog, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.BackupTailLog, - currentValue: restoreDataObject.BackupTailLog, - defaultValue: isTailLogBackupPossible, - isReadOnly: !isTailLogBackupPossible, - isVisible: true - )); - - options.Add(RestoreOptionsHelper.TailLogBackupFile, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.TailLogBackupFile, - currentValue: restoreDataObject.TailLogBackupFile, - defaultValue: restoreDataObject.GetDefaultTailLogbackupFile(databaseName), - isReadOnly: !isTailLogBackupPossible, - isVisible: true - )); - - options.Add(RestoreOptionsHelper.TailLogWithNoRecovery, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.TailLogWithNoRecovery, - currentValue: restoreDataObject.TailLogWithNoRecovery, - defaultValue: isTailLogBackupWithNoRecoveryPossible, - isReadOnly: !isTailLogBackupWithNoRecoveryPossible, - isVisible: true - )); - - - //TODO: make the method public in SMO bool canDropExistingConnections = restoreDataObject.RestorePlan.CanDropExistingConnections(this.Data.RestorePlanner.DatabaseName); - options.Add(RestoreOptionsHelper.CloseExistingConnections, RestorePlanDetailInfo.Create( - name: RestoreOptionsHelper.CloseExistingConnections, - currentValue: restoreDataObject.CloseExistingConnections, - defaultValue: false, - isReadOnly: false, //TODO: !canDropExistingConnections - isVisible: true - )); + RestoreOptionFactory restoreOptionFactory = RestoreOptionFactory.Instance; + foreach (var optionKey in optionNames) + { + var optionInfo = restoreOptionFactory.CreateOptionInfo(optionKey, restoreDataObject); + options.Add(optionKey, optionInfo); + } + // After all options are set verify them all again to set the read only + foreach (var optionKey in optionNames) + { + restoreOptionFactory.UpdateOption(optionKey, restoreDataObject, options[optionKey]); + } return options; } + /// /// Add options to restore plan response /// @@ -372,58 +273,29 @@ internal static void AddOptions(RestorePlanResponse response, RestoreDatabaseTas } } - internal static T GetOptionValue(string optionkey, Dictionary optionsMetadata, IRestoreDatabaseTaskDataObject restoreDataObject) - { - RestorePlanDetailInfo optionMetadata = null; - if(optionsMetadata.TryGetValue(optionkey, out optionMetadata)) - { - if (!optionMetadata.IsReadOnly) - { - return restoreDataObject.RestoreParams.GetOptionValue(optionkey); - } - else - { - return (T)Convert.ChangeType(optionMetadata.DefaultValue, typeof(T)); - } - } - else - { - return default(T); - } - } - /// /// Load options in restore plan /// internal static void UpdateOptionsInPlan(IRestoreDatabaseTaskDataObject restoreDataObject) { - var options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDataObject); - - //Files - restoreDataObject.LogFilesFolder = GetOptionValue(RestoreOptionsHelper.LogFileFolder, options, restoreDataObject); - restoreDataObject.DataFilesFolder = GetOptionValue(RestoreOptionsHelper.DataFileFolder, options, restoreDataObject); - restoreDataObject.RelocateAllFiles = GetOptionValue(RestoreOptionsHelper.RelocateDbFiles, options, restoreDataObject); + RestoreOptionFactory restoreOptionFactory = RestoreOptionFactory.Instance; - //Options - object databaseRecoveryState; - string recoveryState = GetOptionValue(RestoreOptionsHelper.RecoveryState, options, restoreDataObject); - if (Enum.TryParse(typeof(DatabaseRecoveryState), recoveryState, out databaseRecoveryState)) + foreach (var optionKey in optionNames) { - restoreDataObject.RestoreOptions.RecoveryState = (DatabaseRecoveryState)databaseRecoveryState; + restoreOptionFactory.SetValue(optionKey, restoreDataObject); } - restoreDataObject.RestoreOptions.KeepReplication = GetOptionValue(RestoreOptionsHelper.KeepReplication, options, restoreDataObject); - restoreDataObject.RestoreOptions.ReplaceDatabase = GetOptionValue(RestoreOptionsHelper.ReplaceDatabase, options, restoreDataObject); - restoreDataObject.RestoreOptions.SetRestrictedUser = GetOptionValue(RestoreOptionsHelper.SetRestrictedUser, options, restoreDataObject); - restoreDataObject.RestoreOptions.StandByFile = GetOptionValue(RestoreOptionsHelper.StandbyFile, options, restoreDataObject); - - - restoreDataObject.BackupTailLog = GetOptionValue(RestoreOptionsHelper.BackupTailLog, options, restoreDataObject); - restoreDataObject.TailLogBackupFile = GetOptionValue(RestoreOptionsHelper.TailLogBackupFile, options, restoreDataObject); - restoreDataObject.TailLogWithNoRecovery = GetOptionValue(RestoreOptionsHelper.TailLogWithNoRecovery, options, restoreDataObject); - - restoreDataObject.CloseExistingConnections = GetOptionValue(RestoreOptionsHelper.CloseExistingConnections, options, restoreDataObject); + //After all options are set do a vaidation so any invalid option set to default + foreach (var optionKey in optionNames) + { + 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 + } + } + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs index 9144863f16..9db6faf49a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs @@ -11,6 +11,7 @@ using Microsoft.SqlTools.ServiceLayer.Metadata.Contracts; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel; +using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes @@ -292,7 +293,7 @@ protected virtual void PopulateChildren(bool refresh, string name = null) Debug.Assert(IsAlwaysLeaf == false); SmoQueryContext context = this.GetContextAs(); - bool includeSystemObjects = context != null && context.Database != null ? ObjectExplorerUtils.IsSystemDatabaseConnection(context.Database.Name) : true; + bool includeSystemObjects = context != null && context.Database != null ? DatabaseUtils.IsSystemDatabaseConnection(context.Database.Name) : true; if (children.IsPopulating || context == null) diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs index 0a486bae5e..2f360cfcb7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs @@ -21,6 +21,7 @@ using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel; using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.Utility; @@ -617,7 +618,7 @@ public static ObjectExplorerSession CreateSession(ConnectionCompleteParams respo { ServerNode rootNode = new ServerNode(response, serviceProvider); var session = new ObjectExplorerSession(response.OwnerUri, rootNode, serviceProvider, serviceProvider.GetService()); - if (!ObjectExplorerUtils.IsSystemDatabaseConnection(response.ConnectionSummary.DatabaseName)) + if (!DatabaseUtils.IsSystemDatabaseConnection(response.ConnectionSummary.DatabaseName)) { // Assuming the databases are in a folder under server node DatabaseTreeNode databaseNode = new DatabaseTreeNode(rootNode, response.ConnectionSummary.DatabaseName); diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs index f646ffa448..c2ed4c3420 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerUtils.cs @@ -73,19 +73,5 @@ public static TreeNode FindNode(TreeNode node, Predicate condition, Pr } return null; } - - /// - /// Check if the database is a system database - /// - /// the name of database - /// return true if the database is a system database - public static bool IsSystemDatabaseConnection(string databaseName) - { - return (string.IsNullOrWhiteSpace(databaseName) || - string.Compare(databaseName, CommonConstants.MasterDatabaseName, StringComparison.OrdinalIgnoreCase) == 0 || - string.Compare(databaseName, CommonConstants.MsdbDatabaseName, StringComparison.OrdinalIgnoreCase) == 0 || - string.Compare(databaseName, CommonConstants.ModelDatabaseName, StringComparison.OrdinalIgnoreCase) == 0 || - string.Compare(databaseName, CommonConstants.TempDbDatabaseName, StringComparison.OrdinalIgnoreCase) == 0); - } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/ServerNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/ServerNode.cs index eb06453dd5..2af98245f2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/ServerNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/ServerNode.cs @@ -88,7 +88,7 @@ internal string GetConnectionLabel() // TODO Consider adding IsAuthenticatingDatabaseMaster check in the code and // referencing result here - if (!ObjectExplorerUtils.IsSystemDatabaseConnection(connectionSummary.DatabaseName)) + if (!DatabaseUtils.IsSystemDatabaseConnection(connectionSummary.DatabaseName)) { // We either have an azure with a database specified or a Denali database using a contained user if (string.IsNullOrWhiteSpace(userName)) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs new file mode 100644 index 0000000000..fd1428b8dc --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; + +namespace Microsoft.SqlTools.ServiceLayer.Utility +{ + public class DatabaseUtils + { + /// + /// Check if the database is a system database + /// + /// the name of database + /// return true if the database is a system database + public static bool IsSystemDatabaseConnection(string databaseName) + { + return (string.IsNullOrWhiteSpace(databaseName) || + string.Compare(databaseName, CommonConstants.MasterDatabaseName, StringComparison.OrdinalIgnoreCase) == 0 || + string.Compare(databaseName, CommonConstants.MsdbDatabaseName, StringComparison.OrdinalIgnoreCase) == 0 || + string.Compare(databaseName, CommonConstants.ModelDatabaseName, StringComparison.OrdinalIgnoreCase) == 0 || + string.Compare(databaseName, CommonConstants.TempDbDatabaseName, StringComparison.OrdinalIgnoreCase) == 0); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/GeneralRequestDetails.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/GeneralRequestDetails.cs index 817117baa1..4612976c6e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/GeneralRequestDetails.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/GeneralRequestDetails.cs @@ -25,18 +25,7 @@ internal T GetOptionValue(string name) object value = Options[name]; try { - if (value != null && (typeof(T) != value.GetType())) - { - if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) - { - value = Convert.ToInt32(value); - } - else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) - { - value = Convert.ToBoolean(value); - } - } - result = value != null ? (T)value : default(T); + result = GetValueAs(value); } catch { @@ -48,6 +37,42 @@ internal T GetOptionValue(string name) return result; } + internal static T GetValueAs(object value) + { + T result = default(T); + + if (value != null && (typeof(T) != value.GetType())) + { + if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) + { + value = Convert.ToInt32(value); + } + else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) + { + value = Convert.ToBoolean(value); + } + else if (typeof(T).IsEnum) + { + object enumValue; + if (Enum.TryParse(typeof(T), value.ToString(), out enumValue)) + { + value = (T)enumValue; + } + } + } + else if (value != null && (typeof(T).IsEnum)) + { + object enumValue; + if (Enum.TryParse(typeof(T), value.ToString(), out enumValue)) + { + value = enumValue; + } + } + result = value != null ? (T)value : default(T); + + return result; + } + protected void SetOptionValue(string name, T value) { Options = Options ?? new Dictionary(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreDatabaseTaskDataObjectStub.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreDatabaseTaskDataObjectStub.cs new file mode 100644 index 0000000000..6a8657010a --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreDatabaseTaskDataObjectStub.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation; + +namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery +{ + public class RestoreDatabaseTaskDataObjectStub : IRestoreDatabaseTaskDataObject + { + public string DataFilesFolder { get; set; } + + public string DefaultDataFileFolder { get; set; } + + public bool RelocateAllFiles { get; set; } + public string LogFilesFolder { get; set; } + + public string DefaultLogFileFolder { get; set; } + + public List DbFiles { get; set; } + + public RestoreOptions RestoreOptions { get; set; } + + public bool IsTailLogBackupPossible { get; set; } + + public bool IsTailLogBackupWithNoRecoveryPossible { get; set; } + + public bool TailLogWithNoRecovery { get; set; } + public string TailLogBackupFile { get; set; } + + public RestorePlan RestorePlan { get; set; } + + public bool CloseExistingConnections { get; set; } + public RestoreParams RestoreParams { get; set; } + public bool BackupTailLog { get; set; } + + public string DefaultStandbyFile { get; set; } + + public string DefaultTailLogbackupFile { get; set; } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs index 20b3442005..59d9ef37ba 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.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 Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; @@ -20,9 +21,9 @@ public class RestoreOptionsHelperTests public void VerifyOptionsCreatedSuccessfullyIsResponse() { GeneralRequestDetails optionValues = CreateOptionsTestData(); - Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); - Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); VerifyOptions(result, optionValues); } @@ -32,9 +33,9 @@ public void RelocateAllFilesShouldBeReadOnlyGivenNoDbFiles() { GeneralRequestDetails optionValues = CreateOptionsTestData(); optionValues.Options["DbFiles"] = new List(); - Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); - Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); VerifyOptions(result, optionValues); Assert.True(result[RestoreOptionsHelper.RelocateDbFiles].IsReadOnly); @@ -45,9 +46,9 @@ public void BackupTailLogShouldBeReadOnlyTailLogBackupNotPossible() { GeneralRequestDetails optionValues = CreateOptionsTestData(); optionValues.Options["IsTailLogBackupPossible"] = false; - Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); - Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); VerifyOptions(result, optionValues); Assert.True(result[RestoreOptionsHelper.BackupTailLog].IsReadOnly); @@ -59,9 +60,9 @@ public void TailLogWithNoRecoveryShouldBeReadOnlyTailLogBackupWithNoRecoveryNotP { GeneralRequestDetails optionValues = CreateOptionsTestData(); optionValues.Options["IsTailLogBackupWithNoRecoveryPossible"] = false; - Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); - Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); VerifyOptions(result, optionValues); Assert.True(result[RestoreOptionsHelper.TailLogWithNoRecovery].IsReadOnly); @@ -72,9 +73,9 @@ public void StandbyFileShouldNotBeReadOnlyGivenRecoveryStateWithStandBy() { GeneralRequestDetails optionValues = CreateOptionsTestData(); optionValues.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithStandBy; - Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); - Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); VerifyOptions(result, optionValues); Assert.False(result[RestoreOptionsHelper.StandbyFile].IsReadOnly); @@ -85,9 +86,9 @@ public void KeepReplicationShouldNotBeReadOnlyGivenRecoveryStateWithNoRecovery() { GeneralRequestDetails optionValues = CreateOptionsTestData(); optionValues.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithNoRecovery; - Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); - Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); Assert.NotNull(result); VerifyOptions(result, optionValues); Assert.True(result[RestoreOptionsHelper.KeepReplication].IsReadOnly); @@ -99,12 +100,13 @@ public void KeepReplicationShouldSetToDefaultValueGivenRecoveryStateWithNoRecove RestoreParams restoreParams = CreateOptionsTestData(); restoreParams.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithNoRecovery; - Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); - Dictionary options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); + Dictionary options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); restoreParams.Options[RestoreOptionsHelper.KeepReplication] = true; - bool actual = RestoreOptionsHelper.GetOptionValue(RestoreOptionsHelper.KeepReplication, options, restoreDatabaseTaskDataObject.Object); + RestoreOptionsHelper.UpdateOptionsInPlan(restoreDatabaseTaskDataObject); + bool actual = restoreDatabaseTaskDataObject.RestoreOptions.KeepReplication; bool expected = (bool)options[RestoreOptionsHelper.KeepReplication].DefaultValue; Assert.Equal(actual, expected); @@ -114,14 +116,15 @@ public void KeepReplicationShouldSetToDefaultValueGivenRecoveryStateWithNoRecove public void KeepReplicationShouldSetToValueInRequestGivenRecoveryStateWithRecovery() { RestoreParams restoreParams = CreateOptionsTestData(); - + restoreParams.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithRecovery; - Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); - Dictionary options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + IRestoreDatabaseTaskDataObject restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); + Dictionary options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject); restoreParams.Options[RestoreOptionsHelper.KeepReplication] = true; + RestoreOptionsHelper.UpdateOptionsInPlan(restoreDatabaseTaskDataObject); - bool actual = RestoreOptionsHelper.GetOptionValue(RestoreOptionsHelper.KeepReplication, options, restoreDatabaseTaskDataObject.Object); + bool actual = restoreDatabaseTaskDataObject.RestoreOptions.KeepReplication; bool expected = true; Assert.Equal(actual, expected); @@ -153,32 +156,32 @@ private RestoreParams CreateOptionsTestData() return optionValues; } - private Mock CreateRestoreDatabaseTaskDataObject(GeneralRequestDetails optionValues) + private IRestoreDatabaseTaskDataObject CreateRestoreDatabaseTaskDataObject(GeneralRequestDetails optionValues) { - var restoreDataObject = new Mock(); - restoreDataObject.Setup(x => x.CloseExistingConnections).Returns(optionValues.GetOptionValue(RestoreOptionsHelper.CloseExistingConnections)); - restoreDataObject.Setup(x => x.DataFilesFolder).Returns(optionValues.GetOptionValue(RestoreOptionsHelper.DataFileFolder)); - restoreDataObject.Setup(x => x.DbFiles).Returns(optionValues.GetOptionValue>("DbFiles")); - restoreDataObject.Setup(x => x.DefaultDataFileFolder).Returns(optionValues.GetOptionValue("DefaultDataFileFolder")); - restoreDataObject.Setup(x => x.DefaultLogFileFolder).Returns(optionValues.GetOptionValue("DefaultLogFileFolder")); - restoreDataObject.Setup(x => x.IsTailLogBackupPossible(It.IsAny())).Returns(optionValues.GetOptionValue("IsTailLogBackupPossible")); - restoreDataObject.Setup(x => x.IsTailLogBackupWithNoRecoveryPossible(It.IsAny())).Returns(optionValues.GetOptionValue("IsTailLogBackupWithNoRecoveryPossible")); - restoreDataObject.Setup(x => x.GetDefaultStandbyFile(It.IsAny())).Returns(optionValues.GetOptionValue("GetDefaultStandbyFile")); - restoreDataObject.Setup(x => x.GetDefaultTailLogbackupFile(It.IsAny())).Returns(optionValues.GetOptionValue("GetDefaultTailLogbackupFile")); - restoreDataObject.Setup(x => x.LogFilesFolder).Returns(optionValues.GetOptionValue("LogFilesFolder")); - restoreDataObject.Setup(x => x.RelocateAllFiles).Returns(optionValues.GetOptionValue("RelocateAllFiles")); - restoreDataObject.Setup(x => x.TailLogBackupFile).Returns(optionValues.GetOptionValue("TailLogBackupFile")); - restoreDataObject.Setup(x => x.TailLogWithNoRecovery).Returns(optionValues.GetOptionValue("TailLogWithNoRecovery")); - restoreDataObject.Setup(x => x.BackupTailLog).Returns(optionValues.GetOptionValue("BackupTailLog")); - restoreDataObject.Setup(x => x.RestoreParams).Returns(optionValues as RestoreParams); - restoreDataObject.Setup(x => x.RestorePlan).Returns(() => null); + var restoreDataObject = new RestoreDatabaseTaskDataObjectStub(); + restoreDataObject.CloseExistingConnections = optionValues.GetOptionValue(RestoreOptionsHelper.CloseExistingConnections); + restoreDataObject.DataFilesFolder = optionValues.GetOptionValue(RestoreOptionsHelper.DataFileFolder); + restoreDataObject.DbFiles = optionValues.GetOptionValue>("DbFiles"); + restoreDataObject.DefaultDataFileFolder = optionValues.GetOptionValue("DefaultDataFileFolder"); + restoreDataObject.DefaultLogFileFolder = optionValues.GetOptionValue("DefaultLogFileFolder"); + restoreDataObject.IsTailLogBackupPossible = optionValues.GetOptionValue("IsTailLogBackupPossible"); + 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.TailLogBackupFile = optionValues.GetOptionValue("TailLogBackupFile"); + restoreDataObject.TailLogWithNoRecovery = optionValues.GetOptionValue("TailLogWithNoRecovery"); + restoreDataObject.BackupTailLog = optionValues.GetOptionValue("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.RecoveryState = optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState); - restoreDataObject.Setup(x => x.RestoreOptions).Returns(restoreOptions); + restoreDataObject.RestoreOptions = restoreOptions; return restoreDataObject; @@ -216,7 +219,7 @@ private void VerifyOptions(Dictionary optionInRes planDetailInfo = optionInResponse[RestoreOptionsHelper.KeepReplication]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.KeepReplication); - Assert.Equal(planDetailInfo.IsReadOnly, optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState) == DatabaseRecoveryState.WithNoRecovery); + Assert.Equal(planDetailInfo.IsReadOnly, optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState) == DatabaseRecoveryState.WithNoRecovery); Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.KeepReplication)); Assert.Equal(planDetailInfo.DefaultValue, false); Assert.Equal(planDetailInfo.IsVisiable, true); @@ -251,14 +254,16 @@ private void VerifyOptions(Dictionary optionInRes planDetailInfo = optionInResponse[RestoreOptionsHelper.TailLogBackupFile]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.TailLogBackupFile); - Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("IsTailLogBackupPossible")); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("IsTailLogBackupPossible") + | !optionValues.GetOptionValue(RestoreOptionsHelper.BackupTailLog)); Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("TailLogBackupFile")); Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("GetDefaultTailLogbackupFile")); Assert.Equal(planDetailInfo.IsVisiable, true); planDetailInfo = optionInResponse[RestoreOptionsHelper.TailLogWithNoRecovery]; Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.TailLogWithNoRecovery); - Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("IsTailLogBackupWithNoRecoveryPossible")); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("IsTailLogBackupWithNoRecoveryPossible") + | !optionValues.GetOptionValue(RestoreOptionsHelper.BackupTailLog)); Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("TailLogWithNoRecovery")); Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("IsTailLogBackupWithNoRecoveryPossible")); Assert.Equal(planDetailInfo.IsVisiable, true);