Skip to content

[Feature] Redesign DBInstance Controller Around Bounded Level-driven Reconciliation - #230

Open
Yohansenanayake wants to merge 77 commits into
wso2:operatorsfrom
Yohansenanayake:db-controller
Open

[Feature] Redesign DBInstance Controller Around Bounded Level-driven Reconciliation#230
Yohansenanayake wants to merge 77 commits into
wso2:operatorsfrom
Yohansenanayake:db-controller

Conversation

@Yohansenanayake

@Yohansenanayake Yohansenanayake commented Jul 16, 2026

Copy link
Copy Markdown

Summary

Redesigns the DBInstance controller from a phase-pointer workflow into a level-driven, bounded reconciliation pipeline.

The controller now derives decisions from observed Kubernetes and Harvester state rather than using status as memory of previously completed actions. Ordered, idempotent ensure steps continue only while their prerequisites are satisfied and stop after external mutations, asynchronous waits, terminal states, or transient failures.

Closes #229.

Design discussion: #207

Changes

Controller architecture

  • Replaces the status.provisioningPhase dispatcher with an ordered ensure pipeline covering finalization, preflight, credentials, VM, resize, power, database health, connection metadata, monitoring, bootstrap cleanup, and generation completion.
  • Introduces explicit Satisfied, Pending, Terminal, and Transient step outcomes.
  • Stops a reconcile pass at the first non-satisfied outcome and patches accumulated status before returning.
  • Uses watches and bounded fallback requeues instead of blocking or polling inside reconciliation.
  • Adds configurable cross-instance concurrency through --max-concurrent-reconciles.

Status and lifecycle contract

  • Makes conditions the detailed component-level contract and derives the user-facing phase as a summary.
  • Adds typed and enforced condition reasons.
  • Separates PostgreSQL usability (DatabaseReady), monitoring convergence (MonitoringReady), and aggregate product readiness (Ready).
  • Adds descriptive lifecycle phases including degraded, incompatible-parameters, and crash-loop-halted.
  • Advances top-level observedGeneration only after the complete ensure chain is re-observed as converged.
  • Correctly projects provisioning, resize, stop/start, degradation, crash-loop recovery, and deletion states.

Managed resources and credentials

  • Makes monitoring resources and the connection Secret guaranteed, bounded reconciliation steps.
  • Repairs drift or deletion of owned Secrets, Services, Endpoints, VirtualMachines, and ServiceMonitors through controller watches.
  • Splits credential material into:
    • a tenant-facing administrator credentials Secret;
    • a password-free tenant connection Secret;
    • controller-private internal credentials and TLS Secrets.
  • Redacts sensitive cloud-init userdata after PostgreSQL becomes ready, retains the mounted Secret, and re-observes the redaction before completing the generation.
  • Retains monitoring resources while an instance is stopped and removes them only during DBInstance deletion.
  • Uses UID-labelled cleanup for controller-private cross-namespace Secrets.

API and validation changes

  • Removes legacy status fields:
    • status.provisioningPhase
    • status.masterUserSecret
    • status.caCertPem
  • Replaces status.resources.secretName with status.resources.adminCredentialsSecretName.
  • Adds connection, monitoring, internal-credential, and private-TLS resource references.
  • Adds CEL validation and controller-side checks for immutable fields.
  • Removes obsolete Harvester client paths and consolidates reconciliation on the typed client.
  • Updates RBAC, manager configuration, CRDs, generated code, and user documentation.

This is intentionally a breaking redesign. Legacy status compatibility and migration behavior are not included because there are no production DBInstance resources requiring migration.

Testing

Verified with:

  • go test ./... -count=1
  • Envtest coverage for CRD validation and real API-server Secret create/update/no-op behavior.
  • Focused controller tests for bounded step outcomes, status projection, credentials, VM lifecycle, resize, stop/start, health, monitoring, connection Secrets, bootstrap cleanup, deletion, and generation convergence.
  • Manual lifecycle validation covering successful provisioning, class resize, stop/start transitions, crash loop halt and condition/phase reporting.

All automated tests pass.

Checklist

  • Tests / validation for the changed area pass
  • Docs updated if behaviour or interfaces changed
  • No secrets, tokens, or kubeconfigs committed
  • No internal or other-repository names, private hostnames, or environment names included

Summary by CodeRabbit

  • New Features
    • Improved DBInstance status/phase derivation with generation-aware lifecycle/health reporting, including degraded, incompatible parameters, and crash-loop-halted states.
    • Deterministic, tenant-safe connection Secret plus controller-managed credentials and TLS; health-gated monitoring setup and endpoint updates.
    • Cloud-init userdata redaction after Postgres becomes ready; cold resize support that blocks storage shrink.
  • Bug Fixes
    • Hardened deletion cleanup with reliable controller-private Secret removal and protection-aware status updates.
    • Prevented stale health/condition signals from overriding current reconciliation state.
  • Documentation
    • Updated DBInstance documentation for the revised Secret model, status fields, and reconciler behavior.
  • Tests
    • Expanded unit/envtest and env-based coverage for immutable validations, conditions, health/power/resize/credentials, and outcome orchestration.

…ance

- Added ensureReady function to set the DBInstance status to available and update observed generation.
- Created ensure_steps.go to manage the ordered steps during DBInstance provisioning.
- Implemented runEnsureSteps to execute provisioning steps and handle outcomes.
- Developed ensureVM function to ensure the VirtualMachine resource exists and create it if absent.
- Added tests for ensureReady, ensureVM, and provisioning steps to validate functionality.
- Removed obsolete probe_gate_test.go as its functionality is now covered by new tests.
- Introduced stubHarvester for testing, simulating interactions with the Harvester client.
- Added `ensureStorageResize` method to handle cold resizing of VMs based on shape drift.
- Introduced `vmShapeDrift` struct to compare VM's declared shape against desired specifications.
- Created tests for various scenarios in `ensure_resize_test.go`, including:
  - No drift satisfied
  - Class drift requiring VM stop
  - Waiting for teardown during resize
  - Applying class and storage changes when VM is down
  - Handling unsupported storage shrink attempts
- Updated `ensure_steps.go` to include the new resize step in the provisioning process.
- Refactored existing tests to accommodate changes in the lifecycle management of VMs.
- Enhanced `stubHarvester` to track resize calls for testing purposes.
- Modified `typed_client.go` and `typed_client_test.go` to reflect changes in volume claim template handling.
- Update `ensurePowerState` to suspend power management during crash-loop conditions, ensuring VMs are not restarted or stopped unnecessarily.
- Modify tests to validate that power management behaves correctly under crash-loop conditions, ensuring no VM calls are made while in a halted state.
- Enhance `ensureReady` to reflect degraded states accurately, ensuring that the phase remains consistent with health checks.
- Introduce new tests to verify that the system correctly handles degraded states and does not trigger unnecessary VM operations.
- Implement cleanup steps in the provisioning process to remove cloud-init secrets after VM creation.
- Adjust the stub harvester to log operations for better traceability during tests.
- Introduced a new resource package to handle declarative child objects (Service, Endpoints, ServiceMonitor) for monitoring.
- Updated DBInstance reconciler to create and manage monitoring resources with controller ownership.
- Added owner reference handling for VM creation to ensure proper garbage collection.
- Removed legacy monitoring deployment methods from the typed client.
- Enhanced tests to verify the creation and ownership of monitoring resources.
- Ensured that metrics Service and Endpoints are updated correctly on VM IP changes.
Move credential and TLS material generation out of the Harvester client and into a new internal/credentials package (Resolver.Resolve), decomposing the old monolithic per-instance Secret into three:

  - pg-<name>-credentials (tenant ns): admin_user/admin_password only
  - dbi-<uid>-internal (operator ns): repl_password/exporter_password
  - dbi-<uid>-tls (operator ns, type kubernetes.io/tls): CA + server cert/key

Each is get-or-create-once and never regenerated on reentry, preserving the
invariant that a booted VM's password/CA can't drift from what the operator holds. status.CACertPEM is removed; the CA is now served via a new
pg-<name>-connect Secret (internal/resource.ConnectionSecret) alongside
host/port/dbname/jdbcUrl/sslmode, reconciled once status.Endpoint is known.

Cloud-init rendering (internal/credentials/cloudinit.go, moved from internal/harvester) now reads from the resolved Material instead of generating its own secrets inline, and is applied via a new
internal/resource.CloudInitSecret builder before VM creation.
harvester.CreatePostgresVM is slimmed to (VMCreateParams) (vmName, err) with
a CloudInitSecretName field — it no longer generates any credential material
itself, in both the typed and legacy dynamic clients.

Because the two operator-namespace Secrets are cross-namespace from their
DBInstance and can't carry an owner reference, reconcileDelete now removes
them explicitly: by the recorded status.resources ref first, then a
dbaas.opencloud.wso2.com/dbinstance-uid label sweep as a backstop for a ref
lost to a status reset. A new --operator-namespace flag (default
POD_NAMESPACE via downward API, fallback "dbaas-system") controls where
these live.
…ace(PR9)

Close a real enforcement gap first: status.appliedSpec never tracked
spec.staticNetwork/spec.vmPassword (true since the original commit, not a
regression), so immutableDrift() silently allowed editing them post-create.
Both are now snapshotted and compared (equality.Semantic.DeepEqual for the
struct field).

Add +kubebuilder:validation:XValidation "self== oldSelf" transition rules
on networkRef/engineVersion/staticNetwork/vmPassword as API-server-level
defense-in-depth. Deliberately skip the other five documented-immutable
fields (osImage/dbName/masterUsername/port/storageType): immutableDrift()
compares them post-defaulting, so a raw CEL rule on the unset spec value
would be stricter than today's behavior (e.g. rejecting an explicit
port: 5432 after leaving it unset). No enum markers either: dbInstanceClass
is validated against a live Go map already, and storageType's real value on
Harvester is unconfirmed — enum-constraining either now would either
duplicate maintenance or risk locking out the correct value.

Remove the legacy dynamic-client Harvester implementation (client.go,
probe.go, cmd/harvester_dynamic.go) and the dead DialVMListener method it
was built around, both already self-flagged "not used anymore" in the code.
TypedClient is now the only implementation, constructed directly in
cmd/main.go with no build tag.

While touching ClientInterface, drop two more methods that never belonged
on a Harvester-specific contract: CreateDataVolume did no I/O at all (pure
string formatting, now the plain DataVolumeName function next to
buildPostgresVM), and DeleteSecret deleted a plain corev1.Secret via
client-go with nothing Harvester-specific about it (now a direct r.Delete
call in ensureBootstrapCleanup, matching the pattern deleteOperatorSecrets
already used). interface.go now holds only the actual contract: the
interface plus the DTOs its methods reference.
…for clarity, introduce new status for incompatible parameters, and ensure DatabaseReady condition is accurately set during power state and resize operations.
- Introduced a new `finalizeStatus` method to centralize status updates for DBInstance, ensuring consistent handling of Accepted, Ready, and InterventionRequired conditions.
- Replaced legacy condition checks with a more robust mechanism that uses ConditionReason for better clarity and maintainability.
- Updated tests to reflect changes in condition handling, ensuring that the status phase accurately represents the current state of the DBInstance.
- Removed the `markProvisioningFailed` function, consolidating its logic into the new condition management approach.
- Enhanced error handling in various steps to ensure that the DBInstance reflects the correct status based on the latest conditions.
- Added new tests to validate the behavior of the updated status management logic, ensuring comprehensive coverage of edge cases.
…geChangeRejected and update related logic and tests for clarity
… for resize operations and enhance test coverage for resize lifecycle
…ng for database recovery and update test cases for clarity
…ance comments for clarity on resize lifecycle aggregation
- Add ensureConnectionSecret function to publish tenant connection secrets after database health is established.
- Modify ensureCredentials to handle credential changes and requeue if necessary.
- Update tests to cover new connection secret logic and ensure proper handling of credentials.
- Refactor credential resolver to return a result indicating if the state has changed.
- Ensure connection secrets are created only when the database endpoint is known.
- Adjust provisioning steps to include connection secret management.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The DBInstance controller is redesigned around ordered ensure steps and observed state. The change adds condition-based status and phase derivation, durable credential/TLS Secrets, connection and monitoring resources, VM health and resize handling, cloud-init redaction, deletion cleanup, watches, schema validation, and extensive tests.

Changes

DBInstance controller redesign

Layer / File(s) Summary
API contracts and status model
database/api/v1alpha1/*, database/config/crd/*, database/README.md
Adds typed condition reasons, generation-aware condition helpers, phase derivation, immutable-field validation, expanded resource references, generated deepcopy updates, and revised Secret/TLS documentation.
Credentials and child resources
database/internal/credentials/*, database/internal/resource/*
Adds durable credential/TLS resolution, cloud-init generation, password-free connection Secrets, monitoring builders, owner references, and idempotent child-resource application.
Ensure-step runner and preflight
database/internal/controller/ensure_steps.go, errors.go, status*.go, ensure_preflight.go
Replaces phase dispatch with ordered ensure steps, typed outcomes, terminal/transient error handling, status finalization, immutable drift checks, and generation tracking.
VM, resize, and power convergence
database/internal/controller/ensure_vm.go, ensure_resize.go, ensure_power.go
Creates and repairs VMs, snapshots immutable values, applies cold resize operations, coordinates VMI teardown, and handles start, stop, and crash-loop-halted states.
Health, monitoring, and bootstrap cleanup
database/internal/controller/ensure_health.go, ensure_monitoring.go, ensure_connection_secret.go, ensure_bootstrap_cleanup.go
Adds readiness gating, steady-state degradation reporting, crash-loop parking and recovery, connection Secret publication, monitoring reconciliation, and cloud-init userdata redaction.
Deletion, watches, and deployment wiring
database/internal/controller/dbinstance_controller.go, watches.go, database/cmd/*, database/config/*
Adds operator-Secret cleanup, VMI health watches, owned-resource watches, namespace and concurrency flags, API scheme registration, RBAC updates, and manager namespace injection.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the controller reconciliation redesign.
Description check ✅ Passed The description matches the template sections and includes summary, changes, testing, and checklist details.
Linked Issues check ✅ Passed The changes align with #229 by replacing phase-pointer reconciliation with ordered level-driven ensure steps and status-as-progress semantics.
Out of Scope Changes check ✅ Passed No clearly unrelated code changes stand out; the diff stays focused on the controller redesign and supporting docs/tests.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ent shell injection

test(credentials): add test for cloud-init to ensure shell metacharacters are neutralized
fix(controller): add check to prevent VM restart when VMI is gone

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
database/internal/controller/status.go (1)

56-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Resolve and remove the leftover "redudant?" comment.

This line ships with an open question as an inline comment (//redudant ? can we remove this ?, also misspelled). The assignment is likely intentional — it re-syncs inst.Status to the actual persisted object even when only semantically equal to desiredStatus, guarding against subtle field differences (e.g., server-set LastTransitionTime) that equality.Semantic.DeepEqual ignores but exact equality wouldn't. Please resolve the question and replace the comment with a clear rationale rather than shipping a rhetorical note.

📝 Proposed fix
-			inst.Status = *latest.Status.DeepCopy() //redudant ? can we remove this ?
+			// Sync the caller's status with the persisted value; latest.Status may
+			// differ from desiredStatus in fields equality.Semantic.DeepEqual
+			// treats as equal (e.g. LastTransitionTime set by an earlier patch).
+			inst.Status = *latest.Status.DeepCopy()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/internal/controller/status.go` around lines 56 - 60, Resolve the
inline comment on the inst.Status assignment in the status reconciliation flow:
retain the assignment because it re-syncs inst.Status with the persisted
latest.Status when semantic equality hides server-populated field differences,
and replace the rhetorical misspelled note with a concise rationale describing
that purpose.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@database/README.md`:
- Around line 38-53: The Monitoring row in the “What it provisions” section
contains a dangling “see below” reference; remove that cross-reference while
preserving the exporter-install-pending statement, since no corresponding
limitation entry remains in the “Not yet implemented” table.

---

Nitpick comments:
In `@database/internal/controller/status.go`:
- Around line 56-60: Resolve the inline comment on the inst.Status assignment in
the status reconciliation flow: retain the assignment because it re-syncs
inst.Status with the persisted latest.Status when semantic equality hides
server-populated field differences, and replace the rhetorical misspelled note
with a concise rationale describing that purpose.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9aa3d38e-9927-4fbb-83e1-5cc4b4fe6eb0

📥 Commits

Reviewing files that changed from the base of the PR and between 79b0f77 and 6ef8577.

📒 Files selected for processing (22)
  • database/README.md
  • database/api/v1alpha1/dbinstance_types.go
  • database/internal/controller/dbinstance_controller.go
  • database/internal/controller/ensure_connection_secret.go
  • database/internal/controller/ensure_finalizer.go
  • database/internal/controller/ensure_health.go
  • database/internal/controller/ensure_health_test.go
  • database/internal/controller/ensure_monitoring_test.go
  • database/internal/controller/ensure_power.go
  • database/internal/controller/ensure_power_test.go
  • database/internal/controller/ensure_preflight_test.go
  • database/internal/controller/ensure_resize_test.go
  • database/internal/controller/ensure_steps.go
  • database/internal/controller/ensure_vm_test.go
  • database/internal/controller/failed_recovery_test.go
  • database/internal/controller/outcomes.go
  • database/internal/controller/reconcile_delete_test.go
  • database/internal/controller/status.go
  • database/internal/controller/stub_harvester_test.go
  • database/internal/harvester/interface.go
  • database/internal/harvester/typed_client.go
  • database/internal/harvester/typed_client_test.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • database/internal/controller/ensure_finalizer.go
  • database/internal/controller/outcomes.go
  • database/internal/controller/ensure_steps.go
  • database/internal/controller/ensure_connection_secret.go
  • database/internal/controller/ensure_vm_test.go
  • database/internal/controller/stub_harvester_test.go
  • database/internal/harvester/interface.go
  • database/internal/controller/ensure_preflight_test.go
  • database/internal/controller/ensure_health_test.go
  • database/internal/controller/ensure_power.go
  • database/internal/controller/ensure_resize_test.go
  • database/internal/controller/failed_recovery_test.go
  • database/internal/controller/ensure_monitoring_test.go
  • database/api/v1alpha1/dbinstance_types.go
  • database/internal/harvester/typed_client.go
  • database/internal/controller/ensure_health.go

Comment thread database/README.md

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@database/internal/controller/watches.go`:
- Around line 75-90: Update vmiInterfaceIPsEqual to distinguish missing
interface names from entries whose IP is the empty string by checking newIPs key
existence alongside value equality. Preserve the existing length check and
return false whenever a name is absent or its IP differs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c779c69-a4bc-438b-b1b2-503e3e0b63ba

📥 Commits

Reviewing files that changed from the base of the PR and between 6ef8577 and 7fec0c8.

📒 Files selected for processing (8)
  • database/api/v1alpha1/dbinstance_types.go
  • database/internal/controller/status_conditions.go
  • database/internal/controller/status_ready.go
  • database/internal/controller/watches.go
  • database/internal/credentials/cloudinit.go
  • database/internal/credentials/cloudinit_test.go
  • database/internal/resource/builder_test.go
  • database/internal/resource/metrics_endpoints.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • database/internal/controller/status_ready.go
  • database/internal/resource/metrics_endpoints.go
  • database/internal/controller/status_conditions.go
  • database/internal/resource/builder_test.go
  • database/internal/credentials/cloudinit.go
  • database/api/v1alpha1/dbinstance_types.go

Comment thread database/internal/controller/watches.go
…hecks; enhance error classification for image resolution
…ng; remove unused code and enhance status patching logic
- introduce the ensure.Step interface and Runner
- move ensure-step implementations into internal/ensure
- centralize step results and dependencies
- expose shared condition helpers on DBInstance
- move reusable test fixtures into internal/testutil
- preserve existing reconciliation behavior and test coverage
- move step-specific tests beside their implementations
- retain reconciliation and envtest coverage in the controller package
- remove the legacy controller test adapter
- centralize shared fixtures in package-local test helpers
- clean up stale helpers, comments, and scaffold TODOs
@Yohansenanayake Yohansenanayake self-assigned this Jul 25, 2026
@Yohansenanayake Yohansenanayake added the Severity/Blocker Blocks development or testing; must be fixed immediately label Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area/Operators Kubernetes operators Severity/Blocker Blocks development or testing; must be fixed immediately

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants