From 414949d1293d8b55d3087f6f2b1597139cc84177 Mon Sep 17 00:00:00 2001 From: Kate Shin Date: Wed, 12 Jul 2017 13:09:07 -0700 Subject: [PATCH] Add advanced options to backup service (#405) * Add advanced options for Backup * Add backup encryption strings * Add test for backup advanced option * Add strings to SR * Add verify backup restore option * Addressed PR comments * Add MIT license header --- .../DisasterRecovery/BackupOperation.cs | 118 +++++++++-- .../DisasterRecovery/CommonUtilities.cs | 27 +++ .../Contracts/BackupConfigInfo.cs | 23 ++ .../DisasterRecovery/Contracts/BackupInfo.cs | 96 ++++++++- .../DisasterRecoveryService.cs | 2 +- .../DisasterRecovery/IBackupOperation.cs | 2 +- .../Localization/sr.strings | 1 - .../DisasterRecovery/BackupServiceTests.cs | 200 ++++++++++++++++++ .../DisasterRecovery/BackupTests.cs | 98 --------- .../DisasterRecovery/BackupOperationStub.cs | 2 +- .../DisasterRecovery/BackupTests.cs | 6 +- 11 files changed, 450 insertions(+), 125 deletions(-) create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupServiceTests.cs delete mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupTests.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation.cs index 7afae8c4..8022122e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation.cs @@ -13,6 +13,7 @@ using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; +using System.Globalization; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { @@ -137,13 +138,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery /// /// /// - public BackupConfigInfo GetBackupConfigInfo(string databaseName) + public BackupConfigInfo CreateBackupConfigInfo(string databaseName) { - BackupConfigInfo databaseInfo = new BackupConfigInfo(); - databaseInfo.RecoveryModel = this.GetRecoveryModel(databaseName); - databaseInfo.DefaultBackupFolder = this.GetDefaultBackupFolder(); - databaseInfo.LatestBackups = this.GetLatestBackupLocations(databaseName); - return databaseInfo; + BackupConfigInfo configInfo = new BackupConfigInfo(); + configInfo.RecoveryModel = GetRecoveryModel(databaseName); + configInfo.DefaultBackupFolder = GetDefaultBackupFolder(); + configInfo.LatestBackups = GetLatestBackupLocations(databaseName); + configInfo.BackupEncryptors = GetBackupEncryptors(); + return configInfo; } /// @@ -226,17 +228,78 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery } this.backup.CopyOnly = this.backupInfo.IsCopyOnly; + this.backup.FormatMedia = this.backupInfo.FormatMedia; + this.backup.Initialize = this.backupInfo.Initialize; + this.backup.SkipTapeHeader = this.backupInfo.SkipTapeHeader; + this.backup.Checksum = this.backupInfo.Checksum; + this.backup.ContinueAfterError = this.backupInfo.ContinueAfterError; - //TODO: This should be changed to get user inputs - this.backup.FormatMedia = false; - this.backup.Initialize = false; - this.backup.SkipTapeHeader = true; - this.backup.Checksum = false; - this.backup.ContinueAfterError = false; - this.backup.LogTruncation = BackupTruncateLogType.Truncate; + if (!string.IsNullOrEmpty(this.backupInfo.MediaName)) + { + this.backup.MediaName = this.backupInfo.MediaName; + } + + if (!string.IsNullOrEmpty(this.backupInfo.MediaDescription)) + { + this.backup.MediaDescription = this.backupInfo.MediaDescription; + } + if (this.backupInfo.TailLogBackup + && !this.backupRestoreUtil.IsHADRDatabase(this.backupInfo.DatabaseName) + && !this.backupRestoreUtil.IsMirroringEnabled(this.backupInfo.DatabaseName)) + { + this.backup.NoRecovery = true; + } + + if (this.backupInfo.LogTruncation) + { + this.backup.LogTruncation = BackupTruncateLogType.Truncate; + } + else + { + this.backup.LogTruncation = BackupTruncateLogType.NoTruncate; + } + + if (!string.IsNullOrEmpty(this.backupInfo.BackupSetDescription)) + { + this.backup.BackupSetDescription = this.backupInfo.BackupSetDescription; + } + + if (this.backupInfo.RetainDays >= 0) + { + this.backup.RetainDays = this.backupInfo.RetainDays; + } + else + { + this.backup.ExpirationDate = this.backupInfo.ExpirationDate; + } + + this.backup.CompressionOption = (BackupCompressionOptions)this.backupInfo.CompressionOption; + + if (!string.IsNullOrEmpty(this.backupInfo.EncryptorName)) + { + this.backup.EncryptionOption = new BackupEncryptionOptions((BackupEncryptionAlgorithm)this.backupInfo.EncryptionAlgorithm, + (BackupEncryptorType)this.backupInfo.EncryptorType, + this.backupInfo.EncryptorName); + } + // Execute backup this.backup.SqlBackup(this.dataContainer.Server); + + // Verify backup if required + if (this.backupInfo.VerifyBackupRequired) + { + Restore restore = new Restore(); + restore.Devices.AddRange(this.backup.Devices); + restore.Database = this.backup.Database; + + string errorMessage = null; + restore.SqlVerifyLatest(this.dataContainer.Server, out errorMessage); + if (errorMessage != null) + { + throw new Exception(errorMessage); + } + } } /// @@ -277,11 +340,34 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery } /// - /// Return true if backup to URL is supported in the current SQL Server version + /// Returns the certificates and asymmetric keys from master for encryption /// - private bool BackupToUrlSupported() + public List GetBackupEncryptors() { - return BackupRestoreBase.IsBackupUrlDeviceSupported(this.dataContainer.Server.PingSqlServerVersion(this.dataContainer.ServerName)); + List encryptors = new List(); + if (this.dataContainer.Server.Databases.Contains("master")) + { + CertificateCollection certificates = this.dataContainer.Server.Databases["master"].Certificates; + DateTime currentUtcDateTime = DateTime.UtcNow; + foreach (Certificate item in certificates) + { + if ((item.Name.StartsWith("##", StringComparison.InvariantCulture) && item.Name.EndsWith("##", StringComparison.InvariantCulture)) || + DateTime.Compare(item.ExpirationDate, currentUtcDateTime) < 0) + { + continue; + } + encryptors.Add(new BackupEncryptor((int)BackupEncryptorType.ServerCertificate, item.Name)); + } + AsymmetricKeyCollection keys = this.dataContainer.Server.Databases["master"].AsymmetricKeys; + foreach (AsymmetricKey item in keys) + { + if (item.KeyEncryptionAlgorithm == AsymmetricKeyEncryptionAlgorithm.CryptographicProviderDefined) + { + encryptors.Add(new BackupEncryptor((int)BackupEncryptorType.ServerAsymmetricKey, item.Name)); + } + } + } + return encryptors; } #endregion diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/CommonUtilities.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/CommonUtilities.cs index 32377f0f..f7e1d759 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/CommonUtilities.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/CommonUtilities.cs @@ -54,6 +54,33 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery StandBy } + /// + /// Class for backup encryptor + /// + public class BackupEncryptor + { + /// + /// Ctor + /// + /// + /// + public BackupEncryptor(int encryptorType, string encryptorName) + { + this.EncryptorType = encryptorType; + this.EncryptorName = encryptorName; + } + + /// + /// Encryptor type - certificate or asymetric key + /// + public int EncryptorType { get; set; } + + /// + /// Encryptor name + /// + public string EncryptorName { get; set; } + } + /// /// Restore item source /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupConfigInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupConfigInfo.cs index fbee8538..17fc6246 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupConfigInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupConfigInfo.cs @@ -13,11 +13,34 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts /// public class BackupConfigInfo { + /// + /// Gets or sets default database info + /// public DatabaseInfo DatabaseInfo { get; set; } + + /// + /// Gets or sets recovery model of a database + /// public string RecoveryModel { get; set; } + + /// + /// Gets or sets the latest backup set of a database + /// public List LatestBackups { get; set; } + + /// + /// Gets or sets the default backup folder + /// public string DefaultBackupFolder { get; set; } + /// + /// Gets or sets backup encryptors + /// + public List BackupEncryptors { get; set; } + + /// + /// Ctor + /// public BackupConfigInfo() { } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupInfo.cs index cd99bd21..2636ce66 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupInfo.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; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts @@ -13,7 +14,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts /// Name of the datbase to perfom backup /// public string DatabaseName { get; set; } - + /// /// Component to backup - Database or Files /// @@ -48,15 +49,104 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts /// List of {key: backup path, value: device type} /// public Dictionary BackupPathDevices { get; set; } - + /// /// List of selected backup paths /// public List BackupPathList { get; set; } - + /// /// Indicates if the backup should be copy-only /// public bool IsCopyOnly { get; set; } + + /// + /// Gets or sets a Boolean property value that determines whether a media is formatted as the first step of the backup operation. + /// + public bool FormatMedia { get; set; } + + /// + /// Gets or sets a Boolean property value that determines whether the devices associated with a backup operation are initialized as part of the backup operation. + /// + public bool Initialize { get; set; } + + /// + /// Gets or sets Boolean property that determines whether the tape header is read. + /// + public bool SkipTapeHeader { get; set; } + + /// + /// Gets or sets the name used to identify a particular media set. + /// + public string MediaName { get; set; } + + /// + /// Gets or sets a textual description of the medium that contains a backup set. + /// + public string MediaDescription { get; set; } + + /// + /// Gets or sets a Boolean property value that determines whether a checksum value is calculated during backup or restore operations. + /// + public bool Checksum { get; set; } + + /// + /// Gets or sets a Boolean property value that determines whether the backup or restore continues after a checksum error occurs. + /// + public bool ContinueAfterError { get; set; } + + /// + /// Gets or sets a Boolean property value that determines whether to truncate the database log. + /// + public bool LogTruncation { get; set; } + + /// + /// Gets or sets a Boolean property value that determines whether to backup the tail of the log + /// + public bool TailLogBackup { get; set; } + + /// + /// Gets or sets a textual description for a particular backup set. + /// + public string BackupSetDescription { get; set; } + + /// + /// Gets or sets the number of days that must elapse before a backup set can be overwritten. + /// + public int RetainDays { get; set; } + + /// + /// Gets or sets the date and time when the backup set expires and the backup data is no longer considered relevant. + /// + public DateTime ExpirationDate { get; set; } + + /// + /// Gets or sets the backup compression option. + /// This should be converted to BackupCompressionOptions when setting it to Backup object. + /// + public int CompressionOption { get; set; } + + /// + /// Gets or sets a Boolean property that determines whether verify is required. + /// + public bool VerifyBackupRequired { get; set; } + + /// + /// Specifies the algorithm type used for backup encryption. + /// This should be converted to BackupEncryptionAlgorithm when creating BackupEncryptionOptions object. + /// + public int EncryptionAlgorithm { get; set; } + + /// + /// Specifies the encryptor type used to encrypt an encryption key. + /// This should be converted to BackupEncryptorType when creating BackupEncryptionOptions object. + /// + public int EncryptorType { get; set; } + + /// + /// Gets or sets the name of the encryptor. + /// + public string EncryptorName { get; set; } + } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs index 8afacc2a..02f2cc8a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs @@ -281,7 +281,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { BackupOperation backupOperation = new BackupOperation(); backupOperation.Initialize(dataContainer, sqlConnection); - return backupOperation.GetBackupConfigInfo(databaseName); + return backupOperation.CreateBackupConfigInfo(databaseName); } internal BackupOperation SetBackupInput(CDataContainer dataContainer, SqlConnection sqlConnection, BackupInfo input) diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/IBackupOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/IBackupOperation.cs index b2f72862..8358e68a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/IBackupOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/IBackupOperation.cs @@ -26,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery /// /// /// - BackupConfigInfo GetBackupConfigInfo(string databaseName); + BackupConfigInfo CreateBackupConfigInfo(string databaseName); /// /// Set backup input properties diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index ba5bf52e..2616a842 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -812,7 +812,6 @@ Backup_TaskName = Backup Database Task_InProgress = In progress Task_Completed = Completed - ########################################################################### # Restore ConflictWithNoRecovery = Specifying this option when restoring a backup with the NORECOVERY option is not permitted. diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupServiceTests.cs new file mode 100644 index 00000000..42013e54 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupServiceTests.cs @@ -0,0 +1,200 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.IO; +using System.Threading.Tasks; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Admin; +using Microsoft.SqlTools.ServiceLayer.Admin.Contracts; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using Moq; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery +{ + public class BackupServiceTests + { + // Query format to create master key and certificate for backup encryption + private const string CreateCertificateQueryFormat = @"USE master; +IF NOT EXISTS(SELECT * FROM sys.symmetric_keys WHERE symmetric_key_id = 101) +CREATE MASTER KEY ENCRYPTION BY PASSWORD = '{0}'; +IF NOT EXISTS(SELECT * FROM sys.certificates WHERE name = '{1}') +CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; "; + + // Query format to clean up master key and certificate + private const string CleanupCertificateQueryFormat = @"USE master; DROP CERTIFICATE {0}; DROP MASTER KEY"; + + /// + /// Get backup configuration info + /// + /// Test is failing in code coverage runs. Reenable when stable. + ///[Fact] + public async void GetBackupConfigInfoTest() + { + string databaseName = "testbackup_" + new Random().Next(10000000, 99999999); + SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName); + var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName); + + var requestContext = new Mock>(); + requestContext.Setup(x => x.SendResult(It.IsAny())) + .Returns(Task.FromResult(new object())); + + var dbParams = new DefaultDatabaseInfoParams + { + OwnerUri = liveConnection.ConnectionInfo.OwnerUri + }; + + await DisasterRecoveryService.HandleBackupConfigInfoRequest(dbParams, requestContext.Object); + + requestContext.Verify(x => x.SendResult(It.Is + (p => p.BackupConfigInfo.RecoveryModel != string.Empty + && p.BackupConfigInfo.DefaultBackupFolder != string.Empty + && p.BackupConfigInfo.DatabaseInfo != null))); + + testDb.Cleanup(); + } + + /// Test is failing in code coverage runs. Reenable when stable. + ///[Fact] + public void CreateBackupTest() + { + string databaseName = "testbackup_" + new Random().Next(10000000, 99999999); + SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName); + var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName); + + // Initialize backup service + DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true); + SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo); + + // Get default backup path + BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database); + string backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak"); + + BackupInfo backupInfo = CreateBackupInfo(databaseName, + BackupType.Full, + new List(){ backupPath }, + new Dictionary(){{ backupPath, (int)DeviceType.File }}); + + var backupParams = new BackupParams + { + OwnerUri = liveConnection.ConnectionInfo.OwnerUri, + BackupInfo = backupInfo + }; + + // Backup the database + BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); + DisasterRecoveryService.Instance.PerformBackup(backupOperation); + + // Remove the backup file + if (File.Exists(backupPath)) + { + File.Delete(backupPath); + } + + // Clean up the database + testDb.Cleanup(); + } + + /// + /// Test creating backup with advanced options set. + /// + [Fact] + public void CreateBackupWithAdvancedOptionsTest() + { + string databaseName = "testbackup_" + new Random().Next(10000000, 99999999); + SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName); + string certificateName = "backupcertificate" + new Random().Next(10000000, 99999999); + string masterkeyPassword = Guid.NewGuid().ToString(); + string createCertificateQuery = string.Format(CreateCertificateQueryFormat, masterkeyPassword, certificateName); + string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName); + + // create master key and certificate + Console.WriteLine("Create master key and certificate.."); + testDb.RunQuery(createCertificateQuery); + + var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName); + + // Initialize backup service + DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true); + SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo); + + // Get default backup path + Console.WriteLine("Get default backup path.."); + BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database); + string backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak"); + + BackupInfo backupInfo = CreateBackupInfo(databaseName, + BackupType.Full, + new List(){ backupPath }, + new Dictionary(){{ backupPath, (int)DeviceType.File }}); + + // Set advanced options + backupInfo.ContinueAfterError = true; + backupInfo.FormatMedia = true; + backupInfo.SkipTapeHeader = true; + backupInfo.Initialize = true; + backupInfo.MediaName = "backup test media"; + backupInfo.MediaDescription = "backup test"; + backupInfo.RetainDays = 90; + backupInfo.CompressionOption = (int)BackupCompressionOptions.On; + + // Set encryption + backupInfo.EncryptionAlgorithm = (int)BackupEncryptionAlgorithm.Aes128; + backupInfo.EncryptorType = (int)BackupEncryptorType.ServerCertificate; + backupInfo.EncryptorName = certificateName; + + var backupParams = new BackupParams + { + OwnerUri = liveConnection.ConnectionInfo.OwnerUri, + BackupInfo = backupInfo + }; + + // Backup the database + Console.WriteLine("Perform backup operation.."); + BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); + DisasterRecoveryService.Instance.PerformBackup(backupOperation); + + // Verify backup file is created + Assert.True(File.Exists(backupPath)); + + // Remove the backup file + Console.WriteLine("Remove backup file.."); + if (File.Exists(backupPath)) + { + File.Delete(backupPath); + } + + // Delete certificate and master key + Console.WriteLine("Remove certificate and master key.."); + testDb.RunQuery(cleanupCertificateQuery); + + // Clean up the database + Console.WriteLine("Clean up database.."); + testDb.Cleanup(); + } + + private BackupInfo CreateBackupInfo(string databaseName, BackupType backupType, List backupPathList, Dictionary backupPathDevices) + { + BackupInfo backupInfo = new BackupInfo(); + backupInfo.BackupComponent = (int)BackupComponent.Database; + backupInfo.BackupDeviceType = (int)BackupDeviceType.Disk; + backupInfo.BackupPathDevices = backupPathDevices; + backupInfo.BackupPathList = backupPathList; + backupInfo.BackupsetName = "default_backup"; + backupInfo.BackupType = (int)backupType; + backupInfo.DatabaseName = databaseName; + backupInfo.SelectedFileGroup = null; + backupInfo.SelectedFiles = ""; + return backupInfo; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupTests.cs deleted file mode 100644 index b3babd9a..00000000 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.IO; -using System.Threading.Tasks; -using Microsoft.SqlServer.Management.Smo; -using Microsoft.SqlTools.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Admin; -using Microsoft.SqlTools.ServiceLayer.Admin.Contracts; -using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; -using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; -using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; -using Microsoft.SqlTools.ServiceLayer.Test.Common; -using Moq; -using Xunit; - -namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery -{ - public class BackupTests - { - /// - /// Get backup configuration info - /// - /// Test is failing in code coverage runs. Reenable when stable. - /// [Fact] - public async void GetBackupConfigInfoTest() - { - string databaseName = "testbackup_" + new Random().Next(10000000, 99999999); - SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName); - var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName); - - var requestContext = new Mock>(); - requestContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new object())); - - var dbParams = new DefaultDatabaseInfoParams - { - OwnerUri = liveConnection.ConnectionInfo.OwnerUri - }; - - await DisasterRecoveryService.HandleBackupConfigInfoRequest(dbParams, requestContext.Object); - - requestContext.Verify(x => x.SendResult(It.Is - (p => p.BackupConfigInfo.RecoveryModel != string.Empty - && p.BackupConfigInfo.DefaultBackupFolder != string.Empty - && p.BackupConfigInfo.DatabaseInfo != null))); - - testDb.Cleanup(); - } - - /// Test is failing in code coverage runs. Reenable when stable. - ///[Fact] - public void CreateBackupTest() - { - string databaseName = "testbackup_" + new Random().Next(10000000, 99999999); - SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName); - var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName); - - // Initialize backup service - DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true); - SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo); - - // Get default backup path - BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database); - string backupPath = backupConfigInfo.DefaultBackupFolder + "\\" + databaseName + ".bak"; - - var backupInfo = new BackupInfo(); - backupInfo.BackupComponent = (int)BackupComponent.Database; - backupInfo.BackupDeviceType = (int)BackupDeviceType.Disk; - backupInfo.BackupPathDevices = new Dictionary() { { backupPath, (int)DeviceType.File } }; - backupInfo.BackupPathList = new List(new string[] { backupPath }); - backupInfo.BackupsetName = "default_backup"; - backupInfo.BackupType = (int)BackupType.Full; - backupInfo.DatabaseName = databaseName; - backupInfo.SelectedFileGroup = null; - backupInfo.SelectedFiles = ""; - - var backupParams = new BackupParams - { - OwnerUri = liveConnection.ConnectionInfo.OwnerUri, - BackupInfo = backupInfo - }; - - // Backup the database - BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); - DisasterRecoveryService.Instance.PerformBackup(backupOperation); - - // Remove the backup file - if (File.Exists(backupPath)) - { - File.Delete(backupPath); - } - - // Clean up the database - testDb.Cleanup(); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupOperationStub.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupOperationStub.cs index 9a83086b..e3feccf5 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupOperationStub.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupOperationStub.cs @@ -40,7 +40,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery /// /// /// - public BackupConfigInfo GetBackupConfigInfo(string databaseName) + public BackupConfigInfo CreateBackupConfigInfo(string databaseName) { return null; } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupTests.cs index ad1cd65a..c62b7111 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupTests.cs @@ -99,8 +99,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery /// Cancel multiple backup tasks /// /// - /// Test is failing unreliably in AppVeyor runs so disabling for. - ///[Fact] + [Fact] public async Task VerifyCancelMultipleBackupTasks() { using (SqlTaskManager manager = new SqlTaskManager()) @@ -142,8 +141,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery /// Create two backup tasks and cancel one task /// /// - /// Test is failing in AppVeyor unreliabily..disabling for now. please reenalbe when test is stable in AppVeyor builds. - /// [Fact] + [Fact] public async Task VerifyCombinationRunAndCancelBackupTasks() { using (SqlTaskManager manager = new SqlTaskManager())