Files
puppet-openstacklib/lib/puppet/provider/openstack.rb
Sofer Athlan-Guyot fedb3152c9 Add the possibility to execute without retry.
It may be useful to be able to execute a command without a retry.

A good use case is provider in [1], where the keystone_user resource try
to get an user by fetching it.  It is expected to fail when the user is
absent.  With the current implementation, it will takes 60 seconds for
the provider to give up on the user.

[1] https://review.openstack.org/299301

Closes-Bug: #1563898

Change-Id: I5b334e3ffd26df4ba8584d77a5e41b56e73536c8
2016-04-13 14:06:03 +00:00

133 lines
4.1 KiB
Ruby

require 'csv'
require 'puppet'
require 'timeout'
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_command => 'openstack'
@@no_retry_actions = %w(create remove delete)
@@command_timeout = 20
@@request_timeout = 60
@@retry_sleep = 3
class << self
[:no_retry_actions, :request_timeout, :retry_sleep].each do |m|
define_method m do
self.class_variable_get("@@#{m}")
end
define_method :"#{m}=" do |value|
self.class_variable_set("@@#{m}", value)
end
end
end
# timeout the openstack command
# after this number of seconds
# retry the command until the request_timeout,
# unless it's a no_retry_actions call
def self.command_timeout(action=nil)
# give no_retry actions the full time limit to finish
return self.request_timeout() if no_retry_actions.include? action
self.class_variable_get("@@command_timeout")
end
# with command_timeout
def self.openstack(*args)
begin
action = args[1]
Timeout.timeout(command_timeout(action)) do
openstack_command *args
end
rescue Timeout::Error
raise Puppet::ExecutionFailure, "Command: 'openstack #{args.inspect}' has been running for more then #{command_timeout(action)} seconds!"
end
end
# get the current timestamp
def self.current_time
Time.now.to_i
end
def self.request_without_retry(&block)
previous_timeout = self.request_timeout
rc = nil
if block_given?
self.request_timeout = 0
rc = yield
end
ensure
self.request_timeout = previous_timeout
rc
end
# Returns an array of hashes, where the keys are the downcased CSV headers
# with underscores instead of spaces
def self.request(service, action, properties, credentials=nil)
env = credentials ? credentials.to_env : {}
Puppet::Util.withenv(env) do
rv = nil
end_time = current_time + request_timeout
loop do
begin
if action == 'list'
# shell output is:
# ID,Name,Description,Enabled
response = openstack(service, action, '--quiet', '--format', 'csv', properties)
response = parse_csv(response)
keys = response.delete_at(0)
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' or action == 'create'
rv = {}
# shell output is:
# name="value1"
# id="value2"
# description="value3"
openstack(service, action, '--format', 'shell', properties).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, properties)
end
break
rescue Puppet::ExecutionFailure => exception
raise Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate' if exception.message =~ /HTTP 40[13]/
raise exception if current_time > end_time
raise exception if no_retry_actions.include? action
debug "Non-fatal error: '#{exception.message}'. Retrying for #{end_time - current_time} more seconds"
sleep retry_sleep
retry
end
end
return rv
end
end
private
def self.parse_csv(text)
# Ignore warnings - assume legitimate output starts with a double quoted
# string. Errors will be caught and raised prior to this
text = text.split("\n").drop_while { |line| line !~ /^\".*\"/ }.join("\n")
return CSV.parse(text + "\n")
end
end