Skip to content

SCF-830: Added state management for cluster hibernation#17

Open
adshin21 wants to merge 13 commits into
scalefield_v18from
scalefield_v18-SCF-830-cluster-hibernation
Open

SCF-830: Added state management for cluster hibernation#17
adshin21 wants to merge 13 commits into
scalefield_v18from
scalefield_v18-SCF-830-cluster-hibernation

Conversation

@adshin21

Copy link
Copy Markdown

No description provided.

@coveralls

coveralls commented May 13, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 29513254178

Warning

No base build found for commit d16504e on scalefield_v18.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 44.821%

Details

  • Patch coverage: 119 uncovered changes across 7 files (245 of 364 lines covered, 67.31%).

Uncovered Changes

File Changed Covered %
pkg/cluster/lifecycle.go 244 189 77.46%
pkg/cluster/cluster.go 51 34 66.67%
pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go 16 1 6.25%
pkg/cluster/util.go 14 0 0.0%
pkg/apis/acid.zalan.do/v1/util.go 6 0 0.0%
pkg/cluster/sync.go 27 21 77.78%
pkg/util/k8sutil/k8sutil.go 6 0 0.0%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 15738
Covered Lines: 7054
Line Coverage: 44.82%
Coverage Strength: 16.33 hits per line

💛 - Coveralls

@adshin21
adshin21 marked this pull request as ready for review May 21, 2026 07:26
@adshin21
adshin21 requested a review from hughcapet as a code owner May 21, 2026 07:26
@serdardalgic
serdardalgic requested a review from ants May 22, 2026 09:20

@serdardalgic serdardalgic left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor issues @adshin21. After that, I would like to review it again.

Comment thread pkg/cluster/lifecycle.go Outdated
if c.Statefulset == nil || c.Statefulset.Spec.Replicas == nil {
return nil
}
return c.Statefulset.Spec.Replicas

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return c.Statefulset.Spec.Replicas
return c.Statefulset.Status.Replicas

Shouldn't this be Status as you want to get the current replica count of the StatefulSet?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but to get the current status, we need to make an API call to k8s.

Comment thread pkg/cluster/cluster.go
Comment thread pkg/cluster/lifecycle.go Outdated
const (
LifecycleActionNone LifecycleAction = iota
LifecycleActionHibernate // Running -> Stopping (initiate hibernate)
LifecycleActionStoppingCompleted // Stopping -> Stopped (pods fully terminated)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using LifecycleActionStoppingCompleted ? If not, please remove it.

@adshin21
adshin21 requested a review from serdardalgic July 14, 2026 08:15

@serdardalgic serdardalgic left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, some stuff that caught my attention, here it is.

Comment thread pkg/cluster/cluster_test.go Outdated
Comment thread pkg/cluster/lifecycle.go
Comment on lines +39 to +54
isWakingUpSimple := newSpecLifecycle == nil || newSpecLifecycle.Phase != "stopped"
hasPreviousInstances := newSpecPreviousNumberOfInstances > 0
needsRestore := newSpecNumberOfInstances == 0

isWakingUp := isWakingUpSimple && hasPreviousInstances && needsRestore

if currentStatus.Stopped() || isWakingUp {
if isWakingUp || newSpecLifecycle == nil || newSpecLifecycle.Phase != "stopped" {
return LifecycleActionWakeUp
}
}

if newSpecLifecycle != nil &&
newSpecLifecycle.Phase == "stopped" &&
!currentStatus.Stopping() &&
!currentStatus.Stopped() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd write this part a bit simpler.
isWakingUp already requires isWakingUpSimple to be true (it's isWakingUpSimple && hasPreviousInstances && needsRestore), so checking isWakingUp || isWakingUpSimple in the inner if is redundant, it's the same as just checking isWakingUpSimple. That makes the nested if hard to follow: you have to trace three variables back to see that one term never actually changes the outcome. Splitting it into two flat, named checks makes the same logic easier to read:

Suggested change
isWakingUpSimple := newSpecLifecycle == nil || newSpecLifecycle.Phase != "stopped"
hasPreviousInstances := newSpecPreviousNumberOfInstances > 0
needsRestore := newSpecNumberOfInstances == 0
isWakingUp := isWakingUpSimple && hasPreviousInstances && needsRestore
if currentStatus.Stopped() || isWakingUp {
if isWakingUp || newSpecLifecycle == nil || newSpecLifecycle.Phase != "stopped" {
return LifecycleActionWakeUp
}
}
if newSpecLifecycle != nil &&
newSpecLifecycle.Phase == "stopped" &&
!currentStatus.Stopping() &&
!currentStatus.Stopped() {
wantsStopped := newSpecLifecycle != nil && newSpecLifecycle.Phase == "stopped"
// The cluster was in the middle of hibernating (already scaled down,
// old replica count saved) when the spec changed its mind and no
// longer wants it stopped.
if !wantsStopped && newSpecPreviousNumberOfInstances > 0 && newSpecNumberOfInstances == 0 {
return LifecycleActionWakeUp
}
// Already stopped and the spec no longer asks to stay stopped.
if currentStatus.Stopped() && !wantsStopped {
return LifecycleActionWakeUp
}
if wantsStopped && !currentStatus.Stopping() && !currentStatus.Stopped() {
return LifecycleActionHibernate
}

I checked this produces the same result as the original in every case, and all 9 existing detectLifecycleTransition unit tests still pass with no changes needed.

* Suspend the logical backup CronJob (if enabled)
* Set the cluster status to "Stopping", then "Stopped"

When this field is removed from a stopped cluster, the operator will:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small suggestion, just to be more precise here: this doesn't just trigger on removing the field, setting phase to anything other than "stopped" (including "") also wakes the cluster up:

Suggested change
When this field is removed from a stopped cluster, the operator will:
When this field is removed, or set to any value other than `"stopped"`
(including an empty string), on a stopped cluster, the operator will:


// LifecycleSpec describes the lifecycle state of a Postgres cluster.
type LifecycleSpec struct {
Phase string `json:"phase,omitempty"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase is a bare string and the only value the code ever checks for is "stopped", everything else means "not stopped." Since there's no plan for other phase values at least for now, I'd suggest making that explicit rather than leaving it open-ended:

  • A typed constant, e.g. type LifecyclePhase string + LifecyclePhaseStopped LifecyclePhase = "stopped" added to pkg/apis/acid.zalan.do/v1/const.go, used in lifecycle.go:39 and cluster.go:1288 instead of the literal "stopped".
  • A CRD enum: ["", "stopped"] on spec.lifecycle.phase (manifests/postgresql.crd.yaml) so a typo like "Stopped" is rejected at admission instead of silently no-op'ing.

Since this PR is the one introducing spec.lifecycle.phase in the first place, there's no existing-cluster migration risk, worth just adding the enum: now rather than as a follow-up.

Comment thread pkg/cluster/resources.go
Comment on lines +828 to +829


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

This is also a mistake, I guess.

Comment thread pkg/cluster/cluster.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants