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
15from __future__ import absolute_import
16
17import datetime
18import unittest2
19from expects import be_none, expect, equal, raise_error
20
21from google.api.control import messages, metric_value, operation, timestamp
22from google.api.control import MetricKind
23
24_A_FLOAT_VALUE = 1.1
25_REALLY_EARLY = timestamp.to_rfc3339(datetime.datetime(1970, 1, 1, 0, 0, 0))
26_EARLY = timestamp.to_rfc3339(datetime.datetime(1980, 1, 1, 10, 0, 0))
27_LATER = timestamp.to_rfc3339(datetime.datetime(1980, 2, 2, 10, 0, 0))
28_LATER_STILL = timestamp.to_rfc3339(datetime.datetime(1981, 2, 2, 10, 0, 0))
29
30_TEST_LABELS = {
31    'key1': 'value1',
32    'key2': 'value2',
33}
34
35# in tests, the description field is not currently used, but should be filled
36_TESTS = [
37    {
38        'description': 'update the start time to that of the earliest',
39        'kinds': None,
40        'initial': messages.Operation(
41            startTime=_EARLY,
42            endTime=_LATER
43        ),
44        'ops': [
45            messages.Operation(
46                startTime=_REALLY_EARLY,
47                endTime=_LATER
48            ),
49            messages.Operation(
50                startTime=_LATER,
51                endTime=_LATER
52            ),
53        ],
54        'want': messages.Operation(startTime=_REALLY_EARLY, endTime=_LATER)
55    },
56    {
57        'description': 'update the end time to that of the latest',
58        'kinds': None,
59        'initial': messages.Operation(
60            startTime=_EARLY,
61            endTime=_LATER
62        ),
63        'ops': [
64            messages.Operation(
65                startTime=_EARLY,
66                endTime=_LATER
67            ),
68            messages.Operation(
69                startTime=_EARLY,
70                endTime=_LATER_STILL
71            ),
72        ],
73        'want': messages.Operation(startTime=_EARLY, endTime=_LATER_STILL)
74    },
75    {
76        'description': 'combine the log entries',
77        'kinds': None,
78        'initial': messages.Operation(
79            startTime=_EARLY,
80            endTime=_LATER,
81            logEntries=[messages.LogEntry(textPayload='initial')]
82        ),
83        'ops': [
84            messages.Operation(
85                startTime=_EARLY,
86                endTime=_LATER,
87                logEntries=[messages.LogEntry(textPayload='agg1')]
88            ),
89            messages.Operation(
90                startTime=_EARLY,
91                endTime=_LATER,
92                logEntries=[messages.LogEntry(textPayload='agg2')]
93            ),
94        ],
95        'want': messages.Operation(
96            startTime=_EARLY,
97            endTime=_LATER,
98            logEntries=[
99                messages.LogEntry(textPayload='initial'),
100                messages.LogEntry(textPayload='agg1'),
101                messages.LogEntry(textPayload='agg2')
102            ]
103        )
104    },
105    {
106        'description': 'combines the metric value using the default kind',
107        'kinds': None,
108        'initial': messages.Operation(
109            startTime=_EARLY,
110            endTime=_LATER,
111            metricValueSets = [
112                messages.MetricValueSet(
113                    metricName='some_floats',
114                    metricValues=[
115                        metric_value.create(
116                            labels=_TEST_LABELS,
117                            doubleValue=_A_FLOAT_VALUE,
118                            endTime=_EARLY
119                        ),
120                    ]
121                ),
122                messages.MetricValueSet(
123                    metricName='other_floats',
124                    metricValues=[
125                        metric_value.create(
126                            labels=_TEST_LABELS,
127                            doubleValue=_A_FLOAT_VALUE,
128                            endTime=_EARLY
129                        ),
130                    ]
131                )
132            ]
133        ),
134        'ops': [
135            messages.Operation(
136                startTime=_EARLY,
137                endTime=_LATER,
138                metricValueSets = [
139                    messages.MetricValueSet(
140                        metricName='some_floats',
141                        metricValues=[
142                            metric_value.create(
143                                labels=_TEST_LABELS,
144                                doubleValue=_A_FLOAT_VALUE,
145                                endTime=_LATER
146                            ),
147                        ]
148                    ),
149                ]
150            ),
151            messages.Operation(
152                startTime=_EARLY,
153                endTime=_LATER,
154                metricValueSets = [
155                    messages.MetricValueSet(
156                        metricName='other_floats',
157                        metricValues=[
158                            metric_value.create(
159                                labels=_TEST_LABELS,
160                                doubleValue=_A_FLOAT_VALUE,
161                                endTime=_LATER_STILL
162                            ),
163                        ]
164                    )
165                ]
166
167            ),
168        ],
169        'want': messages.Operation(
170            startTime=_EARLY,
171            endTime=_LATER,
172            metricValueSets = [
173                messages.MetricValueSet(
174                    metricName='other_floats',
175                    metricValues=[
176                        metric_value.create(
177                            labels=_TEST_LABELS,
178                            doubleValue=_A_FLOAT_VALUE * 2,
179                            endTime=_LATER_STILL
180                        ),
181                    ]
182                ),
183                messages.MetricValueSet(
184                    metricName='some_floats',
185                    metricValues=[
186                        metric_value.create(
187                            labels=_TEST_LABELS,
188                            doubleValue=_A_FLOAT_VALUE * 2,
189                            endTime=_LATER
190                        ),
191                    ]
192                )
193            ]
194        )
195    },
196    {
197        'description': 'combines a metric value using a kind that is not DELTA',
198        'kinds': { 'some_floats': MetricKind.GAUGE },
199        'initial': messages.Operation(
200            startTime=_EARLY,
201            endTime=_LATER,
202            metricValueSets = [
203                messages.MetricValueSet(
204                    metricName='some_floats',
205                    metricValues=[
206                        metric_value.create(
207                            labels=_TEST_LABELS,
208                            doubleValue=_A_FLOAT_VALUE,
209                            endTime=_EARLY
210                        ),
211                    ]
212                ),
213                messages.MetricValueSet(
214                    metricName='other_floats',
215                    metricValues=[
216                        metric_value.create(
217                            labels=_TEST_LABELS,
218                            doubleValue=_A_FLOAT_VALUE,
219                            endTime=_EARLY
220                        ),
221                    ]
222                )
223            ]
224        ),
225        'ops': [
226            messages.Operation(
227                startTime=_EARLY,
228                endTime=_LATER,
229                metricValueSets = [
230                    messages.MetricValueSet(
231                        metricName='some_floats',
232                        metricValues=[
233                            metric_value.create(
234                                labels=_TEST_LABELS,
235                                doubleValue=_A_FLOAT_VALUE,
236                                endTime=_LATER
237                            ),
238                        ]
239                    ),
240                ]
241            ),
242            messages.Operation(
243                startTime=_EARLY,
244                endTime=_LATER,
245                metricValueSets = [
246                    messages.MetricValueSet(
247                        metricName='other_floats',
248                        metricValues=[
249                            metric_value.create(
250                                labels=_TEST_LABELS,
251                                doubleValue=_A_FLOAT_VALUE,
252                                endTime=_LATER_STILL
253                            ),
254                        ]
255                    )
256                ]
257
258            ),
259        ],
260        'want': messages.Operation(
261            startTime=_EARLY,
262            endTime=_LATER,
263            metricValueSets = [
264                messages.MetricValueSet(
265                    metricName='other_floats',
266                    metricValues=[
267                        metric_value.create(
268                            labels=_TEST_LABELS,
269                            doubleValue=_A_FLOAT_VALUE * 2,
270                            endTime=_LATER_STILL
271                        ),
272                    ]
273                ),
274                messages.MetricValueSet(
275                    metricName='some_floats',
276                    metricValues=[
277                        metric_value.create(
278                            labels=_TEST_LABELS,
279                            doubleValue=_A_FLOAT_VALUE,
280                            endTime=_LATER
281                        ),
282                    ]
283                )
284            ]
285        )
286    }
287]
288
289class TestOperationAggregation(unittest2.TestCase):
290
291    def test_should_aggregate_as_expected(self):
292        for t in _TESTS:
293            desc = t['description']
294            initial = t['initial']
295            want = t['want']
296            agg = operation.Aggregator(initial, kinds=t['kinds'])
297            for o in t['ops']:
298                agg.add(o)
299                got = agg.as_operation()
300            try:
301                expect(got).to(equal(want))
302            except AssertionError as e:
303                raise AssertionError('Failed to {0}\n{1}'.format(desc, e))
304
305
306_INFO_TESTS = [
307    (operation.Info(
308        referer='a_referer',
309        service_name='a_service_name'),
310     messages.Operation(
311         importance=messages.Operation.ImportanceValueValuesEnum.LOW,
312         startTime=_REALLY_EARLY,
313         endTime=_REALLY_EARLY)),
314    (operation.Info(
315        operation_id='an_op_id',
316        referer='a_referer',
317        service_name='a_service_name'),
318     messages.Operation(
319         importance=messages.Operation.ImportanceValueValuesEnum.LOW,
320         operationId='an_op_id',
321         startTime=_REALLY_EARLY,
322         endTime=_REALLY_EARLY)),
323    (operation.Info(
324        operation_id='an_op_id',
325        operation_name='an_op_name',
326        referer='a_referer',
327        service_name='a_service_name'),
328     messages.Operation(
329         importance=messages.Operation.ImportanceValueValuesEnum.LOW,
330         operationId='an_op_id',
331         operationName='an_op_name',
332         startTime=_REALLY_EARLY,
333         endTime=_REALLY_EARLY)),
334    (operation.Info(
335        api_key='an_api_key',
336        api_key_valid=True,
337        operation_id='an_op_id',
338        operation_name='an_op_name',
339        referer='a_referer',
340        service_name='a_service_name'),
341     messages.Operation(
342         importance=messages.Operation.ImportanceValueValuesEnum.LOW,
343         consumerId='api_key:an_api_key',
344         operationId='an_op_id',
345         operationName='an_op_name',
346         startTime=_REALLY_EARLY,
347         endTime=_REALLY_EARLY)),
348    (operation.Info(
349        api_key='an_api_key',
350        api_key_valid=False,
351        consumer_project_id='project_id',
352        operation_id='an_op_id',
353        operation_name='an_op_name',
354        referer='a_referer',
355        service_name='a_service_name'),
356     messages.Operation(
357         importance=messages.Operation.ImportanceValueValuesEnum.LOW,
358         consumerId='project:project_id',
359         operationId='an_op_id',
360         operationName='an_op_name',
361         startTime=_REALLY_EARLY,
362         endTime=_REALLY_EARLY)),
363]
364
365class TestInfo(unittest2.TestCase):
366
367    def test_should_construct_with_no_args(self):
368        expect(operation.Info()).not_to(be_none)
369
370    def test_should_convert_to_operation_ok(self):
371        timer = _DateTimeTimer()
372        for info, want in _INFO_TESTS:
373            expect(info.as_operation(timer=timer)).to(equal(want))
374
375
376class _DateTimeTimer(object):
377    def __init__(self, auto=False):
378        self.auto = auto
379        self.time = datetime.datetime.utcfromtimestamp(0)
380
381    def __call__(self):
382        if self.auto:
383            self.tick()
384        return self.time
385
386    def tick(self):
387        self.time += datetime.timedelta(seconds=1)
388