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

Reverse-spec · Services

octavia — Interactions

Interactions — octavia — branch stable/2026.2

Branch stable/2026.2 @ 2900ed67cac2 — repository https://opendev.org/openstack/octavia — analysed on 2026-11-01

SystemProtocolLibraryDirectionSync/asyncConfiguration
MariaDB/MySQLSQLSQLAlchemy, oslo.db (enginefacade)read/writesync[database]
Provider driverin-process Python calloctavia-lib, stevedore namespace octavia.api.driversoutsync[api_settings] enabled_provider_drivers, default_provider_driver
Driver agent socketsJSON over Unix streamoctavia-lib DriverLibraryinsync per message[driver_agent]
NeutronRESTopenstacksdkoutsync[neutron], [networking]
KeystoneRESTkeystonemiddleware, keystoneauthoutsync[keystone_authtoken], [service_auth]
Barbican (cert manager)RESTbarbicanclient/castellanoutsync[certificates]
RabbitMQAMQPoslo.messagingout, amphora onlycasttransport_url

1. Database

1.1 Tables

TablePrimary keyColumnsForeign keysRole in the Go API
additional_vipload_balancer_id,subnet_id5load_balancer_id→load_balancercore
algorithmname2—lookup (FK target)
amphoraid20load_balancer_id→load_balancer, role→amphora_roles, status→provisioning_statusread only (routes); stays empty
amphora_build_requestamphora_id4—amphora driver only
amphora_build_slotsid2—amphora driver only
amphora_healthamphora_id3—amphora driver only
amphora_member_portport_id5amphora_id→amphoraamphora driver only
amphora_rolesname2—lookup (FK target)
availability_zonename4availability_zone_profile_id→availability_zone_profilecore
availability_zone_profileid4—core
client_authentication_modename1—lookup (FK target)
flavorid5flavor_profile_id→flavor_profilecore
flavor_profileid4—core
health_monitorid19operating_status→operating_status, pool_id→pool, provisioning_status→provisioning_status, type→health_monitor_typecore
health_monitor_typename2—lookup (FK target)
l7policyid16action→l7policy_action, listener_id→listener, operating_status→operating_status, provisioning_status→provisioning_status, redirect_pool_id→poolcore
l7policy_actionname2—lookup (FK target)
l7ruleid13compare_type→l7rule_compare_type, l7policy_id→l7policy, operating_status→operating_status, provisioning_status→provisioning_status, type→l7rule_typecore
l7rule_compare_typename2—lookup (FK target)
l7rule_typename2—lookup (FK target)
lb_topologyname2—lookup (FK target)
listenerid30client_authentication→client_authentication_mode, default_pool_id→pool, load_balancer_id→load_balancer, operating_status→operating_status, protocol→protocol, provisioning_status→provisioning_statuscore
listener_cidrlistener_id,cidr2listener_id→listenercore
listener_statisticslistener_id,amphora_id7—core; written by the stats path
load_balancerid14availability_zone→availability_zone, flavor_id→flavor, operating_status→operating_status, provisioning_status→provisioning_status, topology→lb_topologycore
memberid17operating_status→operating_status, pool_id→pool, provisioning_status→provisioning_statuscore
operating_statusname2—lookup (FK target)
poolid19lb_algorithm→algorithm, load_balancer_id→load_balancer, operating_status→operating_status, protocol→protocol, provisioning_status→provisioning_statuscore
protocolname2—lookup (FK target)
provisioning_statusname2—lookup (FK target)
quotasproject_id15—core
session_persistencepool_id5pool_id→pool, type→session_persistence_typecore
session_persistence_typename2—lookup (FK target)
snilistener_id,tls_container_id3listener_id→listenercore
tagsresource_id,tag2—core
vipload_balancer_id8load_balancer_id→load_balancercore; 1:1 with the LB
vip_security_groupload_balancer_id,sg_id2load_balancer_id→vipcore
vrrp_auth_methodname2—lookup (FK target)
vrrp_groupload_balancer_id5load_balancer_id→load_balancer, vrrp_auth_type→vrrp_auth_methodamphora driver only

Two tables only exist in the migrated database or only in the code: spares_pool (legacy, no model), and alembic_version. Differences of columns and indexes: _rewrite/schema-diff_stable-2026.2.md. Lookup tables and placeholder rows are filled by the migrations: _rewrite/lookup-tables_stable-2026.2.json.

Notable rows and constraints:

  • Status columns have FKs to lookup tables provisioning_status, operating_status, protocol, algorithm, … so an unknown enum value in a status message becomes an integrity error.
  • member is unique on (pool_id, ip_address, protocol_port) (octavia/db/models.py:205); listener on (load_balancer_id, protocol, protocol_port) (octavia/db/models.py:554), the code adds the same-L4-protocol check; session_persistence, health_monitor have pool_id as key: one per pool.
  • l7policy.position: SQLAlchemy ordering_list, 1-based, without holes (octavia/db/models.py:597).
  • listener.tls_versions, alpn_protocols, pool.… are comma-joined lists (ScalarListType) (octavia/db/models.py:375-376, octavia/db/models.py:614-615); listener.insert_headers is a Python pickle in a BLOB (octavia/db/models.py:601).
  • tags is polymorphic: (resource_id, tag) for LB, listener, pool, member, HM, L7 policy, L7 rule (octavia/db/base_models.py:220).
  • quotas: one row per project, limits (NULL = default) and in_use_*.
  • Soft delete: only load_balancer keeps DELETED rows (amphora too, not used). Listeners, pools, etc. are physically deleted by the status path.

1.2 Tables per operation

OperationWrittenRead
POST LBquotas, load_balancer, vip, additional_vip, vip_security_group, tags; graph: pool, session_persistence, member, health_monitor, listener, listener_cidr, sni, l7policy, l7ruleflavor, flavor_profile, availability_zone, availability_zone_profile
child POSTquotas, load_balancer (status), parents (status), objectLB, parents
child PUTLB, parents, objectsame
child DELETELB, parents, object (PENDING_DELETE)same
status messageobject, quotasload_balancer + vip when DELETED
stats messagelistener_statistics—
flavor DELETEload_balancer.flavor_id of DELETED rows → NIL, flavor—

1.3 Transactions and locks

CaseMechanismWhere
LB test-and-setSELECT … FOR UPDATE, populate_existingoctavia/db/repositories.py:809-812
quotaSELECT … FOR UPDATE on the project row, inside the create transactionoctavia/db/repositories.py:469-473
quota record creationseparate transaction, committed before, 5 retries on deadlockoctavia/db/repositories.py:2005-2012
decrementown locking transaction in the status pathoctavia/api/drivers/driver_agent/driver_updater.py:59-87, octavia/db/repositories.py:640-645
flavor and AZ deleteSERIALIZABLE session + 5 deadlock retries on the handleroctavia/api/v2/controllers/flavors.py:147-160
amphora deleteSELECT … FOR UPDATE, status must be ERRORoctavia/db/repositories.py:1470-1477

Lock order in a child write: quotas then load_balancer (or reverse for member: quota first too). The API does not retry except where shown. Lists use "optimised" get_all_API_list with explicit eager loads (octavia/db/repositories.py:766): no contract impact.

1.4 Representative queries

-- GET /v2/lbaas/loadbalancers?limit=2&marker=<m> (show_deleted=False, non-global project)
SELECT … FROM load_balancer
 WHERE project_id=? AND provisioning_status != 'DELETED'
   AND ((created_at > :m_created) OR (created_at = :m_created AND id > :m_id))   -- ordering keys
 ORDER BY created_at ASC, id ASC LIMIT 2;
-- test and set
SELECT * FROM load_balancer WHERE id=? FOR UPDATE;  -- then UPDATE provisioning_status
-- quota
SELECT * FROM quotas WHERE project_id=? LIMIT 1 FOR UPDATE;

2. The provider seam

This is the seam with openkeel-ovn-octavia-provider. Evidence: harness.driver_calls of _rewrite/exchanges_stable-2026.2.json holds, for each recorded request, every driver call with its octavia-lib dictionary.

2.1 Driver selection and calls

driver_factory.get_driver(provider) (octavia/api/drivers/driver_factory.py:29-53): Unset → default provider; empty (legacy row) → amphora. driver.name is set to the name asked for. A driver instance is created per request. All calls go through call_provider(name, method, *args) (octavia/api/drivers/utils.py:38-84), except capabilities.

API operationDriver call(s), in orderArgumentsWhere
POST LBvalidate_flavor, validate_availability_zone if data; create_vip_port; loadbalancer_create(flavor_dict), (az_dict), (lb_id, project_id, vip_dict, [add_vip_dict]), (LoadBalancer) with nested listeners, pools on a graphoctavia/api/v2/controllers/load_balancer.py:432, octavia/api/v2/controllers/load_balancer.py:475, octavia/api/v2/controllers/load_balancer.py:609, octavia/api/v2/controllers/load_balancer.py:666
PUT LBloadbalancer_update(old, new)octavia/api/v2/controllers/load_balancer.py:856
DELETE LBloadbalancer_delete(lb, cascade)octavia/api/v2/controllers/load_balancer.py:903
failoverloadbalancer_failover(lb_id)octavia/api/v2/controllers/load_balancer.py:1026
listener POST/PUT/DELETElistener_create, listener_update, listener_delete(obj), (old, new), (obj)octavia/api/v2/controllers/listener.py:433, octavia/api/v2/controllers/listener.py:674, octavia/api/v2/controllers/listener.py:723
poolpool_create, pool_update, pool_deletesameoctavia/api/v2/controllers/pool.py:306, octavia/api/v2/controllers/pool.py:499, octavia/api/v2/controllers/pool.py:555
membermember_create, member_update, member_delete, member_batch_updatesame; (pool_id, [members])octavia/api/v2/controllers/member.py:234, octavia/api/v2/controllers/member.py:334, octavia/api/v2/controllers/member.py:389, octavia/api/v2/controllers/member.py:544
HMhealth_monitor_create, _update, _deletesameoctavia/api/v2/controllers/health_monitor.py:270, octavia/api/v2/controllers/health_monitor.py:399, octavia/api/v2/controllers/health_monitor.py:449
L7 policyl7policy_create, _update, _deletesameoctavia/api/v2/controllers/l7policy.py:188, octavia/api/v2/controllers/l7policy.py:280, octavia/api/v2/controllers/l7policy.py:336
L7 rulel7rule_create, _update, _deletesameoctavia/api/v2/controllers/l7rule.py:189, octavia/api/v2/controllers/l7rule.py:264, octavia/api/v2/controllers/l7rule.py:323
flavor profile POST, PUTvalidate_flavor(dict)octavia/api/v2/controllers/flavor_profiles.py:110, octavia/api/v2/controllers/flavor_profiles.py:189
AZ profile POST, PUTvalidate_availability_zone(dict)octavia/api/v2/controllers/availability_zone_profiles.py:117, octavia/api/v2/controllers/availability_zone_profiles.py:210
capabilitiesget_supported_flavor_metadata, get_supported_availability_zone_metadatanone; not through call_provideroctavia/api/v2/controllers/provider.py:89, octavia/api/v2/controllers/provider.py:139

Capture counts per method: 1 089 create_vip_port, 1 072 loadbalancer_create, 1 023 pool_create, 952 listener_create, 182 l7policy_create, 101 health_monitor_create, 78 validate_flavor, 78 member_create, 72 validate_availability_zone, 66 l7rule_create, 36 listener_update, 27 loadbalancer_update, 26 loadbalancer_delete, 25 l7policy_update, 23 pool_update, 12 health_monitor_update, 7 member_batch_update, 7 l7rule_update, others below 6. member_batch_update calls have mocked: true: the batch tests replace call_provider, the arguments are recorded all the same.

2.2 Data conversion

All in octavia/api/drivers/utils.py, to octavia-lib data models:

  • _base_to_provider_dict removes provisioning_status, operating_status, provider, created_at, updated_at, tags, flavor_id, topology, vrrp_group, amphorae, vip, listeners, pools, server_group_id, tenant_id, and project_id unless asked; renames enabled → admin_state_up (octavia/api/drivers/utils.py:87-123). So the provider never sees tags, statuses or timestamps.
  • Load balancer: id→loadbalancer_id; vip_address, vip_network_id, vip_port_id, vip_subnet_id, vip_qos_policy_id, vip_sg_ids from the VIP; flavor dictionary fetched by a separate session (octavia/api/drivers/utils.py:128-154); additional_vips, pools, listeners when given.
  • Listener: id→listener_id, load_balancer_id→loadbalancer_id, tls_certificate_id→default_tls_container_ref, client_ca_tls_certificate_id→client_ca_tls_container_ref, client_crl_container_id→client_crl_container_ref; allowed_cidrs to strings; sni_containers→sni_container_refs; the certificates are fetched and inlined as default_tls_container_data, sni_container_data, client_ca_tls_container_data, client_crl_container_data when a reference exists (octavia/api/drivers/utils.py:227-295). for_delete=True tolerates fetch failures. A TLS listener is therefore read from Barbican on every driver call.
  • Pool (octavia/api/drivers/utils.py:379): id→pool_id, load_balancer_id→loadbalancer_id, session_persistence as a dict, health_monitor → healthmonitor, tls_certificate_id→tls_container_ref, ca_tls_certificate_id→ca_tls_container_ref, crl_container_id→crl_container_ref, secrets inlined as *_data.
  • Member (octavia/api/drivers/utils.py:478): id→member_id, ip_address→address, subnet_id as is; HM (octavia/api/drivers/utils.py:495-505): id→healthmonitor_id, fall_threshold→max_retries_down, rise_threshold→max_retries; L7 policy (octavia/api/drivers/utils.py:526): id→l7policy_id, redirect_pool_id, rules; L7 rule (octavia/api/drivers/utils.py:558): id→l7rule_id, invert, key, value.
  • VIP dict for create_vip_port: vip_address, vip_network_id, vip_port_id, vip_subnet_id, vip_qos_policy_id, vip_sg_ids, octavia_owned; result converted by provider_vip_dict_to_vip_obj (octavia/api/drivers/utils.py:568, octavia/api/drivers/utils.py:601-617).
  • Unset vs None is preserved: LoadBalancer.from_dict leaves keys absent as Unset; on update only changed fields appear in new; old is complete. to_dict(recurse=True, render_unsets=False) is exactly the wire form of the Go provider's driver API.

Examples of each call on the wire: _rewrite/exchanges_stable-2026.2.json (harness.driver_calls).

2.3 Status path

Described as a trigger in 05 § 1.2, and proven by _rewrite/cases-driver-seam_stable-2026.2.json. What the API contract needs from it:

  • A child operation's success must report the parents too, because the API has put them in PENDING_UPDATE: LB ACTIVE, listeners ACTIVE, pool ACTIVE… Reporting only the child leaves the LB immutable for ever.
  • DELETED on a non-LB removes the row and frees quota; on a LB marks the row, deallocates the VIP port if octavia_owned, frees quota.
  • A status message can carry only operating_status, for health updates.

2.4 Get path

05 § 1.4.

2.5 Statistics read

get_listener_stats: sums rows of the listener, adding active_connections only for an ALLOCATED amphora or when amphora_id == listener_id (octavia/common/stats.py:32-54). A listener without row → zeros, with a warning. get_loadbalancer_stats: sum over its listeners (octavia/common/stats.py:56-68). Response fields: active_connections, bytes_in, bytes_out, request_errors, total_connections.

3. Neutron (API process only)

Client: NeutronAuth.get_neutron_client (service credentials, cached), get_user_neutron_client(context) (the request's token, new connection per call) (octavia/common/clients.py:73-127). The network driver is loaded with stevedore per use (octavia/common/utils.py:69-75), allowed_address_pairs_driver in production.

The driver's constructor makes calls at load: find_extension for security-group, dns-integration, qos, then allowed-address-pairs; loading aborts when allowed-address-pairs is missing (octavia/network/drivers/neutron/base.py:43-50, octavia/network/drivers/neutron/allowed_address_pairs.py:52-57). It also instantiates the compute driver (octavia/network/drivers/neutron/allowed_address_pairs.py:46-50), which the Go API does not need.

API needNeutron callCredentialsFailure maps toDefined at
check portGET /v2.0/ports/{id}user, unless allow_invisible_resource_usageInvalidSubresource 400 (any error)octavia/common/validate.py:333-340, octavia/network/drivers/neutron/base.py:191-218, octavia/network/drivers/neutron/base.py:265
check subnetGET /v2.0/subnets/{id}user; service for the multi-VIP check and for the fill-insame; Subnet <id> not found.octavia/common/validate.py:352-360, octavia/api/v2/controllers/load_balancer.py:270
check networkGET /v2.0/networks/{id}userInvalidSubresourceoctavia/common/validate.py:381-397
pick subnetGET /v2.0/network-ip-availabilities/{id}: total_ips, used_ips per subnetserviceCould not verify IP availability…octavia/network/drivers/neutron/base.py:296-297, octavia/common/utils.py:156-160
QoSGET /v2.0/qos/policies/{id}userInvalidSubresource(qos_policy)octavia/common/validate.py:363-372
security groupGET /v2.0/security-groups/{id}userInvalidSubresource(Security Group)octavia/common/validate.py:400-409
member subnetGET /v2.0/subnets/{id}userInvalidSubresource(Subnet)octavia/api/v2/controllers/member.py:184-192
VIP port (provider says NotImplemented)GET ports/{id}; POST ports: name=octavia-lb-<id>, admin_state_up=false, device_id=lb-<id>, device_owner=Octavia, fixed_ips, project, security_groupsservice409 VIPInUseException with Neutron's details; other AllocateVIPException with orig_codeoctavia/network/drivers/neutron/allowed_address_pairs.py:558-677
cleanup on failureDELETE ports/{id}, 404 ignoredservicelogged onlyoctavia/api/v2/controllers/load_balancer.py:371-386, octavia/network/drivers/neutron/allowed_address_pairs.py:881-893
deallocation (status path)GET ports, SG cleanup, DELETE ports/{id} if device_owner=Octaviaservicefault replyoctavia/network/drivers/neutron/allowed_address_pairs.py:434-475

Timeouts and retries: openstacksdk defaults; [networking] max_retries etc. apply to the amphora flows only [inferred].

Number of Neutron GETs for a simple LB create with a subnet: at least 2 (validator, multi-VIP check), under two identities. OpenKeel specificity: served behind the Neutron endpoint, the Go API may read Neutron's objects in-process but must apply the same visibility rule (user scope) [inferred]. allow_invisible_resource_usage default false (octavia/common/config.py:273).

4. Keystone

SkippingAuthProtocol validates tokens (octavia/common/keystone.py:112-133) and sets X-Project-Id, X-Roles, etc., which RequestContext.from_environ reads. Without a project on creation: Missing project ID…. Service sessions come from [service_auth], [neutron], with a fallback from [neutron] to [service_auth] per option (octavia/common/keystone.py:44-98). No other Keystone call.

5. Barbican (certificate manager)

Stevedore octavia.cert_manager, [certificates] cert_manager (default barbican). Controllers keep one instance from their constructor (octavia/api/v2/controllers/base.py:40-44). set_acls, get_cert(check_only=True), get_secret with the request's context (octavia/api/v2/controllers/base.py:249-262, octavia/api/v2/controllers/base.py:264-316); cert_parser.load_certificates_data and PEM parsing (cryptography). Any failure → CertificateRetrievalException 400 Could not retrieve certificate: <refs>, except UnreadablePKCS12 raised as is. In tests the manager is a mock (octavia/tests/functional/api/v2/base.py:131-133): the recorded tapes say what it answered.

6. RabbitMQ

  • rpc.init() in prepare_service creates the transports even if unused (octavia/common/rpc.py:25-29).
  • Only AmphoraController, FailoverController, AmphoraUpdateController cast directly, delete_amphora, failover_amphora, update_amphora_agent_config, on topic octavia_provisioning_v2 namespace controller, version 2.0 (octavia/api/v2/controllers/amphora.py:41-46, octavia/api/v2/controllers/amphora.py:110, octavia/api/v2/controllers/amphora.py:165, octavia/api/v2/controllers/amphora.py:204). Unreachable when no amphora exists.
  • Notifications: none from the API process. NOTIFIER is defined (octavia/common/rpc.py:28-29), only the worker's notification_tasks use it, for the amphora driver (octavia/controller/worker/v2/flows/load_balancer_flows.py:113). The Go API sends no notification, as with the OVN provider today.

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