diff --git a/Puppetfile_extras b/Puppetfile_extras index dc267b2c1..65d0fa92a 100644 --- a/Puppetfile_extras +++ b/Puppetfile_extras @@ -57,3 +57,6 @@ mod 'snmp', :git => 'https://github.com/razorsedge/puppet-snmp', :ref => 'master' +mod 'pacemaker', + :git => 'https://github.com/openstack/puppet-pacemaker', + :ref => 'master' diff --git a/lib/puppet/parser/functions/docker_volumes_to_storage_maps.rb b/lib/puppet/parser/functions/docker_volumes_to_storage_maps.rb new file mode 100644 index 000000000..1325640c8 --- /dev/null +++ b/lib/puppet/parser/functions/docker_volumes_to_storage_maps.rb @@ -0,0 +1,52 @@ +# This custom function converts an array of docker volumes to the storage_maps +# hash required by the pacemaker::resource::bundle resource. A prefix is added +# to each entry in the storage map to ensure the Puppet resources are unique. +# +# Given: +# docker_volumes = ["/src/vol1:/tgt/vol1", "/src/vol2:/tgt/vol2:ro"] +# prefix = "my-prefix" +# Returns: +# storage_maps = { +# "my-prefix-src-vol1" => { +# "source-dir" => "/src/vol1", +# "target-dir" => "/tgt/vol1", +# "options" => "rw", +# }, +# "my-prefix-src-vol2" => { +# "source-dir" => "/src/vol2", +# "target-dir" => "/tgt/vol2", +# "options" => "ro", +# } +# } +module Puppet::Parser::Functions + newfunction(:docker_volumes_to_storage_maps, :arity => 2, :type => :rvalue, + :doc => <<-EOS + This function converts an array of docker volumes (SOURCE:TARGET[:OPTIONS]) + to a pacemaker::resource::bundle storage_map (a hash). + EOS + ) do |argv| + docker_volumes = argv[0] + prefix = argv[1] + + unless docker_volumes.is_a?(Array) + raise Puppet::ParseError, "docker_volumes_to_storage_maps: Argument 'docker_volumes' must be an array. The value given was: #{docker_volumes}" + end + unless prefix.is_a?(String) + raise Puppet::ParseError, "docker_volumes_to_storage_maps: Argument 'prefix' must be an string. The value given was: #{prefix}" + end + storage_maps = Hash.new + docker_volumes.each do |docker_vol| + source, target, options = docker_vol.split(":") + unless options + options = "rw" + end + storage_maps[prefix + source.gsub("/", "-")] = { + "source-dir" => source, + "target-dir" => target, + "options" => options, + } + end + return storage_maps + end +end + diff --git a/manifests/profile/pacemaker/cinder/backup_bundle.pp b/manifests/profile/pacemaker/cinder/backup_bundle.pp index 0aa2ee967..46db898e5 100644 --- a/manifests/profile/pacemaker/cinder/backup_bundle.pp +++ b/manifests/profile/pacemaker/cinder/backup_bundle.pp @@ -22,6 +22,14 @@ # (Optional) The docker image to use for creating the pacemaker bundle # Defaults to hiera('tripleo::profile::pacemaker::cinder::backup_bundle::cinder_docker_image', undef) # +# [*docker_volumes*] +# (Optional) The list of volumes to be mounted in the docker container +# Defaults to [] +# +# [*docker_environment*] +# (Optional) The list of environment variables set in the docker container +# Defaults to ['KOLLA_CONFIG_STRATEGY=COPY_ALWAYS'] +# # [*pcs_tries*] # (Optional) The number of times pcs commands should be retried. # Defaults to hiera('pcs_tries', 20) @@ -39,6 +47,8 @@ class tripleo::profile::pacemaker::cinder::backup_bundle ( $bootstrap_node = hiera('cinder_backup_short_bootstrap_node_name'), $cinder_backup_docker_image = hiera('tripleo::profile::pacemaker::cinder::backup_bundle::cinder_backup_docker_image', undef), + $docker_volumes = [], + $docker_environment = ['KOLLA_CONFIG_STRATEGY=COPY_ALWAYS'], $pcs_tries = hiera('pcs_tries', 20), $step = Integer(hiera('step')), ) { @@ -67,18 +77,14 @@ class tripleo::profile::pacemaker::cinder::backup_bundle ( if $pacemaker_master { $cinder_backup_nodes_count = count(hiera('cinder_backup_short_node_names', [])) - pacemaker::resource::bundle { $::cinder::params::backup_service : - image => $cinder_backup_docker_image, - replicas => 1, - location_rule => { - resource_discovery => 'exclusive', - score => 0, - expression => ['cinder-backup-role eq true'], - }, - container_options => 'network=host', - options => '--ipc=host --privileged=true --user=root --log-driver=journald -e KOLLA_CONFIG_STRATEGY=COPY_ALWAYS', - run_command => '/bin/bash /usr/local/bin/kolla_start', - storage_maps => { + $docker_vol_arr = delete(any2array($docker_volumes), '').flatten() + + unless empty($docker_vol_arr) { + $storage_maps = docker_volumes_to_storage_maps($docker_vol_arr, 'cinder-backup') + } else { + notice('Using fixed list of docker volumes for cinder-backup bundle') + # Default to previous hard-coded list + $storage_maps = { 'cinder-backup-cfg-files' => { 'source-dir' => '/var/lib/kolla/config_files/cinder_backup.json', 'target-dir' => '/var/lib/kolla/config_files/config.json', @@ -159,7 +165,24 @@ class tripleo::profile::pacemaker::cinder::backup_bundle ( 'target-dir' => '/var/lib/kolla/config_files/src-ceph', 'options' => 'ro', }, + } + } + + $docker_env_arr = delete(any2array($docker_environment), '').flatten() + $docker_env = join($docker_env_arr.map |$var| { "-e ${var}" }, ' ') + + pacemaker::resource::bundle { $::cinder::params::backup_service : + image => $cinder_backup_docker_image, + replicas => 1, + location_rule => { + resource_discovery => 'exclusive', + score => 0, + expression => ['cinder-backup-role eq true'], }, + container_options => 'network=host', + options => "--ipc=host --privileged=true --user=root --log-driver=journald ${docker_env}", + run_command => '/bin/bash /usr/local/bin/kolla_start', + storage_maps => $storage_maps, } } } diff --git a/manifests/profile/pacemaker/cinder/volume_bundle.pp b/manifests/profile/pacemaker/cinder/volume_bundle.pp index 3396db84b..07a082089 100644 --- a/manifests/profile/pacemaker/cinder/volume_bundle.pp +++ b/manifests/profile/pacemaker/cinder/volume_bundle.pp @@ -22,6 +22,14 @@ # (Optional) The docker image to use for creating the pacemaker bundle # Defaults to hiera('tripleo::profile::pacemaker::cinder::volume_bundle::cinder_docker_image', undef) # +# [*docker_volumes*] +# (Optional) The list of volumes to be mounted in the docker container +# Defaults to [] +# +# [*docker_environment*] +# (Optional) The list of environment variables set in the docker container +# Defaults to ['KOLLA_CONFIG_STRATEGY=COPY_ALWAYS'] +# # [*pcs_tries*] # (Optional) The number of times pcs commands should be retried. # Defaults to hiera('pcs_tries', 20) @@ -39,6 +47,8 @@ class tripleo::profile::pacemaker::cinder::volume_bundle ( $bootstrap_node = hiera('cinder_volume_short_bootstrap_node_name'), $cinder_volume_docker_image = hiera('tripleo::profile::pacemaker::cinder::volume_bundle::cinder_volume_docker_image', undef), + $docker_volumes = [], + $docker_environment = ['KOLLA_CONFIG_STRATEGY=COPY_ALWAYS'], $pcs_tries = hiera('pcs_tries', 20), $step = Integer(hiera('step')), ) { @@ -67,18 +77,14 @@ class tripleo::profile::pacemaker::cinder::volume_bundle ( if $pacemaker_master { $cinder_volume_nodes_count = count(hiera('cinder_volume_short_node_names', [])) - pacemaker::resource::bundle { $::cinder::params::volume_service: - image => $cinder_volume_docker_image, - replicas => 1, - location_rule => { - resource_discovery => 'exclusive', - score => 0, - expression => ['cinder-volume-role eq true'], - }, - container_options => 'network=host', - options => '--ipc=host --privileged=true --user=root --log-driver=journald -e KOLLA_CONFIG_STRATEGY=COPY_ALWAYS', - run_command => '/bin/bash /usr/local/bin/kolla_start', - storage_maps => { + $docker_vol_arr = delete(any2array($docker_volumes), '').flatten() + + unless empty($docker_vol_arr) { + $storage_maps = docker_volumes_to_storage_maps($docker_vol_arr, 'cinder-volume') + } else { + notice('Using fixed list of docker volumes for cinder-volume bundle') + # Default to previous hard-coded list + $storage_maps = { 'cinder-volume-cfg-files' => { 'source-dir' => '/var/lib/kolla/config_files/cinder_volume.json', 'target-dir' => '/var/lib/kolla/config_files/config.json', @@ -159,7 +165,24 @@ class tripleo::profile::pacemaker::cinder::volume_bundle ( 'target-dir' => '/var/lib/kolla/config_files/src-ceph/', 'options' => 'ro', }, + } + } + + $docker_env_arr = delete(any2array($docker_environment), '').flatten() + $docker_env = join($docker_env_arr.map |$var| { "-e ${var}" }, ' ') + + pacemaker::resource::bundle { $::cinder::params::volume_service: + image => $cinder_volume_docker_image, + replicas => 1, + location_rule => { + resource_discovery => 'exclusive', + score => 0, + expression => ['cinder-volume-role eq true'], }, + container_options => 'network=host', + options => "--ipc=host --privileged=true --user=root --log-driver=journald ${docker_env}", + run_command => '/bin/bash /usr/local/bin/kolla_start', + storage_maps => $storage_maps, } } } diff --git a/spec/classes/tripleo_profile_pacemaker_cinder_backup_bundle_spec.rb b/spec/classes/tripleo_profile_pacemaker_cinder_backup_bundle_spec.rb new file mode 100644 index 000000000..3a60ef97f --- /dev/null +++ b/spec/classes/tripleo_profile_pacemaker_cinder_backup_bundle_spec.rb @@ -0,0 +1,121 @@ +# +# Copyright (C) 2018 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'tripleo::profile::pacemaker::cinder::backup_bundle' do + shared_examples_for 'tripleo::profile::pacemaker::cinder::backup_bundle' do + before :each do + facts.merge!({ :step => params[:step] }) + end + + context 'with step less than 2' do + let(:params) { { :step => 1 } } + + it 'should do nothing' do + is_expected.to contain_class('tripleo::profile::base::cinder::backup') + end + end + + context 'with step 2 on bootstrap node' do + let(:params) { { + :step => 2, + } } + + it 'should create pacemaker properties' do + is_expected.to contain_pacemaker__property('cinder-backup-role-c-bak-1') + is_expected.to contain_pacemaker__property('cinder-backup-role-c-bak-2') + end + end + + context 'with step 2 not on bootstrap node' do + let(:params) { { + :step => 2, + :bootstrap_node => 'other.example.com', + } } + + it 'should not create pacemaker properties' do + is_expected.to_not contain_pacemaker__property('cinder-backup-role-c-bak-1') + is_expected.to_not contain_pacemaker__property('cinder-backup-role-c-bak-2') + end + end + + context 'with step 5' do + let(:params) { { + :step => 5, + :cinder_backup_docker_image => 'c-bak-docker-image', + } } + + context 'with default inputs' do + it 'should create default cinder-backup resource bundle' do + is_expected.to contain_pacemaker__resource__bundle('openstack-cinder-backup').with( + :image => 'c-bak-docker-image', + :options => '--ipc=host --privileged=true --user=root --log-driver=journald -e KOLLA_CONFIG_STRATEGY=COPY_ALWAYS', + ) + # The default list of storage_maps is rather long, and this + # just does a spot-check of a few key entries. The point is + # to verify the default list is used when the docker_volumes + # input parameter isn't specified. + storage_maps = catalogue.resource( + 'Pacemaker::Resource::Bundle', 'openstack-cinder-backup').send(:parameters)[:storage_maps] + expect(storage_maps).to include('cinder-backup-cfg-files', 'cinder-backup-cfg-data') + end + end + + context 'with docker volumes and environment inputs' do + before :each do + params.merge!({ + :docker_volumes => ['/src/1:/tgt/1', '/src/2:/tgt/2:ro', '/src/3:/tgt/3:ro,z'], + :docker_environment => ['RIGHT=LEFT', 'UP=DOWN'], + }) + end + it 'should create custom cinder-backup resource bundle' do + is_expected.to contain_pacemaker__resource__bundle('openstack-cinder-backup').with( + :image => 'c-bak-docker-image', + :options => '--ipc=host --privileged=true --user=root --log-driver=journald -e RIGHT=LEFT -e UP=DOWN', + :storage_maps => { + 'cinder-backup-src-1' => { + 'source-dir' => '/src/1', + 'target-dir' => '/tgt/1', + 'options' => 'rw', + }, + 'cinder-backup-src-2' => { + 'source-dir' => '/src/2', + 'target-dir' => '/tgt/2', + 'options' => 'ro', + }, + 'cinder-backup-src-3' => { + 'source-dir' => '/src/3', + 'target-dir' => '/tgt/3', + 'options' => 'ro,z', + }, + }, + ) + end + end + end + end + + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) do + facts.merge({ :hostname => 'node.example.com' }) + end + + it_behaves_like 'tripleo::profile::pacemaker::cinder::backup_bundle' + end + end +end diff --git a/spec/classes/tripleo_profile_pacemaker_cinder_volume_bundle_spec.rb b/spec/classes/tripleo_profile_pacemaker_cinder_volume_bundle_spec.rb new file mode 100644 index 000000000..a2b9c48c7 --- /dev/null +++ b/spec/classes/tripleo_profile_pacemaker_cinder_volume_bundle_spec.rb @@ -0,0 +1,127 @@ +# +# Copyright (C) 2018 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe 'tripleo::profile::pacemaker::cinder::volume_bundle' do + shared_examples_for 'tripleo::profile::pacemaker::cinder::volume_bundle' do + before :each do + facts.merge!({ :step => params[:step] }) + end + + let(:pre_condition) do + # Required to keep tripleo::profile::base::cinder::volume happy. + "class { 'tripleo::profile::base::cinder::volume::iscsi': step => #{params[:step]}, cinder_iscsi_address => ['127.0.0.1'] }" + end + + context 'with step less than 2' do + let(:params) { { :step => 1 } } + + it 'should do nothing' do + is_expected.to contain_class('tripleo::profile::base::cinder::volume') + end + end + + context 'with step 2 on bootstrap node' do + let(:params) { { + :step => 2, + } } + + it 'should create pacemaker properties' do + is_expected.to contain_pacemaker__property('cinder-volume-role-c-vol-1') + is_expected.to contain_pacemaker__property('cinder-volume-role-c-vol-2') + end + end + + context 'with step 2 not on bootstrap node' do + let(:params) { { + :step => 2, + :bootstrap_node => 'other.example.com', + } } + + it 'should not create pacemaker properties' do + is_expected.to_not contain_pacemaker__property('cinder-volume-role-c-vol-1') + is_expected.to_not contain_pacemaker__property('cinder-volume-role-c-vol-2') + end + end + + context 'with step 5' do + let(:params) { { + :step => 5, + :cinder_volume_docker_image => 'c-vol-docker-image', + } } + + context 'with default inputs' do + it 'should create default cinder-volume resource bundle' do + is_expected.to contain_pacemaker__resource__bundle('openstack-cinder-volume').with( + :image => 'c-vol-docker-image', + :options => '--ipc=host --privileged=true --user=root --log-driver=journald -e KOLLA_CONFIG_STRATEGY=COPY_ALWAYS', + ) + # The default list of storage_maps is rather long, and this + # just does a spot-check of a few key entries. The point is + # to verify the default list is used when the docker_volumes + # input parameter isn't specified. + storage_maps = catalogue.resource( + 'Pacemaker::Resource::Bundle', 'openstack-cinder-volume').send(:parameters)[:storage_maps] + expect(storage_maps).to include('cinder-volume-cfg-files', + 'cinder-volume-cfg-data') + end + end + + context 'with docker volumes and environment inputs' do + before :each do + params.merge!({ + :docker_volumes => ['/src/1:/tgt/1', '/src/2:/tgt/2:ro', '/src/3:/tgt/3:ro,z'], + :docker_environment => ['RIGHT=LEFT', 'UP=DOWN'], + }) + end + it 'should create custom cinder-volume resource bundle' do + is_expected.to contain_pacemaker__resource__bundle('openstack-cinder-volume').with( + :image => 'c-vol-docker-image', + :options => '--ipc=host --privileged=true --user=root --log-driver=journald -e RIGHT=LEFT -e UP=DOWN', + :storage_maps => { + 'cinder-volume-src-1' => { + 'source-dir' => '/src/1', + 'target-dir' => '/tgt/1', + 'options' => 'rw', + }, + 'cinder-volume-src-2' => { + 'source-dir' => '/src/2', + 'target-dir' => '/tgt/2', + 'options' => 'ro', + }, + 'cinder-volume-src-3' => { + 'source-dir' => '/src/3', + 'target-dir' => '/tgt/3', + 'options' => 'ro,z', + }, + }, + ) + end + end + end + end + + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) do + facts.merge({ :hostname => 'node.example.com' }) + end + + it_behaves_like 'tripleo::profile::pacemaker::cinder::volume_bundle' + end + end +end diff --git a/spec/fixtures/hieradata/default.yaml b/spec/fixtures/hieradata/default.yaml index 42e3df4f8..364e954da 100644 --- a/spec/fixtures/hieradata/default.yaml +++ b/spec/fixtures/hieradata/default.yaml @@ -27,6 +27,14 @@ ceph::profile::params::rgw_keystone_admin_project: 'keystone_project' ceph::profile::params::rgw_keystone_admin_user: 'keystone_admin_user' ceph::profile::params::rgw_keystone_admin_password: 'keystone_admin_password' # cinder related items +cinder_backup_short_bootstrap_node_name: 'node.example.com' +cinder_backup_short_node_names: + - 'c-bak-1' + - 'c-bak-2' +cinder_volume_short_bootstrap_node_name: 'node.example.com' +cinder_volume_short_node_names: + - 'c-vol-1' + - 'c-vol-2' cinder::rabbit_password: 'password' cinder::keystone::authtoken::password: 'password' # gnocchi related items