Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,53 @@ If you don't have test clusters readily available to try with, check the [altern
[Contributor's guide](./CONTRIBUTING.md), which will set up all the dependencies, including two test clusters and a proxy instance, in a
containerized sandbox environment.

## Runtime Target Toggle API

The proxy exposes a REST API on the metrics HTTP port (default `14001`) to dynamically enable or disable the target cluster at runtime. When the target is disabled, **no requests are forwarded to the target cluster** — all reads and writes go to origin only.

This is intended for emergency use when the target cluster is down or unhealthy. Without this toggle, a failing target causes all client requests to error or timeout because the proxy waits for both clusters to respond.

### Endpoints

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/v1/target` | Returns current target status: `{"enabled": true}` or `{"enabled": false}` |
| `POST` | `/api/v1/target/enable` | Enables the target cluster. All requests resume flowing to both clusters. |
| `POST` | `/api/v1/target/disable` | Disables the target cluster. No requests are sent to target. |

### Usage

```bash
# Check current status
curl http://localhost:14001/api/v1/target

# Disable target (emergency — target is down)
curl -X POST http://localhost:14001/api/v1/target/disable

# Re-enable target (target has recovered)
curl -X POST http://localhost:14001/api/v1/target/enable
```

### Behaviour

- **Default**: Target is enabled. The proxy behaves normally.
- **When disabled**: All `forwardToBoth` and `forwardToTarget` requests are redirected to origin only. No heartbeats are sent to target. Prepared statement caches on the target become stale.
- **When re-enabled**: Requests resume flowing to both clusters. If prepared statements are stale on the target, the client driver's standard UNPREPARED recovery mechanism handles re-preparation automatically.
- **Not persisted**: If the proxy restarts, the target defaults back to enabled.

### Restrictions

The toggle is only available when the proxy is running with default migration-phase configs:

- **`read_mode`** must be `PRIMARY_ONLY` (default). If `DUAL_ASYNC_ON_SECONDARY` is enabled, the disable endpoint returns `409 Conflict`.
- **`forward_client_credentials_to_origin`** must be `false` (default). If enabled, the disable endpoint returns `409 Conflict`.

These configs require the target cluster to be reachable and are used in later migration phases where disabling target is not appropriate. To use the target toggle, restart the proxy without these settings.

### Monitoring

A Prometheus gauge `zdm_target_enabled` is exposed on the `/metrics` endpoint. Value is `1` when enabled, `0` when disabled. Use this for alerting on unintended state.

## Supported Protocol Versions

**ZDM Proxy supports protocol versions v2, v3, v4, DSE_V1 and DSE_V2.**
Expand Down
8 changes: 4 additions & 4 deletions integration-tests/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ they are registered separately on the parent test.
*/

func TestWithHttpHandlers(t *testing.T) {
metricsHandler, readinessHandler := runner.SetupHandlers()
metricsHandler, readinessHandler, _ := runner.SetupHandlers()

t.Run("testMetrics", func(t *testing.T) {
testMetrics(t, metricsHandler)
Expand Down Expand Up @@ -84,7 +84,7 @@ func testHttpEndpointsWithProxyNotInitialized(
wg.Add(1)
go func() {
defer wg.Done()
runner.RunMain(conf, ctx, metricsHandler, healthHandler)
runner.RunMain(conf, ctx, metricsHandler, healthHandler, httpzdmproxy.NewHandlerWithFallback(httpzdmproxy.DefaultTargetHandler()))
}()

time.Sleep(500 * time.Millisecond)
Expand Down Expand Up @@ -121,7 +121,7 @@ func testHttpEndpointsWithProxyInitialized(
wg.Add(1)
go func() {
defer wg.Done()
runner.RunMain(conf, ctx, metricsHandler, healthHandler)
runner.RunMain(conf, ctx, metricsHandler, healthHandler, httpzdmproxy.NewHandlerWithFallback(httpzdmproxy.DefaultTargetHandler()))
}()

httpAddr := fmt.Sprintf("%s:%d", conf.MetricsAddress, conf.MetricsPort)
Expand Down Expand Up @@ -258,7 +258,7 @@ func testHttpEndpointsWithUnavailableNode(
wg.Add(1)
go func() {
defer wg.Done()
runner.RunMain(conf, ctx, metricsHandler, healthHandler)
runner.RunMain(conf, ctx, metricsHandler, healthHandler, httpzdmproxy.NewHandlerWithFallback(httpzdmproxy.DefaultTargetHandler()))
}()

httpAddr := fmt.Sprintf("%s:%d", conf.MetricsAddress, conf.MetricsPort)
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/streamid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestStreamIdsMetrics(t *testing.T) {
require.Nil(t, err)
defer testSetup.Cleanup()

metricsHandler, _ := runner.SetupHandlers()
metricsHandler, _, _ := runner.SetupHandlers()
wg := &sync.WaitGroup{}
defaultConf := setup.NewTestConfig("", "")
srv := startMetricsHandler(t, defaultConf, wg, metricsHandler)
Expand Down
Loading
Loading