Parent: #726
Depends on: #727 (per-workload FSM and site-wide-action bypass must land first).
Background
The current replication REST surface encodes operation semantics in two places:
- HTTP method + URL path
- A
RequestType field on the JSON request body (api/types/replication_rbd.go:107, api/types/replication_cephfs.go:36)
Both are needed today because the URL PUT /ops/replication/{wl} is overloaded: it serves both site-wide actions (promote, demote) and would collide with a resource-scoped path if the resource name happened to be promote, demote, or anything placed at the action position. The body field exists to disambiguate.
This produces several artifacts:
WorkloadReplicationRequest = "" sentinel constant (api/types/replication.go:30).
OverwriteRequestType server-side method that conditionally overwrites the client-supplied value (api/types/replication_rbd.go:158, api/types/replication_cephfs.go:82) — empty overwrite preserves client intent (workload-level), non-empty replaces it (resource-level).
GetAPIRequestType / GetWorkloadRequestType parsers that string-split a hyphen-encoded value carrying both the HTTP method and the FSM event (api/types/replication.go:53,64).
- Client-side HTTP method dispatch reads
RequestType to choose verb (client/replication.go:24,31).
The schema is internally consistent but reflects the URL ambiguity rather than fixing it.
Proposed redesign
URL layout where path identifies the resource, HTTP method identifies the operation, and a separate /actions/ namespace holds site-wide ops so resource names cannot collide:
# Remotes
GET /v1/remotes
POST /v1/remotes
GET /v1/remotes/{name}
DELETE /v1/remotes/{name}
# RBD mirrors
GET /v1/mirrors/rbd
GET /v1/mirrors/rbd/pools/{pool}
POST /v1/mirrors/rbd/pools/{pool}
PATCH /v1/mirrors/rbd/pools/{pool}
DELETE /v1/mirrors/rbd/pools/{pool}
GET /v1/mirrors/rbd/pools/{pool}/images/{image}
POST /v1/mirrors/rbd/pools/{pool}/images/{image}
DELETE /v1/mirrors/rbd/pools/{pool}/images/{image}
# CephFS mirrors
GET /v1/mirrors/cephfs/volumes/{vol}
POST /v1/mirrors/cephfs/volumes/{vol}
DELETE /v1/mirrors/cephfs/volumes/{vol}
# Site-wide actions (separate namespace — cannot collide with resource names)
POST /v1/mirrors/rbd/actions/promote
POST /v1/mirrors/rbd/actions/demote
POST /v1/mirrors/rbd/actions/resync
Properties
- Path identifies the resource. HTTP method identifies the operation. No body field carries that information.
/actions/ is a fixed sub-namespace under the workload root. A pool named actions lives at pools/actions, not at actions. No collision.
POST on a resource = enable. DELETE = disable. PATCH = reconfigure. GET = status or list.
- Site actions are
POST under /actions/ because they are imperative invocations, not resource lifecycle transitions.
/v1/ prefix gives a versioning anchor.
What this kills
RequestType field on the wire (becomes server-internal, derived from (method, path), or replaced by direct dispatch at the API entry).
WorkloadReplicationRequest sentinel.
OverwriteRequestType method.
GetAPIRequestType / GetWorkloadRequestType string-split parsing.
What stays
Migration
The replication API is consumed by the in-tree CLI (cmd/microceph/replication_*.go). Audit needed: enumerate any callers outside the microceph CLI.
- No external consumer: internal rename is feasible — single PR updates server routes, client library, and CLI together.
- External consumers exist: ship
/v1/ alongside legacy paths and deprecate legacy in a follow-up release.
Out of scope
Acceptance
- Replication API responds at
/v1/mirrors/... and /v1/remotes/....
- Body schemas no longer carry a request-type discriminator.
- CLI subcommands construct URLs by resource path; HTTP method is picked directly without consulting a body field.
- Resource names that overlap with action verbs (
promote, demote, resync) work correctly.
- Legacy paths either removed or marked deprecated, depending on the migration audit.
Parent: #726
Depends on: #727 (per-workload FSM and site-wide-action bypass must land first).
Background
The current replication REST surface encodes operation semantics in two places:
RequestTypefield on the JSON request body (api/types/replication_rbd.go:107,api/types/replication_cephfs.go:36)Both are needed today because the URL
PUT /ops/replication/{wl}is overloaded: it serves both site-wide actions (promote, demote) and would collide with a resource-scoped path if the resource name happened to bepromote,demote, or anything placed at the action position. The body field exists to disambiguate.This produces several artifacts:
WorkloadReplicationRequest = ""sentinel constant (api/types/replication.go:30).OverwriteRequestTypeserver-side method that conditionally overwrites the client-supplied value (api/types/replication_rbd.go:158,api/types/replication_cephfs.go:82) — empty overwrite preserves client intent (workload-level), non-empty replaces it (resource-level).GetAPIRequestType/GetWorkloadRequestTypeparsers that string-split a hyphen-encoded value carrying both the HTTP method and the FSM event (api/types/replication.go:53,64).RequestTypeto choose verb (client/replication.go:24,31).The schema is internally consistent but reflects the URL ambiguity rather than fixing it.
Proposed redesign
URL layout where path identifies the resource, HTTP method identifies the operation, and a separate
/actions/namespace holds site-wide ops so resource names cannot collide:Properties
/actions/is a fixed sub-namespace under the workload root. A pool namedactionslives atpools/actions, not atactions. No collision.POSTon a resource = enable.DELETE= disable.PATCH= reconfigure.GET= status or list.POSTunder/actions/because they are imperative invocations, not resource lifecycle transitions./v1/prefix gives a versioning anchor.What this kills
RequestTypefield on the wire (becomes server-internal, derived from(method, path), or replaced by direct dispatch at the API entry).WorkloadReplicationRequestsentinel.OverwriteRequestTypemethod.GetAPIRequestType/GetWorkloadRequestTypestring-split parsing.What stays
(method, path)to event happens once at the API entry.Migration
The replication API is consumed by the in-tree CLI (
cmd/microceph/replication_*.go). Audit needed: enumerate any callers outside the microceph CLI./v1/alongside legacy paths and deprecate legacy in a follow-up release.Out of scope
Acceptance
/v1/mirrors/...and/v1/remotes/....promote,demote,resync) work correctly.