Initial Kingbird framework code base ( part5:test )
Fisrt patch to implement the framework of Kingbird, this is the part5 for api test with readme files added. Replace the Werkzeug wsgi server to oslo.service.wsgi to reduce the requirements Correct some invalid field in common/context.py Blueprint: https://blueprints.launchpad.net/kingbird/+spec/kingbird-framework Change-Id: Iebab2f554ac13ab653bf30adf021193b82a5e84b Signed-off-by: Chaoyi Huang <joehuang@huawei.com>
This commit is contained in:
parent
9ae8bfc2fe
commit
06c20a15fd
|
@ -0,0 +1,17 @@
|
|||
===============================
|
||||
cmd
|
||||
===============================
|
||||
|
||||
Scripts to start the API, JobDaemon and JobWorker service
|
||||
|
||||
api.py:
|
||||
start API service
|
||||
python api.py --config-file=../etc/api.conf
|
||||
|
||||
jobdaemon.py:
|
||||
start JobDaemon service
|
||||
python jobdaemon.py --config-file=../etc/jobdaemon.conf
|
||||
|
||||
jobworker.py:
|
||||
start JobWorker service
|
||||
python jobworker.py --config-file=../etc/jobworker.conf
|
11
cmd/api.py
11
cmd/api.py
|
@ -21,11 +21,10 @@ import sys
|
|||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import wsgi
|
||||
|
||||
import logging as std_logging
|
||||
|
||||
from werkzeug import serving
|
||||
|
||||
from kingbird.api import apicfg
|
||||
from kingbird.api import app
|
||||
|
||||
|
@ -52,13 +51,15 @@ def main():
|
|||
LOG.info(_LI("Server on http://%(host)s:%(port)s with %(workers)s"),
|
||||
{'host': host, 'port': port, 'workers': workers})
|
||||
|
||||
serving.run_simple(host, port,
|
||||
application,
|
||||
processes=workers)
|
||||
service = wsgi.Server(CONF, "Kingbird", application, host, port)
|
||||
|
||||
app.serve(service, CONF, workers)
|
||||
|
||||
LOG.info(_LI("Configuration:"))
|
||||
CONF.log_opt_values(LOG, std_logging.INFO)
|
||||
|
||||
app.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
===============================
|
||||
devstack
|
||||
===============================
|
||||
|
||||
Scripts to integrate the API, JobDaemon and JobWorker service to OpenStack
|
||||
devstack development environment
|
||||
|
||||
local.conf.sample:
|
||||
sample configruation to integrate kingbird into devstack
|
||||
cuntomize the configuration file to tell devstack which OpenStack services
|
||||
will be launched
|
||||
|
||||
plugin.sh: plugin to the devstack
|
||||
devstack will automaticly search the devstack folder, and load, exeucte
|
||||
the plugin.sh in different environment establishment phase
|
||||
|
||||
settins: configuration for kingbird in the devstack
|
||||
devstack will automaticly load the settings to be used in the plugin.sh
|
|
@ -0,0 +1,14 @@
|
|||
===============================
|
||||
etc
|
||||
===============================
|
||||
|
||||
configuration sample for the API, JobDaemon and JobWorker service
|
||||
|
||||
api.conf:
|
||||
configuration sample for API service
|
||||
|
||||
jobdaemon.conf:
|
||||
configuration sample for JobDaemon service
|
||||
|
||||
jobworker.conf:
|
||||
configuration sample for JobWorker service
|
|
@ -0,0 +1,29 @@
|
|||
===============================
|
||||
api
|
||||
===============================
|
||||
|
||||
Kingbird API is Web Server Gateway Interface (WSGI) applications to receive
|
||||
and process API calls, including keystonemiddleware to do the authentication,
|
||||
parameter check and validation, convert API calls to job rpc message, and
|
||||
then send the job to Kingbird Job Daemon through the queue. If the job will
|
||||
be processed by Kingbird Job Daemon in synchronous way, the Kingbird API will
|
||||
wait for the response from the Kingbird Job Daemon. Otherwise, the Kingbird
|
||||
API will send response to the API caller first, and then send the job to
|
||||
Kingbird Job Daemon in asynchronous way. One of the Kingbird Job Daemons
|
||||
will be the owner of the job.
|
||||
|
||||
Multiple Kingbird API could run in parallel, and also can work in multi-worker
|
||||
mode.
|
||||
|
||||
Multiple Kingbird API will be designed and run in stateless mode, persistent
|
||||
data will be accessed (read and write) from the Kingbird Database through the
|
||||
DAL module.
|
||||
|
||||
Setup and encapsulate the API WSGI app
|
||||
|
||||
app.py:
|
||||
Setup and encapsulate the API WSGI app, including integrate the
|
||||
keystonemiddleware app
|
||||
|
||||
apicfg.py:
|
||||
API configuration loading and init
|
|
@ -17,6 +17,7 @@
|
|||
Routines for configuring kingbird, largely copy from Neutron
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
|
@ -40,8 +41,9 @@ common_opts = [
|
|||
help=_("The port to bind to")),
|
||||
cfg.IntOpt('api_workers', default=2,
|
||||
help=_("number of api workers")),
|
||||
cfg.StrOpt('api_paste_config', default="api-paste.ini",
|
||||
help=_("The API paste config file to use")),
|
||||
cfg.StrOpt('state_path',
|
||||
default=os.path.join(os.path.dirname(__file__), '../'),
|
||||
help='Top-level directory for maintaining kingbird state'),
|
||||
cfg.StrOpt('api_extensions_path', default="",
|
||||
help=_("The path for API extensions")),
|
||||
cfg.StrOpt('auth_strategy', default='keystone',
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pecan
|
||||
|
||||
from keystonemiddleware import auth_token
|
||||
from oslo_config import cfg
|
||||
from oslo_middleware import request_id
|
||||
import pecan
|
||||
from oslo_service import service
|
||||
|
||||
from kingbird.common import exceptions as k_exc
|
||||
from kingbird.common.i18n import _
|
||||
|
||||
|
||||
def setup_app(*args, **kwargs):
|
||||
|
@ -64,3 +67,18 @@ def _wrap_app(app):
|
|||
opt_name='auth_strategy', opt_value=cfg.CONF.auth_strategy)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
_launcher = None
|
||||
|
||||
|
||||
def serve(api_service, conf, workers=1):
|
||||
global _launcher
|
||||
if _launcher:
|
||||
raise RuntimeError(_('serve() can only be called once'))
|
||||
|
||||
_launcher = service.launch(conf, api_service, workers=workers)
|
||||
|
||||
|
||||
def wait():
|
||||
_launcher.wait()
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
===============================
|
||||
controllers
|
||||
===============================
|
||||
|
||||
API request processing
|
||||
|
||||
root.py:
|
||||
API root request
|
||||
|
||||
helloworld.py:
|
||||
sample for adding a new resource/controller for http request processing
|
||||
|
||||
restcomm.py:
|
||||
common functionality used in API
|
|
@ -13,14 +13,15 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from kingbird.jobdaemon import jdrpcapi
|
||||
|
||||
import pecan
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
from pecan import rest
|
||||
|
||||
import restcomm
|
||||
|
||||
from kingbird.jobdaemon import jdrpcapi
|
||||
|
||||
|
||||
class HelloWorldController(rest.RestController):
|
||||
|
||||
|
@ -62,9 +63,15 @@ class HelloWorldController(rest.RestController):
|
|||
# jdmanager, jwmanager instead
|
||||
context = restcomm.extract_context_from_environ()
|
||||
|
||||
payload = '## delete call ##, request.body is null'
|
||||
payload = '## delete cast ##, request.body is null'
|
||||
payload = payload + request.body
|
||||
self.jd_api.say_hello_world_cast(context, payload)
|
||||
|
||||
return self._delete_response(context)
|
||||
|
||||
def _delete_response(self, context):
|
||||
|
||||
context = context
|
||||
|
||||
return {'cast example': 'check the log produced by jobdaemon '
|
||||
+ 'and jobworker, no value returned here'}
|
||||
|
|
|
@ -13,9 +13,11 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import helloworld
|
||||
|
||||
import pecan
|
||||
|
||||
from kingbird.api.controllers import helloworld
|
||||
|
||||
|
||||
class RootController(object):
|
||||
|
||||
|
@ -24,7 +26,7 @@ class RootController(object):
|
|||
if version == 'v1.0':
|
||||
return V1Controller(), remainder
|
||||
|
||||
@pecan.expose('json')
|
||||
@pecan.expose(generic=True, template='json')
|
||||
def index(self):
|
||||
return {
|
||||
"versions": [
|
||||
|
@ -42,6 +44,14 @@ class RootController(object):
|
|||
]
|
||||
}
|
||||
|
||||
@index.when(method='POST')
|
||||
@index.when(method='PUT')
|
||||
@index.when(method='DELETE')
|
||||
@index.when(method='HEAD')
|
||||
@index.when(method='PATCH')
|
||||
def not_supported(self):
|
||||
pecan.abort(405)
|
||||
|
||||
|
||||
class V1Controller(object):
|
||||
|
||||
|
@ -54,7 +64,7 @@ class V1Controller(object):
|
|||
for name, ctrl in self.sub_controllers.items():
|
||||
setattr(self, name, ctrl)
|
||||
|
||||
@pecan.expose('json')
|
||||
@pecan.expose(generic=True, template='json')
|
||||
def index(self):
|
||||
return {
|
||||
"version": "1.0",
|
||||
|
@ -67,3 +77,11 @@ class V1Controller(object):
|
|||
for name in sorted(self.sub_controllers)
|
||||
]
|
||||
}
|
||||
|
||||
@index.when(method='POST')
|
||||
@index.when(method='PUT')
|
||||
@index.when(method='DELETE')
|
||||
@index.when(method='HEAD')
|
||||
@index.when(method='PATCH')
|
||||
def not_supported(self):
|
||||
pecan.abort(405)
|
||||
|
|
|
@ -47,24 +47,9 @@ class ContextBase(oslo_ctx.RequestContext):
|
|||
'user_name': self.user_name,
|
||||
'tenant_name': self.tenant_name,
|
||||
'auth_url': self.auth_url,
|
||||
'auth_token': self.auth_token,
|
||||
'auth_token_info': self.auth_token_info,
|
||||
'user': self.user,
|
||||
'user_domain': self.user_domain,
|
||||
'user_domain_name': self.user_domain_name,
|
||||
'project': self.project,
|
||||
'project_name': self.project_name,
|
||||
'project_domain': self.project_domain,
|
||||
'project_domain_name': self.project_domain_name,
|
||||
'domain': self.domain,
|
||||
'domain_name': self.domain_name,
|
||||
'trusts': self.trusts,
|
||||
'region_name': self.region_name,
|
||||
'roles': self.roles,
|
||||
'show_deleted': self.show_deleted,
|
||||
'is_admin': self.is_admin,
|
||||
'request_id': self.request_id,
|
||||
'password': self.password,
|
||||
'default_name': self.default_name,
|
||||
'region_name': self.region_name,
|
||||
})
|
||||
return ctx_dict
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
===============================
|
||||
jobdaemon
|
||||
===============================
|
||||
|
||||
Kingbird Job Daemon has responsibility for:
|
||||
Divid job from Kingbird API to smaller jobs, each smaller job will only
|
||||
be involved with one specific region, and one smaller job will be
|
||||
dispatched to one Kingbird Job Worker. Multiple smaller jobs may be
|
||||
dispatched to the same Kingbird Job Worker, it’s up to the load balancing
|
||||
policy and how many Kingbird Job Workers are running.
|
||||
|
||||
Some job from Kingbird API could not be divided, schedule and re-schedule
|
||||
such kind of (periodically running, like quota enforcement, regular
|
||||
event statistic collection task) job to a specific Kingbird Job Worker.
|
||||
If some Kingbird Job Worker failed, re-balance the job to other Kingbird
|
||||
Job Workers.
|
||||
|
||||
Monitoring the job/smaller jobs status, and return the result to Kingbird
|
||||
API if needed.
|
||||
|
||||
Generate task to purge time-out jobs from Kingbird Database
|
||||
|
||||
Multiple Job Daemon could run in parallel, and also can work in
|
||||
multi-worker mode. But for one job from Kingbird API, only one Kingbird
|
||||
Job Daemon will be the owner. One Kingbird Job Daemon could be the owner
|
||||
of multiple jobs from multiple Kingbird APIs
|
||||
|
||||
Multiple Kingbird Daemon will be designed and run in stateless mode,
|
||||
persistent data will be accessed (read and write) from the Kingbird
|
||||
Database through the DAL module.
|
||||
|
||||
jdrpcapi.py:
|
||||
the client side RPC api for JobDaemon. Often the API service will
|
||||
call the api provided in this file, and the RPC client will send the
|
||||
request to message-bus, and then the JobDaemon can pickup the RPC message
|
||||
from the message bus
|
||||
|
||||
jdservice.py:
|
||||
run JobDaemon in multi-worker mode, and establish RPC server
|
||||
|
||||
jdmanager.py:
|
||||
all rpc messages received by the jdservice RPC server will be processed
|
||||
in the jdmanager's regarding function.
|
||||
|
||||
jdcfg.py:
|
||||
configuration and initialization for JobDaemon
|
|
@ -0,0 +1,38 @@
|
|||
===============================
|
||||
jobworker
|
||||
===============================
|
||||
|
||||
Kingbird Job Worker has responsibility for:
|
||||
Concurrently process the divided smaller jobs from Kingbird Job Daemon.
|
||||
Each smaller job will be a job to a specific OpenStack instance, i.e.,
|
||||
one OpenStack region.
|
||||
|
||||
Periodically running background job which was assigned by the Kingbird
|
||||
Job Daemon, Kingbird Job Worker will generate a new one-time job (for
|
||||
example, for quota enforcement, generate a collecting resource usage job),
|
||||
and send it to the Kingbird Job Daemon for further processing in each
|
||||
cycle. Multiple Job Worker could run in parallel, and also can work in
|
||||
multi-worker mode. But for one smaller job from Kingbird Job Daemon,
|
||||
only one Kingbird Job Worker will be the owner. One Kingbird Job Worker
|
||||
could be the owner of multiple smaller jobs from multiple Kingbird
|
||||
JobDaemons.
|
||||
|
||||
Multiple Kingbird Job Workers will be designed and run in stateless mode,
|
||||
persistent data will be accessed (read and write) from the Kingbird
|
||||
Database through the DAL module.
|
||||
|
||||
jwrpcapi.py:
|
||||
the client side RPC api for JobWoker. Often the JobDaemon service will
|
||||
call the api provided in this file, and the RPC client will send the
|
||||
request to message-bus, and then the JobWorker can pickup the RPC message
|
||||
from the message bus
|
||||
|
||||
jwservice.py:
|
||||
run JobWorker in multi-worker mode, and establish RPC server
|
||||
|
||||
jwmanager.py:
|
||||
all rpc messages received by the jwservice RPC server will be processed
|
||||
in the jwmanager's regarding function.
|
||||
|
||||
jwcfg.py:
|
||||
configuration and initialization for JobWorker
|
|
@ -0,0 +1,312 @@
|
|||
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
|
||||
# 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 mock
|
||||
from mock import patch
|
||||
|
||||
import pecan
|
||||
from pecan.configuration import set_config
|
||||
from pecan.testing import load_test_app
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as fixture_config
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from kingbird.api import apicfg
|
||||
from kingbird.api.controllers import helloworld
|
||||
from kingbird.common import rpc
|
||||
from kingbird.jobdaemon import jdrpcapi
|
||||
from kingbird.tests import base
|
||||
|
||||
|
||||
OPT_GROUP_NAME = 'keystone_authtoken'
|
||||
cfg.CONF.import_group(OPT_GROUP_NAME, "keystonemiddleware.auth_token")
|
||||
|
||||
|
||||
def fake_say_hello_world_call(self, ctxt, payload):
|
||||
info_text = "say_hello_world_call, payload: %s" % payload
|
||||
return {'jobdaemon': info_text}
|
||||
|
||||
|
||||
def fake_say_hello_world_cast(self, ctxt, payload):
|
||||
info_text = "say_hello_world_cast, payload: %s" % payload
|
||||
return {'jobdaemon': info_text}
|
||||
|
||||
|
||||
def fake_delete_response(self, context):
|
||||
resp = jsonutils.dumps(context.to_dict())
|
||||
return resp
|
||||
|
||||
|
||||
class KBFunctionalTest(base.KingbirdTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(KBFunctionalTest, self).setUp()
|
||||
|
||||
self.addCleanup(set_config, {}, overwrite=True)
|
||||
|
||||
apicfg.test_init()
|
||||
|
||||
self.CONF = self.useFixture(fixture_config.Config()).conf
|
||||
|
||||
# self.setup_messaging(self.CONF)
|
||||
self.CONF.set_override('auth_strategy', 'noauth')
|
||||
|
||||
self.app = self._make_app()
|
||||
|
||||
def _make_app(self, enable_acl=False):
|
||||
self.config = {
|
||||
'app': {
|
||||
'root': 'kingbird.api.controllers.root.RootController',
|
||||
'modules': ['kingbird.api'],
|
||||
'enable_acl': enable_acl,
|
||||
'errors': {
|
||||
400: '/error',
|
||||
'__force_dict__': True
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return load_test_app(self.config)
|
||||
|
||||
def tearDown(self):
|
||||
super(KBFunctionalTest, self).tearDown()
|
||||
pecan.set_config({}, overwrite=True)
|
||||
|
||||
|
||||
class TestRootController(KBFunctionalTest):
|
||||
"""Test version listing on root URI."""
|
||||
|
||||
def test_get(self):
|
||||
response = self.app.get('/')
|
||||
self.assertEqual(response.status_int, 200)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
versions = json_body.get('versions')
|
||||
self.assertEqual(1, len(versions))
|
||||
|
||||
def _test_method_returns_405(self, method):
|
||||
api_method = getattr(self.app, method)
|
||||
response = api_method('/', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 405)
|
||||
|
||||
def test_post(self):
|
||||
self._test_method_returns_405('post')
|
||||
|
||||
def test_put(self):
|
||||
self._test_method_returns_405('put')
|
||||
|
||||
def test_patch(self):
|
||||
self._test_method_returns_405('patch')
|
||||
|
||||
def test_delete(self):
|
||||
self._test_method_returns_405('delete')
|
||||
|
||||
def test_head(self):
|
||||
self._test_method_returns_405('head')
|
||||
|
||||
|
||||
class TestV1Controller(KBFunctionalTest):
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_get(self):
|
||||
response = self.app.get('/v1.0')
|
||||
self.assertEqual(response.status_int, 200)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
version = json_body.get('version')
|
||||
self.assertEqual('1.0', version)
|
||||
|
||||
links = json_body.get('links')
|
||||
v1_link = links[0]
|
||||
helloworld_link = links[1]
|
||||
self.assertEqual('self', v1_link['rel'])
|
||||
self.assertEqual('helloworld', helloworld_link['rel'])
|
||||
|
||||
def _test_method_returns_405(self, method):
|
||||
api_method = getattr(self.app, method)
|
||||
response = api_method('/v1.0', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 405)
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_post(self):
|
||||
self._test_method_returns_405('post')
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_put(self):
|
||||
self._test_method_returns_405('put')
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_patch(self):
|
||||
self._test_method_returns_405('patch')
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_delete(self):
|
||||
self._test_method_returns_405('delete')
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_head(self):
|
||||
self._test_method_returns_405('head')
|
||||
|
||||
|
||||
class TestHelloworld(KBFunctionalTest):
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_get(self):
|
||||
response = self.app.get('/v1.0/helloworld')
|
||||
self.assertEqual(response.status_int, 200)
|
||||
self.assertIn('hello world message for', response)
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_post(self):
|
||||
response = self.app.post_json('/v1.0/helloworld',
|
||||
headers={'X-Tenant-Id': 'tenid'})
|
||||
self.assertEqual(response.status_int, 200)
|
||||
self.assertIn('## post call ##', response)
|
||||
self.assertIn('jobdaemon', response)
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_put(self):
|
||||
response = self.app.put_json('/v1.0/helloworld',
|
||||
headers={'X-Tenant-Id': 'tenid'})
|
||||
self.assertEqual(response.status_int, 200)
|
||||
self.assertIn('## put call ##', response)
|
||||
self.assertIn('jobdaemon', response)
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_delete(self):
|
||||
response = self.app.delete('/v1.0/helloworld',
|
||||
headers={'X-Tenant-Id': 'tenid'})
|
||||
self.assertEqual(response.status_int, 200)
|
||||
self.assertIn('cast example', response)
|
||||
self.assertIn('check the log produced by jobdaemon', response)
|
||||
|
||||
|
||||
class TestHelloworldContext(KBFunctionalTest):
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
@patch.object(helloworld.HelloWorldController, '_delete_response',
|
||||
new=fake_delete_response)
|
||||
def test_context_set_in_request(self):
|
||||
response = self.app.delete('/v1.0/helloworld',
|
||||
headers={'X_Auth_Token': 'a-token',
|
||||
'X_TENANT_ID': 't-id',
|
||||
'X_USER_ID': 'u-id',
|
||||
'X_USER_NAME': 'u-name',
|
||||
'X_PROJECT_NAME': 't-name',
|
||||
'X_DOMAIN_ID': 'domainx',
|
||||
'X_USER_DOMAIN_ID': 'd-u',
|
||||
'X_PROJECT_DOMAIN_ID': 'p_d',
|
||||
})
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self.assertIn('a-token', json_body)
|
||||
self.assertIn('t-id', json_body)
|
||||
self.assertIn('u-id', json_body)
|
||||
self.assertIn('u-name', json_body)
|
||||
self.assertIn('t-name', json_body)
|
||||
self.assertIn('domainx', json_body)
|
||||
self.assertIn('d-u', json_body)
|
||||
self.assertIn('p_d', json_body)
|
||||
|
||||
|
||||
class TestErrors(KBFunctionalTest):
|
||||
|
||||
def test_404(self):
|
||||
response = self.app.get('/assert_called_once', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
@patch.object(rpc, 'get_client', new=mock.Mock())
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_call',
|
||||
new=fake_say_hello_world_call)
|
||||
@patch.object(jdrpcapi.JobDaemonAPI, 'say_hello_world_cast',
|
||||
new=fake_say_hello_world_cast)
|
||||
def test_bad_method(self):
|
||||
response = self.app.patch('/v1.0/helloworld/123.json',
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 405)
|
||||
|
||||
|
||||
class TestRequestID(KBFunctionalTest):
|
||||
|
||||
def test_request_id(self):
|
||||
response = self.app.get('/')
|
||||
self.assertIn('x-openstack-request-id', response.headers)
|
||||
self.assertTrue(
|
||||
response.headers['x-openstack-request-id'].startswith('req-'))
|
||||
id_part = response.headers['x-openstack-request-id'].split('req-')[1]
|
||||
self.assertTrue(uuidutils.is_uuid_like(id_part))
|
||||
|
||||
|
||||
class TestKeystoneAuth(KBFunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(KBFunctionalTest, self).setUp()
|
||||
|
||||
self.addCleanup(set_config, {}, overwrite=True)
|
||||
|
||||
apicfg.test_init()
|
||||
|
||||
self.CONF = self.useFixture(fixture_config.Config()).conf
|
||||
|
||||
cfg.CONF.set_override('auth_strategy', 'keystone')
|
||||
|
||||
self.app = self._make_app()
|
||||
|
||||
def test_auth_enforced(self):
|
||||
response = self.app.get('/', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 401)
|
|
@ -14,7 +14,6 @@ pecan>=1.0.0
|
|||
greenlet>=0.3.2
|
||||
httplib2>=0.7.5
|
||||
requests!=2.8.0,>=2.5.2
|
||||
Werkzeug>=0.7 # BSD License
|
||||
Jinja2>=2.8 # BSD License (3 clause)
|
||||
keystonemiddleware!=2.4.0,>=2.0.0
|
||||
netaddr!=0.7.16,>=0.7.12
|
||||
|
|
Loading…
Reference in New Issue