Merge "Add document describing consuming version discovery"

This commit is contained in:
Zuul 2020-06-25 16:17:12 +00:00 committed by Gerrit Code Review
commit 8795075d59
6 changed files with 1783 additions and 471 deletions

View File

@ -22,9 +22,18 @@ from the Service Catalog.
.. note:: The use of the word "object" in this document refers to a JSON
object, not an Object from any particular programming language.
.. _catalog-user-request:
User Request
============
.. note:: It is worth noting that 'user' is a maleable concept. For instance,
the shade library performs service discovery on behalf of its users
so does not expect its users to provide a 'service-type'. In that
case, shade is the 'user' of the keystoneauth library which is the
discovery implementation. It is definitely not required that all
consumers of OpenStack clouds know all of these things.
The ultimate goal of this process is for a user to find the information about
an endpoint for a service given some inputs. The user will start the process
knowing some number of these parameters. Each additional input expected from
@ -35,69 +44,192 @@ in helping the user ask the right question.
.. note:: Be liberal with what you accept and strict with what you emit.
There is one piece of information that is absolutely required that the
user know:
The following is a list of such pieces of information that can be provided
as user input. When an implementation exposes the ability for a user to
express these parameters it is **STRONGLY** recommended that these names
be used, as they show up across the OpenStack ecosystem and make discussion
easier.
service-type
It is assumed that the user has an ``{auth-url}`` and authentication
information. The authentication process itself is out of the scope of this
document.
Required Inputs
---------------
There is one piece of information that is absolutely required that the
user know.
``service-type``
The official name of the service, such as ``compute``, ``image`` or
``block-storage`` as listed in the `OpenStack Service Types Authority`_.
``block-storage`` as listed in the :doc:`OpenStack Service Types Authority
<consuming-catalog/authority>`.
Required. It is impossible for a user to consume service discovery without
knowing what service they want to discover.
The user may also wish to express an alteration to the general algorithm:
be-strict
Forgo lenient backwards compatibility concessions and be more strict in
input and output validation.
Optional Filters
----------------
There are several optional pieces of information that the user might know,
or additional constraints the user might wish to express.
or additional constraints the user might wish to express to control how the
endpoints for a service are selected.
region-name
``region-name``
The region of the service the user desires to work with. May be optional,
depending on whether the cloud has more than one region. Services
all exist within regions, but some clouds only have one region.
If ``{be-strict}`` has been given, ``{region-name}`` is required.
If ``{be-strict}`` (see below) has been given, ``{region-name}`` is required.
.. note:: It is highly recommended that ``{region-name}`` always be required
to protect against single-region clouds adding a region in the
future. However, keystoneauth today allows region-name to be omitted
and there are a large number of clouds in existence with a single
region named ``RegionOne``. For completely new libraries or major
versions where breaking behavior is acceptable, requiring region-name
by default would be preferred.
.. note:: It is highly recommended that ``{region-name}`` always be required
to protect against single-region clouds adding a region in the
future. However, the canonical OpenStack implementation
*keystoneauth* today allows region name to be omitted and there are
a large number of clouds in existence with a single region named
``RegionOne``. For completely new libraries or major versions
where breaking behavior is acceptable, requiring region name
by default would be preferred, but breaking users just to introduce
the restriction is discouraged.
interface
Which API interface, such as ``public``, ``internal``, or ``admin``
the user wants to use. A user can also request a list of interfaces they find
acceptable in the order of their preference, such as
``['internal', 'public']`` (Optional, defaults to ``public``.)
``interface``
Which API interface, such as ``public``, ``internal`` or ``admin``, that
the user wants to use. A user should be able to request a list of interfaces
they find acceptable in the order of their preference, such as
``['internal', 'public']`` (Optional, defaults to ``public``)
service-name
``endpoint-version`` OR ``min-endpoint-version``, ``max-endpoint-version``
The **major** version of the service the user desires to work with. Optional.
An endpoint version is inherently a range with a minimum and a maximum value.
Whether it is presented to the user as a single parameter or a pair of
parameters is an implementation detail.
Each endpoint version is a string with one (``3``) or two (``3.1``) numbers,
separated by a dot.
.. warning:: Care has to be taken to not confuse major versions consisting
of two numbers with microversions. Microversions usually exist
within a certain major version, and also have a form of ``X.Y``.
No services currently use both major versions and microversions
in the form of ``X.Y``.
.. TODO(dtantsur): so, what if a service has both major versions in the form
of ``X.Y`` and microversions?
Version strings are not decimals, the are a tuple of 2 numbers combined with
a dot. Therefore, ``3.10`` is higher than ``3.9``.
A user can omit the endpoint-version indicating that they want to use
whatever endpoint is in the ``{service-catalog}``.
A user can desire to work with the latest available version, in which
case the ``{endpoint-version}`` should be ``latest``. If s
``{min-endpoint-version}`` is ``latest``, ``{max-endpoint-version}`` must be
omitted or also ``latest``.
A version can be specified with a minor value of ``latest`` to indicate
the highest minor version of a given major version. For instance,
``3.latest`` would match the highest of ``3.3`` and ``3.4`` but not ``4.0``.
If the parameter is presented as a single string, a single value should be
interpreted as if ``{min-endpoint-version}`` is the value given and
``{max-endpoint-version}`` is ``MAJOR.latest``. For instance, if ``3.4`` is
given as a single value, ``{min-endpoint-version}`` is ``3.4`` and
``{max-endpoint-version}`` is ``3.latest``.
It may seem strange from an individual user perspective to want a range or
``latest`` - but from a library and framework perspective, things like shade
or terraform may have internal logic that can handle more than one version of
a service and want to use the best version available.
.. note:: Guidance around 'latest' is different from that found in
:ref:`the microversion specification
<microversion-client-interaction>`. It is acceptable for a client
library or framework to be interested in the latest version
available but such a specification is internal and not sent to
the server. In the client case with major versions, ``latest`` acts
as an input to the version discovery process.
``service-name``
Arbitrary name given to the service by the deployer. Optional.
.. note:: In all except the most extreme cases this should never be needed and
its use as a meaningful identifier by Deployers is strongly
discouraged. However, the Consumer has no way to otherwise mitigate
the situation if their Deployer has provided them with a catalog
where a ``service-name`` must be used, so ``service-name`` must be
accepted as input. If ``{be-strict}`` has been requested,
supplying ``{service-name}`` should be an error.
.. note:: In all except the most extreme cases this should never be needed
and its use as a meaningful identifier by Deployers is strongly
discouraged. However, the Consumer has no way to otherwise mitigate
the situation if their Deployer has provided them with a catalog
where a ``service-name`` must be used, so ``service-name`` must be
accepted as input.
If ``{be-strict}`` (see below) has been requested, a user supplying
``{service-name}`` should be an error.
service-id
``service-id``
Unique identifier for an endpoint in the catalog. Optional.
.. note:: On clouds with well-formed catalogs ``service-id`` should never be
needed. If ``{be-strict}`` has been requested, supplying
``{service-id}`` should be an error.
.. note:: On clouds with well-formed catalogs ``service-id`` should never be
needed. If ``{be-strict}`` has been requested, supplying
``{service-id}`` should be an error.
endpoint-override
``endpoint-override``
An endpoint for the service that the user has procured from some other
source. (Optional, defaults to omitted.)
At the end of the discovery process, the user should know the
``{service-endpoint}``, which is the endpoint to use as the root of the
service, and the ``{interface}`` of the endpoint that was found.
Discovery Behavior Modifiers
----------------------------
The user may also wish to express alterations to the general algorithm.
Implementations may present these flags under any name that makes sense,
or may choose to not present them as behavioral modification options at all.
``be-strict``
Forgo leniant backwards compatibility concessions and be more strict in
input and output validation. Defaults to False.
``skip-discovery``
If the user wants to completely skip the version discovery process even if
logic would otherwise do it. This is useful if the user has specified an
``{endpoint-override}`` or they know they just want to use whatever is in
the catalog and do not need additional metadata about the endpoint. Defaults
to False
``fetch-version-information``
If the user has specified an ``{endpoint-version}`` which can be known to
match just from looking at the URL, the version discovery process will not
fetch version information documents. However, the user may need the
information, such as microversion ranges. Using
``{fetch-version-information}`` allows them to request that the version
document be fetched even when an optimization in the process would otherwise
allow fetching the document to be skipped. Defaults to False.
Discovery Results
=================
At the end of the discovery process, the user should know the following:
If the process was successful:
* The actual values found for all of the input values above.
Found values will be referred to in these documents as ``found-{value}`` to
differentiate. So if a user requested an ``{endpoint-version}`` of
``latest``, ``{found-endpoint-version}`` might be ``3.5``.
* ``service-endpoint``
The endpoint to use as the root of the service.
* ``max-version``
If the service supports microversions, what is the maximum microversion the
service supports. Optional, defaults to omitted, which implies that
microversions are not supported.
* ``min-version``
If the service supports microversions, what is the minimum microversion the
service supports. Optional, defaults to omitted, which implies that
microversions are not supported.
If the process was unsuccessful, an error should be returned explaining which
part failed. For instance, was a matching service not found at all or was
a matching version not found. If a matching version was not found, the error
should contain a list of version that were found.
In the description that follows, each of the above inputs and outputs will
be referred to like ``{endpoint-override}`` so that it is clear whether a user
@ -107,459 +239,54 @@ referred to at a later point are similarly referred to like
``{service-catalog}``. Names will not be reused within the process to
hold different content at different times.
It is also assumed that the user has an ``{auth-url}`` and authentication
information. The authentication process itself is out of the scope of this
document.
Discovery Algorithm
===================
Services should be registered in the ``{service-catalog}`` using their
``{service-type}`` from the `OpenStack Service Types Authority`_. However,
for historical reasons there are some services that have old service types
found in the wild. To facilitate moving forward with the correct
``{service-type}`` names, but also support existing users and installations,
the `OpenStack Service Types Authority`_ contains a list of historical
aliases for such services. See `Consuming Service Types Authority`_ for
information on the data itself.
``{service-type}`` from the :doc:`OpenStack Service Types Authority
<consuming-catalog/authority>`. However, for historical reasons there are some
services that have old service types found in the wild. To facilitate moving
forward with the correct ``{service-type}`` names, but also support existing
users and installations, the OpenStack Service Types Authority contains a list
of historical aliases for such services.
Clients will need a copy of the data published in the
`OpenStack Service Types Authority`_ to be able to complete the full Discovery
OpenStack Service Types Authority to be able to complete the full Discovery
Algorithm. A client library could either keep a local copy or fetch the data
from https://service-types.openstack.org/service-types.json and potentially
cache it. It is recommended that client libraries handle consumption of the
historical data for their users but also allow some mechanism for the user to
provide a more up to date version of the data if necessary. See
`Consuming Service Types Authority`_ for information on how to fetch the data.
provide a more up to date verison of the data if necessary.
The basic process is:
#. If the user has provided ``{endpoint-override}``, STOP. This is the
``{service-endpoint}``.
#. Authenticate to keystone at the ``{auth-url}``, retreiving a ``token``
which contains the ``{service-catalog}``.
#. Retrieve ``{catalog-endpoint}`` from the ``{service-catalog}`` given
some combination of ``{service-type}``, ``{interface}``, ``{service-name}``,
``{region-name}`` and ``{service-id}``. (See :ref:`endpoint-from-catalog`.)
.. note:: This step is obviously skipped for clouds without authentication.
.. _endpoint-from-catalog:
#. If the user has provided ``{endpoint-override}``, it is used as
``{catalog-endpoint}``.
Endpoint from Catalog
=====================
#. If the user has not provided ``{endpoint-override}``, retrieve matching
``{catalog-endpoint}`` from the ``{service-catalog}`` using the procedure
explained in :doc:`consuming-catalog/endpoint`.
The ``{service-catalog}`` can be found in the ``token`` returned from
keystone authentication.
If v3 auth is used, the catalog will be in the ``catalog`` property of the
top-level ``token`` object. Such as:
.. code-block:: json
{
"token": {
"catalog": {}
}
}
If v2 auth is used it will be in the ``serviceCatalog`` property of the
top-level ``access`` object. Such as:
.. code-block:: json
{
"access": {
"serviceCatalog": {}
}
}
In both cases, the catalog content itself is a list of objects. Each object has
two main keys that concern discovery:
type
Matches ``{service-type}``
endpoints
List of endpoint objects for that service
Additionally, for backwards compatibility reasons, the following keys may
need to be checked.
name
Matches ``{service-name}``
id
Matches ``{service-id}``
The list of endpoints has a different format depending on whether v2 or v3 auth
was used. For both versions each endpoint object has a ``region`` key,
which should match ``{region-name}`` if one was given.
In v2 auth the endpoint object has three keys ``publicURL``,
``internalURL``, ``adminURL``. The endpoint for the ``{interface}`` requested
by the user is found in the key with the name matching ``{interface}`` plus
the string ``URL``.
In v3 auth the endpoint object has a ``url`` that is the endpoint that is
being requested if the value of ``interface`` matches ``{interface}``.
Concrete examples of tokens with catalogs:
V3 Catalog Objects:
.. code-block:: json
{
"token": {
"catalog": [
{
"endpoints": [
{
"id": "39dc322ce86c4111b4f06c2eeae0841b",
"interface": "public",
"region": "RegionOne",
"url": "https://identity.example.com"
},
{
"id": "ec642f27474842e78bf059f6c48f4e99",
"interface": "internal",
"region": "RegionOne",
"url": "https://identity.example.com"
},
{
"id": "c609fc430175452290b62a4242e8a7e8",
"interface": "admin",
"region": "RegionOne",
"url": "https://identity.example.com"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa2",
"type": "identity",
"name": "keystone"
}
],
}
V2 Catalog Objects:
.. code-block:: json
{
"access": {
"serviceCatalog": [
{
"endpoints_links": [],
"endpoints": [
{
"adminURL": "https://identity.example.com/v2.0",
"region": "RegionOne",
"publicURL": "https://identity.example.com/v2.0",
"internalURL": "https://identity.example.com/v2.0",
"id": "4deb4d0504a044a395d4480741ba628c"
}
],
"type": "identity",
"name": "keystone"
},
]
}
}
The algorithm is:
#. Find the objects in the ``{service-catalog}`` that match the requested
``{service-type}``. (See `Match Candidate Entries`_.)
#. If ``{service-name}`` was given and the objects remaining have a ``name``
field, keep only the ones where ``name`` matches ``{service-name}``.
.. note:: Catalogs from Keystone v3 before v3.3 do not have a name field. If
``{be-strict}`` was not requested and the catalog does not have a
``name`` field, ``{service-name}`` should be ignored.
#. If ``{service-id}`` was given and the objects remaining have a ``id``
field, keep only the ones where ``id`` matches ``{service-id}``.
.. note:: Catalogs from Keystone v2 do not have an id field. If
``{be-strict}`` was not requested and the catalog does not have a
``id`` field, ``{service-id}`` should be ignored.
The list of remaining objects are the ``{candidate-catalog-objects}``. If there
are no endpoints, return an error that there are no endpoints matching
``{service-type}`` and ``{service-name}``.
Use ``{candidate-catalog-objects}`` to produce the list of
``{candidate-endpoints}``.
For each endpoint object in each of the ``{candidate-catalog-objects}``:
#. If v2, if there is no key of the form ``{interface}URL`` for any of the
the ``{interface}`` values given, discard the endpoint.
#. If v3, if ``interface`` does not match any of the ``{interface}`` values
given, discard the endpoint.
If there are no endpoints left, return an error that there are no endpoints
matching any of the ``{interface}`` values, preferrably including the list of
interfaces that were found.
For each remaining endpoint in ``{candidate-endpoints}``:
#. If ``{region_name}`` was given and does not match either of ``region``
or ``region_id``, discard the endpoint.
If there are no remaining endpoints, return an error that there are no
endpoints matching ``{region_name}``, preferrably including the list of
regions that were found.
#. From the set of remaining candidate endpoints, find the ones that best
matches the requested ``{service-type}``.
(See `Find Endpoint Matching Best Service Type`_.)
The remaining ``{candidate-endpoints}`` match the request. If there is more
than one of them, use the first, but emit a warning to the user that more
than one endpoint was left. If ``{be-strict}`` has been requested, return an
error instead with information about each of the endpoints left in the list.
.. note:: It would be more correct to raise an error if there is more than one
endpoint left, but the keystoneauth library returns the first and
changing that would break a large number of existing users. If one
is writing a completely new library from scratch, or a new major
version where behavior change is acceptable, it is preferable to
raise an error here if there is more than one endpoint left.
#. If v2, the ``{catalog-endpoint}`` is the value of ``{interface}URL``.
#. If v3, the ``{catalog-endpoint}`` is the value of ``url``.
Match Candidate Entries
-----------------------
For every entry in the catalog:
#. If the entry's type matches the requested ``{service-type}``, it is a
candidate.
#. If the requested type is an official type from the
`OpenStack Service Types Authority`_ that has aliases and one of the aliases
matches the entry's type, it is a candidate.
#. If the requested type is an alias of an official type from the
`OpenStack Service Types Authority`_ and the entry's type matches the
official type, it is a candidate.
.. note:: Requesting one alias and finding a different alias is not supported
at this point because most aliases carry implied information about
major versions as well. A subsequent spec adds the process for
version discovery at which point it can be safe to attempt to return
an endpoint listed under an alias different than what was requested.
Find Endpoint Matching Best Service Type
----------------------------------------
Given a list of candidate endpoints that have matched the other criteria:
#. Check the list of candidate endpoints to see if one of them matches the
requested ``{service-type}``. If any are an exact match,
`Find Endpoint Matching Best Interface`_.
#. If the requested ``{service-type}`` is an official type in the
`OpenStack Service Types Authority`_ that has aliases, check each alias
in order of preference as listed in the Authority to see if it has a
matching endpoint from the candidate endpoints. For all endpoints that
match the first alias with matching endpoints,
`Find Endpoint Matching Best Interface`_.
#. If ``{skip-discovery}`` is true, STOP and use ``{catalog-endpoint}`` as
``{service-endpoint}``. Otherwise, discover the available API versions
and find the suitable ``{service-endpoint}`` using the version discovery
procedure from :doc:`consuming-catalog/version-discovery`.
#. If the requested ``{service-type}`` is an alias of an official type in the
`OpenStack Service Types Authority`_ and any endpoints match the official
type, `Find Endpoint Matching Best Interface`_.
OpenStack Service Types Authority and any endpoints match the official
type, :ref:`find-endpoint-matching-best-service-type`.
Find Endpoint Matching Best Interface
-------------------------------------
Given a list of candidate endpoints that have matched the other criteria:
Table of Contents
=================
#. In order of preference of ``{interface}`` list, return all endpoints that
match the first ``{interface}`` with matching endpoints.
.. toctree::
For example, given the following catalog:
.. code-block:: json
{
"token": {
"catalog": [
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com/v3"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa3",
"type": "volumev3",
"name": "cinder"
},
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com/v2"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa2",
"type": "volumev2",
"name": "cinder"
}
],
}
Then the following:
::
service_type = 'block-storage'
# block-storage is not found, get list of aliases
# volumev3 is found, return it
service_type = 'volumev2'
# volumev2 not an official type in authority, but is in catalog
# return volumev2 entry
service_type = 'volume'
# volume not in authority or catalog
# volume is an alias of block-storage
# block-storage is not found. Return error.
Given the following catalog:
.. code-block:: json
{
"token": {
"catalog": [
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa3",
"type": "block-storage",
"name": "cinder"
}
],
}
Then the following:
::
service_type = 'block-storage'
# block-storage is found, return it
service_type = 'volumev2'
# volumev2 not in authority, is an alias for block-storage
# block-storage is in the catalog, return it
Given the following catalog:
.. code-block:: json
{
"token": {
"catalog": [
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa3",
"type": "block-storage",
"name": "cinder"
},
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com/v2"
},
{
"interface": "internal",
"region": "RegionOne",
"url": "https://block-storage.example.int/v2"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa2",
"type": "volumev2",
"name": "cinder"
}
],
}
Then the following:
::
service_type = 'block-storage'
interface = ['internal', 'public']
# block-storage is found
# block-storage does not have internal, but has public
# return block-storage public
service_type = 'volumev2'
interface = ['internal', 'public']
# volumev2 not an official type in authority, but is in catalog
# volumev2 has an internal interface
# return volumev2 internal entry
Consuming Service Types Authority
=================================
The `OpenStack Service Types Authority`_ is data about official service type
names and historical service type names commonly in use from before there was
an official list. It is made available to allow libraries and other client
API consumers to be able to provide a consistent interface based on the
official list but still support existing names. Providing this support is
highly recommended, but is ultimately optional. The first step in the matching
process is always to return direct matches between the catalog and the user
request, so the existing consumption models from before the existence of the
authority should always work.
In order to consume the information in the `OpenStack Service Types Authority`_
it is important to know a few things:
#. The data is maintained in YAML format in git. This is the ultimately
authoritative source code for the list.
#. The data is published in JSON format at
https://service-types.openstack.org/service-types.json and has a JSONSchema
at https://service-types.openstack.org/published-schema.json.
#. The published data contains a version which is date based in
`ISO Date Time Format`_, a sha which contains the git sha of the
commit the published data was built from, and pre-built forward and reverse
mappings between official types and aliases.
#. The JSON file is served with ETag support and should be considered highly
cacheable.
#. The current version of the JSON file should always be the preferred file to
use.
#. The JSON file is similar to timezone data. It should not be considered
versioned such that stable releases of distros should provide a
frozen version of it. Distro packages should instead update for all
active releases when a new version of the file is published.
.. _OpenStack Service Types Authority: https://opendev.org/openstack/service-types-authority/
.. _ISO Date Time Format: https://tools.ietf.org/html/rfc3339#section-5.6
consuming-catalog/endpoint
consuming-catalog/version-discovery
consuming-catalog/authority

View File

@ -0,0 +1,42 @@
Consuming Service Types Authority
=================================
The `OpenStack Service Types Authority`_ is data about official service type
names and historical service type names commonly in use from before there was
an official list. It is made available to allow libraries and other client
API consumers to be able to provide a consistent interface based on the
official list but still support existing names. Providing this support is
highly recommended, but is ultimately optional. The first step in the matching
process is always to return direct matches between the catalog and the user
request, so the existing consumption models from before the existence of the
authority should always work.
In order to consume the information in the `OpenStack Service Types Authority`_
it is important to know a few things:
#. The data is maintained in YAML format in git. This is the ultimately
authoritative source code for the list.
#. The data is published in JSON format at
https://service-types.openstack.org/service-types.json and has a JSONSchema
at https://service-types.openstack.org/published-schema.json.
#. The published data contains a version which is date based in
`ISO Date Time Format`_, a sha which contains the git sha of the
commit the published data was built from, and pre-built forward and reverse
mappings between official types and aliases.
#. The JSON file is served with ETag support and should be considered highly
cacheable.
#. The current version of the JSON file should always be the preferred file to
use.
#. The JSON file is similar to timezone data. It should not be considered
versioned such that stable releases of distros should provide a
frozen version of it. Distro packages should instead update for all
active releases when a new version of the file is published.
.. _OpenStack Service Types Authority: https://opendev.org/openstack/service-types-authority/
.. _ISO Date Time Format: https://tools.ietf.org/html/rfc3339#section-5.6

View File

@ -0,0 +1,471 @@
Endpoint Discovery
==================
Endpoint from Catalog
---------------------
The ``{service-catalog}`` can be found in the ``token`` returned from
keystone authentication.
If v3 auth is used, the catalog will be in the ``catalog`` property of the
top-level ``token`` object. Such as:
.. code-block:: json
{
"token": {
"catalog": {}
}
}
If v2 auth is used it will be in the ``serviceCatalog`` property of the
top-level ``access`` object. Such as:
.. code-block:: json
{
"access": {
"serviceCatalog": {}
}
}
In both cases, the catalog content itself is a list of objects. Each object has
two main keys that concern discovery:
``type``
Matches ``{service-type}``
``endpoints``
List of endpoint objects for that service
Additionally, for backwards compatibility reasons, the following keys may
need to be checked.
``name``
Matches ``{service-name}``
``id``
Matches ``{service-id}``
The list of endpoints has a different format depending on whether v2 or v3 auth
was used. For both versions each endpoint object has a ``region`` key,
which should match ``{region-name}`` if one was given.
In v2 auth the endpoint object has three keys ``publicURL``,
``internalURL``, ``adminURL``. The endpoint for the ``{interface}`` requested
by the user is found in the key with the name matching ``{interface}`` plus
the string ``URL``.
In v3 auth the endpoint object has a ``url`` that is the endpoint that is
being requested if the value of ``interface`` matches ``{interface}``.
Examples of Tokens with Catalogs
--------------------------------
V3 Catalog Objects:
.. code-block:: json
{
"token": {
"catalog": [
{
"endpoints": [
{
"id": "39dc322ce86c4111b4f06c2eeae0841b",
"interface": "public",
"region": "RegionOne",
"url": "https://identity.example.com"
},
{
"id": "ec642f27474842e78bf059f6c48f4e99",
"interface": "internal",
"region": "RegionOne",
"url": "https://identity.example.com"
},
{
"id": "c609fc430175452290b62a4242e8a7e8",
"interface": "admin",
"region": "RegionOne",
"url": "https://identity.example.com"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa2",
"type": "identity",
"name": "keystone"
}
],
}
V2 Catalog Objects:
.. code-block:: json
{
"access": {
"serviceCatalog": [
{
"endpoints_links": [],
"endpoints": [
{
"adminURL": "https://identity.example.com/v2.0",
"region": "RegionOne",
"publicURL": "https://identity.example.com/v2.0",
"internalURL": "https://identity.example.com/v2.0",
"id": "4deb4d0504a044a395d4480741ba628c"
}
],
"type": "identity",
"name": "keystone"
},
]
}
}
Endpoint Discovery Algorithm
----------------------------
#. If ``{endpoint-version}`` was given and ``{service-type}`` ends with a
suffix of ``v[0-9]+$`` and ``{endpoint-version}`` does not match that suffix
(see `Comparing Major Versions`_), STOP. Return an error that the user
has requested a versioned ``{service-type}`` alias and an incompatible
``{endpoint-version}``.
#. Find the objects in the ``{service-catalog}`` that match the requested
``{service-type}`` (see `Match Candidate Entries`_).
#. If ``{service-name}`` was given and the objects remaining have a ``name``
field, keep only the ones where ``name`` matches ``{service-name}``.
.. note:: Catalogs from Keystone v3 before v3.3 do not have a name field. If
``{be-strict}`` was not requested and the catalog does not have a
``name`` field, ``{service-name}`` should be ignored.
#. If ``{service-id}`` was given and the objects remaining have a ``id``
field, keep only the ones where ``id`` matches ``{service-id}``.
.. note:: Catalogs from Keystone v2 do not have an id field. If
``{be-strict}`` was not requested and the catalog does not have a
``id`` field, ``{service-id}`` should be ignored.
The list of remaining objects are the ``{candidate-catalog-objects}``. If this
list is empty, return an error that there are no endpoints matching
``{service-type}`` and ``{service-name}``.
#. Use ``{candidate-catalog-objects}`` to produce the list of
``{candidate-endpoints}``. For each endpoint object in each of the
``{candidate-catalog-objects}``:
#. If v2, if there is no key of the form ``{interface}URL`` for any of the
the ``{interface}`` values given, discard the endpoint.
#. If v3, if ``interface`` does not match any of the ``{interface}`` values
given, discard the endpoint.
#. If there are no endpoints left, return an error that there are no endpoints
matching any of the ``{interface}`` values, preferrably including the list
of interfaces that were found.
#. For each remaining endpoint in ``{candidate-endpoints}``, if
``{region_name}`` was given and does not match either of ``region`` or
``region_id``, discard the endpoint.
If there are no remaining endpoints, return an error that there are no
endpoints matching ``{region_name}``, preferrably including the list of
regions that were found.
#. From the set of remaining candidate endpoints, find the ones that best
matches the requested ``{service-type}`` (see `Find Endpoint Matching Best
Service Type`_).
#. From the set of remaining candidate endpoints, find the ones that best
matches the best available requested ``{interface}``: in order of
preference of the ``{interface}`` list, return all endpoints that match
the first ``{interface}`` that has any matching endpoints.
The remaining ``{candidate-endpoints}`` match the request. If there is more
than one of them, use the first, but emit a warning to the user that more
than one endpoint was left. If ``{be-strict}`` has been requested, return an
error instead with information about each of the endpoints left in the list.
.. note:: It would be more correct to raise an error if there is more than one
endpoint left, but the keystoneauth library returns the first and
changing that would break a large number of existing users. If one
is writing a completely new library from scratch, or a new major
version where behavior change is acceptable, it is preferable to
raise an error here if there is more than one endpoint left.
#. If v2, the ``{catalog-endpoint}`` is the value of ``{interface}URL``.
#. If v3, the ``{catalog-endpoint}`` is the value of ``url``.
Match Candidate Entries
~~~~~~~~~~~~~~~~~~~~~~~
For every entry in the catalog:
#. If the entry's type matches the requested ``{service-type}``, it is a
candidate.
#. If the requested type is an official type from the
:doc:`OpenStack Service Types Authority <authority>` that has aliases and
one of the aliases matches the entry's type, it is a candidate.
#. If the requested type is an alias of an official type from the
:doc:`OpenStack Service Types Authority <authority>` and the entry's type
matches the official type, it is a candidate.
#. If the requested type is an alias of an official type from the
:doc:`OpenStack Service Types Authority <authority>` that has aliases and
the entry's type matches one of the aliases and ``{endpoint-version}`` was
given and the found alias ends with a suffix of ``v[0-9]+$`` and
``{endpoint-version}`` matches the version in the suffix (see `Comparing
Major Versions`_) it is a candidate.
.. _find-endpoint-matching-best-service-type:
Find Endpoint Matching Best Service Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Given a list of candidate endpoints that have matched the other criteria:
#. Check the list of candidate endpoints to see if one of them matches the
requested ``{service-type}``. If any are an exact match, return them.
#. If the requested ``{service-type}``
* is an official type from the :doc:`OpenStack Service Types Authority
<authority>` that has aliases
* ``{endpoint-version}`` was given
Look for aliases that end with a version suffix of the form ``v[0-9]+$``.
If there are any aliases with a version suffix that matches the
``{endpoint-version}`` (see `Comparing Major Versions`_), look for those
aliases in the list of candidate endpoints. If any are a match, return them.
#. If the requested ``{service-type}``
* is an official type in the :doc:`OpenStack Service Types Authority
<authority>` that has aliases
* ``{endpoint-version}`` was not given
check each alias in the order listed to see if it has a matching endpoint
from the candidate endpoints. Return the endpoints that match the first
alias that has matching endpoints.
#. If the requested ``{service-type}``
* is an alias of an official type in the
:doc:`OpenStack Service Types Authority <authority>`
* ``{endpoint-version}`` was given
look for aliases that end with a version suffix of the form ``v[0-9]+$``. If
there are any aliases with a version suffix that matches the
``{endpoint-version}`` (see `Comparing Major Versions`_), look for those
aliases in the list of candidate endpoints.
Return the endpoints that match the alias with the highest matching version.
#. If there are no matching endpoints, return an error.
.. note:: The case where
* an alias was requested
* no ``{endpoint-version}`` was given
* there is a different alias in the catalog
is not safe and so is treated as a lack of matching endpoint on
purpose. Many of the aliases carry an implied version, so absent
a requested ``{endpoint-version}`` from the user, returning
an endpoint different than the one explicitly requested has a high
chance of not being the endpoint the user expected.
.. _comparing-major-versions:
Comparing Major Versions
~~~~~~~~~~~~~~~~~~~~~~~~
When comparing Major Versions, there is a ``required`` and a ``candidate``:
* The ``required`` is what the user has requested.
* The ``candidate`` is the possible version being tested.
To be suitable a ``candidate`` must be of the same major version as
``required`` and be at least a match in minor level: ``candidate`` ``3.3``
is a match for ``required`` ``3.1`` but ``4.1`` is not.
Leading 'v' strings should be discarded in all cases.
#. Versions with only a single number normalize to ``.0``. That is,
a version of ``2`` should be treated as if it was ``2.0``.
#. If ``required`` is the string ``latest`` or contains no value, ``candidate``
matches.
#. If ``required`` is a range, any ``candidate`` that is greater than or equal
to the first value and less than or equal to the second value is a match.
Equality is judged by the above rules. Greater than and less than are judged
as expected: first by comparing the first number, and if those match then by
comparing the second number. Thus, a ``{required}`` of ``2,4`` matches
``2``, ``2.3``, ``3``, ``4`` and ``4.7``. A ``{required}`` of ``2.1,4.0``
matches ``2.3``, ``3``, ``4`` and ``4.7`` but not ``2``.
#. If ``required`` is a range without a maximum value, maximum should be
treated as if it is ``latest``.
Examples of discovery
---------------------
For example, given the following catalog:
.. code-block:: json
{
"token": {
"catalog": [
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com/v3"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa3",
"type": "volumev3",
"name": "cinder"
},
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com/v2"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa2",
"type": "volumev2",
"name": "cinder"
}
],
}
Then the following:
::
service_type = 'block-storage'
# block-storage is not found, get list of aliases
# volumev3 is found, return it
service_type = 'volumev2'
# volumev2 not an official type in authority, but is in catalog
# return volumev2 entry
service_type = 'volume'
# volume not in authority or catalog
# volume is an alias of block-storage
# block-storage is not found. Return error.
service_type = 'volume'
api_version = 2
# volume not in authority or catalog
# volume is an alias of block-storage
# block-storage is not found.
# volumev2 is an alias of block-storage and ends with v2 which matches
# api_version of 2
# return volumev2
Given the following catalog:
.. code-block:: json
{
"token": {
"catalog": [
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa3",
"type": "block-storage",
"name": "cinder"
}
],
}
Then the following:
::
service_type = 'block-storage'
# block-storage is found, return it
service_type = 'volumev2'
# volumev2 not in authority, is an alias for block-storage
# block-storage is in the catalog, return it
service_type = 'volumev2'
api_version = '3'
# volumev2 ends with a version suffix of v2 which does not match 3
# return an error before even fetching the catalog
Given the following catalog:
.. code-block:: json
{
"token": {
"catalog": [
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa3",
"type": "block-storage",
"name": "cinder"
},
{
"endpoints": [
{
"interface": "public",
"region": "RegionOne",
"url": "https://block-storage.example.com/v2"
},
{
"interface": "internal",
"region": "RegionOne",
"url": "https://block-storage.example.int/v2"
}
],
"id": "4363ae44bdf34a3981fde3b823cb9aa2",
"type": "volumev2",
"name": "cinder"
}
],
}
Then the following:
::
service_type = 'block-storage'
interface = ['internal', 'public']
# block-storage is found
# block-storage does not have internal, but has public
# return block-storage public
service_type = 'volumev2'
interface = ['internal', 'public']
# volumev2 not an official type in authority, but is in catalog
# volumev2 has an internal interface
# return volumev2 internal entry

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,8 @@ API expose the URIs and resources to end users in a machine-readable way.
See also the topic document on :ref:`consuming-catalog`.
See also the topic document on :doc:`consuming-catalog/version-discovery`.
Guidance
--------

View File

@ -50,6 +50,8 @@ For example, you cannot request the feature which was introduced at
microversion 2.100 without backwards incompatible changes which were
introduced in microversion 2.99 and earlier.
.. _microversion-client-interaction:
Client Interaction
------------------