Files
puppet-openstacklib/lib/puppet/provider/openstack.rb
Dmitry Ilyin 701b6fe0ac Add retries to the openstack command
Sometimes openstackclient can hang if Keystone
API fails to respond to requests.
This patch adds retries to work around these
situations.

* Retry and timeouts for openstack command
  -'command_timeout=20':
    after this timeout openstack command is retried
    untill the request_timeout.
    (The value of command_timeout parameter was updated
    from 10 to 20 as it was observed that "10" value is
    not enough when sytem was overloaded.)
  -'request_timeout=60':
    timeout of the entire request.
  -'retry_sleep=3':
    interval between command retries
* Does not retry non-idempotent actions.
* Improve specs

Change-Id: Ifd8ae1b00321366e3a54fd6fe4a68db46bb743c7
2015-12-21 16:34:50 +02:00

121 lines
3.6 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'
# this actions are not idempotent and retries can cause
# duplications or endless loops
def self.no_retry_actions
%w(create remove delete)
end
# timeout the openstack command
# after this number of seconds
# retry the command until the request_timeout
def self.command_timeout
20
end
# timeout the entire request with error
# after this number of seconds
def self.request_timeout
60
end
# sleep for this number of seconds
# between command retries
def self.retry_sleep
3
end
# run the openstack command
# with command_timeout
def self.openstack(*args)
begin
Timeout.timeout(command_timeout) do
openstack_command *args
end
rescue Timeout::Error
raise Puppet::ExecutionFailure, "Command: 'openstack #{args.inspect}' has been running for more then #{command_timeout} seconds!"
end
end
# get the current timestamp
def self.current_time
Time.now.to_i
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
debug "Non-fatal error: '#{exception.message}'. Retrying for #{end_time - current_time} more seconds"
raise exception if no_retry_actions.include? action
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