mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 02:49:02 -04:00
Add NTFS snapshot testing on Windows using isolated VHD volumes
This commit is contained in:
parent
9c985612ea
commit
0e94163371
3 changed files with 324 additions and 3 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
|
@ -78,7 +78,9 @@ jobs:
|
||||||
install: mingw-w64-x86_64-gcc autoconf automake make
|
install: mingw-w64-x86_64-gcc autoconf automake make
|
||||||
- run: autoreconf -i
|
- run: autoreconf -i
|
||||||
- run: ./configure --enable-warning-as-error
|
- run: ./configure --enable-warning-as-error
|
||||||
- run: make all dist check
|
- run: make all
|
||||||
|
- run: make snapcheck_ntfs
|
||||||
|
- run: make dist check
|
||||||
build-freebsd:
|
build-freebsd:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
||||||
28
Makefile.am
28
Makefile.am
|
|
@ -178,6 +178,7 @@ EXTRA_DIST = \
|
||||||
$(LIST_MAN) \
|
$(LIST_MAN) \
|
||||||
$(LIST_TXT) \
|
$(LIST_TXT) \
|
||||||
test/test-snap.conf \
|
test/test-snap.conf \
|
||||||
|
test/snapcheck-ntfs.ps1 \
|
||||||
test/test-par1.conf \
|
test/test-par1.conf \
|
||||||
test/test-par2.conf \
|
test/test-par2.conf \
|
||||||
test/test-par3.conf \
|
test/test-par3.conf \
|
||||||
|
|
@ -340,6 +341,7 @@ REALLOC_SPLIT2 = $(srcdir)/test/test-realloc-split2.conf
|
||||||
SNAP = $(srcdir)/test/test-snap.conf
|
SNAP = $(srcdir)/test/test-snap.conf
|
||||||
MSG = @echo =====
|
MSG = @echo =====
|
||||||
SNAPRAID = ./checker$(EXEEXT)
|
SNAPRAID = ./checker$(EXEEXT)
|
||||||
|
SNAPCHECK_NTFS = powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$(srcdir)/test/snapcheck-ntfs.ps1"
|
||||||
|
|
||||||
CLEANFILES += rm.exe mv.exe touch.exe
|
CLEANFILES += rm.exe mv.exe touch.exe
|
||||||
|
|
||||||
|
|
@ -355,7 +357,7 @@ if !HAVE_POSIX
|
||||||
endif # !HAVE_POSIX
|
endif # !HAVE_POSIX
|
||||||
|
|
||||||
# Not included in main check because it requires root permissions
|
# Not included in main check because it requires root permissions
|
||||||
snapcheck_btrfs: mktest checker setupcheck
|
snapcheck_btrfs: mktest$(EXEEXT) checker$(EXEEXT) setupcheck
|
||||||
sudo $(MAKE) snapcheck_btrfs_run; \
|
sudo $(MAKE) snapcheck_btrfs_run; \
|
||||||
status=$$?; \
|
status=$$?; \
|
||||||
sudo $(MAKE) snapcheck_btrfs_cleanup; \
|
sudo $(MAKE) snapcheck_btrfs_cleanup; \
|
||||||
|
|
@ -364,7 +366,7 @@ snapcheck_btrfs: mktest checker setupcheck
|
||||||
exit $$status
|
exit $$status
|
||||||
|
|
||||||
# Not included in main check because it requires root permissions
|
# Not included in main check because it requires root permissions
|
||||||
snapcheck_zfs: mktest checker setupcheck
|
snapcheck_zfs: mktest$(EXEEXT) checker$(EXEEXT) setupcheck
|
||||||
sudo $(MAKE) snapcheck_zfs_run; \
|
sudo $(MAKE) snapcheck_zfs_run; \
|
||||||
status=$$?; \
|
status=$$?; \
|
||||||
sudo $(MAKE) snapcheck_zfs_cleanup; \
|
sudo $(MAKE) snapcheck_zfs_cleanup; \
|
||||||
|
|
@ -372,6 +374,15 @@ snapcheck_zfs: mktest checker setupcheck
|
||||||
if test $$cleanup_status -ne 0; then exit $$cleanup_status; fi; \
|
if test $$cleanup_status -ne 0; then exit $$cleanup_status; fi; \
|
||||||
exit $$status
|
exit $$status
|
||||||
|
|
||||||
|
# Not included in main check because it requires administrator privileges
|
||||||
|
snapcheck_ntfs: mktest$(EXEEXT) checker$(EXEEXT) setupcheck
|
||||||
|
$(MAKE) snapcheck_ntfs_run; \
|
||||||
|
status=$$?; \
|
||||||
|
$(MAKE) snapcheck_ntfs_cleanup; \
|
||||||
|
cleanup_status=$$?; \
|
||||||
|
if test $$cleanup_status -ne 0; then exit $$cleanup_status; fi; \
|
||||||
|
exit $$status
|
||||||
|
|
||||||
snapcheck_btrfs_run:
|
snapcheck_btrfs_run:
|
||||||
mkdir bench/snap1
|
mkdir bench/snap1
|
||||||
mkdir bench/snap2
|
mkdir bench/snap2
|
||||||
|
|
@ -401,6 +412,11 @@ snapcheck_zfs_run:
|
||||||
zfs create -o mountpoint=$(CURDIR)/bench/snap3 -o snapdir=visible $$(cat bench/snap-zpool)/snap3
|
zfs create -o mountpoint=$(CURDIR)/bench/snap3 -o snapdir=visible $$(cat bench/snap-zpool)/snap3
|
||||||
$(MAKE) snapcheck_run SNAPCHECK_SHOW=snapcheck_zfs_show
|
$(MAKE) snapcheck_run SNAPCHECK_SHOW=snapcheck_zfs_show
|
||||||
|
|
||||||
|
snapcheck_ntfs_run:
|
||||||
|
$(MSG) Create NTFS storages
|
||||||
|
$(SNAPCHECK_NTFS) setup
|
||||||
|
$(MAKE) snapcheck_run SNAPCHECK_SHOW=snapcheck_ntfs_show
|
||||||
|
|
||||||
snapcheck_btrfs_show:
|
snapcheck_btrfs_show:
|
||||||
@for dir in bench/snap1 bench/snap2 bench/snap3; do \
|
@for dir in bench/snap1 bench/snap2 bench/snap3; do \
|
||||||
echo "$$dir/.snapraid:"; \
|
echo "$$dir/.snapraid:"; \
|
||||||
|
|
@ -411,6 +427,9 @@ snapcheck_zfs_show:
|
||||||
@pool=$$(cat bench/snap-zpool); \
|
@pool=$$(cat bench/snap-zpool); \
|
||||||
zfs list -t snapshot -r "$$pool"
|
zfs list -t snapshot -r "$$pool"
|
||||||
|
|
||||||
|
snapcheck_ntfs_show:
|
||||||
|
@$(SNAPCHECK_NTFS) show
|
||||||
|
|
||||||
snapcheck_run:
|
snapcheck_run:
|
||||||
$(MSG) Create some files
|
$(MSG) Create some files
|
||||||
$(TESTENV) ./mktest$(EXEEXT) generate 1 snap 3 $(CHECKCOUNT) $(CHECKSIZE)
|
$(TESTENV) ./mktest$(EXEEXT) generate 1 snap 3 $(CHECKCOUNT) $(CHECKSIZE)
|
||||||
|
|
@ -503,6 +522,11 @@ snapcheck_zfs_cleanup:
|
||||||
rm -rf bench
|
rm -rf bench
|
||||||
rm -f test.log
|
rm -f test.log
|
||||||
|
|
||||||
|
snapcheck_ntfs_cleanup:
|
||||||
|
$(MSG) Cleanup NTFS storages
|
||||||
|
$(SNAPCHECK_NTFS) cleanup
|
||||||
|
rm -f test.log
|
||||||
|
|
||||||
check-local: setupcheck
|
check-local: setupcheck
|
||||||
mkdir bench/disk1
|
mkdir bench/disk1
|
||||||
mkdir bench/disk2
|
mkdir bench/disk2
|
||||||
|
|
|
||||||
295
test/snapcheck-ntfs.ps1
Normal file
295
test/snapcheck-ntfs.ps1
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateSet("setup", "cleanup", "show")]
|
||||||
|
[string]$Action
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$root = (Get-Location).Path
|
||||||
|
$bench = Join-Path $root "bench"
|
||||||
|
|
||||||
|
|
||||||
|
function Assert-Administrator
|
||||||
|
{
|
||||||
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
|
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
||||||
|
|
||||||
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||||
|
throw "NTFS snapshot tests require Administrator privileges."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Invoke-DiskPart
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string[]]$Commands
|
||||||
|
)
|
||||||
|
|
||||||
|
$scriptPath = Join-Path $env:TEMP ("snapraid-diskpart-" + [Guid]::NewGuid().ToString() + ".txt")
|
||||||
|
|
||||||
|
try {
|
||||||
|
[System.IO.File]::WriteAllLines($scriptPath, $Commands, [System.Text.Encoding]::ASCII)
|
||||||
|
|
||||||
|
$output = & diskpart.exe /s $scriptPath 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
|
||||||
|
$output | ForEach-Object {
|
||||||
|
Write-Host $_
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exitCode -ne 0) {
|
||||||
|
throw "diskpart failed with exit code $exitCode."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Remove-Item -LiteralPath $scriptPath -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Get-MountedVolumeName
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$MountPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$mountArg = $MountPath.TrimEnd([char]92) + "\"
|
||||||
|
$output = & mountvol.exe $mountArg /L 2>$null
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
|
||||||
|
if ($exitCode -ne 0) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($line in $output) {
|
||||||
|
$value = "$line".Trim()
|
||||||
|
|
||||||
|
if ($value -match '^\\\\\?\\Volume\{[^}]+\}\\$') {
|
||||||
|
return $value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Normalize-VolumeName
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$VolumeName
|
||||||
|
)
|
||||||
|
|
||||||
|
return $VolumeName.Trim().TrimEnd([char]92)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Setup-Volume
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$Index
|
||||||
|
)
|
||||||
|
|
||||||
|
$mountPath = Join-Path $bench "snap$Index"
|
||||||
|
$vhdPath = Join-Path $bench "snap$Index.vhdx"
|
||||||
|
$markerPath = Join-Path $bench "snap$Index.volume"
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $mountPath) {
|
||||||
|
throw "Mount path '$mountPath' already exists."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $vhdPath) {
|
||||||
|
throw "VHD '$vhdPath' already exists."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $markerPath) {
|
||||||
|
throw "Volume marker '$markerPath' already exists."
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $mountPath | Out-Null
|
||||||
|
|
||||||
|
$mountForDiskPart = $mountPath.TrimEnd([char]92) + "\"
|
||||||
|
|
||||||
|
Invoke-DiskPart @(
|
||||||
|
"create vdisk file=`"$vhdPath`" maximum=1024 type=expandable",
|
||||||
|
"select vdisk file=`"$vhdPath`"",
|
||||||
|
"attach vdisk",
|
||||||
|
"convert gpt",
|
||||||
|
"create partition primary",
|
||||||
|
"format fs=ntfs quick label=SNAPRAID$Index",
|
||||||
|
"assign mount=`"$mountForDiskPart`""
|
||||||
|
)
|
||||||
|
|
||||||
|
$volumeName = Get-MountedVolumeName $mountPath
|
||||||
|
|
||||||
|
if (-not $volumeName) {
|
||||||
|
throw "Unable to resolve the volume name mounted at '$mountPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Content -LiteralPath $markerPath -Value $volumeName -Encoding ASCII
|
||||||
|
|
||||||
|
Write-Host "Mounted NTFS test volume $volumeName at $mountPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Remove-TestShadows
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$VolumeName
|
||||||
|
)
|
||||||
|
|
||||||
|
$wanted = Normalize-VolumeName $VolumeName
|
||||||
|
|
||||||
|
$shadows = @(
|
||||||
|
Get-WmiObject -Class Win32_ShadowCopy |
|
||||||
|
Where-Object {
|
||||||
|
$_.VolumeName -and ((Normalize-VolumeName $_.VolumeName) -ieq $wanted)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($shadow in $shadows) {
|
||||||
|
Write-Host "Deleting VSS snapshot $($shadow.ID) from $VolumeName"
|
||||||
|
Remove-WmiObject -InputObject $shadow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Cleanup-Volume
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$Index
|
||||||
|
)
|
||||||
|
|
||||||
|
$mountPath = Join-Path $bench "snap$Index"
|
||||||
|
$vhdPath = Join-Path $bench "snap$Index.vhdx"
|
||||||
|
$markerPath = Join-Path $bench "snap$Index.volume"
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $markerPath) {
|
||||||
|
$volumeName = (Get-Content -LiteralPath $markerPath -Raw).Trim()
|
||||||
|
|
||||||
|
if ($volumeName) {
|
||||||
|
Remove-TestShadows $volumeName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $vhdPath) {
|
||||||
|
Invoke-DiskPart @(
|
||||||
|
"select vdisk file=`"$vhdPath`"",
|
||||||
|
"detach vdisk noerr"
|
||||||
|
)
|
||||||
|
|
||||||
|
Remove-Item -LiteralPath $vhdPath -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $mountPath) {
|
||||||
|
Remove-Item -LiteralPath $mountPath -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $markerPath) {
|
||||||
|
Remove-Item -LiteralPath $markerPath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Setup
|
||||||
|
{
|
||||||
|
if (-not (Test-Path -LiteralPath $bench)) {
|
||||||
|
New-Item -ItemType Directory -Path $bench | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Setup-Volume 1
|
||||||
|
Setup-Volume 2
|
||||||
|
Setup-Volume 3
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Cleanup
|
||||||
|
{
|
||||||
|
$failed = $false
|
||||||
|
|
||||||
|
foreach ($index in 1..3) {
|
||||||
|
try {
|
||||||
|
Cleanup-Volume $index
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Cleanup of NTFS test volume $index failed: $($_.Exception.Message)"
|
||||||
|
$failed = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($failed) {
|
||||||
|
throw "Cleanup of one or more NTFS test volumes failed."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $bench) {
|
||||||
|
Remove-Item -LiteralPath $bench -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Show
|
||||||
|
{
|
||||||
|
foreach ($index in 1..3) {
|
||||||
|
$mountPath = Join-Path $bench "snap$Index"
|
||||||
|
$snapshotPath = Join-Path $mountPath ".snapraid"
|
||||||
|
$markerPath = Join-Path $bench "snap$Index.volume"
|
||||||
|
|
||||||
|
Write-Host "${snapshotPath}:"
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $snapshotPath) {
|
||||||
|
Get-ChildItem -LiteralPath $snapshotPath -Force |
|
||||||
|
Sort-Object Name |
|
||||||
|
ForEach-Object {
|
||||||
|
Write-Host " $($_.Name)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host " (missing)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $markerPath) {
|
||||||
|
$volumeName = (Get-Content -LiteralPath $markerPath -Raw).Trim()
|
||||||
|
$wanted = Normalize-VolumeName $volumeName
|
||||||
|
|
||||||
|
Get-WmiObject -Class Win32_ShadowCopy |
|
||||||
|
Where-Object {
|
||||||
|
$_.VolumeName -and ((Normalize-VolumeName $_.VolumeName) -ieq $wanted)
|
||||||
|
} |
|
||||||
|
ForEach-Object {
|
||||||
|
Write-Host " VSS $($_.ID)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($Action) {
|
||||||
|
"setup" {
|
||||||
|
Assert-Administrator
|
||||||
|
Setup
|
||||||
|
}
|
||||||
|
|
||||||
|
"cleanup" {
|
||||||
|
Assert-Administrator
|
||||||
|
Cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
"show" {
|
||||||
|
Show
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
[Console]::Error.WriteLine("error: " + $_.Exception.Message)
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue