Skip to content

Commit cc136ba

Browse files
Test-DbaAgPolicyState - Fix Always On policy coverage (review of #10246)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b19c245 commit cc136ba

3 files changed

Lines changed: 196 additions & 14 deletions

File tree

docs/trackers/features/commit-bug-review-TRACKER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Find real bugs (logic errors, null refs, incorrect behavior) and fix them. Skip
9696
| 1f70b62bd | Add Remove-DbaAgentJobSchedule cmdlet (#10273) | DONE | Fixed duplicate-name detach handling and preserved failure output on detach errors; added unit regression tests. |
9797
| 50c0bfdaf | Connect-DbaInstance - Add -AuthenticationType parameter for Entra ID support (#10271) | DONE | Fixed password-based AuthenticationType handling so explicit Entra auth uses SqlConnectionInfo credentials, added missing SqlCredential validation, and added unit regression tests. |
9898
| 27e4da9d1 | Get-DbaDbOrphanUser - Skip SQL login orphan check for contained databases (#10270) | DONE | Guarded ContainmentType for pre-SQL 2012 servers and added unit regression tests. |
99-
| 21a522047 | Test-DbaAgPolicyState - Add new command for Always On policy state checks (#10246) | PENDING | |
99+
| 21a522047 | Test-DbaAgPolicyState - Add new command for Always On policy state checks (#10246) | DONE | Added missing replica sync policy, corrected Microsoft category/name mismatches, and added regression tests. |
100100
| 1f43cbbf0 | Install-DbaMaintenanceSolution: change Compress/Verify/CheckSum to ValidateSet string params (#10247) | PENDING | |
101101
| 444659b0a | Get-DbaDbMailAccount, Get-DbaDbMailProfile - Add Account-Profile link details (#10280) | PENDING | |
102102
| 57fa89a0e | Invoke-DbaDbShrink - Add error message output for failed shrink operations (#10258) | PENDING | |

public/Test-DbaAgPolicyState.ps1

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ function Test-DbaAgPolicyState {
318318
Documentation: https://learn.microsoft.com/en-us/sql/database-engine/availability-groups/windows/availability-replica-does-not-have-a-healthy-role
319319
Policy Name: Availability Replica Role State
320320
Issue: Availability replica does not have a healthy role.
321-
Category: Warning
321+
Category: Critical
322322
Facet: Availability replica
323323
324324
Name : AlwaysOnArReplicaRoleHealthCondition
@@ -335,7 +335,7 @@ function Test-DbaAgPolicyState {
335335
Replica = $replica.Name
336336
Database = $null
337337
PolicyName = "Availability Replica Role State"
338-
Category = "Warning"
338+
Category = "Critical"
339339
Facet = "Availability replica"
340340
IsHealthy = $isHealthy
341341
Issue = if ($isHealthy) { $null } else { "Availability replica does not have a healthy role." }
@@ -347,7 +347,7 @@ function Test-DbaAgPolicyState {
347347
Documentation: https://learn.microsoft.com/en-us/sql/database-engine/availability-groups/windows/availability-replica-is-disconnected
348348
Policy Name: Availability Replica Connection State
349349
Issue: Availability replica is disconnected.
350-
Category: Warning
350+
Category: Critical
351351
Facet: Availability replica
352352
353353
Name : AlwaysOnArReplicaConnectionHealthCondition
@@ -364,7 +364,7 @@ function Test-DbaAgPolicyState {
364364
Replica = $replica.Name
365365
Database = $null
366366
PolicyName = "Availability Replica Connection State"
367-
Category = "Warning"
367+
Category = "Critical"
368368
Facet = "Availability replica"
369369
IsHealthy = $isHealthy
370370
Issue = if ($isHealthy) { $null } else { "Availability replica is disconnected." }
@@ -399,36 +399,79 @@ function Test-DbaAgPolicyState {
399399
Issue = if ($isHealthy) { $null } else { "Availability replica is not joined." }
400400
Details = "JoinState is $($replica.JoinState)"
401401
}
402+
403+
$replicaDatabaseReplicaStates = @($ag.DatabaseReplicaStates | Where-Object AvailabilityReplicaId -eq $replica.UniqueId)
404+
405+
<#
406+
Data synchronization state of some availability database is not healthy
407+
Documentation: https://learn.microsoft.com/en-us/sql/database-engine/availability-groups/windows/data-synchronization-state-of-some-availability-database-is-not-healthy
408+
Policy Name: Availability Replica Data Synchronization State
409+
Issue: Data synchronization state of some availability database is not healthy.
410+
Category: Warning
411+
Facet: Availability replica
412+
#>
413+
414+
if ($replica.AvailabilityMode -eq "SynchronousCommit") {
415+
$unhealthyReplicaDatabaseStates = @($replicaDatabaseReplicaStates | Where-Object SynchronizationState -ne "Synchronized")
416+
} else {
417+
$unhealthyReplicaDatabaseStates = @($replicaDatabaseReplicaStates | Where-Object SynchronizationState -eq "NotSynchronizing")
418+
}
419+
$isHealthy = $unhealthyReplicaDatabaseStates.Count -eq 0
420+
if ($replicaDatabaseReplicaStates) {
421+
$stateDetails = ($replicaDatabaseReplicaStates | ForEach-Object {
422+
"$($_.AvailabilityDatabaseName):$($_.SynchronizationState)"
423+
}) -join ", "
424+
} else {
425+
$stateDetails = "No availability database states found"
426+
}
427+
[PSCustomObject]@{
428+
ComputerName = $ag.ComputerName
429+
InstanceName = $ag.InstanceName
430+
SqlInstance = $ag.SqlInstance
431+
AvailabilityGroup = $ag.Name
432+
Replica = $replica.Name
433+
Database = $null
434+
PolicyName = "Availability Replica Data Synchronization State"
435+
Category = "Warning"
436+
Facet = "Availability replica"
437+
IsHealthy = $isHealthy
438+
Issue = if ($isHealthy) { $null } else { "Data synchronization state of some availability database is not healthy." }
439+
Details = "AvailabilityMode is $($replica.AvailabilityMode); SynchronizationState(s): $stateDetails"
440+
}
402441
}
403442

404443
foreach ($databaseReplicaState in $ag.DatabaseReplicaStates) {
405444
<#
406445
Data synchronization state of availability database is not healthy
407446
Documentation: https://learn.microsoft.com/en-us/sql/database-engine/availability-groups/windows/data-synchronization-state-of-availability-database-is-not-healthy
408-
Policy Name: Availability Database Synchronization State
447+
Policy Name: Availability Database Data Synchronization State
409448
Issue: Data synchronization state of availability database is not healthy.
410-
Category: Critical
449+
Category: Warning
411450
Facet: Availability database
412451
413452
Name : AlwaysOnDbDataSynchronizationHealthCondition
414453
Facet : IAvailabilityDatabaseState
415-
ExpressionNode : @SynchronizationState = Enum('Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseSynchronizationState', 'Synchronized') OR @SynchronizationState = Enum('Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseSynchronizationState', 'Synchronizing')
454+
Expected State : Synchronized for synchronous-commit replicas, Synchronizing for all others
416455
#>
417456

418-
$isHealthy = $databaseReplicaState.SynchronizationState -in "Synchronized", "Synchronizing"
457+
if ($databaseReplicaState.ReplicaAvailabilityMode -eq "SynchronousCommit") {
458+
$isHealthy = $databaseReplicaState.SynchronizationState -eq "Synchronized"
459+
} else {
460+
$isHealthy = $databaseReplicaState.SynchronizationState -eq "Synchronizing"
461+
}
419462
[PSCustomObject]@{
420463
ComputerName = $ag.ComputerName
421464
InstanceName = $ag.InstanceName
422465
SqlInstance = $ag.SqlInstance
423466
AvailabilityGroup = $ag.Name
424467
Replica = $databaseReplicaState.AvailabilityReplicaServerName
425468
Database = $databaseReplicaState.AvailabilityDatabaseName
426-
PolicyName = "Availability Database Synchronization State"
427-
Category = "Critical"
469+
PolicyName = "Availability Database Data Synchronization State"
470+
Category = "Warning"
428471
Facet = "Availability database"
429472
IsHealthy = $isHealthy
430473
Issue = if ($isHealthy) { $null } else { "Data synchronization state of availability database is not healthy." }
431-
Details = "SynchronizationState is $($databaseReplicaState.SynchronizationState)"
474+
Details = "SynchronizationState is $($databaseReplicaState.SynchronizationState), ReplicaAvailabilityMode is $($databaseReplicaState.ReplicaAvailabilityMode)"
432475
}
433476

434477
<#
@@ -462,9 +505,9 @@ function Test-DbaAgPolicyState {
462505

463506
<#
464507
Availability database is not joined to the availability group
465-
Documentation: https://learn.microsoft.com/en-us/sql/database-engine/availability-groups/windows/availability-database-is-not-joined
508+
Documentation: https://learn.microsoft.com/en-us/sql/database-engine/availability-groups/windows/secondary-database-is-not-joined
466509
Policy Name: Availability Database Join State
467-
Issue: Availability database is not joined to the availability group.
510+
Issue: Secondary database is not joined.
468511
Category: Warning
469512
Facet: Availability database
470513
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0" }
2+
param(
3+
$ModuleName = "dbatools",
4+
$CommandName = "Test-DbaAgPolicyState",
5+
$PSDefaultParameterValues = $TestConfig.Defaults
6+
)
7+
8+
Describe $CommandName -Tag UnitTests {
9+
Context "Parameter validation" {
10+
It "Should have the expected parameters" {
11+
$hasParameters = (Get-Command $CommandName).Parameters.Values.Name | Where-Object { $PSItem -notin ("WhatIf", "Confirm") }
12+
$expectedParameters = $TestConfig.CommonParameters
13+
$expectedParameters += @(
14+
"SqlInstance",
15+
"SqlCredential",
16+
"AvailabilityGroup",
17+
"Secondary",
18+
"SecondarySqlCredential",
19+
"InputObject",
20+
"EnableException"
21+
)
22+
Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty
23+
}
24+
}
25+
26+
InModuleScope dbatools {
27+
Context "Always On predefined policy coverage" {
28+
BeforeAll {
29+
function New-MockAvailabilityReplica {
30+
param(
31+
[string]$Name,
32+
[string]$Role,
33+
[string]$ConnectionState,
34+
[string]$JoinState,
35+
[string]$AvailabilityMode,
36+
[string]$UniqueId
37+
)
38+
39+
$replica = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica
40+
$replica.Name = $Name
41+
$replica | Add-Member -Force -MemberType ScriptProperty -Name Role -Value { $this.psobject.Properties["MockRole"].Value }
42+
$replica | Add-Member -Force -MemberType ScriptProperty -Name ConnectionState -Value { $this.psobject.Properties["MockConnectionState"].Value }
43+
$replica | Add-Member -Force -MemberType ScriptProperty -Name JoinState -Value { $this.psobject.Properties["MockJoinState"].Value }
44+
$replica | Add-Member -Force -MemberType ScriptProperty -Name AvailabilityMode -Value { $this.psobject.Properties["MockAvailabilityMode"].Value }
45+
$replica | Add-Member -Force -MemberType ScriptProperty -Name UniqueId -Value { $this.psobject.Properties["MockUniqueId"].Value }
46+
$replica | Add-Member -Force -NotePropertyName MockRole -NotePropertyValue $Role
47+
$replica | Add-Member -Force -NotePropertyName MockConnectionState -NotePropertyValue $ConnectionState
48+
$replica | Add-Member -Force -NotePropertyName MockJoinState -NotePropertyValue $JoinState
49+
$replica | Add-Member -Force -NotePropertyName MockAvailabilityMode -NotePropertyValue $AvailabilityMode
50+
$replica | Add-Member -Force -NotePropertyName MockUniqueId -NotePropertyValue $UniqueId
51+
$replica
52+
}
53+
54+
function New-MockAvailabilityGroup {
55+
$server = [PSCustomObject]@{
56+
ClusterQuorumState = "NormalQuorum"
57+
}
58+
59+
$primaryReplica = New-MockAvailabilityReplica -Name "PrimaryReplica" -Role "Primary" -ConnectionState "Connected" -JoinState "JoinedStandaloneInstance" -AvailabilityMode "SynchronousCommit" -UniqueId "primary-id"
60+
$syncReplica = New-MockAvailabilityReplica -Name "SyncReplica" -Role "Secondary" -ConnectionState "Connected" -JoinState "JoinedStandaloneInstance" -AvailabilityMode "SynchronousCommit" -UniqueId "sync-id"
61+
62+
$databaseReplicaStates = @(
63+
[PSCustomObject]@{
64+
AvailabilityReplicaId = "primary-id"
65+
AvailabilityReplicaServerName = "PrimaryReplica"
66+
AvailabilityDatabaseName = "AgDb"
67+
SynchronizationState = "Synchronized"
68+
ReplicaAvailabilityMode = "SynchronousCommit"
69+
IsSuspended = $false
70+
IsJoined = $true
71+
},
72+
[PSCustomObject]@{
73+
AvailabilityReplicaId = "sync-id"
74+
AvailabilityReplicaServerName = "SyncReplica"
75+
AvailabilityDatabaseName = "AgDb"
76+
SynchronizationState = "Synchronizing"
77+
ReplicaAvailabilityMode = "SynchronousCommit"
78+
IsSuspended = $false
79+
IsJoined = $true
80+
}
81+
)
82+
83+
$availabilityGroup = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup
84+
$availabilityGroup.Name = "AgOne"
85+
$availabilityGroup | Add-Member -Force -MemberType ScriptMethod -Name Refresh -Value { }
86+
$availabilityGroup | Add-Member -Force -MemberType ScriptProperty -Name ComputerName -Value { "sqlhost" }
87+
$availabilityGroup | Add-Member -Force -MemberType ScriptProperty -Name InstanceName -Value { "MSSQLSERVER" }
88+
$availabilityGroup | Add-Member -Force -MemberType ScriptProperty -Name SqlInstance -Value { "sqlhost" }
89+
$availabilityGroup | Add-Member -Force -MemberType ScriptProperty -Name Parent -Value { $this.psobject.Properties["MockParent"].Value }
90+
$availabilityGroup | Add-Member -Force -MemberType ScriptProperty -Name AvailabilityReplicas -Value { $this.psobject.Properties["MockAvailabilityReplicas"].Value }
91+
$availabilityGroup | Add-Member -Force -MemberType ScriptProperty -Name DatabaseReplicaStates -Value { $this.psobject.Properties["MockDatabaseReplicaStates"].Value }
92+
$availabilityGroup | Add-Member -Force -NotePropertyName MockParent -NotePropertyValue $server
93+
$availabilityGroup | Add-Member -Force -NotePropertyName MockAvailabilityReplicas -NotePropertyValue @($primaryReplica, $syncReplica)
94+
$availabilityGroup | Add-Member -Force -NotePropertyName MockDatabaseReplicaStates -NotePropertyValue $databaseReplicaStates
95+
$availabilityGroup
96+
}
97+
98+
$script:mockAvailabilityGroup = New-MockAvailabilityGroup
99+
100+
Mock Get-DbaAvailabilityGroup { $script:mockAvailabilityGroup }
101+
Mock New-Object {
102+
[PSCustomObject]@{
103+
IsOnline = $true
104+
IsAutoFailover = $true
105+
NumberOfSynchronizedSecondaryReplicas = 1
106+
NumberOfDisconnectedReplicas = 0
107+
NumberOfNotSynchronizingReplicas = 0
108+
NumberOfReplicasWithUnhealthyRole = 0
109+
NumberOfNotSynchronizedReplicas = 0
110+
}
111+
} -ParameterFilter {
112+
$TypeName -eq "Microsoft.SqlServer.Management.Smo.AvailabilityGroupState"
113+
}
114+
}
115+
116+
It "returns every documented policy with the expected categories" {
117+
$results = Test-DbaAgPolicyState -SqlInstance "sqlhost"
118+
119+
$results.Count | Should -Be 21
120+
($results | Where-Object PolicyName -eq "Availability Replica Data Synchronization State").Count | Should -Be 2
121+
($results | Where-Object PolicyName -eq "Availability Replica Role State" | Select-Object -ExpandProperty Category -Unique) | Should -Be "Critical"
122+
($results | Where-Object PolicyName -eq "Availability Replica Connection State" | Select-Object -ExpandProperty Category -Unique) | Should -Be "Critical"
123+
($results | Where-Object PolicyName -eq "Availability Database Data Synchronization State" | Select-Object -ExpandProperty Category -Unique) | Should -Be "Warning"
124+
}
125+
126+
It "marks synchronous replica synchronization lag as unhealthy at replica and database scope" {
127+
$results = Test-DbaAgPolicyState -SqlInstance "sqlhost"
128+
129+
$replicaPolicy = $results | Where-Object { $PSItem.PolicyName -eq "Availability Replica Data Synchronization State" -and $PSItem.Replica -eq "SyncReplica" }
130+
$databasePolicy = $results | Where-Object { $PSItem.PolicyName -eq "Availability Database Data Synchronization State" -and $PSItem.Replica -eq "SyncReplica" -and $PSItem.Database -eq "AgDb" }
131+
132+
$replicaPolicy.IsHealthy | Should -BeFalse
133+
$replicaPolicy.Issue | Should -Be "Data synchronization state of some availability database is not healthy."
134+
$databasePolicy.IsHealthy | Should -BeFalse
135+
$databasePolicy.Issue | Should -Be "Data synchronization state of availability database is not healthy."
136+
}
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)