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
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
iniset_rpc_backend qinling $QINLING_CONF_FILE
@ -99,8 +99,8 @@ function init_qinling {
function start_qinling {
run_process qinling-engine "$QINLING_BIN_DIR/qinling-server --server 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-engine "$QINLING_BIN_DIR/qinling-engine --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):
self.storage_provider = storage_base.load_storage_provider(CONF)
self.engine_client = rpc.get_engine_client()
self.type = 'function'
super(FunctionsController, self).__init__(*args, **kwargs)
@ -92,7 +91,7 @@ class FunctionsController(rest.RestController):
@rest_utils.wrap_pecan_controller_exception
@pecan.expose()
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(
pecan.request.GET.get('download', False)
@ -104,6 +103,7 @@ class FunctionsController(rest.RestController):
pecan.override_template('json')
return resources.Function.from_dict(func_db.to_dict()).to_dict()
else:
LOG.info("Downloading function %s", id)
source = func_db.code['source']
if source == 'package':
@ -126,11 +126,12 @@ class FunctionsController(rest.RestController):
pecan.response.headers['Content-Disposition'] = (
'attachment; filename="%s"' % os.path.basename(func_db.name)
)
LOG.info("Downloaded function %s", id)
@rest_utils.wrap_pecan_controller_exception
@pecan.expose('json')
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
# param.
@ -221,7 +222,7 @@ class FunctionsController(rest.RestController):
filters = rest_utils.get_filters(
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)
functions = [resources.Function.from_dict(db_model.to_dict())
for db_model in db_functions]
@ -232,7 +233,7 @@ class FunctionsController(rest.RestController):
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
def delete(self, id):
"""Delete the specified function."""
LOG.info("Delete resource.", resource={'type': self.type, 'id': id})
LOG.info("Delete function %s.", id)
with db_api.transaction():
func_db = db_api.get_function(id)
@ -280,9 +281,7 @@ class FunctionsController(rest.RestController):
if kwargs.get(key) is not None:
values.update({key: kwargs[key]})
LOG.info('Update resource, params: %s', values,
resource={'type': self.type, 'id': id})
LOG.info('Update function %s, params: %s', id, values)
ctx = context.get_ctx()
if set(values.keys()).issubset(set(['name', 'description'])):

View File

@ -14,25 +14,34 @@
from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import service
from oslo_service import wsgi
from qinling.api import app
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class WSGIService(service.ServiceBase):
"""Provides ability to launch Mistral API from wsgi app."""
def __init__(self, name):
self.name = name
def __init__(self):
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(
cfg.CONF,
name,
"qinling_api",
self.app,
host=cfg.CONF.api.host,
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(
'api_workers',
default=1,
help='Number of workers for Qinling API service '
'default is equal to the number of CPUs available if that can '
'be determined, else a default worker count of 1 is returned.'
@ -134,7 +135,7 @@ kubernetes_opts = [
),
cfg.StrOpt(
'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 '
'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',
'requests.packages.urllib3.connectionpool=CRITICAL',
'urllib3.connectionpool=CRITICAL',
'cotyledon=INFO'
]
default_log_levels = log.get_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
# under the License.
import cotyledon
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging as messaging
from oslo_messaging.rpc import dispatcher
from oslo_service import service
from qinling.db import api as db_api
from qinling.engine import default_engine as engine
@ -28,12 +28,12 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class EngineService(service.Service):
def __init__(self):
super(EngineService, self).__init__()
class EngineService(cotyledon.Service):
def __init__(self, worker_id):
super(EngineService, self).__init__(worker_id)
self.server = None
def start(self):
def run(self):
orchestrator = orchestra_base.load_orchestrator(CONF)
db_api.setup_db()
@ -47,11 +47,10 @@ class EngineService(service.Service):
transport,
target,
[endpoint],
executor='eventlet',
executor='threading',
access_policy=access_policy,
serializer=rpc.ContextSerializer(
messaging.serializer.JsonPayloadSerializer()
)
messaging.serializer.JsonPayloadSerializer())
)
LOG.info('Starting function mapping periodic task...')
@ -60,25 +59,10 @@ class EngineService(service.Service):
LOG.info('Starting engine...')
self.server.start()
super(EngineService, self).start()
def stop(self, graceful=False):
def terminate(self):
periodics.stop()
if self.server:
LOG.info('Stopping engine...')
self.server.stop()
if graceful:
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()
self.server.wait()

View File

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

View File

@ -18,7 +18,6 @@ import os
import time
import jinja2
from kubernetes import client
from oslo_log import log as logging
import requests
import tenacity
@ -27,6 +26,7 @@ import yaml
from qinling.engine import utils
from qinling import exceptions as exc
from qinling.orchestrator import base
from qinling.orchestrator.kubernetes import utils as k8s_util
from qinling.utils import common
LOG = logging.getLogger(__name__)
@ -38,9 +38,9 @@ class KubernetesManager(base.OrchestratorBase):
def __init__(self, conf):
self.conf = conf
client.Configuration().host = self.conf.kubernetes.kube_host
self.v1 = client.CoreV1Api()
self.v1extention = client.ExtensionsV1beta1Api()
clients = k8s_util.get_k8s_clients(self.conf)
self.v1 = clients['v1']
self.v1extention = clients['v1extention']
# Create namespace if not exists
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 data: Package data.
"""
LOG.info(
LOG.debug(
'Store package, function: %s, project: %s', function, project_id
)
@ -64,7 +64,7 @@ class FileSystemStorage(base.PackageStorage):
:param function: Function ID.
:return: File descriptor that needs to close outside.
"""
LOG.info(
LOG.debug(
'Get package data, function: %s, project: %s', function, project_id
)
@ -80,11 +80,12 @@ class FileSystemStorage(base.PackageStorage):
)
f = open(func_zip, 'rb')
LOG.debug('Found package data')
return f
def delete(self, project_id, function):
LOG.info(
LOG.debug(
'Delete package data, function: %s, project: %s', function,
project_id
)

View File

@ -12,11 +12,65 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import sys
import warnings
from oslo_utils import uuidutils
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):
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()
auth = v3.Token(
auth_url=CONF.keystone_authtoken.auth_uri,
auth_url=CONF.keystone_authtoken.www_authenticate_uri,
token=ctx.auth_token,
)
@ -49,7 +49,7 @@ def get_swiftclient():
@common.disable_ssl_warnings
def get_user_client():
ctx = context.get_ctx()
auth_url = CONF.keystone_authtoken.auth_uri
auth_url = CONF.keystone_authtoken.www_authenticate_uri
client = ks_client.Client(
user_id=ctx.user,
token=ctx.auth_token,
@ -67,7 +67,7 @@ def get_service_client():
username=CONF.keystone_authtoken.username,
password=CONF.keystone_authtoken.password,
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,
project_domain_name=CONF.keystone_authtoken.project_domain_name
)
@ -80,7 +80,7 @@ def get_trust_client(trust_id):
client = ks_client.Client(
username=CONF.keystone_authtoken.username,
password=CONF.keystone_authtoken.password,
auth_url=CONF.keystone_authtoken.auth_uri,
auth_url=CONF.keystone_authtoken.www_authenticate_uri,
trust_id=trust_id
)

View File

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

View File

@ -13,12 +13,13 @@
# limitations under the License.
import os
from kubernetes import client as k8s_client
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest import test
import tenacity
from qinling_tempest_plugin.tests import utils
CONF = config.CONF
@ -41,9 +42,9 @@ class BaseQinlingTest(test.BaseTestCase):
cls.admin_client = cls.os_admin.qinling.QinlingClient()
# Initilize k8s client
k8s_client.Configuration().host = CONF.qinling.kube_host
cls.k8s_v1 = k8s_client.CoreV1Api()
cls.k8s_v1extention = k8s_client.ExtensionsV1beta1Api()
clients = utils.get_k8s_clients(CONF)
cls.k8s_v1 = clients['v1']
cls.k8s_v1extention = clients['v1extention']
cls.namespace = 'qinling'
@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
Babel!=2.4.0,>=2.3.4 # BSD
eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT
keystoneauth1>=2.21.0 # Apache-2.0
keystonemiddleware>=4.12.0 # Apache-2.0
oslo.concurrency>=3.8.0 # Apache-2.0
oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
oslo.db>=4.23.0 # Apache-2.0
oslo.messaging!=5.25.0,>=5.24.2 # Apache-2.0
oslo.policy>=1.23.0 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0
oslo.log>=3.22.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
oslo.service>=1.10.0 # Apache-2.0
keystoneauth1>=3.3.0 # Apache-2.0
keystonemiddleware>=4.17.0 # Apache-2.0
oslo.concurrency>=3.20.0 # Apache-2.0
oslo.config>=5.1.0 # Apache-2.0
oslo.db>=4.27.0 # Apache-2.0
oslo.messaging>=5.29.0 # Apache-2.0
oslo.policy>=1.30.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
oslo.log>=3.30.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.service!=1.28.1,>=1.24.0 # Apache-2.0
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
six>=1.9.0 # MIT
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.10.0 # 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
stevedore>=1.20.0 # Apache-2.0
WSME>=0.8 # MIT
kubernetes>=1.0.0b1 # Apache-2.0
PyYAML>=3.10.0 # MIT
WSME>=0.8.0 # MIT
kubernetes>=4.0.0 # Apache-2.0
PyYAML>=3.10 # MIT
python-swiftclient>=3.2.0 # Apache-2.0
croniter>=0.3.4 # MIT License
python-dateutil>=2.4.2 # BSD
tenacity>=3.2.1 # Apache-2.0
PyMySQL>=0.7.6 # MIT License
etcd3gw>=0.2.0 # Apache-2.0
python-dateutil>=2.4.2 # BSD
tenacity>=3.2.1 # Apache-2.0
PyMySQL>=0.7.6 # MIT License
etcd3gw>=0.2.0 # Apache-2.0
cotyledon>=1.3.0 # Apache-2.0

View File

@ -25,7 +25,8 @@ packages =
[entry_points]
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.storage.provider:

View File

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

View File

@ -8,11 +8,10 @@ sphinx>=1.6.2 # BSD
oslotest>=1.10.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
tempest>=16.1.0 # Apache-2.0
futurist>=1.2.0 # Apache-2.0
testtools>=2.2.0 # MIT
tempest>=17.1.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
# releasenotes
reno!=2.3.1,>=1.8.0 # Apache-2.0
kubernetes>=4.0.0 # Apache-2.0