Skip to content

Commit 2af23e2

Browse files
committed
T-SQL Snapshot w VMFS - V1
1 parent f8a8af9 commit 2af23e2

File tree

2 files changed

+390
-0
lines changed

2 files changed

+390
-0
lines changed
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
##############################################################################################################################
2+
# Point In Time Recovery - Using SQL Server 2022's T-SQL Snapshot Backup feature w. VMFS/VMDK datastore/files.
3+
#
4+
# Scenario:
5+
# Perform a point in time restore using SQL Server 2022's T-SQL Snapshot Backup
6+
# feature. This uses a FlashArray snapshot as the base of the restore, then restores
7+
# a log backup.
8+
#
9+
# IMPORTANT NOTE:
10+
# This example script is built for 1 database spanned across two VMDK files/volumes
11+
# from a single datastore.
12+
#
13+
# The granularity or unit of work for this workflow is a VMDK file(s) and the entirety
14+
# of its contents. Therefore, everything in the VMDK file(s) including files for other
15+
# databases will be impacted/overwritten.
16+
#
17+
# This example will need to be adapted if you wish to support multiple databases on
18+
# the same set of VMDK(s).
19+
#
20+
# Prerequisites:
21+
# 1. PowerShell Modules: dbatools & PureStoragePowerShellSDK2
22+
#
23+
# Usage Notes:
24+
# * Each section of the script is meant to be run individually, one after another.
25+
# * The script is NOT meant to be executed all at once.
26+
#
27+
# Disclaimer:
28+
# This example script is provided AS-IS and is meant to be a building
29+
# block to be adapted to fit an individual organization's
30+
# infrastructure.
31+
##############################################################################################################################
32+
33+
34+
35+
# Import powershell modules
36+
Import-Module dbatools
37+
Import-Module PureStoragePowerShellSDK2
38+
39+
40+
41+
# Declare all variables
42+
# VMware variables
43+
$VIServerName = 'vcenter.example.com'
44+
$SourceDatastoreName = 'source_sql_datastore'
45+
$SourceVMDKPaths = @('source_vm/sqldata.vmdk','source_vm/sqllog.vmdk')
46+
47+
48+
49+
# FlashArray variables
50+
$ArrayName = 'flasharray1.example.com' # FlashArray FQDN
51+
$FAHostGroupName = 'FAHostGroupName' # HostGroup Name on FlashArray for the ESXi cluster
52+
$SourceVolumeName = 'volume_name' # Volume name on FlashArray containing database files
53+
$PGroupName = 'protection_group' # Name of the Protection Group on FlashArray1
54+
55+
56+
57+
# Windows/SQL Server variables
58+
$TargetSQLServer = 'target_sqlserver.example.com' # SQL Server Instance FQDN
59+
$TargetVM = 'target_sqlserver' # SQL Server VM name in VCenter
60+
$DbName = 'AdventureWorks' # Name of database
61+
$BackupShare = '\\flashblade1.example.com\backups' # File system location to write the backup metadata file
62+
$TargetDisks = @('1234c29689bc0888d32dcd2919a67z89', '1234c299721c4ba4a937552fb298a76') # The serial numbers of the Windows volume containing database files; use get-disk
63+
64+
65+
66+
# Build a PowerShell Remoting Session to the Server
67+
$SqlServerSession = New-PSSession -ComputerName $TargetSQLServer
68+
69+
70+
71+
# Build a persistent SMO connection
72+
$SqlInstance = Connect-DbaInstance -SqlInstance $TargetSQLServer -TrustServerCertificate -NonPooledConnection
73+
74+
75+
76+
# Let's get some information about our database, take note of the size
77+
Get-DbaDatabase -SqlInstance $SqlInstance -Database $DbName |
78+
Select-Object Name, SizeMB
79+
80+
81+
82+
# Connect to the FlashArray's REST API
83+
$Credential = Get-Credential -UserName "$env:USERNAME" -Message 'Enter your credential information...'
84+
$FlashArray = Connect-Pfa2Array –EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError
85+
86+
87+
88+
####
89+
# Execute our backup
90+
91+
# Freeze the database
92+
$Query = "ALTER DATABASE $DbName SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON"
93+
Invoke-DbaQuery -SqlInstance $SqlInstance -Query $Query -Verbose
94+
95+
96+
97+
# Take a snapshot of the Protection Group while the database is frozen
98+
$Snapshot = New-Pfa2ProtectionGroupSnapshot -Array $FlashArray -SourceName $PGroupName
99+
100+
101+
102+
# Take a metadata backup of the database, this will automatically unfreeze
103+
# if successful
104+
# We'll use MEDIADESCRIPTION to hold some information about our snapshot and
105+
# the flasharray its held on
106+
$BackupFile = "$BackupShare\$DbName-$(Get-Date -Format FileDateTime).bkm"
107+
108+
$Query = "BACKUP DATABASE $DbName
109+
TO DISK='$BackupFile'
110+
WITH METADATA_ONLY, MEDIADESCRIPTION='$($Snapshot.Name)|$($FlashArray.ArrayName)'"
111+
Invoke-DbaQuery -SqlInstance $SqlInstance -Query $Query -Verbose
112+
113+
###
114+
# Backup completed
115+
116+
117+
118+
###
119+
# Backup Verification
120+
121+
# Let's check out the error log to see what SQL Server thinks happened
122+
Get-DbaErrorLog -SqlInstance $SqlInstance -LogNumber 0 -After (Get-Date).AddMinutes(-15) | Format-Table
123+
124+
125+
126+
# The backup is recorded in MSDB as a Full backup with snapshot
127+
$BackupHistory = Get-DbaDbBackupHistory -SqlInstance $SqlInstance -Database $DbName -Last
128+
$BackupHistory
129+
130+
131+
132+
# Let's explore the stuff in the backup header...
133+
# Remember, VDI is just a contract saying what's in the backup matches what SQL Server thinks is in the backup.
134+
Read-DbaBackupHeader -SqlInstance $SqlInstance -Path $BackupFile
135+
136+
137+
138+
###
139+
# Take a Transaction Log backup
140+
#
141+
# NOTE: If you are testing this with a database in SIMPLE RECOVERY, there seems to be an occasional bug in
142+
# Backup-DbaDatabase that keeps a DataReader connection open. Subsequent dbatools cmdlet steps may fail.
143+
# Skip this step if your database in SIMPLE RECOVERY.
144+
$LogBackup = Backup-DbaDatabase -SqlInstance $SqlInstance -Database $DbName -Type Log -Path $BackupShare -CompressBackup
145+
146+
147+
148+
###
149+
# DEMO - Delete a table
150+
Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "SELECT TOP 10 * FROM Sales.Customer"
151+
152+
153+
154+
# Delete a table
155+
Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "DROP TABLE Sales.Customer"
156+
157+
158+
159+
# Confirm it is gone
160+
Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "SELECT TOP 10 * FROM Sales.Customer"
161+
162+
163+
164+
###
165+
# Review State of Database and backup
166+
167+
# Let's check out the state of the database, size, last full and last log
168+
Get-DbaDatabase -SqlInstance $SqlInstance -Database $DbName |
169+
Select-Object Name, Size, LastFullBackup, LastLogBackup
170+
171+
172+
173+
# We can get the snapshot name from the $Snapshot variable above, but what if we didn't know this ahead of time?
174+
# We can also get the snapshot name from the MEDIADESCRIPTION in the backup file.
175+
$Query = "RESTORE LABELONLY FROM DISK = '$BackupFile'"
176+
$Labels = Invoke-DbaQuery -SqlInstance $SqlInstance -Query $Query -Verbose
177+
$SnapshotName = (($Labels | Select-Object MediaDescription -ExpandProperty MediaDescription).Split('|'))[0]
178+
$ArrayName = (($Labels | Select-Object MediaDescription -ExpandProperty MediaDescription).Split('|'))[1]
179+
180+
181+
182+
###
183+
# Start the Restore Process
184+
185+
# Connect to vCenter
186+
$VIServer = Connect-VIServer -Server $VIServerName -Protocol https -Credential $Credential
187+
$TargetSQLServerVM = Get-VM -Server $VIServer -Name $TargetVM
188+
$VMESXiHost = Get-VMhost -VM $TargetSQLServerVM
189+
190+
191+
192+
# Create a new volume from the selected snapshot of the source
193+
$SnapshotSuffix = (Get-Date).ToString("yyyyMMdd-HHmmss")
194+
$NewClonedVolumeName = "$($SourceVolumeName)-clone-$($SnapshotSuffix)"
195+
$SnapshotSourceVolumeName = $SnapshotName + ".$SourceVolumeName"
196+
New-Pfa2Volume -Array $FlashArray -Name $NewClonedVolumeName -SourceName $SnapshotSourceVolumeName -Overwrite $true
197+
198+
199+
200+
# Present the new volume to the ESXi host group
201+
New-Pfa2Connection -Array $FlashArray -HostGroupName $FAHostGroupName -VolumeName $NewClonedVolumeName
202+
203+
204+
205+
# ESXi host must now rescan storage
206+
Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VMESXiHost
207+
208+
209+
210+
# Connect to EsxCli
211+
$EsxCli = Get-EsxCli -VMHost $VMESXiHost
212+
213+
214+
215+
### Diagnostic
216+
# Retrieve a list of the snapshots that have been presented to the host (our cloned volume should be present)
217+
# $snapInfo = $EsxCli.storage.vmfs.snapshot.list()
218+
# $snapInfo | where-object { ($_.VolumeName -match $SourceDatastoreName) }
219+
# $snapInfo
220+
221+
222+
223+
# Resignature the cloned datastore
224+
$EsxCli.storage.vmfs.snapshot.resignature($SourceDatastoreName)
225+
226+
227+
228+
# Find the newly resignatured datastore name
229+
# NOTE:
230+
# After a datastore is resignatured, its name will be "snap-[GUID chars]-[original DS name]"
231+
# This is why the wildcard match below is needed.
232+
$clonedDatastore = (Get-Datastore | ? { $_.name -match 'snap' -and $_.name -match $SourceDatastoreName })
233+
234+
while ($clonedDatastore -eq $null) {
235+
# We may have to wait a little bit before the datastore is fully operational
236+
Start-Sleep -Seconds 5
237+
$clonedDatastore = (Get-Datastore | Where-Object { $_.name -match 'snap' -and $_.name -match $SourceDatastoreName })
238+
}
239+
240+
241+
242+
# Must rescan storage again so ESXi hosts(s) can see the new cloned datastore
243+
Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VMESXiHost
244+
245+
246+
247+
########################################
248+
# Prepare SQL Server & Windows for the
249+
# snapshot overlay operation
250+
########################################
251+
# Offline the database, which we'd have to do anyway if we were restoring a full backup
252+
$Query = "ALTER DATABASE $DbName SET OFFLINE WITH ROLLBACK IMMEDIATE"
253+
Invoke-DbaQuery -SqlInstance $SqlInstance -Database master -Query $Query
254+
255+
256+
257+
# Offline the volume(s) in Windows
258+
Foreach ($TargetDisk in $TargetDisks) {
259+
Invoke-Command -Session $SqlServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDisk } | Set-Disk -IsOffline $True }
260+
}
261+
262+
263+
264+
# Remove the original VMDK(s), within the original datastore
265+
Foreach ($SourceVMDKPath in $SourceVMDKPaths) {
266+
$harddisk = Get-HardDisk -VM $TargetSQLServerVM | ? { $_.FileName -match $SourceVMDKPath }
267+
Remove-HardDisk -HardDisk $harddisk -Confirm:$false -DeletePermanently
268+
}
269+
270+
271+
272+
# Attach the new VMDK(s) from the newly cloned datastore back to the target VM
273+
Foreach ($SourceVMDKPath in $SourceVMDKPaths) {
274+
$newlyAttachedDisk = New-HardDisk -VM $TargetSQLServerVM -DiskPath "[$($clonedDatastore.Name)] $SourceVMDKPath"
275+
}
276+
277+
278+
279+
# Online the volume(s) in Windows
280+
Foreach ($TargetDisk in $TargetDisks) {
281+
Invoke-Command -Session $SqlServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDisk } | Set-Disk -IsOffline $False }
282+
}
283+
284+
285+
286+
# Restore the database with no recovery, which means we can restore LOG native SQL Server backups
287+
$Query = "RESTORE DATABASE $DbName FROM DISK = '$BackupFile' WITH METADATA_ONLY, REPLACE, NORECOVERY"
288+
Invoke-DbaQuery -SqlInstance $SqlInstance -Database master -Query $Query -Verbose
289+
290+
291+
292+
# Let's check the current state of the database...its RESTORING
293+
Get-DbaDbState -SqlInstance $SqlInstance -Database $DbName
294+
295+
296+
297+
# Restore the log backup.
298+
Restore-DbaDatabase -SqlInstance $SqlInstance -Database $DbName -Path $LogBackup.BackupPath -NoRecovery -Continue
299+
300+
301+
302+
# Online the database
303+
$Query = "RESTORE DATABASE $DbName WITH RECOVERY"
304+
Invoke-DbaQuery -SqlInstance $SqlInstance -Database master -Query $Query
305+
306+
307+
308+
# Verify Restore
309+
Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "SELECT TOP 10 * FROM dbo.Recipes"
310+
311+
312+
313+
#########################
314+
# Begin Clean Up Steps
315+
#########################
316+
$destinationDatastore = Get-Datastore -Name $SourceDatastoreName
317+
318+
319+
320+
# Perform Storage vMotion to move the new VMDK disk(s) to the original source datastore. Should be fast
321+
# thanks to XCOPY
322+
Foreach ($SourceVMDKPath in $SourceVMDKPaths) {
323+
$newlyAttachedDisk = Get-HardDisk -VM $TargetSQLServerVM | ? { $_.FileName -match $SourceVMDKPath }
324+
Move-HardDisk -HardDisk $newlyAttachedDisk -Datastore $destinationDatastore -Confirm:$false
325+
}
326+
327+
328+
329+
# Now that the VMDKs have been moved back to the primary datastore, we can remove the temporary cloned
330+
# datastore - this can take a min or two.
331+
# First, removing from VCenter
332+
Remove-Datastore -Datastore $clonedDatastore -VMHost $VMESXiHost -Confirm:$false
333+
334+
335+
336+
# On FlashArray, disconnect the cloned volume from the ESXi cluster
337+
Remove-Pfa2Connection -Array $FlashArray -HostGroupName $FAHostGroupName -VolumeName $NewClonedVolumeName
338+
339+
340+
341+
# On FlashArray, destroy the cloned volume
342+
Remove-Pfa2Volume -Array $FlashArray -Name $NewClonedVolumeName
343+
344+
345+
346+
# Clean up
347+
Remove-PSSession $SqlServerSession
348+
349+
350+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Point In Time Recovery - Using SQL Server 2022's T-SQL Snapshot Backup feature w. VMFS/VMDK datastore/files.
2+
3+
4+
<!-- wp:separator -->
5+
<hr class="wp-block-separator"/>
6+
<!-- /wp:separator -->
7+
8+
# Scenario:
9+
Perform a point in time restore using SQL Server 2022's T-SQL Snapshot Backup feature. This uses a FlashArray snapshot as the base of the restore, then restores a log backup.
10+
11+
# IMPORTANT NOTE:
12+
This example script is built for 1 database spanned across two VMDK files/volumes from a single datastore.
13+
14+
The granularity or unit of work for this workflow is a VMDK file(s) and the entirety of its contents. Therefore, everything in the VMDK file(s) including files for other databases will be impacted/overwritten.
15+
16+
This example will need to be adapted if you wish to support multiple databases on the same set of VMDK(s).
17+
18+
# Prerequisites:
19+
1. PowerShell Modules: dbatools & PureStoragePowerShellSDK2
20+
21+
# Usage Notes:
22+
Each section of the script is meant to be run one after the other. The script is not meant to be executed all at once.
23+
24+
<!-- wp:separator -->
25+
<hr class="wp-block-separator"/>
26+
<!-- /wp:separator -->
27+
28+
# Disclaimer:
29+
This example script is provided AS-IS and is meant to be a building block to be adapted to fit an individual organization's infrastructure.
30+
<BR><BR>
31+
_PLEASE_ do not save your passwords in cleartext here.
32+
Use NTFS secured, encrypted files or whatever else -- never cleartext!
33+
<BR><BR>
34+
We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository.
35+
36+
<!-- wp:separator -->
37+
<hr class="wp-block-separator"/>
38+
<!-- /wp:separator -->
39+
40+
_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._

0 commit comments

Comments
 (0)