mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
214 lines
9.5 KiB
Go
214 lines
9.5 KiB
Go
package evidence
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/contract"
|
|
)
|
|
|
|
type transferEvidence struct {
|
|
WarmupUploadBytes uint64 `json:"warmup_upload_bytes"`
|
|
WarmupDownloadBytes uint64 `json:"warmup_download_bytes"`
|
|
WarmupSHA256 string `json:"warmup_sha256"`
|
|
WarmupDuration time.Duration `json:"warmup_duration"`
|
|
WarmupDeadlineRemaining time.Duration `json:"warmup_deadline_remaining"`
|
|
WarmupQuiescent bool `json:"warmup_quiescent"`
|
|
UploadBytes uint64 `json:"upload_bytes"`
|
|
DownloadBytes uint64 `json:"download_bytes"`
|
|
UploadSHA256 string `json:"upload_sha256"`
|
|
DownloadSHA256 string `json:"download_sha256"`
|
|
UploadChunks uint64 `json:"upload_chunks"`
|
|
DownloadChunks uint64 `json:"download_chunks"`
|
|
UploadDuration time.Duration `json:"upload_duration"`
|
|
DownloadDuration time.Duration `json:"download_duration"`
|
|
RetainedHeapBytes uint64 `json:"retained_heap_bytes"`
|
|
Mode string `json:"mode"`
|
|
CreateDirs bool `json:"create_dirs"`
|
|
UploadReplayRejected bool `json:"upload_replay_rejected"`
|
|
DownloadReplayRejected bool `json:"download_replay_rejected"`
|
|
OversizeRejected bool `json:"oversize_rejected"`
|
|
AgentTempResidue int `json:"agent_temp_residue"`
|
|
DashboardSpoolResidue int `json:"dashboard_spool_residue"`
|
|
OutsideRootSentinelsUnchanged bool `json:"outside_root_sentinels_unchanged"`
|
|
}
|
|
|
|
type transferArtifact struct {
|
|
Scenario string `json:"scenario"`
|
|
Fault string `json:"fault,omitempty"`
|
|
Passed bool `json:"passed"`
|
|
CleanupOK bool `json:"cleanup_ok"`
|
|
Error string `json:"error,omitempty"`
|
|
Evidence transferEvidence `json:"evidence"`
|
|
}
|
|
|
|
type reconnectObservation struct {
|
|
ServerID uint64
|
|
UUID string
|
|
OldGeneration uint64
|
|
NewGeneration uint64
|
|
DisconnectAt time.Time
|
|
ReconnectAt time.Time
|
|
TaskIDs []uint64
|
|
ResultIDs []uint64
|
|
PostReconnect bool
|
|
AgentRestarted bool
|
|
}
|
|
|
|
type listenerIdentity struct {
|
|
Address string
|
|
Inode uint64
|
|
}
|
|
|
|
type fixtureIdentity struct {
|
|
WorkspaceRoot string
|
|
ConfigPath string
|
|
DatabasePath string
|
|
BinaryPath string
|
|
HTTP listenerIdentity
|
|
Receipt listenerIdentity
|
|
HTTPS listenerIdentity
|
|
}
|
|
|
|
type runtimeIdentity struct {
|
|
Generation uint64
|
|
PID int
|
|
ProcessGroupID int
|
|
}
|
|
|
|
type receiptEvent struct {
|
|
Sequence uint64 `json:"sequence"`
|
|
DashboardGeneration uint64 `json:"dashboard_generation"`
|
|
GateGeneration uint64 `json:"gate_generation"`
|
|
ServerID uint64 `json:"server_id"`
|
|
TaskID uint64 `json:"task_id"`
|
|
TaskType uint64 `json:"task_type"`
|
|
Kind string `json:"kind"`
|
|
}
|
|
|
|
type receiptPair struct {
|
|
Task receiptEvent `json:"task"`
|
|
Result receiptEvent `json:"result"`
|
|
}
|
|
|
|
type cleanupRecord struct {
|
|
Name string `json:"name"`
|
|
PID int `json:"pid"`
|
|
Forced bool `json:"forced"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
type cleanupReceipt struct {
|
|
Passed bool `json:"passed"`
|
|
Forced bool `json:"forced"`
|
|
Processes []cleanupRecord `json:"processes"`
|
|
}
|
|
|
|
type reconnectEvidence struct {
|
|
Fixture struct {
|
|
Dashboard fixtureIdentity `json:"dashboard"`
|
|
AgentRoot string `json:"agent_root"`
|
|
AgentConfigPath string `json:"agent_config_path"`
|
|
AgentBinaryPath string `json:"agent_binary_path"`
|
|
} `json:"fixture"`
|
|
Runtime struct {
|
|
DashboardBefore runtimeIdentity `json:"dashboard_before"`
|
|
DashboardAfter runtimeIdentity `json:"dashboard_after"`
|
|
AgentBefore runtimeIdentity `json:"agent_before"`
|
|
AgentAfter runtimeIdentity `json:"agent_after"`
|
|
StateGenerationBeforeAgentRestart uint64 `json:"state_generation_before_agent_restart"`
|
|
StateGenerationAfterAgentRestart uint64 `json:"state_generation_after_agent_restart"`
|
|
} `json:"runtime"`
|
|
Identity struct {
|
|
ServerID uint64 `json:"server_id"`
|
|
UUID string `json:"uuid"`
|
|
DashboardConfigUnchanged bool `json:"dashboard_config_unchanged"`
|
|
AgentConfigUnchanged bool `json:"agent_config_unchanged"`
|
|
DashboardFixtureUnchanged bool `json:"dashboard_fixture_unchanged"`
|
|
ClientsRecreated bool `json:"clients_recreated"`
|
|
BootstrapRecreated bool `json:"bootstrap_recreated"`
|
|
} `json:"identity"`
|
|
Lifecycle struct {
|
|
DisconnectAt time.Time `json:"disconnect_at"`
|
|
ReconnectAt time.Time `json:"reconnect_at"`
|
|
ReconnectInterval time.Duration `json:"reconnect_interval"`
|
|
DashboardReceipts []receiptPair `json:"dashboard_receipts"`
|
|
AgentReceipts []receiptPair `json:"agent_receipts"`
|
|
StaleGenerationReceipts int `json:"stale_generation_receipts"`
|
|
DuplicateTaskIDs int `json:"duplicate_task_ids"`
|
|
LostResultIDs int `json:"lost_result_ids"`
|
|
OutsideRootSentinelUnchanged bool `json:"outside_root_sentinel_unchanged"`
|
|
} `json:"lifecycle"`
|
|
Observation reconnectObservation `json:"observation"`
|
|
AgentCleanup cleanupReceipt `json:"agent_cleanup"`
|
|
DashboardCleanup cleanupReceipt `json:"dashboard_cleanup"`
|
|
}
|
|
|
|
type reconnectArtifact struct {
|
|
Scenario string `json:"scenario"`
|
|
Fault string `json:"fault,omitempty"`
|
|
Passed bool `json:"passed"`
|
|
CleanupOK bool `json:"cleanup_ok"`
|
|
Error string `json:"error,omitempty"`
|
|
Evidence reconnectEvidence `json:"evidence"`
|
|
}
|
|
|
|
func validateTransferArtifact(metadata Metadata, result ScenarioResult, artifact transferArtifact) error {
|
|
if err := validateArtifactHeader(metadata, result, artifact.Scenario, artifact.Fault, artifact.Passed, artifact.CleanupOK, artifact.Error); err != nil {
|
|
return err
|
|
}
|
|
switch artifact.Fault {
|
|
case "":
|
|
if !artifact.Passed {
|
|
return errors.New("transfer success artifact reports failure")
|
|
}
|
|
if err := validateTransferSuccess(artifact.Evidence); err != nil {
|
|
return err
|
|
}
|
|
case contract.FaultTransferHash:
|
|
if artifact.Passed || !strings.Contains(artifact.Error, "injected hash mismatch") {
|
|
return errors.New("transfer-hash artifact does not identify the injected failure")
|
|
}
|
|
evidence := artifact.Evidence
|
|
if evidence.WarmupUploadBytes != 65536 || evidence.WarmupDownloadBytes != 65536 || evidence.WarmupSHA256 == "" || evidence.WarmupDuration <= 0 || evidence.WarmupDeadlineRemaining <= 0 || !evidence.WarmupQuiescent {
|
|
return errors.New("transfer-hash artifact omitted warmup evidence")
|
|
}
|
|
if evidence.UploadBytes != 0 || evidence.DownloadBytes != 0 || evidence.UploadSHA256 != "" || evidence.DownloadSHA256 != "" || evidence.UploadChunks != 0 || evidence.DownloadChunks != 0 || evidence.UploadDuration != 0 || evidence.DownloadDuration != 0 || evidence.RetainedHeapBytes != 0 || evidence.Mode != "" || evidence.CreateDirs || evidence.UploadReplayRejected || evidence.DownloadReplayRejected || evidence.OversizeRejected {
|
|
return errors.New("transfer-hash artifact presents stale success evidence")
|
|
}
|
|
if evidence.AgentTempResidue != 0 || evidence.DashboardSpoolResidue != 0 || !evidence.OutsideRootSentinelsUnchanged {
|
|
return errors.New("transfer-hash artifact cleanup or sentinel evidence failed")
|
|
}
|
|
default:
|
|
return errors.New("transfer artifact has unsupported fault")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateArtifactHeader(metadata Metadata, result ScenarioResult, scenarioName, fault string, passed, cleanupOK bool, errorText string) error {
|
|
if scenarioName != result.Name || scenarioName != metadata.Scenarios[0] || fault != metadata.Fault || passed != result.Passed || !cleanupOK || errorText != result.Error {
|
|
return errors.New("dedicated artifact does not agree with metadata, results, or cleanup")
|
|
}
|
|
if passed && errorText != "" || !passed && errorText == "" {
|
|
return errors.New("dedicated artifact pass/error state is inconsistent")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateTransferSuccess(evidence transferEvidence) error {
|
|
if evidence.WarmupUploadBytes != 65536 || evidence.WarmupDownloadBytes != 65536 || evidence.WarmupSHA256 == "" || evidence.WarmupDuration <= 0 || evidence.WarmupDeadlineRemaining <= 0 || !evidence.WarmupQuiescent {
|
|
return errors.New("transfer warmup evidence is invalid")
|
|
}
|
|
if evidence.UploadBytes != contract.TransferBytes || evidence.DownloadBytes != contract.TransferBytes || evidence.UploadSHA256 == "" || !strings.EqualFold(evidence.UploadSHA256, evidence.DownloadSHA256) {
|
|
return errors.New("transfer byte counts or hashes are invalid")
|
|
}
|
|
if evidence.UploadChunks == 0 || evidence.DownloadChunks == 0 || evidence.UploadDuration <= 0 || evidence.DownloadDuration <= 0 || evidence.RetainedHeapBytes > contract.TransferHeapBytes {
|
|
return errors.New("transfer measurement evidence is invalid")
|
|
}
|
|
if evidence.Mode != "0640" || !evidence.CreateDirs || !evidence.UploadReplayRejected || !evidence.DownloadReplayRejected || !evidence.OversizeRejected || evidence.AgentTempResidue != 0 || evidence.DashboardSpoolResidue != 0 || !evidence.OutsideRootSentinelsUnchanged {
|
|
return errors.New("transfer contract evidence is invalid")
|
|
}
|
|
return nil
|
|
}
|