diff --git a/CHANGELOG.md b/CHANGELOG.md index f8aa52e..a48b852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,16 @@ ## Unreleased +## 1.10.0.0 + +- Changes to xFailOverCluster + - Added xClusterProperty ([issue #169](https://github.com/PowerShell/xFailOverCluster/issues/169)). +- Changes to xClusterNetwork + - Fix the test for the network role never in desired state ([issue #175](https://github.com/PowerShell/xFailOverCluster/issues/175)). + ## 1.9.0.0 -- Changes to xFailoverCluster +- Changes to xFailOverCluster - Update Pester syntax to v4 - Updated year to 2018 in license file and module manifest ([issue #167](https://github.com/PowerShell/xFailOverCluster/issues/167)). - Changes to xClusterNetwork diff --git a/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.psm1 b/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.psm1 index 494be5a..919e41f 100644 --- a/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.psm1 +++ b/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.psm1 @@ -34,11 +34,25 @@ function Get-TargetResource $_.Address -eq $Address -and $_.AddressMask -eq $AddressMask } + <# + On Windows Server 2008 R2 and on Windows Server 2012 R2, the property + Role is of type System.UInt32. On Windows Server 2016, the property Role + is of type Microsoft.FailoverClusters.PowerShell.ClusterNetworkRole. + #> + if ($NetworkResource.Role -is [System.UInt32]) + { + $role = $NetworkResource.Role + } + else + { + $role = $NetworkResource.Role.value__ + } + @{ Address = $Address AddressMask = $AddressMask Name = $NetworkResource.Name - Role = $NetworkResource.Role + Role = $role Metric = $NetworkResource.Metric } } diff --git a/DSCResources/MSFT_xClusterProperty/MSFT_xClusterProperty.psm1 b/DSCResources/MSFT_xClusterProperty/MSFT_xClusterProperty.psm1 new file mode 100644 index 0000000..0b94527 --- /dev/null +++ b/DSCResources/MSFT_xClusterProperty/MSFT_xClusterProperty.psm1 @@ -0,0 +1,391 @@ +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xClusterProperty' + +<# + .SYNOPSIS + Configures cluster properties. + + .PARAMETER Name + Name of the cluster. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + Write-Verbose -Message ($script:localizedData.GettingClusterProperties -f $Name) + + $cluster = Get-Cluster -Name $Name + $returnValue = @{ + Name = $Name + AddEvictDelay = $cluster.AddEvictDelay + ClusterLogLevel = $cluster.ClusterLogLevel + ClusterLogSize = $cluster.ClusterLogSize + CrossSiteDelay = $cluster.CrossSiteDelay + CrossSiteThreshold = $cluster.CrossSiteThreshold + Description = $cluster.Description + CrossSubnetDelay = $cluster.CrossSubnetDelay + CrossSubnetThreshold = $cluster.CrossSubnetThreshold + DatabaseReadWriteMode = $cluster.DatabaseReadWriteMode + DefaultNetworkRole = $cluster.DefaultNetworkRole + DrainOnShutdown = $cluster.DrainOnShutdown + DynamicQuorum = $cluster.DynamicQuorum + NetftIPSecEnabled = $cluster.NetftIPSecEnabled + QuarantineDuration = $cluster.QuarantineDuration + PreferredSite = $cluster.PreferredSite + QuarantineThreshold = $cluster.QuarantineThreshold + SameSubnetDelay = $cluster.SameSubnetDelay + SameSubnetThreshold = $cluster.SameSubnetThreshold + ShutdownTimeoutInMinutes = $cluster.ShutdownTimeoutInMinutes + } + + return $returnValue +} +<# + .SYNOPSIS + Configures cluster properties. + + .PARAMETER AddEvictDelay + Specifies how many seconds after a node is evicted that the failover cluster service will wait before adding a new node. + + .PARAMETER ClusterLogLevel + Controls the level of cluster logging. + + .PARAMETER ClusterLogSize + Controls the maximum size of the cluster log files on each of the nodes. + + .PARAMETER CrossSiteDelay + Controls the time interval, in milliseconds, that the cluster network driver waits between sending Cluster Service heartbeats across sites. + + .PARAMETER CrossSiteThreshold + Controls how many Cluster Service heartbeats can be missed across sites before it determines that Cluster Service has stopped responding. + + .PARAMETER CrossSubnetDelay + Controls the time interval, in milliseconds, that the cluster network driver waits between sending Cluster Service heartbeats across subnets. + + .PARAMETER CrossSubnetThreshold + Controls how many Cluster Service heartbeats can be missed across subnets before it determines that Cluster Service has stopped responding. + + .PARAMETER DatabaseReadWriteMode + Specifies the read/write mode for the cluster database. + + .PARAMETER DefaultNetworkRole + Specifies the role that the cluster automatically assigns to any newly discovered or created network. + + .PARAMETER Description + Stores administrative comments about the cluster. The following table summarizes the attributes of the Description property. + + .PARAMETER DrainOnShutdown + Specifies whether to enable Node Drain for a cluster. + + .PARAMETER DynamicQuorum + Enables the cluster to change the required number of nodes that need to participate in quorum when nodes shut down or crash. + + .PARAMETER Name + Name of the cluster. + + .PARAMETER NetftIPSecEnabled + Specifies whether Internet Protocol Security (IPSec) encryption is enabled for inter-node cluster communication. + + .PARAMETER PreferredSite + Specifies the preferred site for a site-aware cluster. + + .PARAMETER QuarantineDuration + Specifies the quarantine duration for a node, in seconds. + + .PARAMETER QuarantineThreshold + Specifies the quarantine threshold for a node, in minutes. + + .PARAMETER SameSubnetDelay + Controls the delay, in milliseconds, between netft heartbeats. + + .PARAMETER SameSubnetThreshold + Controls how many heartbeats can be missed on the same subnet before the route is declared as unreachable. + + .PARAMETER ShutdownTimeoutInMinutes + Specifies how many minutes after a system shutdown is initiated that the failover cluster service will wait for resources to go offline. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.UInt32] + $AddEvictDelay, + + [Parameter()] + [System.UInt32] + $ClusterLogLevel, + + [Parameter()] + [System.UInt32] + $ClusterLogSize, + + [Parameter()] + [System.UInt32] + $CrossSiteDelay, + + [Parameter()] + [System.UInt32] + $CrossSiteThreshold, + + [Parameter()] + [System.UInt32] + $CrossSubnetDelay, + + [Parameter()] + [System.UInt32] + $CrossSubnetThreshold, + + [Parameter()] + [System.UInt32] + $DatabaseReadWriteMode, + + [Parameter()] + [System.UInt32] + $DefaultNetworkRole, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.UInt32] + $DrainOnShutdown, + + [Parameter()] + [System.UInt32] + $DynamicQuorum, + + [Parameter()] + [System.UInt32] + $NetftIPSecEnabled, + + [Parameter()] + [System.String] + $PreferredSite, + + [Parameter()] + [System.UInt32] + $QuarantineDuration, + + [Parameter()] + [System.UInt32] + $QuarantineThreshold, + + [Parameter()] + [System.UInt32] + $SameSubnetDelay, + + [Parameter()] + [System.UInt32] + $SameSubnetThreshold, + + [Parameter()] + [System.UInt32] + $ShutdownTimeoutInMinutes + ) + + Write-Verbose -Message ($script:localizedData.SettingClusterProperties) + + $boundParameters = $PSBoundParameters + $boundParameters.Remove('Name') | Out-Null + $boundParameters.Remove('Verbose') | Out-Null + + $cluster = Get-Cluster -Name $Name + foreach ($boundParameter in $boundParameters.GetEnumerator()) + { + Write-Verbose -Message ($script:localizedData.SettingClusterProperty -f $($boundParameter.Key), $boundParameter.Value) + $cluster.$($boundParameter.Key) = $boundParameter.Value + } +} +<# + .SYNOPSIS + Configures cluster properties. + + .PARAMETER AddEvictDelay + Specifies how many seconds after a node is evicted that the failover cluster service will wait before adding a new node. + + .PARAMETER ClusterLogLevel + Controls the level of cluster logging. + + .PARAMETER ClusterLogSize + Controls the maximum size of the cluster log files on each of the nodes. + + .PARAMETER CrossSiteDelay + Controls the time interval, in milliseconds, that the cluster network driver waits between sending Cluster Service heartbeats across sites. + + .PARAMETER CrossSiteThreshold + Controls how many Cluster Service heartbeats can be missed across sites before it determines that Cluster Service has stopped responding. + + .PARAMETER CrossSubnetDelay + Controls the time interval, in milliseconds, that the cluster network driver waits between sending Cluster Service heartbeats across subnets. + + .PARAMETER CrossSubnetThreshold + Controls how many Cluster Service heartbeats can be missed across subnets before it determines that Cluster Service has stopped responding. + + .PARAMETER DatabaseReadWriteMode + Specifies the read/write mode for the cluster database. + + .PARAMETER DefaultNetworkRole + Specifies the role that the cluster automatically assigns to any newly discovered or created network. + + .PARAMETER Description + Stores administrative comments about the cluster. The following table summarizes the attributes of the Description property. + + .PARAMETER DrainOnShutdown + Specifies whether to enable Node Drain for a cluster. + + .PARAMETER DynamicQuorum + Enables the cluster to change the required number of nodes that need to participate in quorum when nodes shut down or crash. + + .PARAMETER Name + Name of the cluster. + + .PARAMETER NetftIPSecEnabled + Specifies whether Internet Protocol Security (IPSec) encryption is enabled for inter-node cluster communication. + + .PARAMETER PreferredSite + Specifies the preferred site for a site-aware cluster. + + .PARAMETER QuarantineDuration + Specifies the quarantine duration for a node, in seconds. + + .PARAMETER QuarantineThreshold + Specifies the quarantine threshold for a node, in minutes. + + .PARAMETER SameSubnetDelay + Controls the delay, in milliseconds, between netft heartbeats. + + .PARAMETER SameSubnetThreshold + Controls how many heartbeats can be missed on the same subnet before the route is declared as unreachable. + + .PARAMETER ShutdownTimeoutInMinutes + Specifies how many minutes after a system shutdown is initiated that the failover cluster service will wait for resources to go offline. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.UInt32] + $AddEvictDelay, + + [Parameter()] + [System.UInt32] + $ClusterLogLevel, + + [Parameter()] + [System.UInt32] + $ClusterLogSize, + + [Parameter()] + [System.UInt32] + $CrossSiteDelay, + + [Parameter()] + [System.UInt32] + $CrossSiteThreshold, + + [Parameter()] + [System.UInt32] + $CrossSubnetDelay, + + [Parameter()] + [System.UInt32] + $CrossSubnetThreshold, + + [Parameter()] + [System.UInt32] + $DatabaseReadWriteMode, + + [Parameter()] + [System.UInt32] + $DefaultNetworkRole, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.UInt32] + $DrainOnShutdown, + + [Parameter()] + [System.UInt32] + $DynamicQuorum, + + [Parameter()] + [System.UInt32] + $NetftIPSecEnabled, + + [Parameter()] + [System.String] + $PreferredSite, + + [Parameter()] + [System.UInt32] + $QuarantineDuration, + + [Parameter()] + [System.UInt32] + $QuarantineThreshold, + + [Parameter()] + [System.UInt32] + $SameSubnetDelay, + + [Parameter()] + [System.UInt32] + $SameSubnetThreshold, + + [Parameter()] + [System.UInt32] + $ShutdownTimeoutInMinutes + ) + + Write-Verbose -Message ($script:localizedData.GettingClusterProperties -f $Name) + + $boundParameters = $PSBoundParameters + $boundParameters.Remove('Name') | Out-Null + $boundParameters.Remove('Result') | Out-Null + $boundParameters.Remove('Verbose') | Out-Null + $boundParameters.Remove('Debug') | Out-Null + + $cluster = Get-Cluster -Name $Name + + $output = $true + + foreach ($boundParameter in $boundParameters.GetEnumerator()) + { + if($cluster.$($boundParameter.Key) -ne $boundParameter.Value) + { + Write-Debug -Message ($script:localizedData.IncorrectClusterProperty -f $($boundParameter.Key), $cluster.$($boundParameter.Key), $boundParameter.Value) + Write-Verbose -Message ($script:localizedData.IncorrectClusterProperty -f $($boundParameter.Key), $cluster.$($boundParameter.Key), $boundParameter.Value) + $output = $false + } + } + + return $output +} + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xClusterProperty/MSFT_xClusterProperty.schema.mof b/DSCResources/MSFT_xClusterProperty/MSFT_xClusterProperty.schema.mof new file mode 100644 index 0000000..4b25494 --- /dev/null +++ b/DSCResources/MSFT_xClusterProperty/MSFT_xClusterProperty.schema.mof @@ -0,0 +1,25 @@ + +[ClassVersion("1.0.0.0"), FriendlyName("xClusterProperty")] +class MSFT_xClusterProperty : OMI_BaseResource +{ + [Key, Description("Name of the cluster.")] String Name; + [Write, Description("Specifies how many seconds after a node is evicted that the failover cluster service will wait before adding a new node.")] UInt32 AddEvictDelay; + [Write, Description("Controls the level of cluster logging.")] UInt32 ClusterLogLevel; + [Write, Description("Controls the maximum size of the cluster log files on each of the nodes.")] UInt32 ClusterLogSize; + [Write, Description("Controls the time interval, in milliseconds, that the cluster network driver waits between sending Cluster Service heartbeats across sites.")] UInt32 CrossSiteDelay; + [Write, Description("Controls how many Cluster Service heartbeats can be missed across sites before it determines that Cluster Service has stopped responding.")] UInt32 CrossSiteThreshold; + [Write, Description("Controls the time interval, in milliseconds, that the cluster network driver waits between sending Cluster Service heartbeats across subnets.")] UInt32 CrossSubnetDelay; + [Write, Description("Controls how many Cluster Service heartbeats can be missed across subnets before it determines that Cluster Service has stopped responding.")] UInt32 CrossSubnetThreshold; + [Write, Description("Specifies the read/write mode for the cluster database.")] UInt32 DatabaseReadWriteMode; + [Write, Description("Specifies the role that the cluster automatically assigns to any newly discovered or created network.")] UInt32 DefaultNetworkRole; + [Write, Description("Stores administrative comments about the cluster. The following table summarizes the attributes of the Description property.")] String Description; + [Write, Description("Specifies whether to enable Node Drain for a cluster.")] UInt32 DrainOnShutdown; + [Write, Description("Enables the cluster to change the required number of nodes that need to participate in quorum when nodes shut down or crash.")] UInt32 DynamicQuorum; + [Write, Description("Specifies whether Internet Protocol Security (IPSec) encryption is enabled for inter-node cluster communication.")] UInt32 NetftIPSecEnabled; + [Write, Description("Specifies the preferred site for a site-aware cluster.")] String PreferredSite; + [Write, Description("Specifies the quarantine duration for a node, in seconds.")] UInt32 QuarantineDuration; + [Write, Description("Specifies the quarantine threshold for a node, in minutes.")] UInt32 QuarantineThreshold; + [Write, Description("Controls the delay, in milliseconds, between netft heartbeats.")] UInt32 SameSubnetDelay; + [Write, Description("Controls how many heartbeats can be missed on the same subnet before the route is declared as unreachable.")] UInt32 SameSubnetThreshold; + [Write, Description("Specifies how many minutes after a system shutdown is initiated that the failover cluster service will wait for resources to go offline.")] UInt32 ShutdownTimeoutInMinutes; +}; diff --git a/DSCResources/MSFT_xClusterProperty/en-US/MSFT_xClusterProperty.strings.psd1 b/DSCResources/MSFT_xClusterProperty/en-US/MSFT_xClusterProperty.strings.psd1 new file mode 100644 index 0000000..3dfb97c --- /dev/null +++ b/DSCResources/MSFT_xClusterProperty/en-US/MSFT_xClusterProperty.strings.psd1 @@ -0,0 +1,8 @@ +# Localized resources for xClusterProperty + +ConvertFrom-StringData @' + GettingClusterProperties = Returning current property values from cluster {0}. + SettingClusterProperties = Setting cluster properties. + SettingClusterProperty = Setting cluster property {0} to '{1}'. + IncorrectClusterProperty = Cluster property '{0}' has value '{1}'. Expected value '{2}'. +'@ diff --git a/Examples/Resources/xClusterProperty/1-SetClusterProperties.ps1 b/Examples/Resources/xClusterProperty/1-SetClusterProperties.ps1 new file mode 100644 index 0000000..078d51d --- /dev/null +++ b/Examples/Resources/xClusterProperty/1-SetClusterProperties.ps1 @@ -0,0 +1,24 @@ +<# +.EXAMPLE + This example shows how to set a number of failover cluster properties. + + This example assumes the failover cluster is already present. +#> + +Configuration Example +{ + Import-DscResource -ModuleName xFailOverCluster + + node localhost + { + xClusterProperty SetProperties + { + Name = 'Cluster1' + AddEvictDelay = 60 + ClusterLogSize = 300 + Description = '' + SameSubnetDelay = 1000 + SameSubnetThreshold = 5 + } + } +} diff --git a/README.md b/README.md index bc740dc..01911bc 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ A full list of changes in each version can be found in the [change log](CHANGELO cluster. * [**xClusterPreferredOwner**](#xclusterpreferredowner) Configures preferred owner of a cluster group in a cluster. +* [**xClusterProperty**](#xclusterproperty) Configures cluster properties on a + failover cluster. * [**xClusterQuorum**](#xclusterquorum) Configures quorum in a cluster. * [**xWaitForCluster**](#xwaitforcluster) Ensures that a node waits for a remote cluster is created. @@ -171,6 +173,69 @@ cluster. * [Add preferred owners to a cluster group and cluster resources](/Examples/Resources/xClusterDisk/1-AddPreferredOwner.ps1) * [Remove preferred owners from a cluster group and cluster resources](/Examples/Resources/xClusterDisk/2-RemovePreferredOwner.ps1) +### xClusterProperty + +Configures cluster properties on a failover cluster. + +#### Requirements for xClusterProperty + +* Target machine must be running Windows Server 2008 R2 or later. + +#### Parameters for xClusterProperty + +* **`[String]` Name** _(Key)_: Name of the cluster. +* **`[UInt32]` AddEvictDelay** _(Write)_: Specifies how many seconds after a + node is evicted that the failover cluster service will wait before adding a + new node. +* **`[UInt32]` ClusterLogLevel** _(Write)_: Controls the level of cluster + logging. +* **`[UInt32]` ClusterLogSize** _(Write)_: Controls the maximum size of the + cluster log files on each of the nodes. +* **`[UInt32]` CrossSiteDelay** _(Write)_: Controls the time interval, in + milliseconds, that the cluster network driver waits between sending Cluster + Service heartbeats across sites. +* **`[UInt32]` CrossSiteThreshold** _(Write)_: Controls how many Cluster + Service heartbeats can be missed across sites before it determines that + Cluster Service has stopped responding. +* **`[UInt32]` CrossSubnetDelay** _(Write)_: Controls the time interval, in + milliseconds, that the cluster network driver waits between sending Cluster + Service heartbeats across subnets. +* **`[UInt32]` CrossSubnetThreshold** _(Write)_: Controls how many Cluster + Service heartbeats can be missed across subnets before it determines that + Cluster Service has stopped responding. +* **`[UInt32]` DatabaseReadWriteMode** _(Write)_: Specifies the read/write mode + for the cluster database. +* **`[UInt32]` DefaultNetworkRole** _(Write)_: Specifies the role that the + cluster automatically assigns to any newly discovered or created network. +* **`[String]` Description** _(Write)_: Stores administrative comments about + the cluster. The following table summarizes the attributes of the Description + property. +* **`[UInt32]` DrainOnShutdown** _(Write)_: Specifies whether to enable Node + Drain for a cluster. +* **`[UInt32]` DynamicQuorum** _(Write)_: Enables the cluster to change the + required number of nodes that need to participate in quorum when nodes shut + down or crash. +* **`[UInt32]` NetftIPSecEnabled** _(Write)_: Specifies whether Internet + Protocol Security (IPSec) encryption is enabled for inter-node cluster + communication. +* **`[String]` PreferredSite** _(Write)_: Specifies the preferred site for a + site-aware cluster. +* **`[UInt32]` QuarantineDuration** _(Write)_: Specifies the quarantine + duration for a node, in seconds. +* **`[UInt32]` QuarantineThreshold** _(Write)_: Specifies the quarantine + threshold for a node, in minutes. +* **`[UInt32]` SameSubnetDelay** _(Write)_: Controls the delay, in milliseconds, + between netft heartbeats. +* **`[UInt32]` SameSubnetThreshold** _(Write)_: Controls how many heartbeats can + be missed on the same subnet before the route is declared as unreachable. +* **`[UInt32]` ShutdownTimeoutInMinutes** _(Write)_: Specifies how many minutes + after a system shutdown is initiated that the failover cluster service will + wait for resources to go offline. + +#### Examples for xClusterProperty + +* [Set failover cluster properties](/Examples/Resources/xClusterProperty/1-SetClusterProperties.ps1) + ### xClusterQuorum Configures quorum in a cluster. For information on how to choose the correct diff --git a/Tests/Unit/MSFT_xClusterNetwork.Tests.ps1 b/Tests/Unit/MSFT_xClusterNetwork.Tests.ps1 index f5267b3..d79d066 100644 --- a/Tests/Unit/MSFT_xClusterNetwork.Tests.ps1 +++ b/Tests/Unit/MSFT_xClusterNetwork.Tests.ps1 @@ -39,13 +39,14 @@ try $mockPresentClusterNetworkName = 'Client1' $mockPresentClusterNetworkAddress = '10.0.0.0' $mockPresentClusterNetworkAddressMask = '255.255.255.0' - $mockPresentClusterNetworkRole = '1' + $mockPresentClusterNetworkRole = [System.UInt32] 1 + $mockPresentClusterNetworkRole2 = [PSCustomObject] @{ value__ = 1 } $mockPresentClusterNetworkMetric = '70240' $mockAbsentClusterNetworkName = 'Client2' $mockAbsentClusterNetworkAddress = '10.0.0.0' $mockAbsentClusterNetworkAddressMask = '255.255.255.0' - $mockAbsentClusterNetworkRole = '3' + $mockAbsentClusterNetworkRole = [System.UInt32] 3 $mockAbsentClusterNetworkMetric = '10' $mockGetClusterNetwork = { @@ -61,6 +62,19 @@ try } -PassThru } + $mockGetClusterNetwork2 = { + [PSCustomObject] @{ + Cluster = 'CLUSTER01' + Name = $mockPresentClusterNetworkName + Address = $mockPresentClusterNetworkAddress + AddressMask = $mockPresentClusterNetworkAddressMask + Role = $mockPresentClusterNetworkRole2 + Metric = $mockPresentClusterNetworkMetric + } | Add-Member -MemberType ScriptMethod -Name Update -Value { + $script:mockNumerOfTimesMockedMethodUpdateWasCalled += 1 + } -PassThru + } + $mockTestParameters_PresentNetwork = @{ Address = $mockPresentClusterNetworkAddress AddressMask = $mockPresentClusterNetworkAddressMask @@ -107,6 +121,17 @@ try Assert-MockCalled -CommandName Get-ClusterNetwork -Exactly -Times 1 -Scope It } + + It 'Should not return the the correct values for the cluster network role on WS2016' { + Mock -CommandName 'Get-ClusterNetwork' -MockWith $mockGetClusterNetwork2 + + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.Name | Should -Not -Be $mockAbsentClusterNetworkName + $getTargetResourceResult.Role | Should -Not -Be $mockAbsentClusterNetworkRole + $getTargetResourceResult.Metric | Should -Not -Be $mockAbsentClusterNetworkMetric + + Assert-MockCalled -CommandName Get-ClusterNetwork -Exactly -Times 1 -Scope It + } } Context 'When the system is in the desired state' { @@ -136,6 +161,17 @@ try Assert-MockCalled -CommandName Get-ClusterNetwork -Exactly -Times 1 -Scope It } + + It 'Should return the the correct values for the cluster network role on WS2016' { + Mock -CommandName 'Get-ClusterNetwork' -MockWith $mockGetClusterNetwork2 + + $Result = Get-TargetResource @mockTestParameters + $Result.Name | Should -Be $mockPresentClusterNetworkName + $Result.Role | Should -Be $mockPresentClusterNetworkRole + $Result.Metric | Should -Be $mockPresentClusterNetworkMetric + + Assert-MockCalled -CommandName Get-ClusterNetwork -Exactly -Times 1 -Scope It + } } } Describe 'xClusterNetwork\Test-TargetResource' { diff --git a/Tests/Unit/MSFT_xClusterProperty.Tests.ps1 b/Tests/Unit/MSFT_xClusterProperty.Tests.ps1 new file mode 100644 index 0000000..40ca1b3 --- /dev/null +++ b/Tests/Unit/MSFT_xClusterProperty.Tests.ps1 @@ -0,0 +1,167 @@ +$script:DSCModuleName = 'xFailOverCluster' +$script:DSCResourceName = 'MSFT_xClusterProperty' + +#region Header + +# Unit Test Template Version: 1.2.0 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +try +{ + InModuleScope $script:DSCResourceName { + $script:DSCResourceName = 'MSFT_xClusterProperty' + Describe $script:DSCResourceName { + Context "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Get-Cluster -ParameterFilter {$Name -eq 'Cluster1'} -MockWith { + [PSCustomObject] @{ + SameSubnetDelay = 1000 + SameSubnetThreshold = 5 + CrossSubnetDelay = 1000 + CrossSubnetThreshold = 5 + } + } + + It 'Returns a hashtable' { + Get-TargetResource -Name Cluster1 | Should -BeOfType [System.Collections.Hashtable] + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Returns a hashtable with cluster properties' { + (Get-TargetResource -Name Cluster1).Get_Item('SameSubnetDelay') | Should -Be '1000' + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + } + + Context "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Get-Cluster -ParameterFilter {$Name -eq 'Cluster1'} -MockWith { + [PSCustomObject] @{ + Description = '' + PreferredSite = '' + SameSubnetDelay = 1000 + SameSubnetThreshold = 5 + CrossSubnetDelay = 1000 + CrossSubnetThreshold = 5 + } + } + + It 'Sets a single integer cluster property' { + Set-TargetResource -Name Cluster1 -SameSubnetDelay 2000 | Should -Be $null + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Sets multiple integer cluster properties' { + Set-TargetResource -Name Cluster1 -SameSubnetDelay 2000 -SameSubnetThreshold 5 | Should -Be $null + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Sets a single string cluster property' { + Set-TargetResource -Name Cluster1 -Description 'Exchange DAG' | Should -Be $null + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Sets a single string cluster property to an empty string' { + Set-TargetResource -Name Cluster1 -Description '' | Should -Be $null + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Sets a multiple string cluster properties' { + Set-TargetResource -Name Cluster1 -Description 'Exchange DAG' -PreferredSite 'London' ` + | Should -Be $null + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + } + + Context "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Get-Cluster -ParameterFilter {$Name -eq 'Cluster1'} -MockWith { + [PSCustomObject] @{ + AddEvictDelay = 60 + CrossSubnetDelay = 1000 + CrossSubnetThreshold = 5 + Description = '' + PreferredSite = 'Default-First-Site-Name' + SameSubnetDelay = 1000 + SameSubnetThreshold = 5 + } + } + + It 'Checks a single integer cluster property and returns false if incorrect' { + Test-TargetResource -Name Cluster1 -SameSubnetDelay 2000 | Should -Be $false + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Checks a single integer cluster property and returns true if correct' { + Test-TargetResource -Name Cluster1 -SameSubnetDelay 1000 | Should -Be $true + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Checks multiple integer cluster properties and returns false if incorrect' { + Test-TargetResource -Name Cluster1 -SameSubnetDelay 2000 -SameSubnetThreshold 6 | Should -Be $false + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Checks multiple integer cluster properties and returns true if correct' { + Test-TargetResource -Name Cluster1 -SameSubnetDelay 1000 -SameSubnetThreshold 5 | Should -Be $true + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Checks a single string cluster property and returns false if incorrect' { + Test-TargetResource -Name Cluster1 -Description 'Exchange DAG' | Should -Be $false + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Checks a single string cluster property and returns true if correct' { + Test-TargetResource -Name Cluster1 -PreferredSite 'Default-First-Site-Name' | Should -Be $true + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Checks multiple string cluster properties and returns false if incorrect' { + Test-TargetResource -Name Cluster1 -Description 'Exchange DAG' -PreferredSite 'Default-First-Site-Name' ` + | Should -Be $false + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Checks multiple string cluster properties and returns true if correct' { + Test-TargetResource -Name Cluster1 -Description '' -PreferredSite 'Default-First-Site-Name' ` + | Should -Be $true + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Handles checking multiple string and integer properties and returns true if correct' { + Test-TargetResource -Name Cluster1 -Description '' -PreferredSite 'Default-First-Site-Name' ` + -AddEvictDelay 60 -SameSubnetDelay 1000 | Should -Be $true + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Handles checking multiple string and integer properties and returns false if incorrect' { + Test-TargetResource -Name Cluster1 -Description 'Exchange DAG' -PreferredSite 'Default-First-Site-Name' ` + -AddEvictDelay 60 -SameSubnetDelay 1500 | Should -Be $false + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + + It 'Handles checking properties against empty strings' { + Test-TargetResource -Name Cluster1 -Description '' | Should -Be $true + Assert-MockCalled -CommandName Get-Cluster -Exactly -Times 1 -Scope It + } + } + } + } +} +finally +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} diff --git a/xFailOverCluster.psd1 b/xFailOverCluster.psd1 index e545eb5..3c64819 100644 --- a/xFailOverCluster.psd1 +++ b/xFailOverCluster.psd1 @@ -1,6 +1,6 @@ @{ -ModuleVersion = '1.9.0.0' +moduleVersion = '1.10.0.0' GUID = '026e7fd8-06dd-41bc-b373-59366ab18679' @@ -33,24 +33,10 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '- Changes to xFailoverCluster - - Update Pester syntax to v4 - - Updated year to 2018 in license file and module manifest ([issue 167](https://github.com/PowerShell/xFailOverCluster/issues/167)). + ReleaseNotes = '- Changes to xFailOverCluster + - Added xClusterProperty ([issue 169](https://github.com/PowerShell/xFailOverCluster/issues/169)). - Changes to xClusterNetwork - - Updated readme to describe process for adding and removing additional networks on clusters -- Changes to xCluster - - Allow the cluster to be assigned an IP address from a DHCP ([issue 109](https://github.com/PowerShell/xFailOverCluster/issues/109)). - When the parameter StaticIPAddress is not specified then the cluster will be - configured to use an IP address from a DHCP. - - Get-TargetResource now correctly returns the IP address instead of throwing - and error ([issue 28](https://github.com/PowerShell/xFailOverCluster/issues/28)). - - Added -IgnoreNetwork parameter ([issue 143](https://github.com/PowerShell/xFailOverCluster/issues/143)). -- Changes to xClusterQuorum - - When using NodeAndFileShareMajority on Windows Server 2016 any subsequent run - failed when Test-TargetResource validated the configuration. - - Cleaned up tests which was using over complicated evaluation code. - - Added cloud witness (Azure storage) functionality on Windows 2016 - ([issue 37](https://github.com/PowerShell/xFailOverCluster/issues/37)). + - Fix the test for the network role never in desired state ([issue 175](https://github.com/PowerShell/xFailOverCluster/issues/175)). ' @@ -62,3 +48,4 @@ PrivateData = @{ +