Conversation
egegunes
left a comment
There was a problem hiding this comment.
is it possible to cover this fix with some unit tests?
| syncPgbackrestFromPostgresToPercona(cr, status) | ||
|
|
||
| repoCondition := meta.FindStatusCondition(status.Conditions, postgrescluster.ConditionRepoHostReady) | ||
| repoCondition := meta.FindStatusCondition(cr.Status.Conditions, postgrescluster.ConditionRepoHostReady) |
There was a problem hiding this comment.
Since status and cr are already synced, does it matter switching to cr.Status from status? Just trying to understand if this change needs to be made after all.
There was a problem hiding this comment.
You're right. After sync they're the same. I'm suggesting cr.Status.Conditions mainly for consistency - setClusterNotReadyCondition uses it, so the checks should too. But it's not critical, we can keep status.Conditions.
WDYT?
| } | ||
|
|
||
| newCond := metav1.Condition{ | ||
| _ = meta.SetStatusCondition(&cr.Status.Conditions, metav1.Condition{ |
There was a problem hiding this comment.
Adding this comment here regarding unit testing. Agreeing with @egegunes, IMO we could add comprehensive unit test func TestUpdateConditions(t *testing.T) checking the updateConditions function.
There was a problem hiding this comment.
updateConditions is already covered by integration tests. Should I add unit tests for it as well, or replace the integration tests with unit tests?
| }) | ||
| }) | ||
|
|
||
| var _ = Describe("syncConditionsFromPostgresToPercona", func() { |
There was a problem hiding this comment.
I had in mind something like that:
func TestUpdateConditions(t *testing.T) {
switchover := "switching"
switchoverTimeline := int64(3)
tests := []struct {
name string
crConditions []metav1.Condition
statusConditions []metav1.Condition
pgBackRestStatus *v1beta1.PGBackRestStatus
patroniStatus v1beta1.PatroniStatus
expectedReadyForBackupStatus metav1.ConditionStatus
expectedReadyForBackupReason string
expectedSyncedConditions []metav1.Condition
expectedPGBackRest *v1beta1.PGBackRestStatus
expectedPatroni *v1beta1.PatroniStatus
}{
{
name: "no conditions set - RepoHostReady missing",
expectedReadyForBackupStatus: metav1.ConditionFalse,
expectedReadyForBackupReason: postgrescluster.ConditionRepoHostReady,
},
{
name: "RepoHostReady is false",
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionFalse,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionTrue,
Reason: "test",
},
},
expectedReadyForBackupStatus: metav1.ConditionFalse,
expectedReadyForBackupReason: postgrescluster.ConditionRepoHostReady,
expectedSyncedConditions: []metav1.Condition{
{Type: postgrescluster.ConditionRepoHostReady, Status: metav1.ConditionFalse, Reason: "test"},
{Type: postgrescluster.ConditionReplicaCreate, Status: metav1.ConditionTrue, Reason: "test"},
},
},
{
name: "RepoHostReady is true but ReplicaCreate is missing",
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
},
expectedReadyForBackupStatus: metav1.ConditionFalse,
expectedReadyForBackupReason: postgrescluster.ConditionReplicaCreate,
expectedSyncedConditions: []metav1.Condition{
{Type: postgrescluster.ConditionRepoHostReady, Status: metav1.ConditionTrue, Reason: "test"},
},
},
{
name: "RepoHostReady is true but ReplicaCreate is false",
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionFalse,
Reason: "test",
},
},
expectedReadyForBackupStatus: metav1.ConditionFalse,
expectedReadyForBackupReason: postgrescluster.ConditionReplicaCreate,
expectedSyncedConditions: []metav1.Condition{
{Type: postgrescluster.ConditionRepoHostReady, Status: metav1.ConditionTrue, Reason: "test"},
{Type: postgrescluster.ConditionReplicaCreate, Status: metav1.ConditionFalse, Reason: "test"},
},
},
{
name: "both conditions are true",
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionTrue,
Reason: "test",
},
},
expectedReadyForBackupStatus: metav1.ConditionTrue,
expectedReadyForBackupReason: "AllConditionsAreTrue",
expectedSyncedConditions: []metav1.Condition{
{Type: postgrescluster.ConditionRepoHostReady, Status: metav1.ConditionTrue, Reason: "test"},
{Type: postgrescluster.ConditionReplicaCreate, Status: metav1.ConditionTrue, Reason: "test"},
},
},
{
name: "existing ReadyForBackup condition gets updated when RepoHostReady becomes true",
crConditions: []metav1.Condition{
{
Type: pNaming.ConditionClusterIsReadyForBackup,
Status: metav1.ConditionFalse,
Reason: postgrescluster.ConditionRepoHostReady,
},
},
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionTrue,
Reason: "test",
},
},
expectedReadyForBackupStatus: metav1.ConditionTrue,
expectedReadyForBackupReason: "AllConditionsAreTrue",
expectedSyncedConditions: []metav1.Condition{
{Type: postgrescluster.ConditionRepoHostReady, Status: metav1.ConditionTrue, Reason: "test"},
{Type: postgrescluster.ConditionReplicaCreate, Status: metav1.ConditionTrue, Reason: "test"},
},
},
{
name: "existing true condition stays true when both conditions remain true",
crConditions: []metav1.Condition{
{
Type: pNaming.ConditionClusterIsReadyForBackup,
Status: metav1.ConditionTrue,
Reason: "AllConditionsAreTrue",
},
},
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionTrue,
Reason: "test",
},
},
expectedReadyForBackupStatus: metav1.ConditionTrue,
expectedReadyForBackupReason: "AllConditionsAreTrue",
expectedSyncedConditions: []metav1.Condition{
{Type: postgrescluster.ConditionRepoHostReady, Status: metav1.ConditionTrue, Reason: "test"},
{Type: postgrescluster.ConditionReplicaCreate, Status: metav1.ConditionTrue, Reason: "test"},
},
},
{
name: "syncs conditions from postgres status to percona cr",
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: "SomeOtherCondition",
Status: metav1.ConditionTrue,
Reason: "other",
},
},
expectedReadyForBackupStatus: metav1.ConditionTrue,
expectedReadyForBackupReason: "AllConditionsAreTrue",
expectedSyncedConditions: []metav1.Condition{
{Type: postgrescluster.ConditionRepoHostReady, Status: metav1.ConditionTrue, Reason: "test"},
{Type: postgrescluster.ConditionReplicaCreate, Status: metav1.ConditionTrue, Reason: "test"},
{Type: "SomeOtherCondition", Status: metav1.ConditionTrue, Reason: "other"},
},
},
{
name: "existing condition on cr is not updated by sync",
crConditions: []metav1.Condition{
{
Type: "SomeOtherCondition",
Status: metav1.ConditionFalse,
Reason: "original-reason",
},
},
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: "SomeOtherCondition",
Status: metav1.ConditionTrue,
Reason: "updated-reason",
},
},
expectedReadyForBackupStatus: metav1.ConditionTrue,
expectedReadyForBackupReason: "AllConditionsAreTrue",
expectedSyncedConditions: []metav1.Condition{
{Type: postgrescluster.ConditionRepoHostReady, Status: metav1.ConditionTrue, Reason: "test"},
{Type: postgrescluster.ConditionReplicaCreate, Status: metav1.ConditionTrue, Reason: "test"},
{Type: "SomeOtherCondition", Status: metav1.ConditionTrue, Reason: "updated-reason"},
},
},
{
name: "syncs pgbackrest status from postgres to percona",
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionTrue,
Reason: "test",
},
},
pgBackRestStatus: &v1beta1.PGBackRestStatus{
RepoHost: &v1beta1.RepoHostStatus{Ready: true},
},
expectedReadyForBackupStatus: metav1.ConditionTrue,
expectedReadyForBackupReason: "AllConditionsAreTrue",
expectedPGBackRest: &v1beta1.PGBackRestStatus{
RepoHost: &v1beta1.RepoHostStatus{Ready: true},
},
},
{
name: "syncs patroni status from postgres to percona",
statusConditions: []metav1.Condition{
{
Type: postgrescluster.ConditionRepoHostReady,
Status: metav1.ConditionTrue,
Reason: "test",
},
{
Type: postgrescluster.ConditionReplicaCreate,
Status: metav1.ConditionTrue,
Reason: "test",
},
},
patroniStatus: v1beta1.PatroniStatus{
SystemIdentifier: "12345",
Switchover: &switchover,
SwitchoverTimeline: &switchoverTimeline,
},
expectedReadyForBackupStatus: metav1.ConditionTrue,
expectedReadyForBackupReason: "AllConditionsAreTrue",
expectedPatroni: &v1beta1.PatroniStatus{
SystemIdentifier: "12345",
Switchover: &switchover,
SwitchoverTimeline: &switchoverTimeline,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cr := &v2.PerconaPGCluster{
Status: v2.PerconaPGClusterStatus{},
}
if tt.crConditions != nil {
cr.Status.Conditions = tt.crConditions
}
status := &v1beta1.PostgresClusterStatus{
Patroni: tt.patroniStatus,
}
if tt.statusConditions != nil {
status.Conditions = tt.statusConditions
}
if tt.pgBackRestStatus != nil {
status.PGBackRest = tt.pgBackRestStatus
}
updateConditions(cr, status)
condition := meta.FindStatusCondition(cr.Status.Conditions, pNaming.ConditionClusterIsReadyForBackup)
require.NotNil(t, condition)
assert.Equal(t, tt.expectedReadyForBackupStatus, condition.Status)
assert.Equal(t, tt.expectedReadyForBackupReason, condition.Reason)
for _, expected := range tt.expectedSyncedConditions {
synced := meta.FindStatusCondition(cr.Status.Conditions, expected.Type)
if !assert.NotNil(t, synced) {
continue
}
assert.Equal(t, expected.Status, synced.Status)
assert.Equal(t, expected.Reason, synced.Reason)
}
if tt.expectedPGBackRest != nil {
require.NotNil(t, cr.Status.PGBackRest)
require.NotNil(t, cr.Status.PGBackRest.RepoHost)
assert.Equal(t, tt.expectedPGBackRest.RepoHost.Ready, cr.Status.PGBackRest.RepoHost.Ready)
}
if tt.expectedPatroni != nil {
require.NotNil(t, cr.Status.Patroni.Status)
assert.Equal(t, tt.expectedPatroni.SystemIdentifier, cr.Status.Patroni.Status.SystemIdentifier)
assert.Equal(t, tt.expectedPatroni.Switchover, cr.Status.Patroni.Status.Switchover)
assert.Equal(t, tt.expectedPatroni.SwitchoverTimeline, cr.Status.Patroni.Status.SwitchoverTimeline)
}
})
}
}
Firstly because we dont need to use ginkgo formating for it, and secondly because instead of just testing syncConditionsFromPostgresToPercona, we can check the whole updateConditions func.
| // Test coverage: | ||
| // - ClusterIsReadyForBackup condition logic: | ||
| // - no conditions set (RepoHostReady missing) | ||
| // - RepoHostReady is false | ||
| // - RepoHostReady is true but ReplicaCreate is missing | ||
| // - RepoHostReady is true but ReplicaCreate is false | ||
| // - both conditions are true | ||
| // - existing condition gets updated when status changes | ||
| // - existing condition stays unchanged when status remains the same | ||
| // - Condition synchronization from Postgres to Percona: | ||
| // - syncs all conditions from postgres status | ||
| // - updates existing conditions on CR | ||
| // - Status synchronization: | ||
| // - syncs PGBackRest status | ||
| // - syncs Patroni status |
There was a problem hiding this comment.
I put it by reason, yes, because we test many cases and I thought it could be nice to see all of them as the list. I can delete it. No problem.
There was a problem hiding this comment.
yea I agree, it is not needed, we have names on each test case already, this will be hard to maintain
There was a problem hiding this comment.
yes, it'll be forgotten and quickly become outdated
…percona-postgresql-operator into K8SPG-938_fix_conditions_update
commit: 2898177 |
CHANGE DESCRIPTION
Problem:
The
syncConditionsFromPostgresToPerconafunction wasn't updating existing conditions - it only added new ones.Additionally, the updateConditions function was checking conditions from the status parameter (PostgresCluster status) instead of cr.Status.Conditions (where we just copied them), which could lead to inconsistencies.
Cause:
Short explanation of the root cause of the issue if applicable.
Solution:
Short explanation of the solution we are providing with this PR.
CHECKLIST
Jira
Needs Doc) and QA (Needs QA)?Tests
Config/Logging/Testability