diff --git a/ekko/api/__init__.py b/ekko/api/__init__.py deleted file mode 100644 index c5a60f3..0000000 --- a/ekko/api/__init__.py +++ /dev/null @@ -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) diff --git a/ekko/api/app.py b/ekko/api/app.py deleted file mode 100644 index 9871d87..0000000 --- a/ekko/api/app.py +++ /dev/null @@ -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) diff --git a/ekko/api/config.py b/ekko/api/config.py deleted file mode 100644 index f738d42..0000000 --- a/ekko/api/config.py +++ /dev/null @@ -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, -} diff --git a/ekko/api/controllers/__init__.py b/ekko/api/controllers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ekko/api/controllers/base.py b/ekko/api/controllers/base.py deleted file mode 100644 index ff2832a..0000000 --- a/ekko/api/controllers/base.py +++ /dev/null @@ -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) diff --git a/ekko/api/controllers/root.py b/ekko/api/controllers/root.py deleted file mode 100644 index a3df428..0000000 --- a/ekko/api/controllers/root.py +++ /dev/null @@ -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) diff --git a/ekko/api/controllers/v1/__init__.py b/ekko/api/controllers/v1/__init__.py deleted file mode 100644 index eeeebed..0000000 --- a/ekko/api/controllers/v1/__init__.py +++ /dev/null @@ -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) diff --git a/ekko/api/controllers/v1/versions.py b/ekko/api/controllers/v1/versions.py deleted file mode 100644 index c2b9d84..0000000 --- a/ekko/api/controllers/v1/versions.py +++ /dev/null @@ -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) diff --git a/ekko/api/expose.py b/ekko/api/expose.py deleted file mode 100644 index 9412211..0000000 --- a/ekko/api/expose.py +++ /dev/null @@ -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) diff --git a/ekko/api/middleware/__init__.py b/ekko/api/middleware/__init__.py deleted file mode 100644 index 5266385..0000000 --- a/ekko/api/middleware/__init__.py +++ /dev/null @@ -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) diff --git a/ekko/api/middleware/auth_token.py b/ekko/api/middleware/auth_token.py deleted file mode 100644 index 484bd57..0000000 --- a/ekko/api/middleware/auth_token.py +++ /dev/null @@ -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) diff --git a/ekko/api/middleware/parsable_error.py b/ekko/api/middleware/parsable_error.py deleted file mode 100644 index 40c5d61..0000000 --- a/ekko/api/middleware/parsable_error.py +++ /dev/null @@ -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('' - + '\n'.join(app_iter) - + ''))] - except et.ElementTree.ParseError as err: - LOG.error(_LE('Error parsing HTTP response: %s'), err) - body = ['%s' % state['status_code'] - + ''] - 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 diff --git a/ekko/cmd/__init__.py b/ekko/cmd/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ekko/cmd/api.py b/ekko/cmd/api.py deleted file mode 100644 index bd4a0a5..0000000 --- a/ekko/cmd/api.py +++ /dev/null @@ -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()) diff --git a/ekko/common/__init__.py b/ekko/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ekko/common/config.py b/ekko/common/config.py deleted file mode 100644 index 5a34570..0000000 --- a/ekko/common/config.py +++ /dev/null @@ -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) diff --git a/ekko/common/exception.py b/ekko/common/exception.py deleted file mode 100644 index 064461b..0000000 --- a/ekko/common/exception.py +++ /dev/null @@ -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 diff --git a/ekko/common/service.py b/ekko/common/service.py deleted file mode 100644 index be2b2d9..0000000 --- a/ekko/common/service.py +++ /dev/null @@ -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() diff --git a/ekko/tests/base.py b/ekko/tests/base.py index bc07e11..1c30cdb 100644 --- a/ekko/tests/base.py +++ b/ekko/tests/base.py @@ -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 diff --git a/ekko/tests/unit/__init__.py b/ekko/tests/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ekko/tests/unit/api/__init__.py b/ekko/tests/unit/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ekko/tests/unit/api/base.py b/ekko/tests/unit/api/base.py deleted file mode 100644 index 620a5db..0000000 --- a/ekko/tests/unit/api/base.py +++ /dev/null @@ -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 diff --git a/ekko/tests/unit/api/test_base.py b/ekko/tests/unit/api/test_base.py deleted file mode 100644 index 4de5ebd..0000000 --- a/ekko/tests/unit/api/test_base.py +++ /dev/null @@ -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) diff --git a/ekko/tests/unit/conf_fixture.py b/ekko/tests/unit/conf_fixture.py deleted file mode 100644 index 7770c0b..0000000 --- a/ekko/tests/unit/conf_fixture.py +++ /dev/null @@ -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) diff --git a/requirements.txt b/requirements.txt index d0448f3..13ef382 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/setup.cfg b/setup.cfg index 7fee618..9aaceed 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 = diff --git a/tox.ini b/tox.ini index 304b7a0..771c126 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} - TESTS_DIR=./ekko/tests/unit/ deps = -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}'