OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 Google Inc. All Rights Reserved. |
| 2 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at |
| 6 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # |
| 9 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. |
| 14 |
| 15 """operation provides support for working with `Operation` instances. |
| 16 |
| 17 :class:`~google.api.gen.servicecontrol_v1_message.Operation` represents |
| 18 information regarding an operation, and is a key constituent of |
| 19 :class:`~google.api.gen.servicecontrol_v1_message.CheckRequest` and |
| 20 :class:`~google.api.gen.servicecontrol_v1_message.ReportRequests. |
| 21 |
| 22 The :class:`.Aggregator` support this. |
| 23 |
| 24 """ |
| 25 |
| 26 from __future__ import absolute_import |
| 27 |
| 28 import collections |
| 29 import logging |
| 30 from datetime import datetime |
| 31 |
| 32 from apitools.base.py import encoding |
| 33 |
| 34 from . import messages, metric_value, timestamp, MetricKind |
| 35 |
| 36 logger = logging.getLogger(__name__) |
| 37 |
| 38 |
| 39 class Info( |
| 40 collections.namedtuple( |
| 41 'Info', [ |
| 42 'api_key', |
| 43 'api_key_valid', |
| 44 'consumer_project_id', |
| 45 'operation_id', |
| 46 'operation_name', |
| 47 'referer', |
| 48 'service_name', |
| 49 ])): |
| 50 """Holds basic information about an api call. |
| 51 |
| 52 This class is one of several used to mediate between the raw service |
| 53 control api surface and python frameworks. Client code can construct |
| 54 operations using this surface |
| 55 |
| 56 Attributes: |
| 57 api_key (string): the api key |
| 58 api_key_valid (bool): it the request has a valid api key. By default |
| 59 it is true, it will only be set to false if the api key cannot |
| 60 be validated by the service controller |
| 61 consumer_project_id (string): the project id of the api consumer |
| 62 operation_id (string): identity of the operation, which must be unique |
| 63 within the scope of the service. Calls to report and check on the |
| 64 same operation should carry the same operation id |
| 65 operation_name (string): the fully-qualified name of the operation |
| 66 referer (string): the referer header, or if not present the origin |
| 67 service_name(string): the name of service |
| 68 |
| 69 """ |
| 70 # pylint: disable=too-many-arguments |
| 71 |
| 72 def __new__(cls, |
| 73 api_key='', |
| 74 api_key_valid=False, |
| 75 consumer_project_id='', |
| 76 operation_id='', |
| 77 operation_name='', |
| 78 referer='', |
| 79 service_name=''): |
| 80 """Invokes the base constructor with default values.""" |
| 81 return super(cls, Info).__new__( |
| 82 cls, |
| 83 api_key, |
| 84 api_key_valid, |
| 85 consumer_project_id, |
| 86 operation_id, |
| 87 operation_name, |
| 88 referer, |
| 89 service_name) |
| 90 |
| 91 def as_operation(self, timer=datetime.utcnow): |
| 92 """Makes an ``Operation`` from this instance. |
| 93 |
| 94 Returns: |
| 95 an ``Operation`` |
| 96 |
| 97 """ |
| 98 now = timer() |
| 99 op = messages.Operation( |
| 100 endTime=timestamp.to_rfc3339(now), |
| 101 startTime=timestamp.to_rfc3339(now), |
| 102 importance=messages.Operation.ImportanceValueValuesEnum.LOW) |
| 103 if self.operation_id: |
| 104 op.operationId = self.operation_id |
| 105 if self.operation_name: |
| 106 op.operationName = self.operation_name |
| 107 if self.api_key and self.api_key_valid: |
| 108 op.consumerId = 'api_key:' + self.api_key |
| 109 elif self.consumer_project_id: |
| 110 op.consumerId = 'project:' + self.consumer_project_id |
| 111 return op |
| 112 |
| 113 |
| 114 class Aggregator(object): |
| 115 """Container that implements operation aggregation. |
| 116 |
| 117 Thread compatible. |
| 118 """ |
| 119 DEFAULT_KIND = MetricKind.DELTA |
| 120 """Used when kinds are not specified, or are missing a metric name""" |
| 121 |
| 122 def __init__(self, initial_op, kinds=None): |
| 123 """Constructor. |
| 124 |
| 125 If kinds is not specifed, all operations will be merged assuming |
| 126 they are of Kind ``DEFAULT_KIND`` |
| 127 |
| 128 Args: |
| 129 initial_op ( |
| 130 :class:`google.api.gen.servicecontrol_v1_messages.Operation`): the |
| 131 initial version of the operation |
| 132 kinds (dict[string,[string]]): specifies the metric kind for |
| 133 each metric name |
| 134 |
| 135 """ |
| 136 assert isinstance(initial_op, messages.Operation) |
| 137 if kinds is None: |
| 138 kinds = {} |
| 139 self._kinds = kinds |
| 140 self._metric_values_by_name_then_sign = collections.defaultdict(dict) |
| 141 our_op = encoding.CopyProtoMessage(initial_op) |
| 142 self._merge_metric_values(our_op) |
| 143 our_op.metricValueSets = [] |
| 144 self._op = our_op |
| 145 |
| 146 def as_operation(self): |
| 147 """Obtains a single `Operation` representing this instances contents. |
| 148 |
| 149 Returns: |
| 150 :class:`google.api.gen.servicecontrol_v1_messages.Operation` |
| 151 """ |
| 152 result = encoding.CopyProtoMessage(self._op) |
| 153 names = sorted(self._metric_values_by_name_then_sign.keys()) |
| 154 for name in names: |
| 155 mvs = self._metric_values_by_name_then_sign[name] |
| 156 result.metricValueSets.append( |
| 157 messages.MetricValueSet( |
| 158 metricName=name, metricValues=mvs.values())) |
| 159 return result |
| 160 |
| 161 def add(self, other_op): |
| 162 """Combines `other_op` with the operation held by this aggregator. |
| 163 |
| 164 N.B. It merges the operations log entries and metric values, but makes |
| 165 the assumption the operation is consistent. It's the callers |
| 166 responsibility to ensure consistency |
| 167 |
| 168 Args: |
| 169 other_op ( |
| 170 class:`google.api.gen.servicecontrol_v1_messages.Operation`): |
| 171 an operation merge into this one |
| 172 |
| 173 """ |
| 174 self._op.logEntries.extend(other_op.logEntries) |
| 175 self._merge_timestamps(other_op) |
| 176 self._merge_metric_values(other_op) |
| 177 |
| 178 def _merge_metric_values(self, other_op): |
| 179 for value_set in other_op.metricValueSets: |
| 180 name = value_set.metricName |
| 181 kind = self._kinds.get(name, self.DEFAULT_KIND) |
| 182 by_signature = self._metric_values_by_name_then_sign[name] |
| 183 for mv in value_set.metricValues: |
| 184 signature = metric_value.sign(mv) |
| 185 prior = by_signature.get(signature) |
| 186 if prior is not None: |
| 187 metric_value.merge(kind, prior, mv) |
| 188 by_signature[signature] = mv |
| 189 |
| 190 def _merge_timestamps(self, other_op): |
| 191 # Update the start time and end time in self._op as needed |
| 192 if (other_op.startTime and |
| 193 (self._op.startTime is None or |
| 194 timestamp.compare(other_op.startTime, self._op.startTime) == -1)): |
| 195 self._op.startTime = other_op.startTime |
| 196 |
| 197 if (other_op.endTime and |
| 198 (self._op.endTime is None or timestamp.compare( |
| 199 self._op.endTime, other_op.endTime) == -1)): |
| 200 self._op.endTime = other_op.endTime |
OLD | NEW |