Skip to content

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

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

[Feature] Redesign DBInstance Controller Around Bounded Level-driven Reconciliation#230
Yohansenanayake wants to merge 61 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
    • Introduced condition-based DBInstance phases with generation-aware readiness/health, including degraded, incompatible-parameters, and crash-loop-halted.
    • Published a password-free tenant connection Secret (endpoint/database, JDBC URL, pinned CA) plus controller-managed TLS/credential Secrets, alongside stepped reconciliation.
    • Added monitoring resource provisioning/repair, cloud-init userdata redaction after Postgres readiness, and VM/storage resize (rejecting unsupported shrink).
  • Bug Fixes
    • Improved deletion cleanup and ensured stale health/condition signals can’t override current state.
  • Documentation
    • Updated database documentation to match the revised Secret/connection and reconciliation model.

…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

Warning

Review limit reached

@Yohansenanayake, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6d83de97-3ee1-4620-a93f-5389baf28ea5

📥 Commits

Reviewing files that changed from the base of the PR and between 79b0f77 and 91168a2.

📒 Files selected for processing (1)
  • database/README.md
📝 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 46.34% 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 clearly summarizes the main change: a bounded, level-driven DBInstance controller redesign.
Description check ✅ Passed The description follows the template and includes summary, changes, testing, and checklist sections.
Linked Issues check ✅ Passed The changes replace phase-pointer logic with ordered ensure steps and status-as-progress, matching #229.
Out of Scope Changes check ✅ Passed The docs, CRD, RBAC, dependency, and test updates all support the redesign and do not appear unrelated.
✨ 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.

@Yohansenanayake Yohansenanayake added the Area/Operators Kubernetes operators label Jul 19, 2026

@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: 2

🤖 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/dbinstance_controller.go`:
- Around line 224-226: Update the deletion path around finalizeStatus and
patchStatusIfChanged to capture and return the patch error instead of discarding
it. Preserve the successful return when the status update succeeds, and
propagate failures so controller retry behavior requeues reconciliation.

In `@database/README.md`:
- Around line 20-26: Update the “What’s NOT in this version” table in the README
to remove or revise the entry claiming status.conditions is not written, so it
consistently documents the condition-based status model described in the
Reconciler section.
🪄 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: 49e947f2-ad1c-429f-9ebe-03974aaddcb8

📥 Commits

Reviewing files that changed from the base of the PR and between b81af17 and bbc122f.

📒 Files selected for processing (17)
  • database/README.md
  • database/api/v1alpha1/condition_reason_enforcement_test.go
  • database/config/crd/bases/dbaas.opencloud.wso2.com_dbinstances.yaml
  • database/internal/controller/dbinstance_controller.go
  • database/internal/controller/ensure_health.go
  • database/internal/controller/ensure_health_test.go
  • database/internal/controller/reconcile_delete_test.go
  • database/internal/controller/status_ready.go
  • database/internal/controller/stop_start_test.go
  • database/internal/credentials/material.go
  • database/internal/credentials/resolver.go
  • database/internal/credentials/resolver_test.go
  • database/internal/harvester/typed_client.go
  • database/internal/harvester/typed_client_test.go
  • database/internal/resource/builder_test.go
  • database/internal/resource/connection_secret.go
  • database/internal/resource/metrics_endpoints.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • database/internal/resource/metrics_endpoints.go
  • database/internal/controller/status_ready.go
  • database/internal/resource/connection_secret.go
  • database/api/v1alpha1/condition_reason_enforcement_test.go
  • database/internal/controller/reconcile_delete_test.go
  • database/internal/credentials/resolver_test.go
  • database/internal/credentials/resolver.go
  • database/internal/controller/ensure_health.go
  • database/internal/controller/stop_start_test.go
  • database/internal/controller/ensure_health_test.go
  • database/internal/harvester/typed_client_test.go
  • database/internal/harvester/typed_client.go
  • database/config/crd/bases/dbaas.opencloud.wso2.com_dbinstances.yaml

Comment thread database/internal/controller/dbinstance_controller.go
Comment thread database/README.md Outdated
@Yohansenanayake Yohansenanayake changed the title [Feature]: Redesign DBInstance Controller Around Bounded Level-driven Reconciliation [Feature] Redesign DBInstance Controller Around Bounded Level-driven Reconciliation Jul 19, 2026
…method

fix(docs): update comment to reflect single NIC configuration in BuildNetworkData
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area/Operators Kubernetes operators

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant