From: Michal Fojtik <[email protected]>
Signed-off-by: Michal fojtik <[email protected]> --- client/lib/deltacloud.rb | 67 +++++++++----------------- client/lib/errors.rb | 111 +++++++++++++++++++++++++++++++++++++++++++ client/specs/errors_spec.rb | 59 +++++++++++++++++++++++ 3 files changed, 193 insertions(+), 44 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..97722de 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,23 @@ 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) + xml = Nokogiri::XML(response.to_s) + opts = response.code.to_s =~ /^5(\d{2})$/ ? { + :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.raise_error(xml.root[:status] || response.code, (xml/'message').first.text, opts, backtrace) + end + # Basic request method # def request(*args, &block) @@ -367,55 +383,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..60d59e1 --- /dev/null +++ b/client/lib/errors.rb @@ -0,0 +1,111 @@ +# 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, opts={}, backtrace=nil, &block) + @http_status_code = status_code.to_i + @trace = backtrace + @message = message + @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 + + end + + def self.parse_response_error(response) + + end + + def self.raise_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 -- 1.7.4.4
