Enable serving keystone from apache mod_wsgi

Serving keystone from a wsgi container is recommended for production
setups. SSL is enabled by default.

See the following URLs for explanations:
    http://adam.younglogic.com/2012/03/keystone-should-move-to-apache-httpd/
    https://etherpad.openstack.org/havana-keystone-performance

Documentation in manifests/wsgi/apache.pp

Apache can be configured as a drop in replacement for keystone (using
    ports 5000 & 35357) or with paths using the standard SSL port. See
examples in examples/apache_*.pp

- Also change some 'real_' prefix into '_real' suffix to respect the
coding guide.
- Added the '--insecure' option to keystone client in the provider to
allow using self-signed certificates.
- Fixed parsing the ssl/enable value in the provider.

There is no integer verification done in the manifests
and to get around a bug in rspec, which has been fixed
in https://github.com/rodjek/rspec-puppet/pull/107,
certain parameters that should be integer are treated as
strings

files/httpd/keystone.py updated with lastest from keystone git repo

Change-Id: Ide8c090d105c1ea75a14939f5e8ddb7d24ca3f1c
This commit is contained in:
François Charlier 2013-05-02 18:03:12 +02:00 committed by Matthew Black
parent 85e963b54b
commit e35a6dc6ee
10 changed files with 615 additions and 7 deletions

View File

@ -1,5 +1,7 @@
fixtures:
repositories:
'apache': 'git://github.com/puppetlabs/puppetlabs-apache.git'
'concat': 'git://github.com/puppetlabs/puppetlabs-concat.git'
'apt': 'git://github.com/puppetlabs/puppetlabs-apt.git'
'mysql':
repo: 'git://github.com/puppetlabs/puppetlabs-mysql.git'

View File

@ -7,6 +7,7 @@ summary 'Puppet Labs Keystone Module'
description 'Puppet module to install and configure the Openstack identity service'
project_page 'https://launchpad.net/puppet-openstack'
dependency 'puppetlabs/apache', '>=0.9.0 <1.0.0'
dependency 'puppetlabs/inifile', '>=1.0.0 <2.0.0'
dependency 'puppetlabs/mysql', '>=0.6.1 <1.0.0'
dependency 'puppetlabs/stdlib', '>= 2.5.0'

52
examples/apache_dropin.pp Normal file
View File

@ -0,0 +1,52 @@
# Example using apache to serve keystone
#
# To be sure everything is working, run:
# $ export OS_USERNAME=admin
# $ export OS_PASSWORD=ChangeMe
# $ export OS_TENANT_NAME=openstack
# $ export OS_AUTH_URL=http://keystone.local/keystone/main/v2.0
# $ keystone catalog
# Service: identity
# +-------------+----------------------------------------------+
# | Property | Value |
# +-------------+----------------------------------------------+
# | adminURL | http://keystone.local:80/keystone/admin/v2.0 |
# | id | 4f0f55f6789d4c73a53c51f991559b72 |
# | internalURL | http://keystone.local:80/keystone/main/v2.0 |
# | publicURL | http://keystone.local:80/keystone/main/v2.0 |
# | region | RegionOne |
# +-------------+----------------------------------------------+
#
Exec { logoutput => 'on_failure' }
class { 'mysql::server': }
class { 'keystone::db::mysql':
password => 'keystone',
}
class { 'keystone':
verbose => true,
debug => true,
sql_connection => 'mysql://keystone_admin:keystone@127.0.0.1/keystone',
catalog_type => 'sql',
admin_token => 'admin_token',
enabled => false,
}
class { 'keystone::roles::admin':
email => 'test@puppetlabs.com',
password => 'ChangeMe',
}
class { 'keystone::endpoint':
public_address => $::fqdn,
admin_address => $::fqdn,
internal_address => $::fqdn,
public_protocol => 'https',
admin_protocol => 'https'
}
keystone_config { 'ssl/enable': value => true }
include apache
class { 'keystone::wsgi::apache':
ssl => true
}

View File

@ -0,0 +1,59 @@
# Example using apache to serve keystone
#
# To be sure everything is working, run:
# $ export OS_USERNAME=admin
# $ export OS_PASSWORD=ChangeMe
# $ export OS_TENANT_NAME=openstack
# $ export OS_AUTH_URL=http://keystone.local/keystone/main/v2.0
# $ keystone catalog
# Service: identity
# +-------------+----------------------------------------------+
# | Property | Value |
# +-------------+----------------------------------------------+
# | adminURL | http://keystone.local:80/keystone/admin/v2.0 |
# | id | 4f0f55f6789d4c73a53c51f991559b72 |
# | internalURL | http://keystone.local:80/keystone/main/v2.0 |
# | publicURL | http://keystone.local:80/keystone/main/v2.0 |
# | region | RegionOne |
# +-------------+----------------------------------------------+
#
Exec { logoutput => 'on_failure' }
class { 'mysql::server': }
class { 'keystone::db::mysql':
password => 'keystone',
}
class { 'keystone':
verbose => true,
debug => true,
sql_connection => 'mysql://keystone_admin:keystone@127.0.0.1/keystone',
catalog_type => 'sql',
admin_token => 'admin_token',
enabled => true,
}
class { 'keystone::roles::admin':
email => 'test@puppetlabs.com',
password => 'ChangeMe',
}
class { 'keystone::endpoint':
public_address => $::fqdn,
admin_address => $::fqdn,
internal_address => $::fqdn,
public_port => 443,
admin_port => 443,
public_protocol => 'https',
admin_protocol => 'https'
}
# keystone_config { 'ssl/enable': value => true }
keystone_config { 'ssl/enable': ensure => absent }
include apache
class { 'keystone::wsgi::apache':
ssl => true,
public_port => 443,
admin_port => 443,
public_path => '/main/',
admin_path => '/admin/'
}

54
files/httpd/keystone.py Normal file
View File

@ -0,0 +1,54 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
#
# This file was copied from https://github.com/openstack/keystone/raw/c3b92295b718a41c3136876eb39297081015a97c/httpd/keystone.py
# It's only required for platforms on which it is not packaged yet.
# It should be removed when available everywhere in a package.
#
import logging
import os
from paste import deploy
from keystone.openstack.common import gettextutils
# NOTE(blk-u):
# gettextutils.install() must run to set _ before importing any modules that
# contain static translated strings.
gettextutils.install('keystone')
from keystone.common import environment
from keystone import config
from keystone.openstack.common import log
CONF = config.CONF
CONF(project='keystone')
config.setup_logging(CONF)
environment.use_stdlib()
name = os.path.basename(__file__)
if CONF.debug:
CONF.log_opt_values(log.getLogger(CONF.prog), logging.DEBUG)
# NOTE(ldbragst): 'application' is required in this context by WSGI spec.
# The following is a reference to Python Paste Deploy documentation
# http://pythonpaste.org/deploy/
application = deploy.loadapp('config:%s' % config.find_paste_config(),
name=name)

View File

@ -6,21 +6,26 @@ class keystone::params {
case $::osfamily {
'Debian': {
$package_name = 'keystone'
$service_name = 'keystone'
$package_name = 'keystone'
$service_name = 'keystone'
$keystone_wsgi_script_path = '/usr/lib/cgi-bin/keystone'
case $::operatingsystem {
'Debian': {
$service_provider = undef
$service_provider = undef
$keystone_wsgi_script_source = '/usr/share/keystone/wsgi.py'
}
default: {
$service_provider = 'upstart'
$service_provider = 'upstart'
$keystone_wsgi_script_source = 'puppet:///modules/keystone/httpd/keystone.py'
}
}
}
'RedHat': {
$package_name = 'openstack-keystone'
$service_name = 'openstack-keystone'
$service_provider = undef
$package_name = 'openstack-keystone'
$service_name = 'openstack-keystone'
$keystone_wsgi_script_path = '/var/www/cgi-bin/keystone'
$service_provider = undef
$keystone_wsgi_script_source = 'puppet:///modules/keystone/httpd/keystone.py'
}
}
}

204
manifests/wsgi/apache.pp Normal file
View File

@ -0,0 +1,204 @@
#
# Class to serve keystone with apache mod_wsgi in place of keystone service
#
# Serving keystone from apache is the recommended way to go for production
# systems as the current keystone implementation is not multi-processor aware,
# thus limiting the performance for concurrent accesses.
#
# See the following URIs for reference:
# https://etherpad.openstack.org/havana-keystone-performance
# http://adam.younglogic.com/2012/03/keystone-should-move-to-apache-httpd/
#
# When using this class you should disable your keystone service.
#
# == Parameters
#
# [*servername*]
# The servername for the virtualhost.
# Optional. Defaults to $::fqdn
#
# [*public_port*]
# The public port.
# Optional. Defaults to 5000
#
# [*admin_port*]
# The admin port.
# Optional. Defaults to 35357
#
# [*public_path*]
# The prefix for the public endpoint.
# Optional. Defaults to '/'
#
# [*admin_path*]
# The prefix for the admin endpoint.
# Optional. Defaults to '/'
#
# [*ssl*]
# Use ssl ? (boolean)
# Optional. Defaults to true
#
# [*workers*]
# Number of WSGI workers to spawn.
# Optional. Defaults to 1
#
# [*ssl_cert*]
# [*ssl_key*]
# [*ssl_chain*]
# [*ssl_ca*]
# [*ssl_crl_path*]
# [*ssl_crl*]
# [*ssl_certs_dir*]
# apache::vhost ssl parameters.
# Optional. Default to apache::vhost 'ssl_*' defaults.
#
# == Dependencies
#
# requires Class['apache'] & Class['keystone']
#
# == Examples
#
# include apache
#
# class { 'keystone::wsgi::apache': }
#
# == Note about ports & paths
#
# When using same port for both endpoints (443 anyone ?), you *MUST* use two
# different public_path & admin_path !
#
# == Authors
#
# François Charlier <francois.charlier@enovance.com>
#
# == Copyright
#
# Copyright 2013 eNovance <licensing@enovance.com>
#
class keystone::wsgi::apache (
$servername = $::fqdn,
$public_port = 5000,
$admin_port = 35357,
$public_path = '/',
$admin_path = '/',
$ssl = true,
$workers = 1,
$ssl_cert = undef,
$ssl_key = undef,
$ssl_chain = undef,
$ssl_ca = undef,
$ssl_crl_path = undef,
$ssl_crl = undef,
$ssl_certs_dir = undef
) {
include keystone::params
include ::apache
include ::apache::mod::wsgi
include keystone::db::sync
Exec <| title == 'keystone-manage pki_setup' |> ~> Service['httpd']
Exec <| title == 'keystone-manage db_sync' |> ~> Service['httpd']
Package['keystone'] ~> Service['httpd']
Keystone_config <| |> ~> Service['httpd']
Service['httpd'] -> Keystone_endpoint <| |>
Service['httpd'] -> Keystone_role <| |>
Service['httpd'] -> Keystone_service <| |>
Service['httpd'] -> Keystone_tenant <| |>
Service['httpd'] -> Keystone_user <| |>
Service['httpd'] -> Keystone_user_role <| |>
## Sanitize parameters
# Ensure there's no trailing '/' except if this is also the only character
$public_path_real = regsubst($public_path, '(^/.*)/$', '\1')
# Ensure there's no trailing '/' except if this is also the only character
$admin_path_real = regsubst($admin_path, '(^/.*)/$', '\1')
if $public_port == $admin_port and $public_path_real == $admin_path_real {
fail('When using the same port for public & private endpoints, public_path and admin_path should be different.')
}
file { $::keystone::params::keystone_wsgi_script_path:
ensure => directory,
owner => 'keystone',
group => 'keystone',
require => Package['httpd'],
}
file { 'keystone_wsgi_admin':
ensure => file,
path => "${::keystone::params::keystone_wsgi_script_path}/admin",
source => $::keystone::params::keystone_wsgi_script_source,
owner => 'keystone',
group => 'keystone',
mode => '0644',
require => File[$::keystone::params::keystone_wsgi_script_path],
}
file { 'keystone_wsgi_main':
ensure => file,
path => "${::keystone::params::keystone_wsgi_script_path}/main",
source => $::keystone::params::keystone_wsgi_script_source,
owner => 'keystone',
group => 'keystone',
mode => '0644',
require => File[$::keystone::params::keystone_wsgi_script_path],
}
$wsgi_daemon_process_options = {
user => 'keystone',
group => 'keystone',
processes => $workers,
threads => '1'
}
$wsgi_script_aliases_main = hash([$public_path_real,"${::keystone::params::keystone_wsgi_script_path}/main"])
$wsgi_script_aliases_admin = hash([$admin_path_real, "${::keystone::params::keystone_wsgi_script_path}/admin"])
if $public_port == $admin_port {
$wsgi_script_aliases_main_real = merge($wsgi_script_aliases_main, $wsgi_script_aliases_admin)
} else {
$wsgi_script_aliases_main_real = $wsgi_script_aliases_main
}
apache::vhost { 'keystone_wsgi_main':
servername => $servername,
port => $public_port,
docroot => $::keystone::params::keystone_wsgi_script_path,
docroot_owner => 'keystone',
docroot_group => 'keystone',
ssl => $ssl,
ssl_cert => $ssl_cert,
ssl_key => $ssl_key,
ssl_chain => $ssl_chain,
ssl_ca => $ssl_ca,
ssl_crl_path => $ssl_crl_path,
ssl_crl => $ssl_crl,
ssl_certs_dir => $ssl_certs_dir,
wsgi_daemon_process => 'keystone',
wsgi_daemon_process_options => $wsgi_daemon_process_options,
wsgi_process_group => 'keystone',
wsgi_script_aliases => $wsgi_script_aliases_main_real,
require => [Class['apache::mod::wsgi'], File['keystone_wsgi_main']],
}
if $public_port != $admin_port {
apache::vhost { 'keystone_wsgi_admin':
servername => $servername,
port => $admin_port,
docroot => $::keystone::params::keystone_wsgi_script_path,
docroot_owner => 'keystone',
docroot_group => 'keystone',
ssl => $ssl,
ssl_cert => $ssl_cert,
ssl_key => $ssl_key,
ssl_chain => $ssl_chain,
ssl_ca => $ssl_ca,
ssl_crl_path => $ssl_crl_path,
ssl_crl => $ssl_crl,
ssl_certs_dir => $ssl_certs_dir,
wsgi_process_group => 'keystone',
wsgi_script_aliases => $wsgi_script_aliases_admin,
require => [Class['apache::mod::wsgi'], File['keystone_wsgi_admin']],
}
}
}

View File

@ -0,0 +1,219 @@
require 'spec_helper'
describe 'keystone::wsgi::apache' do
let :global_facts do
{
:processorcount => 42,
:concat_basedir => '/var/lib/puppet/concat',
:fqdn => 'some.host.tld'
}
end
let :pre_condition do
'include apache
class { keystone: admin_token => "dummy" }'
end
shared_examples_for 'apache serving keystone with mod_wsgi' do
it { should contain_service('httpd').with_name(platform_parameters[:httpd_service_name]) }
it { should contain_class('keystone::params') }
it { should contain_class('apache') }
it { should contain_class('apache::mod::wsgi') }
it { should contain_class('keystone::db::sync') }
describe 'with default parameters' do
it { should contain_file("#{platform_parameters[:wsgi_script_path]}").with(
'ensure' => 'directory',
'owner' => 'keystone',
'group' => 'keystone',
'require' => 'Package[httpd]'
)}
it { should contain_file('keystone_wsgi_admin').with(
'ensure' => 'file',
'path' => "#{platform_parameters[:wsgi_script_path]}/admin",
'source' => platform_parameters[:wsgi_script_source],
'owner' => 'keystone',
'group' => 'keystone',
'mode' => '0644',
'require' => "File[#{platform_parameters[:wsgi_script_path]}]"
)}
it { should contain_file('keystone_wsgi_main').with(
'ensure' => 'file',
'path' => "#{platform_parameters[:wsgi_script_path]}/main",
'source' => platform_parameters[:wsgi_script_source],
'owner' => 'keystone',
'group' => 'keystone',
'mode' => '0644',
'require' => "File[#{platform_parameters[:wsgi_script_path]}]"
)}
it { should contain_apache__vhost('keystone_wsgi_admin').with(
'servername' => 'some.host.tld',
'port' => '35357',
'docroot' => "#{platform_parameters[:wsgi_script_path]}",
'docroot_owner' => 'keystone',
'docroot_group' => 'keystone',
'ssl' => 'true',
'wsgi_process_group' => 'keystone',
'wsgi_script_aliases' => { '/' => "#{platform_parameters[:wsgi_script_path]}/admin" },
'require' => ['Class[Apache::Mod::Wsgi]', 'File[keystone_wsgi_admin]']
)}
it { should contain_apache__vhost('keystone_wsgi_main').with(
'servername' => 'some.host.tld',
'port' => '5000',
'docroot' => "#{platform_parameters[:wsgi_script_path]}",
'docroot_owner' => 'keystone',
'docroot_group' => 'keystone',
'ssl' => 'true',
'wsgi_daemon_process' => 'keystone',
'wsgi_process_group' => 'keystone',
'wsgi_script_aliases' => { '/' => "#{platform_parameters[:wsgi_script_path]}/main" },
'require' => ['Class[Apache::Mod::Wsgi]', 'File[keystone_wsgi_main]']
)}
it "should set keystone wsgi options" do
contain_file('25-keystone_wsgi_main.conf').with_content(
/^ WSGIDaemonProcess keystone group=keystone processes=1 threads=1 user=keystone$/
)
end
end
describe 'when overriding parameters using different ports' do
let :params do
{
:servername => 'dummy.host',
:public_port => 12345,
:admin_port => 4142,
:ssl => false,
:workers => 37,
}
end
it { should contain_apache__vhost('keystone_wsgi_admin').with(
'servername' => 'dummy.host',
'port' => '4142',
'docroot' => "#{platform_parameters[:wsgi_script_path]}",
'docroot_owner' => 'keystone',
'docroot_group' => 'keystone',
'ssl' => 'false',
'wsgi_process_group' => 'keystone',
'wsgi_script_aliases' => { '/' => "#{platform_parameters[:wsgi_script_path]}/admin" },
'require' => ['Class[Apache::Mod::Wsgi]', 'File[keystone_wsgi_admin]']
)}
it { should contain_apache__vhost('keystone_wsgi_main').with(
'servername' => 'dummy.host',
'port' => '12345',
'docroot' => "#{platform_parameters[:wsgi_script_path]}",
'docroot_owner' => 'keystone',
'docroot_group' => 'keystone',
'ssl' => 'false',
'wsgi_daemon_process' => 'keystone',
'wsgi_process_group' => 'keystone',
'wsgi_script_aliases' => { '/' => "#{platform_parameters[:wsgi_script_path]}/main" },
'require' => ['Class[Apache::Mod::Wsgi]', 'File[keystone_wsgi_main]']
)}
it "should set keystone wsgi options" do
contain_file('25-keystone_wsgi_main.conf').with_content(
/^ WSGIDaemonProcess keystone group=keystone processes=37 threads=1 user=keystone$/
)
end
end
describe 'when overriding parameters using same port' do
let :params do
{
:servername => 'dummy.host',
:public_port => 4242,
:admin_port => 4242,
:public_path => '/main/endpoint/',
:admin_path => '/admin/endpoint/',
:ssl => true,
:workers => 37,
}
end
it { should_not contain_apache__vhost('keystone_wsgi_admin') }
it { should contain_apache__vhost('keystone_wsgi_main').with(
'servername' => 'dummy.host',
'port' => '4242',
'docroot' => "#{platform_parameters[:wsgi_script_path]}",
'docroot_owner' => 'keystone',
'docroot_group' => 'keystone',
'ssl' => 'true',
'wsgi_daemon_process' => 'keystone',
'wsgi_process_group' => 'keystone',
'wsgi_script_aliases' => {
'/main/endpoint' => "#{platform_parameters[:wsgi_script_path]}/main",
'/admin/endpoint' => "#{platform_parameters[:wsgi_script_path]}/admin"
},
'require' => ['Class[Apache::Mod::Wsgi]', 'File[keystone_wsgi_main]']
)}
it "should set keystone wsgi options" do
contain_file('25-keystone_wsgi_main.conf').with_content(
/^ WSGIDaemonProcess keystone group=keystone processes=37 threads=1 user=keystone$/
)
end
end
describe 'when overriding parameters using same port and same path' do
let :params do
{
:servername => 'dummy.host',
:public_port => 4242,
:admin_port => 4242,
:public_path => '/endpoint/',
:admin_path => '/endpoint/',
:ssl => true,
:workers => 37,
}
end
it_raises 'a Puppet::Error', /When using the same port for public & private endpoints, public_path and admin_path should be different\./
end
end
context 'on RedHat platforms' do
let :facts do
global_facts.merge({
:osfamily => 'RedHat',
:operatingsystemrelease => '6.0'
})
end
let :platform_parameters do
{
:httpd_service_name => 'httpd',
:wsgi_script_path => '/var/www/cgi-bin/keystone',
:wsgi_script_source => 'puppet:///modules/keystone/httpd/keystone.py'
}
end
it_configures 'apache serving keystone with mod_wsgi'
end
context 'on Debian platforms' do
let :facts do
global_facts.merge({
:osfamily => 'Debian',
:operatingsystem => 'Debian',
:operatingsystemrelease => '7.0'
})
end
let :platform_parameters do
{
:httpd_service_name => 'apache2',
:wsgi_script_path => '/usr/lib/cgi-bin/keystone',
:wsgi_script_source => '/usr/share/keystone/wsgi.py'
}
end
it_configures 'apache serving keystone with mod_wsgi'
end
end

5
spec/shared_examples.rb Normal file
View File

@ -0,0 +1,5 @@
shared_examples_for "a Puppet::Error" do |description|
it "with message matching #{description.inspect}" do
expect { should have_class_count(1) }.to raise_error(Puppet::Error, description)
end
end

View File

@ -1 +1,8 @@
require 'puppetlabs_spec_helper/module_spec_helper'
require 'shared_examples'
RSpec.configure do |c|
c.alias_it_should_behave_like_to :it_configures, 'configures'
c.alias_it_should_behave_like_to :it_raises, 'raises'
end