diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index dfce18ef0a360cfc0eceee22b116163cbc77f436..2a4085309faf71b2e805cd1af253ea46901574e1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -19,24 +19,41 @@ class ApplicationController < ActionController::Base before_filter :parse_json_payload, :only => [:create, :update, :destroy] before_filter :set_default_format + # abasu : additional classes introduced to handle all possible exceptions - 02.04.2015 + # abasu : as per status codes https://api.grid5000.fr/doc/stable/reference/spec.html + # abasu : class & subclasses to handle client-side exceptions (Error codes 4xx) class ClientError < ActionController::ActionControllerError; end + class BadRequest < ClientError; end # Error code 400 + class AuthorizationRequired < ClientError; end # Error code 401 + class Forbidden < ClientError; end # Error code 403 + class NotFound < ClientError; end # Error code 404 + class MethodNotAllowed < ClientError; end # Error code 405 + class NotAcceptable < ClientError; end # Error code 406 + class PreconditionFailed < ClientError; end # Error code 412 + + # abasu : class & subclasses to handle server-side exceptions (Error codes 5xx) class ServerError < ActionController::ActionControllerError; end - class UnsupportedMediaType < ClientError; end - class BadRequest < ClientError; end - class Forbidden < ClientError; end - class NotFound < ClientError; end - class BadGateway < ServerError; end + class UnsupportedMediaType < ServerError; end # Error code 415 (moved to server-side) + class BadGateway < ServerError; end # Error code 50x (to be refined later) # This thing must alway come first, or it will override other rescue_from. rescue_from Exception, :with => :server_error - rescue_from UnsupportedMediaType, :with => :unsupported_media_type - rescue_from BadRequest, :with => :bad_request - rescue_from BadGateway, :with => :bad_gateway - rescue_from Forbidden, :with => :forbidden - rescue_from NotFound, :with => :not_found - rescue_from ActiveRecord::RecordNotFound, :with => :not_found - rescue_from ServerError, :with => :server_error + # abasu : exception-handlers for client-side exceptions - 02.04.2015 + rescue_from BadRequest, :with => :bad_request # for 400 + rescue_from AuthorizationRequired, :with => :authorization_required # for 401 + rescue_from Forbidden, :with => :forbidden # for 403 + rescue_from NotFound, :with => :not_found # for 404 + rescue_from ActiveRecord::RecordNotFound, :with => :not_found # for 404 + rescue_from MethodNotAllowed, :with => :method_not_allowed # for 405 + rescue_from NotAcceptable, :with => :not_acceptable # for 406 + rescue_from PreconditionFailed, :with => :precondition_failed # for 412 + + # abasu : exception-handlers for client-side exceptions - 02.04.2015 + rescue_from UnsupportedMediaType, :with => :server_error # for 415 + # abasu : agreed to send exception to server_error (instead of unsupported_media_type) + rescue_from ServerError, :with => :server_error # for 500 + rescue_from BadGateway, :with => :bad_gateway # for 502 protected def set_default_format @@ -75,21 +92,43 @@ class ApplicationController < ActionController::Base # Analyses the response status of the given HTTP response. # # Raise BadGateway if status is 0. - # Raise ServerError if status is not in the expected status codes given via options[:is]. + # Raise ServerError if status is not in the expected status codes in options[:is] . def continue_if!(http, options = {}) + # Allow the list of "non-error" http codes allowed_status = [options[:is] || (200..299).to_a].flatten - status = http.response_header.status - case status - when *allowed_status - true - when 0 - raise BadGateway - else + + status = http.response_header.status # get the status from the http response + + if status.between?(400, 599) # error status # http.method always returns nil. Bug? # msg = "#{http.method} #{http.uri} failed with status #{status}" msg = "Request to #{http.uri.to_s} failed with status #{status}: #{http.response}" Rails.logger.error msg - raise ServerError, msg + end + + case status + when *allowed_status # Status codes (200, ..., 299) + true + when 400 + raise BadRequest, msg + when 401 + raise AuthorizationRequired, msg + when 403 + raise Forbidden, msg + when 404 + raise NotFound, msg + when 405 + raise MethodNotAllowed, msg + when 406 + raise NotAcceptable, msg + when 412 + raise PreconditionFailed, msg + when 415 + raise UnsupportedMediaType, msg + when 502 + raise BadGateway, msg + else + raise ServerError, msg end end @@ -119,29 +158,67 @@ class ApplicationController < ActionController::Base # =============== # = HTTP Errors = # =============== - def unsupported_media_type(exception) - render_error(exception, :status => 415) + # abasu : Most of the new methods added are just stubs for introduced for + # abasu : the sake of completeness of HTTP error codes handling. + # abasu : If such error conditions become prominent in the the future, + # abasu : they should be overloaded in subclasses. + def bad_request(exception) + opts = {:status => 400} + opts[:message] = "Bad Request" if exception.message == exception.class.name + render_error(exception, opts) end - def bad_request(exception) - render_error(exception, :status => 400) + def authorization_required(exception) + opts = {:status => 401} + opts[:message] = "Authorization Required" if exception.message == exception.class.name + render_error(exception, opts) + end + + def forbidden(exception) + opts = {:status => 403} + opts[:message] = "You are not authorized to access this resource" if exception.message == exception.class.name + render_error(exception, opts) end def not_found(exception) - render_error(exception, :status => 404) + opts = {:status => 404} + opts[:message] = "Not Found" if exception.message == exception.class.name + render_error(exception, opts) end - def bad_gateway(exception) - render_error(exception, :status => 502) + def method_not_allowed(exception) + opts = {:status => 405} + opts[:message] = "Method Not Allowed" if exception.message == exception.class.name + render_error(exception, opts) + end + + def not_acceptable(exception) + opts = {:status => 406} + opts[:message] = "Not Acceptable" if exception.message == exception.class.name + render_error(exception, opts) + end + + def precondition_failed(exception) + opts = {:status => 412} + opts[:message] = "Precondition Failed" if exception.message == exception.class.name + render_error(exception, opts) + end + + def unsupported_media_type(exception) + opts = {:status => 415} + opts[:message] = "Unsupported Media Type" if exception.message == exception.class.name + render_error(exception, opts) end def server_error(exception) - render_error(exception, :status => 500) + opts = {:status => 500} + opts[:message] = "Internal Server Error" if exception.message == exception.class.name + render_error(exception, opts) end - def forbidden(exception) - opts = {:status => 403} - opts[:message] = "You are not authorized to access this resource" if exception.message == exception.class.name + def bad_gateway(exception) + opts = {:status => 502} + opts[:message] = "Bad Gateway" if exception.message == exception.class.name render_error(exception, opts) end diff --git a/spec/controllers/jobs_controller_spec.rb b/spec/controllers/jobs_controller_spec.rb index 20d87be06c9797922f90022b00528ed024540c07..daba60c0291fd36dc6f31cc37c12ea77fca72c0b 100644 --- a/spec/controllers/jobs_controller_spec.rb +++ b/spec/controllers/jobs_controller_spec.rb @@ -121,10 +121,59 @@ describe JobsController do ) post :create, :site_id => "rennes", :format => :json - response.status.should == 500 + response.status.should == 400 response.body.should == "Request to #{expected_url} failed with status 400: some error" end + # abasu : unit test for bug ref 5912 to handle error codes - 02.04.2015 + it "should return a 400 error if the OAR API returns 400 error code" do + payload = @valid_job_attributes.merge("resources" => "{ib30g='YES'}/nodes=2") + authenticate_as("crohr") + send_payload(payload, :json) + expected_url = "http://api-out.local:80/sites/rennes/internal/oarapi/jobs.json" + stub_request(:post, expected_url). + with( + :headers => { + 'Accept' => media_type(:json), + 'Content-Type' => media_type(:json), + 'X-Remote-Ident' => "crohr" + }, + :body => Grid5000::Job.new(payload).to_hash(:destination => "oar-2.4-submission").to_json + ). + to_return( + :status => 400, + :body => "Bad Request" + ) + + post :create, :site_id => "rennes", :format => :json + response.status.should == 400 + response.body.should == "Request to #{expected_url} failed with status 400: Bad Request" + end # "should return a 400 error if the OAR API returns 400 error code" + # abasu : unit test for bug ref 5912 to handle error codes - 02.04.2015 + it "should return a 401 error if the OAR API returns 401 error code" do + payload = @valid_job_attributes + authenticate_as("xyz") + send_payload(payload, :json) + + expected_url = "http://api-out.local:80/sites/rennes/internal/oarapi/jobs.json" + stub_request(:post, expected_url). + with( + :headers => { + 'Accept' => media_type(:json), + 'Content-Type' => media_type(:json), + 'X-Remote-Ident' => "xyz" + }, + :body => Grid5000::Job.new(payload).to_hash(:destination => "oar-2.4-submission").to_json + ). + to_return( + :status => 401, + :body => "Authorization Required" + ) + + post :create, :site_id => "rennes", :format => :json + response.status.should == 401 + response.body.should == "Request to #{expected_url} failed with status 401: Authorization Required" + end # "should return a 401 error if the OAR API returns 400 error code" it "should return 201, the job details, and the Location header" do payload = @valid_job_attributes authenticate_as("crohr") @@ -212,7 +261,7 @@ describe JobsController do ) delete :destroy, :site_id => "rennes", :id => @job.uid, :format => :json - response.status.should == 500 + response.status.should == 400 response.body.should == "Request to #{@expected_url} failed with status 400: some error" end