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

Reverse-spec · Services

ec2-api — Interactions

Interactions — ec2-api — branch 17.0.0

Branch 17.0.0 @ 32740781f605 — repository https://opendev.org/openstack/ec2-api (retired; local branch pre-retirement) — analysed on 2026-10-29

Other repositories cited: keystone: = openstack/keystone stable/2026.2 @ e6dc5ae07 (the OpenKeel target, not the Keystone ec2-api 17.0.0 was released with); keystoneclient: = python-keystoneclient stable/2026.2 @ b76de6a3; novaclient: / neutronclient: = local clones used only for exception attributes.

ec2-api uses no message bus (no oslo.messaging import anywhere in ec2api/), no Redis, and no notifications. It talks to its own database, to Keystone, Nova, Neutron, Cinder and Glance over REST, to an S3 endpoint (image bundles), to Nova's metadata service, to an oslo.cache back end (metadata), and runs openssl.

SystemProtocol / libraryDirectionCredentialsConfigurationUsed by
MariaDB/MySQL (or SQLite)SQL, oslo.db EngineFacade (legacy, autocommit=True)out[database] connectionec2api/db/sqlalchemy/api.py:40every action (ID mapping, DB-only resources, tags)
Keystone ec2tokensHTTPS requests.request('POST'), JSONoutnone (the request signature is the credential)keystone_ec2_tokens_url (ec2api/api/__init__.py:48)every EC2 request
Keystone catalogue / identitykeystoneauth session, keystoneclient v3outuser token; service user of [keystone_authtoken]ec2api/clients.py:43endpoint discovery, ec2.list (S3 import)
Novanovaclient, microversion up to 2.32outuser token; admin session for some readsnova_service_type = compute (ec2api/clients.py:28)instances, key pairs, AZs, quotas, volume attach, images (createImage)
Neutronneutronclient v2.0outuser token; admin for NSX metadata lookupsservice type network (ec2api/clients.py:76)VPC, subnets, ports, routers, floating IPs, security groups, VPNaaS
Cindercinderclient, version from the endpoint URLoutuser tokencinder_service_type = volumev3 (ec2api/clients.py:34)volumes, snapshots
Glanceglanceclient v2outuser tokenservice type image (ec2api/clients.py:81)images
S3 endpointbotocore S3 client, SigV4outthe user's first EC2 credential fetched from Keystones3_url, s3_region (ec2api/api/image.py:52)RegisterImage of an S3 bundle
Nova metadata servicehttplib2outHMAC of the instance ID with metadata_proxy_shared_secret[metadata] nova_metadata_* (ec2api/metadata/__init__.py:37)/openstack/… metadata paths
Neutron/NSX metadata proxyHTTPinsame HMAC[metadata] metadata_proxy_shared_secretinstance metadata
oslo.cachedogpile back end (memcached…)out—[cache] (ec2api/metadata/__init__.py:76)metadata cache
openssl binaryprocessutils.executelocal——AES-128-CBC decryption of S3 bundles (ec2api/api/image.py:1037)
Local files—local—buckets_path, image_decryption_dir, [oslo_concurrency] lock_path, x509_root_private_keyS3 server, image import, default-VPC lock

1. Database

1.1 Schema

Migrations: one sqlalchemy-migrate script, ec2api/db/sqlalchemy/migrate_repo/versions/001_juno.py:20 (no Alembic). Models: ec2api/db/sqlalchemy/models.py:41. The migrated SQLite schema is _rewrite/schema-sqlite-migrated_17.0.0.sql, the model DDL _rewrite/schema-mysql_17.0.0.sql; they are identical apart from the migrate_version bookkeeping table and the MySQL table options (_rewrite/schema-diff_17.0.0.md).

TableColumnsKeysContent
itemsid VARCHAR(30), project_id VARCHAR(64), vpc_id VARCHAR(12), os_id VARCHAR(36), data TEXTPK id; **UNIQUE items_os_id_idx (os_id)** across all kinds and projectsone row per EC2 object: the EC2 ID, its owner project (NULL for ID-only mappings), the VPC it belongs to, the OpenStack UUID it maps to (NULL for DB-only objects), and a JSON blob of the other attributes
tagsproject_id VARCHAR(64), item_id VARCHAR(30), key VARCHAR(127), value VARCHAR(255)PK (project_id, item_id, key); no FKEC2 tags

MySQL: ENGINE=InnoDB, CHARSET=utf8 (utf8mb3) on both tables, and the database default charset is altered to utf8 (ec2api/db/sqlalchemy/migrate_repo/versions/001_juno.py:48). With the default utf8_general_ci collation, id, os_id and the LIKE queries below are case-insensitive.

1.2 The item model

_pack_item_data / _unpack_item_data (ec2api/db/sqlalchemy/api.py:319): the Python dict of an item is stored as os_id and vpc_id columns plus data = json.dumps(rest) (Python's default separators ", " and ": "), id excluded. Reading merges them back. The kind is **the prefix of id**; queries by kind use id LIKE '<kind>-%'.

Kind (prefix)OpenStack object (os_id)vpc_id columndata keysCreated by
iNova serverVPC of the instancereservation_id, launch_index, client_token?, disable_api_termination?RunInstances (ec2api/api/instance.py:157); auto-create from Nova OS-EXT-SRV-ATTR:reservation_id/launch_index (ec2api/api/instance.py:1470)
ami, aki, ariGlance image (NULL while CreateImage runs)—is_public, description?, state? (failed)CreateImage, RegisterImage, auto-create (ec2api/api/image.py:820)
volCinder volume——CreateVolume, auto-create
snapCinder snapshot——CreateSnapshot, auto-create
vpcNeutron router—cidr_block, is_default, route_table_id, dhcp_options_id?CreateVpc (ec2api/api/vpc.py:136)
subnetNeutron subnetVPCroute_table_id? (explicit association), os_vpnservice_id?CreateSubnet (ec2api/api/subnet.py:78)
eniNeutron portVPCsubnet_id, description, private_ip_address, source_dest_check?, instance_id?, device_index?, attach_time?, delete_on_termination?CreateNetworkInterface (ec2api/api/network_interface.py:148)
sgNeutron security groupVPC or NULL (Classic)—CreateSecurityGroup, default group, auto-create
eipallocNeutron floating IP—public_ip, network_interface_id?, private_ip_address?AllocateAddress (VPC) (ec2api/api/address.py:228)
rtb—VPCroutes [{destination_cidr_block, gateway_id | network_interface_id}], propagating_gateways?CreateVpc, CreateRouteTable (ec2api/api/route_table.py:304)
igw—attached VPC or NULL—CreateInternetGateway
dopt——dhcp_configuration {key: [values]}CreateDhcpOptions
vgw—attached VPC or NULL—CreateVpnGateway
cgw——ip_addressCreateCustomerGateway
vpn——customer_gateway_id, vpn_gateway_id, **pre_shared_key (clear text)**, os_ikepolicy_id, os_ipsecpolicy_id, cidrs, os_ipsec_site_connections {subnet ID: Neutron connection UUID}CreateVpnConnection (ec2api/api/vpn_connection.py:89)

Not stored: key pairs (Nova, by name), availability zones, reservations (r- IDs are a field of instances), and the derived IDs rtbassoc-, eipassoc-, eni-attach- (§ 07 algorithm 1).

1.3 Queries (reconstructed SQL)

All queries go through model_query without soft-delete filtering (ec2api/db/sqlalchemy/api.py:80). Each function runs in autocommit mode: no transaction spans an action; consistency relies on OnCrashCleaner compensations (ec2api/api/common.py:41).

FunctionSQLSource
add_itemINSERT INTO items (id, project_id, os_id, vpc_id, data) VALUES ('<kind>-<8 hex>', :project, :os_id, :vpc_id, :json); on duplicate os_id: SELECT * FROM items WHERE os_id = :os_id AND (project_id = :project OR project_id IS NULL) AND id LIKE '<kind>-%' (exactly one row expected), merge the new data into it, set project_id = :project, UPDATEec2api/db/sqlalchemy/api.py:98
add_item_idINSERT INTO items (id, os_id[, project_id]) VALUES (…); on duplicate os_id: SELECT id FROM items WHERE os_id = :os_id (any project, any kind)ec2api/db/sqlalchemy/api.py:126
update_itemSELECT * FROM items WHERE project_id = :project AND id = :id (one row, else NoResultFound); refuse to change a non-NULL os_id; UPDATE items SET os_id, vpc_id, dataec2api/db/sqlalchemy/api.py:147
delete_itemDELETE FROM items WHERE project_id = :project AND id = :id; if a row went: DELETE FROM tags WHERE project_id = :project AND item_id = :id (errors ignored)ec2api/db/sqlalchemy/api.py:162
restore_itemINSERT with the given id (rollback of a delete); duplicate → EC2DBDuplicateEntryec2api/db/sqlalchemy/api.py:182
get_itemsSELECT * FROM items WHERE project_id = :project AND id LIKE '<kind>-%'ec2api/db/sqlalchemy/api.py:197
get_item_by_idSELECT * FROM items WHERE project_id = :project AND id = :id LIMIT 1ec2api/db/sqlalchemy/api.py:206
get_items_by_idsSELECT * FROM items WHERE project_id = :project AND id IN (…)ec2api/db/sqlalchemy/api.py:214
get_public_itemsSELECT * FROM items WHERE id LIKE '<kind>-%' AND data LIKE '%"is_public": True%' [AND id IN (…)] — all projects; matches the JSON "is_public": true only because LIKE is case-insensitive (MySQL _ci collations, SQLite ASCII), and only with Python's ": " separatorec2api/db/sqlalchemy/api.py:225
get_items_idsSELECT id, os_id FROM items WHERE id LIKE '<kind>-%' [AND id IN (…)] [AND os_id IN (…)] — all projectsec2api/db/sqlalchemy/api.py:236
add_tagsone transaction; per tag a savepoint INSERT INTO tags; on duplicate UPDATE tags SET value = :v WHERE project_id = :p AND item_id = :i AND key = :kec2api/db/sqlalchemy/api.py:248
delete_tagsDELETE FROM tags WHERE project_id = :p AND item_id IN (…) [AND ((key = :k [AND value = :v]) OR …)]ec2api/db/sqlalchemy/api.py:275
get_tagsSELECT item_id, key, value FROM tags WHERE project_id = :p [AND (item_id LIKE 'k1-%' OR …)] [AND item_id IN (…)]ec2api/db/sqlalchemy/api.py:302

Every function requires a user or admin context (ec2api/db/sqlalchemy/api.py:67, ec2api/context.py:128). The admin context has project_id = None (ec2api/context.py:140), so project-filtered queries return nothing for it; the metadata service sets the instance's project on its admin context first (ec2api/metadata/__init__.py:272).

Describe operations are the heaviest users: DescribeInstances reads instances, ENIs (with their own describe: addresses, ports, security groups), volumes, images and public images, tags (ec2api/api/instance.py:325); see _rewrite/action-translation_17.0.0.md for observed counts.

1.4 Locking and concurrency

  • The only lock is the oslo.concurrency external (file) lock around default-VPC creation, ec2api-default-vpc-lock-<project>- (ec2api/api/vpc.py:37, ec2api/api/vpc.py:175). It serialises processes on one host sharing lock_path; replicas on several hosts can each create a default VPC.
  • No SELECT … FOR UPDATE, no optimistic versioning: two concurrent modifications of the same item (a route table, a VPN connection's cidrs) are last-writer-wins [inferred].
  • Item IDs are random (random.randint(1, 0xffffffff), Mersenne Twister, ec2api/db/sqlalchemy/api.py:91); a collision is not retried.

2. Keystone

2.1 POST /v3/ec2tokens (every request)

Frame sent by ec2-api for a SigV4 request (shape from ec2api/api/__init__.py:190; values illustrative):

POST /v3/ec2tokens HTTP/1.1
Content-Type: application/json

{"ec2Credentials": {
  "access": "a1b2c3…",
  "signature": "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7",
  "host": "ec2.example.org:8788",
  "verb": "POST",
  "path": "/",
  "params": {},
  "headers": {"Host": "ec2.example.org:8788", "X-Amz-Date": "20230622T101500Z",
              "Authorization": "AWS4-HMAC-SHA256 Credential=a1b2c3…/20230622/RegionOne/ec2/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d67…",
              "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", "Content-Length": "43", "User-Agent": "Boto3/…"},
  "body_hash": "<sha256 hex of 'Action=DescribeInstances&Version=2016-11-15'>"}}

For SigV2, params holds every request parameter except Signature (AWSAccessKeyId, SignatureMethod, SignatureVersion=2, Timestamp, Action, Version, …). Keystone answers 200 with X-Subject-Token and a v3 token body (keystone:keystone/api/ec2tokens.py:76); ec2-api reads it with keystoneclient.access.AccessInfo.factory (ec2api/api/__init__.py:219). Errors: 401 (bad or missing signature, expired), 404 (unknown access key, keystone:keystone/api/_shared/EC2_S3_Resource.py:71), 503 when ec2credential is not enabled. No timeout, no retry, no caching.

2.2 Service catalogue and service credentials

  • All OpenStack calls use the session built from the user's token (ec2api/api/__init__.py:229): endpoints come from the catalogue in the token (public interface by default [inferred: keystoneauth default]).
  • The admin session ([keystone_authtoken] auth plugin and session options, ec2api/clients.py:44, ec2api/clients.py:145) is a process-wide singleton used for: Nova servers.list(all_tenants=True, project_id=…) and servers.get in DescribeInstances, DescribeVolumes, DescribeInstanceAttribute, _is_ebs_instance, instance auto-creation (ec2api/api/instance.py:342, ec2api/api/instance.py:494, ec2api/api/instance.py:1126, ec2api/api/instance.py:1472, ec2api/api/volume.py:165); everything in the metadata service (ec2api/metadata/__init__.py:267). The service user therefore needs Nova's admin rules for all_tenants listing and the extended attributes (OS-EXT-SRV-ATTR:*).
  • keystone.ec2.list(user_id) (keystoneclient v3 /v3/users/{id}/credentials/OS-EC2) with the user's token, to sign S3 calls with the user's first EC2 credential (ec2api/api/image.py:1072).

3. Nova

Version negotiation (ec2api/clients.py:102), once per process: GET the compute endpoint's version document; if the current version is v2.1, use microversion 2.32 when supported, else the maximum supported; a non-2.1 endpoint uses legacy 2. 2.3 brings the OS-EXT-SRV-ATTR fields, 2.10 the user_id keypair lookup, 2.32 BDM tags (ec2api/clients.py:52).

CallRESTActions
servers.create(name, image, flavor, min_count=1, max_count=1, availability_zone, block_device_mapping_v2, key_name, userdata, security_groups, nics)POST /serversRunInstances (ec2api/api/instance.py:147)
servers.update(server, name=<i-id>)PUT /servers/{id}RunInstances (ec2api/api/instance.py:170)
servers.delete / server.delete()DELETE /servers/{id}RunInstances rollback, TerminateInstances (ec2api/api/instance.py:204)
servers.getGET /servers/{id}most instance actions, route states (ec2api/api/route_table.py:493), metadata
servers.list(search_opts={'all_tenants': True, 'project_id': p}) [admin]GET /servers/detail?all_tenants=True&project_id=pDescribeInstances, DescribeVolumes
server.reboot() / stop() / start()POST /servers/{id}/action reboot (SOFT) / os-stop / os-startReboot/Stop/StartInstances (ec2api/api/instance.py:445)
server.resize(flavor), confirm_resize()POST …/action resize, confirmResizeModifyInstanceAttribute instanceType (ec2api/api/instance.py:683)
server.get_console_output() / get_password()os-getConsoleOutput / GET /servers/{id}/os-server-passwordGetConsoleOutput, GetPasswordData
server.create_image(name, metadata)POST …/action createImageCreateImage (ec2api/api/image.py:154)
flavors.list() / flavors.get(id)GET /flavors/detailRunInstances, DescribeInstances, attribute instanceType
keypairs.list/create/delete, keypairs._get('/os-keypairs/<name>?user_id=<id>')/os-keypairskey pairs; metadata public key (ec2api/metadata/api.py:221)
availability_zones.list(detailed)GET /os-availability-zone[/detail]DescribeAvailabilityZones
quotas.get(project, user)GET /os-quota-sets/{p}?user_id=uDescribeAccountAttributes
volumes.create_server_volume / delete_server_volumePOST/DELETE /servers/{id}/os-volume_attachmentsAttachVolume, DetachVolume
servers.interface_attach(server, port, None, None)POST /servers/{id}/os-interfaceAttachNetworkInterface (ec2api/api/network_interface.py:468)
servers.add_floating_ip / remove_floating_ipaddFloatingIp / removeFloatingIp actions — removed from Nova in microversion 2.44EC2-Classic Associate/DisassociateAddress (ec2api/api/address.py:321, ec2api/api/address.py:410) [inferred: works only because 2.32 is pinned]

4. Neutron

CallRESTActions
create_router({'router': {}}), update_router(name), delete_router/v2.0/routersCreate/DeleteVpc
add_gateway_router(router, {'network_id': ext}), remove_gateway_routerPUT /v2.0/routers/{id} external_gateway_infoAttach/DetachInternetGateway, Attach/DetachVpnGateway
add_interface_router / remove_interface_router({'subnet_id'})PUT /v2.0/routers/{id}/add_router_interfaceCreate/DeleteSubnet
create_network({'network': {'name': 'subnet-0'}}), update_network(name), delete_network/v2.0/networksCreate/DeleteSubnet
create_subnet({network_id, ip_version 4, cidr, host_routes}), update_subnet(name, gateway_ip, host_routes), show_subnet, list_subnets/v2.0/subnetssubnets, every route-table change (ec2api/api/route_table.py:576)
create_port({network_id, security_groups, fixed_ips}), update_port(name / fixed_ips / security_groups / allowed_address_pairs / extra_dhcp_opts / device_id='' device_owner=''), delete_port, show_port, list_ports(tenant_id)/v2.0/portsnetwork interfaces, RunInstances (VPC), DHCP options, addresses
list_networks(router:external=True, name=external_network)/v2.0/networksexternal network (ec2api/api/ec2utils.py:459)
create_floatingip({floating_network_id}), update_floatingip(port_id, fixed_ip_address), delete_floatingip, show_floatingip, list_floatingips(tenant_id)/v2.0/floatingipselastic IPs
create_security_group(name, description), delete_security_group, show_security_group, list_security_groups(tenant_id), create_security_group_rule, delete_security_group_rule/v2.0/security-groups, /v2.0/security-group-rulessecurity groups
create/update/delete_vpnservice, create/update/delete_ikepolicy, create/update/delete_ipsecpolicy, create/update/delete_ipsec_site_connection, list_ikepolicies/ipsecpolicies/ipsec_site_connections/routers(tenant_id)/v2.0/vpn/* (VPNaaS)VPN gateways and connections
list_subnets(advanced_service_providers=[id]), list_ports(fixed_ips='ip_address=…', network_id) [admin]/v2.0/subnets, /v2.0/portsNSX metadata requester lookup (ec2api/metadata/api.py:73)

tenant_id=<project> filters are passed on most list calls; Neutron error classes are mapped to EC2 codes at the call sites listed in 02 § 3.1.

5. Cinder and Glance

  • Cinder (ec2api/clients.py:86): the API version is deduced from the endpoint URL (get_volume_api_from_url). Calls: volumes.create(size, snapshot_id, volume_type, availability_zone), volume.update(display_name), volumes.get/list/delete, volume_snapshots.create(volume_id, force=True), snapshot.update(display_name, display_description), volume_snapshots.get/list/delete.
  • Glance v2 (ec2api/clients.py:81): images.list(), images.get, images.create(**properties), images.upload(id, data, image_size), images.image_import(id, method='web-download', uri), images.update(id, visibility | image_state), images.delete. Image properties written by ec2-api: name, image_location, root_device_name, bdm_v2, block_device_mapping (JSON string), mappings (S3 manifests), architecture, kernel_id, ramdisk_id, image_state, ec2_id (Nova createImage metadata) (ec2api/api/image.py:221, ec2api/api/image.py:155).

6. S3 (image bundles)

_s3_conn (ec2api/api/image.py:1070): botocore S3 client on s3_url, region s3_region, signature s3v4, keys = access/secret of the first EC2 credential Keystone lists for the user. get_object(Bucket, Key) for the manifest and each part (ec2api/api/image.py:863, ec2api/api/image.py:1013). ClientErrors become EC2 errors (ec2api/api/__init__.py:377).

7. Metadata service exchanges

  • Inbound from the Neutron metadata agent: GET /latest/meta-data/… with X-Instance-ID, X-Tenant-ID, X-Forwarded-For, X-Instance-ID-Signature (ec2api/metadata/__init__.py:202).
  • Outbound to Nova metadata for /openstack/…: the same path and query, headers X-Forwarded-For, X-Instance-ID, X-Tenant-ID, X-Instance-ID-Signature (recomputed), optional client certificate (ec2api/metadata/__init__.py:173).
  • Cache: GET/SET ec2api-metadata-<instance uuid> holding {metadata, owner_id}, expiration cache_expiration (ec2api/metadata/api.py:102). Without [cache] enabled, a warning is logged and every request rebuilds the metadata (one DescribeInstances plus Nova calls) (ec2api/metadata/__init__.py:83).

8. Local processes and files

  • openssl enc -d -aes-128-cbc -in <file> -K <key> -iv <iv> -out <file> (ec2api/api/image.py:1037); key and IV decrypted with the RSA private key x509_root_private_key (PKCS#1 v1.5, ec2api/api/image.py:1091). The key appears on the command line of the child process.
  • Temporary directories under image_decryption_dir (default /tmp) for S3 imports (ec2api/api/image.py:891); bucket directories under buckets_path (ec2api/s3/s3server.py:52).

Source: reverse-spec-ec2-api/03-interactions_17.0.0.md. The _rewrite/ and _inventory/ files are not published on the site.