Processing triggered outside the API — keystone — branch stable/2026.2
Branch
stable/2026.2@e6dc5ae079e6— repository https://opendev.org/openstack/keystone — analysed on 2026-10-28
Finding: no bus consumer, no periodic task
| Family | Present? | Check |
|---|---|---|
| Bus consumers (RPC, notification listeners) | no | oslo.messaging is only used through Notifier (keystone/notifications.py:487-506). The two "subscriptions" in the inventory are false positives: create_consumer creates an OAuth1 consumer (keystone/api/os_oauth1.py:132) |
| SQL polling, periodic tasks | no | no periodic_task, LoopingCall or timer outside tests |
| Cache polling | no | the caches expire (cache_time) or are invalidated by events (see interactions § 4) |
| In-process events | yes | notifications.register_event_callback and the @notifications.listener decorator: callbacks run synchronously, inside the API request, when a manager emits a created, updated, deleted, disabled or internal event |
So the "background" work of Keystone is of two kinds: cascades run inside the request that triggered them, and operator commands (keystone-manage) that must be scheduled outside (cron, Kubernetes jobs).
1. In-process callbacks (cascades)
notify_event_callbacks (keystone/notifications.py:466-485) calls every function registered for (event, resource type), in the request thread and in its transaction context. An exception in a callback propagates to the request [inferred: no try/except around the call].
| Event | Registered reaction | Effect | Defined at |
|---|---|---|---|
deleted user | revocation | revocation event user_id → every token of the user becomes invalid | keystone/revoke/core.py:76-90 |
disabled user | revocation | same | keystone/revoke/core.py:83 |
deleted / disabled user | application credentials | deletion of the user's application credentials and access rules | keystone/application_credential/core.py:45-62 |
deleted user | trusts | deletion of the trusts where the user is trustee | keystone/trust/core.py:40-52 |
deleted / disabled user, domain; disabled project; deleted trust | token and receipt caches | the tokens and receipts regions are invalidated | keystone/token/provider.py:96-130, keystone/receipt/provider.py:73-90 |
deleted project | revocation | revocation event project_id | keystone/revoke/core.py:81 |
deleted project | identity | default_project_id is cleared for the users who had it | keystone/identity/core.py:613-617, keystone/identity/core.py:659 |
deleted project or endpoint | catalog | deletion of the OS-EP-FILTER associations | keystone/catalog/core.py:58-90 |
deleted trust, OAuth1 consumer | revocation | revocation event trust_id / consumer_id | keystone/revoke/core.py:78-79 |
deleted domain | assignments | deletion of every assignment in the domain, invalidation of computed assignments | keystone/assignment/core.py:76-90 |
internal domain_deleted | identity | deletion of the domain's groups and users (SQL backend) | keystone/identity/core.py:608-657 |
internal domain_deleted | federation | deletion of the identity providers tied to the domain | keystone/federation/core.py:48-60 |
internal persist_revocation_event_for_user | revocation | revocation event for the user (emitted by _persist_revocation_event_for_user, keystone/identity/core.py:1583-1594, called by update_user when the password or enabled changes, delete_group, remove_user_from_group and change_password: keystone/identity/core.py:1379, keystone/identity/core.py:1505, keystone/identity/core.py:1567, keystone/identity/core.py:1702) | keystone/revoke/core.py:84-89 |
internal remove_application_credentials_for_user | application credentials | deletion of the application credentials that depend on a removed role assignment (emitted by keystone/assignment/core.py:111) | keystone/application_credential/core.py:59-62 |
internal invalidate_token_cache | token cache | invalidation of tokens, emitted on role, assignment and membership changes (keystone/assignment/core.py:296, keystone/identity/core.py:1386) | keystone/token/provider.py:110-112 |
Project deletion itself also cleans its assignments, credentials, trusts and limits, directly and without an event (keystone/resource/core.py:521-539).
Operational consequences:
- Deleting a domain or a large project is one long request that does all the cleanup. There is no queue: a timeout on the HTTP proxy side can interrupt the response while the work goes on, or leave it half done if the WSGI worker is killed [inferred].
- Revocation events are written in the request, so a token is invalid on every node as soon as the commit is done, within the token cache: each node keeps its
tokenscache, unless the cache is shared and invalidated by region (see 03 § 4).
2. Notifications sent (to the outside)
Every create, update, delete or disable operation, every role assignment and every authentication (success excluded by default) publishes an identity.* event (see interactions § 5). It is fire-and-forget: a bus failure is logged and does not fail the request (keystone/notifications.py:582-636). Consumers outside Keystone include Ceilometer, Designate and the audit middleware [inferred: outside this repository].
3. Work at API process startup
| Step | Purpose | Defined at |
|---|---|---|
keystone.server.configure | read the configuration (OS_KEYSTONE_CONFIG_*), set up logging | keystone/server/flask/core.py:145-170 |
setup_backends | load the drivers through stevedore (keystone.identity, keystone.token.provider…, see pyproject.toml), configure the caches, register the callbacks above | keystone/server/backends.py |
application_factory | build Flask, register the exception handlers and the 27 API modules, mount /healthcheck | keystone/server/flask/application.py:186-238 |
| middleware | stack the pipeline (CORS, size limit, proxy, profiler, request ID, URL normalisation, auth context) | keystone/server/flask/core.py:93-142 |
Unlike Placement, Keystone writes nothing to the database at startup: no standard data is copied. Default roles, the admin user and the catalog come from keystone-manage bootstrap.
4. Operator commands (keystone-manage, keystone/cmd/cli.py)
| Command | Purpose | Frequency | Defined at |
|---|---|---|---|
db_sync [--expand|--migrate|--contract|--check] | Alembic migrations, in rolling-upgrade phases | each upgrade | keystone/cmd/cli.py:361-494 |
bootstrap | create (idempotently) the admin, member, reader and service roles and their implications, the admin domain, project and user, the system assignment, and the region and Keystone endpoints | install, then each upgrade | keystone/cmd/cli.py:64-262, keystone/cmd/bootstrap.py |
fernet_setup / fernet_rotate (and token_setup/token_rotate) | create and rotate the token keys | regular (cron), with the repository synchronised on every node | keystone/cmd/cli.py:613-780 |
create_jws_keypair | ES256 keys for the JWS provider | once | keystone/cmd/cli.py:688 |
receipt_setup / receipt_rotate | MFA receipt keys | regular | keystone/cmd/cli.py:781-826 |
credential_setup / credential_rotate / credential_migrate | credential encryption keys; migrate re-encrypts every credential row with the primary key before old keys are dropped | at each credential key rotation | keystone/cmd/cli.py:828-970 |
trust_flush [--project-id] [--trustor-user-id] [--trustee-user-id] [--date] | purge expired or deleted trusts (keystone never deletes them by itself) | regular | keystone/cmd/cli.py:972-1050 |
mapping_purge / mapping_populate | clean or pre-fill id_mapping (LDAP) | as needed | keystone/cmd/cli.py:1051, keystone/cmd/cli.py:1594 |
domain_config_upload | load domain configuration files into the database | as needed | keystone/cmd/cli.py:1358 |
saml_idp_metadata | generate the SAML IdP metadata | once | keystone/cmd/cli.py:1393 |
mapping_engine | test a federation mapping against an assertion, offline | as needed | keystone/cmd/cli.py:1404 |
doctor | configuration checks | as needed | keystone/cmd/cli.py:345 |
reset_last_active | reset last_active_at (account inactivity) | as needed | keystone/cmd/cli.py:505 |
keystone-status upgrade check | pre-upgrade checks | before an upgrade | keystone/cmd/status.py |
What nobody purges automatically:
- expired trusts:
trust_flushis needed; - users inactive beyond
[security_compliance]/disable_user_account_days_inactive: no task disables them. Theenabledproperty is computed on read fromlast_active_at, orcreated_atif never active (keystone/identity/backends/sql_model.py:217-238), so such a user simply reads as disabled. Accounts with theignore_user_inactivityoption are exempt; - revocation events: purged at each new revocation (see 03 § 3).
Source: reverse-spec-keystone/05-async-processing_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.