Implement fetching and accessor methods
The ServiceTypes class is the main entry point for python programmers. It should allow them to answer the questions they have about the data without necessarily walking the structure. It can also return the raw structure if people want to get at it. We need to know the project codename for services for doing API doc publication validation and also for being able to send legacy microversion headers. The information is in the data, use it. In order to construct and send microversion headers for projects with aliases, we need to know ALL of the aliases plus the official name, because we don't know which one of the values will be the correct name the service is looking for for a given version. The microversion spec, however, requires that consumers be able to handle a header that contains a list of services and to ignore the ones it isn't looking for. By providing a a list of all the possible values we make it easy to construct a header for a service given a service_type that should work for all known identifiers of the service and that is future compatible with the service-type and microversion specifyier aligning. Co-Authored-By: Doug Hellmann <doug@doughellmann.com> Change-Id: I57641c9e3c27688b6d7709b1bb07292740d26659
This commit is contained in:
parent
1865fa56f8
commit
48a94e00a7
@ -15,11 +15,10 @@ Contents:
|
||||
install/index
|
||||
library/index
|
||||
contributor/index
|
||||
reference/index
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
@ -1,7 +1,26 @@
|
||||
========
|
||||
=====
|
||||
Usage
|
||||
========
|
||||
=====
|
||||
|
||||
To use os-service-types in a project::
|
||||
The most basic use of `os-service-types` in a project:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_service_types
|
||||
|
||||
service_types = os_service_types.ServiceTypes()
|
||||
|
||||
However, `os-service-types` expects to be able to fetch remote data, so it's
|
||||
better to pass in a ``Session`` object. Both
|
||||
:class:`requests.sessions.Session` and
|
||||
:class:`keystoneauth1.session.Session` objects are supported. A
|
||||
:class:`keystoneauth1.session.Session` object does not need auth information
|
||||
attached, although it will not break anything if it does.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import keystoneauth1.session
|
||||
import os_service_types
|
||||
|
||||
session = keystoneauth1.session.Session()
|
||||
service_types = os_service_types.ServiceTypes(session=session)
|
||||
|
10
doc/source/reference/index.rst
Normal file
10
doc/source/reference/index.rst
Normal file
@ -0,0 +1,10 @@
|
||||
=============
|
||||
API Reference
|
||||
=============
|
||||
|
||||
.. module:: os_service_types
|
||||
:synopsis: OpenStack Service Types Data
|
||||
|
||||
.. autoclass:: os_service_types.ServiceTypes
|
||||
:members:
|
||||
:inherited-members:
|
@ -11,8 +11,10 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
__all__ = ['__version__', 'ServiceTypes']
|
||||
|
||||
import pbr.version
|
||||
|
||||
from os_service_types.service_types import ServiceTypes # flake8: noqa
|
||||
|
||||
__version__ = pbr.version.VersionInfo('os-service-types').version_string()
|
||||
|
25
os_service_types/data/__init__.py
Normal file
25
os_service_types/data/__init__.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Copyright 2017 Red Hat, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
__all__ = ['read_data']
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
DATA_DIR = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def read_data(filename):
|
||||
"""Return data that is shipped inside the python package."""
|
||||
return json.load(open(os.path.join(DATA_DIR, filename), 'r'))
|
305
os_service_types/data/service-types.json
Normal file
305
os_service_types/data/service-types.json
Normal file
@ -0,0 +1,305 @@
|
||||
{
|
||||
"services": [
|
||||
{
|
||||
"service_type": "identity",
|
||||
"project": "openstack/keystone",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/identity/"
|
||||
},
|
||||
{
|
||||
"service_type": "compute",
|
||||
"project": "openstack/nova",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/compute/"
|
||||
},
|
||||
{
|
||||
"service_type": "image",
|
||||
"project": "openstack/glance",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/image/"
|
||||
},
|
||||
{
|
||||
"service_type": "load-balancer",
|
||||
"project": "openstack/octavia",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/load-balancer/"
|
||||
},
|
||||
{
|
||||
"service_type": "object-store",
|
||||
"project": "openstack/swift",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/object-storage/"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"clustering"
|
||||
],
|
||||
"service_type": "resource-cluster",
|
||||
"project": "openstack/senlin",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/clustering/"
|
||||
},
|
||||
{
|
||||
"service_type": "data-processing",
|
||||
"project": "openstack/sahara",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/data-processing/"
|
||||
},
|
||||
{
|
||||
"service_type": "baremetal",
|
||||
"project": "openstack/ironic",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/baremetal/"
|
||||
},
|
||||
{
|
||||
"service_type": "baremetal-introspection",
|
||||
"project": "openstack/ironic-inspector",
|
||||
"api_reference": "http://docs.openstack.org/developer/ironic-inspector/http-api.html"
|
||||
},
|
||||
{
|
||||
"service_type": "key-manager",
|
||||
"project": "openstack/barbican",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/key-manager/"
|
||||
},
|
||||
{
|
||||
"service_type": "ec2-api",
|
||||
"project": "openstack/ec2-api",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/ec2-api/"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"infra-optim"
|
||||
],
|
||||
"service_type": "resource-optimization",
|
||||
"project": "openstack/watcher",
|
||||
"api_reference": "https://docs.openstack.org/developer/watcher/webapi/v1.html"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"messaging"
|
||||
],
|
||||
"service_type": "message",
|
||||
"project": "openstack/zaqar",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/messaging/"
|
||||
},
|
||||
{
|
||||
"service_type": "application-catalog",
|
||||
"project": "openstack/murano",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/application-catalog/"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"container-infrastructure"
|
||||
],
|
||||
"service_type": "container-infrastructure-management",
|
||||
"project": "openstack/magnum",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/container-infrastructure-management/"
|
||||
},
|
||||
{
|
||||
"service_type": "search",
|
||||
"project": "openstack/searchlight",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/search/"
|
||||
},
|
||||
{
|
||||
"service_type": "dns",
|
||||
"project": "openstack/designate",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/dns/"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"workflowv2"
|
||||
],
|
||||
"service_type": "workflow",
|
||||
"project": "openstack/mistral",
|
||||
"api_reference": "http://docs.openstack.org/developer/mistral/developer/webapi/index.html"
|
||||
},
|
||||
{
|
||||
"service_type": "rating",
|
||||
"project": "openstack/cloudkitty",
|
||||
"api_reference": "http://docs.openstack.org/developer/cloudkitty/webapi/root.html"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"policy"
|
||||
],
|
||||
"service_type": "operator-policy",
|
||||
"project": "openstack/congress",
|
||||
"api_reference": "http://docs.openstack.org/developer/congress/api.html"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"sharev2",
|
||||
"share"
|
||||
],
|
||||
"service_type": "shared-file-system",
|
||||
"project": "openstack/manila",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/shared-file-systems/"
|
||||
},
|
||||
{
|
||||
"service_type": "data-protection-orchestration",
|
||||
"project": "openstack/karbor",
|
||||
"api_reference": "https://developer.openstack.org/api-ref/data-protection-orchestration/"
|
||||
},
|
||||
{
|
||||
"service_type": "orchestration",
|
||||
"project": "openstack/heat",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/orchestration/"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"volume",
|
||||
"volumev2",
|
||||
"volumev3"
|
||||
],
|
||||
"service_type": "block-storage",
|
||||
"project": "openstack/cinder",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/block-storage/"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"alarming"
|
||||
],
|
||||
"service_type": "alarm",
|
||||
"project": "openstack/aodh",
|
||||
"api_reference": "https://docs.openstack.org/developer/aodh/webapi/index.html"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"metering"
|
||||
],
|
||||
"service_type": "meter",
|
||||
"project": "openstack/ceilometer",
|
||||
"api_reference": "https://docs.openstack.org/developer/ceilometer/webapi/index.html"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"events"
|
||||
],
|
||||
"service_type": "event",
|
||||
"project": "openstack/panko",
|
||||
"api_reference": "http://docs.openstack.org/developer/panko/webapi/index.html"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"application_deployment"
|
||||
],
|
||||
"service_type": "application-deployment",
|
||||
"project": "openstack/solum",
|
||||
"api_reference": "http://docs.openstack.org/developer/solum/develop_applications/webapi/index.html"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"tricircle"
|
||||
],
|
||||
"service_type": "multi-region-network-automation",
|
||||
"project": "openstack/tricircle",
|
||||
"api_reference": "http://docs.openstack.org/developer/tricircle/api_v1.html"
|
||||
},
|
||||
{
|
||||
"service_type": "database",
|
||||
"project": "openstack/trove",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/database/"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"container"
|
||||
],
|
||||
"service_type": "application-container",
|
||||
"project": "openstack/zun",
|
||||
"api_reference": "https://git.openstack.org/cgit/openstack/zun/tree/api-ref/source"
|
||||
},
|
||||
{
|
||||
"aliases": [
|
||||
"rca"
|
||||
],
|
||||
"service_type": "root-cause-analysis",
|
||||
"project": "openstack/vitrage",
|
||||
"api_reference": "http://docs.openstack.org/developer/vitrage/vitrage-api.html"
|
||||
},
|
||||
{
|
||||
"service_type": "nfv-orchestration",
|
||||
"project": "openstack/tacker",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/nfv-orchestration/"
|
||||
},
|
||||
{
|
||||
"service_type": "network",
|
||||
"project": "openstack/neutron",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/networking/",
|
||||
"api_reference_project": "openstack/neutron-lib"
|
||||
},
|
||||
{
|
||||
"service_type": "backup",
|
||||
"project": "openstack/freezer-api",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/backup/"
|
||||
},
|
||||
{
|
||||
"service_type": "monitoring-log-api",
|
||||
"project": "openstack/monasca-log-api",
|
||||
"api_reference": "http://developer.openstack.org/api-ref/monitoring-log-api/"
|
||||
}
|
||||
],
|
||||
"forward": {
|
||||
"block-storage": [
|
||||
"volume",
|
||||
"volumev2",
|
||||
"volumev3"
|
||||
],
|
||||
"resource-cluster": [
|
||||
"clustering"
|
||||
],
|
||||
"message": [
|
||||
"messaging"
|
||||
],
|
||||
"root-cause-analysis": [
|
||||
"rca"
|
||||
],
|
||||
"resource-optimization": [
|
||||
"infra-optim"
|
||||
],
|
||||
"multi-region-network-automation": [
|
||||
"tricircle"
|
||||
],
|
||||
"event": [
|
||||
"events"
|
||||
],
|
||||
"operator-policy": [
|
||||
"policy"
|
||||
],
|
||||
"application-container": [
|
||||
"container"
|
||||
],
|
||||
"alarm": [
|
||||
"alarming"
|
||||
],
|
||||
"workflow": [
|
||||
"workflowv2"
|
||||
],
|
||||
"application-deployment": [
|
||||
"application_deployment"
|
||||
],
|
||||
"meter": [
|
||||
"metering"
|
||||
],
|
||||
"shared-file-system": [
|
||||
"sharev2",
|
||||
"share"
|
||||
],
|
||||
"container-infrastructure-management": [
|
||||
"container-infrastructure"
|
||||
]
|
||||
},
|
||||
"version": "2017-07-10T13:19:11.690814",
|
||||
"reverse": {
|
||||
"events": "event",
|
||||
"messaging": "message",
|
||||
"metering": "meter",
|
||||
"rca": "root-cause-analysis",
|
||||
"container": "application-container",
|
||||
"application_deployment": "application-deployment",
|
||||
"volumev3": "block-storage",
|
||||
"volume": "block-storage",
|
||||
"workflowv2": "workflow",
|
||||
"clustering": "resource-cluster",
|
||||
"infra-optim": "resource-optimization",
|
||||
"policy": "operator-policy",
|
||||
"tricircle": "multi-region-network-automation",
|
||||
"container-infrastructure": "container-infrastructure-management",
|
||||
"volumev2": "block-storage",
|
||||
"alarming": "alarm",
|
||||
"sharev2": "shared-file-system",
|
||||
"share": "shared-file-system"
|
||||
},
|
||||
"sha": "329e37b2fe03085875a7f65cd31eb9f1d8182176"
|
||||
}
|
228
os_service_types/service_types.py
Normal file
228
os_service_types/service_types.py
Normal file
@ -0,0 +1,228 @@
|
||||
# Copyright 2017 Red Hat, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
__all__ = ['ServiceTypes']
|
||||
|
||||
import copy
|
||||
|
||||
import os_service_types.data
|
||||
|
||||
BUILTIN_DATA = os_service_types.data.read_data('service-types.json')
|
||||
SERVICE_TYPES_URL = "https://service-types.openstack.org/service-types.json"
|
||||
|
||||
|
||||
class ServiceTypes(object):
|
||||
"""Encapsulation of the OpenStack Service Types Authority data.
|
||||
|
||||
The Service Types Authority data will be either pulled from its remote
|
||||
location or from local files as is appropriate.
|
||||
|
||||
If the user passes a Session, remote data will be fetched. If the user
|
||||
does not do that, local builtin data will be used.
|
||||
|
||||
:param session: An object that behaves like a `requests.sessions.Session`
|
||||
or a `keystoneauth1.session.Session` that provides a get method
|
||||
and returns an object that behaves like a `requests.models.Response`.
|
||||
Optional. If session is omitted, no remote actions will be performed.
|
||||
:param bool only_remote: By default if there is a problem fetching data
|
||||
remotely the builtin data will be returned as a fallback. only_remote
|
||||
will cause remote failures to raise an error instead of falling back.
|
||||
Optional, defaults to False.
|
||||
"""
|
||||
|
||||
def __init__(self, session=None, only_remote=False):
|
||||
self._service_types_data = BUILTIN_DATA
|
||||
if session:
|
||||
try:
|
||||
response = session.get(SERVICE_TYPES_URL)
|
||||
response.raise_for_status()
|
||||
self._service_types_data = response.json()
|
||||
except IOError:
|
||||
# If we can't fetch, fall backto BUILTIN
|
||||
if only_remote:
|
||||
raise
|
||||
|
||||
by_project = {}
|
||||
for s in self._service_types_data['services']:
|
||||
for key in ['project', 'api_reference_project']:
|
||||
name = s.get(key)
|
||||
if name:
|
||||
by_project[self._canonical_project_name(name)] = s
|
||||
self._service_types_data['by_project'] = by_project
|
||||
|
||||
def _canonical_project_name(self, name):
|
||||
"Convert repo name to project name."
|
||||
if name is None:
|
||||
raise ValueError("Empty project name is not allowed")
|
||||
if name.startswith('openstack/'):
|
||||
# Handle openstack/ prefix going away from STA data
|
||||
return name.rpartition('/')[-1]
|
||||
return name
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"The URL from which the data was retrieved."
|
||||
return SERVICE_TYPES_URL
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"The version of the data."
|
||||
return self._service_types_data['version']
|
||||
|
||||
@property
|
||||
def forward(self):
|
||||
"Mapping service-type names to their aliases."
|
||||
return copy.deepcopy(self._service_types_data['forward'])
|
||||
|
||||
@property
|
||||
def reverse(self):
|
||||
"Mapping aliases to their service-type names."
|
||||
return copy.deepcopy(self._service_types_data['reverse'])
|
||||
|
||||
@property
|
||||
def services(self):
|
||||
"Full service-type data listing."
|
||||
return copy.deepcopy(self._service_types_data['services'])
|
||||
|
||||
def get_official_service_data(self, service_type):
|
||||
"""Get the service data for an official service_type.
|
||||
|
||||
:param str service_type: The official service-type to get data for.
|
||||
:returns dict: Service data for the service or None if not found.
|
||||
"""
|
||||
for service in self._service_types_data['services']:
|
||||
if service_type == service['service_type']:
|
||||
return service
|
||||
return None
|
||||
|
||||
def get_service_data(self, service_type):
|
||||
"""Get the service data for a given service_type.
|
||||
|
||||
:param str service_type: The service-type or alias to get data for.
|
||||
:returns dict: Service data for the service or None if not found.
|
||||
"""
|
||||
service_type = self.get_service_type(service_type)
|
||||
if not service_type:
|
||||
return None
|
||||
return self.get_official_service_data(service_type)
|
||||
|
||||
def is_official(self, service_type):
|
||||
"""Is the given service-type an official service-type?
|
||||
|
||||
:param str service_type: The service-type to test.
|
||||
:returns bool: True if it's an official type, False otherwise.
|
||||
"""
|
||||
return self.get_official_service_data(service_type) is not None
|
||||
|
||||
def is_alias(self, service_type):
|
||||
"""Is the given service-type an alias?
|
||||
|
||||
:param str service_type: The service-type to test.
|
||||
:returns bool: True if it's an alias type, False otherwise.
|
||||
"""
|
||||
return service_type in self._service_types_data['reverse']
|
||||
|
||||
def is_known(self, service_type):
|
||||
"""Is the given service-type an official type or an alias?
|
||||
|
||||
:param str service_type: The service-type to test.
|
||||
:returns bool: True if it's a known type, False otherwise.
|
||||
"""
|
||||
return self.is_official(service_type) or self.is_alias(service_type)
|
||||
|
||||
def is_match(self, requested, found):
|
||||
"""Does a requested service-type match one found in the catalog?
|
||||
|
||||
A requested service-type matches a service-type in the catalog if
|
||||
it is either a direct match, if the service-type in the catalog is
|
||||
an official type and the requested type is one of its aliases, or
|
||||
if the requested type is an official type and the type in the catalog
|
||||
is one of its aliases.
|
||||
|
||||
A requested alias cannot match a different alias because there are
|
||||
historical implications related to versioning to some historical
|
||||
aliases that cannot be safely reasoned about in an automatic fashion.
|
||||
|
||||
:param str requested: A service-type that someone is looking for.
|
||||
:param str found: A service-type found in a catalog
|
||||
|
||||
:returns bool: True if the service-type being requested matches the
|
||||
entry in the catalog. False if it does not.
|
||||
"""
|
||||
# Exact match
|
||||
if requested == found:
|
||||
return True
|
||||
|
||||
# Found is official type, requested is one of its aliases
|
||||
if requested in self.get_aliases(found):
|
||||
return True
|
||||
|
||||
# Found is an alias, requested is an official type
|
||||
if requested == self.get_service_type(found):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_aliases(self, service_type):
|
||||
"""Returns the list of aliases for a given official service-type.
|
||||
|
||||
:param str service_type: An official service-type.
|
||||
:returns list: List of aliases, or empty list if there are none.
|
||||
"""
|
||||
return self._service_types_data['forward'].get(service_type, [])
|
||||
|
||||
def get_service_type(self, service_type):
|
||||
"""Given a possible service_type, return the official type.
|
||||
|
||||
:param str service_type: A potential service-type.
|
||||
:returns str: The official service-type, or None if there is no match.
|
||||
"""
|
||||
if self.is_official(service_type):
|
||||
return service_type
|
||||
return self._service_types_data['reverse'].get(service_type)
|
||||
|
||||
def get_all_types(self, service_type):
|
||||
"""Get a list of official type and all known aliases.
|
||||
|
||||
:param str service_type: The service-type or alias to get data for.
|
||||
:returns dict: Service data for the service or None if not found.
|
||||
"""
|
||||
if not self.is_known(service_type):
|
||||
return [service_type]
|
||||
service_type = self.get_service_type(service_type)
|
||||
ret = [service_type]
|
||||
ret.extend(self.get_aliases(service_type))
|
||||
return ret
|
||||
|
||||
def get_project_name(self, service_type):
|
||||
"""Return the OpenStack project name for a given service_type.
|
||||
|
||||
:param str service_type: An official service-type or alias.
|
||||
:returns str: The OpenStack project name or None if there is no match.
|
||||
"""
|
||||
service = self.get_service_data(service_type)
|
||||
if service:
|
||||
return self._canonical_project_name(service['project'])
|
||||
return None
|
||||
|
||||
def get_service_data_for_project(self, project_name):
|
||||
"""Return the service information associated with a project.
|
||||
|
||||
:param name: A repository or project name in the form
|
||||
``'openstack/{project}'`` or just ``'{project}'``.
|
||||
:type name: str
|
||||
:returns: dict or None if not found
|
||||
"""
|
||||
key = self._canonical_project_name(project_name)
|
||||
return self._service_types_data['by_project'].get(key)
|
@ -15,9 +15,196 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
import keystoneauth1.session
|
||||
from oslotest import base
|
||||
from requests_mock.contrib import fixture as rm_fixture
|
||||
|
||||
import os_service_types.service_types
|
||||
|
||||
|
||||
class TestCase(base.BaseTestCase):
|
||||
"""Base test case class before singleton protection is added."""
|
||||
|
||||
"""Test case base class for all unit tests."""
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
||||
|
||||
# use keystoneauth1 to get a Sessiom with no auth information
|
||||
self.session = keystoneauth1.session.Session()
|
||||
|
||||
self.set_adapter()
|
||||
|
||||
self.builtin_content = os_service_types.service_types.BUILTIN_DATA
|
||||
self.builtin_version = self.builtin_content['version']
|
||||
|
||||
# Set up copies of the data so that we can verify that we got the
|
||||
# copy of it we think we should.
|
||||
self.remote_version = datetime.datetime.utcnow().isoformat()
|
||||
self.remote_content = copy.deepcopy(self.builtin_content)
|
||||
self.remote_content['version'] = self.remote_version
|
||||
|
||||
def set_adapter(self):
|
||||
# Set up a requests_mock fixture for all HTTP traffic
|
||||
self.adapter = self.useFixture(rm_fixture.Fixture())
|
||||
|
||||
|
||||
class ServiceDataMixin(object):
|
||||
|
||||
scenarios = [
|
||||
('compute', dict(
|
||||
service_type='compute', official='compute', aliases=[],
|
||||
all_types=['compute'],
|
||||
api_reference='compute', api_reference_project=None,
|
||||
is_known=True, is_alias=False, is_official=True, project='nova')),
|
||||
('volumev2', dict(
|
||||
service_type='volumev2', official='block-storage', aliases=[],
|
||||
all_types=['block-storage', 'volume', 'volumev2', 'volumev3'],
|
||||
api_reference='block-storage', api_reference_project=None,
|
||||
is_known=True, is_alias=True, is_official=False,
|
||||
project='cinder')),
|
||||
('volumev3', dict(
|
||||
service_type='volumev3', official='block-storage', aliases=[],
|
||||
all_types=['block-storage', 'volume', 'volumev2', 'volumev3'],
|
||||
api_reference='block-storage', api_reference_project=None,
|
||||
is_known=True, is_alias=True, is_official=False,
|
||||
project='cinder')),
|
||||
('block-storage', dict(
|
||||
service_type='block-storage', official='block-storage',
|
||||
all_types=['block-storage', 'volume', 'volumev2', 'volumev3'],
|
||||
api_reference='block-storage', api_reference_project=None,
|
||||
aliases=['volume', 'volumev2', 'volumev3'],
|
||||
is_known=True, is_alias=False, is_official=True,
|
||||
project='cinder')),
|
||||
('network', dict(
|
||||
service_type='network', official='network', aliases=[],
|
||||
all_types=['network'],
|
||||
api_reference='networking', api_reference_project='neutron-lib',
|
||||
is_known=True, is_alias=False, is_official=True,
|
||||
project='neutron')),
|
||||
('missing', dict(
|
||||
service_type='missing', official=None,
|
||||
aliases=[],
|
||||
all_types=['missing'],
|
||||
api_reference=None, api_reference_project=None,
|
||||
is_known=False, is_alias=False, is_official=False,
|
||||
project=None)),
|
||||
]
|
||||
|
||||
def test_get_service_type(self):
|
||||
if self.official:
|
||||
self.assertEqual(
|
||||
self.official,
|
||||
self.service_types.get_service_type(self.service_type))
|
||||
else:
|
||||
self.assertIsNone(
|
||||
self.service_types.get_service_type(self.service_type))
|
||||
|
||||
def test_get_aliases(self):
|
||||
self.assertEqual(
|
||||
self.aliases,
|
||||
self.service_types.get_aliases(self.service_type))
|
||||
|
||||
def test_is_known(self):
|
||||
self.assertEqual(
|
||||
self.is_known,
|
||||
self.service_types.is_known(self.service_type))
|
||||
|
||||
def test_is_alias(self):
|
||||
self.assertEqual(
|
||||
self.is_alias,
|
||||
self.service_types.is_alias(self.service_type))
|
||||
|
||||
def test_is_official(self):
|
||||
self.assertEqual(
|
||||
self.is_official,
|
||||
self.service_types.is_official(self.service_type))
|
||||
|
||||
def test_get_project_name(self):
|
||||
if self.project:
|
||||
self.assertEqual(
|
||||
self.project,
|
||||
self.service_types.get_project_name(self.service_type))
|
||||
else:
|
||||
self.assertIsNone(
|
||||
self.service_types.get_project_name(self.service_type))
|
||||
|
||||
def test_get_service_data(self):
|
||||
service_data = self.service_types.get_service_data(self.service_type)
|
||||
# TODO(mordred) Once all the docs have been aligned, remove
|
||||
# self.api_reference and replace with self.service_type
|
||||
api_url = 'http://developer.openstack.org/api-ref/{api_reference}/'
|
||||
|
||||
# Tests self.official here, since we expect to get data back for all
|
||||
# official projects, regardless of service_type being an alias or not
|
||||
if not self.official:
|
||||
self.assertIsNone(service_data)
|
||||
else:
|
||||
self.assertIsNotNone(service_data)
|
||||
self.assertEqual(
|
||||
'openstack/{project}'.format(project=self.project),
|
||||
service_data['project'])
|
||||
self.assertEqual(self.official, service_data['service_type'])
|
||||
self.assertEqual(
|
||||
api_url.format(api_reference=self.api_reference),
|
||||
service_data['api_reference'])
|
||||
|
||||
def test_get_official_service_data(self):
|
||||
service_data = self.service_types.get_official_service_data(
|
||||
self.service_type)
|
||||
# TODO(mordred) Once all the docs have been aligned, remove
|
||||
# self.api_reference and replace with self.service_type
|
||||
api_url = 'http://developer.openstack.org/api-ref/{api_reference}/'
|
||||
|
||||
# Tests self.is_official here, since we expect only get data back for
|
||||
# official projects.
|
||||
if not self.is_official:
|
||||
self.assertIsNone(service_data)
|
||||
else:
|
||||
self.assertIsNotNone(service_data)
|
||||
self.assertEqual(
|
||||
'openstack/{project}'.format(project=self.project),
|
||||
service_data['project'])
|
||||
self.assertEqual(self.official, service_data['service_type'])
|
||||
self.assertEqual(
|
||||
api_url.format(api_reference=self.api_reference),
|
||||
service_data['api_reference'])
|
||||
|
||||
def test_empty_project_error(self):
|
||||
if not self.project:
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
self.service_types.get_service_data_for_project,
|
||||
self.project)
|
||||
|
||||
def test_get_service_data_for_project(self):
|
||||
if not self.project:
|
||||
self.skipTest("Empty project is invalid but tested elsewhere.")
|
||||
return
|
||||
|
||||
service_data = self.service_types.get_service_data_for_project(
|
||||
self.project)
|
||||
# TODO(mordred) Once all the docs have been aligned, remove
|
||||
# self.api_reference and replace with self.service_type
|
||||
api_url = 'http://developer.openstack.org/api-ref/{api_reference}/'
|
||||
|
||||
self.assertIsNotNone(service_data)
|
||||
if self.api_reference_project:
|
||||
self.assertEqual(
|
||||
'openstack/{api_reference_project}'.format(
|
||||
api_reference_project=self.api_reference_project),
|
||||
service_data['api_reference_project'])
|
||||
else:
|
||||
self.assertEqual(
|
||||
'openstack/{project}'.format(project=self.project),
|
||||
service_data['project'])
|
||||
self.assertEqual(self.official, service_data['service_type'])
|
||||
self.assertEqual(
|
||||
api_url.format(api_reference=self.api_reference),
|
||||
service_data['api_reference'])
|
||||
|
||||
def test_get_all_types(self):
|
||||
self.assertEqual(
|
||||
self.all_types,
|
||||
self.service_types.get_all_types(self.service_type))
|
||||
|
@ -13,16 +13,26 @@
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
test_os_service_types
|
||||
----------------------------------
|
||||
test_builtin
|
||||
------------
|
||||
|
||||
Tests for `os_service_types` module.
|
||||
Tests for `ServiceTypes` class builtin data.
|
||||
|
||||
oslotest sets up a TempHomeDir for us, so there should be no ~/.cache files
|
||||
available in these tests.
|
||||
"""
|
||||
from testscenarios import load_tests_apply_scenarios as load_tests # noqa
|
||||
|
||||
import os_service_types
|
||||
from os_service_types.tests import base
|
||||
|
||||
|
||||
class TestOs_service_types(base.TestCase):
|
||||
class TestBuiltin(base.TestCase, base.ServiceDataMixin):
|
||||
|
||||
def test_something(self):
|
||||
pass
|
||||
def setUp(self):
|
||||
super(TestBuiltin, self).setUp()
|
||||
# Make an object with no network access
|
||||
self.service_types = os_service_types.ServiceTypes()
|
||||
|
||||
def test_builtin_version(self):
|
||||
self.assertEqual(self.builtin_version, self.service_types.version)
|
62
os_service_types/tests/test_match.py
Normal file
62
os_service_types/tests/test_match.py
Normal file
@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
test_match
|
||||
----------
|
||||
|
||||
Tests for is_match logic
|
||||
|
||||
oslotest sets up a TempHomeDir for us, so there should be no ~/.cache files
|
||||
available in these tests.
|
||||
"""
|
||||
from testscenarios import load_tests_apply_scenarios as load_tests # noqa
|
||||
|
||||
import os_service_types
|
||||
from os_service_types.tests import base
|
||||
|
||||
|
||||
class TestMatch(base.TestCase):
|
||||
|
||||
scenarios = [
|
||||
('match-official', dict(
|
||||
requested='compute', found='compute', is_match=True)),
|
||||
('direct-match-unknown', dict(
|
||||
requested='unknown', found='unknown', is_match=True)),
|
||||
('volumev2-finds-block', dict(
|
||||
requested='volumev2', found='block-storage', is_match=True)),
|
||||
('volumev3-finds-block', dict(
|
||||
requested='volumev3', found='block-storage', is_match=True)),
|
||||
('block-finds-volumev2', dict(
|
||||
requested='block-storage', found='volumev2', is_match=True)),
|
||||
('block-finds-volumev3', dict(
|
||||
requested='block-storage', found='volumev3', is_match=True)),
|
||||
('volumev2-not-volumev3', dict(
|
||||
requested='volumev2', found='volumev3', is_match=False)),
|
||||
('non-match', dict(
|
||||
requested='unknown', found='compute', is_match=False)),
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TestMatch, self).setUp()
|
||||
# Make an object with no network access
|
||||
self.service_types = os_service_types.ServiceTypes()
|
||||
|
||||
def test_is_match(self):
|
||||
if self.is_match:
|
||||
self.assertTrue(
|
||||
self.service_types.is_match(self.requested, self.found))
|
||||
else:
|
||||
self.assertFalse(
|
||||
self.service_types.is_match(self.requested, self.found))
|
45
os_service_types/tests/test_remote.py
Normal file
45
os_service_types/tests/test_remote.py
Normal file
@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
test_remote
|
||||
-----------
|
||||
|
||||
Tests for `ServiceTypes` class remote data.
|
||||
|
||||
oslotest sets up a TempHomeDir for us, so there should be no ~/.cache files
|
||||
available in these tests.
|
||||
"""
|
||||
from testscenarios import load_tests_apply_scenarios as load_tests # noqa
|
||||
|
||||
import os_service_types
|
||||
import os_service_types.service_types
|
||||
from os_service_types.tests import base
|
||||
|
||||
|
||||
class TestRemote(base.TestCase, base.ServiceDataMixin):
|
||||
|
||||
def setUp(self):
|
||||
super(TestRemote, self).setUp()
|
||||
self.adapter.register_uri(
|
||||
'GET', os_service_types.service_types.SERVICE_TYPES_URL,
|
||||
json=self.remote_content,
|
||||
headers={'etag': self.getUniqueString('etag')})
|
||||
# Make an object that fetches from the network
|
||||
self.service_types = os_service_types.ServiceTypes(
|
||||
session=self.session)
|
||||
self.assertEqual(1, len(self.adapter.request_history))
|
||||
|
||||
def test_remote_version(self):
|
||||
self.assertEqual(self.remote_version, self.service_types.version)
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- Added ServiceTypes class, which is the primary entry
|
||||
point for Python developers who need access to the
|
||||
OpenStack Service Types Authority data.
|
@ -8,8 +8,9 @@ coverage!=4.4,>=4.0 # Apache-2.0
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx>=1.6.2 # BSD
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
requests-mock>=1.1 # Apache-2.0
|
||||
openstackdocstheme>=1.11.0 # Apache-2.0
|
||||
keystoneauth1>=2.21.0 # Apache-2.0
|
||||
# releasenotes
|
||||
reno!=2.3.1,>=1.8.0 # Apache-2.0
|
||||
|
Loading…
Reference in New Issue
Block a user