diff --git a/manifests/api.pp b/manifests/api.pp index 90cd8da4..d09d72db 100644 --- a/manifests/api.pp +++ b/manifests/api.pp @@ -49,6 +49,15 @@ # (optional) Whether to enable services. # Defaults to true. # +# [*service_name*] +# (optional) Name of the service that will be providing the +# server functionality of glance-api. +# If the value is 'httpd', this means glance-api will be a web +# service, and you must use another class to configure that +# web service. For example, use class { 'glance::wsgi::apache'...} +# to make glance-api be a web app using apache mod_wsgi. +# Defaults to '$::glance::params::api_service_name' +# # [*container_formats*] # (optional) List of allowed values for an image container_format attributes # Defaults to $::os_service_default. @@ -313,6 +322,7 @@ class glance::api( $paste_deploy_config_file = $::os_service_default, $manage_service = true, $enabled = true, + $service_name = $::glance::params::api_service_name, $show_image_direct_url = $::os_service_default, $location_strategy = $::os_service_default, $purge_config = false, @@ -644,13 +654,29 @@ enabled_backends instead.') $service_ensure = 'stopped' } - service { 'glance-api': - ensure => $service_ensure, - name => $::glance::params::api_service_name, - enable => $enabled, - hasstatus => true, - hasrestart => true, - tag => 'glance-service', + if $service_name == $::glance::params::api_service_name { + service { 'glance-api': + ensure => $service_ensure, + name => $::glance::params::api_service_name, + enable => $enabled, + hasstatus => true, + hasrestart => true, + tag => 'glance-service', + } + } elsif $service_name == 'httpd' { + service { 'glance-api': + ensure => 'stopped', + name => $::glance::params::api_service_name, + enable => false, + tag => 'glance-service', + } + Service <| title == 'httpd' |> { tag +> 'glance-service' } + + # we need to make sure glance-api/eventlet is stopped before trying to start apache + Service['glance-api'] -> Service[$service_name] + } else { + fail("Invalid service_name. ${::glance::params::api_service_name} for \ +running as a standalone service, or httpd for being run by a httpd server") } } } diff --git a/manifests/params.pp b/manifests/params.pp index 34aa99ef..04ebd081 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -11,24 +11,28 @@ class glance::params { $group = 'glance' $boto3_package_name = 'python3-boto3' + $glance_wsgi_script_source = '/usr/bin/glance-wsgi-api' + case $::osfamily { 'RedHat': { - $package_name = 'openstack-glance' - $api_package_name = undef - $api_service_name = 'openstack-glance-api' - $pyceph_package_name = 'python3-rbd' - $lock_path = '/var/lib/glance/tmp' + $package_name = 'openstack-glance' + $api_package_name = undef + $api_service_name = 'openstack-glance-api' + $pyceph_package_name = 'python3-rbd' + $lock_path = '/var/lib/glance/tmp' + $glance_wsgi_script_path = '/var/www/cgi-bin/glance' } 'Debian': { - $package_name = undef - $api_package_name = 'glance-api' - $api_service_name = 'glance-api' + $package_name = undef + $api_package_name = 'glance-api' + $api_service_name = 'glance-api' if $::operatingsystem == 'Debian' { - $pyceph_package_name = 'python3-ceph' + $pyceph_package_name = 'python3-ceph' } else { - $pyceph_package_name = 'python3-rbd' + $pyceph_package_name = 'python3-rbd' } - $lock_path = '/var/lock/glance' + $lock_path = '/var/lock/glance' + $glance_wsgi_script_path = '/usr/lib/cgi-bin/glance' } default: { fail("Unsupported osfamily: ${::osfamily} operatingsystem: ${::operatingsystem}, \ diff --git a/manifests/wsgi.pp b/manifests/wsgi.pp new file mode 100644 index 00000000..161e2551 --- /dev/null +++ b/manifests/wsgi.pp @@ -0,0 +1,28 @@ +# == Class: glance::wsgi +# +# Configure wsgi options +# +# === Parameters +# +# [*task_pool_threads*] +# (Optional) The number of thredas (per worker process) in the pool for +# processing asynchronous tasks. +# Defaults to $::os_service_default +# +# [*python_interpreter*] +# (Optional) Path to the python interpreter to use when spawning external +# processes. +# Defaults to $::os_service_default +# +class glance::wsgi ( + $task_pool_threads = $::os_service_default, + $python_interpreter = $::os_service_default, +) { + + include glance::deps + + glance_api_config { + 'wsgi/task_pool_threads': value => $task_pool_threads; + 'wsgi/python_interpreter': value => $python_interpreter; + } +} diff --git a/manifests/wsgi/apache.pp b/manifests/wsgi/apache.pp new file mode 100644 index 00000000..32c145c9 --- /dev/null +++ b/manifests/wsgi/apache.pp @@ -0,0 +1,201 @@ +# +# 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. +# +# Class to serve Glance API with apache mod_wsgi in place of glance-api service. +# +# When using this class you should disable your glance-api service. +# +# == Parameters +# +# [*servername*] +# The servername for the virtualhost. +# Optional. Defaults to $::fqdn +# +# [*port*] +# The port. +# Optional. Defaults to 9292 +# +# [*bind_host*] +# The host/ip address Apache will listen on. +# Optional. Defaults to undef (listen on all ip addresses). +# +# [*path*] +# The prefix for the endpoint. +# Optional. Defaults to '/' +# +# [*ssl*] +# Use ssl ? (boolean) +# Optional. Defaults to true +# +# [*workers*] +# Number of WSGI workers to spawn. +# Optional. Defaults to $::os_workers +# +# [*priority*] +# (optional) The priority for the vhost. +# Defaults to '10' +# +# [*threads*] +# (optional) The number of threads for the vhost. +# Defaults to 1 +# +# [*wsgi_process_display_name*] +# (optional) Name of the WSGI process display-name. +# Defaults to undef +# +# [*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. +# +# [*access_log_file*] +# The log file name for the virtualhost. +# Optional. Defaults to false. +# +# [*access_log_pipe*] +# Specifies a pipe where Apache sends access logs for the virtualhost. +# Optional. Defaults to false. +# +# [*access_log_syslog*] +# Sends the virtualhost access log messages to syslog. +# Optional. Defaults to false. +# +# [*access_log_format*] +# The log format for the virtualhost. +# Optional. Defaults to false. +# +# [*error_log_file*] +# The error log file name for the virtualhost. +# Optional. Defaults to undef. +# +# [*error_log_pipe*] +# Specifies a pipe where Apache sends error logs for the virtualhost. +# Optional. Defaults to undef. +# +# [*error_log_syslog*] +# Sends the virtualhost error log messages to syslog. +# Optional. Defaults to undef. +# +# [*custom_wsgi_process_options*] +# (optional) gives you the opportunity to add custom process options or to +# overwrite the default options for the WSGI main process. +# eg. to use a virtual python environment for the WSGI process +# you could set it to: +# { python-path => '/my/python/virtualenv' } +# Defaults to {} +# +# [*headers*] +# (optional) Headers for the vhost. +# Defaults to undef +# +# [*request_headers*] +# (optional) Modifies collected request headers in various ways. +# Defaults to undef +# +# [*vhost_custom_fragment*] +# (optional) Passes a string of custom configuration +# directives to be placed at the end of the vhost configuration. +# Defaults to undef. +# +# == Dependencies +# +# requires Class['apache'] & Class['glance'] +# +# == Examples +# +# include apache +# +# class { 'glance::wsgi::apache': } +# +class glance::wsgi::apache ( + $servername = $::fqdn, + $port = 9292, + $bind_host = undef, + $path = '/', + $ssl = false, + $workers = $::os_workers, + $ssl_cert = undef, + $ssl_key = undef, + $ssl_chain = undef, + $ssl_ca = undef, + $ssl_crl_path = undef, + $ssl_crl = undef, + $ssl_certs_dir = undef, + $wsgi_process_display_name = undef, + $threads = 1, + $priority = '10', + $access_log_file = false, + $access_log_pipe = false, + $access_log_syslog = false, + $access_log_format = false, + $error_log_file = undef, + $error_log_pipe = undef, + $error_log_syslog = undef, + $custom_wsgi_process_options = {}, + $headers = undef, + $request_headers = undef, + $vhost_custom_fragment = undef, +) { + + include glance::deps + include glance::params + include apache + include apache::mod::wsgi + if $ssl { + include apache::mod::ssl + } + + ::openstacklib::wsgi::apache { 'glance_wsgi': + bind_host => $bind_host, + bind_port => $port, + group => 'glance', + path => $path, + priority => $priority, + servername => $servername, + ssl => $ssl, + ssl_ca => $ssl_ca, + ssl_cert => $ssl_cert, + ssl_certs_dir => $ssl_certs_dir, + ssl_chain => $ssl_chain, + ssl_crl => $ssl_crl, + ssl_crl_path => $ssl_crl_path, + ssl_key => $ssl_key, + threads => $threads, + user => 'glance', + vhost_custom_fragment => $vhost_custom_fragment, + workers => $workers, + wsgi_chunked_request => 'On', + wsgi_daemon_process => 'glance-api', + wsgi_process_display_name => $wsgi_process_display_name, + wsgi_process_group => 'glance-api', + wsgi_script_dir => $::glance::params::glance_wsgi_script_path, + wsgi_script_file => 'glance-api', + wsgi_script_source => $::glance::params::glance_wsgi_script_source, + custom_wsgi_process_options => $custom_wsgi_process_options, + headers => $headers, + request_headers => $request_headers, + access_log_file => $access_log_file, + access_log_pipe => $access_log_pipe, + access_log_syslog => $access_log_syslog, + access_log_format => $access_log_format, + error_log_file => $error_log_file, + error_log_pipe => $error_log_pipe, + error_log_syslog => $error_log_syslog, + require => Anchor['glance::install::end'], + } +} diff --git a/releasenotes/notes/wsgi-apache-9b01ec002add3e9b.yaml b/releasenotes/notes/wsgi-apache-9b01ec002add3e9b.yaml new file mode 100644 index 00000000..e69f027d --- /dev/null +++ b/releasenotes/notes/wsgi-apache-9b01ec002add3e9b.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The new ``glance::wsgi`` class, to manage parameters in the ``[wsgi]`` + section, has been added. + + - | + Added support for running the ``glance-api`` service by httpd and mod_wsgi. diff --git a/spec/classes/glance_api_spec.rb b/spec/classes/glance_api_spec.rb index 6845c430..20bd5068 100644 --- a/spec/classes/glance_api_spec.rb +++ b/spec/classes/glance_api_spec.rb @@ -252,6 +252,49 @@ describe 'glance::api' do it { is_expected.to contain_glance_api_config('paste_deploy/flavor').with_value('something') } end + context 'when running glance-api in wsgi' do + let :params do + { + :service_name => 'httpd' + } + end + + let :pre_condition do + "include apache + class { 'glance': } + class { 'glance::api::authtoken': + password => 'foo', + }" + end + + it 'configures glance-api service with Apache' do + is_expected.to contain_class('apache::params') + is_expected.to contain_service('glance-api').with( + :ensure => 'stopped', + :enable => false, + :tag => ['glance-service'], + ) + end + end + + context 'when service_name is not valid' do + let :params do + { + :service_name => 'foobar' + } + end + + let :pre_condition do + "include apache + class { 'glance': } + class { 'glance::api::authtoken': + password => 'foo', + }" + end + + it_raises 'a Puppet::Error', /Invalid service_name/ + end + describe 'with blank flavor' do let :params do { diff --git a/spec/classes/glance_wsgi_apache_spec.rb b/spec/classes/glance_wsgi_apache_spec.rb new file mode 100644 index 00000000..663bc314 --- /dev/null +++ b/spec/classes/glance_wsgi_apache_spec.rb @@ -0,0 +1,184 @@ +require 'spec_helper' + +describe 'glance::wsgi::apache' do + shared_examples 'apache serving glance with mod_wsgi' do + context 'with default parameters' do + it { is_expected.to contain_class('glance::params') } + it { is_expected.to contain_class('apache') } + it { is_expected.to contain_class('apache::mod::wsgi') } + it { is_expected.to_not contain_class('apache::mod::ssl') } + it { is_expected.to contain_openstacklib__wsgi__apache('glance_wsgi').with( + :bind_port => 9292, + :group => 'glance', + :path => '/', + :servername => facts[:fqdn], + :ssl => false, + :threads => 1, + :user => 'glance', + :workers => facts[:os_workers], + :wsgi_chunked_request => 'On', + :wsgi_daemon_process => 'glance-api', + :wsgi_process_group => 'glance-api', + :wsgi_script_dir => platform_params[:wsgi_script_path], + :wsgi_script_file => 'glance-api', + :wsgi_script_source => platform_params[:wsgi_script_source], + :headers => nil, + :request_headers => nil, + :custom_wsgi_process_options => {}, + :access_log_file => false, + :access_log_pipe => false, + :access_log_syslog => false, + :access_log_format => false, + :error_log_file => nil, + :error_log_pipe => nil, + :error_log_syslog => nil, + )} + end + + context'when overriding parameters using different ports' do + let :params do + { + :servername => 'dummy.host', + :bind_host => '10.42.51.1', + :port => 12345, + :ssl => true, + :vhost_custom_fragment => 'Timeout 99', + :wsgi_process_display_name => 'glance-api', + :workers => 37, + :custom_wsgi_process_options => { + 'python_path' => '/my/python/admin/path', + }, + :headers => ['set X-XSS-Protection "1; mode=block"'], + :request_headers => ['set Content-Type "application/json"'], + } + end + it { is_expected.to contain_class('glance::params') } + it { is_expected.to contain_class('apache') } + it { is_expected.to contain_class('apache::mod::wsgi') } + it { is_expected.to contain_class('apache::mod::ssl') } + it { is_expected.to contain_openstacklib__wsgi__apache('glance_wsgi').with( + :bind_host => '10.42.51.1', + :bind_port => 12345, + :group => 'glance', + :path => '/', + :servername => 'dummy.host', + :ssl => true, + :threads => 1, + :user => 'glance', + :vhost_custom_fragment => 'Timeout 99', + :workers => 37, + :wsgi_chunked_request => 'On', + :wsgi_daemon_process => 'glance-api', + :wsgi_process_display_name => 'glance-api', + :wsgi_process_group => 'glance-api', + :wsgi_script_dir => platform_params[:wsgi_script_path], + :wsgi_script_file => 'glance-api', + :wsgi_script_source => platform_params[:wsgi_script_source], + :headers => ['set X-XSS-Protection "1; mode=block"'], + :request_headers => ['set Content-Type "application/json"'], + :custom_wsgi_process_options => { + 'python_path' => '/my/python/admin/path', + }, + )} + end + + context 'with custom access logging' do + let :params do + { + :access_log_format => 'foo', + :access_log_syslog => 'syslog:local0', + :error_log_syslog => 'syslog:local1', + } + end + + it { should contain_openstacklib__wsgi__apache('glance_wsgi').with( + :access_log_format => params[:access_log_format], + :access_log_syslog => params[:access_log_syslog], + :error_log_syslog => params[:error_log_syslog], + )} + end + + context 'with access_log_file' do + let :params do + { + :access_log_file => '/path/to/file', + } + end + + it { should contain_openstacklib__wsgi__apache('glance_wsgi').with( + :access_log_file => params[:access_log_file], + )} + end + + context 'with access_log_pipe' do + let :params do + { + :access_log_pipe => 'pipe', + } + end + + it { should contain_openstacklib__wsgi__apache('glance_wsgi').with( + :access_log_pipe => params[:access_log_pipe], + )} + end + + context 'with error_log_file' do + let :params do + { + :error_log_file => '/path/to/file', + } + end + + it { should contain_openstacklib__wsgi__apache('glance_wsgi').with( + :error_log_file => params[:error_log_file], + )} + end + + context 'with error_log_pipe' do + let :params do + { + :error_log_pipe => 'pipe', + } + end + + it { should contain_openstacklib__wsgi__apache('glance_wsgi').with( + :error_log_pipe => params[:error_log_pipe], + )} + end + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge!(OSDefaults.get_facts({ + :os_workers => 42, + :concat_basedir => '/var/lib/puppet/concat', + :fqdn => 'some.host.tld', + })) + end + + let(:platform_params) do + case facts[:osfamily] + when 'Debian' + { + :httpd_service_name => 'apache2', + :httpd_ports_file => '/etc/apache2/ports.conf', + :wsgi_script_path => '/usr/lib/cgi-bin/glance', + :wsgi_script_source => '/usr/bin/glance-wsgi-api' + } + when 'RedHat' + { + :httpd_service_name => 'httpd', + :httpd_ports_file => '/etc/httpd/conf/ports.conf', + :wsgi_script_path => '/var/www/cgi-bin/glance', + :wsgi_script_source => '/usr/bin/glance-wsgi-api' + } + end + end + + it_behaves_like 'apache serving glance with mod_wsgi' + end + end +end diff --git a/spec/classes/glance_wsgi_spec.rb b/spec/classes/glance_wsgi_spec.rb new file mode 100644 index 00000000..79ce8eac --- /dev/null +++ b/spec/classes/glance_wsgi_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'glance::wsgi' do + shared_examples 'glance::wsgi' do + context 'with default parameters' do + it 'configures the default values' do + is_expected.to contain_glance_api_config('wsgi/task_pool_threads').with_value('') + is_expected.to contain_glance_api_config('wsgi/python_interpreter').with_value('') + end + end + + context 'with specified parameters' do + let :params do + { + :task_pool_threads => 16, + :python_interpreter => '/usr/bin/python3' + } + end + + it 'configures the default values' do + is_expected.to contain_glance_api_config('wsgi/task_pool_threads').with_value(16) + is_expected.to contain_glance_api_config('wsgi/python_interpreter').with_value('/usr/bin/python3') + end + end + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge(OSDefaults.get_facts()) + end + + it_behaves_like 'glance::wsgi' + end + end +end