diff --git a/cmd/api.py b/cmd/api.py new file mode 100755 index 00000000..8ba3078a --- /dev/null +++ b/cmd/api.py @@ -0,0 +1,63 @@ +# Copyright 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. + +# Much of this module is based on the work of the Ironic team +# see http://git.openstack.org/cgit/openstack/ironic/tree/ironic/cmd/api.py + +import logging as std_logging +import sys + +from oslo_config import cfg +from oslo_log import log as logging + +from werkzeug import serving + +from tricircle.api import app +from tricircle.common import config + +from tricircle.i18n import _LI +from tricircle.i18n import _LW + + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + + +def main(): + config.init(sys.argv[1:]) + config.setup_logging() + application = app.setup_app() + + host = CONF.bind_host + port = CONF.bind_port + workers = CONF.api_workers + + if workers < 1: + LOG.warning(_LW("Wrong worker number, worker = %(workers)s"), workers) + workers = 1 + + 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) + + LOG.info(_LI("Configuration:")) + CONF.log_opt_values(LOG, std_logging.INFO) + + +if __name__ == '__main__': + main() diff --git a/devstack/local.conf.sample b/devstack/local.conf.sample index f02574bb..d4087f00 100644 --- a/devstack/local.conf.sample +++ b/devstack/local.conf.sample @@ -29,10 +29,11 @@ PUBLIC_NETWORK_GATEWAY=10.100.100.3 Q_ENABLE_TRICIRCLE=True -enable_plugin tricircle https://git.openstack.org/cgit/stackforge/tricircle/ experiment +enable_plugin tricircle https://git.openstack.org/stackforge/tricircle master # Tricircle Services enable_service t-svc +enable_service t-svc-api # Use Neutron instead of nova-network disable_service n-net @@ -44,4 +45,4 @@ disable_service c-api disable_service c-vol disable_service c-bak disable_service c-sch -disable_service cinder +disable_service cinder \ No newline at end of file diff --git a/devstack/plugin.sh b/devstack/plugin.sh index a5236223..ec3b04af 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -1,4 +1,45 @@ # Devstack extras script to install Tricircle + +# Test if any tricircle services are enabled +# is_tricircle_enabled +function is_tricircle_enabled { + [[ ,${ENABLED_SERVICES} =~ ,"t-svc-" ]] && return 0 + return 1 +} + +# create_tricircle_accounts() - Set up common required tricircle +# service accounts in keystone +# Project User Roles +# ------------------------------------------------------------------------- +# $SERVICE_TENANT_NAME tricircle service + +function create_tricircle_accounts { + if [[ "$ENABLED_SERVICES" =~ "t-svc-api" ]]; then + create_service_user "tricircle" + + if [[ "$KEYSTONE_CATALOG_BACKEND" = 'sql' ]]; then + local tricircle_cascade_service=$(get_or_create_service "tricircle" \ + "Cascading" "OpenStack Cascading Service") + get_or_create_endpoint $tricircle_cascade_service \ + "$REGION_NAME" \ + "$SERVICE_PROTOCOL://$TRICIRCLE_CASCADE_API_HOST:$TRICIRCLE_CASCADE_API_PORT/v1.0" \ + "$SERVICE_PROTOCOL://$TRICIRCLE_CASCADE_API_HOST:$TRICIRCLE_CASCADE_API_PORT/v1.0" \ + "$SERVICE_PROTOCOL://$TRICIRCLE_CASCADE_API_HOST:$TRICIRCLE_CASCADE_API_PORT/v1.0" + fi + fi +} + +# create_tricircle_cache_dir() - Set up cache dir for tricircle +function create_tricircle_cache_dir { + + # Delete existing dir + sudo rm -rf $TRICIRCLE_AUTH_CACHE_DIR + sudo mkdir -p $TRICIRCLE_AUTH_CACHE_DIR + sudo chown `whoami` $TRICIRCLE_AUTH_CACHE_DIR + +} + + function configure_tricircle_plugin { echo "Configuring Neutron for Tricircle" @@ -11,17 +52,43 @@ function configure_tricircle_plugin { fi if is_service_enabled t-svc ; then - echo "Configuring Neutron for Tricircle Cascade Service" - sudo install -d -o $STACK_USER -m 755 $TRICIRCLE_CONF_DIR - cp -p $TRICIRCLE_DIR/etc/cascade_service.conf $TRICIRCLE_CASCADE_CONF + echo "Configuring Neutron for Tricircle Cascade Service" + sudo install -d -o $STACK_USER -m 755 $TRICIRCLE_CONF_DIR + cp -p $TRICIRCLE_DIR/etc/cascade_service.conf $TRICIRCLE_CASCADE_CONF + + iniset $TRICIRCLE_CASCADE_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL + iniset $TRICIRCLE_CASCADE_CONF DEFAULT verbose True + setup_colorized_logging $TRICIRCLE_CASCADE_CONF DEFAULT + iniset $TRICIRCLE_CASCADE_CONF DEFAULT bind_host $TRICIRCLE_CASCADE_LISTEN_ADDRESS + iniset $TRICIRCLE_CASCADE_CONF DEFAULT use_syslog $SYSLOG + iniset_rpc_backend tricircle $TRICIRCLE_CASCADE_CONF + iniset $TRICIRCLE_CASCADE_CONF database connection `database_connection_url tricircle` + fi +} + +function configure_tricircle_cascade_api { + echo "Configuring tricircle cascade api service" + + if is_service_enabled t-svc-api ; then + cp -p $TRICIRCLE_DIR/etc/api.conf $TRICIRCLE_CASCADE_API_CONF + iniset $TRICIRCLE_CASCADE_API_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL + iniset $TRICIRCLE_CASCADE_API_CONF DEFAULT verbose True + iniset $TRICIRCLE_CASCADE_API_CONF DEFAULT use_syslog $SYSLOG + + setup_colorized_logging $TRICIRCLE_CASCADE_API_CONF DEFAULT + + if is_service_enabled keystone; then + + create_tricircle_cache_dir + + # Configure auth token middleware + configure_auth_token_middleware $TRICIRCLE_CASCADE_API_CONF tricircle \ + $TRICIRCLE_AUTH_CACHE_DIR + + else + iniset $TRICIRCLE_CASCADE_API_CONF DEFAULT auth_strategy noauth + fi - iniset $TRICIRCLE_CASCADE_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL - iniset $TRICIRCLE_CASCADE_CONF DEFAULT verbose true - setup_colorized_logging $TRICIRCLE_CASCADE_CONF DEFAULT - iniset $TRICIRCLE_CASCADE_CONF DEFAULT bind_host $TRICIRCLE_CASCADE_LISTEN_ADDRESS - iniset $TRICIRCLE_CASCADE_CONF DEFAULT use_syslog $SYSLOG - iniset_rpc_backend tricircle $TRICIRCLE_CASCADE_CONF - iniset $TRICIRCLE_CASCADE_CONF database connection `database_connection_url tricircle` fi } @@ -37,7 +104,10 @@ if [[ "$Q_ENABLE_TRICIRCLE" == "True" ]]; then elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then echo_summary "Configure Tricircle" + configure_tricircle_plugin + configure_tricircle_cascade_api + echo export PYTHONPATH=\$PYTHONPATH:$TRICIRCLE_DIR >> $RC_DIR/.localrc.auto recreate_database tricircle @@ -49,6 +119,13 @@ if [[ "$Q_ENABLE_TRICIRCLE" == "True" ]]; then if is_service_enabled t-svc; then run_process t-svc "python $TRICIRCLE_CASCADE_SERVICE --config-file $TRICIRCLE_CASCADE_CONF" fi + + if is_service_enabled t-svc-api; then + + create_tricircle_accounts + + run_process t-svc-api "python $TRICIRCLE_CASCADE_API --config-file $TRICIRCLE_CASCADE_API_CONF" + fi fi if [[ "$1" == "unstack" ]]; then @@ -56,5 +133,9 @@ if [[ "$Q_ENABLE_TRICIRCLE" == "True" ]]; then if is_service_enabled t-svc; then stop_process t-svc fi + + if is_service_enabled t-svc-api; then + stop_process t-svc-api + fi fi fi diff --git a/devstack/settings b/devstack/settings index 035d54ea..78b49a2d 100644 --- a/devstack/settings +++ b/devstack/settings @@ -11,4 +11,15 @@ TRICIRCLE_CASCADE_SERVICE=$TRICIRCLE_DIR/cmd/cascade_service.py TRICIRCLE_CASCADE_CONF=$TRICIRCLE_CONF_DIR/cascade_service.conf TRICIRCLE_CASCADE_LISTEN_ADDRESS=${TRICIRCLE_CASCADE_LISTEN_ADDRESS:-0.0.0.0} +# cascade rest api +TRICIRCLE_CASCADE_API=$TRICIRCLE_DIR/cmd/api.py +TRICIRCLE_CASCADE_API_CONF=$TRICIRCLE_CONF_DIR/api.conf + +TRICIRCLE_CASCADE_API_LISTEN_ADDRESS=${TRICIRCLE_CASCADE_API_LISTEN_ADDRESS:-0.0.0.0} +TRICIRCLE_CASCADE_API_HOST=${TRICIRCLE_CASCADE_API_HOST:-$SERVICE_HOST} +TRICIRCLE_CASCADE_API_PORT=${TRICIRCLE_CASCADE_API_PORT:-19999} +TRICIRCLE_CASCADE_API_PROTOCOL=${TRICIRCLE_CASCADE_API_PROTOCOL:-$SERVICE_PROTOCOL} + +TRICIRCLE_AUTH_CACHE_DIR=${TRICIRCLE_AUTH_CACHE_DIR:-/var/cache/tricircle} + export PYTHONPATH=$PYTHONPATH:$TRICIRCLE_DIR diff --git a/etc/api.conf b/etc/api.conf new file mode 100755 index 00000000..27d85fa8 --- /dev/null +++ b/etc/api.conf @@ -0,0 +1,373 @@ +[DEFAULT] +# Print more verbose output (set logging level to INFO instead of default WARNING level). +# verbose = True + +# Print debugging output (set logging level to DEBUG instead of default WARNING level). +# debug = False + +# Where to store Tricircle state files. This directory must be writable by the +# user executing the agent. +# state_path = /var/lib/tricircle + +# log_format = %(asctime)s %(levelname)8s [%(name)s] %(message)s +# log_date_format = %Y-%m-%d %H:%M:%S + +# use_syslog -> syslog +# log_file and log_dir -> log_dir/log_file +# (not log_file) and log_dir -> log_dir/{binary_name}.log +# use_stderr -> stderr +# (not user_stderr) and (not log_file) -> stdout +# publish_errors -> notification system + +# use_syslog = False +# syslog_log_facility = LOG_USER + +# use_stderr = True +# log_file = +# log_dir = + +# publish_errors = False + +# Address to bind the API server to +# bind_host = 127.0.0.1 + +# Port the bind the API server to +# bind_port = 19999 + +# Paste configuration file +# api_paste_config = api-paste.ini + +# (StrOpt) Hostname to be used by the tricircle server, agents and services +# running on this machine. All the agents and services running on this machine +# must use the same host value. +# The default value is hostname of the machine. +# +# host = + +# admin_tenant_name = %SERVICE_TENANT_NAME% +# admin_user = %SERVICE_USER% +# admin_password = %SERVICE_PASSWORD% + +# Enable or disable bulk create/update/delete operations +# allow_bulk = True +# Enable or disable pagination +# allow_pagination = False +# Enable or disable sorting +# allow_sorting = False + +# Default maximum number of items returned in a single response, +# value == infinite and value < 0 means no max limit, and value must +# be greater than 0. If the number of items requested is greater than +# pagination_max_limit, server will just return pagination_max_limit +# of number of items. +# pagination_max_limit = -1 + +# =========== WSGI parameters related to the API server ============== +# Number of separate worker processes to spawn. The default, 0, runs the +# worker thread in the current process. Greater than 0 launches that number of +# child processes as workers. The parent process manages them. +# api_workers = 3 + +# Number of separate RPC worker processes to spawn. The default, 0, runs the +# worker thread in the current process. Greater than 0 launches that number of +# child processes as RPC workers. The parent process manages them. +# This feature is experimental until issues are addressed and testing has been +# enabled for various plugins for compatibility. +# rpc_workers = 0 + +# Timeout for client connections socket operations. If an +# incoming connection is idle for this number of seconds it +# will be closed. A value of '0' means wait forever. (integer +# value) +# client_socket_timeout = 900 + +# wsgi keepalive option. Determines if connections are allowed to be held open +# by clients after a request is fulfilled. A value of False will ensure that +# the socket connection will be explicitly closed once a response has been +# sent to the client. +# wsgi_keep_alive = True + +# Sets the value of TCP_KEEPIDLE in seconds to use for each server socket when +# starting API server. Not supported on OS X. +# tcp_keepidle = 600 + +# Number of seconds to keep retrying to listen +# retry_until_window = 30 + +# Number of backlog requests to configure the socket with. +# backlog = 4096 + +# Max header line to accommodate large tokens +# max_header_line = 16384 + +# Enable SSL on the API server +# use_ssl = False + +# Certificate file to use when starting API server securely +# ssl_cert_file = /path/to/certfile + +# Private key file to use when starting API server securely +# ssl_key_file = /path/to/keyfile + +# CA certificate file to use when starting API server securely to +# verify connecting clients. This is an optional parameter only required if +# API clients need to authenticate to the API server using SSL certificates +# signed by a trusted CA +# ssl_ca_file = /path/to/cafile +# ======== end of WSGI parameters related to the API server ========== + +# The strategy to be used for auth. +# Supported values are 'keystone'(default), 'noauth'. +# auth_strategy = keystone + +[filter:authtoken] +# paste.filter_factory = keystonemiddleware.auth_token:filter_factory + +[keystone_authtoken] +# auth_uri = http://162.3.111.227:35357/v3 +# identity_uri = http://162.3.111.227:35357 +# admin_tenant_name = service +# admin_user = tricircle +# admin_password = 1234 +# auth_version = 3 + +[database] +# This line MUST be changed to actually run the plugin. +# Example: +# connection = mysql://root:pass@127.0.0.1:3306/neutron +# Replace 127.0.0.1 above with the IP address of the database used by the +# main neutron server. (Leave it as is if the database runs on this host.) +# connection = sqlite:// +# NOTE: In deployment the [database] section and its connection attribute may +# be set in the corresponding core plugin '.ini' file. However, it is suggested +# to put the [database] section and its connection attribute in this +# configuration file. + +# Database engine for which script will be generated when using offline +# migration +# engine = + +# The SQLAlchemy connection string used to connect to the slave database +# slave_connection = + +# Database reconnection retry times - in event connectivity is lost +# set to -1 implies an infinite retry count +# max_retries = 10 + +# Database reconnection interval in seconds - if the initial connection to the +# database fails +# retry_interval = 10 + +# Minimum number of SQL connections to keep open in a pool +# min_pool_size = 1 + +# Maximum number of SQL connections to keep open in a pool +# max_pool_size = 10 + +# Timeout in seconds before idle sql connections are reaped +# idle_timeout = 3600 + +# If set, use this value for max_overflow with sqlalchemy +# max_overflow = 20 + +# Verbosity of SQL debugging information. 0=None, 100=Everything +# connection_debug = 0 + +# Add python stack traces to SQL as comment strings +# connection_trace = False + +# If set, use this value for pool_timeout with sqlalchemy +# pool_timeout = 10 + +[oslo_concurrency] + +# Directory to use for lock files. For security, the specified directory should +# only be writable by the user running the processes that need locking. +# Defaults to environment variable OSLO_LOCK_PATH. If external locks are used, +# a lock path must be set. +lock_path = $state_path/lock + +# Enables or disables inter-process locks. +# disable_process_locking = False + +[oslo_policy] + +# The JSON file that defines policies. +# policy_file = policy.json + +# Default rule. Enforced when a requested rule is not found. +# policy_default_rule = default + +# Directories where policy configuration files are stored. +# They can be relative to any directory in the search path defined by the +# config_dir option, or absolute paths. The file defined by policy_file +# must exist for these directories to be searched. Missing or empty +# directories are ignored. +# policy_dirs = policy.d + +[oslo_messaging_amqp] + +# +# From oslo.messaging +# + +# Address prefix used when sending to a specific server (string value) +# server_request_prefix = exclusive + +# Address prefix used when broadcasting to all servers (string value) +# broadcast_prefix = broadcast + +# Address prefix when sending to any server in group (string value) +# group_request_prefix = unicast + +# Name for the AMQP container (string value) +# container_name = + +# Timeout for inactive connections (in seconds) (integer value) +# idle_timeout = 0 + +# Debug: dump AMQP frames to stdout (boolean value) +# trace = false + +# CA certificate PEM file for verifing server certificate (string value) +# ssl_ca_file = + +# Identifying certificate PEM file to present to clients (string value) +# ssl_cert_file = + +# Private key PEM file used to sign cert_file certificate (string value) +# ssl_key_file = + +# Password for decrypting ssl_key_file (if encrypted) (string value) +# ssl_key_password = + +# Accept clients using either SSL or plain TCP (boolean value) +# allow_insecure_clients = false + + +[oslo_messaging_qpid] + +# +# From oslo.messaging +# + +# Use durable queues in AMQP. (boolean value) +# amqp_durable_queues = false + +# Auto-delete queues in AMQP. (boolean value) +# amqp_auto_delete = false + +# Size of RPC connection pool. (integer value) +# rpc_conn_pool_size = 30 + +# Qpid broker hostname. (string value) +# qpid_hostname = localhost + +# Qpid broker port. (integer value) +# qpid_port = 5672 + +# Qpid HA cluster host:port pairs. (list value) +# qpid_hosts = $qpid_hostname:$qpid_port + +# Username for Qpid connection. (string value) +# qpid_username = + +# Password for Qpid connection. (string value) +# qpid_password = + +# Space separated list of SASL mechanisms to use for auth. (string value) +# qpid_sasl_mechanisms = + +# Seconds between connection keepalive heartbeats. (integer value) +# qpid_heartbeat = 60 + +# Transport to use, either 'tcp' or 'ssl'. (string value) +# qpid_protocol = tcp + +# Whether to disable the Nagle algorithm. (boolean value) +# qpid_tcp_nodelay = true + +# The number of prefetched messages held by receiver. (integer value) +# qpid_receiver_capacity = 1 + +# The qpid topology version to use. Version 1 is what was originally used by +# impl_qpid. Version 2 includes some backwards-incompatible changes that allow +# broker federation to work. Users should update to version 2 when they are +# able to take everything down, as it requires a clean break. (integer value) +# qpid_topology_version = 1 + + +[oslo_messaging_rabbit] + +# +# From oslo.messaging +# + +# Use durable queues in AMQP. (boolean value) +# amqp_durable_queues = false + +# Auto-delete queues in AMQP. (boolean value) +# amqp_auto_delete = false + +# Size of RPC connection pool. (integer value) +# rpc_conn_pool_size = 30 + +# SSL version to use (valid only if SSL enabled). Valid values are TLSv1 and +# SSLv23. SSLv2, SSLv3, TLSv1_1, and TLSv1_2 may be available on some +# distributions. (string value) +# kombu_ssl_version = + +# SSL key file (valid only if SSL enabled). (string value) +# kombu_ssl_keyfile = + +# SSL cert file (valid only if SSL enabled). (string value) +# kombu_ssl_certfile = + +# SSL certification authority file (valid only if SSL enabled). (string value) +# kombu_ssl_ca_certs = + +# How long to wait before reconnecting in response to an AMQP consumer cancel +# notification. (floating point value) +# kombu_reconnect_delay = 1.0 + +# The RabbitMQ broker address where a single node is used. (string value) +# rabbit_host = localhost + +# The RabbitMQ broker port where a single node is used. (integer value) +# rabbit_port = 5672 + +# RabbitMQ HA cluster host:port pairs. (list value) +# rabbit_hosts = $rabbit_host:$rabbit_port + +# Connect over SSL for RabbitMQ. (boolean value) +# rabbit_use_ssl = false + +# The RabbitMQ userid. (string value) +# rabbit_userid = guest + +# The RabbitMQ password. (string value) +# rabbit_password = guest + +# The RabbitMQ login method. (string value) +# rabbit_login_method = AMQPLAIN + +# The RabbitMQ virtual host. (string value) +# rabbit_virtual_host = / + +# How frequently to retry connecting with RabbitMQ. (integer value) +# rabbit_retry_interval = 1 + +# How long to backoff for between retries when connecting to RabbitMQ. (integer +# value) +# rabbit_retry_backoff = 2 + +# Maximum number of RabbitMQ connection retries. Default is 0 (infinite retry +# count). (integer value) +# rabbit_max_retries = 0 + +# Use HA queues in RabbitMQ (x-ha-policy: all). If you change this option, you +# must wipe the RabbitMQ database. (boolean value) +# rabbit_ha_queues = false + +# Deprecated, use rpc_backend=kombu+memory or rpc_backend=fake (boolean value) +# fake_rabbit = false diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 00000000..be9d6890 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,38 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +pbr<2.0,>=1.3 + +Paste +PasteDeploy>=1.5.0 +Routes!=2.0,!=2.1,>=1.12.3;python_version=='2.7' +Routes!=2.0,>=1.12.3;python_version!='2.7' +debtcollector>=0.3.0 # Apache-2.0 +eventlet>=0.17.4 +pecan>=0.9.0 +greenlet>=0.3.2 +httplib2>=0.7.5 +requests>=2.5.2 +Jinja2>=2.6 # BSD License (3 clause) +keystonemiddleware>=2.0.0 +netaddr>=0.7.12 +retrying!=1.3.0,>=1.2.3 # Apache-2.0 +SQLAlchemy<1.1.0,>=0.9.7 +WebOb>=1.2.3 +python-keystoneclient>=1.6.0 +alembic>=0.7.2 +six>=1.9.0 +stevedore>=1.5.0 # Apache-2.0 +oslo.concurrency>=2.1.0 # Apache-2.0 +oslo.config>=1.11.0 # Apache-2.0 +oslo.context>=0.2.0 # Apache-2.0 +oslo.db>=1.12.0 # Apache-2.0 +oslo.i18n>=1.5.0 # Apache-2.0 +oslo.log>=1.6.0 # Apache-2.0 +oslo.messaging>=1.16.0 # Apache-2.0 +oslo.middleware>=2.4.0 # Apache-2.0 +oslo.policy>=0.5.0 # Apache-2.0 +oslo.rootwrap>=2.0.0 # Apache-2.0 +oslo.serialization>=1.4.0 # Apache-2.0 +oslo.service>=0.1.0 # Apache-2.0 +oslo.utils>=1.9.0 # Apache-2.0 diff --git a/tricircle/api/__init__.py b/tricircle/api/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/tricircle/api/app.py b/tricircle/api/app.py new file mode 100755 index 00000000..6cabfc59 --- /dev/null +++ b/tricircle/api/app.py @@ -0,0 +1,66 @@ +# Copyright (c) 2015 Huawei, Tech. 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 keystonemiddleware import auth_token +from oslo_config import cfg +from oslo_middleware import request_id +import pecan + +from tricircle.common import exceptions as t_exc + + +def setup_app(*args, **kwargs): + config = { + 'server': { + 'port': cfg.CONF.bind_port, + 'host': cfg.CONF.bind_host + }, + 'app': { + 'root': 'tricircle.api.controllers.root.RootController', + 'modules': ['tricircle.api'], + 'errors': { + 400: '/error', + '__force_dict__': True + } + } + } + pecan_config = pecan.configuration.conf_from_dict(config) + + # app_hooks = [], hook collection will be put here later + + app = pecan.make_app( + pecan_config.app.root, + debug=False, + wrap_app=_wrap_app, + force_canonical=False, + hooks=[], + guess_content_type_from_ext=True + ) + + return app + + +def _wrap_app(app): + app = request_id.RequestId(app) + + if cfg.CONF.auth_strategy == 'noauth': + pass + elif cfg.CONF.auth_strategy == 'keystone': + app = auth_token.AuthProtocol(app, {}) + else: + raise t_exc.InvalidConfigurationOption( + opt_name='auth_strategy', opt_value=cfg.CONF.auth_strategy) + + return app diff --git a/tricircle/api/controllers/__init__.py b/tricircle/api/controllers/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/tricircle/api/controllers/root.py b/tricircle/api/controllers/root.py new file mode 100755 index 00000000..25b23702 --- /dev/null +++ b/tricircle/api/controllers/root.py @@ -0,0 +1,102 @@ +# Copyright (c) 2015 Huawei Tech. 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 pecan +from pecan import rest + + +def expose(*args, **kwargs): + kwargs.setdefault('content_type', 'application/json') + kwargs.setdefault('template', 'json') + return pecan.expose(*args, **kwargs) + + +def when(index, *args, **kwargs): + kwargs.setdefault('content_type', 'application/json') + kwargs.setdefault('template', 'json') + return index.when(*args, **kwargs) + + +class RootController(object): + + @expose() + def _lookup(self, version, *remainder): + if version == 'v1.0': + return V1Controller(), remainder + + @pecan.expose('json') + def index(self): + return { + "versions": [ + { + "status": "CURRENT", + "links": [ + { + "rel": "self", + "href": pecan.request.application_url + "/v1.0/" + } + ], + "id": "v1.0", + "updated": "2015-09-09" + } + ] + } + + +class V1Controller(object): + + def __init__(self): + + self.sub_controllers = { + "sites": SitesController() + } + + for name, ctrl in self.sub_controllers.items(): + setattr(self, name, ctrl) + + @pecan.expose('json') + def index(self): + return { + "version": "1.0", + "links": [ + {"rel": "self", + "href": pecan.request.application_url + "/v1.0"} + ] + [ + {"rel": name, + "href": pecan.request.application_url + "/v1.0/" + name} + for name in sorted(self.sub_controllers) + ] + } + + +class SitesController(rest.RestController): + + @expose(generic=True) + def index(self): + if pecan.request.method != 'GET': + pecan.abort(405) + return {'message': 'GET'} + + @when(index, method='PUT') + def put(self, **kw): + return {'message': 'PUT'} + + @when(index, method='POST') + def post(self, **kw): + return {'message': 'POST'} + + @when(index, method='DELETE') + def delete(self): + return {'message': 'DELETE'} diff --git a/tricircle/common/config.py b/tricircle/common/config.py new file mode 100755 index 00000000..fb70b182 --- /dev/null +++ b/tricircle/common/config.py @@ -0,0 +1,120 @@ +# Copyright 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. + +""" +Routines for configuring tricircle, largely copy from Neutron +""" + +import os +import sys + +from oslo_config import cfg +from oslo_log import log as logging +from paste import deploy + +from tricircle.i18n import _ +from tricircle.i18n import _LI + +# from tricircle import policy +from tricircle import version + + +LOG = logging.getLogger(__name__) + +common_opts = [ + cfg.StrOpt('bind_host', default='0.0.0.0', + help=_("The host IP to bind to")), + cfg.IntOpt('bind_port', default=19999, + help=_("The port to bind to")), + cfg.IntOpt('api_workers', default=1, + help=_("number of api workers")), + cfg.StrOpt('api_paste_config', default="api-paste.ini", + help=_("The API paste config file to use")), + cfg.StrOpt('api_extensions_path', default="", + help=_("The path for API extensions")), + cfg.StrOpt('auth_strategy', default='keystone', + help=_("The type of authentication to use")), + cfg.BoolOpt('allow_bulk', default=True, + help=_("Allow the usage of the bulk API")), + cfg.BoolOpt('allow_pagination', default=False, + help=_("Allow the usage of the pagination")), + cfg.BoolOpt('allow_sorting', default=False, + help=_("Allow the usage of the sorting")), + cfg.StrOpt('pagination_max_limit', default="-1", + help=_("The maximum number of items returned in a single " + "response, value was 'infinite' or negative integer " + "means no limit")), +] + + +def init(args, **kwargs): + # Register the configuration options + cfg.CONF.register_opts(common_opts) + + # ks_session.Session.register_conf_options(cfg.CONF) + # auth.register_conf_options(cfg.CONF) + logging.register_options(cfg.CONF) + + cfg.CONF(args=args, project='tricircle', + version='%%(prog)s %s' % version.version_info.release_string(), + **kwargs) + + +def setup_logging(): + """Sets up the logging options for a log with supplied name.""" + product_name = "tricircle" + logging.setup(cfg.CONF, product_name) + LOG.info(_LI("Logging enabled!")) + LOG.info(_LI("%(prog)s version %(version)s"), + {'prog': sys.argv[0], + 'version': version.version_info.release_string()}) + LOG.debug("command line: %s", " ".join(sys.argv)) + + +def reset_service(): + # Reset worker in case SIGHUP is called. + # Note that this is called only in case a service is running in + # daemon mode. + setup_logging() + + # (TODO) enforce policy later + # policy.refresh() + + +def load_paste_app(app_name): + """Builds and returns a WSGI app from a paste config file. + + :param app_name: Name of the application to load + :raises ConfigFilesNotFoundError when config file cannot be located + :raises RuntimeError when application cannot be loaded from config file + """ + + config_path = cfg.CONF.find_file(cfg.CONF.api_paste_config) + if not config_path: + raise cfg.ConfigFilesNotFoundError( + config_files=[cfg.CONF.api_paste_config]) + config_path = os.path.abspath(config_path) + LOG.info(_LI("Config paste file: %s"), config_path) + + try: + app = deploy.loadapp("config:%s" % config_path, name=app_name) + except (LookupError, ImportError): + msg = (_("Unable to load %(app_name)s from " + "configuration file %(config_path)s.") % + {'app_name': app_name, + 'config_path': config_path}) + LOG.exception(msg) + raise RuntimeError(msg) + return app diff --git a/tricircle/common/exceptions.py b/tricircle/common/exceptions.py new file mode 100755 index 00000000..57f41ad6 --- /dev/null +++ b/tricircle/common/exceptions.py @@ -0,0 +1,83 @@ +# Copyright 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. + +""" +Tricircle base exception handling. +""" + +from oslo_utils import excutils +import six +from tricircle.i18n import _ + + +class TricircleException(Exception): + """Base Tricircle Exception. + + To correctly use this class, inherit from it and define + a 'message' property. That message will get printf'd + with the keyword arguments provided to the constructor. + """ + message = _("An unknown exception occurred.") + + def __init__(self, **kwargs): + try: + super(TricircleException, self).__init__(self.message % kwargs) + self.msg = self.message % kwargs + except Exception: + with excutils.save_and_reraise_exception() as ctxt: + if not self.use_fatal_exceptions(): + ctxt.reraise = False + # at least get the core message out if something happened + super(TricircleException, self).__init__(self.message) + + if six.PY2: + def __unicode__(self): + return unicode(self.msg) + + def use_fatal_exceptions(self): + return False + + +class BadRequest(TricircleException): + message = _('Bad %(resource)s request: %(msg)s') + + +class NotFound(TricircleException): + pass + + +class Conflict(TricircleException): + pass + + +class NotAuthorized(TricircleException): + message = _("Not authorized.") + + +class ServiceUnavailable(TricircleException): + message = _("The service is unavailable") + + +class AdminRequired(NotAuthorized): + message = _("User does not have admin privileges: %(reason)s") + + +class InUse(TricircleException): + message = _("The resource is inuse") + + +class InvalidConfigurationOption(TricircleException): + message = _("An invalid value was provided for %(opt_name)s: " + "%(opt_value)s") diff --git a/tricircle/i18n.py b/tricircle/i18n.py new file mode 100755 index 00000000..bb53d287 --- /dev/null +++ b/tricircle/i18n.py @@ -0,0 +1,30 @@ +# 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 + +_translators = oslo_i18n.TranslatorFactory(domain='tricircle') + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical diff --git a/tricircle/version.py b/tricircle/version.py new file mode 100755 index 00000000..18a1d825 --- /dev/null +++ b/tricircle/version.py @@ -0,0 +1,17 @@ +# Copyright 2011 OpenStack Foundation +# +# 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('tricircle')