Processing outside the API — skyline-apiserver — branch stable/2026.2
Branch
stable/2026.2@4af72bfd4577— repository https://opendev.org/openstack/skyline-apiserver — analysed on 2026-10-28
skyline-apiserver has no bus consumer, notification listener, periodic task, looping call, OVSDB event or background thread (the inventory finds 0 triggers; grep finds no asyncio.create_task, BackgroundTasks or scheduler). All its work happens inside an HTTP request or at process start. This file lists the processing that is not an endpoint's own logic but runs implicitly, because a re-implementation must reproduce it.
| Process | Trigger | Type | Purpose | Reads | Writes / emits | Defined at |
|---|---|---|---|---|---|---|
| API worker | start of each worker (FastAPI lifespan) | start-up | load configuration, logging, policies, database engine | skyline.yaml, embedded policy modules | — | skyline_apiserver/main.py:45 |
| API worker | every /api/v1/* request except the public paths | per-request hook (middleware) | purge expired revocations, check revocation, validate the Keystone token, renew the session cookie | revoked_token, Keystone GET /v3/auth/tokens | DELETE revoked_token, Set-Cookie | skyline_apiserver/main.py:76 |
| API worker | first use, then token expiry | lazy re-authentication | keep a valid system token per worker | Keystone | POST /v3/auth/tokens | skyline_apiserver/client/utils.py:58 |
| API worker | every policy evaluation | file polling (mtime) | pick up operator policy overrides without restart | <policy_file_path>/<service>_policy.yaml | in-memory rule cache | skyline_apiserver/policy/base.py:105 |
| container start | container/start_service.sh:23 | one-shot | generate the nginx configuration from the Keystone catalog | Keystone /v3/endpoints, /v3/services | /etc/nginx/nginx.conf | skyline_apiserver/cmd/generate_nginx.py:230 |
| bootstrap | KOLLA_BOOTSTRAP set | one-shot | migrate the database | Alembic versions | tables | container/start_service.sh:10 |
Start-up of a worker (lifespan)
- Trigger: gunicorn starts
(1 + CPUs) // 2uvicorn workers (etc/gunicorn.py:21); each runs the lifespan hook (skyline_apiserver/main.py:45). - Flow: 1.
configure("skyline")reads$OS_CONFIG_DIR/skyline.yamland validates every option with pydantic (skyline_apiserver/config/base.py:113) — missing file or wrong type stops the worker; 2. logging to<log_dir>/<log_file>; 3.policies_setup()parses the embedded rules of the 16 services (skyline_apiserver/policy/__init__.py:25); 4.db_setup()creates the SQLAlchemy engine (no connection yet,skyline_apiserver/db/base.py:65); 5. CORS middleware ifcors_allow_originsis not empty — which fails on Starlette 1.x (00 § 3). - Not done at start-up: no Keystone call (the system session is created on first use), no database migration, no check that the database is reachable.
- Cost: the policy setup rebuilds an
Enforcerafter each rule of a service (skyline_apiserver/policy/__init__.py:33): quadratic in the number of rules per service, negligible in absolute terms.
Middleware work on every authenticated request
- Trigger: any path under
/api/v1except the public list (skyline_apiserver/main.py:81). The match is a prefix match, so/api/v1/login/totpand/api/v1/contrib/regionsXYZare public too. - Purpose: turn a cookie or a raw Keystone token into a validated profile.
- Data read:
revoked_tokenby primary key; the Keystone token data. - Flow and effects: see 01 § Authentication and 04 § 3. The purge
DELETE FROM revoked_token WHERE expire < now - 1runs on every request (skyline_apiserver/db/api.py:65), before the revocation check. - Concurrency and idempotency: several workers and replicas purge concurrently; the delete is idempotent. A logged-out session is refused by every worker as soon as its row is committed (shared database). With SQLite on local disk (default
/tmp/skyline.db), replicas do not share revocations. - Error behaviour: any exception → 401 (02).
- Cost: 1
DELETE+ 1SELECT+ 2 Keystone HTTP requests per call; the console issues one Skyline call per list page, plus its direct OpenStack calls which do not go through Skyline.
System token lifecycle
- Trigger: first call needing the system session in a worker (
skyline_apiserver/client/utils.py:60); then keystoneauth re-authenticates when the cached token is about to expire (reauthenticate=True). - Purpose: one privileged token per worker, reused by all users' requests.
- Error behaviour: if the password is wrong, every authenticated request fails with 401 and every public catalog call with 500, until the configuration is fixed and the workers restarted (the configuration is read once).
- Concurrency: the global
SESSIONis created without a lock [inferred: two threads of the same worker may authenticate twice on the first requests, harmless].
Policy file reload
- Trigger: each
Enforcer.authorizecall (skyline_apiserver/policy/base.py:128), i.e. ~1 860 times forGET /policies. - Data read:
statof<policy_file_path>/<service>_<policy_file_suffix>; the file is re-parsed only when its modification time changes (oslo.policy_cache_handler). - Effect: the file's rules replace the embedded rule of the same name; rules absent from the file keep the embedded default.
- Error behaviour: an unreadable file is ignored at DEBUG level (
skyline_apiserver/policy/base.py:131).
nginx configuration generation
- Trigger: container start (
container/start_service.sh:23). - Data read: all Keystone endpoints of
interface_typeand the service list (skyline_apiserver/cmd/generate_nginx.py:124). - Flow: for each endpoint whose service type is in
service_mapping: location/<nginx_prefix>/<region lower>/<service>/, upstream = endpoint URL without the trailing version segment (v2,v2.1…) or%(tenant_id)s/%(project_id)ssegment (skyline_apiserver/cmd/generate_nginx.py:149). - Keystone unreachable at start: the script runs with
set -ex(container/start_service.sh:2), so aConnectFailureof the generator stops the container; under Kubernetes the pod crash-loops with back-off until Keystone answers, then recovers by itself. An init container that waits for Keystone avoids the back-off. Checked in kind by the skyline-console reverse-spec session (reverse-spec-skyline-console/07-rewrite-pack_stable-2026.2.md§ 3.1, § 6.3). - Readiness: nginx is started (
container/start_service.sh:25) before gunicorn binds/var/lib/skyline/skyline.sock(container/start_service.sh:28), soGET /answers while/api/openstack/skyline/…still returns 502 for a few seconds. ProbeGET /api/openstack/skyline/api/v1/config(public) instead. - Error behaviour: two endpoints of the same type in one region →
KeyError, the container start fails (skyline_apiserver/cmd/generate_nginx.py:137); the generator is not re-run when the catalog changes, so new services or regions need a restart.
Offline tools
| Tool | Purpose | Defined at |
|---|---|---|
skyline-policy-manager generate-sample / generate-conf | write sample or full policy YAML files per service | skyline_apiserver/cmd/policy_manager.py:95, skyline_apiserver/cmd/policy_manager.py:129 |
skyline-policy-manager generate-rule <service> | regenerate skyline_apiserver/policy/manager/<service>.py from the service's oslo.policy.policies entry points (skyline_apiserver/types/constants.py:49) | skyline_apiserver/cmd/policy_manager.py:146 |
skyline-policy-manager validate [--diff] | compare embedded rules with the installed services' defaults | skyline_apiserver/cmd/policy_manager.py:238 |
skyline-swagger-generator | dump the OpenAPI document | skyline_apiserver/cmd/generate_swagger.py:37 |
skyline-sample-config-generator | write the sample skyline.yaml | skyline_apiserver/cmd/generate_sample_config.py:36 |
Source: reverse-spec-skyline-apiserver/05-async-processing_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.