Support kubernetes python client 4.0.0

From 4.0.0, kubernetes-incubator/client-python uses multiprocessing
libaray to send request to k8s cluster, which is not supported by
eventlet. This patch introduced the following changes to fix the issue:

- Use cotyledon for engine service rather than oslo.service
- Update global requirments
- Provide separate scripts for api and engine service

References:
[1] https://github.com/eventlet/eventlet/issues/147
[2] https://bugs.launchpad.net/taskflow/+bug/1225275

Change-Id: Ib99565e00eedc72c388e8ebec6b7f1453f77f30f
This commit is contained in:
Lingxian Kong 2017-12-29 14:41:52 +13:00
parent 76f87d59ec
commit aa765e2ae9
21 changed files with 306 additions and 251 deletions

View File

@ -80,7 +80,7 @@ function configure_qinling {
# Setup keystone_authtoken section # Setup keystone_authtoken section
configure_auth_token_middleware $QINLING_CONF_FILE qinling $QINLING_AUTH_CACHE_DIR configure_auth_token_middleware $QINLING_CONF_FILE qinling $QINLING_AUTH_CACHE_DIR
iniset $QINLING_CONF_FILE keystone_authtoken auth_uri $KEYSTONE_AUTH_URI_V3 iniset $QINLING_CONF_FILE keystone_authtoken www_authenticate_uri $KEYSTONE_AUTH_URI_V3
# Setup RabbitMQ credentials # Setup RabbitMQ credentials
iniset_rpc_backend qinling $QINLING_CONF_FILE iniset_rpc_backend qinling $QINLING_CONF_FILE
@ -99,8 +99,8 @@ function init_qinling {
function start_qinling { function start_qinling {
run_process qinling-engine "$QINLING_BIN_DIR/qinling-server --server engine --config-file $QINLING_CONF_FILE" run_process qinling-engine "$QINLING_BIN_DIR/qinling-engine --config-file $QINLING_CONF_FILE"
run_process qinling-api "$QINLING_BIN_DIR/qinling-server --server api --config-file $QINLING_CONF_FILE" run_process qinling-api "$QINLING_BIN_DIR/qinling-api --config-file $QINLING_CONF_FILE"
} }

View File

@ -76,7 +76,6 @@ class FunctionsController(rest.RestController):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.storage_provider = storage_base.load_storage_provider(CONF) self.storage_provider = storage_base.load_storage_provider(CONF)
self.engine_client = rpc.get_engine_client() self.engine_client = rpc.get_engine_client()
self.type = 'function'
super(FunctionsController, self).__init__(*args, **kwargs) super(FunctionsController, self).__init__(*args, **kwargs)
@ -92,7 +91,7 @@ class FunctionsController(rest.RestController):
@rest_utils.wrap_pecan_controller_exception @rest_utils.wrap_pecan_controller_exception
@pecan.expose() @pecan.expose()
def get(self, id): def get(self, id):
LOG.info("Get resource.", resource={'type': self.type, 'id': id}) LOG.info("Get function %s.", id)
download = strutils.bool_from_string( download = strutils.bool_from_string(
pecan.request.GET.get('download', False) pecan.request.GET.get('download', False)
@ -104,6 +103,7 @@ class FunctionsController(rest.RestController):
pecan.override_template('json') pecan.override_template('json')
return resources.Function.from_dict(func_db.to_dict()).to_dict() return resources.Function.from_dict(func_db.to_dict()).to_dict()
else: else:
LOG.info("Downloading function %s", id)
source = func_db.code['source'] source = func_db.code['source']
if source == 'package': if source == 'package':
@ -126,11 +126,12 @@ class FunctionsController(rest.RestController):
pecan.response.headers['Content-Disposition'] = ( pecan.response.headers['Content-Disposition'] = (
'attachment; filename="%s"' % os.path.basename(func_db.name) 'attachment; filename="%s"' % os.path.basename(func_db.name)
) )
LOG.info("Downloaded function %s", id)
@rest_utils.wrap_pecan_controller_exception @rest_utils.wrap_pecan_controller_exception
@pecan.expose('json') @pecan.expose('json')
def post(self, **kwargs): def post(self, **kwargs):
LOG.info("Creating %s, params: %s", self.type, kwargs) LOG.info("Creating function, params: %s", kwargs)
# When using image to create function, runtime_id is not a required # When using image to create function, runtime_id is not a required
# param. # param.
@ -221,7 +222,7 @@ class FunctionsController(rest.RestController):
filters = rest_utils.get_filters( filters = rest_utils.get_filters(
project_id=project_id, project_id=project_id,
) )
LOG.info("Get all %ss. filters=%s", self.type, filters) LOG.info("Get all functions. filters=%s", filters)
db_functions = db_api.get_functions(insecure=all_projects, **filters) db_functions = db_api.get_functions(insecure=all_projects, **filters)
functions = [resources.Function.from_dict(db_model.to_dict()) functions = [resources.Function.from_dict(db_model.to_dict())
for db_model in db_functions] for db_model in db_functions]
@ -232,7 +233,7 @@ class FunctionsController(rest.RestController):
@wsme_pecan.wsexpose(None, types.uuid, status_code=204) @wsme_pecan.wsexpose(None, types.uuid, status_code=204)
def delete(self, id): def delete(self, id):
"""Delete the specified function.""" """Delete the specified function."""
LOG.info("Delete resource.", resource={'type': self.type, 'id': id}) LOG.info("Delete function %s.", id)
with db_api.transaction(): with db_api.transaction():
func_db = db_api.get_function(id) func_db = db_api.get_function(id)
@ -280,9 +281,7 @@ class FunctionsController(rest.RestController):
if kwargs.get(key) is not None: if kwargs.get(key) is not None:
values.update({key: kwargs[key]}) values.update({key: kwargs[key]})
LOG.info('Update resource, params: %s', values, LOG.info('Update function %s, params: %s', id, values)
resource={'type': self.type, 'id': id})
ctx = context.get_ctx() ctx = context.get_ctx()
if set(values.keys()).issubset(set(['name', 'description'])): if set(values.keys()).issubset(set(['name', 'description'])):

View File

@ -14,25 +14,34 @@
from oslo_concurrency import processutils from oslo_concurrency import processutils
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import service from oslo_service import service
from oslo_service import wsgi from oslo_service import wsgi
from qinling.api import app from qinling.api import app
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class WSGIService(service.ServiceBase): class WSGIService(service.ServiceBase):
"""Provides ability to launch Mistral API from wsgi app.""" """Provides ability to launch Mistral API from wsgi app."""
def __init__(self, name): def __init__(self):
self.name = name
self.app = app.setup_app() self.app = app.setup_app()
self.workers = (
cfg.CONF.api.api_workers or processutils.get_worker_count() self.workers = CONF.api.api_workers
) if self.workers is not None and self.workers < 1:
LOG.warning(
"Value of config option api_workers must be integer "
"greater than 1. Input value ignored."
)
self.workers = None
self.workers = self.workers or processutils.get_worker_count()
self.server = wsgi.Server( self.server = wsgi.Server(
cfg.CONF, cfg.CONF,
name, "qinling_api",
self.app, self.app,
host=cfg.CONF.api.host, host=cfg.CONF.api.host,
port=cfg.CONF.api.port, port=cfg.CONF.api.port,

49
qinling/cmd/api.py Normal file
View File

@ -0,0 +1,49 @@
# Copyright 2017 - Catalyst IT Limited
#
# 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 eventlet
eventlet.monkey_patch()
import sys
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import service
from qinling.api import service as api_service
from qinling import config
from qinling import rpc
from qinling.utils import common
CONF = cfg.CONF
def main():
try:
config.parse_args(args=common.get_properly_ordered_parameters())
common.print_server_info("api")
logging.setup(CONF, 'qinling')
# Initialize RPC configuration.
rpc.get_transport()
api_server = api_service.WSGIService()
launcher = service.launch(CONF, api_server, workers=api_server.workers)
launcher.wait()
except RuntimeError as excp:
sys.stderr.write("ERROR: %s\n" % excp)
sys.exit(1)
if __name__ == '__main__':
main()

49
qinling/cmd/engine.py Normal file
View File

@ -0,0 +1,49 @@
# Copyright 2017 - Catalyst IT Limited
#
# 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
import cotyledon
from oslo_config import cfg
from oslo_log import log as logging
from qinling import config
from qinling.engine import service as eng_service
from qinling import rpc
from qinling.utils import common
CONF = cfg.CONF
def main():
try:
config.parse_args(args=common.get_properly_ordered_parameters())
common.print_server_info("engine")
logging.setup(CONF, 'qinling')
# Initialize RPC configuration.
rpc.get_transport()
sm = cotyledon.ServiceManager()
sm.add(
eng_service.EngineService,
workers=1,
)
sm.run()
except RuntimeError as excp:
sys.stderr.write("ERROR: %s\n" % excp)
sys.exit(1)
if __name__ == '__main__':
main()

View File

@ -1,161 +0,0 @@
# Copyright 2017 - Catalyst IT Limited
#
# 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
import eventlet
eventlet.monkey_patch(
os=True,
select=True,
socket=True,
thread=False if '--use-debugger' in sys.argv else True,
time=True)
import os
# If ../qinling/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'qinling', '__init__.py')):
sys.path.insert(0, POSSIBLE_TOPDIR)
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import service
from qinling.api import service as api_service
from qinling import config
from qinling.engine import service as eng_service
from qinling import rpc
from qinling import version
CONF = cfg.CONF
def launch_api():
try:
server = api_service.WSGIService('qinling_api')
launcher = service.launch(CONF, server, workers=server.workers)
return launcher
except Exception as e:
sys.stderr.write("ERROR: %s\n" % e)
sys.exit(1)
def launch_engine():
try:
server = eng_service.EngineService()
launcher = service.launch(CONF, server)
return launcher
except Exception as e:
sys.stderr.write("ERROR: %s\n" % e)
sys.exit(1)
def launch_any(options):
launchers = [LAUNCH_OPTIONS[option]() for option in options]
for l in launchers:
l.wait()
LAUNCH_OPTIONS = {
'api': launch_api,
'engine': launch_engine
}
QINLING_TITLE = r"""
/^L_ ,."\
/~\ __ /~ \ ./ \
/ _\ _/ \ /T~\|~\_\ / \_ /~| _^
/ \ /W \ / V^\/X /~ T . \/ \ ,v-./
,'`-. /~ ^ H , . \/ ; . \ `. \-' /
M ~ | . ; / , _ : . ~\_,-'
/ ~ . \ / : ' \ ,/`
I o. ^ oP '98b - _ 9.` `\9b.
8oO888. oO888P d888b9bo. .8o 888o. 8bo. o 988o.
88888888888888888888888888bo.98888888bo. 98888bo. .d888P
88888888888888888888888888888888888888888888888888888888888
_ __ _
___ _ (_) ___ / / (_) ___ ___ _
/ _ `/ / / / _ \ / / / / / _ \ / _ `/
\_, / /_/ /_//_//_/ /_/ /_//_/ \_, /
/_/ /___/
Function as a Service in OpenStack, version: %s
""" % version.version_string()
def print_server_info():
print(QINLING_TITLE)
comp_str = ("[%s]" % ','.join(LAUNCH_OPTIONS)
if cfg.CONF.server == ['all'] else cfg.CONF.server)
print('Launching server components %s...' % comp_str)
def get_properly_ordered_parameters():
"""Orders launch parameters in the right order.
In oslo it's important the order of the launch parameters.
if --config-file came after the command line parameters the command
line parameters are ignored.
So to make user command line parameters are never ignored this method
moves --config-file to be always first.
"""
args = sys.argv[1:]
for arg in sys.argv[1:]:
if arg == '--config-file' or arg.startswith('--config-file='):
if "=" in arg:
conf_file_value = arg.split("=", 1)[1]
else:
conf_file_value = args[args.index(arg) + 1]
args.remove(conf_file_value)
args.remove(arg)
args.insert(0, "--config-file")
args.insert(1, conf_file_value)
return args
def main():
try:
config.parse_args(get_properly_ordered_parameters())
print_server_info()
logging.setup(CONF, 'Qinling')
# Initialize RPC configuration.
rpc.get_transport()
if cfg.CONF.server == ['all']:
launch_any(LAUNCH_OPTIONS.keys())
else:
if set(cfg.CONF.server) - set(LAUNCH_OPTIONS.keys()):
raise Exception('Valid options are all or any combination of '
', '.join(LAUNCH_OPTIONS.keys()))
launch_any(set(cfg.CONF.server))
except RuntimeError as excp:
sys.stderr.write("ERROR: %s\n" % excp)
sys.exit(1)
if __name__ == '__main__':
sys.exit(main())

View File

@ -38,6 +38,7 @@ api_opts = [
), ),
cfg.IntOpt( cfg.IntOpt(
'api_workers', 'api_workers',
default=1,
help='Number of workers for Qinling API service ' help='Number of workers for Qinling API service '
'default is equal to the number of CPUs available if that can ' 'default is equal to the number of CPUs available if that can '
'be determined, else a default worker count of 1 is returned.' 'be determined, else a default worker count of 1 is returned.'
@ -134,7 +135,7 @@ kubernetes_opts = [
), ),
cfg.StrOpt( cfg.StrOpt(
'kube_host', 'kube_host',
default='127.0.0.1:8001', default='http://127.0.0.1:8001',
help='Kubernetes server address, e.g. you can start a proxy to the ' help='Kubernetes server address, e.g. you can start a proxy to the '
'Kubernetes API server by using "kubectl proxy" command.' 'Kubernetes API server by using "kubectl proxy" command.'
), ),
@ -194,6 +195,7 @@ def parse_args(args=None, usage=None, default_config_files=None):
'keystoneclient=INFO', 'keystoneclient=INFO',
'requests.packages.urllib3.connectionpool=CRITICAL', 'requests.packages.urllib3.connectionpool=CRITICAL',
'urllib3.connectionpool=CRITICAL', 'urllib3.connectionpool=CRITICAL',
'cotyledon=INFO'
] ]
default_log_levels = log.get_default_log_levels() default_log_levels = log.get_default_log_levels()
default_log_levels.extend(_DEFAULT_LOG_LEVELS) default_log_levels.extend(_DEFAULT_LOG_LEVELS)

View File

@ -12,11 +12,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import cotyledon
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
import oslo_messaging as messaging import oslo_messaging as messaging
from oslo_messaging.rpc import dispatcher from oslo_messaging.rpc import dispatcher
from oslo_service import service
from qinling.db import api as db_api from qinling.db import api as db_api
from qinling.engine import default_engine as engine from qinling.engine import default_engine as engine
@ -28,12 +28,12 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
class EngineService(service.Service): class EngineService(cotyledon.Service):
def __init__(self): def __init__(self, worker_id):
super(EngineService, self).__init__() super(EngineService, self).__init__(worker_id)
self.server = None self.server = None
def start(self): def run(self):
orchestrator = orchestra_base.load_orchestrator(CONF) orchestrator = orchestra_base.load_orchestrator(CONF)
db_api.setup_db() db_api.setup_db()
@ -47,11 +47,10 @@ class EngineService(service.Service):
transport, transport,
target, target,
[endpoint], [endpoint],
executor='eventlet', executor='threading',
access_policy=access_policy, access_policy=access_policy,
serializer=rpc.ContextSerializer( serializer=rpc.ContextSerializer(
messaging.serializer.JsonPayloadSerializer() messaging.serializer.JsonPayloadSerializer())
)
) )
LOG.info('Starting function mapping periodic task...') LOG.info('Starting function mapping periodic task...')
@ -60,25 +59,10 @@ class EngineService(service.Service):
LOG.info('Starting engine...') LOG.info('Starting engine...')
self.server.start() self.server.start()
super(EngineService, self).start() def terminate(self):
def stop(self, graceful=False):
periodics.stop() periodics.stop()
if self.server: if self.server:
LOG.info('Stopping engine...') LOG.info('Stopping engine...')
self.server.stop() self.server.stop()
if graceful: self.server.wait()
LOG.info(
'Consumer successfully stopped. Waiting for final '
'messages to be processed...'
)
self.server.wait()
super(EngineService, self).stop(graceful=graceful)
def reset(self):
if self.server:
self.server.reset()
super(EngineService, self).reset()

View File

@ -82,7 +82,7 @@ def get_request_data(conf, function_id, execution_id, input, entry, trust_id):
data.update( data.update(
{ {
'token': ctx.auth_token, 'token': ctx.auth_token,
'auth_url': conf.keystone_authtoken.auth_uri, 'auth_url': conf.keystone_authtoken.www_authenticate_uri,
'username': conf.keystone_authtoken.username, 'username': conf.keystone_authtoken.username,
'password': conf.keystone_authtoken.password, 'password': conf.keystone_authtoken.password,
'trust_id': trust_id 'trust_id': trust_id

View File

@ -18,7 +18,6 @@ import os
import time import time
import jinja2 import jinja2
from kubernetes import client
from oslo_log import log as logging from oslo_log import log as logging
import requests import requests
import tenacity import tenacity
@ -27,6 +26,7 @@ import yaml
from qinling.engine import utils from qinling.engine import utils
from qinling import exceptions as exc from qinling import exceptions as exc
from qinling.orchestrator import base from qinling.orchestrator import base
from qinling.orchestrator.kubernetes import utils as k8s_util
from qinling.utils import common from qinling.utils import common
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -38,9 +38,9 @@ class KubernetesManager(base.OrchestratorBase):
def __init__(self, conf): def __init__(self, conf):
self.conf = conf self.conf = conf
client.Configuration().host = self.conf.kubernetes.kube_host clients = k8s_util.get_k8s_clients(self.conf)
self.v1 = client.CoreV1Api() self.v1 = clients['v1']
self.v1extention = client.ExtensionsV1beta1Api() self.v1extention = clients['v1extention']
# Create namespace if not exists # Create namespace if not exists
self._ensure_namespace() self._ensure_namespace()

View File

@ -0,0 +1,34 @@
# Copyright 2018 Catalyst IT Limited
#
# 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 kubernetes.client import api_client
from kubernetes.client.apis import core_v1_api
from kubernetes.client.apis import extensions_v1beta1_api
from kubernetes.client import configuration as k8s_config
def get_k8s_clients(conf):
config = k8s_config.Configuration()
config.host = conf.kubernetes.kube_host
config.verify_ssl = False
client = api_client.ApiClient(configuration=config)
v1 = core_v1_api.CoreV1Api(client)
v1extention = extensions_v1beta1_api.ExtensionsV1beta1Api(client)
clients = {
'v1': v1,
'v1extention': v1extention
}
return clients

View File

@ -39,7 +39,7 @@ class FileSystemStorage(base.PackageStorage):
:param function: Function ID. :param function: Function ID.
:param data: Package data. :param data: Package data.
""" """
LOG.info( LOG.debug(
'Store package, function: %s, project: %s', function, project_id 'Store package, function: %s, project: %s', function, project_id
) )
@ -64,7 +64,7 @@ class FileSystemStorage(base.PackageStorage):
:param function: Function ID. :param function: Function ID.
:return: File descriptor that needs to close outside. :return: File descriptor that needs to close outside.
""" """
LOG.info( LOG.debug(
'Get package data, function: %s, project: %s', function, project_id 'Get package data, function: %s, project: %s', function, project_id
) )
@ -80,11 +80,12 @@ class FileSystemStorage(base.PackageStorage):
) )
f = open(func_zip, 'rb') f = open(func_zip, 'rb')
LOG.debug('Found package data')
return f return f
def delete(self, project_id, function): def delete(self, project_id, function):
LOG.info( LOG.debug(
'Delete package data, function: %s, project: %s', function, 'Delete package data, function: %s, project: %s', function,
project_id project_id
) )

View File

@ -12,11 +12,65 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import functools import functools
import sys
import warnings import warnings
from oslo_utils import uuidutils from oslo_utils import uuidutils
import six import six
from qinling import version
def print_server_info(service):
QINLING_TITLE = r"""
/^L_ ,."\
/~\ __ /~ \ ./ \
/ _\ _/ \ /T~\|~\_\ / \_ /~| _^
/ \ /W \ / V^\/X /~ T . \/ \ ,v-./
,'`-. /~ ^ H , . \/ ; . \ `. \-' /
M ~ | . ; / , _ : . ~\_,-'
/ ~ . \ / : ' \ ,/`
I o. ^ oP '98b - _ 9.` `\9b.
8oO888. oO888P d888b9bo. .8o 888o. 8bo. o 988o.
88888888888888888888888888bo.98888888bo. 98888bo. .d888P
88888888888888888888888888888888888888888888888888888888888
_ __ _
___ _ (_) ___ / / (_) ___ ___ _
/ _ `/ / / / _ \ / / / / / _ \ / _ `/
\_, / /_/ /_//_//_/ /_/ /_//_/ \_, /
/_/ /___/
Function as a Service in OpenStack, version: %s
""" % version.version_string()
print(QINLING_TITLE)
print('Launching server components %s...' % service)
def get_properly_ordered_parameters():
"""Orders launch parameters in the right order.
In oslo it's important the order of the launch parameters.
if --config-file came after the command line parameters the command
line parameters are ignored.
So to make user command line parameters are never ignored this method
moves --config-file to be always first.
"""
args = sys.argv[1:]
for arg in sys.argv[1:]:
if arg == '--config-file' or arg.startswith('--config-file='):
if "=" in arg:
conf_file_value = arg.split("=", 1)[1]
else:
conf_file_value = args[args.index(arg) + 1]
args.remove(conf_file_value)
args.remove(arg)
args.insert(0, "--config-file")
args.insert(1, conf_file_value)
return args
def convert_dict_to_string(d): def convert_dict_to_string(d):
temp_list = ['%s=%s' % (k, v) for k, v in d.items()] temp_list = ['%s=%s' % (k, v) for k, v in d.items()]

View File

@ -30,7 +30,7 @@ def _get_user_keystone_session():
ctx = context.get_ctx() ctx = context.get_ctx()
auth = v3.Token( auth = v3.Token(
auth_url=CONF.keystone_authtoken.auth_uri, auth_url=CONF.keystone_authtoken.www_authenticate_uri,
token=ctx.auth_token, token=ctx.auth_token,
) )
@ -49,7 +49,7 @@ def get_swiftclient():
@common.disable_ssl_warnings @common.disable_ssl_warnings
def get_user_client(): def get_user_client():
ctx = context.get_ctx() ctx = context.get_ctx()
auth_url = CONF.keystone_authtoken.auth_uri auth_url = CONF.keystone_authtoken.www_authenticate_uri
client = ks_client.Client( client = ks_client.Client(
user_id=ctx.user, user_id=ctx.user,
token=ctx.auth_token, token=ctx.auth_token,
@ -67,7 +67,7 @@ def get_service_client():
username=CONF.keystone_authtoken.username, username=CONF.keystone_authtoken.username,
password=CONF.keystone_authtoken.password, password=CONF.keystone_authtoken.password,
project_name=CONF.keystone_authtoken.project_name, project_name=CONF.keystone_authtoken.project_name,
auth_url=CONF.keystone_authtoken.auth_uri, auth_url=CONF.keystone_authtoken.www_authenticate_uri,
user_domain_name=CONF.keystone_authtoken.user_domain_name, user_domain_name=CONF.keystone_authtoken.user_domain_name,
project_domain_name=CONF.keystone_authtoken.project_domain_name project_domain_name=CONF.keystone_authtoken.project_domain_name
) )
@ -80,7 +80,7 @@ def get_trust_client(trust_id):
client = ks_client.Client( client = ks_client.Client(
username=CONF.keystone_authtoken.username, username=CONF.keystone_authtoken.username,
password=CONF.keystone_authtoken.password, password=CONF.keystone_authtoken.password,
auth_url=CONF.keystone_authtoken.auth_uri, auth_url=CONF.keystone_authtoken.www_authenticate_uri,
trust_id=trust_id trust_id=trust_id
) )

View File

@ -41,6 +41,6 @@ QinlingGroup = [
'publicURL', 'adminURL', 'internalURL'], 'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the qinling service."), help="The endpoint type to use for the qinling service."),
cfg.StrOpt('kube_host', cfg.StrOpt('kube_host',
default='127.0.0.1:8001', default='http://127.0.0.1:8001',
help="The Kubernetes service address."), help="The Kubernetes service address."),
] ]

View File

@ -13,12 +13,13 @@
# limitations under the License. # limitations under the License.
import os import os
from kubernetes import client as k8s_client
from tempest import config from tempest import config
from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import data_utils
from tempest import test from tempest import test
import tenacity import tenacity
from qinling_tempest_plugin.tests import utils
CONF = config.CONF CONF = config.CONF
@ -41,9 +42,9 @@ class BaseQinlingTest(test.BaseTestCase):
cls.admin_client = cls.os_admin.qinling.QinlingClient() cls.admin_client = cls.os_admin.qinling.QinlingClient()
# Initilize k8s client # Initilize k8s client
k8s_client.Configuration().host = CONF.qinling.kube_host clients = utils.get_k8s_clients(CONF)
cls.k8s_v1 = k8s_client.CoreV1Api() cls.k8s_v1 = clients['v1']
cls.k8s_v1extention = k8s_client.ExtensionsV1beta1Api() cls.k8s_v1extention = clients['v1extention']
cls.namespace = 'qinling' cls.namespace = 'qinling'
@tenacity.retry( @tenacity.retry(

View File

@ -0,0 +1,34 @@
# Copyright 2018 Catalyst IT Limited
#
# 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 kubernetes.client import api_client
from kubernetes.client.apis import core_v1_api
from kubernetes.client.apis import extensions_v1beta1_api
from kubernetes.client import configuration as k8s_config
def get_k8s_clients(conf):
config = k8s_config.Configuration()
config.host = conf.qinling.kube_host
config.verify_ssl = False
client = api_client.ApiClient(configuration=config)
v1 = core_v1_api.CoreV1Api(client)
v1extention = extensions_v1beta1_api.ExtensionsV1beta1Api(client)
clients = {
'v1': v1,
'v1extention': v1extention
}
return clients

View File

@ -4,30 +4,30 @@
pbr!=2.1.0,>=2.0.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0
Babel!=2.4.0,>=2.3.4 # BSD Babel!=2.4.0,>=2.3.4 # BSD
eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT keystoneauth1>=3.3.0 # Apache-2.0
keystoneauth1>=2.21.0 # Apache-2.0 keystonemiddleware>=4.17.0 # Apache-2.0
keystonemiddleware>=4.12.0 # Apache-2.0 oslo.concurrency>=3.20.0 # Apache-2.0
oslo.concurrency>=3.8.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0
oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0 oslo.db>=4.27.0 # Apache-2.0
oslo.db>=4.23.0 # Apache-2.0 oslo.messaging>=5.29.0 # Apache-2.0
oslo.messaging!=5.25.0,>=5.24.2 # Apache-2.0 oslo.policy>=1.30.0 # Apache-2.0
oslo.policy>=1.23.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0
oslo.log>=3.22.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0 oslo.service!=1.28.1,>=1.24.0 # Apache-2.0
oslo.service>=1.10.0 # Apache-2.0
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
setuptools!=24.0.0,!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2,>=16.0 # PSF/ZPL setuptools!=24.0.0,!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2,!=36.2.0,>=16.0 # PSF/ZPL
six>=1.9.0 # MIT six>=1.10.0 # MIT
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
sqlalchemy-migrate>=0.11.0 # Apache-2.0 sqlalchemy-migrate>=0.11.0 # Apache-2.0
stevedore>=1.20.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0
WSME>=0.8 # MIT WSME>=0.8.0 # MIT
kubernetes>=1.0.0b1 # Apache-2.0 kubernetes>=4.0.0 # Apache-2.0
PyYAML>=3.10.0 # MIT PyYAML>=3.10 # MIT
python-swiftclient>=3.2.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0
croniter>=0.3.4 # MIT License croniter>=0.3.4 # MIT License
python-dateutil>=2.4.2 # BSD python-dateutil>=2.4.2 # BSD
tenacity>=3.2.1 # Apache-2.0 tenacity>=3.2.1 # Apache-2.0
PyMySQL>=0.7.6 # MIT License PyMySQL>=0.7.6 # MIT License
etcd3gw>=0.2.0 # Apache-2.0 etcd3gw>=0.2.0 # Apache-2.0
cotyledon>=1.3.0 # Apache-2.0

View File

@ -25,7 +25,8 @@ packages =
[entry_points] [entry_points]
console_scripts = console_scripts =
qinling-server = qinling.cmd.launch:main qinling-api = qinling.cmd.api:main
qinling-engine = qinling.cmd.engine:main
qinling-db-manage = qinling.db.sqlalchemy.migration.cli:main qinling-db-manage = qinling.db.sqlalchemy.migration.cli:main
qinling.storage.provider: qinling.storage.provider:

View File

@ -25,5 +25,5 @@ except ImportError:
pass pass
setuptools.setup( setuptools.setup(
setup_requires=['pbr>=2.0.0'], setup_requires=['pbr'],
pbr=True) pbr=True)

View File

@ -8,11 +8,10 @@ sphinx>=1.6.2 # BSD
oslotest>=1.10.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT testtools>=2.2.0 # MIT
tempest>=16.1.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0
futurist>=1.2.0 # Apache-2.0 futurist>=1.2.0 # Apache-2.0
openstackdocstheme>=1.17.0 # Apache-2.0
reno>=2.5.0 # Apache-2.0
openstackdocstheme>=1.16.0 # Apache-2.0 kubernetes>=4.0.0 # Apache-2.0
# releasenotes
reno!=2.3.1,>=1.8.0 # Apache-2.0