Add nimble-api command

Change-Id: I4b00409a79a14c2f75f8627ddd9caae15c768deb
This commit is contained in:
Zhenguo Niu 2016-08-16 19:43:43 +08:00
parent 9ea7fb537d
commit 7fb06d16cd
13 changed files with 537 additions and 1 deletions

View File

@ -45,3 +45,12 @@ def setup_app(pecan_config=None, extra_hooks=None):
)
return app
class VersionSelectorApplication(object):
def __init__(self):
pc = get_pecan_config()
self.v1 = setup_app(pecan_config=pc)
def __call__(self, environ, start_response):
return self.v1(environ, start_response)

18
nimble/cmd/__init__.py Normal file
View File

@ -0,0 +1,18 @@
# Copyright 2016 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 oslo_i18n as i18n
i18n.install('nimble')

38
nimble/cmd/api.py Normal file
View File

@ -0,0 +1,38 @@
# Copyright 2016 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.
"""The Nimble Service API."""
import sys
from oslo_config import cfg
from nimble.common import service as nimble_service
CONF = cfg.CONF
def main():
# Parse config file and command line options, then start logging
nimble_service.prepare_service(sys.argv)
# Build and start the WSGI app
launcher = nimble_service.process_launcher()
server = nimble_service.WSGIService('nimble_api', CONF.api.enable_ssl_api)
launcher.launch_service(server, workers=server.workers)
launcher.wait()
if __name__ == '__main__':
sys.exit(main())

28
nimble/common/config.py Normal file
View File

@ -0,0 +1,28 @@
# Copyright 2016 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.
from oslo_config import cfg
from nimble.common import rpc
from nimble import version
def parse_args(argv, default_config_files=None):
rpc.set_defaults(control_exchange='nimble')
cfg.CONF(argv[1:],
project='nimble',
version=version.version_info.release_string(),
default_config_files=default_config_files)
rpc.init(cfg.CONF)

99
nimble/common/context.py Normal file
View File

@ -0,0 +1,99 @@
# -*- encoding: utf-8 -*-
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_context import context
class RequestContext(context.RequestContext):
"""Extends security contexts from the oslo.context library."""
def __init__(self, auth_token=None, domain_id=None, domain_name=None,
user=None, tenant=None, is_admin=False, is_public_api=False,
read_only=False, show_deleted=False, request_id=None,
roles=None, show_password=True, overwrite=True):
"""Initialize the RequestContext
:param auth_token: The authentication token of the current request.
:param domain_id: The ID of the domain.
:param domain_name: The name of the domain.
:param user: The name of the user.
:param tenant: The name of the tenant.
:param is_admin: Indicates if the request context is an administrator
context.
:param is_public_api: Specifies whether the request should be processed
without authentication.
:param request_id: The UUID of the request.
:param roles: List of user's roles if any.
:param show_password: Specifies whether passwords should be masked
before sending back to API call.
:param overwrite: Set to False to ensure that the greenthread local
copy of the index is not overwritten.
"""
super(RequestContext, self).__init__(auth_token=auth_token,
user=user, tenant=tenant,
is_admin=is_admin,
read_only=read_only,
show_deleted=show_deleted,
request_id=request_id,
overwrite=overwrite)
self.is_public_api = is_public_api
self.domain_id = domain_id
self.domain_name = domain_name
self.show_password = show_password
# NOTE(dims): roles was added in context.RequestContext recently.
# we should pass roles in __init__ above instead of setting the
# value here once the minimum version of oslo.context is updated.
self.roles = roles or []
def to_dict(self):
return {'auth_token': self.auth_token,
'user': self.user,
'tenant': self.tenant,
'is_admin': self.is_admin,
'read_only': self.read_only,
'show_deleted': self.show_deleted,
'request_id': self.request_id,
'domain_id': self.domain_id,
'roles': self.roles,
'domain_name': self.domain_name,
'show_password': self.show_password,
'is_public_api': self.is_public_api}
@classmethod
def from_dict(cls, values):
values.pop('user', None)
values.pop('tenant', None)
return cls(**values)
def ensure_thread_contain_context(self):
"""Ensure threading contains context
For async/periodic tasks, the context of local thread is missing.
Set it with request context and this is useful to log the request_id
in log messages.
"""
if context.get_current():
return
self.update_store()
def get_admin_context():
"""Create an administrator context."""
context = RequestContext(None,
tenant=None,
is_admin=True,
overwrite=False)
return context

View File

@ -114,3 +114,7 @@ class TemporaryFailure(NimbleException):
class NotAcceptable(NimbleException):
_msg_fmt = _("Request not acceptable.")
code = http_client.NOT_ACCEPTABLE
class ConfigInvalid(NimbleException):
_msg_fmt = _("Invalid configuration file. %(error_msg)s")

130
nimble/common/rpc.py Normal file
View File

@ -0,0 +1,130 @@
# Copyright 2016 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.
from oslo_config import cfg
import oslo_messaging as messaging
from nimble.common import context as nimble_context
from nimble.common import exception
CONF = cfg.CONF
TRANSPORT = None
NOTIFICATION_TRANSPORT = None
NOTIFIER = None
ALLOWED_EXMODS = [
exception.__name__,
]
EXTRA_EXMODS = []
TRANSPORT_ALIASES = {
'nimble.rpc.impl_kombu': 'rabbit',
'nimble.rpc.impl_qpid': 'qpid',
'nimble.rpc.impl_zmq': 'zmq',
}
def init(conf):
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
exmods = get_allowed_exmods()
TRANSPORT = messaging.get_transport(conf,
allowed_remote_exmods=exmods,
aliases=TRANSPORT_ALIASES)
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
conf,
allowed_remote_exmods=exmods,
aliases=TRANSPORT_ALIASES)
serializer = RequestContextSerializer(messaging.JsonPayloadSerializer())
NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
serializer=serializer)
def cleanup():
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
assert TRANSPORT is not None
assert NOTIFICATION_TRANSPORT is not None
assert NOTIFIER is not None
TRANSPORT.cleanup()
NOTIFICATION_TRANSPORT.cleanup()
TRANSPORT = NOTIFICATION_TRANSPORT = NOTIFIER = None
def set_defaults(control_exchange):
messaging.set_transport_defaults(control_exchange)
def add_extra_exmods(*args):
EXTRA_EXMODS.extend(args)
def clear_extra_exmods():
del EXTRA_EXMODS[:]
def get_allowed_exmods():
return ALLOWED_EXMODS + EXTRA_EXMODS
class RequestContextSerializer(messaging.Serializer):
def __init__(self, base):
self._base = base
def serialize_entity(self, context, entity):
if not self._base:
return entity
return self._base.serialize_entity(context, entity)
def deserialize_entity(self, context, entity):
if not self._base:
return entity
return self._base.deserialize_entity(context, entity)
def serialize_context(self, context):
return context.to_dict()
def deserialize_context(self, context):
return nimble_context.RequestContext.from_dict(context)
def get_transport_url(url_str=None):
return messaging.TransportURL.parse(CONF, url_str, TRANSPORT_ALIASES)
def get_client(target, version_cap=None, serializer=None):
assert TRANSPORT is not None
serializer = RequestContextSerializer(serializer)
return messaging.RPCClient(TRANSPORT,
target,
version_cap=version_cap,
serializer=serializer)
def get_server(target, endpoints, serializer=None):
assert TRANSPORT is not None
serializer = RequestContextSerializer(serializer)
return messaging.get_rpc_server(TRANSPORT,
target,
endpoints,
executor='eventlet',
serializer=serializer)
def get_notifier(service=None, host=None, publisher_id=None):
assert NOTIFIER is not None
if not publisher_id:
publisher_id = "%s.%s" % (service, host or CONF.host)
return NOTIFIER.prepare(publisher_id=publisher_id)

106
nimble/common/service.py Normal file
View File

@ -0,0 +1,106 @@
# -*- encoding: utf-8 -*-
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_concurrency import processutils
from oslo_log import log
from oslo_service import service
from oslo_service import wsgi
from nimble.api import app
from nimble.common import config
from nimble.common import exception
from nimble.common.i18n import _
from nimble.conf import CONF
LOG = log.getLogger(__name__)
def prepare_service(argv=None):
argv = [] if argv is None else argv
log.register_options(CONF)
log.set_defaults(default_log_levels=['amqp=WARNING',
'amqplib=WARNING',
'qpid.messaging=INFO',
'oslo_messaging=INFO',
'sqlalchemy=WARNING',
'stevedore=INFO',
'eventlet.wsgi.server=INFO',
'iso8601=WARNING',
'paramiko=WARNING',
'requests=WARNING',
'neutronclient=WARNING',
'glanceclient=WARNING',
'urllib3.connectionpool=WARNING',
'keystonemiddleware.auth_token=INFO',
'keystoneauth.session=INFO',
])
config.parse_args(argv)
log.setup(CONF, 'nimble')
def process_launcher():
return service.ProcessLauncher(CONF)
class WSGIService(service.ServiceBase):
"""Provides ability to launch nimble API from wsgi app."""
def __init__(self, name, use_ssl=False):
"""Initialize, but do not start the WSGI server.
:param name: The name of the WSGI server given to the loader.
:param use_ssl: Wraps the socket in an SSL context if True.
:returns: None
"""
self.name = name
self.app = app.VersionSelectorApplication()
self.workers = (CONF.api.api_workers or
processutils.get_worker_count())
if self.workers and self.workers < 1:
raise exception.ConfigInvalid(
_("api_workers value of %d is invalid, "
"must be greater than 0.") % self.workers)
self.server = wsgi.Server(CONF, name, self.app,
host=CONF.api.host_ip,
port=CONF.api.port,
use_ssl=use_ssl)
def start(self):
"""Start serving this service using loaded configuration.
:returns: None
"""
self.server.start()
def stop(self):
"""Stop serving this API.
:returns: None
"""
self.server.stop()
def wait(self):
"""Wait for the service to stop serving this API.
:returns: None
"""
self.server.wait()
def reset(self):
"""Reset server greenpool size to default.
:returns: None
"""
self.server.reset()

22
nimble/conf/__init__.py Normal file
View File

@ -0,0 +1,22 @@
# Copyright 2016 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.
from oslo_config import cfg
from nimble.conf import api
CONF = cfg.CONF
api.register_opts(CONF)

59
nimble/conf/api.py Normal file
View File

@ -0,0 +1,59 @@
# Copyright 2016 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.
from oslo_config import cfg
from nimble.common.i18n import _
opts = [
cfg.StrOpt('host_ip',
default='0.0.0.0',
help=_('The IP address on which nimble-api listens.')),
cfg.PortOpt('port',
default=6688,
help=_('The TCP port on which nimble-api listens.')),
cfg.IntOpt('max_limit',
default=1000,
help=_('The maximum number of items returned in a single '
'response from a collection resource.')),
cfg.StrOpt('public_endpoint',
help=_("Public URL to use when building the links to the API "
"resources (for example, \"https://nimble.rocks:6688\")."
" If None the links will be built using the request's "
"host URL. If the API is operating behind a proxy, you "
"will want to change this to represent the proxy's URL. "
"Defaults to None.")),
cfg.IntOpt('api_workers',
help=_('Number of workers for OpenStack Nimble API service. '
'The default is equal to the number of CPUs available '
'if that can be determined, else a default worker '
'count of 1 is returned.')),
cfg.BoolOpt('enable_ssl_api',
default=False,
help=_("Enable the integrated stand-alone API to service "
"requests via HTTPS instead of HTTP. If there is a "
"front-end service performing HTTPS offloading from "
"the service, this option should be False; note, you "
"will want to change public API endpoint to represent "
"SSL termination URL with 'public_endpoint' option.")),
]
opt_group = cfg.OptGroup(name='api',
title='Options for the nimble-api service')
def register_opts(conf):
conf.register_group(opt_group)
conf.register_opts(opts, group=opt_group)

18
nimble/version.py Normal file
View File

@ -0,0 +1,18 @@
# Copyright 2016 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 pbr.version
version_info = pbr.version.VersionInfo('nimble')

View File

@ -3,6 +3,7 @@
# process, which may cause wedges in the gate later.
pbr>=1.6 # Apache-2.0
eventlet!=0.18.3,>=0.18.2 # MIT
WebOb>=1.2.3 # MIT
oslo.concurrency>=3.8.0 # Apache-2.0
oslo.config>=3.14.0 # Apache-2.0

View File

@ -23,6 +23,10 @@ classifier =
packages =
nimble
[entry_points]
console_scripts =
nimble-api = nimble.cmd.api:main
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
@ -48,4 +52,4 @@ output_file = nimble/locale/nimble.pot
[build_releasenotes]
all_files = 1
build-dir = releasenotes/build
source-dir = releasenotes/source
source-dir = releasenotes/source