From 0a4e06abb0f5b3f324464ff5219d2885816311ce Mon Sep 17 00:00:00 2001 From: Sofer Athlan-Guyot Date: Mon, 26 Oct 2015 14:28:32 +0100 Subject: [PATCH] Keystone_endpoint match service by name/type. This enable keystone_endpoint to specify the type of the service matched. This way one can match services which are different only by type and not only by name, like services nova/compute and nova/computev3 for instance. It does so by fetching the _id_ of the service when it has the type information instead of just using the name. This should be required, and deprecation has been added, as the current code work only because of a convention. Change-Id: I9ea20fbad274d583485bc09a52b9df8000eb1af5 Closes-Bug: #1506996 --- README.md | 28 +++ .../provider/keystone_endpoint/openstack.rb | 135 +++++++++++-- lib/puppet/type/keystone_endpoint.rb | 88 ++++++-- lib/puppet_x/keystone/type/required.rb | 7 +- spec/acceptance/basic_keystone_spec.rb | 35 +++- spec/acceptance/keystone_wsgi_apache_spec.rb | 44 ++++ .../keystone_endpoint/openstack_spec.rb | 188 ++++++++++++++++-- spec/unit/type/keystone_endpoint_spec.rb | 59 +++++- 8 files changed, 523 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 560328436..c81ae8dab 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,31 @@ keystone_service { 'nova': description => 'Openstack Compute Service', } +``` + +Services can also be written with the type as a suffix: + +```puppet +keystone_service { 'nova::type': + ensure => present, + description => 'Openstack Compute Service', +} + + # Setup nova keystone endpoint keystone_endpoint { 'example-1-west/nova': + ensure => present, + type => 'compute', + public_url => "http://127.0.0.1:8774/v2/%(tenant_id)s", + admin_url => "http://127.0.0.1:8774/v2/%(tenant_id)s", + internal_url => "http://127.0.0.1:8774/v2/%(tenant_id)s", +} +``` + +Endpoints can also be written with the type as a suffix: + +```puppet +keystone_endpoint { 'example-1-west/nova::compute': ensure => present, public_url => "http://127.0.0.1:8774/v2/%(tenant_id)s", admin_url => "http://127.0.0.1:8774/v2/%(tenant_id)s", @@ -106,6 +129,11 @@ keystone_endpoint { 'example-1-west/nova': } ``` +Defining a endpoint without the type is supported in Liberty release +for backward compatibility, but will be dropped in Mitaka, as this can +lead to corruption of the endpoint database if omitted. See (this +bug)[https://bugs.launchpad.net/puppet-keystone/+bug/1506996] + **Setting up a database for keystone** A keystone database can be configured separately from the keystone services. diff --git a/lib/puppet/provider/keystone_endpoint/openstack.rb b/lib/puppet/provider/keystone_endpoint/openstack.rb index 83c5dc654..410d49291 100644 --- a/lib/puppet/provider/keystone_endpoint/openstack.rb +++ b/lib/puppet/provider/keystone_endpoint/openstack.rb @@ -7,6 +7,10 @@ Puppet::Type.type(:keystone_endpoint).provide( desc "Provider to manage keystone endpoints." + include PuppetX::Keystone::CompositeNamevar::Helpers + + @endpoints = nil + @services = nil @credentials = Puppet::Provider::Openstack::CredentialsV3.new def initialize(value={}) @@ -15,16 +19,42 @@ Puppet::Type.type(:keystone_endpoint).provide( end def create - region, name = resource[:name].split('/') + # Reset the cache. + self.class.services = nil + name = resource[:name] + region = resource[:region] + type = resource[:type] + type = self.class.type_from_service(name) unless set?(:type) + @property_hash[:type] = type + services = self.class.services.find_all { |s| s[:name] == name } + service = services.find { |s| s[:type] == type } + + if service.nil? && services.count == 1 + # For backward comptatibility, match the service by name only. + name = services[0][:id] + else + # Math the service by id. + name = service[:id] if service + end ids = [] + + created = false [:admin_url, :internal_url, :public_url].each do |scope| if resource[scope] - ids << endpoint_create(name, region, scope.to_s.sub(/_url$/,''), - resource[scope])[:id] + created = true + ids << endpoint_create(name, region, scope.to_s.sub(/_url$/, ''), + resource[scope])[:id] end end - @property_hash[:id] = ids.join(',') - @property_hash[:ensure] = :present + if created + @property_hash[:id] = ids.join(',') + @property_hash[:ensure] = :present + else + warning('Specifying a keystone_endpoint without an ' \ + 'admin_url/public_url/internal_url ' \ + "won't create the endpoint at all, despite what Puppet is saying.") + @property_hash[:ensure] = :absent + end end def destroy @@ -53,21 +83,22 @@ Puppet::Type.type(:keystone_endpoint).provide( @property_flush[:admin_url] = value end - def region=(value) - raise(Puppet::Error, "Updating the endpoint's region is not currently supported.") + def region=(_) + fail(Puppet::Error, "Updating the endpoint's region is not currently supported.") end def self.instances - names=[] - list=[] - endpoints = request('endpoint', 'list') + names = [] + list = [] endpoints.each do |current| - name = "#{current[:region]}/#{current[:service_name]}" + name = transform_name(current[:region], current[:service_name], current[:service_type]) unless names.include?(name) names << name endpoint = { :name => name, current[:interface].to_sym => current } endpoints.each do |ep_osc| - if (ep_osc[:id] != current[:id]) && (ep_osc[:service_name] == current[:service_name]) + if (ep_osc[:id] != current[:id]) && + (ep_osc[:service_name] == current[:service_name]) && + (ep_osc[:service_type] == current[:service_type]) endpoint.merge!(ep_osc[:interface].to_sym => ep_osc) end end @@ -88,11 +119,11 @@ Puppet::Type.type(:keystone_endpoint).provide( end def self.prefetch(resources) - endpoints = instances - resources.keys.each do |name| - if provider = endpoints.find{ |endpoint| endpoint.name == name } - resources[name].provider = provider - end + prefetch_composite(resources) do |sorted_namevars| + name = sorted_namevars[0] + region = sorted_namevars[1] + type = sorted_namevars[2] + transform_name(region, name, type) end end @@ -118,4 +149,74 @@ Puppet::Type.type(:keystone_endpoint).provide( properties = [name, interface, url, '--region', region] self.class.request('endpoint', 'create', properties) end + + private + + def self.endpoints + return @endpoints unless @endpoints.nil? + @endpoints = request('endpoint', 'list') + end + + def self.endpoints=(value) + @endpoints = value + end + + def self.services + return @services unless @services.nil? + @services = request('service', 'list') + end + + def self.services=(value) + @services = value + end + + def self.endpoint_from_region_name(region, name) + endpoints.find_all { |e| e[:region] == region && e[:service_name] == name } + .map { |e| e[:service_type] }.uniq + end + + def self.type_from_service(name) + types = services.find_all { |s| s[:name] == name }.map { |e| e[:type] }.uniq + if types.count == 1 + types[0] + else + # We don't fail here as it can happen during a ensure => absent. + PuppetX::Keystone::CompositeNamevar::Unset + end + end + + def self.service_type(services, region, name) + nbr_of_services = services.count + err_msg = ["endpoint matching #{region}/#{name}:"] + type = nil + + case + when nbr_of_services == 1 + type = services[0] + when nbr_of_services > 1 + err_msg += [endpoint_from_region_name(region, name).join(' ')] + when nbr_of_services < 1 + # Then we try to get the type by service name. + type = type_from_service(name) + end + + if !type.nil? + type + else + fail(Puppet::Error, 'Cannot get the correct endpoint type: ' \ + "#{err_msg.join(' ')}") + end + end + + def self.transform_name(region, name, type) + if type == PuppetX::Keystone::CompositeNamevar::Unset + type = service_type(endpoint_from_region_name(region, name), region, name) + end + if type == PuppetX::Keystone::CompositeNamevar::Unset + Puppet.debug("Could not find the type for endpoint #{region}/#{name}") + "#{region}/#{name}" + else + "#{region}/#{name}::#{type}" + end + end end diff --git a/lib/puppet/type/keystone_endpoint.rb b/lib/puppet/type/keystone_endpoint.rb index 55d69f427..e7d8ee309 100644 --- a/lib/puppet/type/keystone_endpoint.rb +++ b/lib/puppet/type/keystone_endpoint.rb @@ -1,34 +1,43 @@ # LP#1408531 File.expand_path('../..', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) } File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) } +require 'puppet_x/keystone/composite_namevar' +require 'puppet_x/keystone/type' Puppet::Type.newtype(:keystone_endpoint) do desc 'Type for managing keystone endpoints.' + include PuppetX::Keystone::CompositeNamevar::Helpers ensurable - newparam(:name, :namevar => true) do - newvalues(/\S+\/\S+/) - end + newparam(:name, :namevar => true) newproperty(:id) do - validate do |v| - raise(Puppet::Error, 'This is a read only property') + include PuppetX::Keystone::Type::ReadOnly + end + + newparam(:region) do + isnamevar + include PuppetX::Keystone::Type::Required + end + + newparam(:type) do + isnamevar + defaultto do + deprecation_msg = 'Support for a endpoint without the type ' \ + 'set is deprecated in Liberty. ' \ + 'It will be dropped in Mitaka.' + warning(deprecation_msg) + PuppetX::Keystone::CompositeNamevar::Unset end end - newproperty(:region) do - end + newproperty(:public_url) - newproperty(:public_url) do - end + newproperty(:internal_url) - newproperty(:internal_url) do - end - - newproperty(:admin_url) do - end + newproperty(:admin_url) # we should not do anything until the keystone service is started autorequire(:anchor) do @@ -36,7 +45,54 @@ Puppet::Type.newtype(:keystone_endpoint) do end autorequire(:keystone_service) do - (region, service_name) = self[:name].split('/') - [service_name] + if parameter_set?(:type) + "#{name}::#{self[:type]}" + else + title = catalog.resources + .find_all { |e| e.type == :keystone_service && e[:name] == name } + .map { |e| e.title }.uniq + if title.count == 1 + title + else + warning("Couldn't find the type of the domain to require using #{name}") + name + end + end + end + + def self.title_patterns + name = PuppetX::Keystone::CompositeNamevar.not_two_colon_regex + type = Regexp.new(/.+/) + region = Regexp.new(/[^\/]+/) + [ + [ + /^(#{region})\/(#{name})::(#{type})$/, + [ + [:region], + [:name], + [:type] + ] + ], + [ + /^(#{region})\/(#{name})$/, + [ + [:region], + [:name] + ] + ], + [ + /^(#{name})::(#{type})$/, + [ + [:name], + [:type] + ] + ], + [ + /^(#{name})$/, + [ + [:name] + ] + ] + ] end end diff --git a/lib/puppet_x/keystone/type/required.rb b/lib/puppet_x/keystone/type/required.rb index d8f3538aa..13ae26f40 100644 --- a/lib/puppet_x/keystone/type/required.rb +++ b/lib/puppet_x/keystone/type/required.rb @@ -5,8 +5,13 @@ module PuppetX def self.included(klass) klass.class_eval do defaultto do + custom = '' + if respond_to?(:required_custom_message) + custom = send(:required_custom_message) + end fail(Puppet::ResourceError, - "Parameter #{name} failed on " \ + "#{custom}" \ + "Parameter #{name} failed on " \ "#{resource.class.to_s.split('::')[-1]}[#{resource.name}]: " \ 'Required parameter.') end diff --git a/spec/acceptance/basic_keystone_spec.rb b/spec/acceptance/basic_keystone_spec.rb index bf2d93933..3adcc9ff2 100644 --- a/spec/acceptance/basic_keystone_spec.rb +++ b/spec/acceptance/basic_keystone_spec.rb @@ -234,16 +234,49 @@ describe 'basic keystone server with resources' do end end end - describe 'composite namevar for keystone_service' do + + describe 'composite namevar for keystone_service and keystone_endpoint' do let(:pp) do <<-EOM keystone_service { 'service_1::type_1': ensure => present } keystone_service { 'service_1': type => 'type_2', ensure => present } + keystone_endpoint { 'RegionOne/service_1::type_2': + ensure => present, + public_url => 'http://public_service1_type2', + internal_url => 'http://internal_service1_type2', + admin_url => 'http://admin_service1_type2' + } + keystone_endpoint { 'service_1': + ensure => present, + region => 'RegionOne', + type => 'type_1', + public_url => 'http://public_url/', + internal_url => 'http://public_url/', + admin_url => 'http://public_url/' + } EOM end it 'should be possible to create two services different only by their type' do apply_manifest(pp, :catch_failures => true) apply_manifest(pp, :catch_changes => true) end + describe 'puppet service are created' do + it 'for service' do + shell('puppet resource keystone_service') do |result| + expect(result.stdout) + .to include_regexp([/keystone_service { 'service_1::type_1':/, + /keystone_service { 'service_1::type_2':/]) + end + end + end + describe 'puppet endpoints are created' do + it 'for service' do + shell('puppet resource keystone_endpoint') do |result| + expect(result.stdout) + .to include_regexp([/keystone_endpoint { 'RegionOne\/service_1::type_1':/, + /keystone_endpoint { 'RegionOne\/service_1::type_2':/]) + end + end + end end end diff --git a/spec/acceptance/keystone_wsgi_apache_spec.rb b/spec/acceptance/keystone_wsgi_apache_spec.rb index ecaafc6ac..23f59c4bc 100644 --- a/spec/acceptance/keystone_wsgi_apache_spec.rb +++ b/spec/acceptance/keystone_wsgi_apache_spec.rb @@ -215,4 +215,48 @@ describe 'keystone server running with Apache/WSGI with resources' do apply_manifest(pp, :catch_changes => true) end end + describe 'composite namevar for keystone_service and keystone_endpoint' do + let(:pp) do + <<-EOM + keystone_service { 'service_1::type_1': ensure => present } + keystone_service { 'service_1': type => 'type_2', ensure => present } + keystone_endpoint { 'RegionOne/service_1::type_2': + ensure => present, + public_url => 'http://public_service1_type2', + internal_url => 'http://internal_service1_type2', + admin_url => 'http://admin_service1_type2' + } + keystone_endpoint { 'service_1': + ensure => present, + region => 'RegionOne', + type => 'type_1', + public_url => 'http://public_url/', + internal_url => 'http://public_url/', + admin_url => 'http://public_url/' + } + EOM + end + it 'should be possible to create two services different only by their type' do + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + describe 'puppet service are created' do + it 'for service' do + shell('puppet resource keystone_service') do |result| + expect(result.stdout) + .to include_regexp([/keystone_service { 'service_1::type_1':/, + /keystone_service { 'service_1::type_2':/]) + end + end + end + describe 'puppet endpoints are created' do + it 'for service' do + shell('puppet resource keystone_endpoint') do |result| + expect(result.stdout) + .to include_regexp([/keystone_endpoint { 'RegionOne\/service_1::type_1':/, + /keystone_endpoint { 'RegionOne\/service_1::type_2':/]) + end + end + end + end end diff --git a/spec/unit/provider/keystone_endpoint/openstack_spec.rb b/spec/unit/provider/keystone_endpoint/openstack_spec.rb index ffa5b14a0..9bc38922d 100644 --- a/spec/unit/provider/keystone_endpoint/openstack_spec.rb +++ b/spec/unit/provider/keystone_endpoint/openstack_spec.rb @@ -2,9 +2,7 @@ require 'puppet' require 'spec_helper' require 'puppet/provider/keystone_endpoint/openstack' -provider_class = Puppet::Type.type(:keystone_endpoint).provider(:openstack) - -describe provider_class do +describe Puppet::Type.type(:keystone_endpoint).provider(:openstack) do let(:set_env) do ENV['OS_USERNAME'] = 'test' @@ -17,11 +15,11 @@ describe provider_class do let(:endpoint_attrs) do { - :name => 'region/endpoint', + :title => 'region/endpoint', :ensure => 'present', :public_url => 'http://127.0.0.1:5000', :internal_url => 'http://127.0.0.1:5001', - :admin_url => 'http://127.0.0.1:5002', + :admin_url => 'http://127.0.0.1:5002' } end @@ -30,47 +28,79 @@ describe provider_class do end let(:provider) do - provider_class.new(resource) + described_class.new(resource) end before(:each) do set_env + described_class.endpoints = nil + described_class.services = nil end describe '#create' do - it 'creates an endpoint' do - provider.class.expects(:openstack) - .with('endpoint', 'create', '--format', 'shell', ['endpoint', 'admin', 'http://127.0.0.1:5002', '--region', 'region']) + before(:each) do + described_class.expects(:openstack) + .with('endpoint', 'create', '--format', 'shell', + ['service_id1', 'admin', 'http://127.0.0.1:5002', '--region', 'region']) .returns('admin_url="http://127.0.0.1:5002" id="endpoint1_id" region="region" ') - provider.class.expects(:openstack) - .with('endpoint', 'create', '--format', 'shell', ['endpoint', 'internal', 'http://127.0.0.1:5001', '--region', 'region']) + described_class.expects(:openstack) + .with('endpoint', 'create', '--format', 'shell', + ['service_id1', 'internal', 'http://127.0.0.1:5001', '--region', 'region']) .returns('internal_url="http://127.0.0.1:5001" id="endpoint2_id" region="region" ') - provider.class.expects(:openstack) - .with('endpoint', 'create', '--format', 'shell', ['endpoint', 'public', 'http://127.0.0.1:5000', '--region', 'region']) + described_class.expects(:openstack) + .with('endpoint', 'create', '--format', 'shell', + ['service_id1', 'public', 'http://127.0.0.1:5000', '--region', 'region']) .returns('public_url="http://127.0.0.1:5000" id="endpoint3_id" region="region" ') - provider.create - expect(provider.exists?).to be_truthy - expect(provider.id).to eq('endpoint1_id,endpoint2_id,endpoint3_id') + described_class.expects(:openstack) + .with('service', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Name","Type" +"service_id1","endpoint","type_one" +') + end + context 'without the type' do + it 'creates an endpoint' do + provider.create + expect(provider.exists?).to be_truthy + expect(provider.id).to eq('endpoint1_id,endpoint2_id,endpoint3_id') + end + end + context 'with the type' do + let(:endpoint_attrs) do + { + :title => 'region/endpoint', + :ensure => 'present', + :public_url => 'http://127.0.0.1:5000', + :internal_url => 'http://127.0.0.1:5001', + :admin_url => 'http://127.0.0.1:5002', + :type => 'type_one' + } + end + + it 'creates an endpoint' do + provider.create + expect(provider.exists?).to be_truthy + expect(provider.id).to eq('endpoint1_id,endpoint2_id,endpoint3_id') + end end end describe '#destroy' do it 'destroys an endpoint' do provider.instance_variable_get('@property_hash')[:id] = 'endpoint1_id,endpoint2_id,endpoint3_id' - provider.class.expects(:openstack) + described_class.expects(:openstack) .with('endpoint', 'delete', 'endpoint1_id') - provider.class.expects(:openstack) + described_class.expects(:openstack) .with('endpoint', 'delete', 'endpoint2_id') - provider.class.expects(:openstack) + described_class.expects(:openstack) .with('endpoint', 'delete', 'endpoint3_id') provider.destroy expect(provider.exists?).to be_falsey @@ -80,7 +110,7 @@ region="region" describe '#exists' do context 'when tenant does not exist' do subject(:response) do - response = provider.exists? + provider.exists? end it { is_expected.to be_falsey } @@ -89,16 +119,130 @@ region="region" describe '#instances' do it 'finds every tenant' do - provider.class.expects(:openstack) + described_class.expects(:openstack) .with('endpoint', 'list', '--quiet', '--format', 'csv', []) .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL" "endpoint1_id","RegionOne","keystone","identity",True,"admin","http://127.0.0.1:5002" "endpoint2_id","RegionOne","keystone","identity",True,"internal","https://127.0.0.1:5001" "endpoint3_id","RegionOne","keystone","identity",True,"public","https://127.0.0.1:5000" ') - instances = Puppet::Type::Keystone_endpoint::ProviderOpenstack.instances + instances = described_class.instances expect(instances.count).to eq(1) end end + + describe '#prefetch' do + context 'working: fq or nfq and matching resource' do + before(:each) do + described_class.expects(:openstack) + .with('endpoint', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL" +"endpoint1_id","RegionOne","keystone","identity",True,"admin","http://127.0.0.1:5002" +"endpoint2_id","RegionOne","keystone","identity",True,"internal","https://127.0.0.1:5001" +"endpoint3_id","RegionOne","keystone","identity",True,"public","https://127.0.0.1:5000" +') + end + context '#fq resource in title' do + let(:resources) do + [Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identity', :ensure => :present), + Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identityv3', :ensure => :present)] + end + include_examples 'prefetch the resources' + end + context '#fq resource' do + let(:resources) do + [Puppet::Type.type(:keystone_endpoint).new(:title => 'keystone', :region => 'RegionOne', :type => 'identity', :ensure => :present), + Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identityv3', :ensure => :present)] + end + include_examples 'prefetch the resources' + end + context '#nfq resource in title matching existing endpoint' do + let(:resources) do + [Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone', :ensure => :present), + Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identityv3', :ensure => :present)] + end + include_examples 'prefetch the resources' + end + context '#nfq resource matching existing endpoint' do + let(:resources) do + [Puppet::Type.type(:keystone_endpoint).new(:title => 'keystone', :region => 'RegionOne', :ensure => :present), + Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identityv3', :ensure => :present)] + end + include_examples 'prefetch the resources' + end + end + + context 'not working' do + context 'too many type' do + before(:each) do + described_class.expects(:openstack) + .with('endpoint', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL" +"endpoint1_id","RegionOne","keystone","identity",True,"admin","http://127.0.0.1:5002" +"endpoint2_id","RegionOne","keystone","identity",True,"internal","https://127.0.0.1:5001" +"endpoint3_id","RegionOne","keystone","identity",True,"public","https://127.0.0.1:5000" +"endpoint4_id","RegionOne","keystone","identityv3",True,"admin","http://127.0.0.1:5002" +"endpoint5_id","RegionOne","keystone","identityv3",True,"internal","https://127.0.0.1:5001" +"endpoint6_id","RegionOne","keystone","identityv3",True,"public","https://127.0.0.1:5000" +') + end + it "should fail as it's not possible to get the right type here" do + existing = Puppet::Type.type(:keystone_endpoint) + .new(:title => 'RegionOne/keystone', :ensure => :present) + resource = mock + r = [] + r << existing + + catalog = Puppet::Resource::Catalog.new + r.each { |res| catalog.add_resource(res) } + m_value = mock + m_first = mock + resource.expects(:values).returns(m_value) + m_value.expects(:first).returns(m_first) + m_first.expects(:catalog).returns(catalog) + m_first.expects(:class).returns(described_class.resource_type) + expect { described_class.prefetch(resource) } + .to raise_error(Puppet::Error, + /endpoint matching RegionOne\/keystone: identity identityv3/) + end + end + end + + context 'not any type but existing service' do + before(:each) do + described_class.expects(:openstack) + .with('endpoint', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL" +"endpoint1_id","RegionOne","keystone","identity",True,"admin","http://127.0.0.1:5002" +"endpoint2_id","RegionOne","keystone","identity",True,"internal","https://127.0.0.1:5001" +"endpoint3_id","RegionOne","keystone","identity",True,"public","https://127.0.0.1:5000" +') + described_class.expects(:openstack) + .with('service', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Name","Type" +"service1_id","keystonev3","identity" +') + end + it 'should be successful' do + existing = Puppet::Type.type(:keystone_endpoint) + .new(:title => 'RegionOne/keystonev3', :ensure => :present) + resource = mock + r = [] + r << existing + + catalog = Puppet::Resource::Catalog.new + r.each { |res| catalog.add_resource(res) } + m_value = mock + m_first = mock + resource.expects(:values).returns(m_value) + m_value.expects(:first).returns(m_first) + m_first.expects(:catalog).returns(catalog) + m_first.expects(:class).returns(described_class.resource_type) + + expect { described_class.prefetch(resource) }.not_to raise_error + expect(existing.provider.ensure).to eq(:absent) + end + end + end end end diff --git a/spec/unit/type/keystone_endpoint_spec.rb b/spec/unit/type/keystone_endpoint_spec.rb index a3667c546..d13d0bb41 100644 --- a/spec/unit/type/keystone_endpoint_spec.rb +++ b/spec/unit/type/keystone_endpoint_spec.rb @@ -1,9 +1,60 @@ +require 'spec_helper' +require 'puppet' +require 'puppet/type/keystone_endpoint' + describe Puppet::Type.type(:keystone_endpoint) do - it 'should fail when the namevar does not contain a region' do - expect do - Puppet::Type.type(:keystone_endpoint).new(:name => 'foo') - end.to raise_error(Puppet::Error, /Invalid value/) + describe 'region_one/endpoint_name::type_one' do + include_examples 'parse title correctly', + :name => 'endpoint_name', + :region => 'region_one', + :type => 'type_one' end + describe 'new_endpoint_without_region::type' do + include_examples 'croak on the required parameter', + 'Parameter region failed on Keystone_endpoint[new_endpoint_without_region]:' + end + + describe '#autorequire' do + let(:service_one) do + Puppet::Type.type(:keystone_service).new(:title => 'service_one', :type => 'type_one') + end + + let(:service_two) do + Puppet::Type.type(:keystone_service).new(:title => 'service_one::type_two') + end + + let(:service_three) do + Puppet::Type.type(:keystone_service).new(:title => 'service_two::type_one') + end + + context 'domain autorequire from title' do + let(:endpoint) do + Puppet::Type.type(:keystone_endpoint).new(:title => 'region_one/service_one::type_one') + end + describe 'should require the correct domain' do + let(:resources) { [endpoint, service_one, service_two] } + include_examples 'autorequire the correct resources' + end + end + context 'domain autorequire from title without type (to be removed at Mitaka)' do + let(:endpoint) do + Puppet::Type.type(:keystone_endpoint).new(:title => 'region_one/service_one') + end + describe 'should require the correct domain' do + let(:resources) { [endpoint, service_one, service_two] } + include_examples 'autorequire the correct resources' + end + end + context 'domain autorequire from title without type on fq service name (to be removed at Mitaka)' do + let(:endpoint) do + Puppet::Type.type(:keystone_endpoint).new(:title => 'region_one/service_two') + end + describe 'should require the correct domain' do + let(:resources) { [endpoint, service_three, service_one] } + include_examples 'autorequire the correct resources' + end + end + end end