168 lines
5.3 KiB
Python
168 lines
5.3 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2021 Google LLC. All Rights Reserved.
|
|
#
|
|
# Licensed 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.
|
|
"""A library used to interact with Operations objects."""
|
|
# TODO(b/73491568) Refactor to use api_lib.util.waiter
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
from googlecloudsdk.api_lib.functions.v1 import exceptions
|
|
from googlecloudsdk.core.console import progress_tracker as console_progress_tracker
|
|
from googlecloudsdk.core.util import encoding
|
|
from googlecloudsdk.core.util import retry
|
|
|
|
MAX_WAIT_MS = 1820000
|
|
WAIT_CEILING_MS = 2000
|
|
SLEEP_MS = 1000
|
|
|
|
|
|
def OperationErrorToString(error):
|
|
"""Returns a human readable string representation from the operation.
|
|
|
|
Args:
|
|
error: A string representing the raw json of the operation error.
|
|
|
|
Returns:
|
|
A human readable string representation of the error.
|
|
"""
|
|
return 'OperationError: code={0}, message={1}'.format(
|
|
error.code, encoding.Decode(error.message)
|
|
)
|
|
|
|
|
|
# TODO(b/130604453): Remove try_set_invoker option.
|
|
def _GetOperationStatus(
|
|
client,
|
|
get_request,
|
|
progress_tracker=None,
|
|
try_set_invoker=None,
|
|
on_every_poll=None,
|
|
):
|
|
"""Helper function for getting the status of an operation.
|
|
|
|
Args:
|
|
client: The client used to make requests.
|
|
get_request: A GetOperationRequest message.
|
|
progress_tracker: progress_tracker.ProgressTracker, A reference for the
|
|
progress tracker to tick, in case this function is used in a Retryer.
|
|
try_set_invoker: function to try setting invoker, see above TODO.
|
|
on_every_poll: list of functions to execute every time we poll. Functions
|
|
should take in Operation as an argument.
|
|
|
|
Returns:
|
|
True if the operation succeeded without error.
|
|
False if the operation is not yet done.
|
|
|
|
Raises:
|
|
FunctionsError: If the operation is finished with error.
|
|
"""
|
|
if try_set_invoker:
|
|
try_set_invoker()
|
|
if progress_tracker:
|
|
progress_tracker.Tick()
|
|
op = client.operations.Get(get_request)
|
|
if op.error:
|
|
raise exceptions.FunctionsError(OperationErrorToString(op.error))
|
|
if on_every_poll:
|
|
for function in on_every_poll:
|
|
function(op)
|
|
return op.done
|
|
|
|
|
|
# TODO(b/139026575): Remove try_set_invoker option.
|
|
def _WaitForOperation(
|
|
client, get_request, message, try_set_invoker=None, on_every_poll=None
|
|
):
|
|
"""Wait for an operation to complete.
|
|
|
|
No operation is done instantly. Wait for it to finish following this logic:
|
|
* we wait 1s (jitter is also 1s)
|
|
* we query service
|
|
* if the operation is not finished we loop to first point
|
|
* wait limit is 1820s - if we get to that point it means something is wrong
|
|
and we can throw an exception
|
|
|
|
Args:
|
|
client: The client used to make requests.
|
|
get_request: A GetOperationRequest message.
|
|
message: str, The string to print while polling.
|
|
try_set_invoker: function to try setting invoker, see above TODO.
|
|
on_every_poll: list of functions to execute every time we poll. Functions
|
|
should take in Operation as an argument.
|
|
|
|
Returns:
|
|
True if the operation succeeded without error.
|
|
|
|
Raises:
|
|
FunctionsError: If the operation takes more than 1820s.
|
|
"""
|
|
|
|
with console_progress_tracker.ProgressTracker(message, autotick=False) as pt:
|
|
# This is actually linear retryer.
|
|
retryer = retry.Retryer(
|
|
exponential_sleep_multiplier=1,
|
|
max_wait_ms=MAX_WAIT_MS,
|
|
wait_ceiling_ms=WAIT_CEILING_MS,
|
|
)
|
|
try:
|
|
retryer.RetryOnResult(
|
|
_GetOperationStatus,
|
|
[client, get_request],
|
|
{
|
|
'progress_tracker': pt,
|
|
'try_set_invoker': try_set_invoker,
|
|
'on_every_poll': on_every_poll,
|
|
},
|
|
should_retry_if=lambda done, _: not done,
|
|
sleep_ms=SLEEP_MS,
|
|
)
|
|
except retry.WaitException:
|
|
raise exceptions.FunctionsError(
|
|
'Operation {0} is taking too long'.format(get_request.name)
|
|
)
|
|
|
|
|
|
def Wait(
|
|
operation,
|
|
messages,
|
|
client,
|
|
notice=None,
|
|
try_set_invoker=None,
|
|
on_every_poll=None,
|
|
):
|
|
"""Initialize waiting for operation to finish.
|
|
|
|
Generate get request based on the operation and wait for an operation
|
|
to complete.
|
|
|
|
Args:
|
|
operation: The operation which we are waiting for.
|
|
messages: GCF messages module.
|
|
client: GCF client module.
|
|
notice: str, displayed when waiting for the operation to finish.
|
|
try_set_invoker: function to try setting invoker, see above TODO.
|
|
on_every_poll: list of functions to execute every time we poll. Functions
|
|
should take in Operation as an argument.
|
|
|
|
Raises:
|
|
FunctionsError: If the operation takes more than 620s.
|
|
"""
|
|
if notice is None:
|
|
notice = 'Waiting for operation to finish'
|
|
request = messages.CloudfunctionsOperationsGetRequest()
|
|
request.name = operation.name
|
|
_WaitForOperation(client, request, notice, try_set_invoker, on_every_poll)
|