
puppetlabs_spec_helper recommends rspec-mocks instead of mocha[1] and
it uses rspec-mocks by default instead of mocha since v 5.0.0[2]
This is the prep work to adapt to that migration.
[1] https://github.com/puppetlabs/puppetlabs_spec_helper/#mock_with
[2] 493f0cbc1c
Closes-Bug: #2004135
Change-Id: I273711da547446f2c37be84656d0e3be2a3f9ec7
214 lines
8.9 KiB
Ruby
214 lines
8.9 KiB
Ruby
require 'puppet'
|
|
require 'spec_helper'
|
|
require 'puppet/provider/openstack'
|
|
|
|
describe Puppet::Provider::Openstack do
|
|
before(:each) do
|
|
ENV['OS_USERNAME'] = nil
|
|
ENV['OS_PASSWORD'] = nil
|
|
ENV['OS_PROJECT_NAME'] = nil
|
|
ENV['OS_AUTH_URL'] = nil
|
|
end
|
|
|
|
let(:type) do
|
|
Puppet::Type.newtype(:test_resource) do
|
|
newparam(:name, :namevar => true)
|
|
newparam(:log_file)
|
|
end
|
|
end
|
|
|
|
let(:credentials) do
|
|
credentials = double('credentials')
|
|
allow(credentials).to receive(:to_env).and_return({
|
|
'OS_USERNAME' => 'user',
|
|
'OS_PASSWORD' => 'password',
|
|
'OS_PROJECT_NAME' => 'project',
|
|
'OS_AUTH_URL' => 'http://url',
|
|
})
|
|
credentials
|
|
end
|
|
|
|
let(:list_data) do
|
|
<<-eos
|
|
"ID","Name","Description","Enabled"
|
|
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
|
eos
|
|
end
|
|
|
|
let(:show_data) do
|
|
<<-eos
|
|
description="Test tenant"
|
|
enabled="True"
|
|
id="1cb05cfed7c24279be884ba4f6520262"
|
|
name="test"
|
|
eos
|
|
end
|
|
|
|
describe '#request' do
|
|
let(:resource_attrs) do
|
|
{
|
|
:name => 'stubresource',
|
|
}
|
|
end
|
|
|
|
let(:provider) do
|
|
Puppet::Provider::Openstack.new(type.new(resource_attrs))
|
|
end
|
|
|
|
it 'makes a successful list request' do
|
|
expect(provider.class).to receive(:openstack)
|
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
|
.and_return list_data
|
|
response = Puppet::Provider::Openstack.request('project', 'list', ['--long'])
|
|
expect(response.first[:description]).to eq 'Test tenant'
|
|
end
|
|
|
|
it 'makes a successful show request' do
|
|
expect(provider.class).to receive(:openstack)
|
|
.with('project', 'show', '--format', 'shell', ['1cb05cfed7c24279be884ba4f6520262'])
|
|
.and_return show_data
|
|
response = Puppet::Provider::Openstack.request('project', 'show', ['1cb05cfed7c24279be884ba4f6520262'])
|
|
expect(response[:description]).to eq 'Test tenant'
|
|
end
|
|
|
|
it 'makes a successful set request' do
|
|
expect(provider.class).to receive(:openstack)
|
|
.with('project', 'set', ['--name', 'new name', '1cb05cfed7c24279be884ba4f6520262'])
|
|
.and_return ''
|
|
response = Puppet::Provider::Openstack.request('project', 'set', ['--name', 'new name', '1cb05cfed7c24279be884ba4f6520262'])
|
|
expect(response).to eq ''
|
|
end
|
|
|
|
it 'uses provided credentials' do
|
|
expect(Puppet::Util).to receive(:withenv).with(credentials.to_env)
|
|
Puppet::Provider::Openstack.request('project', 'list', ['--long'], credentials)
|
|
end
|
|
|
|
it 'redacts sensitive data from an exception message' do
|
|
e1 = Puppet::ExecutionFailure.new "Execution of 'openstack user create --format shell hello --password world --enable --email foo@example.com --domain Default' returned 1: command failed"
|
|
expect do
|
|
Puppet::Provider::Openstack.redact_and_raise(e1)
|
|
end.to raise_error(Puppet::ExecutionFailure, /Execution of \'openstack user create --format shell hello --password \[redacted secret\] --enable --email foo@example.com --domain Default/)
|
|
e2 = Puppet::ExecutionFailure.new "Execution of 'openstack user create --format shell hello --password world' returned 1: command failed"
|
|
expect do
|
|
Puppet::Provider::Openstack.redact_and_raise(e2)
|
|
end.to raise_error(Puppet::ExecutionFailure, /Execution of \'openstack user create --format shell hello --password \[redacted secret\]\' returned/)
|
|
end
|
|
|
|
it 'redacts password in execution output on exception' do
|
|
allow(provider.class).to receive(:execute)
|
|
.and_raise(Puppet::ExecutionFailure, "Execution of '/usr/bin/openstack user create --format shell hello --password world --enable --email foo@example.com --domain Default' returned 1: command failed")
|
|
expect do
|
|
Puppet::Provider::Openstack.request('user', 'create', ['hello', '--password', 'world', '--enable', '--email', 'foo@example.com', '--domain', 'Default'])
|
|
end.to raise_error Puppet::ExecutionFailure, "Execution of '/usr/bin/openstack user create --format shell hello --password [redacted secret] --enable --email foo@example.com --domain Default' returned 1: command failed"
|
|
end
|
|
|
|
context 'on connection errors' do
|
|
it 'retries the failed command' do
|
|
allow(provider.class).to receive(:openstack)
|
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
|
.and_invoke(
|
|
lambda { |*args| raise Puppet::ExecutionFailure, 'Unable to establish connection' },
|
|
lambda { |*args| return list_data }
|
|
)
|
|
expect(provider.class).to receive(:sleep).with(3).and_return(nil)
|
|
response = Puppet::Provider::Openstack.request('project', 'list', ['--long'])
|
|
expect(response.first[:description]).to eq 'Test tenant'
|
|
end
|
|
|
|
it 'fails after the timeout and redacts' do
|
|
expect(provider.class).to receive(:execute)
|
|
.and_raise(Puppet::ExecutionFailure, "Execution of 'openstack user create foo --password secret' returned 1: command failed")
|
|
.exactly(3).times
|
|
allow(provider.class).to receive(:sleep)
|
|
allow(provider.class).to receive(:current_time)
|
|
.and_return(0, 10, 10, 20, 20, 200, 200)
|
|
expect do
|
|
Puppet::Provider::Openstack.request('project', 'list', ['--long'])
|
|
end.to raise_error Puppet::ExecutionFailure, /Execution of \'openstack user create foo --password \[redacted secret\]\' returned 1/
|
|
end
|
|
|
|
it 'fails after the timeout' do
|
|
expect(provider.class).to receive(:openstack)
|
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
|
.and_raise(Puppet::ExecutionFailure, 'Unable to establish connection')
|
|
.exactly(3).times
|
|
allow(provider.class).to receive(:sleep)
|
|
allow(provider.class).to receive(:current_time)
|
|
.and_return(0, 10, 10, 20, 20, 200, 200)
|
|
expect do
|
|
Puppet::Provider::Openstack.request('project', 'list', ['--long'])
|
|
end.to raise_error Puppet::ExecutionFailure, /Unable to establish connection/
|
|
end
|
|
|
|
it 'does not retry non-idempotent commands' do
|
|
expect(provider.class).to receive(:openstack)
|
|
.with('project', 'create', '--format', 'shell', ['--quiet'])
|
|
.and_raise(Puppet::ExecutionFailure, 'Unable to establish connection')
|
|
.exactly(1).times
|
|
expect(provider.class).to receive(:sleep).never
|
|
expect do
|
|
Puppet::Provider::Openstack.request('project', 'create', ['--quiet'])
|
|
end.to raise_error Puppet::ExecutionFailure, /Unable to establish connection/
|
|
end
|
|
|
|
end
|
|
|
|
context 'catch unauthorized errors' do
|
|
it 'should raise an error with non-existent user' do
|
|
ENV['OS_USERNAME'] = 'test'
|
|
ENV['OS_PASSWORD'] = 'abc123'
|
|
ENV['OS_PROJECT_NAME'] = 'test'
|
|
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
|
|
allow(provider.class).to receive(:openstack)
|
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
|
.and_raise(Puppet::ExecutionFailure, 'Could not find user: test (HTTP 401)')
|
|
expect do
|
|
Puppet::Provider::Openstack.request('project', 'list', ['--long'])
|
|
end.to raise_error(Puppet::Error::OpenstackUnauthorizedError, /Could not authenticate/)
|
|
end
|
|
|
|
it 'should raise an error with not authorized to perform' do
|
|
allow(provider.class).to receive(:openstack)
|
|
.with('role', 'list', '--quiet', '--format', 'csv', ['--long'])
|
|
.and_raise(Puppet::ExecutionFailure, 'You are not authorized to perform the requested action: identity:list_grants (HTTP 403)')
|
|
expect do
|
|
Puppet::Provider::Openstack.request('role', 'list', ['--long'])
|
|
end.to raise_error(Puppet::Error::OpenstackUnauthorizedError, /Could not authenticate/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'parse_csv' do
|
|
context 'with mixed stderr' do
|
|
text = "ERROR: Testing\n\"field\",\"test\",1,2,3\n"
|
|
csv = Puppet::Provider::Openstack.parse_csv(text)
|
|
it 'should ignore non-CSV text at the beginning of the input' do
|
|
expect(csv).to be_kind_of(Array)
|
|
expect(csv[0]).to match_array(%w(field test 1 2 3))
|
|
expect(csv.size).to eq(1)
|
|
end
|
|
end
|
|
|
|
context 'with \r\n line endings' do
|
|
text = "ERROR: Testing\r\n\"field\",\"test\",1,2,3\r\n"
|
|
csv = Puppet::Provider::Openstack.parse_csv(text)
|
|
it 'ignore the carriage returns' do
|
|
expect(csv).to be_kind_of(Array)
|
|
expect(csv[0]).to match_array(%w(field test 1 2 3))
|
|
expect(csv.size).to eq(1)
|
|
end
|
|
end
|
|
|
|
context 'with embedded newlines' do
|
|
text = "ERROR: Testing\n\"field\",\"te\nst\",1,2,3\n"
|
|
csv = Puppet::Provider::Openstack.parse_csv(text)
|
|
it 'should parse correctly' do
|
|
expect(csv).to be_kind_of(Array)
|
|
expect(csv[0]).to match_array(['field', "te\nst", '1', '2', '3'])
|
|
expect(csv.size).to eq(1)
|
|
end
|
|
end
|
|
end
|
|
end
|