Add local manual leader follower stack
This commit is contained in:
parent
fee9ec0bef
commit
763ff710f0
|
|
@ -51,6 +51,77 @@ These leader-side actions enqueue async snapshot push:
|
|||
|
||||
Queue retries with backoff until follower accepts snapshot.
|
||||
|
||||
## Local Manual Testing
|
||||
|
||||
Backend now has local manual setup target:
|
||||
|
||||
```bash
|
||||
inv docker-blackbox-follow-manual-up
|
||||
```
|
||||
|
||||
This starts:
|
||||
|
||||
- leader production image on `http://127.0.0.1:3002`
|
||||
- follower production image on `http://127.0.0.1:3003`
|
||||
- separate leader and follower Postgres containers
|
||||
- bootstrapped owner users on both backends
|
||||
|
||||
Teardown:
|
||||
|
||||
```bash
|
||||
inv docker-blackbox-follow-down
|
||||
```
|
||||
|
||||
### Local topology
|
||||
|
||||
Compose setup now separates DB traffic:
|
||||
|
||||
- `source-postgres` only on `source-db` internal network
|
||||
- `follower-postgres` only on `follower-db` internal network
|
||||
- both apps share only `sync` network
|
||||
|
||||
This gives:
|
||||
|
||||
- separate production app containers
|
||||
- separate production DB containers
|
||||
- DB isolation between leader and follower
|
||||
|
||||
Important limit:
|
||||
|
||||
- Docker Compose does **not** enforce true one-way HTTP reachability here
|
||||
- local setup still allows follower container to reach leader container over shared `sync` network
|
||||
- real production trust model still comes from backend design:
|
||||
- leader pushes
|
||||
- follower never pulls
|
||||
- follower has no code path that contacts leader
|
||||
|
||||
So local setup is good for frontend/manual testing of behavior, not strict network-policy simulation.
|
||||
|
||||
### Manual frontend testing flow
|
||||
|
||||
1. Run `inv docker-blackbox-follow-manual-up`
|
||||
2. Open leader backend at `http://127.0.0.1:3002`
|
||||
3. Open follower backend at `http://127.0.0.1:3003`
|
||||
4. Log in with:
|
||||
- email: `e2e@example.com`
|
||||
- password: `password123`
|
||||
5. On follower backend, create empty read-only follower tournament
|
||||
6. On leader backend, create normal tournament
|
||||
7. On leader tournament, set:
|
||||
- `sync_target_url` to follower `PATCH /tournaments/:id/sync_state`
|
||||
- `sync_auth_token` to same shared token used on follower
|
||||
8. Point local frontend leader instance to `http://127.0.0.1:3002`
|
||||
9. Point local frontend follower/remote instance to `http://127.0.0.1:3003`
|
||||
10. Progress tournament on leader, verify follower mirrors state and stays read only
|
||||
|
||||
Frontend team can use this setup to build:
|
||||
|
||||
- owner sync configuration UI
|
||||
- follower read-only UX
|
||||
- status display for `sync_last_pushed_at`
|
||||
- status display for `sync_last_push_error`
|
||||
- takeover flow by disabling `read_only_mode`
|
||||
|
||||
## Takeover
|
||||
|
||||
If remote must become writable after sync phase:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ services:
|
|||
retries: 20
|
||||
volumes:
|
||||
- turniere-blackbox-follow-source-postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
- source-db
|
||||
|
||||
follower-postgres:
|
||||
image: ${TURNIERE_BLACKBOX_POSTGRES_IMAGE:-postgres:16}
|
||||
|
|
@ -26,6 +28,8 @@ services:
|
|||
retries: 20
|
||||
volumes:
|
||||
- turniere-blackbox-follow-follower-postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
- follower-db
|
||||
|
||||
source-app:
|
||||
image: ${TURNIERE_BLACKBOX_APP_IMAGE:-registry.gitlab.com/turniere/turniere-backend/production/commits:local}
|
||||
|
|
@ -44,6 +48,9 @@ services:
|
|||
RAILS_SERVE_STATIC_FILES: "1"
|
||||
ports:
|
||||
- "${TURNIERE_BLACKBOX_SOURCE_HOST_PORT:-3002}:3000"
|
||||
networks:
|
||||
- source-db
|
||||
- sync
|
||||
|
||||
follower-app:
|
||||
image: ${TURNIERE_BLACKBOX_APP_IMAGE:-registry.gitlab.com/turniere/turniere-backend/production/commits:local}
|
||||
|
|
@ -62,6 +69,9 @@ services:
|
|||
RAILS_SERVE_STATIC_FILES: "1"
|
||||
ports:
|
||||
- "${TURNIERE_BLACKBOX_FOLLOWER_HOST_PORT:-3003}:3000"
|
||||
networks:
|
||||
- follower-db
|
||||
- sync
|
||||
|
||||
e2e:
|
||||
image: ${TURNIERE_BLACKBOX_RUNNER_IMAGE:-registry.gitlab.com/turniere/turniere-backend/test/commits:local}
|
||||
|
|
@ -82,7 +92,16 @@ services:
|
|||
TURNIERE_E2E_ALT_EMAIL: ${TURNIERE_E2E_ALT_EMAIL:-e2e-alt@example.com}
|
||||
TURNIERE_E2E_ALT_PASSWORD: ${TURNIERE_E2E_ALT_PASSWORD:-password123}
|
||||
TURNIERE_E2E_ALT_USERNAME: ${TURNIERE_E2E_ALT_USERNAME:-e2e-alt-user}
|
||||
networks:
|
||||
- sync
|
||||
|
||||
volumes:
|
||||
turniere-blackbox-follow-source-postgres:
|
||||
turniere-blackbox-follow-follower-postgres:
|
||||
|
||||
networks:
|
||||
source-db:
|
||||
internal: true
|
||||
follower-db:
|
||||
internal: true
|
||||
sync:
|
||||
|
|
|
|||
49
tasks.py
49
tasks.py
|
|
@ -994,6 +994,55 @@ def docker_blackbox_follow_up(
|
|||
print("Follower production apps ready for blackbox E2E.")
|
||||
|
||||
|
||||
@task(name="docker-blackbox-follow-manual-up")
|
||||
def docker_blackbox_follow_manual_up(
|
||||
c,
|
||||
source_host_port=BLACKBOX_SOURCE_HOST_PORT,
|
||||
follower_host_port=BLACKBOX_FOLLOWER_HOST_PORT,
|
||||
app_image=PRODUCTION_TAG,
|
||||
runner_image=TEST_TAG,
|
||||
postgres_image=BLACKBOX_POSTGRES_IMAGE,
|
||||
build=True,
|
||||
):
|
||||
"""Boot local leader/follower production setup for manual testing and print next steps."""
|
||||
docker_blackbox_follow_up(
|
||||
c,
|
||||
source_host_port=source_host_port,
|
||||
follower_host_port=follower_host_port,
|
||||
app_image=app_image,
|
||||
runner_image=runner_image,
|
||||
postgres_image=postgres_image,
|
||||
build=build,
|
||||
)
|
||||
|
||||
source_env = _production_env_for("source-postgres", BLACKBOX_SOURCE_DB_NAME)
|
||||
follower_env = _production_env_for("follower-postgres", BLACKBOX_FOLLOWER_DB_NAME)
|
||||
|
||||
_print_header("Running source production database migrations")
|
||||
c.run("bundle exec rails db:migrate", env=source_env, pty=True)
|
||||
|
||||
_print_header("Running follower production database migrations")
|
||||
c.run("bundle exec rails db:migrate", env=follower_env, pty=True)
|
||||
|
||||
_print_header("Bootstrapping confirmed E2E users on source and follower")
|
||||
_bootstrap_blackbox_users(c, source_env, E2E_EMAIL, E2E_PASSWORD, E2E_USERNAME, E2E_ALT_EMAIL, E2E_ALT_PASSWORD, E2E_ALT_USERNAME)
|
||||
_bootstrap_blackbox_users(c, follower_env, E2E_EMAIL, E2E_PASSWORD, E2E_USERNAME, E2E_ALT_EMAIL, E2E_ALT_PASSWORD, E2E_ALT_USERNAME)
|
||||
|
||||
source_base_url = f"http://127.0.0.1:{source_host_port}"
|
||||
follower_base_url = f"http://127.0.0.1:{follower_host_port}"
|
||||
_print_header("Leader/follower manual setup ready")
|
||||
print(f"Leader URL: {source_base_url}")
|
||||
print(f"Follower URL: {follower_base_url}")
|
||||
print(f"E2E owner email: {E2E_EMAIL}")
|
||||
print(f"E2E owner password: {E2E_PASSWORD}")
|
||||
print("Next steps:")
|
||||
print("1. Create read-only follower tournament on follower backend.")
|
||||
print("2. Create normal leader tournament on leader backend.")
|
||||
print("3. Configure leader sync_target_url to follower /sync_state endpoint with shared token.")
|
||||
print("4. Point frontend local leader view at leader URL and remote view at follower URL.")
|
||||
print("5. Tear down with: inv docker-blackbox-follow-down")
|
||||
|
||||
|
||||
@task(name="docker-blackbox-follow-down")
|
||||
def docker_blackbox_follow_down(
|
||||
c,
|
||||
|
|
|
|||
Loading…
Reference in New Issue