Skip to content

Commit

Permalink
Patch support for long running operation in ruby (Azure#1011)
Browse files Browse the repository at this point in the history
  • Loading branch information
vishrutshah committed May 12, 2016
1 parent 1150967 commit e1d41ab
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

#
Expand All @@ -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
Expand All @@ -162,37 +139,32 @@ 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
error_data.message = "Long running operation failed with status #{polling_state.status}"

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

Expand All @@ -212,34 +184,15 @@ 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.
#
# @return [MsRest::HttpOperationResponse] the response.
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
Expand All @@ -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.
Expand All @@ -263,24 +216,24 @@ 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'})

# Send 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)

Expand Down

0 comments on commit e1d41ab

Please sign in to comment.