Skip to content

Commit

Permalink
xSQLServerAlwaysOnAvailabilityGroupDatabaseMembership: Make Cluster A…
Browse files Browse the repository at this point in the history
…ware (#895)

- Changes to xSQLServerAlwaysOnAvailabilityGroupDatabaseMembership
  - Made the resource cluster aware. When ProcessOnlyOnActiveNode is specified,
    the resource will only determine if a change is needed if the target node
    is the active host of the SQL Server instance (issue #869).
  • Loading branch information
randomnote1 authored and johlju committed Oct 30, 2017
1 parent 6e80bab commit 88bcd27
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
- Made the resource cluster aware. When ProcessOnlyOnActiveNode is specified,
the resource will only determine if a change is needed if the target node
is the active host of the SQL Server instance ([issue #868](https://github.com/PowerShell/xSQLServer/issues/868)).
- Changes to xSQLServerAlwaysOnAvailabilityGroupDatabaseMembership
- Made the resource cluster aware. When ProcessOnlyOnActiveNode is specified,
the resource will only determine if a change is needed if the target node
is the active host of the SQL Server instance ([issue #869](https://github.com/PowerShell/xSQLServer/issues/869)).
- Changes to xSQLServerAlwaysOnAvailabilityGroupReplica
- Made the resource cluster aware. When ProcessOnlyOnActiveNode is specified,
the resource will only determine if a change is needed if the target node is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,15 @@ function Get-TargetResource
Ensure = ''
Force = $false
MatchDatabaseOwner = $false
IsActiveNode = $false
}

# Connect to the instance
$serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName

# Is this node actively hosting the SQL instance?
$currentConfiguration.IsActiveNode = Test-ActiveNode -ServerObject $serverObject

# Get the Availability group object
$availabilityGroup = $serverObject.AvailabilityGroups[$AvailabilityGroupName]

Expand Down Expand Up @@ -139,6 +143,10 @@ function Get-TargetResource
If set to $false, the owner of the database will be the PSDscRunAsCredential.
The default is '$true'.
.PARAMETER ProcessOnlyOnActiveNode
Specifies that the resource will only determine if a change is needed if the target node is the active host of the SQL Server Instance.
Not used in Set-TargetResource.
#>
function Set-TargetResource
{
Expand Down Expand Up @@ -176,7 +184,11 @@ function Set-TargetResource

[Parameter()]
[Boolean]
$MatchDatabaseOwner
$MatchDatabaseOwner,

[Parameter()]
[Boolean]
$ProcessOnlyOnActiveNode
)

Import-SQLPSModule
Expand Down Expand Up @@ -597,6 +609,9 @@ function Set-TargetResource
If set to $false, the owner of the database will be the PSDscRunAsCredential.
The default is '$true'.
.PARAMETER ProcessOnlyOnActiveNode
Specifies that the resource will only determine if a change is needed if the target node is the active host of the SQL Server Instance.
#>
function Test-TargetResource
{
Expand Down Expand Up @@ -635,7 +650,11 @@ function Test-TargetResource

[Parameter()]
[Boolean]
$MatchDatabaseOwner
$MatchDatabaseOwner,

[Parameter()]
[Boolean]
$ProcessOnlyOnActiveNode
)

$configurationInDesiredState = $true
Expand All @@ -649,6 +668,16 @@ function Test-TargetResource
}
$currentConfiguration = Get-TargetResource @getTargetResourceParameters

<#
If this is supposed to process only the active node, and this is not the
active node, don't bother evaluating the test.
#>
if ( $ProcessOnlyOnActiveNode -and -not $currentConfiguration.IsActiveNode )
{
Write-Verbose -Message ( $script:localizedData.NotActiveNode -f $env:COMPUTERNAME,$SQLInstanceName )
return $configurationInDesiredState
}

# Connect to the defined instance
$serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ class MSFT_xSQLServerAlwaysOnAvailabilityGroupDatabaseMembership : OMI_BaseResou
[Write, Description("Specifies the membership of the database(s) in the availability group. The options are: Present: The defined database(s) are added to the availability group. All other databases that may be a member of the availability group are ignored. Absent: The defined database(s) are removed from the availability group. All other databases that may be a member of the availability group are ignored. The default is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
[Write, Description("When used with 'Ensure = 'Present'' it ensures the specified database(s) are the only databases that are a member of the specified Availability Group. This parameter is ignored when 'Ensure' is 'Absent'.")] Boolean Force;
[Write, Description("If set to $true, this ensures the database owner of the database on the primary replica is the owner of the database on all secondary replicas. This requires the database owner is available as a login on all replicas and that the PSDscRunAsAccount has impersonate permissions. If set to $false, the owner of the database will be the PSDscRunAsAccount. The default is '$true'")] Boolean MatchDatabaseOwner;
[Write, Description("Specifies that the resource will only determine if a change is needed if the target node is the active host of the SQL Server Instance.")] Boolean ProcessOnlyOnActiveNode;
[Read, Description("Determines if the current node is actively hosting the SQL Server instance.")] Boolean IsActiveNode;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ConvertFrom-StringData @'
DatabaseShouldNotBeMember = The following databases should not be a member of the availability group '{0}': {1}.
DatabasesNotFound = The following databases were not found in the instance: {0}.
ImpersonatePermissionsMissing = The login '{0}' is missing impersonate permissions in the instances '{1}'.
NotActiveNode = The node '{0}' is not actively hosting the instance '{1}'. Exiting the test.
ParameterNotOfType = The parameter '{0}' is not of the type '{1}'.
ParameterNullOrEmpty = The parameter '{0}' is NULL or empty.
RemovingDatabasesToAvailabilityGroup = Removing the following databases from the '{0}' availability group: {1}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ PARAMETER MatchDatabaseOwner
If set to $false, the owner of the database will be the PSDscRunAsCredential.

The default is '$true'.

.PARAMETER ProcessOnlyOnActiveNode
Specifies that the resource will only determine if a change is needed if the target node is the active host of the SQL Server Instance.
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<#
.EXAMPLE
This example shows how to ensure that the databases 'DB*' and 'AdventureWorks' are members in the Availability Group 'TestAG'.
In the event this is applied to a Failover Cluster Instance (FCI), the
ProcessOnlyOnActiveNode property will tell the Test-TargetResource function
to evaluate if any changes are needed if the node is actively hosting the
SQL Server Instance.
#>

$ConfigurationData = @{
Expand Down Expand Up @@ -101,13 +106,14 @@ Configuration Example
{
xSQLServerAlwaysOnAvailabilityGroupDatabaseMembership 'TestAGDatabaseMemberships'
{
AvailabilityGroupName = $Node.AvailabilityGroupName
BackupPath = '\\SQL1\AgInitialize'
DatabaseName = 'DB*', 'AdventureWorks'
SQLInstanceName = $Node.SQLInstanceName
SQLServer = $Node.NodeName
Ensure = 'Present'
PsDscRunAsCredential = $SysAdminAccount
AvailabilityGroupName = $Node.AvailabilityGroupName
BackupPath = '\\SQL1\AgInitialize'
DatabaseName = 'DB*', 'AdventureWorks'
SQLInstanceName = $Node.SQLInstanceName
SQLServer = $Node.NodeName
Ensure = 'Present'
ProcessOnlyOnActiveNode = $true
PsDscRunAsCredential = $SysAdminAccount
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@ group.
login on all replicas and that the PSDscRunAsAccount has impersonate permissions.
If set to $false, the owner of the database will be the PSDscRunAsAccount.
The default is '$true'.
* **`[Boolean]` ProcessOnlyOnActiveNode** _(Write)_: Specifies that the resource
will only determine if a change is needed if the target node is the active
host of the SQL Server Instance.

#### Read-Only Properties from Get-TargetResource

* **`[Boolean]` IsActiveNode** _(Read)_: Determines if the current node is
actively hosting the SQL Server instance.

### xSQLServerAlwaysOnAvailabilityGroupReplica

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ try

$mockBackupPath = 'X:\Backup'

$mockProcessOnlyOnActiveNode = $false

#endregion Parameter Mocks

#region mock names
Expand Down Expand Up @@ -1140,6 +1142,9 @@ WITH NORECOVERY'
Mock -CommandName Connect-SQL -MockWith { return $mockServerObject } -Verifiable
Mock -CommandName Get-PrimaryReplicaServerObject -MockWith { return $mockServerObject } -Verifiable -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' }
Mock -CommandName Get-PrimaryReplicaServerObject -MockWith { return $mockServer2Object } -Verifiable -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' }
Mock -CommandName Test-ActiveNode -MockWith {
return -not $mockProcessOnlyOnActiveNode
} -Verifiable
}

BeforeEach {
Expand All @@ -1152,6 +1157,7 @@ WITH NORECOVERY'
Ensure = 'Present'
Force = $false
MatchDatabaseOwner = $false
ProcessOnlyOnActiveNode = $false
}
}

Expand All @@ -1164,6 +1170,7 @@ WITH NORECOVERY'
Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' }
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' }
Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly
}

It 'Should return $false when the specified availability group is not found' {
Expand All @@ -1174,6 +1181,7 @@ WITH NORECOVERY'
Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' }
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' }
Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly
}

It 'Should return $false when no matching databases are found' {
Expand All @@ -1184,6 +1192,7 @@ WITH NORECOVERY'
Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' }
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' }
Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly
}

It 'Should return $false when databases are found to add to the availability group' {
Expand All @@ -1192,6 +1201,7 @@ WITH NORECOVERY'
Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' }
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' }
Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly
}

It 'Should return $true when the configuration is in the desired state and the primary replica is on another server' {
Expand All @@ -1203,6 +1213,21 @@ WITH NORECOVERY'
Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' }
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' }
Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly
}

It 'Should return $true when ProcessOnlyOnActiveNode is "$true" and the current node is not actively hosting the instance' {
$mockProcessOnlyOnActiveNode = $true

$mockTestTargetResourceParameters.DatabaseName = $mockAvailabilityDatabaseNames.Clone()
$mockTestTargetResourceParameters.ProcessOnlyOnActiveNode = $mockProcessOnlyOnActiveNode

Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true

Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' }
Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' }
Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly
}
}

Expand Down

0 comments on commit 88bcd27

Please sign in to comment.