Class: RightScale::CloudApi::ResponseAnalyzer

Inherits:
Routine show all
Defined in:
lib/base/routines/response_analyzer.rb

Overview

The routine analyzes HTTP responses and in the case of HTTP error it takes actions defined through error_pattern definitions.

Defined Under Namespace

Classes: Error

Instance Attribute Summary

Attributes inherited from Routine

#data

Instance Method Summary collapse

Methods inherited from Routine

#cloud_api_logger, #execute, #invoke_callback_method, #options, #reset, #with_timer

Instance Method Details

#processObject

Analyzes an HTTP response.

In the case of 4xx, 5xx HTTP errors the method parses the response body to get the error message. Then it tries to find an error pattern that would match to the response. If the pattern found it takes the action (:retry, :abort, :disconnect_and_abort or :reconnect_and_retry) acordingly to the error patern. If the pattern not fount it just fails with RightScale::CloudApi::CloudError.

In the case of 2xx code the method does nothing.

In the case of any other unexpected HTTP code it fails with RightScale::CloudApi::CloudError.

Examples:

error_pattern :abort_on_timeout,     :path     => /Action=(Run|Create)/
error_pattern :retry,                :response => /InternalError|Internal Server Error|internal service error/i
error_pattern :disconnect_and_abort, :code     => /5..|403|408/
error_pattern :reconnect_and_retry,  :code     => /4../, :if => Proc.new{ |opts| rand(100) < 10 }


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/base/routines/response_analyzer.rb', line 53

def process
  # Extract the current response and log it.
  response = data[:response][:instance]
  unless response.nil?
    cloud_api_logger.log("Response received: #{response.to_s}", :response_analyzer)
    cloud_api_logger.log("Response headers:  #{response.headers_info}", :response_analyzer)
    log_method = (response.is_error? || response.is_redirect?) ? :response_analyzer_body_error : :response_analyzer_body
    cloud_api_logger.log("Response body:     #{response.body_info}", log_method)
  end

  code = data[:response][:instance].code
  body = data[:response][:instance].body
  close_current_connection_proc = data[:callbacks][:close_current_connection]

  # Analyze the response code.
  case code
  when /^(5..|4..)/
    # Try to parse the received error message.
    error_message = if data[:options][:response_error_parser]
                      parser = data[:options][:response_error_parser]
                      with_timer("Error parsing with #{parser}") do
                        parser::parse(data[:response][:instance], data[:options])
                      end
                    else
                      "#{code}: #{body.to_s}"
                    end
    # Get the list of patterns.
    error_patterns = data[:options][:error_patterns] || []
    opts = { :request  => data[:request][:instance],
             :response => data[:response][:instance],
             :verb     => data[:request][:verb],
             :params   => data[:request][:orig_params].dup }
    # Walk through all the patterns and find the first that matches.
    error_patterns.each do |pattern|
      if Utils::pattern_matches?(pattern, opts)
        cloud_api_logger.log("Error code: #{code}, pattern match: #{pattern.inspect}", :response_analyzer, :warn)
        # Take the required action.
        case pattern[:action]
        when :disconnect_and_abort
          close_current_connection_proc && close_current_connection_proc.call("Error code: #{code}")
          fail(HttpError::new(code, error_message))
        when :reconnect_and_retry
          close_current_connection_proc && close_current_connection_proc.call("Error code: #{code}")
          @data[:vars][:retry][:http] = { :code => code, :message => error_message }
          fail(RetryAttempt::new)
        when :abort
          fail(HttpError::new(code, error_message))
        when :retry
          invoke_callback_method(data[:options][:before_retry_callback],
                                 :routine => self,
                                 :pattern => pattern,
                                 :opts    => opts)
          @data[:vars][:retry][:http] = { :code => code, :message => error_message }
          fail(RetryAttempt::new)
        end
      end
    end
    # The default behavior: this guy hits when there is no any matching pattern
    fail(HttpError::new(code, error_message))
  when /^3..$/
    # In the case of redirect: update a request URI and retry
    location = Array(data[:response][:instance].headers['location']).first
    # ----- AMAZON HACK BEGIN ----------------------------------------------------------
    # Amazon sometimes hide a location host into a response body.
    if location._blank? && body && body[/<Endpoint>(.*?)<\/Endpoint>/] && $1
      data[:connection][:uri].host = $1
      location = data[:connection][:uri].to_s
    end
    # ----- AMAZON HACK END ------------------------------------------------------------
    # Replace URI and retry if the location was successfully set
    unless location._blank?
      data[:connection][:uri] = ::URI.parse(location)
      old_request = data[:request].delete(:instance)
      data[:request].delete(:path)
      cloud_api_logger.log("Redirect detected: #{location.inspect}", :response_analyzer)
      invoke_callback_method(data[:options][:before_redirect_callback],
                             :routine     => self,
                             :old_request => old_request,
                             :location    => location)
      raise(RetryAttempt::new)
    else
      # ----- OPENSTACK BEGIN ----------------------------------------------------------
      # some OS services like Glance returns a list of supported api versions with status 300
      # if there is at least one href in the body we need to further analize it  in the OS manager
      return true if body && body[/href/]
      # ----- OPENSTACK END ----------------------------------------------------------
      raise HttpError::new(code, "Cannot parse a redirect location")
    end
  when /^2../
    # There is nothing to do on 2xx code
    return true
  else
    fail(Error::new("Unexpected response code: #{code.inspect}"))
  end
end