Skip to content

Reset cp_sync_status on update of artifacts from gateway#2505

Merged
thivindu merged 1 commit into
wso2:mainfrom
thivindu:dp-readonly-platform-api
Jul 7, 2026
Merged

Reset cp_sync_status on update of artifacts from gateway#2505
thivindu merged 1 commit into
wso2:mainfrom
thivindu:dp-readonly-platform-api

Conversation

@thivindu

@thivindu thivindu commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

This pull request updates the config upsert logic to ensure that cp_sync_status and cp_sync_info are always reset on update, not just on insert. This fixes an issue where these fields could become stale, especially for gateway-origin artifacts, and adds regression tests to prevent future breakage.

Fixes -

Bug fix: Upsert logic for sync status fields

  • Updated the comment in sql_store.go to clarify that cp_sync_status and cp_sync_info are reset on update, while cp_artifact_id and created_at are only set on insert.
  • Modified the SQL upsert operation in sql_store.go to explicitly set cp_sync_status and cp_sync_info on updates, ensuring their values are refreshed appropriately.

Testing improvements

  • Added regression tests in sqlite_test.go to verify that:
    • Updating a gateway-origin config resets cp_sync_status to pending but preserves cp_artifact_id.
    • Updating a control-plane-origin config keeps cp_sync_status as NULL.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The gateway config upsert now overwrites CP sync fields on update, and platform-api adds shared unique-violation detection plus retry handling for handle-based artifact import conflicts, with new regression and contract tests.

Changes

UpsertConfig CP Sync Semantics

Layer / File(s) Summary
Update semantics for CP sync fields
gateway/gateway-controller/pkg/storage/sql_store.go
Documentation comment and upsertSpec setClauses/setValues updated so cp_sync_status and cp_sync_info are overwritten on UPDATE, while cp_artifact_id and created_at remain INSERT-only/preserved.
Regression tests for sync status behavior
gateway/gateway-controller/pkg/storage/sqlite_test.go
New subtests verify gateway-origin updates re-mark CPSyncStatus to pending while preserving CPArtifactID, and control-plane-origin updates keep CPSyncStatus empty/NULL across updates.

Shared Unique-Violation Handling and Import Retries

Layer / File(s) Summary
Shared unique-violation helper
platform-api/internal/repository/errors.go, platform-api/internal/repository/errors_test.go, platform-api/internal/repository/user_identity_mapping.go
A shared repository helper detects unique-constraint violations across SQLite, PostgreSQL, and SQL Server, the identity mapping code delegates to it, and tests cover nil, wrapped, and database-specific error formats.
Import retry orchestration
platform-api/internal/service/artifact_import.go
importValidated now retries handle resolution on unique-violation errors, uses a bounded retry count, clears missing existing context, and applies metadata-write decisions from incomingDeployedAt after helper execution.
Import conflict tests
platform-api/internal/service/artifact_import_test.go
New tests simulate a one-time unique violation, verify retries reuse the existing artifact and preserve last-in-wins behavior, and assert duplicate-handle imports surface errors recognized as unique violations across artifact kinds.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • wso2/api-platform#2289: Touches the same UpsertConfig UPDATE clause in gateway/gateway-controller/pkg/storage/sql_store.go.
  • wso2/api-platform#2448: Covers DP→CP sync scenarios that assert pending sync status behavior similar to the gateway storage change.

Suggested reviewers: RakhithaRR, Tharsanan1, VirajSalaka, malinthaprasan, Krishanx92, renuka-fernando

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: resetting cp_sync_status on gateway artifact updates.
Description check ✅ Passed The description covers purpose, goals, implementation details, issue link, and regression tests, so it is mostly complete.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
gateway/gateway-controller/pkg/storage/sqlite_test.go (1)

1365-1397: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't exercise cp_sync_info reset with a non-empty prior value.

UpdateCPSyncStatus(..., reason="") (Line 1379) leaves cp_sync_info at NULL, and the second UpsertConfig call never sets cfg.CPSyncInfo, so it stays NULL throughout. The test therefore only validates cp_sync_status reset, not the cp_sync_info reset the updated doc comment in sql_store.go also claims. Consider setting a non-empty cp_sync_info reason before the update-triggering call and asserting it's cleared/overwritten to strengthen coverage of the actual behavior change.

Suggested addition
 		// Simulate a successful DP->CP push: status success, CP UUID recorded.
-		assert.NilError(t, storage.UpdateCPSyncStatus(cfg.UUID, "cp-uuid-123", models.CPSyncStatusSuccess, ""))
+		assert.NilError(t, storage.UpdateCPSyncStatus(cfg.UUID, "cp-uuid-123", models.CPSyncStatusSuccess, "prior sync info"))
 		assert.Equal(t, models.CPSyncStatusPending, retrieved.CPSyncStatus)
 		// The CP UUID from the prior successful sync is preserved (not wiped by the update).
 		assert.Equal(t, "cp-uuid-123", retrieved.CPArtifactID)
+		// cp_sync_info should be reset (not carried over from the prior success state).
+		assert.Equal(t, "", retrieved.CPSyncInfo)
🤖 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 `@gateway/gateway-controller/pkg/storage/sqlite_test.go` around lines 1365 -
1397, The test in sqlite_test.go only verifies that UpsertConfig resets
cp_sync_status for gateway-origin configs, but it never exercises cp_sync_info
because UpdateCPSyncStatus is called with an empty reason and cfg.CPSyncInfo is
never set. Update the test around UpdateCPSyncStatus and the second UpsertConfig
call to seed a non-empty cp_sync_info value before the overwrite path, then
assert that GetConfig returns the expected cleared or overwritten cp_sync_info
after the update; keep the existing checks for CPSyncStatus and CPArtifactID in
the same test case.
🤖 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.

Nitpick comments:
In `@gateway/gateway-controller/pkg/storage/sqlite_test.go`:
- Around line 1365-1397: The test in sqlite_test.go only verifies that
UpsertConfig resets cp_sync_status for gateway-origin configs, but it never
exercises cp_sync_info because UpdateCPSyncStatus is called with an empty reason
and cfg.CPSyncInfo is never set. Update the test around UpdateCPSyncStatus and
the second UpsertConfig call to seed a non-empty cp_sync_info value before the
overwrite path, then assert that GetConfig returns the expected cleared or
overwritten cp_sync_info after the update; keep the existing checks for
CPSyncStatus and CPArtifactID in the same test case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 133fe693-ab51-459b-b261-9f623095e92a

📥 Commits

Reviewing files that changed from the base of the PR and between 3bf3abc and dd119a3.

📒 Files selected for processing (2)
  • gateway/gateway-controller/pkg/storage/sql_store.go
  • gateway/gateway-controller/pkg/storage/sqlite_test.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
platform-api/internal/service/artifact_import_test.go (2)

380-453: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test covering retry exhaustion.

Current tests only cover a single conflict resolved on the first retry. Add a case where failOnceImporter (or a variant) fails on every call so importConflictMaxRetries is exhausted, asserting Import ultimately returns the original error rather than looping incorrectly.

🤖 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 `@platform-api/internal/service/artifact_import_test.go` around lines 380 -
453, Add a retry-exhaustion test for ArtifactImport import conflicts: the
current tests around TestArtifactImport_RetriesAsUpdateOnHandleConflict and
TestArtifactImport_RetryPreservesLastInWinsForStalePush only cover a single
conflict that succeeds on retry. Introduce a failing importer variant (or extend
failOnceImporter) that returns the unique-violation on every call so the import
path in svc.Import exhausts importConflictMaxRetries, then assert Import returns
the original error and does not keep retrying indefinitely.

455-541: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider asserting no orphan artifacts row remains after the duplicate-handle violation.

This test proves duplicate handles surface a detectable unique violation, but doesn't assert that the losing Create left no dangling parent artifacts row. This is downstream of the transactionality concern flagged in artifact_import.go (retry loop / resolveAndImport) — see that comment for the root-cause discussion.

🤖 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.

Nitpick comments:
In `@platform-api/internal/service/artifact_import_test.go`:
- Around line 380-453: Add a retry-exhaustion test for ArtifactImport import
conflicts: the current tests around
TestArtifactImport_RetriesAsUpdateOnHandleConflict and
TestArtifactImport_RetryPreservesLastInWinsForStalePush only cover a single
conflict that succeeds on retry. Introduce a failing importer variant (or extend
failOnceImporter) that returns the unique-violation on every call so the import
path in svc.Import exhausts importConflictMaxRetries, then assert Import returns
the original error and does not keep retrying indefinitely.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fe40f2fe-b8f3-4117-8b69-d9f4e9badb3c

📥 Commits

Reviewing files that changed from the base of the PR and between dd119a3 and cb33be6.

📒 Files selected for processing (5)
  • platform-api/internal/repository/errors.go
  • platform-api/internal/repository/errors_test.go
  • platform-api/internal/repository/user_identity_mapping.go
  • platform-api/internal/service/artifact_import.go
  • platform-api/internal/service/artifact_import_test.go

"display_name = ?", "version = ?", "data_version = ?", "kind = ?", "handle = ?",
"desired_state = ?", "deployment_id = ?", "origin = ?",
"updated_at = ?", "deployed_at = ?",
"cp_sync_status = ?", "cp_sync_info = ?",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So in the existing code, any updates to an Artifact are not reflected in the CP in general?
I mean, as defined in this issue #2475 the GW has to restart and switch on/off sync. Or is just a PUT /rest-apis also not working?

@thivindu thivindu Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updates to artifacts using PUT calls when the sync was off were not reflected in AI Workspace when sync was turned on and the gateway connects. Updates to an artifact is reflected in the CP in real time when sync is on. The issue only arises when the update is done when sync is off.

@thivindu
thivindu merged commit 4c43468 into wso2:main Jul 7, 2026
16 of 18 checks passed
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