Move openstackclient parent provider to openstacklib

This patch abandons the use of the Aviator library for interacting with
the openstack APIs in favor of the universal openstack client[1]. This
work has already been implemented in the keystone module. This patch
moves that work into openstacklib in order to make it available for the
other modules.

[1] https://wiki.openstack.org/wiki/OpenStackClient

Change-Id: I68705c28955a54e26d98f1de718016691c64e4b1
This commit is contained in:
Colleen Murphy 2015-03-05 11:16:50 -08:00
parent 75124baf89
commit ed58789665
15 changed files with 440 additions and 845 deletions

View File

@ -1,7 +1,6 @@
fixtures:
repositories:
apache: git://github.com/puppetlabs/puppetlabs-apache.git
aviator: git://github.com/aimonb/puppet_aviator.git
concat: git://github.com/puppetlabs/puppetlabs-concat.git
mysql: git://github.com/puppetlabs/puppetlabs-mysql.git
postgresql: git://github.com/puppetlabs/puppetlabs-postgresql.git

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
*.swp
spec/fixtures/*
!spec/fixtures/vcr/
pkg
Gemfile.lock

View File

@ -8,8 +8,6 @@ group :development, :test do
gem 'rspec'
gem 'mocha'
gem 'json'
gem 'faraday', '0.8.8', :require => false
gem 'vcr', :require => false
end
if puppetversion = ENV['PUPPET_GEM_VERSION']

View File

@ -1,297 +0,0 @@
require 'puppet'
require 'puppet/feature/aviator'
require 'puppet/util/inifile'
class Puppet::Provider::Aviator < Puppet::Provider
def session
@session ||= authenticate(resource[:auth], resource[:log_file])
end
def self.session
@session ||= authenticate(nil, nil)
end
def request(service, request, &block)
self.class.make_request(service, request, session_data, &block)
end
def self.request(service, request, &block)
self.make_request(service, request, session_data, &block)
end
# needed for tests
def session_data
@session_data
end
def self.session_data
@session_data
end
def session_data=(data)
@session_data=data
end
def self.session_data=(data)
@session_data=data
end
private
# Attempt to find credentials in this order:
# 1. username,password,tenant,host set in type parameters
# 2. openrc file path set in type parameters
# 3. service token and host set in type parameters
# 4. username,password,tenant,host set in environment variables
# 5. service token and host set in keystone.conf (backwards compatible version)
def authenticate(auth_params, log_file)
auth_params ||= {}
if password_credentials_set?(auth_params)
@session = get_authenticated_session(auth_params, log_file)
elsif openrc_set?(auth_params)
credentials = get_credentials_from_openrc(auth_params['openrc'])
@session = get_authenticated_session(credentials, log_file)
elsif service_credentials_set?(auth_params)
session_hash = get_unauthenticated_session(auth_params, log_file)
@session_data = session_hash[:data]
@session = session_hash[:session]
elsif env_vars_set?
credentials = get_credentials_from_env
@session = get_authenticated_session(credentials, log_file)
else # Last effort: try to get the token from keystone.conf
session_hash = self.class.try_auth_with_token(keystone_file, log_file)
@session_data = session_hash[:data]
@session = session_hash[:session]
end
end
def self.authenticate(auth_params, log_file)
auth_params = {} unless auth_params
if env_vars_set?
credentials = get_credentials_from_env
@session = get_authenticated_session(credentials, log_file)
else # Last effort: try to get the token from keystone.conf
session_hash = try_auth_with_token(keystone_file, log_file)
@session_data = session_hash[:data]
@session = session_hash[:session]
end
end
def self.try_auth_with_token(conf_file, log_file)
service_token = get_admin_token_from_keystone_file(conf_file)
auth_url = get_auth_url_from_keystone_file(conf_file)
session_hash = {}
if service_token
credentials = {
'service_token' => service_token,
'host_uri' => auth_url,
}
session_hash = get_unauthenticated_session(credentials, log_file)
else # All authentication efforts failed
raise(Puppet::Error, 'No credentials provided.')
end
end
def self.make_request(service, request, session_data, &block)
response = nil
if service && service.default_session_data
response = service.request(request, :endpoint_type => 'admin') do |params|
yield(params) if block
end
elsif session_data
response = service.request(request, :endpoint_type => 'admin',
:session_data => session_data) do |params|
yield(params) if block
end
else
raise(Puppet::Error, 'Cannot make a request with no session data.')
end
if response.body.hash['error']
raise(Puppet::Error, "Error making request: #{response.body.hash['error']['code']} #{response.body.hash['error']['title']}")
end
response
end
def password_credentials_set?(auth_params)
auth_params['username'] && auth_params['password'] && auth_params['tenant_name'] && auth_params['host_uri']
end
def openrc_set?(auth_params)
auth_params['openrc']
end
def service_credentials_set?(auth_params)
auth_params['service_token'] && auth_params['host_uri']
end
def self.env_vars_set?
ENV['OS_USERNAME'] && ENV['OS_PASSWORD'] && ENV['OS_TENANT_NAME'] && ENV['OS_AUTH_URL']
end
def env_vars_set?
self.class.env_vars_set?
end
def get_credentials_from_openrc(file)
creds = {}
begin
File.open(file).readlines.delete_if{|l| l=~ /^#/}.each do |line|
key, value = line.split('=')
key = key.split(' ').last
value = value.chomp.gsub(/'/, '')
creds[key] = value
end
return creds
rescue Exception => error
return {}
end
end
def self.get_credentials_from_env
ENV.to_hash.dup.delete_if { |key, _| ! (key =~ /^OS/) } # Ruby 1.8.7
end
def get_credentials_from_env
self.class.get_credentials_from_env
end
def self.keystone_file
keystone_file = Puppet::Util::IniConfig::File.new
keystone_file.read('/etc/keystone/keystone.conf')
keystone_file
end
def keystone_file
return @keystone_file if @keystone_file
@keystone_file = Puppet::Util::IniConfig::File.new
@keystone_file.read('/etc/keystone/keystone.conf')
@keystone_file
end
def self.get_admin_token_from_keystone_file(conf_file)
if conf_file and conf_file['DEFAULT'] and conf_file['DEFAULT']['admin_token']
return "#{conf_file['DEFAULT']['admin_token'].strip}"
else
return nil
end
end
def get_admin_token_from_keystone_file
conf_file = keystone_file
self.class.get_admin_token_from_keystone_file(conf_file)
end
def self.get_auth_url_from_keystone_file(conf_file)
if conf_file
if conf_file['DEFAULT']
if conf_file['DEFAULT']['admin_endpoint']
auth_url = conf_file['DEFAULT']['admin_endpoint'].strip
return versioned_endpoint(auth_url)
end
if conf_file['DEFAULT']['admin_port']
admin_port = conf_file['DEFAULT']['admin_port'].strip
else
admin_port = '35357'
end
if conf_file['DEFAULT']['admin_bind_host']
host = conf_file['DEFAULT']['admin_bind_host'].strip
if host == "0.0.0.0"
host = "127.0.0.1"
end
else
host = "127.0.0.1"
end
end
if conf_file['ssl'] && conf_file['ssl']['enable'] && conf_file['ssl']['enable'].strip.downcase == 'true'
protocol = 'https'
else
protocol = 'http'
end
end
"#{protocol}://#{host}:#{admin_port}/v2.0/"
end
def get_auth_url_from_keystone_file
self.class.get_auth_url_from_keystone_file(keystone_file)
end
def self.make_configuration(credentials)
host_uri = versioned_endpoint(credentials['host_uri'] || credentials['OS_AUTH_URL'], credentials['api_version'])
{
:provider => 'openstack',
:auth_service => {
:name => 'identity',
:host_uri => host_uri,
:request => 'create_token',
:validator => 'list_tenants',
},
:auth_credentials => {
:username => credentials['username'] || credentials['OS_USERNAME'],
:password => credentials['password'] || credentials['OS_PASSWORD'],
:tenant_name => credentials['tenant_name'] || credentials['OS_TENANT_NAME']
}
}
end
def self.get_authenticated_session(credentials, log_file)
configuration = make_configuration(credentials)
session = ::Aviator::Session.new(:config => configuration, :log_file => log_file)
session.authenticate
session
end
def get_authenticated_session(credentials, log_file)
self.class.get_authenticated_session(credentials, log_file)
end
def self.get_unauthenticated_session(credentials, log_file)
configuration = {
:provider => 'openstack',
}
session_data = {
:base_url => credentials['host_uri'],
:service_token => credentials['service_token']
}
session = ::Aviator::Session.new(:config => configuration, :log_file => log_file)
{ :session => session, :data => session_data }
end
def get_unauthenticated_session(credentials, log_file)
self.class.get_unauthenticated_session(credentials, log_file)
end
def self.versioned_endpoint(endpoint, version = 'v2.0')
version = 'v2.0' if version.nil?
if endpoint =~ /\/#{version}\/?$/ || endpoint =~ /\/v2.0\/?$/ || endpoint =~ /\/v3\/?$/
endpoint
else
"#{endpoint.chomp('/')}/#{version}"
end
end
end

View File

@ -0,0 +1,183 @@
require 'csv'
require 'puppet'
class Puppet::Error::OpenstackAuthInputError < Puppet::Error
end
class Puppet::Error::OpenstackUnauthorizedError < Puppet::Error
end
class Puppet::Provider::Openstack < Puppet::Provider
initvars # so commands will work
commands :openstack => 'openstack'
def request(service, action, object, credentials, *properties)
if password_credentials_set?(credentials)
auth_args = password_auth_args(credentials)
elsif openrc_set?(credentials)
credentials = get_credentials_from_openrc(credentials['openrc'])
auth_args = password_auth_args(credentials)
elsif service_credentials_set?(credentials)
auth_args = token_auth_args(credentials)
elsif env_vars_set?
# noop; auth needs no extra arguments
auth_args = nil
else # All authentication efforts failed
raise(Puppet::Error::OpenstackAuthInputError, 'No credentials provided.')
end
args = [object, properties, auth_args].flatten.compact
authenticate_request(service, action, args)
end
def self.request(service, action, object, *properties)
if env_vars_set?
# noop; auth needs no extra arguments
auth_args = nil
else # All authentication efforts failed
raise(Puppet::Error::OpenstackAuthInputError, 'No credentials provided.')
end
args = [object, properties, auth_args].flatten.compact
authenticate_request(service, action, args)
end
# Returns an array of hashes, where the keys are the downcased CSV headers
# with underscores instead of spaces
def self.authenticate_request(service, action, *args)
rv = nil
timeout = 10
end_time = Time.now.to_i + timeout
loop do
begin
if(action == 'list')
response = openstack(service, action, '--quiet', '--format', 'csv', args)
# Ignore warnings - assume legitimate output starts with a double quote
# Errors will be caught and raised prior to this
response = response.split("\n").select { |line| line =~ /^\".*\",\".*\"/ }.join("\n")
response = CSV.parse(response.to_s)
keys = response.delete_at(0) # ID,Name,Description,Enabled
rv = response.collect do |line|
hash = {}
keys.each_index do |index|
key = keys[index].downcase.gsub(/ /, '_').to_sym
hash[key] = line[index]
end
hash
end
elsif(action == 'show' || action == 'create')
rv = {}
# shell output is name="value"\nid="value2"\ndescription="value3" etc.
openstack(service, action, '--format', 'shell', args).split("\n").each do |line|
# key is everything before the first "="
key, val = line.split("=", 2)
next unless val # Ignore warnings
# value is everything after the first "=", with leading and trailing double quotes stripped
val = val.gsub(/\A"|"\Z/, '')
rv[key.downcase.to_sym] = val
end
else
rv = openstack(service, action, args)
end
break
rescue Puppet::ExecutionFailure => e
if e.message =~ /HTTP 401/
raise(Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate.')
elsif e.message =~ /Unable to establish connection/
current_time = Time.now.to_i
if current_time > end_time
break
else
wait = end_time - current_time
Puppet::debug("Non-fatal error: \"#{e.message}\"; retrying for #{wait} more seconds.")
if wait > timeout - 2 # Only notice the first time
notice("#{service} service is unavailable. Will retry for up to #{wait} seconds.")
end
end
sleep(2)
else
raise e
end
end
end
return rv
end
def authenticate_request(service, action, *args)
self.class.authenticate_request(service, action, *args)
end
private
def password_credentials_set?(auth_params)
auth_params && auth_params['username'] && auth_params['password'] && auth_params['tenant_name'] && auth_params['auth_url']
end
def openrc_set?(auth_params)
auth_params && auth_params['openrc']
end
def service_credentials_set?(auth_params)
auth_params && auth_params['token'] && auth_params['auth_url']
end
def self.env_vars_set?
ENV['OS_USERNAME'] && ENV['OS_PASSWORD'] && ENV['OS_TENANT_NAME'] && ENV['OS_AUTH_URL']
end
def env_vars_set?
self.class.env_vars_set?
end
def self.password_auth_args(credentials)
['--os-username', credentials['username'],
'--os-password', credentials['password'],
'--os-tenant-name', credentials['tenant_name'],
'--os-auth-url', credentials['auth_url']]
end
def password_auth_args(credentials)
self.class.password_auth_args(credentials)
end
def self.token_auth_args(credentials)
['--os-token', credentials['token'],
'--os-url', credentials['auth_url']]
end
def token_auth_args(credentials)
self.class.token_auth_args(credentials)
end
def get_credentials_from_openrc(file)
creds = {}
File.open(file).readlines.delete_if{|l| l=~ /^#/}.each do |line|
key, value = line.split('=')
key = key.split(' ').last.downcase.sub(/^os_/, '')
value = value.chomp.gsub(/'/, '')
creds[key] = value
end
return creds
end
def self.get_credentials_from_env
env = ENV.to_hash.dup.delete_if { |key, _| ! (key =~ /^OS_/) }
credentials = {}
env.each do |name, value|
credentials[name.downcase.sub(/^os_/, '')] = value
end
credentials
end
def get_credentials_from_env
self.class.get_credentials_from_env
end
end

View File

@ -1,6 +1,6 @@
# Add the auth parameter to whatever type is given
module Puppet::Util::Aviator
def self.add_aviator_params(type)
module Puppet::Util::Openstack
def self.add_openstack_type_methods(type, comment)
type.newparam(:auth) do
@ -12,7 +12,7 @@ auth => {
'username' => 'test',
'password' => 'passw0rd',
'tenant_name' => 'test',
'host_uri' => 'http://localhost:35357/v2.0',
'auth_url' => 'http://localhost:35357/v2.0',
}
or a path to an openrc file containing these credentials, e.g.:
@ -25,12 +25,13 @@ or a service token and host, e.g.:
auth => {
'service_token' => 'ADMIN',
'host_uri' => 'http://localhost:35357/v2.0',
'auth_url' => 'http://localhost:35357/v2.0',
}
If not present, the provider will first look for environment variables
for password credentials and then to /etc/keystone/keystone.conf for a
service token.
If not present, the provider will look for environment variables for
password credentials.
#{comment}
EOT
validate do |value|
@ -38,9 +39,9 @@ EOT
end
end
type.newparam(:log_file) do
desc 'Log file. Defaults to no logging.'
defaultto('/dev/null')
type.autorequire(:package) do
'python-openstackclient'
end
end
end

View File

@ -0,0 +1,18 @@
# == Class: openstacklib::openstackclient
#
# Installs the openstackclient
#
# == Parameters
#
# [package_ensure]
# Ensure state of the openstackclient package.
# Optional. Defaults to 'present'.
#
class openstacklib::openstackclient(
$package_ensure = 'present',
){
package { 'python-openstackclient':
ensure => $package_ensure,
tag => 'openstack',
}
}

View File

@ -31,7 +31,6 @@
],
"description": "Puppet module library to expose common functionality between OpenStack modules.",
"dependencies": [
{ "name": "aimonb/aviator", "version_requirement": ">=0.4.2 <1.0.0" },
{ "name": "puppetlabs/apache", "version_requirement": ">=1.0.0 <2.0.0" },
{ "name": "puppetlabs/mysql", "version_requirement": ">=3.0.0 <4.0.0" },
{ "name": "puppetlabs/stdlib", "version_requirement": ">=4.0.0 <5.0.0" },

File diff suppressed because one or more lines are too long

View File

@ -1,36 +0,0 @@
---
http_interactions:
- request:
method: get
uri: "http://192.168.11.4:35357/v2.0/tenants"
body:
encoding: US-ASCII
string: ""
headers:
Content-Type:
- application/json
User-Agent:
- "Faraday v0.8.8"
X-Auth-Token:
- sosp-kyl
response:
status:
code: 200
message:
headers:
vary:
- X-Auth-Token
content-type:
- application/json
content-length:
- "491"
date:
- "Tue, 30 Sep 2014 06:59:48 GMT"
connection:
- close
body:
encoding: UTF-8
string: "{\x22tenants_links\x22: [], \x22tenants\x22: [{\x22description\x22: \x22Test tenant\x22, \x22enabled\x22: true, \x22id\x22: \x2234e463e2bab24f78990ca864e4a28ba2\x22, \x22name\x22: \x22test2\x22}, {\x22description\x22: \x22Tenant for the openstack services\x22, \x22enabled\x22: true, \x22id\x22: \x2268c8fcf77aff4b409cc158c0f6cbff7b\x22, \x22name\x22: \x22services\x22}, {\x22description\x22: \x22Test tenant\x22, \x22enabled\x22: true, \x22id\x22: \x22c330f1bc663648df9c1e7835a1e7a955\x22, \x22name\x22: \x22test\x22}, {\x22description\x22: \x22admin tenant\x22, \x22enabled\x22: true, \x22id\x22: \x22c518b36fa220499b85ba9a71014ce2a5\x22, \x22name\x22: \x22admin\x22}]}"
http_version:
recorded_at: "Tue, 30 Sep 2014 06:59:48 GMT"
recorded_with: "VCR 2.9.3"

File diff suppressed because one or more lines are too long

View File

@ -1,36 +0,0 @@
---
http_interactions:
- request:
method: get
uri: "http://192.168.11.4:35357/v2.0/tenants"
body:
encoding: US-ASCII
string: ""
headers:
Content-Type:
- application/json
User-Agent:
- "Faraday v0.8.8"
X-Auth-Token:
- sosp-kyl
response:
status:
code: 200
message:
headers:
vary:
- X-Auth-Token
content-type:
- application/json
content-length:
- "491"
date:
- "Tue, 30 Sep 2014 06:59:48 GMT"
connection:
- close
body:
encoding: UTF-8
string: "{\x22tenants_links\x22: [], \x22tenants\x22: [{\x22description\x22: \x22Test tenant\x22, \x22enabled\x22: true, \x22id\x22: \x2234e463e2bab24f78990ca864e4a28ba2\x22, \x22name\x22: \x22test2\x22}, {\x22description\x22: \x22Tenant for the openstack services\x22, \x22enabled\x22: true, \x22id\x22: \x2268c8fcf77aff4b409cc158c0f6cbff7b\x22, \x22name\x22: \x22services\x22}, {\x22description\x22: \x22Test tenant\x22, \x22enabled\x22: true, \x22id\x22: \x22c330f1bc663648df9c1e7835a1e7a955\x22, \x22name\x22: \x22test\x22}, {\x22description\x22: \x22admin tenant\x22, \x22enabled\x22: true, \x22id\x22: \x22c518b36fa220499b85ba9a71014ce2a5\x22, \x22name\x22: \x22admin\x22}]}"
http_version:
recorded_at: "Tue, 30 Sep 2014 06:59:48 GMT"
recorded_with: "VCR 2.9.3"

View File

@ -1,14 +1,7 @@
require 'puppetlabs_spec_helper/module_spec_helper'
require 'shared_examples'
require 'vcr'
RSpec.configure do |c|
c.alias_it_should_behave_like_to :it_configures, 'configures'
c.alias_it_should_behave_like_to :it_raises, 'raises'
end
VCR.configure do |c|
c.cassette_library_dir = 'spec/fixtures/vcr'
c.hook_into :faraday
end

View File

@ -1,320 +0,0 @@
# Load libraries from aviator here to simulate how they live together in a real puppet run
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'modules', 'aviator', 'lib'))
require 'puppet'
require 'vcr'
require 'spec_helper'
require 'puppet/provider/aviator'
describe Puppet::Provider::Aviator do
before(:each) do
ENV['OS_USERNAME'] = nil
ENV['OS_PASSWORD'] = nil
ENV['OS_TENANT_NAME'] = nil
ENV['OS_AUTH_URL'] = nil
end
let(:log_file) { '/tmp/aviator_spec.log' }
let(:type) do
Puppet::Type.newtype(:test_resource) do
newparam(:name, :namevar => true)
newparam(:auth)
newparam(:log_file)
end
end
shared_examples 'creating a session using environment variables' do
it 'creates an authenticated session' do
ENV['OS_USERNAME'] = 'admin'
ENV['OS_PASSWORD'] = 'fyby-tet'
ENV['OS_TENANT_NAME'] = 'admin'
ENV['OS_AUTH_URL'] = 'http://192.168.11.4:35357/v2.0'
response = nil
VCR.use_cassette('aviator/session/with_password') do
session = provider.session
response = session.identity_service.request(:list_tenants, :session_data => provider.session_data)
end
expect(response.status).to eq(200)
end
end
shared_examples 'creating a session using a service token from keystone.conf' do
it 'creates an unauthenticated session' do
data = "[DEFAULT]\nadmin_token=sosp-kyl\nadmin_endpoint=http://192.168.11.4:35357/v2.0"
response = nil
VCR.use_cassette('aviator/session/with_token') do
# Stubbing File.read produces inconsistent results because of how IniConfig
# overrides the File class in some versions of Puppet.
# Stubbing FileType.filetype(:flat) simplifies working with IniConfig
Puppet::Util::FileType.filetype(:flat).any_instance.expects(:read).returns(StringIO.new(data).read)
session = provider.session
Puppet::Util::FileType.filetype(:flat).any_instance.unstub(:read)
response = session.identity_service.request(:list_tenants, :session_data => provider.session_data)
end
expect(response.status).to eq(200)
end
end
shared_examples 'it has no credentials' do
it 'fails to authenticate' do
expect{ provider.session }.to raise_error(Puppet::Error, /No credentials provided/)
end
end
shared_examples 'making request with an existing session' do
it 'makes a successful request' do
VCR.use_cassette('aviator/request/with_session') do
session = provider.session
response = provider.request(session.identity_service, :list_tenants)
expect(response.status).to eq(200)
end
end
end
shared_examples 'making request with injected session data' do
it 'makes a successful request' do
VCR.use_cassette('aviator/request/without_session') do
session = provider.session
response = provider.request(session.identity_service, :list_tenants)
expect(response.status).to eq(200)
end
end
end
shared_examples 'making request with no session or session data' do
it 'fails to make a request' do
expect{ provider.request(nil, :list_tenants) }.to raise_error(Puppet::Error, /Cannot make a request/)
end
end
describe '#session' do
context 'with valid password credentials in parameters' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'username' => 'admin',
'password' => 'fyby-tet',
'tenant_name' => 'admin',
'host_uri' => 'http://192.168.11.4:35357/v2.0',
}
}
end
it 'creates a session' do
provider = Puppet::Provider::Aviator.new(type.new(resource_attrs))
response = nil
VCR.use_cassette('aviator/session/with_password') do
session = provider.session
response = session.identity_service.request(:list_tenants)
end
expect(response.status).to eq(200)
end
end
context 'with valid openrc file in parameters' do
data = "export OS_USERNAME='admin'\nexport OS_PASSWORD='fyby-tet'\nexport OS_TENANT_NAME='admin'\nexport OS_AUTH_URL='http://192.168.11.4:35357/v2.0'"
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'openrc' => '/root/openrc'
}
}
end
it 'creates a session' do
provider = Puppet::Provider::Aviator.new(type.new(resource_attrs))
response = nil
VCR.use_cassette('aviator/session/with_password') do
File.expects(:open).with('/root/openrc').returns(StringIO.new(data))
session = provider.session
File.unstub(:open) # Ignore File.open calls to cassette file
response = session.identity_service.request(:list_tenants)
end
expect(response.status).to eq(200)
end
end
context 'with valid service token in parameters' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'service_token' => 'sosp-kyl',
'host_uri' => 'http://192.168.11.4:35357/v2.0'
}
}
end
subject(:session) do
provider = Puppet::Provider::Aviator.new(type.new(resource_attrs))
VCR.use_cassette('aviator/session/with_token') do
session = provider.session
response = session.identity_service.request(:list_tenants, :session_data => provider.session_data)
end
end
it 'creates a session' do
expect(session.status).to eq(200)
end
end
context 'with valid password credentials in environment variables' do
it_behaves_like 'creating a session using environment variables' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Aviator.new(type.new(resource_attrs))
end
end
end
context 'with valid service token in keystone.conf' do
it_behaves_like 'creating a session using a service token from keystone.conf' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Aviator.new(type.new(resource_attrs))
end
end
end
context 'with no valid credentials' do
it_behaves_like 'it has no credentials' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) { Puppet::Provider::Aviator.new(type.new(resource_attrs)) }
end
end
end
describe '::session' do
context 'with valid password credentials in environment variables' do
it_behaves_like 'creating a session using environment variables' do
let(:provider) { Puppet::Provider::Aviator.dup }
end
end
context 'with valid service token in keystone.conf' do
it_behaves_like 'creating a session using a service token from keystone.conf' do
let(:provider) { Puppet::Provider::Aviator.dup }
end
end
context 'with no valid credentials' do
it_behaves_like 'it has no credentials' do
let(:provider) { Puppet::Provider::Aviator.dup }
end
end
end
describe '#request' do
context 'when a session exists' do
it_behaves_like 'making request with an existing session' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'username' => 'admin',
'password' => 'fyby-tet',
'tenant_name' => 'admin',
'host_uri' => 'http://192.168.11.4:35357/v2.0',
}
}
end
let (:provider) { Puppet::Provider::Aviator.new(type.new(resource_attrs)) }
end
end
context 'when injecting session data' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'service_token' => 'sosp-kyl',
'host_uri' => 'http://192.168.11.4:35357/v2.0'
}
}
end
let(:provider) { Puppet::Provider::Aviator.new(type.new(resource_attrs)) }
it 'makes a successful request' do
provider = Puppet::Provider::Aviator.new(type.new(resource_attrs))
VCR.use_cassette('aviator/request/without_session') do
session = provider.session
response = provider.request(session.identity_service, :list_tenants)
expect(response.status).to eq(200)
end
end
end
context 'when there is no session or session data' do
it_behaves_like 'making request with no session or session data' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) {Puppet::Provider::Aviator.new(type.new(resource_attrs)) }
end
end
end
describe '::request' do
context 'when a session exists' do
it_behaves_like 'making request with an existing session' do
let(:provider) { provider = Puppet::Provider::Aviator.dup }
before(:each) do
ENV['OS_USERNAME'] = 'admin'
ENV['OS_PASSWORD'] = 'fyby-tet'
ENV['OS_TENANT_NAME'] = 'admin'
ENV['OS_AUTH_URL'] = 'http://192.168.11.4:35357/v2.0'
end
end
end
context 'when injecting session data' do
let(:session_data) do
{
:base_url => 'http://192.168.11.4:35357/v2.0',
:service_token => 'sosp-kyl'
}
end
it 'makes a successful request' do
provider = Puppet::Provider::Aviator.dup
VCR.use_cassette('aviator/request/without_session') do
session = ::Aviator::Session.new(:config => { :provider => 'openstack' }, :log_file => log_file)
provider.session_data = session_data
response = provider.request(session.identity_service, :list_tenants)
expect(response.status).to eq(200)
end
end
end
context 'when there is no session or session data' do
it_behaves_like 'making request with no session or session data' do
let(:provider) { Puppet::Provider::Aviator.dup }
end
end
end
end

View File

@ -0,0 +1,228 @@
# Load libraries from aviator here to simulate how they live together in a real puppet run
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'modules', 'aviator', 'lib'))
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_TENANT_NAME'] = nil
ENV['OS_AUTH_URL'] = nil
end
let(:type) do
Puppet::Type.newtype(:test_resource) do
newparam(:name, :namevar => true)
newparam(:auth)
newparam(:log_file)
end
end
shared_examples 'authenticating with environment variables' do
it 'makes a successful request' do
ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_TENANT_NAME'] = 'test'
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v2.0'
if provider.class == Class
provider.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [[ '--long' ]])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
else
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [[ '--long' ]])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
end
response = provider.request('project', 'list', nil, nil, '--long' )
expect(response.first[:description]).to match /Test tenant/
end
end
shared_examples 'it has no credentials' do
it 'fails to authenticate' do
expect{ provider.request('project', 'list', nil, nil, '--long') }.to raise_error(Puppet::Error::OpenstackAuthInputError, /No credentials provided/)
end
end
describe '#request' do
context 'with valid password credentials in parameters' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'username' => 'test',
'password' => 'abc123',
'tenant_name' => 'test',
'auth_url' => 'http://127.0.0.1:5000/v2.0',
}
}
end
let(:provider) do
Puppet::Provider::Openstack.new(type.new(resource_attrs))
end
it 'makes a successful request' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'test', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
response = provider.request('project', 'list', nil, resource_attrs[:auth], '--long')
expect(response.first[:description]).to match /Test tenant/
end
end
context 'with valid openrc file in parameters' do
mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_TENANT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000/v2.0'"
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'openrc' => '/root/openrc'
}
}
end
let(:provider) do
Puppet::Provider::Openstack.new(type.new(resource_attrs))
end
it 'makes a successful request' do
File.expects(:open).with('/root/openrc').returns(StringIO.new(mock))
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'test', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
response = provider.request('project', 'list', nil, resource_attrs[:auth], '--long')
expect(response.first[:description]).to match /Test tenant/
end
end
context 'with valid service token in parameters' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'token' => 'secrettoken',
'auth_url' => 'http://127.0.0.1:5000/v2.0'
}
}
end
let(:provider) do
Puppet::Provider::Openstack.new(type.new(resource_attrs))
end
it 'makes a successful request' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-token', 'secrettoken', '--os-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
response = provider.request('project', 'list', nil, resource_attrs[:auth], '--long')
expect(response.first[:description]).to match /Test tenant/
end
it 'makes a successful show request' do
provider.class.stubs(:openstack)
.with('project', 'show', '--format', 'shell', [['test', '--os-token', 'secrettoken', '--os-url', 'http://127.0.0.1:5000/v2.0']])
.returns('ID="1cb05cfed7c24279be884ba4f6520262"
Name="test"
Description="Test Tenant"
Enabled="True"
')
response = provider.request('project', 'show', 'test', resource_attrs[:auth])
expect(response[:description]).to match /Test Tenant/
expect(response[:id]).to match /1cb05cfed7c24279be884ba4f6520262/
expect(response[:name]).to match /test/
expect(response[:enabled]).to match /True/
end
end
context 'with valid password credentials in environment variables' do
it_behaves_like 'authenticating with environment variables' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Openstack.new(type.new(resource_attrs))
end
end
end
context 'with no valid credentials' do
it_behaves_like 'it has no credentials' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Openstack.new(type.new(resource_attrs))
end
end
end
context 'it retries on connection errors' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'username' => 'test',
'password' => 'abc123',
'tenant_name' => 'test',
'auth_url' => 'http://127.0.0.1:5000/v2.0',
}
}
end
let(:provider) do
Puppet::Provider::Openstack.new(type.new(resource_attrs))
end
it 'retries' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'test', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.raises(Puppet::ExecutionFailure, 'Unable to establish connection')
.then
.returns('')
provider.class.expects(:sleep).with(2).returns(nil)
provider.request('project', 'list', nil, resource_attrs[:auth], '--long')
end
end
end
describe '::request' do
context 'with valid password credentials in environment variables' do
it_behaves_like 'authenticating with environment variables' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Openstack.dup
end
end
end
context 'with no valid credentials' do
it_behaves_like 'it has no credentials' do
let(:provider) { Puppet::Provider::Openstack.dup }
end
end
end
end