Skip to content

tests: waitForTenantProvisioning races the boot Quartz firing instead of triggering provisioning #6785

Description

@delchev

Problem

IntegrationTest.waitForTenantProvisioning polls for TenantStatus.PROVISIONED and caps at 35 seconds:

protected void waitForTenantProvisioning(DirigibleTestTenant tenant) {
    Awaitility.await()
              .pollInterval(3, TimeUnit.SECONDS)
              .atMost(35, TimeUnit.SECONDS)
              .until(() -> tenantCreator.isTenantProvisioned(tenant));
}

Nothing in that path triggers provisioning. TenantCreator.createTenant only saves a Tenant row with
status = INITIAL; the transition to PROVISIONED happens exclusively when the Quartz
TenantsProvisioningJob fires — and its schedule is

TENANTS_PROVISIONING_FREQUENCY_SECONDS("DIRIGIBLE_TENANTS_PROVISIONING_FREQUENCY_SECONDS", "900"), // 15 minutes

The only prompt firing is the one at scheduler startup. So the helper is not waiting for provisioning,
it is racing the boot firing:

  • a tenant created before that firing is provisioned in seconds — this is why NumberingSdkIT,
    EnabledMultitenantModeIT, MultitenancyUserInterfaceIntegrationTest and friends pass;
  • a tenant created after it can only be provisioned 15 minutes later, so the 35s cap expires.

How it surfaces

As a bare ConditionTimeoutException pointing at the framework, which reads like a product bug rather
than a test-infrastructure one:

[ERROR] SomeIT.someTest:76->IntegrationTest.waitForTenantProvisioning:76 » ConditionTimeout
        Condition with Lambda expression in org.eclipse.dirigible.tests.base.IntegrationTest
        was not fulfilled within 35 seconds.

Observed 2026-08-17 while writing the IT for #6765: a class that provisioned two tenants (one per
test method) had the first method's tenant provisioned and the second's time out, purely on where the
boot firing landed. Every existing caller happens to provision at most one tenant per class, which is
why the latent flake has never been hit — but nothing states that constraint, and the next
multi-tenant IT will walk into it.

Ask

Make provisioning deterministic rather than incidental. The mechanism used in JavaListenerTenantIT
(PR #6782) as a local workaround is a one-liner and could move into the framework:

createTenants(tenant);
scheduler.triggerJob(JobKey.jobKey("TenantsProvisioningJob", "system"));  // @Autowired org.quartz.Scheduler
waitForTenantProvisioning(tenant);

("system" is the group every SystemJob registers under, per DirigibleJob.createJob().)

Whether that belongs inside createTenants, inside waitForTenantProvisioning, or in a new
provisionTenants(...) helper is the design call. Two things worth deciding alongside it:

  • waitForTenantProvisioning returns too early for some callers. The status flips to PROVISIONED
    before the TenantPostProvisioningSteps run, so anything depending on those — the synchronizer
    retrigger, the client-Java listener subscription top-up added in fix(messaging): the client-Java listener path subscribes per tenant (#6765) #6782 — still needs its own poll
    afterwards. NumberingSdkIT already works around this with a second Awaitility loop that tolerates
    IllegalStateException "until the post-provisioning synchronizer retrigger finishes". A helper that
    waited for provisioning including post-provisioning would remove that pattern from callers.
  • Changing the shared helper's behaviour affects downstream editions, which reuse
    tests-integrations / tests-framework as src/main. Adding a new helper is safe; altering the
    existing one needs a look at who calls it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions