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