mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
125 lines
6.8 KiB
Go
125 lines
6.8 KiB
Go
package evidence
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/contract"
|
|
)
|
|
|
|
func validateDedicatedArtifact(files evidenceSnapshot, metadata Metadata, result ScenarioResult) error {
|
|
definition, err := contract.ScenarioDefinitionByName(result.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch definition.DedicatedArtifact {
|
|
case contract.DedicatedArtifactTransfer:
|
|
artifact, err := readJSONFile[transferArtifact](files, "transfer.json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return validateTransferArtifact(metadata, result, artifact)
|
|
case contract.DedicatedArtifactReconnect:
|
|
artifact, err := readJSONFile[reconnectArtifact](files, "reconnect.json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return validateReconnectArtifact(metadata, result, artifact)
|
|
case contract.DedicatedArtifactNone:
|
|
return nil
|
|
default:
|
|
return errors.New("unsupported dedicated artifact kind")
|
|
}
|
|
}
|
|
|
|
func validateReconnectArtifact(metadata Metadata, result ScenarioResult, artifact reconnectArtifact) 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("reconnect success artifact reports failure")
|
|
}
|
|
if err := validateReconnectSuccess(artifact.Evidence); err != nil {
|
|
return err
|
|
}
|
|
if err := validateReconnectSuccessReceipts(artifact.Evidence); err != nil {
|
|
return err
|
|
}
|
|
return validateReconnectFinalEvidence(artifact.Evidence, 2)
|
|
case contract.FaultDashboardExit:
|
|
if artifact.Passed || !strings.Contains(artifact.Error, "injected Dashboard exit") {
|
|
return errors.New("dashboard-exit artifact does not identify the injected failure")
|
|
}
|
|
evidence := artifact.Evidence
|
|
if reconnectPostFaultEvidencePresent(evidence) {
|
|
return errors.New("dashboard-exit artifact presents stale reconnect success")
|
|
}
|
|
if evidence.Lifecycle.DisconnectAt.IsZero() || !evidence.Lifecycle.ReconnectAt.IsZero() || !evidence.Lifecycle.OutsideRootSentinelUnchanged {
|
|
return errors.New("dashboard-exit lifecycle evidence is invalid")
|
|
}
|
|
return validateReconnectFinalEvidence(evidence, 1)
|
|
default:
|
|
return errors.New("reconnect artifact has unsupported fault")
|
|
}
|
|
}
|
|
|
|
func reconnectPostFaultEvidencePresent(evidence reconnectEvidence) bool {
|
|
return evidence.Runtime.DashboardAfter != (runtimeIdentity{}) || evidence.Runtime.AgentAfter != (runtimeIdentity{}) ||
|
|
evidence.Runtime.StateGenerationBeforeAgentRestart != 0 || evidence.Runtime.StateGenerationAfterAgentRestart != 0 ||
|
|
evidence.Identity.DashboardConfigUnchanged || evidence.Identity.AgentConfigUnchanged || evidence.Identity.DashboardFixtureUnchanged || evidence.Identity.ClientsRecreated || evidence.Identity.BootstrapRecreated ||
|
|
!evidence.Lifecycle.ReconnectAt.IsZero() || evidence.Lifecycle.ReconnectInterval != 0 || len(evidence.Lifecycle.DashboardReceipts) != 0 || len(evidence.Lifecycle.AgentReceipts) != 0 ||
|
|
evidence.Lifecycle.StaleGenerationReceipts != 0 || evidence.Lifecycle.DuplicateTaskIDs != 0 || evidence.Lifecycle.LostResultIDs != 0 ||
|
|
evidence.Observation.ServerID != 0 || evidence.Observation.UUID != "" || evidence.Observation.OldGeneration != 0 || evidence.Observation.NewGeneration != 0 ||
|
|
!evidence.Observation.DisconnectAt.IsZero() || !evidence.Observation.ReconnectAt.IsZero() || len(evidence.Observation.TaskIDs) != 0 || len(evidence.Observation.ResultIDs) != 0 || evidence.Observation.PostReconnect || evidence.Observation.AgentRestarted
|
|
}
|
|
|
|
func validateReconnectSuccess(evidence reconnectEvidence) error {
|
|
observation := evidence.Observation
|
|
if observation.ServerID == 0 || observation.UUID == "" || observation.OldGeneration == 0 || observation.NewGeneration <= observation.OldGeneration || observation.DisconnectAt.IsZero() || !observation.ReconnectAt.After(observation.DisconnectAt) || len(observation.TaskIDs) != 5 || !slices.Equal(observation.TaskIDs, observation.ResultIDs) || !observation.PostReconnect || !observation.AgentRestarted {
|
|
return errors.New("reconnect observation evidence is invalid")
|
|
}
|
|
if evidence.Runtime.DashboardAfter.Generation <= evidence.Runtime.DashboardBefore.Generation || evidence.Runtime.DashboardAfter.PID == evidence.Runtime.DashboardBefore.PID || evidence.Runtime.AgentAfter.Generation <= evidence.Runtime.AgentBefore.Generation || evidence.Runtime.AgentAfter.PID == evidence.Runtime.AgentBefore.PID || evidence.Runtime.StateGenerationAfterAgentRestart <= evidence.Runtime.StateGenerationBeforeAgentRestart {
|
|
return errors.New("reconnect runtime generations are invalid")
|
|
}
|
|
identity := evidence.Identity
|
|
if identity.ServerID != observation.ServerID || identity.UUID != observation.UUID || !identity.DashboardConfigUnchanged || !identity.AgentConfigUnchanged || !identity.DashboardFixtureUnchanged || !identity.ClientsRecreated || !identity.BootstrapRecreated {
|
|
return errors.New("reconnect identity evidence is invalid")
|
|
}
|
|
if evidence.Lifecycle.StaleGenerationReceipts != 0 || evidence.Lifecycle.DuplicateTaskIDs != 0 || evidence.Lifecycle.LostResultIDs != 0 || !evidence.Lifecycle.OutsideRootSentinelUnchanged {
|
|
return errors.New("reconnect lifecycle accounting is invalid")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateReconnectSuccessReceipts(evidence reconnectEvidence) error {
|
|
if len(evidence.Lifecycle.DashboardReceipts) != 3 || len(evidence.Lifecycle.AgentReceipts) != 2 || len(evidence.Observation.TaskIDs) != 5 {
|
|
return errors.New("reconnect receipt accounting is not exact")
|
|
}
|
|
if evidence.Lifecycle.DisconnectAt.IsZero() || evidence.Lifecycle.ReconnectAt.IsZero() || evidence.Lifecycle.ReconnectAt.Sub(evidence.Lifecycle.DisconnectAt) != evidence.Lifecycle.ReconnectInterval {
|
|
return errors.New("reconnect timestamp interval is inconsistent")
|
|
}
|
|
if !evidence.Observation.DisconnectAt.Equal(evidence.Lifecycle.DisconnectAt) || !evidence.Observation.ReconnectAt.Equal(evidence.Lifecycle.ReconnectAt) {
|
|
return errors.New("reconnect lifecycle and observation timestamps differ")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateReconnectFinalEvidence(evidence reconnectEvidence, expectedProcessCount int) error {
|
|
if !evidence.AgentCleanup.Passed || evidence.AgentCleanup.Forced || !evidence.DashboardCleanup.Passed || evidence.DashboardCleanup.Forced {
|
|
return errors.New("reconnect cleanup receipts failed")
|
|
}
|
|
if len(evidence.AgentCleanup.Processes) != expectedProcessCount || len(evidence.DashboardCleanup.Processes) != expectedProcessCount {
|
|
return errors.New("reconnect cleanup receipt process count is invalid")
|
|
}
|
|
for _, path := range []string{evidence.Fixture.Dashboard.WorkspaceRoot, evidence.Fixture.AgentRoot, evidence.Fixture.Dashboard.ConfigPath, evidence.Fixture.AgentConfigPath, evidence.Fixture.Dashboard.BinaryPath, evidence.Fixture.AgentBinaryPath} {
|
|
if path == "" || !filepath.IsAbs(path) {
|
|
return errors.New("reconnect fixture paths are invalid")
|
|
}
|
|
}
|
|
return nil
|
|
}
|