Skip to content

feat(experimentation): provision per-org ingestion infra on external warehouse connect#8052

Open
gagantrivedi wants to merge 1 commit into
mainfrom
feat/wire-ingestion-to-warehouse-connections
Open

feat(experimentation): provision per-org ingestion infra on external warehouse connect#8052
gagantrivedi wants to merge 1 commit into
mainfrom
feat/wire-ingestion-to-warehouse-connections

Conversation

@gagantrivedi

@gagantrivedi gagantrivedi commented Jul 21, 2026

Copy link
Copy Markdown
Member

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Contributes to the experimentation analytics ingestion pipeline (builds on #8035 and #8020).

Wires the per-organisation events S3 bucket and Firehose delivery stream to warehouse connections:

  • Adds an OrganisationIngestionInfrastructure model tracking, per organisation, whether the bucket and stream have been created (create-once).
  • Creating an external (non-flagsmith) warehouse connection provisions the organisation's bucket and stream, then syncs the environment's ingestion keys.
  • Deleting an organisation tears the infrastructure down, when it exists.
  • The Flagsmith-managed (flagsmith) warehouse path is unchanged.

Note: recovering from errors raised during infrastructure provisioning or teardown (partial failures, retries, orphaned AWS resources) is manual for now.

How did you test this code?

Unit tests covering the AWS provision/deprovision primitives (via moto), the create-once/errored service layer, the two tasks, and the warehouse-connection and organisation lifecycle hooks. New modules are at 100% coverage and the full experimentation and organisation model suites pass.

@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 21, 2026 8:53am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ignored Ignored Preview Jul 21, 2026 8:53am
flagsmith-frontend-staging Ignored Ignored Preview Jul 21, 2026 8:53am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds organisation-scoped ingestion infrastructure persistence with lifecycle statuses and storage metadata. External warehouse creation now queues organisation provisioning, which records success or failure and writes environment keys. Teardown deletes Firehose streams and S3 buckets, including bucket contents. Organisation deletion schedules asynchronous cleanup. New task, service, model, AWS teardown, and deletion-hook tests cover these paths, while the observability catalogue documents the new events.

Estimated code review effort: 3 (Moderate) | ~25 minutes


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added api Issue related to the REST API docs Documentation updates infrastructure labels Jul 21, 2026
@gagantrivedi
gagantrivedi force-pushed the feat/wire-ingestion-to-warehouse-connections branch from 3fd6331 to 535d4e0 Compare July 21, 2026 07:54
@github-actions github-actions Bot added feature New feature or request and removed infrastructure docs Documentation updates labels Jul 21, 2026
…warehouse connect

Wire per-organisation events S3 bucket and Firehose stream provisioning to
external (non-flagsmith) warehouse connection creation, tracked by a new
OrganisationIngestionInfrastructure model, and tear it down on organisation
deletion.
@gagantrivedi
gagantrivedi force-pushed the feat/wire-ingestion-to-warehouse-connections branch from 535d4e0 to 421a205 Compare July 21, 2026 08:53
@github-actions github-actions Bot added the docs Documentation updates label Jul 21, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 21, 2026
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.66%. Comparing base (7266074) to head (421a205).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #8052    +/-   ##
========================================
  Coverage   98.65%   98.66%            
========================================
  Files        1514     1517     +3     
  Lines       60110    60292   +182     
========================================
+ Hits        59304    59486   +182     
  Misses        806      806            

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gagantrivedi

Copy link
Copy Markdown
Member Author

@themis-blindfold review

@Zaimwa9
Zaimwa9 marked this pull request as ready for review July 21, 2026 09:27
@Zaimwa9
Zaimwa9 requested review from a team as code owners July 21, 2026 09:27
@Zaimwa9
Zaimwa9 requested review from emyller and removed request for a team July 21, 2026 09:27
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jul 21, 2026
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-e2e:pr-8052 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api-test:pr-8052 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-frontend:pr-8052 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-api:pr-8052 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-8052 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8052 Finished ✅ Results

Comment thread api/experimentation/ingestion_infra_service.py
@themis-blindfold

Copy link
Copy Markdown

⚖️ Themis judgement: 🟠 Fix before merge

Wires per-org ingestion infrastructure (S3 bucket + Firehose delivery stream) to warehouse connections: provisions on external warehouse create, tears down on organisation delete. The create-once pattern in enable_ingestion_for_organisation is well-structured and the CREATED early-return makes the provisioning task retry-safe. However, the deprovision path is not safe to retry when it partially completes.

🎯 Correctness 3/5
🧪 Test coverage 4/5
📐 Code quality 4/5
🚀 Product impact 3/5

🟠 Majors

  • api/experimentation/ingestion_infra_service.pydeprovision_ingestion_infrastructure is not retry-safe; a partial failure leaves the stream deleted but the bucket leaked on every subsequent retry. See inline comment.
📝 Walkthrough
  • Per-org infrastructure model — new OrganisationIngestionInfrastructure model tracks provisioning state (pending/created/errored) per organisation, with a CreateModel-only migration.
  • Provisioning wiringWarehouseConnection.sync_to_ingestion_on_create now branches: FLAGSMITH connections sync keys directly; external connections enqueue a new provision_external_warehouse_ingestion_infrastructure task that provisions the org bucket+stream first, then chains key sync.
  • Teardown wiringOrganisation.teardown_ingestion_infrastructure (AFTER_DELETE hook) enqueues async teardown; disable_ingestion_for_organisation deprovisions AWS resources and deletes the tracking row.
  • Deprovision primitivedeprovision_ingestion_infrastructure empties the S3 bucket (paginated delete) and deletes both the bucket and Firehose stream.
  • Tests — unit tests cover provisioning (fresh, idempotent, errored), deprovisioning (happy path, bucket with objects), task wrappers, model hooks (with and without infrastructure), plus an autouse fixture to prevent real provisioning during other experimentation tests.
  • Docs — events catalogue updated with four new structured log events and corrected line references for two existing events.
🧪 How to verify
  1. In a moto-backed test, provision infrastructure, then have _delete_events_bucket raise mid-way; retry deprovision_ingestion_infrastructure and confirm the stream deletion does not raise ResourceNotFoundException and the bucket is still cleaned up.
  2. Create a ClickHouse WarehouseConnection in a test environment and confirm OrganisationIngestionInfrastructure transitions to CREATED.
  3. Delete an organisation that has ingestion infrastructure and confirm both the Firehose stream and S3 bucket are removed.
  4. Run the full api/tests/unit/experimentation/ suite to confirm the new autouse mock does not break existing tests.

Automate: add a test for deprovision_ingestion_infrastructure retry after partial failure (stream deleted, bucket remains).

Product take: This is the glue that makes per-org event ingestion real for external (bring-your-own) warehouse users. It's a solid step toward self-service experimentation infrastructure, but the deprovisioning path needs to be reliable before it can run unsupervised in production.

🧭 Assumptions & unverified claims
  • The review assumes that @register_task_handler() retries tasks that raise exceptions; the task_processor package was not available locally for source inspection.
  • The review assumes Organisation.delete() is always a soft delete (via SoftDeleteExportableModel), so CASCADE does not fire and the OrganisationIngestionInfrastructure row survives for the AFTER_DELETE hook to find. If hard deletes ever occur, the teardown path would silently skip.

The provisioning side is retry-proof; the deprovisioning side is retry-hostile — a rare asymmetry worth closing before merge. · reviewed at 421a205

@themis-blindfold

Copy link
Copy Markdown

Review was cancelled before completing (worker timeout or shutdown). Mention @themis-blindfold with review to retry.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #18560 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  4 passed

Details

stats  4 tests across 4 suites
duration  6.8 seconds
commit  421a205
info  🔄 Run: #18560 (attempt 1)

🗂️ Previous results
✅ private-cloud · depot-ubuntu-latest-16 — run #18560 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  5 passed

Details

stats  5 tests across 5 suites
duration  32.5 seconds
commit  421a205
info  🔄 Run: #18560 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #18560 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  35.7 seconds
commit  421a205
info  🔄 Run: #18560 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #18560 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  36.9 seconds
commit  421a205
info  🔄 Run: #18560 (attempt 1)

@github-actions

Copy link
Copy Markdown
Contributor

Visual Regression

19 screenshots compared. See report for details.
View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 749a0e98-f03d-4265-84ef-c9a73d79ca88

📥 Commits

Reviewing files that changed from the base of the PR and between f388ee8 and 421a205.

📒 Files selected for processing (13)
  • api/experimentation/ingestion_infra_service.py
  • api/experimentation/migrations/0011_organisation_ingestion_infrastructure.py
  • api/experimentation/models.py
  • api/experimentation/organisation_ingestion_service.py
  • api/experimentation/tasks.py
  • api/organisations/models.py
  • api/tests/unit/experimentation/conftest.py
  • api/tests/unit/experimentation/test_ingestion_infra_service.py
  • api/tests/unit/experimentation/test_models.py
  • api/tests/unit/experimentation/test_organisation_ingestion_service.py
  • api/tests/unit/experimentation/test_tasks.py
  • api/tests/unit/organisations/test_unit_organisations_models.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md

Comment thread api/experimentation/ingestion_infra_service.py
Comment thread api/experimentation/ingestion_infra_service.py
Comment thread api/experimentation/models.py
Comment thread api/experimentation/organisation_ingestion_service.py
Comment thread api/experimentation/organisation_ingestion_service.py
Comment thread api/tests/unit/experimentation/conftest.py
@gagantrivedi
gagantrivedi requested review from Zaimwa9 and removed request for emyller July 21, 2026 11:10

@Zaimwa9 Zaimwa9 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprovisioning path doesn't work as expected from what I see

infrastructure = OrganisationIngestionInfrastructure.objects.filter(
organisation_id=organisation_id
).first()
if infrastructure is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect the organisation soft delete would delete the OrganisationIngestionInfrastructure row here (it's not a soft deletable model) so we would always hit the early return.

I think we could either:

  1. Have the check upstream ?
  2. Use a SoftDeletableModel and include the deleted records in the query ? But that's a bit dirty

I'm not sure we can verify my claims with a test there ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I just gave it a try with this test that fails

def test_organisation__delete_with_created_infrastructure__deprovisions_aws_resources(
    organisation: Organisation,
    mocker: MockerFixture,
) -> None:
    # Given
    OrganisationIngestionInfrastructure.objects.create(
        organisation=organisation,
        status=IngestionInfrastructureStatus.CREATED,
        bucket_name="flagsmith-events-lake-org-1-123456789012-eu-west-2-an",
        stream_name="events-ingestion-org-1",
    )
    deprovision = mocker.patch(
        "experimentation.organisation_ingestion_service"
        ".deprovision_ingestion_infrastructure",
    )
    organisation_id = organisation.id

    # When
    organisation.delete()

    # Then
    deprovision.assert_called_once_with(organisation_id)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants