Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xCluster: add Nodes parameter (enables cluster creation with several nodes) #177

Closed
wants to merge 15 commits into from
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- 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)).
- Changes to xCluster
- Added a node parameter to be able to create a cluster with multiple nodes ([issue #148 (https://github.com/PowerShell/xFailOverCluster/issues/148), issue #157 (https://github.com/PowerShell/xFailOverCluster/issues/157))

## 1.9.0.0

Expand Down
92 changes: 71 additions & 21 deletions DSCResources/MSFT_xCluster/MSFT_xCluster.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ function Get-TargetResource
.PARAMETER Name
Name of the failover cluster.

.PARAMETER Node
Specifies a comma-separated list of cluster node names to add
to the local physical computer when creating a cluster. If this
parameter is not specified, then a one node cluster is created
on the local physical computer.

.PARAMETER StaticIPAddress
Static IP Address of the failover cluster.

Expand Down Expand Up @@ -131,6 +137,10 @@ function Set-TargetResource
[System.String]
$StaticIPAddress,

[Parameter()]
[System.String[]]
$Node = @($env:COMPUTERNAME),

[Parameter()]
[System.String[]]
$IgnoreNetwork,
Expand Down Expand Up @@ -169,13 +179,16 @@ function Set-TargetResource
{
($oldToken, $context, $newToken) = Set-ImpersonateAs -Credential $DomainAdministratorCredential

# Remove duplicates
$nodes = $Node | Sort-Object -unique

if ($bCreate)
{
Write-Verbose -Message ($script:localizedData.ClusterAbsent -f $Name)

$newClusterParameters = @{
Name = $Name
Node = $env:COMPUTERNAME
Node = $nodes
NoStorage = $true
Force = $true
ErrorAction = 'Stop'
Expand Down Expand Up @@ -207,27 +220,38 @@ function Set-TargetResource
}
else
{
$targetNodeName = $env:COMPUTERNAME

Write-Verbose -Message ($script:localizedData.AddNodeToCluster -f $targetNodeName, $Name)

$list = Get-ClusterNode -Cluster $Name
foreach ($node in $list)
$existingNodes = Get-ClusterNode -Cluster $Name
foreach ($node in $existingNodes)
{
if ($node.Name -eq $targetNodeName)
if ($nodes -notcontains $node)
{
if ($node.State -eq 'Down')
Remove-ClusterNode -Name $targetNodeName -Cluster $Name -Force
}
foreach ($targetNodeName in $nodes)
{
if ($node.Name -eq $targetNodeName)
{
Write-Verbose -Message ($script:localizedData.RemoveOfflineNodeFromCluster -f $targetNodeName, $Name)
if ($node.State -eq 'Down')
{
Write-Verbose -Message ($script:localizedData.RemoveOfflineNodeFromCluster -f $targetNodeName, $Name)

Remove-ClusterNode -Name $targetNodeName -Cluster $Name -Force
}

Remove-ClusterNode -Name $targetNodeName -Cluster $Name -Force
break
}
}
}

Add-ClusterNode -Name $targetNodeName -Cluster $Name -NoStorage
foreach ($targetNodeName in $nodes)
{
Add-ClusterNode -Name $targetNodeName -Cluster $Name -NoStorage

Write-Verbose -Message ($script:localizedData.AddNodeToClusterSuccessful -f $targetNodeName, $Name)
}

Write-Verbose -Message ($script:localizedData.AddNodeToClusterSuccessful -f $targetNodeName, $Name)
}
}
finally
Expand All @@ -249,6 +273,12 @@ function Set-TargetResource
.PARAMETER Name
Name of the failover cluster.

.PARAMETER Node
Specifies a comma-separated list of cluster node names, to add
to the local physical computer when creating a cluster. If this
parameter is not specified, then a one node cluster is created
on the local physical computer.

.PARAMETER StaticIPAddress
Static IP Address of the failover cluster.
Not used in Test-TargetResource.
Expand Down Expand Up @@ -289,6 +319,10 @@ function Test-TargetResource
[System.String]
$Name,

[Parameter()]
[System.String[]]
$Node = @($env:COMPUTERNAME),

[Parameter()]
[System.String]
$StaticIPAddress,
Expand Down Expand Up @@ -323,25 +357,41 @@ function Test-TargetResource

if ($cluster)
{
$targetNodeName = $env:COMPUTERNAME
$returnValue = $true

Write-Verbose -Message ($script:localizedData.CheckClusterNodeIsUp -f $targetNodeName, $Name)

$allNodes = Get-ClusterNode -Cluster $Name
$existingNodes = Get-ClusterNode -Cluster $Name

# Remove duplicates
$nodes = $Node | Sort-Object -unique

if ($existingNodes.Count -ne $nodes.Count)
{
return $false
}

foreach ($node in $allNodes)
foreach ($targetNodeName in $nodes)
{
if ($node.Name -eq $targetNodeName)
$found = $false
foreach ($node in $existingNodes)
{
if ($node.State -eq 'Up')
{
$returnValue = $true
}
else
if ($node.Name -eq $targetNodeName)
{
Write-Verbose -Message ($script:localizedData.ClusterNodeIsDown -f $targetNodeName, $Name)
$found = $true
if ($node.State -eq 'Down')
{
Write-Verbose -Message ($script:localizedData.ClusterNodeIsDown -f $targetNodeName, $Name)
$returnValue = $false
}

break
}
}

if ($false -eq $found)
{
$returnValue = $false
break
}
}
Expand Down
1 change: 1 addition & 0 deletions DSCResources/MSFT_xCluster/MSFT_xCluster.schema.mof
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
class MSFT_xCluster : OMI_BaseResource
{
[Key, Description("Name of the Cluster")] String Name;
[Write, Description("Additional Nodes of the Cluster. Default value is current node")] String Node[];
[Write, Description("StaticIPAddress of the Cluster")] String StaticIPAddress;
[Required, EmbeddedInstance("MSFT_Credential"), Description("Credential to create the cluster")] String DomainAdministratorCredential;
[Write, Description("One or more networks to ignore when creating the cluster. Only networks using Static IP can be ignored, networks that are assigned an IP address through DHCP cannot be ignored, and are added for cluster communication. To remove networks assigned an IP address through DHCP use the resource xClusterNetwork to change the role of the network. This parameter is only used during the creation of the cluster and is not monitored after.")] String IgnoreNetwork[];
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ the target node ($env:COMPUTERNAME) to the cluster.
* **`[String]` StaticIPAddress** _(Write)_: The static IP address of the failover
cluster. If this is not specified then the IP address will be assigned from a
DHCP.
* **`[String[]]` Node** _(Write)_: Specifies a comma-separated list of cluster
node names to add to the local physical computer when creating a cluster. If
this parameter is not specified, then a one node cluster is created on the
local physical computer.
* **`[String[]]` IgnoreNetwork** _(Write)_: One or more networks to ignore when
creating the cluster. Only networks using Static IP can be ignored, networks
that are assigned an IP address through DHCP cannot be ignored, and are added
Expand Down
35 changes: 33 additions & 2 deletions Tests/Unit/MSFT_xCluster.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,34 @@ try
}
}

Context 'When Node is passed' {
It 'Should call New-Cluster cmdlet with Node as an array' {
$withNodesParameter = $mockDefaultParameters + @{
Node = ('foo','bar')
}
{ Set-TargetResource @withNodesParameter } | Should Not Throw

Assert-MockCalled -CommandName New-Cluster -Exactly -Times 1 -Scope It -ParameterFilter {
$Node.Contains('foo') -eq $true `
-and $Node.Contains('bar') -eq $true
}
Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It
Assert-MockCalled -CommandName Add-ClusterNode -Exactly -Times 0 -Scope It
}
}

Context 'When Node is not passed' {
It 'Should call New-Cluster cmdlet with Node as an array' {
{ Set-TargetResource @mockDefaultParameters } | Should Not Throw

Assert-MockCalled -CommandName New-Cluster -Exactly -Times 1 -Scope It -ParameterFilter {
$Node.Contains($env:COMPUTERNAME) -eq $true
}
Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It
Assert-MockCalled -CommandName Add-ClusterNode -Exactly -Times 0 -Scope It
}
}

Context 'When IgnoreNetwork is passed as an array' {
It 'Should call New-Cluster cmdlet with IgnoreNetwork parameter' {
$withIgnoreNetworkParameter = $mockDefaultParameters + @{ IgnoreNetwork = ('10.0.2.0/24', '192.168.4.0/24') }
Expand Down Expand Up @@ -385,14 +413,16 @@ try
}

Context 'When the cluster exist and the node is down' {
BeforeEach {
Mock -CommandName Get-ClusterNode -MockWith $mockGetClusterNode
$mockGetClusterNode_ParameterFilter = {
$Name -eq $mockDefaultParameters.Name
}

$mockDynamicClusterNodeState = 'Down'

It 'Should call both Remove-ClusterNode and Add-ClusterNode cmdlet' {
Mock -CommandName Get-ClusterNode -MockWith $mockGetClusterNode -ParameterFilter $mockGetClusterNode_ParameterFilter
Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter
Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter

{ Set-TargetResource @mockDefaultParameters } | Should -Not -Throw

Expand Down Expand Up @@ -540,6 +570,7 @@ try
$mockDynamicClusterNodeState = 'Up'

Context 'When the node already exist' {

It 'Should return $true' {
$testTargetResourceResult = Test-TargetResource @mockDefaultParameters
$testTargetResourceResult | Should -Be $true
Expand Down