Dateien nach "/" hochladen
This commit is contained in:
@@ -0,0 +1,687 @@
|
||||
#Requires -Version 5.1
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$InstallPath = "$env:ProgramFiles\SMTPGraphRelay"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [Text.Encoding]::UTF8
|
||||
|
||||
$TaskName = "SMTPGraphRelay"
|
||||
$ConfigPath = Join-Path $InstallPath "config.json"
|
||||
$ShutdownSignalPath = Join-Path $InstallPath "shutdown.request"
|
||||
|
||||
function Write-Title {
|
||||
param([string]$Text)
|
||||
Write-Host ""
|
||||
Write-Host "==========================================================" -ForegroundColor Cyan
|
||||
Write-Host " $Text" -ForegroundColor Cyan
|
||||
Write-Host "==========================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
}
|
||||
function Write-Ok { param([string]$Text) Write-Host "[OK] $Text" -ForegroundColor Green }
|
||||
function Write-Warn { param([string]$Text) Write-Host "[WARN] $Text" -ForegroundColor Yellow }
|
||||
function Write-Fail { param([string]$Text) Write-Host "[FAIL] $Text" -ForegroundColor Red }
|
||||
function Write-Info { param([string]$Text) Write-Host "[INFO] $Text" -ForegroundColor Cyan }
|
||||
|
||||
function Get-Config {
|
||||
if (-not (Test-Path -LiteralPath $ConfigPath)) {
|
||||
throw "config.json nicht gefunden: $ConfigPath"
|
||||
}
|
||||
return Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Save-Config {
|
||||
param([Parameter(Mandatory)]$Config)
|
||||
|
||||
$tmp = "$ConfigPath.qa.tmp"
|
||||
$Config | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
||||
Move-Item -LiteralPath $tmp -Destination $ConfigPath -Force
|
||||
}
|
||||
|
||||
function Get-FullPath {
|
||||
param([string]$Value)
|
||||
|
||||
if ([IO.Path]::IsPathRooted($Value)) { return $Value }
|
||||
return Join-Path $InstallPath $Value
|
||||
}
|
||||
|
||||
function Stop-RelayGracefully {
|
||||
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if (-not $task -or $task.State -ne "Running") { return }
|
||||
|
||||
[IO.File]::WriteAllText(
|
||||
$ShutdownSignalPath,
|
||||
[DateTime]::UtcNow.ToString("o"),
|
||||
(New-Object Text.UTF8Encoding($false))
|
||||
)
|
||||
|
||||
$deadline = (Get-Date).AddSeconds(40)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
Start-Sleep -Milliseconds 250
|
||||
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if (-not $task -or $task.State -ne "Running") {
|
||||
Remove-Item $ShutdownSignalPath -Force -ErrorAction SilentlyContinue
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Write-Warn "Graceful Stop dauerte zu lange; Task wird hart gestoppt."
|
||||
Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
Remove-Item $ShutdownSignalPath -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
|
||||
function Start-Relay {
|
||||
Remove-Item $ShutdownSignalPath -Force -ErrorAction SilentlyContinue
|
||||
Start-ScheduledTask -TaskName $TaskName
|
||||
|
||||
$deadline = (Get-Date).AddSeconds(10)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
Start-Sleep -Milliseconds 250
|
||||
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if ($task -and $task.State -eq "Running") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
throw "Relay konnte nicht gestartet werden."
|
||||
}
|
||||
|
||||
function Restart-Relay {
|
||||
Stop-RelayGracefully
|
||||
Start-Relay
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
function Set-QADebug {
|
||||
param(
|
||||
[int]$DelayBeforeGraphSendSeconds = 0,
|
||||
[int]$SimulateGraphStatus = 0,
|
||||
[bool]$SimulateQueueWriteFailure = $false,
|
||||
[int]$OverridePendingCount = -1,
|
||||
[int]$OverrideFreeDiskSpaceMB = -1
|
||||
)
|
||||
|
||||
$config = Get-Config
|
||||
|
||||
$debug = [pscustomobject]@{
|
||||
Enabled = $true
|
||||
DelayBeforeGraphSendSeconds = $DelayBeforeGraphSendSeconds
|
||||
SimulateGraphStatus = $SimulateGraphStatus
|
||||
SimulateQueueWriteFailure = $SimulateQueueWriteFailure
|
||||
OverridePendingCount = $OverridePendingCount
|
||||
OverrideFreeDiskSpaceMB = $OverrideFreeDiskSpaceMB
|
||||
}
|
||||
|
||||
if ($config.PSObject.Properties.Name -contains "Debug") {
|
||||
$config.Debug = $debug
|
||||
}
|
||||
else {
|
||||
$config | Add-Member -NotePropertyName Debug -NotePropertyValue $debug
|
||||
}
|
||||
|
||||
Save-Config -Config $config
|
||||
}
|
||||
|
||||
function Restore-OriginalConfig {
|
||||
param([Parameter(Mandatory)][string]$BackupPath)
|
||||
|
||||
Copy-Item -LiteralPath $BackupPath -Destination $ConfigPath -Force
|
||||
}
|
||||
|
||||
function Convert-SecureToPlain {
|
||||
param([Security.SecureString]$Secure)
|
||||
|
||||
$ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secure)
|
||||
try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr) }
|
||||
finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr) }
|
||||
}
|
||||
|
||||
function Get-SmtpAuthForLocalTest {
|
||||
$config = Get-Config
|
||||
$required = $false
|
||||
|
||||
if ($config.Smtp.PSObject.Properties.Name -contains "RequireAuth") {
|
||||
$required = [bool]$config.Smtp.RequireAuth
|
||||
}
|
||||
|
||||
if (-not $required) {
|
||||
return [pscustomobject]@{ Username=$null; Password=$null }
|
||||
}
|
||||
|
||||
# Prüfen, ob localhost als Ausnahme eingetragen ist.
|
||||
foreach ($entry in @($config.Smtp.AllowUnauthenticatedNetworks)) {
|
||||
if ([string]$entry -in @("127.0.0.1","127.0.0.1/32","127.0.0.0/8","*")) {
|
||||
return [pscustomobject]@{ Username=$null; Password=$null }
|
||||
}
|
||||
}
|
||||
|
||||
$user = Read-Host "SMTP-Benutzer für QA-Test"
|
||||
$secure = Read-Host "SMTP-Passwort" -AsSecureString
|
||||
$plain = Convert-SecureToPlain -Secure $secure
|
||||
return [pscustomobject]@{ Username=$user; Password=$plain }
|
||||
}
|
||||
|
||||
function Read-SmtpResponse {
|
||||
param(
|
||||
[Parameter(Mandatory)][IO.StreamReader]$Reader
|
||||
)
|
||||
|
||||
$lines = New-Object Collections.Generic.List[string]
|
||||
$first = $Reader.ReadLine()
|
||||
if ($null -eq $first) { throw "SMTP-Verbindung unerwartet beendet." }
|
||||
$lines.Add($first)
|
||||
|
||||
if ($first -match '^(\d{3})-') {
|
||||
$code = $matches[1]
|
||||
while ($true) {
|
||||
$line = $Reader.ReadLine()
|
||||
if ($null -eq $line) { throw "SMTP-Verbindung unerwartet beendet." }
|
||||
$lines.Add($line)
|
||||
if ($line -match "^$code ") { break }
|
||||
}
|
||||
}
|
||||
|
||||
$status = 0
|
||||
if ($lines[0] -match '^(\d{3})') { $status = [int]$matches[1] }
|
||||
|
||||
return [pscustomobject]@{
|
||||
Code = $status
|
||||
Text = ($lines -join "`n")
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-AuthIfNeeded {
|
||||
param(
|
||||
[IO.StreamReader]$Reader,
|
||||
[IO.StreamWriter]$Writer,
|
||||
$Auth
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace([string]$Auth.Username)) { return }
|
||||
|
||||
$Writer.WriteLine("AUTH LOGIN")
|
||||
$r = Read-SmtpResponse -Reader $Reader
|
||||
if ($r.Code -ne 334) { throw "AUTH LOGIN: $($r.Text)" }
|
||||
|
||||
$Writer.WriteLine([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Auth.Username)))
|
||||
$r = Read-SmtpResponse -Reader $Reader
|
||||
if ($r.Code -ne 334) { throw "AUTH Benutzer: $($r.Text)" }
|
||||
|
||||
$Writer.WriteLine([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Auth.Password)))
|
||||
$r = Read-SmtpResponse -Reader $Reader
|
||||
if ($r.Code -ne 235) { throw "AUTH Passwort: $($r.Text)" }
|
||||
}
|
||||
|
||||
function Open-SmtpSession {
|
||||
$config = Get-Config
|
||||
$port = [int]$config.Smtp.Port
|
||||
|
||||
$client = New-Object Net.Sockets.TcpClient
|
||||
$client.Connect("127.0.0.1", $port)
|
||||
$stream = $client.GetStream()
|
||||
$stream.ReadTimeout = 10000
|
||||
$reader = New-Object IO.StreamReader($stream, [Text.Encoding]::UTF8, $true, 4096, $true)
|
||||
$writer = New-Object IO.StreamWriter($stream, [Text.Encoding]::ASCII, 4096, $true)
|
||||
$writer.NewLine = "`r`n"
|
||||
$writer.AutoFlush = $true
|
||||
|
||||
$greeting = Read-SmtpResponse -Reader $reader
|
||||
if ($greeting.Code -ne 220) { throw "Greeting: $($greeting.Text)" }
|
||||
|
||||
$writer.WriteLine("EHLO qa.local")
|
||||
$ehlo = Read-SmtpResponse -Reader $reader
|
||||
if ($ehlo.Code -ne 250) { throw "EHLO: $($ehlo.Text)" }
|
||||
|
||||
$auth = Get-SmtpAuthForLocalTest
|
||||
Invoke-AuthIfNeeded -Reader $reader -Writer $writer -Auth $auth
|
||||
|
||||
return [pscustomobject]@{
|
||||
Client = $client
|
||||
Stream = $stream
|
||||
Reader = $reader
|
||||
Writer = $writer
|
||||
Config = $config
|
||||
Auth = $auth
|
||||
}
|
||||
}
|
||||
|
||||
function Close-SmtpSession {
|
||||
param($Session)
|
||||
try { $Session.Writer.WriteLine("QUIT") } catch {}
|
||||
try { $Session.Reader.Dispose() } catch {}
|
||||
try { $Session.Writer.Dispose() } catch {}
|
||||
try { $Session.Stream.Dispose() } catch {}
|
||||
try { $Session.Client.Close() } catch {}
|
||||
if ($Session.Auth) { $Session.Auth.Password = $null }
|
||||
}
|
||||
|
||||
function Send-TestMail {
|
||||
param(
|
||||
[switch]$StopBeforeBody,
|
||||
[string]$UniqueTag = $([guid]::NewGuid().ToString("N").Substring(0,10))
|
||||
)
|
||||
|
||||
$s = Open-SmtpSession
|
||||
try {
|
||||
$from = [string]$s.Config.Graph.SenderMailbox
|
||||
$to = Read-Host "Test-Empfänger [Standard: $from]"
|
||||
if ([string]::IsNullOrWhiteSpace($to)) { $to = $from }
|
||||
|
||||
$s.Writer.WriteLine("MAIL FROM:<$from>")
|
||||
$r = Read-SmtpResponse $s.Reader
|
||||
if ($r.Code -ne 250) { return [pscustomobject]@{ Session=$s; Response=$r; QueueId=$null; Stage="MAIL" } }
|
||||
|
||||
$s.Writer.WriteLine("RCPT TO:<$to>")
|
||||
$r = Read-SmtpResponse $s.Reader
|
||||
if ($r.Code -ne 250) { return [pscustomobject]@{ Session=$s; Response=$r; QueueId=$null; Stage="RCPT" } }
|
||||
|
||||
$s.Writer.WriteLine("DATA")
|
||||
$r = Read-SmtpResponse $s.Reader
|
||||
|
||||
if ($r.Code -ne 354) {
|
||||
return [pscustomobject]@{ Session=$s; Response=$r; QueueId=$null; Stage="DATA" }
|
||||
}
|
||||
|
||||
$s.Writer.WriteLine("From: <$from>")
|
||||
$s.Writer.WriteLine("To: <$to>")
|
||||
$s.Writer.WriteLine("Subject: SMTPGraphRelay QA $UniqueTag")
|
||||
$s.Writer.WriteLine("X-SMTPGraphRelay-QA: $UniqueTag")
|
||||
$s.Writer.WriteLine("")
|
||||
|
||||
if ($StopBeforeBody) {
|
||||
return [pscustomobject]@{ Session=$s; Response=$r; QueueId=$null; Stage="DATA-OPEN"; Tag=$UniqueTag }
|
||||
}
|
||||
|
||||
$s.Writer.WriteLine("QA test $UniqueTag")
|
||||
$s.Writer.WriteLine(".")
|
||||
$final = Read-SmtpResponse $s.Reader
|
||||
|
||||
$qid = $null
|
||||
if ($final.Text -match 'queue-id=([A-Fa-f0-9]+)') { $qid = $matches[1] }
|
||||
|
||||
return [pscustomobject]@{ Session=$s; Response=$final; QueueId=$qid; Stage="FINAL"; Tag=$UniqueTag }
|
||||
}
|
||||
catch {
|
||||
Close-SmtpSession $s
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
function Get-QueuePaths {
|
||||
$config = Get-Config
|
||||
$queueRoot = Get-FullPath ([string]$config.Paths.Queue)
|
||||
return [pscustomobject]@{
|
||||
Pending = Join-Path $queueRoot "pending"
|
||||
Processing = Join-Path $queueRoot "processing"
|
||||
Failed = Get-FullPath ([string]$config.Paths.Failed)
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-QAMessage {
|
||||
param([string]$QueueId)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($QueueId)) { return }
|
||||
$paths = Get-QueuePaths
|
||||
|
||||
foreach ($dir in @($paths.Pending,$paths.Processing,$paths.Failed)) {
|
||||
foreach ($p in @(
|
||||
(Join-Path $dir "$QueueId.eml"),
|
||||
(Join-Path $dir "$QueueId.eml.json")
|
||||
)) {
|
||||
Remove-Item -LiteralPath $p -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-ForQueueMeta {
|
||||
param(
|
||||
[string]$QueueId,
|
||||
[int]$TimeoutSeconds = 15
|
||||
)
|
||||
|
||||
$paths = Get-QueuePaths
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
foreach ($dir in @($paths.Pending,$paths.Processing,$paths.Failed)) {
|
||||
$metaPath = Join-Path $dir "$QueueId.eml.json"
|
||||
if (Test-Path $metaPath) {
|
||||
try {
|
||||
$meta = Get-Content $metaPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
return [pscustomobject]@{ Directory=$dir; Meta=$meta; Path=$metaPath }
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
Start-Sleep -Milliseconds 250
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Invoke-WithTemporaryConfig {
|
||||
param(
|
||||
[Parameter(Mandatory)][scriptblock]$Configure,
|
||||
[Parameter(Mandatory)][scriptblock]$Test
|
||||
)
|
||||
|
||||
$backup = Join-Path $env:TEMP ("SMTPGraphRelay-config-qa-{0}.json" -f [guid]::NewGuid().ToString("N"))
|
||||
Copy-Item $ConfigPath $backup -Force
|
||||
|
||||
try {
|
||||
& $Configure
|
||||
Restart-Relay
|
||||
& $Test
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
Restore-OriginalConfig -BackupPath $backup
|
||||
Restart-Relay
|
||||
Write-Ok "Original-config.json wiederhergestellt."
|
||||
}
|
||||
catch {
|
||||
Write-Fail "Config-Restore/Relay-Restart fehlgeschlagen: $($_.Exception.Message)"
|
||||
}
|
||||
Remove-Item $backup -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Test-GraphStatus {
|
||||
param([int]$StatusCode)
|
||||
|
||||
Invoke-WithTemporaryConfig `
|
||||
-Configure {
|
||||
Set-QADebug -SimulateGraphStatus $StatusCode
|
||||
} `
|
||||
-Test {
|
||||
$result = Send-TestMail
|
||||
try {
|
||||
if ($result.Response.Code -ne 250 -or -not $result.QueueId) {
|
||||
Write-Fail "SMTP-Annahme fehlgeschlagen: $($result.Response.Text)"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Ok "SMTP hat Testmail angenommen: $($result.QueueId)"
|
||||
$found = Wait-ForQueueMeta -QueueId $result.QueueId -TimeoutSeconds 15
|
||||
|
||||
if (-not $found) {
|
||||
Write-Fail "Queue-Metadaten nicht gefunden."
|
||||
return
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 1
|
||||
$found = Wait-ForQueueMeta -QueueId $result.QueueId -TimeoutSeconds 5
|
||||
|
||||
if ($found -and [int]$found.Meta.RetryCount -ge 1 -and [int]$found.Meta.LastStatusCode -eq $StatusCode) {
|
||||
Write-Ok "HTTP $StatusCode korrekt als Retry behandelt (RetryCount=$($found.Meta.RetryCount))."
|
||||
}
|
||||
else {
|
||||
Write-Fail "Erwartete Retry-Metadaten für HTTP $StatusCode fehlen."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($result) {
|
||||
Close-SmtpSession $result.Session
|
||||
Remove-QAMessage $result.QueueId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Test-QueueWriteFailure {
|
||||
Invoke-WithTemporaryConfig `
|
||||
-Configure {
|
||||
Set-QADebug -SimulateQueueWriteFailure $true
|
||||
} `
|
||||
-Test {
|
||||
$result = Send-TestMail
|
||||
try {
|
||||
if ($result.Stage -eq "FINAL" -and $result.Response.Code -eq 451) {
|
||||
Write-Ok "Queue-Schreibfehler korrekt mit 451 zurückgewiesen."
|
||||
}
|
||||
else {
|
||||
Write-Fail "Erwartet 451 nach DATA, erhalten: $($result.Response.Text)"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($result) { Close-SmtpSession $result.Session }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Test-PendingBackpressure {
|
||||
$config = Get-Config
|
||||
$limit = if ($config.Queue.PSObject.Properties.Name -contains "MaxPendingMessages") { [int]$config.Queue.MaxPendingMessages } else { 5000 }
|
||||
|
||||
Invoke-WithTemporaryConfig `
|
||||
-Configure {
|
||||
Set-QADebug -OverridePendingCount $limit
|
||||
} `
|
||||
-Test {
|
||||
$result = Send-TestMail
|
||||
try {
|
||||
if ($result.Stage -eq "DATA" -and $result.Response.Code -eq 452) {
|
||||
Write-Ok "Pending-Queue-Limit korrekt vor DATA mit 452 blockiert."
|
||||
}
|
||||
else {
|
||||
Write-Fail "Erwartet 452 bei DATA, erhalten: $($result.Response.Text)"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($result) { Close-SmtpSession $result.Session }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Test-DiskBackpressure {
|
||||
Invoke-WithTemporaryConfig `
|
||||
-Configure {
|
||||
Set-QADebug -OverrideFreeDiskSpaceMB 0
|
||||
} `
|
||||
-Test {
|
||||
$result = Send-TestMail
|
||||
try {
|
||||
if ($result.Stage -eq "DATA" -and $result.Response.Code -eq 452) {
|
||||
Write-Ok "Disk-Backpressure korrekt vor DATA mit 452 blockiert."
|
||||
}
|
||||
else {
|
||||
Write-Fail "Erwartet 452 bei DATA, erhalten: $($result.Response.Text)"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($result) { Close-SmtpSession $result.Session }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Test-GracefulDuringData {
|
||||
Invoke-WithTemporaryConfig `
|
||||
-Configure {
|
||||
Set-QADebug
|
||||
} `
|
||||
-Test {
|
||||
$result = Send-TestMail -StopBeforeBody
|
||||
try {
|
||||
if ($result.Stage -ne "DATA-OPEN") {
|
||||
Write-Fail "DATA-Session konnte nicht geöffnet werden."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Info "DATA ist offen. Fordere jetzt Graceful Shutdown an..."
|
||||
[IO.File]::WriteAllText($ShutdownSignalPath, [DateTime]::UtcNow.ToString("o"))
|
||||
|
||||
Start-Sleep -Seconds 1
|
||||
$task = Get-ScheduledTask -TaskName $TaskName
|
||||
if ($task.State -eq "Running") {
|
||||
Write-Ok "Relay bleibt während aktiver DATA-Session erwartungsgemäß aktiv."
|
||||
}
|
||||
else {
|
||||
Write-Fail "Relay wurde beendet, obwohl DATA-Session noch offen war."
|
||||
return
|
||||
}
|
||||
|
||||
$result.Session.Writer.WriteLine("QA graceful DATA test")
|
||||
$result.Session.Writer.WriteLine(".")
|
||||
$final = Read-SmtpResponse $result.Session.Reader
|
||||
|
||||
if ($final.Code -eq 250) {
|
||||
Write-Ok "Laufende DATA-Mail wurde trotz Shutdown sauber angenommen."
|
||||
if ($final.Text -match 'queue-id=([A-Fa-f0-9]+)') {
|
||||
$result.QueueId = $matches[1]
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Fail "Mailabschluss während Shutdown: $($final.Text)"
|
||||
}
|
||||
|
||||
$deadline = (Get-Date).AddSeconds(15)
|
||||
do {
|
||||
Start-Sleep -Milliseconds 250
|
||||
$task = Get-ScheduledTask -TaskName $TaskName
|
||||
} while ($task.State -eq "Running" -and (Get-Date) -lt $deadline)
|
||||
|
||||
if ($task.State -ne "Running") {
|
||||
Write-Ok "Relay hat sich nach Abschluss der Session sauber beendet."
|
||||
}
|
||||
else {
|
||||
Write-Fail "Relay läuft nach Abschluss der Session weiter."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($result) {
|
||||
Close-SmtpSession $result.Session
|
||||
Remove-QAMessage $result.QueueId
|
||||
}
|
||||
Remove-Item $ShutdownSignalPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Test-GracefulDuringGraph {
|
||||
Invoke-WithTemporaryConfig `
|
||||
-Configure {
|
||||
Set-QADebug -DelayBeforeGraphSendSeconds 8 -SimulateGraphStatus 500
|
||||
} `
|
||||
-Test {
|
||||
$result = Send-TestMail
|
||||
try {
|
||||
if ($result.Response.Code -ne 250 -or -not $result.QueueId) {
|
||||
Write-Fail "SMTP-Testmail wurde nicht angenommen."
|
||||
return
|
||||
}
|
||||
|
||||
Close-SmtpSession $result.Session
|
||||
$result.Session = $null
|
||||
|
||||
$paths = Get-QueuePaths
|
||||
$processing = Join-Path $paths.Processing "$($result.QueueId).eml"
|
||||
$deadline = (Get-Date).AddSeconds(10)
|
||||
|
||||
while (-not (Test-Path $processing) -and (Get-Date) -lt $deadline) {
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
|
||||
if (-not (Test-Path $processing)) {
|
||||
Write-Fail "Mail erreichte processing nicht rechtzeitig."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Ok "Mail ist in processing / künstlicher Graph-Verzögerung."
|
||||
[IO.File]::WriteAllText($ShutdownSignalPath, [DateTime]::UtcNow.ToString("o"))
|
||||
|
||||
Start-Sleep -Seconds 1
|
||||
$task = Get-ScheduledTask -TaskName $TaskName
|
||||
|
||||
if ($task.State -eq "Running") {
|
||||
Write-Ok "Relay wartet während aktivem Queue-/Graph-Worker."
|
||||
}
|
||||
else {
|
||||
Write-Fail "Relay wurde zu früh beendet."
|
||||
}
|
||||
|
||||
$deadline = (Get-Date).AddSeconds(20)
|
||||
do {
|
||||
Start-Sleep -Milliseconds 250
|
||||
$task = Get-ScheduledTask -TaskName $TaskName
|
||||
} while ($task.State -eq "Running" -and (Get-Date) -lt $deadline)
|
||||
|
||||
if ($task.State -ne "Running") {
|
||||
Write-Ok "Relay hat Worker auslaufen lassen und sich danach beendet."
|
||||
}
|
||||
else {
|
||||
Write-Fail "Graceful Shutdown während Queue-/Graph-Worker dauerte zu lange."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($result -and $result.Session) { Close-SmtpSession $result.Session }
|
||||
if ($result) { Remove-QAMessage $result.QueueId }
|
||||
Remove-Item $ShutdownSignalPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Clear-QADebug {
|
||||
$config = Get-Config
|
||||
if ($config.PSObject.Properties.Name -contains "Debug") {
|
||||
$config.PSObject.Properties.Remove("Debug")
|
||||
Save-Config $config
|
||||
Restart-Relay
|
||||
Write-Ok "Debug-/QA-Block aus config.json entfernt."
|
||||
}
|
||||
else {
|
||||
Write-Ok "Kein Debug-/QA-Block vorhanden."
|
||||
}
|
||||
}
|
||||
|
||||
if ($PSVersionTable.PSEdition -ne "Desktop" -or $PSVersionTable.PSVersion.Major -ne 5) {
|
||||
throw "Dieses QA-Script muss mit Windows PowerShell 5.1 ausgeführt werden."
|
||||
}
|
||||
|
||||
while ($true) {
|
||||
Clear-Host
|
||||
Write-Title "SMTPGraphRelay - Failure Mode QA"
|
||||
|
||||
Write-Warn "Dieses Werkzeug erzeugt absichtlich Fehlerzustände."
|
||||
Write-Host "Die Original-config.json wird für jeden Test gesichert und danach wiederhergestellt."
|
||||
Write-Host ""
|
||||
Write-Host " [1] Graph HTTP 429 / Retry testen"
|
||||
Write-Host " [2] Graph HTTP 500 / Retry testen"
|
||||
Write-Host " [3] Queue-Schreibfehler / SMTP 451 testen"
|
||||
Write-Host " [4] Pending-Queue-Backpressure / SMTP 452 testen"
|
||||
Write-Host " [5] Disk-Backpressure / SMTP 452 testen"
|
||||
Write-Host " [6] Graceful Shutdown während SMTP DATA testen"
|
||||
Write-Host " [7] Graceful Shutdown während Queue/Graph testen"
|
||||
Write-Host " [8] QA-/Debug-Block aus Config entfernen"
|
||||
Write-Host " [0] Beenden"
|
||||
Write-Host ""
|
||||
|
||||
$choice = Read-Host "Auswahl"
|
||||
|
||||
try {
|
||||
switch ($choice) {
|
||||
"1" { Test-GraphStatus -StatusCode 429 }
|
||||
"2" { Test-GraphStatus -StatusCode 500 }
|
||||
"3" { Test-QueueWriteFailure }
|
||||
"4" { Test-PendingBackpressure }
|
||||
"5" { Test-DiskBackpressure }
|
||||
"6" { Test-GracefulDuringData }
|
||||
"7" { Test-GracefulDuringGraph }
|
||||
"8" { Clear-QADebug }
|
||||
"0" { break }
|
||||
default { Write-Warn "Ungültige Auswahl." }
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Fail $_.Exception.Message
|
||||
}
|
||||
|
||||
if ($choice -ne "0") {
|
||||
Write-Host ""
|
||||
Read-Host "Enter drücken"
|
||||
}
|
||||
|
||||
if ($choice -eq "0") { break }
|
||||
}
|
||||
Reference in New Issue
Block a user