mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
158 lines
7.6 KiB
Go
158 lines
7.6 KiB
Go
//go:build linux
|
|
|
|
package scenario
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/client"
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/contract"
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/fixture"
|
|
)
|
|
|
|
type transferExecution struct {
|
|
client *client.Client
|
|
serverID uint64
|
|
root fixture.AgentRoot
|
|
residueScope transferResidueScope
|
|
sentinels transferSentinels
|
|
}
|
|
|
|
func (execution transferExecution) run(ctx context.Context, assertions *AssertionSet, fault contract.Fault) (TransferEvidence, error) {
|
|
payload, err := fixture.NewPayload(contract.DefaultSeed, contract.TransferBytes)
|
|
if err != nil {
|
|
return TransferEvidence{}, err
|
|
}
|
|
stableDigest, err := fixture.VerifyPayload(payload.Reader(), contract.TransferBytes)
|
|
if err != nil {
|
|
return TransferEvidence{}, err
|
|
}
|
|
warmupEvidence, err := execution.runWarmup(ctx)
|
|
if err != nil {
|
|
return TransferEvidence{}, fmt.Errorf("transfer warm-up: %w", err)
|
|
}
|
|
quiescenceDeadline, err := confirmTransferQuiescence(ctx, execution.residueScope)
|
|
if err != nil {
|
|
return TransferEvidence{}, err
|
|
}
|
|
warmupEvidence.deadline = quiescenceDeadline
|
|
assertions.Record("small real upload and download warm-up precedes event and deadline quiescence", warmupEvidence.valid(), fmt.Sprintf("completion_event=download_response upload_bytes=%d download_bytes=%d sha256=%s duration=%s deadline_remaining=%s", warmupEvidence.uploadBytes, warmupEvidence.downloadBytes, warmupEvidence.sha256, warmupEvidence.duration, warmupEvidence.deadline))
|
|
|
|
uploadPath, err := execution.root.Path("measured/nested/upload.bin")
|
|
if err != nil {
|
|
return TransferEvidence{}, err
|
|
}
|
|
heapProbe := fixture.NewRetainedHeapProbe()
|
|
uploadEvidence, uploadURL, uploadErr := execution.upload(ctx, uploadPath, payload, stableDigest, fault)
|
|
if fault.String() == "transfer-hash" {
|
|
return execution.finishHashFault(ctx, assertions, uploadPath, warmupEvidence, uploadErr)
|
|
}
|
|
if uploadErr != nil {
|
|
return TransferEvidence{}, uploadErr
|
|
}
|
|
assertions.Record("exact 100MiB upload has size mode SHA and create_dirs", uploadEvidence.validUpload(stableDigest), uploadEvidence.details())
|
|
|
|
downloadEvidence, downloadURL, downloadErr := execution.download(ctx, uploadPath)
|
|
if downloadErr != nil {
|
|
return TransferEvidence{}, downloadErr
|
|
}
|
|
assertions.Record("exact 100MiB download has equal nonempty SHA", downloadEvidence.validDownload(stableDigest), downloadEvidence.details())
|
|
retainedHeapBytes := heapProbe.RetainedBytes()
|
|
|
|
uploadReplayErr := execution.replayUpload(ctx, uploadURL)
|
|
uploadReplayRejected := isTransferHTTPError(uploadReplayErr, 401, "already-used")
|
|
assertions.Record("upload token replay is typed unauthorized", uploadReplayRejected, errorText(uploadReplayErr))
|
|
|
|
downloadReplayErr := execution.replayDownload(ctx, downloadURL)
|
|
downloadReplayRejected := isTransferHTTPError(downloadReplayErr, 401, "")
|
|
assertions.Record("download token replay is typed unauthorized", downloadReplayRejected, errorText(downloadReplayErr))
|
|
oversizeErr := execution.probeOversize(ctx)
|
|
oversizeRejected := isTransferHTTPError(oversizeErr, 413, "transfer cap")
|
|
assertions.Record("100MiB plus one upload is typed too large", oversizeRejected, errorText(oversizeErr))
|
|
|
|
if _, err := confirmTransferQuiescence(ctx, execution.residueScope); err != nil {
|
|
return TransferEvidence{}, err
|
|
}
|
|
residue, err := transferResidue(execution.residueScope)
|
|
if err != nil {
|
|
return TransferEvidence{}, err
|
|
}
|
|
agentResidue, dashboardResidue := countTransferResidue(residue)
|
|
assertions.Record("Dashboard spool and Agent temp residue are zero", agentResidue == 0 && dashboardResidue == 0, fmt.Sprintf("agent_temp=%d dashboard_spool=%d", agentResidue, dashboardResidue))
|
|
sentinelsUnchanged, sentinelErr := execution.sentinels.unchanged()
|
|
assertions.Record("outside-root sentinels remain unchanged", sentinelErr == nil && sentinelsUnchanged, errorText(sentinelErr))
|
|
|
|
evidence := TransferEvidence{
|
|
WarmupUploadBytes: warmupEvidence.uploadBytes, WarmupDownloadBytes: warmupEvidence.downloadBytes,
|
|
WarmupSHA256: warmupEvidence.sha256, WarmupDuration: warmupEvidence.duration,
|
|
WarmupDeadlineRemaining: warmupEvidence.deadline, WarmupQuiescent: true,
|
|
UploadBytes: uploadEvidence.bytes, DownloadBytes: downloadEvidence.bytes,
|
|
UploadSHA256: uploadEvidence.sha256, DownloadSHA256: downloadEvidence.sha256,
|
|
UploadChunks: uploadEvidence.chunks, DownloadChunks: downloadEvidence.chunks,
|
|
UploadDuration: uploadEvidence.duration, DownloadDuration: downloadEvidence.duration,
|
|
RetainedHeapBytes: retainedHeapBytes, Mode: "0640", CreateDirs: true,
|
|
UploadReplayRejected: uploadReplayRejected, DownloadReplayRejected: downloadReplayRejected, OversizeRejected: oversizeRejected,
|
|
AgentTempResidue: agentResidue, DashboardSpoolResidue: dashboardResidue,
|
|
OutsideRootSentinelsUnchanged: sentinelsUnchanged,
|
|
}
|
|
heapErr := evidence.Validate()
|
|
assertions.Record("retained live heap stays within 16MiB", !errors.Is(heapErr, ErrTransferEvidenceHeapBudget), fmt.Sprintf("retained_heap_bytes=%d", evidence.RetainedHeapBytes))
|
|
if err := evidence.Validate(); err != nil {
|
|
return evidence, err
|
|
}
|
|
return evidence, nil
|
|
}
|
|
|
|
func (execution transferExecution) finishHashFault(ctx context.Context, assertions *AssertionSet, uploadPath fixture.AgentPath, warmup transferWarmupEvidence, uploadErr error) (TransferEvidence, error) {
|
|
assertions.Record("transfer-hash rejects upload with typed 502", isTransferHTTPError(uploadErr, 502, "sha256 mismatch"), errorText(uploadErr))
|
|
_, statErr := os.Stat(uploadPath.String())
|
|
assertions.Record("transfer-hash leaves target absent", errors.Is(statErr, os.ErrNotExist), errorText(statErr))
|
|
if _, err := confirmTransferQuiescence(ctx, execution.residueScope); err != nil {
|
|
return TransferEvidence{}, err
|
|
}
|
|
residue, err := transferResidue(execution.residueScope)
|
|
if err != nil {
|
|
return TransferEvidence{}, err
|
|
}
|
|
agentResidue, dashboardResidue := countTransferResidue(residue)
|
|
assertions.Record("Dashboard spool and Agent temp residue are zero", agentResidue == 0 && dashboardResidue == 0, fmt.Sprintf("agent_temp=%d dashboard_spool=%d", agentResidue, dashboardResidue))
|
|
unchanged, sentinelErr := execution.sentinels.unchanged()
|
|
assertions.Record("outside-root sentinels remain unchanged", sentinelErr == nil && unchanged, errorText(sentinelErr))
|
|
return TransferEvidence{
|
|
WarmupUploadBytes: warmup.uploadBytes, WarmupDownloadBytes: warmup.downloadBytes,
|
|
WarmupSHA256: warmup.sha256, WarmupDuration: warmup.duration,
|
|
WarmupDeadlineRemaining: warmup.deadline, WarmupQuiescent: true,
|
|
AgentTempResidue: agentResidue, DashboardSpoolResidue: dashboardResidue, OutsideRootSentinelsUnchanged: unchanged,
|
|
}, ErrTransferHashFault
|
|
}
|
|
|
|
func isTransferHTTPError(err error, status int, text string) bool {
|
|
var httpError *client.HTTPError
|
|
return errors.As(err, &httpError) && httpError.StatusCode == status && (text == "" || strings.Contains(httpError.Message, text))
|
|
}
|
|
|
|
type transferPathEvidence struct {
|
|
bytes uint64
|
|
sha256 string
|
|
chunks uint64
|
|
duration time.Duration
|
|
mode os.FileMode
|
|
}
|
|
|
|
func (evidence transferPathEvidence) details() string {
|
|
return fmt.Sprintf("bytes=%d sha256=%s chunks=%d duration=%s mode=%04o", evidence.bytes, evidence.sha256, evidence.chunks, evidence.duration, evidence.mode.Perm())
|
|
}
|
|
|
|
func (evidence transferPathEvidence) validUpload(want fixture.PayloadDigest) bool {
|
|
return evidence.validDownload(want) && evidence.mode.Perm() == 0o640
|
|
}
|
|
|
|
func (evidence transferPathEvidence) validDownload(want fixture.PayloadDigest) bool {
|
|
return evidence.bytes == contract.TransferBytes && evidence.sha256 != "" && evidence.sha256 == want.Hex()
|
|
}
|