diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..89f2e1b2 --- /dev/null +++ b/Gemfile @@ -0,0 +1,14 @@ +source 'https://rubygems.org' + +group :development, :test do + gem 'puppetlabs_spec_helper', :require => false + gem 'puppet-lint', '~> 0.3.2' +end + +if puppetversion = ENV['PUPPET_GEM_VERSION'] + gem 'puppet', puppetversion, :require => false +else + gem 'puppet', :require => false +end + +# vim:ft=ruby diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..fd4bb0c8 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,41 @@ +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.2.4) + facter (1.7.1) + hiera (1.2.1) + json_pure + json_pure (1.8.0) + metaclass (0.0.1) + mocha (0.14.0) + metaclass (~> 0.0.1) + puppet (3.2.2) + facter (~> 1.6) + hiera (~> 1.0) + rgen (~> 0.6) + puppet-lint (0.3.2) + puppetlabs_spec_helper (0.4.1) + mocha (>= 0.10.5) + rake + rspec (>= 2.9.0) + rspec-puppet (>= 0.1.1) + rake (10.1.0) + rgen (0.6.5) + rspec (2.13.0) + rspec-core (~> 2.13.0) + rspec-expectations (~> 2.13.0) + rspec-mocks (~> 2.13.0) + rspec-core (2.13.1) + rspec-expectations (2.13.0) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.13.1) + rspec-puppet (0.1.6) + rspec + +PLATFORMS + ruby + +DEPENDENCIES + puppet + puppet-lint (~> 0.3.2) + puppetlabs_spec_helper diff --git a/LICENSE b/LICENSE index e06d2081..68c771a0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Apache License + + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -173,30 +174,3 @@ Apache License incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. - diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..4c2b2ed0 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' + +PuppetLint.configuration.fail_on_warnings = true +PuppetLint.configuration.send('disable_80chars') +PuppetLint.configuration.send('disable_class_parameter_defaults') diff --git a/lib/puppet/provider/ironic.rb b/lib/puppet/provider/ironic.rb new file mode 100644 index 00000000..36371188 --- /dev/null +++ b/lib/puppet/provider/ironic.rb @@ -0,0 +1,150 @@ +require 'csv' +require 'puppet/util/inifile' + +class Puppet::Provider::Ironic < Puppet::Provider + + def self.conf_filename + '/etc/ironic/ironic.conf' + end + + def self.withenv(hash, &block) + saved = ENV.to_hash + hash.each do |name, val| + ENV[name.to_s] = val + end + + yield + ensure + ENV.clear + saved.each do |name, val| + ENV[name] = val + end + end + + def self.ironic_credentials + @ironic_credentials ||= get_ironic_credentials + end + + def self.get_ironic_credentials + auth_keys = ['auth_host', 'auth_port', 'auth_protocol', + 'admin_tenant_name', 'admin_user', 'admin_password'] + conf = ironic_conf + if conf and conf['keystone_authtoken'] and + auth_keys.all?{|k| !conf['keystone_authtoken'][k].nil?} + return Hash[ auth_keys.map \ + { |k| [k, conf['keystone_authtoken'][k].strip] } ] + else + raise(Puppet::Error, "File: #{conf_filename} does not contain all \ +required sections. Ironic types will not work if ironic is not \ +correctly configured.") + end + end + + def ironic_credentials + self.class.ironic_credentials + end + + def self.auth_endpoint + @auth_endpoint ||= get_auth_endpoint + end + + def self.get_auth_endpoint + q = ironic_credentials + "#{q['auth_protocol']}://#{q['auth_host']}:#{q['auth_port']}/v2.0/" + end + + def self.ironic_conf + return @ironic_conf if @ironic_conf + @ironic_conf = Puppet::Util::IniConfig::File.new + @ironic_conf.read(conf_filename) + @ironic_conf + end + + def self.auth_ironic(*args) + q = ironic_credentials + authenv = { + :OS_AUTH_URL => self.auth_endpoint, + :OS_USERNAME => q['admin_user'], + :OS_TENANT_NAME => q['admin_tenant_name'], + :OS_PASSWORD => q['admin_password'] + } + begin + withenv authenv do + ironic(args) + end + rescue Exception => e + if (e.message =~ /\[Errno 111\] Connection refused/) or + (e.message =~ /\(HTTP 400\)/) + sleep 10 + withenv authenv do + ironic(args) + end + else + raise(e) + end + end + end + + def auth_ironic(*args) + self.class.auth_ironic(args) + end + + def self.reset + @ironic_conf = nil + @ironic_credentials = nil + end + + def self.list_ironic_resources(type) + ids = [] + list = auth_ironic("#{type}-list", '--format=csv', + '--column=id', '--quote=none') + (list.split("\n")[1..-1] || []).compact.collect do |line| + ids << line.strip + end + return ids + end + + def self.get_ironic_resource_attrs(type, id) + attrs = {} + net = auth_ironic("#{type}-show", '--format=shell', id) + last_key = nil + (net.split("\n") || []).compact.collect do |line| + if line.include? '=' + k, v = line.split('=', 2) + attrs[k] = v.gsub(/\A"|"\Z/, '') + last_key = k + else + # Handle the case of a list of values + v = line.gsub(/\A"|"\Z/, '') + attrs[last_key] = [attrs[last_key], v] + end + end + return attrs + end + + def self.get_tenant_id(catalog, name) + instance_type = 'keystone_tenant' + instance = catalog.resource("#{instance_type.capitalize!}[#{name}]") + if ! instance + instance = Puppet::Type.type(instance_type).instances.find do |i| + i.provider.name == name + end + end + if instance + return instance.provider.id + else + fail("Unable to find #{instance_type} for name #{name}") + end + end + + def self.parse_creation_output(data) + hash = {} + data.split("\n").compact.each do |line| + if line.include? '=' + hash[line.split('=').first] = line.split('=', 2)[1].gsub(/\A"|"\Z/, '') + end + end + hash + end + +end diff --git a/lib/puppet/provider/ironic_config/ini_setting.rb b/lib/puppet/provider/ironic_config/ini_setting.rb new file mode 100644 index 00000000..92ab9e20 --- /dev/null +++ b/lib/puppet/provider/ironic_config/ini_setting.rb @@ -0,0 +1,22 @@ +Puppet::Type.type(:ironic_config).provide( + :ini_setting, + :parent => Puppet::Type.type(:ini_setting).provider(:ruby) +) do + + def section + resource[:name].split('/', 2).first + end + + def setting + resource[:name].split('/', 2).last + end + + def separator + '=' + end + + def file_path + '/etc/ironic/ironic.conf' + end + +end diff --git a/lib/puppet/type/ironic_config.rb b/lib/puppet/type/ironic_config.rb new file mode 100644 index 00000000..e40e221d --- /dev/null +++ b/lib/puppet/type/ironic_config.rb @@ -0,0 +1,18 @@ +Puppet::Type.newtype(:ironic_config) do + + ensurable + + newparam(:name, :namevar => true) do + desc 'Section/setting name to manage from ironic.conf' + newvalues(/\S+\/\S+/) + end + + newproperty(:value) do + desc 'The value of the setting to be defined.' + munge do |value| + value = value.to_s.strip + value.capitalize! if value =~ /^(true|false)$/i + value + end + end +end diff --git a/manifests/client.pp b/manifests/client.pp new file mode 100644 index 00000000..6da86ec5 --- /dev/null +++ b/manifests/client.pp @@ -0,0 +1,39 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# ironic::client +# +# Manages the ironic client package on systems +# +# === Parameters: +# +# [*package_ensure*] +# (optional) The state of the package +# Defaults to present +# +class ironic::client ( + $package_ensure = present +) { + + include ironic::params + + package { 'python-ironicclient': + ensure => $package_ensure, + name => $::ironic::params::client_package, + } + +} diff --git a/manifests/db/mysql.pp b/manifests/db/mysql.pp new file mode 100644 index 00000000..843a049e --- /dev/null +++ b/manifests/db/mysql.pp @@ -0,0 +1,55 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# ironic::db::mysql +# + +class ironic::db::mysql ( + $password, + $dbname = 'ironic', + $user = 'ironic', + $host = '127.0.0.1', + $allowed_hosts = undef, + $charset = 'latin1', + $cluster_id = 'localzone' +) { + + require mysql::python + + mysql::db { $dbname: + user => $user, + password => $password, + host => $host, + charset => $charset, + require => Class['mysql::config'], + } + + # 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) + } elsif is_string($allowed_hosts) and ($allowed_hosts != $host) { + $real_allowed_hosts = $allowed_hosts + } + + if $real_allowed_hosts { + ironic::db::mysql::host_access { $real_allowed_hosts: + user => $user, + password => $password, + database => $dbname, + } + } +} diff --git a/manifests/db/mysql/host_access.pp b/manifests/db/mysql/host_access.pp new file mode 100644 index 00000000..2b6551b9 --- /dev/null +++ b/manifests/db/mysql/host_access.pp @@ -0,0 +1,33 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Used to grant access to the ironic mysql DB +# + +define ironic::db::mysql::host_access ($user, $password, $database) { + database_user { "${user}@${name}": + password_hash => mysql_password($password), + provider => 'mysql', + require => Database[$database], + } + database_grant { "${user}@${name}/${database}": + # TODO figure out which privileges to grant. + privileges => 'all', + provider => 'mysql', + require => Database_user["${user}@${name}"] + } +} diff --git a/manifests/init.pp b/manifests/init.pp new file mode 100644 index 00000000..1673d645 --- /dev/null +++ b/manifests/init.pp @@ -0,0 +1,19 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# ironic +# diff --git a/manifests/keystone/auth.pp b/manifests/keystone/auth.pp new file mode 100644 index 00000000..991c8623 --- /dev/null +++ b/manifests/keystone/auth.pp @@ -0,0 +1,113 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# ironic::keystone::auth +# +# Configures Ironic user, service and endpoint in Keystone. +# +# === Parameters +# +# [*password*] +# (required) Password for Ironic user. +# +# [*auth_name*] +# Username for Ironic service. Defaults to 'ironic'. +# +# [*email*] +# Email for Ironic user. Defaults to 'ironic@localhost'. +# +# [*tenant*] +# Tenant for Ironic user. Defaults to 'services'. +# +# [*configure_endpoint*] +# Should Ironic endpoint be configured? Defaults to 'true'. +# +# [*service_type*] +# Type of service. Defaults to 'baremetal'. +# +# [*public_protocol*] +# Protocol for public endpoint. Defaults to 'http'. +# +# [*public_address*] +# Public address for endpoint. Defaults to '127.0.0.1'. +# +# [*admin_address*] +# Admin address for endpoint. Defaults to '127.0.0.1'. +# +# [*internal_address*] +# Internal address for endpoint. Defaults to '127.0.0.1'. +# +# [*port*] +# Port for endpoint. Defaults to '6385'. +# +# [*public_port*] +# Port for public endpoint. Defaults to $port. +# +# [*region*] +# Region for endpoint. Defaults to 'RegionOne'. +# +class ironic::keystone::auth ( + $password, + $auth_name = 'ironic', + $email = 'ironic@localhost', + $tenant = 'services', + $configure_endpoint = true, + $service_type = 'baremetal', + $public_protocol = 'http', + $public_address = '127.0.0.1', + $admin_address = '127.0.0.1', + $internal_address = '127.0.0.1', + $port = '6385', + $public_port = undef, + $region = 'RegionOne' +) { + + Keystone_user_role["${auth_name}@${tenant}"] ~> Service <| name == 'ironic-server' |> + Keystone_endpoint["${region}/${auth_name}"] ~> Service <| name == 'ironic-server' |> + + if ! $public_port { + $real_public_port = $port + } else { + $real_public_port = $public_port + } + + keystone_user { $auth_name: + ensure => present, + password => $password, + email => $email, + tenant => $tenant, + } + keystone_user_role { "${auth_name}@${tenant}": + ensure => present, + roles => 'admin', + } + keystone_service { $auth_name: + ensure => present, + type => $service_type, + description => 'Ironic Networking Service', + } + + if $configure_endpoint { + keystone_endpoint { "${region}/${auth_name}": + ensure => present, + public_url => "${public_protocol}://${public_address}:${real_public_port}/", + internal_url => "http://${internal_address}:${port}/", + admin_url => "http://${admin_address}:${port}/", + } + + } +} diff --git a/manifests/params.pp b/manifests/params.pp new file mode 100644 index 00000000..a3e77d87 --- /dev/null +++ b/manifests/params.pp @@ -0,0 +1,46 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# ironic::params +# + +class ironic::params { + + if($::osfamily == 'Redhat') { + + $package_name = 'openstack-ironic' + $api_package = false + $conductor_package = false + $api_service = 'ironic-api' + $conductor_service = 'ironic-conductor' + $client_package = 'python-ironicclient' + + } elsif($::osfamily == 'Debian') { + + $package_name = 'ironic-common' + $api_service = 'ironic-api' + $conductor_service = 'ironic-conductor' + $api_package = 'ironic-api' + $conductor_package = 'ironic-conductor' + $client_package = 'python-ironicclient' + + } else { + + fail("Unsupported osfamily ${::osfamily}") + + } +} diff --git a/spec/classes/ironic_client_spec.rb b/spec/classes/ironic_client_spec.rb new file mode 100644 index 00000000..fa473f01 --- /dev/null +++ b/spec/classes/ironic_client_spec.rb @@ -0,0 +1,40 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Unit tests for ironic::client +# + +require 'spec_helper' + +describe 'ironic::client' do + + context 'on Debian platforms' do + let :facts do + { :osfamily => 'Debian' } + end + + it { should contain_class('ironic::client') } + end + + context 'on RedHat platforms' do + let :facts do + { :osfamily => 'RedHat' } + end + + it { should contain_class('ironic::client') } + end +end diff --git a/spec/classes/ironic_db_mysql_spec.rb b/spec/classes/ironic_db_mysql_spec.rb new file mode 100644 index 00000000..57897053 --- /dev/null +++ b/spec/classes/ironic_db_mysql_spec.rb @@ -0,0 +1,103 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Unit tests for ironic::db::mysql +# + +require 'spec_helper' + +describe 'ironic::db::mysql' do + + let :pre_condition do + 'include mysql::server' + end + + let :params do + { :password => 'passw0rd' } + end + let :facts do + { :osfamily => 'Debian' } + end + + + context 'on Debian platforms' do + let :facts do + { :osfamily => 'Debian' } + end + + it { should contain_class('ironic::db::mysql') } + end + + context 'on RedHat platforms' do + let :facts do + { :osfamily => 'RedHat' } + end + + it { should contain_class('ironic::db::mysql') } + end + + describe "overriding allowed_hosts param to array" do + let :params do + { + :password => 'ironicpass', + :allowed_hosts => ['127.0.0.1','%'] + } + end + + it {should_not contain_ironic__db__mysql__host_access("127.0.0.1").with( + :user => 'ironic', + :password => 'ironicpass', + :database => 'ironic' + )} + it {should contain_ironic__db__mysql__host_access("%").with( + :user => 'ironic', + :password => 'ironicpass', + :database => 'ironic' + )} + end + + describe "overriding allowed_hosts param to string" do + let :params do + { + :password => 'ironicpass2', + :allowed_hosts => '192.168.1.1' + } + end + + it {should contain_ironic__db__mysql__host_access("192.168.1.1").with( + :user => 'ironic', + :password => 'ironicpass2', + :database => 'ironic' + )} + end + + describe "overriding allowed_hosts param equals to host param " do + let :params do + { + :password => 'ironicpass2', + :allowed_hosts => '127.0.0.1' + } + end + + it {should_not contain_ironic__db__mysql__host_access("127.0.0.1").with( + :user => 'ironic', + :password => 'ironicpass2', + :database => 'ironic' + )} + end +end + diff --git a/spec/classes/ironic_init_spec.rb b/spec/classes/ironic_init_spec.rb new file mode 100644 index 00000000..c51a0a1b --- /dev/null +++ b/spec/classes/ironic_init_spec.rb @@ -0,0 +1,19 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Unit tests for ironic +# diff --git a/spec/classes/ironic_keystone_auth_spec.rb b/spec/classes/ironic_keystone_auth_spec.rb new file mode 100644 index 00000000..7e569fca --- /dev/null +++ b/spec/classes/ironic_keystone_auth_spec.rb @@ -0,0 +1,120 @@ +# +# Copyright (C) 2013 eNovance SAS +# +# Author: Emilien Macchi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Unit tests for ironic::keystone::auth +# + +require 'spec_helper' + +describe 'ironic::keystone::auth' do + + describe 'with default class parameters' do + let :params do + { + :password => 'ironic_password', + :tenant => 'foobar' + } + end + + it { should contain_keystone_user('ironic').with( + :ensure => 'present', + :password => 'ironic_password', + :tenant => 'foobar' + ) } + + it { should contain_keystone_user_role('ironic@foobar').with( + :ensure => 'present', + :roles => 'admin' + )} + + it { should contain_keystone_service('ironic').with( + :ensure => 'present', + :type => 'network', + :description => 'Ironic Networking Service' + ) } + + it { should contain_keystone_endpoint('RegionOne/ironic').with( + :ensure => 'present', + :public_url => "http://127.0.0.1:6385/", + :admin_url => "http://127.0.0.1:6385/", + :internal_url => "http://127.0.0.1:6385/" + ) } + + end + + describe 'when configuring ironic-server' do + let :pre_condition do + "class { 'ironic::server': auth_password => 'test' }" + end + + let :facts do + { :osfamily => 'Debian' } + end + + let :params do + { + :password => 'ironic_password', + :tenant => 'foobar' + } + end + + it { should contain_keystone_endpoint('RegionOne/ironic').with_notify('Service[ironic-server]') } + end + + describe 'when overriding public_protocol, public_port and public address' do + + let :params do + { + :password => 'ironic_password', + :public_protocol => 'https', + :public_port => '80', + :public_address => '10.10.10.10', + :port => '81', + :internal_address => '10.10.10.11', + :admin_address => '10.10.10.12' + } + end + + it { should contain_keystone_endpoint('RegionOne/ironic').with( + :ensure => 'present', + :public_url => "https://10.10.10.10:80/", + :internal_url => "http://10.10.10.11:81/", + :admin_url => "http://10.10.10.12:81/" + ) } + + end + + describe 'when overriding auth name' do + + let :params do + { + :password => 'foo', + :auth_name => 'ironicy' + } + end + + it { should contain_keystone_user('ironicy') } + + it { should contain_keystone_user_role('ironicy@services') } + + it { should contain_keystone_service('ironicy') } + + it { should contain_keystone_endpoint('RegionOne/ironicy') } + + end + +end diff --git a/spec/defines/ironic_db_mysql_host_access_spec.rb b/spec/defines/ironic_db_mysql_host_access_spec.rb new file mode 100644 index 00000000..d71fd31d --- /dev/null +++ b/spec/defines/ironic_db_mysql_host_access_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'ironic::db::mysql::host_access' do + + let :pre_condition do + 'include mysql' + end + + let :title do + '127.0.0.1' + end + + let :params do + { :user => 'ironic', + :password => 'passw0rd', + :database => 'ironic' } + end + + let :facts do + { :osfamily => 'Debian' } + end + + it { should contain_database_user('ironic@127.0.0.1') } + it { should contain_database_grant('ironic@127.0.0.1/ironic') } +end diff --git a/spec/fixtures/manifests/site.pp b/spec/fixtures/manifests/site.pp new file mode 100644 index 00000000..e69de29b diff --git a/spec/shared_examples.rb b/spec/shared_examples.rb new file mode 100644 index 00000000..d92156a3 --- /dev/null +++ b/spec/shared_examples.rb @@ -0,0 +1,5 @@ +shared_examples_for "a Puppet::Error" do |description| + it "with message matching #{description.inspect}" do + expect { should have_class_count(1) }.to raise_error(Puppet::Error, description) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..53d4dd02 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,7 @@ +require 'puppetlabs_spec_helper/module_spec_helper' +require 'shared_examples' + +RSpec.configure do |c| + c.alias_it_should_behave_like_to :it_configures, 'configures' + c.alias_it_should_behave_like_to :it_raises, 'raises' +end diff --git a/spec/unit/provider/ironic_spec.rb b/spec/unit/provider/ironic_spec.rb new file mode 100644 index 00000000..cf81ead2 --- /dev/null +++ b/spec/unit/provider/ironic_spec.rb @@ -0,0 +1,111 @@ +require 'puppet' +require 'spec_helper' +require 'puppet/provider/ironic' +require 'tempfile' + +describe Puppet::Provider::Ironic do + + def klass + described_class + end + + let :credential_hash do + { + 'auth_host' => '192.168.56.210', + 'auth_port' => '35357', + 'auth_protocol' => 'https', + 'admin_tenant_name' => 'admin_tenant', + 'admin_user' => 'admin', + 'admin_password' => 'password', + } + end + + let :auth_endpoint do + 'https://192.168.56.210:35357/v2.0/' + end + + let :credential_error do + /Ironic types will not work/ + end + + after :each do + klass.reset + end + + describe 'when determining credentials' do + + it 'should fail if config is empty' do + conf = {} + klass.expects(:ironic_conf).returns(conf) + expect do + klass.ironic_credentials + end.to raise_error(Puppet::Error, credential_error) + end + + it 'should fail if config does not have keystone_authtoken section.' do + conf = {'foo' => 'bar'} + klass.expects(:ironic_conf).returns(conf) + expect do + klass.ironic_credentials + end.to raise_error(Puppet::Error, credential_error) + end + + it 'should fail if config does not contain all auth params' do + conf = {'keystone_authtoken' => {'invalid_value' => 'foo'}} + klass.expects(:ironic_conf).returns(conf) + expect do + klass.ironic_credentials + end.to raise_error(Puppet::Error, credential_error) + end + + it 'should use specified host/port/protocol in the auth endpoint' do + conf = {'keystone_authtoken' => credential_hash} + klass.expects(:ironic_conf).returns(conf) + klass.get_auth_endpoint.should == auth_endpoint + end + + end + + describe 'when invoking the ironic cli' do + + it 'should set auth credentials in the environment' do + authenv = { + :OS_AUTH_URL => auth_endpoint, + :OS_USERNAME => credential_hash['admin_user'], + :OS_TENANT_NAME => credential_hash['admin_tenant_name'], + :OS_PASSWORD => credential_hash['admin_password'], + } + klass.expects(:get_ironic_credentials).with().returns(credential_hash) + klass.expects(:withenv).with(authenv) + klass.auth_ironic('test_retries') + end + + ['[Errno 111] Connection refused', + '(HTTP 400)'].reverse.each do |valid_message| + it "should retry when ironic cli returns with error #{valid_message}" do + klass.expects(:get_ironic_credentials).with().returns({}) + klass.expects(:sleep).with(10).returns(nil) + klass.expects(:ironic).twice.with(['test_retries']).raises( + Exception, valid_message).then.returns('') + klass.auth_ironic('test_retries') + end + end + + end + + describe 'when listing ironic resources' do + + it 'should exclude the column header' do + output = <<-EOT + id + net1 + net2 + EOT + klass.expects(:auth_ironic).returns(output) + result = klass.list_ironic_resources('foo') + result.should eql(['net1', 'net2']) + end + + end + +end