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