diff --git a/examples/nova_wsgi.pp b/examples/nova_wsgi.pp new file mode 100644 index 000000000..814c15b3b --- /dev/null +++ b/examples/nova_wsgi.pp @@ -0,0 +1,31 @@ +# This manifest documents different use cases when running WSGI in Nova API + +# Use Case #1: running Nova API with osapi_compute in WSGI, and metadata +class { '::nova': } +class { '::nova::api': + admin_password => 'a_big_secret', + service_name => 'httpd', +} +include ::apache +class { '::nova::wsgi::apache': + ssl => false, +} + +# Use Case #2: running Nova API with osapi_compute in WSGI, and metadata disabled +class { '::nova': } +class { '::nova::api': + admin_password => 'a_big_secret', + enabled_apis => ['osapi_compute'], + service_name => 'httpd', +} +include ::apache +class { '::nova::wsgi::apache': + ssl => false, +} + +# Use Case #3: not running osapi_compute, just enabling metadata +class { '::nova': } +class { '::nova::api': + admin_password => 'a_big_secret', + enabled_apis => ['metadata'], +} diff --git a/manifests/api.pp b/manifests/api.pp index a8ac8e741..98e43bb19 100644 --- a/manifests/api.pp +++ b/manifests/api.pp @@ -61,8 +61,10 @@ # Defaults to 8775 # # [*enabled_apis*] -# (optional) A comma separated list of apis to enable -# Defaults to 'osapi_compute,metadata' +# (optional) A list of apis to enable +# It was a string until now but will be an array. +# To avoid a warning, use an array, like ['osapi_compute', metadata'] for example. +# Defaults to ['osapi_compute', 'metadata'] # # [*keystone_ec2_url*] # (optional) DEPRECATED. The keystone url where nova should send requests for ec2tokens @@ -162,6 +164,15 @@ # try_sleep: 10 # Defaults to {} # +# [*service_name*] +# (optional) Name of the service that will be providing the +# server functionality of nova-api. +# If the value is 'httpd', this means nova-api will be a web +# service, and you must use another class to configure that +# web service. For example, use class { 'nova::wsgi::apache'...} +# to make nova be a web app using apache mod_wsgi. +# Defaults to '$::nova::params::api_service_name' +# class nova::api( $admin_password, $enabled = true, @@ -176,7 +187,7 @@ class nova::api( $osapi_compute_listen_port = 8774, $metadata_listen = '0.0.0.0', $metadata_listen_port = 8775, - $enabled_apis = 'osapi_compute,metadata', + $enabled_apis = ['osapi_compute', 'metadata'], $volume_api_class = 'nova.volume.cinder.API', $use_forwarded_for = false, $osapi_compute_workers = $::processorcount, @@ -188,23 +199,23 @@ class nova::api( $default_floating_pool = 'nova', $pci_alias = undef, $ratelimits = undef, - $ratelimits_factory = + $ratelimits_factory = 'nova.api.openstack.compute.limits:RateLimitingMiddleware.factory', $validate = false, $validation_options = {}, $instance_name_template = undef, $fping_path = '/usr/sbin/fping', + $service_name = $::nova::params::api_service_name, # DEPRECATED PARAMETER $conductor_workers = undef, $ec2_listen_port = undef, $ec2_workers = undef, $keystone_ec2_url = undef, $auth_version = false, -) { +) inherits nova::params { include ::nova::deps include ::nova::db - include ::nova::params include ::nova::policy include ::cinder::client @@ -226,8 +237,53 @@ class nova::api( } } + # In N release, enabled_apis should be an array by default + if is_array($enabled_apis) { + # let's transform the array in a string + # ['osapi_compute', 'metadata'] would become 'osapi_compute,metadata' + $enabled_apis_string = join($enabled_apis, ',') + } else { + # But for Mitaka cycle, we maintain backward compatibility: + # when running wsgi, we need to know what to exactly enable or not. + # since enabled_apis is not an array, so we need to grep services + # so we can detect what is actually activated for eventlet or not. + $enabled_apis_string = $enabled_apis + warning('In N cycle, enabled_apis will have to be an array of APIs to enable.') + } + + # metadata can't be run in wsgi so we have to enable it in eventlet anyway. + if ('metadata' in $enabled_apis and $service_name == 'httpd') { + $enable_metadata = true + } else { + $enable_metadata = false + } + + # sanitize service_name and prepare DEFAULT/enabled_apis parameter + if $service_name == $::nova::params::api_service_name { + # if running evenlet, we use the original puppet parameter + # so people can enable custom service names and we keep backward compatibility. + $enabled_apis_real = $enabled_apis_string + $service_enabled = $enabled + } elsif $service_name == 'httpd' { + # when running wsgi, we want to enable metadata in eventlet if part of enabled_apis + if $enable_metadata { + $enabled_apis_real = 'metadata' + $service_enabled = $enabled + } else { + # otherwise, set it to undef + $enabled_apis_real = undef + # if running wsgi for compute, and metadata disabled + # we don't need to enable nova-api service. + $service_enabled = false + } + # make sure we start apache before nova-api to avoid binding issues + Service[$service_name] -> Service['nova-api'] + } else { + fail('Invalid service_name. Either nova-api/openstack-nova-api for running as a standalone service, or httpd for being run by a httpd server') + } + nova::generic_service { 'api': - enabled => $enabled, + enabled => $service_enabled, manage_service => $manage_service, ensure_package => $ensure_package, package_name => $::nova::params::api_package_name, @@ -236,8 +292,8 @@ class nova::api( } nova_config { - 'DEFAULT/enabled_apis': value => $enabled_apis; 'DEFAULT/api_paste_config': value => $api_paste_config; + 'DEFAULT/enabled_apis': value => $enabled_apis_real; 'DEFAULT/volume_api_class': value => $volume_api_class; 'DEFAULT/osapi_compute_listen': value => $api_bind_address; 'DEFAULT/metadata_listen': value => $metadata_listen; diff --git a/manifests/params.pp b/manifests/params.pp index 9863f692b..2c0569e0d 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -30,24 +30,26 @@ class nova::params { $pymysql_package_name = undef $ceph_client_package_name = 'ceph-common' # service names - $api_service_name = 'openstack-nova-api' - $cells_service_name = 'openstack-nova-cells' - $cert_service_name = 'openstack-nova-cert' - $compute_service_name = 'openstack-nova-compute' - $conductor_service_name = 'openstack-nova-conductor' - $consoleauth_service_name = 'openstack-nova-consoleauth' - $libvirt_service_name = 'libvirtd' - $network_service_name = 'openstack-nova-network' - $objectstore_service_name = 'openstack-nova-objectstore' - $scheduler_service_name = 'openstack-nova-scheduler' - $tgt_service_name = 'tgtd' - $vncproxy_service_name = 'openstack-nova-novncproxy' - $serialproxy_service_name = 'openstack-nova-serialproxy' - $spicehtml5proxy_service_name = 'openstack-nova-spicehtml5proxy' + $api_service_name = 'openstack-nova-api' + $cells_service_name = 'openstack-nova-cells' + $cert_service_name = 'openstack-nova-cert' + $compute_service_name = 'openstack-nova-compute' + $conductor_service_name = 'openstack-nova-conductor' + $consoleauth_service_name = 'openstack-nova-consoleauth' + $libvirt_service_name = 'libvirtd' + $network_service_name = 'openstack-nova-network' + $objectstore_service_name = 'openstack-nova-objectstore' + $scheduler_service_name = 'openstack-nova-scheduler' + $tgt_service_name = 'tgtd' + $vncproxy_service_name = 'openstack-nova-novncproxy' + $serialproxy_service_name = 'openstack-nova-serialproxy' + $spicehtml5proxy_service_name = 'openstack-nova-spicehtml5proxy' # redhat specific config defaults - $root_helper = 'sudo nova-rootwrap' - $lock_path = '/var/lib/nova/tmp' - $nova_log_group = 'nova' + $root_helper = 'sudo nova-rootwrap' + $lock_path = '/var/lib/nova/tmp' + $nova_log_group = 'nova' + $nova_wsgi_script_path = '/var/www/cgi-bin/nova' + $nova_api_wsgi_script_source = '/usr/lib/python2.7/site-packages/nova/wsgi/nova-api.py' case $::operatingsystem { 'Fedora': { $special_service_provider = undef @@ -100,6 +102,8 @@ class nova::params { $serialproxy_service_name = 'nova-serialproxy' $tgt_service_name = 'tgt' $nova_log_group = 'adm' + $nova_wsgi_script_path = '/usr/lib/cgi-bin/nova' + $nova_api_wsgi_script_source = '/usr/lib/python2.7/dist-packages/nova/wsgi/nova-api.py' # debian specific nova config $root_helper = 'sudo nova-rootwrap' $lock_path = '/var/lock/nova' diff --git a/manifests/wsgi/apache.pp b/manifests/wsgi/apache.pp new file mode 100644 index 000000000..99e67c008 --- /dev/null +++ b/manifests/wsgi/apache.pp @@ -0,0 +1,133 @@ +# +# Copyright (C) 2015 eNovance SAS +# +# Author: Emilien Macchi +# +# 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 Nova API and EC2 with apache mod_wsgi in place of nova-api and nova-api-ec2 services. +# +# Serving Nova API and Nova API EC2 from apache is the recommended way to go for production +# because of limited performance for concurrent accesses. +# +# When using this class you should disable your nova-api and nova-api-ec2 service. +# +# == Parameters +# +# [*servername*] +# The servername for the virtualhost. +# Optional. Defaults to $::fqdn +# +# [*api_port*] +# The port for Nova API service. +# Optional. Defaults to 8774 +# +# [*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 $::processorcount +# +# [*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['nova'] & Class['nova::api'] +# +# == Examples +# +# include apache +# +# class { 'nova::wsgi::apache': } +# +class nova::wsgi::apache ( + $servername = $::fqdn, + $api_port = 8774, + $bind_host = undef, + $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, + $threads = $::processorcount, + $priority = '10', +) { + + include ::nova::params + include ::apache + include ::apache::mod::wsgi + if $ssl { + include ::apache::mod::ssl + } + + if ! defined(Class[::nova::api]) { + fail('::nova::api class must be declared in composition layer.') + } + + ::openstacklib::wsgi::apache { 'nova_api_wsgi': + bind_host => $bind_host, + bind_port => $api_port, + group => 'nova', + 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 => 'nova', + workers => $workers, + wsgi_daemon_process => 'nova-api', + wsgi_process_group => 'nova-api', + wsgi_script_dir => $::nova::params::nova_wsgi_script_path, + wsgi_script_file => 'nova-api', + wsgi_script_source => $::nova::params::nova_api_wsgi_script_source, + } + +} diff --git a/metadata.json b/metadata.json index 97e9f4d81..420f652ab 100644 --- a/metadata.json +++ b/metadata.json @@ -31,6 +31,7 @@ ], "description": "Installs and configures OpenStack Nova (Compute).", "dependencies": [ + { "name": "puppetlabs/apache", "version_requirement": ">=1.0.0 <2.0.0" }, { "name": "dprince/qpid", "version_requirement": ">=1.0.0 <2.0.0" }, { "name": "duritong/sysctl", "version_requirement": ">=0.0.1 <1.0.0" }, { "name": "openstack/cinder", "version_requirement": ">=7.0.0 <8.0.0" }, diff --git a/spec/acceptance/basic_nova_spec.rb b/spec/acceptance/nova_wsgi_apache_spec.rb similarity index 91% rename from spec/acceptance/basic_nova_spec.rb rename to spec/acceptance/nova_wsgi_apache_spec.rb index 9bad25445..accce9cf0 100644 --- a/spec/acceptance/basic_nova_spec.rb +++ b/spec/acceptance/nova_wsgi_apache_spec.rb @@ -48,6 +48,11 @@ describe 'basic nova' do admin_password => 'a_big_secret', identity_uri => 'http://127.0.0.1:35357/', osapi_v3 => true, + service_name => 'httpd', + } + include ::apache + class { '::nova::wsgi::apache': + ssl => false, } class { '::nova::cert': } class { '::nova::client': } @@ -71,15 +76,15 @@ describe 'basic nova' do end describe port(8774) do - it { is_expected.to be_listening.with('tcp') } + it { is_expected.to be_listening } end describe port(8775) do - it { is_expected.to be_listening.with('tcp') } + it { is_expected.to be_listening } end describe port(6080) do - it { is_expected.to be_listening.with('tcp') } + it { is_expected.to be_listening } end describe cron do diff --git a/spec/classes/nova_api_spec.rb b/spec/classes/nova_api_spec.rb index e6362312b..1b18acba5 100644 --- a/spec/classes/nova_api_spec.rb +++ b/spec/classes/nova_api_spec.rb @@ -49,6 +49,10 @@ describe 'nova::api' do 'keystone_authtoken/admin_password').with_value('passw0rd').with_secret(true) end + it 'enable metadata in evenlet configuration' do + is_expected.to contain_nova_config('DEFAULT/enabled_apis').with_value('osapi_compute,metadata') + end + it { is_expected.to contain_nova_config('DEFAULT/instance_name_template').with_ensure('absent')} it 'configures various stuff' do @@ -276,11 +280,98 @@ describe 'nova::api' do end end + context 'when running nova API in wsgi compute, and enabling metadata' do + before do + params.merge!({ :service_name => 'httpd' }) + end + + let :pre_condition do + "include ::apache + include ::nova" + end + + it 'enable nova API service' do + is_expected.to contain_service('nova-api').with( + :ensure => 'running', + :name => platform_params[:nova_api_service], + :enable => true, + :tag => 'nova-service', + ) + end + it 'enable metadata in evenlet configuration' do + is_expected.to contain_nova_config('DEFAULT/enabled_apis').with_value('metadata') + end + end + + context 'when running nova API in wsgi for compute, and disabling metadata' do + before do + params.merge!({ + :service_name => 'httpd', + :enabled_apis => ['osapi_compute'] }) + end + + let :pre_condition do + "include ::apache + include ::nova" + end + + it 'disable nova API service' do + is_expected.to contain_service('nova-api').with( + :ensure => 'stopped', + :name => platform_params[:nova_api_service], + :enable => false, + :tag => 'nova-service', + ) + end + end + + context 'when enabled_apis is not an array' do + before do + params.merge!({ + :service_name => 'httpd', + :enabled_apis => 'osapi_compute' }) + end + + let :pre_condition do + "include ::apache + include ::nova" + end + + it 'disable nova API service' do + is_expected.to contain_service('nova-api').with( + :ensure => 'stopped', + :name => platform_params[:nova_api_service], + :enable => false, + :tag => 'nova-service', + ) + end + end + + context 'when service_name is not valid' do + before do + params.merge!({ :service_name => 'foobar' }) + end + + let :pre_condition do + "include ::apache + include ::nova" + end + + it_raises 'a Puppet::Error', /Invalid service_name/ + end + end context 'on Debian platforms' do before do - facts.merge!( :osfamily => 'Debian' ) + facts.merge!( + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => '8.0', + :operatingsystemmajrelease => '8', + :concat_basedir => '/var/lib/puppet/concat', + :fqdn => 'some.host.tld', + ) end let :platform_params do @@ -293,7 +384,14 @@ describe 'nova::api' do context 'on RedHat platforms' do before do - facts.merge!( :osfamily => 'RedHat' ) + facts.merge!( + :osfamily => 'RedHat', + :operatingsystem => 'RedHat', + :operatingsystemrelease => '7.0', + :operatingsystemmajrelease => '7', + :concat_basedir => '/var/lib/puppet/concat', + :fqdn => 'some.host.tld', + ) end let :platform_params do diff --git a/spec/classes/nova_wsgi_apache_spec.rb b/spec/classes/nova_wsgi_apache_spec.rb new file mode 100644 index 000000000..a64b79f59 --- /dev/null +++ b/spec/classes/nova_wsgi_apache_spec.rb @@ -0,0 +1,168 @@ +require 'spec_helper' + +describe 'nova::wsgi::apache' do + + let :global_facts do + @default_facts.merge({ + :processorcount => 42, + :concat_basedir => '/var/lib/puppet/concat', + :fqdn => 'some.host.tld' + }) + end + + let :pre_condition do + "include nova + class { '::nova::api': + service_name => 'httpd', + admin_password => 'secrete', + }" + end + + shared_examples_for 'apache serving nova with mod_wsgi' do + it { is_expected.to contain_service('httpd').with_name(platform_parameters[:httpd_service_name]) } + it { is_expected.to contain_class('nova::params') } + it { is_expected.to contain_class('apache') } + it { is_expected.to contain_class('apache::mod::wsgi') } + + describe 'with default parameters' do + + let :pre_condition do + "include nova + class { '::nova::api': + service_name => 'httpd', + admin_password => 'secrete', + }" + end + + it { is_expected.to contain_file("#{platform_parameters[:wsgi_script_path]}").with( + 'ensure' => 'directory', + 'owner' => 'nova', + 'group' => 'nova', + 'require' => 'Package[httpd]' + )} + + + it { is_expected.to contain_file('nova_api_wsgi').with( + 'ensure' => 'file', + 'path' => "#{platform_parameters[:wsgi_script_path]}/nova-api", + 'source' => platform_parameters[:api_wsgi_script_source], + 'owner' => 'nova', + 'group' => 'nova', + 'mode' => '0644' + )} + it { is_expected.to contain_file('nova_api_wsgi').that_requires("File[#{platform_parameters[:wsgi_script_path]}]") } + + it { is_expected.to contain_apache__vhost('nova_api_wsgi').with( + 'servername' => 'some.host.tld', + 'ip' => nil, + 'port' => '8774', + 'docroot' => "#{platform_parameters[:wsgi_script_path]}", + 'docroot_owner' => 'nova', + 'docroot_group' => 'nova', + 'ssl' => 'true', + 'wsgi_daemon_process' => 'nova-api', + 'wsgi_process_group' => 'nova-api', + 'wsgi_script_aliases' => { '/' => "#{platform_parameters[:wsgi_script_path]}/nova-api" }, + 'require' => 'File[nova_api_wsgi]' + )} + it { is_expected.to contain_file("#{platform_parameters[:httpd_ports_file]}") } + + it { is_expected.to contain_file('nova_api_wsgi').with( + 'ensure' => 'file', + 'path' => "#{platform_parameters[:wsgi_script_path]}/nova-api", + 'source' => platform_parameters[:api_wsgi_script_source], + 'owner' => 'nova', + 'group' => 'nova', + 'mode' => '0644' + )} + it { is_expected.to contain_file('nova_api_wsgi').that_requires("File[#{platform_parameters[:wsgi_script_path]}]") } + + it { is_expected.to contain_file("#{platform_parameters[:httpd_ports_file]}") } + end + + describe 'when overriding parameters using different ports' do + let :pre_condition do + "include nova + class { '::nova::api': + service_name => 'httpd', + admin_password => 'secrete', + }" + end + + let :params do + { + :servername => 'dummy.host', + :bind_host => '10.42.51.1', + :api_port => 12345, + :ssl => false, + :workers => 37, + } + end + + it { is_expected.to contain_apache__vhost('nova_api_wsgi').with( + 'servername' => 'dummy.host', + 'ip' => '10.42.51.1', + 'port' => '12345', + 'docroot' => "#{platform_parameters[:wsgi_script_path]}", + 'docroot_owner' => 'nova', + 'docroot_group' => 'nova', + 'ssl' => 'false', + 'wsgi_daemon_process' => 'nova-api', + 'wsgi_process_group' => 'nova-api', + 'wsgi_script_aliases' => { '/' => "#{platform_parameters[:wsgi_script_path]}/nova-api" }, + 'require' => 'File[nova_api_wsgi]' + )} + end + + describe 'when ::nova::api is missing in the composition layer' do + + let :pre_condition do + "include nova" + end + + it { is_expected.to raise_error Puppet::Error, /::nova::api class must be declared in composition layer./ } + end + + end + + context 'on RedHat platforms' do + let :facts do + global_facts.merge({ + :osfamily => 'RedHat', + :operatingsystemrelease => '7.0' + }) + end + + let :platform_parameters do + { + :httpd_service_name => 'httpd', + :httpd_ports_file => '/etc/httpd/conf/ports.conf', + :wsgi_script_path => '/var/www/cgi-bin/nova', + :api_wsgi_script_source => '/usr/lib/python2.7/site-packages/nova/wsgi/nova-api.py', + } + end + + it_configures 'apache serving nova 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', + :httpd_ports_file => '/etc/apache2/ports.conf', + :wsgi_script_path => '/usr/lib/cgi-bin/nova', + :api_wsgi_script_source => '/usr/lib/python2.7/dist-packages/nova/wsgi/nova-api.py', + } + end + + it_configures 'apache serving nova with mod_wsgi' + end +end