Reindex vulnerabilities when group tree transfers to new organization

What does this MR do and why?

When a top-level group is transferred to a different organization, Organizations::Transfer::GroupsService updates organization_id on every namespace and project in the group tree, then publishes a single Organizations::GroupTransferredEvent for the root group. However, the organization_id field on Elasticsearch vulnerability documents was never refreshed, so transferred vulnerabilities kept appearing on the old organization's Security Dashboard (which is scoped only by that ES field).

This MR adds an event-driven re-index, following the process of Vulnerabilities::ProcessTransferEventsWorker:

  • Vulnerabilities::ProcessOrganizationTransferEventWorker subscribes to Organizations::GroupTransferredEvent. Since the event fires only for the root group, it traverses the group tree with Gitlab::Database::NamespaceProjectIdsEachBatch, keeps only projects with has_vulnerabilities, and bulk-enqueues per-project jobs in slices of 1,000.
  • Vulnerabilities::ReindexProjectVulnerabilitiesWorker re-indexes a single project's vulnerability_reads in batches of 100 via Vulnerabilities::BulkEsOperationService, without touching database rows.

Why a re-index is sufficient

  • Search::Elastic::References::Vulnerability#as_indexed_json computes organization_id from project.namespace.organization_id at index time. The event is published after commit, so the new organization ID is already persisted when the worker runs.
  • ES routing (es_parent = "group_#{root_ancestor.id}") is unchanged: only root groups can be transferred and the hierarchy stays intact, so documents are updated in place and no stale duplicates are left behind. For the same reason, no traversal_ids update is needed.
  • The organization_id field is written via set_field, gated on the add_organization_id_to_vulnerability migration, so this is safe on instances where that ES migration has not finished.
  • No denormalized organization_id columns exist on vulnerability_reads, vulnerability_statistics, or vulnerability_namespace_statistics, so Elasticsearch is the only place this value needs resyncing.

Both workers are idempotent and deduplicated (:until_executing). Follow-up verification of the end-to-end application logic is tracked in #605426 (closed).

How to set up and validate locally

  1. Enable Elasticsearch indexing and index the instance (Admin > Settings > Search).
  2. Create two organizations, a group tree (group + subgroup) in the first organization, and a project with vulnerabilities in each group.
  3. Confirm the vulnerabilities appear on the first organization's Security Dashboard.
  4. Transfer the top-level group to the second organization.
  5. After Sidekiq processes the jobs and the ES index refreshes, verify the vulnerabilities now appear on the second organization's Security Dashboard and no longer on the first.

Related to #605425 (closed)

Edited by Subashis Chakraborty

Merge request reports

Loading
Loading