1# -*- coding: utf-8 -*- # 2# Copyright 2016 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"""CRM API Operations utilities.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21import time 22 23from apitools.base.py import encoding 24from apitools.base.py import exceptions 25from googlecloudsdk.api_lib.util import apis 26from googlecloudsdk.core import exceptions as core_exceptions 27from googlecloudsdk.core import resources 28from googlecloudsdk.core.console import progress_tracker as tracker 29from googlecloudsdk.core.util import retry 30 31 32OPERATIONS_API_VERSION = 'v1' 33 34 35class OperationError(exceptions.Error): 36 pass 37 38 39def OperationsClient(): 40 return apis.GetClientInstance('cloudresourcemanager', OPERATIONS_API_VERSION) 41 42 43def OperationsRegistry(): 44 registry = resources.REGISTRY.Clone() 45 registry.RegisterApiByName('cloudresourcemanager', OPERATIONS_API_VERSION) 46 return registry 47 48 49def OperationsService(): 50 return OperationsClient().operations 51 52 53def OperationsMessages(): 54 return apis.GetMessagesModule('cloudresourcemanager', OPERATIONS_API_VERSION) 55 56 57def OperationNameToId(operation_name): 58 return operation_name[len('operations/'):] 59 60 61def OperationIdToName(operation_id): 62 return 'operations/{0}'.format(operation_id) 63 64 65def GetOperation(operation_id): 66 return OperationsService().Get( 67 OperationsMessages().CloudresourcemanagerOperationsGetRequest( 68 operationsId=operation_id)) 69 70 71def WaitForOperation(operation): 72 wait_message = 'Waiting for [{0}] to finish'.format(operation.name) 73 with tracker.ProgressTracker(wait_message, autotick=False) as pt: 74 retryer = OperationRetryer() 75 poller = OperationPoller(pt) 76 return retryer.RetryPollOperation(poller, operation) 77 78 79def ExtractOperationResponse(operation, response_message_type): 80 raw_dict = encoding.MessageToDict(operation.response) 81 return encoding.DictToMessage(raw_dict, response_message_type) 82 83 84def ToOperationResponse(message): 85 raw_dict = encoding.MessageToDict(message) 86 return encoding.DictToMessage(raw_dict, 87 OperationsMessages().Operation.ResponseValue) 88 89 90class OperationRetryer(object): 91 """A wrapper around a Retryer that works with CRM operations. 92 93 Uses predefined constants for retry timing, so all CRM operation commands can 94 share their retry timing settings. 95 """ 96 97 def __init__(self, 98 pre_start_sleep=lambda: time.sleep(1), 99 max_retry_ms=2000, 100 max_wait_ms=300000, 101 wait_ceiling_ms=20000, 102 first_retry_sleep_ms=2000): 103 self._pre_start_sleep = pre_start_sleep 104 self._max_retry_ms = max_retry_ms 105 self._max_wait_ms = max_wait_ms 106 self._wait_ceiling_ms = wait_ceiling_ms 107 self._first_retry_sleep_ms = first_retry_sleep_ms 108 109 def RetryPollOperation(self, operation_poller, operation): 110 self._pre_start_sleep() 111 return self._Retryer().RetryOnResult( 112 lambda: operation_poller.Poll(operation), 113 should_retry_if=self._ShouldRetry, 114 sleep_ms=self._first_retry_sleep_ms) 115 116 def _Retryer(self): 117 return retry.Retryer( 118 exponential_sleep_multiplier=2, 119 max_wait_ms=self._max_wait_ms, 120 wait_ceiling_ms=self._wait_ceiling_ms) 121 122 def _ShouldRetry(self, result, state): 123 if isinstance(result, exceptions.HttpError): 124 return self._CheckTimePassedBelowMax(result, state) 125 return self._CheckResultNotException(result) 126 127 def _CheckTimePassedBelowMax(self, result, state): 128 if state.time_passed_ms > self._max_retry_ms: 129 raise result 130 return True 131 132 def _CheckResultNotException(self, result): 133 if isinstance(result, Exception): 134 raise result 135 return not result.done 136 137 138class OperationPoller(object): 139 140 def __init__(self, progress_tracker=None): 141 self._progress_tracker = progress_tracker 142 143 def Poll(self, operation): 144 if self._progress_tracker: 145 self._progress_tracker.Tick() 146 latest = GetOperation(OperationNameToId(operation.name)) 147 if latest.done and latest.error: 148 raise OperationFailedException(latest) 149 return latest 150 151 152class OperationFailedException(core_exceptions.Error): 153 154 def __init__(self, operation_with_error): 155 op_id = OperationNameToId(operation_with_error.name) 156 error_code = operation_with_error.error.code 157 error_message = operation_with_error.error.message 158 message = 'Operation [{0}] failed: {1}: {2}'.format(op_id, error_code, 159 error_message) 160 super(OperationFailedException, self).__init__(message) 161 162 163def GetUri(resource): 164 """Returns the uri for resource.""" 165 operation_id = OperationNameToId(resource.name) 166 operation_ref = OperationsRegistry().Parse( 167 None, 168 params={'operationsId': operation_id}, 169 collection='cloudresourcemanager.operations') 170 return operation_ref.SelfLink() 171