You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by mf...@redhat.com on 2012/02/27 16:48:18 UTC

[PATCH core 1/3] Core: Added XML output for 504 and 502 errors

From: Michal Fojtik <mf...@redhat.com>

The 501, 502 and 504 errors previously has no XML view
associated and thus client cannot report errors correctly.
This patch add these missing HAML templates.

Signed-off-by: Michal fojtik <mf...@redhat.com>
---
 server/lib/deltacloud/base_driver/exceptions.rb    |   16 +++++++
 server/lib/deltacloud/drivers/mock/mock_driver.rb  |   32 +++++++++++++-
 .../lib/deltacloud/helpers/application_helper.rb   |    2 +-
 server/lib/sinatra/lazy_auth.rb                    |    2 +-
 server/views/errors/501.html.haml                  |   43 ++++++++++++++++++++
 server/views/errors/501.xml.haml                   |   12 +++++
 server/views/errors/502.xml.haml                   |   13 ++++--
 server/views/errors/504.html.haml                  |   43 ++++++++++++++++++++
 server/views/errors/504.xml.haml                   |   12 +++++
 9 files changed, 166 insertions(+), 9 deletions(-)
 create mode 100644 server/views/errors/501.html.haml
 create mode 100644 server/views/errors/501.xml.haml
 create mode 100644 server/views/errors/504.html.haml
 create mode 100644 server/views/errors/504.xml.haml

diff --git a/server/lib/deltacloud/base_driver/exceptions.rb b/server/lib/deltacloud/base_driver/exceptions.rb
index 0c66659..e30f94c 100644
--- a/server/lib/deltacloud/base_driver/exceptions.rb
+++ b/server/lib/deltacloud/base_driver/exceptions.rb
@@ -72,6 +72,20 @@ module Deltacloud
       end
     end
 
+    class ProviderTimeout < DeltacloudException
+      def initialize(e, message)
+        message ||= e.message
+        super(504, e.class.name, message, e.backtrace)
+      end
+    end
+
+    class NotImplemented < DeltacloudException
+      def initialize(e, message)
+        message ||= e.message
+        super(501, e.class.name, message, e.backtrace)
+      end
+    end
+
     class ObjectNotFound < DeltacloudException
       def initialize(e, message)
         message ||= e.message
@@ -127,7 +141,9 @@ module Deltacloud
           when 405 then Deltacloud::ExceptionHandler::MethodNotAllowed.new(e, @message)
           when 400 then Deltacloud::ExceptionHandler::ValidationFailure.new(e, @message)
           when 500 then Deltacloud::ExceptionHandler::BackendError.new(e, @message)
+          when 501 then Deltacloud::ExceptionHandler::NotImplemented.new(e, @message)
           when 502 then Deltacloud::ExceptionHandler::ProviderError.new(e, @message)
+          when 504 then Deltacloud::ExceptionHandler::ProviderTimeout.new(e, @message)
         end
       end
 
diff --git a/server/lib/deltacloud/drivers/mock/mock_driver.rb b/server/lib/deltacloud/drivers/mock/mock_driver.rb
index caf14bc..765338d 100644
--- a/server/lib/deltacloud/drivers/mock/mock_driver.rb
+++ b/server/lib/deltacloud/drivers/mock/mock_driver.rb
@@ -100,8 +100,18 @@ module Deltacloud::Drivers::Mock
     end
 
     def realms(credentials, opts=nil)
-      return REALMS if ( opts.nil? )
-      results = REALMS
+      check_credentials( credentials )
+      results = []
+      safely do
+        # This hack is used to test if client capture exceptions correctly
+        # To raise an exception do GET /api/realms/50[0-2]
+        raise "DeltacloudErrorTest" if opts and opts[:id] == "500"
+        raise "NotImplementedTest" if opts and opts[:id] == "501"
+        raise "ProviderErrorTest" if opts and opts[:id] == "502"
+        raise "ProviderTimeoutTest" if opts and opts[:id] == "504"
+        return REALMS if ( opts.nil? )
+        results = REALMS
+      end
       results = filter_on( results, :id, opts )
       results
     end
@@ -521,8 +531,24 @@ module Deltacloud::Drivers::Mock
         message "Could not delete a non existent blob"
       end
 
-      on /Err/ do
+      on /DeltacloudErrorTest/ do
         status 500
+        message "DeltacloudErrorMessage"
+      end
+
+      on /NotImplementedTest/ do
+        status 501
+        message "NotImplementedMessage"
+      end
+
+      on /ProviderErrorTest/ do
+        status 502
+        message "ProviderErrorMessage"
+      end
+
+      on /ProviderTimeoutTest/ do
+        status 504
+        message "ProviderTimeoutMessage"
       end
 
     end
diff --git a/server/lib/deltacloud/helpers/application_helper.rb b/server/lib/deltacloud/helpers/application_helper.rb
index d44f108..4353c4d 100644
--- a/server/lib/deltacloud/helpers/application_helper.rb
+++ b/server/lib/deltacloud/helpers/application_helper.rb
@@ -101,7 +101,7 @@ module ApplicationHelper
         format.json { convert_to_json(model, @element) }
       end
     else
-        report_error(404)
+      report_error(404)
     end
   end
 
diff --git a/server/lib/sinatra/lazy_auth.rb b/server/lib/sinatra/lazy_auth.rb
index ac8f5c7..9556bbc 100644
--- a/server/lib/sinatra/lazy_auth.rb
+++ b/server/lib/sinatra/lazy_auth.rb
@@ -63,7 +63,7 @@ module Sinatra
     def authorize!
       r = "#{driver_symbol}-deltacloud@#{HOSTNAME}"
       response['WWW-Authenticate'] = %(Basic realm="#{r}")
-      throw(:halt, [401, "Not authorized\n"])
+      report_error(401)
     end
 
     # Request the current user's credentials. Actual credentials are only
diff --git a/server/views/errors/501.html.haml b/server/views/errors/501.html.haml
new file mode 100644
index 0000000..19cf090
--- /dev/null
+++ b/server/views/errors/501.html.haml
@@ -0,0 +1,43 @@
+%div{ :'data-role' => :content, :'data-theme' => 'b'}
+  %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'e'}
+    %li{ :'data-role' => 'list-divider'} Server message
+    %li
+      %h3=[@error.class.name, @error.message].join(' - ')
+    %li{ :'data-role' => 'list-divider'} Original request URI
+    %li
+      %a{ :href => request.env['REQUEST_URI'], :'data-ajax' => 'false'}
+        %span=request.env['REQUEST_URI']
+        %span{ :class => 'ui-li-count'} Retry
+    %li{ :'data-role' => 'list-divider'} Error details
+    %li
+      - if @error.class.method_defined? :details
+        %p= @error.details
+      - else
+        %em No details
+
+  %div{ 'data-role' => :collapsible, 'data-collapsed' => "true"}
+    %h3 Backtrace
+    %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'e'}
+      %li
+        %pre=@error.backtrace.join("\n")
+
+  %div{ 'data-role' => :collapsible, 'data-collapsed' => "true"}
+    %h3 Parameters
+    %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'e'}
+      - if params.keys.empty?
+        %li{ :'data-role' => 'list-divider'} No parameters
+      - params.each do |key, value|
+        - next if value.inspect.to_s == '#'
+        %li{ :'data-role' => 'list-divider'}=key
+        %li
+          %span{:style => 'font-weight:normal;'}=value.inspect
+
+
+  %div{ 'data-role' => :collapsible, 'data-collapsed' => "true"}
+    %h3 Request details
+    %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'e'}
+      - request.env.each do |key, value|
+        - next if value.inspect.to_s == '#'
+        %li{ :'data-role' => 'list-divider'}=key
+        %li
+          %span{:style => 'font-weight:normal;'}=value.inspect
diff --git a/server/views/errors/501.xml.haml b/server/views/errors/501.xml.haml
new file mode 100644
index 0000000..788fe4b
--- /dev/null
+++ b/server/views/errors/501.xml.haml
@@ -0,0 +1,12 @@
+%error{:url => "#{request.env['REQUEST_URI']}", :status => "#{response.status}"}
+  %kind backend_error
+  %backend{ :driver => driver_symbol, :provider => "#{Thread::current[:provider] || ENV['API_PROVIDER'] || 'default'}" }
+  - if @error.respond_to?(:details) && @error.details
+    %details< #{cdata @error.details.join("\n")}
+  %message< #{cdata @error.message}
+  - if @error.respond_to? :backtrace
+    %backtrace=cdata @error.backtrace.join("\n")
+  - if params
+    %request
+      - params.each do |k, v|
+        %param{ :name => k}=v
diff --git a/server/views/errors/502.xml.haml b/server/views/errors/502.xml.haml
index 6e7a7b8..788fe4b 100644
--- a/server/views/errors/502.xml.haml
+++ b/server/views/errors/502.xml.haml
@@ -1,7 +1,12 @@
 %error{:url => "#{request.env['REQUEST_URI']}", :status => "#{response.status}"}
   %kind backend_error
-  %backend{ :driver => driver_symbol }
-    %code= @error.code
-    - if @error.respond_to?(:details) && @error.details
-      %details< #{cdata @error.details.join("\n")}
+  %backend{ :driver => driver_symbol, :provider => "#{Thread::current[:provider] || ENV['API_PROVIDER'] || 'default'}" }
+  - if @error.respond_to?(:details) && @error.details
+    %details< #{cdata @error.details.join("\n")}
   %message< #{cdata @error.message}
+  - if @error.respond_to? :backtrace
+    %backtrace=cdata @error.backtrace.join("\n")
+  - if params
+    %request
+      - params.each do |k, v|
+        %param{ :name => k}=v
diff --git a/server/views/errors/504.html.haml b/server/views/errors/504.html.haml
new file mode 100644
index 0000000..19cf090
--- /dev/null
+++ b/server/views/errors/504.html.haml
@@ -0,0 +1,43 @@
+%div{ :'data-role' => :content, :'data-theme' => 'b'}
+  %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'e'}
+    %li{ :'data-role' => 'list-divider'} Server message
+    %li
+      %h3=[@error.class.name, @error.message].join(' - ')
+    %li{ :'data-role' => 'list-divider'} Original request URI
+    %li
+      %a{ :href => request.env['REQUEST_URI'], :'data-ajax' => 'false'}
+        %span=request.env['REQUEST_URI']
+        %span{ :class => 'ui-li-count'} Retry
+    %li{ :'data-role' => 'list-divider'} Error details
+    %li
+      - if @error.class.method_defined? :details
+        %p= @error.details
+      - else
+        %em No details
+
+  %div{ 'data-role' => :collapsible, 'data-collapsed' => "true"}
+    %h3 Backtrace
+    %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'e'}
+      %li
+        %pre=@error.backtrace.join("\n")
+
+  %div{ 'data-role' => :collapsible, 'data-collapsed' => "true"}
+    %h3 Parameters
+    %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'e'}
+      - if params.keys.empty?
+        %li{ :'data-role' => 'list-divider'} No parameters
+      - params.each do |key, value|
+        - next if value.inspect.to_s == '#'
+        %li{ :'data-role' => 'list-divider'}=key
+        %li
+          %span{:style => 'font-weight:normal;'}=value.inspect
+
+
+  %div{ 'data-role' => :collapsible, 'data-collapsed' => "true"}
+    %h3 Request details
+    %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'e'}
+      - request.env.each do |key, value|
+        - next if value.inspect.to_s == '#'
+        %li{ :'data-role' => 'list-divider'}=key
+        %li
+          %span{:style => 'font-weight:normal;'}=value.inspect
diff --git a/server/views/errors/504.xml.haml b/server/views/errors/504.xml.haml
new file mode 100644
index 0000000..788fe4b
--- /dev/null
+++ b/server/views/errors/504.xml.haml
@@ -0,0 +1,12 @@
+%error{:url => "#{request.env['REQUEST_URI']}", :status => "#{response.status}"}
+  %kind backend_error
+  %backend{ :driver => driver_symbol, :provider => "#{Thread::current[:provider] || ENV['API_PROVIDER'] || 'default'}" }
+  - if @error.respond_to?(:details) && @error.details
+    %details< #{cdata @error.details.join("\n")}
+  %message< #{cdata @error.message}
+  - if @error.respond_to? :backtrace
+    %backtrace=cdata @error.backtrace.join("\n")
+  - if params
+    %request
+      - params.each do |k, v|
+        %param{ :name => k}=v
-- 
1.7.9.1


Re: [PATCH core 3/3] Core: Display user-defined exception message

Posted by Michal Fojtik <mf...@redhat.com>.
Michal Fojtik
http://deltacloud.org
mfojtik@redhat.com



On Feb 28, 2012, at 12:32 AM, David Lutterkort wrote:

> On Mon, 2012-02-27 at 16:48 +0100, mfojtik@redhat.com wrote:
>> From: Michal Fojtik <mf...@redhat.com>
>> 
>> The exception handling DSL we're using allow user
>> to define custom exception message using the 'message'
>> directive. This message is then used in XML/HTML view
>> and client can fetch it.
>> However DC logs the original error message to system log.
>> This patch should make DC log user-defined message to log.
>> 
>> Signed-off-by: Michal fojtik <mf...@redhat.com>
> 
> ACK. One comment:
> 
>> diff --git a/server/lib/deltacloud/base_driver/exceptions.rb b/server/lib/deltacloud/base_driver/exceptions.rb
>> index e30f94c..08f2683 100644
>> --- a/server/lib/deltacloud/base_driver/exceptions.rb
>> +++ b/server/lib/deltacloud/base_driver/exceptions.rb
>> @@ -175,9 +175,10 @@ module Deltacloud
>>         report_method = $stderr.respond_to?(:err) ? :err : :puts
>>         Deltacloud::ExceptionHandler::exceptions.each do |exdef|
>>           if exdef.match?($!)
>> -            $stderr.send(report_method, "#{[$!.class.to_s, $!.message].join(':')}\n#{$!.backtrace.join("\n")}")
>>             new_exception = exdef.handler($!)
>> -            raise exdef.handler($!) if new_exception
>> +            m = new_exception.message.nil? ? $1.message : new_exception.message
> 
> That $1 is supposed to be a $!, right ?

Yes, seems like brain overflow. Thanks for spotting that.

  -- Michal


Re: [PATCH core 3/3] Core: Display user-defined exception message

Posted by David Lutterkort <lu...@redhat.com>.
On Mon, 2012-02-27 at 16:48 +0100, mfojtik@redhat.com wrote:
> From: Michal Fojtik <mf...@redhat.com>
> 
> The exception handling DSL we're using allow user
> to define custom exception message using the 'message'
> directive. This message is then used in XML/HTML view
> and client can fetch it.
> However DC logs the original error message to system log.
> This patch should make DC log user-defined message to log.
> 
> Signed-off-by: Michal fojtik <mf...@redhat.com>

ACK. One comment:

> diff --git a/server/lib/deltacloud/base_driver/exceptions.rb b/server/lib/deltacloud/base_driver/exceptions.rb
> index e30f94c..08f2683 100644
> --- a/server/lib/deltacloud/base_driver/exceptions.rb
> +++ b/server/lib/deltacloud/base_driver/exceptions.rb
> @@ -175,9 +175,10 @@ module Deltacloud
>          report_method = $stderr.respond_to?(:err) ? :err : :puts
>          Deltacloud::ExceptionHandler::exceptions.each do |exdef|
>            if exdef.match?($!)
> -            $stderr.send(report_method, "#{[$!.class.to_s, $!.message].join(':')}\n#{$!.backtrace.join("\n")}")
>              new_exception = exdef.handler($!)
> -            raise exdef.handler($!) if new_exception
> +            m = new_exception.message.nil? ? $1.message : new_exception.message

That $1 is supposed to be a $!, right ?

David



[PATCH core 3/3] Core: Display user-defined exception message

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

The exception handling DSL we're using allow user
to define custom exception message using the 'message'
directive. This message is then used in XML/HTML view
and client can fetch it.
However DC logs the original error message to system log.
This patch should make DC log user-defined message to log.

Signed-off-by: Michal fojtik <mf...@redhat.com>
---
 server/lib/deltacloud/base_driver/exceptions.rb |    5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/server/lib/deltacloud/base_driver/exceptions.rb b/server/lib/deltacloud/base_driver/exceptions.rb
index e30f94c..08f2683 100644
--- a/server/lib/deltacloud/base_driver/exceptions.rb
+++ b/server/lib/deltacloud/base_driver/exceptions.rb
@@ -175,9 +175,10 @@ module Deltacloud
         report_method = $stderr.respond_to?(:err) ? :err : :puts
         Deltacloud::ExceptionHandler::exceptions.each do |exdef|
           if exdef.match?($!)
-            $stderr.send(report_method, "#{[$!.class.to_s, $!.message].join(':')}\n#{$!.backtrace.join("\n")}")
             new_exception = exdef.handler($!)
-            raise exdef.handler($!) if new_exception
+            m = new_exception.message.nil? ? $1.message : new_exception.message
+            $stderr.send(report_method, "#{[$!.class.to_s, m].join(':')}\n#{$!.backtrace[0..10].join("\n")}")
+            raise exdef.handler($!) unless new_exception.nil?
           end
         end
         $stderr.send(report_method, "[NO HANDLED] #{[$!.class.to_s, $!.message].join(': ')}\n#{$!.backtrace.join("\n")}")
-- 
1.7.9.1


Re: [PATCH core 1/3] Core: Added XML output for 504 and 502 errors

Posted by David Lutterkort <lu...@redhat.com>.
On Mon, 2012-02-27 at 16:48 +0100, mfojtik@redhat.com wrote:
> From: Michal Fojtik <mf...@redhat.com>
> 
> The 501, 502 and 504 errors previously has no XML view
> associated and thus client cannot report errors correctly.
> This patch add these missing HAML templates.
> 
> Signed-off-by: Michal fojtik <mf...@redhat.com>

ACK



Re: [PATCH core 2/3] Client: Revamped client/server error handling

Posted by David Lutterkort <lu...@redhat.com>.
On Mon, 2012-02-27 at 16:48 +0100, mfojtik@redhat.com wrote:
> From: Michal Fojtik <mf...@redhat.com>
> 
> This patch will various HTTP error exceptions to
> client library. The exception classes are then used
> to properly report server/client exceptions and they
> should help handle various HTTP errors.

ACK, with a couple nits:

> Signed-off-by: Michal fojtik <mf...@redhat.com>
> ---
>  client/lib/deltacloud.rb         |   72 ++++++++------------
>  client/lib/errors.rb             |  140 ++++++++++++++++++++++++++++++++++++++

This is more of a longstanding problem, than something about htis patch:
we are very unclean with the namespace of our client gem; we should
really have everything under lib/deltacloud/ for the client to comply
with Ruby conventions.

>  client/specs/errors_spec.rb      |   59 ++++++++++++++++
>  server/views/errors/500.xml.haml |    2 +
>  server/views/errors/501.xml.haml |   13 +---
>  server/views/errors/502.xml.haml |   13 +---
>  server/views/errors/504.xml.haml |   13 +---

The changes to the error templates for 502 and 504 should really be
folded into patch 1/3; that probably means that the changes to 500 and
501 also need to go into that patch, or into a separate one preceding
1/3.

> diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb
> index 614eab2..a52af1f 100644
> --- a/client/lib/deltacloud.rb
> +++ b/client/lib/deltacloud.rb

> @@ -349,6 +348,28 @@ module DeltaCloud
>        headers
>      end
>  
> +    def response_successfull?(code)

There's a typo in successful: it should only have one l.

David



[PATCH core 2/3] Client: Revamped client/server error handling

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

This patch will various HTTP error exceptions to
client library. The exception classes are then used
to properly report server/client exceptions and they
should help handle various HTTP errors.

Signed-off-by: Michal fojtik <mf...@redhat.com>
---
 client/lib/deltacloud.rb         |   72 ++++++++------------
 client/lib/errors.rb             |  140 ++++++++++++++++++++++++++++++++++++++
 client/specs/errors_spec.rb      |   59 ++++++++++++++++
 server/views/errors/500.xml.haml |    2 +
 server/views/errors/501.xml.haml |   13 +---
 server/views/errors/502.xml.haml |   13 +---
 server/views/errors/504.xml.haml |   13 +---
 7 files changed, 232 insertions(+), 80 deletions(-)
 create mode 100644 client/lib/errors.rb
 create mode 100644 client/specs/errors_spec.rb

diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb
index 614eab2..a52af1f 100644
--- a/client/lib/deltacloud.rb
+++ b/client/lib/deltacloud.rb
@@ -21,6 +21,7 @@ require 'hwp_properties'
 require 'instance_state'
 require 'documentation'
 require 'base_object'
+require 'errors'
 require 'client_bucket_methods'
 
 module DeltaCloud
@@ -314,9 +315,7 @@ module DeltaCloud
 
         request(:post, entry_points[:"#{$1}s"], {}, params) do |response|
           obj = base_object(:"#{$1}", response)
-          # All create calls must respond 201 HTTP code
-          # to indicate that resource was created.
-          handle_backend_error(response) if response.code!=201
+          response_error(response) unless response_successfull?(response.code)
           yield obj if block_given?
         end
         return obj
@@ -349,6 +348,28 @@ module DeltaCloud
       headers
     end
 
+    def response_successfull?(code)
+      return true if code.to_s =~ /^2(\d{2})$/
+      return true if code.to_s =~ /^3(\d{2})$/
+      return false
+    end
+
+    def response_error(response)
+      if response.code.to_s =~ /4(\d{2})/
+        DeltaCloud::HTTPError.client_error(response.code)
+      else
+        xml = Nokogiri::XML(response.to_s)
+        opts = {
+          :driver => (xml/'backend').first[:driver],
+          :provider => (xml/'backend').first[:provider],
+          :params => (xml/'request/param').inject({}) { |r,p| r[:"#{p[:name]}"] = p.text; r }
+        }
+        backtrace = (xml/'backtrace').empty? ? nil : (xml/'backtrace').first.text.split("\n")[1..10].map { |l| l.strip }
+        DeltaCloud::HTTPError.server_error(xml.root[:status] || response.code,
+                                           (xml/'message').first.text, opts, backtrace)
+      end
+    end
+
     # Basic request method
     #
     def request(*args, &block)
@@ -367,55 +388,18 @@ module DeltaCloud
       if conf[:method].eql?(:post)
         resource = RestClient::Resource.new(conf[:path], :open_timeout => conf[:open_timeout], :timeout => conf[:timeout])
         resource.send(:post, conf[:form_data], default_headers.merge(extended_headers)) do |response, request, block|
-          handle_backend_error(response) if [500, 502, 501, 401, 504].include? response.code
-          if response.respond_to?('body')
-            yield response.body if block_given?
-          else
-            yield response.to_s if block_given?
-          end
+          response_error(response) unless response_successfull? response.code
+          yield response.to_s
         end
       else
         resource = RestClient::Resource.new(conf[:path], :open_timeout => conf[:open_timeout], :timeout => conf[:timeout])
         resource.send(conf[:method], default_headers.merge(extended_headers)) do |response, request, block|
-          handle_backend_error(response) if [500, 502, 501, 504, 401].include? response.code
-          if conf[:method].eql?(:get) and [301, 302, 307].include? response.code
-            response.follow_redirection(request) do |response, request, block|
-              if response.respond_to?('body')
-                yield response.body if block_given?
-              else
-                yield response.to_s if block_given?
-              end
-            end
-          else
-            if response.respond_to?('body')
-              yield response.body if block_given?
-            else
-              yield response.to_s if block_given?
-            end
-          end
+          response_error(response) unless response_successfull? response.code
+          yield response.to_s
         end
       end
     end
 
-    # Re-raise backend errors as on exception in client with message from
-    # backend
-    class BackendError < StandardError
-
-      def initialize(opts={})
-        opts[:message] = "Not authorized / Invalid credentials" if opts[:code] == 401
-        super("#{opts[:code]} : #{opts[:message]}")
-        set_backtrace(opts[:backtrace].split("\n").map { |l| l.strip }[0..10]) if opts[:backtrace]
-      end
-
-    end
-
-    def handle_backend_error(response)
-      response_xml = Nokogiri::XML(response)
-      backtrace = (response_xml/'error/backtrace').empty? ? nil : (response_xml/'error/backtrace').text
-      raise BackendError.new(:message => (response_xml/'error/message').text,
-                             :code => response.code,
-                             :backtrace => backtrace)
-    end
 
     # Check if specified collection have wanted feature
     def feature?(collection, name)
diff --git a/client/lib/errors.rb b/client/lib/errors.rb
new file mode 100644
index 0000000..d0eff44
--- /dev/null
+++ b/client/lib/errors.rb
@@ -0,0 +1,140 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.  The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+module DeltaCloud
+  module HTTPError
+
+    class ClientError < StandardError
+
+      attr_reader :params, :driver, :provider
+
+      def initialize(code, message, opts={}, backtrace=nil)
+        @params, @driver, @provider = opts[:params], opts[:driver], opts[:provider]
+        if code.to_s =~ /^5(\d{2})/
+          message += "\nParameters: #{@params.inspect}\n"
+          message += "Driver: #{@driver}@#{@provider}"
+        end
+        super("#{code}\n\n#{self.class.superclass}: #{message}\n\n")
+        # If server provided us the backtrace, then replace client backtrace
+        # with the server one.
+        set_backtrace(backtrace) unless backtrace.nil?
+      end
+    end
+
+    class ServerError < ClientError; end
+    class UknownError < ClientError; end
+
+    # For sake of consistent documentation we need to create
+    # this exceptions manually, instead of using some meta-programming.
+    # Client will really appreciate this it will try to catch some
+    # specific exception.
+
+    # Client errors (4xx)
+    class BadRequest < ClientError; end
+    class Unauthorized < ClientError; end
+    class Forbidden < ClientError; end
+    class NotFound < ClientError; end
+    class MethodNotAllowed < ClientError; end
+    class NotAcceptable < ClientError; end
+    class RequestTimeout < ClientError; end
+    class Gone < ClientError; end
+    class ExpectationFailed < ClientError; end
+    class UnsupportedMediaType < ClientError; end
+
+    # Server errors (5xx)
+    class DeltacloudError < ServerError; end
+    class ProviderError < ServerError; end
+    class ProviderTimeout < ServerError; end
+    class ServiceUnavailable < ServerError; end
+    class NotImplemented < ServerError; end
+
+    class ExceptionHandler
+
+      attr_reader :http_status_code, :message, :trace
+
+      def initialize(status_code, message=nil, opts={}, backtrace=nil, &block)
+        @http_status_code = status_code.to_i
+        @trace = backtrace
+        @message = message || client_error_messages[status_code] || 'No error message received'
+        @options = opts
+        instance_eval(&block) if block_given?
+      end
+
+      def on(code, exception_class)
+        if code == @http_status_code
+          raise exception_class.new(code, @message, @options, @trace)
+        end
+      end
+
+      private
+
+      def client_error_messages
+        {
+          400 => 'The request could not be understood by the server due to malformed syntax.',
+          401 => 'Authentication required for this request or invalid credentials provided.',
+          403 => 'Requested operation is not allowed for this resource.',
+          404 => 'Not Found',
+          405 => 'Method not allowed for this resource.',
+          406 => 'Requested media type is not supported by server.',
+          408 => 'The client did not produce a request within the time that the server was prepared to wait.',
+          410 => 'The resource is no longer available'
+        }
+      end
+
+    end
+
+    def self.parse_response_error(response)
+    
+    end
+
+    def self.client_error(code)
+      ExceptionHandler.new(code) do
+        # Client errors
+        on 400, BadRequest
+        on 401, Unauthorized
+        on 403, Forbidden
+        on 404, NotFound
+        on 405, MethodNotAllowed
+        on 406, NotAcceptable
+        on 408, RequestTimeout
+        on 410, Gone
+      end
+    end
+
+    def self.server_error(code, message, opts={}, backtrace=nil)
+      ExceptionHandler.new(code, message, opts, backtrace) do
+        # Client errors
+        on 400, BadRequest
+        on 401, Unauthorized
+        on 403, Forbidden
+        on 404, NotFound
+        on 405, MethodNotAllowed
+        on 406, NotAcceptable
+        on 408, RequestTimeout
+        on 410, Gone
+        on 415, UnsupportedMediaType
+        on 417, ExpectationFailed
+        # Server errors
+        on 500, DeltacloudError
+        on 501, NotImplemented
+        on 502, ProviderError
+        on 503, ServiceUnavailable
+        on 504, ProviderTimeout
+      end
+      raise Deltacloud::HTTPError::UnknownError.new(code, message, opts, backtrace)
+    end
+
+  end
+end
diff --git a/client/specs/errors_spec.rb b/client/specs/errors_spec.rb
new file mode 100644
index 0000000..031e3b7
--- /dev/null
+++ b/client/specs/errors_spec.rb
@@ -0,0 +1,59 @@
+#
+# Copyright (C) 2009-2011  Red Hat, Inc.
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.  The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+require 'specs/spec_helper'
+
+describe "server error handler" do
+
+  it_should_behave_like "all resources"
+
+  it 'should capture HTTP 500 error as DeltacloudError' do
+    DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client|
+      expect { client.realm('500') }.should raise_error(DeltaCloud::HTTPError::DeltacloudError)
+    end
+  end
+
+  it 'should capture HTTP 502 error as ProviderError' do
+    DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client|
+      expect { client.realm('502') }.should raise_error(DeltaCloud::HTTPError::ProviderError)
+    end
+  end
+
+  it 'should capture HTTP 501 error as NotImplemented' do
+    DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client|
+      expect { client.realm('501') }.should raise_error(DeltaCloud::HTTPError::NotImplemented)
+    end
+  end
+
+  it 'should capture HTTP 504 error as ProviderTimeout' do
+    DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client|
+      expect { client.realm('504') }.should raise_error(DeltaCloud::HTTPError::ProviderTimeout)
+    end
+  end
+
+end
+
+describe "client error handler" do
+
+  it 'should capture HTTP 404 error as NotFound' do
+    DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client|
+      expect { client.realm('non-existing-realm') }.should raise_error(DeltaCloud::HTTPError::NotFound)
+    end
+  end
+
+end
diff --git a/server/views/errors/500.xml.haml b/server/views/errors/500.xml.haml
index 5c111e8..0b2d14a 100644
--- a/server/views/errors/500.xml.haml
+++ b/server/views/errors/500.xml.haml
@@ -1,3 +1,5 @@
+- unless defined?(partial)
+  !!! XML
 %error{:url => "#{request.env['REQUEST_URI']}", :status => "#{response.status}"}
   %kind backend_error
   %backend{ :driver => driver_symbol, :provider => "#{Thread::current[:provider] || ENV['API_PROVIDER'] || 'default'}" }
diff --git a/server/views/errors/501.xml.haml b/server/views/errors/501.xml.haml
index 788fe4b..a4015a4 100644
--- a/server/views/errors/501.xml.haml
+++ b/server/views/errors/501.xml.haml
@@ -1,12 +1 @@
-%error{:url => "#{request.env['REQUEST_URI']}", :status => "#{response.status}"}
-  %kind backend_error
-  %backend{ :driver => driver_symbol, :provider => "#{Thread::current[:provider] || ENV['API_PROVIDER'] || 'default'}" }
-  - if @error.respond_to?(:details) && @error.details
-    %details< #{cdata @error.details.join("\n")}
-  %message< #{cdata @error.message}
-  - if @error.respond_to? :backtrace
-    %backtrace=cdata @error.backtrace.join("\n")
-  - if params
-    %request
-      - params.each do |k, v|
-        %param{ :name => k}=v
+= haml :'errors/500', :locals => { :@error => @error, :partial => true }
diff --git a/server/views/errors/502.xml.haml b/server/views/errors/502.xml.haml
index 788fe4b..a4015a4 100644
--- a/server/views/errors/502.xml.haml
+++ b/server/views/errors/502.xml.haml
@@ -1,12 +1 @@
-%error{:url => "#{request.env['REQUEST_URI']}", :status => "#{response.status}"}
-  %kind backend_error
-  %backend{ :driver => driver_symbol, :provider => "#{Thread::current[:provider] || ENV['API_PROVIDER'] || 'default'}" }
-  - if @error.respond_to?(:details) && @error.details
-    %details< #{cdata @error.details.join("\n")}
-  %message< #{cdata @error.message}
-  - if @error.respond_to? :backtrace
-    %backtrace=cdata @error.backtrace.join("\n")
-  - if params
-    %request
-      - params.each do |k, v|
-        %param{ :name => k}=v
+= haml :'errors/500', :locals => { :@error => @error, :partial => true }
diff --git a/server/views/errors/504.xml.haml b/server/views/errors/504.xml.haml
index 788fe4b..a4015a4 100644
--- a/server/views/errors/504.xml.haml
+++ b/server/views/errors/504.xml.haml
@@ -1,12 +1 @@
-%error{:url => "#{request.env['REQUEST_URI']}", :status => "#{response.status}"}
-  %kind backend_error
-  %backend{ :driver => driver_symbol, :provider => "#{Thread::current[:provider] || ENV['API_PROVIDER'] || 'default'}" }
-  - if @error.respond_to?(:details) && @error.details
-    %details< #{cdata @error.details.join("\n")}
-  %message< #{cdata @error.message}
-  - if @error.respond_to? :backtrace
-    %backtrace=cdata @error.backtrace.join("\n")
-  - if params
-    %request
-      - params.each do |k, v|
-        %param{ :name => k}=v
+= haml :'errors/500', :locals => { :@error => @error, :partial => true }
-- 
1.7.9.1