|
|
|
@@ -32,11 +32,9 @@ func completeChecks() instance.ManagementChecks {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func capability(t *testing.T, value *instance.Instance, checks instance.ManagementChecks,
|
|
|
|
|
registry instance.RegistryState,
|
|
|
|
|
) instance.CapabilityObservation {
|
|
|
|
|
func capability(t *testing.T, value *instance.Instance, checks instance.ManagementChecks) instance.CapabilityObservation {
|
|
|
|
|
t.Helper()
|
|
|
|
|
o, err := instance.NewCapabilityObservation(value.Target(), testServerVersion, checks, registry)
|
|
|
|
|
o, err := instance.NewCapabilityObservation(value.Target(), testServerVersion, checks)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
@@ -45,8 +43,8 @@ func capability(t *testing.T, value *instance.Instance, checks instance.Manageme
|
|
|
|
|
|
|
|
|
|
func readyInstance(t *testing.T) *instance.Instance {
|
|
|
|
|
t.Helper()
|
|
|
|
|
i := lifecycleInstance(t, instance.Snapshot{Phase: instance.PhaseInitializingRegistry}, false)
|
|
|
|
|
if err := i.AssessRegistryResult(instance.RegistryReadBack(capability(t, i, completeChecks(), instance.RegistryUsable))); err != nil {
|
|
|
|
|
i := lifecycleInstance(t, instance.Snapshot{Phase: instance.PhaseValidating}, false)
|
|
|
|
|
if err := i.AssessManagement(capability(t, i, completeChecks())); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := i.RequireProvisioningReady(); err != nil {
|
|
|
|
@@ -60,38 +58,27 @@ func TestReadinessRequiresCompleteReadBack(t *testing.T) {
|
|
|
|
|
if err := i.BeginValidation(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
absent := capability(t, i, completeChecks(), instance.RegistryAbsent)
|
|
|
|
|
if err := i.AssessManagement(absent); err != nil {
|
|
|
|
|
if i.RequireProvisioningReady() == nil {
|
|
|
|
|
t.Fatal("validation intent authorized provisioning")
|
|
|
|
|
}
|
|
|
|
|
if err := i.AssessManagement(capability(t, i, instance.ManagementChecks{})); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if s := i.Snapshot(); s.Phase != instance.PhaseInitializingRegistry || s.ObservedRevision != 0 || s.Readiness != instance.Unknown {
|
|
|
|
|
t.Fatalf("management observation prematurely concluded readiness: %+v", s)
|
|
|
|
|
if snapshot := i.Snapshot(); snapshot.Phase != instance.PhaseValidating ||
|
|
|
|
|
snapshot.Readiness != instance.NotReady || snapshot.Failure != instance.ObservationIncomplete {
|
|
|
|
|
t.Fatalf("incomplete observation accepted: %+v", snapshot)
|
|
|
|
|
}
|
|
|
|
|
for range 2 {
|
|
|
|
|
decision, err := i.PlanRegistryPreparation(absent)
|
|
|
|
|
if err != nil || decision != instance.PreparationAllowed {
|
|
|
|
|
t.Fatalf("preparation: %v, %v", decision, err)
|
|
|
|
|
}
|
|
|
|
|
if i.RequireProvisioningReady() == nil {
|
|
|
|
|
t.Fatal("preparation authorized provisioning")
|
|
|
|
|
}
|
|
|
|
|
if i.RequireProvisioningReady() == nil {
|
|
|
|
|
t.Fatal("incomplete observation authorized provisioning")
|
|
|
|
|
}
|
|
|
|
|
if err := i.AssessRegistryResult(instance.RegistryReadBack(absent)); err != nil {
|
|
|
|
|
if err := i.AssessManagement(capability(t, i, completeChecks())); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if i.Snapshot().Failure != instance.RegistryNotUsable || i.RequireProvisioningReady() == nil {
|
|
|
|
|
t.Fatal("absent registry accepted as ready")
|
|
|
|
|
}
|
|
|
|
|
usable := capability(t, i, completeChecks(), instance.RegistryUsable)
|
|
|
|
|
decision, err := i.PlanRegistryPreparation(usable)
|
|
|
|
|
if err != nil || decision != instance.AlreadyUsable {
|
|
|
|
|
t.Fatalf("retry after external preparation: %v, %v", decision, err)
|
|
|
|
|
}
|
|
|
|
|
if err := i.AssessRegistryResult(instance.RegistryReadBack(usable)); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if s := i.Snapshot(); s.Readiness != instance.Ready || s.ReportedVersion != testServerVersion || s.ObservedRevision != i.Target().Revision().Value() {
|
|
|
|
|
t.Fatalf("complete observation not accepted: %+v", s)
|
|
|
|
|
snapshot := i.Snapshot()
|
|
|
|
|
if snapshot.Phase != instance.PhaseReady || snapshot.Readiness != instance.Ready ||
|
|
|
|
|
snapshot.ReportedVersion != testServerVersion ||
|
|
|
|
|
snapshot.ObservedRevision != i.Target().Revision().Value() {
|
|
|
|
|
t.Fatalf("complete management observation did not establish readiness: %+v", snapshot)
|
|
|
|
|
}
|
|
|
|
|
if err := i.RequireProvisioningReady(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
@@ -109,7 +96,7 @@ func TestReadinessRecoveryAndInvalidation(t *testing.T) {
|
|
|
|
|
t.Fatal("persisted Ready fabricated fresh evidence")
|
|
|
|
|
}
|
|
|
|
|
for range 2 {
|
|
|
|
|
if err := restored.AssessReadiness(capability(t, restored, completeChecks(), instance.RegistryUsable)); err != nil {
|
|
|
|
|
if err := restored.AssessReadiness(capability(t, restored, completeChecks())); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := restored.RequireProvisioningReady(); err != nil {
|
|
|
|
@@ -138,120 +125,77 @@ func TestReadinessRecoveryAndInvalidation(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestEachManagementCheckIsRequired(t *testing.T) {
|
|
|
|
|
for field := range 6 {
|
|
|
|
|
for _, result := range []instance.CheckResult{instance.CheckUnobserved, instance.CheckUnavailable,
|
|
|
|
|
instance.CheckAuthenticationFailed, instance.CheckInsufficientPrivileges, 255} {
|
|
|
|
|
checks := completeChecks()
|
|
|
|
|
fields := []*instance.CheckResult{&checks.Connection, &checks.Metadata, &checks.Roles,
|
|
|
|
|
&checks.Databases, &checks.Grants, &checks.Extensions}
|
|
|
|
|
*fields[field] = result
|
|
|
|
|
i := readyInstance(t)
|
|
|
|
|
if err := i.AssessReadiness(capability(t, i, checks, instance.RegistryUsable)); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if s := i.Snapshot(); s.Phase != instance.PhaseValidating || s.Readiness != instance.NotReady ||
|
|
|
|
|
s.Failure == instance.NoFailure || i.RequireProvisioningReady() == nil {
|
|
|
|
|
t.Fatalf("check %d result %d accepted: %+v", field, result, s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRegistryDecisionsAndReadinessLoss(t *testing.T) {
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
|
state instance.RegistryState
|
|
|
|
|
decision instance.PreparationDecision
|
|
|
|
|
failure instance.Failure
|
|
|
|
|
checkNames := []string{"connection", "metadata", "roles", "databases", "grants", "extensions"}
|
|
|
|
|
failures := []struct {
|
|
|
|
|
name string
|
|
|
|
|
result instance.CheckResult
|
|
|
|
|
want instance.Failure
|
|
|
|
|
}{
|
|
|
|
|
{instance.RegistryUsable, instance.AlreadyUsable, instance.NoFailure},
|
|
|
|
|
{instance.RegistryAbsent, instance.PreparationAllowed, instance.RegistryNotUsable},
|
|
|
|
|
{instance.RegistryNeedsMigration, instance.PreparationAllowed, instance.RegistryNotUsable},
|
|
|
|
|
{instance.RegistryUnsupported, instance.PreparationDenied, instance.RegistryIncompatible},
|
|
|
|
|
{instance.RegistryUnavailable, instance.PreparationDenied, instance.DependencyUnavailable},
|
|
|
|
|
{instance.RegistryUnobserved, instance.PreparationDenied, instance.ObservationIncomplete},
|
|
|
|
|
{255, instance.PreparationDenied, instance.ObservationIncomplete},
|
|
|
|
|
} {
|
|
|
|
|
i := lifecycleInstance(t, instance.Snapshot{Phase: instance.PhaseInitializingRegistry}, false)
|
|
|
|
|
o := capability(t, i, completeChecks(), tc.state)
|
|
|
|
|
decision, err := i.PlanRegistryPreparation(o)
|
|
|
|
|
if err != nil || decision != tc.decision {
|
|
|
|
|
t.Fatalf("registry %d: %v, %v", tc.state, decision, err)
|
|
|
|
|
}
|
|
|
|
|
i = readyInstance(t)
|
|
|
|
|
if err := i.AssessReadiness(o); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if i.Snapshot().Failure != tc.failure {
|
|
|
|
|
t.Fatalf("registry %d: %+v", tc.state, i.Snapshot())
|
|
|
|
|
}
|
|
|
|
|
if tc.state != instance.RegistryUsable {
|
|
|
|
|
wantPhase := instance.PhaseInitializingRegistry
|
|
|
|
|
if tc.state == instance.RegistryUnavailable {
|
|
|
|
|
wantPhase = instance.PhaseValidating
|
|
|
|
|
}
|
|
|
|
|
if i.Snapshot().Phase != wantPhase || i.RequireProvisioningReady() == nil {
|
|
|
|
|
t.Fatal("registry drift retained readiness")
|
|
|
|
|
{"unobserved", instance.CheckUnobserved, instance.ObservationIncomplete},
|
|
|
|
|
{"unavailable", instance.CheckUnavailable, instance.DependencyUnavailable},
|
|
|
|
|
{"authentication", instance.CheckAuthenticationFailed, instance.AuthenticationFailed},
|
|
|
|
|
{"privileges", instance.CheckInsufficientPrivileges, instance.InsufficientPrivileges},
|
|
|
|
|
{"unknown", 255, instance.ObservationIncomplete},
|
|
|
|
|
}
|
|
|
|
|
for field, name := range checkNames {
|
|
|
|
|
for _, failure := range failures {
|
|
|
|
|
for _, phase := range []instance.Phase{instance.PhaseValidating, instance.PhaseReady} {
|
|
|
|
|
t.Run(name+"/"+failure.name+"/"+string(phase), func(t *testing.T) {
|
|
|
|
|
checks := completeChecks()
|
|
|
|
|
fields := []*instance.CheckResult{
|
|
|
|
|
&checks.Connection, &checks.Metadata, &checks.Roles,
|
|
|
|
|
&checks.Databases, &checks.Grants, &checks.Extensions,
|
|
|
|
|
}
|
|
|
|
|
*fields[field] = failure.result
|
|
|
|
|
value := lifecycleInstance(t, instance.Snapshot{Phase: phase}, false)
|
|
|
|
|
assess := value.AssessManagement
|
|
|
|
|
if phase == instance.PhaseReady {
|
|
|
|
|
value = readyInstance(t)
|
|
|
|
|
assess = value.AssessReadiness
|
|
|
|
|
}
|
|
|
|
|
if err := assess(capability(t, value, checks)); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
snapshot := value.Snapshot()
|
|
|
|
|
if snapshot.Phase != instance.PhaseValidating ||
|
|
|
|
|
snapshot.Readiness != instance.NotReady ||
|
|
|
|
|
snapshot.Failure != failure.want ||
|
|
|
|
|
snapshot.ObservedRevision != value.Target().Revision().Value() {
|
|
|
|
|
t.Fatalf("incorrect failed observation: %+v", snapshot)
|
|
|
|
|
}
|
|
|
|
|
if value.RequireProvisioningReady() == nil {
|
|
|
|
|
t.Fatal("failed check authorized provisioning")
|
|
|
|
|
}
|
|
|
|
|
// 依赖恢复后重新验证,不保留失败或旧就绪证据。
|
|
|
|
|
if err := value.AssessManagement(capability(t, value, completeChecks())); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := value.RequireProvisioningReady(); err != nil {
|
|
|
|
|
t.Fatal("dependency recovery did not restore readiness", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestInitializationRejectsIncompleteOrFailedManagement(t *testing.T) {
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
|
checks instance.ManagementChecks
|
|
|
|
|
registry instance.RegistryState
|
|
|
|
|
failure instance.Failure
|
|
|
|
|
}{
|
|
|
|
|
{instance.ManagementChecks{}, instance.RegistryUsable, instance.ObservationIncomplete},
|
|
|
|
|
{completeChecks(), instance.RegistryUnsupported, instance.RegistryIncompatible},
|
|
|
|
|
{completeChecks(), instance.RegistryUnavailable, instance.DependencyUnavailable},
|
|
|
|
|
} {
|
|
|
|
|
i := lifecycleInstance(t, instance.Snapshot{Phase: instance.PhaseValidating}, false)
|
|
|
|
|
if err := i.AssessManagement(capability(t, i, tc.checks, tc.registry)); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if s := i.Snapshot(); s.Phase != instance.PhaseValidating || s.Failure != tc.failure ||
|
|
|
|
|
s.ObservedRevision != i.Target().Revision().Value() || s.Readiness != instance.NotReady {
|
|
|
|
|
t.Fatalf("invalid management accepted: %+v", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
i := lifecycleInstance(t, instance.Snapshot{Phase: instance.PhaseInitializingRegistry}, false)
|
|
|
|
|
decision, err := i.PlanRegistryPreparation(capability(t, i, instance.ManagementChecks{}, instance.RegistryAbsent))
|
|
|
|
|
if err != nil || decision != instance.PreparationDenied || i.Snapshot().Failure != instance.ObservationIncomplete {
|
|
|
|
|
t.Fatal("incomplete management allowed registry writes")
|
|
|
|
|
}
|
|
|
|
|
if err := i.AssessRegistryResult(instance.RegistryReadBack(capability(t, i, completeChecks(), instance.RegistryUsable))); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := i.RequireProvisioningReady(); err != nil {
|
|
|
|
|
t.Fatal("dependency recovery did not restore readiness", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestReadinessMethodsRejectWrongPhaseAndDeletion(t *testing.T) {
|
|
|
|
|
for _, deleting := range []bool{false, true} {
|
|
|
|
|
for _, phase := range []instance.Phase{instance.PhasePending, instance.PhaseValidating,
|
|
|
|
|
instance.PhaseInitializingRegistry, instance.PhaseReady, instance.PhaseDeleting} {
|
|
|
|
|
instance.PhaseReady, instance.PhaseDeleting} {
|
|
|
|
|
for _, operation := range []struct {
|
|
|
|
|
phase instance.Phase
|
|
|
|
|
apply func(*instance.Instance, instance.CapabilityObservation) error
|
|
|
|
|
}{
|
|
|
|
|
{instance.PhaseValidating, (*instance.Instance).AssessManagement},
|
|
|
|
|
{instance.PhaseReady, (*instance.Instance).AssessReadiness},
|
|
|
|
|
{instance.PhaseInitializingRegistry, func(i *instance.Instance, o instance.CapabilityObservation) error {
|
|
|
|
|
_, err := i.PlanRegistryPreparation(o)
|
|
|
|
|
return err
|
|
|
|
|
}},
|
|
|
|
|
{instance.PhaseInitializingRegistry, func(i *instance.Instance, o instance.CapabilityObservation) error {
|
|
|
|
|
return i.AssessRegistryResult(instance.RegistryReadBack(o))
|
|
|
|
|
}},
|
|
|
|
|
} {
|
|
|
|
|
if !deleting && operation.phase == phase {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
i := lifecycleInstance(t, instance.Snapshot{Phase: phase}, deleting)
|
|
|
|
|
before := i.Snapshot()
|
|
|
|
|
if err := operation.apply(i, capability(t, i, completeChecks(), instance.RegistryUsable)); err == nil {
|
|
|
|
|
if err := operation.apply(i, capability(t, i, completeChecks())); err == nil {
|
|
|
|
|
t.Fatalf("phase %s deleting=%t accepted operation for %s", phase, deleting, operation.phase)
|
|
|
|
|
}
|
|
|
|
|
if i.Snapshot() != before {
|
|
|
|
@@ -273,7 +217,7 @@ func TestOldGenerationObservationDoesNotReplaceEvidence(t *testing.T) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
o, err := instance.NewCapabilityObservation(other, testServerVersion, completeChecks(), instance.RegistryUsable)
|
|
|
|
|
o, err := instance.NewCapabilityObservation(other, testServerVersion, completeChecks())
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
@@ -286,44 +230,14 @@ func TestOldGenerationObservationDoesNotReplaceEvidence(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPreparationFailureCannotEstablishReadiness(t *testing.T) {
|
|
|
|
|
i := lifecycleInstance(t, instance.Snapshot{Phase: instance.PhaseInitializingRegistry}, false)
|
|
|
|
|
for _, failure := range []instance.Failure{instance.DependencyUnavailable, instance.AuthenticationFailed,
|
|
|
|
|
instance.InsufficientPrivileges, instance.RegistryIncompatible} {
|
|
|
|
|
result, err := instance.RegistryPreparationFailed(i.Target(), failure)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := i.AssessRegistryResult(result); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if s := i.Snapshot(); s.Failure != failure || s.Readiness != instance.NotReady ||
|
|
|
|
|
s.Phase != instance.PhaseInitializingRegistry || i.RequireProvisioningReady() == nil {
|
|
|
|
|
t.Fatalf("failed operation accepted: %+v", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, failure := range []instance.Failure{instance.NoFailure, 255} {
|
|
|
|
|
if _, err := instance.RegistryPreparationFailed(i.Target(), failure); err == nil {
|
|
|
|
|
t.Fatal("invalid failure accepted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCapabilityInputsAndLifecycleGuards(t *testing.T) {
|
|
|
|
|
i := readyInstance(t)
|
|
|
|
|
if _, err := instance.NewCapabilityObservation(instance.ObservationTarget{}, testServerVersion,
|
|
|
|
|
completeChecks(), instance.RegistryUsable); err == nil {
|
|
|
|
|
completeChecks()); err == nil {
|
|
|
|
|
t.Fatal("invalid target accepted")
|
|
|
|
|
}
|
|
|
|
|
if _, err := instance.RegistryPreparationFailed(instance.ObservationTarget{}, instance.DependencyUnavailable); err == nil {
|
|
|
|
|
t.Fatal("invalid failure target accepted")
|
|
|
|
|
}
|
|
|
|
|
for _, method := range []func(instance.CapabilityObservation) error{
|
|
|
|
|
i.AssessManagement, i.AssessReadiness,
|
|
|
|
|
func(o instance.CapabilityObservation) error { _, err := i.PlanRegistryPreparation(o); return err },
|
|
|
|
|
func(o instance.CapabilityObservation) error {
|
|
|
|
|
return i.AssessRegistryResult(instance.RegistryReadBack(o))
|
|
|
|
|
},
|
|
|
|
|
} {
|
|
|
|
|
before := i.Snapshot()
|
|
|
|
|
if err := method(instance.CapabilityObservation{}); err == nil || i.Snapshot() != before {
|
|
|
|
@@ -333,13 +247,13 @@ func TestCapabilityInputsAndLifecycleGuards(t *testing.T) {
|
|
|
|
|
old := i.Snapshot()
|
|
|
|
|
old.ObservedRevision = 0
|
|
|
|
|
changed := lifecycleInstance(t, old, false)
|
|
|
|
|
if err := changed.AssessReadiness(capability(t, changed, completeChecks(), instance.RegistryUsable)); err != nil {
|
|
|
|
|
if err := changed.AssessReadiness(capability(t, changed, completeChecks())); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if s := changed.Snapshot(); s.Phase != instance.PhaseValidating || s.ObservedRevision != 0 || s.Readiness != instance.Unknown {
|
|
|
|
|
t.Fatalf("changed generation accepted old checkpoint: %+v", s)
|
|
|
|
|
}
|
|
|
|
|
o, err := instance.NewCapabilityObservation(i.Target(), "", completeChecks(), instance.RegistryUsable)
|
|
|
|
|
o, err := instance.NewCapabilityObservation(i.Target(), "", completeChecks())
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|