diff --git a/lib/puppet/provider/cinder_qos/openstack.rb b/lib/puppet/provider/cinder_qos/openstack.rb new file mode 100644 index 00000000..bf2c9a8c --- /dev/null +++ b/lib/puppet/provider/cinder_qos/openstack.rb @@ -0,0 +1,90 @@ +require File.join(File.dirname(__FILE__), '..','..','..', 'puppet/provider/cinder') + +Puppet::Type.type(:cinder_qos).provide( + :openstack, + :parent => Puppet::Provider::Cinder +) do + + desc 'Provider for cinder QOS.' + + @credentials = Puppet::Provider::Openstack::CredentialsV3.new + + mk_resource_methods + + def create + properties = [] + unless resource[:consumer].empty? + properties << '--consumer' << resource[:consumer] + end + resource[:properties].each do |item| + properties << '--property' << item + end + properties << name + self.class.request('volume qos', 'create', properties) + @property_hash[:ensure] = :present + @property_hash[:properties] = resource[:properties] + @property_hash[:consumer] = resource[:consumer] + @property_hash[:name] = name + unless resource[:associations].empty? + resource[:associations].each do |item| + self.class.request('volume qos', 'associate', [name, item]) + end + @property_hash[:associations] = resource[:associations] + end + end + + def destroy + self.class.request('volume qos', 'delete', name) + @property_hash.clear + end + + def exists? + @property_hash[:ensure] == :present + end + + def properties=(value) + properties = [] + (value - @property_hash[:properties]).each do |item| + properties << '--property' << item + end + unless properties.empty? + self.class.request('volume qos', 'set', [properties, name]) + @property_hash[:properties] = value + end + end + + def associations=(value) + properties = value - @property_hash[:associations] + properties.each do |item| + self.class.request('volume qos', 'associate', [name, item]) + @property_hash[:associations] = value + end + end + + def self.instances + list = request('volume qos', 'list') + list.collect do |qos| + new({ + :name => qos[:name], + :ensure => :present, + :id => qos[:id], + :properties => string2array(qos[:specs]), + :consumer => qos[:consumer], + :associations => string2array(qos[:associations]) + }) + end + end + + def self.prefetch(resources) + qoss = instances + resources.keys.each do |name| + if provider = qoss.find{ |qos| qos.name == name } + resources[name].provider = provider + end + end + end + + def self.string2array(input) + return input.delete("'").split(/,\s/) + end +end diff --git a/lib/puppet/type/cinder_qos.rb b/lib/puppet/type/cinder_qos.rb new file mode 100644 index 00000000..b9e012ce --- /dev/null +++ b/lib/puppet/type/cinder_qos.rb @@ -0,0 +1,43 @@ +Puppet::Type.newtype(:cinder_qos) do + + desc 'Type for managing cinder QOS.' + + ensurable + + newparam(:name, :namevar => true) do + newvalues(/\S+/) + end + + newproperty(:associations, :array_matching => :all) do + desc 'The cinder Types associations. Should be an array' + defaultto [] + def insync?(is) + return false unless is.is_a? Array + is.sort == should.sort + end + validate do |value| + raise ArgumentError, "Types name doesn't match" unless value.match(/^[-\w]+$/) + end + end + + newproperty(:properties, :array_matching => :all) do + desc 'The Properties of the cinder qos. Should be an array' + defaultto [] + def insync?(is) + return false unless is.is_a? Array + is.sort == should.sort + end + validate do |value| + raise ArgumentError, "Properties doesn't match" unless value.match(/^\s*[^=\s]+=[^=\s]+$/) + end + end + + newparam(:consumer) do + desc 'The consumer QOS' + defaultto('') + end + + autorequire(:anchor) do + ['cinder::service::end'] + end +end diff --git a/manifests/qos.pp b/manifests/qos.pp new file mode 100644 index 00000000..ab915e5c --- /dev/null +++ b/manifests/qos.pp @@ -0,0 +1,33 @@ +# == Define: cinder::qos +# +# Creates cinder QOS and assigns properties and volume type +# +# === Parameters +# +# [*associations*] +# (optional) List of cinder type associated with this QOS +# Defaults to 'undef'. +# +# [*consumer*] +# (optional) QOS consumer parameter (typicaly front-end/back-end/both) +# Defaults to 'undef'. +# +# [*properties*] +# (optional) List QOS properties +# Defaults to 'undef'. +# +define cinder::qos ( + $associations = undef, + $consumer = undef, + $properties = undef, +) { + + include ::cinder::deps + + cinder_qos { $name: + ensure => present, + properties => $properties, + consumer => $consumer, + associations => $associations, + } +} diff --git a/spec/defines/cinder_qos_spec.rb b/spec/defines/cinder_qos_spec.rb new file mode 100644 index 00000000..caee023f --- /dev/null +++ b/spec/defines/cinder_qos_spec.rb @@ -0,0 +1,25 @@ + +require 'spec_helper' + +describe 'cinder::qos' do + + let(:title) {'tomato'} + + context 'default creation' do + it 'should create basic qos' do + should contain_cinder_qos('tomato').with(:ensure => :present) + end + end + + context 'creation with properties' do + let :params do { + :properties => ['var1=value1','var2=value2'], + :associations => ['vol_type1','vol_type2'], + :consumer => 'front-end', + } + end + it 'should create qos with properties' do + should contain_cinder_qos('tomato').with(:ensure => :present, :properties => ['var1=value1','var2=value2'], :associations => ['vol_type1','vol_type2'], :consumer => 'front-end') + end + end +end diff --git a/spec/unit/provider/cinder_qos/openstack_spec.rb b/spec/unit/provider/cinder_qos/openstack_spec.rb new file mode 100644 index 00000000..13beb94c --- /dev/null +++ b/spec/unit/provider/cinder_qos/openstack_spec.rb @@ -0,0 +1,89 @@ +require 'puppet' +require 'puppet/provider/cinder_qos/openstack' + +provider_class = Puppet::Type.type(:cinder_qos).provider(:openstack) + +describe provider_class do + + let(:set_creds_env) do + ENV['OS_USERNAME'] = 'test' + ENV['OS_PASSWORD'] = 'abc123' + ENV['OS_PROJECT_NAME'] = 'test' + ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000' + end + + let(:type_attributes) do + { + :name => 'QoS_1', + :ensure => :present, + :properties => ['key1=value1', 'key2=value2'], + } + end + + let(:resource) do + Puppet::Type::Cinder_qos.new(type_attributes) + end + + let(:provider) do + provider_class.new(resource) + end + + before(:each) { set_creds_env } + + after(:each) do + Puppet::Type.type(:cinder_qos).provider(:openstack).reset + provider_class.reset + end + + describe 'managing qos' do + describe '#create' do + it 'creates a qos' do + provider_class.expects(:openstack) + .with('volume qos', 'create', '--format', 'shell', ['--property', 'key1=value1', '--property', 'key2=value2', 'QoS_1']) + .returns('id="e0df397a-72d5-4494-9e26-4ac37632ff04" +name="QoS_1" +properties="key1=\'value1\', key2=\'value2\'" +') + provider.create + expect(provider.exists?).to be_truthy + end + end + + describe '#destroy' do + it 'destroys a qos' do + provider_class.expects(:openstack) + .with('volume qos', 'delete', 'QoS_1') + provider.destroy + expect(provider.exists?).to be_falsey + end + end + + describe '#instances' do + it 'finds qos' do + provider_class.expects(:openstack) + .with('volume qos', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Name","Consumer","Associations","Specs" +"28b632e8-6694-4bba-bf68-67b19f619019","qos-1","front-end","my_type1, my_type2","read_iops=\'value1\', write_iops=\'value2\'" +"4f992f69-14ec-4132-9313-55cc06a6f1f6","qos-2","both","","" +') + instances = provider_class.instances + expect(instances.count).to eq(2) + expect(instances[0].name).to eq('qos-1') + expect(instances[0].associations).to eq(['my_type1', 'my_type2']) + expect(instances[0].consumer).to eq('front-end') + expect(instances[0].properties).to eq(['read_iops=value1', 'write_iops=value2']) + expect(instances[1].name).to eq('qos-2') + expect(instances[1].consumer).to eq('both') + expect(instances[1].associations).to eq([]) + expect(instances[1].properties).to eq([]) + end + end + + describe '#string2array' do + it 'should return an array with key-value' do + s = "key='value', key2='value2'" + expect(provider_class.string2array(s)).to eq(['key=value', 'key2=value2']) + end + end + end +end diff --git a/spec/unit/type/cinder_qos_spec.rb b/spec/unit/type/cinder_qos_spec.rb new file mode 100644 index 00000000..5459bc33 --- /dev/null +++ b/spec/unit/type/cinder_qos_spec.rb @@ -0,0 +1,53 @@ +require 'puppet' +require 'puppet/type/cinder_qos' + +describe Puppet::Type.type(:cinder_qos) do + + before :each do + Puppet::Type.rmtype(:cinder_qos) + end + + it 'should reject an invalid property value' do + incorrect_input = { + :name => 'test_qos', + :properties => ['some_key1 = some_value2'] + } + expect { Puppet::Type.type(:cinder_qos).new(incorrect_input) }.to raise_error(Puppet::ResourceError, /Parameter properties failed/) + end + + it 'should default to no properties and no associations' do + catalog = Puppet::Resource::Catalog.new + anchor = Puppet::Type.type(:anchor).new(:name => 'cinder::service::end') + correct_input = { + :name => 'test_qos', + } + cinder_qos = Puppet::Type.type(:cinder_qos).new(correct_input) + expect(cinder_qos[:properties]).to eq([]) + expect(cinder_qos[:associations]).to eq([]) + + catalog.add_resource anchor, cinder_qos + dependency = cinder_qos.autorequire + expect(dependency.size).to eq(1) + expect(dependency[0].target).to eq(cinder_qos) + expect(dependency[0].source).to eq(anchor) + end + + + it 'should autorequire cinder-api service' do + catalog = Puppet::Resource::Catalog.new + anchor = Puppet::Type.type(:anchor).new(:name => 'cinder::service::end') + properties = ['read_iops=value1', 'write_iops=value2'] + correct_input = { + :name => 'test_qos', + :properties => properties, + } + cinder_qos = Puppet::Type.type(:cinder_qos).new(correct_input) + expect(cinder_qos[:properties]).to eq(properties) + + catalog.add_resource anchor, cinder_qos + dependency = cinder_qos.autorequire + expect(dependency.size).to eq(1) + expect(dependency[0].target).to eq(cinder_qos) + expect(dependency[0].source).to eq(anchor) + end +end