From de5c1689740c1c7c534f937b4ec750ff90a5f91a Mon Sep 17 00:00:00 2001 From: Dan Reist Date: Wed, 25 Oct 2017 10:34:04 -0400 Subject: [PATCH] xSQLServerAlwaysOnAvailabilityGroupReplica: Make Cluster Aware (#892) - 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 the active host of the SQL Server instance (issue #870). --- CHANGELOG.md | 4 +++ ...erverAlwaysOnAvailabilityGroupReplica.psm1 | 34 +++++++++++++++++-- ...lwaysOnAvailabilityGroupReplica.schema.mof | 2 ++ .../1-CreateAvailabilityGroupReplica.ps1 | 14 ++++++-- README.md | 5 +++ ...AlwaysOnAvailabilityGroupReplica.Tests.ps1 | 26 ++++++++++++++ 6 files changed, 80 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c310c0afd..c92c55042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,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 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 + the active host of the SQL Server instance ([issue #870](https://github.com/PowerShell/xSQLServer/issues/870)). - Added the CommonTestHelper.psm1 to store common testing functions. - Added the Import-SQLModuleStub function to ensure the correct version of the module stubs are loaded ([issue #784](https://github.com/PowerShell/xSQLServer/issues/784)). diff --git a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.psm1 b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.psm1 index f0e6fb388..1aba2bbf2 100644 --- a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.psm1 +++ b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.psm1 @@ -44,6 +44,9 @@ function Get-TargetResource # Connect to the instance $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + # Is this node actively hosting the SQL instance? + $isActiveNode = Test-ActiveNode -ServerObject $serverObject + # Get the endpoint properties $endpoint = $serverObject.Endpoints | Where-Object { $_.EndpointType -eq 'DatabaseMirroring' } if ( $endpoint ) @@ -62,6 +65,7 @@ function Get-TargetResource ConnectionModeInSecondaryRole = '' FailoverMode = '' EndpointUrl = '' + IsActiveNode = $isActiveNode ReadOnlyRoutingConnectionUrl = '' ReadOnlyRoutingList = @() SQLServer = $SQLServer @@ -148,6 +152,10 @@ function Get-TargetResource .PARAMETER ReadOnlyRoutingList Specifies an ordered list of replica server names that represent the probe sequence for connection director to use when redirecting read-only connections through this availability replica. This parameter applies if the availability replica is the current primary replica of the availability group. + + .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 { @@ -218,7 +226,11 @@ function Set-TargetResource [Parameter()] [String[]] - $ReadOnlyRoutingList + $ReadOnlyRoutingList, + + [Parameter()] + [Boolean] + $ProcessOnlyOnActiveNode ) Import-SQLPSModule @@ -496,6 +508,9 @@ function Set-TargetResource .PARAMETER ReadOnlyRoutingList Specifies an ordered list of replica server names that represent the probe sequence for connection director to use when redirecting read-only connections through this availability replica. This parameter applies if the availability replica is the current primary replica of the availability group. + + .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 { @@ -567,7 +582,11 @@ function Test-TargetResource [Parameter()] [String[]] - $ReadOnlyRoutingList + $ReadOnlyRoutingList, + + [Parameter()] + [Boolean] + $ProcessOnlyOnActiveNode ) $getTargetResourceParameters = @{ @@ -582,6 +601,17 @@ function Test-TargetResource $getTargetResourceResult = 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 $getTargetResourceResult.IsActiveNode ) + { + # Use localization if the resource has been converted + New-VerboseMessage -Message ( 'The node "{0}" is not actively hosting the instance "{1}". Exiting the test.' -f $env:COMPUTERNAME,$SQLInstanceName ) + return $result + } + switch ($Ensure) { 'Absent' diff --git a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.schema.mof b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.schema.mof index 5918a09b9..d6aba9b0b 100644 --- a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.schema.mof +++ b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.schema.mof @@ -16,7 +16,9 @@ class MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica : OMI_BaseResource [Write, Description("Specifies the failover mode. Default is 'Manual'."), ValueMap{"Automatic","Manual"}, Values{"Automatic","Manual"}] String FailoverMode; [Write, Description("Specifies the fully-qualified domain name (FQDN) and port to use when routing to the replica for read only connections.")] String ReadOnlyRoutingConnectionUrl; [Write, Description("Specifies an ordered list of replica server names that represent the probe sequence for connection director to use when redirecting read-only connections through this availability replica. This parameter applies if the availability replica is the current primary replica of the availability group.")] String ReadOnlyRoutingList[]; + [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("Output the network port the endpoint is listening on. Used by Get-TargetResource.")] Uint16 EndpointPort; [Read, Description("Output the endpoint URL of the Availability Group Replica. Used by Get-TargetResource.")] String EndpointUrl; [Read, Description("Output the NetName property from the SQL Server object. Used by Get-TargetResource.")] String SqlServerNetName; + [Read, Description("Determines if the current node is actively hosting the SQL Server instance.")] Boolean IsActiveNode; }; diff --git a/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/1-CreateAvailabilityGroupReplica.ps1 b/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/1-CreateAvailabilityGroupReplica.ps1 index 75d47d774..05805026e 100644 --- a/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/1-CreateAvailabilityGroupReplica.ps1 +++ b/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/1-CreateAvailabilityGroupReplica.ps1 @@ -1,14 +1,21 @@ <# .EXAMPLE This example shows how to ensure that the Availability Group Replica 'SQL2' exists 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 = @{ AllNodes = @( @{ - NodeName = '*' - SQLInstanceName = 'MSSQLSERVER' - AvailabilityGroupName = 'TestAG' + NodeName = '*' + SQLInstanceName = 'MSSQLSERVER' + AvailabilityGroupName = 'TestAG' + ProcessOnlyOnActiveNode = $true + }, @{ @@ -94,6 +101,7 @@ Configuration Example SQLInstanceName = $Node.SQLInstanceName PrimaryReplicaSQLServer = ( $AllNodes | Where-Object { $_.Role -eq 'PrimaryReplica' } ).NodeName PrimaryReplicaSQLInstanceName = ( $AllNodes | Where-Object { $_.Role -eq 'PrimaryReplica' } ).SQLInstanceName + ProcessOnlyOnActiveNode = $Node.ProcessOnlyOnActiveNode } } } diff --git a/README.md b/README.md index af8c35a98..c647d64e6 100644 --- a/README.md +++ b/README.md @@ -364,6 +364,9 @@ Always On Availability Group Replica. when redirecting read-only connections through this availability replica. This parameter applies if the availability replica is the current primary replica of the availability group. +* **`[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 @@ -373,6 +376,8 @@ Always On Availability Group Replica. Availability Group Replica. Used by Get-TargetResource. * **`[String]` SQLServerNetName** _(Read)_: Output the NetName property from the SQL Server object. +* **`[Boolean]` IsActiveNode** _(Read)_: Determines if the current node is + actively hosting the SQL Server instance. #### Examples diff --git a/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.Tests.ps1 index a739e005a..24a24b8a4 100644 --- a/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.Tests.ps1 @@ -52,6 +52,7 @@ try $mockFailoverMode = 'Manual' $mockReadOnlyRoutingConnectionUrl = "TCP://$($mockSqlServer).domain.com:1433" $mockReadOnlyRoutingList = @($mockSqlServer) + $mockProcessOnlyOnActiveNode = $false #endregion @@ -1288,9 +1289,13 @@ try FailoverMode = $mockFailoverMode ReadOnlyRoutingConnectionUrl = $mockReadOnlyRoutingConnectionUrl ReadOnlyRoutingList = $mockReadOnlyRoutingList + ProcessOnlyOnActiveNode = $mockProcessOnlyOnActiveNode } Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer1 -Verifiable + Mock -CommandName Test-ActiveNode -MockWith { + return -not $mockProcessOnlyOnActiveNode + } -Verifiable } Context 'When the desired state is absent' { @@ -1338,6 +1343,7 @@ try Test-TargetResource @testTargetResourceParameters | Should -Be $false Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly } It 'Should return $true when the Availability Replica is present' { @@ -1345,6 +1351,7 @@ try Test-TargetResource @testTargetResourceParameters | Should -Be $true Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly } foreach ( $propertyToCheck in $propertiesToCheck.GetEnumerator() ) @@ -1355,6 +1362,7 @@ try Test-TargetResource @testTargetResourceParameters | Should -Be $false Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly } } @@ -1365,6 +1373,7 @@ try Test-TargetResource @testTargetResourceParameters | Should -Be $false Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly } It 'Should return $true when the Availability Replica is present and the Endpoint Hostname is not specified' { @@ -1374,6 +1383,7 @@ try Test-TargetResource @testTargetResourceParameters | Should -Be $true Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly } It 'Should return $false when the Availability Replica is present and the Endpoint Hostname is not in the desired state' { @@ -1383,6 +1393,7 @@ try Test-TargetResource @testTargetResourceParameters | Should -Be $false Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly } It 'Should return $false when the Availability Replica is present and the Endpoint Protocol is not in the desired state' { @@ -1392,6 +1403,7 @@ try Test-TargetResource @testTargetResourceParameters | Should -Be $false Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly } It 'Should return $false when the Availability Replica is present and the Endpoint Port is not in the desired state' { @@ -1401,6 +1413,20 @@ try Test-TargetResource @testTargetResourceParameters | Should -Be $false Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly + + $mockAlternateEndpointPort = $false + } + + It 'Should return $true when ProcessOnlyOnActiveNode is "$true" and the current node is not actively hosting the instance' { + $mockProcessOnlyOnActiveNode = $true + + $testTargetResourceParameters.ProcessOnlyOnActiveNode = $mockProcessOnlyOnActiveNode + + Test-TargetResource @testTargetResourceParameters | Should -Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly } } }