Skip to content

Commit

Permalink
SqlAGDatabase Fix for issue #1492 (#1642)
Browse files Browse the repository at this point in the history
- SqlAGDatabase
  - Added AutomaticSeeding for this resource. In Set-TargetResource added logic that looks 
    at all replicas of an availability group. When automatic seeding is found, it will use that 
    (issue #1492).
  - Lots of extra tests to check AutomaticSeeding.
  - The parameter `BackupPath` is still needed just in case a database never has been backuped
  • Loading branch information
Fiander committed Dec 13, 2020
1 parent 29d8648 commit 0731bc9
Show file tree
Hide file tree
Showing 5 changed files with 1,989 additions and 383 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- SqlAGDatabase
- Fix for issue ([issue #1492](https://github.com/dsccommunity/SqlServerDsc/issues/1492))
added AutomaticSeeding for this resource. In Set-TargetResource added logic that looks
at all replicas of an availability group. When automatic seedig is found, it will use that.
- Lots of extra tests to check AutomaticSeeding.
- The parameter `BackupPath` is still needed just in case a database never has been backuped before.
- SqlMaxDop
- Fixes ([issue #396](https://github.com/dsccommunity/SqlServerDsc/issues/396)).
Added three return values in Get-Target resource.
Expand Down
222 changes: 132 additions & 90 deletions source/DSCResources/DSC_SqlAGDatabase/DSC_SqlAGDatabase.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ function Set-TargetResource
# Create a hash table to store the databases that failed to be added to the Availability Group
$databasesToAddFailures = @{}

# Create a hash table to store the databases that failed to be added to the Availability Group
# Create a hash table to store the databases that failed to be removed from the Availability Group
$databasesToRemoveFailures = @{}

if ( $databasesToAddToAvailabilityGroup.Count -gt 0 )
Expand Down Expand Up @@ -422,57 +422,89 @@ function Set-TargetResource
}
}

if ( $prerequisiteCheckFailures.Count -eq 0 )
{
$databaseFullBackupFile = Join-Path -Path $BackupPath -ChildPath "$($databaseObject.Name)_Full_$(Get-Date -Format 'yyyyMMddhhmmss').bak"
$databaseLogBackupFile = Join-Path -Path $BackupPath -ChildPath "$($databaseObject.Name)_Log_$(Get-Date -Format 'yyyyMMddhhmmss').trn"

# Build the backup parameters. If no backup was previously taken, a standard full will be taken. Otherwise a CopyOnly backup will be taken.
$backupSqlDatabaseParameters = @{
DatabaseObject = $databaseObject
BackupAction = 'Database'
BackupFile = $databaseFullBackupFile
ErrorAction = 'Stop'
}
<#
Determine whether SEEDING_MODE = for all replicas is Automatic or Manual. If all replicas are Automatic, a restore is not needed.
When the database is fresh, and never backuped before, a backup is still needed.
#>
$backupNeeded = $false
$restoreNeeded = $false

# If database object last backup data not equal to 0 then backup with CopyOnly.
if ( $databaseObject.LastBackupDate -ne 0 )
foreach ( $availabilityGroupReplica in $secondaryReplicas )
{
if ( $availabilityGroupReplica.SeedingMode -eq 'Manual')
{
$backupSqlDatabaseParameters.Add('CopyOnly', $true)
$backupNeeded = $true
$restoreNeeded = $true
}
}

try
if ( $backupNeeded -eq $false)
{
<#
Because an availability group can only be made when the database has at least one backup,
if there is no backup, create one. This backup is not needed for restore, only the initial setup
of the LSN in the database is needed
#>
if ( $primaryServerObject.Databases[$databaseToAddToAvailabilityGroup].CreateDate -gt $primaryServerObject.Databases[$databaseToAddToAvailabilityGroup].LastBackupDate)
{
Backup-SqlDatabase @backupSqlDatabaseParameters
$needsBackup = $true
}
catch
}

if ( $prerequisiteCheckFailures.Count -eq 0 )
{
if ( $backupNeeded)
{
# Log the failure
$databasesToAddFailures.Add($databaseToAddToAvailabilityGroup, $_.Exception)
$databaseFullBackupFile = Join-Path -Path $BackupPath -ChildPath "$($databaseObject.Name)_Full_$(Get-Date -Format 'yyyyMMddhhmmss').bak"
$databaseLogBackupFile = Join-Path -Path $BackupPath -ChildPath "$($databaseObject.Name)_Log_$(Get-Date -Format 'yyyyMMddhhmmss').trn"

# Build the backup parameters. If no backup was previously taken, a standard full will be taken. Otherwise a CopyOnly backup will be taken.
$backupSqlDatabaseParameters = @{
DatabaseObject = $databaseObject
BackupAction = 'Database'
BackupFile = $databaseFullBackupFile
ErrorAction = 'Stop'
}

# Move on to the next database
continue
}
# If database object last backup data not equal to 0 then backup with CopyOnly.
if ( $databaseObject.LastBackupDate -ne 0 )
{
$backupSqlDatabaseParameters.Add('CopyOnly', $true)
}

# Create the parameters to perform a transaction log backup
$backupSqlDatabaseLogParams = @{
DatabaseObject = $databaseObject
BackupAction = 'Log'
BackupFile = $databaseLogBackupFile
ErrorAction = 'Stop'
}
try
{
Backup-SqlDatabase @backupSqlDatabaseParameters
}
catch
{
# Log the failure
$databasesToAddFailures.Add($databaseToAddToAvailabilityGroup, $_.Exception)

try
{
Backup-SqlDatabase @backupSqlDatabaseLogParams
}
catch
{
# Log the failure
$databasesToAddFailures.Add($databaseToAddToAvailabilityGroup, $_.Exception)
# Move on to the next database
continue
}

# Move on to the next database
continue
# Create the parameters to perform a transaction log backup
$backupSqlDatabaseLogParams = @{
DatabaseObject = $databaseObject
BackupAction = 'Log'
BackupFile = $databaseLogBackupFile
ErrorAction = 'Stop'
}

try
{
Backup-SqlDatabase @backupSqlDatabaseLogParams
}
catch
{
# Log the failure
$databasesToAddFailures.Add($databaseToAddToAvailabilityGroup, $_.Exception)

# Move on to the next database
continue
}
}

# Add the database to the availability group on the primary instance
Expand All @@ -489,62 +521,65 @@ function Set-TargetResource
continue
}

# Need to restore the database with a query in order to impersonate the correct login
$restoreDatabaseQueryStringBuilder = New-Object -TypeName System.Text.StringBuilder

if ( $MatchDatabaseOwner )
if ( $restoreNeeded)
{
$restoreDatabaseQueryStringBuilder.Append('EXECUTE AS LOGIN = ''') | Out-Null
$restoreDatabaseQueryStringBuilder.Append($databaseObject.Owner) | Out-Null
# Need to restore the database with a query in order to impersonate the correct login
$restoreDatabaseQueryStringBuilder = New-Object -TypeName System.Text.StringBuilder

if ( $MatchDatabaseOwner )
{
$restoreDatabaseQueryStringBuilder.Append('EXECUTE AS LOGIN = ''') | Out-Null
$restoreDatabaseQueryStringBuilder.Append($databaseObject.Owner) | Out-Null
$restoreDatabaseQueryStringBuilder.AppendLine('''') | Out-Null
}

$restoreDatabaseQueryStringBuilder.Append('RESTORE DATABASE [') | Out-Null
$restoreDatabaseQueryStringBuilder.Append($databaseToAddToAvailabilityGroup) | Out-Null
$restoreDatabaseQueryStringBuilder.AppendLine(']') | Out-Null
$restoreDatabaseQueryStringBuilder.Append('FROM DISK = ''') | Out-Null
$restoreDatabaseQueryStringBuilder.Append($databaseFullBackupFile) | Out-Null
$restoreDatabaseQueryStringBuilder.AppendLine('''') | Out-Null
}
$restoreDatabaseQueryStringBuilder.Append('WITH NORECOVERY') | Out-Null

$restoreDatabaseQueryStringBuilder.Append('RESTORE DATABASE [') | Out-Null
$restoreDatabaseQueryStringBuilder.Append($databaseToAddToAvailabilityGroup) | Out-Null
$restoreDatabaseQueryStringBuilder.AppendLine(']') | Out-Null
$restoreDatabaseQueryStringBuilder.Append('FROM DISK = ''') | Out-Null
$restoreDatabaseQueryStringBuilder.Append($databaseFullBackupFile) | Out-Null
$restoreDatabaseQueryStringBuilder.AppendLine('''') | Out-Null
$restoreDatabaseQueryStringBuilder.Append('WITH NORECOVERY') | Out-Null
if ( $ReplaceExisting )
{
$restoreDatabaseQueryStringBuilder.Append(',REPLACE') | Out-Null
}

if ( $ReplaceExisting )
{
$restoreDatabaseQueryStringBuilder.Append(',REPLACE') | Out-Null
}
if ( $MatchDatabaseOwner )
{
$restoreDatabaseQueryStringBuilder.AppendLine() | Out-Null
$restoreDatabaseQueryStringBuilder.Append('REVERT') | Out-Null
}
$restoreDatabaseQueryString = $restoreDatabaseQueryStringBuilder.ToString()

if ( $MatchDatabaseOwner )
{
$restoreDatabaseQueryStringBuilder.AppendLine() | Out-Null
$restoreDatabaseQueryStringBuilder.Append('REVERT') | Out-Null
}
$restoreDatabaseQueryString = $restoreDatabaseQueryStringBuilder.ToString()
# Need to restore the database with a query in order to impersonate the correct login
$restoreLogQueryStringBuilder = New-Object -TypeName System.Text.StringBuilder

# Need to restore the database with a query in order to impersonate the correct login
$restoreLogQueryStringBuilder = New-Object -TypeName System.Text.StringBuilder
if ( $MatchDatabaseOwner )
{
$restoreLogQueryStringBuilder.Append('EXECUTE AS LOGIN = ''') | Out-Null
$restoreLogQueryStringBuilder.Append($databaseObject.Owner) | Out-Null
$restoreLogQueryStringBuilder.AppendLine('''') | Out-Null
}

if ( $MatchDatabaseOwner )
{
$restoreLogQueryStringBuilder.Append('EXECUTE AS LOGIN = ''') | Out-Null
$restoreLogQueryStringBuilder.Append($databaseObject.Owner) | Out-Null
$restoreLogQueryStringBuilder.Append('RESTORE DATABASE [') | Out-Null
$restoreLogQueryStringBuilder.Append($databaseToAddToAvailabilityGroup) | Out-Null
$restoreLogQueryStringBuilder.AppendLine(']') | Out-Null
$restoreLogQueryStringBuilder.Append('FROM DISK = ''') | Out-Null
$restoreLogQueryStringBuilder.Append($databaseLogBackupFile) | Out-Null
$restoreLogQueryStringBuilder.AppendLine('''') | Out-Null
}
$restoreLogQueryStringBuilder.Append('WITH NORECOVERY') | Out-Null

$restoreLogQueryStringBuilder.Append('RESTORE DATABASE [') | Out-Null
$restoreLogQueryStringBuilder.Append($databaseToAddToAvailabilityGroup) | Out-Null
$restoreLogQueryStringBuilder.AppendLine(']') | Out-Null
$restoreLogQueryStringBuilder.Append('FROM DISK = ''') | Out-Null
$restoreLogQueryStringBuilder.Append($databaseLogBackupFile) | Out-Null
$restoreLogQueryStringBuilder.AppendLine('''') | Out-Null
$restoreLogQueryStringBuilder.Append('WITH NORECOVERY') | Out-Null
if ( $MatchDatabaseOwner )
{
$restoreLogQueryStringBuilder.AppendLine() | Out-Null
$restoreLogQueryStringBuilder.Append('REVERT') | Out-Null
}

if ( $MatchDatabaseOwner )
{
$restoreLogQueryStringBuilder.AppendLine() | Out-Null
$restoreLogQueryStringBuilder.Append('REVERT') | Out-Null
$restoreLogQueryString = $restoreLogQueryStringBuilder.ToString()
}

$restoreLogQueryString = $restoreLogQueryStringBuilder.ToString()

try
{
foreach ( $availabilityGroupReplica in $secondaryReplicas )
Expand All @@ -554,9 +589,12 @@ function Set-TargetResource
$currentAvailabilityGroupReplicaServerObject = Connect-SQL @connectSqlParameters
$currentReplicaAvailabilityGroupObject = $currentAvailabilityGroupReplicaServerObject.AvailabilityGroups[$AvailabilityGroupName]

# Restore the database
Invoke-Query -ServerName $currentAvailabilityGroupReplicaServerObject.NetName -InstanceName $currentAvailabilityGroupReplicaServerObject.ServiceName -Database master -Query $restoreDatabaseQueryString -StatementTimeout 0
Invoke-Query -ServerName $currentAvailabilityGroupReplicaServerObject.NetName -InstanceName $currentAvailabilityGroupReplicaServerObject.ServiceName -Database master -Query $restoreLogQueryString -StatementTimeout 0
if ( $availabilityGroupReplica.SeedingMode -eq 'MANUAL')
{
# Restore the database
Invoke-Query -ServerName $currentAvailabilityGroupReplicaServerObject.NetName -InstanceName $currentAvailabilityGroupReplicaServerObject.ServiceName -Database master -Query $restoreDatabaseQueryString -StatementTimeout 0
Invoke-Query -ServerName $currentAvailabilityGroupReplicaServerObject.NetName -InstanceName $currentAvailabilityGroupReplicaServerObject.ServiceName -Database master -Query $restoreLogQueryString -StatementTimeout 0
}

# Add the database to the Availability Group
Add-SqlAvailabilityDatabase -InputObject $currentReplicaAvailabilityGroupObject -Database $databaseToAddToAvailabilityGroup
Expand All @@ -572,8 +610,12 @@ function Set-TargetResource
}
finally
{
# Clean up the backup files
Remove-Item -Path $databaseFullBackupFile, $databaseLogBackupFile -Force -ErrorAction Continue
# When a backup was made, it should be removed.
if ( $backupNeeded)
{
# Clean up the backup files
Remove-Item -Path $databaseFullBackupFile, $databaseLogBackupFile -Force -ErrorAction Continue
}
}
}
else
Expand Down
2 changes: 2 additions & 0 deletions source/DSCResources/DSC_SqlAGDatabase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

The `SqlAGDatabase` DSC resource is used to add databases or remove
databases from a specified availability group.
When a replica has Automatic seeding on Automatic, no restore is use for that replica.
When all replicas are on automatic seeding, no backup is made, unless the database has never been backuped.

## Requirements

Expand Down
Loading

0 comments on commit 0731bc9

Please sign in to comment.