From 1737ae877dd2f621bd8ee9968d5fe11ed5cd3daf Mon Sep 17 00:00:00 2001 From: tonytan4ever Date: Mon, 28 Jul 2014 11:55:51 -0400 Subject: [PATCH] Add pecan transport driver and tests --- cdn/manager/base/v1.py | 29 ++++ cdn/manager/default/v1.py | 31 +++++ cdn/openstack/common/context.py | 126 ++++++++++++++++++ cdn/transport/app.py | 37 +++++ cdn/transport/pecan/__init__.py | 22 +++ cdn/transport/pecan/controllers/__init__.py | 26 ++++ cdn/transport/pecan/controllers/base.py | 25 ++++ cdn/transport/pecan/controllers/root.py | 51 +++++++ cdn/transport/pecan/controllers/services.py | 35 +++++ cdn/transport/pecan/controllers/v1.py | 26 ++++ cdn/transport/pecan/driver.py | 74 ++++++++++ cdn/transport/pecan/hooks/__init__.py | 22 +++ cdn/transport/pecan/hooks/context.py | 40 ++++++ tests/functional/transport/pecan/__init__.py | 0 tests/functional/transport/pecan/base.py | 41 ++++++ .../transport/pecan/controllers/__init__.py | 0 .../pecan/controllers/test_services.py | 10 ++ .../controllers/test_services_controller.py | 24 ++++ .../pecan/controllers/test_v1_controller.py | 28 ++++ .../transport/pecan/hooks/__init__.py | 0 .../transport/pecan/hooks/test_context.py | 43 ++++++ tests/unit/transport/pecan/__init__.py | 0 .../unit/transport/pecan/test_pecan_driver.py | 77 +++++++++++ 23 files changed, 767 insertions(+) create mode 100644 cdn/manager/base/v1.py create mode 100644 cdn/manager/default/v1.py create mode 100644 cdn/openstack/common/context.py create mode 100644 cdn/transport/app.py create mode 100644 cdn/transport/pecan/__init__.py create mode 100644 cdn/transport/pecan/controllers/__init__.py create mode 100644 cdn/transport/pecan/controllers/base.py create mode 100644 cdn/transport/pecan/controllers/root.py create mode 100644 cdn/transport/pecan/controllers/services.py create mode 100644 cdn/transport/pecan/controllers/v1.py create mode 100644 cdn/transport/pecan/driver.py create mode 100644 cdn/transport/pecan/hooks/__init__.py create mode 100644 cdn/transport/pecan/hooks/context.py create mode 100644 tests/functional/transport/pecan/__init__.py create mode 100644 tests/functional/transport/pecan/base.py create mode 100644 tests/functional/transport/pecan/controllers/__init__.py create mode 100644 tests/functional/transport/pecan/controllers/test_services.py create mode 100644 tests/functional/transport/pecan/controllers/test_services_controller.py create mode 100644 tests/functional/transport/pecan/controllers/test_v1_controller.py create mode 100644 tests/functional/transport/pecan/hooks/__init__.py create mode 100644 tests/functional/transport/pecan/hooks/test_context.py create mode 100644 tests/unit/transport/pecan/__init__.py create mode 100644 tests/unit/transport/pecan/test_pecan_driver.py diff --git a/cdn/manager/base/v1.py b/cdn/manager/base/v1.py new file mode 100644 index 00000000..410eb490 --- /dev/null +++ b/cdn/manager/base/v1.py @@ -0,0 +1,29 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc +import six + +from cdn.manager.base import controller + + +@six.add_metaclass(abc.ABCMeta) +class V1ControllerBase(controller.ManagerControllerBase): + def __init__(self, manager): + super(V1ControllerBase, self).__init__(manager) + + @abc.abstractmethod + def get(self): + raise NotImplementedError diff --git a/cdn/manager/default/v1.py b/cdn/manager/default/v1.py new file mode 100644 index 00000000..3c259edf --- /dev/null +++ b/cdn/manager/default/v1.py @@ -0,0 +1,31 @@ +from cdn.manager import base + +JSON_HOME = { + "resources": { + "rel/cdn": { + "href-template": "services{?marker,limit}", + "href-vars": { + "marker": "param/marker", + "limit": "param/limit" + }, + "hints": { + "allow": [ + "GET" + ], + "formats": { + "application/json": {} + } + } + } + } + } + + +class DefaultV1Controller(base.V1Controller): + def __init__(self, manager): + super(DefaultV1Controller, self).__init__(manager) + + self.JSON_HOME = JSON_HOME + + def get(self): + return self.JSON_HOME diff --git a/cdn/openstack/common/context.py b/cdn/openstack/common/context.py new file mode 100644 index 00000000..b612db71 --- /dev/null +++ b/cdn/openstack/common/context.py @@ -0,0 +1,126 @@ +# Copyright 2011 OpenStack Foundation. +# 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. + +""" +Simple class that stores security context information in the web request. + +Projects should subclass this class if they wish to enhance the request +context or provide additional information in their specific WSGI pipeline. +""" + +import itertools +import uuid + + +def generate_request_id(): + return b'req-' + str(uuid.uuid4()).encode('ascii') + + +class RequestContext(object): + + """Helper class to represent useful information about a request context. + + Stores information about the security context under which the user + accesses the system, as well as additional request information. + """ + + user_idt_format = '{user} {tenant} {domain} {user_domain} {p_domain}' + + def __init__(self, auth_token=None, user=None, tenant=None, domain=None, + user_domain=None, project_domain=None, is_admin=False, + read_only=False, show_deleted=False, request_id=None, + instance_uuid=None): + self.auth_token = auth_token + self.user = user + self.tenant = tenant + self.domain = domain + self.user_domain = user_domain + self.project_domain = project_domain + self.is_admin = is_admin + self.read_only = read_only + self.show_deleted = show_deleted + self.instance_uuid = instance_uuid + if not request_id: + request_id = generate_request_id() + self.request_id = request_id + + def to_dict(self): + user_idt = ( + self.user_idt_format.format(user=self.user or '-', + tenant=self.tenant or '-', + domain=self.domain or '-', + user_domain=self.user_domain or '-', + p_domain=self.project_domain or '-')) + + return {'user': self.user, + 'tenant': self.tenant, + 'domain': self.domain, + 'user_domain': self.user_domain, + 'project_domain': self.project_domain, + 'is_admin': self.is_admin, + 'read_only': self.read_only, + 'show_deleted': self.show_deleted, + 'auth_token': self.auth_token, + 'request_id': self.request_id, + 'instance_uuid': self.instance_uuid, + 'user_identity': user_idt} + + @classmethod + def from_dict(cls, ctx): + return cls( + auth_token=ctx.get("auth_token"), + user=ctx.get("user"), + tenant=ctx.get("tenant"), + domain=ctx.get("domain"), + user_domain=ctx.get("user_domain"), + project_domain=ctx.get("project_domain"), + is_admin=ctx.get("is_admin", False), + read_only=ctx.get("read_only", False), + show_deleted=ctx.get("show_deleted", False), + request_id=ctx.get("request_id"), + instance_uuid=ctx.get("instance_uuid")) + + +def get_admin_context(show_deleted=False): + context = RequestContext(None, + tenant=None, + is_admin=True, + show_deleted=show_deleted) + return context + + +def get_context_from_function_and_args(function, args, kwargs): + """Find an arg of type RequestContext and return it. + + This is useful in a couple of decorators where we don't + know much about the function we're wrapping. + """ + + for arg in itertools.chain(kwargs.values(), args): + if isinstance(arg, RequestContext): + return arg + + return None + + +def is_user_context(context): + """Indicates if the request context is a normal user.""" + if not context: + return False + if context.is_admin: + return False + if not context.user_id or not context.project_id: + return False + return True diff --git a/cdn/transport/app.py b/cdn/transport/app.py new file mode 100644 index 00000000..86e9c456 --- /dev/null +++ b/cdn/transport/app.py @@ -0,0 +1,37 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""WSGI callable for WSGI containers + +This app should be used by external WSGI +containers. For example: + + $ gunicorn dory.transport.app:app + +NOTE: As for external containers, it is necessary +to put config files in the standard paths. There's +no common way to specify / pass configuration files +to the WSGI app when it is called from other apps. +""" + +from oslo.config import cfg + +from dory import bootstrap + + +conf = cfg.CONF +conf(project='cdn', prog='cdn', args=[]) + +app = bootstrap.Bootstrap(conf).transport.app diff --git a/cdn/transport/pecan/__init__.py b/cdn/transport/pecan/__init__.py new file mode 100644 index 00000000..0dab23d1 --- /dev/null +++ b/cdn/transport/pecan/__init__.py @@ -0,0 +1,22 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pecan Transport Driver""" + +from cdn.transport.pecan import driver + + +# Hoist into package namespace +Driver = driver.PecanTransportDriver diff --git a/cdn/transport/pecan/controllers/__init__.py b/cdn/transport/pecan/controllers/__init__.py new file mode 100644 index 00000000..56a04d00 --- /dev/null +++ b/cdn/transport/pecan/controllers/__init__.py @@ -0,0 +1,26 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pecan Controllers""" + +from cdn.transport.pecan.controllers import root +from cdn.transport.pecan.controllers import services +from cdn.transport.pecan.controllers import v1 + + +# Hoist into package namespace +Root = root.RootController +Services = services.ServicesController +V1 = v1.V1Controller diff --git a/cdn/transport/pecan/controllers/base.py b/cdn/transport/pecan/controllers/base.py new file mode 100644 index 00000000..9b58255e --- /dev/null +++ b/cdn/transport/pecan/controllers/base.py @@ -0,0 +1,25 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pecan import rest + + +class Controller(rest.RestController): + + def __init__(self, driver): + self._driver = driver + + def add_controller(self, path, controller): + setattr(self, path, controller) diff --git a/cdn/transport/pecan/controllers/root.py b/cdn/transport/pecan/controllers/root.py new file mode 100644 index 00000000..6d0a3f08 --- /dev/null +++ b/cdn/transport/pecan/controllers/root.py @@ -0,0 +1,51 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +import pecan + +from cdn.transport.pecan.controllers import base + + +class RootController(base.Controller): + + def __init__(self, driver): + super(RootController, self).__init__(driver) + self.paths = [] + + def add_controller(self, path, controller): + super(RootController, self).add_controller(path, controller) + self.paths.append(path) + + @pecan.expose() + def _route(self, args, request=None): + # Optionally allow OpenStack project ID in the URL + # Remove it from the URL if it's present + # ['v1', 'todos'] or ['v1', '123', 'todos'] + if ( + len(args) >= 2 + and args[0] in self.paths + and re.match('^[0-9]+$', args[1]) + ): + args.pop(1) + + return super(RootController, self)._route(args, request) + + @pecan.expose('json') + def get_all(self): + return { + 'status': 'up', + } diff --git a/cdn/transport/pecan/controllers/services.py b/cdn/transport/pecan/controllers/services.py new file mode 100644 index 00000000..0515a949 --- /dev/null +++ b/cdn/transport/pecan/controllers/services.py @@ -0,0 +1,35 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import uuid + +import pecan + +from cdn.transport.pecan.controllers import base + + +class ServicesController(base.Controller): + + @pecan.expose('json') + def get_all(self): + tenant_id = pecan.request.context.to_dict()['tenant'] + services_controller = self._driver.manager.services_controller + return services_controller.list(tenant_id) + + @pecan.expose('json') + def get_one(self): + tenant_id = pecan.request.context.to_dict()['tenant'] + services_controller = self._driver.manager.services_controller + return services_controller.list(tenant_id) diff --git a/cdn/transport/pecan/controllers/v1.py b/cdn/transport/pecan/controllers/v1.py new file mode 100644 index 00000000..57e0d24d --- /dev/null +++ b/cdn/transport/pecan/controllers/v1.py @@ -0,0 +1,26 @@ + # Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pecan + +from cdn.transport.pecan.controllers import base + + +class V1Controller(base.Controller): + + @pecan.expose('json') + def get(self): + v1_controller = self._driver.manager.v1_controller + return v1_controller.get() diff --git a/cdn/transport/pecan/driver.py b/cdn/transport/pecan/driver.py new file mode 100644 index 00000000..7a7a7ed7 --- /dev/null +++ b/cdn/transport/pecan/driver.py @@ -0,0 +1,74 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from wsgiref import simple_server + +import pecan +from oslo.config import cfg + +from cdn.openstack.common import log +from cdn import transport +from cdn.transport.pecan import controllers +from cdn.transport.pecan import hooks + + +_PECAN_OPTIONS = [ + cfg.StrOpt('bind', default='127.0.0.1', + help='Address on which the self-hosting server will listen'), + cfg.IntOpt('port', default=8888, + help='Port on which the self-hosting server will listen'), +] + +_PECAN_GROUP = 'drivers:transport:pecan' + +LOG = log.getLogger(__name__) + + +class PecanTransportDriver(transport.Driver): + + def __init__(self, conf, manager): + super(PecanTransportDriver, self).__init__(conf, manager) + + self._conf.register_opts(_PECAN_OPTIONS, group=_PECAN_GROUP) + self._pecan_conf = self._conf[_PECAN_GROUP] + + self._setup_app() + + def _setup_app(self): + root_controller = controllers.Root(self) + + pecan_hooks = [hooks.Context()] + + self._app = pecan.make_app(root_controller, hooks=pecan_hooks) + + v1_controller = controllers.V1(self) + root_controller.add_controller('v1.0', v1_controller) + + services_controller = controllers.Services(self) + v1_controller.add_controller('services', services_controller) + + def listen(self): + LOG.info( + 'Serving on host %(bind)s:%(port)s', + { + 'bind': self._pecan_conf.bind, + 'port': self._pecan_conf.port, + }, + ) + + httpd = simple_server.make_server(self._pecan_conf.bind, + self._pecan_conf.port, + self.app) + httpd.serve_forever() \ No newline at end of file diff --git a/cdn/transport/pecan/hooks/__init__.py b/cdn/transport/pecan/hooks/__init__.py new file mode 100644 index 00000000..f1b941ba --- /dev/null +++ b/cdn/transport/pecan/hooks/__init__.py @@ -0,0 +1,22 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pecan Hooks""" + +from cdn.transport.pecan.hooks import context + + +# Hoist into package namespace +Context = context.ContextHook \ No newline at end of file diff --git a/cdn/transport/pecan/hooks/context.py b/cdn/transport/pecan/hooks/context.py new file mode 100644 index 00000000..615da52d --- /dev/null +++ b/cdn/transport/pecan/hooks/context.py @@ -0,0 +1,40 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pecan import hooks + +from cdn.openstack.common import context +from cdn.openstack.common import local + + +class ContextHook(hooks.PecanHook): + + def on_route(self, state): + context_kwargs = {} + + if 'X-Project-ID' in state.request.headers: + context_kwargs['tenant'] = state.request.headers['X-Project-ID'] + + if 'tenant' not in context_kwargs: + # Didn't find the X-Project-Id header, pull from URL instead + # Expects form /v1/{project_id}/path + context_kwargs['tenant'] = state.request.path.split('/')[2] + + if 'X-Auth-Token' in state.request.headers: + context_kwargs['auth_token'] = state.request.headers['X-Auth-Token'] + + request_context = context.RequestContext(**context_kwargs) + state.request.context = request_context + local.store.context = request_context \ No newline at end of file diff --git a/tests/functional/transport/pecan/__init__.py b/tests/functional/transport/pecan/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/functional/transport/pecan/base.py b/tests/functional/transport/pecan/base.py new file mode 100644 index 00000000..f62dc225 --- /dev/null +++ b/tests/functional/transport/pecan/base.py @@ -0,0 +1,41 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from oslo.config import cfg +import testtools +import webtest + +from cdn import bootstrap + + +class BaseFunctionalTest(testtools.TestCase): + + def setUp(self): + super(BaseFunctionalTest, self).setUp() + + tests_path = os.path.abspath(os.path.dirname( + os.path.dirname( + os.path.dirname(os.path.dirname(__file__) + )))) + conf_path = os.path.join(tests_path, 'etc', 'default_functional.conf') + cfg.CONF(args=[], default_config_files=[conf_path]) + cdn_wsgi = bootstrap.Bootstrap(cfg.CONF).transport.app + + self.app = webtest.TestApp(cdn_wsgi) + + +FunctionalTest = BaseFunctionalTest diff --git a/tests/functional/transport/pecan/controllers/__init__.py b/tests/functional/transport/pecan/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/functional/transport/pecan/controllers/test_services.py b/tests/functional/transport/pecan/controllers/test_services.py new file mode 100644 index 00000000..293f2017 --- /dev/null +++ b/tests/functional/transport/pecan/controllers/test_services.py @@ -0,0 +1,10 @@ +from tests.functional.transport.pecan import base + + +class ServiceControllerTest(base.FunctionalTest): + + def test_get_all(self): + pass + #response = self.app.get('/health') + + #self.assertEqual(204, response.status_code) diff --git a/tests/functional/transport/pecan/controllers/test_services_controller.py b/tests/functional/transport/pecan/controllers/test_services_controller.py new file mode 100644 index 00000000..0dab969c --- /dev/null +++ b/tests/functional/transport/pecan/controllers/test_services_controller.py @@ -0,0 +1,24 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from tests.functional.transport.pecan import base + + +class ServicesControllerTest(base.FunctionalTest): + + def test_get_all(self): + response = self.app.get('/v1.0/00001/services') + + self.assertEqual(200, response.status_code) \ No newline at end of file diff --git a/tests/functional/transport/pecan/controllers/test_v1_controller.py b/tests/functional/transport/pecan/controllers/test_v1_controller.py new file mode 100644 index 00000000..b845b6d5 --- /dev/null +++ b/tests/functional/transport/pecan/controllers/test_v1_controller.py @@ -0,0 +1,28 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cdn.manager.default import v1 + +from tests.functional.transport.pecan import base + + +class V1ControllerTest(base.FunctionalTest): + + def test_get_all(self): + response = self.app.get('/v1.0/00001') + + self.assertEqual(200, response.status_code) + # Temporary until actual implementation + self.assertEqual(v1.JSON_HOME, response.json) diff --git a/tests/functional/transport/pecan/hooks/__init__.py b/tests/functional/transport/pecan/hooks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/functional/transport/pecan/hooks/test_context.py b/tests/functional/transport/pecan/hooks/test_context.py new file mode 100644 index 00000000..0b3268dd --- /dev/null +++ b/tests/functional/transport/pecan/hooks/test_context.py @@ -0,0 +1,43 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import uuid + +from cdn.manager.default import v1 +from tests.functional.transport.pecan import base + + +class ContextHookTest(base.FunctionalTest): + + def setUp(self): + super(ContextHookTest, self).setUp() + + self.headers = {'X-Auth-Token': str(uuid.uuid4())} + + def test_project_id_in_header(self): + self.headers['X-Project-Id'] = '000001' + response = self.app.get('/v1.0', headers=self.headers) + + self.assertEqual(200, response.status_code) + + # Temporary until actual implementation + self.assertEqual(v1.JSON_HOME, response.json) + + def test_project_id_in_url(self): + response = self.app.get('/v1.0/000001', headers=self.headers) + + self.assertEqual(200, response.status_code) + # Temporary until actual implementation + self.assertEqual(v1.JSON_HOME, response.json) diff --git a/tests/unit/transport/pecan/__init__.py b/tests/unit/transport/pecan/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/transport/pecan/test_pecan_driver.py b/tests/unit/transport/pecan/test_pecan_driver.py new file mode 100644 index 00000000..bba8ddee --- /dev/null +++ b/tests/unit/transport/pecan/test_pecan_driver.py @@ -0,0 +1,77 @@ +# Copyright (c) 2014 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +import threading +import ctypes + +import requests +from oslo.config import cfg + +from cdn.transport.pecan import driver + + +def terminate_thread(thread): + """Terminates a python thread from another thread. + + :param thread: a threading.Thread instance + """ + if not thread.isAlive(): + return + + exc = ctypes.py_object(SystemExit) + res = ctypes.pythonapi.PyThreadState_SetAsyncExc( + ctypes.c_long(thread.ident), exc) + if res == 0: + raise ValueError("nonexistent thread id") + elif res > 1: + # """if it returns a number greater than one, you're in trouble, + # and you should call it again with exc=NULL to revert the effect""" + ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None) + raise SystemError("PyThreadState_SetAsyncExc failed") + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, **kwargs): + super(StoppableThread, self).__init__(**kwargs) + self._stop = threading.Event() + + def stop(self): + self._stop.set() + + def stopped(self): + return self._stop.isSet() + + +class TestPecanDriver(unittest.TestCase): + def setUp(self): + # Let manager = None for now + self.pecan_driver = driver.PecanTransportDriver(cfg.CONF, None) + + + def test_app_created(self): + self.assertEquals(self.pecan_driver.app is not None, True) + t = StoppableThread(target = self.pecan_driver.listen) + t.start() + #r = requests.get('http://127.0.0.1:8888') + #print r + #assertR + t.stop() + terminate_thread(t) + + \ No newline at end of file