REST API — skyline-apiserver — branch stable/2026.2
Branch
stable/2026.2@4af72bfd4577— repository https://opendev.org/openstack/skyline-apiserver — analysed on 2026-10-28
The API is a FastAPI application (skyline_apiserver/main.py:68) whose routers are all mounted under /api/v1 (skyline_apiserver/main.py:188, skyline_apiserver/types/constants.py:47). Behind nginx the public prefix is /api/openstack/skyline/api/v1 (skyline_apiserver/templates/nginx.conf.j2:91, skyline-console:src/client/client/constants.js:60). The route table was extracted from the running application (_rewrite/routes_stable-2026.2.json) and matches the decorators one to one; the OpenAPI document is FastAPI's own (_rewrite/openapi_stable-2026.2.json). There is no api-ref, no microversion and no oslo.policy rule protecting these endpoints: authorisation is a role check in code.
Common behaviour
Authentication (middleware validate_token, skyline_apiserver/main.py:76)
Every request whose path starts with /api/v1 goes through the middleware, except the paths that start with one of: /api/v1/login (so also /api/v1/login/totp), /api/v1/websso, /static, /docs, /api/v1/openapi.json, /favicon.ico, /api/v1/sso, /api/v1/config, /api/v1/contrib/keystone_endpoints, /api/v1/contrib/regions (skyline_apiserver/main.py:81). For the others:
- The credential is the
sessioncookie (name:default.session_name) or, if absent, theX-Auth-Tokenheader carrying a raw Keystone token (skyline_apiserver/main.py:107). Neither →401 {"message": "Unauthorized: Token not found"}. - Expired rows of
revoked_tokenare deleted (skyline_apiserver/main.py:117,skyline_apiserver/db/api.py:65). - With the cookie: the JWT is decoded with
default.secret_key, HS256 (skyline_apiserver/core/security.py:31); itsuuidis looked up inrevoked_token(skyline_apiserver/main.py:124) →401 {"message": "Unauthorized: Token revoked"}if present. - In both cases the Keystone token is validated live with
GET /v3/auth/tokensusing the system session (skyline_apiserver/core/security.py:66), which returns the user, project and roles; this builds theProfileplaced inrequest.state.profile(skyline_apiserver/main.py:157). With the header, the region isopenstack.default_region(skyline_apiserver/main.py:139). - If the JWT has less than
access_token_renewseconds (default 1 800) left, a new JWT validaccess_token_expireseconds (default 3 600) is set in the response cookiessessionandtime_expired(skyline_apiserver/main.py:160,skyline_apiserver/main.py:181). The header path never renews (itsexpis always now + 3 600). - Any exception in steps 2–5 →
401 {"message": "Unauthorized: <text>"}; an expired JWT givesUnauthorized: Token expired - Signature has expired.(skyline_apiserver/main.py:167).
Note the two error shapes: the middleware answers {"message": …}, the endpoints answer {"detail": …} (02).
Roles
Authorisation is decided from the role names of the current token (skyline_apiserver/utils/roles.py:23):
| Check | Rule | Default roles |
|---|---|---|
| system admin | any role in openstack.system_admin_roles | admin, system_admin (skyline_apiserver/config/openstack.py:107) |
| system admin or reader | any role in system_admin_roles + system_reader_roles | + system_reader (skyline_apiserver/config/openstack.py:114) |
The scope of the token (project, domain, system) is not considered. A user with the admin role in any project is "system admin" here.
Shared headers and parameters
X-Openstack-Request-Id(optional): must be empty or match^req-\w{8}(-\w{4}){3}-\w{12}(skyline_apiserver/types/constants.py:26), otherwise 422. It is forwarded asX-OpenStack-Request-ID(global request ID) to Keystone, Nova, Cinder, Glance and Neutron. The console sendsreq-<uuid4>on every call (skyline-console:src/client/client/request.js:53).- The client address (
default.secure_proxy_addr_header, otherwise the socket peer,skyline_apiserver/api/deps.py:28) is forwarded to OpenStack in aForwarded: for=<ip>;by=<client>header by keystoneauth (03). - List endpoints accept
limit(> 0,skyline_apiserver/types/constants.py:36),marker,sort_dirs(asc/desc) and repeatedsort_keysfrom per-resource enums (skyline_apiserver/schemas/extension.py:131); an unknown enum value gives 422.
Summary
| Method | Path | Handler | Authorisation | Success | Errors |
|---|---|---|---|---|---|
| POST | /api/v1/login | skyline_apiserver/api/v1/login.py:395 login | public | 200 Profile + cookies | 401, 422 |
| POST | /api/v1/login/totp | skyline_apiserver/api/v1/login.py:448 login_totp | public | 200 Profile + cookies | 401, 422 |
| GET | /api/v1/config | skyline_apiserver/api/v1/login.py:501 get_config | public | 200 | — |
| GET | /api/v1/sso | skyline_apiserver/api/v1/login.py:518 get_sso | public | 200 | — |
| POST | /api/v1/websso | skyline_apiserver/api/v1/login.py:577 websso | public (form token) | 302 → /base/overview + cookies | 401, 422 |
| GET | /api/v1/profile | skyline_apiserver/api/v1/login.py:638 get_profile | authenticated | 200 Profile | 401 |
| POST | /api/v1/logout | skyline_apiserver/api/v1/login.py:664 logout | authenticated | 200 Message | 401 (middleware) |
| POST | /api/v1/switch_project/{project_id} | skyline_apiserver/api/v1/login.py:699 switch_project | authenticated | 200 Profile + cookies | 401 |
| POST | /api/v1/switch_region/{region} | skyline_apiserver/api/v1/login.py:756 switch_region | authenticated | 200 Profile + cookies | 401 |
| GET | /api/v1/extension/servers | skyline_apiserver/api/v1/extension.py:62 list_servers | authenticated; all_projects needs admin or reader | 200 | 400, 401, 403, 422, 500 |
| GET | /api/v1/extension/recycle_servers | skyline_apiserver/api/v1/extension.py:284 list_recycle_servers | idem | 200 | 400, 401, 403, 422, 500 |
| GET | /api/v1/extension/volumes | skyline_apiserver/api/v1/extension.py:501 list_volumes | idem | 200 | 401, 403, 422, 500 |
| GET | /api/v1/extension/volume_snapshots | skyline_apiserver/api/v1/extension.py:677 list_volume_snapshots | idem | 200 | 401, 403, 422, 500 |
| GET | /api/v1/extension/ports | skyline_apiserver/api/v1/extension.py:867 list_ports | idem | 200 | 401, 403, 422, 500 |
| GET | /api/v1/extension/compute-services | skyline_apiserver/api/v1/extension.py:1055 compute_services | admin or reader | 200 | 401, 403, 500 |
| GET | /api/v1/contrib/keystone_endpoints | skyline_apiserver/api/v1/contrib.py:44 list_keystone_endpoints | public | 200 | 500 |
| GET | /api/v1/contrib/domains | skyline_apiserver/api/v1/contrib.py:75 list_domains | authenticated | 200 | 401, 500 |
| GET | /api/v1/contrib/regions | skyline_apiserver/api/v1/contrib.py:97 list_regions | public | 200 | 500 |
| GET | /api/v1/policies | skyline_apiserver/api/v1/policy.py:96 list_policies | authenticated | 200 | 401, 500 |
| POST | /api/v1/policies/check | skyline_apiserver/api/v1/policy.py:157 check_policies | authenticated | 200 | 401, 403, 422 |
| GET | /api/v1/query | skyline_apiserver/api/v1/prometheus.py:112 prometheus_query | authenticated | 200 | 401, Prometheus status, 500 |
| GET | /api/v1/query_range | skyline_apiserver/api/v1/prometheus.py:157 prometheus_query_range | authenticated | 200 | idem |
| GET | /api/v1/settings | skyline_apiserver/api/v1/setting.py:112 list_settings | authenticated | 200 | 401 |
| GET | /api/v1/setting/{key} | skyline_apiserver/api/v1/setting.py:52 show_setting | authenticated | 200 | 401, 404 |
| PUT | /api/v1/setting | skyline_apiserver/api/v1/setting.py:84 update_setting | system admin | 200 | 401, 403, 404, 422 |
| DELETE | /api/v1/setting/{key} | skyline_apiserver/api/v1/setting.py:144 reset_setting | system admin | 200 | 401, 403, 404 |
All success statuses are 200 (no 201/204), including the creations, updates and deletions.
Session endpoints
The Profile object
Returned by login, TOTP login, profile, switch project and switch region (skyline_apiserver/schemas/login.py:92):
| Field | Content | Source |
|---|---|---|
keystone_token | the raw project-scoped Keystone token | skyline_apiserver/schemas/login.py:69 |
region | current region | — |
exp, uuid | expiry (epoch s) and ID of the Skyline session (JWT) | skyline_apiserver/core/security.py:81 |
project, user, roles | from Keystone token data | skyline_apiserver/core/security.py:76 |
keystone_token_exp | Keystone token expires_at | skyline_apiserver/core/security.py:79 |
base_domains | openstack.base_domains (default heat_user_domain) | skyline_apiserver/config/openstack.py:98 |
endpoints | map service name → nginx path /<nginx_prefix>/<region lower>/<service> built from the catalog and service_mapping, plus Neutron extension aliases mapped by extension_mapping | skyline_apiserver/client/openstack/system.py:55 |
projects | {id: {name, enabled, domain_id, domain_name, description}} of the user's projects | skyline_apiserver/api/v1/login.py:363 |
default_project_id | user's default project | skyline_apiserver/api/v1/login.py:374 |
regions | sorted region IDs in the user's catalog | skyline_apiserver/api/v1/login.py:314 |
version | installed skyline-apiserver version | skyline_apiserver/version.py:22 |
Cookies set on success: session = HS256 JWT {keystone_token, region, exp, uuid} (skyline_apiserver/schemas/login.py:84) and time_expired = exp (skyline_apiserver/api/v1/login.py:171). Both are session cookies (no Max-Age), path /, SameSite=Lax, not HttpOnly, not Secure (Starlette defaults).
POST /api/v1/login
Functional description
- Purpose: open a dashboard session with a user name and password, landing in the user's default project.
- Actor: anyone; the Keystone user must have a role on at least one enabled project.
- Preconditions: Keystone reachable with the system account (
openstack.system_user_*), which is used to find the identity endpoint ofdefault_regionin the catalog (skyline_apiserver/api/v1/login.py:256). - Business rules:
- The region is always
openstack.default_region; theregionfield of the body is ignored (skyline_apiserver/api/v1/login.py:405). - Domain: body
domain, elseopenstack.user_default_domain(skyline_apiserver/api/v1/login.py:406). - Only enabled projects count (
skyline_apiserver/api/v1/login.py:300); none → 401You are not authorized for any projects or domains.(skyline_apiserver/api/v1/login.py:303). - Landing project: the user's
default_project_idif it is among the enabled ones, else the first project returned byGET /v3/auth/projects(skyline_apiserver/api/v1/login.py:155). - If Keystone answers that a second factor is required (receipt + a rule containing both
passwordandtotp), the answer is401 {"detail": {"totp_required": true, "receipt": "<receipt>"}}(skyline_apiserver/api/v1/login.py:60,skyline_apiserver/api/v1/login.py:73). Any other multi-factor rule gives401 "An error occurred authenticating. Please try again later.".
- The region is always
- Flow (all synchronous; 16 outbound requests in the capture, see 03):
- Password authentication (unscoped token) →
GET /v3/auth/projectswith it. - Validate the token and read the user (
GET /v3/users/{id}) with the system session to getdefault_project_id(skyline_apiserver/api/v1/login.py:230). - Re-scope to the landing project (
POST /v3/auth/tokens, token method) (skyline_apiserver/client/openstack/system.py:30), validate it, build the profile. - Enrich: regions, endpoints (with
GET /v2.0/extensionson Neutron), projects of the user (GET /v3/users/{id}/projects), default project (skyline_apiserver/api/v1/login.py:330). - Set the cookies, return the profile.
- Password authentication (unscoped token) →
- Outcome: 200 with the profile; nothing is written in the database.
- Limitations: the Keystone error text is returned as is in
detail(skyline_apiserver/api/v1/login.py:427); the console parses it (07 § 4).
Technical sheet
- Request: JSON
Credential(skyline_apiserver/schemas/login.py:26):username(str, required),password(str, required),domain(str, optional),region(str, optional, ignored). HeaderX-Openstack-Request-Idoptional. - Response: 200
Profile; cookiessession,time_expired. - Errors: 401 with
detail= Keystone's message or the TOTP challenge; 422 on a missing field (FastAPI validation).
POST /api/v1/login/totp
Functional description
- Purpose: second step of a password + TOTP login.
- Actor: a user who received
totp_requiredfrom/login. - Business rules:
passcodemust be 6 digits (skyline_apiserver/schemas/login.py:134); region = bodyregionordefault_region(unlike/login, the body region is used,skyline_apiserver/api/v1/login.py:458); the receipt is sent back to Keystone with the TOTP method (skyline_apiserver/api/v1/login.py:215). - Flow: TOTP + receipt authentication → unscoped token → same steps 1–5 as
/login, through_finish_login(skyline_apiserver/api/v1/login.py:176). - Errors:
401 "receipt_expired"when Keystone says the receipt expired or asks again for methods without a receipt header;401 "invalid_totp"on a Keystone 401;401 "authentication_failed"on anything else (skyline_apiserver/api/v1/login.py:111). These three strings are matched by the console (07 § 4).
Technical sheet
- Request: JSON
TOTPCredential(skyline_apiserver/schemas/login.py:130):username,passcode(^\d{6}$),receiptrequired;domain,regionoptional. - Response: as
/login.
GET /api/v1/config
- Purpose: tell the login page which domain and region are the defaults.
- Response:
{"default_domain": <user_default_domain>, "default_region": <default_region>}(skyline_apiserver/api/v1/login.py:501). No outbound call.
GET /api/v1/sso
- Purpose: tell the login page whether federated login is offered and where to send the browser.
- Business rules (
skyline_apiserver/api/v1/login.py:518): ifopenstack.sso_enabledis false →{"enable_sso": false, "protocols": []}. Otherwise theorigin(return URL) is<http|https per default.ssl_enabled>://<Host of the request>[:port]/<nginx_prefix>/skyline/api/v1/websso; withsso_identity_providerseach entry gives{protocol, label, url: <keystone_url>/auth/OS-FEDERATION/identity_providers/<name>/protocols/<protocol>/websso?origin=…}, else each ofsso_protocolsgives<keystone_url>/auth/OS-FEDERATION/websso/<protocol>?origin=…. - Limitations:
originis not URL-encoded and is built from theHostheader of the request.
POST /api/v1/websso
Functional description
- Purpose: landing point of Keystone WebSSO: the browser posts the federated unscoped token and receives a dashboard session.
- Actor: the browser, redirected by Keystone after the IdP.
- Business rules: region
openstack.sso_region(skyline_apiserver/api/v1/login.py:589); landing project as for/login. - Flow: same as
/loginfrom step 1 with the posted token; then302 Location: /base/overviewwith the cookies (skyline_apiserver/api/v1/login.py:621). - Errors: any failure →
401 {"detail": "<text>"}(skyline_apiserver/api/v1/login.py:615); missingtokenform field → 422.
Technical sheet
- Request:
application/x-www-form-urlencoded, fieldtoken(required). - Response: 302 to the relative URL
/base/overview(hard-coded, not undernginx_prefix).
GET /api/v1/profile
- Purpose: return the current session profile, refreshed (projects, regions, endpoints).
- Flow: profile from the middleware (validated token) →
_patch_profile(skyline_apiserver/api/v1/login.py:647) → 200. 8 outbound requests (03). - Errors: 401 from the middleware; 401
{"detail": …}if enrichment fails (skyline_apiserver/api/v1/login.py:376). - Note: works with the
X-Auth-Tokenheader too; the console calls it after login with the cookie and the header (skyline-console:src/stores/root.js:210).
POST /api/v1/logout
Functional description
- Purpose: end the session.
- Flow (
skyline_apiserver/api/v1/login.py:664): if a profile was built by the middleware, its JWT payload is re-parsed, the profile regenerated, a user session built, the Keystone token revoked (DELETE /v3/auth/tokens,skyline_apiserver/client/openstack/keystone.py:57) and the sessionuuidinserted intorevoked_tokenwith itsexp(skyline_apiserver/db/api.py:56). Every error there is swallowed (logged at debug,skyline_apiserver/api/v1/login.py:682). Thesessioncookie is deleted. - Outcome: always
200 {"message": "Logout OK", "code": 200, "title": "OK"}once past the middleware. - Limitations:
- With only the
X-Auth-Tokenheader, the profile has a fresh randomuuid, so the revocation row is useless, but the Keystone token is revoked. - The
time_expiredcookie is left in place. - Without any credential, the middleware answers 401 before the handler (the console ignores it).
- With only the
POST /api/v1/switch_project/{project_id}
Functional description
- Purpose: move the session to another project of the user.
- Business rules: the check "project in
profile.projects" (skyline_apiserver/api/v1/login.py:712) never fires, because the middleware's profile has noprojects; Keystone decides (re-scoping fails with 401 if the user has no role there). - Flow: re-scope the current token to
project_id(POST /v3/auth/tokens) → validate → enrich → new cookies (skyline_apiserver/api/v1/login.py:717). - Outcome: 200 with the new profile; the old Keystone token is not revoked and the old JWT is not added to
revoked_token. - Technical sheet: no body is read (the console sends
{project_id, project_domain_id}and?project_domain_id=, both ignored,skyline-console:src/client/skyline/index.js:103).
POST /api/v1/switch_region/{region}
Functional description
- Purpose: move the session to another region of the same Keystone.
- Business rules: the region must be in
profile.regionsor, since the middleware's profile has none, in the regions of the user's catalog (skyline_apiserver/api/v1/login.py:768); else401 "Region not accessible". If reading the catalog fails, the list is empty and every region is refused (skyline_apiserver/api/v1/login.py:326). - Flow: the same Keystone token is validated through the identity endpoint of the new region (no re-scoping,
skyline_apiserver/api/v1/login.py:778) → enrich → new cookies carrying the new region.
Aggregated list endpoints (/extension/*)
These endpoints exist to give the console, in one call, lists that need several services: server lists with image names, volume lists with server names, etc. Common rules:
all_projects=truerequires system admin or reader (skyline_apiserver/utils/roles.py:54) → 403Not allowed to get <resources> for all projects.; when false,project_id/project_namefilters are ignored (reset toNone).- Two Keystone sessions are used: the user session (current token re-scoped to the current project,
skyline_apiserver/client/utils.py:36) and the system session (theskylineservice user,skyline_apiserver/client/utils.py:58). Which one is used for each call is in 03. - Lookups by ID lists are split in batches of 100 IDs (
skyline_apiserver/types/constants.py:38) to keep URLs short. - Each item carries
origin_data, the untouched resource as returned by the service (skyline_apiserver/api/wrapper/openstack.py:110), next to the flattened Skyline fields (skyline_apiserver/api/wrapper/skyline.py:56). - Errors of the service calls become 400/401/403/500 with the client library's message (02).
GET /api/v1/extension/servers
Functional description
- Purpose: list instances with their image name and OS distribution, and their project name for administrators.
- Actor: project member (own project) or system admin/reader (
all_projects). - Business rules:
project_name(withall_projects) is resolved to an ID first with the system session; no match, or a mismatch withproject_id, returns an empty list (skyline_apiserver/api/v1/extension.py:143).- Filters passed to Nova:
name(Nova regex match),status,host,flavor,project_id,all_tenants,uuid,ip(skyline_apiserver/api/v1/extension.py:162). - Image of a volume-booted server: taken from the root volume's
volume_image_metadata(skyline_apiserver/api/v1/extension.py:229); otherwise from Glance by image ID; otherwisenull. hostis blanked unlessall_projects(skyline_apiserver/api/v1/extension.py:242), even for an administrator;project_nameonly filled withall_projects(skyline_apiserver/api/v1/extension.py:254).
- Flow: 1.
GET /servers/detail(user session, microversion 2.79); 2. GlanceGET /v2/images?id=in:<ids>by batches (system session); 3. CinderGET /volumes/detail?id=[…]&all_tenants=Trueby batches (system session); 4. withall_projects, KeystoneGET /v3/projects(user session) for names. - Outcome:
{"servers": [...]}(skyline_apiserver/schemas/extension.py:275); the Nova page is returned as is (one page,limit/markerpassed through). - Limitations: server and image IDs must be UUID v4 (
skyline_apiserver/schemas/extension.py:206), else 500.
Technical sheet
- Query:
limit(int > 0),marker,sort_dirs(asc|desc),sort_keys(repeatable:uuid,display_name,vm_state,locked,created_at,host,project_id,skyline_apiserver/schemas/extension.py:139),all_projects(bool),project_id,project_name,name,status(enumskyline_apiserver/schemas/extension.py:28),host,flavor_id,uuid,ip. - Response item (
skyline_apiserver/schemas/extension.py:205):id,name,project_id,project_name,host,hostname,image,image_name,image_os_distro,fixed_addresses,floating_addresses,flavor,flavor_info,status,locked,created_at,updated_at,task_state,vm_state,power_state,volumes_attached,root_device_name,metadata,origin_data.
GET /api/v1/extension/recycle_servers
Functional description
- Purpose: list soft-deleted instances (recycle bin) with the time at which Nova will reclaim them.
- Business rules: Nova only returns deleted servers to administrators, so the call always uses the system session with
status=soft_deleted,deleted=True,all_tenants=True, andproject_id=<current project>unlessall_projects(skyline_apiserver/api/v1/extension.py:374).deleted_at=updated_at;reclaim_timestamp=updated_at+openstack.reclaim_instance_interval(default 7 days, must match Nova's own setting) (skyline_apiserver/api/v1/extension.py:456). - Outcome:
{"recycle_servers": [...]}. - Limitations: 500 whenever the list is not empty (see 00 § Points of attention 1).
- Query: as servers, without
status,host,flavor_id;sort_keysamonguuid,display_name,updated_at,project_id(skyline_apiserver/schemas/extension.py:152).
GET /api/v1/extension/volumes
Functional description
- Purpose: list volumes with a total count and the names of the servers they are attached to, including soft-deleted servers.
- Business rules:
- With
all_projects, a reader without admin role gets the system session (Cinder would ignoreall_tenantsfor him) (skyline_apiserver/api/v1/extension.py:563). - With
uuid(repeatable), the system session is used and, unlessall_projects, restricted withproject_id=<current project>(skyline_apiserver/api/v1/extension.py:586). - Sort:
sort=<key>[:<dir>],…(skyline_apiserver/api/v1/extension.py:568);multiattachandbootableare sent as"True"/"False"strings. - For each attached server, two Nova calls (normal, then soft-deleted) are made per server (
skyline_apiserver/api/v1/extension.py:624).
- With
- Outcome:
{"count": <total from Cinder>, "volumes": [...]}(skyline_apiserver/schemas/extension.py:399). - Query:
limit,marker,sort_dirs,sort_keys(id,name,size,status,bootable,created_at,skyline_apiserver/schemas/extension.py:162),all_projects,project_id,name,multiattach,status(skyline_apiserver/schemas/extension.py:54),bootable,uuid(list).
GET /api/v1/extension/volume_snapshots
Functional description
- Purpose: list snapshots with their volume name, host (administrators), project name and the volumes created from each snapshot (
child_volumes). - Business rules: with
uuidand withoutall_projects, the snapshot is first read with the user session and an empty list is returned if it is not found or belongs to another project (skyline_apiserver/api/v1/extension.py:744); then the list is made with the system session andall_tenants=True. Volumes and child volumes are looked up by batches with the system session (skyline_apiserver/api/v1/extension.py:799). - Outcome:
{"count": …, "volume_snapshots": [...]};hostonly withall_projects(skyline_apiserver/api/v1/extension.py:850). - Query:
limit,marker,sort_dirs,sort_keys,all_projects,project_id,name,status,volume_id,uuid.
GET /api/v1/extension/ports
Functional description
- Purpose: list Neutron ports with the name of their network and of the server using them.
- Business rules: without
all_projects,project_id=<current project>is forced (skyline_apiserver/api/v1/extension.py:936);network_nameis resolved to network IDs first (none → empty list);network_idmust then be one of them (skyline_apiserver/api/v1/extension.py:948); sort directions default toascper key. - Flow: Neutron
GET /v2.0/ports(one page only:retrieve_all=False,skyline_apiserver/client/openstack/neutron.py:67) → shared networks + networks by ID batches → one Nova call per server (device_owner == compute:nova) (skyline_apiserver/api/v1/extension.py:1022). All with the user session. - Outcome:
{"ports": [...]}(no count). - Query:
limit,marker,sort_dirs,sort_keys(skyline_apiserver/schemas/extension.py:184),all_projects,project_id,name,status,network_name,network_id,device_id,device_owner(list, enumskyline_apiserver/schemas/extension.py:106),uuid(list).
GET /api/v1/extension/compute-services
- Purpose: list Nova compute services (hypervisor overview) for administrators and readers.
- Rules: system admin or reader, else 403
Not allowed to get compute services.(skyline_apiserver/api/v1/extension.py:1069); always the system session; filtersbinary,host. - Outcome:
{"services": [...]}withid,binary,disabled_reason,host,state,status,updated_at,forced_down,zone(skyline_apiserver/api/wrapper/skyline.py:179),nullfields omitted.
Catalog helpers (/contrib/*)
| Endpoint | Purpose | Behaviour |
|---|---|---|
GET /contrib/keystone_endpoints (public) | let the login page show one entry per region | for each region of the system catalog, {"region_name", "url": <nginx path of keystone>} (skyline_apiserver/api/v1/contrib.py:44); one Neutron GET /v2.0/extensions per region; any error → 500 |
GET /contrib/domains (authenticated) | list enabled domain names | GET /v3/domains?enabled=True with the system session (skyline_apiserver/client/openstack/system.py:109) |
GET /contrib/regions (public) | list region IDs | from the system catalog, unordered (skyline_apiserver/client/openstack/system.py:123) |
Policy endpoints
GET /api/v1/policies
Functional description
- Purpose: tell the console, for every known rule of 16 services, whether the current user would be allowed, so that it hides what would fail.
- Business rules:
- Credentials: built from the user's re-scoped token (
skyline_apiserver/policy/base.py:32): roles, project, domains,is_admin(= any system admin role),system_scope. system_scopeisallonly if the user's token can be re-scoped to the system (skyline_apiserver/api/v1/policy.py:104): Skyline asks Keystone for a system-scoped token; a 401 or 500 there is ignored.- Target: the user's own IDs under every attribute name used by the services' rules (
project_id,tenant,target.project.id,owner…), plusenforce_new_defaults(skyline_apiserver/api/v1/policy.py:40). - Rule source: for each service,
<policy_file_path>/<service>_<policy_file_suffix>(default/etc/skyline/policy/nova_policy.yaml) overrides the embedded default, reloaded when the file changes (skyline_apiserver/policy/base.py:105); the embedded rules usebasic_check_strwhen present (skyline_apiserver/policy/base.py:124). - A rule that cannot be evaluated is
false; an unknown rule isfalse(skyline_apiserver/policy/base.py:135).
- Credentials: built from the user's re-scoped token (
- Outcome:
{"policies": [{"rule": "<service>:<rule>", "allowed": bool}, …]}, ~1 860 entries. - Limitations: evaluation is only an approximation of each service's own policy: Skyline's copy may differ from the deployed service's
policy.yamlunless the operator copies it intopolicy_file_path.
POST /api/v1/policies/check
- Purpose: evaluate a chosen list of rules, optionally against a target object.
- Request (
skyline_apiserver/schemas/policy.py:31):{"rules": ["nova:os_compute_api:servers:create", …], "target": {"project_id": "…"}};targetentries override the default target. - Errors: an unknown service prefix or a rule without
:→403 {"detail": "<error text>"}, e.g.'foo'(skyline_apiserver/api/v1/policy.py:193).
Prometheus proxy
GET /api/v1/query and GET /api/v1/query_range
- Purpose: let the console draw monitoring charts without exposing Prometheus.
- Flow: the query parameters present among
query,time,timeout(instant) orquery,start,end,step,timeout(range) are forwarded to<prometheus_endpoint>/api/v1/query[_range]with optional basic auth (skyline_apiserver/api/v1/prometheus.py:132). A non-200 Prometheus answer is returned with the same status anddetail= Prometheus body text (skyline_apiserver/api/v1/prometheus.py:138). - Business rules: for users without a system admin or reader role, series without a
project_idlabel equal to the current project are removed (skyline_apiserver/api/v1/prometheus.py:50). - Outcome: Prometheus's own shape:
status,data.resultType,data.result[].metric,data.result[].value(for ranges, Prometheusvaluesare renamedvalue,skyline_apiserver/api/v1/prometheus.py:79);warnings,errorType,errorpassed through;nullfields omitted.
Settings
Settings are UI preferences (flavor families, GPU and USB models) whose defaults come from the setting section of skyline.yaml (skyline_apiserver/config/setting.py:23) and can be overridden at run time in the settings table. The allowed keys are setting.base_settings (default flavor_families, gpu_models, usb_models).
| Endpoint | Behaviour |
|---|---|
GET /settings | every key of base_settings with the database value if any, else the configured value (skyline_apiserver/api/v1/setting.py:112) |
GET /setting/{key} | one key; unknown key → 404 "Not a valid setting key." (skyline_apiserver/api/v1/setting.py:32) |
PUT /setting | system admin only (403 "Not allowed to update settings."); body {"key", "value"} (skyline_apiserver/schemas/setting.py:30); upsert into settings (skyline_apiserver/db/api.py:93); returns the stored value |
DELETE /setting/{key} | system admin only (403 "Not allowed to reset settings."); deletes the override, returns the configured default (skyline_apiserver/api/v1/setting.py:144) |
Response object: {"key", "value", "hidden", "restart_service"} (skyline_apiserver/schemas/setting.py:25); hidden and restart_service are always false on this branch (empty sets, skyline_apiserver/types/constants.py:40).
Lifecycle: Skyline session
The only stateful object owned by skyline-apiserver is the session (JWT); its states are observed through the cookie and the revoked_token table.
stateDiagram-v2 [*] --> Active: POST /login, /login/totp, /websso (cookie session + time_expired) [*] --> TotpPending: POST /login → 401 totp_required (receipt held by the browser) TotpPending --> Active: POST /login/totp TotpPending --> [*]: receipt_expired / invalid_totp Active --> Active: any call with < access_token_renew left (middleware renews the cookie) Active --> Active: POST /switch_project, /switch_region (new JWT, old one not revoked) Active --> Revoked: POST /logout (uuid inserted in revoked_token, Keystone token revoked) Active --> Expired: exp reached (JWT signature check fails → 401) Active --> Invalid: Keystone token expired or revoked elsewhere (validation fails → 401) Revoked --> [*]: row purged by the next request once exp < now Expired --> [*] Invalid --> [*]
Source: reverse-spec-skyline-apiserver/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.