Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Steps Down the OpenAPI Path

Steps Down the OpenAPI Path

OpenStack has been on a journey towards implementing formal OpenAPI v3.1 API specifications for all services for a number of releases now. We have many goals - better understanding our APIs, improving our documentation, easing client and library development, etc. - and we are finally delivering on many of these. Come learn about the milestones we’ve reached to date, and the benefits we’re already seeing from these efforts, and our plans for the next . We’ll talk about the complexities of implementing common functionality across services with vastly different APIs and stacks, the cool/horrifying things we’ve (re)discovered about our APIs, and the oxidation of our clients. We’ll also demonstrate some of the fruits of our labours and discuss how you, as a user, deployer, or contributor, can get involved in the effort.

Avatar for Stephen Finucane

Stephen Finucane

October 18, 2025
Tweet

More Decks by Stephen Finucane

Other Decks in Programming

Transcript

  1. Steps down the OpenAPI Path An up-to-date history of OpenAPI

    in OpenStack OpenInfra Summit Europe ‘25 @stephenfin (Stephen Finucane) @gtema (Artem Goncharov)
  2. Writing & Maintaining SDKs is hard work There are so

    many services There are so many APIs 🎶 A Good Song Bad API Never Dies 🎶 …
  3. Other issues OpenStackClient (and SDK) can be slooowwww… 🦀 Completeness

    of existing SDKs New languages, new environments Obsolete web frameworks and libraries
  4. Superset of JSONSchema (draft 2020-12) Support for extensions Schemas given

    in JSON or YAML Support in multiple languages (validation, doc generation, …)
  5. openapi: "3.1.0" info: version: "1.0.0" title: "Swagger Petstore" license: name:

    "MIT" servers: - url: "http://petstore.swagger.io/v1" paths: /pets: get: # ... petstore.yaml
  6. # ... paths: /pets: get: summary: List all pets operationId:

    listPets tags: - pets parameters: - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer maximum: 100 format: int32 # ... petstore.yaml
  7. # ... paths: /pets: get: # ... responses: '200': description:

    A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/Pets" # ... petstore.yaml
  8. Problems api-ref documentation is not machine-readable api-ref documentation can be

    incomplete/outdated api-ref is reStructuredText Verification is difficult Schemas live in another project
  9. Superset of JSONSchema (draft 2020-12) Support for extensions Schemas given

    in JSON or YAML Support in multiple languages (validation, doc generation, …)
  10. Superset of JSONSchema (draft 2020-12) Support for extensions Schemas given

    in JSON or YAML Support in multiple languages (validation, doc generation, …)
  11. Superset of JSONSchema (draft 2020-12) Support for extensions 🌟🌟 Tooling

    support 🌟🌟 Schemas given in JSON or YAML Support in multiple languages (validation, doc generation, …)
  12. class FlavorAccessController(wsgi.Controller): """The flavor access API controller for the OpenStack

    API.""" @wsgi.expected_errors(404) @validation.query_schema(schema.index_query) def index(self, req, flavor_id): context = req.environ['nova.context'] context.can(fa_policies.BASE_POLICY_NAME) flavor = common.get_flavor(context, flavor_id) # public flavor to all projects if flavor.is_public: explanation = _("...") raise webob.exc.HTTPNotFound(explanation=explanation) # private flavor to listed projects only return _marshall_flavor_access(flavor) nova/api/openstack/compute/flavor_access.py
  13. class FlavorAccessController(wsgi.Controller): """The flavor access API controller for the OpenStack

    API.""" @wsgi.expected_errors(404) @validation.query_schema(schema.index_query) def index(self, req, flavor_id): context = req.environ['nova.context'] context.can(fa_policies.BASE_POLICY_NAME) flavor = common.get_flavor(context, flavor_id) # public flavor to all projects if flavor.is_public: explanation = _("...") raise webob.exc.HTTPNotFound(explanation=explanation) # private flavor to listed projects only return _marshall_flavor_access(flavor) nova/api/openstack/compute/flavor_access.py
  14. class FlavorAccessController(wsgi.Controller): """The flavor access API controller for the OpenStack

    API.""" @wsgi.expected_errors(404) @validation.query_schema(schema.index_query) @validation.response_body_schema(schema.index_response) def index(self, req, flavor_id): context = req.environ['nova.context'] context.can(fa_policies.BASE_POLICY_NAME) flavor = common.get_flavor(context, flavor_id) # public flavor to all projects if flavor.is_public: explanation = _("...") raise webob.exc.HTTPNotFound(explanation=explanation) # private flavor to listed projects only return _marshall_flavor_access(flavor) nova/api/openstack/compute/flavor_access.py
  15. from nova.api.validation import parameter_types # ... index_response = { 'type':

    'object', 'properties': { # NOTE(stephenfin): While we accept numbers as values, we always # return strings 'extra_specs': parameter_types.metadata, }, 'required': ['extra_specs'], 'additionalProperties': False, } # ... nova/api/openstack/compute/schemas/flavor_access.py
  16. @wsgi.Controller.api_version(RESOURCE_LOCKS_MIN_API_VERSION) @wsgi.Controller.authorize('get_all') @validation.request_query_schema(schema.index_request_query) @validation.response_body_schema(schema.index_response_body) def index(self, req): """Returns a list

    of locks, transformed through view builder.""" context = req.environ['manila.context'] filters = req.params.copy() params = common.get_pagination_params(req) limit, offset = [ params.pop('limit', None), params.pop('offset', None) ] sort_key, sort_dir = common.get_sort_params(filters) for key in ('limit', 'offset'): filters.pop(key, None) # ... manila/api/v2/resource_locks.py
  17. @wsgi.Controller.api_version(RESOURCE_LOCKS_MIN_API_VERSION) @wsgi.Controller.authorize('get_all') @validation.request_query_schema(schema.index_request_query) @validation.response_body_schema(schema.index_response_body) def index(self, req): """Returns a list

    of locks, transformed through view builder.""" context = req.environ['manila.context'] filters = req.params.copy() params = common.get_pagination_params(req) limit, offset = [ params.pop('limit', None), params.pop('offset', None) ] sort_key, sort_dir = common.get_sort_params(filters) for key in ('limit', 'offset'): filters.pop(key, None) # ... manila/api/v2/resource_locks.py
  18. from manila.api.validation import helpers create_request_body = { 'type': 'object', 'properties':

    { 'resource_lock': { 'type': 'object', 'properties': { 'resource_id': { 'type': 'string', 'format': 'uuid', 'description': helpers.description( 'resource_lock_resource_id' ), }, }, }, }, } manila/api/schemas/resource_locks.py
  19. from manila.api.validation import helpers create_request_body = { 'type': 'object', 'properties':

    { 'resource_lock': { 'type': 'object', 'properties': { 'resource_id': { 'type': 'string', 'format': 'uuid', 'description': helpers.description( 'resource_lock_resource_id' ), }, }, }, }, } manila/api/schemas/resource_locks.py
  20. Nova ✅⭐ Keystone ✅⭐ Manila 📝⭐ Ironic 📝⭐ Cinder 📝⭐

    Neutron ✅* Designate 📝 Octavia ✅* Magnum 📝 Swift ✅* Glance 📝 Barbican 📝
  21. # ... paths: /v2.1/flavors/{flavor_id}/os-flavor-access: parameters: - $ref: '#/components/parameters/flavors_os_flavor_access_flavor_id' get: operationId:

    flavors/flavor_id/os-flavor-access:get responses: '404': description: Error '200': description: Ok content: application/json: schema: $ref: '#/components/schemas/FlavorsOs_Flavor_AccessListResponse' openapi_specs/compute/v2.96.yaml (generated file)
  22. # ... components: schemas: FlavorsOs_Flavor_AccessListResponse: type: object properties: flavor_access: type:

    array items: type: object properties: flavor_id: type: string format: uuid tenant_id: type: string format: uuid openapi_specs/compute/v2.96.yaml (generated file with field omitted)
  23. What does it do? Generates OpenAPI schemas Generate bindings in

    chosen language Generates CLI and TUI (and SDK)
  24. resources: network.security_group_rule: api_version: v2 operations: create: operation_id: security-group-rules:post operation_type: create

    targets: rust-cli: cli_full_command: security-group-rule create module_name: create sdk_mod_name: create rust-sdk: module_name: create rust-tui: module_name: create ... spec_file: wrk/openapi_specs/network/v2.yaml codegenerator/src/metadata/network_metadata.yaml
  25. Recipe: 🔪 OpenAPI converted to ADT 🥗ADT converted to target

    specific types (pydantic 🫣) ♨ Jinja2 templates 󰞫🎛 => 🥧 7k+ files
  26. Get final Nova patches merged in Gazpacho Continue with Ironic,

    Cinder etc. Flesh out OpenAPI Sphinx extension Prepare for OpenAPI Moonwalk (4.0)
  27. Want to get involved? #openstack-sdk on OFTC IRC ↗ [email protected]

    ↗ Catch us in person 🙋 (We don’t bite)
  28. Steps down the OpenAPI Path An up-to-date history of OpenAPI

    in OpenStack OpenInfra Summit Europe ‘25 @stephenfin (Stephen Finucane) @gtema (Artem Goncharov)