From dd25406b9ec075b7e28c7a9fb97a51d31d272bfd Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 7 Jul 2014 14:03:47 -0700 Subject: [PATCH] Add db::mysql and db::mysql::host_access to openstacklib The openstacklib::db::mysql resource is a library resource that can be used by nova, cinder, ceilometer, etc., rather than replicating equivalent functionality across all of these modules. This resource reimplements most of the functionality of the puppetlabs mysql::db resource. The primary purpose of writing this code from scratch rather than using the mysql::db resource is to allow the use of a password hash rather than a plaintext password as a parameter. Other differences from the mysql::db implementation are: * It does not have an ensure parameter, we will assume the db should be present * It does not accept and execute arbitrary SQL because the db sync exec manages the state of the db * It does not use ensure_resource because the database and user should only be created from within this resource and creating them elsewhere should be an error Implements: blueprint commmon-openstack-database-resource Change-Id: I76bd93d1579179932d1f48cea4bb80a2576a7fba --- .fixtures.yml | 3 + Modulefile | 3 + README.md | 80 ++++++- manifests/db/mysql.pp | 90 ++++++++ manifests/db/mysql/host_access.pp | 41 ++++ spec/defines/openstacklib_db_mysql_spec.rb | 229 +++++++++++++++++++++ 6 files changed, 444 insertions(+), 2 deletions(-) create mode 100644 manifests/db/mysql.pp create mode 100644 manifests/db/mysql/host_access.pp create mode 100644 spec/defines/openstacklib_db_mysql_spec.rb 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