From c1f019f8e14c1a82e456517e288850a57510ff86 Mon Sep 17 00:00:00 2001 From: Emilien Macchi Date: Fri, 21 Apr 2017 11:34:46 -0400 Subject: [PATCH] Support Neutron API in WSGI with Apache Allow to deploy Neutron API in WSGI with Apache. Note: the feature is not tested yet in our functional testing jobs because it doesn't work in devstack yet. This code will maybe need some change but that's fine, it's for Pike cycle, nothing needs to be backported, so we can easily iterate later. Also remove dependencies in keystone auth manifest where keystone resources had to be created before we start neutron-server. This is not possible anymore since neutron-server is run in httpd. Change-Id: I27bdd8d011097c5fd49277578404b4facafaca25 --- examples/neutron_wsgi.pp | 17 ++ manifests/keystone/auth.pp | 4 +- manifests/params.pp | 4 + manifests/server.pp | 49 ++++-- manifests/server/notifications.pp | 3 - manifests/wsgi/apache.pp | 153 ++++++++++++++++++ .../notes/neutron-wsgi-1c0c06dddb8ac447.yaml | 6 + spec/classes/neutron_keystone_auth_spec.rb | 1 - spec/classes/neutron_server_spec.rb | 49 ++++-- spec/classes/neutron_wsgi_apache_spec.rb | 126 +++++++++++++++ 10 files changed, 386 insertions(+), 26 deletions(-) create mode 100644 examples/neutron_wsgi.pp create mode 100644 manifests/wsgi/apache.pp create mode 100644 releasenotes/notes/neutron-wsgi-1c0c06dddb8ac447.yaml create mode 100644 spec/classes/neutron_wsgi_apache_spec.rb 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