Tests/Integration/MSFT_xExchAutoMountPoint.Integration.Tests.ps1

<#
    .SYNOPSIS
        Automated unit integration for MSFT_xExchAutoMountPoint DSC Resource.
#>


#region HEADER
[System.String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
[System.String] $script:DSCModuleName = 'xExchange'
[System.String] $script:DSCResourceFriendlyName = 'xExchAutoMountPoint'
[System.String] $script:DSCResourceName = "MSFT_$($script:DSCResourceFriendlyName)"

Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Tests' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'xExchangeTestHelper.psm1'))) -Force
Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResources' -ChildPath (Join-Path -Path "$($script:DSCResourceName)" -ChildPath "$($script:DSCResourceName).psm1")))

#endregion HEADER

# Performs tests against all disks listed in DiskToDBMap, as well as
# all Spare Volumes
function Test-MountPointSetup
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $AutoDagDatabasesRootFolderPath,

        [Parameter()]
        [System.String]
        $AutoDagVolumesRootFolderPath,

        [Parameter()]
        [System.String[]]
        $DiskToDBMap,

        [Parameter(Mandatory = $true)]
        [System.UInt32]
        $SpareVolumeCount,

        [Parameter()]
        [System.Boolean]
        $EnsureExchangeVolumeMountPointIsLast,

        [Parameter()]
        [System.Boolean]
        $CreateSubfolders,

        [Parameter()]
        [System.String]
        $FileSystem,

        [Parameter()]
        [System.String]
        $PartitioningScheme,

        [Parameter()]
        [System.String]
        $UnitSize,

        [Parameter()]
        [System.String]
        $VolumePrefix
    )

    # Keep track of the disk numbers of database disks that we find
    $exDiskNumbers = @()

    # Test each disk in the disk map
    foreach ($diskMap in $DiskToDBMap)
    {
        # Keep track of whether we found an EXVOL mount point for this disk
        $foundExVolMountPoint = $false

        # Keep track of the number of EXVOL mount points on this disk
        $exVolMPCount = 0

        # Keep track of whether the EXVOL mount point is last in the list of mount points for the disk
        $exVolLastInList = $false

        # Create a generic EXVOL path name until we find the real one
        $exVolMPName = "$AutoDagVolumesRootFolderPath\$VolumePrefix##"

        # Test individual databases for this disk
        foreach ($dbName in $diskMap.Split(','))
        {
            $dbPath = Join-Path $AutoDagDatabasesRootFolderPath $dbName

            $dbPartition = Get-Partition | Where-Object {$_.AccessPaths.Count -gt 0 -and $_.AccessPaths.Contains("$dbPath\")}

            if ($null -ne $dbPartition)
            {
                # If we haven't looked at this disk yet, make sure it is partitioned and formatted correctly
                if (!$exDiskNumbers.Contains($dbPartition.DiskNumber))
                {
                    $exDiskNumbers += $dbPartition.DiskNumber

                    Test-DiskAndPartitionSetup -Partition $dbPartition -FileSystem $FileSystem -PartitioningScheme $PartitioningScheme -UnitSize $UnitSize
                }
            }

            It "Mount Point Exists: $dbPath" {
                $null -ne $dbPartition | Should Be $true
            }

            # If requested, make sure subfolders were created under the mount points
            if ($null -ne $dbPartition -and $CreateSubfolders)
            {
                $dbdbPath = Join-Path $dbPath "$dbName.db"
                $dblogPath = Join-Path $dbPath "$dbName.log"

                It "$dbdbPath Exists" {
                    Test-Path -Path $dbdbPath | Should Be $true
                }

                It "$dblogPath Exists" {
                    Test-Path -Path $dblogPath | Should Be $true
                }
            }

            # If we haven't found it yet, check if the ExchangeVolumes mount point was added, and store
            # what we know about it
            if (!$foundExVolMountPoint)
            {
                [String[]] $exVolAccessPaths = $dbPartition.AccessPaths | Where-Object {$_ -like "*$AutoDagVolumesRootFolderPath\$VolumePrefix*"}

                if ($exVolAccessPaths.Count -gt 0)
                {
                    $foundExVolMountPoint = $true

                    $exVolMPCount = $exVolAccessPaths.Count
                    $exVolMPName = $exVolAccessPaths[0]
                    $exVolLastInList = $dbPartition.AccessPaths[$dbPartition.AccessPaths.Count - 2] -like "*$VolumePrefix*"
                }
            }
        }

        It "Mount Point Exists: $exVolMPName" {
            $foundExVolMountPoint | Should Be $true
        }

        It "$VolumePrefix Mount Point Count == 1" {
            $exVolMPCount -eq 1 | Should Be $true
        }

        if ($EnsureExchangeVolumeMountPointIsLast)
        {
            It "$VolumePrefix Mount Point Is Last In List" {
                $exVolLastInList | Should Be $true
            }
        }
    }

    # Now try to find and test any requested spare volumes
    [Object[]] $otherExVolPartitions = Get-Partition | Where-Object {$_.AccessPaths -like "*$AutoDagVolumesRootFolderPath\$VolumePrefix*" -and $_.DiskNumber -NotIn $exDiskNumbers}
    [Object[]] $otherExVolDisks = $otherExVolPartitions.DiskNumber

    It "Extra $VolumePrefix Partition Count Same as Disk Count" {
        $otherExVolPartitions.Count -eq $otherExVolDisks.Count | Should Be $true
    }

    It "$SpareVolumeCount Spare Volumes Configured" {
        $SpareVolumeCount -eq $otherExVolDisks.Count | Should Be $true
    }

    foreach ($partition in $otherExVolPartitions)
    {
        Test-DiskAndPartitionSetup -Partition $partition -FileSystem $FileSystem -PartitioningScheme $PartitioningScheme -UnitSize $UnitSize
    }
}

# Performs disk, volume, and partition level tests against
# the specified partition
function Test-DiskAndPartitionSetup
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.Object]
        $Partition,

        [Parameter()]
        [System.String]
        $FileSystem,

        [Parameter()]
        [System.String]
        $PartitioningScheme,

        [Parameter()]
        [System.String]
        $UnitSize
    )

    # Get the disk properties and test the partitioning scheme
    $exDisk = Get-Disk -Number $Partition.DiskNumber

    It "PartitioningScheme for Disk $($Partition.DiskNumber) is $PartitioningScheme" {
        $exDisk.PartitionStyle -like $PartitioningScheme | Should Be $true
    }

    # Get the properties exposed via PowerShell and test the file system
    $exDiskVol = Get-Volume -Partition $Partition

    It "File System for Volume is $FileSystem" {
        $exDiskVol.FileSystem -like $FileSystem | Should Be $true
    }

    # Use WMI/CIM to access the BlockSize, and test that the UnitSize was set correctly
    [Object[]] $exDiskVolExtendedProps = Get-CimInstance -Query "SELECT Blocksize FROM Win32_Volume WHERE Label='$($exDiskVol.FileSystemLabel)'"

    It "Only 1 disk exists with label '$($exDiskVol.FileSystemLabel)'" {
        $exDiskVolExtendedProps.Count -eq 1 | Should Be $true
    }

    if ($exDiskVolExtendedProps.Count -eq 1)
    {
        if ($UnitSize.ToLower().EndsWith('k'))
        {
            [UInt64] $UnitSizeBytes = [UInt64]::Parse($UnitSize.Substring(0, $UnitSize.Length - 1)) * 1024
        }
        else
        {
            [UInt64] $UnitSizeBytes = [UInt64]::Parse($UnitSize)
        }

        It "Volume Unit Size == $UnitSize" {
            $UnitSizeBytes -eq $exDiskVolExtendedProps[0].BlockSize | Should Be $true
        }
    }
}

# Removes existing Exchange partitions and related folders
function Remove-MountPointAndFolderSetup
{
    [CmdletBinding(SupportsShouldProcess=$True)]
    param
    (
        [Parameter()]
        [System.String]
        $AutoDagDatabasesRootFolderPath,

        [Parameter()]
        [System.String]
        $AutoDagVolumesRootFolderPath
    )

    # Find the disk numbers of any disk that currently has an Exchange partition on it
    [Object[]] $exDiskNumbers = (Get-Partition | Where-Object {$_.AccessPaths -like "*$AutoDagDatabasesRootFolderPath*" -or $_.AccessPaths -like "*$AutoDagVolumesRootFolderPath*"}).DiskNumber

    # Remove all partitions from any Exchange disk. This ensures the System Reserved
    # partitions are also removed, which is necessary for xExchAutoMountPoint to find
    # the disk as usable.
    foreach ($number in $exDiskNumbers)
    {
        Get-Disk -Number $number | Get-Partition | Remove-Partition -Confirm:$false
    }

    # Remove any folders in the ExchangeDatabases and ExchangeVolumes directories.
    # Do so using Directory.Delete(), as Remove-Item doesn't seem to work against
    # current or former mount points, even with -Force.
    foreach($folder in Get-ChildItem -Path $AutoDagDatabasesRootFolderPath -ErrorAction SilentlyContinue | Where-Object {$_.GetType().Name -like 'DirectoryInfo'})
    {
        [System.IO.Directory]::Delete($folder.FullName,$true)
    }

    foreach($folder in Get-ChildItem -Path $AutoDagVolumesRootFolderPath -ErrorAction SilentlyContinue | Where-Object {$_.GetType().Name -like 'DirectoryInfo'})
    {
        [System.IO.Directory]::Delete($folder.FullName,$true)
    }
}

# Define where Exchange databases and volumes will go
[System.String] $autoDagDatabasesRootFolderPath = Join-Path $env:SystemDrive 'ExchangeDatabases'
[System.String] $autoDagVolumesRootFolderPath = Join-Path $env:SystemDrive 'ExchangeVolumes'

# Clean up any existing mount points or folders
Remove-MountPointAndFolderSetup -AutoDagDatabasesRootFolderPath $autoDagDatabasesRootFolderPath -AutoDagVolumesRootFolderPath $autoDagVolumesRootFolderPath

# Make sure we have enough empty disks to work with to perform required tests
$unpartitionedDisks = Get-Disk | ForEach-Object {if (($_ | Get-Partition).Count -eq 0) {$_}}

if ($unpartitionedDisks.Count -lt 2)
{
    Write-Verbose -Message 'Testing xExchAutoMountPoint requires at least 2 available disks with no partitions configured'
    return
}

$existingExMountPoints = Get-Partition | Where-Object {$_.AccessPaths -like "*$autoDagDatabasesRootFolderPath*" -or $_.AccessPaths -like "*$autoDagVolumesRootFolderPath*"}

if ($existingExMountPoints.Count -gt 0)
{
    Write-Verbose -Message "$($existingExMountPoints.Count) mount points already exist in the Exchange Databases or Exchange Volumes folder. Clean these up before running tests."
    return
}

# Begin testing
Describe 'Test xExchAutoMountPoint Scenarios' {
    # Run through initial testing using 1 DB disk with 4 DB mount points, 1 spare, REFS file system, and GPT partitioning
    $testParams = @{
        Identity = $env:COMPUTERNAME
        AutoDagDatabasesRootFolderPath = $autoDagDatabasesRootFolderPath
        AutoDagVolumesRootFolderPath = $autoDagVolumesRootFolderPath
        DiskToDBMap = @('IntegrationTestDB1,IntegrationTestDB2,IntegrationTestDB3,IntegrationTestDB4')
        SpareVolumeCount = 1
        EnsureExchangeVolumeMountPointIsLast = $true
        CreateSubfolders = $true
        FileSystem = 'REFS'
        PartitioningScheme = 'GPT'
        UnitSize = '64K'
        VolumePrefix = 'EXVOL'
    }

    $expectedGetResults = @{
        Identity = $env:COMPUTERNAME
        AutoDagDatabasesRootFolderPath = $autoDagDatabasesRootFolderPath
        AutoDagVolumesRootFolderPath = $autoDagVolumesRootFolderPath
        SpareVolumeCount = 1
        FileSystem = 'REFS'
        PartitioningScheme = 'GPT'
        UnitSize = '64K'
        VolumePrefix = 'EXVOL'
    }

    Test-TargetResourceFunctionality -Params $testParams `
                                     -ContextLabel 'Configure database disk with 4 DB folder mount points, and a spare disk' `
                                     -ExpectedGetResults $expectedGetResults

    Test-MountPointSetup -AutoDagDatabasesRootFolderPath $testParams.AutoDagDatabasesRootFolderPath -AutoDagVolumesRootFolderPath $testParams.AutoDagVolumesRootFolderPath -DiskToDBMap $testParams.DiskToDBMap -SpareVolumeCount $testParams.SpareVolumeCount -EnsureExchangeVolumeMountPointIsLast $testParams.EnsureExchangeVolumeMountPointIsLast -CreateSubfolders $testParams.CreateSubfolders -FileSystem $testParams.FileSystem -PartitioningScheme $testParams.PartitioningScheme -UnitSize $testParams.UnitSize -VolumePrefix $testParams.VolumePrefix

    # Cleanup mount points before next round of testing
    Remove-MountPointAndFolderSetup -AutoDagDatabasesRootFolderPath $autoDagDatabasesRootFolderPath -AutoDagVolumesRootFolderPath $autoDagVolumesRootFolderPath

    # Update test parameters and re-test
    # Add an additional disk to the disk map
    $testParams.DiskToDBMap = $expectedGetResults.DiskToDBMap += ('IntegrationTestDB5,IntegrationTestDB6,IntegrationTestDB7,IntegrationTestDB8')

    # Switch to using no spare volumes
    $testParams.SpareVolumeCount = $expectedGetResults.SpareVolumeCount = 0

    # Change file system to NTFS
    $testParams.FileSystem = $expectedGetResults.FileSystem = 'NTFS'

    # Change partitioning scheme to MBR
    $testParams.PartitioningScheme = $expectedGetResults.PartitioningScheme = 'MBR'

    # Specify unit size in bytes instead of kilobytes
    $testParams.UnitSize = $expectedGetResults.UnitSize = '65536'

    # Don't ask for the EXVOL mount point to be added last
    $testParams.EnsureExchangeVolumeMountPointIsLast = $false

    # Don't create subfolders under mount points
    $testParams.CreateSubfolders = $false

    Test-TargetResourceFunctionality -Params $testParams `
                                     -ContextLabel 'Configure 2 database disks with 8 DB folder mount points, and no spare disk' `
                                     -ExpectedGetResults $expectedGetResults

    Test-MountPointSetup -AutoDagDatabasesRootFolderPath $testParams.AutoDagDatabasesRootFolderPath -AutoDagVolumesRootFolderPath $testParams.AutoDagVolumesRootFolderPath -DiskToDBMap $testParams.DiskToDBMap -SpareVolumeCount $testParams.SpareVolumeCount -EnsureExchangeVolumeMountPointIsLast $testParams.EnsureExchangeVolumeMountPointIsLast -CreateSubfolders $testParams.CreateSubfolders -FileSystem $testParams.FileSystem -PartitioningScheme $testParams.PartitioningScheme -UnitSize $testParams.UnitSize -VolumePrefix $testParams.VolumePrefix
}

# Clean up mount points and folders one last time
Remove-MountPointAndFolderSetup -AutoDagDatabasesRootFolderPath $autoDagDatabasesRootFolderPath -AutoDagVolumesRootFolderPath $autoDagVolumesRootFolderPath