Revert "Add bare bones ekko-api"
This implementation of the API is no longer tested or supported. We will be using a new one based on flask This reverts commit a5f99317acb24f757651e4a840d0e17324084300. Change-Id: I7e1a3f0f341685b2455a4cdfeb0b13b7fba5bfc0
This commit is contained in:
parent
e48a55ba0e
commit
34942d3aeb
@ -1,58 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
# TODO(pbourke): add i18n support
|
||||
_ = lambda x: x
|
||||
|
||||
API_SERVICE_OPTS = [
|
||||
cfg.StrOpt('host_ip',
|
||||
default='0.0.0.0',
|
||||
help=_('The IP address on which ekko-api listens.')),
|
||||
cfg.PortOpt('port',
|
||||
default=6800,
|
||||
help=_('The TCP port on which ekko-api listens.')),
|
||||
cfg.IntOpt('max_limit',
|
||||
default=1000,
|
||||
help=_('The maximum number of items returned in a single '
|
||||
'response from a collection resource.')),
|
||||
cfg.StrOpt('public_endpoint',
|
||||
default=None,
|
||||
# TODO(pbourke): decide on port for ekko
|
||||
help=_("Public URL to use when building the links to the API "
|
||||
"resources (for example, \"https://ekko.rocks:6800\")."
|
||||
" If None the links will be built using the request's "
|
||||
"host URL. If the API is operating behind a proxy, you "
|
||||
"will want to change this to represent the proxy's URL. "
|
||||
"Defaults to None.")),
|
||||
cfg.IntOpt('api_workers',
|
||||
help=_('Number of workers for OpenStack ekko API service. '
|
||||
'The default is equal to the number of CPUs available '
|
||||
'if that can be determined, else a default worker '
|
||||
'count of 1 is returned.')),
|
||||
cfg.BoolOpt('enable_ssl_api',
|
||||
default=False,
|
||||
help=_("Enable the integrated stand-alone API to service "
|
||||
"requests via HTTPS instead of HTTP. If there is a "
|
||||
"front-end service performing HTTPS offloading from "
|
||||
"the service, this option should be False; note, you "
|
||||
"will want to change public API endpoint to represent "
|
||||
"SSL termination URL with 'public_endpoint' option.")),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
opt_group = cfg.OptGroup(name='api',
|
||||
title='Options for the ekko-api service')
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(API_SERVICE_OPTS, opt_group)
|
@ -1,95 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pecan
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ekko.api import config
|
||||
# TODO(pbourke): port hooks module
|
||||
# from ekko.api import hooks
|
||||
from ekko.api import middleware
|
||||
|
||||
# TODO(pbourke): add i18n support
|
||||
_ = lambda x: x
|
||||
|
||||
api_opts = [
|
||||
cfg.StrOpt(
|
||||
'auth_strategy',
|
||||
default='keystone',
|
||||
help=_('Authentication strategy used by ekko-api: one of "keystone" '
|
||||
'or "noauth". "noauth" should not be used in a production '
|
||||
'environment because all authentication will be disabled.')),
|
||||
cfg.BoolOpt('debug_tracebacks_in_api',
|
||||
default=False,
|
||||
help=_('Return server tracebacks in the API response for any '
|
||||
'error responses. WARNING: this is insecure '
|
||||
'and should not be used in a production environment.')),
|
||||
cfg.BoolOpt('pecan_debug',
|
||||
default=False,
|
||||
help=_('Enable pecan debug mode. WARNING: this is insecure '
|
||||
'and should not be used in a production environment.')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(api_opts)
|
||||
|
||||
|
||||
def get_pecan_config():
|
||||
# Set up the pecan configuration
|
||||
filename = config.__file__.replace('.pyc', '.py')
|
||||
return pecan.configuration.conf_from_file(filename)
|
||||
|
||||
|
||||
def setup_app(pecan_config=None, extra_hooks=None):
|
||||
# TODO(pbourke): port hooks module
|
||||
# app_hooks = [hooks.ConfigHook(),
|
||||
# hooks.DBHook(),
|
||||
# hooks.ContextHook(pecan_config.app.acl_public_routes),
|
||||
# hooks.RPCHook(),
|
||||
# hooks.NoExceptionTracebackHook(),
|
||||
# hooks.PublicUrlHook()]
|
||||
# if extra_hooks:
|
||||
# app_hooks.extend(extra_hooks)
|
||||
|
||||
if not pecan_config:
|
||||
pecan_config = get_pecan_config()
|
||||
|
||||
# if pecan_config.app.enable_acl:
|
||||
# app_hooks.append(hooks.TrustedCallHook())
|
||||
|
||||
pecan.configuration.set_config(dict(pecan_config), overwrite=True)
|
||||
|
||||
app = pecan.make_app(
|
||||
pecan_config.app.root,
|
||||
static_root=pecan_config.app.static_root,
|
||||
debug=CONF.pecan_debug,
|
||||
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
|
||||
# hooks=app_hooks,
|
||||
wrap_app=middleware.ParsableErrorMiddleware,
|
||||
)
|
||||
|
||||
# if pecan_config.app.enable_acl:
|
||||
# app = acl.install(app, cfg.CONF, pecan_config.app.acl_public_routes)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
class VersionSelectorApplication(object):
|
||||
def __init__(self):
|
||||
pc = get_pecan_config()
|
||||
pc.app.enable_acl = (CONF.auth_strategy == 'keystone')
|
||||
self.v1 = setup_app(pecan_config=pc)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
return self.v1(environ, start_response)
|
@ -1,39 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Server Specific Configurations
|
||||
# See https://pecan.readthedocs.org/en/latest/configuration.html#server-configuration # noqa
|
||||
server = {
|
||||
'port': '6800',
|
||||
'host': '0.0.0.0'
|
||||
}
|
||||
|
||||
# Pecan Application Configurations
|
||||
# See https://pecan.readthedocs.org/en/latest/configuration.html#application-configuration # noqa
|
||||
app = {
|
||||
'root': 'ekko.api.controllers.root.RootController',
|
||||
'modules': ['ekko.api'],
|
||||
'static_root': '%(confdir)s/public',
|
||||
'debug': False,
|
||||
'enable_acl': True,
|
||||
'acl_public_routes': [
|
||||
'/',
|
||||
'/v1',
|
||||
],
|
||||
}
|
||||
|
||||
# WSME Configurations
|
||||
# See https://wsme.readthedocs.org/en/latest/integrate.html#configuration
|
||||
wsme = {
|
||||
'debug': False,
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import datetime
|
||||
import functools
|
||||
|
||||
from webob import exc
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
# TODO(pbourke): add i18n support
|
||||
_ = lambda x: x
|
||||
|
||||
|
||||
class APIBase(wtypes.Base):
|
||||
|
||||
created_at = wsme.wsattr(datetime.datetime, readonly=True)
|
||||
"""The time in UTC at which the object is created"""
|
||||
|
||||
updated_at = wsme.wsattr(datetime.datetime, readonly=True)
|
||||
"""The time in UTC at which the object is updated"""
|
||||
|
||||
def as_dict(self):
|
||||
"""Render this object as a dict of its fields."""
|
||||
return dict((k, getattr(self, k))
|
||||
for k in self.fields
|
||||
if hasattr(self, k) and
|
||||
getattr(self, k) != wsme.Unset)
|
||||
|
||||
def unset_fields_except(self, except_list=None):
|
||||
"""Unset fields so they don't appear in the message body.
|
||||
|
||||
:param except_list: A list of fields that won't be touched.
|
||||
|
||||
"""
|
||||
if except_list is None:
|
||||
except_list = []
|
||||
|
||||
for k in self.as_dict():
|
||||
if k not in except_list:
|
||||
setattr(self, k, wsme.Unset)
|
||||
|
||||
|
||||
@functools.total_ordering
|
||||
class Version(object):
|
||||
"""API Version object."""
|
||||
|
||||
string = 'X-OpenStack-Ekko-API-Version'
|
||||
"""HTTP Header string carrying the requested version"""
|
||||
|
||||
min_string = 'X-OpenStack-Ekko-API-Minimum-Version'
|
||||
"""HTTP response header"""
|
||||
|
||||
max_string = 'X-OpenStack-Ekko-API-Maximum-Version'
|
||||
"""HTTP response header"""
|
||||
|
||||
def __init__(self, headers, default_version, latest_version):
|
||||
"""Create an API Version object from the supplied headers.
|
||||
|
||||
:param headers: webob headers
|
||||
:param default_version: version to use if not specified in headers
|
||||
:param latest_version: version to use if latest is requested
|
||||
:raises: webob.HTTPNotAcceptable
|
||||
"""
|
||||
(self.major, self.minor) = Version.parse_headers(
|
||||
headers, default_version, latest_version)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s.%s' % (self.major, self.minor)
|
||||
|
||||
@staticmethod
|
||||
def parse_headers(headers, default_version, latest_version):
|
||||
"""Determine the API version requested based on the headers supplied.
|
||||
|
||||
:param headers: webob headers
|
||||
:param default_version: version to use if not specified in headers
|
||||
:param latest_version: version to use if latest is requested
|
||||
:returns: a tupe of (major, minor) version numbers
|
||||
:raises: webob.HTTPNotAcceptable
|
||||
"""
|
||||
version_str = headers.get(Version.string, default_version)
|
||||
|
||||
if version_str.lower() == 'latest':
|
||||
parse_str = latest_version
|
||||
else:
|
||||
parse_str = version_str
|
||||
|
||||
try:
|
||||
version = tuple(int(i) for i in parse_str.split('.'))
|
||||
except ValueError:
|
||||
version = ()
|
||||
|
||||
if len(version) != 2:
|
||||
raise exc.HTTPNotAcceptable(_(
|
||||
"Invalid value for %s header") % Version.string)
|
||||
return version
|
||||
|
||||
def __gt__(a, b):
|
||||
return (a.major, a.minor) > (b.major, b.minor)
|
||||
|
||||
def __eq__(a, b):
|
||||
return (a.major, a.minor) == (b.major, b.minor)
|
@ -1,111 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from wsme import types as wtypes
|
||||
|
||||
from ekko.api.controllers import base
|
||||
from ekko.api.controllers import v1
|
||||
from ekko.api.controllers.v1 import versions
|
||||
from ekko.api import expose
|
||||
|
||||
ID_VERSION1 = 'v1'
|
||||
|
||||
|
||||
class Version(base.APIBase):
|
||||
"""An API version representation.
|
||||
|
||||
This class represents an API version, including the minimum and
|
||||
maximum minor versions that are supported within the major version.
|
||||
"""
|
||||
|
||||
id = wtypes.text
|
||||
"""The ID of the (major) version, also acts as the release number"""
|
||||
|
||||
status = wtypes.text
|
||||
"""Status of the version.
|
||||
|
||||
One of:
|
||||
* CURRENT - the latest version of API,
|
||||
* SUPPORTED - supported, but not latest, version of API,
|
||||
* DEPRECATED - supported, but deprecated, version of API.
|
||||
"""
|
||||
|
||||
version = wtypes.text
|
||||
"""The current, maximum supported (major.minor) version of API."""
|
||||
|
||||
min_version = wtypes.text
|
||||
"""Minimum supported (major.minor) version of API."""
|
||||
|
||||
def __init__(self, id, min_version, version, status='CURRENT'):
|
||||
self.id = id
|
||||
self.status = status
|
||||
self.version = version
|
||||
self.min_version = min_version
|
||||
|
||||
|
||||
class Root(base.APIBase):
|
||||
|
||||
name = wtypes.text
|
||||
"""The name of the API"""
|
||||
|
||||
description = wtypes.text
|
||||
"""Some information about this API"""
|
||||
|
||||
versions = [Version]
|
||||
"""Links to all the versions available in this API"""
|
||||
|
||||
default_version = Version
|
||||
"""A link to the default version of the API"""
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
root = Root()
|
||||
root.name = "OpenStack Ekko API"
|
||||
root.description = ("Block-based backups stored in object-storage.")
|
||||
root.default_version = Version(ID_VERSION1,
|
||||
versions.MIN_VERSION_STRING,
|
||||
versions.MAX_VERSION_STRING)
|
||||
root.versions = [root.default_version]
|
||||
return root
|
||||
|
||||
|
||||
class RootController(rest.RestController):
|
||||
|
||||
_versions = [ID_VERSION1]
|
||||
"""All supported API versions"""
|
||||
|
||||
_default_version = ID_VERSION1
|
||||
"""The default API version"""
|
||||
|
||||
v1 = v1.Controller()
|
||||
|
||||
@expose.expose(Root)
|
||||
def get(self):
|
||||
# NOTE: The reason why convert() it's being called for every
|
||||
# request is because we need to get the host url from
|
||||
# the request object to make the links.
|
||||
return Root.convert()
|
||||
|
||||
@pecan.expose()
|
||||
def _route(self, args):
|
||||
"""Overrides the default routing behavior.
|
||||
|
||||
It redirects the request to the default version of the ekko API
|
||||
if the version number is not specified in the url.
|
||||
"""
|
||||
|
||||
if args[0] and args[0] not in self._versions:
|
||||
args = [self._default_version] + args
|
||||
return super(RootController, self)._route(args)
|
@ -1,119 +0,0 @@
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from webob import exc
|
||||
from wsme import types as wtypes
|
||||
|
||||
from ekko.api.controllers import base
|
||||
from ekko.api.controllers.v1 import versions
|
||||
from ekko.api import expose
|
||||
|
||||
# TODO(pbourke): add i18n support
|
||||
_ = lambda x: x
|
||||
|
||||
BASE_VERSION = versions.BASE_VERSION
|
||||
|
||||
MIN_VER = base.Version(
|
||||
{base.Version.string: versions.MIN_VERSION_STRING},
|
||||
versions.MIN_VERSION_STRING, versions.MAX_VERSION_STRING)
|
||||
MAX_VER = base.Version(
|
||||
{base.Version.string: versions.MAX_VERSION_STRING},
|
||||
versions.MIN_VERSION_STRING, versions.MAX_VERSION_STRING)
|
||||
|
||||
|
||||
class MediaType(base.APIBase):
|
||||
"""A media type representation."""
|
||||
|
||||
base = wtypes.text
|
||||
type = wtypes.text
|
||||
|
||||
def __init__(self, base, type):
|
||||
self.base = base
|
||||
self.type = type
|
||||
|
||||
|
||||
class V1(base.APIBase):
|
||||
"""The representation of the version 1 of the API."""
|
||||
|
||||
id = wtypes.text
|
||||
"""The ID of the version, also acts as the release number"""
|
||||
|
||||
media_types = [MediaType]
|
||||
"""An array of supported media types for this version"""
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
v1 = V1()
|
||||
v1.id = "v1"
|
||||
|
||||
v1.media_types = [MediaType('application/json',
|
||||
'application/vnd.openstack.ekko.v1+json')]
|
||||
|
||||
return v1
|
||||
|
||||
|
||||
class Controller(rest.RestController):
|
||||
"""Version 1 API controller root."""
|
||||
|
||||
@expose.expose(V1)
|
||||
def get(self):
|
||||
# NOTE: The reason why convert() it's being called for every
|
||||
# request is because we need to get the host url from
|
||||
# the request object to make the links.
|
||||
return V1.convert()
|
||||
|
||||
def _check_version(self, version, headers=None):
|
||||
if headers is None:
|
||||
headers = {}
|
||||
# ensure that major version in the URL matches the header
|
||||
if version.major != BASE_VERSION:
|
||||
raise exc.HTTPNotAcceptable(_(
|
||||
"Mutually exclusive versions requested. Version %(ver)s "
|
||||
"requested but not supported by this service. The supported "
|
||||
"version range is: [%(min)s, %(max)s].") %
|
||||
{'ver': version, 'min': versions.MIN_VERSION_STRING,
|
||||
'max': versions.MAX_VERSION_STRING},
|
||||
headers=headers)
|
||||
# ensure the minor version is within the supported range
|
||||
if version < MIN_VER or version > MAX_VER:
|
||||
raise exc.HTTPNotAcceptable(_(
|
||||
"Version %(ver)s was requested but the minor version is not "
|
||||
"supported by this service. The supported version range is: "
|
||||
"[%(min)s, %(max)s].") %
|
||||
{'ver': version, 'min': versions.MIN_VERSION_STRING,
|
||||
'max': versions.MAX_VERSION_STRING},
|
||||
headers=headers)
|
||||
|
||||
@pecan.expose()
|
||||
def _route(self, args):
|
||||
v = base.Version(pecan.request.headers, versions.MIN_VERSION_STRING,
|
||||
versions.MAX_VERSION_STRING)
|
||||
|
||||
# Always set the min and max headers
|
||||
pecan.response.headers[base.Version.min_string] = (
|
||||
versions.MIN_VERSION_STRING)
|
||||
pecan.response.headers[base.Version.max_string] = (
|
||||
versions.MAX_VERSION_STRING)
|
||||
|
||||
# assert that requested version is supported
|
||||
self._check_version(v, pecan.response.headers)
|
||||
pecan.response.headers[base.Version.string] = str(v)
|
||||
pecan.request.version = v
|
||||
|
||||
return super(Controller, self)._route(args)
|
||||
|
||||
|
||||
__all__ = (Controller)
|
@ -1,32 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This is the version 1 API
|
||||
BASE_VERSION = 1
|
||||
|
||||
# Here goes a short log of changes in every version.
|
||||
# Refer to doc/source/webapi/v1.rst for a detailed explanation of what
|
||||
# each version contains.
|
||||
#
|
||||
# v1.0: Initial version of the Ekko API
|
||||
|
||||
MINOR_0_INITIAL_VERSION = 0
|
||||
|
||||
# When adding another version, update MINOR_MAX_VERSION and also update
|
||||
# doc/source/webapi/v1.rst with a detailed explanation of what the version has
|
||||
# changed.
|
||||
MINOR_MAX_VERSION = MINOR_0_INITIAL_VERSION
|
||||
|
||||
# String representations of the minor and maximum versions
|
||||
MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_0_INITIAL_VERSION)
|
||||
MAX_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_MAX_VERSION)
|
@ -1,21 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
|
||||
def expose(*args, **kwargs):
|
||||
"""Ensure that only JSON, and not XML, is supported."""
|
||||
if 'rest_content_types' not in kwargs:
|
||||
kwargs['rest_content_types'] = ('json',)
|
||||
return wsme_pecan.wsexpose(*args, **kwargs)
|
@ -1,22 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
from ekko.api.middleware import auth_token
|
||||
from ekko.api.middleware import parsable_error
|
||||
|
||||
|
||||
ParsableErrorMiddleware = parsable_error.ParsableErrorMiddleware
|
||||
AuthTokenMiddleware = auth_token.AuthTokenMiddleware
|
||||
|
||||
__all__ = (ParsableErrorMiddleware,
|
||||
AuthTokenMiddleware)
|
@ -1,64 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import re
|
||||
|
||||
from keystonemiddleware import auth_token
|
||||
from oslo_log import log
|
||||
|
||||
from ekko.common import exception
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
# TODO(pbourke): add i18n support
|
||||
_ = lambda x: x
|
||||
|
||||
|
||||
class AuthTokenMiddleware(auth_token.AuthProtocol):
|
||||
"""A wrapper on Keystone auth_token middleware.
|
||||
|
||||
Does not perform verification of authentication tokens
|
||||
for public routes in the API.
|
||||
|
||||
"""
|
||||
def __init__(self, app, conf, public_api_routes=[]):
|
||||
self._ekko_app = app
|
||||
# TODO(mrda): Remove .xml and ensure that doesn't result in a
|
||||
# 401 Authentication Required instead of 404 Not Found
|
||||
route_pattern_tpl = '%s(\.json|\.xml)?$'
|
||||
|
||||
try:
|
||||
self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl)
|
||||
for route_tpl in public_api_routes]
|
||||
except re.error as e:
|
||||
msg = _('Cannot compile public API routes: %s') % e
|
||||
|
||||
LOG.error(msg)
|
||||
raise exception.ConfigInvalid(error_msg=msg)
|
||||
|
||||
super(AuthTokenMiddleware, self).__init__(app, conf)
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
# TODO(pbourke): ironic uses a utils.safe_rstrip function here.
|
||||
path = env.get('PATH_INFO').rstrip('/')
|
||||
|
||||
# The information whether the API call is being performed against the
|
||||
# public API is required for some other components. Saving it to the
|
||||
# WSGI environment is reasonable thereby.
|
||||
env['is_public_api'] = any(map(lambda pattern: re.match(pattern, path),
|
||||
self.public_api_routes))
|
||||
|
||||
if env['is_public_api']:
|
||||
return self._ekko_app(env, start_response)
|
||||
|
||||
return super(AuthTokenMiddleware, self).__call__(env, start_response)
|
@ -1,93 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Middleware to replace the plain text message body of an error
|
||||
response with one formatted so the client can parse it.
|
||||
|
||||
Based on pecan.middleware.errordocument
|
||||
"""
|
||||
|
||||
import json
|
||||
from xml import etree as et
|
||||
|
||||
from oslo_log import log
|
||||
import six
|
||||
import webob
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
# TODO(pbourke): add i18n support
|
||||
_ = _LE = lambda x: x
|
||||
|
||||
|
||||
class ParsableErrorMiddleware(object):
|
||||
"""Replace error body with something the client can parse."""
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
# Request for this state, modified by replace_start_response()
|
||||
# and used when an error is being reported.
|
||||
state = {}
|
||||
|
||||
def replacement_start_response(status, headers, exc_info=None):
|
||||
"""Overrides the default response to make errors parsable."""
|
||||
try:
|
||||
status_code = int(status.split(' ')[0])
|
||||
state['status_code'] = status_code
|
||||
except (ValueError, TypeError): # pragma: nocover
|
||||
raise Exception(_(
|
||||
'ErrorDocumentMiddleware received an invalid '
|
||||
'status %s') % status)
|
||||
else:
|
||||
if (state['status_code'] // 100) not in (2, 3):
|
||||
# Remove some headers so we can replace them later
|
||||
# when we have the full error message and can
|
||||
# compute the length.
|
||||
headers = [(h, v)
|
||||
for (h, v) in headers
|
||||
if h not in ('Content-Length', 'Content-Type')
|
||||
]
|
||||
# Save the headers in case we need to modify them.
|
||||
state['headers'] = headers
|
||||
return start_response(status, headers, exc_info)
|
||||
|
||||
app_iter = self.app(environ, replacement_start_response)
|
||||
if (state['status_code'] // 100) not in (2, 3):
|
||||
req = webob.Request(environ)
|
||||
if (req.accept.best_match(['application/json', 'application/xml'])
|
||||
== 'application/xml'):
|
||||
try:
|
||||
# simple check xml is valid
|
||||
body = [et.ElementTree.tostring(
|
||||
et.ElementTree.fromstring('<error_message>'
|
||||
+ '\n'.join(app_iter)
|
||||
+ '</error_message>'))]
|
||||
except et.ElementTree.ParseError as err:
|
||||
LOG.error(_LE('Error parsing HTTP response: %s'), err)
|
||||
body = ['<error_message>%s' % state['status_code']
|
||||
+ '</error_message>']
|
||||
state['headers'].append(('Content-Type', 'application/xml'))
|
||||
else:
|
||||
if six.PY3:
|
||||
app_iter = [i.decode('utf-8') for i in app_iter]
|
||||
body = [json.dumps({'error_message': '\n'.join(app_iter)})]
|
||||
if six.PY3:
|
||||
body = [item.encode('utf-8') for item in body]
|
||||
state['headers'].append(('Content-Type', 'application/json'))
|
||||
state['headers'].append(('Content-Length', str(len(body[0]))))
|
||||
else:
|
||||
body = app_iter
|
||||
return body
|
@ -1,34 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import sys
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ekko.common import service as ekko_service
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def main():
|
||||
# Parse config file and command line options, then start logging
|
||||
ekko_service.prepare_service(sys.argv)
|
||||
|
||||
# Build and start the WSGI app
|
||||
launcher = ekko_service.process_launcher()
|
||||
server = ekko_service.WSGIService('ekko_api', CONF.api.enable_ssl_api)
|
||||
launcher.launch_service(server, workers=server.workers)
|
||||
launcher.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
@ -1,26 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
# from ekko.common import rpc
|
||||
# from ekko import version
|
||||
|
||||
|
||||
def parse_args(argv, default_config_files=None):
|
||||
# rpc.set_defaults(control_exchange='ekko')
|
||||
cfg.CONF(argv[1:],
|
||||
project='ekko',
|
||||
# version=version.version_info.release_string(),
|
||||
default_config_files=default_config_files)
|
||||
# rpc.init(cfg.CONF)
|
@ -1,131 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
from six.moves import http_client
|
||||
|
||||
# TODO(pbourke): add i18n support
|
||||
_ = _LE = _LW = lambda x: x
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
exc_log_opts = [
|
||||
cfg.BoolOpt('fatal_exception_format_errors',
|
||||
default=False,
|
||||
help=_('Used if there is a formatting error when generating '
|
||||
'an exception message (a programming error). If True, '
|
||||
'raise an exception; if False, use the unformatted '
|
||||
'message.')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(exc_log_opts)
|
||||
|
||||
|
||||
class EkkoException(Exception):
|
||||
"""Base Ekko Exception
|
||||
|
||||
To correctly use this class, inherit from it and define
|
||||
a '_msg_fmt' property. That message will get printf'd
|
||||
with the keyword arguments provided to the constructor.
|
||||
|
||||
If you need to access the message from an exception you should use
|
||||
six.text_type(exc)
|
||||
|
||||
"""
|
||||
_msg_fmt = _("An unknown exception occurred.")
|
||||
code = http_client.INTERNAL_SERVER_ERROR
|
||||
headers = {}
|
||||
safe = False
|
||||
|
||||
def __init__(self, message=None, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
if 'code' not in self.kwargs:
|
||||
try:
|
||||
self.kwargs['code'] = self.code
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if not message:
|
||||
# Check if class is using deprecated 'message' attribute.
|
||||
if (hasattr(self, 'message') and self.message):
|
||||
LOG.warning(_LW("Exception class: %s Using the 'message' "
|
||||
"attribute in an exception has been "
|
||||
"deprecated. The exception class should be "
|
||||
"modified to use the '_msg_fmt' "
|
||||
"attribute."), self.__class__.__name__)
|
||||
self._msg_fmt = self.message
|
||||
|
||||
try:
|
||||
message = self._msg_fmt % kwargs
|
||||
|
||||
except Exception as e:
|
||||
# kwargs doesn't match a variable in self._msg_fmt
|
||||
# log the issue and the kwargs
|
||||
LOG.exception(_LE('Exception in string format operation'))
|
||||
for name, value in kwargs.items():
|
||||
LOG.error("%s: %s" % (name, value))
|
||||
|
||||
if CONF.fatal_exception_format_errors:
|
||||
raise e
|
||||
else:
|
||||
# at least get the core self._msg_fmt out if something
|
||||
# happened
|
||||
message = self._msg_fmt
|
||||
|
||||
super(EkkoException, self).__init__(message)
|
||||
|
||||
def __str__(self):
|
||||
"""Encode to utf-8 then wsme api can consume it as well."""
|
||||
if not six.PY3:
|
||||
return unicode(self.args[0]).encode('utf-8')
|
||||
|
||||
return self.args[0]
|
||||
|
||||
def __unicode__(self):
|
||||
"""Return a unicode representation of the exception message."""
|
||||
return unicode(self.args[0])
|
||||
|
||||
|
||||
class NotAuthorized(EkkoException):
|
||||
_msg_fmt = _("Not authorized.")
|
||||
code = http_client.FORBIDDEN
|
||||
|
||||
|
||||
class OperationNotPermitted(NotAuthorized):
|
||||
_msg_fmt = _("Operation not permitted.")
|
||||
|
||||
|
||||
class Invalid(EkkoException):
|
||||
_msg_fmt = _("Unacceptable parameters.")
|
||||
code = http_client.BAD_REQUEST
|
||||
|
||||
|
||||
class Conflict(EkkoException):
|
||||
_msg_fmt = _('Conflict.')
|
||||
code = http_client.CONFLICT
|
||||
|
||||
|
||||
class TemporaryFailure(EkkoException):
|
||||
_msg_fmt = _("Resource temporarily unavailable, please retry.")
|
||||
code = http_client.SERVICE_UNAVAILABLE
|
||||
|
||||
|
||||
class NotAcceptable(EkkoException):
|
||||
# TODO(deva): We need to set response headers in the API for this exception
|
||||
_msg_fmt = _("Request not acceptable.")
|
||||
code = http_client.NOT_ACCEPTABLE
|
@ -1,109 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import socket
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_service import service
|
||||
from oslo_service import wsgi
|
||||
|
||||
from ekko.api import app
|
||||
from ekko.common import config
|
||||
|
||||
# TODO(pbourke): add i18n support
|
||||
_ = lambda x: x
|
||||
|
||||
|
||||
service_opts = [
|
||||
cfg.IntOpt('periodic_interval',
|
||||
default=60,
|
||||
help=_('Seconds between running periodic tasks.')),
|
||||
cfg.StrOpt('host',
|
||||
default=socket.getfqdn(),
|
||||
help=_('Name of this node. This can be an opaque identifier. '
|
||||
'It is not necessarily a hostname, FQDN, or IP address. '
|
||||
'However, the node name must be valid within '
|
||||
'an AMQP key, and if using ZeroMQ, a valid '
|
||||
'hostname, FQDN, or IP address.')),
|
||||
]
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
CONF.register_opts(service_opts)
|
||||
|
||||
|
||||
def prepare_service(argv=[]):
|
||||
log.register_options(CONF)
|
||||
config.parse_args(argv)
|
||||
log.setup(CONF, 'ekko')
|
||||
|
||||
|
||||
def process_launcher():
|
||||
return service.ProcessLauncher(CONF)
|
||||
|
||||
|
||||
class WSGIService(service.ServiceBase):
|
||||
"""Provides ability to launch ekko API from wsgi app."""
|
||||
|
||||
def __init__(self, name, use_ssl=False):
|
||||
"""Initialize, but do not start the WSGI server.
|
||||
|
||||
:param name: The name of the WSGI server given to the loader.
|
||||
:param use_ssl: Wraps the socket in an SSL context if True.
|
||||
:returns: None
|
||||
"""
|
||||
self.name = name
|
||||
self.app = app.VersionSelectorApplication()
|
||||
self.workers = (CONF.api.api_workers or
|
||||
processutils.get_worker_count())
|
||||
if self.workers and self.workers < 1:
|
||||
raise Exception(
|
||||
_("api_workers value of %d is invalid, "
|
||||
"must be greater than 0.") % self.workers)
|
||||
|
||||
self.server = wsgi.Server(CONF, name, self.app,
|
||||
host=CONF.api.host_ip,
|
||||
port=CONF.api.port,
|
||||
use_ssl=use_ssl)
|
||||
|
||||
def start(self):
|
||||
"""Start serving this service using loaded configuration.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
self.server.start()
|
||||
|
||||
def stop(self):
|
||||
"""Stop serving this API.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
self.server.stop()
|
||||
|
||||
def wait(self):
|
||||
"""Wait for the service to stop serving this API.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
self.server.wait()
|
||||
|
||||
def reset(self):
|
||||
"""Reset server greenpool size to default.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
self.server.reset()
|
@ -1,142 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2010-2011 OpenStack Foundation
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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.
|
||||
|
||||
"""Base classes for our unit tests.
|
||||
|
||||
Allows overriding of config for use of fakes, and some black magic for
|
||||
inline callbacks.
|
||||
|
||||
"""
|
||||
|
||||
# import copy
|
||||
import os
|
||||
import sys
|
||||
# import tempfile
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch(os=False)
|
||||
import fixtures
|
||||
from oslo_config import cfg
|
||||
from oslo_context import context as ekko_context
|
||||
from oslo_log import log as logging
|
||||
import testtools
|
||||
|
||||
# from ekko.objects import base as objects_base
|
||||
from ekko.tests.unit import conf_fixture
|
||||
# from ekko.tests.unit import policy_fixture
|
||||
from oslotest import base
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
logging.register_options(CONF)
|
||||
CONF.set_override('use_stderr', False)
|
||||
class TestCase(base.BaseTestCase):
|
||||
|
||||
logging.setup(CONF, 'ekko')
|
||||
|
||||
|
||||
class ReplaceModule(fixtures.Fixture):
|
||||
"""Replace a module with a fake module."""
|
||||
|
||||
def __init__(self, name, new_value):
|
||||
self.name = name
|
||||
self.new_value = new_value
|
||||
|
||||
def _restore(self, old_value):
|
||||
sys.modules[self.name] = old_value
|
||||
|
||||
def setUp(self):
|
||||
super(ReplaceModule, self).setUp()
|
||||
old_value = sys.modules.get(self.name)
|
||||
sys.modules[self.name] = self.new_value
|
||||
self.addCleanup(self._restore, old_value)
|
||||
|
||||
|
||||
class TestingException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
"""Test case base class for all unit tests."""
|
||||
|
||||
def setUp(self):
|
||||
"""Run before each test method to initialize test environment."""
|
||||
super(TestCase, self).setUp()
|
||||
self.context = ekko_context.get_admin_context()
|
||||
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
|
||||
try:
|
||||
test_timeout = int(test_timeout)
|
||||
except ValueError:
|
||||
# If timeout value is invalid do not set a timeout.
|
||||
test_timeout = 0
|
||||
if test_timeout > 0:
|
||||
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
|
||||
self.useFixture(fixtures.NestedTempfile())
|
||||
self.useFixture(fixtures.TempHomeDir())
|
||||
# self.config(tempdir=tempfile.tempdir)
|
||||
|
||||
if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
|
||||
os.environ.get('OS_STDOUT_CAPTURE') == '1'):
|
||||
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
|
||||
if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
|
||||
os.environ.get('OS_STDERR_CAPTURE') == '1'):
|
||||
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
|
||||
|
||||
self.log_fixture = self.useFixture(fixtures.FakeLogger())
|
||||
self.useFixture(conf_fixture.ConfFixture(CONF))
|
||||
|
||||
# NOTE(danms): Make sure to reset us back to non-remote objects
|
||||
# for each test to avoid interactions. Also, backup the object
|
||||
# registry
|
||||
# objects_base.EkkoObject.indirection_api = None
|
||||
# self._base_test_obj_backup = copy.copy(
|
||||
# objects_base.EkkoObjectRegistry.obj_classes())
|
||||
# self.addCleanup(self._restore_obj_registry)
|
||||
|
||||
self.addCleanup(self._clear_attrs)
|
||||
self.useFixture(fixtures.EnvironmentVariable('http_proxy'))
|
||||
# self.policy = self.useFixture(policy_fixture.PolicyFixture())
|
||||
# CONF.set_override('fatal_exception_format_errors', True)
|
||||
|
||||
# def _restore_obj_registry(self):
|
||||
# objects_base.EkkoObjectRegistry._registry._obj_classes = (
|
||||
# self._base_test_obj_backup)
|
||||
|
||||
def _clear_attrs(self):
|
||||
# Delete attributes that don't start with _ so they don't pin
|
||||
# memory around unnecessarily for the duration of the test
|
||||
# suite
|
||||
for key in [k for k in self.__dict__.keys() if k[0] != '_']:
|
||||
del self.__dict__[key]
|
||||
|
||||
def config(self, **kw):
|
||||
"""Override config options for a test."""
|
||||
group = kw.pop('group', None)
|
||||
for k, v in kw.items():
|
||||
CONF.set_override(k, v, group)
|
||||
|
||||
def path_get(self, project_file=None):
|
||||
"""Get the absolute path to a file. Used for testing the API.
|
||||
|
||||
:param project_file: File whose path to return. Default: None.
|
||||
:returns: path to the specified file, or path to project root.
|
||||
"""
|
||||
root = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'..',
|
||||
'..',
|
||||
)
|
||||
)
|
||||
if project_file:
|
||||
return os.path.join(root, project_file)
|
||||
else:
|
||||
return root
|
||||
|
@ -1,237 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
# NOTE: Ported from ceilometer/tests/api.py (subsequently moved to
|
||||
# ceilometer/tests/api/__init__.py). This should be oslo'ified:
|
||||
# https://bugs.launchpad.net/ekko/+bug/1255115.
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import pecan
|
||||
import pecan.testing
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
# from ekko.tests.unit.db import base
|
||||
from ekko.tests import base
|
||||
|
||||
PATH_PREFIX = '/v1'
|
||||
|
||||
cfg.CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token')
|
||||
|
||||
|
||||
# class BaseApiTest(base.DbTestCase):
|
||||
class BaseApiTest(base.TestCase):
|
||||
"""Pecan controller functional testing class.
|
||||
|
||||
Used for functional tests of Pecan controllers where you need to
|
||||
test your literal application and its integration with the
|
||||
framework.
|
||||
"""
|
||||
|
||||
SOURCE_DATA = {'test_source': {'somekey': '666'}}
|
||||
|
||||
def setUp(self):
|
||||
super(BaseApiTest, self).setUp()
|
||||
cfg.CONF.set_override("auth_version", "v2.0",
|
||||
group='keystone_authtoken')
|
||||
cfg.CONF.set_override("admin_user", "admin",
|
||||
group='keystone_authtoken')
|
||||
self.app = self._make_app()
|
||||
|
||||
def reset_pecan():
|
||||
pecan.set_config({}, overwrite=True)
|
||||
|
||||
self.addCleanup(reset_pecan)
|
||||
|
||||
p = mock.patch('ekko.api.controllers.v1.Controller._check_version')
|
||||
self._check_version = p.start()
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
def _make_app(self, enable_acl=False):
|
||||
# Determine where we are so we can set up paths in the config
|
||||
root_dir = self.path_get()
|
||||
|
||||
self.config = {
|
||||
'app': {
|
||||
'root': 'ekko.api.controllers.root.RootController',
|
||||
'modules': ['ekko.api'],
|
||||
'static_root': '%s/public' % root_dir,
|
||||
'template_path': '%s/api/templates' % root_dir,
|
||||
'enable_acl': enable_acl,
|
||||
'acl_public_routes': ['/', '/v1'],
|
||||
},
|
||||
}
|
||||
|
||||
return pecan.testing.load_test_app(self.config)
|
||||
|
||||
def _request_json(self, path, params, expect_errors=False, headers=None,
|
||||
method="post", extra_environ=None, status=None,
|
||||
path_prefix=PATH_PREFIX):
|
||||
"""Sends simulated HTTP request to Pecan test app.
|
||||
|
||||
:param path: url path of target service
|
||||
:param params: content for wsgi.input of request
|
||||
:param expect_errors: Boolean value; whether an error is expected based
|
||||
on request
|
||||
:param headers: a dictionary of headers to send along with the request
|
||||
:param method: Request method type. Appropriate method function call
|
||||
should be used rather than passing attribute in.
|
||||
:param extra_environ: a dictionary of environ variables to send along
|
||||
with the request
|
||||
:param status: expected status code of response
|
||||
:param path_prefix: prefix of the url path
|
||||
"""
|
||||
full_path = path_prefix + path
|
||||
print('%s: %s %s' % (method.upper(), full_path, params))
|
||||
response = getattr(self.app, "%s_json" % method)(
|
||||
str(full_path),
|
||||
params=params,
|
||||
headers=headers,
|
||||
status=status,
|
||||
extra_environ=extra_environ,
|
||||
expect_errors=expect_errors
|
||||
)
|
||||
print('GOT:%s' % response)
|
||||
return response
|
||||
|
||||
def put_json(self, path, params, expect_errors=False, headers=None,
|
||||
extra_environ=None, status=None):
|
||||
"""Sends simulated HTTP PUT request to Pecan test app.
|
||||
|
||||
:param path: url path of target service
|
||||
:param params: content for wsgi.input of request
|
||||
:param expect_errors: Boolean value; whether an error is expected based
|
||||
on request
|
||||
:param headers: a dictionary of headers to send along with the request
|
||||
:param extra_environ: a dictionary of environ variables to send along
|
||||
with the request
|
||||
:param status: expected status code of response
|
||||
"""
|
||||
return self._request_json(path=path, params=params,
|
||||
expect_errors=expect_errors,
|
||||
headers=headers, extra_environ=extra_environ,
|
||||
status=status, method="put")
|
||||
|
||||
def post_json(self, path, params, expect_errors=False, headers=None,
|
||||
extra_environ=None, status=None):
|
||||
"""Sends simulated HTTP POST request to Pecan test app.
|
||||
|
||||
:param path: url path of target service
|
||||
:param params: content for wsgi.input of request
|
||||
:param expect_errors: Boolean value; whether an error is expected based
|
||||
on request
|
||||
:param headers: a dictionary of headers to send along with the request
|
||||
:param extra_environ: a dictionary of environ variables to send along
|
||||
with the request
|
||||
:param status: expected status code of response
|
||||
"""
|
||||
return self._request_json(path=path, params=params,
|
||||
expect_errors=expect_errors,
|
||||
headers=headers, extra_environ=extra_environ,
|
||||
status=status, method="post")
|
||||
|
||||
def patch_json(self, path, params, expect_errors=False, headers=None,
|
||||
extra_environ=None, status=None):
|
||||
"""Sends simulated HTTP PATCH request to Pecan test app.
|
||||
|
||||
:param path: url path of target service
|
||||
:param params: content for wsgi.input of request
|
||||
:param expect_errors: Boolean value; whether an error is expected based
|
||||
on request
|
||||
:param headers: a dictionary of headers to send along with the request
|
||||
:param extra_environ: a dictionary of environ variables to send along
|
||||
with the request
|
||||
:param status: expected status code of response
|
||||
"""
|
||||
return self._request_json(path=path, params=params,
|
||||
expect_errors=expect_errors,
|
||||
headers=headers, extra_environ=extra_environ,
|
||||
status=status, method="patch")
|
||||
|
||||
def delete(self, path, expect_errors=False, headers=None,
|
||||
extra_environ=None, status=None, path_prefix=PATH_PREFIX):
|
||||
"""Sends simulated HTTP DELETE request to Pecan test app.
|
||||
|
||||
:param path: url path of target service
|
||||
:param expect_errors: Boolean value; whether an error is expected based
|
||||
on request
|
||||
:param headers: a dictionary of headers to send along with the request
|
||||
:param extra_environ: a dictionary of environ variables to send along
|
||||
with the request
|
||||
:param status: expected status code of response
|
||||
:param path_prefix: prefix of the url path
|
||||
"""
|
||||
full_path = path_prefix + path
|
||||
print('DELETE: %s' % (full_path))
|
||||
response = self.app.delete(str(full_path),
|
||||
headers=headers,
|
||||
status=status,
|
||||
extra_environ=extra_environ,
|
||||
expect_errors=expect_errors)
|
||||
print('GOT:%s' % response)
|
||||
return response
|
||||
|
||||
def get_json(self, path, expect_errors=False, headers=None,
|
||||
extra_environ=None, q=[], path_prefix=PATH_PREFIX, **params):
|
||||
"""Sends simulated HTTP GET request to Pecan test app.
|
||||
|
||||
:param path: url path of target service
|
||||
:param expect_errors: Boolean value;whether an error is expected based
|
||||
on request
|
||||
:param headers: a dictionary of headers to send along with the request
|
||||
:param extra_environ: a dictionary of environ variables to send along
|
||||
with the request
|
||||
:param q: list of queries consisting of: field, value, op, and type
|
||||
keys
|
||||
:param path_prefix: prefix of the url path
|
||||
:param params: content for wsgi.input of request
|
||||
"""
|
||||
full_path = path_prefix + path
|
||||
query_params = {'q.field': [],
|
||||
'q.value': [],
|
||||
'q.op': [],
|
||||
}
|
||||
for query in q:
|
||||
for name in ['field', 'op', 'value']:
|
||||
query_params['q.%s' % name].append(query.get(name, ''))
|
||||
all_params = {}
|
||||
all_params.update(params)
|
||||
if q:
|
||||
all_params.update(query_params)
|
||||
print('GET: %s %r' % (full_path, all_params))
|
||||
response = self.app.get(full_path,
|
||||
params=all_params,
|
||||
headers=headers,
|
||||
extra_environ=extra_environ,
|
||||
expect_errors=expect_errors)
|
||||
if not expect_errors:
|
||||
response = response.json
|
||||
print('GOT:%s' % response)
|
||||
return response
|
||||
|
||||
def validate_link(self, link, bookmark=False):
|
||||
"""Checks if the given link can get correct data."""
|
||||
# removes the scheme and net location parts of the link
|
||||
url_parts = list(urlparse.urlparse(link))
|
||||
url_parts[0] = url_parts[1] = ''
|
||||
|
||||
# bookmark link should not have the version in the URL
|
||||
if bookmark and url_parts[2].startswith(PATH_PREFIX):
|
||||
return False
|
||||
|
||||
full_path = urlparse.urlunparse(url_parts)
|
||||
try:
|
||||
self.get_json(full_path, path_prefix='')
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
@ -1,117 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
from six.moves import http_client
|
||||
from webob import exc
|
||||
|
||||
from ekko.api.controllers import base as cbase
|
||||
from ekko.tests.unit.api import base
|
||||
|
||||
|
||||
class TestBase(base.BaseApiTest):
|
||||
|
||||
def test_api_setup(self):
|
||||
pass
|
||||
|
||||
def test_bad_uri(self):
|
||||
response = self.get_json('/bad/path',
|
||||
expect_errors=True,
|
||||
headers={"Accept": "application/json"})
|
||||
self.assertEqual(http_client.NOT_FOUND, response.status_int)
|
||||
self.assertEqual("application/json", response.content_type)
|
||||
self.assertTrue(response.json['error_message'])
|
||||
|
||||
|
||||
class TestVersion(base.BaseApiTest):
|
||||
|
||||
@mock.patch('ekko.api.controllers.base.Version.parse_headers')
|
||||
def test_init(self, mock_parse):
|
||||
a = mock.Mock()
|
||||
b = mock.Mock()
|
||||
mock_parse.return_value = (a, b)
|
||||
v = cbase.Version('test', 'foo', 'bar')
|
||||
|
||||
mock_parse.assert_called_with('test', 'foo', 'bar')
|
||||
self.assertEqual(a, v.major)
|
||||
self.assertEqual(b, v.minor)
|
||||
|
||||
@mock.patch('ekko.api.controllers.base.Version.parse_headers')
|
||||
def test_repr(self, mock_parse):
|
||||
mock_parse.return_value = (123, 456)
|
||||
v = cbase.Version('test', mock.ANY, mock.ANY)
|
||||
result = "%s" % v
|
||||
self.assertEqual('123.456', result)
|
||||
|
||||
@mock.patch('ekko.api.controllers.base.Version.parse_headers')
|
||||
def test_repr_with_strings(self, mock_parse):
|
||||
mock_parse.return_value = ('abc', 'def')
|
||||
v = cbase.Version('test', mock.ANY, mock.ANY)
|
||||
result = "%s" % v
|
||||
self.assertEqual('abc.def', result)
|
||||
|
||||
def test_parse_headers_ok(self):
|
||||
version = cbase.Version.parse_headers(
|
||||
{cbase.Version.string: '123.456'}, mock.ANY, mock.ANY)
|
||||
self.assertEqual((123, 456), version)
|
||||
|
||||
def test_parse_headers_latest(self):
|
||||
for s in ['latest', 'LATEST']:
|
||||
version = cbase.Version.parse_headers(
|
||||
{cbase.Version.string: s}, mock.ANY, '1.9')
|
||||
self.assertEqual((1, 9), version)
|
||||
|
||||
def test_parse_headers_bad_length(self):
|
||||
self.assertRaises(
|
||||
exc.HTTPNotAcceptable,
|
||||
cbase.Version.parse_headers,
|
||||
{cbase.Version.string: '1'},
|
||||
mock.ANY,
|
||||
mock.ANY)
|
||||
self.assertRaises(
|
||||
exc.HTTPNotAcceptable,
|
||||
cbase.Version.parse_headers,
|
||||
{cbase.Version.string: '1.2.3'},
|
||||
mock.ANY,
|
||||
mock.ANY)
|
||||
|
||||
def test_parse_no_header(self):
|
||||
# this asserts that the minimum version string of "1.1" is applied
|
||||
version = cbase.Version.parse_headers({}, '1.1', '1.5')
|
||||
self.assertEqual((1, 1), version)
|
||||
|
||||
def test_equals(self):
|
||||
ver_1 = cbase.Version(
|
||||
{cbase.Version.string: '123.456'}, mock.ANY, mock.ANY)
|
||||
ver_2 = cbase.Version(
|
||||
{cbase.Version.string: '123.456'}, mock.ANY, mock.ANY)
|
||||
self.assertTrue(hasattr(ver_1, '__eq__'))
|
||||
self.assertTrue(ver_1 == ver_2)
|
||||
|
||||
def test_greaterthan(self):
|
||||
ver_1 = cbase.Version(
|
||||
{cbase.Version.string: '123.457'}, mock.ANY, mock.ANY)
|
||||
ver_2 = cbase.Version(
|
||||
{cbase.Version.string: '123.456'}, mock.ANY, mock.ANY)
|
||||
self.assertTrue(hasattr(ver_1, '__gt__'))
|
||||
self.assertTrue(ver_1 > ver_2)
|
||||
|
||||
def test_lessthan(self):
|
||||
# __lt__ is created by @functools.total_ordering, make sure it exists
|
||||
# and works
|
||||
ver_1 = cbase.Version(
|
||||
{cbase.Version.string: '123.456'}, mock.ANY, mock.ANY)
|
||||
ver_2 = cbase.Version(
|
||||
{cbase.Version.string: '123.457'}, mock.ANY, mock.ANY)
|
||||
self.assertTrue(hasattr(ver_1, '__lt__'))
|
||||
self.assertTrue(ver_1 < ver_2)
|
@ -1,37 +0,0 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import fixtures
|
||||
from oslo_config import cfg
|
||||
|
||||
from ekko.common import config
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('host', 'ekko.common.service')
|
||||
|
||||
|
||||
class ConfFixture(fixtures.Fixture):
|
||||
"""Fixture to manage global conf settings."""
|
||||
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
|
||||
def setUp(self):
|
||||
super(ConfFixture, self).setUp()
|
||||
|
||||
self.conf.set_default('host', 'fake-mini')
|
||||
# self.conf.set_default('connection', "sqlite://", group='database')
|
||||
# self.conf.set_default('sqlite_synchronous', False, group='database')
|
||||
self.conf.set_default('verbose', True)
|
||||
config.parse_args([], default_config_files=[])
|
||||
self.addCleanup(self.conf.reset)
|
@ -6,10 +6,3 @@ pbr>=1.6 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
oslo.utils>=3.11.0 # Apache-2.0
|
||||
stevedore>=1.10.0 # Apache-2.0
|
||||
oslo.concurrency>=3.8.0 # Apache-2.0
|
||||
oslo.config>=3.10.0 # Apache-2.0
|
||||
oslo.log>=1.14.0 # Apache-2.0
|
||||
oslo.service>=1.10.0 # Apache-2.0
|
||||
pecan>=1.0.0 # BSD
|
||||
WSME>=0.8 # MIT
|
||||
keystonemiddleware!=4.1.0,!=4.5.0,>=4.0.0 # Apache-2.0
|
||||
|
@ -33,8 +33,6 @@ ekko.storage.compression_drivers =
|
||||
zlib = ekko.storage._compression_drivers.zlib_:ZlibCompression
|
||||
ekko.storage.encryption_drivers =
|
||||
noop = ekko.storage._encryption_drivers.noop:NoopEncryption
|
||||
console_scripts =
|
||||
ekko-api = ekko.cmd.api:main
|
||||
|
||||
[files]
|
||||
packages =
|
||||
|
Loading…
x
Reference in New Issue
Block a user