Split the dashboard and webserver recipes

In order to be able to use alternative webservers, split the parts
relating to setting up the dashboard itself and setting up the webserver
into two new recipes.

Also introduce a new configuration variable, defaulting to `apache2`,
which will be used in the `server` recipe to select the type of
webserver being installed.

Change-Id: I70dcb820239547b0059ad15d19d5e1689ddff3d3
blueprint: dashboard-split-horizon-apache
This commit is contained in:
Jens Rosenboom 2014-11-17 14:24:27 +01:00
parent a142662fbd
commit 97404520bc
14 changed files with 1354 additions and 1202 deletions

View File

@ -20,6 +20,7 @@ This file is used to list changes made in each version of the openstack-dashboar
* Set default to use only TLS for SSL. OpenStack security note OSSN-0039
* Allow TraceEnable to be configured
* Allow volume_api_version to be configured for Horizon
* Allow webserver to be configurable
## 9.1
* python_packages database client attributes have been moved to the -common cookbook

View File

@ -21,10 +21,23 @@ The following cookbooks are dependencies:
Usage
=====
horizon
-------
Sets up the packages needed to run the Horizon dashboard and its dependencies.
Will be included from the `server` recipe.
apache2-server
--------------
Installs the Apache webserver and sets up an `mod_wsgi` container to run the
Horizon dashboard. Will be included from the `server` recipe.
server
------
Sets up the Horizon dashboard within an Apache `mod_wsgi` container.
Sets up the Horizon dashboard and a webserver of type `['openstack']['dashboard']['server_type']`
to run it, default type is 'apache2'.
```json
"run_list": [
@ -35,8 +48,9 @@ Sets up the Horizon dashboard within an Apache `mod_wsgi` container.
Attributes
==========
* `openstack['dashboard']['server_type']` - Selects the type of webserver to install
* `openstack['dashboard']['db']['username']` - Username for horizon database access
* `openstack['dashboard']['server_hostname']` - Sets the ServerName in the Apache config
* `openstack['dashboard']['server_hostname']` - Sets the ServerName in the webserver config
* `openstack['dashboard']['allowed_hosts']` - List of host/domain names we can service (default: '\[\*\]')
* `openstack['dashboard']['dash_path']` - Base path for dashboard files (document root)
* `openstack['dashboard']['wsgi_path']` - Path for wsgi dir
@ -115,12 +129,14 @@ License and Author
| **Author** | Jian Hua Geng (<gengjh@cn.ibm.com>) |
| **Author** | Ionut Artarisi (<iartarisi@suse.cz>) |
| **Author** | Eric Zhou (<iartarisi@suse.cz>) |
| **Author** | Jens Rosenboom (<j.rosenboom@x-ion.de>) |
| | |
| **Copyright** | Copyright (c) 2012, Rackspace US, Inc. |
| **Copyright** | Copyright (c) 2012-2013, AT&T Services, Inc. |
| **Copyright** | Copyright (c) 2013, Opscode, Inc. |
| **Copyright** | Copyright (c) 2013-2014, IBM, Corp. |
| **Copyright** | Copyright (c) 2013-2014, SUSE Linux GmbH. |
| **Copyright** | Copyright (c) 2014, x-ion GmbH. |
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -26,6 +26,8 @@ default['openstack']['dashboard']['custom_template_banner'] = '
# Do not edit, changes will be overwritten
'
default['openstack']['dashboard']['server_type'] = 'apache2'
default['openstack']['dashboard']['debug'] = false
# The Keystone role used by default for users logging into the dashboard

View File

@ -2,11 +2,13 @@ name 'openstack-dashboard'
maintainer 'openstack-chef'
maintainer_email 'opscode-chef-openstack@googlegroups.com'
license 'Apache 2.0'
description 'Installs/Configures the OpenStack Dasboard (Horizon)'
description 'Installs/Configures the OpenStack Dashboard (Horizon)'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '10.0'
recipe 'openstack-dashboard::server', 'Sets up the Horizon dashboard within an Apache `mod_wsgi` container.'
recipe 'openstack-dashboard::horizon', 'Sets up the Horizon dashboard.'
recipe 'openstack-dashboard::apache2-server', 'Sets up an Apache `mod_wsgi` container to run the dashboard.'
recipe 'openstack-dashboard::server', 'Sets up the Horizon dashboard and webserver to run it.'
%w{ ubuntu fedora redhat centos suse }.each do |os|
supports os

193
recipes/apache2-server.rb Normal file
View File

@ -0,0 +1,193 @@
# encoding: UTF-8
#
# Cookbook Name:: openstack-dashboard
# Recipe:: apache2-server
#
# Copyright 2012, Rackspace US, Inc.
# Copyright 2012-2013, AT&T Services, Inc.
# Copyright 2013-2014, IBM, Corp.
# Copyright 2014, SUSE Linux, GmbH.
# Copyright 2014, x-ion GmbH.
#
# 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.
#
require 'uri'
class ::Chef::Recipe # rubocop:disable Documentation
include ::Openstack
end
#
# Workaround to install apache2 on a fedora machine with selinux set to enforcing
# TODO(breu): this should move to a subscription of the template from the apache2 recipe
# and it should simply be a restorecon on the configuration file(s) and not
# change the selinux mode
#
execute 'set-selinux-permissive' do
command '/sbin/setenforce Permissive'
action :run
only_if "[ ! -e /etc/httpd/conf/httpd.conf ] && [ -e /etc/redhat-release ] && [ $(/sbin/sestatus | grep -c '^Current mode:.*enforcing') -eq 1 ]"
end
include_recipe 'apache2'
include_recipe 'apache2::mod_wsgi'
include_recipe 'apache2::mod_rewrite'
include_recipe 'apache2::mod_ssl'
#
# Workaround to re-enable selinux after installing apache on a fedora machine that has
# selinux enabled and is currently permissive and the configuration set to enforcing.
# TODO(breu): get the other one working and this won't be necessary
#
execute 'set-selinux-enforcing' do
command '/sbin/setenforce Enforcing ; restorecon -R /etc/httpd'
action :run
only_if "[ -e /etc/httpd/conf/httpd.conf ] && [ -e /etc/redhat-release ] && [ $(/sbin/sestatus | grep -c '^Current mode:.*permissive') -eq 1 ] && [ $(/sbin/sestatus | grep -c '^Mode from config file:.*enforcing') -eq 1 ]"
end
# delete the openstack-dashboard.conf before reload apache2 service on redhat and centos
# since this file is not valid on those platforms for the apache2 service.
file "#{node["apache"]["dir"]}/conf.d/openstack-dashboard.conf" do
action :delete
backup false
only_if { platform_family?('rhel') } # :pragma-foodcritic: ~FC024 - won't fix this
end
cert_file = "#{node['openstack']['dashboard']['ssl']['dir']}/certs/#{node['openstack']['dashboard']['ssl']['cert']}"
cert_mode = 00644
cert_owner = 'root'
cert_group = 'root'
if node['openstack']['dashboard']['ssl']['cert_url']
remote_file cert_file do
sensitive true
source node['openstack']['dashboard']['ssl']['cert_url']
mode cert_mode
owner cert_owner
group cert_group
notifies :run, 'execute[restore-selinux-context]', :immediately
end
else
cookbook_file cert_file do
sensitive true
source 'horizon.pem'
mode cert_mode
owner cert_owner
group cert_group
notifies :run, 'execute[restore-selinux-context]', :immediately
end
end
key_file = "#{node['openstack']['dashboard']['ssl']['dir']}/private/#{node['openstack']['dashboard']['ssl']['key']}"
key_mode = 00640
key_owner = 'root'
case node['platform_family']
when 'debian'
key_group = 'ssl-cert'
else
key_group = 'root'
end
if node['openstack']['dashboard']['ssl']['key_url']
remote_file key_file do
sensitive true
source node['openstack']['dashboard']['ssl']['key_url']
mode key_mode
owner key_owner
group key_group
notifies :restart, 'service[apache2]', :immediately
notifies :run, 'execute[restore-selinux-context]', :immediately
end
else
cookbook_file key_file do
sensitive true
source 'horizon.key'
mode key_mode
owner key_owner
group key_group
notifies :run, 'execute[restore-selinux-context]', :immediately
end
end
# make sure this file has correct permission
file node['openstack']['dashboard']['secret_key_path'] do
owner node['openstack']['dashboard']['horizon_user']
group node['openstack']['dashboard']['horizon_group']
mode 00600
# the only time the file should be created is if we have secret_key_content
# set, otherwise let apache create it when someone first accesses the
# dashboard
if node['openstack']['dashboard']['secret_key_content'].nil?
only_if { ::File.exists?(node['openstack']['dashboard']['secret_key_path']) }
else
content node['openstack']['dashboard']['secret_key_content']
notifies :restart, 'service[apache2]'
end
end
# stop apache bitching
directory "#{node["openstack"]["dashboard"]["dash_path"]}/.blackhole" do
owner 'root'
action :create
end
template node['openstack']['dashboard']['apache']['sites-path'] do
source 'dash-site.erb'
owner 'root'
group 'root'
mode 00644
variables(
ssl_cert_file: "#{node["openstack"]["dashboard"]["ssl"]["dir"]}/certs/#{node["openstack"]["dashboard"]["ssl"]["cert"]}",
ssl_key_file: "#{node["openstack"]["dashboard"]["ssl"]["dir"]}/private/#{node["openstack"]["dashboard"]["ssl"]["key"]}"
)
notifies :run, 'execute[restore-selinux-context]', :immediately
notifies :reload, 'service[apache2]', :immediately
end
# The `apache_site` provided by the apache2 cookbook
# is not an LWRP. Guards do not apply to definitions.
# http://tickets.opscode.com/browse/CHEF-778
if platform_family?('debian')
apache_site '000-default' do
enable false
end
elsif platform_family?('rhel') then
apache_site 'default' do
enable false
notifies :run, 'execute[restore-selinux-context]', :immediately
end
end
apache_site 'openstack-dashboard' do
enable true
notifies :run, 'execute[restore-selinux-context]', :immediately
notifies :reload, 'service[apache2]', :immediately
end
execute 'restore-selinux-context' do
command 'restorecon -Rv /etc/httpd /etc/pki; chcon -R -t httpd_sys_content_t /usr/share/openstack-dashboard || :'
action :nothing
only_if { platform_family?('fedora') }
end

106
recipes/horizon.rb Normal file
View File

@ -0,0 +1,106 @@
# encoding: UTF-8
#
# Cookbook Name:: openstack-dashboard
# Recipe:: horizon
#
# Copyright 2012, Rackspace US, Inc.
# Copyright 2012-2013, AT&T Services, Inc.
# Copyright 2013-2014, IBM, Corp.
# Copyright 2014, SUSE Linux, GmbH.
# Copyright 2014, x-ion, GmbH.
#
# 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.
#
require 'uri'
class ::Chef::Recipe # rubocop:disable Documentation
include ::Openstack
end
platform_options = node['openstack']['dashboard']['platform']
identity_admin_endpoint = endpoint 'identity-admin'
auth_admin_uri = auth_uri_transform identity_admin_endpoint.to_s, node['openstack']['dashboard']['api']['auth']['version']
identity_endpoint = endpoint 'identity-api'
auth_uri = auth_uri_transform identity_endpoint.to_s, node['openstack']['dashboard']['api']['auth']['version']
db_pass = get_password 'db', 'horizon'
db_info = db 'dashboard'
python_packages = node['openstack']['db']['python_packages'][db_info['service_type']]
# Add dashboard specific database packages
python_packages += Array(node['openstack']['dashboard']['db_python_packages'][db_info['service_type']])
(platform_options['horizon_packages'] + python_packages).each do |pkg|
package pkg do
action :upgrade
options platform_options['package_overrides']
end
end
if node['openstack']['dashboard']['session_backend'] == 'memcached'
platform_options['memcache_python_packages'].each do |pkg|
package pkg
end
end
memcached = memcached_servers
template node['openstack']['dashboard']['local_settings_path'] do
source 'local_settings.py.erb'
owner 'root'
group node['openstack']['dashboard']['horizon_group']
mode 00640
sensitive true
variables(
db_pass: db_pass,
db_info: db_info,
auth_uri: auth_uri,
auth_admin_uri: auth_admin_uri,
memcached_servers: memcached
)
notifies :restart, "service[#{node['openstack']['dashboard']['server_type']}]", :immediately
end
execute 'openstack-dashboard syncdb' do
cwd node['openstack']['dashboard']['django_path']
environment 'PYTHONPATH' => "/etc/openstack-dashboard:#{node['openstack']['dashboard']['django_path']}:$PYTHONPATH"
command 'python manage.py syncdb --noinput'
action :run
only_if do
node['openstack']['dashboard']['session_backend'] == 'sql' &&
node['openstack']['db']['dashboard']['migrate'] ||
db_info['service_type'] == 'sqlite'
end
end
directory "#{node['openstack']['dashboard']['dash_path']}/local" do
owner 'root'
group node['openstack']['dashboard']['horizon_group']
mode 02770
action :create
end
# ubuntu includes their own branding - we need to delete this until ubuntu makes this a
# configurable paramter
package 'openstack-dashboard-ubuntu-theme' do
action :purge
only_if { platform_family?('debian') }
end
# TODO(shep)
# Horizon has a forced dependency on there being a volume service endpoint in your keystone catalog
# https://answers.launchpad.net/horizon/+question/189551

View File

@ -7,6 +7,7 @@
# Copyright 2012-2013, AT&T Services, Inc.
# Copyright 2013-2014, IBM, Corp.
# Copyright 2014, SUSE Linux, GmbH.
# Copyright 2014, x-ion GmbH.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -21,249 +22,5 @@
# limitations under the License.
#
require 'uri'
class ::Chef::Recipe # rubocop:disable Documentation
include ::Openstack
end
#
# Workaround to install apache2 on a fedora machine with selinux set to enforcing
# TODO(breu): this should move to a subscription of the template from the apache2 recipe
# and it should simply be a restorecon on the configuration file(s) and not
# change the selinux mode
#
execute 'set-selinux-permissive' do
command '/sbin/setenforce Permissive'
action :run
only_if "[ ! -e /etc/httpd/conf/httpd.conf ] && [ -e /etc/redhat-release ] && [ $(/sbin/sestatus | grep -c '^Current mode:.*enforcing') -eq 1 ]"
end
platform_options = node['openstack']['dashboard']['platform']
include_recipe 'apache2'
include_recipe 'apache2::mod_wsgi'
include_recipe 'apache2::mod_rewrite'
include_recipe 'apache2::mod_ssl'
#
# Workaround to re-enable selinux after installing apache on a fedora machine that has
# selinux enabled and is currently permissive and the configuration set to enforcing.
# TODO(breu): get the other one working and this won't be necessary
#
execute 'set-selinux-enforcing' do
command '/sbin/setenforce Enforcing ; restorecon -R /etc/httpd'
action :run
only_if "[ -e /etc/httpd/conf/httpd.conf ] && [ -e /etc/redhat-release ] && [ $(/sbin/sestatus | grep -c '^Current mode:.*permissive') -eq 1 ] && [ $(/sbin/sestatus | grep -c '^Mode from config file:.*enforcing') -eq 1 ]"
end
identity_admin_endpoint = endpoint 'identity-admin'
auth_admin_uri = auth_uri_transform identity_admin_endpoint.to_s, node['openstack']['dashboard']['api']['auth']['version']
identity_endpoint = endpoint 'identity-api'
auth_uri = auth_uri_transform identity_endpoint.to_s, node['openstack']['dashboard']['api']['auth']['version']
db_pass = get_password 'db', 'horizon'
db_info = db 'dashboard'
python_packages = node['openstack']['db']['python_packages'][db_info['service_type']]
# Add dashboard specific database packages
python_packages += Array(node['openstack']['dashboard']['db_python_packages'][db_info['service_type']])
(platform_options['horizon_packages'] + python_packages).each do |pkg|
package pkg do
action :upgrade
options platform_options['package_overrides']
end
end
if node['openstack']['dashboard']['session_backend'] == 'memcached'
platform_options['memcache_python_packages'].each do |pkg|
package pkg
end
end
memcached = memcached_servers
# delete the openstack-dashboard.conf before reload apache2 service on redhat and centos
# since this file is not valid on those platforms for the apache2 service.
file "#{node["apache"]["dir"]}/conf.d/openstack-dashboard.conf" do
action :delete
backup false
only_if { platform_family?('rhel') } # :pragma-foodcritic: ~FC024 - won't fix this
end
template node['openstack']['dashboard']['local_settings_path'] do
source 'local_settings.py.erb'
owner 'root'
group node['openstack']['dashboard']['horizon_group']
mode 00640
sensitive true
variables(
db_pass: db_pass,
db_info: db_info,
auth_uri: auth_uri,
auth_admin_uri: auth_admin_uri,
memcached_servers: memcached
)
notifies :restart, 'service[apache2]', :immediately
end
execute 'openstack-dashboard syncdb' do
cwd node['openstack']['dashboard']['django_path']
environment 'PYTHONPATH' => "/etc/openstack-dashboard:#{node['openstack']['dashboard']['django_path']}:$PYTHONPATH"
command 'python manage.py syncdb --noinput'
action :run
only_if do
node['openstack']['dashboard']['session_backend'] == 'sql' &&
node['openstack']['db']['dashboard']['migrate'] ||
db_info['service_type'] == 'sqlite'
end
end
cert_file = "#{node['openstack']['dashboard']['ssl']['dir']}/certs/#{node['openstack']['dashboard']['ssl']['cert']}"
cert_mode = 00644
cert_owner = 'root'
cert_group = 'root'
if node['openstack']['dashboard']['ssl']['cert_url']
remote_file cert_file do
sensitive true
source node['openstack']['dashboard']['ssl']['cert_url']
mode cert_mode
owner cert_owner
group cert_group
notifies :run, 'execute[restore-selinux-context]', :immediately
end
else
cookbook_file cert_file do
sensitive true
source 'horizon.pem'
mode cert_mode
owner cert_owner
group cert_group
notifies :run, 'execute[restore-selinux-context]', :immediately
end
end
key_file = "#{node['openstack']['dashboard']['ssl']['dir']}/private/#{node['openstack']['dashboard']['ssl']['key']}"
key_mode = 00640
key_owner = 'root'
case node['platform_family']
when 'debian'
key_group = 'ssl-cert'
else
key_group = 'root'
end
if node['openstack']['dashboard']['ssl']['key_url']
remote_file key_file do
sensitive true
source node['openstack']['dashboard']['ssl']['key_url']
mode key_mode
owner key_owner
group key_group
notifies :restart, 'service[apache2]', :immediately
notifies :run, 'execute[restore-selinux-context]', :immediately
end
else
cookbook_file key_file do
sensitive true
source 'horizon.key'
mode key_mode
owner key_owner
group key_group
notifies :run, 'execute[restore-selinux-context]', :immediately
end
end
directory "#{node['openstack']['dashboard']['dash_path']}/local" do
owner 'root'
group node['openstack']['dashboard']['horizon_group']
mode 02770
action :create
end
# make sure this file has correct permission
file node['openstack']['dashboard']['secret_key_path'] do
owner node['openstack']['dashboard']['horizon_user']
group node['openstack']['dashboard']['horizon_group']
mode 00600
# the only time the file should be created is if we have secret_key_content
# set, otherwise let apache create it when someone first accesses the
# dashboard
if node['openstack']['dashboard']['secret_key_content'].nil?
only_if { ::File.exists?(node['openstack']['dashboard']['secret_key_path']) }
else
content node['openstack']['dashboard']['secret_key_content']
notifies :restart, 'service[apache2]'
end
end
# stop apache bitching
directory "#{node["openstack"]["dashboard"]["dash_path"]}/.blackhole" do
owner 'root'
action :create
end
template node['openstack']['dashboard']['apache']['sites-path'] do
source 'dash-site.erb'
owner 'root'
group 'root'
mode 00644
variables(
ssl_cert_file: "#{node["openstack"]["dashboard"]["ssl"]["dir"]}/certs/#{node["openstack"]["dashboard"]["ssl"]["cert"]}",
ssl_key_file: "#{node["openstack"]["dashboard"]["ssl"]["dir"]}/private/#{node["openstack"]["dashboard"]["ssl"]["key"]}"
)
notifies :run, 'execute[restore-selinux-context]', :immediately
notifies :reload, 'service[apache2]', :immediately
end
# ubuntu includes their own branding - we need to delete this until ubuntu makes this a
# configurable paramter
package 'openstack-dashboard-ubuntu-theme' do
action :purge
only_if { platform_family?('debian') }
end
# The `apache_site` provided by the apache2 cookbook
# is not an LWRP. Guards do not apply to definitions.
# http://tickets.opscode.com/browse/CHEF-778
if platform_family?('debian')
apache_site '000-default' do
enable false
end
elsif platform_family?('rhel') then
apache_site 'default' do
enable false
notifies :run, 'execute[restore-selinux-context]', :immediately
end
end
apache_site 'openstack-dashboard' do
enable true
notifies :run, 'execute[restore-selinux-context]', :immediately
notifies :reload, 'service[apache2]', :immediately
end
execute 'restore-selinux-context' do
command 'restorecon -Rv /etc/httpd /etc/pki; chcon -R -t httpd_sys_content_t /usr/share/openstack-dashboard || :'
action :nothing
only_if { platform_family?('fedora') }
end
# TODO(shep)
# Horizon has a forced dependency on there being a volume service endpoint in your keystone catalog
# https://answers.launchpad.net/horizon/+question/189551
include_recipe 'openstack-dashboard::horizon'
include_recipe "openstack-dashboard::#{node['openstack']['dashboard']['server_type']}-server"

View File

@ -1,7 +1,7 @@
# encoding: UTF-8
require_relative 'spec_helper'
describe 'openstack-dashboard::server' do
describe 'openstack-dashboard::apache2-server' do
describe 'redhat' do
@ -20,47 +20,12 @@ describe 'openstack-dashboard::server' do
expect(chef_run).to run_execute(cmd)
end
it 'installs packages' do
expect(chef_run).to upgrade_package('openstack-dashboard')
expect(chef_run).to upgrade_package('MySQL-python')
end
it 'installs db2 python packages if explicitly told' do
node.set['openstack']['db']['dashboard']['service_type'] = 'db2'
%w{python-ibm-db python-ibm-db-django python-ibm-db-sa}.each do |pkg|
expect(chef_run).to upgrade_package(pkg)
end
end
it 'executes set-selinux-enforcing' do
cmd = '/sbin/setenforce Enforcing ; restorecon -R /etc/httpd'
expect(chef_run).to run_execute(cmd)
end
describe 'local_settings' do
let(:file) { chef_run.template('/etc/openstack-dashboard/local_settings') }
it 'has proper owner' do
expect(file.owner).to eq('root')
expect(file.group).to eq('apache')
end
it 'has proper modes' do
expect(sprintf('%o', file.mode)).to eq('640')
end
it 'has urls set' do
[
%r(^LOGIN_URL = '/auth/login/'$),
%r(^LOGOUT_URL = '/auth/logout/'$),
/^LOGIN_REDIRECT_URL = '\/'$/
].each do |line|
expect(chef_run).to render_file(file.name).with_content(line)
end
end
end
describe 'certs' do
let(:crt) { chef_run.cookbook_file('/etc/pki/tls/certs/horizon.pem') }
let(:key) { chef_run.cookbook_file('/etc/pki/tls/private/horizon.key') }
@ -89,11 +54,6 @@ describe 'openstack-dashboard::server' do
expect(chef_run).to delete_file(file)
end
it 'does not remove openstack-dashboard-ubuntu-theme package' do
expect(chef_run).not_to purge_package('openstack-dashboard-ubuntu-theme')
end
it 'does not execute restore-selinux-context' do
cmd = 'restorecon -Rv /etc/httpd /etc/pki; chcon -R -t httpd_sys_content_t /usr/share/openstack-dashboard || :'

View File

@ -0,0 +1,28 @@
# encoding: UTF-8
require_relative 'spec_helper'
describe 'openstack-dashboard::apache2-server' do
describe 'suse' do
let(:runner) { ChefSpec::Runner.new(SUSE_OPTS) }
let(:node) { runner.node }
let(:chef_run) do
runner.converge(described_recipe)
end
include_context 'non_redhat_stubs'
include_context 'dashboard_stubs'
it 'creates .blackhole dir with proper owner' do
dir = '/srv/www/openstack-dashboard/openstack_dashboard/.blackhole'
expect(chef_run.directory(dir).owner).to eq('root')
end
it 'has correct ownership on file with attribute defaults' do
file = chef_run.file('/srv/www/openstack-dashboard/openstack_dashboard/local/.secret_key_store')
expect(file.owner).to eq('wwwrun')
expect(file.group).to eq('www')
end
end
end

404
spec/apache2-server_spec.rb Normal file
View File

@ -0,0 +1,404 @@
# encoding: UTF-8
require_relative 'spec_helper'
shared_examples 'virtualhost port configurator' do |port_attribute_name, port_attribute_value|
let(:virtualhost_directive) { "<VirtualHost \\*:#{port_attribute_value}>" }
before do
node.set['openstack']['dashboard'][port_attribute_name] = port_attribute_value
end
it "sets Listen and NameVirtualHost directives when apache's listen_ports does not include #{port_attribute_value}" do
node.set['apache']['listen_ports'] = [port_attribute_value.to_i + 1]
%w(Listen NameVirtualHost).each do |directive|
expect(chef_run).to render_file(file.name).with_content(/^#{directive} \*:#{port_attribute_value}$/)
end
end
it "does not set Listen and NameVirtualHost directives when apache's listen_ports include #{port_attribute_value}" do
node.set['apache']['listen_ports'] = [port_attribute_value]
chef_run.converge(described_recipe)
%w(Listen NameVirtualHost).each do |directive|
expect(chef_run).not_to render_file(file.name).with_content(/^#{directive} \*:#{port_attribute_value}$/)
end
end
it 'sets the VirtualHost directive' do
expect(chef_run).to render_file(file.name).with_content(/^#{virtualhost_directive}$/)
end
context 'server_hostname' do
it 'sets the value if the server_hostname is present' do
node.set['openstack']['dashboard']['server_hostname'] = 'server_hostname_value'
expect(chef_run).to render_file(file.name).with_content(/^#{virtualhost_directive}\s*ServerName server_hostname_value$/)
end
it 'does not set the value if the server_hostname is not present' do
node.set['openstack']['dashboard']['server_hostname'] = nil
expect(chef_run).not_to render_file(file.name).with_content(/^#{virtualhost_directive}\s*ServerName$/)
end
end
end
describe 'openstack-dashboard::apache2-server' do
describe 'ubuntu' do
let(:runner) { ChefSpec::Runner.new(UBUNTU_OPTS) }
let(:node) { runner.node }
let(:chef_run) do
runner.converge(described_recipe)
end
let(:chef_run_session_sql) do
node.set['openstack']['dashboard']['session_backend'] = 'sql'
runner.converge(described_recipe)
end
include_context 'non_redhat_stubs'
include_context 'dashboard_stubs'
it 'does not execute set-selinux-permissive' do
cmd = '/sbin/setenforce Permissive'
expect(chef_run).not_to run_execute(cmd)
end
it 'installs apache packages' do
expect(chef_run).to include_recipe('apache2')
expect(chef_run).to include_recipe('apache2::mod_wsgi')
expect(chef_run).to include_recipe('apache2::mod_rewrite')
expect(chef_run).to include_recipe('apache2::mod_ssl')
end
it 'does not execute set-selinux-enforcing' do
cmd = '/sbin/setenforce Enforcing ; restorecon -R /etc/httpd'
expect(chef_run).not_to run_execute(cmd)
end
describe 'certs' do
let(:crt) { chef_run.cookbook_file('/etc/ssl/certs/horizon.pem') }
let(:key) { chef_run.cookbook_file('/etc/ssl/private/horizon.key') }
let(:remote_key) { chef_run.remote_file('/etc/ssl/private/horizon.key') }
it 'has proper owner and group' do
expect(crt.owner).to eq('root')
expect(crt.group).to eq('root')
expect(key.owner).to eq('root')
expect(key.group).to eq('ssl-cert')
end
it 'has proper modes' do
expect(sprintf('%o', crt.mode)).to eq('644')
expect(sprintf('%o', key.mode)).to eq('640')
end
it 'has proper sensitvity' do
expect(crt.sensitive).to eq(true)
expect(key.sensitive).to eq(true)
end
it 'notifies restore-selinux-context' do
expect(crt).to notify('execute[restore-selinux-context]').to(:run)
expect(key).to notify('execute[restore-selinux-context]').to(:run)
end
it 'does not download certs if not needed' do
expect(chef_run).not_to create_remote_file('/etc/ssl/certs/horizon.pem')
expect(chef_run).not_to create_remote_file('/etc/ssl/private/horizon.key')
end
it 'downloads certs if needed and restarts apache' do
node.set['openstack']['dashboard']['ssl']['cert_url'] = 'http://server/mycert.pem'
node.set['openstack']['dashboard']['ssl']['key_url'] = 'http://server/mykey.key'
expect(chef_run).to create_remote_file('/etc/ssl/certs/horizon.pem').with(
sensitive: true,
user: 'root',
group: 'root',
mode: 0644
)
expect(chef_run).to create_remote_file('/etc/ssl/private/horizon.key').with(
sensitive: true,
user: 'root',
group: 'ssl-cert',
mode: 0640
)
expect(remote_key).to notify('service[apache2]').to(:restart)
end
end
it 'creates .blackhole dir with proper owner' do
dir = '/usr/share/openstack-dashboard/openstack_dashboard/.blackhole'
expect(chef_run.directory(dir).owner).to eq('root')
end
describe 'openstack-dashboard virtual host' do
let(:file) { chef_run.template('/etc/apache2/sites-available/openstack-dashboard') }
it 'has proper owner' do
expect(file.owner).to eq('root')
expect(file.group).to eq('root')
end
it 'has proper modes' do
expect(sprintf('%o', file.mode)).to eq('644')
end
context 'template content' do
let(:rewrite_ssl_directive) { /^\s*RewriteEngine On\s*RewriteCond \%\{HTTPS\} off$/ }
let(:default_rewrite_rule) { %r(^\s*RewriteRule \^\(\.\*\)\$ https\://%\{HTTP_HOST\}%\{REQUEST_URI\} \[L,R\]$) }
it 'has the default banner' do
node.set['openstack']['dashboard']['custom_template_banner'] = 'custom_template_banner_value'
expect(chef_run).to render_file(file.name).with_content(/^custom_template_banner_value$/)
end
it_should_behave_like 'virtualhost port configurator', 'http_port', 8080
context 'with use_ssl enabled' do
before do
node.set['openstack']['dashboard']['use_ssl'] = true
end
it_should_behave_like 'virtualhost port configurator', 'https_port', 4433
it 'shows rewrite ssl directive' do
expect(chef_run).to render_file(file.name).with_content(rewrite_ssl_directive)
end
context 'rewrite rule' do
it 'shows the default rewrite rule when http_port is 80 and https_port is 443' do
node.set['openstack']['dashboard']['http_port'] = 80
node.set['openstack']['dashboard']['https_port'] = 443
expect(chef_run).to render_file(file.name).with_content(default_rewrite_rule)
end
it 'shows the parameterized rewrite rule when http_port is different from 80' do
https_port_value = 443
node.set['openstack']['dashboard']['http_port'] = 81
node.set['openstack']['dashboard']['https_port'] = https_port_value
expect(chef_run).to render_file(file.name)
.with_content(%r(^\s*RewriteRule \^\(\.\*\)\$ https://%\{SERVER_NAME\}:#{https_port_value}%\{REQUEST_URI\} \[L,R\]$))
end
it 'shows the parameterized rewrite rule when https_port is different from 443' do
https_port_value = 444
node.set['openstack']['dashboard']['http_port'] = 80
node.set['openstack']['dashboard']['https_port'] = https_port_value
expect(chef_run).to render_file(file.name)
.with_content(%r(^\s*RewriteRule \^\(\.\*\)\$ https://%\{SERVER_NAME\}:#{https_port_value}%\{REQUEST_URI\} \[L,R\]$))
end
end
it 'shows ssl certificate related directives defaults' do
[/^\s*SSLEngine on$/,
%r(^\s*SSLCertificateFile /etc/ssl/certs/horizon.pem$),
%r(^\s*SSLCertificateKeyFile /etc/ssl/private/horizon.key$),
/^\s*SSLProtocol All -SSLv2 -SSLv3$/].each do |ssl_certificate_directive|
expect(chef_run).to render_file(file.name).with_content(ssl_certificate_directive)
end
end
it 'shows ssl certificate related directives overrides' do
node.set['openstack']['dashboard']['ssl']['dir'] = 'ssl_dir_value'
node.set['openstack']['dashboard']['ssl']['cert'] = 'ssl_cert_value'
node.set['openstack']['dashboard']['ssl']['key'] = 'ssl_key_value'
node.set['openstack']['dashboard']['ssl']['protocol'] = 'ssl_protocol_value'
[/^\s*SSLEngine on$/,
%r(^\s*SSLCertificateFile ssl_dir_value/certs/ssl_cert_value$),
%r(^\s*SSLCertificateKeyFile ssl_dir_value/private/ssl_key_value$),
/^\s*SSLProtocol ssl_protocol_value$/].each do |ssl_certificate_directive|
expect(chef_run).to render_file(file.name).with_content(ssl_certificate_directive)
end
end
end
context 'with use_ssl disabled' do
before do
node.set['openstack']['dashboard']['use_ssl'] = false
end
it 'does not show rewrite ssl directive' do
expect(chef_run).not_to render_file(file.name).with_content(rewrite_ssl_directive)
end
it 'does not show the default rewrite rule' do
node.set['openstack']['dashboard']['http_port'] = 80
node.set['openstack']['dashboard']['https_port'] = 443
expect(chef_run).not_to render_file(file.name).with_content(default_rewrite_rule)
end
it 'does not show ssl certificate related directives' do
[/^\s*SSLEngine on$/,
/^\s*SSLCertificateFile/,
/^\s*SSLCertificateKeyFile/].each do |ssl_certificate_directive|
expect(chef_run).not_to render_file(file.name).with_content(ssl_certificate_directive)
end
end
end
it 'shows the ServerAdmin' do
node.set['apache']['contact'] = 'apache_contact_value'
expect(chef_run).to render_file(file.name).with_content(/\s*ServerAdmin apache_contact_value$/)
end
it 'sets the WSGI script alias defaults' do
expect(chef_run).to render_file(file.name).with_content(%r(^\s*WSGIScriptAlias / /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi$))
end
it 'sets the WSGI script alias' do
node.set['openstack']['dashboard']['wsgi_path'] = 'wsgi_path_value'
node.set['openstack']['dashboard']['webroot'] = 'root'
expect(chef_run).to render_file(file.name).with_content(/^\s*WSGIScriptAlias root wsgi_path_value$/)
end
it 'sets the WSGI daemon process' do
node.set['openstack']['dashboard']['horizon_user'] = 'horizon_user_value'
node.set['openstack']['dashboard']['horizon_group'] = 'horizon_group_value'
node.set['openstack']['dashboard']['dash_path'] = 'dash_path_value'
expect(chef_run).to render_file(file.name).with_content(
/^\s*WSGIDaemonProcess dashboard user=horizon_user_value group=horizon_group_value processes=3 threads=10 python-path=dash_path_value$/)
end
it 'has the default DocRoot' do
node.set['openstack']['dashboard']['dash_path'] = 'dash_path_value'
expect(chef_run).to render_file(file.name)
.with_content(%r(\s*DocumentRoot dash_path_value/.blackhole/$))
end
it 'has TraceEnable set' do
node.set['openstack']['dashboard']['traceenable'] = 'value'
expect(chef_run).to render_file(file.name)
.with_content(/^ TraceEnable value$/)
end
it 'sets the right Alias path for /static' do
node.set['openstack']['dashboard']['static_path'] = 'static_path_value'
expect(chef_run).to render_file(file.name).with_content(/^\s+Alias \/static static_path_value$/)
end
%w(dash_path static_path).each do |dir_attribute|
it "sets the #{dir_attribute} directory directive" do
node.set['openstack']['dashboard'][dir_attribute] = "#{dir_attribute}_value"
expect(chef_run).to render_file(file.name).with_content(/^\s*<Directory #{dir_attribute}_value>$/)
end
end
context 'log directives' do
before do
node.set['apache']['log_dir'] = 'log_dir_value'
end
it 'sets de ErrorLog directive' do
node.set['openstack']['dashboard']['error_log'] = 'error_log_value'
expect(chef_run).to render_file(file.name).with_content(/^\s*ErrorLog log_dir_value\/error_log_value$/)
end
it 'sets de CustomLog directive' do
node.set['openstack']['dashboard']['access_log'] = 'access_log_value'
expect(chef_run).to render_file(file.name).with_content(/^\s*CustomLog log_dir_value\/access_log_value combined$/)
end
end
it 'sets wsgi socket prefix if wsgi_socket_prefix attribute is preset' do
node.set['openstack']['dashboard']['wsgi_socket_prefix'] = '/var/run/wsgi'
expect(chef_run).to render_file(file.name).with_content(%r(^WSGISocketPrefix /var/run/wsgi$))
end
it 'omits wsgi socket prefix if wsgi_socket_prefix attribute is not preset' do
node.set['openstack']['dashboard']['wsgi_socket_prefix'] = nil
expect(chef_run).not_to render_file(file.name).with_content(/^WSGISocketPrefix $/)
end
end
it 'notifies restore-selinux-context' do
expect(file).to notify('execute[restore-selinux-context]').to(:run)
end
end
describe 'secret_key_path file' do
secret_key_path = '/var/lib/openstack-dashboard/secret_key'
let(:file) { chef_run.file(secret_key_path) }
it 'has correct ownership' do
expect(file.owner).to eq('horizon')
expect(file.group).to eq('horizon')
end
it 'has correct mode' do
expect(file.mode).to eq(00600)
end
it 'does not notify apache2 restart' do
expect(file).not_to notify('service[apache2]').to(:restart)
end
it 'has configurable path and ownership settings' do
node.set['openstack']['dashboard']['secret_key_path'] = 'somerandompath'
node.set['openstack']['dashboard']['horizon_user'] = 'somerandomuser'
node.set['openstack']['dashboard']['horizon_group'] = 'somerandomgroup'
file = chef_run.file('somerandompath')
expect(file.owner).to eq('somerandomuser')
expect(file.group).to eq('somerandomgroup')
end
describe 'secret_key_content set' do
before do
node.set['openstack']['dashboard']['secret_key_content'] = 'somerandomcontent'
end
it 'has configurable secret_key_content setting' do
expect(chef_run).to render_file(file.name).with_content('somerandomcontent')
end
it 'notifies apache2 restart when secret_key_content set' do
expect(file).to notify('service[apache2]').to(:restart)
end
end
end
it 'does not delete openstack-dashboard.conf' do
file = '/etc/httpd/conf.d/openstack-dashboard.conf'
expect(chef_run).not_to delete_file(file)
end
it 'calls apache_site to disable 000-default virtualhost' do
resource = chef_run.find_resource('execute',
'a2dissite 000-default').to_hash
expect(resource).to include(
action: 'run',
params: {
enable: false,
name: '000-default'
}
)
end
it 'calls apache_site to enable openstack-dashboard virtualhost' do
resource = chef_run.find_resource('execute',
'a2ensite openstack-dashboard').to_hash
expect(resource).to include(
action: 'run',
params: {
enable: true,
notifies: [:reload, 'service[apache2]', :immediately],
name: 'openstack-dashboard'
}
)
end
it 'notifies apache2 restart' do
skip 'TODO: how to test when tied to an LWRP'
end
it 'does not execute restore-selinux-context' do
cmd = 'restorecon -Rv /etc/httpd /etc/pki; chcon -R -t httpd_sys_content_t /usr/share/openstack-dashboard || :'
expect(chef_run).not_to run_execute(cmd)
end
end
end

View File

@ -0,0 +1,57 @@
# encoding: UTF-8
require_relative 'spec_helper'
describe 'openstack-dashboard::horizon' do
describe 'redhat' do
let(:runner) { ChefSpec::Runner.new(REDHAT_OPTS) }
let(:node) { runner.node }
let(:chef_run) do
runner.converge('openstack-dashboard::server')
end
include_context 'dashboard_stubs'
include_context 'redhat_stubs'
it 'installs packages' do
expect(chef_run).to upgrade_package('openstack-dashboard')
expect(chef_run).to upgrade_package('MySQL-python')
end
it 'installs db2 python packages if explicitly told' do
node.set['openstack']['db']['dashboard']['service_type'] = 'db2'
%w{python-ibm-db python-ibm-db-django python-ibm-db-sa}.each do |pkg|
expect(chef_run).to upgrade_package(pkg)
end
end
describe 'local_settings' do
let(:file) { chef_run.template('/etc/openstack-dashboard/local_settings') }
it 'has proper owner' do
expect(file.owner).to eq('root')
expect(file.group).to eq('apache')
end
it 'has proper modes' do
expect(sprintf('%o', file.mode)).to eq('640')
end
it 'has urls set' do
[
%r(^LOGIN_URL = '/auth/login/'$),
%r(^LOGOUT_URL = '/auth/logout/'$),
/^LOGIN_REDIRECT_URL = '\/'$/
].each do |line|
expect(chef_run).to render_file(file.name).with_content(line)
end
end
end
it 'does not remove openstack-dashboard-ubuntu-theme package' do
expect(chef_run).not_to purge_package('openstack-dashboard-ubuntu-theme')
end
end
end

View File

@ -1,14 +1,14 @@
# encoding: UTF-8
require_relative 'spec_helper'
describe 'openstack-dashboard::server' do
describe 'openstack-dashboard::horizon' do
describe 'suse' do
let(:runner) { ChefSpec::Runner.new(SUSE_OPTS) }
let(:node) { runner.node }
let(:chef_run) do
runner.converge(described_recipe)
runner.converge('openstack-dashboard::server')
end
include_context 'non_redhat_stubs'
@ -54,11 +54,6 @@ describe 'openstack-dashboard::server' do
expect(chef_run).to render_file(file.name).with_content('autogenerated')
end
it 'creates .blackhole dir with proper owner' do
dir = '/srv/www/openstack-dashboard/openstack_dashboard/.blackhole'
expect(chef_run.directory(dir).owner).to eq('root')
end
it 'does not execute openstack-dashboard syncdb by default' do
cmd = 'python manage.py syncdb --noinput'
expect(chef_run).not_to run_execute(cmd).with(
@ -71,11 +66,5 @@ describe 'openstack-dashboard::server' do
)
end
end
it 'has correct ownership on file with attribute defaults' do
file = chef_run.file('/srv/www/openstack-dashboard/openstack_dashboard/local/.secret_key_store')
expect(file.owner).to eq('wwwrun')
expect(file.group).to eq('www')
end
end
end

519
spec/horizon_spec.rb Normal file
View File

@ -0,0 +1,519 @@
# encoding: UTF-8
require_relative 'spec_helper'
describe 'openstack-dashboard::horizon' do
describe 'ubuntu' do
let(:runner) { ChefSpec::Runner.new(UBUNTU_OPTS) }
let(:node) { runner.node }
let(:chef_run) do
runner.converge('openstack-dashboard::server')
end
let(:chef_run_session_sql) do
node.set['openstack']['dashboard']['session_backend'] = 'sql'
runner.converge('openstack-dashboard::server')
end
include_context 'non_redhat_stubs'
include_context 'dashboard_stubs'
it 'installs packages' do
expect(chef_run).to upgrade_package('lessc')
expect(chef_run).to upgrade_package('openstack-dashboard')
expect(chef_run).to upgrade_package('python-mysqldb')
end
describe 'local_settings.py' do
let(:file) { chef_run.template('/etc/openstack-dashboard/local_settings.py') }
it 'has proper owner' do
expect(file.owner).to eq('root')
expect(file.group).to eq('horizon')
end
it 'has proper modes' do
expect(sprintf('%o', file.mode)).to eq('640')
end
it 'has proper sensitvity' do
expect(file.sensitive).to eq(true)
end
context 'template contents' do
it 'has the customer banner' do
node.set['openstack']['dashboard']['custom_template_banner'] = 'custom_template_banner_value'
expect(chef_run).to render_file(file.name).with_content(/^custom_template_banner_value$/)
end
context 'misc settings' do
before do
node.set['openstack']['dashboard']['misc_local_settings'] = {
'CUSTOM_CONFIG_A' => {
'variable1' => 'value1',
'variable2' => 'value2'
},
'CUSTOM_CONFIG_B' => {
'variable1' => 'value1',
'variable2' => 'value2'
}
}
end
it 'sets misc settings properly' do
[
['CUSTOM_CONFIG_A = {',
' \'variable1\': \'value1\',',
' \'variable2\': \'value2\',',
'}'],
['CUSTOM_CONFIG_B = {',
' \'variable1\': \'value1\',',
' \'variable2\': \'value2\',',
'}']
].each do |content|
expect(chef_run).to render_file(file.name).with_content(build_section(content))
end
end
end
context 'debug setting' do
context 'set to true' do
before do
node.set['openstack']['dashboard']['debug'] = true
end
it 'has a true value for the DEBUG attribute' do
expect(chef_run).to render_file(file.name).with_content(/^DEBUG = True$/)
end
it 'sets the console logging level to DEBUG' do
expect(chef_run).to render_file(file.name).with_content(/^\s*'level': 'DEBUG',$/)
end
end
context 'set to false' do
before do
node.set['openstack']['dashboard']['debug'] = false
end
it 'has a false value for the DEBUG attribute' do
expect(chef_run).to render_file(file.name).with_content(/^DEBUG = False$/)
end
it 'sets the console logging level to INFO' do
expect(chef_run).to render_file(file.name).with_content(/^\s*'level': 'INFO',$/)
end
end
end
context 'config ssl_no_verify' do
context 'set to the default value' do
it 'has a True value for the OPENSTACK_SSL_NO_VERIFY attribute' do
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_SSL_NO_VERIFY = True$/)
end
end
context 'set to False' do
before do
node.set['openstack']['dashboard']['ssl_no_verify'] = 'False'
end
it 'has a False value for the OPENSTACK_SSL_NO_VERIFY attribute' do
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_SSL_NO_VERIFY = False$/)
end
end
end
it 'config ssl_cacert' do
node.set['openstack']['dashboard']['ssl_cacert'] = '/path_to_cacert.pem'
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_SSL_CACERT = '\/path_to_cacert.pem'$/)
end
it 'has some allowed hosts set' do
node.set['openstack']['dashboard']['allowed_hosts'] = ['dashboard.example.net']
expect(chef_run).to render_file(file.name).with_content(/^ALLOWED_HOSTS = \["dashboard.example.net"\]$/)
end
context 'config hash_algorithm' do
context 'set to the default value' do
it 'has the default value for the OPENSTACK_TOKEN_HASH_ALGORITHM attribute' do
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_TOKEN_HASH_ALGORITHM = 'md5'$/)
end
end
context 'set to sha256' do
before do
node.set['openstack']['dashboard']['hash_algorithm'] = 'sha256'
end
it 'has a sha256 value for the OPENSTACK_TOKEN_HASH_ALGORITHM attribute' do
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_TOKEN_HASH_ALGORITHM = 'sha256'$/)
end
end
end
context 'ssl offload' do
let(:secure_proxy_string) { 'SECURE_PROXY_SSL_HEADER = \(\'HTTP_X_FORWARDED_PROTOCOL\', \'https\'\)' }
it 'does not configure ssl proxy when ssl_offload is false' do
node.set['openstack']['dashboard']['ssl_offload'] = false
expect(chef_run).not_to render_file(file.name).with_content(/^#{secure_proxy_string}$/)
end
it 'configures ssl proxy when ssl_offload is set to true' do
node.set['openstack']['dashboard']['ssl_offload'] = true
expect(chef_run).to render_file(file.name).with_content(/^#{secure_proxy_string}$/)
end
end
context 'temp dir override' do
context 'temp dir is nil' do
it 'does not override temp dir when it is nil' do
node.set['openstack']['dashboard']['file_upload_temp_dir'] = nil
expect(chef_run).not_to render_file(file.name).with_content(/^FILE_UPLOAD_TEMP_DIR =/)
end
it 'does override temp dir when it is not nil' do
node.set['openstack']['dashboard']['file_upload_temp_dir'] = '/foobar'
expect(chef_run).to render_file(file.name).with_content(/^FILE_UPLOAD_TEMP_DIR = "\/foobar"$/)
end
end
end
context 'ssl settings' do
context 'use_ssl enabled' do
before do
node.set['openstack']['dashboard']['use_ssl'] = true
end
context 'csrf_cookie_secure setting' do
it 'sets secure csrf cookie to true when the attribute is enabled' do
node.set['openstack']['dashboard']['csrf_cookie_secure'] = true
expect(chef_run).to render_file(file.name).with_content(/^CSRF_COOKIE_SECURE = True$/)
end
it 'sets secure csrf cookie to false when the attribute is disabled' do
node.set['openstack']['dashboard']['csrf_cookie_secure'] = false
expect(chef_run).to render_file(file.name).with_content(/^CSRF_COOKIE_SECURE = False$/)
end
end
context 'session_cookie_secure setting' do
it 'set secure csrf cookie to true when the sttribute is enabled' do
node.set['openstack']['dashboard']['session_cookie_secure'] = true
expect(chef_run).to render_file(file.name).with_content(/^SESSION_COOKIE_SECURE = True$/)
end
it 'set secure csrf cookie to false when the sttribute is disabled' do
node.set['openstack']['dashboard']['session_cookie_secure'] = false
expect(chef_run).to render_file(file.name).with_content(/^SESSION_COOKIE_SECURE = False$/)
end
end
end
it 'does not set secure csrf nor secure session cookie settings when use_ssl is disabled' do
node.set['openstack']['dashboard']['use_ssl'] = false
[/^CSRF_COOKIE_SECURE$/, /^SESSION_COOKIE_SECURE$/].each do |setting|
expect(chef_run).not_to render_file(file.name).with_content(setting)
end
end
end
it 'does not have urls set' do
[
/^LOGIN_URL =$/,
/^LOGOUT_URL =$/,
/^LOGIN_REDIRECT_URL =$/
].each do |line|
expect(chef_run).to_not render_file(file.name).with_content(line)
end
end
context 'identity and volume api version setting' do
it 'is configurable directly' do
node.set['openstack']['dashboard']['identity_api_version'] = 'identity_api_version_value'
node.set['openstack']['dashboard']['volume_api_version'] = 'volume_api_version_value'
[
/^\s*"identity": identity_api_version_value,$/,
/^\s*"volume": volume_api_version_value$/
].each do |line|
expect(chef_run).to render_file(file.name).with_content(line)
end
end
it 'sets the proper value for identity v2.0 with volume default v2 from common attributes' do
node.set['openstack']['api']['auth']['version'] = 'v2.0'
[
/^\s*"identity": 2\.0,$/,
/^\s*"volume": 2$/
].each do |line|
expect(chef_run).to render_file(file.name).with_content(line)
end
end
it 'sets the proper value for identity v3.0 with volume default v2 from common attributes' do
node.set['openstack']['api']['auth']['version'] = 'v3.0'
[
/^\s*"identity": 3,$/,
/^\s*"volume": 2$/
].each do |line|
expect(chef_run).to render_file(file.name).with_content(line)
end
end
end
context 'keystone multidomain support' do
it 'sets to true when the attribute is enabled' do
node.set['openstack']['dashboard']['keystone_multidomain_support'] = true
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True$/)
end
it 'sets to false when the attribute is disabled' do
node.set['openstack']['dashboard']['keystone_multidomain_support'] = false
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = False$/)
end
end
it 'has a keystone default domain setting if identity api version is 3' do
node.set['openstack']['dashboard']['identity_api_version'] = 3
node.set['openstack']['dashboard']['keystone_default_domain'] = 'keystone_default_domain_value'
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "keystone_default_domain_value"$/)
end
it 'has a console_type setting' do
node.set['openstack']['dashboard']['console_type'] = 'console_type_value'
expect(chef_run).to render_file(file.name).with_content(/^CONSOLE_TYPE = "console_type_value"$/)
end
it 'has a help_url setting' do
node.set['openstack']['dashboard']['help_url'] = 'help_url_value'
expect(chef_run).to render_file(file.name).with_content(/\s*'help_url': "help_url_value",$/)
end
context 'simple ip management' do
it 'enables the setting when the attribute is set' do
node.set['openstack']['dashboard']['simple_ip_management'] = true
expect(chef_run).to render_file(file.name).with_content('HORIZON_CONFIG["simple_ip_management"] = True')
end
it 'disables the setting when the attribute is not set' do
node.set['openstack']['dashboard']['simple_ip_management'] = false
expect(chef_run).to render_file(file.name).with_content('HORIZON_CONFIG["simple_ip_management"] = False')
end
end
it 'has default password_autocomplete setting' do
node.set['openstack']['dashboard']['password_autocomplete'] = 'password_autocomplete_value'
expect(chef_run).to render_file(file.name).with_content(/^HORIZON_CONFIG\["password_autocomplete"\] = "password_autocomplete_value"$/)
end
it 'has configurable secret_key_path setting' do
node.set['openstack']['dashboard']['secret_key_path'] = 'secret_key_path_value'
expect(chef_run).to render_file(file.name).with_content(/^SECRET_KEY = secret_key.generate_or_read_from_file\(os.path.realpath\('secret_key_path_value'\)\)$/)
end
context 'session backend' do
it 'sets the session engine to file when it is the session backend' do
node.set['openstack']['dashboard']['session_backend'] = 'file'
expect(chef_run).to render_file(file.name).with_content(/^SESSION_ENGINE = 'django.contrib.sessions.backends.file'$/)
end
context 'memcached as session backend' do
let(:memcached_session_engine_setting) { /^SESSION_ENGINE = 'django.contrib.sessions.backends.cache'$/ }
context 'with memcache servers' do
it 'sets the session engine attribute' do
expect(chef_run).to render_file(file.name).with_content(memcached_session_engine_setting)
end
it 'sets the location of the caches to the memcached servers addresses' do
expect(chef_run).to render_file(file.name).with_content(/^\s*'LOCATION': \[\s*'hostA:port',\s*'hostB:port',\s*\]$/)
end
end
context 'without memcache servers' do
[nil, []].each do |empty_value|
it "does not configure caching when backend == memcache and #{empty_value} provided as memcache servers" do
allow_any_instance_of(Chef::Recipe).to receive(:memcached_servers)
.and_return(empty_value)
expect(chef_run).not_to render_file(file.name)
.with_content(memcached_session_engine_setting)
end
end
end
end
it 'sets the session engine to db when sql is the session backend' do
node.set['openstack']['dashboard']['session_backend'] = 'sql'
expect(chef_run).to render_file(file.name).with_content(/^SESSION_ENGINE = 'django.contrib.sessions.backends.db'$/)
end
end
it 'has a keystone url' do
expect(chef_run).to render_file(file.name).with_content(%r(OPENSTACK_KEYSTONE_URL = "http://127.0.0.1:5000/v2.0"))
end
it 'has a keystone admin url' do
expect(chef_run).to render_file(file.name).with_content(%r(OPENSTACK_KEYSTONE_ADMIN_URL = "http://127.0.0.1:35357/v2.0"))
end
it 'has a keystone default role' do
node.set['openstack']['dashboard']['keystone_default_role'] = 'keystone_default_role_value'
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_KEYSTONE_DEFAULT_ROLE = "keystone_default_role_value"$/)
end
context 'keystone_backend settings' do
%w(native ldap).each do |keystone_backend_name|
it "sets the backend name to #{keystone_backend_name}" do
node.set['openstack']['dashboard']['keystone_backend']['name'] = keystone_backend_name
expect(chef_run).to render_file(file.name).with_content(/^\s*'name': '#{keystone_backend_name}',$/)
end
end
%w(can_edit_user can_edit_group can_edit_project can_edit_domain can_edit_role).each do |keystone_setting|
it "enables the #{keystone_setting} keystone backend setting when the attribute is True" do
node.set['openstack']['dashboard']['keystone_backend'][keystone_setting] = true
expect(chef_run).to render_file(file.name).with_content(/^\s*\'#{keystone_setting}\': True,$/)
end
it "disables the #{keystone_setting} keystone backend setting when the attribute is False" do
node.set['openstack']['dashboard']['keystone_backend'][keystone_setting] = false
expect(chef_run).to render_file(file.name).with_content(/^\s*\'#{keystone_setting}\': False,$/)
end
end
end
context 'neutron settings' do
%w(enable_lb enable_quotas enable_firewall enable_vpn).each do |neutron_setting|
it "enables the #{neutron_setting} setting when the attributes is True" do
node.set['openstack']['dashboard']['neutron'][neutron_setting] = true
expect(chef_run).to render_file(file.name).with_content(/^\s*\'#{neutron_setting}\': True,$/)
end
it "disables the #{neutron_setting} setting when the attributes is False" do
node.set['openstack']['dashboard']['neutron'][neutron_setting] = false
expect(chef_run).to render_file(file.name).with_content(/^\s*\'#{neutron_setting}\': False,$/)
end
end
end
%w(horizon openstack_dashboard novaclient cinderclient keystoneclient
glanceclient neutronclient heatclient ceilometerclient troveclient
swiftclient openstack_auth nose.plugins.manager django).each do |component|
it "sets the logger level for #{component}" do
node.set['openstack']['dashboard']['log_level'][component] = "#{component}_log_level_value"
expect(chef_run).to render_file(file.name).with_content(
/^\s*'#{component}': {\s*'handlers': \['console'\],\s*'level': '#{component}_log_level_value',$/)
end
end
{ 'mysql' => 'django.db.backends.mysql',
'sqlite' => 'django.db.backends.sqlite3',
'postgresql' => 'django.db.backends.postgresql_psycopg2',
'db2' => 'ibm_db_django' }.each do |service_type, backend|
context "#{service_type} database settings" do
before do
allow_any_instance_of(Chef::Recipe).to receive(:db)
.with('dashboard')
.and_return('service_type' => service_type,
'db_name' => "#{service_type}_db",
'host' => "#{service_type}_host")
node.set['openstack']['db']['dashboard']['username'] = "#{service_type}_user"
node.set['openstack']['db']['python_packages'][service_type] = ['pkg1', 'pkg2']
end
[/^\s*'ENGINE': '#{backend}',$/,
/^\s*'NAME': '#{service_type}_db',$/].each do |cfg|
it "configures the #{service_type} backend with #{cfg}" do
expect(chef_run).to render_file(file.name).with_content(cfg)
end
end
[/^\s*'USER': '#{service_type}_user',$/,
/^\s*'PASSWORD': 'test-passes',$/,
/^\s*'HOST': '#{service_type}_host',$/].each do |cfg|
unless service_type == 'sqlite'
it "configures the #{service_type} backend with #{cfg}" do
expect(chef_run).to render_file(file.name).with_content(cfg)
end
end
end
end
end
context 'plugins' do
let(:mod_regex) { /^mod = sys.modules\['openstack_dashboard.settings'\]$/ }
context 'plugins enabled' do
let(:plugins) { %w(testPlugin1 testPlugin2) }
before do
node.set['openstack']['dashboard']['plugins'] = plugins
end
it 'shows the mod setting' do
expect(chef_run).to render_file(file.name).with_content(mod_regex)
end
it 'shows enabled plugins as installed apps' do
plugins.each do |plugin|
expect(chef_run).to render_file(file.name).with_content(/^mod\.INSTALLED_APPS \+= \('#{plugin}', \)$/)
end
end
end
it 'does not show the mod setting if there are no plugins' do
node.set['openstack']['dashboard']['plugins'] = nil
expect(chef_run).not_to render_file(file.name).with_content(mod_regex)
end
end
end
end
describe 'openstack-dashboard syncdb' do
sync_db_cmd = 'python manage.py syncdb --noinput'
sync_db_environment = {
'PYTHONPATH' => '/etc/openstack-dashboard:' \
'/usr/share/openstack-dashboard:' \
'$PYTHONPATH'
}
it 'does not execute when session_backend is not sql' do
expect(chef_run).not_to run_execute(sync_db_cmd).with(
cwd: node['openstack']['dashboard']['django_path'],
environment: sync_db_environment
)
end
it 'executes when session_backend is sql' do
expect(chef_run_session_sql).to run_execute(sync_db_cmd).with(
cwd: node['openstack']['dashboard']['django_path'],
environment: sync_db_environment
)
end
it 'does not execute when the migrate attribute is set to false' do
node.set['openstack']['db']['dashboard']['migrate'] = false
expect(chef_run_session_sql).not_to run_execute(sync_db_cmd).with(
cwd: node['openstack']['dashboard']['django_path'],
environment: sync_db_environment
)
end
it 'executes when database backend is sqlite' do
node.set['openstack']['db']['dashboard']['service_type'] = 'sqlite'
expect(chef_run_session_sql).to run_execute(sync_db_cmd).with(
cwd: node['openstack']['dashboard']['django_path'],
environment: sync_db_environment
)
end
end
it 'removes openstack-dashboard-ubuntu-theme package' do
expect(chef_run).to purge_package('openstack-dashboard-ubuntu-theme')
end
it 'has group write mode on path' do
path = chef_run.directory("#{chef_run.node['openstack']['dashboard']['dash_path']}/local")
expect(path.mode).to eq(02770)
expect(path.group).to eq(chef_run.node['openstack']['dashboard']['horizon_group'])
end
end
end

View File

@ -1,905 +1,23 @@
# encoding: UTF-8
require_relative 'spec_helper'
shared_examples 'virtualhost port configurator' do |port_attribute_name, port_attribute_value|
let(:virtualhost_directive) { "<VirtualHost \\*:#{port_attribute_value}>" }
before do
node.set['openstack']['dashboard'][port_attribute_name] = port_attribute_value
end
it "sets Listen and NameVirtualHost directives when apache's listen_ports does not include #{port_attribute_value}" do
node.set['apache']['listen_ports'] = [port_attribute_value.to_i + 1]
%w(Listen NameVirtualHost).each do |directive|
expect(chef_run).to render_file(file.name).with_content(/^#{directive} \*:#{port_attribute_value}$/)
end
end
it "does not set Listen and NameVirtualHost directives when apache's listen_ports include #{port_attribute_value}" do
node.set['apache']['listen_ports'] = [port_attribute_value]
chef_run.converge(described_recipe)
%w(Listen NameVirtualHost).each do |directive|
expect(chef_run).not_to render_file(file.name).with_content(/^#{directive} \*:#{port_attribute_value}$/)
end
end
it 'sets the VirtualHost directive' do
expect(chef_run).to render_file(file.name).with_content(/^#{virtualhost_directive}$/)
end
context 'server_hostname' do
it 'sets the value if the server_hostname is present' do
node.set['openstack']['dashboard']['server_hostname'] = 'server_hostname_value'
expect(chef_run).to render_file(file.name).with_content(/^#{virtualhost_directive}\s*ServerName server_hostname_value$/)
end
it 'does not set the value if the server_hostname is not present' do
node.set['openstack']['dashboard']['server_hostname'] = nil
expect(chef_run).not_to render_file(file.name).with_content(/^#{virtualhost_directive}\s*ServerName$/)
end
end
end
describe 'openstack-dashboard::server' do
describe 'ubuntu' do
let(:runner) { ChefSpec::Runner.new(UBUNTU_OPTS) }
let(:node) { runner.node }
let(:chef_run) do
runner.converge(described_recipe)
end
let(:chef_run_session_sql) do
node.set['openstack']['dashboard']['session_backend'] = 'sql'
runner.converge(described_recipe)
end
include_context 'non_redhat_stubs'
include_context 'dashboard_stubs'
it 'does not execute set-selinux-permissive' do
cmd = '/sbin/setenforce Permissive'
expect(chef_run).not_to run_execute(cmd)
end
it 'installs apache packages' do
expect(chef_run).to include_recipe('apache2')
expect(chef_run).to include_recipe('apache2::mod_wsgi')
expect(chef_run).to include_recipe('apache2::mod_rewrite')
expect(chef_run).to include_recipe('apache2::mod_ssl')
end
it 'does not execute set-selinux-enforcing' do
cmd = '/sbin/setenforce Enforcing ; restorecon -R /etc/httpd'
expect(chef_run).not_to run_execute(cmd)
end
it 'installs packages' do
expect(chef_run).to upgrade_package('lessc')
expect(chef_run).to upgrade_package('openstack-dashboard')
expect(chef_run).to upgrade_package('python-mysqldb')
end
describe 'local_settings.py' do
let(:file) { chef_run.template('/etc/openstack-dashboard/local_settings.py') }
it 'has proper owner' do
expect(file.owner).to eq('root')
expect(file.group).to eq('horizon')
end
it 'has proper modes' do
expect(sprintf('%o', file.mode)).to eq('640')
end
it 'has proper sensitvity' do
expect(file.sensitive).to eq(true)
end
context 'template contents' do
it 'has the customer banner' do
node.set['openstack']['dashboard']['custom_template_banner'] = 'custom_template_banner_value'
expect(chef_run).to render_file(file.name).with_content(/^custom_template_banner_value$/)
end
context 'misc settings' do
before do
node.set['openstack']['dashboard']['misc_local_settings'] = {
'CUSTOM_CONFIG_A' => {
'variable1' => 'value1',
'variable2' => 'value2'
},
'CUSTOM_CONFIG_B' => {
'variable1' => 'value1',
'variable2' => 'value2'
}
}
end
it 'sets misc settings properly' do
[
['CUSTOM_CONFIG_A = {',
' \'variable1\': \'value1\',',
' \'variable2\': \'value2\',',
'}'],
['CUSTOM_CONFIG_B = {',
' \'variable1\': \'value1\',',
' \'variable2\': \'value2\',',
'}']
].each do |content|
expect(chef_run).to render_file(file.name).with_content(build_section(content))
end
end
end
context 'debug setting' do
context 'set to true' do
before do
node.set['openstack']['dashboard']['debug'] = true
end
it 'has a true value for the DEBUG attribute' do
expect(chef_run).to render_file(file.name).with_content(/^DEBUG = True$/)
end
it 'sets the console logging level to DEBUG' do
expect(chef_run).to render_file(file.name).with_content(/^\s*'level': 'DEBUG',$/)
end
end
context 'set to false' do
before do
node.set['openstack']['dashboard']['debug'] = false
end
it 'has a false value for the DEBUG attribute' do
expect(chef_run).to render_file(file.name).with_content(/^DEBUG = False$/)
end
it 'sets the console logging level to INFO' do
expect(chef_run).to render_file(file.name).with_content(/^\s*'level': 'INFO',$/)
end
end
end
context 'config ssl_no_verify' do
context 'set to the default value' do
it 'has a True value for the OPENSTACK_SSL_NO_VERIFY attribute' do
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_SSL_NO_VERIFY = True$/)
end
end
context 'set to False' do
before do
node.set['openstack']['dashboard']['ssl_no_verify'] = 'False'
end
it 'has a False value for the OPENSTACK_SSL_NO_VERIFY attribute' do
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_SSL_NO_VERIFY = False$/)
end
end
end
it 'config ssl_cacert' do
node.set['openstack']['dashboard']['ssl_cacert'] = '/path_to_cacert.pem'
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_SSL_CACERT = '\/path_to_cacert.pem'$/)
end
it 'has some allowed hosts set' do
node.set['openstack']['dashboard']['allowed_hosts'] = ['dashboard.example.net']
expect(chef_run).to render_file(file.name).with_content(/^ALLOWED_HOSTS = \["dashboard.example.net"\]$/)
end
context 'config hash_algorithm' do
context 'set to the default value' do
it 'has the default value for the OPENSTACK_TOKEN_HASH_ALGORITHM attribute' do
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_TOKEN_HASH_ALGORITHM = 'md5'$/)
end
end
context 'set to sha256' do
before do
node.set['openstack']['dashboard']['hash_algorithm'] = 'sha256'
end
it 'has a sha256 value for the OPENSTACK_TOKEN_HASH_ALGORITHM attribute' do
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_TOKEN_HASH_ALGORITHM = 'sha256'$/)
end
end
end
context 'ssl offload' do
let(:secure_proxy_string) { 'SECURE_PROXY_SSL_HEADER = \(\'HTTP_X_FORWARDED_PROTOCOL\', \'https\'\)' }
it 'does not configure ssl proxy when ssl_offload is false' do
node.set['openstack']['dashboard']['ssl_offload'] = false
expect(chef_run).not_to render_file(file.name).with_content(/^#{secure_proxy_string}$/)
end
it 'configures ssl proxy when ssl_offload is set to true' do
node.set['openstack']['dashboard']['ssl_offload'] = true
expect(chef_run).to render_file(file.name).with_content(/^#{secure_proxy_string}$/)
end
end
context 'temp dir override' do
context 'temp dir is nil' do
it 'does not override temp dir when it is nil' do
node.set['openstack']['dashboard']['file_upload_temp_dir'] = nil
expect(chef_run).not_to render_file(file.name).with_content(/^FILE_UPLOAD_TEMP_DIR =/)
end
it 'does override temp dir when it is not nil' do
node.set['openstack']['dashboard']['file_upload_temp_dir'] = '/foobar'
expect(chef_run).to render_file(file.name).with_content(/^FILE_UPLOAD_TEMP_DIR = "\/foobar"$/)
end
end
end
context 'ssl settings' do
context 'use_ssl enabled' do
before do
node.set['openstack']['dashboard']['use_ssl'] = true
end
context 'csrf_cookie_secure setting' do
it 'sets secure csrf cookie to true when the attribute is enabled' do
node.set['openstack']['dashboard']['csrf_cookie_secure'] = true
expect(chef_run).to render_file(file.name).with_content(/^CSRF_COOKIE_SECURE = True$/)
end
it 'sets secure csrf cookie to false when the attribute is disabled' do
node.set['openstack']['dashboard']['csrf_cookie_secure'] = false
expect(chef_run).to render_file(file.name).with_content(/^CSRF_COOKIE_SECURE = False$/)
end
end
context 'session_cookie_secure setting' do
it 'set secure csrf cookie to true when the sttribute is enabled' do
node.set['openstack']['dashboard']['session_cookie_secure'] = true
expect(chef_run).to render_file(file.name).with_content(/^SESSION_COOKIE_SECURE = True$/)
end
it 'set secure csrf cookie to false when the sttribute is disabled' do
node.set['openstack']['dashboard']['session_cookie_secure'] = false
expect(chef_run).to render_file(file.name).with_content(/^SESSION_COOKIE_SECURE = False$/)
end
end
end
it 'does not set secure csrf nor secure session cookie settings when use_ssl is disabled' do
node.set['openstack']['dashboard']['use_ssl'] = false
[/^CSRF_COOKIE_SECURE$/, /^SESSION_COOKIE_SECURE$/].each do |setting|
expect(chef_run).not_to render_file(file.name).with_content(setting)
end
end
end
it 'does not have urls set' do
[
/^LOGIN_URL =$/,
/^LOGOUT_URL =$/,
/^LOGIN_REDIRECT_URL =$/
].each do |line|
expect(chef_run).to_not render_file(file.name).with_content(line)
end
end
context 'identity and volume api version setting' do
it 'is configurable directly' do
node.set['openstack']['dashboard']['identity_api_version'] = 'identity_api_version_value'
node.set['openstack']['dashboard']['volume_api_version'] = 'volume_api_version_value'
[
/^\s*"identity": identity_api_version_value,$/,
/^\s*"volume": volume_api_version_value$/
].each do |line|
expect(chef_run).to render_file(file.name).with_content(line)
end
end
it 'sets the proper value for identity v2.0 with volume default v2 from common attributes' do
node.set['openstack']['api']['auth']['version'] = 'v2.0'
[
/^\s*"identity": 2\.0,$/,
/^\s*"volume": 2$/
].each do |line|
expect(chef_run).to render_file(file.name).with_content(line)
end
end
it 'sets the proper value for identity v3.0 with volume default v2 from common attributes' do
node.set['openstack']['api']['auth']['version'] = 'v3.0'
[
/^\s*"identity": 3,$/,
/^\s*"volume": 2$/
].each do |line|
expect(chef_run).to render_file(file.name).with_content(line)
end
end
end
context 'keystone multidomain support' do
it 'sets to true when the attribute is enabled' do
node.set['openstack']['dashboard']['keystone_multidomain_support'] = true
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True$/)
end
it 'sets to false when the attribute is disabled' do
node.set['openstack']['dashboard']['keystone_multidomain_support'] = false
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = False$/)
end
end
it 'has a keystone default domain setting if identity api version is 3' do
node.set['openstack']['dashboard']['identity_api_version'] = 3
node.set['openstack']['dashboard']['keystone_default_domain'] = 'keystone_default_domain_value'
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "keystone_default_domain_value"$/)
end
it 'has a console_type setting' do
node.set['openstack']['dashboard']['console_type'] = 'console_type_value'
expect(chef_run).to render_file(file.name).with_content(/^CONSOLE_TYPE = "console_type_value"$/)
end
it 'has a help_url setting' do
node.set['openstack']['dashboard']['help_url'] = 'help_url_value'
expect(chef_run).to render_file(file.name).with_content(/\s*'help_url': "help_url_value",$/)
end
context 'simple ip management' do
it 'enables the setting when the attribute is set' do
node.set['openstack']['dashboard']['simple_ip_management'] = true
expect(chef_run).to render_file(file.name).with_content('HORIZON_CONFIG["simple_ip_management"] = True')
end
it 'disables the setting when the attribute is not set' do
node.set['openstack']['dashboard']['simple_ip_management'] = false
expect(chef_run).to render_file(file.name).with_content('HORIZON_CONFIG["simple_ip_management"] = False')
end
end
it 'has default password_autocomplete setting' do
node.set['openstack']['dashboard']['password_autocomplete'] = 'password_autocomplete_value'
expect(chef_run).to render_file(file.name).with_content(/^HORIZON_CONFIG\["password_autocomplete"\] = "password_autocomplete_value"$/)
end
it 'has configurable secret_key_path setting' do
node.set['openstack']['dashboard']['secret_key_path'] = 'secret_key_path_value'
expect(chef_run).to render_file(file.name).with_content(/^SECRET_KEY = secret_key.generate_or_read_from_file\(os.path.realpath\('secret_key_path_value'\)\)$/)
end
context 'session backend' do
it 'sets the session engine to file when it is the session backend' do
node.set['openstack']['dashboard']['session_backend'] = 'file'
expect(chef_run).to render_file(file.name).with_content(/^SESSION_ENGINE = 'django.contrib.sessions.backends.file'$/)
end
context 'memcached as session backend' do
let(:memcached_session_engine_setting) { /^SESSION_ENGINE = 'django.contrib.sessions.backends.cache'$/ }
context 'with memcache servers' do
it 'sets the session engine attribute' do
expect(chef_run).to render_file(file.name).with_content(memcached_session_engine_setting)
end
it 'sets the location of the caches to the memcached servers addresses' do
expect(chef_run).to render_file(file.name).with_content(/^\s*'LOCATION': \[\s*'hostA:port',\s*'hostB:port',\s*\]$/)
end
end
context 'without memcache servers' do
[nil, []].each do |empty_value|
it "does not configure caching when backend == memcache and #{empty_value} provided as memcache servers" do
allow_any_instance_of(Chef::Recipe).to receive(:memcached_servers)
.and_return(empty_value)
expect(chef_run).not_to render_file(file.name)
.with_content(memcached_session_engine_setting)
end
end
end
end
it 'sets the session engine to db when sql is the session backend' do
node.set['openstack']['dashboard']['session_backend'] = 'sql'
expect(chef_run).to render_file(file.name).with_content(/^SESSION_ENGINE = 'django.contrib.sessions.backends.db'$/)
end
end
it 'has a keystone url' do
expect(chef_run).to render_file(file.name).with_content(%r(OPENSTACK_KEYSTONE_URL = "http://127.0.0.1:5000/v2.0"))
end
it 'has a keystone admin url' do
expect(chef_run).to render_file(file.name).with_content(%r(OPENSTACK_KEYSTONE_ADMIN_URL = "http://127.0.0.1:35357/v2.0"))
end
it 'has a keystone default role' do
node.set['openstack']['dashboard']['keystone_default_role'] = 'keystone_default_role_value'
expect(chef_run).to render_file(file.name).with_content(/^OPENSTACK_KEYSTONE_DEFAULT_ROLE = "keystone_default_role_value"$/)
end
context 'keystone_backend settings' do
%w(native ldap).each do |keystone_backend_name|
it "sets the backend name to #{keystone_backend_name}" do
node.set['openstack']['dashboard']['keystone_backend']['name'] = keystone_backend_name
expect(chef_run).to render_file(file.name).with_content(/^\s*'name': '#{keystone_backend_name}',$/)
end
end
%w(can_edit_user can_edit_group can_edit_project can_edit_domain can_edit_role).each do |keystone_setting|
it "enables the #{keystone_setting} keystone backend setting when the attribute is True" do
node.set['openstack']['dashboard']['keystone_backend'][keystone_setting] = true
expect(chef_run).to render_file(file.name).with_content(/^\s*\'#{keystone_setting}\': True,$/)
end
it "disables the #{keystone_setting} keystone backend setting when the attribute is False" do
node.set['openstack']['dashboard']['keystone_backend'][keystone_setting] = false
expect(chef_run).to render_file(file.name).with_content(/^\s*\'#{keystone_setting}\': False,$/)
end
end
end
context 'neutron settings' do
%w(enable_lb enable_quotas enable_firewall enable_vpn).each do |neutron_setting|
it "enables the #{neutron_setting} setting when the attributes is True" do
node.set['openstack']['dashboard']['neutron'][neutron_setting] = true
expect(chef_run).to render_file(file.name).with_content(/^\s*\'#{neutron_setting}\': True,$/)
end
it "disables the #{neutron_setting} setting when the attributes is False" do
node.set['openstack']['dashboard']['neutron'][neutron_setting] = false
expect(chef_run).to render_file(file.name).with_content(/^\s*\'#{neutron_setting}\': False,$/)
end
end
end
%w(horizon openstack_dashboard novaclient cinderclient keystoneclient
glanceclient neutronclient heatclient ceilometerclient troveclient
swiftclient openstack_auth nose.plugins.manager django).each do |component|
it "sets the logger level for #{component}" do
node.set['openstack']['dashboard']['log_level'][component] = "#{component}_log_level_value"
expect(chef_run).to render_file(file.name).with_content(
/^\s*'#{component}': {\s*'handlers': \['console'\],\s*'level': '#{component}_log_level_value',$/)
end
end
{ 'mysql' => 'django.db.backends.mysql',
'sqlite' => 'django.db.backends.sqlite3',
'postgresql' => 'django.db.backends.postgresql_psycopg2',
'db2' => 'ibm_db_django' }.each do |service_type, backend|
context "#{service_type} database settings" do
before do
allow_any_instance_of(Chef::Recipe).to receive(:db)
.with('dashboard')
.and_return('service_type' => service_type,
'db_name' => "#{service_type}_db",
'host' => "#{service_type}_host")
node.set['openstack']['db']['dashboard']['username'] = "#{service_type}_user"
node.set['openstack']['db']['python_packages'][service_type] = ['pkg1', 'pkg2']
end
[/^\s*'ENGINE': '#{backend}',$/,
/^\s*'NAME': '#{service_type}_db',$/].each do |cfg|
it "configures the #{service_type} backend with #{cfg}" do
expect(chef_run).to render_file(file.name).with_content(cfg)
end
end
[/^\s*'USER': '#{service_type}_user',$/,
/^\s*'PASSWORD': 'test-passes',$/,
/^\s*'HOST': '#{service_type}_host',$/].each do |cfg|
unless service_type == 'sqlite'
it "configures the #{service_type} backend with #{cfg}" do
expect(chef_run).to render_file(file.name).with_content(cfg)
end
end
end
end
end
context 'plugins' do
let(:mod_regex) { /^mod = sys.modules\['openstack_dashboard.settings'\]$/ }
context 'plugins enabled' do
let(:plugins) { %w(testPlugin1 testPlugin2) }
before do
node.set['openstack']['dashboard']['plugins'] = plugins
end
it 'shows the mod setting' do
expect(chef_run).to render_file(file.name).with_content(mod_regex)
end
it 'shows enabled plugins as installed apps' do
plugins.each do |plugin|
expect(chef_run).to render_file(file.name).with_content(/^mod\.INSTALLED_APPS \+= \('#{plugin}', \)$/)
end
end
end
it 'does not show the mod setting if there are no plugins' do
node.set['openstack']['dashboard']['plugins'] = nil
expect(chef_run).not_to render_file(file.name).with_content(mod_regex)
end
end
end
it 'notifies apache2 restart' do
expect(file).to notify('service[apache2]').to(:restart)
end
end
describe 'openstack-dashboard syncdb' do
sync_db_cmd = 'python manage.py syncdb --noinput'
sync_db_environment = {
'PYTHONPATH' => '/etc/openstack-dashboard:' \
'/usr/share/openstack-dashboard:' \
'$PYTHONPATH'
}
it 'does not execute when session_backend is not sql' do
expect(chef_run).not_to run_execute(sync_db_cmd).with(
cwd: node['openstack']['dashboard']['django_path'],
environment: sync_db_environment
)
end
it 'executes when session_backend is sql' do
expect(chef_run_session_sql).to run_execute(sync_db_cmd).with(
cwd: node['openstack']['dashboard']['django_path'],
environment: sync_db_environment
)
end
it 'does not execute when the migrate attribute is set to false' do
node.set['openstack']['db']['dashboard']['migrate'] = false
expect(chef_run_session_sql).not_to run_execute(sync_db_cmd).with(
cwd: node['openstack']['dashboard']['django_path'],
environment: sync_db_environment
)
end
it 'executes when database backend is sqlite' do
node.set['openstack']['db']['dashboard']['service_type'] = 'sqlite'
expect(chef_run_session_sql).to run_execute(sync_db_cmd).with(
cwd: node['openstack']['dashboard']['django_path'],
environment: sync_db_environment
)
end
end
describe 'certs' do
let(:crt) { chef_run.cookbook_file('/etc/ssl/certs/horizon.pem') }
let(:key) { chef_run.cookbook_file('/etc/ssl/private/horizon.key') }
let(:remote_key) { chef_run.remote_file('/etc/ssl/private/horizon.key') }
it 'has proper owner and group' do
expect(crt.owner).to eq('root')
expect(crt.group).to eq('root')
expect(key.owner).to eq('root')
expect(key.group).to eq('ssl-cert')
end
it 'has proper modes' do
expect(sprintf('%o', crt.mode)).to eq('644')
expect(sprintf('%o', key.mode)).to eq('640')
end
it 'has proper sensitvity' do
expect(crt.sensitive).to eq(true)
expect(key.sensitive).to eq(true)
end
it 'notifies restore-selinux-context' do
expect(crt).to notify('execute[restore-selinux-context]').to(:run)
expect(key).to notify('execute[restore-selinux-context]').to(:run)
end
it 'does not download certs if not needed' do
expect(chef_run).not_to create_remote_file('/etc/ssl/certs/horizon.pem')
expect(chef_run).not_to create_remote_file('/etc/ssl/private/horizon.key')
end
it 'downloads certs if needed and restarts apache' do
node.set['openstack']['dashboard']['ssl']['cert_url'] = 'http://server/mycert.pem'
node.set['openstack']['dashboard']['ssl']['key_url'] = 'http://server/mykey.key'
expect(chef_run).to create_remote_file('/etc/ssl/certs/horizon.pem').with(
sensitive: true,
user: 'root',
group: 'root',
mode: 0644
)
expect(chef_run).to create_remote_file('/etc/ssl/private/horizon.key').with(
sensitive: true,
user: 'root',
group: 'ssl-cert',
mode: 0640
)
expect(remote_key).to notify('service[apache2]').to(:restart)
end
end
it 'creates .blackhole dir with proper owner' do
dir = '/usr/share/openstack-dashboard/openstack_dashboard/.blackhole'
expect(chef_run.directory(dir).owner).to eq('root')
end
describe 'openstack-dashboard virtual host' do
let(:file) { chef_run.template('/etc/apache2/sites-available/openstack-dashboard') }
it 'has proper owner' do
expect(file.owner).to eq('root')
expect(file.group).to eq('root')
end
it 'has proper modes' do
expect(sprintf('%o', file.mode)).to eq('644')
end
context 'template content' do
let(:rewrite_ssl_directive) { /^\s*RewriteEngine On\s*RewriteCond \%\{HTTPS\} off$/ }
let(:default_rewrite_rule) { %r(^\s*RewriteRule \^\(\.\*\)\$ https\://%\{HTTP_HOST\}%\{REQUEST_URI\} \[L,R\]$) }
it 'has the default banner' do
node.set['openstack']['dashboard']['custom_template_banner'] = 'custom_template_banner_value'
expect(chef_run).to render_file(file.name).with_content(/^custom_template_banner_value$/)
end
it_should_behave_like 'virtualhost port configurator', 'http_port', 8080
context 'with use_ssl enabled' do
before do
node.set['openstack']['dashboard']['use_ssl'] = true
end
it_should_behave_like 'virtualhost port configurator', 'https_port', 4433
it 'shows rewrite ssl directive' do
expect(chef_run).to render_file(file.name).with_content(rewrite_ssl_directive)
end
context 'rewrite rule' do
it 'shows the default rewrite rule when http_port is 80 and https_port is 443' do
node.set['openstack']['dashboard']['http_port'] = 80
node.set['openstack']['dashboard']['https_port'] = 443
expect(chef_run).to render_file(file.name).with_content(default_rewrite_rule)
end
it 'shows the parameterized rewrite rule when http_port is different from 80' do
https_port_value = 443
node.set['openstack']['dashboard']['http_port'] = 81
node.set['openstack']['dashboard']['https_port'] = https_port_value
expect(chef_run).to render_file(file.name)
.with_content(%r(^\s*RewriteRule \^\(\.\*\)\$ https://%\{SERVER_NAME\}:#{https_port_value}%\{REQUEST_URI\} \[L,R\]$))
end
it 'shows the parameterized rewrite rule when https_port is different from 443' do
https_port_value = 444
node.set['openstack']['dashboard']['http_port'] = 80
node.set['openstack']['dashboard']['https_port'] = https_port_value
expect(chef_run).to render_file(file.name)
.with_content(%r(^\s*RewriteRule \^\(\.\*\)\$ https://%\{SERVER_NAME\}:#{https_port_value}%\{REQUEST_URI\} \[L,R\]$))
end
end
it 'shows ssl certificate related directives defaults' do
[/^\s*SSLEngine on$/,
%r(^\s*SSLCertificateFile /etc/ssl/certs/horizon.pem$),
%r(^\s*SSLCertificateKeyFile /etc/ssl/private/horizon.key$),
/^\s*SSLProtocol All -SSLv2 -SSLv3$/].each do |ssl_certificate_directive|
expect(chef_run).to render_file(file.name).with_content(ssl_certificate_directive)
end
end
it 'shows ssl certificate related directives overrides' do
node.set['openstack']['dashboard']['ssl']['dir'] = 'ssl_dir_value'
node.set['openstack']['dashboard']['ssl']['cert'] = 'ssl_cert_value'
node.set['openstack']['dashboard']['ssl']['key'] = 'ssl_key_value'
node.set['openstack']['dashboard']['ssl']['protocol'] = 'ssl_protocol_value'
[/^\s*SSLEngine on$/,
%r(^\s*SSLCertificateFile ssl_dir_value/certs/ssl_cert_value$),
%r(^\s*SSLCertificateKeyFile ssl_dir_value/private/ssl_key_value$),
/^\s*SSLProtocol ssl_protocol_value$/].each do |ssl_certificate_directive|
expect(chef_run).to render_file(file.name).with_content(ssl_certificate_directive)
end
end
end
context 'with use_ssl disabled' do
before do
node.set['openstack']['dashboard']['use_ssl'] = false
end
it 'does not show rewrite ssl directive' do
expect(chef_run).not_to render_file(file.name).with_content(rewrite_ssl_directive)
end
it 'does not show the default rewrite rule' do
node.set['openstack']['dashboard']['http_port'] = 80
node.set['openstack']['dashboard']['https_port'] = 443
expect(chef_run).not_to render_file(file.name).with_content(default_rewrite_rule)
end
it 'does not show ssl certificate related directives' do
[/^\s*SSLEngine on$/,
/^\s*SSLCertificateFile/,
/^\s*SSLCertificateKeyFile/].each do |ssl_certificate_directive|
expect(chef_run).not_to render_file(file.name).with_content(ssl_certificate_directive)
end
end
end
it 'shows the ServerAdmin' do
node.set['apache']['contact'] = 'apache_contact_value'
expect(chef_run).to render_file(file.name).with_content(/\s*ServerAdmin apache_contact_value$/)
end
it 'sets the WSGI script alias defaults' do
expect(chef_run).to render_file(file.name).with_content(%r(^\s*WSGIScriptAlias / /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi$))
end
it 'sets the WSGI script alias' do
node.set['openstack']['dashboard']['wsgi_path'] = 'wsgi_path_value'
node.set['openstack']['dashboard']['webroot'] = 'root'
expect(chef_run).to render_file(file.name).with_content(/^\s*WSGIScriptAlias root wsgi_path_value$/)
end
it 'sets the WSGI daemon process' do
node.set['openstack']['dashboard']['horizon_user'] = 'horizon_user_value'
node.set['openstack']['dashboard']['horizon_group'] = 'horizon_group_value'
node.set['openstack']['dashboard']['dash_path'] = 'dash_path_value'
expect(chef_run).to render_file(file.name).with_content(
/^\s*WSGIDaemonProcess dashboard user=horizon_user_value group=horizon_group_value processes=3 threads=10 python-path=dash_path_value$/)
end
it 'has the default DocRoot' do
node.set['openstack']['dashboard']['dash_path'] = 'dash_path_value'
expect(chef_run).to render_file(file.name)
.with_content(%r(\s*DocumentRoot dash_path_value/.blackhole/$))
end
it 'has TraceEnable set' do
node.set['openstack']['dashboard']['traceenable'] = 'value'
expect(chef_run).to render_file(file.name)
.with_content(/^ TraceEnable value$/)
end
it 'sets the right Alias path for /static' do
node.set['openstack']['dashboard']['static_path'] = 'static_path_value'
expect(chef_run).to render_file(file.name).with_content(/^\s+Alias \/static static_path_value$/)
end
%w(dash_path static_path).each do |dir_attribute|
it "sets the #{dir_attribute} directory directive" do
node.set['openstack']['dashboard'][dir_attribute] = "#{dir_attribute}_value"
expect(chef_run).to render_file(file.name).with_content(/^\s*<Directory #{dir_attribute}_value>$/)
end
end
context 'log directives' do
before do
node.set['apache']['log_dir'] = 'log_dir_value'
end
it 'sets de ErrorLog directive' do
node.set['openstack']['dashboard']['error_log'] = 'error_log_value'
expect(chef_run).to render_file(file.name).with_content(/^\s*ErrorLog log_dir_value\/error_log_value$/)
end
it 'sets de CustomLog directive' do
node.set['openstack']['dashboard']['access_log'] = 'access_log_value'
expect(chef_run).to render_file(file.name).with_content(/^\s*CustomLog log_dir_value\/access_log_value combined$/)
end
end
it 'sets wsgi socket prefix if wsgi_socket_prefix attribute is preset' do
node.set['openstack']['dashboard']['wsgi_socket_prefix'] = '/var/run/wsgi'
expect(chef_run).to render_file(file.name).with_content(%r(^WSGISocketPrefix /var/run/wsgi$))
end
it 'omits wsgi socket prefix if wsgi_socket_prefix attribute is not preset' do
node.set['openstack']['dashboard']['wsgi_socket_prefix'] = nil
expect(chef_run).not_to render_file(file.name).with_content(/^WSGISocketPrefix $/)
end
end
it 'notifies restore-selinux-context' do
expect(file).to notify('execute[restore-selinux-context]').to(:run)
end
end
describe 'secret_key_path file' do
secret_key_path = '/var/lib/openstack-dashboard/secret_key'
let(:file) { chef_run.file(secret_key_path) }
it 'has correct ownership' do
expect(file.owner).to eq('horizon')
expect(file.group).to eq('horizon')
end
it 'has correct mode' do
expect(file.mode).to eq(00600)
end
it 'does not notify apache2 restart' do
expect(file).not_to notify('service[apache2]').to(:restart)
end
it 'has configurable path and ownership settings' do
node.set['openstack']['dashboard']['secret_key_path'] = 'somerandompath'
node.set['openstack']['dashboard']['horizon_user'] = 'somerandomuser'
node.set['openstack']['dashboard']['horizon_group'] = 'somerandomgroup'
file = chef_run.file('somerandompath')
expect(file.owner).to eq('somerandomuser')
expect(file.group).to eq('somerandomgroup')
end
describe 'secret_key_content set' do
before do
node.set['openstack']['dashboard']['secret_key_content'] = 'somerandomcontent'
end
it 'has configurable secret_key_content setting' do
expect(chef_run).to render_file(file.name).with_content('somerandomcontent')
end
it 'notifies apache2 restart when secret_key_content set' do
expect(file).to notify('service[apache2]').to(:restart)
end
end
end
it 'does not delete openstack-dashboard.conf' do
file = '/etc/httpd/conf.d/openstack-dashboard.conf'
expect(chef_run).not_to delete_file(file)
end
it 'removes openstack-dashboard-ubuntu-theme package' do
expect(chef_run).to purge_package('openstack-dashboard-ubuntu-theme')
end
it 'calls apache_site to disable 000-default virtualhost' do
resource = chef_run.find_resource('execute',
'a2dissite 000-default').to_hash
expect(resource).to include(
action: 'run',
params: {
enable: false,
name: '000-default'
}
)
end
it 'calls apache_site to enable openstack-dashboard virtualhost' do
resource = chef_run.find_resource('execute',
'a2ensite openstack-dashboard').to_hash
expect(resource).to include(
action: 'run',
params: {
enable: true,
notifies: [:reload, 'service[apache2]', :immediately],
name: 'openstack-dashboard'
}
)
end
it 'notifies apache2 restart' do
skip 'TODO: how to test when tied to an LWRP'
end
it 'does not execute restore-selinux-context' do
cmd = 'restorecon -Rv /etc/httpd /etc/pki; chcon -R -t httpd_sys_content_t /usr/share/openstack-dashboard || :'
expect(chef_run).not_to run_execute(cmd)
end
it 'has group write mode on path' do
path = chef_run.directory("#{chef_run.node['openstack']['dashboard']['dash_path']}/local")
expect(path.mode).to eq(02770)
expect(path.group).to eq(chef_run.node['openstack']['dashboard']['horizon_group'])
end
let(:runner) { ChefSpec::Runner.new(UBUNTU_OPTS) }
let(:node) { runner.node }
let(:chef_run) do
runner.converge(described_recipe)
end
include_context 'non_redhat_stubs'
include_context 'dashboard_stubs'
it 'installs the horizon dashboard' do
expect(chef_run).to include_recipe('openstack-dashboard::horizon')
end
it 'by default installs the apache2 webserver' do
expect(chef_run).to include_recipe('openstack-dashboard::apache2-server')
end
end