diff --git a/lib/puppet/provider/manila.rb b/lib/puppet/provider/manila.rb new file mode 100644 index 00000000..039ce8a6 --- /dev/null +++ b/lib/puppet/provider/manila.rb @@ -0,0 +1,100 @@ +File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) } + +require 'puppet/util/inifile' +require 'puppet/provider/openstack' +require 'puppet/provider/openstack/auth' +require 'puppet/provider/openstack/credentials' + +class Puppet::Provider::Manila < Puppet::Provider::Openstack + + extend Puppet::Provider::Openstack::Auth + + def self.conf_filename + '/etc/manila/manila.conf' + end + + def self.manila_conf + return @manila_conf if @manila_conf + @manila_conf = Puppet::Util::IniConfig::File.new + @manila_conf.read(conf_filename) + @manila_conf + end + + def self.request(service, action, properties=nil) + begin + super + rescue Puppet::Error::OpenstackAuthInputError, Puppet::Error::OpenstackUnauthorizedError => error + manila_request(service, action, error, properties) + end + end + + def self.manila_request(service, action, error, properties=nil) + properties ||= [] + @credentials.username = manila_credentials['username'] + @credentials.password = manila_credentials['password'] + @credentials.project_name = manila_credentials['project_name'] + @credentials.auth_url = auth_endpoint + @credentials.user_domain_name = manila_credentials['user_domain_name'] + @credentials.project_domain_name = manila_credentials['project_domain_name'] + if manila_credentials['region_name'] + @credentials.region_name = manila_credentials['region_name'] + end + raise error unless @credentials.set? + Puppet::Provider::Openstack.request(service, action, properties, @credentials) + end + + def self.manila_credentials + @manila_credentials ||= get_manila_credentials + end + + def manila_credentials + self.class.manila_credentials + end + + def self.get_manila_credentials + auth_keys = ['auth_url', 'project_name', 'username', + 'password'] + conf = manila_conf + if conf and conf['keystone_authtoken'] and + auth_keys.all?{|k| !conf['keystone_authtoken'][k].nil?} + creds = Hash[ auth_keys.map \ + { |k| [k, conf['keystone_authtoken'][k].strip] } ] + if conf['keystone_authtoken']['project_domain_name'] + creds['project_domain_name'] = conf['keystone_authtoken']['project_domain_name'] + else + creds['project_domain_name'] = 'Default' + end + + if conf['keystone_authtoken']['user_domain_name'] + creds['user_domain_name'] = conf['keystone_authtoken']['user_domain_name'] + else + creds['user_domain_name'] = 'Default' + end + + if conf['keystone_authtoken']['region_name'] + creds['region_name'] = conf['keystone_authtoken']['region_name'] + end + + return creds + else + raise(Puppet::Error, "File: #{conf_filename} does not contain all " + + "required sections. Manila types will not work if manila is not " + + "correctly configured.") + end + end + + def self.get_auth_endpoint + q = manila_credentials + "#{q['auth_url']}" + end + + def self.auth_endpoint + @auth_endpoint ||= get_auth_endpoint + end + + def self.reset + @manila_conf = nil + @manila_credentials = nil + @auth_endpoint = nil + end +end diff --git a/lib/puppet/provider/manila_type/openstack.rb b/lib/puppet/provider/manila_type/openstack.rb new file mode 100644 index 00000000..435cc21e --- /dev/null +++ b/lib/puppet/provider/manila_type/openstack.rb @@ -0,0 +1,154 @@ +require File.join(File.dirname(__FILE__), '..','..','..', 'puppet/provider/manila') + +Puppet::Type.type(:manila_type).provide( + :openstack, + :parent => Puppet::Provider::Manila +) do + + desc 'Provider for manila types.' + + @credentials = Puppet::Provider::Openstack::CredentialsV3.new + + mk_resource_methods + + def initialize(value={}) + super(value) + @property_flush = {} + end + + def self.do_not_manage + @do_not_manage + end + + def self.do_not_manage=(value) + @do_not_manage = value + end + + def create + if self.class.do_not_manage + fail("Not managing Manila_type[#{@resource[:name]}] due to earlier Manila API failures.") + end + opts = [@resource[:name]] + opts << @resource[:driver_handles_share_servers].to_s.capitalize + opts << '--public' << @resource[:is_public].to_s.capitalize + opts << '--snapshot-support' << @resource[:snapshot_support].to_s.capitalize + opts << '--create-share-from-snapshot-support' << @resource[:create_share_from_snapshot_support].to_s.capitalize + opts << '--revert-to-snapshot-support' << @resource[:revert_to_snapshot_support].to_s.capitalize + opts << '--mount-snapshot-support' << @resource[:mount_snapshot_support].to_s.capitalize + + self.class.request('share type', 'create', opts) + + [ + :name, + :is_public, + :driver_handles_share_servers, + :snapshot_support, + :create_share_from_snapshot_support, + :revert_to_snapshot_support, + :mount_snapshot_support + ].each do |attr| + @property_hash[attr] = @resource[attr] + end + @property_hash[:ensure] = :present + end + + def destroy + if self.class.do_not_manage + fail("Not managing Manila_type[#{@resource[:name]}] due to earlier Manila API failures.") + end + self.class.request('share type', 'delete', name) + @property_hash.clear + @property_hash[:ensure] = :absent + end + + def exists? + @property_hash[:ensure] == :present + end + + def self.parse_specs(specs) + JSON.parse(specs.gsub(/'/, '"')) + end + + def self.instances + self.do_not_manage = true + list = request('share type', 'list').collect do |type| + required_extra_specs = self.parse_specs(type[:required_extra_specs]) + optional_extra_specs = self.parse_specs(type[:optional_extra_specs]) + + new({ + :name => type[:name], + :ensure => :present, + :id => type[:id], + :is_public => (type[:visibility] == 'public'), + :driver_handles_share_servers => (required_extra_specs['driver_handles_share_servers'] == 'True'), + :snapshot_support => (optional_extra_specs['snapshot_support'] == 'True'), + :create_share_from_snapshot_support => (optional_extra_specs['create_share_from_snapshot_support'] == 'True'), + :revert_to_snapshot_support => (optional_extra_specs['revert_to_snapshot_support'] == 'True'), + :mount_snapshot_support => (optional_extra_specs['mount_snapshot_support'] == 'True'), + }) + end + self.do_not_manage = false + list + end + + def self.prefetch(resources) + types = instances + resources.keys.each do |name| + if provider = types.find{ |type| type.name == name } + resources[name].provider = provider + end + end + end + + def flush + if !@property_flush.empty? + opts = [@resource[:name]] + + if @property_flush.has_key?(:is_public) + opts << '--public' << @property_flush[:is_public].to_s.capitalize + end + + if @property_flush.has_key?(:snapshot_support) + opts << '--snapshot-support' << @property_flush[:snapshot_support].to_s.capitalize + end + + if @property_flush.has_key?(:create_share_from_snapshot_support) + opts << '--create-share-from-snapshot-support' << @property_flush[:create_share_from_snapshot_support].to_s.capitalize + end + + if @property_flush.has_key?(:revert_to_snapshot_support) + opts << '--revert-to-snapshot-support' << @property_flush[:revert_to_snapshot_support].to_s.capitalize + end + + if @property_flush.has_key?(:mount_snapshot_support) + opts << '--mount-snapshot-support' << @property_flush[:mount_snapshot_support].to_s.capitalize + end + + self.class.request('share type', 'set', opts) + @property_flush.clear + end + end + + [ + :is_public, + :snapshot_support, + :create_share_from_snapshot_support, + :revert_to_snapshot_support, + :mount_snapshot_support + ].each do |attr| + define_method(attr.to_s + "=") do |value| + if self.class.do_not_manage + fail("Not managing Manila_type[#{@resource[:name]}] due to earlier Manila API failures.") + end + @property_flush[attr] = value + end + end + + [ + :driver_handles_share_servers, + ].each do |attr| + define_method(attr.to_s + "=") do |value| + fail("Property #{attr.to_s} does not support being updated") + end + end +end diff --git a/lib/puppet/type/manila_type.rb b/lib/puppet/type/manila_type.rb new file mode 100644 index 00000000..97f58714 --- /dev/null +++ b/lib/puppet/type/manila_type.rb @@ -0,0 +1,50 @@ +Puppet::Type.newtype(:manila_type) do + + desc 'Type for managing manila types.' + + ensurable + + newparam(:name, :namevar => true) do + newvalues(/\S+/) + end + + newparam(:is_public, :boolean => true) do + desc 'Whether the type is public or not. Default to `true`' + newvalues(:true, :false) + defaultto true + end + + newparam(:driver_handles_share_servers, :boolean => true) do + desc 'Whether the driver handles share servers. Default to `false`' + newvalues(:true, :false) + defaultto false + end + + newparam(:snapshot_support, :boolean => true) do + desc 'Filter backends by their capability to create share snapshots' + newvalues(:true, :false) + defaultto false + end + + newparam(:create_share_from_snapshot_support, :boolean => true) do + desc 'Filter backends by their capability to create shares from snapshots.' + newvalues(:true, :false) + defaultto false + end + + newparam(:revert_to_snapshot_support, :boolean => true) do + desc 'Filter backends by their capability to revert shares to snapshots.' + newvalues(:true, :false) + defaultto false + end + + newparam(:mount_snapshot_support, :boolean => true) do + desc 'Filter backends by their capability to mount share snapshots.' + newvalues(:true, :false) + defaultto false + end + + autorequire(:anchor) do + ['manila::service::end'] + end +end diff --git a/manifests/type.pp b/manifests/type.pp index 9ff19af4..84f33b02 100644 --- a/manifests/type.pp +++ b/manifests/type.pp @@ -1,5 +1,6 @@ # ==Define: manila::type # +# DEPRECATED!! # Creates manila type and assigns backends. # # === Parameters @@ -48,8 +49,7 @@ define manila::type ( include manila::deps include manila::client -# TODO: (xarses) This should be moved to a ruby provider so that among other -# reasons, the credential discovery magic can occur like in neutron. + warning('The manila::type resource type is deprecated. Use the manila_type resource instead') $manila_env = [ "OS_TENANT_NAME=${os_tenant_name}", diff --git a/manifests/type_set.pp b/manifests/type_set.pp index 9c666c82..b629a7e1 100644 --- a/manifests/type_set.pp +++ b/manifests/type_set.pp @@ -41,8 +41,7 @@ define manila::type_set ( include manila::deps include manila::client -# TODO: (xarses) This should be moved to a ruby provider so that among other -# reasons, the credential discovery magic can occur like in neutron. + warning('The manila::type_set resource type is deprecated. Use the manila_type resource instead') $manila_env = [ "OS_TENANT_NAME=${os_tenant_name}", diff --git a/releasenotes/notes/manila_type-42266cf70a5f0f66.yaml b/releasenotes/notes/manila_type-42266cf70a5f0f66.yaml new file mode 100644 index 00000000..4b1f20b8 --- /dev/null +++ b/releasenotes/notes/manila_type-42266cf70a5f0f66.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + The new ``manila_type`` resource has been added. This can be used to manage + share types in Manila. + +deprecations: + - | + The ``manila::type`` resource type and the ``manila::type_set`` resource + type has been deprecated in favor of the new ``manila_type`` resource. diff --git a/spec/acceptance/basic_manila_spec.rb b/spec/acceptance/basic_manila_spec.rb index cb9741e7..f9777a5a 100644 --- a/spec/acceptance/basic_manila_spec.rb +++ b/spec/acceptance/basic_manila_spec.rb @@ -63,6 +63,9 @@ describe 'basic manila' do class { 'manila::cron::db_purge': } # missing: backends, share, service_instance + + manila_type { 'sharetype': + } EOS diff --git a/spec/unit/provider/manila_spec.rb b/spec/unit/provider/manila_spec.rb new file mode 100644 index 00000000..f199722c --- /dev/null +++ b/spec/unit/provider/manila_spec.rb @@ -0,0 +1,48 @@ +require 'puppet' +require 'spec_helper' +require 'puppet/provider/manila' +require 'tempfile' + +klass = Puppet::Provider::Manila + +describe Puppet::Provider::Manila do + + after :each do + klass.reset + end + + describe 'when retrieving the auth credentials' do + + it 'should fail if no auth params are passed and the manila config file does not have the expected contents' do + mock = {} + Puppet::Util::IniConfig::File.expects(:new).returns(mock) + mock.expects(:read).with('/etc/manila/manila.conf') + expect do + klass.manila_credentials + end.to raise_error(Puppet::Error, /Manila types will not work/) + end + + it 'should read conf file with all sections' do + creds_hash = { + 'auth_url' => 'https://192.168.56.210:5000/v3/', + 'project_name' => 'admin_tenant', + 'username' => 'admin', + 'password' => 'password', + 'project_domain_name' => 'Default', + 'user_domain_name' => 'Default', + } + mock = { + 'keystone_authtoken' => { + 'auth_url' => 'https://192.168.56.210:5000/v3/', + 'project_name' => 'admin_tenant', + 'username' => 'admin', + 'password' => 'password', + } + } + Puppet::Util::IniConfig::File.expects(:new).returns(mock) + mock.expects(:read).with('/etc/manila/manila.conf') + expect(klass.manila_credentials).to eq(creds_hash) + end + + end +end diff --git a/spec/unit/provider/manila_type/openstack_spec.rb b/spec/unit/provider/manila_type/openstack_spec.rb new file mode 100644 index 00000000..92179523 --- /dev/null +++ b/spec/unit/provider/manila_type/openstack_spec.rb @@ -0,0 +1,135 @@ +require 'puppet' +require 'puppet/provider/manila_type/openstack' + +provider_class = Puppet::Type.type(:manila_type).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 => 'test_type', + :ensure => :present, + :is_public => true, + :driver_handles_share_servers => false, + :snapshot_support => false, + :create_share_from_snapshot_support => false, + :revert_to_snapshot_support => false, + :mount_snapshot_support => false, + } + end + + let(:resource) do + Puppet::Type::Manila_type.new(type_attributes) + end + + let(:provider) do + provider_class.new(resource) + end + + before(:each) { set_creds_env } + + after(:each) do + Puppet::Type.type(:manila_type).provider(:openstack).reset + provider_class.reset + end + + describe 'managing type' do + describe '#create' do + context 'with defaults' do + it 'creates a type' do + provider_class.expects(:openstack) + .with('share type', 'create', '--format', 'shell', + ['test_type', 'False', '--public', 'True', + '--snapshot-support', 'False', + '--create-share-from-snapshot-support', 'False', + '--revert-to-snapshot-support', 'False', + '--mount-snapshot-support', 'False']) + .returns('id="90e19aff-1b35-4d60-9ee3-383c530275ab" +name="test_type" +visibility="public" +required_extra_specs="{\'driver_handles_share_servers\': \'False\'}" +optional_extra_specs="{\'snapshot_support\': \'False\', \'create_share_from_snapshot_support\': \'False\', \'revert_to_snapshot_support\': \'False\', \'mount_snapshot_support\': \'False\'}" +descrpiton="None" +') + provider.create + expect(provider.exists?).to be_truthy + end + end + end + + describe '#instances' do + it 'finds types' do + provider_class.expects(:openstack) + .with('share type', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Name","Visibility","Is Default","Required Extra Specs","Optional Extra Specs","Description" +"90e19aff-1b35-4d60-9ee3-383c530275ab","type0","public","False","{\'driver_handles_share_servers\': \'True\'}","{\'snapshot_support\': \'True\'}","" +"90e19aff-1b35-4d60-9ee3-383c530275ab","type1","private","False","{\'driver_handles_share_servers\': \'False\'}","{}","" +') + instances = provider_class.instances + expect(instances.count).to eq(2) + + expect(instances[0].name).to eq('type0') + expect(instances[0].is_public).to be true + expect(instances[0].driver_handles_share_servers).to be true + expect(instances[0].snapshot_support).to be true + expect(instances[0].create_share_from_snapshot_support).to be false + expect(instances[0].revert_to_snapshot_support).to be false + expect(instances[0].mount_snapshot_support).to be false + + expect(instances[1].name).to eq('type1') + expect(instances[1].is_public).to be false + expect(instances[1].driver_handles_share_servers).to be false + expect(instances[1].snapshot_support).to be false + expect(instances[1].create_share_from_snapshot_support).to be false + expect(instances[1].revert_to_snapshot_support).to be false + expect(instances[1].mount_snapshot_support).to be false + end + end + + describe '#flush' do + context '.is_public' do + it 'updates type' do + provider_class.expects(:openstack) + .with('share type', 'set', ['test_type', '--public', 'False']) + provider.is_public = false + provider.flush + + provider_class.expects(:openstack) + .with('share type', 'set', ['test_type', '--public', 'True']) + provider.is_public = true + provider.flush + end + end + + context '.snapshot_support' do + it 'updates type' do + provider_class.expects(:openstack) + .with('share type', 'set', ['test_type', '--snapshot-support', 'False']) + provider.snapshot_support = false + provider.flush + + provider_class.expects(:openstack) + .with('share type', 'set', ['test_type', '--snapshot-support', 'True']) + provider.snapshot_support = true + provider.flush + end + end + end + + describe '#destroy' do + it 'destroys a type' do + provider_class.expects(:openstack) + .with('share type', 'delete', 'test_type') + provider.destroy + expect(provider.exists?).to be_falsey + end + end + end +end diff --git a/spec/unit/type/manila_type_spec.rb b/spec/unit/type/manila_type_spec.rb new file mode 100644 index 00000000..e6d7651e --- /dev/null +++ b/spec/unit/type/manila_type_spec.rb @@ -0,0 +1,24 @@ +require 'puppet' +require 'puppet/type/manila_type' + +describe Puppet::Type.type(:manila_type) do + + before :each do + Puppet::Type.rmtype(:manila_type) + end + + it 'should autorequire manila-api service' do + catalog = Puppet::Resource::Catalog.new + anchor = Puppet::Type.type(:anchor).new(:name => 'manila::service::end') + correct_input = { + :name => 'test_type', + } + manila_type = Puppet::Type.type(:manila_type).new(correct_input) + + catalog.add_resource anchor, manila_type + dependency = manila_type.autorequire + expect(dependency.size).to eq(1) + expect(dependency[0].target).to eq(manila_type) + expect(dependency[0].source).to eq(anchor) + end +end