1# -*- coding: utf-8 -*- # 2# Copyright 2021 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""A library used to interact with Operations objects.""" 16# TODO(b/73491568) Refactor to use api_lib.util.waiter 17 18from __future__ import absolute_import 19from __future__ import division 20from __future__ import unicode_literals 21 22from googlecloudsdk.api_lib.functions.v1 import exceptions 23from googlecloudsdk.core.console import progress_tracker as console_progress_tracker 24from googlecloudsdk.core.util import encoding 25from googlecloudsdk.core.util import retry 26 27MAX_WAIT_MS = 1820000 28WAIT_CEILING_MS = 2000 29SLEEP_MS = 1000 30 31 32def OperationErrorToString(error): 33 """Returns a human readable string representation from the operation. 34 35 Args: 36 error: A string representing the raw json of the operation error. 37 38 Returns: 39 A human readable string representation of the error. 40 """ 41 return 'OperationError: code={0}, message={1}'.format( 42 error.code, encoding.Decode(error.message)) 43 44 45# TODO(b/130604453): Remove try_set_invoker option. 46def _GetOperationStatus(client, get_request, 47 progress_tracker=None, try_set_invoker=None, 48 on_every_poll=None): 49 """Helper function for getting the status of an operation. 50 51 Args: 52 client: The client used to make requests. 53 get_request: A GetOperationRequest message. 54 progress_tracker: progress_tracker.ProgressTracker, A reference for the 55 progress tracker to tick, in case this function is used in a Retryer. 56 try_set_invoker: function to try setting invoker, see above TODO. 57 on_every_poll: list of functions to execute every time we poll. 58 Functions should take in Operation as an argument. 59 60 Returns: 61 True if the operation succeeded without error. 62 False if the operation is not yet done. 63 64 Raises: 65 FunctionsError: If the operation is finished with error. 66 """ 67 if try_set_invoker: 68 try_set_invoker() 69 if progress_tracker: 70 progress_tracker.Tick() 71 op = client.operations.Get(get_request) 72 if op.error: 73 raise exceptions.FunctionsError(OperationErrorToString(op.error)) 74 if on_every_poll: 75 for function in on_every_poll: 76 function(op) 77 return op.done 78 79 80# TODO(b/139026575): Remove try_set_invoker option. 81def _WaitForOperation(client, get_request, message, try_set_invoker=None, 82 on_every_poll=None): 83 """Wait for an operation to complete. 84 85 No operation is done instantly. Wait for it to finish following this logic: 86 * we wait 1s (jitter is also 1s) 87 * we query service 88 * if the operation is not finished we loop to first point 89 * wait limit is 1820s - if we get to that point it means something is wrong 90 and we can throw an exception 91 92 Args: 93 client: The client used to make requests. 94 get_request: A GetOperationRequest message. 95 message: str, The string to print while polling. 96 try_set_invoker: function to try setting invoker, see above TODO. 97 on_every_poll: list of functions to execute every time we poll. 98 Functions should take in Operation as an argument. 99 100 Returns: 101 True if the operation succeeded without error. 102 103 Raises: 104 FunctionsError: If the operation takes more than 1820s. 105 """ 106 107 with console_progress_tracker.ProgressTracker(message, autotick=False) as pt: 108 # This is actually linear retryer. 109 retryer = retry.Retryer(exponential_sleep_multiplier=1, 110 max_wait_ms=MAX_WAIT_MS, 111 wait_ceiling_ms=WAIT_CEILING_MS) 112 try: 113 retryer.RetryOnResult(_GetOperationStatus, 114 [client, get_request], 115 { 116 'progress_tracker': pt, 117 'try_set_invoker': try_set_invoker, 118 'on_every_poll': on_every_poll 119 }, 120 should_retry_if=None, 121 sleep_ms=SLEEP_MS) 122 except retry.WaitException: 123 raise exceptions.FunctionsError( 124 'Operation {0} is taking too long'.format(get_request.name)) 125 126 127def Wait(operation, messages, client, notice=None, try_set_invoker=None, 128 on_every_poll=None): 129 """Initialize waiting for operation to finish. 130 131 Generate get request based on the operation and wait for an operation 132 to complete. 133 134 Args: 135 operation: The operation which we are waiting for. 136 messages: GCF messages module. 137 client: GCF client module. 138 notice: str, displayed when waiting for the operation to finish. 139 try_set_invoker: function to try setting invoker, see above TODO. 140 on_every_poll: list of functions to execute every time we poll. 141 Functions should take in Operation as an argument. 142 143 Raises: 144 FunctionsError: If the operation takes more than 620s. 145 """ 146 if notice is None: 147 notice = 'Waiting for operation to finish' 148 request = messages.CloudfunctionsOperationsGetRequest() 149 request.name = operation.name 150 _WaitForOperation(client, request, notice, try_set_invoker, 151 on_every_poll) 152