OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

skyline-console — Interactions

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-apiserver stable/2026.2 @ 4af72bfd4577 (prefix skyline-apiserver:), microversion-parse @ 77ea32a (prefix microversion-parse:), cinder stable/2026.2 @ c117f9cf0 (prefix cinder:), nova stable/2026.2 @ 8df1922926 (prefix nova:).

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).

AspectBehaviourWhere
Base URL/: every path is absolute on the page's originsrc/client/client/request.js:159
Default headersContent-Type: application/json;charset=utf-8, cache-control: no-cache, pragma: no-cachesrc/client/client/request.js:157
Request IDX-Openstack-Request-Id: req-<uuid4> on every requestsrc/client/client/request.js:51
TokenX-Auth-Token: <keystone_token from localStorage> when the time_expired cookie is in the futuresrc/client/client/request.js:56
Microversionfrom the URL and the body, see § 2src/client/client/request.js:69
Per-call optionsextra headers, isFormData (drops Content-Type), any axios option (e.g. onUploadProgress, cancelToken)src/client/client/request.js:80
Query stringGET/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
Cookiessent automatically by the browser (same origin): session (JWT for skyline-apiserver) and time_expiredrecorded, _rewrite/smoke-exchanges_stable-2026.2.json
Errorssee 02 § 1src/client/client/request.js:114

URL construction

TargetURL prefixWhere
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/octaviasrc/client/client/constants.js:41
project in pathCinder, 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):

  1. Per service, by URL substring (src/client/client/constants.js:90): the first key of nova, placement, cinder, ironic, manila, zun contained 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 |
  2. Per action (body with one key, any method): Nova os-migrateLive 2.30, rebuild 2.93, rescue 2.93, lock 2.42, remote_console 2.53; Cinder os-extend 3.60. Header OpenStack-API-Version: <compute|volume> <version> (src/client/client/constants.js:117, src/client/client/constants.js:158).
  3. 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 contains volume: it always gets 3.59. The 3.60 intended for os-extend and the 3.29 intended for /messages never 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)).

CallWhenFields readConsole side
POST login, POST login/totplogin formthe whole profile; cookiessrc/stores/root.js:123, src/stores/root.js:140
GET profile, GET policiesevery page load, after login and after each switchendpoints, 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 logoutuser menu—src/stores/root.js:236
POST switch_project/{id} (body and project_domain_id query), POST switch_region/{region}header menusthe new profilesrc/client/skyline/index.js:103, src/client/skyline/index.js:118
GET config, GET sso, GET contrib/regions, GET contrib/domainslogin pagedefault domain, SSO protocols, regions, domainssrc/stores/skyline/skyline.js:37
GET contrib/keystone_endpointspassword-expired flow (before any session)first item's url used as Keystone prefixsrc/stores/root.js:280
GET extension/servers, recycle_servers, volumes, volume_snapshots, ports, compute-serviceslists of instances, recycle bin, volumes, snapshots, ports, compute hoststhe joined resources, count for pagingsrc/stores/nova/instance.js:78, src/stores/cinder/volume.js:58
GET query, GET query_rangemonitoringPrometheus resultsrc/stores/prometheus/monitor-base.js:98
GET settings, PUT setting, DELETE setting/{key}settings pagesettings[] (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 reachedDeclaredMicroversionClient
neutron (neutron, plus neutron_qos, neutron_vpn, neutron_firewall, neutron_port_forwarding for menus)119270—src/client/neutron/index.js:18
manila (manilav2)60166X-OpenStack-Manila-API-Version: 2.55src/client/manila/index.js:18
keystone58225—src/client/keystone/index.js:18
cinder52126volume 3.59 (+3.60/3.29, ignored by Cinder)src/client/cinder/index.js:18
nova50143compute 2.79 (+2.30/2.42/2.53/2.63/2.93)src/client/nova/index.js:18
trove3190—src/client/trove/index.js:18
zun3080container 1.40src/client/zun/index.js:18
ironic27671.58src/client/ironic/index.js:18
octavia2763—src/client/octavia/index.js:18
glance1947—src/client/glance/index.js:18
magnum1638—src/client/magnum/index.js:16
masakari1426—src/client/masakari/index.js:16
designate1336—src/client/designate/index.js:16
swift1223—src/client/swift/index.js:18
heat1125—src/client/heat/index.js:18
barbican1035—src/client/barbican/index.js:18
placement434placement 1.28src/client/placement/index.js:18

Patterns worth knowing for any implementation of these APIs:

  • Lists: GET <collection> or <collection>/detail with the page's filters. Server-side paging (isFilterByBackend) sends limit, marker, sort_key/sort_dir (Neutron) or sort_keys/sort_dirs (skyline extensions). Other lists fetch everything; fetchListByLimit stores page with limit=100 and the last item's id as marker until a short page (src/stores/base.js:287).
  • Admin lists add all_projects=true (src/stores/base.js:360); Cinder backups and Manila use all_tenants instead (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 with GET /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, plus os-keypairs?limit=100 and Cinder types.

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

InteractionTargetNotesWhere
noVNC / serial consoleURL returned by Nova remote-consoles, opened with window.openmust be reachable from the user's browser; &language=<locale> appendedsrc/pages/compute/containers/Instance/actions/Console.jsx:69
Zun container consoleWebSocket to the URL returned by Zun, subprotocols binary, base64the generated nginx has no WebSocket upgrade settings, so this URL must not go through itsrc/pages/container-service/containers/Containers/Detail/Console.jsx:37
Swift public URL (copy)profile.endpoints.swift origin endpointshown to the user, not calledsrc/client/client/constants.js:83

6. Browser-side state

StoreKeyContentLifetimeWhere
localStoragekeystone_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
localStoragelangUI languagekeptsrc/stores/root.js:325
cookie (set by skyline-apiserver)sessionJWT read by skyline-apiserversessionskyline-apiserver:skyline_apiserver/api/v1/login.py:172
cookie (set by skyline-apiserver)time_expiredepoch seconds; read by JavaScript to decide whether to send the tokensessionsrc/client/client/request.js:58, src/utils/cookie.js:16
globalwindow.clientevery client object (debugging aid)pagesrc/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.