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

Reverse-spec · Services

masakari — Interactions

Interactions — masakari — branch stable/2026.2

Branch stable/2026.2 @ ae41528e1ebc — repository https://opendev.org/openstack/masakari — analysed on 2026-10-28

SystemProtocolLibraryConfigurationUsed byUsage
NovaHTTPS REST, microversion 2.53python-novaclient over a keystoneauth session[DEFAULT]/os_privileged_user_*, nova_catalog_admin_info, os_region_name, nova_ca_certificates_file, nova_api_insecure (masakari/conf/nova.py:19)API (host check), engine (recovery)the recovery actions
KeystoneHTTPS REST v3keystoneauth1 (password plugin of [keystone_authtoken]/auth_type)os_privileged_user_auth_url, os_user_domain_name, os_project_domain_name, os_system_scopeAPI, engineservice token for Nova; keystonemiddleware validates incoming tokens
RabbitMQAMQP 0-9-1oslo.messaging[DEFAULT]/transport_url, [oslo_messaging_notifications]API → engine (RPC), API and engine → listeners (notifications)cast, call, versioned notifications
MariaDB/MySQLSQLoslo.db, SQLAlchemy[database]/connectionAPI, enginesegments, hosts, notifications, vmoves
MariaDB/MySQL (taskflow)SQLtaskflow persistence (SQLAlchemy backend)[taskflow]/connection (required by the engine, masakari/engine/drivers/taskflow/driver.py:50)engine, read by GET /notifications/{id} 1.1 through the engineworkflow logbooks and per-task progress
tooz backend (etcd, Redis, ZooKeeper…)backend-specifictooz[coordination]/backend_url (default none, masakari/conf/coordination.py:19)API onlydistributed lock masakari-create_host_notification-<hostname>

Outbound REST calls

Authentication

novaclient() (masakari/compute/nova.py:86) builds a new keystoneauth session for every Nova call, with password auth for os_privileged_user_name/os_privileged_user_password in project os_privileged_user_tenant (or system_scope). Each call therefore costs a POST /v3/auth/tokens [inferred: no session or token cache is kept between calls]. The Nova endpoint is the catalogue entry compute/nova/publicURL of os_region_name, unless the request context already carries a catalogue. The X-OpenStack-Request-ID of the incoming request is passed as global_request_id. No timeout is set (timeout=None, masakari/compute/nova.py:131), so a hung Nova blocks a green thread until TCP gives up.

Calls to Nova

All calls carry X-Auth-Token, X-OpenStack-Nova-API-Version: 2.53 and OpenStack-API-Version: compute 2.53. Recorded with the real novaclient in [_rewrite/nova-calls_stable-2026.2.json](_rewrite/nova-calls_stable-2026.2.json).

Method + pathBodyWhenExpected answerErrors handledDefined at
GET /v2.1/os-services?binary=nova-compute&host=<name>—host create / rename (API); before every enable/disable (engine)services[] with id (UUID at 2.53), statusempty list → ComputeNotFoundByName (API 400); in the engine [0] on an empty list → IndexError, which escapes the translation → generic MasakariException → notification error [inferred]masakari/compute/nova.py:256, masakari/compute/nova.py:162
PUT /v2.1/os-services/{service_uuid}{"status": "disabled", "disabled_reason": "Masakari detected host failed."}DisableComputeServiceTask, DisableComputeNodeTask200 servicetranslated (masakari/compute/nova.py:52)masakari/compute/nova.py:167
PUT /v2.1/os-services/{service_uuid}{"status": "enabled"}reserved host before evacuation200idemmasakari/compute/nova.py:172
GET /v2.1/servers/detail?all_tenants=true&host=<name>—PrepareHAEnabledInstancesTaskservers[] with metadata, OS-EXT-STS:vm_statesingle page (no limit/marker loop)masakari/compute/nova.py:147
GET /v2.1/servers/{id}—before every action, and every verify_interval while pollingserver with OS-EXT-STS:vm_state, OS-EXT-STS:task_state, OS-EXT-STS:power_state, OS-EXT-SRV-ATTR:hypervisor_hostname, locked, metadata404 → NotFoundmasakari/compute/nova.py:200
POST /v2.1/servers/{id}/action{"lock": null} / {"unlock": null}around each evacuation, if not already locked202unlock failure only loggedmasakari/compute/nova.py:240, masakari/compute/nova.py:248
POST /v2.1/servers/{id}/action{"os-resetState": {"state": "error"}} (or "active" for a resized VM in instance recovery)state normalisation before evacuation; after the evacuation of an error VM202—masakari/compute/nova.py:191
POST /v2.1/servers/{id}/action{"evacuate": {}} (auto) or {"evacuate": {"host": "<reserved host>"}}EvacuateInstancesTask200Nova 400/409 → vmove failedmasakari/compute/nova.py:182
POST /v2.1/servers/{id}/action{"os-stop": null} / {"os-start": null}instance recovery; re-stop after evacuating a stopped VM202409 on stop tolerated if already stoppedmasakari/compute/nova.py:208, masakari/compute/nova.py:216
GET /v2.1/os-aggregates—reserved host, if add_reserved_host_to_aggregateaggregates[] with hosts—masakari/compute/nova.py:224
POST /v2.1/os-aggregates/{id}/action{"add_host": {"host": "<reserved host>"}}idem, for each aggregate containing the failed host200409 ignored ("already been added")masakari/compute/nova.py:231

Error translation (translate_nova_exception, masakari/compute/nova.py:52):

  • Timeout, CommandError, keystoneauth ConnectionError, DiscoveryFailure → MasakariException;
  • 400 → InvalidInput;
  • 403 → Forbidden;
  • 404 → NotFound;
  • 409 → Conflict;
  • **a 5xx (ClientException) is not translated** and escapes as a novaclient exception (observed: API 500 "…<class 'novaclient.exceptions.ClientException'>").

Sample frame (host failure, auto method), as recorded:

PUT /v2.1/os-services/9811decb-80f0-5552-99d1-1d2f6f5bea9a HTTP/1.1
X-Auth-Token: <service token>
X-OpenStack-Nova-API-Version: 2.53
Content-Type: application/json

{"status": "disabled", "disabled_reason": "Masakari detected host failed."}

POST /v2.1/servers/aa7ba2b1-b24c-5227-90dc-49925583e0f3/action HTTP/1.1
X-OpenStack-Nova-API-Version: 2.53

{"evacuate": {}}

RabbitMQ

Topology

ExchangeTypeRouting key / queueProducerConsumer
openstack (control exchange)topicha_engine (shared, round-robin) and ha_engine.<host>masakari-api (EngineAPI, masakari/engine/rpcapi.py:43)masakari-engine (masakari/service.py:91)
ha_engine_fanoutfanoutha_engine_fanout_<uuid>nobody (oslo.messaging creates it)masakari-engine
openstacktopicversioned_notifications.info / .errormasakari-api, masakari-engine (masakari/rpc.py:55)external listeners (none in Masakari)
reply queues reply_<uuid>direct—masakari-enginemasakari-api (for the call)

RPC methods

MethodDirectionModeTopic / serverVersionArgumentsResponseDefined at
process_notificationapi → enginecastha_engine (any engine)1.0notification: Notification object 1.3nonemasakari/engine/rpcapi.py:48
get_notification_recovery_workflow_detailsapi → enginecall (rpc_response_timeout, 60 s by default)ha_engine1.1notificationthe same Notification with recovery_workflow_details (list of NotificationProgressDetails 1.0)masakari/engine/rpcapi.py:53

Objects travel as oslo.versionedobjects primitives through MasakariObjectSerializer. The request context is serialised with every message, including auth_token, service_catalog, roles, user_id, project_id, request_id and global_request_id (context_keys in [_rewrite/rpc-frames_stable-2026.2.json](_rewrite/rpc-frames_stable-2026.2.json)).

Sample frame (process_notification, oslo.messaging message body, as recorded, timestamps shortened):

{"method": "process_notification", "version": "1.0",
 "args": {"notification": {
   "masakari_object.name": "Notification", "masakari_object.namespace": "masakari",
   "masakari_object.version": "1.3",
   "masakari_object.data": {"id": 1, "notification_uuid": "7882ccb9-ebb1-43b3-848b-f2333ae4b79c",
     "generated_time": "2026-10-28T10:00:00Z", "type": "PROCESS", "status": "new",
     "payload": {"event": "STARTED", "process_name": "nova-compute"},
     "source_host_uuid": "e3fe016c-f32c-4971-8e7f-8ca518e30f72",
     "failover_segment_uuid": "44f3f05c-8e15-4fac-9e0b-994d3c1f4991", "message": null,
     "created_at": "2026-10-28T22:43:57Z", "updated_at": null, "deleted": false, "deleted_at": null}}},
 "_context_auth_token": "…", "_context_user_id": "…", "_context_project_id": "…",
 "_context_request_id": "req-…", "_context_roles": ["admin"]}

Versioned notifications

They are emitted only when [oslo_messaging_notifications]/driver is set (the oslo default is none, which makes them no-ops). They use topic versioned_notifications (masakari/rpc.py:57), publisher_id masakari-api:<hostname> or masakari-engine:<hostname>, and priority INFO (or ERROR with a fault payload). Samples are in doc/notification_samples/.

event_typeEmitted whenPayloadEmitted at
segment.create.start / .endaround the INSERTSegmentApiPayload (segment fields)masakari/objects/segment.py:80
segment.update.start / .end, segment.delete.start / .endaround the update / soft deleteidemmasakari/objects/segment.py:104, masakari/objects/segment.py:122
segment.create.error (and update/delete)the database operation failedidem + fault (exception, function, module, message)masakari/ha/api.py:105
host.create.*, host.update.*, host.delete.*idem for hostsHostApiPayload (with the segment)masakari/objects/host.py:91, masakari/objects/host.py:123, masakari/objects/host.py:144
notification.create.start / .end / .erroraround the INSERT of a notification, and an error of the insert or of the castNotificationApiPayloadmasakari/objects/notification.py:90, masakari/ha/api.py:337
notification.process.startthe engine starts a notificationidemmasakari/engine/manager.py:328
notification.process.end / .errorat the end of the type handleridem (+ fault, traceback)masakari/engine/manager.py:116, masakari/engine/manager.py:257

Host updates made by the engine (on_maintenance, reserved) go through Host.save() and therefore also emit host.update.start/end, with publisher masakari-api (the default binary of masakari/api/utils.py:63) even though the engine sends them.

Database

Tables (main database)

TableRoleWritten byRead by
failover_segmentssegments; unique (name, deleted), (uuid); recovery_method ENUMAPIAPI, engine
hostsmembers of a segment; FK failover_segment_id → failover_segments.uuid; unique (name, deleted), i.e. a name is unique across segmentsAPI; engine (on_maintenance, reserved)API, engine
notificationsfailure reports and their status; unique (notification_uuid); status ENUM; payload JSON text (dict of strings); generated_time set by the senderAPI (insert), engine (status, message)API, engine (periodic tasks)
vmovesone row per instance evacuation; no FK, linked by notification_uuidengineAPI (1.3)

The schema is identical between the models and the alembic migrations ([_rewrite/schema-diff_stable-2026.2.md](_rewrite/schema-diff_stable-2026.2.md)). All tables are soft-deleted (deleted = id when deleted, oslo.db SoftDeleteMixin).

Taskflow persistence database

Tables logbooks, flowdetails, atomdetails (and their own alembic_version) are created by TaskFlowDriver.upgrade_backend (masakari/engine/drivers/taskflow/driver.py:250) ([_rewrite/schema-sqlite-taskflow_stable-2026.2.sql](_rewrite/schema-sqlite-taskflow_stable-2026.2.sql)). There is one logbook per notification, named after the notification UUID (masakari/engine/drivers/taskflow/base.py:132), one flow detail per run (retries add flow details), and one atom detail per task, whose meta holds progress and progress_details (written by update_details, masakari/engine/drivers/taskflow/base.py:58). Nothing ever purges this database.

Notable transactions

  • Every DB API function is its own transaction (@context_manager.reader/writer), retried 5 times on deadlock (masakari/db/sqlalchemy/api.py:171). There is **no SELECT … FOR UPDATE** and no row versioning.
  • The duplicate check (SELECT then INSERT) is not atomic. It relies on the tooz lock (COMPUTE_HOST only) or on the random sleep.
  • The "in use" check of segment/host update and delete is a separate SELECT COUNT(*) before the write (masakari/db/sqlalchemy/api.py:304): racy, but harmless.
  • Segment delete soft-deletes the segment and its hosts in one transaction (masakari/db/sqlalchemy/api.py:289).

Representative queries (reconstructed):

-- duplicate detection (masakari/ha/api.py:277)
SELECT * FROM notifications WHERE deleted = 0 AND type = :type AND source_host_uuid = :host
  AND generated_time >= :generated_time - INTERVAL 180 SECOND ORDER BY created_at DESC, id DESC;
-- segment under recovery (masakari/db/sqlalchemy/api.py:304)
SELECT count(id) FROM notifications WHERE deleted = 0 AND status IN ('new','running','error')
  AND source_host_uuid IN (SELECT uuid FROM hosts WHERE deleted = 0 AND failover_segment_id = :seg);
-- reserved hosts (masakari/engine/manager.py:223)
SELECT * FROM hosts WHERE deleted = 0 AND failover_segment_id = :seg AND reserved = 1 AND on_maintenance = 0
  ORDER BY created_at DESC, id DESC;

Other

  • No subprocess, rootwrap or privsep. The engine needs no access to the compute nodes: everything goes through Nova.
  • GET /healthcheck (oslo.middleware, disable_by_file backend, /etc/masakari/healthcheck_disable) is served by the API paste composite.

Source: reverse-spec-masakari/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.