From 2dc80310bae36ef8dcbec4c990b23a6405043f4f Mon Sep 17 00:00:00 2001 From: zhangguoqing Date: Tue, 6 Sep 2016 18:14:22 +0800 Subject: [PATCH] Add WSGI support for `cloudkitty-api' Recommands to setup cloudkitty through an other WSGI services like Apache 'mod_wsgi'. And the community has set a community wide goal in Pike cycle: "Control Plane API endpoints deployment via WSGI" https://governance.openstack.org/tc/goals/pike/deploy-api-in-wsgi.html Work Item: Add WSGI support 1. Provide WSGI application script file. 2. Removing the cloudkitty-api command line. 3. Adding cloudkitty-api wsgi_scripts, by 'cloudkitty-api -p 8889' to run. Work Item: Make the devstack setup ck-api with wsgi 1. Switch devstack jobs to deploy control-plane API services in WSGI with Apache. 2. Default to deploy with Apache by global ENABLE_HTTPD_MOD_WSGI_SERVICES, in local.conf expose CLOUDKITTY_USE_MOD_WSGI=False to run without Apache. Work Item: Update the docs about installation 1. Installing the cloudkitty-api behind mod_wsgi. 2. Updating the installation about the cloudkitty-api. Implements: blueprint wsgi-support Change-Id: I207587c5360bb80c0e856cd0239e4073578951aa --- cloudkitty/api/app.py | 44 ++++------------- cloudkitty/{cli/api.py => api/app.wsgi} | 28 ++++------- devstack/apache-cloudkitty.template | 15 ++++++ devstack/plugin.sh | 64 +++++++++++++++++++++++-- devstack/settings | 3 +- doc/source/index.rst | 1 + doc/source/installation.rst | 21 ++++++-- doc/source/mod_wsgi.rst | 53 ++++++++++++++++++++ etc/apache2/cloudkitty | 39 +++++++++++++++ setup.cfg | 4 +- 10 files changed, 208 insertions(+), 64 deletions(-) rename cloudkitty/{cli/api.py => api/app.wsgi} (66%) create mode 100644 devstack/apache-cloudkitty.template create mode 100644 doc/source/mod_wsgi.rst create mode 100644 etc/apache2/cloudkitty diff --git a/cloudkitty/api/app.py b/cloudkitty/api/app.py index 43375e97..f298d6c4 100644 --- a/cloudkitty/api/app.py +++ b/cloudkitty/api/app.py @@ -15,9 +15,7 @@ # # @author: Stéphane Albert # -import logging import os -from wsgiref import simple_server from oslo_config import cfg from oslo_log import log @@ -26,6 +24,7 @@ import pecan from cloudkitty.api import config as api_config from cloudkitty.api import hooks +from cloudkitty import service from cloudkitty import storage @@ -34,8 +33,7 @@ LOG = log.getLogger(__name__) auth_opts = [ cfg.StrOpt('api_paste_config', default="api_paste.ini", - help="Configuration file for WSGI definition of API." - ), + help="Configuration file for WSGI definition of API."), cfg.StrOpt('auth_strategy', choices=['noauth', 'keystone'], default='keystone', @@ -45,11 +43,11 @@ auth_opts = [ api_opts = [ cfg.IPOpt('host_ip', - default="0.0.0.0", - help='Host serving the API.'), + default='0.0.0.0', + help='The listen IP for the cloudkitty API server.'), cfg.PortOpt('port', default=8889, - help='Host port serving the API.'), + help='The port for the cloudkitty API server.'), cfg.BoolOpt('pecan_debug', default=False, help='Toggle Pecan Debug Middleware.'), @@ -103,35 +101,9 @@ def load_app(): return deploy.loadapp("config:" + cfg_file) -def build_server(): - # Create the WSGI server and start it - host = CONF.api.host_ip - port = CONF.api.port - LOG.info('Starting server in PID %s', os.getpid()) - LOG.info("Configuration:") - cfg.CONF.log_opt_values(LOG, logging.INFO) - - if host == '0.0.0.0': - LOG.info('serving on 0.0.0.0:%(sport)s, view at \ - http://127.0.0.1:%(vport)s', - {'sport': port, 'vport': port}) - else: - LOG.info("serving on http://%(host)s:%(port)s", - {'host': host, 'port': port}) - - server_cls = simple_server.WSGIServer - handler_cls = simple_server.WSGIRequestHandler - - app = load_app() - - srv = simple_server.make_server( - host, - port, - app, - server_cls, - handler_cls) - - return srv +def build_wsgi_app(argv=None): + service.prepare_service([]) + return load_app() def app_factory(global_config, **local_conf): diff --git a/cloudkitty/cli/api.py b/cloudkitty/api/app.wsgi similarity index 66% rename from cloudkitty/cli/api.py rename to cloudkitty/api/app.wsgi index 80a46cab..9165717a 100644 --- a/cloudkitty/cli/api.py +++ b/cloudkitty/api/app.wsgi @@ -1,5 +1,6 @@ -# -*- coding: utf-8 -*- -# Copyright 2014 Objectif Libre +# -*- mode: python -*- +# +# Copyright 2013 New Dream Network, LLC (DreamHost) # # 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 @@ -12,21 +13,12 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# -# @author: Stéphane Albert -# + +"""Use this file for deploying the API under mod_wsgi. + +See http://pecan.readthedocs.org/en/latest/deployment.html for details. +""" + from cloudkitty.api import app -from cloudkitty import service - -def main(): - service.prepare_service() - server = app.build_server() - try: - server.serve_forever() - except KeyboardInterrupt: - pass - - -if __name__ == '__main__': - main() +application = app.build_wsgi_app(argv=[]) diff --git a/devstack/apache-cloudkitty.template b/devstack/apache-cloudkitty.template new file mode 100644 index 00000000..2046ab39 --- /dev/null +++ b/devstack/apache-cloudkitty.template @@ -0,0 +1,15 @@ +Listen %PORT% + + + WSGIDaemonProcess cloudkitty-api processes=2 threads=10 user=%USER% display-name=%{GROUP} %VIRTUALENV% + WSGIProcessGroup cloudkitty-api + WSGIScriptAlias / %WSGIAPP% + WSGIApplicationGroup %{GLOBAL} + = 2.4> + ErrorLogFormat "%{cu}t %M" + + ErrorLog /var/log/%APACHE_NAME%/cloudkitty.log + CustomLog /var/log/%APACHE_NAME%/cloudkitty_access.log combined + + +WSGISocketPrefix /var/run/%APACHE_NAME% diff --git a/devstack/plugin.sh b/devstack/plugin.sh index b88a3092..e996bf5a 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -73,9 +73,19 @@ function is_cloudkitty_enabled { return 1 } +# Remove WSGI files, disable and remove Apache vhost file +function _cloudkitty_cleanup_apache_wsgi { + if is_service_enabled ck-api && [ "$CLOUDKITTY_USE_MOD_WSGI" == "True" ]; then + sudo rm -f "$CLOUDKITTY_WSGI_DIR"/* + sudo rm -rf "$CLOUDKITTY_WSGI_DIR" + sudo rm -f $(apache_site_config_for cloudkitty) + fi +} + # cleanup_cloudkitty() - Remove residual data files, anything left over from previous # runs that a clean run would need to clean up function cleanup_cloudkitty { + _cloudkitty_cleanup_apache_wsgi # Clean up dirs rm -rf $CLOUDKITTY_AUTH_CACHE_DIR/* rm -rf $CLOUDKITTY_CONF_DIR/* @@ -85,6 +95,31 @@ function cleanup_cloudkitty { done } + +# Configure mod_wsgi +function _cloudkitty_config_apache_wsgi { + sudo mkdir -m 755 -p $CLOUDKITTY_WSGI_DIR + + local cloudkitty_apache_conf=$(apache_site_config_for cloudkitty) + local venv_path="" + + # Copy proxy vhost and wsgi file + sudo cp $CLOUDKITTY_DIR/cloudkitty/api/app.wsgi $CLOUDKITTY_WSGI_DIR/app.wsgi + + if [[ ${USE_VENV} = True ]]; then + venv_path="python-path=${PROJECT_VENV["cloudkitty"]}/lib/$(python_version)/site-packages" + fi + + sudo cp $CLOUDKITTY_DIR/devstack/apache-cloudkitty.template $cloudkitty_apache_conf + sudo sed -e " + s|%PORT%|$CLOUDKITTY_SERVICE_PORT|g; + s|%APACHE_NAME%|$APACHE_NAME|g; + s|%WSGIAPP%|$CLOUDKITTY_WSGI_DIR/app.wsgi|g; + s|%USER%|$STACK_USER|g; + s|%VIRTUALENV%|$venv_path|g + " -i $cloudkitty_apache_conf +} + # configure_cloudkitty() - Set config files, create data dirs, etc function configure_cloudkitty { setup_develop $CLOUDKITTY_DIR @@ -137,6 +172,10 @@ function configure_cloudkitty { # keystone middleware configure_auth_token_middleware $CLOUDKITTY_CONF cloudkitty $CLOUDKITTY_AUTH_CACHE_DIR + + if is_service_enabled ck-api && [ "$CLOUDKITTY_USE_MOD_WSGI" == "True" ]; then + _cloudkitty_config_apache_wsgi + fi } # create_cloudkitty_cache_dir() - Part of the init_cloudkitty() process @@ -195,7 +234,14 @@ function install_cloudkitty { # start_cloudkitty() - Start running processes, including screen function start_cloudkitty { run_process ck-proc "$CLOUDKITTY_BIN_DIR/cloudkitty-processor --config-file=$CLOUDKITTY_CONF" - run_process ck-api "$CLOUDKITTY_BIN_DIR/cloudkitty-api --config-file=$CLOUDKITTY_CONF" + if [[ "$CLOUDKITTY_USE_MOD_WSGI" == "False" ]]; then + run_process ck-api "$CLOUDKITTY_BIN_DIR/cloudkitty-api --config-file=$CLOUDKITTY_CONF" + elif is_service_enabled ck-api; then + enable_apache_site cloudkitty + restart_apache_server + tail_log cloudkitty /var/log/$APACHE_NAME/cloudkitty.log + tail_log cloudkitty-api /var/log/$APACHE_NAME/cloudkitty_access.log + fi echo "Waiting for ck-api ($CLOUDKITTY_SERVICE_HOST:$CLOUDKITTY_SERVICE_PORT) to start..." if ! wait_for_service $SERVICE_TIMEOUT $CLOUDKITTY_SERVICE_PROTOCOL://$CLOUDKITTY_SERVICE_HOST:$CLOUDKITTY_SERVICE_PORT; then die $LINENO "ck-api did not start" @@ -205,9 +251,19 @@ function start_cloudkitty { # stop_cloudkitty() - Stop running processes function stop_cloudkitty { # Kill the cloudkitty screen windows - for serv in ck-api ck-proc; do - stop_process $serv - done + if is_service_enabled ck-proc ; then + stop_process ck-proc + fi + + if is_service_enabled ck-api ; then + if [ "$CLOUDKITTY_USE_MOD_WSGI" == "True" ]; then + disable_apache_site cloudkitty + restart_apache_server + else + # Kill the cloudkitty screen windows + stop_process ck-api + fi + fi } # install_python_cloudkittyclient() - Collect source and prepare diff --git a/devstack/settings b/devstack/settings index 76de6b8b..b3089412 100644 --- a/devstack/settings +++ b/devstack/settings @@ -10,12 +10,12 @@ CLOUDKITTY_DIR=$DEST/cloudkitty CLOUDKITTY_CONF_DIR=/etc/cloudkitty CLOUDKITTY_CONF=$CLOUDKITTY_CONF_DIR/cloudkitty.conf CLOUDKITTY_API_LOG_DIR=/var/log/cloudkitty +CLOUDKITTY_WSGI_DIR=${CLOUDKITTY_WSGI_DIR:-/var/www/cloudkitty} CLOUDKITTY_AUTH_CACHE_DIR=${CLOUDKITTY_AUTH_CACHE_DIR:-/var/cache/cloudkitty} CLOUDKITTY_DATA_DIR=${CLOUDKITTY_DATA_DIR:-/var/lib/cloudkitty} CLOUDKITTY_REPORTS_DIR=${DATA_DIR}/cloudkitty/reports # Horizon enabled file - CLOUDKITTY_DASHBOARD=$DEST/cloudkitty-dashboard/cloudkittydashboard CLOUDKITTY_ENABLED_DIR=${CLOUDKITTY_ENABLED_DIR:-${CLOUDKITTY_DASHBOARD}/enabled} CLOUDKITTY_HORIZON_ENABLED_DIR=${CLOUDKITTY_HORIZON_ENABLED_DIR:-$HORIZON_DIR/openstack_dashboard/enabled} @@ -32,6 +32,7 @@ CLOUDKITTY_SERVICE_HOST=${CLOUDKITTY_SERVICE_HOST:-$SERVICE_HOST} CLOUDKITTY_SERVICE_PORT=${CLOUDKITTY_SERVICE_PORT:-8889} CLOUDKITTY_SERVICE_HOSTPORT="$CLOUDKITTY_SERVICE_HOST:$CLOUDKITTY_SERVICE_PORT" CLOUDKITTY_SERVICE_PROTOCOL=${CLOUDKITTY_SERVICE_PROTOCOL:-$SERVICE_PROTOCOL} +CLOUDKITTY_USE_MOD_WSGI=${CLOUDKITTY_USE_MOD_WSGI:-${ENABLE_HTTPD_MOD_WSGI_SERVICES}} # Set CloudKitty auth info CLOUDKITTY_PRICING_USER=${CLOUDKITTY_PRICING_USER:-"admin"} diff --git a/doc/source/index.rst b/doc/source/index.rst index 8eb12646..51e9332b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -21,6 +21,7 @@ Installation devstack installation + mod_wsgi Architecture diff --git a/doc/source/installation.rst b/doc/source/installation.rst index 984f045e..c8a902e3 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -327,17 +327,30 @@ Start cloudkitty If you installed cloudkitty from packages ----------------------------------------- -Start the API and processing services:: +Start the processing services:: - systemctl start cloudkitty-api.service systemctl start cloudkitty-processor.service If you installed cloudkitty from sources ----------------------------------------- -Start the API and processing services:: +Start the processing services:: - cloudkitty-api --config-file /etc/cloudkitty/cloudkitty.conf cloudkitty-processor --config-file /etc/cloudkitty/cloudkitty.conf +Choose and start the API server +------------------------------- + + Cloudkitty includes the ``cloudkitty-api`` command. It can be + used to run the API server. For smaller or proof-of-concept + installations this is a reasonable choice. For larger installations it + is strongly recommended to install the API server in a WSGI host + such as mod_wsgi (see :doc:`mod_wsgi`). Doing so will provide better + performance and more options for making adjustments specific to the + installation environment. + + If you are using the ``cloudkitty-api`` command it can be started + as:: + + $ cloudkitty-api -p 8889 diff --git a/doc/source/mod_wsgi.rst b/doc/source/mod_wsgi.rst new file mode 100644 index 00000000..e9de644e --- /dev/null +++ b/doc/source/mod_wsgi.rst @@ -0,0 +1,53 @@ +.. + Copyright 2013 New Dream Network, LLC (DreamHost) + + 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. + +=================================== + Installing the API behind mod_wsgi +=================================== + +Cloudkitty comes with a few example files for configuring the API +service to run behind Apache with ``mod_wsgi``. + +app.wsgi +======== + +The file ``cloudkitty/api/app.wsgi`` sets up the V1 API WSGI +application. The file needs to be copied to /var/www/cloudkitty/, +and should not need to be modified. + +etc/apache2/cloudkitty +====================== + +The ``etc/apache2/cloudkitty`` file contains example settings that +work with a copy of cloudkitty installed via devstack. + +.. literalinclude:: ../../etc/apache2/cloudkitty + +1. On deb-based systems copy or symlink the file to + ``/etc/apache2/sites-available``. For rpm-based systems the file will go in + ``/etc/httpd/conf.d``. + +2. Modify the ``WSGIDaemonProcess`` directive to set the ``user`` and + ``group`` values to an appropriate user on your server. In many + installations ``cloudkitty`` will be correct. + +3. Enable the cloudkitty site. On deb-based systems:: + + $ a2ensite cloudkitty + $ service apache2 reload + + On rpm-based systems:: + + $ service httpd reload diff --git a/etc/apache2/cloudkitty b/etc/apache2/cloudkitty new file mode 100644 index 00000000..1115d6ad --- /dev/null +++ b/etc/apache2/cloudkitty @@ -0,0 +1,39 @@ +# Copyright (c) 2013 New Dream Network, LLC (DreamHost) +# +# 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. + +# This is an example Apache2 configuration file for using the +# cloudkitty API through mod_wsgi. + +# Note: If you are using a Debian-based system then the paths +# "/var/log/httpd" and "/var/run/httpd" will use "apache2" instead +# of "httpd". +# +# The number of processes and threads is an example only and should +# be adjusted according to local requirements. + +Listen 8889 + + + WSGIDaemonProcess cloudkitty-api processes=2 threads=10 user=SOMEUSER display-name=%{GROUP} + WSGIProcessGroup cloudkitty-api + WSGIScriptAlias / /var/www/cloudkitty/app.wsgi + WSGIApplicationGroup %{GLOBAL} + = 2.4> + ErrorLogFormat "%{cu}t %M" + + ErrorLog /var/log/httpd/cloudkitty_error.log + CustomLog /var/log/httpd/cloudkitty_access.log combined + + +WSGISocketPrefix /var/run/httpd diff --git a/setup.cfg b/setup.cfg index f46e302c..3b7548b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,12 +24,14 @@ packages = [entry_points] console_scripts = - cloudkitty-api = cloudkitty.cli.api:main cloudkitty-dbsync = cloudkitty.cli.dbsync:main cloudkitty-processor = cloudkitty.cli.processor:main cloudkitty-storage-init = cloudkitty.cli.storage:main cloudkitty-writer = cloudkitty.cli.writer:main +wsgi_scripts = + cloudkitty-api = cloudkitty.api.app:build_wsgi_app + oslo.config.opts = cloudkitty.common.config = cloudkitty.common.config:list_opts