mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-12 23:54:01 +09:00
test(mariadb): cover the absent Complete condition and expired deadlines
Every first reconcile after the operator Backup or Restore is created sees no Complete condition, and the guards that keep that case from dereferencing nil could be dropped with the outcome tables staying green. The tables now also cover the deadline failure for a run that never reports a known reason, and read the job status back from the API, so a lost status write fails them instead of passing on the in-memory object. Assisted-by: LLM Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
This commit is contained in:
1 parent
ccac0e17e9
commit
7e1fc7173c
1 file changed
+79
-43
@@ -871,30 +871,64 @@ func TestReconcileMariaDBRestore_KindCheckedBeforeSrcBackupGet(t *testing.T) {
|
||||
// JobFailed (SetCompleteWithJob in mariadb-operator 25.10.2), so the
|
||||
// Complete status alone does not tell success from failure.
|
||||
var mariadbCompleteOutcomeCases = []struct {
|
||||
name string
|
||||
status metav1.ConditionStatus
|
||||
reason string
|
||||
wantFailed bool
|
||||
wantDone bool
|
||||
name string
|
||||
noCondition bool
|
||||
status metav1.ConditionStatus
|
||||
reason string
|
||||
message string
|
||||
expired bool
|
||||
wantFailed bool
|
||||
wantDone bool
|
||||
wantMessage []string
|
||||
}{
|
||||
{name: "Complete=True/JobComplete succeeds", status: metav1.ConditionTrue, reason: mariadbtypes.ConditionReasonJobComplete, wantDone: true},
|
||||
{name: "Complete=True/JobFailed fails", status: metav1.ConditionTrue, reason: mariadbtypes.ConditionReasonJobFailed, wantFailed: true},
|
||||
{name: "Complete=False/JobFailed fails", status: metav1.ConditionFalse, reason: mariadbtypes.ConditionReasonJobFailed, wantFailed: true},
|
||||
{name: "Complete=True with an unknown reason keeps waiting", status: metav1.ConditionTrue, reason: "SomethingElse"},
|
||||
{name: "Complete=False/JobComplete keeps waiting", status: metav1.ConditionFalse, reason: mariadbtypes.ConditionReasonJobComplete},
|
||||
{name: "Complete=False/JobRunning keeps waiting", status: metav1.ConditionFalse, reason: mariadbtypes.ConditionReasonJobRunning},
|
||||
{name: "Complete=True/JobComplete succeeds", status: metav1.ConditionTrue, reason: mariadbtypes.ConditionReasonJobComplete, message: "Success", wantDone: true},
|
||||
{name: "Complete=True/JobFailed fails", status: metav1.ConditionTrue, reason: mariadbtypes.ConditionReasonJobFailed, message: "Failed", wantFailed: true,
|
||||
wantMessage: []string{"tenant/op"}},
|
||||
{name: "Complete=False/JobFailed fails", status: metav1.ConditionFalse, reason: mariadbtypes.ConditionReasonJobFailed, message: "Failed", wantFailed: true,
|
||||
wantMessage: []string{"tenant/op"}},
|
||||
{name: "Complete=True with an unknown reason keeps waiting", status: metav1.ConditionTrue, reason: "SomethingElse", message: "Something"},
|
||||
{name: "Complete=False/JobComplete keeps waiting", status: metav1.ConditionFalse, reason: mariadbtypes.ConditionReasonJobComplete, message: "Success"},
|
||||
{name: "Complete=False/JobRunning keeps waiting", status: metav1.ConditionFalse, reason: mariadbtypes.ConditionReasonJobRunning, message: "Running"},
|
||||
{name: "no Complete condition keeps waiting", noCondition: true},
|
||||
{name: "no Complete condition past the deadline fails", noCondition: true, expired: true, wantFailed: true,
|
||||
wantMessage: []string{"no Complete condition observed"}},
|
||||
{name: "Complete=True with an unknown reason past the deadline fails", status: metav1.ConditionTrue, reason: "SomethingElse", message: "Something", expired: true, wantFailed: true,
|
||||
wantMessage: []string{"Complete=True/SomethingElse"}},
|
||||
}
|
||||
|
||||
func mariadbCompleteCondition(status metav1.ConditionStatus, reason string) []metav1.Condition {
|
||||
func mariadbCompleteConditions(noCondition bool, status metav1.ConditionStatus, reason, message string) []metav1.Condition {
|
||||
if noCondition {
|
||||
return nil
|
||||
}
|
||||
return []metav1.Condition{{
|
||||
Type: mariadbtypes.ConditionTypeComplete,
|
||||
Status: status,
|
||||
Reason: reason,
|
||||
Message: "Failed",
|
||||
Message: message,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
}}
|
||||
}
|
||||
|
||||
// mariadbOutcomeStartedAt returns a StartedAt that is fresh, or older than
|
||||
// both the backup and the default restore deadline.
|
||||
func mariadbOutcomeStartedAt(expired bool) *metav1.Time {
|
||||
if !expired {
|
||||
now := metav1.Now()
|
||||
return &now
|
||||
}
|
||||
past := metav1.NewTime(time.Now().Add(-2 * max(mariadbDefaultBackupDeadline, mariadbDefaultRestoreDeadline)))
|
||||
return &past
|
||||
}
|
||||
|
||||
func assertMariaDBFailureMessage(t *testing.T, message string, want []string) {
|
||||
t.Helper()
|
||||
for _, w := range want {
|
||||
if !strings.Contains(message, w) {
|
||||
t.Errorf("failure message must contain %q, got %q", w, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcileMariaDBBackup_CompleteConditionOutcome(t *testing.T) {
|
||||
strategyAPIGroup := strategyv1alpha1.GroupVersion.Group
|
||||
resolved := &ResolvedBackupConfig{
|
||||
@@ -907,9 +941,8 @@ func TestReconcileMariaDBBackup_CompleteConditionOutcome(t *testing.T) {
|
||||
|
||||
for _, tc := range mariadbCompleteOutcomeCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
now := metav1.Now()
|
||||
seed := newMariaDBBackupJob("bj", "tenant")
|
||||
seed.Status.StartedAt = &now
|
||||
seed.Status.StartedAt = mariadbOutcomeStartedAt(tc.expired)
|
||||
strategy := &strategyv1alpha1.MariaDB{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mdb-strategy"},
|
||||
Spec: strategyv1alpha1.MariaDBSpec{Template: *newRenderedMariaDBTemplate()},
|
||||
@@ -920,13 +953,13 @@ func TestReconcileMariaDBBackup_CompleteConditionOutcome(t *testing.T) {
|
||||
operatorBackup := &mariadbtypes.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "tenant",
|
||||
Name: "bj-op",
|
||||
Name: "op",
|
||||
Labels: map[string]string{
|
||||
backupsv1alpha1.OwningJobNameLabel: seed.Name,
|
||||
backupsv1alpha1.OwningJobNamespaceLabel: seed.Namespace,
|
||||
},
|
||||
},
|
||||
Status: mariadbtypes.BackupStatus{Conditions: mariadbCompleteCondition(tc.status, tc.reason)},
|
||||
Status: mariadbtypes.BackupStatus{Conditions: mariadbCompleteConditions(tc.noCondition, tc.status, tc.reason, tc.message)},
|
||||
}
|
||||
c := newMariaDBStrategyTestClient(t, seed, newMariaDBApp(seed.Spec.ApplicationRef.Name, "tenant"),
|
||||
strategy, operatorMariaDB, operatorBackup)
|
||||
@@ -941,31 +974,33 @@ func TestReconcileMariaDBBackup_CompleteConditionOutcome(t *testing.T) {
|
||||
t.Fatalf("reconcileMariaDB: %v", err)
|
||||
}
|
||||
|
||||
stored := &backupsv1alpha1.BackupJob{}
|
||||
if err := c.Get(context.Background(), client.ObjectKeyFromObject(seed), stored); err != nil {
|
||||
t.Fatalf("persisted Get: %v", err)
|
||||
}
|
||||
artifacts := &backupsv1alpha1.BackupList{}
|
||||
if err := c.List(context.Background(), artifacts, client.InNamespace("tenant")); err != nil {
|
||||
t.Fatalf("list Backups: %v", err)
|
||||
}
|
||||
switch {
|
||||
case tc.wantFailed:
|
||||
if bj.Status.Phase != backupsv1alpha1.BackupJobPhaseFailed {
|
||||
t.Errorf("expected Phase=Failed, got %q", bj.Status.Phase)
|
||||
if stored.Status.Phase != backupsv1alpha1.BackupJobPhaseFailed {
|
||||
t.Errorf("expected Phase=Failed, got %q", stored.Status.Phase)
|
||||
}
|
||||
if !strings.Contains(bj.Status.Message, "bj-op") {
|
||||
t.Errorf("failure message must name the operator Backup, got %q", bj.Status.Message)
|
||||
}
|
||||
if bj.Status.BackupRef != nil || len(artifacts.Items) != 0 {
|
||||
t.Errorf("a failed run must not produce a Backup: ref=%v artifacts=%d", bj.Status.BackupRef, len(artifacts.Items))
|
||||
assertMariaDBFailureMessage(t, stored.Status.Message, tc.wantMessage)
|
||||
if stored.Status.BackupRef != nil || len(artifacts.Items) != 0 {
|
||||
t.Errorf("a failed run must not produce a Backup: ref=%v artifacts=%d", stored.Status.BackupRef, len(artifacts.Items))
|
||||
}
|
||||
case tc.wantDone:
|
||||
if bj.Status.Phase != backupsv1alpha1.BackupJobPhaseSucceeded {
|
||||
t.Errorf("expected Phase=Succeeded, got %q (message %q)", bj.Status.Phase, bj.Status.Message)
|
||||
if stored.Status.Phase != backupsv1alpha1.BackupJobPhaseSucceeded {
|
||||
t.Errorf("expected Phase=Succeeded, got %q (message %q)", stored.Status.Phase, stored.Status.Message)
|
||||
}
|
||||
if bj.Status.BackupRef == nil || len(artifacts.Items) != 1 {
|
||||
t.Errorf("a successful run must produce exactly one Backup: ref=%v artifacts=%d", bj.Status.BackupRef, len(artifacts.Items))
|
||||
if stored.Status.BackupRef == nil || len(artifacts.Items) != 1 {
|
||||
t.Errorf("a successful run must produce exactly one Backup: ref=%v artifacts=%d", stored.Status.BackupRef, len(artifacts.Items))
|
||||
}
|
||||
default:
|
||||
if bj.Status.Phase != backupsv1alpha1.BackupJobPhaseRunning {
|
||||
t.Errorf("expected Phase=Running, got %q", bj.Status.Phase)
|
||||
if stored.Status.Phase != backupsv1alpha1.BackupJobPhaseRunning {
|
||||
t.Errorf("expected Phase=Running, got %q", stored.Status.Phase)
|
||||
}
|
||||
if res.RequeueAfter == 0 {
|
||||
t.Errorf("expected a requeue while the run is in progress, got %+v", res)
|
||||
@@ -983,7 +1018,6 @@ func TestReconcileMariaDBRestore_CompleteConditionOutcome(t *testing.T) {
|
||||
|
||||
for _, tc := range mariadbCompleteOutcomeCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
now := metav1.Now()
|
||||
cozyBackup := &backupsv1alpha1.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "tenant", Name: "src-cozy-bk"},
|
||||
Spec: backupsv1alpha1.BackupSpec{
|
||||
@@ -997,18 +1031,18 @@ func TestReconcileMariaDBRestore_CompleteConditionOutcome(t *testing.T) {
|
||||
BackupRef: corev1.LocalObjectReference{Name: cozyBackup.Name},
|
||||
TargetApplicationRef: &corev1.TypedLocalObjectReference{Kind: "MariaDB", Name: "target", APIGroup: &apps},
|
||||
},
|
||||
Status: backupsv1alpha1.RestoreJobStatus{StartedAt: &now, Phase: backupsv1alpha1.RestoreJobPhaseRunning},
|
||||
Status: backupsv1alpha1.RestoreJobStatus{StartedAt: mariadbOutcomeStartedAt(tc.expired), Phase: backupsv1alpha1.RestoreJobPhaseRunning},
|
||||
}
|
||||
operatorRestore := &mariadbtypes.Restore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "tenant",
|
||||
Name: "rj-op",
|
||||
Name: "op",
|
||||
Labels: map[string]string{
|
||||
backupsv1alpha1.OwningJobNameLabel: seed.Name,
|
||||
backupsv1alpha1.OwningJobNamespaceLabel: seed.Namespace,
|
||||
},
|
||||
},
|
||||
Status: mariadbtypes.RestoreStatus{Conditions: mariadbCompleteCondition(tc.status, tc.reason)},
|
||||
Status: mariadbtypes.RestoreStatus{Conditions: mariadbCompleteConditions(tc.noCondition, tc.status, tc.reason, tc.message)},
|
||||
}
|
||||
c := newMariaDBStrategyTestClient(t, cozyBackup, seed, operatorRestore,
|
||||
&mariadbtypes.Backup{ObjectMeta: metav1.ObjectMeta{Namespace: "tenant", Name: "src-op-bk"}},
|
||||
@@ -1024,21 +1058,23 @@ func TestReconcileMariaDBRestore_CompleteConditionOutcome(t *testing.T) {
|
||||
t.Fatalf("reconcileMariaDBRestore: %v", err)
|
||||
}
|
||||
|
||||
stored := &backupsv1alpha1.RestoreJob{}
|
||||
if err := c.Get(context.Background(), client.ObjectKeyFromObject(seed), stored); err != nil {
|
||||
t.Fatalf("persisted Get: %v", err)
|
||||
}
|
||||
switch {
|
||||
case tc.wantFailed:
|
||||
if rj.Status.Phase != backupsv1alpha1.RestoreJobPhaseFailed {
|
||||
t.Errorf("expected Phase=Failed, got %q", rj.Status.Phase)
|
||||
}
|
||||
if !strings.Contains(rj.Status.Message, "rj-op") {
|
||||
t.Errorf("failure message must name the operator Restore, got %q", rj.Status.Message)
|
||||
if stored.Status.Phase != backupsv1alpha1.RestoreJobPhaseFailed {
|
||||
t.Errorf("expected Phase=Failed, got %q", stored.Status.Phase)
|
||||
}
|
||||
assertMariaDBFailureMessage(t, stored.Status.Message, tc.wantMessage)
|
||||
case tc.wantDone:
|
||||
if rj.Status.Phase != backupsv1alpha1.RestoreJobPhaseSucceeded {
|
||||
t.Errorf("expected Phase=Succeeded, got %q (message %q)", rj.Status.Phase, rj.Status.Message)
|
||||
if stored.Status.Phase != backupsv1alpha1.RestoreJobPhaseSucceeded {
|
||||
t.Errorf("expected Phase=Succeeded, got %q (message %q)", stored.Status.Phase, stored.Status.Message)
|
||||
}
|
||||
default:
|
||||
if rj.Status.Phase != backupsv1alpha1.RestoreJobPhaseRunning {
|
||||
t.Errorf("expected Phase=Running, got %q", rj.Status.Phase)
|
||||
if stored.Status.Phase != backupsv1alpha1.RestoreJobPhaseRunning {
|
||||
t.Errorf("expected Phase=Running, got %q", stored.Status.Phase)
|
||||
}
|
||||
if res.RequeueAfter == 0 {
|
||||
t.Errorf("expected a requeue while the run is in progress, got %+v", res)
|
||||
|
||||
Reference in new issue
Block a user