From d2786570e16a71908bec9cf5d6dfc208036edaff Mon Sep 17 00:00:00 2001 From: Dimo Lenkov Date: Mon, 29 Aug 2022 13:47:58 +0300 Subject: [PATCH 1/4] Fixes Issue dsccommunity#871 --- CHANGELOG.md | 4 ++ .../DSC_SqlAGListener/DSC_SqlAGListener.psm1 | 37 ++++++++++++++++++- .../DSC_SqlAGListener.schema.mof | 3 +- ...labilityGroupListenerWithSameNameAsVCO.ps1 | 22 +++++++---- ...ityGroupListenerWithDifferentNameAsVCO.ps1 | 22 +++++++---- ...stenerUsingDHCPWithDefaultServerSubnet.ps1 | 22 +++++++---- ...oupListenerUsingDHCPWithSpecificSubnet.ps1 | 24 +++++++----- 7 files changed, 98 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c8612a12..23bbf95fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 no SQL module is found. The script-terminating error is caught and made into a statement-terminating error. - Bump GitHub Action Checkout to v4. +- SqlAGListener + - 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 #871](https://github.com/dsccommunity/SqlServerDsc/issues/871)). ### Remove diff --git a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 index bfc99b739..a6ba6b769 100644 --- a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 +++ b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 @@ -51,6 +51,11 @@ function Get-TargetResource $script:localizedData.GetAvailabilityGroupListener -f $Name, $AvailabilityGroup, $InstanceName ) + $serverObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName + + # Is this node actively hosting the SQL instance? + $isActiveNode = Test-ActiveNode -ServerObject $serverObject + try { $availabilityGroupListener = Get-SQLAlwaysOnAvailabilityGroupListener -Name $Name -AvailabilityGroup $AvailabilityGroup -ServerName $ServerName -InstanceName $InstanceName @@ -107,6 +112,7 @@ function Get-TargetResource IpAddress = [System.String[]] $ipAddress Port = [System.UInt16] $port DHCP = [System.Boolean] $dhcp + IsActiveNode = [System.Boolean] $isActiveNode } } @@ -137,6 +143,10 @@ function Get-TargetResource .PARAMETER DHCP If DHCP should be used for the availability group listener instead of static IP address. + + .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 { @@ -175,7 +185,11 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $DHCP + $DHCP, + + [Parameter()] + [System.Boolean] + $ProcessOnlyOnActiveNode ) $parameters = @{ @@ -413,6 +427,9 @@ function Set-TargetResource .PARAMETER DHCP If DHCP should be used for the availability group listener instead of static IP address. + + .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 { @@ -453,7 +470,11 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $DHCP + $DHCP, + + [Parameter()] + [System.Boolean] + $ProcessOnlyOnActiveNode ) $parameters = @{ @@ -471,6 +492,18 @@ function Test-TargetResource [System.Boolean] $result = $false + <# + 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 ) + $result = $true + return $result + } + if ($availabilityGroupListenerState.Ensure -eq $Ensure) { $result = $true diff --git a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.schema.mof b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.schema.mof index b01663121..2f8bbeb99 100644 --- a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.schema.mof +++ b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.schema.mof @@ -10,5 +10,6 @@ class DSC_SqlAGListener : OMI_BaseResource [Write, Description("The IP address used for the availability group listener, in the format `'192.168.10.45/255.255.252.0'`. If using DHCP, set to the first IP-address of the DHCP subnet, in the format `'192.168.8.1/255.255.252.0'`. Must be valid in the cluster-allowed IP range.")] String IpAddress[]; [Write, Description("The port used for the availability group listener.")] UInt16 Port; [Write, Description("If DHCP should be used for the availability group listener instead of static IP address.")] Boolean DHCP; + [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; }; - diff --git a/source/Examples/Resources/SqlAGListener/1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1 b/source/Examples/Resources/SqlAGListener/1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1 index 3a5a39c6b..76f1f39bd 100644 --- a/source/Examples/Resources/SqlAGListener/1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1 +++ b/source/Examples/Resources/SqlAGListener/1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1 @@ -2,6 +2,11 @@ .DESCRIPTION This example will add an Availability Group listener with the same name as the cluster role VCO. + + 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. #> Configuration Example { @@ -18,15 +23,16 @@ Configuration Example { SqlAGListener 'AvailabilityGroupListenerWithSameNameAsVCO' { - Ensure = 'Present' - ServerName = 'SQLNODE01.company.local' - InstanceName = 'MSSQLSERVER' - AvailabilityGroup = 'AG-01' - Name = 'AG-01' - IpAddress = '192.168.0.73/255.255.255.0' - Port = 5301 + Ensure = 'Present' + ServerName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AG-01' + Name = 'AG-01' + IpAddress = '192.168.0.73/255.255.255.0' + Port = 5301 + ProcessOnlyOnActiveNode = $true - PsDscRunAsCredential = $SqlAdministratorCredential + PsDscRunAsCredential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlAGListener/2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 b/source/Examples/Resources/SqlAGListener/2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 index 6e3f417df..9507453b0 100644 --- a/source/Examples/Resources/SqlAGListener/2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 +++ b/source/Examples/Resources/SqlAGListener/2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 @@ -2,6 +2,11 @@ .DESCRIPTION This example will add an Availability Group listener with a different than the cluster role VCO. + + 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. #> Configuration Example { @@ -18,15 +23,16 @@ Configuration Example { SqlAGListener 'AvailabilityGroupListenerWithDifferentNameAsVCO' { - Ensure = 'Present' - ServerName = 'SQLNODE01.company.local' - InstanceName = 'MSSQLSERVER' - AvailabilityGroup = 'AvailabilityGroup-01' - Name = 'AG-01' - IpAddress = '192.168.0.74/255.255.255.0' - Port = 5302 + Ensure = 'Present' + ServerName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AvailabilityGroup-01' + Name = 'AG-01' + IpAddress = '192.168.0.74/255.255.255.0' + Port = 5302 + ProcessOnlyOnActiveNode = $true - PsDscRunAsCredential = $SqlAdministratorCredential + PsDscRunAsCredential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlAGListener/5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1 b/source/Examples/Resources/SqlAGListener/5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1 index c0c71539b..ce5a86809 100644 --- a/source/Examples/Resources/SqlAGListener/5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1 +++ b/source/Examples/Resources/SqlAGListener/5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1 @@ -2,6 +2,11 @@ .DESCRIPTION This example will add an Availability Group listener using DHCP on the default server subnet. + + 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. #> Configuration Example { @@ -18,19 +23,20 @@ Configuration Example { SqlAGListener 'AvailabilityGroupListenerWithSameNameAsVCO' { - Ensure = 'Present' - ServerName = 'SQLNODE01.company.local' - InstanceName = 'MSSQLSERVER' - AvailabilityGroup = 'AG-01' - Name = 'AG-01' + Ensure = 'Present' + ServerName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AG-01' + Name = 'AG-01' <# If not specifying parameter DHCP, then the default will be DHCP with the default server subnet. #> - DHCP = $true - Port = 5301 + DHCP = $true + Port = 5301 + ProcessOnlyOnActiveNode = $true - PsDscRunAsCredential = $SqlAdministratorCredential + PsDscRunAsCredential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlAGListener/6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1 b/source/Examples/Resources/SqlAGListener/6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1 index decdb0bdb..357d3b99a 100644 --- a/source/Examples/Resources/SqlAGListener/6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1 +++ b/source/Examples/Resources/SqlAGListener/6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1 @@ -1,6 +1,11 @@ <# .DESCRIPTION This example will add an Availability Group listener using DHCP with a specific subnet. + + 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. #> Configuration Example { @@ -17,16 +22,17 @@ Configuration Example { SqlAGListener 'AvailabilityGroupListenerWithSameNameAsVCO' { - Ensure = 'Present' - ServerName = 'SQLNODE01.company.local' - InstanceName = 'MSSQLSERVER' - AvailabilityGroup = 'AG-01' - Name = 'AG-01' - DHCP = $true - IpAddress = '192.168.0.1/255.255.252.0' - Port = 5301 + Ensure = 'Present' + ServerName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AG-01' + Name = 'AG-01' + DHCP = $true + IpAddress = '192.168.0.1/255.255.252.0' + Port = 5301 + ProcessOnlyOnActiveNode = $true - PsDscRunAsCredential = $SqlAdministratorCredential + PsDscRunAsCredential = $SqlAdministratorCredential } } } From a7ce6c32c29e4e8591cd8ca5a5b74e6f96d332af Mon Sep 17 00:00:00 2001 From: Dimo Lenkov Date: Wed, 31 Aug 2022 14:01:12 +0300 Subject: [PATCH 2/4] Fix var name --- source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 index a6ba6b769..be03534e9 100644 --- a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 +++ b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 @@ -496,7 +496,7 @@ function Test-TargetResource 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 ) + if ( $ProcessOnlyOnActiveNode -and -not $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 ) From dce8bc813975f7776c059da32bcf82bcc2597c31 Mon Sep 17 00:00:00 2001 From: Dimo Lenkov Date: Thu, 15 Sep 2022 15:38:25 +0300 Subject: [PATCH 3/4] Move testing of active node in try block --- .../DSC_SqlAGListener/DSC_SqlAGListener.psm1 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 index be03534e9..724570481 100644 --- a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 +++ b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 @@ -24,7 +24,6 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' #> function Get-TargetResource { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Connect-Sql is called when Get-SQLAlwaysOnAvailabilityGroupListener is called')] [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param @@ -51,13 +50,13 @@ function Get-TargetResource $script:localizedData.GetAvailabilityGroupListener -f $Name, $AvailabilityGroup, $InstanceName ) - $serverObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName - - # Is this node actively hosting the SQL instance? - $isActiveNode = Test-ActiveNode -ServerObject $serverObject - try { + $serverObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName + + # Is this node actively hosting the SQL instance? + $isActiveNode = Test-ActiveNode -ServerObject $serverObject + $availabilityGroupListener = Get-SQLAlwaysOnAvailabilityGroupListener -Name $Name -AvailabilityGroup $AvailabilityGroup -ServerName $ServerName -InstanceName $InstanceName if ($null -ne $availabilityGroupListener) From c6f6283108c03ed69b82fc7f59a43c8cf68fc64b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 30 Sep 2023 14:09:58 +0200 Subject: [PATCH 4/4] Fix unit test --- .../DSC_SqlAGListener/DSC_SqlAGListener.psm1 | 33 +- tests/Unit/DSC_SqlAGListener.Tests.ps1 | 538 ++++++++++-------- 2 files changed, 324 insertions(+), 247 deletions(-) diff --git a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 index 724570481..a77b0b72e 100644 --- a/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 +++ b/source/DSCResources/DSC_SqlAGListener/DSC_SqlAGListener.psm1 @@ -43,7 +43,11 @@ function Get-TargetResource [Parameter(Mandatory = $true)] [System.String] - $AvailabilityGroup + $AvailabilityGroup, + + [Parameter()] + [System.Boolean] + $ProcessOnlyOnActiveNode ) Write-Verbose -Message ( @@ -103,15 +107,16 @@ function Get-TargetResource } return @{ - InstanceName = [System.String] $InstanceName - ServerName = [System.String] $ServerName - Name = [System.String] $Name - Ensure = [System.String] $ensure - AvailabilityGroup = [System.String] $AvailabilityGroup - IpAddress = [System.String[]] $ipAddress - Port = [System.UInt16] $port - DHCP = [System.Boolean] $dhcp - IsActiveNode = [System.Boolean] $isActiveNode + InstanceName = [System.String] $InstanceName + ServerName = [System.String] $ServerName + Name = [System.String] $Name + Ensure = [System.String] $ensure + AvailabilityGroup = [System.String] $AvailabilityGroup + IpAddress = [System.String[]] $ipAddress + Port = [System.UInt16] $port + DHCP = [System.Boolean] $dhcp + ProcessOnlyOnActiveNode = [System.Boolean] $ProcessOnlyOnActiveNode + IsActiveNode = [System.Boolean] $isActiveNode } } @@ -432,7 +437,7 @@ function Set-TargetResource #> function Test-TargetResource { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Connect-Sql is called when Get-TargetResource is called')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification = 'The command Connect-Sql is called when Get-TargetResource is called')] [CmdletBinding()] [OutputType([System.Boolean])] param @@ -495,11 +500,13 @@ function Test-TargetResource 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 $IsActiveNode ) + if ($ProcessOnlyOnActiveNode -and -not $availabilityGroupListenerState.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 ) + Write-Verbose -Message ('The node ''{0}'' is not actively hosting the instance ''{1}''. Exiting the test.' -f (Get-ComputerName), $InstanceName) + $result = $true + return $result } diff --git a/tests/Unit/DSC_SqlAGListener.Tests.ps1 b/tests/Unit/DSC_SqlAGListener.Tests.ps1 index 35199f43b..007ad7c78 100644 --- a/tests/Unit/DSC_SqlAGListener.Tests.ps1 +++ b/tests/Unit/DSC_SqlAGListener.Tests.ps1 @@ -45,6 +45,9 @@ BeforeAll { Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') + # Load the correct SQL Module stub $script:stubModuleName = Import-SQLModuleStub -PassThru @@ -72,7 +75,7 @@ AfterAll { Remove-Item -Path 'env:SqlServerDscCI' } -Describe 'SqlAGListener\Get-TargetResource' { +Describe 'SqlAGListener\Get-TargetResource' -Tag 'Get' { BeforeAll { InModuleScope -ScriptBlock { # Default parameters that are used for the It-blocks. @@ -95,6 +98,14 @@ Describe 'SqlAGListener\Get-TargetResource' { Context 'When the system is in the desired state' { Context 'When the listener is absent' { BeforeAll { + Mock -CommandName Connect-SQL -MockWith { + return New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + } + + Mock -CommandName Test-ActiveNode -MockWith { + return $false + } + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener } @@ -152,12 +163,20 @@ Describe 'SqlAGListener\Get-TargetResource' { Context 'When listener is present and not using DHCP' { BeforeAll { + Mock -CommandName Connect-SQL -MockWith { + return New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + } + + Mock -CommandName Test-ActiveNode -MockWith { + return $false + } + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { return @{ - PortNumber = 5031 + PortNumber = 5031 AvailabilityGroupListenerIPAddresses = @{ - IsDHCP = $false - IPAddress = '192.168.0.1' + IsDHCP = $false + IPAddress = '192.168.0.1' SubnetMask = '255.255.255.0' } } @@ -209,6 +228,17 @@ Describe 'SqlAGListener\Get-TargetResource' { } } + It 'Should return that it is not the active node' { + InModuleScope -ScriptBlock { + $mockGetTargetResourceParameters.ProcessOnlyOnActiveNode = $true + + $result = Get-TargetResource @mockGetTargetResourceParameters + + $result.ProcessOnlyOnActiveNode | Should -BeTrue + $result.IsActiveNode | Should -BeFalse + } + } + It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { InModuleScope -ScriptBlock { { Get-TargetResource @mockGetTargetResourceParameters } | Should -Not -Throw @@ -220,12 +250,20 @@ Describe 'SqlAGListener\Get-TargetResource' { Context 'When listener is present and using DHCP' { BeforeAll { + Mock -CommandName Connect-SQL -MockWith { + return New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + } + + Mock -CommandName Test-ActiveNode -MockWith { + return $false + } + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { return @{ - PortNumber = 5031 + PortNumber = 5031 AvailabilityGroupListenerIPAddresses = @{ - IsDHCP = $true - IPAddress = '192.168.0.1' + IsDHCP = $true + IPAddress = '192.168.0.1' SubnetMask = '255.255.255.0' } } @@ -286,12 +324,20 @@ Describe 'SqlAGListener\Get-TargetResource' { Context 'When listener does not have subnet mask' { BeforeAll { + Mock -CommandName Connect-SQL -MockWith { + return New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + } + + Mock -CommandName Test-ActiveNode -MockWith { + return $false + } + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { return @{ - PortNumber = 5031 + PortNumber = 5031 AvailabilityGroupListenerIPAddresses = @{ - IsDHCP = $false - IPAddress = '192.168.0.1' + IsDHCP = $false + IPAddress = '192.168.0.1' SubnetMask = '' } } @@ -416,30 +462,30 @@ Describe 'SqlAGListener\Test-TargetResource' { Context 'When the property is in desired state' -ForEach @( @{ - MockPropertyName = 'IpAddress' + MockPropertyName = 'IpAddress' MockExpectedValue = '192.168.10.45/255.255.252.0' - MockActualValue = '192.168.10.45/255.255.252.0' + MockActualValue = '192.168.10.45/255.255.252.0' } @{ - MockPropertyName = 'Port' + MockPropertyName = 'Port' MockExpectedValue = '5031' - MockActualValue = '5031' + MockActualValue = '5031' } @{ - MockPropertyName = 'DHCP' + MockPropertyName = 'DHCP' MockExpectedValue = $false - MockActualValue = $false + MockActualValue = $false } @{ - MockPropertyName = 'DHCP' + MockPropertyName = 'DHCP' MockExpectedValue = $true - MockActualValue = $true + MockActualValue = $true } - ) { + ) { BeforeAll { Mock -CommandName Get-TargetResource -MockWith { return @{ - Ensure = 'Present' + Ensure = 'Present' $MockPropertyName = $MockActualValue } } @@ -505,25 +551,25 @@ Describe 'SqlAGListener\Test-TargetResource' { Context 'When using static IP address' { Context 'When the property is not in desired state' -ForEach @( @{ - MockPropertyName = 'IpAddress' + MockPropertyName = 'IpAddress' MockExpectedValue = '192.168.10.45/255.255.252.0' - MockActualValue = '192.168.10.45/255.255.255.0' + MockActualValue = '192.168.10.45/255.255.255.0' } @{ - MockPropertyName = 'Port' + MockPropertyName = 'Port' MockExpectedValue = '5031' - MockActualValue = '5030' + MockActualValue = '5030' } @{ - MockPropertyName = 'DHCP' + MockPropertyName = 'DHCP' MockExpectedValue = $false - MockActualValue = $true + MockActualValue = $true } - ) { + ) { BeforeAll { Mock -CommandName Get-TargetResource -MockWith { return @{ - Ensure = 'Present' + Ensure = 'Present' $MockPropertyName = $MockActualValue } } @@ -543,12 +589,12 @@ Describe 'SqlAGListener\Test-TargetResource' { } Context 'When static IP address is desired but current state is using DHCP' { - BeforeAll { + BeforeAll { Mock -CommandName Get-TargetResource -MockWith { return @{ - Ensure = 'Present' + Ensure = 'Present' IpAddress = '192.168.0.1' - DHCP = $true + DHCP = $true } } } @@ -587,15 +633,15 @@ Describe 'SqlAGListener\Test-TargetResource' { Context 'When using DHCP' { Context 'When the property is not in desired state' -ForEach @( @{ - MockPropertyName = 'DHCP' + MockPropertyName = 'DHCP' MockExpectedValue = $true - MockActualValue = $false + MockActualValue = $false } ) { BeforeAll { Mock -CommandName Get-TargetResource -MockWith { return @{ - Ensure = 'Present' + Ensure = 'Present' $MockPropertyName = $MockActualValue } } @@ -616,14 +662,14 @@ Describe 'SqlAGListener\Test-TargetResource' { Context 'When DHCP is desired but current state is using static IP address' { BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - Ensure = 'Present' - IpAddress = '192.168.0.1' - DHCP = $false - } - } - } + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + IpAddress = '192.168.0.1' + DHCP = $false + } + } + } It 'Should return $false' { InModuleScope -ScriptBlock { @@ -636,7 +682,31 @@ Describe 'SqlAGListener\Test-TargetResource' { Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It } - } + } + } + + Context 'When enforcing the state shall happen only when the node is the active node' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Present' + IpAddress = '192.168.0.1' + DHCP = $false + IsActiveNode = $false + } + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $mockTestTargetResourceParameters.DHCP = $true + $mockTestTargetResourceParameters.ProcessOnlyOnActiveNode = $true + + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue + } + + Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } } } } @@ -692,30 +762,30 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - 'AG01' = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - @( - @{ - AGListener = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListenerIPAddresses' -Value { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + return @( + @{ + 'AG01' = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + @( + @{ + AGListener = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListenerIPAddresses' -Value { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection (New-Object -TypeName Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member -MemberType 'NoteProperty' -Name 'IsDHCP' -Value $true -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'IPAddress' -Value '192.168.0.1' -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'SubnetMask' -Value '255.255.255.0' -PassThru + Add-Member -MemberType 'NoteProperty' -Name 'IsDHCP' -Value $true -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IPAddress' -Value '192.168.0.1' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'SubnetMask' -Value '255.255.255.0' -PassThru ) + ) + } -PassThru -Force + } ) } -PassThru -Force - } - ) - } -PassThru -Force - } - ) - } -PassThru -Force + } + ) + } -PassThru -Force } } @@ -731,26 +801,26 @@ Describe 'SqlAGListener\Set-TargetResource' { Context 'When the property is in desired state' -ForEach @( @{ - MockPropertyName = 'IpAddress' + MockPropertyName = 'IpAddress' MockExpectedValue = '192.168.10.45/255.255.252.0' } @{ - MockPropertyName = 'Port' + MockPropertyName = 'Port' MockExpectedValue = '5031' } @{ - MockPropertyName = 'DHCP' + MockPropertyName = 'DHCP' MockExpectedValue = $false } @{ - MockPropertyName = 'DHCP' + MockPropertyName = 'DHCP' MockExpectedValue = $true } ) { BeforeAll { Mock -CommandName Get-TargetResource -MockWith { return @{ - Ensure = 'Present' + Ensure = 'Present' $MockPropertyName = $MockExpectedValue } } @@ -767,30 +837,30 @@ Describe 'SqlAGListener\Set-TargetResource' { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - @( - @{ - AGListener = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListenerIPAddresses' -Value { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - return @( + return @( + @{ + AG01 = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + @( + @{ + AGListener = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListenerIPAddresses' -Value { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + return @( (New-Object -TypeName Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member -MemberType 'NoteProperty' -Name 'IsDHCP' -Value $MockDynamicDhcpValue -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'IPAddress' -Value '192.168.0.1' -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'SubnetMask' -Value '255.255.252.0' -PassThru + Add-Member -MemberType 'NoteProperty' -Name 'IsDHCP' -Value $MockDynamicDhcpValue -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IPAddress' -Value '192.168.0.1' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'SubnetMask' -Value '255.255.252.0' -PassThru ) + ) + } -PassThru -Force + } ) } -PassThru -Force - } - ) - } -PassThru -Force - } - ) - } -PassThru -Force + } + ) + } -PassThru -Force } } @@ -820,12 +890,12 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object + } + ) + } -PassThru -Force } } @@ -854,12 +924,12 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object + } + ) + } -PassThru -Force } } @@ -888,12 +958,12 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object + } + ) + } -PassThru -Force } } @@ -925,19 +995,19 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - return @( - @{ - AGListener = New-Object -TypeName Object - } - ) - } -PassThru -Force - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + return @( + @{ + AGListener = New-Object -TypeName Object + } + ) + } -PassThru -Force + } + ) + } -PassThru -Force } } @@ -966,19 +1036,19 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - return @( - @{ - AGListener = New-Object -TypeName Object - } - ) - } -PassThru -Force - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + return @( + @{ + AGListener = New-Object -TypeName Object + } + ) + } -PassThru -Force + } + ) + } -PassThru -Force } } @@ -1011,19 +1081,19 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - return @( - @{ - AGListener = New-Object -TypeName Object - } - ) - } -PassThru -Force - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + return @( + @{ + AGListener = New-Object -TypeName Object + } + ) + } -PassThru -Force + } + ) + } -PassThru -Force } } @@ -1050,12 +1120,12 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object + } + ) + } -PassThru -Force } } @@ -1087,12 +1157,12 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object + } + ) + } -PassThru -Force } } @@ -1123,12 +1193,12 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object - } - ) - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object + } + ) + } -PassThru -Force } } @@ -1159,23 +1229,23 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - return @{ - AGListener = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptMethod' -Name 'Drop' -Value { - InModuleScope -ScriptBlock { - $script:mockMethodDropWasRunCount += 1 - } - } -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + return @{ + AGListener = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptMethod' -Name 'Drop' -Value { + InModuleScope -ScriptBlock { + $script:mockMethodDropWasRunCount += 1 + } + } -PassThru -Force + } + } -PassThru -Force } - } -PassThru -Force - } - ) - } -PassThru -Force + ) + } -PassThru -Force } } @@ -1205,27 +1275,27 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Get-TargetResource -MockWith { return @{ Ensure = 'Present' - Port = 5031 + Port = 5031 } } Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - @( - @{ - AGListener = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru -Force + return @( + @{ + AG01 = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + @( + @{ + AGListener = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru -Force + } + ) + } -PassThru -Force } ) } -PassThru -Force - } - ) - } -PassThru -Force } } @@ -1247,7 +1317,7 @@ Describe 'SqlAGListener\Set-TargetResource' { BeforeAll { Mock -CommandName Get-TargetResource -MockWith { return @{ - Ensure = 'Present' + Ensure = 'Present' IpAddress = '192.168.0.1/255.255.252.0' } } @@ -1271,7 +1341,7 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Add-SqlAvailabilityGroupListenerStaticIp Mock -CommandName Get-TargetResource -MockWith { return @{ - Ensure = 'Present' + Ensure = 'Present' IpAddress = @( '192.168.0.1/255.255.252.0' ) @@ -1281,29 +1351,29 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - @( - @{ - AGListener = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListenerIPAddresses' -Value { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - return @( + return @( + @{ + AG01 = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + @( + @{ + AGListener = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListenerIPAddresses' -Value { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + return @( (New-Object -TypeName Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member -MemberType 'NoteProperty' -Name 'IPAddress' -Value '192.168.0.1' -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'SubnetMask' -Value '255.255.252.0' -PassThru -Force + Add-Member -MemberType 'NoteProperty' -Name 'IPAddress' -Value '192.168.0.1' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'SubnetMask' -Value '255.255.252.0' -PassThru -Force ) + ) + } -PassThru -Force + } ) } -PassThru -Force - } - ) - } -PassThru -Force - } - ) - } -PassThru -Force + } + ) + } -PassThru -Force } } @@ -1329,7 +1399,7 @@ Describe 'SqlAGListener\Set-TargetResource' { Mock -CommandName Get-TargetResource -MockWith { return @{ Ensure = 'Present' - DHCP = $false + DHCP = $false } } } @@ -1349,35 +1419,35 @@ Describe 'SqlAGListener\Set-TargetResource' { } } -Describe 'SqlAGListener\Get-SQLAlwaysOnAvailabilityGroupListener' { +Describe 'SqlAGListener\Get-SQLAlwaysOnAvailabilityGroupListener' -Skip:($IsLinux -or $IsMacOS) { BeforeAll { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName Object | Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroups' -Value { - return @( - @{ - AG01 = New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { - @( - @{ - AGListener = New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListenerIPAddresses' -Value { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + return @( + @{ + AG01 = New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListeners' -Value { + @( + @{ + AGListener = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'PortNumber' -Value 5031 -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'AvailabilityGroupListenerIPAddresses' -Value { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection (New-Object -TypeName Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member -MemberType 'NoteProperty' -Name 'IsDHCP' -Value $true -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'IPAddress' -Value '192.168.0.1' -PassThru | - Add-Member -MemberType 'NoteProperty' -Name 'SubnetMask' -Value '255.255.255.0' -PassThru + Add-Member -MemberType 'NoteProperty' -Name 'IsDHCP' -Value $true -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IPAddress' -Value '192.168.0.1' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'SubnetMask' -Value '255.255.255.0' -PassThru ) + ) + } -PassThru -Force + } ) } -PassThru -Force - } - ) - } -PassThru -Force - } - ) - } -PassThru -Force + } + ) + } -PassThru -Force } }