mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
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
This commit is contained in:
@@ -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
|
||||
/// </summary>
|
||||
/// <param name="databaseName"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -277,11 +340,34 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if backup to URL is supported in the current SQL Server version
|
||||
/// Returns the certificates and asymmetric keys from master for encryption
|
||||
/// </summary>
|
||||
private bool BackupToUrlSupported()
|
||||
public List<BackupEncryptor> GetBackupEncryptors()
|
||||
{
|
||||
return BackupRestoreBase.IsBackupUrlDeviceSupported(this.dataContainer.Server.PingSqlServerVersion(this.dataContainer.ServerName));
|
||||
List<BackupEncryptor> encryptors = new List<BackupEncryptor>();
|
||||
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
|
||||
|
||||
@@ -54,6 +54,33 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
StandBy
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class for backup encryptor
|
||||
/// </summary>
|
||||
public class BackupEncryptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Ctor
|
||||
/// </summary>
|
||||
/// <param name="encryptorType"></param>
|
||||
/// <param name="encryptorName"></param>
|
||||
public BackupEncryptor(int encryptorType, string encryptorName)
|
||||
{
|
||||
this.EncryptorType = encryptorType;
|
||||
this.EncryptorName = encryptorName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encryptor type - certificate or asymetric key
|
||||
/// </summary>
|
||||
public int EncryptorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encryptor name
|
||||
/// </summary>
|
||||
public string EncryptorName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore item source
|
||||
/// </summary>
|
||||
|
||||
@@ -13,11 +13,34 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
|
||||
/// </summary>
|
||||
public class BackupConfigInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets default database info
|
||||
/// </summary>
|
||||
public DatabaseInfo DatabaseInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets recovery model of a database
|
||||
/// </summary>
|
||||
public string RecoveryModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the latest backup set of a database
|
||||
/// </summary>
|
||||
public List<RestoreItemSource> LatestBackups { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default backup folder
|
||||
/// </summary>
|
||||
public string DefaultBackupFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets backup encryptors
|
||||
/// </summary>
|
||||
public List<BackupEncryptor> BackupEncryptors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ctor
|
||||
/// </summary>
|
||||
public BackupConfigInfo()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public string DatabaseName { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Component to backup - Database or Files
|
||||
/// </summary>
|
||||
@@ -48,15 +49,104 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
|
||||
/// List of {key: backup path, value: device type}
|
||||
/// </summary>
|
||||
public Dictionary<string, int> BackupPathDevices { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// List of selected backup paths
|
||||
/// </summary>
|
||||
public List<string> BackupPathList { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the backup should be copy-only
|
||||
/// </summary>
|
||||
public bool IsCopyOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Boolean property value that determines whether a media is formatted as the first step of the backup operation.
|
||||
/// </summary>
|
||||
public bool FormatMedia { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool Initialize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets Boolean property that determines whether the tape header is read.
|
||||
/// </summary>
|
||||
public bool SkipTapeHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name used to identify a particular media set.
|
||||
/// </summary>
|
||||
public string MediaName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a textual description of the medium that contains a backup set.
|
||||
/// </summary>
|
||||
public string MediaDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Boolean property value that determines whether a checksum value is calculated during backup or restore operations.
|
||||
/// </summary>
|
||||
public bool Checksum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Boolean property value that determines whether the backup or restore continues after a checksum error occurs.
|
||||
/// </summary>
|
||||
public bool ContinueAfterError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Boolean property value that determines whether to truncate the database log.
|
||||
/// </summary>
|
||||
public bool LogTruncation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Boolean property value that determines whether to backup the tail of the log
|
||||
/// </summary>
|
||||
public bool TailLogBackup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a textual description for a particular backup set.
|
||||
/// </summary>
|
||||
public string BackupSetDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of days that must elapse before a backup set can be overwritten.
|
||||
/// </summary>
|
||||
public int RetainDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time when the backup set expires and the backup data is no longer considered relevant.
|
||||
/// </summary>
|
||||
public DateTime ExpirationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the backup compression option.
|
||||
/// This should be converted to BackupCompressionOptions when setting it to Backup object.
|
||||
/// </summary>
|
||||
public int CompressionOption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Boolean property that determines whether verify is required.
|
||||
/// </summary>
|
||||
public bool VerifyBackupRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the algorithm type used for backup encryption.
|
||||
/// This should be converted to BackupEncryptionAlgorithm when creating BackupEncryptionOptions object.
|
||||
/// </summary>
|
||||
public int EncryptionAlgorithm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the encryptor type used to encrypt an encryption key.
|
||||
/// This should be converted to BackupEncryptorType when creating BackupEncryptionOptions object.
|
||||
/// </summary>
|
||||
public int EncryptorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the encryptor.
|
||||
/// </summary>
|
||||
public string EncryptorName { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
/// </summary>
|
||||
/// <param name="databaseName"></param>
|
||||
/// <returns></returns>
|
||||
BackupConfigInfo GetBackupConfigInfo(string databaseName);
|
||||
BackupConfigInfo CreateBackupConfigInfo(string databaseName);
|
||||
|
||||
/// <summary>
|
||||
/// Set backup input properties
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
/// <summary>
|
||||
/// Get backup configuration info
|
||||
/// </summary>
|
||||
/// 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<BackupConfigInfoResponse>>();
|
||||
requestContext.Setup(x => x.SendResult(It.IsAny<BackupConfigInfoResponse>()))
|
||||
.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<BackupConfigInfoResponse>
|
||||
(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<string>(){ backupPath },
|
||||
new Dictionary<string, int>(){{ 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test creating backup with advanced options set.
|
||||
/// </summary>
|
||||
[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<string>(){ backupPath },
|
||||
new Dictionary<string, int>(){{ 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<string> backupPathList, Dictionary<string, int> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Get backup configuration info
|
||||
/// </summary>
|
||||
/// 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<BackupConfigInfoResponse>>();
|
||||
requestContext.Setup(x => x.SendResult(It.IsAny<BackupConfigInfoResponse>()))
|
||||
.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<BackupConfigInfoResponse>
|
||||
(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<string, int>() { { backupPath, (int)DeviceType.File } };
|
||||
backupInfo.BackupPathList = new List<string>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||
/// </summary>
|
||||
/// <param name="databaseName"></param>
|
||||
/// <returns></returns>
|
||||
public BackupConfigInfo GetBackupConfigInfo(string databaseName)
|
||||
public BackupConfigInfo CreateBackupConfigInfo(string databaseName)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -99,8 +99,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||
/// Cancel multiple backup tasks
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// 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())
|
||||
|
||||
Reference in New Issue
Block a user