Class: VagrantPlugins::Skytap::API::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant-skytap/api/client.rb

Constant Summary collapse

DEFAULT_TIMEOUT =
120
MAX_RATE_LIMIT_RETRIES =
3
DEFAULT_RETRY_AFTER_SECONDS =
5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Client

Returns a new instance of Client.



37
38
39
40
41
42
43
44
45
46
47
# File 'lib/vagrant-skytap/api/client.rb', line 37

def initialize(config)
  @logger = Log4r::Logger.new("vagrant_skytap::api_client")

  @config = config
  uri = URI.parse(config.base_url)
  @http = Net::HTTP.new(uri.host, uri.port)

  @http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  @http.use_ssl = uri.port == 443 || uri.scheme == 'https'
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



31
32
33
# File 'lib/vagrant-skytap/api/client.rb', line 31

def config
  @config
end

#httpObject (readonly)

Returns the value of attribute http.



31
32
33
# File 'lib/vagrant-skytap/api/client.rb', line 31

def http
  @http
end

Instance Method Details

#delete(path, options = {}) ⇒ Object



61
62
63
# File 'lib/vagrant-skytap/api/client.rb', line 61

def delete(path, options={})
  req('DELETE', path, nil, options)
end

#error_string_from_body(resp) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/vagrant-skytap/api/client.rb', line 132

def error_string_from_body(resp)
  resp = resp.body if resp.respond_to?(:body)
  begin
    resp = JSON.load(resp)
    errors = resp['error'] || resp['errors']
    errors = errors.join('; ') if errors.respond_to? :join
  rescue
    # treat non-JSON string as error text
    errors = resp
  end
  errors if errors.present?
end

#get(path, options = {}) ⇒ Object



49
50
51
# File 'lib/vagrant-skytap/api/client.rb', line 49

def get(path, options={})
  req('GET', path, nil, options)
end

#post(path, body = nil, options = {}) ⇒ Object



53
54
55
# File 'lib/vagrant-skytap/api/client.rb', line 53

def post(path, body=nil, options={})
  req('POST', path, body, options)
end

#put(path, body = nil, options = {}) ⇒ Object



57
58
59
# File 'lib/vagrant-skytap/api/client.rb', line 57

def put(path, body=nil, options={})
  req('PUT', path, body, options)
end

#req(method, path, body, options = {}) ⇒ Object

path may optionally include query and fragment parts

options are:

query: A string or hash of the query string
extra_headers: A hash of extra headers


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/vagrant-skytap/api/client.rb', line 70

def req(method, path, body, options={})
  @logger.info("REST API call: #{method} #{path} #{'body: ' + body if body.present?}")

  uri = URI.parse(path)

  if qq = options[:query]
    if qq.is_a?(Hash)
      extra_query = qq.collect{|k,v| [k,v].join('=')}.join('&')
    else
      extra_query = qq.to_s
    end
  end

  if (query = [uri.query, extra_query].compact.join('&')).present?
    path = [uri.path, query].join('?')
  end

  headers = default_headers.merge(options[:extra_headers] || {})
  retry_after = DEFAULT_RETRY_AFTER_SECONDS
  most_recent_exception = nil

  begin
    Timeout.timeout(options[:timeout] || DEFAULT_TIMEOUT) do
      begin
        http.send_request(method, URI.encode(path), body, headers).tap do |ret|
          @logger.debug("REST API response: #{ret.body}")
          unless ret.code =~ /^2\d\d/
            raise Errors::DoesNotExist, object_name: "Object '#{path}'" if ret.code == '404'
            error_class = case ret.code
            when '403'
              Errors::Unauthorized
            when '422'
              Errors::UnprocessableEntity
            when '423'
              Errors::ResourceBusy
            when '429'
              retry_after = ret['Retry-After'] || DEFAULT_RETRY_AFTER_SECONDS
              Errors::RateLimited
            else
              Errors::OperationFailed
            end
            raise error_class, err: error_string_from_body(ret)
          end
        end
      rescue Errors::RateLimited => ex
        most_recent_exception = ex
        @logger.info("Rate limited, wil retry in #{retry_after} seconds")
        sleep retry_after.to_f + 0.1
        retry
      rescue Errors::ResourceBusy => ex
        most_recent_exception = ex
        @logger.debug("Resource busy, retrying")
        sleep DEFAULT_RETRY_AFTER_SECONDS
        retry
      end
    end
  rescue Timeout::Error => ex
    raise most_recent_exception if most_recent_exception
    raise Errors::OperationFailed, err: "Timeout exceeded"
  end
end