Use system scope credentials in providers

This change enforces usage of system scope credentials to manage
flavors and aggregates, following the new policy rules for SRBAC
support in nova.

Depends-on: https://review.opendev.org/815311
Change-Id: Ic87422ae98943054ee58343157301d8fc780211f
This commit is contained in:
Takashi Kajinami 2021-10-28 22:50:37 +09:00
parent ee0e3834b0
commit 5e62f69b5e
10 changed files with 221 additions and 272 deletions

View File

@ -1,9 +1,3 @@
# Run test ie with: rspec spec/unit/provider/nova_spec.rb
# Add openstacklib code to $LOAD_PATH so that we can load this during
# standalone compiles without error.
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/util/inifile'
require 'puppet/provider/openstack' require 'puppet/provider/openstack'
require 'puppet/provider/openstack/auth' require 'puppet/provider/openstack/auth'
@ -13,27 +7,44 @@ class Puppet::Provider::Nova < Puppet::Provider::Openstack
extend Puppet::Provider::Openstack::Auth extend Puppet::Provider::Openstack::Auth
def self.request(service, action, properties=nil) CLOUDS_FILENAME = '/etc/nova/clouds.yaml'
begin
super def self.project_request(service, action, properties=nil, options={})
rescue Puppet::Error::OpenstackAuthInputError => error self.request(service, action, properties, options, 'project')
nova_request(service, action, error, properties)
end
end end
def self.nova_request(service, action, error, properties=nil) def self.system_request(service, action, properties=nil, options={})
self.request(service, action, properties, options, 'system')
end
def self.request(service, action, properties=nil, options={}, scope='project')
super
rescue Puppet::Error::OpenstackAuthInputError => error
nova_request(service, action, error, properties, options, scope)
end
def self.nova_request(service, action, error, properties=nil, options={}, scope='project')
properties ||= [] properties ||= []
@credentials.username = nova_credentials['username']
@credentials.password = nova_credentials['password'] unless @system_credential
@credentials.project_name = nova_credentials['project_name'] @system_credential = Puppet::Provider::Openstack::CredentialsV3.new
@credentials.auth_url = auth_endpoint @system_credential.cloud = 'system'
@credentials.user_domain_name = nova_credentials['user_domain_name'] @system_credential.client_config_file = clouds_filename
@credentials.project_domain_name = nova_credentials['project_domain_name']
if nova_credentials['region_name']
@credentials.region_name = nova_credentials['region_name']
end end
raise error unless @credentials.set?
Puppet::Provider::Openstack.request(service, action, properties, @credentials) unless @project_credential
@project_credential = Puppet::Provider::Openstack::CredentialsV3.new
@project_credential.cloud = 'project'
@project_credential.client_config_file = clouds_filename
end
if scope == 'system'
cred = @system_credential
else
cred = @project_credential
end
Puppet::Provider::Openstack.request(service, action, properties, cred, options)
end end
def self.nova_manage_request(*args) def self.nova_manage_request(*args)
@ -70,58 +81,13 @@ class Puppet::Provider::Nova < Puppet::Provider::Openstack
@nova_conf @nova_conf
end end
def self.nova_credentials def self.clouds_filename
@nova_credentials ||= get_nova_credentials CLOUDS_FILENAME
end
def nova_credentials
self.class.nova_credentials
end
def self.get_nova_credentials
#needed keys for authentication
auth_keys = ['auth_url', 'project_name', 'username', 'password']
conf = nova_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']['region_name'].nil?
creds['region_name'] = conf['keystone_authtoken']['region_name'].strip
end
if !conf['keystone_authtoken']['project_domain_name'].nil?
creds['project_domain_name'] = conf['keystone_authtoken']['project_domain_name'].strip
else
creds['project_domain_name'] = 'Default'
end
if !conf['keystone_authtoken']['user_domain_name'].nil?
creds['user_domain_name'] = conf['keystone_authtoken']['user_domain_name'].strip
else
creds['user_domain_name'] = 'Default'
end
return creds
else
raise(Puppet::Error, "File: #{conf_filename} does not contain all " +
"required sections. Nova types will not work if nova is not " +
"correctly configured.")
end
end
def self.get_auth_endpoint
q = nova_credentials
"#{q['auth_url']}"
end
def self.auth_endpoint
@auth_endpoint ||= get_auth_endpoint
end end
def self.reset def self.reset
@nova_conf = nil @project_credential = nil
@nova_credentials = nil @system_credential = nil
end end
def self.str2hash(s) def self.str2hash(s)

View File

@ -13,8 +13,8 @@ Puppet::Type.type(:nova_aggregate).provide(
mk_resource_methods mk_resource_methods
def self.instances def self.instances
request('aggregate', 'list').collect do |el| system_request('aggregate', 'list').collect do |el|
attrs = request('aggregate', 'show', el[:name]) attrs = system_request('aggregate', 'show', el[:name])
properties = parsestring(attrs[:properties]) rescue nil properties = parsestring(attrs[:properties]) rescue nil
new( new(
:ensure => :present, :ensure => :present,
@ -43,7 +43,7 @@ Puppet::Type.type(:nova_aggregate).provide(
def self.get_known_hosts def self.get_known_hosts
# get list of hosts known to be active from openstack # get list of hosts known to be active from openstack
return request('compute service', 'list', ['--service', 'nova-compute']).map{|el| el[:host]} return system_request('compute service', 'list', ['--service', 'nova-compute']).map{|el| el[:host]}
end end
def exists? def exists?
@ -53,9 +53,9 @@ Puppet::Type.type(:nova_aggregate).provide(
def destroy def destroy
@property_hash[:hosts].each do |h| @property_hash[:hosts].each do |h|
properties = [@property_hash[:name], h] properties = [@property_hash[:name], h]
self.class.request('aggregate', 'remove host', properties) self.class.system_request('aggregate', 'remove host', properties)
end end
self.class.request('aggregate', 'delete', @property_hash[:name]) self.class.system_request('aggregate', 'delete', @property_hash[:name])
end end
def create def create
@ -68,7 +68,7 @@ Puppet::Type.type(:nova_aggregate).provide(
properties << "--property" << "#{key}=#{value}" properties << "--property" << "#{key}=#{value}"
end end
end end
@property_hash = self.class.request('aggregate', 'create', properties) @property_hash = self.class.system_request('aggregate', 'create', properties)
if not @resource[:hosts].nil? and not @resource[:hosts].empty? if not @resource[:hosts].nil? and not @resource[:hosts].empty?
# filter host list by known hosts if filter_hosts is set # filter host list by known hosts if filter_hosts is set
if @resource[:filter_hosts] == :true if @resource[:filter_hosts] == :true
@ -76,13 +76,14 @@ Puppet::Type.type(:nova_aggregate).provide(
end end
@resource[:hosts].each do |host| @resource[:hosts].each do |host|
properties = [@property_hash[:name], host] properties = [@property_hash[:name], host]
self.class.request('aggregate', 'add host', properties) self.class.system_request('aggregate', 'add host', properties)
end end
end end
@property_hash[:ensure] = :present
end end
def availability_zone=(value) def availability_zone=(value)
self.class.request('aggregate', 'set', [ @resource[:name], '--zone', @resource[:availability_zone] ]) self.class.system_request('aggregate', 'set', [ @resource[:name], '--zone', @resource[:availability_zone] ])
end end
def metadata=(value) def metadata=(value)
@ -92,13 +93,13 @@ Puppet::Type.type(:nova_aggregate).provide(
(@property_hash[:metadata].keys - @resource[:metadata].keys).each do |key| (@property_hash[:metadata].keys - @resource[:metadata].keys).each do |key|
properties << "--property" << "#{key}" properties << "--property" << "#{key}"
end end
self.class.request('aggregate', 'unset', properties) self.class.system_request('aggregate', 'unset', properties)
end end
properties = [@resource[:name] ] properties = [@resource[:name] ]
@resource[:metadata].each do |key, value| @resource[:metadata].each do |key, value|
properties << "--property" << "#{key}=#{value}" properties << "--property" << "#{key}=#{value}"
end end
self.class.request('aggregate', 'set', properties) self.class.system_request('aggregate', 'set', properties)
end end
def hosts=(value) def hosts=(value)
@ -109,11 +110,11 @@ Puppet::Type.type(:nova_aggregate).provide(
if not @property_hash[:hosts].nil? if not @property_hash[:hosts].nil?
# remove hosts that are not present in update # remove hosts that are not present in update
(@property_hash[:hosts] - value).each do |host| (@property_hash[:hosts] - value).each do |host|
self.class.request('aggregate', 'remove host', [@property_hash[:id], host]) self.class.system_request('aggregate', 'remove host', [@property_hash[:id], host])
end end
# add hosts that are not already present # add hosts that are not already present
(value - @property_hash[:hosts]).each do |host| (value - @property_hash[:hosts]).each do |host|
self.class.request('aggregate', 'add host', [@property_hash[:id], host]) self.class.system_request('aggregate', 'add host', [@property_hash[:id], host])
end end
end end
end end

View File

@ -26,16 +26,16 @@ Puppet::Type.type(:nova_flavor).provide(
(opts << '--vcpus' << @resource[:vcpus]) if @resource[:vcpus] (opts << '--vcpus' << @resource[:vcpus]) if @resource[:vcpus]
(opts << '--swap' << @resource[:swap]) if @resource[:swap] (opts << '--swap' << @resource[:swap]) if @resource[:swap]
(opts << '--rxtx-factor' << @resource[:rxtx_factor]) if @resource[:rxtx_factor] (opts << '--rxtx-factor' << @resource[:rxtx_factor]) if @resource[:rxtx_factor]
@property_hash = self.class.request('flavor', 'create', opts) @property_hash = self.class.system_request('flavor', 'create', opts)
if @resource[:properties] if @resource[:properties]
prop_opts = [@resource[:name]] prop_opts = [@resource[:name]]
prop_opts << props_to_s(@resource[:properties]) prop_opts << props_to_s(@resource[:properties])
self.class.request('flavor', 'set', prop_opts) self.class.system_request('flavor', 'set', prop_opts)
end end
if @resource[:project] if @resource[:project] and @resource[:project] != ''
proj_opts = [@resource[:name]] proj_opts = [@resource[:name]]
proj_opts << '--project' << @resource[:project] proj_opts << '--project' << @resource[:project]
self.class.request('flavor', 'set', proj_opts) self.class.system_request('flavor', 'set', proj_opts)
end end
@property_hash[:ensure] = :present @property_hash[:ensure] = :present
end end
@ -45,7 +45,7 @@ Puppet::Type.type(:nova_flavor).provide(
end end
def destroy def destroy
self.class.request('flavor', 'delete', @property_hash[:id]) self.class.system_request('flavor', 'delete', @property_hash[:id])
end end
mk_resource_methods mk_resource_methods
@ -87,8 +87,8 @@ Puppet::Type.type(:nova_flavor).provide(
end end
def self.instances def self.instances
request('flavor', 'list', ['--long', '--all']).collect do |attrs| system_request('flavor', 'list', ['--long', '--all']).collect do |attrs|
project = request('flavor', 'show', [attrs[:id], '-c', 'access_project_ids']) project = system_request('flavor', 'show', [attrs[:id], '-c', 'access_project_ids'])
access_project_ids = project[:access_project_ids] access_project_ids = project[:access_project_ids]
# Client can return None and this should be considered as '' # Client can return None and this should be considered as ''
@ -133,17 +133,17 @@ Puppet::Type.type(:nova_flavor).provide(
opts = [@resource[:name]] opts = [@resource[:name]]
opts << props_to_s(@property_flush[:properties]) opts << props_to_s(@property_flush[:properties])
self.class.request('flavor', 'set', opts) self.class.system_request('flavor', 'set', opts)
@property_flush.clear @property_flush.clear
end end
unless @project_flush.empty? unless @project_flush.empty?
opts = [@resource[:name]] opts = [@resource[:name]]
unless @project_flush[:project] unless @project_flush[:project] == ''
opts << '--project' << @project_flush[:project] opts << '--project' << @project_flush[:project]
self.class.request('flavor', 'set', opts) self.class.system_request('flavor', 'set', opts)
else else
opts << '--project' << @property_hash[:project] opts << '--project' << @property_hash[:project]
self.class.request('flavor', 'unset', opts) self.class.system_request('flavor', 'unset', opts)
end end
@project_flush.clear @project_flush.clear
end end
@ -154,4 +154,3 @@ Puppet::Type.type(:nova_flavor).provide(
props.flat_map{ |k, v| ['--property', "#{k}=#{v}"] } props.flat_map{ |k, v| ['--property', "#{k}=#{v}"] }
end end
end end

View File

@ -14,7 +14,7 @@ Puppet::Type.type(:nova_service).provide(
def self.instances def self.instances
hosts = {} hosts = {}
request('compute service', 'list').collect do |host_svc| system_request('compute service', 'list').collect do |host_svc|
hname = host_svc[:host] hname = host_svc[:host]
if hosts[hname].nil? if hosts[hname].nil?
hosts[hname] = Hash.new {|h,k| h[k]=[]} hosts[hname] = Hash.new {|h,k| h[k]=[]}
@ -53,7 +53,7 @@ Puppet::Type.type(:nova_service).provide(
svcname_id_map.each do |service_name, id| svcname_id_map.each do |service_name, id|
if (@resource[:service_name].empty? || if (@resource[:service_name].empty? ||
(@resource[:service_name].include? service_name)) (@resource[:service_name].include? service_name))
self.class.request('compute service', 'delete', id) self.class.system_request('compute service', 'delete', id)
end end
end end
@property_hash[:ensure] = :absent @property_hash[:ensure] = :absent

View File

@ -284,4 +284,33 @@ class nova::keystone::authtoken(
service_type => $service_type, service_type => $service_type,
interface => $interface; interface => $interface;
} }
$system_scope_real = is_service_default($system_scope) ? {
true => pick($::nova::keystone::auth::system_scope, 'all'),
default => $system_scope,
}
$region_name_real = is_service_default($region_name) ? {
true => undef,
default => $region_name,
}
$interface_real = is_service_default($interface) ? {
true => undef,
default => $interface,
}
openstacklib::clouds { '/etc/nova/clouds.yaml':
username => $username,
password => $password,
auth_url => $auth_url,
project_name => $project_name,
system_scope => $system_scope_real,
region_name => $region_name_real,
interface => $interface_real,
}
Anchor['nova::config::begin']
-> Openstacklib::Clouds['/etc/nova/clouds.yaml']
-> Anchor['nova::config::end']
} }

View File

@ -50,7 +50,8 @@ describe 'nova::keystone::auth' do
:system_roles => ['admin', 'member', 'reader'], :system_roles => ['admin', 'member', 'reader'],
:public_url => 'https://10.10.10.10:80', :public_url => 'https://10.10.10.10:80',
:internal_url => 'http://10.10.10.11:81', :internal_url => 'http://10.10.10.11:81',
:admin_url => 'http://10.10.10.12:81' } :admin_url => 'http://10.10.10.12:81',
}
end end
it { is_expected.to contain_keystone__resource__service_identity('nova').with( it { is_expected.to contain_keystone__resource__service_identity('nova').with(

View File

@ -2,14 +2,12 @@ require 'puppet'
require 'spec_helper' require 'spec_helper'
require 'puppet/provider/nova_aggregate/openstack' require 'puppet/provider/nova_aggregate/openstack'
provider_class = Puppet::Type.type(:nova_aggregate).provider(:openstack) describe Puppet::Type.type(:nova_aggregate).provider(:openstack) do
describe provider_class do let(:set_env) do
shared_examples 'authenticated with environment variables' do
ENV['OS_USERNAME'] = 'test' ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123' ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_PROJECT_NAME'] = 'test' ENV['OS_SYSTEM_SCOPE'] = 'all'
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000/v3' ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000/v3'
end end
@ -30,96 +28,93 @@ describe provider_class do
end end
let(:provider) do let(:provider) do
provider_class.new(resource) described_class.new(resource)
end end
it_behaves_like 'authenticated with environment variables' do before(:each) do
describe '#instances' do set_env
it 'finds existing aggregates' do end
provider_class.expects(:openstack)
.with('aggregate', 'list', '--quiet', '--format', 'csv', []) describe '#instances' do
.returns('"ID","Name","Availability Zone" it 'finds existing aggregates' do
described_class.expects(:openstack)
.with('aggregate', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Availability Zone"
just,"simple","just" just,"simple","just"
') ')
provider_class.expects(:openstack) described_class.expects(:openstack)
.with('aggregate', 'show', '--format', 'shell', 'simple') .with('aggregate', 'show', '--format', 'shell', 'simple')
.returns('"id="just" .returns('"id="just"
name="simple" name="simple"
availability_zone=just" availability_zone=just"
properties="key1=\'tomato\', key2=\'mushroom\'" properties="key1=\'tomato\', key2=\'mushroom\'"
hosts="[]" hosts="[]"
') ')
instances = provider_class.instances instances = described_class.instances
expect(instances.count).to eq(1) expect(instances.count).to eq(1)
expect(instances[0].name).to eq('simple') expect(instances[0].name).to eq('simple')
expect(instances[0].metadata).to eq({"key1"=>"tomato", "key2"=>"mushroom"}) expect(instances[0].metadata).to eq({"key1"=>"tomato", "key2"=>"mushroom"})
end
end end
end
describe '#create' do describe '#create' do
it 'creates aggregate' do it 'creates aggregate' do
provider.class.stubs(:openstack) described_class.expects(:openstack)
.with('aggregate', 'list', '--quiet', '--format', 'csv', '--long') .with('aggregate', 'create', '--format', 'shell',
.returns('"ID","Name","Availability Zone","Properties" ['just', '--zone', 'simple', '--property', 'nice=cookie' ])
') .returns('name="just"
provider.class.stubs(:openstack)
.with('aggregate', 'create', '--format', 'shell', ['just', '--zone', 'simple', '--property', 'nice=cookie' ])
.returns('name="just"
id="just" id="just"
availability_zone="simple" availability_zone="simple"
properties="{u\'nice\': u\'cookie\'}" properties="{u\'nice\': u\'cookie\'}"
hosts="[]" hosts="[]"
') ')
provider.class.stubs(:openstack) described_class.expects(:openstack)
.with('aggregate', 'add host', ['just', 'example']) .with('aggregate', 'add host', ['just', 'example'])
.returns('name="just" .returns('name="just"
id="just" id="just"
availability_zone="simple" availability_zone="simple"
properties="{u\'nice\': u\'cookie\'}" properties="{u\'nice\': u\'cookie\'}"
hosts="[u\'example\']" hosts="[u\'example\']"
') ')
provider.exists? provider.create
provider.create expect(provider.exists?).to be_truthy
expect(provider.exists?).to be_falsey end
end end
describe '#destroy' do
it 'removes aggregate with hosts' do
described_class.expects(:openstack)
.with('aggregate', 'remove host', ['just', 'example'])
described_class.expects(:openstack)
.with('aggregate', 'delete', 'just')
provider.instance_variable_set(:@property_hash, aggregate_attrs)
provider.destroy
expect(provider.exists?).to be_falsey
end
end
describe '#pythondict2hash' do
it 'should return a hash with key-value when provided with a unicode python dict' do
s = "{u'key': 'value', u'key2': 'value2'}"
expect(described_class.pythondict2hash(s)).to eq({"key"=>"value", "key2"=>"value2"})
end end
describe '#destroy' do it 'should return a hash with key-value when provided with a python dict' do
it 'removes aggregate with hosts' do s = "{'key': 'value', 'key2': 'value2'}"
provider_class.expects(:openstack) expect(described_class.pythondict2hash(s)).to eq({"key"=>"value", "key2"=>"value2"})
.with('aggregate', 'remove host', ['just', 'example']) end
provider_class.expects(:openstack) end
.with('aggregate', 'delete', 'just')
provider.instance_variable_set(:@property_hash, aggregate_attrs) describe '#parsestring' do
provider.destroy it 'should call string2hash when provided with a string' do
expect(provider.exists?).to be_falsey s = "key='value', key2='value2'"
end expect(described_class.parsestring(s)).to eq({"key"=>"value", "key2"=>"value2"})
end end
describe '#pythondict2hash' do it 'should call pythondict2hash when provided with a hash' do
it 'should return a hash with key-value when provided with a unicode python dict' do s = "{u'key': 'value', u'key2': 'value2'}"
s = "{u'key': 'value', u'key2': 'value2'}" expect(described_class.parsestring(s)).to eq({"key"=>"value", "key2"=>"value2"})
expect(provider_class.pythondict2hash(s)).to eq({"key"=>"value", "key2"=>"value2"})
end
it 'should return a hash with key-value when provided with a python dict' do
s = "{'key': 'value', 'key2': 'value2'}"
expect(provider_class.pythondict2hash(s)).to eq({"key"=>"value", "key2"=>"value2"})
end
end end
describe '#parsestring' do
it 'should call string2hash when provided with a string' do
s = "key='value', key2='value2'"
expect(provider_class.parsestring(s)).to eq({"key"=>"value", "key2"=>"value2"})
end
it 'should call pythondict2hash when provided with a hash' do
s = "{u'key': 'value', u'key2': 'value2'}"
expect(provider_class.parsestring(s)).to eq({"key"=>"value", "key2"=>"value2"})
end
end
end end
end end
@ -143,83 +138,83 @@ hosts="[u\'example\']"
end end
let(:provider) do let(:provider) do
provider_class.new(resource) described_class.new(resource)
end end
it_behaves_like 'authenticated with environment variables' do before(:each) do
set_env
end
# create an aggregate and actually aggregate hosts to it # create an aggregate and actually aggregate hosts to it
describe 'create aggregate and add/remove hosts with filter_hosts toggled' do describe 'create aggregate and add/remove hosts with filter_hosts toggled' do
it 'creates aggregate with filter_hosts toggled' do it 'creates aggregate with filter_hosts toggled' do
provider.class.stubs(:get_known_hosts) provider.class.stubs(:get_known_hosts)
.returns(['known', 'known_too']) .returns(['known', 'known_too'])
# these expectations are the actual tests that check the provider's behaviour # these expectations are the actual tests that check the provider's behaviour
# and make sure only known hosts ('known' is the only known host) will be # and make sure only known hosts ('known' is the only known host) will be
# aggregated. # aggregated.
provider_class.expects(:openstack) described_class.expects(:openstack)
.with('aggregate', 'create', '--format', 'shell', ['just', '--zone', 'simple', "--property", "nice=cookie"]) .with('aggregate', 'create', '--format', 'shell', ['just', '--zone', 'simple', "--property", "nice=cookie"])
.once .once
.returns('name="just" .returns('name="just"
id="just" id="just"
availability_zone="simple" availability_zone="simple"
properties="{u\'nice\': u\'cookie\'}" properties="{u\'nice\': u\'cookie\'}"
hosts="[]" hosts="[]"
') ')
provider_class.expects(:openstack) described_class.expects(:openstack)
.with('aggregate', 'add host', ['just', 'known']) .with('aggregate', 'add host', ['just', 'known'])
.once .once
.returns('name="just" .returns('name="just"
id="just" id="just"
availability_zone="simple" availability_zone="simple"
properties="{u\'nice\': u\'cookie\'}" properties="{u\'nice\': u\'cookie\'}"
hosts="[u\'known\']" hosts="[u\'known\']"
') ')
provider_class.expects(:openstack) described_class.expects(:openstack)
.with('aggregate', 'add host', ['just', 'known_too']) .with('aggregate', 'add host', ['just', 'known_too'])
.once .once
.returns('name="just" .returns('name="just"
id="just" id="just"
availability_zone="simple" availability_zone="simple"
properties="{u\'nice\': u\'cookie\'}" properties="{u\'nice\': u\'cookie\'}"
hosts="[u\'known\', u\'known_too\']" hosts="[u\'known\', u\'known_too\']"
') ')
provider_class.expects(:openstack) described_class.expects(:openstack)
.with('aggregate', 'remove host', ['just', 'known']) .with('aggregate', 'remove host', ['just', 'known'])
.once .once
.returns('name="just" .returns('name="just"
id="just" id="just"
availability_zone="simple" availability_zone="simple"
properties="{u\'nice\': u\'cookie\'}" properties="{u\'nice\': u\'cookie\'}"
hosts="[u\'known_too\']" hosts="[u\'known_too\']"
') ')
# this creates a provider with the attributes defined above as 'aggregate_attrs' # this creates a provider with the attributes defined above as 'aggregate_attrs'
# and tries to add some hosts and then to remove some hosts. # and tries to add some hosts and then to remove some hosts.
# the hosts will be filtered against known active hosts and the expectations # the hosts will be filtered against known active hosts and the expectations
# described above are the actual tests that check the provider's behaviour # described above are the actual tests that check the provider's behaviour
provider.create provider.create
property_hash = provider.instance_variable_get(:@property_hash) property_hash = provider.instance_variable_get(:@property_hash)
property_hash[:hosts] = ['known'] property_hash[:hosts] = ['known']
provider.instance_variable_set(:@property_hash, property_hash) provider.instance_variable_set(:@property_hash, property_hash)
provider.hosts = ['known', 'known_too', 'unknown'] provider.hosts = ['known', 'known_too', 'unknown']
property_hash = provider.instance_variable_get(:@property_hash) property_hash = provider.instance_variable_get(:@property_hash)
property_hash[:hosts] = ['known', 'known_too'] property_hash[:hosts] = ['known', 'known_too']
provider.instance_variable_set(:@property_hash, property_hash) provider.instance_variable_set(:@property_hash, property_hash)
provider.hosts = ['known_too'] provider.hosts = ['known_too']
end
end end
end end
end end

View File

@ -2,9 +2,14 @@ require 'puppet'
require 'spec_helper' require 'spec_helper'
require 'puppet/provider/nova_flavor/openstack' require 'puppet/provider/nova_flavor/openstack'
provider_class = Puppet::Type.type(:nova_flavor).provider(:openstack) describe Puppet::Type.type(:nova_flavor).provider(:openstack) do
describe provider_class do let(:set_env) do
ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_SYSTEM_SCOPE'] = 'all'
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000/v3'
end
describe 'managing flavors' do describe 'managing flavors' do
let(:flavor_attrs) do let(:flavor_attrs) do
@ -23,17 +28,20 @@ describe provider_class do
end end
let(:provider) do let(:provider) do
provider_class.new(resource) described_class.new(resource)
end
before(:each) do
set_env
end end
describe '#create' do describe '#create' do
it 'creates flavor' do it 'creates flavor' do
provider.class.stubs(:openstack) described_class.expects(:openstack)
.with('flavor', 'list', ['--long', '--all']) .with('flavor', 'create', '--format', 'shell',
.returns('"ID", "Name", "RAM", "Disk", "Ephemeral", "VCPUs", "Is Public", "Swap", "RXTX Factor", "Properties"') ['example', '--public', '--id', '1', '--ram', '512',
provider.class.stubs(:openstack) '--disk', '1', '--vcpus', '1'])
.with('flavor', 'create', 'shell', ['example', '--public', '--id', '1', '--ram', '512', '--disk', '1', '--vcpus', '1']) .returns('os-flv-disabled:disabled="False"
.returns('os-flv-disabled:disabled="False"
os-flv-ext-data:ephemeral="0" os-flv-ext-data:ephemeral="0"
disk="1" disk="1"
id="1" id="1"
@ -43,12 +51,14 @@ ram="512"
rxtx_factor="1.0" rxtx_factor="1.0"
swap="" swap=""
vcpus="1"') vcpus="1"')
provider.create
expect(provider.exists?).to be_truthy
end end
end end
describe '#destroy' do describe '#destroy' do
it 'removes flavor' do it 'removes flavor' do
provider_class.expects(:openstack) described_class.expects(:openstack)
.with('flavor', 'delete', '1') .with('flavor', 'delete', '1')
provider.instance_variable_set(:@property_hash, flavor_attrs) provider.instance_variable_set(:@property_hash, flavor_attrs)
provider.destroy provider.destroy

View File

@ -9,7 +9,7 @@ describe provider_class do
shared_examples 'authenticated with environment variables' do shared_examples 'authenticated with environment variables' do
ENV['OS_USERNAME'] = 'test' ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123' ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_PROJECT_NAME'] = 'test' ENV['OS_SYSTEM_SCOPE'] = 'all'
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000/v3' ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000/v3'
end end

View File

@ -9,62 +9,10 @@ describe Puppet::Provider::Nova do
described_class described_class
end end
let :credential_hash do
{
'auth_url' => 'https://192.168.56.210:5000/v3/',
'project_name' => 'admin_tenant',
'username' => 'admin',
'password' => 'password',
'region_name' => 'Region1',
}
end
let :auth_endpoint do
'https://192.168.56.210:5000/v3/'
end
let :credential_error do
/Nova types will not work/
end
after :each do after :each do
klass.reset klass.reset
end end
describe 'when determining credentials' do
it 'should fail if config is empty' do
conf = {}
klass.expects(:nova_conf).returns(conf)
expect do
klass.nova_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(:nova_conf).returns(conf)
expect do
klass.nova_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(:nova_conf).returns(conf)
expect do
klass.nova_credentials
end.to raise_error(Puppet::Error, credential_error)
end
it 'should use specified uri in the auth endpoint' do
conf = {'keystone_authtoken' => credential_hash}
klass.expects(:nova_conf).returns(conf)
expect(klass.get_auth_endpoint).to eq(auth_endpoint)
end
end
describe 'when parse a string line' do describe 'when parse a string line' do
it 'should return the same string' do it 'should return the same string' do
res = klass.str2hash("zone1") res = klass.str2hash("zone1")