Full AWS RDS parity: management plane, discovery, cost & metrics - #301
Conversation
Add parameter-group support to the RDS emulation as an optional ParameterGroups capability (mirroring the SubnetGroups pattern): create, describe, modify, delete, describe-parameters, reset, and copy — for both DB parameter groups and DB cluster parameter groups (14 actions). Only user-set parameters are modeled; the emulator does not fabricate the hundreds of engine defaults real AWS returns. Real AWS reuses the DBParameterGroup fault codes for the cluster variants, so error mapping is shared via a 'parameter group' message keyword. Covered by provider unit tests and aws-sdk-go-v2 SDK round-trip tests.
Add option-group support as an optional OptionGroups capability: create, describe (with engine-name filter), modify (include/remove options), delete, copy, and describe-option-group-options (6 actions). DescribeOptionGroupOptions returns a small, representative per-engine catalog of well-known option names rather than fabricating AWS's exhaustive version-specific list. Covered by provider unit tests and SDK round-trip tests.
Add CreateDBInstanceReadReplica and PromoteReadReplica as an optional ReadReplicas capability. A replica inherits its source's engine, version and storage; the source tracks its replica identifiers and the replica records its source. Promotion detaches the replica (clears its source, removes it from the source's list). Instance XML now carries ReadReplicaSourceDBInstanceIdentifier and ReadReplicaDBInstanceIdentifiers. Covered by provider unit tests and SDK round-trip tests.
Add an optional AdvancedRestore capability: CopyDBSnapshot, CopyDBClusterSnapshot, RestoreDBInstanceToPointInTime, and RestoreDBClusterToPointInTime (4 actions). Copies clone the source snapshot's engine/version/storage under a new identifier; PITR clones the source instance/cluster's current spec into a new resource. The emulator has no historical timeline, so a point-in-time restore reflects the source as it is now; RestoreTime/UseLatestRestorableTime are accepted but not replayed. Covered by provider unit tests and SDK round-trip tests.
Add RDS Proxy as an optional DBProxies capability: create, describe, modify, delete proxies; register/deregister targets; describe targets and target groups (8 actions). A proxy has a single implicit 'default' target group; targets may be RDS instances (RDS_INSTANCE) or clusters (TRACKED_CLUSTER), validated against existing resources on registration. Covered by provider unit tests and SDK round-trip tests.
Add an optional EventSubscriptions capability: create/describe/modify/delete event subscriptions, DescribeEvents, and DescribeEventCategories (6 actions). Enabled defaults to true on create (matching AWS). DescribeEventCategories returns AWS's published per-source-type categories. DescribeEvents returns an empty list by design: the emulator retains no event timeline, so there are truthfully no events for any window. Covered by provider unit tests and SDK round-trip tests.
Add three optional Aurora capabilities (10 actions): - ClusterEndpoints: create/describe/modify/delete custom cluster endpoints. - ClusterFailover: FailoverDBCluster promotes the target member to writer (or rotates the first reader when no target is given). - GlobalClusters: create (optionally adopting a source cluster as writer), describe, modify (rename / engine version), delete, and remove-from. Covered by provider unit tests and SDK round-trip tests.
Add two optional capabilities (5 actions): - Metadata: DescribeDBEngineVersions and DescribeOrderableDBInstanceOptions, backed by representative per-engine version and instance-class catalogs. - Tagging: AddTagsToResource, RemoveTagsFromResource, ListTagsForResource, addressed by resource ARN over the tag-bearing stores (instances, clusters, and instance/cluster snapshots). Covered by provider unit tests and SDK round-trip tests.
Issue stackshy#295 workstream A: RDS instances, Aurora clusters, and snapshots are emulated but were never enumerable via Resource Explorer. Add a RelationalDatabases discovery capability + neutral DiscoveredDatabase projection, a walkRelationalDB walker, and an rdsDiscovery adapter in the AWS provider (mirroring the Kubernetes eksDiscovery pattern, keeping services/ free of provider imports). Map rds<->relationaldb in the Resource Explorer 2 filter/handler so 'service:rds' narrows to these resources and their ResourceType/Service render correctly. Covered by an SDK indexing round-trip test. GCP Cloud SQL / Azure SQL discovery are deliberately out of scope here.
Add relationaldb operation rates to the cost Tracker's rate catalog
(instances and read replicas per instance-hour, RDS Proxy per hour; snapshots
and Aurora cluster grouping free), consistent with how the other services
populate the catalog.
Enrich instance metric emission with FreeStorageSpace, ReadLatency,
WriteLatency, and Network{Receive,Transmit}Throughput alongside the existing
CPU/connections/memory/IOPS series; latency and throughput read zero when the
instance is stopped. Covered by cost and metrics tests.
Drop the write-only DiscoveredDatabase.Engine field; refactor the RDS error-code switches into ordered keyword->fault tables (keeps them under the gocyclo gate); wrap long signatures, fix cuddling/var-naming/receiver/goconst, and annotate the intentional per-resource duplication. Full build/vet/gofmt and the whole test suite pass; golangci-lint clean on the touched packages.
Update services.md section 17 with the 11 new optional capability interfaces and their operations (parameter/option groups, read replicas, snapshot copy/PITR, RDS Proxy, event subscriptions, Aurora endpoints/failover/global clusters, metadata, tagging), refresh the totals, and note AWS RDS discovery through Resource Explorer 2 in section 19 (and features.md).
NitinKumar004
left a comment
There was a problem hiding this comment.
Review — full AWS RDS parity (deep pass: edge cases, blast radius, cascade)
Substantial, well-structured PR — 55 actions across 8 capability groups, correctly using the optional-capability type-assertion pattern, idgen ARNs, cerrors, and mirroring the merged Kubernetes discovery adapter. Build/tests pass. The findings below are what a deep sweep surfaced; the two HIGH items are worth fixing before merge.
✅ Blast radius — clean
- 55 new actions are purely additive: all 24 existing RDS actions route byte-for-byte unchanged,
default:still returnsInvalidAction. ThenotFoundCode/alreadyExistsCoderefactor to ordered fault-tables preserves every existing code (most-specific-first; "DB cluster snapshot" precedes "DB cluster"; new keywords aren't substrings of existing messages). - New
xml.gofields are,omitempty→ existing response bytes identical. - All 12 new capabilities are separate optional interfaces (comma-ok assertion,
writeUnsupported→InvalidAction); Azure/GCP relational drivers unchanged; no nil-panic in wiring. - resourceexplorer2
service:rdsmapping is additive, no collision.
🔴 HIGH
1. Concurrent-map-write panic (crashes the process) — Tags & Parameters. memstore returns shallow struct copies, so map fields alias stored state. Describe paths leak the live map (DescribeInstances rds.go:289-291, DescribeClusters 491, DescribeSnapshots 645; DescribeDBParameterGroups parametergroup.go:95, cluster variant 248), and the mutators write in place: AddTagsToResource/RemoveTagsFromResource (existing[k]=v / delete, metadata.go:145-159), ModifyDBParameterGroup/ResetDBParameterGroup (parametergroup.go:126,171). A DescribeInstances-range concurrent with a tag write → fatal error: concurrent map read and map write. Same class as the sibling-PR panics. The correct pattern already exists in ModifyInstance (inst.Tags = copyTags(...)) — apply it on the Describe read paths and have the mutators replace rather than mutate.
2. DeleteInstance doesn't block on read replicas. rds.go:350 only unlinks from the cluster; it never inspects inst.ReadReplicaTargets. Deleting a source that still has replicas succeeds and leaves every replica with ReadReplicaSource pointing at a missing instance. Real AWS rejects with InvalidDBInstanceState until replicas are promoted/deleted. Expected: cerrors.FailedPrecondition when len(inst.ReadReplicaTargets) > 0.
🟠 MEDIUM
- Deleting a replica leaves a stale entry on its source (
rds.go:350): the deleted replica isn't stripped fromsrc.ReadReplicaTargets, soDescribeInstancesstill lists a replica that's gone.PromoteReadReplicadoes this cleanup correctly —DeleteInstanceis the inconsistent path. DeleteGlobalClusterhas no member guard (aurora.go:276): deletes unconditionally regardless ofgc.Members; AWS blocks withInvalidGlobalClusterStateFault. Asymmetric vsDeleteCluster, which guards correctly.- In-place slice filters corrupt aliased backing arrays.
DeregisterDBProxyTargetsp.Targets[:0](dbproxy.go:191),RemoveFromGlobalClustergc.Members[:0](aurora.go:301),ModifyOptionGroupog.Options[:0](optiongroup.go:127) — combined with the Describe paths leaking those same slices (DescribeDBProxiesTargets/Auth/Subnets/SGs,DescribeGlobalClustersMembers,DescribeDBClusterEndpointsStatic/Excluded,DescribeEventSubscriptionsSourceIDs/EventCategories,DescribeOptionGroupsOptions), a previously-returned slice gets clobbered underneath a Go-library caller.DescribeDBProxyTargets(dbproxy.go:214) already does the right thing (append([]ProxyTarget(nil), …)) — mirror it. DeleteDBSubnetGrouptakes nom.mulock (subnetgroup.go:91) — the only mutating RDS method without one. The in-use scan + delete aren't atomic vs a concurrentCreateInstanceplacing an instance into the group (strands it in a deleted group).- Parameter/option groups: no in-use enforcement, attachment unmodeled.
Delete{DB,DBCluster}ParameterGroup/DeleteOptionGroupare barestore.Delete— no instance/cluster carries aDBParameterGroupName/OptionGroupName, so "in use" can't be checked and default groups aren't seeded/protected. AWS returnsInvalidDBParameterGroupState/ refusesdefault.*. The honest parity gap; modeling attachment is a prerequisite. ApplyMethodsilently dropped (parametergroup.go:56):mergeParamsstores only name→value andparamsToDriverhardcodespending-reboot, soModifyDBParameterGroup(...,ApplyMethod:"immediate")reads back aspending-reboot.
🟡 LOW (fidelity / robustness)
- PITR: aliases source's
VPCSecurityGroupsslice (advancedrestore.go:120) and retainssrc.ClusterIDwithout adding the clone tocluster.Members(orphan member reference).RestoreTimeaccepted-but-unused is documented and genuinely inert (good). - Read replica off a non-
availablesource isn't state-checked (readreplica.go:16). - RDS Proxy:
CreateDBProxydoesn't validateRoleArn/Auth; thetargetGrouparg is ignored (typo target group silently succeeds); duplicate register appends a dup; deregister of an unregistered target returns nil (AWS:DBProxyTarget{AlreadyRegistered,NotFound}Fault). - Global clusters: no way to add a secondary (
CreateDBClusterignoresGlobalClusterIdentifier, noAddClusterToGlobalCluster);RemoveFromGlobalClusterof a non-member returns success. - Event subscriptions: no
SourceType/SourceIdsvalidation;Add/RemoveSourceIdentifierFromSubscriptionunimplemented.DescribeEventsempty list confirmed clean (non-nil, no panic). - Custom endpoints:
EndpointTypeunvalidated;DescribeDBClusterEndpointsreturns the endpoint even when the passedclusterIDdoesn't match. - Metadata:
DescribeOrderableDBInstanceOptionsuses a suppliedEngineVersionverbatim (nonexistent version → full results); unknown engine → empty list instead of an error (alsoDescribeOptionGroupOptions). - Nondeterministic tag XML order (
xml.go:420toTagListXMLranges the map) — sort keys. subnetgroup.go:56hand-builds the ARN instead ofidgen.AWSARN(byte-identical output, but the one ARN bypassing idgen).DescribeEventCategoriesreturns the package-global catalog slices verbatim (caller could mutate the shared table).- Missing compile-time conformance guards (
var _ rdsdriver.X = (*Mock)(nil)) forSubnetGroups,ClusterEndpoints,ClusterFailover,GlobalClusters,Metadata,Tagging— a signature drift would silently degrade toInvalidActionat runtime instead of failing the build. - Metrics: RDS emits a single datapoint at
now(no 5-point backfill like EC2), so aGetMetricStatisticswindow ending beforenowis empty.emitInstanceMetricsruns underm.mu.LockacrossPutMetricData+evaluateAlarms— safe today (RDS isn't an alarm-action target) but the lock-held-across-external-call shape CLAUDE.md warns about.
Verified correct (no action)
Cluster-member delete block; PromoteReadReplica; subnet-group in-use scan; proxy delete cascade; snapshot/PITR existence + AlreadyExists guards; FailoverDBCluster (single-member no-op is a deliberate, tested choice; unknown/non-member checks correct); discovery pattern conformance (fail-loud, engine free of provider imports, copyTags on emitted Resource); cost catalog (additive, correct naming); metrics namespace/dimension/stopped-check; determinism of the describe-metadata catalogs; ARN-parse robustness in tagging (no panic on malformed).
Bottom line
Architecturally sound and blast-radius-clean. The two HIGH items — the Tags/Parameters concurrent-map-write panic (fix by copying on read + replacing on write, mirroring ModifyInstance) and the read-replica delete block — are the ones to fix before merge. The MEDIUM aliasing/slice-mutation set is the same theme and worth sweeping together (mirror DescribeDBProxyTargets). The param/option-group in-use enforcement is the honest parity limitation to call out. A concurrency -race test that runs a Describe alongside a tag/param mutation would catch the HIGH class.
Address PR review (both HIGH + the MEDIUM aliasing set): HIGH - Concurrent-map-write panic on Tags/Parameters: copy-on-read in the Describe paths (instances/clusters/snapshots tags; parameter groups) and replace-on-write in the mutators (AddTags/RemoveTags build a fresh map; parameter Modify/Reset build a fresh map), mirroring ModifyInstance. Add a go test -race concurrency test (Describe + caller iteration vs tag/param writes). - DeleteInstance now refuses (FailedPrecondition) while the instance still has read replicas. MEDIUM - DeleteInstance strips a deleted replica from its source's target list; make removeString non-mutating so it never corrupts a slice a Describe handed out. - DeleteGlobalCluster refuses while members remain. - Fresh-slice rebuilds (no in-place [:0]/append into aliased backing arrays) in DeregisterDBProxyTargets, RegisterDBProxyTargets, RemoveFromGlobalCluster, ModifyOptionGroup. - DeleteDBSubnetGroup takes m.mu across the in-use scan + delete. - Preserve per-parameter ApplyMethod (store map[string]Parameter). LOW - Deterministic tag XML ordering; DescribeEventCategories/PITR return copied slices; PITR clears ClusterID; SubnetGroups conformance guard; subnet-group ARN via idgen. New tests: -race concurrency; delete-blocked-by-replica; delete-global-with- members; ApplyMethod round-trip.
|
Thanks @NitinKumar004 — thorough pass. All fixed in f224ff8. 🔴 HIGH
🟠 MEDIUM
🟡 LOW addressed: deterministic tag XML ordering (sorted keys); Deliberately deferred (documented parity gaps, agree they're the honest limitations): parameter/option-group in-use enforcement requires modeling attachment ( New tests: |
NitinKumar004
left a comment
There was a problem hiding this comment.
Re-review @ f224ff8 — both HIGH + the MEDIUM set fixed, race-tested
Verified each fix directly; go test -race ./providers/aws/rds/... is green.
✅ Fixed & verified
- HIGH — concurrent-map-write panic (Tags/Parameters). Describe paths now copy on read (
DescribeInstancesv.Tags = copyTags(v.Tags), clusters/snapshots likewise; parameter groups copy too), and the mutators replace-on-write (AddTagsToResource/RemoveTagsFromResourcebuild a fresh map; parameter Modify/Reset rebuild). MirrorsModifyInstance. NewTestConcurrentDescribeAndMutateruns Describe+iteration vs tag/param writes under-race. Panic class closed. - HIGH —
DeleteInstanceblocks on read replicas (FailedPrecondition), and now also strips a deleted replica from its source'sReadReplicaTargets.TestDeleteInstanceBlockedByReplicacovers it. - MED —
removeStringis non-mutating (fresh slice, no[:0]), so it never corrupts a slice a Describe handed out. - MED —
DeleteGlobalClusterblocks while members remain (TestGlobalClusterLifecycle). - MED — fresh-slice rebuilds in
DeregisterDBProxyTargets/RegisterDBProxyTargets/RemoveFromGlobalCluster/ModifyOptionGroup(no in-place[:0]/append into aliased backing). - MED —
DeleteDBSubnetGroupnow holdsm.muacross the in-use scan + delete. - MED —
ApplyMethodpreserved (storesmap[string]Parameter);TestDBParameterGroupApplyMethodRoundTrips. - LOW — deterministic tag XML order, PITR clears
ClusterID+ copies slices,DescribeEventCategoriescopies, subnet-group ARN viaidgen,SubnetGroupsconformance guard added.
🟡 Remaining (all LOW / acknowledged parity)
- Read-side slice aliasing still present in the Describe paths. The mutator (write) side was fixed, which closes the concurrent-corruption vector — but
DescribeDBProxies(Targets/Auth/subnets/SGs),DescribeGlobalClusters(Members),DescribeDBClusterEndpoints(Static/Excluded),DescribeEventSubscriptions(SourceIDs/EventCategories),DescribeOptionGroups(Options) still return slices that alias the store. A Go-library caller mutating a returned slice corrupts internal state. Now inconsistent with the tags path, which copies on read — worth mirroringcopyTagsfor these slices (orDescribeDBProxyTargets'sappend([]T(nil), …)). Panic-vector-closed, so LOW. - Parameter/option-group in-use enforcement still absent (the honest parity gap): no instance/cluster carries a
DBParameterGroupName/OptionGroupName, soDelete{,Cluster}ParameterGroup/DeleteOptionGroupcan't refuse an in-use ordefault.*group. Modeling attachment is the prerequisite; reasonable to call out as a scoped limitation rather than block. - Missing compile-time conformance guards for
ClusterEndpoints,ClusterFailover,GlobalClusters,Metadata,Tagging(SubnetGroups + 6 others now guarded) — a signature drift on these still degrades silently toInvalidAction. - Prior LOW fidelity items out of this commit's scope remain:
CreateDBProxyRoleArn/Authvalidation + ignoredtargetGroup+ dup-register/deregister-unregistered handling; global-cluster secondary-member gap; event-subscriptionSourceType/SourceIdsvalidation +Add/RemoveSourceIdentifier; custom-endpointEndpointTypevalidation + cluster-mismatch describe; metadata engine/version validation; single metric datapoint (no EC2-style backfill). - Lint (verify against CI): my local
golangci-lintflagsduplon the subnet-group/aurora/parameter-group ARN+scan helpers (aurora.go:223↔subnetgroup.go:68,server/aws/rds/parametergroup.go:438↔subnetgroup.go:87). Could be linter-version drift (you report clean) — worth a confirm; extracting the shared helper would settle it.
Bottom line
The two HIGH items and the whole MEDIUM aliasing/delete-guard set are correctly fixed and now race-tested — this addresses everything blocking from the first pass. What's left is LOW: read-side slice copies (mirror the tags fix for consistency), the param/option-group in-use parity gap (scoped limitation), and a handful of fidelity/validation nits. Solid.
…nforcement Addresses the remaining LOW review items: - Read-side slice aliasing (LOW stackshy#1): Describe paths now copy their slice/map fields so a returned value never aliases the store — DescribeDBProxies (Targets/Auth/subnets/SGs), DescribeGlobalClusters (Members), DescribeDBClusterEndpoints (Static/Excluded), DescribeEventSubscriptions (SourceIDs/EventCategories), DescribeOptionGroups (Options), and the parameter-group Parameters maps. Adds a generic cloneSlice helper; consistent with the tags copy-on-read. - Parameter/option-group in-use enforcement (LOW stackshy#2): instances now carry DBParameterGroupName/OptionGroupName and clusters DBClusterParameterGroupName (parsed from the wire). Delete{,Cluster}ParameterGroup and DeleteOptionGroup refuse an in-use group (FailedPrecondition) and refuse the reserved default.* / default:* names. Not changed: conformance guards for ClusterEndpoints/ClusterFailover/ GlobalClusters/Metadata/Tagging already exist (var _ blocks in aurora.go / metadata.go) — the review's stackshy#3 was a false alarm. Remaining LOW fidelity nits (proxy RoleArn/Auth validation, global secondary attach, metric backfill, etc.) stay as documented follow-ups. New tests: parameter/option-group delete guards (in-use + default), copy-on-read regression. build/vet/gofmt/full test + -race green; golangci-lint clean.
|
Thanks @NitinKumar004 — LOWs swept in 7c58b7a. #1 — read-side slice aliasing. The Describe paths now copy their slice/map fields so a returned value never aliases the store, consistent with the tags copy-on-read (added a generic
Regression test #2 — parameter/option-group in-use enforcement. Modeled the attachment: instances now carry #3 — conformance guards. Already present — #5 — lint (dupl). Confirmed and resolved: the read-side copies broke most of the duplication; the residual describe-list wire handlers now carry Still deferred (documented follow-ups, #4): proxy |
NitinKumar004
left a comment
There was a problem hiding this comment.
Re-review @ 7c58b7a (multi-agent, in depth)
Two-agent verification of the LOW-fix commit + direct checks. go test -race ./providers/aws/rds/... and ./server/aws/rds/... are green.
✅ Correction to my earlier note
The conformance-guard point (#3) was a false alarm on my side — you were right to push back. The var _ rdsdriver.{ClusterEndpoints,ClusterFailover,GlobalClusters} guards live in a grouped var ( … ) block at aurora.go:12-14, and {Metadata,Tagging} at metadata.go:12-13; my single-line grep missed grouped entries (no var keyword on the line). All five are present and load-bearing. Withdrawn.
✅ Fixed & verified
- Read-side slice aliasing — the 6 enumerated Describe paths are complete.
cloneSlice[T]is correct (nil→nil, else fresh backing array), applied in both branches (theSortedValues()/len==0branch and the named-lookup branch) ofDescribeDBProxies(Targets/Auth/subnets/SGs),DescribeGlobalClusters(Members),DescribeDBClusterEndpoints(Static/Excluded),DescribeEventSubscriptions(SourceIDs/EventCategories),DescribeOptionGroups(Options), and the parameter-groupParametersmaps. - Parameter/option-group in-use enforcement — complete and correctly wired end-to-end. Right store scanned per type (
dbParameterGroupInUseBy→instances,clusterParameterGroupInUseBy→clusters,optionGroupInUseBy→instances; no cross-type confusion); reserved-name refusal uses the correct prefixes (default.for param groups,default:for option groups) with no false-match on user names;CreateDBInstance/CreateDBClusterparse and storeDBParameterGroupName/OptionGroupName/DBClusterParameterGroupName(so in-use is genuinely detectable); scan+delete underm.mu. Tests exercise the real in-use path (attach → delete blocked → delete instance → delete clean), not just the default-name path. - Wire-parsing change is byte-neutral (the describe XML structs carry no param/option-group fields, so stored-but-unrendered — existing response bytes unchanged).
🟠 MEDIUM (new — a gap the enforcement introduces)
ModifyDBInstance/ModifyDBCluster can't change the parameter/option-group attachment. ModifyInstanceInput (driver.go:90) has no DBParameterGroupName/OptionGroupName field and modifyDBInstance doesn't parse them, so a group attached at create can only be released by deleting the instance/cluster. Scenario: Create(pg1) → Modify(pg2) leaves the instance recording pg1, so DeleteDBParameterGroup(pg1) stays FailedPrecondition forever while pg2 (the actually-attached one) can be deleted. Real AWS lets you re-point then delete the old group. Now that in-use is enforced, this stranding is reachable. Fix: add the fields to ModifyInstanceInput + wire the modify handlers to update the attachment.
🟡 LOW
- The commit's "every Describe path copies its slice/map fields" claim is overstated — three still alias:
DescribeInstances(VPCSecurityGroups,ReadReplicaTargets— clonesTagsonly),DescribeClusters(Members,VPCSecurityGroups),DescribeDBSubnetGroups(SubnetIDs— returnsSortedValues()raw, no cloning). A caller mutating a returned element in place corrupts the store — the exact contract the fix set out to hold, and these are the two most central RDS Describe paths. Store→caller direction is mostly safe today (internal mutations reallocate), so LOW, but worth finishing for consistency. Create{,Cluster}ParameterGroup/CreateOptionGroupdon't reject reserveddefault.*/default:*names, so a user can self-inflict an undeletable group.DescribeOptionGroupsclones[]OptionbutcloneSliceis shallow —Option.Settings map[string]stringstill aliases; not exploitable today (Settingsis never populated), latent only.- The new
TestDescribeDBProxiesReturnsIndependentCopiesonly covers theDescribeDBProxiesnamed branch — not thelen==0branch or the other five services, so it wouldn't catch a regression there.
Bottom line
The in-use enforcement (LOW#2) and the six targeted Describe copies (LOW#1) are correctly and completely done, race-tested, and the conformance-guard concern was my own false alarm. What remains: one MEDIUM fidelity gap the enforcement newly exposes (Modify can't re-point/release a param/option group), plus three still-aliased Describe paths that make the "every path" claim overstated (LOW). Solid progress; the Modify-attachment gap is the one worth addressing.
…to feat/rds-full-support # Conflicts: # docs/services.md
Addresses the re-review of the LOW-fix commit.
MEDIUM — the new in-use enforcement could strand a group: a group attached
at Create could only be released by deleting the instance/cluster. Add
DBParameterGroupName/OptionGroupName (instance) and DBClusterParameterGroupName
(cluster) to ModifyInstanceInput and wire the modify handlers, so re-pointing
an instance/cluster to a new group releases the old one (which then deletes).
LOW:
- Finish read-side copies: DescribeInstances (VPCSecurityGroups,
ReadReplicaTargets), DescribeClusters (Members, VPCSecurityGroups), and
DescribeDBSubnetGroups (SubnetIDs) now return independent copies too, so the
'every Describe path copies its slice/map fields' contract actually holds.
- Create{,Cluster}ParameterGroup / CreateOptionGroup reject the reserved
default. / default: prefixes so a user can't self-inflict an undeletable group.
- DescribeOptionGroups deep-copies Option.Settings (cloneSlice was shallow).
Tests: Modify re-point/release for instance param, cluster param, and option
groups; reserved-name rejection on create; copy-on-read across the list-all and
named branches for proxies and instances.
|
Rebased onto latest Merge conflict: only 🟠 MEDIUM — Modify couldn't re-point/release a param/option group. Fixed: 🟡 LOW
Also — thanks for withdrawing the conformance-guard note; confirmed all 12
|
NitinKumar004
left a comment
There was a problem hiding this comment.
Final re-review @ d8af059
The last commit closes every item from the prior pass. Verified each directly; go build ./..., go test -race ./providers/aws/rds/... ./server/aws/rds/..., and golangci-lint on the touched packages are all green (0 issues).
✅ Fixed & verified
- MEDIUM — Modify can now re-point/release a parameter/option group.
ModifyInstanceInputgainedDBParameterGroupName/OptionGroupName,ModifyInstanceapplies them (rds.go:378), andModifyClusterappliesDBClusterParameterGroupName(rds.go:591); the modify handlers parse them from the wire. Re-pointing an instance/cluster to a new group frees the old one, which then deletes — the stranding I flagged is resolved. Covered by new re-point/release tests (instance param, cluster param, option group). - LOW — the "every Describe path copies" contract now actually holds.
DescribeInstances/DescribeClustersroute both branches through newcloneInstance/cloneClusterhelpers that copyTags+VPCSecurityGroups+ReadReplicaTargets(instance) /Tags+Members+VPCSecurityGroups(cluster);DescribeDBSubnetGroupsnow clonesSubnetIDsin theSortedValues()branch too. No returned Describe value aliases the store. - LOW — reserved-name rejection on create.
CreateDBParameterGroup/CreateDBClusterParameterGrouprejectdefault.andCreateOptionGrouprejectsdefault:, so a caller can no longer self-inflict an undeletable group. - LOW —
DescribeOptionGroupsdeep-copiesOption.Settings(the previously-shallowcloneSlicenested-map gap). - Merge of
development(incl. the now-merged Bedrock #298) is clean — full build/tests green.
Convergence
No open findings remain. Across the review rounds every item has been fixed and verified:
- HIGH: Tags/Parameters concurrent-map-write panic;
DeleteInstancereplica block. - MEDIUM: stale-replica cleanup;
DeleteGlobalClustermember guard; in-place slice-filter corruption;DeleteDBSubnetGrouplock; ApplyMethod preservation; param/option-group in-use enforcement; Modify re-point/release. - LOW: read-side slice/map copies across all Describe paths; reserved-name rejection; Option.Settings copy; subnet-group idgen ARN; deterministic tag XML.
- The conformance-guard concern was my own false alarm (guards exist in grouped
var()blocks).
Blast radius stayed clean throughout (additive, optional-capability-gated, Azure/GCP untouched, existing response bytes unchanged). The documented follow-ups remain out of scope by design: proxy RoleArn/Auth validation, global-cluster secondary attach, EC2-style metric backfill, param-group family defaults, and echoing param/option-group names in the describe response.
Bottom line
Everything raised is resolved and race/lint-tested — this is a clean, converged final state. LGTM on substance.
NitinKumar004
left a comment
There was a problem hiding this comment.
Approving @ d8af059
Converged and clean. Every finding across the review rounds is fixed and verified — go build ./..., go test -race ./providers/aws/rds/... ./server/aws/rds/..., and golangci-lint on the touched packages are all green (0 issues).
- HIGH — Tags/Parameters concurrent-map-write panic (copy-on-read + replace-on-write) and
DeleteInstanceread-replica block: fixed, race-tested. - MEDIUM — stale-replica cleanup,
DeleteGlobalClustermember guard, in-place slice-filter corruption,DeleteDBSubnetGrouplock, ApplyMethod preservation, param/option-group in-use enforcement, and Modify re-point/release: all fixed with tests. - LOW — read-side slice/map copies across every Describe path (
cloneInstance/cloneCluster, subnet-group SubnetIDs, Option.Settings), reserved-name rejection on create, subnet-group idgen ARN, deterministic tag XML: all done. - The conformance-guard concern was my own false alarm (guards live in grouped
var()blocks).
Blast radius stayed clean throughout — additive, optional-capability-gated by type assertion, Azure/GCP relational drivers untouched, and existing action responses byte-unchanged. The out-of-scope items (proxy RoleArn/Auth validation, global-cluster secondary attach, metric backfill, param-group family defaults, echoing param/option-group names in describe) are reasonable documented follow-ups.
Nice, thorough work across the iterations. LGTM.
Objective
Bring cloudemu's AWS RDS emulation from basic coverage to comprehensive management-plane parity, surface RDS in resource discovery (issue #295 workstream A), and wire it into the cost tracker — as one PR, broken into independently-green commits.
What was there before
24 wired actions: instances, Aurora clusters, instance + cluster snapshots, DB subnet groups; engines mysql/postgres/aurora-*/docdb/neptune; 5 CloudWatch metrics. Not surfaced in discovery; not in the cost catalog.
What this adds (55 new actions across 8 feature groups)
Each is an optional capability discovered by type assertion (mirroring the existing
SubnetGroupspattern), so non-AWS relational drivers answerInvalidActiontruthfully.bb5d92c0adaae6a2cedf6011bb751aa510482201aaa07bb84219d0ddPlus:
91e191cDiscovery (AWS): aRelationalDatabasescapability +rdsDiscoveryadapter (mirrors the merged KuberneteseksDiscoverypattern, keepingservices/free of provider imports) +walkRelationalDB; RDS/Aurora instances, clusters and snapshots now enumerate through Resource Explorer 2 (service:rds). GCP Cloud SQL / Azure SQL discovery are intentionally out of scope.9bfef17Cost + metrics:relationaldb:*rates added to the cost tracker's catalog; instance metrics enriched (FreeStorageSpace, Read/WriteLatency, Network{Receive,Transmit}Throughput), latency/throughput read zero when stopped.41e897alint/cleanup sweep.Design notes / deliberate limitations (emulator honesty)
RestoreTime/UseLatestRestorableTimeare accepted but not replayed.Test plan
newTestMock, table-driven,cerrorscode assertions) and aws-sdk-go-v2 SDK round-trip tests (real client → in-memory handler), incl. an RE2 indexing round-trip for discovery.go build ./...,go vet ./...,gofmt -l, and the fullgo test ./...suite pass.golangci-lint runis clean on all touched packages.Risk & rollback
Additive and capability-gated; no existing action's behavior changes. Each commit is independently green — rollback = revert the relevant commit(s).
Follow-ups (out of scope)
GCP Cloud SQL / Azure SQL discovery (issue #295 item 1 for the other clouds); real hourly RDS pricing; a recorded event timeline for DescribeEvents.