feat: Support version history and rollback for traffic rules#1477
Open
mochengqian wants to merge 6 commits into
Open
feat: Support version history and rollback for traffic rules#1477mochengqian wants to merge 6 commits into
mochengqian wants to merge 6 commits into
Conversation
6deb25e to
9596f7e
Compare
2471544 to
d2a6ddc
Compare
New pkg/core/versioning package + REST endpoints record an immutable
version row for every condition-route, tag-route, and dynamic-config
change. ADMIN writes go through an intent (Begin → Apply → Commit)
that the event-bus subscriber attaches to the matching upstream echo.
UPSTREAM/ROLLBACK/BOOTSTRAP sources land via the same subscriber path.
Default off (versioning.enabled=false). When enabled, GORM AutoMigrate
creates rule_version + rule_version_meta + rule_version_intent; a
bootstrap scan emits one source=BOOTSTRAP row per existing rule.
Retention defaults to 5 versions per rule, trimmed on insert.
Optimistic CAS via ?expectedVersionId=<id> on existing PUT/POST/DELETE;
mismatch returns 409 VERSION_CONFLICT. Open intents on a rule return
409 VERSION_LEDGER_PENDING with intentId, recoverable via
/rule-version-intents/:id/{repair,abandon}.
Shared _shared/{RuleHistoryDrawer,RuleHistoryPanel,RuleDiffEditor,ruleVersion}
components wired into routingRule, tagRule, and dynamicConfig pages.
Monaco diff against current or any historical version; rollback opens
a modal requiring a non-empty reason. Concurrent edits surface a sticky
409 notification with a Reload action; pending intents offer Repair /
Abandon. Existing edit forms now thread expectedVersionId through PUT/
POST/DELETE for optimistic concurrency. MSW mocks cover the new
versioning endpoints and the conflict / pending / disabled rule-name
conventions.
990402d to
b525c2b
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces immutable version history, diff viewing, and rollback for governor-managed traffic rules (Condition Route, Tag Route, Dynamic Config) in Dubbo Admin, along with optimistic concurrency control to prevent silent overwrites.
Changes:
- Backend: adds a versioning subsystem (stores, service, subscriber, bootstrap, intent workflow) plus REST endpoints for listing/getting/diffing/rollback and intent repair/abandon.
- Frontend: adds shared history/diff/rollback UI components, wires them into rule pages, and threads
expectedVersionIdthrough mutations with 409 conflict handling. - Store/core plumbing: adds
ListResources()and aligns empty-indexListByIndexessemantics across memory/GORM stores.
Reviewed changes
Copilot reviewed 62 out of 62 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| ui-vue3/src/views/traffic/tagRule/tabs/updateByYAMLView.vue | Adds expectedVersionId concurrency + version-error notifications for YAML editing. |
| ui-vue3/src/views/traffic/tagRule/tabs/updateByFormView.vue | Threads version precondition into form updates and handles version conflicts. |
| ui-vue3/src/views/traffic/tagRule/tabs/formView.vue | Integrates history panel entry point and current version badge. |
| ui-vue3/src/views/traffic/tagRule/index.vue | Uses current version id precondition on delete and conflict notifications. |
| ui-vue3/src/views/traffic/routingRule/tabs/updateByYAMLView.vue | Adds expectedVersionId concurrency + version-error notifications for YAML editing. |
| ui-vue3/src/views/traffic/routingRule/tabs/updateByFormView.vue | Threads version precondition into form updates and handles version conflicts. |
| ui-vue3/src/views/traffic/routingRule/tabs/formView.vue | Integrates history panel entry point and current version badge. |
| ui-vue3/src/views/traffic/routingRule/index.vue | Uses current version id precondition on delete and conflict notifications. |
| ui-vue3/src/views/traffic/dynamicConfig/tabs/YAMLView.vue | Adds history panel + expectedVersionId concurrency for YAML-based configurator edits. |
| ui-vue3/src/views/traffic/dynamicConfig/tabs/formView.vue | Adds history panel + expectedVersionId concurrency for form-based configurator edits. |
| ui-vue3/src/views/traffic/dynamicConfig/index.vue | Uses current version id precondition on delete and conflict notifications. |
| ui-vue3/src/views/traffic/_shared/ruleVersion.ts | Shared helpers for fetching current version state and displaying conflict/pending notifications. |
| ui-vue3/src/views/traffic/_shared/RuleHistoryPanel.vue | New history panel orchestrating list/view/diff/rollback flows. |
| ui-vue3/src/views/traffic/_shared/RuleHistoryDrawer.vue | Drawer UI for version timeline and actions. |
| ui-vue3/src/views/traffic/_shared/RuleDiffEditor.vue | Monaco diff editor wrapper for version comparisons. |
| ui-vue3/src/mocks/handlers/tagRule.ts | MSW: simulates version conflicts/pending and records admin writes for tag rules. |
| ui-vue3/src/mocks/handlers/routingRule.ts | MSW: simulates version conflicts/pending and records admin writes for condition rules. |
| ui-vue3/src/mocks/handlers/dynamicConfig.ts | MSW: simulates version conflicts/pending and records admin writes for configurators (with URL decoding). |
| ui-vue3/src/mocks/handlers/ruleVersion.ts | MSW: full in-browser mock ledger + diff/rollback + intent repair/abandon flows. |
| ui-vue3/src/mocks/handlers.ts | Registers ruleVersion MSW handlers. |
| ui-vue3/src/base/http/request.ts | Suppresses generic error toasts for version conflict/pending so the dedicated notifications can be used. |
| ui-vue3/src/api/service/traffic.ts | Adds versioning API surface + threads expectedVersionId into existing mutations. |
| pkg/store/memory/store.go | Adds ListResources() with sorting and error propagation. |
| pkg/store/memory/store_test.go | Tests ListResources() sorting and empty-index semantics. |
| pkg/store/dbcommon/gorm_store.go | Adds ListResources() and aligns empty-index semantics with memory store. |
| pkg/store/dbcommon/gorm_store_test.go | Tests empty-index behavior and ListResources() ordering. |
| pkg/core/versioning/types.go | Defines version/meta/intent models, enums, and shared errors. |
| pkg/core/versioning/subscriber.go | Records upstream changes and attaches events to matching admin intents. |
| pkg/core/versioning/store.go | In-memory immutable ledger store + intent lifecycle + retention trimming + dedup. |
| pkg/core/versioning/store_gorm.go | GORM-backed immutable ledger store + intent lifecycle + trimming + dedup. |
| pkg/core/versioning/store_gorm_test.go | Tests GORM store migration, trimming, dedup, intents, and concurrency monotonicity. |
| pkg/core/versioning/service.go | Versioning service API: list/get/diff, expected-version check, intents, repair helpers. |
| pkg/core/versioning/normalize.go | Canonical JSON normalization and sha256 hashing for dedup and intent matching. |
| pkg/core/versioning/e2e_rollback_drill_test.go | End-to-end drill covering bootstrap, admin edit, upstream push, rollback, and retention trim. |
| pkg/core/versioning/component.go | Runtime component wiring: store selection, event subscriptions, startup repair/bootstrap scan. |
| pkg/core/store/store.go | Extends ResourceStore interface with ListResources(). |
| pkg/core/manager/manager.go | Adds List(rk) to manager via store’s ListResources(). |
| pkg/core/manager/manager_test.go | Verifies manager List returns sorted resources. |
| pkg/core/events/eventbus.go | Adds SourceRegistryContextKey and clarifies event context immutability expectations. |
| pkg/core/discovery/subscriber/zk_config.go | Adds ZK delete nil-guard and emits source-registry context for version attribution. |
| pkg/core/discovery/subscriber/zk_config_test.go | Tests delete path uses local old rule and missing-local-rule is a noop. |
| pkg/core/bootstrap/bootstrap.go | Registers the versioning component as an optional bootstrap component. |
| pkg/console/service/tag_rule.go | Wraps tag rule mutations with intent-based versioning and expectedVersionId checks. |
| pkg/console/service/configurator_rule.go | Wraps configurator mutations with intent-based versioning and expectedVersionId checks. |
| pkg/console/service/condition_rule.go | Wraps condition rule mutations with intent-based versioning and expectedVersionId checks. |
| pkg/console/service/rule_version.go | Adds console-layer services for version list/get/diff/rollback and intent repair/abandon. |
| pkg/console/service/rule_version_test.go | Covers conflict handling, pending intents, rollback paths, delete marker semantics, and intent recovery. |
| pkg/console/model/tag_rule.go | Exposes force and priority fields in tag rule responses. |
| pkg/console/model/condition_rule.go | Exposes force and priority fields in condition rule responses. |
| pkg/console/handler/tag_rule.go | Adds mutation options parsing and maps versioning conflicts/pending to 409. |
| pkg/console/handler/configurator_rule.go | Adds mutation options parsing and maps versioning conflicts/pending to 409. |
| pkg/console/handler/condition_rule.go | Adds mutation options parsing and maps versioning conflicts/pending to 409. |
| pkg/console/handler/rule_version.go | Implements versioning REST endpoints and error-to-HTTP mapping. |
| pkg/console/handler/rule_version_test.go | Tests status/code mapping for InvalidArgument and pending intent id propagation. |
| pkg/console/router/router.go | Registers versioning endpoints under the existing traffic rule routes + intent ops routes. |
| pkg/console/context/context.go | Exposes RuleVersioning service from the runtime component. |
| pkg/console/component_test.go | Ensures auth middleware blocks rollback endpoint without a session. |
| pkg/config/versioning/config.go | Adds versioning config block, defaults, sanitize and validation. |
| pkg/config/versioning/config_test.go | Tests defaults, sanitize, and validation for versioning config. |
| pkg/config/app/admin.go | Adds versioning config to AdminConfig and ensures defaults for nil config blocks. |
| pkg/config/app/admin_test.go | Tests AdminConfig defaulting behavior when Versioning config is missing. |
Comments suppressed due to low confidence (1)
ui-vue3/src/views/traffic/dynamicConfig/tabs/YAMLView.vue:205
- The
catchblock only callsnotifyRuleVersionError(...)and then swallows the exception. This can hide non-versioning failures (notably YAML parse errors fromyaml.loador other runtime exceptions) because the request interceptor won’t run for those cases. Consider rethrowing or showing a generic error toast whennotifyRuleVersionErrorreturns false.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #1473.
Please view the detailed test report at https://notion-next-iota-amber-43.vercel.app/article/dubbo-admin-reviewer-report
1. What this PR delivers
Dubbo Admin currently saves traffic-rule edits with a destructive overwrite. There is no history, no audit trail for upstream-registry pushes, and no way to recover from a bad edit. This PR adds an immutable release ledger plus one-click rollback for the three governor-managed rule kinds — Condition Route, Tag Route, and Dynamic Config (Configurator).
The approach was picked after evaluating five candidate solutions during design:
User-visible capabilities
ADMIN/UPSTREAM/ROLLBACK/BOOTSTRAP), operator, timestamp and reason.ROLLBACKrow. The old row is never mutated.versioning.maxVersionsPerRule).2. How it works
source=UPSTREAM; ZK currently setsevents.SourceRegistryContextKey, while Nacos / Apollo source labelling is deferred.source=ROLLBACKandrolled_back_from_idpointing at the source version. The original row is never modified.version_nois monotonic and not reused after trim — users always see strictly increasing version labels.3. Scope
In this PR
Backend
pkg/core/versioningpackage: types, memory + GORM stores, service, subscriber, component, and admin mutation intents.pkg/config/versioningconfig block with sane defaults and validation. Disabled by default in this release — setversioning.enabled: trueto turn it on (see §8).BOOTSTRAPbaseline row for every existing rule (idempotent)./rule-version-intents/:intentId/{repair,abandon}) to recover ledger state when an admin write crashes mid-flight or de-syncs from the registry.expectedVersionIdprecondition on existing PUT / POST / DELETE.events.SourceRegistryContextKeyconstant +ZKConfigEventSubscriberemits registry context.Frontend
ui-vue3/src/views/traffic/_shared/:RuleHistoryDrawer.vue,RuleHistoryPanel.vue,RuleDiffEditor.vue,ruleVersion.ts.notificationwithduration: 0and a Reload action.Tests
e2e_rollback_drill_test.go) covering bootstrap → intent-backed admin edit → upstream push → intent-backed rollback → overflow trim → audit-chain assertions.Out of scope for this PR
@mochengqianas an immediate follow-up. AffinityRoute is not currently ingovernor.RuleResourceKinds, so its write path bypasses the governor that this ledger hooks into. Once it is brought onto the governor path, all four kinds will share the same versioning code.ZKConfigEventSubscribercurrently emitsevents.SourceRegistryContextKey. Other registry subscribers fall back to authorsystem:upstreaminstead ofsystem:nacos/system:apollo. The wiring to fix this is one line per subscriber and is left for a separate PR to keep this one bounded.4. Locked design decisions
governor.RuleResourceKinds; out of scope.expectedVersionIdis weak-CAS but intent-awareVERSION_LEDGER_PENDINGorVERSION_CONFLICT.5. API surface
New endpoints (one set per rule kind:
condition-rule,tag-rule,configurator)diff?against=currentrequires the rule to still have a current live version. If the rule has since been deleted, diff-vs-current returns rule-version not found; callers can still diff against an explicit historical version id.Operator endpoints (cross-cutting, intent reconciliation)
When an admin write crashes between "intent created" and "intent committed/failed", a pending intent can be left dangling and block subsequent edits on the same rule with
409 VERSION_LEDGER_PENDING. Two endpoints let an operator reconcile:The UI surfaces both as Repair / Abandon buttons inside the
VERSION_LEDGER_PENDINGnotification.Existing endpoints (backward-compatible)
PUT / POST / DELETE /api/v1/{kind}/:ruleNameaccept an optional?expectedVersionId=<id>.Omit → unchanged behavior. Provide → mismatch returns:
Feature flag
With
versioning.enabled=false, all new endpoints return503 + {"code":"FEATURE_DISABLED"}. Existing CRUD is completely untouched.6. Database
Two new tables, created via
AutoMigrateon the existing GORM connection (MySQL / Postgres). Withstore.type=memorya pure-Go in-memory implementation is used — no config changes needed.Upgrade path: on first start, scan every existing rule and write one
source=BOOTSTRAPbaseline row. Idempotent — safe to re-run.7. Verification
Automated tests
go test ./pkg/core/versioning/... \ ./pkg/config/versioning/... \ ./pkg/console/handler/... \ ./pkg/console/service/... \ ./pkg/store/... \ ./pkg/core/discovery/subscriber/... \ ./pkg/core/manager/...All green.
go vet ./pkg/...reports no new warnings.Frontend build
Passes.
npm run type-checkstill reports the pre-existing repo-wide TypeScript debt (home/index.vue, AuthUtil.ts, GrafanaPage, etc.) — that count does not increase under this branch.Manual smoke
A full bootstrap → admin edit → diff → rollback → optimistic-lock 409 → retention cap → upstream ZK push → cross-rule rollback sweep was run end-to-end against MySQL + ZooKeeper. Evidence (HTTP transcripts, JSON ledger dumps, UI screenshots) is in the PR comments.
8. Upgrade and rollback
versioning.enabled: false. The feature creates two new tables viaAutoMigrateand writes aBOOTSTRAPbaseline row per existing rule on first start; making that the default for every upstream user is too noisy. Existing CRUD behavior is unchanged when the feature is off.versioning.enabled: trueinadmin.ymland restart. On first startAutoMigratecreatesrule_version/rule_version_meta(+rule_version_intentfor the intent ledger), and the bootstrap scan writes a baseline row per existing rule. Idempotent — safe to re-run.versioning.enabledback tofalseand restart. CRUD paths are unaffected; new endpoints return 503. The tables are left in place in case the feature is re-enabled later.feat(versioning)commits is sufficient; the two preparatory commits at the bottom of the ladder are harmless to keep.9. Test plan checklist
go test ./pkg/...passes locallycd ui-vue3 && npm run buildpassesmaxVersionsPerRule