diff --git a/AutoRest/Generators/Ruby/Azure.Ruby/Templates/AzureMethodTemplate.cshtml b/AutoRest/Generators/Ruby/Azure.Ruby/Templates/AzureMethodTemplate.cshtml index 55a987c34f994..ce6484d4ba299 100644 --- a/AutoRest/Generators/Ruby/Azure.Ruby/Templates/AzureMethodTemplate.cshtml +++ b/AutoRest/Generators/Ruby/Azure.Ruby/Templates/AzureMethodTemplate.cshtml @@ -42,7 +42,7 @@ def @(Model.Name)(@(Model.MethodParameterDeclaration)) @EmptyLine # Waiting for response. - @(Model.ClientReference).get_post_or_delete_operation_result(response, deserialize_method) + @(Model.ClientReference).get_long_running_operation_result(response, deserialize_method) end @EmptyLine @@ -84,7 +84,7 @@ def @(Model.Name)(@(Model.MethodParameterDeclaration)) @EmptyLine # Waiting for response. - @(Model.ClientReference).get_put_operation_result(response, deserialize_method) + @(Model.ClientReference).get_long_running_operation_result(response, deserialize_method) end @EmptyLine diff --git a/ClientRuntimes/Ruby/ms-rest-azure/lib/ms_rest_azure/azure_service_client.rb b/ClientRuntimes/Ruby/ms-rest-azure/lib/ms_rest_azure/azure_service_client.rb index d80f556413c66..50f2da8a48b30 100644 --- a/ClientRuntimes/Ruby/ms-rest-azure/lib/ms_rest_azure/azure_service_client.rb +++ b/ClientRuntimes/Ruby/ms-rest-azure/lib/ms_rest_azure/azure_service_client.rb @@ -15,36 +15,35 @@ class AzureServiceClient < MsRest::ServiceClient attr_accessor :api_version # - # Retrieves the result of 'PUT' operation. Perfroms polling of required. + # Retrieves the result of 'POST','DELETE','PUT' or 'PATCH' operation. Performs polling of required. # @param azure_response [MsRestAzure::AzureOperationResponse] response from Azure service. # @param custom_deserialization_block [Proc] custom logic for response deserialization. # # @return [MsRest::HttpOperationResponse] the response. - def get_put_operation_result(azure_response, custom_deserialization_block) - fail MsRest::ValidationError, 'Azure response cannot be nil' if azure_response.nil? + def get_long_running_operation_result(azure_response, custom_deserialization_block) + check_for_status_code_failure(azure_response) - status_code = azure_response.response.status - - if (status_code != 200 && status_code != 201 && status_code != 202) - fail AzureOperationError, "Unexpected polling status code from long running operation #{status_code}" - end + http_method = azure_response.request.method polling_state = PollingState.new(azure_response, @long_running_operation_retry_timeout) request = azure_response.request - if (!AsyncOperationStatus.is_terminal_status(polling_state.status)) + if !AsyncOperationStatus.is_terminal_status(polling_state.status) task = Concurrent::TimerTask.new do begin if !polling_state.azure_async_operation_header_link.nil? update_state_from_azure_async_operation_header(polling_state.get_request(headers: request.headers, base_uri: request.base_uri), polling_state) elsif !polling_state.location_header_link.nil? - update_state_from_location_header_on_put(polling_state.get_request(headers: request.headers, base_uri: request.base_uri), polling_state, custom_deserialization_block) - else + update_state_from_location_header(polling_state.get_request(headers: request.headers, base_uri: request.base_uri), polling_state, custom_deserialization_block) + elsif http_method === :put get_request = MsRest::HttpOperationRequest.new(request.base_uri, request.build_path.to_s, 'get', query_params: request.query_params) update_state_from_get_resource_operation(get_request, polling_state, custom_deserialization_block) + else + task.shutdown + fail AzureOperationError, 'Location header is missing from long running operation' end - if (AsyncOperationStatus.is_terminal_status(polling_state.status)) + if AsyncOperationStatus.is_terminal_status(polling_state.status) task.shutdown end rescue Exception => e @@ -64,74 +63,52 @@ def get_put_operation_result(azure_response, custom_deserialization_block) fail polling_error if polling_error.is_a?(Exception) end - if (AsyncOperationStatus.is_successful_status(polling_state.status) && polling_state.resource.nil?) + if (http_method === :put || http_method === :patch) && AsyncOperationStatus.is_successful_status(polling_state.status) && polling_state.resource.nil? get_request = MsRest::HttpOperationRequest.new(request.base_uri, request.build_path.to_s, 'get', query_params: request.query_params) update_state_from_get_resource_operation(get_request, polling_state, custom_deserialization_block) end - if (AsyncOperationStatus.is_failed_status(polling_state.status)) + if AsyncOperationStatus.is_failed_status(polling_state.status) fail polling_state.get_operation_error end - return polling_state.get_operation_response + polling_state.get_operation_response + end + + # @TODO Update name of the method to get_put_or_patch_operation_result when introducing breaking change / updating major version of gem + # Retrieves the result of 'PUT' or 'PATCH' operation. Performs polling of required. + # @param azure_response [MsRestAzure::AzureOperationResponse] response from Azure service. + # @param custom_deserialization_block [Proc] custom logic for response deserialization. + # + # @return [MsRest::HttpOperationResponse] the response. + def get_put_operation_result(azure_response, custom_deserialization_block) + get_long_running_operation_result(azure_response, custom_deserialization_block) end # - # Retrieves the result of 'POST' or 'DELETE' operations. Perfroms polling of required. + # Retrieves the result of 'POST' or 'DELETE' operations. Performs polling of required. # @param azure_response [MsRestAzure::AzureOperationResponse] response from Azure service. # @param custom_deserialization_block [Proc] custom logic for response deserialization. # # @return [MsRest::HttpOperationResponse] the response. def get_post_or_delete_operation_result(azure_response, custom_deserialization_block) + get_long_running_operation_result(azure_response, custom_deserialization_block) + end + + # + # Verifies for unexpected polling status code + # @param azure_response [MsRestAzure::AzureOperationResponse] response from Azure service. + def check_for_status_code_failure(azure_response) fail MsRest::ValidationError, 'Azure response cannot be nil' if azure_response.nil? fail MsRest::ValidationError, 'Azure response cannot have empty response object' if azure_response.response.nil? + fail MsRest::ValidationError, 'Azure response cannot have empty request object' if azure_response.request.nil? status_code = azure_response.response.status + http_method = azure_response.request.method - if (status_code != 200 && status_code != 202 && status_code != 204) - fail AzureOperationError, "Unexpected polling status code from long running operation #{status_code}" - end - - polling_state = PollingState.new(azure_response, @long_running_operation_retry_timeout) - request = azure_response.request - - if (!AsyncOperationStatus.is_terminal_status(polling_state.status)) - task = Concurrent::TimerTask.new do - begin - if !polling_state.azure_async_operation_header_link.nil? - update_state_from_azure_async_operation_header(polling_state.get_request(headers: request.headers, base_uri: request.base_uri), polling_state) - elsif !polling_state.location_header_link.nil? - update_state_from_location_header_on_post_or_delete(polling_state.get_request(headers: request.headers, base_uri: request.base_uri), polling_state, custom_deserialization_block) - else - task.shutdown - fail AzureOperationError, 'Location header is missing from long running operation' - end - - if (AsyncOperationStatus.is_terminal_status(polling_state.status)) - task.shutdown - end - rescue Exception => e - task.shutdown - e - end - end - - polling_delay = polling_state.get_delay - polling_delay = 0.1 if polling_delay.nil? || polling_delay == 0 - - task.execution_interval = polling_delay - task.execute - task.wait_for_termination - - polling_error = task.value - fail polling_error if polling_error.is_a?(Exception) - end - - if (AsyncOperationStatus.is_failed_status(polling_state.status)) - fail polling_state.get_operation_error - end - - return polling_state.get_operation_response + fail AzureOperationError, "Unexpected polling status code from long running operation #{status_code}" unless status_code === 200 || status_code === 202 || + (status_code === 201 && http_method === :put) || + (status_code === 204 && (http_method === :delete || http_method === :post)) end # @@ -145,7 +122,7 @@ def update_state_from_get_resource_operation(request, polling_state, custom_dese fail AzureOperationError, 'The response from long running operation does not contain a body' if result.response.body.nil? || result.response.body.empty? - if (result.body.respond_to?(:properties) && result.body.properties.respond_to?(:provisioning_state) && !result.body.properties.provisioning_state.nil?) + if result.body.respond_to?(:properties) && result.body.properties.respond_to?(:provisioning_state) && !result.body.properties.provisioning_state.nil? polling_state.status = result.body.properties.provisioning_state else polling_state.status = AsyncOperationStatus::SUCCESS_STATUS @@ -162,30 +139,23 @@ def update_state_from_get_resource_operation(request, polling_state, custom_dese end # - # Updates polling state based on location header for PUT HTTP requests. + # Updates polling state based on location header for HTTP requests. # @param request [MsRest::HttpOperationRequest] The url retrieve data from. # @param polling_state [MsRestAzure::PollingState] polling state to update. # @param custom_deserialization_block [Proc] custom deserialization method for parsing response. - def update_state_from_location_header_on_put(request, polling_state, custom_deserialization_block) + def update_state_from_location_header(request, polling_state, custom_deserialization_block) result = get_async_with_custom_deserialization(request, custom_deserialization_block) polling_state.update_response(result.response) - polling_state.request = result.response - + polling_state.request = result.request status_code = result.response.status + http_method = request.method - if (status_code == 202) + if status_code === 202 polling_state.status = AsyncOperationStatus::IN_PROGRESS_STATUS - elsif (status_code == 200 || status_code == 201) - fail AzureOperationError, 'The response from long running operation does not contain a body' if result.body.nil? - - # In 202 pattern on PUT ProvisioningState may not be present in - # the response. In that case the assumption is the status is Succeeded. - if (result.body.respond_to?(:properties) && result.body.properties.respond_to?(:provisioning_state) && !result.body.properties.provisioning_state.nil?) - polling_state.status = result.body.properties.provisioning_state - else - polling_state.status = AsyncOperationStatus::SUCCESS_STATUS - end + elsif status_code === 200 || (status_code === 201 && http_method === :put) || + (status_code === 204 && (http_method === :delete || http_method === :post)) + polling_state.status = AsyncOperationStatus::SUCCESS_STATUS error_data = CloudErrorData.new error_data.code = polling_state.status @@ -193,6 +163,8 @@ def update_state_from_location_header_on_put(request, polling_state, custom_dese polling_state.error_data = error_data polling_state.resource = result.body + else + fail AzureOperationError, 'The response from long running operation does not have a valid status code' end end @@ -212,26 +184,7 @@ def update_state_from_azure_async_operation_header(request, polling_state) end # - # Updates polling state based on location header for POST and DELETE HTTP requests. - # @param polling_state [MsRest::HttpOperationRequest] [description] - # @param custom_deserialization_block [Proc] custom deserialization method for parsing response. - def update_state_from_location_header_on_post_or_delete(request, polling_state, custom_deserialization_block) - result = get_async_with_custom_deserialization(request, custom_deserialization_block) - - polling_state.update_response(result.response) - polling_state.request = result.request - status_code = result.response.status - - if (status_code == 202) - polling_state.status = AsyncOperationStatus::IN_PROGRESS_STATUS - elsif (status_code == 200 || status_code == 201 || status_code == 204) - polling_state.status = AsyncOperationStatus::SUCCESS_STATUS - polling_state.resource = result.body - end - end - - # - # Retrives data by given URL. + # Retrieves data by given URL. # @param request [MsRest::HttpOperationRequest] the URL. # @param custom_deserialization_block [Proc] function to perform deserialization of the HTTP response. # @@ -239,7 +192,7 @@ def update_state_from_location_header_on_post_or_delete(request, polling_state, def get_async_with_custom_deserialization(request, custom_deserialization_block) result = get_async_common(request) - if (!result.body.nil? && !custom_deserialization_block.nil?) + if !result.body.nil? && !custom_deserialization_block.nil? begin result.body = custom_deserialization_block.call(result.body) rescue Exception => e @@ -251,7 +204,7 @@ def get_async_with_custom_deserialization(request, custom_deserialization_block) end # - # Retrives data by given URL. + # Retrieves data by given URL. # @param request [MsRest::HttpOperationRequest] the URL. # # @return [MsRest::HttpOperationResponse] the response. @@ -263,13 +216,13 @@ def get_async_with_async_operation_deserialization(request) end # - # Retrives data by given URL. + # Retrieves data by given URL. # @param request [MsRest::HttpOperationRequest] the URL. # # @return [MsRest::HttpOperationResponse] the response. def get_async_common(request) fail ValidationError, 'Request cannot be nil' if request.nil? - + request.middlewares = [[MsRest::RetryPolicyMiddleware, times: 3, retry: 0.02], [:cookie_jar]] request.headers.merge!({'x-ms-client-request-id' => SecureRandom.uuid, 'Content-Type' => 'application/json'}) @@ -277,10 +230,10 @@ def get_async_common(request) http_response = request.run_promise do |req| @credentials.sign_request(req) unless @credentials.nil? end.execute.value! - + status_code = http_response.status - if (status_code != 200 && status_code != 201 && status_code != 202 && status_code != 204) + if status_code != 200 && status_code != 201 && status_code != 202 && status_code != 204 json_error_data = JSON.load(http_response.body) error_data = CloudErrorData.deserialize_object(json_error_data) diff --git a/ClientRuntimes/Ruby/ms-rest-azure/spec/azure_service_client_spec.rb b/ClientRuntimes/Ruby/ms-rest-azure/spec/azure_service_client_spec.rb index 18fec9a77b12d..e3c0eee6afe5f 100644 --- a/ClientRuntimes/Ruby/ms-rest-azure/spec/azure_service_client_spec.rb +++ b/ClientRuntimes/Ruby/ms-rest-azure/spec/azure_service_client_spec.rb @@ -9,25 +9,30 @@ module MsRestAzure describe AzureServiceClient do + before(:all) do + @methods = ['put', 'post', 'delete', 'patch'] + end + it 'should throw error in case provided azure response is nil' do azure_service_client = AzureServiceClient.new nil - expect { azure_service_client.get_put_operation_result(nil, nil) }.to raise_error(MsRest::ValidationError) + expect { azure_service_client.get_long_running_operation_result(nil, nil) }.to raise_error(MsRest::ValidationError) end it 'should throw error if unexpected polling state is passed' do azure_service_client = AzureServiceClient.new nil response = double('response', :status => 404) + request = double('request', headers: {}, base_uri: '', method: @methods[0]) azure_response = double('azure_response', - :request => nil, + :request => request, :response => response, :body => nil) - expect { azure_service_client.get_put_operation_result(azure_response, nil) }.to raise_error(AzureOperationError) + expect { azure_service_client.get_long_running_operation_result(azure_response, nil) }.to raise_error(AzureOperationError) end - it 'should use async operation header for getting PUT result' do + it 'should use async operation header when async_operation_header present' do azure_service_client = AzureServiceClient.new nil azure_service_client.long_running_operation_retry_timeout = 0 @@ -36,46 +41,43 @@ module MsRestAzure polling_state.resource = 'resource' end - response = double('response', headers: - { 'Azure-AsyncOperation' => 'async_operation_header', - 'Location' => 'location_header'}, + response = double('response', + :headers => + { 'Azure-AsyncOperation' => 'async_operation_header', + 'Location' => 'location_header'}, :status => 202) - - request = double('request', headers: {}, base_uri: '') - - azure_response = double('azure_response', - :request => request, - :response => response, - :body => nil) - expect(azure_service_client).to receive(:update_state_from_azure_async_operation_header) - azure_service_client.get_put_operation_result(azure_response, nil) + @methods.each do |method| + request = double('request', headers: {}, base_uri: '', method: method) + azure_response = double('azure_response', + :request => request, + :response => response, + :body => nil) + azure_service_client.get_long_running_operation_result(azure_response, nil) + end end - it 'should use location operation header for getting PUT result' do + it 'should use location operation header when location_header present' do azure_service_client = AzureServiceClient.new nil azure_service_client.long_running_operation_retry_timeout = 0 - allow(azure_service_client).to receive(:update_state_from_location_header_on_put) do |request, polling_state| + allow(azure_service_client).to receive(:update_state_from_location_header) do |request, polling_state| polling_state.status = AsyncOperationStatus::SUCCESS_STATUS polling_state.resource = 'resource' end - response = double('response', :headers => - { 'Location' => 'location_header'}, - :status => 202) - - request = double('request', headers: {}, base_uri: '') + response = double('response', :headers => { 'Location' => 'location_header'}, :status => 202) + expect(azure_service_client).to receive(:update_state_from_location_header) - azure_response = double('azure_response', - :request => request, - :response => response, - :body => nil) - - expect(azure_service_client).to receive(:update_state_from_location_header_on_put) - - azure_service_client.get_put_operation_result(azure_response, nil) + @methods.each do |method| + request = double('request', headers: {}, base_uri: '', method: method) + azure_response = double('azure_response', + :request => request, + :response => response, + :body => nil) + azure_service_client.get_long_running_operation_result(azure_response, nil) + end end it 'should throw error in case LRO ends up with failed status' do @@ -90,15 +92,14 @@ module MsRestAzure { 'Azure-AsyncOperation' => 'async_operation_header' }, :status => 202) - request = double('request', headers: {}, base_uri: '') - - azure_response = double('azure_response', - :request => request, - :response => response, - :body => nil) - - expect { azure_service_client.get_put_operation_result(azure_response, nil) }.to raise_error(AzureOperationError) + @methods.each do |method| + request = double('request', headers: {}, base_uri: '', method: method) + azure_response = double('azure_response', + :request => request, + :response => response, + :body => nil) + expect { azure_service_client.get_long_running_operation_result(azure_response, nil) }.to raise_error(AzureOperationError) + end end end - -end \ No newline at end of file +end