diff --git a/deployment/puppet/cgroups/Gemfile b/deployment/puppet/cgroups/Gemfile new file mode 100644 index 0000000000..eedceee61d --- /dev/null +++ b/deployment/puppet/cgroups/Gemfile @@ -0,0 +1,19 @@ +source 'https://rubygems.org' + +group :development, :test do + gem 'rake', :require => false + gem 'pry', :require => false + gem 'rspec', '~>3.3', :require => false + gem 'rspec-puppet', '~>2.2.0', :require => false + gem 'puppetlabs_spec_helper', :require => false + gem 'puppet-lint', '~> 0.3.2' + gem 'json', :require => false +end + +if puppetversion = ENV['PUPPET_GEM_VERSION'] + gem 'puppet', puppetversion, :require => false +else + gem 'puppet', '<4.0', :require => false +end + +# vim:ft=ruby diff --git a/deployment/puppet/cgroups/Rakefile b/deployment/puppet/cgroups/Rakefile new file mode 100644 index 0000000000..484f1d2f2e --- /dev/null +++ b/deployment/puppet/cgroups/Rakefile @@ -0,0 +1,8 @@ +require 'rubygems' +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' + +PuppetLint.configuration.fail_on_warnings = false +PuppetLint.configuration.send('disable_80chars') +PuppetLint.configuration.send('disable_class_parameter_defaults') +PuppetLint.configuration.send('disable_class_inherits_from_params_class') diff --git a/deployment/puppet/cgroups/lib/puppet/parser/functions/prepare_cgroups_hash.rb b/deployment/puppet/cgroups/lib/puppet/parser/functions/prepare_cgroups_hash.rb new file mode 100644 index 0000000000..11f6539085 --- /dev/null +++ b/deployment/puppet/cgroups/lib/puppet/parser/functions/prepare_cgroups_hash.rb @@ -0,0 +1,72 @@ +require 'json' + +module CgroupsSettings + require 'facter' + # value is valid if value has integer type or + # matches with pattern: %percent, min_value, max_value + def self.handle_value(group, value) + return value if value.is_a?(Numeric) + if group == 'memory' and value.match(/%(\d+), (\d+), (\d+)/) + percent, min, max = value.scan(/%(\d+), (\d+), (\d+)/).flatten.map { |i| i.to_i } + total_memory = Facter.value(:memorysize_mb) + res = (total_memory.to_f / 100.0) * percent.to_f + return [min, max, res].sort[1] + end + end +end + +Puppet::Parser::Functions::newfunction(:prepare_cgroups_hash, :type => :rvalue, :arity => 1, :doc => <<-EOS + This function get hash contains service and its cgroups settings(in JSON format) and serialize it. + + ex: prepare_cgroups_hash(hiera('cgroups')) + + Following input: + { + 'metadata' => { + 'always_editable' => true, + 'group' => 'general', + 'label' => 'Cgroups', + 'weight' => 50 + }, + cinder-api: {"blkio":{"blkio.weight":500}} + } + + will be transformed to: + [{"cinder-api"=>{"blkio"=>{"blkio.weight"=>500}}}] + + Pattern for value field: + { + group1 => { + param1 => value1, + param2 => value2 + }, + group2 => { + param3 => value3, + param4 => value4 + } + } + + EOS + ) do |argv| + raise(Puppet::ParseError, "prepare_cgroups_hash(...): Wrong type of argument. Hash is expected.") unless argv[0].is_a?(Hash) + + # wipe out UI metadata + cgroups = argv[0].tap { |el| el.delete('metadata') } + + serialized_data = {} + + cgroups.each do |service, settings| + hash_settings = JSON.parse(settings) rescue raise("'#{service}': JSON parsing error for : #{settings}") + hash_settings.each do |group, options| + raise("'#{service}': group '#{group}' options is not a HASH instance") unless options.is_a?(Hash) + options.each do |option, value| + options[option] = CgroupsSettings.handle_value(group, value) + raise("'#{service}': group '#{group}': option '#{option}' has wrong value") if options[option].nil? + end + end + serialized_data[service] = hash_settings + end + serialized_data +end + +# vim: set ts=2 sw=2 et : diff --git a/deployment/puppet/cgroups/spec/functions/prepare_cgroups_hash__spec.rb b/deployment/puppet/cgroups/spec/functions/prepare_cgroups_hash__spec.rb new file mode 100644 index 0000000000..5a2dfb23b7 --- /dev/null +++ b/deployment/puppet/cgroups/spec/functions/prepare_cgroups_hash__spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +describe Puppet::Parser::Functions.function(:prepare_cgroups_hash) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + subject do + function_name = Puppet::Parser::Functions.function(:prepare_cgroups_hash) + scope.method(function_name) + end + + it 'should exist' do + subject == Puppet::Parser::Functions.function(:prepare_cgroups_hash) + end + + Facter.stubs(:fact).with(:memorysize_mb).returns Facter.add(:memorysize_mb) { setcode { 1024 } } + + context "transform simple hash" do + let(:sample) { + { + 'metadata' => { + 'always_editable' => true, + 'group' => 'general', + 'label' => 'Cgroups', + 'weight' => 50 + }, + 'cinder' => '{"blkio":{"blkio.weight":500, "blkio.test":800}, "memory":{"memory.soft_limit_in_bytes":700}}', + 'keystone' => '{"cpu":{"cpu.shares":70}}' + } + } + + let(:result) { + { + 'cinder' => { + 'blkio' => { + 'blkio.weight' => 500, + 'blkio.test' => 800 + }, + 'memory' => { + 'memory.soft_limit_in_bytes' => 700 + }, + }, + 'keystone' => { + 'cpu' => { + 'cpu.shares' => 70 + } + } + } + } + + it 'should transform hash with simple values' do + should run.with_params(sample).and_return(result) + end + + end + + + context "transform hash with expression" do + + let(:sample) { + { + 'metadata' => { + 'always_editable' => true, + 'group' => 'general', + 'label' => 'Cgroups', + 'weight' => 50 + }, + 'neutron' => '{"memory":{"memory.soft_limit_in_bytes":"%50, 300, 700"}}' + } + } + + let(:result) { + { + 'neutron' => { + 'memory' => { + 'memory.soft_limit_in_bytes' => 512 + } + } + } + } + + it 'should transform hash including expression to compute' do + should run.with_params(sample).and_return(result) + end + + end + + context "wrong JSON format" do + + let(:sample) { + { + 'neutron' => '{"memory":{"memory.soft_limit_in_bytes":"%50, 300, 700"}}}}' + } + } + + let(:result) { + {} + } + + it 'should raise if settings have wrong JSON format' do + is_expected.to run.with_params(sample).and_raise_error(RuntimeError, /JSON parsing error/) + end + + end + + context "service's cgroup settings are not a HASH" do + + let(:sample) { + { + 'neutron' => '{"memory": 28}' + } + } + + it 'should raise if group option is not a Hash' do + is_expected.to run.with_params(sample).and_raise_error(RuntimeError, /options is not a HASH instance/) + end + + end + + context "cgroup limit is not an integer" do + + let(:sample) { + { + 'neutron' => '{"memory":{"memory.soft_limit_in_bytes":"test"}}' + } + } + + it 'should raise if limit value is not an integer or template' do + is_expected.to run.with_params(sample).and_raise_error(RuntimeError, /has wrong value/) + end + + end + +end diff --git a/deployment/puppet/cgroups/spec/spec_helper.rb b/deployment/puppet/cgroups/spec/spec_helper.rb new file mode 100644 index 0000000000..edc5252dfc --- /dev/null +++ b/deployment/puppet/cgroups/spec/spec_helper.rb @@ -0,0 +1,30 @@ +require 'rubygems' +require 'puppetlabs_spec_helper/module_spec_helper' + +fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures')) + +PROJECT_ROOT = File.expand_path('..', File.dirname(__FILE__)) +$LOAD_PATH.unshift(File.join(PROJECT_ROOT, "lib")) + +# Add fixture lib dirs to LOAD_PATH. Work-around for PUP-3336 +if Puppet.version < '4.0.0' + Dir["#{fixture_path}/modules/*/lib"].entries.each do |lib_dir| + $LOAD_PATH << lib_dir + end +end + +RSpec.configure do |c| + c.module_path = File.join(fixture_path, 'modules') + c.manifest_dir = File.join(fixture_path, 'manifests') + c.mock_with(:mocha) + c.alias_it_should_behave_like_to :it_configures, 'configures' +end + +def puppet_debug_override + if ENV['SPEC_PUPPET_DEBUG'] + Puppet::Util::Log.level = :debug + Puppet::Util::Log.newdestination(:console) + end +end + +###