Interactions — octavia — branch stable/2026.2
Branch
stable/2026.2@2900ed67cac2— repository https://opendev.org/openstack/octavia — analysed on 2026-11-01
| System | Protocol | Library | Direction | Sync/async | Configuration |
|---|---|---|---|---|---|
| MariaDB/MySQL | SQL | SQLAlchemy, oslo.db (enginefacade) | read/write | sync | [database] |
| Provider driver | in-process Python call | octavia-lib, stevedore namespace octavia.api.drivers | out | sync | [api_settings] enabled_provider_drivers, default_provider_driver |
| Driver agent sockets | JSON over Unix stream | octavia-lib DriverLibrary | in | sync per message | [driver_agent] |
| Neutron | REST | openstacksdk | out | sync | [neutron], [networking] |
| Keystone | REST | keystonemiddleware, keystoneauth | out | sync | [keystone_authtoken], [service_auth] |
| Barbican (cert manager) | REST | barbicanclient/castellan | out | sync | [certificates] |
| RabbitMQ | AMQP | oslo.messaging | out, amphora only | cast | transport_url |
1. Database
1.1 Tables
| Table | Primary key | Columns | Foreign keys | Role in the Go API |
|---|---|---|---|---|
additional_vip | load_balancer_id,subnet_id | 5 | load_balancer_id→load_balancer | core |
algorithm | name | 2 | — | lookup (FK target) |
amphora | id | 20 | load_balancer_id→load_balancer, role→amphora_roles, status→provisioning_status | read only (routes); stays empty |
amphora_build_request | amphora_id | 4 | — | amphora driver only |
amphora_build_slots | id | 2 | — | amphora driver only |
amphora_health | amphora_id | 3 | — | amphora driver only |
amphora_member_port | port_id | 5 | amphora_id→amphora | amphora driver only |
amphora_roles | name | 2 | — | lookup (FK target) |
availability_zone | name | 4 | availability_zone_profile_id→availability_zone_profile | core |
availability_zone_profile | id | 4 | — | core |
client_authentication_mode | name | 1 | — | lookup (FK target) |
flavor | id | 5 | flavor_profile_id→flavor_profile | core |
flavor_profile | id | 4 | — | core |
health_monitor | id | 19 | operating_status→operating_status, pool_id→pool, provisioning_status→provisioning_status, type→health_monitor_type | core |
health_monitor_type | name | 2 | — | lookup (FK target) |
l7policy | id | 16 | action→l7policy_action, listener_id→listener, operating_status→operating_status, provisioning_status→provisioning_status, redirect_pool_id→pool | core |
l7policy_action | name | 2 | — | lookup (FK target) |
l7rule | id | 13 | compare_type→l7rule_compare_type, l7policy_id→l7policy, operating_status→operating_status, provisioning_status→provisioning_status, type→l7rule_type | core |
l7rule_compare_type | name | 2 | — | lookup (FK target) |
l7rule_type | name | 2 | — | lookup (FK target) |
lb_topology | name | 2 | — | lookup (FK target) |
listener | id | 30 | client_authentication→client_authentication_mode, default_pool_id→pool, load_balancer_id→load_balancer, operating_status→operating_status, protocol→protocol, provisioning_status→provisioning_status | core |
listener_cidr | listener_id,cidr | 2 | listener_id→listener | core |
listener_statistics | listener_id,amphora_id | 7 | — | core; written by the stats path |
load_balancer | id | 14 | availability_zone→availability_zone, flavor_id→flavor, operating_status→operating_status, provisioning_status→provisioning_status, topology→lb_topology | core |
member | id | 17 | operating_status→operating_status, pool_id→pool, provisioning_status→provisioning_status | core |
operating_status | name | 2 | — | lookup (FK target) |
pool | id | 19 | lb_algorithm→algorithm, load_balancer_id→load_balancer, operating_status→operating_status, protocol→protocol, provisioning_status→provisioning_status | core |
protocol | name | 2 | — | lookup (FK target) |
provisioning_status | name | 2 | — | lookup (FK target) |
quotas | project_id | 15 | — | core |
session_persistence | pool_id | 5 | pool_id→pool, type→session_persistence_type | core |
session_persistence_type | name | 2 | — | lookup (FK target) |
sni | listener_id,tls_container_id | 3 | listener_id→listener | core |
tags | resource_id,tag | 2 | — | core |
vip | load_balancer_id | 8 | load_balancer_id→load_balancer | core; 1:1 with the LB |
vip_security_group | load_balancer_id,sg_id | 2 | load_balancer_id→vip | core |
vrrp_auth_method | name | 2 | — | lookup (FK target) |
vrrp_group | load_balancer_id | 5 | load_balancer_id→load_balancer, vrrp_auth_type→vrrp_auth_method | amphora 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. memberis unique on (pool_id,ip_address,protocol_port) (octavia/db/models.py:205);listeneron (load_balancer_id,protocol,protocol_port) (octavia/db/models.py:554), the code adds the same-L4-protocol check;session_persistence,health_monitorhavepool_idas key: one per pool.l7policy.position: SQLAlchemyordering_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_headersis a Python pickle in a BLOB (octavia/db/models.py:601).tagsis 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) andin_use_*.- Soft delete: only
load_balancerkeepsDELETEDrows (amphoratoo, not used). Listeners, pools, etc. are physically deleted by the status path.
1.2 Tables per operation
| Operation | Written | Read |
|---|---|---|
| POST LB | quotas, load_balancer, vip, additional_vip, vip_security_group, tags; graph: pool, session_persistence, member, health_monitor, listener, listener_cidr, sni, l7policy, l7rule | flavor, flavor_profile, availability_zone, availability_zone_profile |
| child POST | quotas, load_balancer (status), parents (status), object | LB, parents |
| child PUT | LB, parents, object | same |
| child DELETE | LB, parents, object (PENDING_DELETE) | same |
| status message | object, quotas | load_balancer + vip when DELETED |
| stats message | listener_statistics | — |
| flavor DELETE | load_balancer.flavor_id of DELETED rows → NIL, flavor | — |
1.3 Transactions and locks
| Case | Mechanism | Where |
|---|---|---|
| LB test-and-set | SELECT … FOR UPDATE, populate_existing | octavia/db/repositories.py:809-812 |
| quota | SELECT … FOR UPDATE on the project row, inside the create transaction | octavia/db/repositories.py:469-473 |
| quota record creation | separate transaction, committed before, 5 retries on deadlock | octavia/db/repositories.py:2005-2012 |
| decrement | own locking transaction in the status path | octavia/api/drivers/driver_agent/driver_updater.py:59-87, octavia/db/repositories.py:640-645 |
| flavor and AZ delete | SERIALIZABLE session + 5 deadlock retries on the handler | octavia/api/v2/controllers/flavors.py:147-160 |
| amphora delete | SELECT … FOR UPDATE, status must be ERROR | octavia/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 operation | Driver call(s), in order | Arguments | Where |
|---|---|---|---|
| POST LB | validate_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 graph | octavia/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 LB | loadbalancer_update | (old, new) | octavia/api/v2/controllers/load_balancer.py:856 |
| DELETE LB | loadbalancer_delete | (lb, cascade) | octavia/api/v2/controllers/load_balancer.py:903 |
| failover | loadbalancer_failover | (lb_id) | octavia/api/v2/controllers/load_balancer.py:1026 |
| listener POST/PUT/DELETE | listener_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 |
| pool | pool_create, pool_update, pool_delete | same | octavia/api/v2/controllers/pool.py:306, octavia/api/v2/controllers/pool.py:499, octavia/api/v2/controllers/pool.py:555 |
| member | member_create, member_update, member_delete, member_batch_update | same; (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 |
| HM | health_monitor_create, _update, _delete | same | octavia/api/v2/controllers/health_monitor.py:270, octavia/api/v2/controllers/health_monitor.py:399, octavia/api/v2/controllers/health_monitor.py:449 |
| L7 policy | l7policy_create, _update, _delete | same | octavia/api/v2/controllers/l7policy.py:188, octavia/api/v2/controllers/l7policy.py:280, octavia/api/v2/controllers/l7policy.py:336 |
| L7 rule | l7rule_create, _update, _delete | same | octavia/api/v2/controllers/l7rule.py:189, octavia/api/v2/controllers/l7rule.py:264, octavia/api/v2/controllers/l7rule.py:323 |
| flavor profile POST, PUT | validate_flavor | (dict) | octavia/api/v2/controllers/flavor_profiles.py:110, octavia/api/v2/controllers/flavor_profiles.py:189 |
| AZ profile POST, PUT | validate_availability_zone | (dict) | octavia/api/v2/controllers/availability_zone_profiles.py:117, octavia/api/v2/controllers/availability_zone_profiles.py:210 |
| capabilities | get_supported_flavor_metadata, get_supported_availability_zone_metadata | none; not through call_provider | octavia/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_dictremovesprovisioning_status,operating_status,provider,created_at,updated_at,tags,flavor_id,topology,vrrp_group,amphorae,vip,listeners,pools,server_group_id,tenant_id, andproject_idunless asked; renamesenabled→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_idsfrom the VIP;flavordictionary fetched by a separate session (octavia/api/drivers/utils.py:128-154);additional_vips,pools,listenerswhen 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_cidrsto strings;sni_containers→sni_container_refs; the certificates are fetched and inlined asdefault_tls_container_data,sni_container_data,client_ca_tls_container_data,client_crl_container_datawhen a reference exists (octavia/api/drivers/utils.py:227-295).for_delete=Truetolerates 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_persistenceas 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_idas 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 byprovider_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_dictleaves keys absent asUnset; on update only changed fields appear innew;oldis 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: LBACTIVE, listenersACTIVE, poolACTIVE… Reporting only the child leaves the LB immutable for ever. DELETEDon a non-LB removes the row and frees quota; on a LB marks the row, deallocates the VIP port ifoctavia_owned, frees quota.- A status message can carry only
operating_status, for health updates.
2.4 Get path
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 need | Neutron call | Credentials | Failure maps to | Defined at |
|---|---|---|---|---|
| check port | GET /v2.0/ports/{id} | user, unless allow_invisible_resource_usage | InvalidSubresource 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 subnet | GET /v2.0/subnets/{id} | user; service for the multi-VIP check and for the fill-in | same; Subnet <id> not found. | octavia/common/validate.py:352-360, octavia/api/v2/controllers/load_balancer.py:270 |
| check network | GET /v2.0/networks/{id} | user | InvalidSubresource | octavia/common/validate.py:381-397 |
| pick subnet | GET /v2.0/network-ip-availabilities/{id}: total_ips, used_ips per subnet | service | Could not verify IP availability… | octavia/network/drivers/neutron/base.py:296-297, octavia/common/utils.py:156-160 |
| QoS | GET /v2.0/qos/policies/{id} | user | InvalidSubresource(qos_policy) | octavia/common/validate.py:363-372 |
| security group | GET /v2.0/security-groups/{id} | user | InvalidSubresource(Security Group) | octavia/common/validate.py:400-409 |
| member subnet | GET /v2.0/subnets/{id} | user | InvalidSubresource(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_groups | service | 409 VIPInUseException with Neutron's details; other AllocateVIPException with orig_code | octavia/network/drivers/neutron/allowed_address_pairs.py:558-677 |
| cleanup on failure | DELETE ports/{id}, 404 ignored | service | logged only | octavia/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=Octavia | service | fault reply | octavia/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()inprepare_servicecreates the transports even if unused (octavia/common/rpc.py:25-29).- Only
AmphoraController,FailoverController,AmphoraUpdateControllercast directly,delete_amphora,failover_amphora,update_amphora_agent_config, on topicoctavia_provisioning_v2namespacecontroller, 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.
NOTIFIERis defined (octavia/common/rpc.py:28-29), only the worker'snotification_tasksuse 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.