Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[In Review] Patch support for long running operation in ruby #1011

Merged
merged 1 commit into from
May 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's check on whether we need to keep this one or not per our offline conversation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per our offline discussion, we found the use case when it's required for backward compatibility Example: mgmt gem uses old template (0.2.2) with ms_rest_azure moves to 0.2.2 to 0.2.3 with this change then we can break.

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
Loading