diff --git a/README.md b/README.md index 86abb12..7fe12a4 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,16 @@ Array-based snapshots are used to decouple database operations from the size of | Demo | Description | | | | ----------- | ----------- | ----------- | ----------- | -| **Volume Database Refresh on VMDK Virtual Disks** | Refresh a database from Volume snapshot where all of the databases's files are using a VMware VMDK Virtual Disk type on a single VMFS Datastore on the same Volume on the same FlashArray. | [More Info](./demos-sdk2/VMFS-VMDK%20Snapshot/) | [Sample Code](./demos-sdk2/VMFS-VMDK%20Snapshot/VMFS-VMDK%20Snapshot.ps1) | +| **Volume Database Refresh on VMDK Virtual Disks** | Refresh a database from a Volume snapshot where all of the databases's files are using a VMware VMDK Virtual Disk type on a single VMFS Datastore on the same Volume on the same FlashArray. | [More Info](./demos-sdk2/VMFS-VMDK%20Snapshot/) | [Sample Code](./demos-sdk2/VMFS-VMDK%20Snapshot/VMFS-VMDK%20Snapshot.ps1) | +| **Point in Time Recovery on VMDK Virtual Disks** | Combine a FlashArray snapshot with SQL Server 2022 T-SQL Snapshot Backup on a VMFS datastore for application-consistent snapshots and point-in-time recovery. | [More Info](./demos-sdk2/VMFS-VMDK%20Snapshot/) | [Sample Code](./demos-sdk2/VMFS-VMDK%20Snapshot/Point%20in%20Time%20Recovery%20-%20VMFS.ps1) | + + +## Using Snapshots for Databases on Hyper-V Cluster Shared Volumes (CSV) + +| Demo | Description | | | +| ----------- | ----------- | ----------- | ----------- | +| **Hyper-V CSV Crash-Consistent Clone** | Refresh a dev/test SQL Server from a crash-consistent FlashArray snapshot of a production Hyper-V CSV, with no source downtime. | [More Info](./demos-sdk2/Hyper-V%20CSV%20w%20SQL%20Server%20Snapshot/) | [Sample Code](./demos-sdk2/Hyper-V%20CSV%20w%20SQL%20Server%20Snapshot/HyperV%20CrashConsistentClone.ps1) | +| **Hyper-V CSV T-SQL Snapshot Backup** | Combine a FlashArray volume snapshot with SQL Server 2022 T-SQL Snapshot Backup on a Hyper-V CSV for application-consistent snapshots and point-in-time recovery. | [More Info](./demos-sdk2/Hyper-V%20CSV%20w%20SQL%20Server%20Snapshot/) | [Sample Code](./demos-sdk2/Hyper-V%20CSV%20w%20SQL%20Server%20Snapshot/HyperV-TSQL-SnapshotBackup.ps1) | ## Using ActiveDR diff --git a/demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 b/demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 index 73b5d44..ffdc21d 100644 --- a/demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 +++ b/demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 @@ -52,7 +52,7 @@ $ProductionSQLServerSession = New-PSSession -ComputerName $ProductionSQLServer # Connect to FlashArray $Credential = Get-Credential -$FlashArray = Connect-Pfa2Array -Endpoint $DRArrayName -Credential $Credential -IgnoreCertificateError +$FlashArray = Connect-Pfa2Array -Endpoint $ProductionArrayName -Credential $Credential -IgnoreCertificateError diff --git a/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 b/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 index f739f62..5a412b0 100644 --- a/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 +++ b/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 @@ -1,30 +1,45 @@ -#################################################################################################################### -#################################################################################################################### -## -## ActiveDR failover testing for SQL Server Failover Cluster Instance -## -## This demo script runs through two scenarios: - -## 1. Failover of clustered SQL Server role to node on same array -## 2. Failover of clustered SQL Server role to node on remote array -## -## The second test involves the following steps: - -###### Stop clustered SQL Server role in FCI -###### Demote source pod -###### Promote target pod -###### Move clustered role to node on target array -###### Start up clustered SQL Server role -## -## Author - Andrew Pruski -## apruski@purestorage.com -## -#################################################################################################################### -#################################################################################################################### +############################################################################################################################## +# ActiveDR Failover Testing for SQL Server Failover Cluster Instance +# +# Scenario: +# This demo script runs through two scenarios: +# 1. Failover of a clustered SQL Server role to a node on the same storage array +# 2. Failover of a clustered SQL Server role to a node on a remote storage array +# +# The remote array failover involves the following steps: +# - Stop the clustered SQL Server role in the FCI +# - Demote the source pod +# - Promote the target pod +# - Move the clustered role to a node on the target array +# - Start the clustered SQL Server role +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## -# import powershell modules +# Import PowerShell modules Import-Module FailoverClusters -Import-Module PureStoragePowershellSDK2 +Import-Module PureStoragePowerShellSDK2 + + + +# Variables +$ClusterName = "WindowsClusterName" +$ClusterRole = "SQL Server (MSSQLSERVER)" # Clustered SQL Server role name +$NodeSameArray = "NodeOnSameArray" # Cluster node on the same storage array +$NodeRemoteArray = "NodeOnRemoteArray" # Cluster node on the remote storage array +$SourceFlashArrayIp = "flasharray1.example.com" # Source FlashArray endpoint +$SourcePodName = "PodNameOnSourceArray" # Pod name on the source FlashArray +$TargetFlashArrayIp = "flasharray2.example.com" # Target FlashArray endpoint +$TargetPodName = "PodNameOnTargetArray" # Pod name on the target FlashArray + + + +# Set credentials +$PureCred = Get-Credential @@ -36,124 +51,95 @@ Import-Module PureStoragePowershellSDK2 -# set variables -$ClusterName = "WindowsClusterName" -$ClusterRole = "SQL Server (MSSQLSERVER)" -$NodeSameArray = "NodeOnSameArray" - - - -# confirm cluster +# Confirm cluster Get-Cluster $ClusterName -# confirm cluster nodes +# Confirm cluster nodes Get-Cluster $ClusterName | Get-ClusterNode -# confirm clustered SQL Server service +# Confirm clustered SQL Server service Get-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -# test failing over clustered service to node on same storage array +# Test failing over clustered service to node on same storage array Move-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -Node $NodeSameArray -# confirm clustered SQL Server service +# Confirm clustered SQL Server service Get-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -################################################################################################################ +#################################################################################################################### # # Performing failover to node on remote storage array # -################################################################################################################ - - - -# set source array details -$SourceFlashArrayIp = "SourceFlashArrayIpAddress" -$SourcePodName = "PodNameOnSourceArray" - - - -# set Pure credentials -$PureCred = Get-Credentials +#################################################################################################################### -# connect to source flasharray +# Connect to the source FlashArray $SourceFlashArray = Connect-Pfa2Array -EndPoint $SourceFlashArrayIp -Credential $PureCred -IgnoreCertificateError -# confirm pod replication status +# Confirm pod replication status Get-Pfa2PodReplicaLink -Array $SourceFlashArray -LocalPodName $SourcePodName -# confirm clustered SQL Server service +# Confirm clustered SQL Server service Get-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -# stop clustered service - taking volumes offline +# Stop clustered service - taking volumes offline Stop-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -# confirm clustered service offline +# Confirm clustered service offline Get-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -# demote Production Pod with Quiesce +# Demote the source pod with quiesce Update-Pfa2Pod -Array $SourceFlashArray -Name $SourcePodName -Quiesce $True -RequestedPromotionState "demoted" -# confirm Production Pod status - PromotionStatus : demoted +# Confirm source pod status - PromotionStatus : demoted Get-Pfa2Pod -Array $SourceFlashArray -Name $SourcePodName -# set target array details -$TargetFlashArrayIp = "TargetFlashArrayIpAddress" -$TargetPodName = "PodNameOnTargetArray" - - - -# connect to target flasharray +# Connect to the target FlashArray $TargetFlashArray = Connect-Pfa2Array -EndPoint $TargetFlashArrayIp -Credential $PureCred -IgnoreCertificateError -# promote pod +# Promote the target pod Update-Pfa2Pod -Array $TargetFlashArray -Name $TargetPodName -RequestedPromotionState "promoted" -# confirm pod promoted - PromotionStatus : promoted -Get-Pfa2Pod -Array $FlashArray -Name $TargetPodName - - - -# set node name on remote array -$NodeSameArray2 = "NodeOnRemoteArray" +# Confirm pod promoted - PromotionStatus : promoted +Get-Pfa2Pod -Array $TargetFlashArray -Name $TargetPodName -# move clustered role to node on target array -Move-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -Node $NodeSameArray2 +# Move clustered role to node on the target array +Move-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -Node $NodeRemoteArray -# start the clustered role +# Start the clustered role Start-ClusterGroup -Cluster $ClusterName -Name $ClusterRole diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 deleted file mode 100644 index d898ae8..0000000 --- a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 +++ /dev/null @@ -1,226 +0,0 @@ -############################################################################################################################## -# Hyper-V Cluster Shared Volume (CSV) with SQL Server Snapshot Example -# -# Scenario: -# This script will clone a Hyper-V Cluster Shared Volume (CSV), using a crash consistent snapshot, and present it back -# to the originating Hyper-V cluster as a second CSV "copy." -# -# This example scenario is useful if you have isolated a VLDB SQL Server database exclusively onto this CSV -# -# See https://github.com/PureStorage-OpenConnect/sqlserver-scripts/tree/master/demos-sdk2/Hyper-V%20CSV%20Snapshot -# for more details -# -# -# Prerequisities: -# 1. An additional Windows server (referred to as a staging server). This staging server does not have to be a -# Hyper-V host. -# 2. A pre-created volume of equal size to the source CSV, pre-attached to the staging server. -# 3. The 'Failover Cluster Module for Windows PowerShell' Feature in Windows is required on the Hyper-V host. -# Add-WindowsFeature RSAT-Clustering-PowerShell -# -# -# Usage Notes: -# -# The staging server is needed because each CSV has a unique signature. If the CSV is presented back to the Hyper-V -# host unaltered, a signature collision will be detected and the new CSV will not be able to be used by Windows. -# Hyper-V is unable to resignature in this state either. Instead, the CSV must be presented to another machine (aka -# the staging server), resignatured there, then can be re-snapshotted and cloned back to the originating Hyper-V -# host. -# -# This script may be adjusted to clone and present the CSV snapshot to a different Hyper-V host. If this is done, then -# the staging server and resignature step is not required, since the new target Hyper-V host will not have two of the -# same CSV causing a signature conflict. -# -# -# Disclaimer: -# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual -# organization's infrastructure. -############################################################################################################################## -Import-Module PureStoragePowerShellSDK2 - - - -# Variables -$FlashArrayEndPoint = 'flasharray1.example.com' -$SourceVMCluster = 'hyperv-cluster-01.fsa.lab' -$SourceVMHost = 'hyperv-host-01.example.com' -$SourceVM = 'hyperv-vm-source' # No FQDN -$SourceVolumeName = 'hyperv-vm-source-csv-01' # Name of the volume in FlashArray -$StagingServer = 'windows-staging-server' -$StagingVolumeName = 'temporary-volume-for-csv-resignature' -$StagingDiskSerialNumber = '6000c2945ce069b03b9750d2afe72828' -$TargetVMHost = 'hyperv-host-02.example.com' # No FQDN -$TargetVM = 'hyperv-vm-target' # No FQDN -$TargetVolumeName = 'hyperv-vm-target-csv-01-cloned' -$TargetClusterDiskNumber = 'Cluster Disk 3' -$DatabaseName = 'MyDatabaseName' -$ClusteredStorageFolder = "C:\ClusterStorage\volume4\hv-sqldata-01\data\*.*" # Target Host Folder containing cloned VHDX/AVHDX files - - - -# Establish credential to use for all connections -$Credential = Get-Credential -Message 'Enter your Pure credentials' - - - -# Connect to the FlashArray -$FlashArray = Connect-Pfa2Array -Endpoint $FlashArrayEndPoint -Credential ($Credential) -IgnoreCertificateError - - - -# Determine which Hyper-V node each role currently resides on -$HyperVClusterSession = New-PSSession -ComputerName $SourceVMCluster -Credential $Credential - -$SourceClusterGroup = Invoke-Command -Session $HyperVClusterSession -ScriptBlock { Get-ClusterGroup -Name $Using:SourceVM } -$TargetClusterGroup = Invoke-Command -Session $HyperVClusterSession -ScriptBlock { Get-ClusterGroup -Name $Using:TargetVM } - -$SourceVMHost = $SourceClusterGroup.OwnerNode -$TargetVMHost = $TargetClusterGroup.OwnerNode - -# Verify -$SourceVM -$SourceVMHost - -$TargetVM -$TargetVMHost - - - -# Prepare the staging CSV for overlay -# Connect to staging VM -$StagingServerSession = New-PSSession -ComputerName $StagingServer -Credential $Credential - - - -# Offline the volume -# NOTE: use Get-Disk prior to get the correct Serial Number -Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:StagingDiskSerialNumber } | Set-Disk -IsOffline $True } - -# Verify -Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:StagingDiskSerialNumber }} - - - -# Snapshot the source CSV -# This example is for an on-demand snapshot. Can adjust code to also use a prior snapshot; ex. regularly scheduled -# snapshots or an asynchronously replicated snapshot from another FlashArray - -# Clone the source CSV to the staging CSV -New-Pfa2Volume -Array $FlashArray -Name $StagingVolumeName -SourceName $SourceVolumeName -Overwrite $true - - - -# Now must resignature the CSV on the staging VM -# Build DISKPART script commands for resignature -$StagingDisk = Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $Using:StagingDiskSerialNumber }} -$DiskNumber = $StagingDisk.Number -$NewUniqueID = [GUID]::NewGuid() -$Commands = "`"SELECT DISK $DiskNumber`"", - "`"UNIQUEID DISK ID=$NewUniqueID`"" -$ScriptBlock = [string]::Join(",",$Commands) -$DiskpartScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$ScriptBlock | DISKPART") - -# Verify DISKPART command -$DiskpartScriptBlock - -# Issue resignature command -Invoke-Command -Session $StagingServerSession -ScriptBlock $DiskpartScriptBlock - - - -# Prepare target VM -$TargetVMSession = New-PSSession -ComputerName $TargetVM -Credential $Credential - -# Offline the database -$Query = "ALTER DATABASE $DatabaseName SET OFFLINE WITH ROLLBACK IMMEDIATE" -Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) - -# Offline the volume -# Because this is a Hyper-V VM, volume serial numbers are not populated by Hyper-V into a virtual machine -# Therefore must use a different method identify the proper volume to offline - -# Confirm which drive you want -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } - - - -# Specify the drive number -$DiskNumber = 1 -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk -Number $using:DiskNumber | Get-Disk | Set-Disk -IsOffline $True } - -# Verify offline -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } - - - -# Prepare target VM Host -$TargetVMHostSession = New-PSSession -ComputerName $TargetVMHost -Credential $Credential - -# Remove SQL Server cluster resource dependency on database volume -# Only use this if you are using Clustered Disks (NOT Clustered Shared Volumes) -# Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterResource 'SQL Server' | Remove-ClusterResourceDependency $TargetVolumeName } - -# Stop the disk cluster resource -# NOTE: need to know which Cluster Disk Number first -# This will put the target Hyper-V VM into a Saved state -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Stop-ClusterResource $Using:TargetClusterDiskNumber } - -# Verify -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterSharedVolume $Using:TargetClusterDiskNumber } - - - -# Clone the staging CSV to the target CSV -New-Pfa2Volume -Array $FlashArray -Name $TargetVolumeName -SourceName $StagingVolumeName -Overwrite $true - - - -# Start the disk cluster resource -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Start-ClusterResource $Using:TargetClusterDiskNumber } - -# Verify -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterSharedVolume $Using:TargetClusterDiskNumber } - - - -# Must now update permissions in Windows to grant the new VM access to the VHDX files - -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { - $VMID = "NT VIRTUAL MACHINE\" - Get-VM -name $Using:TargetVM | Select-Object -ExpandProperty VMID - - $fileAclList = Get-Acl $Using:ClusteredStorageFolder - Foreach ($acl in $fileAclList) { - # Add a new rule to grant full control to a user - $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($VMID, "FullControl", "Allow") - $acl.AddAccessRule($rule) - Set-Acl -Path $acl.PSPath -AclObject $acl - } -} - - - -# Online the volume - -# Confirm which drive you want -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } - - - -# Specify the drive number -$DiskNumber = 1 -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk -Number $using:DiskNumber | Get-Disk | Set-Disk -IsOffline $False } - -# Verify -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } - - - -# Online the database -$Query = "ALTER DATABASE $DatabaseName SET ONLINE" -Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) - -# Verify -$Query = "SELECT @@SERVERNAME, name, state_desc, GETDATE() FROM sys.databases WHERE database_id = DB_ID('$DatabaseName')" -Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) - diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV CrashConsistentClone.ps1 b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV CrashConsistentClone.ps1 new file mode 100644 index 0000000..eba7478 --- /dev/null +++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV CrashConsistentClone.ps1 @@ -0,0 +1,225 @@ +############################################################################################################################## +# Pure Storage FlashArray Snapshot Clone - Hyper-V Edition +# Crash-Consistent Snapshot Clone to a Dev/Test SQL Server +# +# Scenario: +# This script will clone a Hyper-V Cluster Shared Volume (CSV), using a crash-consistent snapshot, and present +# it to a second VM running a dev/test SQL Server instance. The source SQL Server continues running uninterrupted +# throughout the entire operation. The target SQL Server performs automatic crash recovery when the database is +# brought online. +# +# Storage topology: +# Source : hyperv-csv-01-DATA-PROD (CSV backing VM1 VHDXs) -> SQL 01 (Production) +# Target : hyperv-csv-01-DATA-DEV (CSV backing VM2 VHDXs) -> SQL 02 (Dev/Test) +# +# This example scenario is useful for refreshing a dev/test SQL Server database from a production FlashArray CSV +# without any downtime on the source. +# +# +# Prerequisites: +# +# FlashArray: +# 1. Source and target volumes must be pre-configured on the FlashArray. +# The target volume must be the same size as the source CSV volume. +# +# Hyper-V Cluster: +# 2. The Failover Cluster PowerShell module must be installed on the Hyper-V node(s). +# Add-WindowsFeature RSAT-Clustering-PowerShell +# 3. PowerShell Remoting (WinRM) must be enabled on all Hyper-V cluster nodes. +# 4. The target VM must have a SCSI controller — hot-add/remove of virtual disks requires SCSI (not IDE). +# +# Modules: +# 5. PureStoragePowerShellSDK2 must be installed on the machine running this script. +# Install-Module PureStoragePowerShellSDK2 +# 6. dbatools must be installed. +# Install-Module dbatools +# +# Credentials: +# 7. Saved credentials file: $HOME\FA_Cred.xml +# $FACred | Export-CliXml -Path "$HOME\FA_Cred.xml" +# 8. $DbName must already exist on the target SQL Server. +# +# +# Usage Notes: +# +# This is a crash-consistent snapshot — SQL Server write IO is NOT frozen before the snapshot is taken. +# The source database remains online and unaffected. The target SQL Server will perform automatic crash +# recovery (roll forward committed transactions, roll back uncommitted) when the database is brought online. +# +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## + + + +# Import PowerShell modules +Import-Module dbatools +Import-Module PureStoragePowerShellSDK2 + + + +# Initialize variables -- edit these to match your environment +$SourceSQLServer = 'sql-01.example.com' # SQL Server hosting the source database +$TargetSQLServer = 'sql-02.example.com' # SQL Server that will receive the clone +$ArrayName = 'flasharray1.example.com' # FlashArray endpoint +$DbName = 'MyDatabaseName' # Database name + +# Hyper-V cluster details +$HVNode2 = 'hyperv-node-02.example.com' # Hyper-V cluster node that owns the target CSV +$TargetCSVResource = 'Cluster Disk 3' # Cluster resource name for the target CSV + +# FlashArray volume names +$SourceVolName = 'hyperv-csv-01-DATA-PROD' # FA volume backing the source CSV +$TargetVolName = 'hyperv-csv-01-DATA-DEV' # FA volume backing the target CSV + +# VHDX paths on the target CSV (mirrors the source CSV layout after clone) +$ClonedDataVhdx = 'C:\ClusterStorage\Volume3\hyperv-vm-01\hyperv-vm-01-Data.vhdx' +$ClonedLogVhdx = 'C:\ClusterStorage\Volume3\hyperv-vm-01\hyperv-vm-01-Log.vhdx' + +# Target VM name +$TargetVM = 'hyperv-vm-02' +$DataCtrlNum = 0; $DataCtrlLoc = 1 # Data VHDX at SCSI 0:1 +$LogCtrlNum = 0; $LogCtrlLoc = 2 # Log VHDX at SCSI 0:2 + + + +# Build connections +# PowerShell remoting session to the Hyper-V cluster node hosting the target CSV +$HVSession = New-PSSession -ComputerName $HVNode2 + +# SQL connections -- dbatools maintains a persistent SMO connection across cmdlet calls +$SqlInstance1 = Connect-DbaInstance -SqlInstance $SourceSQLServer -TrustServerCertificate -NonPooledConnection +$SqlInstance2 = Connect-DbaInstance -SqlInstance $TargetSQLServer -TrustServerCertificate -NonPooledConnection + +# Connect to the FlashArray's REST API +$FACred = Import-CliXml -Path "$HOME\FA_Cred.xml" +$FlashArray = Connect-Pfa2Array -EndPoint $ArrayName -Credential $FACred -IgnoreCertificateError + + + + +# Let's get some information about the source database on SQL 01; take note of the size +Get-DbaDatabase -SqlInstance $SqlInstance1 -Database $DbName | + Select-Object Name, SizeMB, Status + + + +############################################################################# +# Take a Crash-Consistent Snapshot +############################################################################# + +# Time the full operation from snapshot to database online on SQL 02 +$Start = (Get-Date) + + +# Take a crash-consistent volume snapshot -- SQL Server is running, no freeze +# This is equivalent to pulling the power cord and taking a picture of the disk. +# SQL 02 will perform automatic crash recovery when the database is attached. +$Snapshot = New-Pfa2VolumeSnapshot -Array $FlashArray -SourceName $SourceVolName +$Snapshot + + + +############################################################################# +# Prepare SQL 02 -- Offline Databases and Release File Handles +############################################################################# + +# Offline any user databases on SQL 02 so their file handles are released +$Query = "ALTER DATABASE [$DbName] SET OFFLINE WITH ROLLBACK IMMEDIATE" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Query $Query + + + +############################################################################# +# Prepare the Hyper-V Storage -- Detach, Clone, Reattach +############################################################################# + +# Detach the data and log VHDXs from the target VM at the hypervisor level +# The CSV must be offlined before overwriting the FlashArray volume, and the +# VHDXs must be detached before the CSV can be taken offline cleanly +Invoke-Command -Session $HVSession -ScriptBlock { + param($vm) + Remove-VMHardDiskDrive -VMName $vm -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 1 + Remove-VMHardDiskDrive -VMName $vm -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 2 + Write-Output "Data VHDX detached (SCSI 0:1)" + Write-Output "Log VHDX detached (SCSI 0:2)" +} -ArgumentList $TargetVM + + +# Take the target CSV offline so the underlying FlashArray volume can be overwritten +Invoke-Command -Session $HVSession -ScriptBlock { + param($res) + Stop-ClusterResource -Name $res -Cluster (Get-Cluster).Name | Out-Null + Write-Output "$res offline" +} -ArgumentList $TargetCSVResource + + +# Clone the snapshot to the target volume -- instantaneous on FlashArray +# New-Pfa2Volume with -Overwrite $true reverts the target volume to the snapshot's contents. +# On a Pure Storage FlashArray this is a metadata operation -- no data is copied. +New-Pfa2Volume -Array $FlashArray -Name $TargetVolName -SourceName $Snapshot.Name -Overwrite $true + + +# Bring the target CSV back online -- the cluster will mount the now-cloned volume +Invoke-Command -Session $HVSession -ScriptBlock { + param($res) + Start-ClusterResource -Name $res -Cluster (Get-Cluster).Name | Out-Null + Write-Output "$res online" +} -ArgumentList $TargetCSVResource + +Start-Sleep -Seconds 5 + + +# Re-attach the cloned VHDXs to the target VM at the same SCSI controller locations +Invoke-Command -Session $HVSession -ScriptBlock { + param($dataVhdx, $logVhdx, $dCN, $dCL, $lCN, $lCL, $vm) + Add-VMHardDiskDrive -VMName $vm -Path $dataVhdx -ControllerType SCSI -ControllerNumber $dCN -ControllerLocation $dCL + Add-VMHardDiskDrive -VMName $vm -Path $logVhdx -ControllerType SCSI -ControllerNumber $lCN -ControllerLocation $lCL + Write-Output "Attached: $dataVhdx (SCSI $($dCN):$($dCL))" + Write-Output "Attached: $logVhdx (SCSI $($lCN):$($lCL))" +} -ArgumentList $ClonedDataVhdx, $ClonedLogVhdx, $DataCtrlNum, $DataCtrlLoc, $LogCtrlNum, $LogCtrlLoc, $TargetVM + + + + +############################################################################# +# Bring the Cloned Database Online on SQL 02 +############################################################################# + +# Wait briefly for CSV and VHDXs to settle +Start-Sleep -Seconds 5 + +# Bring the database online -- SQL Server performs automatic crash recovery +# The database was never cleanly shut down before the snapshot, so SQL Server will +# roll forward the log and roll back any incomplete transactions, just as it would +# after a server restart +$Query = "ALTER DATABASE [$DbName] SET ONLINE" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Query $Query +Write-Output "$DbName is online on $TargetSQLServer" + + + +############################################################################# +# Verify +############################################################################# + +# Check the database state on SQL 02 -- it should be ONLINE after crash recovery +Get-DbaDbState -SqlInstance $SqlInstance2 -Database $DbName + +# Show all user databases on SQL 02 +Get-DbaDatabase -SqlInstance $SqlInstance2 -Database $DbName | + Select-Object Name, Status, SizeMB + +$Stop = (Get-Date) +Write-Output "Total time from snapshot to database online: $(($Stop - $Start).Seconds) seconds" + + + +############################################################################# +# Cleanup +############################################################################# + +Remove-PSSession $HVSession +Disconnect-Pfa2Array -Array $FlashArray diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV-TSQL-SnapshotBackup.ps1 b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV-TSQL-SnapshotBackup.ps1 new file mode 100644 index 0000000..a37e13d --- /dev/null +++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV-TSQL-SnapshotBackup.ps1 @@ -0,0 +1,276 @@ +############################################################################################################################## +# T-SQL Snapshot Backup and Point-in-Time Recovery - Hyper-V Edition +# +# Scenario: +# This script demonstrates SQL Server 2022 T-SQL Snapshot Backup on a Hyper-V cluster where database files +# reside on VHDXs stored on a Pure Storage FlashArray Cluster Shared Volume (CSV). An application-consistent +# snapshot is taken while SQL Server is running on VM1 (SQL 01), then the cloned volume is presented to VM2 +# (SQL 02) for a point-in-time restore. +# +# Storage topology: +# Source : hyperv-csv-01-DATA-PROD (CSV backing VM1 VHDXs) -> SQL 01 (Production) +# Target : hyperv-csv-01-DATA-DEV (CSV backing VM2 VHDXs) -> SQL 02 (Dev/Test) +# +# This example scenario is useful for seeding a dev/test SQL Server from a production FlashArray CSV with +# a full point-in-time recovery chain. +# +# +# Prerequisites: +# +# FlashArray: +# 1. Source and target volumes must be pre-configured on the FlashArray. +# The target volume must be the same size as the source CSV volume. +# +# Hyper-V Cluster: +# 2. The Failover Cluster PowerShell module must be installed on the Hyper-V node(s). +# Add-WindowsFeature RSAT-Clustering-PowerShell +# 3. PowerShell Remoting (WinRM) must be enabled on all Hyper-V cluster nodes. +# 4. The target VM must have a SCSI controller — hot-add/remove of virtual disks requires SCSI (not IDE). +# +# Modules: +# 5. PureStoragePowerShellSDK2 must be installed on the machine running this script. +# Install-Module PureStoragePowerShellSDK2 +# 6. dbatools must be installed. +# Install-Module dbatools +# +# SQL Server: +# 7. SQL Server 2022 or later is required on both instances for T-SQL Snapshot Backup support. +# 8. $DbName must already exist on the target SQL Server. +# 9. A backup share must be accessible from both SQL 01 and SQL 02 for metadata and log backup files. +# +# Credentials: +# 10. Saved credentials file: $HOME\FA_Cred.xml +# $FACred | Export-CliXml -Path "$HOME\FA_Cred.xml" +# +# +# Usage Notes: +# +# This script uses a volume snapshot (New-Pfa2VolumeSnapshot) rather than a Protection Group snapshot because +# the data and log VHDXs share a single CSV volume — a single volume snapshot captures both files consistently. +# The SUSPEND_FOR_SNAPSHOT_BACKUP and BACKUP METADATA_ONLY commands must share the same SQL Server session; +# this script uses a persistent, non-pooled dbatools connection to satisfy that requirement. +# +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## + + + +# Import PowerShell modules +Import-Module dbatools +Import-Module PureStoragePowerShellSDK2 + + + +# Initialize variables — edit these to match your environment +$SourceSQLServer = 'sql-01.example.com' # SQL Server hosting the source database +$TargetSQLServer = 'sql-02.example.com' # SQL Server that will receive the clone +$ArrayName = 'flasharray1.example.com' # FlashArray endpoint +$DbName = 'MyDatabaseName' # Database name +$BackupShare = '\\backup-server\BACKUP' # UNC path for metadata and log backups + +# Hyper-V cluster details +$HVNode2 = 'hyperv-node-02.example.com' # Hyper-V cluster node that owns the target CSV +$TargetVM = 'hyperv-vm-02' # VM name receiving the cloned VHDXs +$TargetCSVResource = 'Cluster Disk 3' # Cluster resource name for the target CSV + +# FlashArray volume names +$SourceVolName = 'hyperv-csv-01-DATA-PROD' # FA volume backing the source CSV +$TargetVolName = 'hyperv-csv-01-DATA-DEV' # FA volume backing the target CSV + +# VHDX paths on the target CSV (after clone) +$ClonedDataVhdx = 'C:\ClusterStorage\Volume3\hyperv-vm-01\hyperv-vm-01-Data.vhdx' +$ClonedLogVhdx = 'C:\ClusterStorage\Volume3\hyperv-vm-01\hyperv-vm-01-Log.vhdx' + +# SCSI controller locations for VHDXs on the target VM +$DataCtrlNum = 0; $DataCtrlLoc = 1 # Data VHDX at SCSI 0:1 +$LogCtrlNum = 0; $LogCtrlLoc = 2 # Log VHDX at SCSI 0:2 + + + +# Build connections +# PowerShell remoting session to the Hyper-V cluster node hosting the target CSV +$HVSession = New-PSSession -ComputerName $HVNode2 + +# Persistent, non-pooled SMO connections — required so SUSPEND_FOR_SNAPSHOT_BACKUP +# and BACKUP METADATA_ONLY share the same session (SUSPEND is session-scoped) +$SqlInstance1 = Connect-DbaInstance -SqlInstance $SourceSQLServer -TrustServerCertificate -NonPooledConnection +$SqlInstance2 = Connect-DbaInstance -SqlInstance $TargetSQLServer -TrustServerCertificate -NonPooledConnection + +# Connect to the FlashArray's REST API +$FACred = Import-CliXml -Path "$HOME\FA_Cred.xml" +$FlashArray = Connect-Pfa2Array -EndPoint $ArrayName -Credential $FACred -IgnoreCertificateError + + + + +# Let's get some information about the source database; take note of the size +Get-DbaDatabase -SqlInstance $SqlInstance1 -Database $DbName | + Select-Object Name, SizeMB, Status + + + +############################################################################# +# Take a T-SQL Snapshot Backup +############################################################################# + +# Time the freeze window — this measures how long SQL Server write IO is frozen +$Start = (Get-Date) + + +# Freeze the database for write IO on SQL 01 +$Query = "ALTER DATABASE [$DbName] SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON" +Invoke-DbaQuery -SqlInstance $SqlInstance1 -Query $Query -Verbose + + +# Take a volume snapshot while the database is frozen +$Snapshot = New-Pfa2VolumeSnapshot -Array $FlashArray -SourceName $SourceVolName +$Snapshot + + +# Write the metadata-only backup — this releases the write IO freeze +# MEDIADESCRIPTION stores the snapshot name and array so we can locate the snapshot later +$BackupFile = "$BackupShare\${DbName}_$(Get-Date -Format FileDateTime).bkm" +$Query = "BACKUP DATABASE [$DbName] + TO DISK='$BackupFile' + WITH METADATA_ONLY, + MEDIADESCRIPTION='$($Snapshot.Name)|$($FlashArray.ArrayName)'" +Invoke-DbaQuery -SqlInstance $SqlInstance1 -Query $Query -Verbose + +$Stop = (Get-Date) +Write-Output "The snapshot time takes...$(($Stop - $Start).TotalMilliseconds)ms!" + + +# Check the error log to see what SQL Server thinks happened +Get-DbaErrorLog -SqlInstance $SqlInstance1 -LogNumber 0 | Format-Table + + +# The backup is recorded in MSDB as a Full backup with snapshot +Get-DbaDbBackupHistory -SqlInstance $SqlInstance1 -Database $DbName -Last + + + +############################################################################# +# Take a Log Backup — this extends the restore chain for point-in-time recovery +############################################################################# + +$LogBackup = Backup-DbaDatabase -SqlInstance $SqlInstance1 ` + -Database $DbName ` + -Type Log ` + -Path $BackupShare ` + -CompressBackup + +$LogBackup + + + +############################################################################# +# Point in Time Recovery — Clone the Snapshot to SQL 02 +# +# This is the Hyper-V equivalent of taking the database disk offline, cloning +# the storage snapshot, and bringing the disk back online as described in the +# blog series. Instead of managing a Windows disk serial number, we manage the +# CSV cluster resource and the VHDXs attached to the target Hyper-V VM. +############################################################################# + +# Retrieve the snapshot name from the metadata backup file +# MEDIADESCRIPTION holds the pipe-delimited string we wrote during the backup +$Query = "RESTORE LABELONLY FROM DISK = '$BackupFile'" +$Labels = Invoke-DbaQuery -SqlInstance $SqlInstance2 -Query $Query -Verbose +$SnapshotName = (($Labels | Select-Object MediaDescription -ExpandProperty MediaDescription).Split('|'))[0] +$ArrayName = (($Labels | Select-Object MediaDescription -ExpandProperty MediaDescription).Split('|'))[1] +$SnapshotName +$ArrayName + + +# Offline the database on SQL 02 to release file handles +$Query = "ALTER DATABASE [$DbName] SET OFFLINE WITH ROLLBACK IMMEDIATE" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Query $Query +$RestoreStart = (Get-Date) + + + +# Detach VHDXs from the target VM before taking the CSV offline +Invoke-Command -Session $HVSession -ScriptBlock { + param($vm) + Remove-VMHardDiskDrive -VMName $vm -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 1 + Remove-VMHardDiskDrive -VMName $vm -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 2 + Write-Output "Data VHDX detached (SCSI 0:1)" + Write-Output "Log VHDX detached (SCSI 0:2)" +} -ArgumentList $TargetVM + + +# Take the target CSV offline so the FlashArray volume can be overwritten +Invoke-Command -Session $HVSession -ScriptBlock { + param($res) + Stop-ClusterResource -Name $res -Cluster (Get-Cluster).Name | Out-Null + Write-Output "$res offline" +} -ArgumentList $TargetCSVResource + + +# Clone the snapshot to the target volume — instantaneous on FlashArray +New-Pfa2Volume -Array $FlashArray -Name $TargetVolName -SourceName $SnapshotName -Overwrite $true + + +# Bring the target CSV back online +Invoke-Command -Session $HVSession -ScriptBlock { + param($res) + Start-ClusterResource -Name $res -Cluster (Get-Cluster).Name | Out-Null + Write-Output "$res online" +} -ArgumentList $TargetCSVResource + +Start-Sleep -Seconds 5 + + +# Re-attach the cloned VHDXs to the target VM +Invoke-Command -Session $HVSession -ScriptBlock { + param($dataVhdx, $logVhdx, $dCN, $dCL, $lCN, $lCL, $vm) + Add-VMHardDiskDrive -VMName $vm -Path $dataVhdx -ControllerType SCSI -ControllerNumber $dCN -ControllerLocation $dCL + Add-VMHardDiskDrive -VMName $vm -Path $logVhdx -ControllerType SCSI -ControllerNumber $lCN -ControllerLocation $lCL + Write-Output "Attached: $dataVhdx (SCSI $($dCN):$($dCL))" + Write-Output "Attached: $logVhdx (SCSI $($lCN):$($lCL))" +} -ArgumentList $ClonedDataVhdx, $ClonedLogVhdx, $DataCtrlNum, $DataCtrlLoc, $LogCtrlNum, $LogCtrlLoc, $TargetVM + + + +# Restore the database from the metadata-only backup file +# METADATA_ONLY tells SQL Server the files are already in place from the snapshot +# NORECOVERY leaves the database in RESTORING mode so we can apply log backups +$Query = "RESTORE DATABASE [$DbName] FROM DISK = '$BackupFile' WITH METADATA_ONLY, REPLACE, NORECOVERY" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Database master -Query $Query -Verbose + + +# Check the current state of the database — it should be in RESTORING mode +Get-DbaDbState -SqlInstance $SqlInstance2 -Database $DbName + + +# Restore the log backup up to the point in time — database remains in RESTORING mode +$Query = "RESTORE LOG [$DbName] FROM DISK = '$($LogBackup.BackupPath)' WITH NORECOVERY" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Database master -Query $Query -Verbose + + +# Bring the database online +$Query = "RESTORE DATABASE [$DbName] WITH RECOVERY" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Database master -Query $Query + +$RestoreStop = (Get-Date) +Write-Output "The restore time takes...$(($RestoreStop - $RestoreStart).TotalMilliseconds)ms!" + + +# Verify the database is online +Get-DbaDbState -SqlInstance $SqlInstance2 -Database $DbName + +Get-DbaDatabase -SqlInstance $SqlInstance2 -Database $DbName | + Select-Object Name, Status, SizeMB + + + + +############################################################################# +# Cleanup +############################################################################# + +Remove-PSSession $HVSession +Disconnect-Pfa2Array -Array $FlashArray diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md index e77eb26..d8f6e6f 100644 --- a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md +++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md @@ -1,57 +1,95 @@ -**Hyper-V Cluster Shared Volume + SQL Server Snapshot Scripts** -
-This folder contains Hyper-V Cluster Shared Volume + SQL Server example snapshot scripts. +# Hyper-V Cluster Shared Volume + SQL Server Snapshot Scripts + +This folder contains Hyper-V Cluster Shared Volume (CSV) + SQL Server example snapshot scripts using the Pure Storage FlashArray. **Files:** -- Hyper-V CSV w SQL Server Snapshot.ps1 +- `Hyper-V CSV w SQL Server Snapshot.ps1` - CSV clone with disk resignature; presents the clone back to the originating Hyper-V cluster as a second CSV +- `HyperV CrashConsistentClone.ps1` - crash-consistent snapshot clone from a production CSV to a dev/test SQL Server VM +- `HyperV-TSQL-SnapshotBackup.ps1` - SQL Server 2022 T-SQL Snapshot Backup with point-in-time recovery on a Hyper-V CSV + +--- + +## Hyper-V CSV w SQL Server Snapshot.ps1 + +**Scenario:** + +This script clones a Hyper-V Cluster Shared Volume (CSV) using a crash-consistent snapshot and presents it back to the originating Hyper-V cluster as a second CSV. The target VM spans two CSVs: the first holds the VM OS disk (untouched by this script) and the second holds the SQL Server user database VHDX. Only the database CSV is swapped - the database VHDX is hot-removed, the CSV is overwritten with a fresh clone from the FlashArray, and the VHDX is hot-added back to the running VM. + +**Prerequisites:** + +1. A pre-created target volume on the FlashArray, connected to the target Hyper-V host, must be the same size as the source CSV volume. +2. The Failover Cluster PowerShell module must be installed: `Add-WindowsFeature RSAT-Clustering-PowerShell` +3. PowerShell Remoting (WinRM) must be enabled on the Hyper-V host and inside the target VM guest. +4. The target VM must have a SCSI controller - hot-add/remove requires SCSI (not IDE). +5. The `SqlServer` module must be installed in the guest: `Install-Module SqlServer` +6. The credential used must have Administrator rights on the Hyper-V host, cluster nodes, and the VM guest, plus FlashArray array admin or storage admin role. + +**Important Usage Notes:** + +The clone carries the same disk signature as the source CSV, so it must be resignatured on the target host while offline before it can be used as a CSV. This is handled automatically via DISKPART before the cluster resource is started. + +--- - -