Interactions — skyline-console — branch stable/2026.2
Branch
stable/2026.2@806d200392ee— repository https://opendev.org/openstack/skyline-console — analysed on 2026-10-29 Other repositories cited:skyline-apiserverstable/2026.2@4af72bfd4577(prefixskyline-apiserver:),microversion-parse@77ea32a(prefixmicroversion-parse:),cinderstable/2026.2@c117f9cf0(prefixcinder:),novastable/2026.2@8df1922926(prefixnova:).
The console has no database, no message bus and no cache server. Its only interactions are HTTP requests from the browser, all to the same origin as the page, plus two browser-side stores (cookies and localStorage) and the console windows it opens. The full per-service call list, generated from the code, is [_rewrite/outbound-calls_stable-2026.2.md](_rewrite/outbound-calls_stable-2026.2.md) (577 calls reached across 18 services; JSON in _rewrite/outbound-calls_stable-2026.2.json).
1. The request layer
One axios instance per request, created by HttpRequest.buildRequest (src/client/client/request.js:206). axios 0.21.4 (yarn.lock:3172).
| Aspect | Behaviour | Where |
|---|---|---|
| Base URL | /: every path is absolute on the page's origin | src/client/client/request.js:159 |
| Default headers | Content-Type: application/json;charset=utf-8, cache-control: no-cache, pragma: no-cache | src/client/client/request.js:157 |
| Request ID | X-Openstack-Request-Id: req-<uuid4> on every request | src/client/client/request.js:51 |
| Token | X-Auth-Token: <keystone_token from localStorage> when the time_expired cookie is in the future | src/client/client/request.js:56 |
| Microversion | from the URL and the body, see § 2 | src/client/client/request.js:69 |
| Per-call options | extra headers, isFormData (drops Content-Type), any axios option (e.g. onUploadProgress, cancelToken) | src/client/client/request.js:80 |
| Query string | GET/HEAD only: null, undefined and '' dropped, strings trimmed, arrays repeated (id=a&id=b) | src/client/client/request.js:173, src/client/client/request.js:186, src/client/client/request.js:213 |
| Cookies | sent automatically by the browser (same origin): session (JWT for skyline-apiserver) and time_expired | recorded, _rewrite/smoke-exchanges_stable-2026.2.json |
| Errors | see 02 § 1 | src/client/client/request.js:114 |
URL construction
| Target | URL prefix | Where |
|---|---|---|
| skyline-apiserver | /api/openstack/skyline/api/v1/ (fixed) | src/client/client/constants.js:60 |
OpenStack service <key> | profile.endpoints[<key>] + version suffix (keystone v3, nova v2.1, cinder v3, glance v2, neutron v2.0, ironic v1, heat v1, octavia v2, swift v1, trove v1.0, manilav2 v2, barbican v1, zun v1, magnum v1, designate v2, masakari v1; placement none) | src/client/client/constants.js:22, src/client/client/constants.js:46 |
| fallback when the profile has no entry | /api/openstack/ironic, /api/openstack/octavia | src/client/client/constants.js:41 |
| project in path | Cinder, Manila, Heat, Trove: /{project_id} after the version; Swift: /AUTH_{project_id} | src/client/client/base.js:22, src/client/swift/index.js:28 |
profile.endpoints values are nginx locations generated by skyline-apiserver: /<nginx_prefix>/<region lower-case>/<service name> (skyline-apiserver:skyline_apiserver/client/openstack/system.py:76), for example /api/openstack/regionone/nova. The nginx location strips the version and %(project_id)s parts of the catalog URL, which the console adds back (skyline-apiserver:skyline_apiserver/cmd/generate_nginx.py:149).
2. Microversion headers
Three tables in src/client/client/constants.js decide the headers; getOpenstackApiVersionForRequest applies them in this order (src/client/client/constants.js:214):
- Per service, by URL substring (
src/client/client/constants.js:90): the first key ofnova,placement,cinder,ironic,manila,zuncontained anywhere in the URL selects the header. | Key in URL | Header | Value | |---|---|---| |nova|Openstack-Api-Version|compute 2.79| |placement|Openstack-Api-Version|placement 1.28| |cinder|Openstack-Api-Version|volume 3.59| |ironic|X-Openstack-Ironic-Api-Version|1.58| |manila|X-OpenStack-Manila-API-Version|2.55| |zun|OpenStack-API-Version|container 1.40| - Per action (body with one key, any method): Nova
os-migrateLive2.30,rebuild2.93,rescue2.93,lock2.42,remote_console2.53; Cinderos-extend3.60. HeaderOpenStack-API-Version: <compute|volume> <version>(src/client/client/constants.js:117,src/client/client/constants.js:158). - Per URL, GET only: Nova
/os-server-groups[/<id>]2.63; Cinder/messages[/<id>]3.29 (src/client/client/constants.js:135).
The headers of steps 1 and 2/3 differ only by case (Openstack-Api-Version and OpenStack-API-Version), so the browser merges them into one header with two comma-separated values, base first. Recorded on the wire (Chromium, _rewrite/smoke-exchanges_stable-2026.2.json):
POST /api/openstack/regionone/nova/v2.1/servers/srv1/action
Openstack-Api-Version: compute 2.79, compute 2.42
{"lock": {"locked_reason": "smoke"}}
POST /api/openstack/regionone/cinder/v3/p1/volumes/vol1/action
Openstack-Api-Version: volume 3.59, volume 3.60
{"os-extend": {"new_size": 2}}
GET /api/openstack/regionone/nova/v2.1/os-server-groups
Openstack-Api-Version: compute 2.79, compute 2.63
What the services do with the merged value:
- Nova uses microversion-parse, which reads the values from the last and keeps the first one for its service type: the action version wins (2.42, 2.63, 2.93…) (
microversion-parse:microversion_parse/__init__.py:116,nova:nova/api/openstack/wsgi.py:151). - Cinder splits on
,and keeps the first value that containsvolume: it always gets 3.59. The 3.60 intended foros-extendand the 3.29 intended for/messagesnever take effect (cinder:cinder/api/openstack/wsgi.py:292).
Pitfalls of the substring rule: the region name, a service name or any path segment containing nova, cinder, placement, ironic, manila or zun attracts that service's header. With the default service_mapping (skyline-apiserver:skyline_apiserver/config/openstack.py:121) the locations contain the right names (…/nova/…, …/cinder/…, …/manilav2/…). With another mapping (for example compute: compute), no microversion header is sent and Nova answers at 2.1. Then the console breaks on fields it needs (e.g. hypervisor_hostname on create, server tags).
3. Calls to skyline-apiserver
All under /api/openstack/skyline/api/v1/, declared in src/client/skyline/index.js:18. Endpoint definitions are in the apiserver reverse-spec ([../reverse-spec-skyline-apiserver/01-rest-api_stable-2026.2.md](../reverse-spec-skyline-apiserver/01-rest-api_stable-2026.2.md)).
| Call | When | Fields read | Console side |
|---|---|---|---|
POST login, POST login/totp | login form | the whole profile; cookies | src/stores/root.js:123, src/stores/root.js:140 |
GET profile, GET policies | every page load, after login and after each switch | endpoints, keystone_token, keystone_token_exp, project, user, roles, region, regions, base_domains, version; policies[].{rule,allowed} | src/stores/root.js:207, src/stores/root.js:179 |
POST logout | user menu | — | src/stores/root.js:236 |
POST switch_project/{id} (body and project_domain_id query), POST switch_region/{region} | header menus | the new profile | src/client/skyline/index.js:103, src/client/skyline/index.js:118 |
GET config, GET sso, GET contrib/regions, GET contrib/domains | login page | default domain, SSO protocols, regions, domains | src/stores/skyline/skyline.js:37 |
GET contrib/keystone_endpoints | password-expired flow (before any session) | first item's url used as Keystone prefix | src/stores/root.js:280 |
GET extension/servers, recycle_servers, volumes, volume_snapshots, ports, compute-services | lists of instances, recycle bin, volumes, snapshots, ports, compute hosts | the joined resources, count for paging | src/stores/nova/instance.js:78, src/stores/cinder/volume.js:58 |
GET query, GET query_range | monitoring | Prometheus result | src/stores/prometheus/monitor-base.js:98 |
GET settings, PUT setting, DELETE setting/{key} | settings page | settings[] (items with hidden are filtered out) | src/stores/skyline/setting.js:19 |
POST policies/check is declared (src/client/skyline/index.js:74) but never called; POST websso is reached by Keystone's form post, not by the console.
4. Calls to the OpenStack APIs (through the nginx proxy)
Calls reached per service (from the generated catalogue; "declared" counts every operation the client table generates):
| Service (profile key) | Calls reached | Declared | Microversion | Client |
|---|---|---|---|---|
neutron (neutron, plus neutron_qos, neutron_vpn, neutron_firewall, neutron_port_forwarding for menus) | 119 | 270 | — | src/client/neutron/index.js:18 |
manila (manilav2) | 60 | 166 | X-OpenStack-Manila-API-Version: 2.55 | src/client/manila/index.js:18 |
| keystone | 58 | 225 | — | src/client/keystone/index.js:18 |
| cinder | 52 | 126 | volume 3.59 (+3.60/3.29, ignored by Cinder) | src/client/cinder/index.js:18 |
| nova | 50 | 143 | compute 2.79 (+2.30/2.42/2.53/2.63/2.93) | src/client/nova/index.js:18 |
| trove | 31 | 90 | — | src/client/trove/index.js:18 |
| zun | 30 | 80 | container 1.40 | src/client/zun/index.js:18 |
| ironic | 27 | 67 | 1.58 | src/client/ironic/index.js:18 |
| octavia | 27 | 63 | — | src/client/octavia/index.js:18 |
| glance | 19 | 47 | — | src/client/glance/index.js:18 |
| magnum | 16 | 38 | — | src/client/magnum/index.js:16 |
| masakari | 14 | 26 | — | src/client/masakari/index.js:16 |
| designate | 13 | 36 | — | src/client/designate/index.js:16 |
| swift | 12 | 23 | — | src/client/swift/index.js:18 |
| heat | 11 | 25 | — | src/client/heat/index.js:18 |
| barbican | 10 | 35 | — | src/client/barbican/index.js:18 |
| placement | 4 | 34 | placement 1.28 | src/client/placement/index.js:18 |
Patterns worth knowing for any implementation of these APIs:
- Lists:
GET <collection>or<collection>/detailwith the page's filters. Server-side paging (isFilterByBackend) sendslimit,marker,sort_key/sort_dir(Neutron) orsort_keys/sort_dirs(skyline extensions). Other lists fetch everything;fetchListByLimitstores page withlimit=100and the last item'sidasmarkeruntil a short page (src/stores/base.js:287). - Admin lists add
all_projects=true(src/stores/base.js:360); Cinder backups and Manila useall_tenantsinstead (src/stores/cinder/backup.js:60,src/stores/manila/share-network.js:41). - Project names: unknown
project_ids in admin lists are fetched one by one withGET /v3/projects/{id}(src/stores/base.js:255). - Quotas (overview, recorded):
GET nova os-quota-sets/{p}/detail,GET cinder os-quota-sets/{p}?usage=True,GET neutron quotas/{p}/details, plusos-keypairs?limit=100and Cindertypes.
Sample frames (recorded against a stand-in, _rewrite/smoke-exchanges_stable-2026.2.json)
POST /api/openstack/skyline/api/v1/login
Content-Type: application/json;charset=UTF-8
X-Openstack-Request-Id: req-fa548f3a-470c-49c3-b50a-b575d5379e84
{"password": "***", "username": "smoke", "domain": "Default"}
GET /api/openstack/regionone/keystone/v3/system/users/u1/roles
X-Auth-Token: <keystone token>
X-Openstack-Request-Id: req-1f2d5c8b-2091-4c04-9cb3-d2fe82a0124c
Cookie: session=<jwt>; time_expired=1793232668
GET /api/openstack/skyline/api/v1/extension/servers?limit=10&sort_keys=created_at&sort_dirs=desc
GET /api/openstack/regionone/neutron/v2.0/networks?project_id=p1&limit=10&sort_key=status&sort_dir=desc
GET /api/openstack/regionone/cinder/v3/p1/os-quota-sets/p1?usage=True (Openstack-Api-Version: volume 3.59)
5. Outside the proxy
| Interaction | Target | Notes | Where |
|---|---|---|---|
| noVNC / serial console | URL returned by Nova remote-consoles, opened with window.open | must be reachable from the user's browser; &language=<locale> appended | src/pages/compute/containers/Instance/actions/Console.jsx:69 |
| Zun container console | WebSocket to the URL returned by Zun, subprotocols binary, base64 | the generated nginx has no WebSocket upgrade settings, so this URL must not go through it | src/pages/container-service/containers/Containers/Detail/Console.jsx:37 |
| Swift public URL (copy) | profile.endpoints.swift origin endpoint | shown to the user, not called | src/client/client/constants.js:83 |
6. Browser-side state
| Store | Key | Content | Lifetime | Where |
|---|---|---|---|---|
localStorage | keystone_token | {"expires": <ms>, "value": <token>} | 10 days locally; cleared on logout, project or region switch (except lang) | src/stores/root.js:117, src/utils/local-storage.js:31, src/stores/root.js:325 |
localStorage | lang | UI language | kept | src/stores/root.js:325 |
| cookie (set by skyline-apiserver) | session | JWT read by skyline-apiserver | session | skyline-apiserver:skyline_apiserver/api/v1/login.py:172 |
| cookie (set by skyline-apiserver) | time_expired | epoch seconds; read by JavaScript to decide whether to send the token | session | src/client/client/request.js:58, src/utils/cookie.js:16 |
| global | window.client | every client object (debugging aid) | page | src/client/index.js:55 |
Source: reverse-spec-skyline-console/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.