From 808172bc20296bcb186b400921e2b88f0fdf367b Mon Sep 17 00:00:00 2001 From: Cory Rivera Date: Mon, 12 Jun 2023 11:44:17 -0700 Subject: [PATCH] Retrieve Azure SLO details in Database Handler (#2094) --- .../Admin/Database/AzureSqlDbHelper.cs | 37 +++--- .../Admin/Database/DatabasePrototypeAzure.cs | 96 +++++++-------- .../Localization/sr.cs | 88 ++++++++++++++ .../Localization/sr.resx | 32 +++++ .../Localization/sr.strings | 14 ++- .../Localization/sr.xlf | 40 +++++++ .../ObjectTypes/Database/DatabaseHandler.cs | 111 ++++++++++++++++-- .../ObjectTypes/Database/DatabaseInfo.cs | 5 + .../ObjectTypes/Database/DatabaseViewInfo.cs | 12 ++ .../ObjectManagement/DatabaseHandlerTests.cs | 100 ++++++++++++++++ 10 files changed, 462 insertions(+), 73 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/AzureSqlDbHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/AzureSqlDbHelper.cs index a1585636..33f5be2e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/AzureSqlDbHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/AzureSqlDbHelper.cs @@ -21,19 +21,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin [DebuggerDisplay("{Name,nq}")] public class AzureEdition { - public static readonly AzureEdition Basic = new AzureEdition("Basic", "SR.BasicAzureEdition"); - public static readonly AzureEdition Standard = new AzureEdition("Standard", "SR.StandardAzureEdition"); - public static readonly AzureEdition Premium = new AzureEdition("Premium", "SR.PremiumAzureEdition"); - public static readonly AzureEdition DataWarehouse = new AzureEdition("DataWarehouse", "SR.DataWarehouseAzureEdition"); - public static readonly AzureEdition GeneralPurpose = new AzureEdition("GeneralPurpose", "SR.GeneralPurposeAzureEdition"); - public static readonly AzureEdition BusinessCritical = new AzureEdition("BusinessCritical", "SR.BusinessCriticalAzureEdition"); - - public static readonly AzureEdition Hyperscale = new AzureEdition("Hyperscale", "SR.HyperscaleAzureEdition"); - // Free does not offer DatabaseSize >=1GB, hence it's not "supported". - //public static readonly AzureEdition Free = new AzureEdition("Free", SR.FreeAzureEdition); - // Stretch and system do not seem to be applicable, so I'm commenting them out - //public static readonly AzureEdition Stretch = new AzureEdition("Stretch", SR.StretchAzureEdition); - //public static readonly AzureEdition System = new AzureEdition("System", SR.SystemAzureEdition); + public static readonly AzureEdition Basic = new AzureEdition("Basic", SR.BasicAzureEdition); + public static readonly AzureEdition Standard = new AzureEdition("Standard", SR.StandardAzureEdition); + public static readonly AzureEdition Premium = new AzureEdition("Premium", SR.PremiumAzureEdition); + public static readonly AzureEdition DataWarehouse = new AzureEdition("DataWarehouse", SR.DataWarehouseAzureEdition); + public static readonly AzureEdition GeneralPurpose = new AzureEdition("GeneralPurpose", SR.GeneralPurposeAzureEdition); + public static readonly AzureEdition BusinessCritical = new AzureEdition("BusinessCritical", SR.BusinessCriticalAzureEdition); + public static readonly AzureEdition Hyperscale = new AzureEdition("Hyperscale", SR.HyperscaleAzureEdition); internal string Name { get; private set; } internal string DisplayName { get; private set; } @@ -320,6 +314,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { "LRS", "Local" }, { "ZRS", "Zone" } }; + private static readonly string[] backupRedundancyLevels = bsrAPIToUIValueMapping.Values.ToArray(); + + /// + /// All valid backup storage redundancy levels for an Azure SQL database + /// + public static string[] BackupStorageRedundancyLevels + { + get + { + return backupRedundancyLevels; + } + } //KeyValuePair contains the BackupStorageRedundancy values for all azure editions. private static readonly KeyValuePair keyValuePair = new KeyValuePair(0, bsrAPIToUIValueMapping.Values.ToArray()); @@ -516,7 +522,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin /// We do this so that the AzureEdition enum can have values such as NONE or DEFAULT added /// without requiring clients to explicitly filter out those values themselves each time. /// - public static IEnumerable GetValidAzureEditionOptions(object unused) + public static IEnumerable GetValidAzureEditionOptions() { yield return AzureEdition.Basic; yield return AzureEdition.Standard; @@ -524,10 +530,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin yield return AzureEdition.DataWarehouse; yield return AzureEdition.BusinessCritical; yield return AzureEdition.GeneralPurpose; - //yield return AzureEdition.Free; yield return AzureEdition.Hyperscale; - //yield return AzureEdition.Stretch; - //yield return AzureEdition.System; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototypeAzure.cs b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototypeAzure.cs index 8c90bab1..d6d6582f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototypeAzure.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Admin/Database/DatabasePrototypeAzure.cs @@ -16,6 +16,8 @@ using Microsoft.SqlTools.ServiceLayer.Management; using AzureEdition = Microsoft.SqlTools.ServiceLayer.Admin.AzureSqlDbHelper.AzureEdition; using System; using System.Data; +using Microsoft.SqlTools.Utility; +using System.Diagnostics; namespace Microsoft.SqlTools.ServiceLayer.Admin { @@ -91,58 +93,56 @@ namespace Microsoft.SqlTools.ServiceLayer.Admin { return AzureSqlDbHelper.GetAzureEditionDisplayName(this.currentState.azureEdition); } - // set - // { - // AzureEdition edition; - // if (AzureSqlDbHelper.TryGetAzureEditionFromDisplayName(value, out edition)) - // { - // //Try to get the ServiceLevelObjective from the api,if not the default hardcoded service level objectives will be retrieved. - // string serverLevelObjective = AzureServiceLevelObjectiveProvider.TryGetAzureServiceLevelObjective(value, AzureServiceLocation); + set + { + AzureEdition edition; + if (AzureSqlDbHelper.TryGetAzureEditionFromDisplayName(value, out edition)) + { + //Try to get the ServiceLevelObjective from the api,if not the default hardcoded service level objectives will be retrieved. + // string serverLevelObjective = AzureServiceLevelObjectiveProvider.TryGetAzureServiceLevelObjective(value, AzureServiceLocation); - // if (!string.IsNullOrEmpty(serverLevelObjective)) - // { - // this.currentState.azureEdition = edition; - // this.currentState.currentServiceLevelObjective = serverLevelObjective; - // // Instead of creating db instance with default Edition, update EditionToCreate while selecting Edition from the UI. - // this.EditionToCreate = MapAzureEditionToDbEngineEdition(edition); - // string storageAccountType = AzureServiceLevelObjectiveProvider.TryGetAzureStorageType(value, AzureServiceLocation); - // if (!string.IsNullOrEmpty(storageAccountType)) - // { - // this.currentState.backupStorageRedundancy = storageAccountType; - // } + // if (!string.IsNullOrEmpty(serverLevelObjective)) + // { + // this.currentState.azureEdition = edition; + // this.currentState.currentServiceLevelObjective = serverLevelObjective; + // // Instead of creating db instance with default Edition, update EditionToCreate while selecting Edition from the UI. + // this.EditionToCreate = MapAzureEditionToDbEngineEdition(edition); + // string storageAccountType = AzureServiceLevelObjectiveProvider.TryGetAzureStorageType(value, AzureServiceLocation); + // if (!string.IsNullOrEmpty(storageAccountType)) + // { + // this.currentState.backupStorageRedundancy = storageAccountType; + // } - // // Try to get the azure maxsize from the api,if not the default hardcoded maxsize will be retrieved. - // DbSize dbSize = AzureServiceLevelObjectiveProvider.TryGetAzureMaxSize(value, serverLevelObjective, AzureServiceLocation); - // if (!string.IsNullOrEmpty(dbSize.ToString())) - // { - // this.currentState.maxSize = new DbSize(dbSize.Size, dbSize.SizeUnit); - // } - // } - // else - // { - // if (edition == this.currentState.azureEdition) - // { //No changes, return early since we don't need to do any of the changes below - // return; - // } + // // Try to get the azure maxsize from the api,if not the default hardcoded maxsize will be retrieved. + // DbSize dbSize = AzureServiceLevelObjectiveProvider.TryGetAzureMaxSize(value, serverLevelObjective, AzureServiceLocation); + // if (!string.IsNullOrEmpty(dbSize.ToString())) + // { + // this.currentState.maxSize = new DbSize(dbSize.Size, dbSize.SizeUnit); + // } + // } + // else + // { + if (edition == this.currentState.azureEdition) + { // No changes, return early since we don't need to do any of the changes below + return; + } - // this.currentState.azureEdition = edition; - // this.EditionToCreate = MapAzureEditionToDbEngineEdition(edition); - // this.CurrentServiceLevelObjective = AzureSqlDbHelper.GetDefaultServiceObjective(edition); - // this.BackupStorageRedundancy = AzureSqlDbHelper.GetDefaultBackupStorageRedundancy(edition); - // var defaultSize = AzureSqlDbHelper.GetDatabaseDefaultSize(edition); + this.currentState.azureEdition = edition; + this.EditionToCreate = MapAzureEditionToDbEngineEdition(edition); + this.CurrentServiceLevelObjective = AzureSqlDbHelper.GetDefaultServiceObjective(edition); + this.BackupStorageRedundancy = AzureSqlDbHelper.GetDefaultBackupStorageRedundancy(edition); + var defaultSize = AzureSqlDbHelper.GetDatabaseDefaultSize(edition); - // this.MaxSize = defaultSize == null ? String.Empty : defaultSize.ToString(); - // } - // this.NotifyObservers(); - // } - // else - // { - // //Can't really do much if we fail to parse the display name so just leave it as is and log a message - // System.Diagnostics.Debug.Assert(false, - // string.Format(CultureInfo.InvariantCulture, - // "Failed to parse edition display name '{0}' back into AzureEdition", value)); - // } - // } + this.MaxSize = defaultSize == null ? String.Empty : defaultSize.ToString(); + // } + this.NotifyObservers(); + } + else + { + // Can't really do much if we fail to parse the display name so just leave it as is and log a message + Logger.Write(TraceEventType.Error, $"Failed to parse edition display name '{value}' back into AzureEdition"); + } + } } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 0fee0153..2735ad3b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -12437,6 +12437,70 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string BasicAzureEdition + { + get + { + return Keys.GetString(Keys.BasicAzureEdition); + } + } + + public static string StandardAzureEdition + { + get + { + return Keys.GetString(Keys.StandardAzureEdition); + } + } + + public static string PremiumAzureEdition + { + get + { + return Keys.GetString(Keys.PremiumAzureEdition); + } + } + + public static string DataWarehouseAzureEdition + { + get + { + return Keys.GetString(Keys.DataWarehouseAzureEdition); + } + } + + public static string GeneralPurposeAzureEdition + { + get + { + return Keys.GetString(Keys.GeneralPurposeAzureEdition); + } + } + + public static string BusinessCriticalAzureEdition + { + get + { + return Keys.GetString(Keys.BusinessCriticalAzureEdition); + } + } + + public static string ErrorInvalidEdition + { + get + { + return Keys.GetString(Keys.ErrorInvalidEdition); + } + } + + public static string HyperscaleAzureEdition + { + get + { + return Keys.GetString(Keys.HyperscaleAzureEdition); + } + } + public static string ConnectionServiceListDbErrorNotConnected(string uri) { return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri); @@ -17794,6 +17858,30 @@ namespace Microsoft.SqlTools.ServiceLayer public const string prototype_file_noApplicableFileGroup = "prototype_file_noApplicableFileGroup"; + public const string BasicAzureEdition = "BasicAzureEdition"; + + + public const string StandardAzureEdition = "StandardAzureEdition"; + + + public const string PremiumAzureEdition = "PremiumAzureEdition"; + + + public const string DataWarehouseAzureEdition = "DataWarehouseAzureEdition"; + + + public const string GeneralPurposeAzureEdition = "GeneralPurposeAzureEdition"; + + + public const string BusinessCriticalAzureEdition = "BusinessCriticalAzureEdition"; + + + public const string ErrorInvalidEdition = "ErrorInvalidEdition"; + + + public const string HyperscaleAzureEdition = "HyperscaleAzureEdition"; + + private Keys() { } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index c0834cff..361f25ac 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -6760,4 +6760,36 @@ The Query Processor estimates that implementing the following index could improv No Applicable Filegroup + + Basic + + + + Standard + + + + Premium + + + + DataWarehouse + + + + General Purpose + + + + Business Critical + + + + Edition value is not valid + + + + Hyperscale + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index cea89ec4..b6b5065f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -2801,4 +2801,16 @@ compatibilityLevel_sqlv160 = SQL Server 2022 (160) general_containmentType_None = None general_containmentType_Partial = Partial filegroups_filestreamFiles = FILESTREAM Files -prototype_file_noApplicableFileGroup = No Applicable Filegroup \ No newline at end of file +prototype_file_noApplicableFileGroup = No Applicable Filegroup + +############################################################################ +# Azure SQL DB + +BasicAzureEdition = Basic +StandardAzureEdition = Standard +PremiumAzureEdition = Premium +DataWarehouseAzureEdition = DataWarehouse +GeneralPurposeAzureEdition = General Purpose +BusinessCriticalAzureEdition = Business Critical +ErrorInvalidEdition = Edition value is not valid +HyperscaleAzureEdition = Hyperscale \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 123f4e6a..53aa0ef1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -8281,6 +8281,46 @@ The Query Processor estimates that implementing the following index could improv No Applicable Filegroup + + Basic + Basic + + + + Standard + Standard + + + + Premium + Premium + + + + DataWarehouse + DataWarehouse + + + + General Purpose + General Purpose + + + + Business Critical + Business Critical + + + + Edition value is not valid + Edition value is not valid + + + + Hyperscale + Hyperscale + + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs index 6056326e..127d4b38 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs @@ -17,6 +17,7 @@ using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Management; using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { @@ -37,6 +38,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement private static readonly Dictionary containmentTypeEnums = new Dictionary(); private static readonly Dictionary recoveryModelEnums = new Dictionary(); + internal static readonly string[] AzureEditionNames; + internal static readonly string[] AzureBackupLevels; + internal static readonly AzureEditionDetails[] AzureMaxSizes; + internal static readonly AzureEditionDetails[] AzureServiceLevels; + static DatabaseHandler() { displayCompatLevels.Add(CompatibilityLevel.Version70, SR.compatibilityLevel_sphinx); @@ -71,6 +77,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { recoveryModelEnums.Add(displayRecoveryModels[key], key); } + + // Azure SLO info is invariant of server information, so set up static objects we can return later + var editions = AzureSqlDbHelper.GetValidAzureEditionOptions(); + AzureEditionNames = editions.Select(edition => edition.DisplayName).ToArray(); + AzureBackupLevels = AzureSqlDbHelper.BackupStorageRedundancyLevels; + AzureMaxSizes = GetAzureMaxSizes(editions); + AzureServiceLevels = GetAzureServiceLevels(editions); } public DatabaseHandler(ConnectionService connectionService) : base(connectionService) @@ -99,18 +112,17 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement var prototype = taskHelper.Prototype; var azurePrototype = prototype as DatabasePrototypeAzure; bool isDw = azurePrototype != null && azurePrototype.AzureEdition == AzureEdition.DataWarehouse; + bool isAzureDB = dataContainer.Server.ServerType == DatabaseEngineType.SqlAzureDatabase; var databaseViewInfo = new DatabaseViewInfo() { - ObjectInfo = new DatabaseInfo() + ObjectInfo = new DatabaseInfo(), + IsAzureDB = isAzureDB }; // azure sql db doesn't have a sysadmin fixed role - var compatibilityLevelEnabled = !isDw && - (dataContainer.LoggedInUserIsSysadmin || - dataContainer.Server.ServerType == - DatabaseEngineType.SqlAzureDatabase); - if (dataContainer.Server.ServerType == DatabaseEngineType.SqlAzureDatabase) + var compatibilityLevelEnabled = !isDw && (dataContainer.LoggedInUserIsSysadmin || isAzureDB); + if (isAzureDB) { // Azure doesn't allow modifying the collation after DB creation bool collationEnabled = !prototype.Exists; @@ -133,6 +145,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement databaseViewInfo.CompatibilityLevels = GetCompatibilityLevels(dataContainer.SqlServerVersion, prototype); } } + databaseViewInfo.AzureBackupRedundancyLevels = AzureBackupLevels; + databaseViewInfo.AzureServiceLevelObjectives = AzureServiceLevels; + databaseViewInfo.AzureEditions = AzureEditionNames; + databaseViewInfo.AzureMaxSizes = AzureMaxSizes; } else { @@ -148,7 +164,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement } // Skip adding logins if running against an Azure SQL DB - if (dataContainer.Server.ServerType != DatabaseEngineType.SqlAzureDatabase) + if (!isAzureDB) { var logins = new List(); foreach (Login login in dataContainer.Server.Logins) @@ -283,6 +299,27 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement db110.DatabaseContainmentType = containmentTypeEnums[database.ContainmentType]; } + if (prototype is DatabasePrototypeAzure dbAz) + { + // Set edition first since the prototype will fill all the Azure fields with default values + if (database.AzureEdition != null) + { + dbAz.AzureEditionDisplay = database.AzureEdition; + } + if (database.AzureBackupRedundancyLevel != null) + { + dbAz.BackupStorageRedundancy = database.AzureBackupRedundancyLevel; + } + if (database.AzureServiceLevelObjective != null) + { + dbAz.CurrentServiceLevelObjective = database.AzureServiceLevelObjective; + } + if (database.AzureMaxSize != null) + { + dbAz.MaxSize = database.AzureMaxSize; + } + } + string sqlScript = string.Empty; using (var actions = new DatabaseActions(dataContainer, configAction, prototype)) using (var executionHandler = new ExecutonHandler(actions)) @@ -583,5 +620,65 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement // previous loop did not find the prototype compatibility level in this server's compatability options, so treat compatibility levels as unsupported for this server return Array.Empty(); } + + /// + /// Get supported service level objectives for this Azure server. + /// + private static AzureEditionDetails[] GetAzureServiceLevels(IEnumerable editions) + { + var levels = new List(); + foreach (AzureEdition edition in editions) + { + if (AzureSqlDbHelper.TryGetServiceObjectiveInfo(edition, out var serviceInfoPair)) + { + // Move default value to the front of the list + var serviceLevelsList = new List(serviceInfoPair.Value); + var defaultIndex = serviceInfoPair.Key; + if (defaultIndex >= 0 && defaultIndex < serviceLevelsList.Count) + { + var defaultServiceObjective = serviceLevelsList[defaultIndex]; + serviceLevelsList.RemoveAt(defaultIndex); + serviceLevelsList.Insert(0, defaultServiceObjective); + } + var details = new AzureEditionDetails() { EditionDisplayName = edition.DisplayName, Details = serviceLevelsList.ToArray() }; + levels.Add(details); + } + else + { + Logger.Error($"Failed to get service level objective info for edition '{edition.Name}'"); + } + } + return levels.ToArray(); + } + + /// + /// Get supported maximum sizes for this Azure server. + /// + private static AzureEditionDetails[] GetAzureMaxSizes(IEnumerable editions) + { + var sizes = new List(); + foreach (AzureEdition edition in editions) + { + if (AzureSqlDbHelper.TryGetDatabaseSizeInfo(edition, out var sizeInfoPair)) + { + // Move default value to the front of the list + var sizeInfoList = new List(sizeInfoPair.Value); + var defaultIndex = sizeInfoPair.Key; + if (defaultIndex >= 0 && defaultIndex < sizeInfoList.Count) + { + var defaultSizeInfo = sizeInfoList[defaultIndex]; + sizeInfoList.RemoveAt(defaultIndex); + sizeInfoList.Insert(0, defaultSizeInfo); + } + var details = new AzureEditionDetails() { EditionDisplayName = edition.DisplayName, Details = sizeInfoList.Select(info => info.ToString()).ToArray() }; + sizes.Add(details); + } + else + { + Logger.Error($"Failed to get database size info for edition '{edition.Name}'"); + } + } + return sizes.ToArray(); + } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs index ae7cb76f..7063633e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseInfo.cs @@ -15,5 +15,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public string? RecoveryModel { get; set; } public string? CompatibilityLevel { get; set; } public string? ContainmentType { get; set; } + + public string? AzureBackupRedundancyLevel { get; set; } + public string? AzureServiceLevelObjective { get; set; } + public string? AzureEdition { get; set; } + public string? AzureMaxSize { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs index ee1eca49..089d1ca7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseViewInfo.cs @@ -14,5 +14,17 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement public string[] CompatibilityLevels { get; set; } public string[] ContainmentTypes { get; set; } public string[] RecoveryModels { get; set; } + + public bool IsAzureDB { get; set; } + public string[] AzureBackupRedundancyLevels { get; set; } + public AzureEditionDetails[] AzureServiceLevelObjectives { get; set; } + public string[] AzureEditions { get; set; } + public AzureEditionDetails[] AzureMaxSizes { get; set; } + } + + public class AzureEditionDetails + { + public string EditionDisplayName { get; set; } + public string[] Details { get; set; } } } \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs index 731ef021..6cfd5a30 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/DatabaseHandlerTests.cs @@ -3,15 +3,19 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Data.SqlClient; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; using Microsoft.SqlTools.ServiceLayer.ObjectManagement; using Microsoft.SqlTools.ServiceLayer.Test.Common; using NUnit.Framework; +using static Microsoft.SqlTools.ServiceLayer.Admin.AzureSqlDbHelper; namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement { @@ -162,6 +166,102 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement } } + [Test] + public void GetAzureEditionsTest() + { + var actualEditionNames = DatabaseHandler.AzureEditionNames; + var expectedEditionNames = AzureSqlDbHelper.GetValidAzureEditionOptions().Select(edition => edition.DisplayName); + Assert.That(actualEditionNames, Is.EquivalentTo(expectedEditionNames)); + } + + [Test] + public void GetAzureBackupRedundancyLevelsTest() + { + var actualLevels = DatabaseHandler.AzureBackupLevels; + var expectedLevels = new string[] { "Geo", "Local", "Zone" }; + Assert.That(actualLevels, Is.EquivalentTo(expectedLevels)); + } + + [Test] + public void GetAzureServiceLevelObjectivesTest() + { + var actualLevelsMap = new Dictionary(); + foreach (AzureEditionDetails serviceDetails in DatabaseHandler.AzureServiceLevels) + { + actualLevelsMap.Add(serviceDetails.EditionDisplayName, serviceDetails.Details); + } + + var expectedDefaults = new Dictionary() + { + { AzureEdition.Basic, "Basic" }, + { AzureEdition.Standard, "S0" }, + { AzureEdition.Premium, "P1" }, + { AzureEdition.DataWarehouse, "DW400" }, + { AzureEdition.BusinessCritical, "BC_Gen5_2" }, + { AzureEdition.GeneralPurpose, "GP_Gen5_2" }, + { AzureEdition.Hyperscale, "HS_Gen5_2" } + }; + Assert.That(actualLevelsMap.Count, Is.EqualTo(expectedDefaults.Count), "Did not get expected number of editions for DatabaseHandler's service levels"); + foreach(AzureEdition edition in expectedDefaults.Keys) + { + if (AzureSqlDbHelper.TryGetServiceObjectiveInfo(edition, out var expectedLevelInfo)) + { + var expectedServiceLevels = expectedLevelInfo.Value; + var actualServiceLevels = actualLevelsMap[edition.DisplayName]; + Assert.That(actualServiceLevels, Is.EquivalentTo(expectedServiceLevels), "Did not get expected SLO list for edition '{0}'", edition.DisplayName); + + var expectedDefaultIndex = expectedLevelInfo.Key; + var expectedDefault = expectedServiceLevels[expectedDefaultIndex]; + var actualDefault = actualServiceLevels[0]; + Assert.That(actualDefault, Is.EqualTo(expectedDefault), "Did not get expected default SLO for edition '{0}'", edition.DisplayName); + } + else + { + Assert.Fail("Could not retrieve SLO info for Azure edition '{0}'", edition.DisplayName); + } + } + } + + [Test] + public void GetAzureMaxSizesTest() + { + var actualSizesMap = new Dictionary(); + foreach (AzureEditionDetails sizeDetails in DatabaseHandler.AzureMaxSizes) + { + actualSizesMap.Add(sizeDetails.EditionDisplayName, sizeDetails.Details); + } + + var expectedDefaults = new Dictionary() + { + { AzureEdition.Basic, "2GB" }, + { AzureEdition.Standard, "250GB" }, + { AzureEdition.Premium, "500GB" }, + { AzureEdition.DataWarehouse, "10240GB" }, + { AzureEdition.BusinessCritical, "32GB" }, + { AzureEdition.GeneralPurpose, "32GB" }, + { AzureEdition.Hyperscale, "0MB" } + }; + Assert.That(actualSizesMap.Count, Is.EqualTo(expectedDefaults.Count), "Did not get expected number of editions for DatabaseHandler's max sizes"); + foreach(AzureEdition edition in expectedDefaults.Keys) + { + if (AzureSqlDbHelper.TryGetDatabaseSizeInfo(edition, out var expectedSizeInfo)) + { + var expectedSizes = expectedSizeInfo.Value.Select(size => size.ToString()).ToArray(); + var actualSizes = actualSizesMap[edition.DisplayName]; + Assert.That(actualSizes, Is.EquivalentTo(expectedSizes), "Did not get expected size list for edition '{0}'", edition.DisplayName); + + var expectedDefaultIndex = expectedSizeInfo.Key; + var expectedDefault = expectedSizes[expectedDefaultIndex]; + var actualDefault = actualSizes[0]; + Assert.That(actualDefault, Is.EqualTo(expectedDefault.ToString()), "Did not get expected default size for edition '{0}'", edition.DisplayName); + } + else + { + Assert.Fail("Could not retrieve max size info for Azure edition '{0}'", edition.DisplayName); + } + } + } + private bool DatabaseExists(string dbName, Server server) { server.Databases.Refresh();