diff --git a/examples/neutron_wsgi.pp b/examples/neutron_wsgi.pp new file mode 100644 index 000000000..1798aa722 --- /dev/null +++ b/examples/neutron_wsgi.pp @@ -0,0 +1,17 @@ +# Example of manifest to deploy Neutron API in WSGI with Apache +class { '::neutron': + allow_overlapping_ips => true, + rabbit_password => 'password', + rabbit_user => 'guest', + rabbit_host => 'localhost', +} + +class { '::neutron::server': + auth_password => 'password', + database_connection => 'mysql://neutron:password@192.168.1.1/neutron', + service_name => 'httpd', +} +include ::apache +class { '::neutron::wsgi::apache': + ssl => false, +} diff --git a/manifests/keystone/auth.pp b/manifests/keystone/auth.pp index b19cedab7..d05abe018 100644 --- a/manifests/keystone/auth.pp +++ b/manifests/keystone/auth.pp @@ -80,11 +80,11 @@ class neutron::keystone::auth ( include ::neutron::deps if $configure_endpoint { - Keystone_endpoint["${region}/${service_name}::${service_type}"] ~> Service <| title == 'neutron-server' |> + Keystone_endpoint["${region}/${service_name}::${service_type}"] ~> Service <| tag == 'neutron-server-eventlet' |> } if $configure_user_role { - Keystone_user_role["${auth_name}@${tenant}"] ~> Service <| title == 'neutron-server' |> + Keystone_user_role["${auth_name}@${tenant}"] ~> Service <| tag == 'neutron-server-eventlet' |> } keystone::resource::service_identity { 'neutron': diff --git a/manifests/params.pp b/manifests/params.pp index e553dbf5d..fdb0645b4 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -71,6 +71,8 @@ class neutron::params { $libreswan_package = 'libreswan' $l3_agent_package = false $fwaas_package = 'openstack-neutron-fwaas' + $neutron_wsgi_script_path = '/var/www/cgi-bin/neutron' + $neutron_wsgi_script_source = '/usr/bin/neutron-api' } elsif($::osfamily == 'Debian') { $nobody_user_group = 'nogroup' $package_name = 'neutron-common' @@ -102,6 +104,8 @@ class neutron::params { $fwaas_package = 'python-neutron-fwaas' $l2gw_agent_package = 'neutron-l2gateway-agent' $l2gw_package = 'python-networking-l2gw' + $neutron_wsgi_script_path = '/usr/lib/cgi-bin/neutron' + $neutron_wsgi_script_source = '/usr/bin/neutron-api' } else { fail("Unsupported osfamily ${::osfamily}") } diff --git a/manifests/server.pp b/manifests/server.pp index 8633728b9..5e27a8990 100644 --- a/manifests/server.pp +++ b/manifests/server.pp @@ -17,8 +17,13 @@ # Defaults to true # # [*service_name*] -# (optional) The name of the neutron-server service -# Defaults to $::neutron::params::server_service +# (optional) Name of the service that will be providing the +# server functionality of neutron-api. +# If the value is 'httpd', this means neutron-api will be a web +# service, and you must use another class to configure that +# web service. For example, use class { 'neutron::wsgi::apache'...} +# to make neutron-api be a web app using apache mod_wsgi. +# Defaults to '$::neutron::params::server_service' # # [*log_file*] # REMOVED: Use log_file of neutron class instead. @@ -409,13 +414,37 @@ class neutron::server ( $service_ensure = 'stopped' } } - - service { 'neutron-server': - ensure => $service_ensure, - name => $service_name, - enable => $enabled, - hasstatus => true, - hasrestart => true, - tag => ['neutron-service', 'neutron-db-sync-service'], + if $service_name == $::neutron::params::server_service { + service { 'neutron-server': + ensure => $service_ensure, + name => $::neutron::params::server_service, + enable => $enabled, + hasstatus => true, + hasrestart => true, + tag => ['neutron-service', 'neutron-db-sync-service', 'neutron-server-eventlet'], + } + } elsif $service_name == 'httpd' { + include ::apache::params + service { 'neutron-server': + ensure => 'stopped', + name => $::neutron::params::server_service, + enable => false, + hasstatus => true, + hasrestart => true, + tag => ['neutron-service', 'neutron-db-sync-service'], + } + Service <<| title == 'httpd' |>> { tag +> 'neutron-service' } + # we need to make sure neutron-server is stopped before trying to start apache + Service[$::neutron::params::server_service] -> Service[$service_name] + } else { + # backward compatibility so operators can customize the service name. + service { 'neutron-server': + ensure => $service_ensure, + name => $service_name, + enable => $enabled, + hasstatus => true, + hasrestart => true, + tag => ['neutron-service', 'neutron-db-sync-service'], + } } } diff --git a/manifests/server/notifications.pp b/manifests/server/notifications.pp index 31a305942..d3982457e 100644 --- a/manifests/server/notifications.pp +++ b/manifests/server/notifications.pp @@ -111,9 +111,6 @@ class neutron::server::notifications ( include ::neutron::deps - # Depend on the specified keystone_user resource, if it exists. - Keystone_user <| title == 'nova' |> -> Class[neutron::server::notifications] - if is_service_default($tenant_id) and (! $tenant_name) { fail('You must provide either tenant_name or tenant_id.') } diff --git a/manifests/wsgi/apache.pp b/manifests/wsgi/apache.pp new file mode 100644 index 000000000..c673f9e5f --- /dev/null +++ b/manifests/wsgi/apache.pp @@ -0,0 +1,153 @@ +# +# Copyright (C) 2017 Red Hat Inc. +# +# 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 neutron API with apache mod_wsgi in place of neutron-api service. +# +# Serving neutron API from apache is the recommended way to go for production +# because of limited performance for concurrent accesses when running eventlet. +# +# When using this class you should disable your neutron-api service. +# +# == Parameters +# +# [*servername*] +# The servername for the virtualhost. +# Optional. Defaults to $::fqdn +# +# [*port*] +# The port. +# Optional. Defaults to 9696 +# +# [*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 1 +# +# [*priority*] +# (optional) The priority for the vhost. +# Defaults to '10' +# +# [*threads*] +# (optional) The number of threads for the vhost. +# Defaults to $::os_workers +# +# [*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. +# +# == Dependencies +# +# requires Class['apache'] & Class['neutron'] +# +# == Examples +# +# include apache +# +# class { 'neutron::wsgi::apache': } +# +class neutron::wsgi::apache ( + $servername = $::fqdn, + $port = 9696, + $bind_host = undef, + $path = '/', + $ssl = true, + $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', +) { + + include ::neutron::deps + include ::neutron::params + include ::apache + include ::apache::mod::wsgi + if $ssl { + include ::apache::mod::ssl + } + + # The httpd package is untagged, but needs to have ordering enforced, + # so handle it here rather than in the deps class. + Anchor['neutron::install::begin'] + -> Package['httpd'] + -> Anchor['neutron::install::end'] + + # Configure apache during the config phase + Anchor['neutron::config::begin'] + -> Apache::Vhost<||> + ~> Anchor['neutron::config::end'] + + # Start the service during the service phase + Anchor['neutron::service::begin'] + -> Service['httpd'] + -> Anchor['neutron::service::end'] + + # Notify the service when config changes + Anchor['neutron::config::end'] + ~> Service['httpd'] + + ::openstacklib::wsgi::apache { 'neutron_wsgi': + bind_host => $bind_host, + bind_port => $port, + group => 'neutron', + 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 => 'neutron', + workers => $workers, + wsgi_daemon_process => 'neutron', + wsgi_process_display_name => $wsgi_process_display_name, + wsgi_process_group => 'neutron', + wsgi_script_dir => $::neutron::params::neutron_wsgi_script_path, + wsgi_script_file => 'app', + wsgi_script_source => $::neutron::params::neutron_wsgi_script_source, + } +} diff --git a/releasenotes/notes/neutron-wsgi-1c0c06dddb8ac447.yaml b/releasenotes/notes/neutron-wsgi-1c0c06dddb8ac447.yaml new file mode 100644 index 000000000..ee7c25b6f --- /dev/null +++ b/releasenotes/notes/neutron-wsgi-1c0c06dddb8ac447.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Neutron API can now be deployed in WSGI with Apache, like we support it + for other modules. Switch neutron::server::service_name to 'httpd' and use + neutron::wsgi::apache class to deploy it. diff --git a/spec/classes/neutron_keystone_auth_spec.rb b/spec/classes/neutron_keystone_auth_spec.rb index 948fb8ae4..f26cc0fdc 100644 --- a/spec/classes/neutron_keystone_auth_spec.rb +++ b/spec/classes/neutron_keystone_auth_spec.rb @@ -61,7 +61,6 @@ describe 'neutron::keystone::auth' do } end - it { is_expected.to contain_keystone_endpoint('RegionOne/neutron::network').with_notify(['Service[neutron-server]']) } end describe 'with endpoint URL parameters' do diff --git a/spec/classes/neutron_server_spec.rb b/spec/classes/neutron_server_spec.rb index a04b3aab0..8ae358d86 100644 --- a/spec/classes/neutron_server_spec.rb +++ b/spec/classes/neutron_server_spec.rb @@ -66,7 +66,7 @@ describe 'neutron::server' do :name => platform_params[:server_service], :enable => true, :ensure => 'running', - :tag => ['neutron-service', 'neutron-db-sync-service'], + :tag => ['neutron-service', 'neutron-db-sync-service', 'neutron-server-eventlet'], ) is_expected.to contain_service('neutron-server').that_subscribes_to('Anchor[neutron::service::begin]') is_expected.to contain_service('neutron-server').that_notifies('Anchor[neutron::service::end]') @@ -140,15 +140,6 @@ describe 'neutron::server' do end end - context 'with custom service name' do - before :each do - params.merge!(:service_name => 'custom-service-name') - end - it 'should configure proper service name' do - is_expected.to contain_service('neutron-server').with_name('custom-service-name') - end - end - context 'with state_path and lock_path parameters' do before :each do params.merge!(:state_path => 'state_path', @@ -234,6 +225,44 @@ describe 'neutron::server' do it { is_expected.to contain_neutron_config('oslo_middleware/enable_proxy_headers_parsing').with_value(true) } end + + context 'when running neutron-api in wsgi' do + before :each do + params.merge!({ :service_name => 'httpd' }) + end + + let :pre_condition do + "class { 'neutron': rabbit_password => 'passw0rd' } + include ::apache + class { '::neutron::keystone::authtoken': + password => 'passw0rd', + }" + end + + it 'configures neutron-api service with Apache' do + is_expected.to contain_service('neutron-server').with( + :ensure => 'stopped', + :name => platform_params[:server_service], + :enable => false, + :tag => ['neutron-service', 'neutron-db-sync-service'], + ) + end + end + + context 'when service_name is customized' do + before :each do + params.merge!({ :service_name => 'foobar' }) + end + + it 'configures neutron-api service with custom name' do + is_expected.to contain_service('neutron-server').with( + :name => 'foobar', + :enable => true, + :ensure => 'running', + :tag => ['neutron-service', 'neutron-db-sync-service'], + ) + end + end end shared_examples_for 'VPNaaS, FWaaS and LBaaS package installation' do diff --git a/spec/classes/neutron_wsgi_apache_spec.rb b/spec/classes/neutron_wsgi_apache_spec.rb new file mode 100644 index 000000000..405b786af --- /dev/null +++ b/spec/classes/neutron_wsgi_apache_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe 'neutron::wsgi::apache' do + + shared_examples_for 'apache serving neutron with mod_wsgi' do + it { is_expected.to contain_service('httpd').with_name(platform_parameters[:httpd_service_name]) } + it { is_expected.to contain_class('neutron::deps') } + it { is_expected.to contain_class('neutron::params') } + it { is_expected.to contain_class('apache') } + it { is_expected.to contain_class('apache::mod::wsgi') } + + describe 'with default parameters' do + + it { is_expected.to contain_file("#{platform_parameters[:wsgi_script_path]}").with( + 'ensure' => 'directory', + 'owner' => 'neutron', + 'group' => 'neutron', + 'require' => 'Package[httpd]' + )} + + + it { is_expected.to contain_file('neutron_wsgi').with( + 'ensure' => 'file', + 'path' => "#{platform_parameters[:wsgi_script_path]}/app", + 'source' => platform_parameters[:wsgi_script_source], + 'owner' => 'neutron', + 'group' => 'neutron', + 'mode' => '0644' + )} + it { is_expected.to contain_file('neutron_wsgi').that_requires("File[#{platform_parameters[:wsgi_script_path]}]") } + + it { is_expected.to contain_apache__vhost('neutron_wsgi').with( + 'servername' => 'some.host.tld', + 'ip' => nil, + 'port' => '9696', + 'docroot' => "#{platform_parameters[:wsgi_script_path]}", + 'docroot_owner' => 'neutron', + 'docroot_group' => 'neutron', + 'ssl' => 'true', + 'wsgi_daemon_process' => 'neutron', + 'wsgi_daemon_process_options' => { + 'user' => 'neutron', + 'group' => 'neutron', + 'processes' => '8', + 'threads' => '1', + 'display-name' => 'neutron_wsgi', + }, + 'wsgi_process_group' => 'neutron', + 'wsgi_script_aliases' => { '/' => "#{platform_parameters[:wsgi_script_path]}/app" }, + 'require' => 'File[neutron_wsgi]' + )} + it { is_expected.to contain_concat("#{platform_parameters[:httpd_ports_file]}") } + end + + describe 'when overriding parameters using different ports' do + let :params do + { + :servername => 'dummy.host', + :bind_host => '10.42.51.1', + :port => 12345, + :ssl => false, + :wsgi_process_display_name => 'neutron', + :workers => 37, + } + end + + it { is_expected.to contain_apache__vhost('neutron_wsgi').with( + 'servername' => 'dummy.host', + 'ip' => '10.42.51.1', + 'port' => '12345', + 'docroot' => "#{platform_parameters[:wsgi_script_path]}", + 'docroot_owner' => 'neutron', + 'docroot_group' => 'neutron', + 'ssl' => 'false', + 'wsgi_daemon_process' => 'neutron', + 'wsgi_daemon_process_options' => { + 'user' => 'neutron', + 'group' => 'neutron', + 'processes' => '37', + 'threads' => '1', + 'display-name' => 'neutron', + }, + 'wsgi_process_group' => 'neutron', + 'wsgi_script_aliases' => { '/' => "#{platform_parameters[:wsgi_script_path]}/app" }, + 'require' => 'File[neutron_wsgi]' + )} + + it { is_expected.to contain_concat("#{platform_parameters[:httpd_ports_file]}") } + 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 => 8, + :concat_basedir => '/var/lib/puppet/concat', + :fqdn => 'some.host.tld' + })) + end + + let(:platform_parameters) do + case facts[:osfamily] + when 'Debian' + { + :httpd_service_name => 'apache2', + :httpd_ports_file => '/etc/apache2/ports.conf', + :wsgi_script_path => '/usr/lib/cgi-bin/neutron', + :wsgi_script_source => '/usr/bin/neutron-api' + } + when 'RedHat' + { + :httpd_service_name => 'httpd', + :httpd_ports_file => '/etc/httpd/conf/ports.conf', + :wsgi_script_path => '/var/www/cgi-bin/neutron', + :wsgi_script_source => '/usr/bin/neutron-api' + } + + end + end + it_configures 'apache serving neutron with mod_wsgi' + end + end +end