diff --git a/.fixtures.yml b/.fixtures.yml index 10581a6b..d33ced52 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -1,3 +1,6 @@ fixtures: + repositories: + mysql: git://github.com/puppetlabs/puppetlabs-mysql.git + stdlib: git://github.com/puppetlabs/puppetlabs-stdlib.git symlinks: 'openstacklib': "#{source_dir}" diff --git a/Modulefile b/Modulefile index ba2bff6a..7c73b3ec 100644 --- a/Modulefile +++ b/Modulefile @@ -6,3 +6,6 @@ license 'Apache License 2.0' summary 'Puppet Labs OpenStackLib Module' description 'Puppet module library to expose common functionality between OpenStack modules' project_page 'https://launchpad.net/puppet-openstacklib' + +dependency 'puppetlabs/mysql', '>=2.2.0 <3.0.0' +dependency 'puppetlabs/stdlib', '>=3.2.0' diff --git a/README.md b/README.md index 4e34bd22..fee75095 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,85 @@ Setup example% puppet module install puppetlabs/openstacklib -### Beginning with openstacklib +Usage +----- -Instructions for beginning with openstacklib will be added later. +### Classes and Defined Types + +#### Defined type: openstacklib::db::mysql + +The db::mysql resource is a library resource that can be used by nova, cinder, +ceilometer, etc., to create a mysql database with configurable privileges for +a user connecting from defined hosts. + +Typically this resource will be declared with a notify parameter to configure +the sync command to execute when the database resource is changed. + +For example, in heat::db::mysql you might declare: + +``` +::openstacklib::db::mysql { 'heat': + password_hash => mysql_password($password), + dbname => $dbname, + user => $user, + host => $host, + charset => $charset, + collate => $collate, + allowed_hosts => $allowed_hosts, + notify => Exec['heat-dbsync'], + } +``` + +Some modules should ensure that the database is created before the service is +set up. For example, in keystone::db::mysql you would have: + +``` +::openstacklib::db::mysql { 'keystone': + password_hash => mysql_password($password), + dbname => $dbname, + user => $user, + host => $host, + charset => $charset, + collate => $collate, + allowed_hosts => $allowed_hosts, + notify => Exec['keystone-manage db_sync'], + before => Service['keystone'], + } +``` + +** Parameters for openstacklib::db::mysql: ** + +#####`password_hash` +Password hash to use for the database user for this service; +string; required + +#####`dbname` +The name of the database +string; optional; default to the $title of the resource, i.e. 'nova' + +#####`user` +The database user to create; +string; optional; default to the $title of the resource, i.e. 'nova' + +#####`host` +The IP address or hostname of the user in mysql_grant; +string; optional; default to '127.0.0.1' + +#####`charset` +The charset to use for the database; +string; optional; default to 'utf8' + +#####`collate` +The collate to use for the database; +string; optional; default to 'utf8_unicode_ci' + +#####`allowed_hosts` +Additional hosts that are allowed to access this database; +array or string; optional; default to undef + +#####`privileges` +Privileges given to the database user; +string or array of strings; optional; default to 'ALL' Implementation -------------- diff --git a/manifests/db/mysql.pp b/manifests/db/mysql.pp new file mode 100644 index 00000000..ac504e4a --- /dev/null +++ b/manifests/db/mysql.pp @@ -0,0 +1,90 @@ +# == Definition: openstacklib::db::mysql +# +# This resource configures a mysql database for an OpenStack service +# +# == Parameters: +# +# [*password_hash*] +# Password hash to use for the database user for this service; +# string; required +# +# [*dbname*] +# The name of the database +# string; optional; default to the $title of the resource, i.e. 'nova' +# +# [*user*] +# The database user to create; +# string; optional; default to the $title of the resource, i.e. 'nova' +# +# [*host*] +# The IP address or hostname of the user in mysql_grant; +# string; optional; default to '127.0.0.1' +# +# [*charset*] +# The charset to use for the database; +# string; optional; default to 'utf8' +# +# [*collate*] +# The collate to use for the database; +# string; optional; default to 'utf8_unicode_ci' +# +# [*allowed_hosts*] +# Additional hosts that are allowed to access this database; +# array or string; optional; default to undef +# +# [*privileges*] +# Privileges given to the database user; +# string or array of strings; optional; default to 'ALL' + +define openstacklib::db::mysql ( + $password_hash, + $dbname = $title, + $user = $title, + $host = '127.0.0.1', + $charset = 'utf8', + $collate = 'utf8_unicode_ci', + $allowed_hosts = undef, + $privileges = 'ALL', +) { + + include ::mysql::client + + mysql_database { $dbname: + ensure => present, + charset => $charset, + collate => $collate, + require => [ Class['mysql::server'], Class['mysql::client'] ], + } + + mysql_user { "${user}@${host}": + ensure => present, + password_hash => $password_hash, + require => Class['mysql::server'], + } + + mysql_grant { "${user}@${host}/${dbname}.*": + privileges => $privileges, + user => "${user}@${host}", + table => "${dbname}.*", + require => [Mysql_database[$dbname], Mysql_user["${user}@${host}"], Class['mysql::server'] ], + } + + # Check allowed_hosts to avoid duplicate resource declarations + if is_array($allowed_hosts) and delete($allowed_hosts,$host) != [] { + $real_allowed_hosts = delete($allowed_hosts,$host) + $unique_real_allowed_hosts = prefix($real_allowed_hosts, "${dbname}_") + } elsif is_string($allowed_hosts) and ($allowed_hosts != $host) { + $real_allowed_hosts = $allowed_hosts + $unique_real_allowed_hosts = "${dbname}_${real_allowed_hosts}" + } + + if $real_allowed_hosts { + openstacklib::db::mysql::host_access { $unique_real_allowed_hosts: + user => $user, + password_hash => $password_hash, + database => $dbname, + privileges => $privileges, + } + } + +} diff --git a/manifests/db/mysql/host_access.pp b/manifests/db/mysql/host_access.pp new file mode 100644 index 00000000..4909f631 --- /dev/null +++ b/manifests/db/mysql/host_access.pp @@ -0,0 +1,41 @@ +# Allow a user to access the database for the service +# +# == Namevar +# String with the form dbname_host. The host part of the string is the host +# to allow +# +# == Parameters +# [*user*] +# username to allow +# +# [*password_hash*] +# user password hash +# +# [*database*] +# the database name +# +# [*privileges*] +# the privileges to grant to this user +# +define openstacklib::db::mysql::host_access ( + $user, + $password_hash, + $database, + $privileges, +) { + validate_re($title, '_', 'Title must be $dbname_$host') + + $host = inline_template('<%= @title.split("_").last %>') + + mysql_user { "${user}@${host}": + password_hash => $password_hash, + require => Mysql_database[$database], + } + + mysql_grant { "${user}@${host}/${database}.*": + privileges => $privileges, + table => "${database}.*", + require => Mysql_user["${user}@${host}"], + user => "${user}@${host}", + } +} diff --git a/spec/defines/openstacklib_db_mysql_spec.rb b/spec/defines/openstacklib_db_mysql_spec.rb new file mode 100644 index 00000000..9f4edfb0 --- /dev/null +++ b/spec/defines/openstacklib_db_mysql_spec.rb @@ -0,0 +1,229 @@ +require 'spec_helper' + +describe 'openstacklib::db::mysql' do + + let :pre_condition do + 'include mysql::server' + end + + password_hash = 'AA1420F182E88B9E5F874F6FBE7459291E8F4601' + let :required_params do + { :password_hash => password_hash } + end + + title = 'nova' + let (:title) { title } + context 'on a Debian osfamily' do + let :facts do + { :osfamily => "Debian" } + end + + context 'with only required parameters' do + let :params do + required_params + end + + it { should contain_mysql_database(title).with( + :charset => 'utf8', + :collate => 'utf8_unicode_ci' + )} + it { should contain_mysql_user("#{title}@127.0.0.1").with( + :password_hash => password_hash + )} + it { should contain_mysql_grant("#{title}@127.0.0.1/#{title}.*").with( + :user => "#{title}@127.0.0.1", + :privileges => 'ALL', + :table => "#{title}.*" + )} + end + + context 'when overriding charset' do + let :params do + { :charset => 'latin1' }.merge(required_params) + end + + it { should contain_mysql_database(title).with_charset(params[:charset]) } + end + + context 'when omitting the required parameter password_hash' do + let :params do + required_params.delete(:password_hash) + end + it { expect { should raise_error(Puppet::Error) } } + end + + context 'when notifying other resources' do + let :pre_condition do + 'exec {"nova-db-sync":}' + end + let :params do + { :notify => 'Exec[nova-db-sync]'}.merge(required_params) + end + + it { should contain_exec('nova-db-sync').that_subscribes_to("Openstacklib::Db::Mysql[#{title}]") } + end + + context 'when required for other openstack services' do + let :pre_condition do + 'service {"keystone":}' + end + let :title do + 'keystone' + end + let :params do + { :before => 'Service[keystone]'}.merge(required_params) + end + + it { should contain_service('keystone').that_requires("Openstacklib::Db::Mysql[keystone]") } + end + + context "overriding allowed_hosts param to array" do + let :params do + { :allowed_hosts => ['127.0.0.1','%'] }.merge(required_params) + end + + it {should_not contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( + :user => title, + :password_hash => password_hash, + :database => title + )} + it {should contain_openstacklib__db__mysql__host_access("#{title}_%").with( + :user => title, + :password_hash => password_hash, + :database => title + )} + end + + context "overriding allowed_hosts param to string" do + let :params do + { + :password_hash => password_hash, + :allowed_hosts => '192.168.1.1' + } + end + + it {should contain_openstacklib__db__mysql__host_access("#{title}_192.168.1.1").with( + :user => title, + :password_hash => password_hash, + :database => title + )} + end + + context "overriding allowed_hosts param equals to host param " do + let :params do + { + :password_hash => password_hash, + :allowed_hosts => '127.0.0.1' + } + end + + it {should_not contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( + :user => title, + :password_hash => password_hash, + :database => title + )} + end + end + + context 'on a RedHat osfamily' do + let :facts do + { :osfamily => 'RedHat' } + end + + context 'with only required parameters' do + let :params do + required_params + end + + it { should contain_mysql_database(title).with( + :charset => 'utf8', + :collate => 'utf8_unicode_ci' + )} + it { should contain_mysql_user("#{title}@127.0.0.1").with( + :password_hash => password_hash + )} + it { should contain_mysql_grant("#{title}@127.0.0.1/#{title}.*").with( + :user => "#{title}@127.0.0.1", + :privileges => 'ALL', + :table => "#{title}.*" + )} + end + + context 'when overriding charset' do + let :params do + { :charset => 'latin1' }.merge(required_params) + end + + it { should contain_mysql_database(title).with_charset(params[:charset]) } + end + + context 'when omitting the required parameter password' do + let :params do + required_params.delete(:password) + end + it { expect { should raise_error(Puppet::Error) } } + end + + context 'when notifying other resources' do + let(:pre_condition) { 'exec {"nova-db-sync":}' } + let(:params) { { :notify => 'Exec[nova-db-sync]'}.merge(required_params) } + + it { should contain_exec('nova-db-sync').that_subscribes_to("Openstacklib::Db::Mysql[#{title}]") } + end + + context 'when required for other openstack services' do + let(:pre_condition) { 'service {"keystone":}' } + let(:title) { 'keystone' } + let(:params) { { :before => 'Service[keystone]'}.merge(required_params) } + + it { should contain_service('keystone').that_requires("Openstacklib::Db::Mysql[keystone]") } + end + + context "overriding allowed_hosts param to array" do + let :params do + { :allowed_hosts => ['127.0.0.1','%'] }.merge(required_params) + end + + it {should_not contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( + :user => title, + :password_hash => password_hash, + :database => title + )} + it {should contain_openstacklib__db__mysql__host_access("#{title}_%").with( + :user => title, + :password_hash => password_hash, + :database => title + )} + end + + context "overriding allowed_hosts param to string" do + let :params do + { + :password_hash => password_hash, + :allowed_hosts => '192.168.1.1' + } + end + + it {should contain_openstacklib__db__mysql__host_access("#{title}_192.168.1.1").with( + :user => title, + :password_hash => password_hash, + :database => title + )} + end + + context "overriding allowed_hosts param equals to host param " do + let :params do + { + :password_hash => password_hash, + :allowed_hosts => '127.0.0.1' + } + end + + it {should_not contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( + :user => title, + :password_hash => password_hash, + :database => title + )} + end + end +end